Making WordPress.org

Changeset 3952


Ignore:
Timestamp:
09/04/2016 07:57:05 PM (10 years ago)
Author:
ocean90
Message:

Translate, Rosetta Roles: Refactor the plugin to use an autoloader and add an initial list table to manage translators.

See #1770.

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins
Files:
10 added
2 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/wporg-gp-rosetta-roles.php

    r3692 r3952  
    33 * Plugin name: GlotPress: Rosetta Roles
    44 * Description: Ties roles on Rosetta sites directly into translate.wordpress.org.
    5  * Version:     1.0
     5 * Version:     2.0
    66 * Author:      WordPress.org
    77 * Author URI:  http://wordpress.org/
    … …  
    99 */
    1010
    11 class WPorg_GP_Rosetta_Roles {
     11namespace WordPressdotorg\GlotPress\Rosetta_Roles;
    1212
    13         /**
    14          * Cache group.
    15          *
    16          * @var string
    17          */
    18         public $cache_group = 'wporg-translate';
     13use WordPressdotorg\Autoload;
    1914
    20         /**
    21          * Holds the plugin ID.
    22          *
    23          * @var string
    24          */
    25         public $id = 'wporg-rosetta-roles';
     15// Store the root plugin file for usage with functions which use the plugin basename.
     16define( __NAMESPACE__ . '\PLUGIN_FILE', __FILE__ );
    2617
    27         /**
    28          * Database table for translation editors.
    29          */
    30         const TRANSLATION_EDITORS_TABLE = 'translate_translation_editors';
    31 
    32         /**
    33          * Role of a per project translation editor.
    34          */
    35         const TRANSLATION_EDITOR_ROLE = 'translation_editor';
    36 
    37         /**
    38          * Role of a general translation editor.
    39          */
    40         const GENERAL_TRANSLATION_EDITOR_ROLE = 'general_translation_editor';
    41 
    42         /**
    43          * Contructor.
    44          */
    45         public function __construct() {
    46                 $GLOBALS['wpdb']->wporg_translation_editors = self::TRANSLATION_EDITORS_TABLE;
    47 
    48                 add_filter( 'gp_pre_can_user', array( $this, 'pre_can_user' ), 9 , 2 );
    49                 add_action( 'gp_project_created', array( $this, 'project_created' ) );
    50                 add_action( 'gp_project_saved', array( $this, 'project_saved' ) );
    51         }
    52 
    53         /**
    54          * Filter to check if the current user has permissions to approve strings, based
    55          * on a role on the Rosetta site.
    56          *
    57          * @param string $verdict Verdict.
    58          * @param array  $args    Array of arguments.
    59          * @return bool True if user has permissions, false if not.
    60          */
    61         public function pre_can_user( $verdict, $args ) {
    62                 if ( 'delete' === $args['action'] ) {
    63                         return false;
    64                 }
    65 
    66                 // Administrators on global.wordpress.org are considered global admins in GlotPress.
    67                 if ( $this->is_global_administrator( $args['user_id'] ) ) {
    68                         return true;
    69                 }
    70 
    71                 if ( 'approve' !== $args['action'] || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
    72                         return false;
    73                 }
    74 
    75                 // Get locale and current project ID.
    76                 $locale_and_project_id = (object) $this->get_locale_and_project_id( $args['object_type'], $args['object_id'] );
    77                 if ( ! $locale_and_project_id ) {
    78                         return false;
    79                 }
    80 
    81                 $locale_slug = $locale_and_project_id->locale;
    82                 $current_project_id = $locale_and_project_id->project_id;
    83 
    84                 // Simple check to see if they're an approver or not.
    85                 if ( ! $this->is_approver_for_locale( $args['user_id'], $locale_slug ) ) {
    86                         return false;
    87                 }
    88 
    89                 // Grab the list of Projects (or 'all') that the user can approve.
    90                 $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug );
    91                 if ( ! $project_access_list ) {
    92                         return false;
    93                 }
    94 
    95                 // Short circuit the check if user can approve all projects.
    96                 if ( in_array( 'all', $project_access_list ) ) {
    97                         return true;
    98                 }
    99 
    100                 // If current project is a parent ID.
    101                 if ( in_array( $current_project_id, $project_access_list ) ) {
    102                         return true;
    103                 }
    104 
    105                 // A user is allowed to approve sub projects as well.
    106                 $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug, /* $include_children = */ true );
    107                 if ( in_array( $current_project_id, $project_access_list ) ) {
    108                         return true;
    109                 }
    110 
    111                 return false;
    112         }
    113 
    114         /**
    115          * Callback for when a project is created.
    116          */
    117         public function project_created() {
    118                 $this->clear_project_cache();
    119         }
    120 
    121         /**
    122          * Callback for when a project is saved.
    123          */
    124         public function project_saved() {
    125                 $this->clear_project_cache();
    126         }
    127 
    128         /**
    129          * Determines if a given user is a Global Admin.
    130          *
    131          * Users present as an administrator on global.wordpress.org are treated as a
    132          * global administrator in GlotPress.
    133          *
    134          * @param int $user_id User ID.
    135          * @return bool True, if user is an admin, false if not.
    136          */
    137         public function is_global_administrator( $user_id ) {
    138                 $user = get_user_by( 'id', $user_id );
    139 
    140                 // 115 = global.wordpress.org. Administrators on this site are considered global admins in GlotPress.
    141                 if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) ) {
    142                         return true;
    143                 }
    144 
    145                 return false;
    146         }
    147 
    148         /**
    149          * Determines if a given user is a Translation Approver for a Locale.
    150          *
    151          * @param int    $user_id     User ID.
    152          * @param string $locale_slug The Locale for which we are checking.
    153          * @return bool True, if user is an approver, false if not.
    154          */
    155         public function is_approver_for_locale( $user_id, $locale_slug ) {
    156                 static $cache = null;
    157 
    158                 if ( null === $cache ) {
    159                         $cache = array();
    160                 }
    161 
    162                 if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
    163                         return $cache[ $user_id ][ $locale_slug ];
    164                 }
    165 
    166                 if ( ! isset( $cache[ $user_id ] ) ) {
    167                         $cache[ $user_id ] = array();
    168                 }
    169 
    170                 // Get blog prefix of the associated Rosetta site.
    171                 if ( ! $blog_prefix = $this->get_blog_prefix( $locale_slug ) ) {
    172                         $cache[ $user_id ][ $locale_slug ] = false;
    173                         return false;
    174                 }
    175 
    176                 $user = get_user_by( 'id', $user_id );
    177 
    178                 $cap_key = $blog_prefix . 'capabilities';
    179                 if ( ! isset( $user->{$cap_key} ) ) {
    180                         $cache[ $user_id ][ $locale_slug ] = false;
    181                         return false;
    182                 }
    183 
    184                 $capabilities = $user->{$cap_key};
    185                 $is_approver = ! empty( $capabilities[ self::TRANSLATION_EDITOR_ROLE ] ) || ! empty( $capabilities[ self::GENERAL_TRANSLATION_EDITOR_ROLE ] );
    186                 $cache[ $user_id ][ $locale_slug ] = $is_approver;
    187 
    188                 return $is_approver;
    189         }
    190 
    191         /**
    192          * Retrieves a list of project ID's which a user can approve for.
    193          *
    194          * This is likely to be incorrrect in the event that the user is a Translation Editor or Global Admin.
    195          * The array item 'all' is special, which means to allow access to all projects.
    196          *
    197          * @param int    $user_id          User ID.
    198          * @param string $locale_slug      The Locale for which we are checking.
    199          * @param bool   $include_children Whether to include the children project ID's in the return.
    200          * @return array A list of the Project ID's for which the current user can approve translations for.
    201          */
    202         public function get_project_id_access_list( $user_id, $locale_slug, $include_children = false ) {
    203                 global $wpdb;
    204                 static $cache = null;
    205 
    206                 if ( null === $cache ) {
    207                         $cache = array();
    208                 }
    209 
    210                 if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
    211                         $project_access_list = $cache[ $user_id ][ $locale_slug ];
    212                 } else {
    213                         $project_access_list = $wpdb->get_col( $wpdb->prepare( "
    214                                 SELECT project_id FROM
    215                                 {$wpdb->wporg_translation_editors}
    216                                 WHERE user_id = %d AND locale = %s
    217                         ", $user_id, $locale_slug ) );
    218 
    219                         if ( ! isset( $cache[ $user_id ] ) ) {
    220                                 $cache[ $user_id ] = array();
    221                         }
    222 
    223                         $cache[ $user_id ][ $locale_slug ] = $project_access_list;
    224                 }
    225 
    226                 if ( ! $project_access_list ) {
    227                         return array();
    228                 }
    229 
    230                 if ( in_array( '0', $project_access_list, true ) ) {
    231                         $project_access_list = array( 'all' );
    232                 }
    233 
    234                 // If we don't want the children, or the user has access to all projects.
    235                 if ( ! $include_children || in_array( 'all', $project_access_list ) ) {
    236                         return $project_access_list;
    237                 }
    238 
    239                 // A user is allowed to approve sub projects as well.
    240                 $allowed_sub_project_ids = array();
    241                 foreach ( $project_access_list as $project_id ) {
    242                         if ( 'all' === $project_id ) {
    243                                 continue;
    244                         }
    245                         $sub_project_ids = $this->get_sub_project_ids( $project_id );
    246                         if ( $sub_project_ids ) {
    247                                 $allowed_sub_project_ids = array_merge( $allowed_sub_project_ids, $sub_project_ids );
    248                         }
    249                 }
    250 
    251                 // $project_access_list contains parent project IDs, merge them with the sub-project IDs.
    252                 $project_access_list = array_merge( $project_access_list, $allowed_sub_project_ids );
    253 
    254                 return $project_access_list;
    255         }
    256 
    257         /**
    258          * Fetches all projects from database.
    259          *
    260          * @return array List of projects with ID and parent ID.
    261          */
    262         public function get_all_projects() {
    263                 global $wpdb;
    264                 static $projects = null;
    265 
    266                 if ( null !== $projects ) {
    267                         return $projects;
    268                 }
    269 
    270                 $_projects = $wpdb->get_results( "
    271                         SELECT
    272                                 id, parent_project_id
    273                         FROM {$wpdb->gp_projects}
    274                         ORDER BY id
    275                 " );
    276 
    277                 $projects = array();
    278                 foreach ( $_projects as $project ) {
    279                         $project->sub_projects = array();
    280                         $projects[ $project->id ] = $project;
    281                 }
    282 
    283                 return $projects;
    284         }
    285 
    286         /**
    287          * Returns projects as a hierarchy tree.
    288          *
    289          * @return array The project tree.
    290          */
    291         public function get_project_tree() {
    292                 static $project_tree = null;
    293 
    294                 if ( null !== $project_tree ) {
    295                         return $project_tree;
    296                 }
    297 
    298                 $projects = $this->get_all_projects();
    299 
    300                 $project_tree = array();
    301                 foreach ( $projects as $project_id => $project ) {
    302                         $projects[ $project->parent_project_id ]->sub_projects[ $project_id ] = &$projects[ $project_id ];
    303                         if ( ! $project->parent_project_id ) {
    304                                 $project_tree[ $project_id ] = &$projects[ $project_id ];
    305                         }
    306                 }
    307 
    308                 return $project_tree;
    309         }
    310 
    311         /**
    312          * Returns all sub project IDs of a parent ID.
    313          *
    314          * @param int $project_id Parent ID.
    315          * @return array IDs of the sub projects.
    316          */
    317         public function get_sub_project_ids( $project_id ) {
    318                 $cache_key = 'project:' . $project_id . ':childs';
    319                 $cache = wp_cache_get( $cache_key, $this->cache_group );
    320                 if ( false !== $cache ) {
    321                         return $cache;
    322                 }
    323 
    324                 $project_tree = $this->get_project_tree();
    325                 $project_branch = $this->get_project_branch( $project_id, $project_tree );
    326 
    327                 $project_ids = array();
    328                 if ( isset( $project_branch->sub_projects ) ) {
    329                         $project_ids = self::array_keys_multi( $project_branch->sub_projects, 'sub_projects' );
    330                 }
    331 
    332                 wp_cache_set( $cache_key, $project_ids, $this->cache_group );
    333 
    334                 return $project_ids;
    335         }
    336 
    337         /**
    338          * Returns a specific branch of a hierarchy tree.
    339          *
    340          * @param int   $project_id Project ID.
    341          * @param array $projects   Hierarchy tree of projects.
    342          * @return mixed False if project ID doesn't exist, project branch on success.
    343          */
    344         public function get_project_branch( $project_id, $projects ) {
    345                 if ( ! is_array( $projects ) ) {
    346                         return false;
    347                 }
    348 
    349                 foreach ( $projects as $project ) {
    350                         if ( $project->id == $project_id ) {
    351                                 return $project;
    352                         }
    353 
    354                         if ( isset( $project->sub_projects ) ) {
    355                                 $sub = $this->get_project_branch( $project_id, $project->sub_projects );
    356                                 if ( $sub ) {
    357                                         return $sub;
    358                                 }
    359                         }
    360                 }
    361 
    362                 return false;
    363         }
    364 
    365         /**
    366          * Removes all of the project ids from the cache.
    367          */
    368         public function clear_project_cache() {
    369                 $projects = $this->get_all_projects();
    370 
    371                 foreach ( $projects as $project ) {
    372                         $cache_key = 'project:' . $project->id . ':childs';
    373                         wp_cache_delete( $cache_key, $this->cache_group );
    374                 }
    375         }
    376 
    377         /**
    378          * Extracts project ID and locale slug from object type and ID.
    379          *
    380          * @param string $object_type Current object type.
    381          * @param string $object_id   Current object ID.
    382          * @return array|false Locale and project ID, false on failure.
    383          */
    384         public function get_locale_and_project_id( $object_type, $object_id ) {
    385                 switch ( $object_type ) {
    386                         case 'translation-set' :
    387                                 $set = GP::$translation_set->get( $object_id );
    388                                 return array( 'locale' => $set->locale, 'project_id' => (int) $set->project_id );
    389 
    390                         case 'project|locale|set-slug' :
    391                                 list( $project_id, $locale ) = explode( '|', $object_id );
    392                                 return array( 'locale' => $locale, 'project_id' => (int) $project_id );
    393                 }
    394                 return false;
    395         }
    396 
    397         /**
    398          * Returns the blog prefix of a locale.
    399          *
    400          * @param string $locale_slug Slug of GlotPress locale.
    401          * @return bool|string Blog prefix on success, false on failure.
    402          */
    403         public function get_blog_prefix( $locale_slug ) {
    404                 global $wpdb;
    405                 static $ros_locale_assoc;
    406 
    407                 $gp_locale = GP_Locales::by_slug( $locale_slug );
    408                 if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) ) {
    409                         return false;
    410                 }
    411 
    412                 $wp_locale = $gp_locale->wp_locale;
    413 
    414                 if ( ! isset( $ros_locale_assoc ) ) {
    415                         $ros_locale_assoc = $wpdb->get_results( 'SELECT locale, subdomain FROM locales', OBJECT_K );
    416                 }
    417 
    418                 if ( isset( $ros_locale_assoc[ $wp_locale ] ) ) {
    419                         $subdomain = $ros_locale_assoc[ $wp_locale ]->subdomain;
    420                 } else {
    421                         return false;
    422                 }
    423 
    424                 $result = get_sites( [
    425                         'network_id' => get_current_network_id(),
    426                         'domain'     => "$subdomain.wordpress.org",
    427                         'path'       => '/',
    428                         'number'     => 1,
    429                 ] );
    430                 $site = array_shift( $result );
    431 
    432                 if ( $site ) {
    433                         return 'wporg_' . $site->blog_id . '_';
    434                 }
    435 
    436                 return false;
    437         }
    438 
    439         /**
    440          * Returns all keys of a multidimensional array.
    441          *
    442          * @param array  $array      Multidimensional array to extract keys from.
    443          * @param string $childs_key Optional. Key of the child elements. Default 'childs'.
    444          * @return array Array keys.
    445          */
    446         public static function array_keys_multi( $array, $childs_key = 'childs' ) {
    447                 $keys = array();
    448 
    449                 foreach ( $array as $key => $value ) {
    450                         $keys[] = $key;
    451 
    452                         if ( isset( $value->$childs_key ) && is_array( $value->$childs_key ) ) {
    453                                 $keys = array_merge( $keys, self::array_keys_multi( $value->$childs_key ) );
    454                         }
    455                 }
    456 
    457                 return $keys;
    458         }
     18if ( ! class_exists( '\WordPressdotorg\Autoload\Autoloader', false ) ) {
     19        include __DIR__ . '/vendor/wordpressdotorg/class-autoloader.php';
    45920}
    46021
    461 function wporg_gp_rosetta_roles() {
    462         global $wporg_gp_rosetta_roles;
     22// Register an Autoloader for all files.
     23Autoload\register_class_path( __NAMESPACE__, __DIR__ . '/inc' );
    46324
    464         if ( ! isset( $wporg_gp_rosetta_roles ) ) {
    465                 $wporg_gp_rosetta_roles = new WPorg_GP_Rosetta_Roles();
    466         }
    467 
    468         return $wporg_gp_rosetta_roles;
    469 }
    470 add_action( 'plugins_loaded', 'wporg_gp_rosetta_roles' );
     25// Instantiate the Plugin.
     26Plugin::get_instance();
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-routes/inc/routes/class-locale.php

    r3277 r3952  
    88use GP_Route;
    99use stdClass;
     10use WordPressdotorg\GlotPress\Rosetta_Roles\Plugin as Rosetta_Roles;
    1011
    1112/**
    … …  
    1718
    1819        /**
     20         * Adapter for the rosetta roles plugin.
     21         *
     22         * @var null|Rosetta_Roles
     23         */
     24        private $roles_adapter = null;
     25
     26        public function __construct() {
     27                parent::__construct();
     28
     29                if ( method_exists( Rosetta_Roles::class, 'get_instance' ) ) {
     30                        $this->roles_adapter = Rosetta_Roles::get_instance();
     31                }
     32        }
     33
     34        /**
    1935         * Prints projects/translation sets of a top level project.
    2036         *
    … …  
    4561                if (
    4662                        ! is_user_logged_in() ||
    47                         ! function_exists( 'wporg_gp_rosetta_roles' ) || // Rosetta Roles plugin is not enabled
     63                        ! $this->roles_adapter || // Rosetta Roles plugin is not enabled
    4864                        ! (
    49                                 wporg_gp_rosetta_roles()->is_global_administrator( $user_id ) || // Not a global admin
    50                                 wporg_gp_rosetta_roles()->is_approver_for_locale( $user_id, $locale_slug ) // Doesn't have project-level access either
     65                                $this->roles_adapter->is_global_administrator( $user_id ) || // Not a global admin
     66                                $this->roles_adapter->is_approver_for_locale( $user_id, $locale_slug ) // Doesn't have project-level access either
    5167                        )
    5268                        // Add check to see if there are any waiting translations for this locale?
    … …  
    565581                // Special Waiting Project Tab
    566582                // This removes the parent_project_id restriction and replaces it with all-translation-editer-projects
    567                 if ( 'waiting' == $project->slug && is_user_logged_in() && function_exists( 'wporg_gp_rosetta_roles' ) ) {
     583                if ( 'waiting' == $project->slug && is_user_logged_in() && $this->roles_adapter ) {
    568584
    569585                        if ( ! $filter ) {
    … …  
    574590
    575591                        // Global Admin or Locale-specific admin
    576                         $can_approve_for_all = wporg_gp_rosetta_roles()->is_global_administrator( $user_id );
     592                        $can_approve_for_all = $this->roles_adapter->is_global_administrator( $user_id );
    577593
    578594                        // Check to see if they have any special approval permissions
    579595                        $allowed_projects = array();
    580                         if ( ! $can_approve_for_all && wporg_gp_rosetta_roles()->is_approver_for_locale( $user_id, $locale ) ) {
    581                                 $allowed_projects = wporg_gp_rosetta_roles()->get_project_id_access_list( $user_id, $locale, true );
     596                        if ( ! $can_approve_for_all && $this->roles_adapter->is_approver_for_locale( $user_id, $locale ) ) {
     597                                $allowed_projects = $this->roles_adapter->get_project_id_access_list( $user_id, $locale, true );
    582598
    583599                                // Check to see if they can approve for all projects in this locale.
    … …  
    588604                        }
    589605
    590                         $parent_project_sql = '';
    591606                        if ( $can_approve_for_all ) {
    592607                                // The current user can approve for all projects, so just grab all with any waiting strings.
    … …  
    607622                        // Limit to only showing base-level projects
    608623                        $parent_project_sql .= " AND tp.parent_project_id IN( (SELECT id FROM {$wpdb->gp_projects} WHERE parent_project_id IS NULL AND active = 1) )";
    609 
    610624                }
    611625
Note: See TracChangeset for help on using the changeset viewer.