WordPress.org

Making WordPress.org

Ticket #1691: class-plugin-i18n.diff

File class-plugin-i18n.diff, 13.0 KB (added by tellyworth, 6 years ago)
  • class-plugin-directory.php

     
    2626                add_filter( 'term_link', array( $this, 'term_link' ), 10, 2 );
    2727                add_filter( 'pre_insert_term', array( $this, 'pre_insert_term_prevent' ) );
    2828                add_action( 'pre_get_posts', array( $this, 'use_plugins_in_query' ) );
     29                add_filter( 'the_content', array( $this, 'translate_post_content' ), 1, 2 );
     30                add_filter( 'the_title', array( $this, 'translate_post_title' ), 1, 2 );
     31                add_filter( 'get_the_excerpt', array( $this, 'translate_post_excerpt' ), 1 );
    2932                add_filter( 'rest_api_allowed_post_types', array( $this, 'filter_allowed_post_types' ) );
    3033                add_filter( 'pre_update_option_jetpack_options', array( $this, 'filter_jetpack_options' ) );
    3134                add_action( 'template_redirect', array( $this, 'redirect_hidden_plugins' ) );
     
    441444        }
    442445
    443446        /**
     447         * Returns the requested page's content, translated.
     448         *
     449         * @param string $content
     450         * @return string
     451         */
     452        public function translate_post_content( $content, $section = null ) {
     453                if ( is_null( $section ) ) {
     454                        return $content;
     455                }
     456                return Plugin_i18n::instance()->translate( $section, $content );
     457        }
     458
     459        /**
     460         * Returns the requested page's content, translated.
     461         *
     462         * @param string $content
     463         * @return string
     464         */
     465        public function translate_post_title( $title, $post_id ) {
     466                if ( $post_id === get_post()->ID )
     467                        return Plugin_i18n::instance()->translate( 'title', $title );
     468                return $title;
     469        }
     470
     471        /**
     472         * Returns the requested page's excerpt, translated.
     473         *
     474         * @param string $content
     475         * @return string
     476         */
     477        public function translate_post_excerpt( $excerpt ) {
     478                return Plugin_i18n::instance()->translate( 'excerpt', $excerpt );
     479        }
     480
     481        /**
    444482         * Filter for rest_api_allowed_post_types to enable JP syncing of the CPT
    445483         *
    446484         * @param array $allowed_post_types
  • class-plugin-i18n.php

     
     1<?php
     2namespace WordPressdotorg\Plugin_Directory;
     3
     4/**
     5 * Translation for plugin content.
     6 *
     7 * @package WordPressdotorg\Plugin_Directory
     8 */
     9class Plugin_I18n {
     10
     11        /**
     12         * @var string Global cache group for related caching
     13         */
     14        var $i18n_cache_group = 'plugins-i18n';
     15
     16        public static $use_cache = true;
     17        public static $set_cache = true;
     18
     19        /**
     20         * Fetch the instance of the Plugin_I18n class.
     21         */
     22        public static function instance() {
     23                static $instance = null;
     24
     25                global $wpdb;
     26                return ! is_null( $instance ) ? $instance : $instance = new Plugin_I18n( $wpdb );
     27        }
     28
     29    function __construct( $db, $tracker = null ) {
     30        if ( !empty( $db ) && is_object( $db ) ) {
     31            $this->db = $db;
     32        }
     33
     34        wp_cache_add_global_groups( $this->i18n_cache_group );
     35        }
     36
     37        function wp_get_locale() {
     38                // Similar to bb_get_locale()
     39                if ( defined( WP_LANG ) )
     40                        return WP_LANG;
     41                global $locale;
     42                return $locale;
     43        }
     44
     45        /**
     46         * Generates and returns a standard cache key format, for consistency
     47         *
     48         * @param string $slug Plugin slug
     49         * @param string $branch dev|stable
     50         * @param string $suffix Arbitrary cache key suffix, if needed for uniqueness
     51         *
     52         * @return string Cache key
     53         */
     54        function cache_key( $slug, $branch, $suffix = null ) {
     55                // EG keys
     56                // plugin:press-this:stable-readme:originals
     57                // plugin:press-this:stable-readme:original:title
     58                // plugin:press-this:stable-readme:fr:title
     59                $key = "{$this->master_project}:{$slug}:{$branch}";
     60                if ( !empty( $suffix ) ) {
     61                        $key .= ":{$suffix}";
     62                }
     63                return $key;
     64        }
     65
     66        /**
     67         * Cache getting, with proper global cache group
     68         *
     69         * @param string $slug Plugin slug
     70         * @param string $branch dev|stable
     71         * @param string $suffix Arbitrary cache key suffix, if needed for uniqueness
     72         *
     73         * @return bool|mixed As returned by wp_cache_set()
     74         */
     75        function cache_get( $slug, $branch, $suffix = null ) {
     76                if ( ! self::$use_cache ) {
     77                        return false;
     78                }
     79
     80                $key = $this->cache_key( $slug, $branch, $suffix );
     81                return wp_cache_get( $key, $this->i18n_cache_group );
     82        }
     83
     84        /**
     85         * Cache setting, with proper global cache group
     86         *
     87         * @param string $slug Plugin slug
     88         * @param string $branch dev|stable
     89         * @param string $suffix Arbitrary cache key suffix, if needed for uniqueness
     90         *
     91         * @return bool As returned by wp_cache_set()
     92         */
     93        function cache_set( $slug, $branch, $content, $suffix = null ) {
     94                if ( ! self::$set_cache ) {
     95                        return false;
     96                }
     97
     98                $key = $this->cache_key( $slug, $branch, $suffix );
     99                return wp_cache_set( $key, $content, $this->i18n_cache_group );
     100        }
     101
     102        /**
     103         * Gets a GlotPress branch ID
     104         *
     105         * @param string $slug Plugin slug
     106         * @param string $branch dev|stable
     107         *
     108         * @return bool|int|mixed
     109         */
     110        function get_gp_branch_id( $slug, $branch ) {
     111                $cache_suffix = "branch_id";
     112
     113                if ( false !== ( $branch_id = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
     114                        return $branch_id;
     115                }
     116
     117                $branch_id = $this->db->get_var( $this->db->prepare(
     118                        'SELECT id FROM translate_projects WHERE path = %s',
     119                        "wp-plugins/{$slug}/{$branch}"
     120                ) );
     121
     122                if ( empty( $branch_id ) ) {
     123                        $branch_id = 0;
     124                }
     125
     126                $this->cache_set( $slug, $branch, $branch_id, $cache_suffix );
     127
     128                return $branch_id;
     129        }
     130
     131        /**
     132         * Gets GlotPress "originals" based on passed parameters
     133         *
     134         * @param string $slug Plugin slug
     135         * @param string $branch dev|stable
     136         * @param string $key Unique key
     137         * @param string $str String to match in GP
     138         *
     139         * @return array|bool|mixed|null
     140         */
     141        function get_gp_originals( $slug, $branch, $key, $str ) {
     142                // Try to get a single original with the whole content first (title, etc), if passed, or get them all otherwise.
     143                if ( !empty( $key ) && !empty( $str ) ) {
     144                        $originals = $this->search_gp_original( $slug, $branch, $key, $str );
     145                        if ( !empty( $originals ) ) {
     146                                return array( $originals );
     147                        }
     148                        // Do not cache this as originals, search_gp_original() does its own caching
     149                }
     150
     151                $cache_suffix = 'originals';
     152
     153                if ( false !== ( $originals = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
     154                        return $originals;
     155                }
     156
     157                $branch_id = $this->get_gp_branch_id( $slug, $branch );
     158
     159                if ( empty( $branch_id ) ) {
     160                        return array();
     161                }
     162
     163                $originals = $this->db->get_results( $this->db->prepare(
     164                        'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s ORDER BY CHAR_LENGTH(singular) DESC',
     165                        $branch_id, '+active'
     166                ) );
     167
     168                if ( empty( $originals ) ) {
     169                        $originals = array(); // still cache if empty, but as array, never false
     170                }
     171
     172                $this->cache_set( $slug, $branch, $originals, $cache_suffix );
     173
     174                return $originals;
     175        }
     176
     177        /**
     178         * Get GlotPress translation set ID based on passed params
     179         *
     180         * @param string $slug Plugin slug
     181         * @param string $branch dev|stable
     182         * @param string $locale EG: fr
     183         *
     184         * @return bool|int|mixed
     185         */
     186        function get_gp_translation_set_id( $slug, $branch, $locale ) {
     187                $cache_suffix = "{$locale}:translation_set_id";
     188
     189                if ( false !== ( $translation_set_id = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
     190                        return $translation_set_id;
     191                }
     192
     193                $branch_id = $this->get_gp_branch_id( $slug, $branch );
     194
     195                if ( empty( $branch_id ) ) {
     196                        return 0;
     197                }
     198
     199                $translation_set_id = $this->db->get_var( $this->db->prepare(
     200                        'SELECT id FROM translate_translation_sets WHERE project_id = %d AND locale = %s',
     201                        $branch_id, $locale ) );
     202
     203                if ( empty( $translation_set_id ) ) {
     204                        // Don't give up yet. Might be given fr_FR, which actually exists as locale=fr in GP.
     205                        $translation_set_id = $this->db->get_var( $this->db->prepare(
     206                                'SELECT id FROM translate_translation_sets WHERE project_id = %d AND locale = %s',
     207                                $branch_id, preg_replace( '/^([^-]+)(-.+)?$/', '\1', $locale ) ) );
     208                }
     209
     210                if ( empty( $translation_set_id ) ) {
     211                        $translation_set_id = 0;
     212                }
     213
     214                $this->cache_set( $slug, $branch, $translation_set_id, $cache_suffix );
     215
     216                return $translation_set_id;
     217        }
     218
     219        /**
     220         * Searches GlotPress "originals" for the passed string
     221         *
     222         * @param string $slug Plugin slug
     223         * @param string $branch dev|stable
     224         * @param string $key Unique key
     225         * @param string $str String to be searched for
     226         *
     227         * @return bool|mixed|null
     228         */
     229        function search_gp_original( $slug, $branch, $key, $str ) {
     230                $cache_suffix = "original:{$key}";
     231
     232                if ( false !== ( $original = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
     233                        return $original;
     234                }
     235
     236                $branch_id = $this->get_gp_branch_id( $slug, $branch );
     237
     238                if ( empty( $branch_id ) ) {
     239                        return false;
     240                }
     241
     242                $original = $this->db->get_row( $this->db->prepare(
     243                        'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s AND singular = %s',
     244                        $branch_id, '+active', $str
     245                ) );
     246
     247                if ( empty( $original ) ) {
     248                        $original = null;
     249                }
     250
     251                $this->cache_set( $slug, $branch, $original, $cache_suffix );
     252
     253                return $original;
     254        }
     255
     256        /**
     257         * Somewhat emulated equivalent of __() for content translation drawn directly from the GlotPress DB
     258         *
     259         * @param string $key Unique key, used for caching
     260         * @param string $content Content to be translated
     261         * @param array $args Misc arguments, such as BBPress topic id (otherwise acquired from global $topic_id)
     262         *
     263         * @return mixed
     264         */
     265        function translate( $key, $content, $args = array() ) {
     266                if ( empty( $key ) || empty( $content ) ) {
     267                        return $content;
     268                }
     269
     270                if ( !empty( $args['post_id'] ) && is_numeric( $args['post_id'] ) ) {
     271                        $topic = get_post( $args['post_id'] );
     272                } else {
     273                        global $post;
     274                }
     275
     276                if ( empty( $post ) ) {
     277                        return $content;
     278                }
     279
     280                if ( !empty( $args['locale'] ) ) {
     281                        $wp_locale = $args['locale'];
     282                } else {
     283                        $wp_locale = $this->wp_get_locale();
     284                }
     285
     286                $server_name  = strtolower( $_SERVER['SERVER_NAME'] );
     287                if ( 'api.wordpress.org' == $server_name ) {
     288                        // Support formats like fr, haz, and en_GB
     289                        if ( ! empty( $_REQUEST['locale'] ) ) {
     290                                $wp_locale = preg_replace( '/[^a-zA-Z_]/', '', $_REQUEST['locale'] );
     291                        } else if ( ! empty( $_REQUEST['request'] ) ) {
     292                                $request = maybe_unserialize( $_REQUEST['request'] );
     293                                if ( ! empty( $request ) && ! empty( $request->locale ) ) {
     294                                        $wp_locale = preg_replace( '/[^a-zA-Z_]/', '', $request->locale );
     295                                }
     296                        }
     297                }
     298
     299                if ( ! $wp_locale ) {
     300                        return $content;
     301                }
     302
     303                require_once GLOTPRESS_LOCALES_PATH;
     304                $gp_locale = \GP_Locales::by_field( 'wp_locale', $wp_locale );
     305
     306                if ( ! $gp_locale || 'en' === $gp_locale->slug ) {
     307                        return $content;
     308                }
     309
     310                $locale = $gp_locale->slug; // The slug is the locale of a translation set.
     311                $slug = $post->post_name;
     312
     313                $post->stable_tag = get_post_meta( $post->ID, 'stable_tag', true );
     314
     315                if ( empty( $slug ) ) {
     316                        return $content;
     317                }
     318
     319                $branch = ( empty( $post->stable_tag ) || 'trunk' === $post->stable_tag ) ? 'dev' : 'stable';
     320
     321                if ( empty( $args['code_i18n'] ) || true !== $args['code_i18n'] ) {
     322                        $branch .= '-readme';
     323                }
     324
     325                $cache_suffix = "{$locale}:{$key}";
     326
     327                // Try the cache
     328                if ( false !== ( $cache = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
     329                        // DEBUG
     330                        // var_dump( array( $slug, $branch, $cache_suffix, $cache ) );
     331                        return $cache;
     332                }
     333
     334                $originals = $this->get_gp_originals( $slug, $branch, $key, $content );
     335
     336                if ( empty( $originals ) ) {
     337                        return $content;
     338                }
     339
     340                $translation_set_id = $this->get_gp_translation_set_id( $slug, $branch, $locale );
     341
     342                if ( empty( $translation_set_id ) ) {
     343                        return $content;
     344                }
     345
     346                foreach ( $originals as $original ) {
     347                        if ( empty( $original->id ) ) {
     348                                continue;
     349                        }
     350
     351                        $translation = $this->db->get_var( $this->db->prepare(
     352                                'SELECT translation_0 FROM translate_translations WHERE original_id = %d AND translation_set_id = %d AND status = %s',
     353                                $original->id, $translation_set_id, 'current'
     354                        ) );
     355
     356                        if ( empty( $translation ) ) {
     357                                continue;
     358                        }
     359
     360                        $content = $this->translate_gp_original( $original->singular, $translation, $content );
     361                }
     362
     363                $this->cache_set( $slug, $branch, $content, $cache_suffix );
     364
     365                return $content;
     366        }
     367
     368        /**
     369         * Takes content, searches for $original, and replaces it by $translation
     370         *
     371         * @param string $original English string
     372         * @param string $translation Translation
     373         * @param string $content Content to be searched
     374         *
     375         * @return mixed
     376         */
     377        function translate_gp_original( $original, $translation, $content ) {
     378                if ( false === strpos( $content, '<' ) ) {
     379                        $content = str_replace( $original, $translation, $content );
     380                } else {
     381                        $original = preg_quote( $original, '/' );
     382                        $content = preg_replace( "/(<([a-z0-9]*)\b[^>]*>){$original}(<\/\\2>)/m", "\\1{$translation}\\3", $content );
     383                }
     384
     385                return $content;
     386        }
     387
     388}
     389