Making WordPress.org

Changeset 1425


Ignore:
Timestamp:
03/19/2015 07:31:41 PM (9 years ago)
Author:
ocean90
Message:

Translate: Update Rosetta bridge to reflect new role/permissions system.

see #741.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-rosetta-roles/wporg-rosetta-roles.php

    r909 r1425  
    33 * Tie roles on Rosetta sites directly into translate.wordpress.org.
    44 *
    5  * Anyone with the role of Validator, Contributor, Author, or Editor
    6  * has the ability to validate strings for that language.
    7  *
    8  * Future improvements to this would make this more granular (i.e. per-project)
    9  * with a UI in Rosetta to control those permissions.
    10  *
    11  * @author Nacin
     5 * @author Nacin, ocean90
    126 */
    137class GP_WPorg_Rosetta_Roles extends GP_Plugin {
    14     var $id = 'wporg-rosetta-roles';
    15 
    16     function __construct() {
     8    /**
     9     * Holds the plugin ID.
     10     *
     11     * @var string
     12     */
     13    public $id = 'wporg-rosetta-roles';
     14
     15    /**
     16     * Holds the role of an approver.
     17     *
     18     * @var string
     19     */
     20    public $approver_role = 'translation_editor';
     21
     22    /**
     23     * Holds the meta key of the project access list.
     24     *
     25     * @var string
     26     */
     27    public $project_access_meta_key = 'translation_editor_project_access_list';
     28
     29    /**
     30     * Contructor.
     31     */
     32    public function __construct() {
    1733        parent::__construct();
    1834        $this->add_filter( 'pre_can_user', array( 'args' => 2, 'priority' => 9 ) );
    1935    }
    2036
    21     function pre_can_user( $verdict, $args ) {
    22         if ( ! class_exists( 'BP_Roles' ) )
     37    /**
     38     * Filter to check if the current user has permissions to approve strings, based
     39     * on a role on the Rosetta site.
     40     *
     41     * @param string $verdict Verdict.
     42     * @param array  $args    Array of arguments.
     43     * @return bool True if user has permissions, false if not.
     44     */
     45    public function pre_can_user( $verdict, $args ) {
     46        if ( ! class_exists( 'BP_Roles' ) ) {
    2347            require_once( BACKPRESS_PATH . 'class.bp-roles.php' );
    24         if ( ! class_exists( 'BP_User' ) )
     48        }
     49        if ( ! class_exists( 'BP_User' ) ) {
    2550            require_once( BACKPRESS_PATH . 'class.bp-user.php' );
     51        }
     52
     53        // Current user.
    2654        $user = new BP_User( $args['user_id'] );
    2755
    2856        // 115 = global.wordpress.org. Administrators on this site are considered global admins in GlotPress.
    29         if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) )
     57        if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) ) {
    3058            return true;
    31 
    32         if ( $args['action'] !== 'approve' || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) )
    33             return false;
    34  
    35         if ( ! $locale_slug = $this->get_locale_slug( $args['object_type'], $args['object_id'] ) )
    36             return false;
    37 
    38         if ( ! $maybe_cap_key = $this->get_cap_key( $locale_slug ) )
    39             return false;
    40 
    41         $user->cap_key = $maybe_cap_key;
     59        }
     60
     61        if ( $args['action'] !== 'approve' || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
     62            return false;
     63        }
     64
     65        // Get locale and current project ID.
     66        $locale_and_project_id = (object) $this->get_locale_and_project_id( $args['object_type'], $args['object_id'] );
     67        if ( ! $locale_and_project_id ) {
     68            return false;
     69        }
     70
     71        $locale_slug = $locale_and_project_id->locale;
     72        $current_project_id = $locale_and_project_id->project_id;
     73
     74        // Get blog prefix of the associated Rosetta site.
     75        if ( ! $blog_prefix = $this->get_blog_prefix( $locale_slug ) ) {
     76            return false;
     77        }
     78
     79        // Check if current user has the approver role.
     80        $user->cap_key = $blog_prefix . 'capabilities';
    4281        $user->caps = &$user->{$user->cap_key};
    43         if ( ! is_array( $user->caps ) )
     82        if ( ! is_array( $user->caps ) ) {
    4483            $user->caps = array();
     84        }
    4585        $user->get_role_caps();
    46         foreach ( array( 'administrator', 'editor', 'author', 'contributor', 'validator' ) as $role ) {
    47             if ( $user->has_cap( $role ) )
    48                 return true;
    49         }
     86        if ( ! $user->has_cap( $this->approver_role ) ) {
     87            return false;
     88        }
     89
     90        // Get IDs of projects which the user can approve.
     91        $meta_key =  $blog_prefix . $this->project_access_meta_key;
     92        if ( empty( $user->$meta_key ) || ! is_array( $user->$meta_key ) ) {
     93            return false;
     94        }
     95
     96        $project_access_list = $user->$meta_key;
     97
     98        // Short circuit the check if user can approve all projects.
     99        if ( in_array( 'all', $project_access_list ) ) {
     100            return true;
     101        }
     102
     103        // If current project is a parent ID.
     104        if ( in_array( $current_project_id, $project_access_list ) ) {
     105            return true;
     106        }
     107
     108        // An user is allowed to approve potential sub projects as well.
     109        $projects = $this->get_all_projects(); // Flat array
     110        $project_tree = $this->get_project_tree( $projects );
     111        $allowed_sub_project_ids = array();
     112        foreach ( $project_access_list as $project_id ) {
     113            $sub_project_ids = $this->get_sub_project_ids( $project_id, $project_tree );
     114            if ( $sub_project_ids ) {
     115                $allowed_sub_project_ids = array_merge( $allowed_sub_project_ids, $sub_project_ids );
     116            }
     117        }
     118        $allowed_sub_project_ids = array_unique( $allowed_sub_project_ids );
     119
     120        if ( in_array( $current_project_id, $allowed_sub_project_ids ) ) {
     121            return true;
     122        }
     123
    50124        return false;
    51125    }
    52126
    53     function get_locale_slug( $object_type, $object_id ) {
     127    /**
     128     * Fetches all projects from database.
     129     *
     130     * @return array List of projects with ID and parent ID.
     131     */
     132    public function get_all_projects() {
     133        global $gpdb;
     134        static $projects;
     135
     136        if ( isset( $projects ) ) {
     137            return $projects;
     138        }
     139
     140        $table = GP::$project->table;
     141
     142        $_projects = $gpdb->get_results( "
     143            SELECT
     144                id, parent_project_id
     145            FROM $table
     146            ORDER BY id
     147        " );
     148
     149        $projects = array();
     150        foreach ( $_projects as $project ) {
     151            $projects[ $project->id ] = $project;
     152        }
     153
     154        return $projects;
     155    }
     156
     157    /**
     158     * Transforms a flat array to a hierarchy tree.
     159     *
     160     * @param array $projects  The projects
     161     * @param int   $parent_id Optional. Parent ID. Default 0.
     162     * @param int   $max_depth Optional. Max depth to avoid endless recursion. Default 5.
     163     * @return array The project tree.
     164     */
     165    public function get_project_tree( $projects, $parent_id = 0, $max_depth = 5 ) {
     166        if ( $max_depth < 0 ) { // Avoid an endless recursion.
     167            return;
     168        }
     169
     170        $tree = array();
     171        foreach ( $projects as $project ) {
     172            if ( $project->parent_project_id == $parent_id ) {
     173                $sub_projects = $this->get_project_tree( $projects, $project->id, $max_depth - 1 );
     174                if ( $sub_projects ) {
     175                    $project->sub_projects = $sub_projects;
     176                }
     177
     178                $tree[ $project->id ] = $project;
     179                unset( $projects[ $project->id ] );
     180            }
     181        }
     182        return $tree;
     183    }
     184
     185    /**
     186     * Returns all sub project IDs of a parent ID.
     187     *
     188     * @param int   $project_id Parent ID.
     189     * @param array $projects   Hierarchy tree of projects.
     190     * @return array IDs of the sub projects.
     191     */
     192    public function get_sub_project_ids( $project_id, $projects ) {
     193        $project_branch = $this->get_project_branch( $project_id, $projects );
     194        $project_ids = self::array_keys_multi( $project_branch->sub_projects, 'sub_projects' );
     195        return $project_ids;
     196    }
     197
     198    /**
     199     * Returns a specifc branch of a hierarchy tree.
     200     *
     201     * @param int   $project_id Project ID.
     202     * @param array $projects   Hierarchy tree of projects.
     203     * @return mixed False if project ID doesn't exist, project branch on success.
     204     */
     205    public function get_project_branch( $project_id, $projects ) {
     206        if ( ! is_array( $projects ) ) {
     207            return false;
     208        }
     209
     210        foreach ( $projects as $project ) {
     211            if ( $project->id == $project_id ) {
     212                return $project;
     213            }
     214
     215            $sub = $this->get_project_branch( $project_id, $project->sub_projects );
     216            if ( $sub ) {
     217                return $sub;
     218            }
     219        }
     220
     221        return false;
     222    }
     223
     224    /**
     225     * Extracts project ID and locale slug from object type and ID.
     226     *
     227     * @param string $object_type Current object type.
     228     * @param string $object_id   Current object ID.
     229     * @return array Locale and project ID.
     230     */
     231    public function get_locale_and_project_id( $object_type, $object_id ) {
    54232        switch ( $object_type ) {
    55233            case 'translation-set' :
    56                 return GP::$translation_set->get( $object_id )->locale;
    57                 break;
     234                $set = GP::$translation_set->get( $object_id );
     235                return array( 'locale' => $set->locale, 'project_id' => (int) $set->project_id );
     236
    58237            case 'project|locale|set-slug' :
    59                 list( , $locale ) = explode( '|', $object_id );
    60                 return $locale;
    61                 break;
     238                list( $project_id, $locale ) = explode( '|', $object_id );
     239                return array( 'locale' => $locale, 'project_id' => (int) $project_id );
    62240        }
    63241        return false;
    64242    }
    65243
    66     function get_cap_key( $locale_slug ) {
     244    /**
     245     * Returns the blog prefix of a locale.
     246     *
     247     * @param string $locale_slug Slug of GlotPress locale.
     248     * @return bool|string Blog prefix on success, false on failure.
     249     */
     250    public function get_blog_prefix( $locale_slug ) {
    67251        global $gpdb;
    68252        static $ros_blogs, $ros_locale_assoc;
    69253
    70254        $gp_locale = GP_Locales::by_slug( $locale_slug );
    71         if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) )
    72             return false;
     255        if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) ) {
     256            return false;
     257        }
    73258
    74259        $wp_locale = $gp_locale->wp_locale;
    75260
    76261        if ( ! isset( $ros_blogs ) ) {
    77             $ros_locale_assoc = $gpdb->get_results( "SELECT locale, subdomain FROM locales", OBJECT_K );
     262            $ros_locale_assoc = $gpdb->get_results( 'SELECT locale, subdomain FROM locales', OBJECT_K );
    78263            // 6 = Rosetta sites
    79             $ros_blogs = $gpdb->get_results( "SELECT domain, blog_id FROM wporg_blogs WHERE site_id = 6", OBJECT_K );
    80         }
    81 
    82         if ( isset( $ros_locale_assoc[ $wp_locale ] ) )
     264            $ros_blogs = $gpdb->get_results( 'SELECT domain, blog_id FROM wporg_blogs WHERE site_id = 6', OBJECT_K );
     265        }
     266
     267        if ( isset( $ros_locale_assoc[ $wp_locale ] ) ) {
    83268            $subdomain = $ros_locale_assoc[ $wp_locale ]->subdomain;
    84         else
    85             return false;
    86 
    87         if ( isset( $ros_blogs[ "$subdomain.wordpress.org" ] ) )
    88             return 'wporg_' . $ros_blogs[ "$subdomain.wordpress.org" ]->blog_id . '_capabilities';
     269        } else {
     270            return false;
     271        }
     272
     273        if ( isset( $ros_blogs[ "$subdomain.wordpress.org" ] ) ) {
     274            return 'wporg_' . $ros_blogs[ "$subdomain.wordpress.org" ]->blog_id . '_';
     275        }
    89276
    90277        return false;
    91278    }
     279
     280    /**
     281     * Returns all keys of a multidimensional array.
     282     *
     283     * @param array  $array      Multidimensional array to extract keys from.
     284     * @param string $childs_key Optional. Key of the child elements. Default 'childs'.
     285     * @return array Array keys.
     286     */
     287    public static function array_keys_multi( $array, $childs_key = 'childs' ) {
     288        $keys = array();
     289
     290        foreach ( $array as $key => $value ) {
     291            $keys[] = $key;
     292
     293            if ( is_array( $value->$childs_key ) ) {
     294                $keys = array_merge( $keys, self::array_keys_multi( $value->$childs_key ) );
     295            }
     296        }
     297
     298        return $keys;
     299    }
    92300}
     301
    93302GP::$plugins->wporg_rosetta_roles = new GP_WPorg_Rosetta_Roles;
Note: See TracChangeset for help on using the changeset viewer.