<!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>[2197] sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles: Rosetta: Update roles plugin to use the new `translation_editors` table.</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/2197">2197</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/2197","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>2015-12-17 12:32:48 +0000 (Thu, 17 Dec 2015)</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'>Rosetta: Update roles plugin to use the new `translation_editors` table.

* Introduce a new role for General Translation Editors.
* Introduce a new capability to manage Translation Editors, assigned to General Translation Editors.
* Fix wrong action name for bulk removal.
* Remove custom "Role" column, obsolete with WordPress 4.4.
* Introduce `translation_editor_added`, `translation_editor_updated` and `translation_editor_removed` actions.
* Make "Translation Editors" a top-level menu.
* Add views to the "Translation Editors" table.

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

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesclasstranslationeditorslisttablephp">sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/class-translation-editors-list-table.php</a></li>
<li><a href="#sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesjsrosettarolesjs">sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js</a></li>
<li><a href="#sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesrosettarolesphp">sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php</a></li>
<li><a href="#sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesviewstranslationeditorsphp">sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/translation-editors.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesclasstranslationeditorslisttablephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/class-translation-editors-list-table.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/class-translation-editors-list-table.php 2015-12-17 11:59:45 UTC (rev 2196)
+++ sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/class-translation-editors-list-table.php   2015-12-17 12:32:48 UTC (rev 2197)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,18 +7,18 @@
</span><span class="cx" style="display: block; padding: 0 10px"> class Rosetta_Translation_Editors_List_Table extends WP_List_Table {
</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">-         * Holds the role of a translation editor.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Holds the roles for translation editors.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var arrays
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public $user_role;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public $user_roles;
</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">-         * Holds the meta key of the project access list.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Holds the instance of the Rosetta_Roles class.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var 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">-        public $project_access_meta_key;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public $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">         * Whether the current user can promote users.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -55,11 +55,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'screen'   => isset( $args['screen'] ) ? $args['screen'] : null,
</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">-                $this->user_role = $args['user_role'];
-               $this->project_access_meta_key = $args['project_access_meta_key'];
-               $this->projects = $args['projects'];
-               $this->project_tree = $args['project_tree'];
-               $this->user_can_promote = current_user_can( 'promote_users' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->user_roles       = $args['user_roles'];
+               $this->projects         = $args['projects'];
+               $this->project_tree     = $args['project_tree'];
+               $this->rosetta_roles    = $args['rosetta_roles'];
+               $this->user_can_promote = current_user_can( Rosetta_Roles::MANAGE_TRANSLATION_EDITORS_CAP );
</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">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -70,12 +70,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $per_page = $this->get_items_per_page( 'translation_editors_per_page', 10 );
</span><span class="cx" style="display: block; padding: 0 10px">                $paged =    $this->get_pagenum();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $role__in = $this->user_roles;
+               if ( isset( $_REQUEST['role'] ) ) {
+                       $role__in = $_REQUEST['role'];
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $args = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'number' => $per_page,
-                       'offset' => ( $paged - 1 ) * $per_page,
-                       'role'   => $this->user_role,
-                       'search' => $search,
-                       'fields' => 'all_with_meta'
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'number'   => $per_page,
+                       'offset'   => ( $paged - 1 ) * $per_page,
+                       'role__in' => $role__in,
+                       'search'   => $search,
+                       'fields'   => 'all_with_meta'
</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">                if ( '' !== $args['search'] ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -136,13 +141,112 @@
</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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Provides a list of roles and user count for that role for easy
+        * filtering of the table.
+        *
+        * @return array An array of HTML links, one for each view.
+        */
+       protected function get_views() {
+               $class = '';
+               $view_links = array();
+
+               $users_of_blog = count_users();
+
+               $count_translation_editors = isset( $users_of_blog['avail_roles'][ Rosetta_Roles::TRANSLATION_EDITOR_ROLE ] ) ? $users_of_blog['avail_roles'][ Rosetta_Roles::TRANSLATION_EDITOR_ROLE ] : 0 ;
+               $count_general_translation_editors = isset( $users_of_blog['avail_roles'][ Rosetta_Roles::GENERAL_TRANSLATION_EDITOR_ROLE ] ) ? $users_of_blog['avail_roles'][ Rosetta_Roles::GENERAL_TRANSLATION_EDITOR_ROLE ] : 0 ;
+               $total_translation_editors = $count_translation_editors + $count_general_translation_editors;
+
+               $all_inner_html = sprintf(
+                       _nx(
+                               'All <span class="count">(%s)</span>',
+                               'All <span class="count">(%s)</span>',
+                               $total_translation_editors,
+                               'translation editors'
+                       ),
+                       number_format_i18n( $total_translation_editors )
+               );
+
+               if ( ! isset( $_REQUEST['role'] ) ) {
+                       $class = 'current';
+               }
+
+               $view_links['all'] = $this->get_view_link( array(), $all_inner_html, $class );
+
+               if ( $count_translation_editors ) {
+                       $class = '';
+                       $translation_editors_inner_html = sprintf(
+                               _n(
+                                       'Translation Editor <span class="count">(%s)</span>',
+                                       'Translation Editor <span class="count">(%s)</span>',
+                                       $count_translation_editors
+                               ),
+                               number_format_i18n( $count_translation_editors )
+                       );
+
+                       if ( isset( $_REQUEST['role'] ) && Rosetta_Roles::TRANSLATION_EDITOR_ROLE === $_REQUEST['role'] ) {
+                               $class = 'current';
+                       }
+
+                       $view_links[ Rosetta_Roles::TRANSLATION_EDITOR_ROLE ] = $this->get_view_link( array( 'role' => Rosetta_Roles::TRANSLATION_EDITOR_ROLE ), $translation_editors_inner_html, $class );
+               }
+
+               if ( $count_translation_editors ) {
+                       $class = '';
+                       $general_translation_editors_inner_html = sprintf(
+                               _n(
+                                       'General Translation Editor <span class="count">(%s)</span>',
+                                       'General Translation Editor <span class="count">(%s)</span>',
+                                       $count_general_translation_editors
+                               ),
+                               number_format_i18n( $count_general_translation_editors )
+                       );
+
+                       if ( isset( $_REQUEST['role'] ) && Rosetta_Roles::GENERAL_TRANSLATION_EDITOR_ROLE === $_REQUEST['role'] ) {
+                               $class = 'current';
+                       }
+
+                       $view_links[ Rosetta_Roles::GENERAL_TRANSLATION_EDITOR_ROLE ] = $this->get_view_link( array( 'role' => Rosetta_Roles::GENERAL_TRANSLATION_EDITOR_ROLE ), $general_translation_editors_inner_html, $class );
+               }
+
+               return $view_links;
+       }
+
+       /**
+        * Helper to create view links with params.
+        *
+        * @param array  $args  URL parameters for the link.
+        * @param string $label Link text.
+        * @param string $class Optional. Class attribute. Default empty string.
+        * @return string The formatted link string.
+        */
+       protected function get_view_link( $args, $label, $class = '' ) {
+               $page_url = menu_page_url( 'translation-editors', false );
+               $url = add_query_arg( $args, $page_url );
+
+               $class_html = '';
+               if ( ! empty( $class ) ) {
+                        $class_html = sprintf(
+                               ' class="%s"',
+                               esc_attr( $class )
+                       );
+               }
+
+               return sprintf(
+                       '<a href="%s"%s>%s</a>',
+                       esc_url( $url ),
+                       $class_html,
+                       $label
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Return a list of bulk actions available on this table.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array Array of bulk actions.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function get_bulk_actions() {
</span><span class="cx" style="display: block; padding: 0 10px">                return array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'remove' => _x( 'Remove', 'translation editor', 'rosetta' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'remove-translation-editors' => _x( 'Remove', 'translation editor', 'rosetta' ),
</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"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -209,7 +313,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_User $user The current user.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function column_email( $user ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                echo "<a href='mailto:$user->user_email'>$user->user_email</a>";
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         echo "<a href='" . esc_url( "mailto:$user->user_email" ) . "'>$user->user_email</a>";
</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">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -218,14 +322,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_User $user The current user.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function column_projects( $user ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $project_access_list = get_user_meta( $user->ID, $this->project_access_meta_key, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $project_access_list = $this->rosetta_roles->get_users_projects( $user->ID );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( empty( $project_access_list ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        _e( 'No projects', 'rosetta' );
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</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">-                if ( in_array( 'all', $project_access_list ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( in_array( 'all', $project_access_list, true ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         _e( 'All projects', 'rosetta' );
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span></span></pre></div>
<a id="sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesjsrosettarolesjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js      2015-12-17 11:59:45 UTC (rev 2196)
+++ sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js        2015-12-17 12:32:48 UTC (rev 2197)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -88,7 +88,7 @@
</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">                initialize: function() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        this.set( 'checked', _.contains( projects.settings.accessList, parseInt( this.get( 'id' ), 10 ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 this.set( 'checked', _.contains( projects.settings.accessList, this.get( 'id' ) ) );
</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"> 
</span></span></pre></div>
<a id="sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesrosettarolesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php        2015-12-17 11:59:45 UTC (rev 2196)
+++ sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php  2015-12-17 12:32:48 UTC (rev 2197)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,9 +4,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Plugin URI: https://wordpress.org/
</span><span class="cx" style="display: block; padding: 0 10px">  * Description: WordPress interface for managing roles.
</span><span class="cx" style="display: block; padding: 0 10px">  * Author: ocean90
</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: 1.1
</ins><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">+if ( ! class_exists( 'GP_Locales' ) ) {
+       require_once GLOTPRESS_LOCALES_PATH;
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> class Rosetta_Roles {
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Endpoint for profiles.wordpress.org updates.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -14,76 +18,78 @@
</span><span class="cx" style="display: block; padding: 0 10px">        const PROFILES_HANDLER_URL = 'https://profiles.wordpress.org/wp-admin/admin-ajax.php';
</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">-         * Holds the role of a translation editor.
-        *
-        * @var string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Database table for translation editors.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public $translation_editor_role = 'translation_editor';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ const TRANSLATION_EDITORS_TABLE = 'translate_translation_editors';
</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">-         * Holds the meta key of the project access list.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * 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';
+
+       /**
+        * Capabaility to promote translation editor.
+        */
+       const MANAGE_TRANSLATION_EDITORS_CAP = 'manage_translation_editors';
+
+       /**
+        * Holds the GlotPress locale of current site.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var string
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var GP_Locale
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public $project_access_meta_key = 'translation_editor_project_access_list';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ private $gp_locale = null;
</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">         * Constructor.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function __construct() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                global $wpdb;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               $wpdb->wporg_translation_editors = self::TRANSLATION_EDITORS_TABLE;
</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">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Attaches hooks once plugins are loaded.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function plugins_loaded() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $locale = get_locale();
+               $gp_locale = GP_Locales::by_field( 'wp_locale', $locale );
+               if ( ! $gp_locale ) {
+                       return;
+               }
+
+               $this->gp_locale = $gp_locale;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 add_filter( 'editable_roles', array( $this, 'editable_roles' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_filter( 'manage_users_columns',  array( $this, 'add_roles_column' ) );
-               add_filter( 'manage_users_custom_column',  array( $this, 'display_user_roles' ), 10, 3 );
</del><span class="cx" style="display: block; padding: 0 10px">                 add_action( 'admin_init', array( $this, 'role_modifications' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'set_user_role', array( $this, 'restore_translation_editor_role' ), 10, 3 );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'gettext_with_context', array( $this, 'rename_user_roles' ), 10, 4 );
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'admin_menu', array( $this, 'register_translation_editors_page' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_filter( 'user_row_actions', array( $this, 'promote_user_to_translation_editor' ), 10, 2 );
</del><span class="cx" style="display: block; padding: 0 10px">                 add_filter( 'set-screen-option', array( $this, 'save_custom_screen_options' ), 10, 3 );
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Adds an action link to promote an user to a translation editor.
-        *
-        * @param array   $actions     An array of action links to be displayed.
-        * @param WP_User $user_object WP_User object for the currently-listed user.
-        * @return array $actions An array of action links to be displayed.
-        */
-       public function promote_user_to_translation_editor( $actions, $user ) {
-               if ( in_array( $this->translation_editor_role, $user->roles ) || ! current_user_can( 'promote_users' ) ) {
-                       return $actions;
-               }
-
-               $url = menu_page_url( 'translation-editors', false );
-               $url = add_query_arg( array(
-                       'action' => 'add-translation-editor',
-                       'user'   => $user->ID,
-               ), $url );
-               $url = wp_nonce_url( $url, 'add-translation-editor', '_nonce_add-translation-editor' );
-               $actions['translation-editor'] = sprintf(
-                       '<a href="%s">%s</a>',
-                       esc_url( $url ),
-                       __( 'Promote to Translation Editor', 'rosetta' )
-               );
-
-               return $actions;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         add_action( 'translation_editor_added', array( $this, 'update_wporg_profile_badge' ) );
+               add_action( 'translation_editor_removed', array( $this, 'update_wporg_profile_badge' ) );
</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">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Registers "Translation Editor" role and modifies editor role.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Registers "(General) Translation Editor" role and modifies editor role.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function role_modifications() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! get_role( $this->translation_editor_role ) ) {
-                       add_role( $this->translation_editor_role, __( 'Translation Editor', 'rosetta' ), array( 'read' => true, 'level_0' => true ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! get_role( self::TRANSLATION_EDITOR_ROLE ) ) {
+                       add_role( self::TRANSLATION_EDITOR_ROLE, __( 'Translation Editor', 'rosetta' ), array( 'read' => true, 'level_0' => true ) );
</ins><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">+                if ( ! get_role( self::GENERAL_TRANSLATION_EDITOR_ROLE ) ) {
+                       add_role( self::GENERAL_TRANSLATION_EDITOR_ROLE, __( 'General Translation Editor', 'rosetta' ), array( 'read' => true, 'level_0' => true, self::MANAGE_TRANSLATION_EDITORS_CAP => true ) );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $editor_role = get_role( 'editor' );
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $editor_role && ! $editor_role->has_cap( 'remove_users' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $editor_role->add_cap( 'edit_theme_options' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -91,28 +97,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $editor_role->add_cap( 'promote_users' );
</span><span class="cx" style="display: block; padding: 0 10px">                        $editor_role->add_cap( 'remove_users' );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               // Remove deprecated validator role.
-               $validator_role = get_role( 'validator' );
-               if ( $validator_role ) {
-                       remove_role( 'validator' );
-               }
</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">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Restores the "Translation Editor" role if an user is promoted.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Restores the "(General) Translation Editor" role if an user is promoted.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int    $user_id   The user ID.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $role      The new role.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array  $old_roles An array of the user's previous roles.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function restore_translation_editor_role( $user_id, $role, $old_roles ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! in_array( $this->translation_editor_role, $old_roles ) ) {
-                       return;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( self::GENERAL_TRANSLATION_EDITOR_ROLE !== $role && in_array( self::TRANSLATION_EDITOR_ROLE, $old_roles ) ) {
+                       $user = new WP_User( $user_id );
+                       $user->add_role( self::TRANSLATION_EDITOR_ROLE );
</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">-                $user = new WP_User( $user_id );
-               $user->add_role( $this->translation_editor_role );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( self::TRANSLATION_EDITOR_ROLE !== $role && in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $old_roles ) ) {
+                       $user = new WP_User( $user_id );
+                       $user->add_role( self::GENERAL_TRANSLATION_EDITOR_ROLE );
+               }
</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">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -125,7 +128,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array Filtered list of editable roles.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function editable_roles( $roles ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                unset( $roles[ $this->translation_editor_role ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         unset( $roles[ self::TRANSLATION_EDITOR_ROLE ], $roles[ self::GENERAL_TRANSLATION_EDITOR_ROLE ] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_super_admin() && ! is_main_site() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        unset( $roles['administrator'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -150,63 +153,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 'Translation Editor' === $text ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return __( 'Translation Editor', 'rosetta' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                } elseif ( 'General Translation Editor' === $text ) {
+                       return __( 'General Translation Editor', 'rosetta' );
</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">                return $translation;
</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">-         * Replaces the "Role" column with a "Roles" column.
-        *
-        * @param array $columns An array of column headers.
-        * @return array An array of column headers.
-        */
-       public function add_roles_column( $columns ) {
-               $posts = $columns['posts'];
-               unset( $columns['role'], $columns['posts'] );
-               reset( $columns );
-               $columns['roles'] = __( 'Roles', 'rosetta' );
-               $columns['posts'] = $posts;
-
-               return $columns;
-       }
-
-       /**
-        * Displays a comma separated list of user's roles.
-        *
-        * @param string $output      Custom column output.
-        * @param string $column_name Column name.
-        * @param int    $user_id     ID of the currently-listed user.
-        * @return string Comma separated list of user's roles.
-        */
-       public function display_user_roles( $output, $column_name, $user_id ) {
-               global $wp_roles;
-
-               if ( 'roles' == $column_name ) {
-                       $user_roles = array();
-                       $user = new WP_User( $user_id );
-                       foreach ( $user->roles as $role ) {
-                               $role_name = $wp_roles->role_names[ $role ];
-                               $role_name = translate_user_role( $role_name );
-                               $user_roles[] = $role_name;
-                       }
-
-                       return implode( ', ', $user_roles );
-               }
-
-               return $output;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Registers page for managing translation editors.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function register_translation_editors_page() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->translation_editors_page = add_users_page(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->translation_editors_page = add_menu_page(
</ins><span class="cx" style="display: block; padding: 0 10px">                         __( 'Translation Editors', 'rosetta' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        __( 'Translation Editors', 'rosetta' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'list_users',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 self::MANAGE_TRANSLATION_EDITORS_CAP,
</ins><span class="cx" style="display: block; padding: 0 10px">                         'translation-editors',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        array( $this, 'render_translation_editors_page' )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 array( $this, 'render_translation_editors_page' ),
+                       'dashicons-translation',
+                       71 // After Users
</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">                add_action( 'load-' . $this->translation_editors_page, array( $this, 'load_translation_editors_page' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -220,7 +185,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Enqueues scripts.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function enqueue_scripts() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery', 'wp-backbone' ), '3', true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery', 'wp-backbone' ), '4', true );
</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">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -328,7 +293,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                case 'add-translation-editor':
</span><span class="cx" style="display: block; padding: 0 10px">                                        check_admin_referer( 'add-translation-editor', '_nonce_add-translation-editor' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        if ( ! current_user_can( 'promote_users' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( ! current_user_can( self::MANAGE_TRANSLATION_EDITORS_CAP ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 wp_redirect( $redirect );
</span><span class="cx" style="display: block; padding: 0 10px">                                                exit;
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -359,32 +324,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                $user_details = get_user_by( 'id', $user_details->ID );
</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">-                                        if ( in_array( $this->translation_editor_role, $user_details->roles ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( in_array( self::TRANSLATION_EDITOR_ROLE, $user_details->roles ) || in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user_details->roles ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 wp_redirect( add_query_arg( array( 'error' => 'user-exists' ), $redirect ) );
</span><span class="cx" style="display: block; padding: 0 10px">                                                exit;
</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">-                                        $user_details->add_role( $this->translation_editor_role );
-                                       $this->notify_translation_editor_update( $user_details->ID, 'add' );
-
-                                       $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;
-
</del><span class="cx" style="display: block; padding: 0 10px">                                         $projects = empty( $_REQUEST['projects'] ) ? '' : $_REQUEST['projects'];
</span><span class="cx" style="display: block; padding: 0 10px">                                        if ( 'custom' === $projects ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                update_user_meta( $user_details->ID, $meta_key, array() );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         $this->update_translation_editor( $user_details );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 $redirect = add_query_arg( 'user_id', $user_details->ID, $redirect );
</span><span class="cx" style="display: block; padding: 0 10px">                                                wp_redirect( add_query_arg( array( 'update' => 'user-added-custom-projects' ), $redirect ) );
</span><span class="cx" style="display: block; padding: 0 10px">                                                exit;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        } else {
+                                               $this->update_translation_editor( $user_details, array( 'all' ) );
+
+                                               wp_redirect( add_query_arg( array( 'update' => 'user-added' ), $redirect ) );
+                                               exit;
</ins><span class="cx" style="display: block; padding: 0 10px">                                         }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-                                       update_user_meta( $user_details->ID, $meta_key, array( 'all' ) );
-
-                                       wp_redirect( add_query_arg( array( 'update' => 'user-added' ), $redirect ) );
-                                       exit;
</del><span class="cx" style="display: block; padding: 0 10px">                                 case 'remove-translation-editors':
</span><span class="cx" style="display: block; padding: 0 10px">                                        check_admin_referer( 'bulk-translation-editors' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        if ( ! current_user_can( 'promote_users' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( ! current_user_can( self::MANAGE_TRANSLATION_EDITORS_CAP ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 wp_redirect( $redirect );
</span><span class="cx" style="display: block; padding: 0 10px">                                                exit;
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -395,13 +356,9 @@
</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">                                        $count = 0;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;
</del><span class="cx" style="display: block; padding: 0 10px">                                         $user_ids = array_map( 'intval', (array) $_REQUEST['translation-editors'] );
</span><span class="cx" style="display: block; padding: 0 10px">                                        foreach ( $user_ids as $user_id ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                $user = get_user_by( 'id', $user_id );
-                                               $user->remove_role( $this->translation_editor_role );
-                                               delete_user_meta( $user_id, $meta_key );
-                                               $this->notify_translation_editor_update( $user_id, 'remove' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         $this->remove_translation_editor( $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 $count++;
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -410,7 +367,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                case 'remove-translation-editor':
</span><span class="cx" style="display: block; padding: 0 10px">                                        check_admin_referer( 'remove-translation-editor' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        if ( ! current_user_can( 'promote_users' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( ! current_user_can( self::MANAGE_TRANSLATION_EDITORS_CAP ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                                 wp_redirect( $redirect );
</span><span class="cx" style="display: block; padding: 0 10px">                                                exit;
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -421,11 +378,7 @@
</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">                                        $user_id = (int) $_REQUEST['translation-editor'];
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $user = get_user_by( 'id', $user_id );
-                                       $user->remove_role( $this->translation_editor_role );
-                                       $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;
-                                       delete_user_meta( $user_id, $meta_key );
-                                       $this->notify_translation_editor_update( $user_id, 'remove' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $this->remove_translation_editor( $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                        wp_redirect( add_query_arg( array( 'update' => 'user-removed' ), $redirect ) );
</span><span class="cx" style="display: block; padding: 0 10px">                                        exit;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -443,7 +396,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $redirect = menu_page_url( 'translation-editors', false );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! current_user_can( 'promote_users' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! current_user_can( self::MANAGE_TRANSLATION_EDITORS_CAP ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         wp_redirect( $redirect );
</span><span class="cx" style="display: block; padding: 0 10px">                        exit;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -460,7 +413,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        exit;
</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">-                if ( ! user_can( $user_details, $this->translation_editor_role ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! in_array( self::TRANSLATION_EDITOR_ROLE, $user_details->roles ) && ! in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user_details->roles ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         wp_redirect( add_query_arg( array( 'error' => 'user-cannot' ), $redirect ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        exit;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -477,22 +430,181 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $all_projects = array_map( 'intval', $all_projects );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                $projects = (array) $_REQUEST['projects'];
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( in_array( 'all', $projects ) ) {
-                                       $projects = array( 'all' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( in_array( 'all', $projects, true ) ) {
+                                       $this->update_translation_editor( $user_details, array( 'all' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 } else {
</span><span class="cx" style="display: block; padding: 0 10px">                                        $projects = array_map( 'intval', $projects );
</span><span class="cx" style="display: block; padding: 0 10px">                                        $projects = array_values( array_intersect( $all_projects, $projects ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        $this->update_translation_editor( $user_details, $projects );
</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">-                                $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;
-                               update_user_meta( $user_details->ID, $meta_key, $projects );
-
</del><span class="cx" style="display: block; padding: 0 10px">                                 wp_redirect( add_query_arg( array( 'update' => 'user-updated' ), $redirect ) );
</span><span class="cx" style="display: block; padding: 0 10px">                                exit;
</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><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Removes a translation editor.
+        *
+        * @param int|WP_User $user User ID or object.
+        * @return bool True on success, false on failure.
+        */
+       private function remove_translation_editor( $user ) {
+               global $wpdb;
+
+               if ( ! $user instanceof WP_User ) {
+                       $user = get_user_by( 'id', $user );
+               }
+
+               if ( ! $user->exists() ) {
+                       return false;
+               }
+
+               if ( in_array( self::TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                       $user->remove_role( self::TRANSLATION_EDITOR_ROLE );
+               }
+
+               if ( in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                       $user->remove_role( self::GENERAL_TRANSLATION_EDITOR_ROLE );
+               }
+
+               $wpdb->query( $wpdb->prepare( "
+                       DELETE FROM {$wpdb->wporg_translation_editors}
+                       WHERE `user_id` = %d AND `locale` = %s
+               ", $user->ID, $this->gp_locale->slug ) );
+
+               do_action( 'translation_editor_removed', $user->ID );
+
+               return true;
+       }
+
+       /**
+        * Creates or updates a translation editor.
+        *
+        * @param int|WP_User $user     User ID or object.
+        * @param array       $projects The projects to which the user should get assigned.
+        *                              Pass `array( 'all' )` to make their a general translation
+        *                              editor.
+        * @return bool True on success, false on failure.
+        */
+       private function update_translation_editor( $user, $projects = array() ) {
+               global $wpdb;
+
+               if ( ! $user instanceof WP_User ) {
+                       $user = get_user_by( 'id', $user );
+               }
+
+               if ( ! $user->exists() ) {
+                       return false;
+               }
+
+               $update = in_array( self::TRANSLATION_EDITOR_ROLE, $user->roles ) || in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user->roles );
+
+               $projects = array_map( 'strval', $projects );
+               $current_projects = $this->get_users_projects( $user->ID );
+               $projects_to_add = $projects_to_remove = array();
+
+               if ( in_array( 'all', $projects, true ) ) {
+                       $projects_to_remove = array_diff( $current_projects, array( '0' ) );
+                       if ( ! in_array( '0', $current_projects, true ) ) {
+                               $projects_to_add[] = '0';
+                       }
+
+                       if ( in_array( self::TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                               $user->remove_role( self::TRANSLATION_EDITOR_ROLE );
+                       }
+
+                       if ( ! in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                               $user->add_role( self::GENERAL_TRANSLATION_EDITOR_ROLE );
+                       }
+               } else {
+                       $projects_to_remove = array_diff( $current_projects, $projects );
+                       $projects_to_add = array_diff( $projects, $current_projects );
+
+                       if ( in_array( self::GENERAL_TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                               $user->remove_role( self::GENERAL_TRANSLATION_EDITOR_ROLE );
+                       }
+
+                       if ( ! in_array( self::TRANSLATION_EDITOR_ROLE, $user->roles ) ) {
+                               $user->add_role( self::TRANSLATION_EDITOR_ROLE );
+                       }
+               }
+
+               $values_to_add = array();
+               foreach ( $projects_to_add as $project_id ) {
+                       $values_to_add[] = $wpdb->prepare( '(%d, %d, %s, %s)',
+                               $user->ID,
+                               $project_id,
+                               $this->gp_locale->slug,
+                               'default'
+                       );
+               }
+
+               if ( $values_to_add ) {
+                       $wpdb->query( "
+                               INSERT INTO {$wpdb->wporg_translation_editors}
+                               (`user_id`,`project_id`, `locale`, `locale_slug`)
+                               VALUES " . implode( ', ', $values_to_add ) . "
+                       " );
+               }
+
+               $values_to_remove = array_map( 'intval', $projects_to_remove );
+               if ( $values_to_remove ) {
+                       $wpdb->query( $wpdb->prepare( "
+                               DELETE FROM {$wpdb->wporg_translation_editors}
+                               WHERE `user_id` = %d AND `locale` = %s
+                               AND project_id IN (" . implode( ', ', $values_to_remove ) . ")
+                       ", $user->ID, $this->gp_locale->slug ) );
+               }
+
+               if ( $update ) {
+                       do_action( 'translation_editor_updated', $user->ID );
+               } else {
+                       do_action( 'translation_editor_added', $user->ID );
+               }
+
+               return true;
+       }
+
+       /**
+        * Handles the update of the translation editor badges on
+        * profiles.wordpress.org.
+        *
+        * @param int $user_id User ID.
+        */
+       public function update_wporg_profile_badge( $user_id ) {
+               $action = 'translation_editor_added' === current_filter() ? 'add' : 'remove';
+
+               $this->notify_profiles_wporg_translation_editor_update( $user_id, $action );
+       }
+
+       /**
+        * Retrieves the assigned projects of a user
+        *
+        * @param int $user_id User ID.
+        * @return array List of project IDs.
+        */
+       public function get_users_projects( $user_id ) {
+               global $wpdb;
+
+               $projects = $wpdb->get_col( $wpdb->prepare( "
+                       SELECT project_id FROM
+                       {$wpdb->wporg_translation_editors}
+                       WHERE user_id = %d AND locale = %s
+               ", $user_id, $this->gp_locale->slug ) );
+
+               if ( ! $projects ) {
+                       return array();
+               }
+
+               if ( in_array( '0', $projects, true ) ) {
+                       return array( 'all' );
+               }
+
+               return $projects;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Renders the overview page.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        private function render_translation_editors() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -523,11 +635,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">                $project_tree = array_values( $project_tree );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;
-               $project_access_list = get_user_meta( $user_id, $meta_key, true );
-               if ( ! $project_access_list ) {
-                       $project_access_list = array();
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $project_access_list = $this->get_users_projects( $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                wp_localize_script( 'rosetta-roles', '_rosettaProjectsSettings', array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'l10n' => array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -607,10 +715,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $project_tree = $this->get_project_tree( $projects, 0, 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $args = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'user_role'               => $this->translation_editor_role,
-                       'projects'                => $projects,
-                       'project_tree'            => $project_tree,
-                       'project_access_meta_key' => $wpdb->get_blog_prefix() . $this->project_access_meta_key,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'user_roles'    => array( self::TRANSLATION_EDITOR_ROLE, self::GENERAL_TRANSLATION_EDITOR_ROLE ),
+                       'projects'      => $projects,
+                       'project_tree'  => $project_tree,
+                       'rosetta_roles' => $this,
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                $list_table = new Rosetta_Translation_Editors_List_Table( $args );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -623,7 +731,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param  int    $user_id User ID.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param  string $action  Can be 'add' or 'remove'.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        private function notify_translation_editor_update( $user_id, $action ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ private function notify_profiles_wporg_translation_editor_update( $user_id, $action ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'body' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'action'      => 'wporg_handle_association',
</span></span></pre></div>
<a id="sitestrunkglobalwordpressorgpublic_htmlwpcontentmupluginsrolesviewstranslationeditorsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/translation-editors.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/translation-editors.php    2015-12-17 11:59:45 UTC (rev 2196)
+++ sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/translation-editors.php      2015-12-17 12:32:48 UTC (rev 2197)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -16,11 +16,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                <?php $list_table->search_box( __( 'Search Translation Editors', 'rosetta' ), 'rosetta' ); ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </form>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        <?php $list_table->views(); ?>
+
</ins><span class="cx" style="display: block; padding: 0 10px">         <form method="post">
</span><span class="cx" style="display: block; padding: 0 10px">                <?php $list_table->display(); ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </form>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <?php if ( current_user_can( 'promote_users' ) ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <?php if ( current_user_can( Rosetta_Roles::MANAGE_TRANSLATION_EDITORS_CAP ) ) : ?>
</ins><span class="cx" style="display: block; padding: 0 10px">                 <h3><?php _e( 'Add Translation Editor', 'rosetta' ); ?></h3>
</span><span class="cx" style="display: block; padding: 0 10px">                <p><?php _e( 'Enter the email address or username of an existing user on wordpress.org.', 'rosetta' ); ?></p>
</span><span class="cx" style="display: block; padding: 0 10px">                <form action="" method="post">
</span></span></pre>
</div>
</div>

</body>
</html>