Making WordPress.org

Ticket #741: 741-glotpress.patch

File 741-glotpress.patch, 10.3 KB (added by ocean90, 9 years ago)
  • trunk/translate.wordpress.org/includes/gp-plugins/wporg-rosetta-roles/wporg-rosetta-roles.php

     
    22/**
    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, Dominik Schilling
    126 */
    137class GP_WPorg_Rosetta_Roles extends GP_Plugin {
    14         var $id = 'wporg-rosetta-roles';
     8        /**
     9         * Holds the plugin ID.
     10         *
     11         * @var string
     12         */
     13        public $id = 'wporg-rosetta-roles';
    1514
    16         function __construct() {
     15        /**
     16         * Holds the role of an approver.
     17         *
     18         * @var string
     19         */
     20        public $approver_role = 'translation_editor';
     21
     22        /**
     23         * Contructor.
     24         */
     25        public function __construct() {
    1726                parent::__construct();
    1827                $this->add_filter( 'pre_can_user', array( 'args' => 2, 'priority' => 9 ) );
    1928        }
    2029
    21         function pre_can_user( $verdict, $args ) {
    22                 if ( ! class_exists( 'BP_Roles' ) )
     30        /**
     31         * Filter to check if the current user has permissions to approve strings, based
     32         * on a role on the Rosetta site.
     33         *
     34         * @param string $verdict Verdict.
     35         * @param array  $args    Array of arguments.
     36         * @return bool True if user has permissions, false if not.
     37         */
     38        public function pre_can_user( $verdict, $args ) {
     39                if ( ! class_exists( 'BP_Roles' ) ) {
    2340                        require_once( BACKPRESS_PATH . 'class.bp-roles.php' );
    24                 if ( ! class_exists( 'BP_User' ) )
     41                }
     42                if ( ! class_exists( 'BP_User' ) ) {
    2543                        require_once( BACKPRESS_PATH . 'class.bp-user.php' );
     44                }
     45
     46                // Current user.
    2647                $user = new BP_User( $args['user_id'] );
    2748
    2849                // 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'] ) )
     50                if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) ) {
    3051                        return true;
     52                }
    3153
    32                 if ( $args['action'] !== 'approve' || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) )
     54                if ( $args['action'] !== 'approve' || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
    3355                        return false;
    34  
    35                 if ( ! $locale_slug = $this->get_locale_slug( $args['object_type'], $args['object_id'] ) )
     56                }
     57
     58                // Get locale and current project ID.
     59                $locale_and_project_id = (object) $this->get_locale_and_project_id( $args['object_type'], $args['object_id'] );
     60                if ( ! $locale_and_project_id ) {
    3661                        return false;
     62                }
    3763
    38                 if ( ! $maybe_cap_key = $this->get_cap_key( $locale_slug ) )
     64                $locale_slug = $locale_and_project_id->locale;
     65                $current_project_id = $locale_and_project_id->project_id;
     66
     67                // Get blog prefix of the associated Rosetta site.
     68                if ( ! $blog_prefix = $this->get_blog_prefix( $locale_slug ) ) {
    3969                        return false;
     70                }
    4071
    41                 $user->cap_key = $maybe_cap_key;
     72                // Check if current user has the approver role.
     73                $user->cap_key = $blog_prefix . 'capabilities';
    4274                $user->caps = &$user->{$user->cap_key};
    43                 if ( ! is_array( $user->caps ) )
     75                if ( ! is_array( $user->caps ) ) {
    4476                        $user->caps = array();
     77                }
    4578                $user->get_role_caps();
    46                 foreach ( array( 'administrator', 'editor', 'author', 'contributor', 'validator' ) as $role ) {
    47                         if ( $user->has_cap( $role ) )
    48                                 return true;
     79                if ( ! $user->has_cap( $this->approver_role ) ) {
     80                        return false;
    4981                }
     82
     83                // Get IDs of projects which the user can approve.
     84                $meta_key =  $blog_prefix . 'allowed_translate_projects';
     85                if ( empty( $user->$meta_key ) || ! is_array( $user->$meta_key ) ) {
     86                        return false;
     87                }
     88
     89                // Short circuit the check if user can approve all projects.
     90                $allowed_projects = $user->$meta_key;
     91                if ( in_array( 'all', $allowed_projects ) ) {
     92                        return true;
     93                }
     94
     95                if ( in_array( $current_project_id, $allowed_projects ) ) {
     96                        return true;
     97                }
     98
     99                // An user is allowed to approve potential sub projects as well.
     100                $projects = $this->get_all_projects(); // Flat array
     101                $project_tree = $this->get_project_tree( $projects );
     102                $allowed_sub_project_ids = array();
     103                foreach ( $allowed_projects as $project_id ) {
     104                        $sub_project_ids = $this->get_sub_project_ids( $project_id, $project_tree );
     105                        if ( $sub_project_ids ) {
     106                                $allowed_sub_project_ids = array_merge( $allowed_sub_project_ids, $sub_project_ids );
     107                        }
     108                }
     109                $allowed_sub_project_ids = array_unique( $allowed_sub_project_ids );
     110
     111                if ( in_array( $current_project_id, $allowed_sub_project_ids ) ) {
     112                        return true;
     113                }
     114
    50115                return false;
    51116        }
    52117
    53         function get_locale_slug( $object_type, $object_id ) {
     118        /**
     119         * Fetches all projects from database.
     120         *
     121         * @return array List of projects with ID and parent ID.
     122         */
     123        public function get_all_projects() {
     124                global $gpdb;
     125
     126                $table = GP::$project->table;
     127
     128                $_projects = $gpdb->get_results( "
     129                        SELECT
     130                                id, parent_project_id
     131                        FROM $table
     132                        ORDER BY id
     133                " );
     134
     135                $projects = array();
     136                foreach ( $_projects as $project ) {
     137                        $projects[ $project->id ] = $project;
     138                }
     139
     140                return $projects;
     141        }
     142
     143        /**
     144         * Transforms a flat array to a hierarchy tree.
     145         *
     146         * @param array $projects  The projects
     147         * @param int   $parent_id Parent ID.
     148         * @param int   $max_depth Max depth to avoid endless recursion.
     149         * @return array The project tree.
     150         */
     151        public function get_project_tree( $projects, $parent_id = 0, $max_depth = 5 ) {
     152                if ( $max_depth < 0 ) { // Avoid an endless recursion.
     153                        return;
     154                }
     155
     156                $tree = array();
     157                foreach ( $projects as $project ) {
     158                        if ( $project->parent_project_id == $parent_id ) {
     159                                $sub_projects = $this->get_project_tree( $projects, $project->id, $max_depth - 1 );
     160                                if ( $sub_projects ) {
     161                                        $project->sub_projects = $sub_projects;
     162                                }
     163
     164                                $tree[ $project->id ] = $project;
     165                                unset( $projects[ $project->id ] );
     166                        }
     167                }
     168                return $tree;
     169        }
     170
     171        /**
     172         * Returns all sub project IDs of a parent ID.
     173         *
     174         * @param int   $project_id Parent ID.
     175         * @param array $projects   Hierarchy tree of projects.
     176         * @return array IDs of the sub projects.
     177         */
     178        public function get_sub_project_ids( $project_id, $projects ) {
     179                $project_branch = $this->get_project_branch( $project_id, $projects );
     180                $project_ids = self::array_keys_multi( $project_branch->sub_projects, 'sub_projects' );
     181                return $project_ids;
     182        }
     183
     184        /**
     185         * Returns a specifc branch of a hierarchy tree.
     186         *
     187         * @param int   $project_id Project ID.
     188         * @param array $projects   Hierarchy tree of projects.
     189         * @return mixed False if project ID doesn't exist, project branch on success.
     190         */
     191        public function get_project_branch( $project_id, $projects ) {
     192                if ( ! is_array( $projects ) ) {
     193                        return false;
     194                }
     195
     196                foreach ( $projects as $project ) {
     197                        if ( $project->id == $project_id ) {
     198                                return $project;
     199                        }
     200
     201                        $sub = $this->get_project_branch( $project_id, $project->sub_projects );
     202                        if ( $sub ) {
     203                                return $sub;
     204                        }
     205                }
     206
     207                return false;
     208        }
     209
     210        /**
     211         * Extracts project ID and locale slug from object type and ID.
     212         *
     213         * @param string $object_type Current object type.
     214         * @param string $object_id   Current object ID.
     215         * @return array Locale and project ID.
     216         */
     217        public function get_locale_and_project_id( $object_type, $object_id ) {
    54218                switch ( $object_type ) {
    55219                        case 'translation-set' :
    56                                 return GP::$translation_set->get( $object_id )->locale;
     220                                $set = GP::$translation_set->get( $object_id );
     221                                return array( 'locale' => $set->locale, 'project_id' => (int) $set->project_id );
    57222                                break;
    58223                        case 'project|locale|set-slug' :
    59                                 list( , $locale ) = explode( '|', $object_id );
    60                                 return $locale;
     224                                list( $project_id, $locale ) = explode( '|', $object_id );
     225                                return array( 'locale' => $locale, 'project_id' => (int) $project_id );
    61226                                break;
    62227                }
    63228                return false;
    64229        }
    65230
    66         function get_cap_key( $locale_slug ) {
     231        /**
     232         * Returns the blog prefix of a locale.
     233         *
     234         * @param string $locale_slug Slug of GlotPress locale.
     235         * @return bool|string Blog prefix on success, false on failure.
     236         */
     237        public function get_blog_prefix( $locale_slug ) {
    67238                global $gpdb;
    68239                static $ros_blogs, $ros_locale_assoc;
    69240
    70241                $gp_locale = GP_Locales::by_slug( $locale_slug );
    71                 if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) )
     242                if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) ) {
    72243                        return false;
     244                }
    73245
    74246                $wp_locale = $gp_locale->wp_locale;
    75247
    76248                if ( ! isset( $ros_blogs ) ) {
    77                         $ros_locale_assoc = $gpdb->get_results( "SELECT locale, subdomain FROM locales", OBJECT_K );
     249                        $ros_locale_assoc = $gpdb->get_results( 'SELECT locale, subdomain FROM locales', OBJECT_K );
    78250                        // 6 = Rosetta sites
    79                         $ros_blogs = $gpdb->get_results( "SELECT domain, blog_id FROM wporg_blogs WHERE site_id = 6", OBJECT_K );
     251                        $ros_blogs = $gpdb->get_results( 'SELECT domain, blog_id FROM wporg_blogs WHERE site_id = 6', OBJECT_K );
    80252                }
    81253
    82                 if ( isset( $ros_locale_assoc[ $wp_locale ] ) )
     254                if ( isset( $ros_locale_assoc[ $wp_locale ] ) ) {
    83255                        $subdomain = $ros_locale_assoc[ $wp_locale ]->subdomain;
    84                 else
     256                } else {
    85257                        return false;
     258                }
    86259
    87                 if ( isset( $ros_blogs[ "$subdomain.wordpress.org" ] ) )
    88                         return 'wporg_' . $ros_blogs[ "$subdomain.wordpress.org" ]->blog_id . '_capabilities';
     260                if ( isset( $ros_blogs[ "$subdomain.wordpress.org" ] ) ) {
     261                        return 'wporg_' . $ros_blogs[ "$subdomain.wordpress.org" ]->blog_id . '_';
     262                }
    89263
    90264                return false;
    91265        }
     266
     267        /**
     268         * Returns all keys of a multidimensional array.
     269         *
     270         * @param array $array Multidimensional array to extract keys from.
     271         * @return array Array keys.
     272         */
     273        public static function array_keys_multi( $array, $childs_name = 'childs' ) {
     274                $keys = array();
     275
     276                foreach ( $array as $key => $value ) {
     277                        $keys[] = $key;
     278
     279                        if ( is_array( $value->$childs_name ) ) {
     280                                $keys = array_merge( $keys, self::array_keys_multi( $value->$childs_name ) );
     281                        }
     282                }
     283
     284                return $keys;
     285        }
    92286}
    93287GP::$plugins->wporg_rosetta_roles = new GP_WPorg_Rosetta_Roles;