WordPress.org

Making WordPress.org

Changeset 10628


Ignore:
Timestamp:
01/24/2021 02:30:29 PM (9 months ago)
Author:
ocean90
Message:

WP I18N Teams: Split main plugin class out into separate include files.

See #3423.

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/wp-i18n-teams
Files:
3 added
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wp-i18n-teams/wp-i18n-teams.php

    r10318 r10628  
    22/*
    33Plugin Name: WP I18N Teams
    4 Description: Provides shortcodes for displaying details about translation teams.
     4Description: Provides shortcodes and blocks for managing translation teams.
    55Version:     1.0
    66License:     GPLv2 or later
     
    1010*/
    1111
    12 class WP_I18n_Teams {
    13     const TEAM_PAGE = 'https://make.wordpress.org/polyglots/teams/?locale=%s';
     12namespace WordPressdotorg\I18nTeams;
    1413
    15     public function __construct() {
    16         add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
    17     }
     14const PLUGIN_FILE = __FILE__;
     15const PLUGIN_DIR  = __DIR__;
    1816
    19     /**
    20      * Attaches hooks and registers shortcodes once plugins are loasded.
    21      */
    22     public function plugins_loaded() {
    23         add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ), 20 );
    24         add_shortcode( 'wp-locales',      array( $this, 'wp_locales' ) );
     17require_once PLUGIN_DIR . '/inc/namespace.php';
     18require_once PLUGIN_DIR . '/inc/locales.php';
    2519
    26         add_filter( 'term_link', array( $this, 'link_locales' ), 10, 3 );
    27     }
    28 
    29     /**
    30      * Links #locale to the teams page.
    31      *
    32      * @param string $termlink Term link URL.
    33      * @param object $term     Term object.
    34      * @param string $taxonomy Taxonomy slug.
    35      * @return string URL to teams page of a locale.
    36      */
    37     public function link_locales( $termlink, $term, $taxonomy ) {
    38         if ( 'post_tag' !== $taxonomy ) {
    39             return $termlink;
    40         }
    41 
    42         static $available_locales;
    43 
    44         if ( ! isset( $available_locales ) ) {
    45             $available_locales = self::get_locales();
    46             $available_locales = wp_list_pluck( $available_locales, 'wp_locale' );
    47             $available_locales = array_flip( $available_locales );
    48         }
    49 
    50         if ( isset( $available_locales[ $term->name ] ) || isset( $available_locales[ $term->slug ] ) ) {
    51             return sprintf( self::TEAM_PAGE, $term->name );
    52         }
    53 
    54         return $termlink;
    55     }
    56 
    57     /**
    58      * Enqueue JavaScript and CSS
    59      */
    60     public function enqueue_assets() {
    61         if ( is_singular() && false !== strpos( get_post()->post_content, '[wp-locales' ) ) {
    62             wp_enqueue_style( 'wp-i18n-teams', plugins_url( 'css/i18n-teams.css', __FILE__ ), array(), 13 );
    63             wp_enqueue_script( 'wp-i18n-teams', plugins_url( 'js/i18n-teams.js', __FILE__ ), array( 'jquery', 'o2-app' ), 5 );
    64         }
    65     }
    66 
    67     /**
    68      * Render the [wp-locales] shortcode.
    69      *
    70      * @param array $attributes
    71      *
    72      * @return string
    73      */
    74     public function wp_locales( $attributes ) {
    75         ob_start();
    76 
    77         if ( empty( $_GET['locale'] ) ) {
    78             $locales = self::get_locales();
    79             $locale_data = $this->get_locales_data();
    80             $percentages = $this->get_core_translation_data();
    81             $language_packs_data = $this->get_language_packs_data();
    82             require( __DIR__ . '/views/all-locales.php' );
    83         } else {
    84             require_once GLOTPRESS_LOCALES_PATH;
    85             $locale = GP_Locales::by_field( 'wp_locale', $_GET['locale'] );
    86             if ( $locale ) {
    87                 $locale_data = $this->get_extended_locale_data( $locale );
    88                 require( __DIR__ . '/views/locale-details.php' );
    89             } else {
    90                 printf(
    91                     '<div class="callout callout-warning"><p>%s</p><p><a href="%s">%s</a></p></div>',
    92                     sprintf(
    93                         __( 'Locale %s doesn&#8217;t exist.', 'wporg' ),
    94                         '<code>' . esc_html( $_GET['locale'] ) . '</code>'
    95                     ),
    96                     esc_url( get_permalink() ),
    97                     __( 'Return to All Locales', 'wporg' )
    98                 );
    99             }
    100         }
    101 
    102         return ob_get_clean();
    103     }
    104 
    105     /**
    106      * Get GlotPress locales that have a wp_locale, sorted alphabetically.
    107      *
    108      * @return array
    109      */
    110     protected static function get_locales() {
    111         require_once GLOTPRESS_LOCALES_PATH;
    112 
    113         $locales = GP_Locales::locales();
    114         $locales = array_filter( $locales, array( __CLASS__, 'filter_locale_for_wp' ) );
    115         unset( $locales['en'] );
    116         usort( $locales, array( __CLASS__, 'sort_locales' ) );
    117 
    118         return $locales;
    119     }
    120 
    121     /**
    122      * Remove locales that are missing a wp_locale.
    123      *
    124      * This is a callback for array_filter().
    125      *
    126      * @param GP_Locale $element
    127      *
    128      * @return bool
    129      */
    130     protected static function filter_locale_for_wp( $element ) {
    131         return isset( $element->wp_locale );
    132     }
    133 
    134     /**
    135      * Sort GlotPress locales alphabetically by the English name.
    136      *
    137      * @param GP_Locale $a
    138      * @param GP_Locale $b
    139      *
    140      * @return int
    141      */
    142     protected static function sort_locales( $a, $b ) {
    143         return strcmp( $a->english_name, $b->english_name );
    144     }
    145 
    146     /**
    147      * Gather all the required data and cache it.
    148      */
    149     public function get_locales_data() {
    150         global $wpdb;
    151 
    152         $cache = get_transient( 'wp_i18n_teams_locales_data' );
    153         if ( false !== $cache ) {
    154             return $cache;
    155         }
    156 
    157         $gp_locales = self::get_locales();
    158         $translation_data = $this->get_core_translation_data();
    159         $language_packs_data = $this->get_language_packs_data();
    160         $locale_data = array();
    161 
    162         $statuses = array(
    163             'no-wp-project'      => 0,
    164             'no-site'            => 0,
    165             'no-releases'        => 0,
    166             'latest'             => 0,
    167             'minor-behind'       => 0,
    168             'major-behind-one'   => 0,
    169             'major-behind-many'  => 0,
    170             'translated-100'     => 0,
    171             'translated-95'      => 0,
    172             'translated-90'      => 0,
    173             'translated-50'      => 0,
    174             'translated-50-less' => 0,
    175             'has-language-pack'  => 0,
    176             'no-language-pack'   => 0,
    177         );
    178 
    179         $wporg_data = $wpdb->get_results( 'SELECT locale, subdomain, latest_release FROM wporg_locales ORDER BY locale', OBJECT_K );
    180 
    181         foreach ( $gp_locales as $locale ) {
    182             $subdomain = $latest_release = '';
    183             if ( ! empty( $wporg_data[ $locale->wp_locale ] ) ) {
    184                 $subdomain = $wporg_data[ $locale->wp_locale ]->subdomain;
    185                 $latest_release = $wporg_data[ $locale->wp_locale ]->latest_release;
    186             }
    187             $release_status = self::get_locale_release_status( $subdomain, $latest_release );
    188             $statuses[ $release_status ]++;
    189 
    190             if ( isset( $translation_data[ $locale->wp_locale ] ) ) {
    191                 $translation_status = self::get_locale_translation_status( $translation_data[ $locale->wp_locale ] );
    192             } else {
    193                 $translation_status = 'no-wp-project';
    194             }
    195             $statuses[ $translation_status ]++;
    196 
    197             if ( isset( $language_packs_data[ $locale->wp_locale ] ) ) {
    198                 $language_pack_status = 'has-language-pack';
    199             } else {
    200                 $language_pack_status = 'no-language-pack';
    201             }
    202             $statuses[ $language_pack_status ]++;
    203 
    204             $sites = get_sites( [
    205                 'locale'     => $locale->wp_locale,
    206                 'network_id' => WPORG_GLOBAL_NETWORK_ID,
    207                 'orderby'    => 'path_length',
    208                 'number'     => '',
    209             ] );
    210 
    211             $locale_data[ $locale->wp_locale ] = array(
    212                 'release_status'       => $release_status,
    213                 'translation_status'   => $translation_status,
    214                 'language_pack_status' => $language_pack_status,
    215                 'sites'                => $sites,
    216                 'subdomain'            => $subdomain,
    217                 'rosetta_site_url'     => "https://$subdomain.wordpress.org/",
    218                 'latest_release'       => $latest_release ? $latest_release : false,
    219             );
    220         }
    221 
    222         $locale_data['status_counts'] = $statuses;
    223         $locale_data['status_counts']['all'] = count( $gp_locales );
    224         set_transient( 'wp_i18n_teams_locales_data', $locale_data, 900 );
    225         return $locale_data;
    226     }
    227 
    228     public function get_language_packs_data() {
    229         global $wpdb;
    230 
    231         $cache = get_transient( 'wp_i18n_teams_language_packs_data' );
    232         if ( false !== $cache ) {
    233             return $cache;
    234         }
    235 
    236         $language_packs = $wpdb->get_results( "SELECT language AS locale, version FROM `language_packs` WHERE `type` = 'core' AND `active` = 1 AND `version` NOT LIKE '%-%'" );
    237 
    238         $language_packs_data = array();
    239         foreach ( $language_packs as $pack ) {
    240             if ( ! isset( $language_packs_data[ $pack->locale ] ) ) {
    241                 $language_packs_data[ $pack->locale ] = array();
    242             }
    243 
    244             $language_packs_data[ $pack->locale ][] = $pack->version;
    245         }
    246 
    247         set_transient( 'wp_i18n_teams_language_packs_data', $language_packs_data, 900 );
    248         return $language_packs_data;
    249     }
    250 
    251     public function get_extended_locale_data( $locale ) {
    252         $locales_data = $this->get_locales_data();
    253         $locale_data = $locales_data[ $locale->wp_locale ];
    254         $locale_data['localized_core_url'] = $locale_data['language_pack_url'] = false;
    255 
    256         $latest_release = $locale_data['latest_release'];
    257         if ( $latest_release ) {
    258             $locale_data['localized_core_url'] = sprintf( 'https://downloads.wordpress.org/release/%s/wordpress-%s.zip', $locale->wp_locale, $latest_release );
    259             $language_packs_data = $this->get_language_packs_data();
    260 
    261             if ( version_compare( $latest_release, '4.0', '>=' ) && ! empty( $language_packs_data[ $locale->wp_locale ] ) ) {
    262                 list( $x, $y ) = explode( '.', $latest_release );
    263                 $latest_branch = "$x.$y";
    264 
    265                 $pack_version = null;
    266                 if ( in_array( $latest_release, $language_packs_data[ $locale->wp_locale ] ) ) {
    267                     $pack_version = $latest_release;
    268                 } elseif ( in_array( $latest_branch, $language_packs_data[ $locale->wp_locale ] ) ) {
    269                     $pack_version = $latest_branch;
    270                 }
    271 
    272                 if ( $pack_version ) {
    273                     $locale_data['language_pack_version'] = $pack_version;
    274                     $locale_data['language_pack_url'] = sprintf( 'https://downloads.wordpress.org/translation/core/%s/%s.zip', $pack_version, $locale->wp_locale );
    275                 }
    276             }
    277         }
    278 
    279         $contributors = $this->get_contributors( $locale );
    280         $locale_data['locale_managers'] = $contributors['locale_managers'];
    281         $locale_data['validators'] = $contributors['validators'];
    282         $locale_data['project_validators'] = $contributors['project_validators'];
    283         $locale_data['translators'] = $contributors['translators'];
    284         $locale_data['translators_past'] = $contributors['translators_past'];
    285 
    286         return $locale_data;
    287     }
    288 
    289     /**
    290      * Get the translators and validators for the given locale.
    291      *
    292      * @param GP_Locale $locale
    293      * @return array
    294      */
    295     public function get_contributors( $locale ) {
    296         $cache = wp_cache_get( 'contributors-data:' . $locale->wp_locale, 'wp-i18n-teams' );
    297         if ( false !== $cache ) {
    298             return $cache;
    299         }
    300 
    301         // Editors are only assigned to the parent locale.
    302         $parent_locale = null;
    303         if ( isset( $locale->root_slug ) ) {
    304             $parent_locale = GP_Locales::by_slug( $locale->root_slug );
    305         }
    306 
    307         $contributors = [];
    308         $contributors['locale_managers'] = $this->get_locale_managers( $parent_locale ?? $locale );
    309         $contributors['validators'] = $this->get_general_translation_editors( $parent_locale ?? $locale );
    310         $contributors['project_validators'] = $this->get_project_translation_editors( $parent_locale ?? $locale );
    311         $contributors['translators'] = $this->get_translation_contributors( $locale, 365 ); // Contributors from the past year
    312         $contributors['translators_past'] = array_diff_key( $this->get_translation_contributors( $locale ), $contributors['translators'] );
    313 
    314         wp_cache_set( 'contributors-data:' . $locale->wp_locale, $contributors, 'wp-i18n-teams', 2 * HOUR_IN_SECONDS );
    315 
    316         return $contributors;
    317     }
    318 
    319     public function get_core_translation_data() {
    320         $cache = get_transient( 'core_translation_data' );
    321         if ( false !== $cache ) {
    322             return $cache;
    323         }
    324 
    325         $projects = array( 'wp/dev', 'wp/dev/cc', 'wp/dev/admin', 'wp/dev/admin/network' );
    326         $counts = $percentages = array();
    327         foreach ( $projects as $project ) {
    328             $results = json_decode( file_get_contents( 'https://translate.wordpress.org/api/projects/' . $project ) );
    329             foreach ( $results->translation_sets as $set ) {
    330 
    331                 if ( ! isset( $set->wp_locale ) ) {
    332                     continue;
    333                 }
    334 
    335                 $wp_locale = $set->wp_locale;
    336                 if ( $set->slug !== 'default' ) {
    337                     $wp_locale = $wp_locale . '_' . $set->slug;
    338                 }
    339 
    340                 if ( ! isset( $counts[ $wp_locale ] ) ) {
    341                     $counts[ $wp_locale ] = 0;
    342                 }
    343                 $counts[ $wp_locale ] += (int) $set->percent_translated;
    344             }
    345         }
    346 
    347         foreach ( $counts as $locale => $percent_translated ) {
    348             // English locales don't have wp/dev/cc.
    349             $projects_count = 0 === strpos( $locale, 'en_' ) ? 3 : 4;
    350 
    351             /*
    352              * > 50% round down, so that a project with all strings except 1 translated shows 99%, instead of 100%.
    353              * < 50% round up, so that a project with just a few strings shows 1%, instead of 0%.
    354              */
    355             $percent_complete = 100 / ( 100 * $projects_count ) * $percent_translated;
    356             $percent_complete = ( $percent_complete > 50 ) ? floor( $percent_complete ) : ceil( $percent_complete );
    357 
    358             $percentages[ $locale ] = $percent_complete;
    359         }
    360 
    361         set_transient( 'core_translation_data', $percentages, 900 );
    362 
    363         return $percentages;
    364     }
    365 
    366     /**
    367      * Get the locale managers for the given locale.
    368      *
    369      * @param GP_Locale $locale
    370      * @return array
    371      */
    372     private function get_locale_managers( $locale ) {
    373         $locale_managers = [];
    374 
    375         $result = get_sites( [
    376             'locale'     => $locale->wp_locale,
    377             'network_id' => WPORG_GLOBAL_NETWORK_ID,
    378             'path'       => '/',
    379             'fields'     => 'ids',
    380             'number'     => '1',
    381         ] );
    382         $site_id = array_shift( $result );
    383         if ( ! $site_id ) {
    384             return $locale_managers;
    385         }
    386 
    387         $users = get_users( [
    388             'blog_id'     => $site_id,
    389             'role'        => 'locale_manager',
    390             'count_total' => false,
    391         ] );
    392 
    393         foreach ( $users as $user ) {
    394             $locale_managers[ $user->user_nicename ] = $this->prepare_user( $user );
    395         }
    396 
    397         uasort( $locale_managers, [ $this, '_sort_display_name_callback' ] );
    398 
    399         return $locale_managers;
    400     }
    401 
    402     /**
    403      * Get the general translation editors for the given locale.
    404      *
    405      * @param GP_Locale $locale
    406      * @return array
    407      */
    408     private function get_general_translation_editors( $locale ) {
    409         $editors = [];
    410 
    411         $result = get_sites( [
    412             'locale'     => $locale->wp_locale,
    413             'network_id' => WPORG_GLOBAL_NETWORK_ID,
    414             'path'       => '/',
    415             'fields'     => 'ids',
    416             'number'     => '1',
    417         ] );
    418         $site_id = array_shift( $result );
    419         if ( ! $site_id ) {
    420             return $editors;
    421         }
    422 
    423         $users = get_users( [
    424             'blog_id'     => $site_id,
    425             'role'        => 'general_translation_editor',
    426             'count_total' => false,
    427         ] );
    428 
    429         foreach ( $users as $user ) {
    430             $editors[ $user->user_nicename ] = $this->prepare_user( $user );
    431         }
    432 
    433         uasort( $editors, [ $this, '_sort_display_name_callback' ] );
    434 
    435         return $editors;
    436     }
    437 
    438     /**
    439      * Get the general translation editors for the given locale.
    440      *
    441      * @param GP_Locale $locale
    442      * @return array
    443      */
    444     private function get_project_translation_editors( $locale ) {
    445         $editors = [];
    446 
    447         $result = get_sites( [
    448             'locale'     => $locale->wp_locale,
    449             'network_id' => WPORG_GLOBAL_NETWORK_ID,
    450             'path'       => '/',
    451             'fields'     => 'ids',
    452             'number'     => '1',
    453         ] );
    454         $site_id = array_shift( $result );
    455         if ( ! $site_id ) {
    456             return $editors;
    457         }
    458 
    459         $users = get_users( [
    460             'blog_id'     => $site_id,
    461             'role'        => 'translation_editor',
    462             'count_total' => false,
    463         ] );
    464 
    465         foreach ( $users as $user ) {
    466             $editors[ $user->user_nicename ] = $this->prepare_user( $user );
    467         }
    468 
    469         uasort( $editors, [ $this, '_sort_display_name_callback' ] );
    470 
    471         return $editors;
    472     }
    473 
    474     /**
    475      * Prepares user objects for output.
    476      *
    477      * @param \WP_User $user The user.
    478      * @return array List of user data.
    479      */
    480     private function prepare_user( $user ) {
    481         if ( $user->display_name && $user->display_name !== $user->user_nicename ) {
    482             return [
    483                 'display_name' => $user->display_name,
    484                 'email'        => $user->user_email,
    485                 'nice_name'    => $user->user_nicename,
    486                 'slack'        => self::get_slack_username( $user->ID ),
    487             ];
    488         } else {
    489             return [
    490                 'display_name' => $user->user_nicename,
    491                 'email'        => $user->user_email,
    492                 'nice_name'    => $user->user_nicename,
    493                 'slack'        => self::get_slack_username( $user->ID ),
    494             ];
    495         }
    496     }
    497 
    498     /**
    499      * Get the translation contributors for the given locale.
    500      *
    501      * @param GP_Locale $locale
    502      * @return array
    503      */
    504     private function get_translation_contributors( $locale, $max_age_days = null ) {
    505         global $wpdb;
    506 
    507         $contributors = array();
    508 
    509         $date_constraint = '';
    510         if ( null !== $max_age_days ) {
    511             $date_constraint = $wpdb->prepare( " AND date_modified >= CURRENT_DATE - INTERVAL %d DAY", $max_age_days );
    512         }
    513 
    514         [ $locale, $locale_slug ] = array_merge( explode( '/', $locale->slug ), [ 'default' ] );
    515 
    516         $users = $wpdb->get_col( $wpdb->prepare(
    517             "SELECT DISTINCT user_id FROM translate_user_translations_count WHERE accepted > 0 AND locale = %s AND locale_slug = %s",
    518             $locale,
    519             $locale_slug
    520         ) . $date_constraint );
    521 
    522         if ( ! $users ) {
    523             return $contributors;
    524         }
    525 
    526         $user_data = $wpdb->get_results( "SELECT user_nicename, display_name, user_email FROM $wpdb->users WHERE ID IN (" . implode( ',', $users ) . ")" );
    527         foreach ( $user_data as $user ) {
    528             if ( $user->display_name && $user->display_name !== $user->user_nicename ) {
    529                 $contributors[ $user->user_nicename ] = array(
    530                     'display_name' => $user->display_name,
    531                     'nice_name'    => $user->user_nicename,
    532                 );
    533             } else {
    534                 $contributors[ $user->user_nicename ] = array(
    535                     'display_name' => $user->user_nicename,
    536                     'nice_name'    => $user->user_nicename,
    537                 );
    538             }
    539         }
    540 
    541         uasort( $contributors, array( $this, '_sort_display_name_callback' ) );
    542 
    543         return $contributors;
    544     }
    545 
    546     /**
    547      * Determine the release status of the given locale,
    548      *
    549      * @param string $rosetta_site_url
    550      * @param string $latest_release
    551      *
    552      * @return string
    553      */
    554     protected static function get_locale_release_status( $rosetta_site_url, $latest_release ) {
    555         if ( ! $rosetta_site_url ) {
    556             return 'no-site';
    557         }
    558 
    559         if ( ! $latest_release ) {
    560             return 'no-releases';
    561         }
    562 
    563         $one_lower = floatval( WP_CORE_LATEST_RELEASE ) - 0.1;
    564 
    565         if ( $latest_release == WP_CORE_LATEST_RELEASE ) {
    566             return 'latest';
    567         } elseif ( substr( $latest_release, 0, 3 ) == substr( WP_CORE_LATEST_RELEASE, 0, 3 ) ) {
    568             return 'minor-behind';
    569         } elseif ( substr( $latest_release, 0, 3 ) == substr( $one_lower, 0, 3 ) ) {
    570             return 'major-behind-one';
    571         } else {
    572             return 'major-behind-many';
    573         }
    574     }
    575 
    576     /**
    577      * Determine the translation status of the given locale.
    578      *
    579      * @param int $percent_translated
    580      *
    581      * @return string
    582      */
    583     protected static function get_locale_translation_status( $percent_translated ) {
    584         if ( $percent_translated == 100 ) {
    585             return 'translated-100';
    586         } elseif ( $percent_translated >= 95 ) {
    587             return 'translated-95';
    588         } elseif ( $percent_translated >= 90 ) {
    589             return 'translated-90';
    590         } elseif ( $percent_translated >= 50 ) {
    591             return 'translated-50';
    592         } else {
    593             return 'translated-50-less';
    594         }
    595     }
    596 
    597     /**
    598      * Get the Slack username for a .org user.
    599      *
    600      * @param int $user_id
    601      *
    602      * @return string
    603      */
    604     protected static function get_slack_username( $user_id ) {
    605         global $wpdb;
    606 
    607         $slack_username = '';
    608 
    609         $data = $wpdb->get_var( $wpdb->prepare( "SELECT profiledata FROM slack_users WHERE user_id = %d", $user_id ) );
    610         if ( $data && ( $data = json_decode( $data, true ) ) ) {
    611             if ( !empty( $data['profile']['display_name'] ) && empty( $data['deleted'] ) ) {
    612                 // Optional Display Name field.
    613                 $slack_username = $data['profile']['display_name'];
    614             } elseif ( !empty( $data['profile']['real_name'] ) && empty( $data['deleted'] ) ) {
    615                 // Fall back to "Full Name" field.
    616                 $slack_username = $data['profile']['real_name'];
    617             }
    618         }
    619 
    620         return $slack_username;
    621     }
    622 
    623     public function _sort_display_name_callback( $a, $b ) {
    624         return strnatcasecmp( $a['display_name'], $b['display_name'] );
    625     }
    626 }
    627 
    628 $GLOBALS['wp_i18n_teams'] = new WP_I18n_Teams();
     20bootstrap();
Note: See TracChangeset for help on using the changeset viewer.