<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[3952] sites/trunk/wordpress.org/public_html/wp-content/plugins: Translate, Rosetta Roles: Refactor the plugin to use an autoloader and add an initial list table to manage translators.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/3952">3952</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/3952","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>ocean90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-09-04 19:57:05 +0000 (Sun, 04 Sep 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Translate, Rosetta Roles: Refactor the plugin to use an autoloader and add an initial list table to manage translators.

See <a href="http://meta.trac.wordpress.org/ticket/1770">#1770</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettaroleswporggprosettarolesphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/wporg-gp-rosetta-roles.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggproutesincroutesclasslocalephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-routes/inc/routes/class-locale.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/</li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincadminclasstranslatorsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/class-translators.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/list-table/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincadminlisttableclasstranslatorsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/list-table/class-translators.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincclasspluginphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincclassutilsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-utils.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/</li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/wordpressdotorg/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesvendorwordpressdotorgclassautoloaderphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/wordpressdotorg/class-autoloader.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincadminclasstranslatorsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/class-translators.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/class-translators.php                           (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/class-translators.php     2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,122 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordPressdotorg\GlotPress\Rosetta_Roles\Admin;
+
+class Translators {
+
+       const PAGE_SLUG = 'translators';
+
+       /**
+        * @var string
+        */
+       private $page_hook;
+
+       public function register_page() {
+               $this->page_hook = add_menu_page(
+                       __( 'Translators', 'wporg-translate' ),
+                       __( 'Translators', 'wporg-translate' ),
+                       'promote_users',
+                       self::PAGE_SLUG,
+                       [ $this, 'render_page' ],
+                       'dashicons-translation',
+                       71 // After Users
+               );
+
+               add_action( 'load-' . $this->page_hook, [ $this, 'load_page' ] );
+               add_action( 'load-' . $this->page_hook, array( $this, 'register_screen_options' ) );
+               add_filter( 'set-screen-option', array( $this, 'save_screen_options' ), 10, 3 );
+       }
+
+       /**
+        * Registers a 'per_page' screen option for the list table.
+        */
+       public function register_screen_options() {
+               $option = 'per_page';
+               $args   = array(
+                       'default' => 10,
+                       'option'  => 'translators_per_page',
+               );
+               add_screen_option( $option, $args );
+       }
+
+       /**
+        * Adds the 'per_page' screen option to the whitelist so it gets saved.
+        *
+        * @param bool|int $new_value Screen option value. Default false to skip.
+        * @param string   $option    The option name.
+        * @param int      $value     The number of rows to use.
+        * @return bool|int New screen option value.
+        */
+       public function save_screen_options( $new_value, $option, $value ) {
+               if ( 'translators_per_page' !== $option ) {
+                       return $new_value;
+               }
+
+               $value = (int) $value;
+               if ( $value < 1 || $value > 999 ) {
+                       return $new_value;
+               }
+
+               return $value;
+       }
+
+       /**
+        * Renders the admin page with a list table.
+        */
+       public function render_page() {
+               $list_table = $this->get_list_table();
+               $list_table->prepare_items();
+               ?>
+               <div class="wrap">
+                       <h2>
+                               <?php
+                               _e( 'Translators', 'wporg-translate' );
+
+                               if ( ! empty( $_REQUEST['s'] ) ) {
+                                       echo '<span class="subtitle">' . sprintf( __( 'Search results for &#8220;%s&#8221;', 'wporg-translate' ), esc_html( wp_unslash( $_REQUEST['s'] ) ) ) . '</span>';
+                               }
+                               ?>
+                       </h2>
+
+                       <form method="get">
+                               <input type="hidden" name="page" value="translators">
+                               <?php $list_table->search_box( __( 'Search Translators', 'wporg-translate' ), self::PAGE_SLUG ); ?>
+                       </form>
+
+                       <?php $list_table->views(); ?>
+
+                       <form method="post">
+                               <?php $list_table->display(); ?>
+                       </form>
+               </div>
+               <?php
+       }
+
+       /**
+        * Handler for loading the page.
+        */
+       public function load_page() {
+               $list_table = $this->get_list_table();
+               $current_action = $list_table->current_action();
+
+               switch ( $current_action ) {
+               }
+       }
+
+       /**
+        * Retrieves an instance of the list table.
+        *
+        * @return List_Table\Translators
+        */
+       private function get_list_table() {
+               static $list_table = null;
+
+               if ( $list_table instanceof List_Table\Translators ) {
+                       return $list_table;
+               }
+
+               $list_table = new List_Table\Translators();
+
+               return $list_table;
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincadminlisttableclasstranslatorsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/list-table/class-translators.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/list-table/class-translators.php                                (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/admin/list-table/class-translators.php  2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,215 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordPressdotorg\GlotPress\Rosetta_Roles\Admin\List_Table;
+
+use WordPressdotorg\GlotPress\Rosetta_Roles\Admin\Translators as Translators_Page;
+
+use WP_List_Table;
+use WP_User_Query;
+
+if ( ! class_exists( 'WP_List_Table' ) ) {
+       require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
+}
+
+class Translators extends WP_List_Table {
+
+       /**
+        * Whether the current user can promote users.
+        *
+        * @var bool
+        */
+       public $user_can_promote;
+
+       /**
+        * Constructor.
+        *
+        * @param array $args An associative array of arguments.
+        */
+       public function __construct( $args = array() ) {
+               parent::__construct( array(
+                       'singular' => 'translator',
+                       'plural'   => 'translators',
+                       'screen'   => isset( $args['screen'] ) ? $args['screen'] : null,
+               ) );
+
+               $this->user_can_promote = current_user_can( 'promote_users' );
+       }
+
+       /**
+        * Prepare the list for display.
+        */
+       public function prepare_items() {
+               global $wpdb;
+
+               $search   = isset( $_REQUEST['s'] ) ? wp_unslash( trim( $_REQUEST['s'] ) ) : '';
+               $per_page = $this->get_items_per_page( 'translators_per_page', 10 );
+               $paged    = $this->get_pagenum();
+
+               $role__in = [];
+               if ( isset( $_REQUEST['role'] ) ) {
+                       $role__in = $_REQUEST['role'];
+               }
+
+               $args = array(
+                       'number'   => $per_page,
+                       'offset'   => ( $paged - 1 ) * $per_page,
+                       'role__in' => $role__in,
+                       'search'   => $search,
+                       'fields'   => 'all_with_meta',
+               );
+
+               if ( '' !== $args['search'] ) {
+                       $args['search'] = '*' . $args['search'] . '*';
+               }
+
+               if ( isset( $_REQUEST['orderby'] ) ) {
+                       $args['orderby'] = $_REQUEST['orderby'];
+               }
+
+               if ( isset( $_REQUEST['order'] ) ) {
+                       $args['order'] = $_REQUEST['order'];
+               }
+
+               $translators = $wpdb->get_col(
+                       "SELECT DISTINCT(user_id) FROM {$wpdb->wporg_translation_editors}"
+               );
+
+               $args['blog_id'] = 0;
+               $args['include'] = $translators;
+
+               $user_query = new WP_User_Query( $args );
+               $this->items = $user_query->get_results();
+
+               $this->set_pagination_args( array(
+                       'total_items' => $user_query->get_total(),
+                       'per_page'    => $per_page,
+               ) );
+       }
+
+       /**
+        * Output 'no users' message.
+        */
+       public function no_items() {
+               _e( 'No translators were found.', 'wporg-translate' );
+       }
+
+       /**
+        * Get a list of columns for the list table.
+        *
+        * @return array Array in which the key is the ID of the column,
+        *               and the value is the description.
+        */
+       public function get_columns() {
+               return array(
+                       'cb'       => '<input type="checkbox">',
+                       'username' => __( 'Username', 'wporg-translate' ),
+                       'name'     => __( 'Name', 'wporg-translate' ),
+                       'email'    => __( 'E-mail', 'wporg-translate' ),
+                       'locales'  => __( 'Locales', 'wporg-translate' ),
+               );
+       }
+
+       /**
+        * Get a list of sortable columns for the list table.
+        *
+        * @return array Array of sortable columns.
+        */
+       protected function get_sortable_columns() {
+               return array(
+                       'username' => 'login',
+                       'name'     => 'name',
+                       'email'    => 'email',
+               );
+       }
+
+       /**
+        * Return a list of bulk actions available on this table.
+        *
+        * @return array Array of bulk actions.
+        */
+       protected function get_bulk_actions() {
+               return array(
+                       'remove-translators' => _x( 'Remove', 'translator', 'wporg-translate' ),
+               );
+       }
+
+       /**
+        * Generates content for a single row of the table.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function single_row( $user ) {
+               $user->filter = 'display';
+               parent::single_row( $user );
+       }
+
+       /**
+        * Prints the checkbox column.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function column_cb( $user ) {
+               if ( $this->user_can_promote ) {
+                       ?>
+                       <label class="screen-reader-text" for="cb-select-<?php echo $user->ID; ?>"><?php _e( 'Select translator', 'wporg-translate' ); ?></label>
+                       <input id="cb-select-<?php echo $user->ID; ?>" type="checkbox" name="translators[]" value="<?php echo $user->ID; ?>">
+                       <?php
+               }
+       }
+
+       /**
+        * Prints the username column.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function column_username( $user ) {
+               $avatar = get_avatar( $user->ID, 32 );
+
+               if ( $this->user_can_promote ) {
+                       $page_url = menu_page_url( Translators_Page::PAGE_SLUG, false );
+                       $edit_link = esc_url( add_query_arg( 'user_id', $user->ID, $page_url ) );
+                       $edit = "<strong><a href=\"$edit_link\">$user->user_login</a></strong>";
+
+                       $actions = array();
+                       $actions['edit'] = '<a href="' . $edit_link . '">' . __( 'Edit', 'wporg-translate' ) . '</a>';
+                       $edit .= $this->row_actions( $actions );
+               } else {
+                       $edit = "<strong>$user->user_login</strong>";
+               }
+
+               echo "$avatar $edit";
+       }
+
+       /**
+        * Prints the name column.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function column_name( $user ) {
+               echo "$user->first_name $user->last_name";
+       }
+
+       /**
+        * Prints the email column.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function column_email( $user ) {
+               echo "<a href='" . esc_url( "mailto:$user->user_email" ) . "'>$user->user_email</a>";
+       }
+
+       /**
+        * Prints the locales column.
+        *
+        * @param WP_User $user The current user.
+        */
+       public function column_locales( $user ) {
+               global $wpdb;
+
+               $locales = $wpdb->get_col( $wpdb->prepare(
+                       "SELECT DISTINCT(locale) FROM {$wpdb->wporg_translation_editors} WHERE user_id = %d"
+               , $user->ID ) );
+
+               echo implode( ', ', $locales );
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincclasspluginphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php                              (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php        2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,454 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordPressdotorg\GlotPress\Rosetta_Roles;
+
+class Plugin {
+
+       /**
+        * Cache group.
+        *
+        * @var string
+        */
+       public static $cache_group = 'wporg-translate';
+
+       /**
+        * Database table for translation editors.
+        */
+       const TRANSLATION_EDITORS_TABLE = 'translate_translation_editors';
+
+       /**
+        * Role of a per project translation editor.
+        */
+       const TRANSLATION_EDITOR_ROLE = 'translation_editor';
+
+       /**
+        * Role of a general translation editor.
+        */
+       const GENERAL_TRANSLATION_EDITOR_ROLE = 'general_translation_editor';
+
+       /**
+        * @var Plugin The singleton instance.
+        */
+       private static $instance;
+
+       /**
+        * Returns always the same instance of this plugin.
+        *
+        * @return Plugin
+        */
+       public static function get_instance() {
+               if ( ! ( self::$instance instanceof Plugin ) ) {
+                       self::$instance = new Plugin();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * Instantiates a new Plugin object.
+        */
+       private function __construct() {
+               add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
+       }
+
+       /**
+        * Initializes the plugin.
+        */
+       public function plugins_loaded() {
+               $GLOBALS['wpdb']->wporg_translation_editors = self::TRANSLATION_EDITORS_TABLE;
+
+               add_filter( 'gp_pre_can_user', array( $this, 'pre_can_user' ), 9 , 2 );
+               add_action( 'gp_project_created', array( $this, 'project_created' ) );
+               add_action( 'gp_project_saved', array( $this, 'project_saved' ) );
+
+               if ( is_admin() ) {
+                       $users = new Admin\Translators();
+                       add_action( 'admin_menu', [ $users, 'register_page' ] );
+               }
+       }
+
+       /**
+        * Filter to check if the current user has permissions to approve strings, based
+        * on a role on the Rosetta site.
+        *
+        * @param string $verdict Verdict.
+        * @param array  $args    Array of arguments.
+        * @return bool True if user has permissions, false if not.
+        */
+       public function pre_can_user( $verdict, $args ) {
+               if ( 'delete' === $args['action'] ) {
+                       return false;
+               }
+
+               // Administrators on global.wordpress.org are considered global admins in GlotPress.
+               if ( $this->is_global_administrator( $args['user_id'] ) ) {
+                       return true;
+               }
+
+               if ( 'approve' !== $args['action'] || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
+                       return false;
+               }
+
+               // Get locale and current project ID.
+               $locale_and_project_id = (object) $this->get_locale_and_project_id( $args['object_type'], $args['object_id'] );
+               if ( ! $locale_and_project_id ) {
+                       return false;
+               }
+
+               $locale_slug = $locale_and_project_id->locale;
+               $current_project_id = $locale_and_project_id->project_id;
+
+               // Simple check to see if they're an approver or not.
+               if ( ! $this->is_approver_for_locale( $args['user_id'], $locale_slug ) ) {
+                       return false;
+               }
+
+               // Grab the list of Projects (or 'all') that the user can approve.
+               $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug );
+               if ( ! $project_access_list ) {
+                       return false;
+               }
+
+               // Short circuit the check if user can approve all projects.
+               if ( in_array( 'all', $project_access_list ) ) {
+                       return true;
+               }
+
+               // If current project is a parent ID.
+               if ( in_array( $current_project_id, $project_access_list ) ) {
+                       return true;
+               }
+
+               // A user is allowed to approve sub projects as well.
+               $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug, /* $include_children = */ true );
+               if ( in_array( $current_project_id, $project_access_list ) ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Callback for when a project is created.
+        */
+       public function project_created() {
+               $this->clear_project_cache();
+       }
+
+       /**
+        * Callback for when a project is saved.
+        */
+       public function project_saved() {
+               $this->clear_project_cache();
+       }
+
+       /**
+        * Determines if a given user is a Global Admin.
+        *
+        * Users present as an administrator on global.wordpress.org are treated as a
+        * global administrator in GlotPress.
+        *
+        * @param int $user_id User ID.
+        * @return bool True, if user is an admin, false if not.
+        */
+       public function is_global_administrator( $user_id ) {
+               $user = get_user_by( 'id', $user_id );
+
+               // 115 = global.wordpress.org. Administrators on this site are considered global admins in GlotPress.
+               if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) ) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Determines if a given user is a Translation Approver for a Locale.
+        *
+        * @param int    $user_id     User ID.
+        * @param string $locale_slug The Locale for which we are checking.
+        * @return bool True, if user is an approver, false if not.
+        */
+       public function is_approver_for_locale( $user_id, $locale_slug ) {
+               static $cache = null;
+
+               if ( null === $cache ) {
+                       $cache = array();
+               }
+
+               if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
+                       return $cache[ $user_id ][ $locale_slug ];
+               }
+
+               if ( ! isset( $cache[ $user_id ] ) ) {
+                       $cache[ $user_id ] = array();
+               }
+
+               // Get blog prefix of the associated Rosetta site.
+               if ( ! $blog_prefix = $this->get_blog_prefix( $locale_slug ) ) {
+                       $cache[ $user_id ][ $locale_slug ] = false;
+                       return false;
+               }
+
+               $user = get_user_by( 'id', $user_id );
+
+               $cap_key = $blog_prefix . 'capabilities';
+               if ( ! isset( $user->{$cap_key} ) ) {
+                       $cache[ $user_id ][ $locale_slug ] = false;
+                       return false;
+               }
+
+               $capabilities = $user->{$cap_key};
+               $is_approver = ! empty( $capabilities[ self::TRANSLATION_EDITOR_ROLE ] ) || ! empty( $capabilities[ self::GENERAL_TRANSLATION_EDITOR_ROLE ] );
+               $cache[ $user_id ][ $locale_slug ] = $is_approver;
+
+               return $is_approver;
+       }
+
+       /**
+        * Retrieves a list of project ID's which a user can approve for.
+        *
+        * This is likely to be incorrrect in the event that the user is a Translation Editor or Global Admin.
+        * The array item 'all' is special, which means to allow access to all projects.
+        *
+        * @param int    $user_id          User ID.
+        * @param string $locale_slug      The Locale for which we are checking.
+        * @param bool   $include_children Whether to include the children project ID's in the return.
+        * @return array A list of the Project ID's for which the current user can approve translations for.
+        */
+       public function get_project_id_access_list( $user_id, $locale_slug, $include_children = false ) {
+               global $wpdb;
+               static $cache = null;
+
+               if ( null === $cache ) {
+                       $cache = array();
+               }
+
+               if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
+                       $project_access_list = $cache[ $user_id ][ $locale_slug ];
+               } else {
+                       $project_access_list = $wpdb->get_col( $wpdb->prepare( "
+                               SELECT project_id FROM
+                               {$wpdb->wporg_translation_editors}
+                               WHERE user_id = %d AND locale = %s
+                       ", $user_id, $locale_slug ) );
+
+                       if ( ! isset( $cache[ $user_id ] ) ) {
+                               $cache[ $user_id ] = array();
+                       }
+
+                       $cache[ $user_id ][ $locale_slug ] = $project_access_list;
+               }
+
+               if ( ! $project_access_list ) {
+                       return array();
+               }
+
+               if ( in_array( '0', $project_access_list, true ) ) {
+                       $project_access_list = array( 'all' );
+               }
+
+               // If we don't want the children, or the user has access to all projects.
+               if ( ! $include_children || in_array( 'all', $project_access_list ) ) {
+                       return $project_access_list;
+               }
+
+               // A user is allowed to approve sub projects as well.
+               $allowed_sub_project_ids = array();
+               foreach ( $project_access_list as $project_id ) {
+                       if ( 'all' === $project_id ) {
+                               continue;
+                       }
+                       $sub_project_ids = $this->get_sub_project_ids( $project_id );
+                       if ( $sub_project_ids ) {
+                               $allowed_sub_project_ids = array_merge( $allowed_sub_project_ids, $sub_project_ids );
+                       }
+               }
+
+               // $project_access_list contains parent project IDs, merge them with the sub-project IDs.
+               $project_access_list = array_merge( $project_access_list, $allowed_sub_project_ids );
+
+               return $project_access_list;
+       }
+
+       /**
+        * Fetches all projects from database.
+        *
+        * @return array List of projects with ID and parent ID.
+        */
+       public function get_all_projects() {
+               global $wpdb;
+               static $projects = null;
+
+               if ( null !== $projects ) {
+                       return $projects;
+               }
+
+               $_projects = $wpdb->get_results( "
+                       SELECT
+                               id, parent_project_id
+                       FROM {$wpdb->gp_projects}
+                       ORDER BY id
+               " );
+
+               $projects = array();
+               foreach ( $_projects as $project ) {
+                       $project->sub_projects = array();
+                       $projects[ $project->id ] = $project;
+               }
+
+               return $projects;
+       }
+
+       /**
+        * Returns projects as a hierarchy tree.
+        *
+        * @return array The project tree.
+        */
+       public function get_project_tree() {
+               static $project_tree = null;
+
+               if ( null !== $project_tree ) {
+                       return $project_tree;
+               }
+
+               $projects = $this->get_all_projects();
+
+               $project_tree = array();
+               foreach ( $projects as $project_id => $project ) {
+                       $projects[ $project->parent_project_id ]->sub_projects[ $project_id ] = &$projects[ $project_id ];
+                       if ( ! $project->parent_project_id ) {
+                               $project_tree[ $project_id ] = &$projects[ $project_id ];
+                       }
+               }
+
+               return $project_tree;
+       }
+
+       /**
+        * Returns all sub project IDs of a parent ID.
+        *
+        * @param int $project_id Parent ID.
+        * @return array IDs of the sub projects.
+        */
+       public function get_sub_project_ids( $project_id ) {
+               $cache_key = 'project:' . $project_id . ':childs';
+               $cache = wp_cache_get( $cache_key, self::$cache_group );
+               if ( false !== $cache ) {
+                       return $cache;
+               }
+
+               $project_tree = $this->get_project_tree();
+               $project_branch = $this->get_project_branch( $project_id, $project_tree );
+
+               $project_ids = array();
+               if ( isset( $project_branch->sub_projects ) ) {
+                       $project_ids = Utils::array_keys_multi( $project_branch->sub_projects, 'sub_projects' );
+               }
+
+               wp_cache_set( $cache_key, $project_ids, self::$cache_group );
+
+               return $project_ids;
+       }
+
+       /**
+        * Returns a specific branch of a hierarchy tree.
+        *
+        * @param int   $project_id Project ID.
+        * @param array $projects   Hierarchy tree of projects.
+        * @return mixed False if project ID doesn't exist, project branch on success.
+        */
+       public function get_project_branch( $project_id, $projects ) {
+               if ( ! is_array( $projects ) ) {
+                       return false;
+               }
+
+               foreach ( $projects as $project ) {
+                       if ( $project->id == $project_id ) {
+                               return $project;
+                       }
+
+                       if ( isset( $project->sub_projects ) ) {
+                               $sub = $this->get_project_branch( $project_id, $project->sub_projects );
+                               if ( $sub ) {
+                                       return $sub;
+                               }
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Removes all of the project ids from the cache.
+        */
+       public function clear_project_cache() {
+               $projects = $this->get_all_projects();
+
+               foreach ( $projects as $project ) {
+                       $cache_key = 'project:' . $project->id . ':childs';
+                       wp_cache_delete( $cache_key, self::$cache_group );
+               }
+       }
+
+       /**
+        * Extracts project ID and locale slug from object type and ID.
+        *
+        * @param string $object_type Current object type.
+        * @param string $object_id   Current object ID.
+        * @return array|false Locale and project ID, false on failure.
+        */
+       public function get_locale_and_project_id( $object_type, $object_id ) {
+               switch ( $object_type ) {
+                       case 'translation-set' :
+                               $set = GP::$translation_set->get( $object_id );
+                               return array( 'locale' => $set->locale, 'project_id' => (int) $set->project_id );
+
+                       case 'project|locale|set-slug' :
+                               list( $project_id, $locale ) = explode( '|', $object_id );
+                               return array( 'locale' => $locale, 'project_id' => (int) $project_id );
+               }
+               return false;
+       }
+
+       /**
+        * Returns the blog prefix of a locale.
+        *
+        * @param string $locale_slug Slug of GlotPress locale.
+        * @return bool|string Blog prefix on success, false on failure.
+        */
+       public function get_blog_prefix( $locale_slug ) {
+               global $wpdb;
+               static $ros_locale_assoc;
+
+               $gp_locale = GP_Locales::by_slug( $locale_slug );
+               if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) ) {
+                       return false;
+               }
+
+               $wp_locale = $gp_locale->wp_locale;
+
+               if ( ! isset( $ros_locale_assoc ) ) {
+                       $ros_locale_assoc = $wpdb->get_results( 'SELECT locale, subdomain FROM locales', OBJECT_K );
+               }
+
+               if ( isset( $ros_locale_assoc[ $wp_locale ] ) ) {
+                       $subdomain = $ros_locale_assoc[ $wp_locale ]->subdomain;
+               } else {
+                       return false;
+               }
+
+               $result = get_sites( [
+                       'network_id' => get_current_network_id(),
+                       'domain'     => "$subdomain.wordpress.org",
+                       'path'       => '/',
+                       'number'     => 1,
+               ] );
+               $site = array_shift( $result );
+
+               if ( $site ) {
+                       return 'wporg_' . $site->blog_id . '_';
+               }
+
+               return false;
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesincclassutilsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-utils.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-utils.php                               (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-utils.php 2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,27 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordPressdotorg\GlotPress\Rosetta_Roles;
+
+class Utils {
+
+       /**
+        * Returns all keys of a multidimensional array.
+        *
+        * @param array  $array      Multidimensional array to extract keys from.
+        * @param string $childs_key Optional. Key of the child elements. Default 'childs'.
+        * @return array Array keys.
+        */
+       public static function array_keys_multi( $array, $childs_key = 'childs' ) {
+               $keys = array();
+
+               foreach ( $array as $key => $value ) {
+                       $keys[] = $key;
+
+                       if ( isset( $value->$childs_key ) && is_array( $value->$childs_key ) ) {
+                               $keys = array_merge( $keys, self::array_keys_multi( $value->$childs_key ) );
+                       }
+               }
+
+               return $keys;
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettarolesvendorwordpressdotorgclassautoloaderphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/wordpressdotorg/class-autoloader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/wordpressdotorg/class-autoloader.php                               (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/vendor/wordpressdotorg/class-autoloader.php 2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,93 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+namespace WordPressdotorg\Autoload;
+
+/**
+ * An Autoloader which respects WordPress's filename standards.
+ *
+ * @package WordPressdotorg\Autoload
+ */
+class Autoloader {
+
+       /**
+        * Namespace separator.
+        */
+       const NS_SEPARATOR = '\\';
+
+       /**
+        * The prefix to compare classes against.
+        *
+        * @var string
+        * @access protected
+        */
+       protected $prefix;
+
+       /**
+        * Length of the prefix string.
+        *
+        * @var int
+        * @access protected
+        */
+       protected $prefix_length;
+
+       /**
+        * Path to the file to be loaded.
+        *
+        * @var string
+        * @access protected
+        */
+       protected $path;
+
+       /**
+        * Constructor.
+        *
+        * @param string $prefix Prefix all classes have in common.
+        * @param string $path   Path to the files to be loaded.
+        */
+       public function __construct( $prefix, $path ) {
+               $this->prefix        = $prefix;
+               $this->prefix_length = strlen( $prefix );
+               $this->path          = trailingslashit( $path );
+       }
+
+       /**
+        * Loads a class if it starts with `$this->prefix`.
+        *
+        * @param string $class The class to be loaded.
+        */
+       public function load( $class ) {
+               if ( strpos( $class, $this->prefix . self::NS_SEPARATOR ) !== 0 ) {
+                       return;
+               }
+
+               // Strip prefix from the start (ala PSR-4)
+               $class = substr( $class, $this->prefix_length + 1 );
+               $class = strtolower( $class );
+               $file  = '';
+
+               if ( false !== ( $last_ns_pos = strripos( $class, self::NS_SEPARATOR ) ) ) {
+                       $namespace = substr( $class, 0, $last_ns_pos );
+                       $namespace = str_replace( '_', '-', $namespace );
+                       $class     = substr( $class, $last_ns_pos + 1 );
+                       $file      = str_replace( self::NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
+               }
+
+               $file .= 'class-' . str_replace( '_', '-', $class ) . '.php';
+
+               $path = $this->path . $file;
+
+               if ( file_exists( $path ) ) {
+                       require $path;
+               }
+       }
+}
+
+/**
+ * Registers Autoloader's autoload function.
+ *
+ * @param string $prefix
+ * @param string $path
+ */
+function register_class_path( $prefix, $path ) {
+       $loader = new Autoloader( $prefix, $path );
+       spl_autoload_register( array( $loader, 'load' ) );
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggprosettaroleswporggprosettarolesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/wporg-gp-rosetta-roles.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/wporg-gp-rosetta-roles.php        2016-09-04 19:46:24 UTC (rev 3951)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/wporg-gp-rosetta-roles.php  2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,469 +2,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Plugin name: GlotPress: Rosetta Roles
</span><span class="cx" style="display: block; padding: 0 10px">  * Description: Ties roles on Rosetta sites directly into translate.wordpress.org.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Version:     1.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Version:     2.0
</ins><span class="cx" style="display: block; padding: 0 10px">  * Author:      WordPress.org
</span><span class="cx" style="display: block; padding: 0 10px">  * Author URI:  http://wordpress.org/
</span><span class="cx" style="display: block; padding: 0 10px">  * License:     GPLv2 or later
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-class WPorg_GP_Rosetta_Roles {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+namespace WordPressdotorg\GlotPress\Rosetta_Roles;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Cache group.
-        *
-        * @var string
-        */
-       public $cache_group = 'wporg-translate';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WordPressdotorg\Autoload;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Holds the plugin ID.
-        *
-        * @var string
-        */
-       public $id = 'wporg-rosetta-roles';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Store the root plugin file for usage with functions which use the plugin basename.
+define( __NAMESPACE__ . '\PLUGIN_FILE', __FILE__ );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Database table for translation editors.
-        */
-       const TRANSLATION_EDITORS_TABLE = 'translate_translation_editors';
-
-       /**
-        * Role of a per project translation editor.
-        */
-       const TRANSLATION_EDITOR_ROLE = 'translation_editor';
-
-       /**
-        * Role of a general translation editor.
-        */
-       const GENERAL_TRANSLATION_EDITOR_ROLE = 'general_translation_editor';
-
-       /**
-        * Contructor.
-        */
-       public function __construct() {
-               $GLOBALS['wpdb']->wporg_translation_editors = self::TRANSLATION_EDITORS_TABLE;
-
-               add_filter( 'gp_pre_can_user', array( $this, 'pre_can_user' ), 9 , 2 );
-               add_action( 'gp_project_created', array( $this, 'project_created' ) );
-               add_action( 'gp_project_saved', array( $this, 'project_saved' ) );
-       }
-
-       /**
-        * Filter to check if the current user has permissions to approve strings, based
-        * on a role on the Rosetta site.
-        *
-        * @param string $verdict Verdict.
-        * @param array  $args    Array of arguments.
-        * @return bool True if user has permissions, false if not.
-        */
-       public function pre_can_user( $verdict, $args ) {
-               if ( 'delete' === $args['action'] ) {
-                       return false;
-               }
-
-               // Administrators on global.wordpress.org are considered global admins in GlotPress.
-               if ( $this->is_global_administrator( $args['user_id'] ) ) {
-                       return true;
-               }
-
-               if ( 'approve' !== $args['action'] || ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
-                       return false;
-               }
-
-               // Get locale and current project ID.
-               $locale_and_project_id = (object) $this->get_locale_and_project_id( $args['object_type'], $args['object_id'] );
-               if ( ! $locale_and_project_id ) {
-                       return false;
-               }
-
-               $locale_slug = $locale_and_project_id->locale;
-               $current_project_id = $locale_and_project_id->project_id;
-
-               // Simple check to see if they're an approver or not.
-               if ( ! $this->is_approver_for_locale( $args['user_id'], $locale_slug ) ) {
-                       return false;
-               }
-
-               // Grab the list of Projects (or 'all') that the user can approve.
-               $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug );
-               if ( ! $project_access_list ) {
-                       return false;
-               }
-
-               // Short circuit the check if user can approve all projects.
-               if ( in_array( 'all', $project_access_list ) ) {
-                       return true;
-               }
-
-               // If current project is a parent ID.
-               if ( in_array( $current_project_id, $project_access_list ) ) {
-                       return true;
-               }
-
-               // A user is allowed to approve sub projects as well.
-               $project_access_list = $this->get_project_id_access_list( $args['user_id'], $locale_slug, /* $include_children = */ true );
-               if ( in_array( $current_project_id, $project_access_list ) ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Callback for when a project is created.
-        */
-       public function project_created() {
-               $this->clear_project_cache();
-       }
-
-       /**
-        * Callback for when a project is saved.
-        */
-       public function project_saved() {
-               $this->clear_project_cache();
-       }
-
-       /**
-        * Determines if a given user is a Global Admin.
-        *
-        * Users present as an administrator on global.wordpress.org are treated as a
-        * global administrator in GlotPress.
-        *
-        * @param int $user_id User ID.
-        * @return bool True, if user is an admin, false if not.
-        */
-       public function is_global_administrator( $user_id ) {
-               $user = get_user_by( 'id', $user_id );
-
-               // 115 = global.wordpress.org. Administrators on this site are considered global admins in GlotPress.
-               if ( ! empty( $user->wporg_115_capabilities ) && is_array( $user->wporg_115_capabilities ) && ! empty( $user->wporg_115_capabilities['administrator'] ) ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Determines if a given user is a Translation Approver for a Locale.
-        *
-        * @param int    $user_id     User ID.
-        * @param string $locale_slug The Locale for which we are checking.
-        * @return bool True, if user is an approver, false if not.
-        */
-       public function is_approver_for_locale( $user_id, $locale_slug ) {
-               static $cache = null;
-
-               if ( null === $cache ) {
-                       $cache = array();
-               }
-
-               if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
-                       return $cache[ $user_id ][ $locale_slug ];
-               }
-
-               if ( ! isset( $cache[ $user_id ] ) ) {
-                       $cache[ $user_id ] = array();
-               }
-
-               // Get blog prefix of the associated Rosetta site.
-               if ( ! $blog_prefix = $this->get_blog_prefix( $locale_slug ) ) {
-                       $cache[ $user_id ][ $locale_slug ] = false;
-                       return false;
-               }
-
-               $user = get_user_by( 'id', $user_id );
-
-               $cap_key = $blog_prefix . 'capabilities';
-               if ( ! isset( $user->{$cap_key} ) ) {
-                       $cache[ $user_id ][ $locale_slug ] = false;
-                       return false;
-               }
-
-               $capabilities = $user->{$cap_key};
-               $is_approver = ! empty( $capabilities[ self::TRANSLATION_EDITOR_ROLE ] ) || ! empty( $capabilities[ self::GENERAL_TRANSLATION_EDITOR_ROLE ] );
-               $cache[ $user_id ][ $locale_slug ] = $is_approver;
-
-               return $is_approver;
-       }
-
-       /**
-        * Retrieves a list of project ID's which a user can approve for.
-        *
-        * This is likely to be incorrrect in the event that the user is a Translation Editor or Global Admin.
-        * The array item 'all' is special, which means to allow access to all projects.
-        *
-        * @param int    $user_id          User ID.
-        * @param string $locale_slug      The Locale for which we are checking.
-        * @param bool   $include_children Whether to include the children project ID's in the return.
-        * @return array A list of the Project ID's for which the current user can approve translations for.
-        */
-       public function get_project_id_access_list( $user_id, $locale_slug, $include_children = false ) {
-               global $wpdb;
-               static $cache = null;
-
-               if ( null === $cache ) {
-                       $cache = array();
-               }
-
-               if ( isset( $cache[ $user_id ][ $locale_slug ] ) ) {
-                       $project_access_list = $cache[ $user_id ][ $locale_slug ];
-               } else {
-                       $project_access_list = $wpdb->get_col( $wpdb->prepare( "
-                               SELECT project_id FROM
-                               {$wpdb->wporg_translation_editors}
-                               WHERE user_id = %d AND locale = %s
-                       ", $user_id, $locale_slug ) );
-
-                       if ( ! isset( $cache[ $user_id ] ) ) {
-                               $cache[ $user_id ] = array();
-                       }
-
-                       $cache[ $user_id ][ $locale_slug ] = $project_access_list;
-               }
-
-               if ( ! $project_access_list ) {
-                       return array();
-               }
-
-               if ( in_array( '0', $project_access_list, true ) ) {
-                       $project_access_list = array( 'all' );
-               }
-
-               // If we don't want the children, or the user has access to all projects.
-               if ( ! $include_children || in_array( 'all', $project_access_list ) ) {
-                       return $project_access_list;
-               }
-
-               // A user is allowed to approve sub projects as well.
-               $allowed_sub_project_ids = array();
-               foreach ( $project_access_list as $project_id ) {
-                       if ( 'all' === $project_id ) {
-                               continue;
-                       }
-                       $sub_project_ids = $this->get_sub_project_ids( $project_id );
-                       if ( $sub_project_ids ) {
-                               $allowed_sub_project_ids = array_merge( $allowed_sub_project_ids, $sub_project_ids );
-                       }
-               }
-
-               // $project_access_list contains parent project IDs, merge them with the sub-project IDs.
-               $project_access_list = array_merge( $project_access_list, $allowed_sub_project_ids );
-
-               return $project_access_list;
-       }
-
-       /**
-        * Fetches all projects from database.
-        *
-        * @return array List of projects with ID and parent ID.
-        */
-       public function get_all_projects() {
-               global $wpdb;
-               static $projects = null;
-
-               if ( null !== $projects ) {
-                       return $projects;
-               }
-
-               $_projects = $wpdb->get_results( "
-                       SELECT
-                               id, parent_project_id
-                       FROM {$wpdb->gp_projects}
-                       ORDER BY id
-               " );
-
-               $projects = array();
-               foreach ( $_projects as $project ) {
-                       $project->sub_projects = array();
-                       $projects[ $project->id ] = $project;
-               }
-
-               return $projects;
-       }
-
-       /**
-        * Returns projects as a hierarchy tree.
-        *
-        * @return array The project tree.
-        */
-       public function get_project_tree() {
-               static $project_tree = null;
-
-               if ( null !== $project_tree ) {
-                       return $project_tree;
-               }
-
-               $projects = $this->get_all_projects();
-
-               $project_tree = array();
-               foreach ( $projects as $project_id => $project ) {
-                       $projects[ $project->parent_project_id ]->sub_projects[ $project_id ] = &$projects[ $project_id ];
-                       if ( ! $project->parent_project_id ) {
-                               $project_tree[ $project_id ] = &$projects[ $project_id ];
-                       }
-               }
-
-               return $project_tree;
-       }
-
-       /**
-        * Returns all sub project IDs of a parent ID.
-        *
-        * @param int $project_id Parent ID.
-        * @return array IDs of the sub projects.
-        */
-       public function get_sub_project_ids( $project_id ) {
-               $cache_key = 'project:' . $project_id . ':childs';
-               $cache = wp_cache_get( $cache_key, $this->cache_group );
-               if ( false !== $cache ) {
-                       return $cache;
-               }
-
-               $project_tree = $this->get_project_tree();
-               $project_branch = $this->get_project_branch( $project_id, $project_tree );
-
-               $project_ids = array();
-               if ( isset( $project_branch->sub_projects ) ) {
-                       $project_ids = self::array_keys_multi( $project_branch->sub_projects, 'sub_projects' );
-               }
-
-               wp_cache_set( $cache_key, $project_ids, $this->cache_group );
-
-               return $project_ids;
-       }
-
-       /**
-        * Returns a specific branch of a hierarchy tree.
-        *
-        * @param int   $project_id Project ID.
-        * @param array $projects   Hierarchy tree of projects.
-        * @return mixed False if project ID doesn't exist, project branch on success.
-        */
-       public function get_project_branch( $project_id, $projects ) {
-               if ( ! is_array( $projects ) ) {
-                       return false;
-               }
-
-               foreach ( $projects as $project ) {
-                       if ( $project->id == $project_id ) {
-                               return $project;
-                       }
-
-                       if ( isset( $project->sub_projects ) ) {
-                               $sub = $this->get_project_branch( $project_id, $project->sub_projects );
-                               if ( $sub ) {
-                                       return $sub;
-                               }
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Removes all of the project ids from the cache.
-        */
-       public function clear_project_cache() {
-               $projects = $this->get_all_projects();
-
-               foreach ( $projects as $project ) {
-                       $cache_key = 'project:' . $project->id . ':childs';
-                       wp_cache_delete( $cache_key, $this->cache_group );
-               }
-       }
-
-       /**
-        * Extracts project ID and locale slug from object type and ID.
-        *
-        * @param string $object_type Current object type.
-        * @param string $object_id   Current object ID.
-        * @return array|false Locale and project ID, false on failure.
-        */
-       public function get_locale_and_project_id( $object_type, $object_id ) {
-               switch ( $object_type ) {
-                       case 'translation-set' :
-                               $set = GP::$translation_set->get( $object_id );
-                               return array( 'locale' => $set->locale, 'project_id' => (int) $set->project_id );
-
-                       case 'project|locale|set-slug' :
-                               list( $project_id, $locale ) = explode( '|', $object_id );
-                               return array( 'locale' => $locale, 'project_id' => (int) $project_id );
-               }
-               return false;
-       }
-
-       /**
-        * Returns the blog prefix of a locale.
-        *
-        * @param string $locale_slug Slug of GlotPress locale.
-        * @return bool|string Blog prefix on success, false on failure.
-        */
-       public function get_blog_prefix( $locale_slug ) {
-               global $wpdb;
-               static $ros_locale_assoc;
-
-               $gp_locale = GP_Locales::by_slug( $locale_slug );
-               if ( ! $gp_locale || ! isset( $gp_locale->wp_locale ) ) {
-                       return false;
-               }
-
-               $wp_locale = $gp_locale->wp_locale;
-
-               if ( ! isset( $ros_locale_assoc ) ) {
-                       $ros_locale_assoc = $wpdb->get_results( 'SELECT locale, subdomain FROM locales', OBJECT_K );
-               }
-
-               if ( isset( $ros_locale_assoc[ $wp_locale ] ) ) {
-                       $subdomain = $ros_locale_assoc[ $wp_locale ]->subdomain;
-               } else {
-                       return false;
-               }
-
-               $result = get_sites( [
-                       'network_id' => get_current_network_id(),
-                       'domain'     => "$subdomain.wordpress.org",
-                       'path'       => '/',
-                       'number'     => 1,
-               ] );
-               $site = array_shift( $result );
-
-               if ( $site ) {
-                       return 'wporg_' . $site->blog_id . '_';
-               }
-
-               return false;
-       }
-
-       /**
-        * Returns all keys of a multidimensional array.
-        *
-        * @param array  $array      Multidimensional array to extract keys from.
-        * @param string $childs_key Optional. Key of the child elements. Default 'childs'.
-        * @return array Array keys.
-        */
-       public static function array_keys_multi( $array, $childs_key = 'childs' ) {
-               $keys = array();
-
-               foreach ( $array as $key => $value ) {
-                       $keys[] = $key;
-
-                       if ( isset( $value->$childs_key ) && is_array( $value->$childs_key ) ) {
-                               $keys = array_merge( $keys, self::array_keys_multi( $value->$childs_key ) );
-                       }
-               }
-
-               return $keys;
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( ! class_exists( '\WordPressdotorg\Autoload\Autoloader', false ) ) {
+       include __DIR__ . '/vendor/wordpressdotorg/class-autoloader.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function wporg_gp_rosetta_roles() {
-       global $wporg_gp_rosetta_roles;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Register an Autoloader for all files.
+Autoload\register_class_path( __NAMESPACE__, __DIR__ . '/inc' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! isset( $wporg_gp_rosetta_roles ) ) {
-               $wporg_gp_rosetta_roles = new WPorg_GP_Rosetta_Roles();
-       }
-
-       return $wporg_gp_rosetta_roles;
-}
-add_action( 'plugins_loaded', 'wporg_gp_rosetta_roles' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Instantiate the Plugin.
+Plugin::get_instance();
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggproutesincroutesclasslocalephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-routes/inc/routes/class-locale.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-routes/inc/routes/class-locale.php      2016-09-04 19:46:24 UTC (rev 3951)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-routes/inc/routes/class-locale.php        2016-09-04 19:57:05 UTC (rev 3952)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,6 +7,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use GP_Locales;
</span><span class="cx" style="display: block; padding: 0 10px"> use GP_Route;
</span><span class="cx" style="display: block; padding: 0 10px"> use stdClass;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WordPressdotorg\GlotPress\Rosetta_Roles\Plugin as Rosetta_Roles;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Locale Route Class.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -16,6 +17,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> class Locale extends GP_Route {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Adapter for the rosetta roles plugin.
+        *
+        * @var null|Rosetta_Roles
+        */
+       private $roles_adapter = null;
+
+       public function __construct() {
+               parent::__construct();
+
+               if ( method_exists( Rosetta_Roles::class, 'get_instance' ) ) {
+                       $this->roles_adapter = Rosetta_Roles::get_instance();
+               }
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Prints projects/translation sets of a top level project.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string      $locale_slug  Slug of the locale.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -44,10 +60,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $user_id = get_current_user_id();
</span><span class="cx" style="display: block; padding: 0 10px">                if (
</span><span class="cx" style="display: block; padding: 0 10px">                        ! is_user_logged_in() ||
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        ! function_exists( 'wporg_gp_rosetta_roles' ) || // Rosetta Roles plugin is not enabled
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 ! $this->roles_adapter || // Rosetta Roles plugin is not enabled
</ins><span class="cx" style="display: block; padding: 0 10px">                         ! (
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                wporg_gp_rosetta_roles()->is_global_administrator( $user_id ) || // Not a global admin
-                               wporg_gp_rosetta_roles()->is_approver_for_locale( $user_id, $locale_slug ) // Doesn't have project-level access either
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $this->roles_adapter->is_global_administrator( $user_id ) || // Not a global admin
+                               $this->roles_adapter->is_approver_for_locale( $user_id, $locale_slug ) // Doesn't have project-level access either
</ins><span class="cx" style="display: block; padding: 0 10px">                         )
</span><span class="cx" style="display: block; padding: 0 10px">                        // Add check to see if there are any waiting translations for this locale?
</span><span class="cx" style="display: block; padding: 0 10px">                        ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -564,7 +580,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Special Waiting Project Tab
</span><span class="cx" style="display: block; padding: 0 10px">                // This removes the parent_project_id restriction and replaces it with all-translation-editer-projects
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 'waiting' == $project->slug && is_user_logged_in() && function_exists( 'wporg_gp_rosetta_roles' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'waiting' == $project->slug && is_user_logged_in() && $this->roles_adapter ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! $filter ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $filter = 'strings-waiting-and-fuzzy';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -573,12 +589,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $user_id = get_current_user_id();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Global Admin or Locale-specific admin
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $can_approve_for_all = wporg_gp_rosetta_roles()->is_global_administrator( $user_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $can_approve_for_all = $this->roles_adapter->is_global_administrator( $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Check to see if they have any special approval permissions
</span><span class="cx" style="display: block; padding: 0 10px">                        $allowed_projects = array();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! $can_approve_for_all && wporg_gp_rosetta_roles()->is_approver_for_locale( $user_id, $locale ) ) {
-                               $allowed_projects = wporg_gp_rosetta_roles()->get_project_id_access_list( $user_id, $locale, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( ! $can_approve_for_all && $this->roles_adapter->is_approver_for_locale( $user_id, $locale ) ) {
+                               $allowed_projects = $this->roles_adapter->get_project_id_access_list( $user_id, $locale, true );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                // Check to see if they can approve for all projects in this locale.
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( in_array( 'all', $allowed_projects ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -587,7 +603,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $parent_project_sql = '';
</del><span class="cx" style="display: block; padding: 0 10px">                         if ( $can_approve_for_all ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                // The current user can approve for all projects, so just grab all with any waiting strings.
</span><span class="cx" style="display: block; padding: 0 10px">                                $parent_project_sql = 'AND ( stats.waiting > 0 OR stats.fuzzy > 0 )';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -606,7 +621,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Limit to only showing base-level projects
</span><span class="cx" style="display: block; padding: 0 10px">                        $parent_project_sql .= " AND tp.parent_project_id IN( (SELECT id FROM {$wpdb->gp_projects} WHERE parent_project_id IS NULL AND active = 1) )";
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $filter_order_by = $filter_where = '';
</span></span></pre>
</div>
</div>

</body>
</html>