<!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 “%s”', '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>