Making WordPress.org

Changeset 3952


Ignore:
Timestamp:
09/04/2016 07:57:05 PM (7 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.