WordPress.org

Making WordPress.org

Ticket #2000: 2000.diff

File 2000.diff, 23.6 KB (added by akirk, 5 years ago)
  • new file global.wordpress.org/public_html/wp-content/mu-plugins/roles/cross-locale-pte.php

    diff --git global.wordpress.org/public_html/wp-content/mu-plugins/roles/cross-locale-pte.php global.wordpress.org/public_html/wp-content/mu-plugins/roles/cross-locale-pte.php
    new file mode 100644
    index 0000000..a36b27b
    - +  
     1<?php
     2/**
     3 * Plugin Name: Cross-Locale PTE
     4 * Plugin URI: https://meta.trac.wordpress.org/ticket/2000
     5 * Description: Implements a user that can approve and import translations in all
     6 * translation-sets of a project but cannot overwrite current translations by others.
     7 */
     8class Cross_Locale_PTE {
     9
     10        /**
     11         * The special locale name.
     12         */
     13        const ALL_LOCALES = 'all-locales';
     14
     15        /**
     16         * The capability the user needs to have to manage Cross-Locale PTEs.
     17         */
     18        const MANAGE_CROSS_LOCALE_PTES_CAP = 'manage_network_users';
     19
     20        /**
     21         * Cache group.
     22         *
     23         * @var string
     24         */
     25        public static $cache_group = 'wporg-translate';
     26
     27        /**
     28         * The admin page hook suffix.
     29         *
     30         * @var string
     31         */
     32        private static $admin_page;
     33
     34        /**
     35         * The user that is being administered.
     36         *
     37         * @var WP_User
     38         */
     39        private static $user;
     40
     41        /**
     42         * Init Admin hooks.
     43         */
     44        public static function init_admin() {
     45                add_action( 'admin_menu', array( __CLASS__, 'register_admin_page' ) );
     46        }
     47
     48        /**
     49         * Register the Cross-Locale PTE Admin page in wp-admin.
     50         */
     51        public static function register_admin_page() {
     52                self::$admin_page = add_menu_page(
     53                        __( 'Cross-Locale PTE', 'rosetta' ),
     54                        __( 'Cross-Locale PTE', 'rosetta' ),
     55                        'manage_network_users',
     56                        'cross-locale-pte',
     57                        array( __CLASS__, 'render_admin_page' ),
     58                        'dashicons-translation',
     59                        71 // After Users.
     60                );
     61
     62                add_action( 'load-' . self::$admin_page, array( __CLASS__, 'handle_admin_post' ) );
     63                add_action( 'admin_print_scripts-' . self::$admin_page, array( 'Rosetta_Roles', 'enqueue_scripts' ) );
     64                add_action( 'admin_footer-' . self::$admin_page, array( 'Rosetta_Roles', 'print_js_templates' ) );
     65                add_action( 'admin_print_styles-' . self::$admin_page, array( 'Rosetta_Roles', 'enqueue_styles' ) );
     66        }
     67
     68        /**
     69         * Handle POST requests for the admin page.
     70         */
     71        public static function handle_admin_post() {
     72                $redirect = menu_page_url( 'cross-locale-pte', false );
     73
     74                if ( ! empty( $_REQUEST['user'] ) ) {
     75                        check_admin_referer( 'cross-locale-pte', '_nonce_cross-locale-pte' );
     76
     77                        self::$user = get_user_by( 'login', $_REQUEST['user'] );
     78                        if ( ! self::$user ) {
     79                                self::$user = get_user_by( 'email', $_REQUEST['user'] );
     80                        }
     81
     82                        if ( self::$user ) {
     83                                wp_redirect( add_query_arg( array( 'user_id' => self::$user->ID ), $redirect ) );
     84                        } else {
     85                                wp_redirect( add_query_arg( array( 'error' => 'no-user-found' ), $redirect ) );
     86                        }
     87                        exit;
     88                }
     89
     90                if ( ! empty( $_REQUEST['user_id'] ) ) {
     91                        self::$user = get_user_by( 'id', $_REQUEST['user_id'] );
     92                        if ( ! self::$user ) {
     93                                wp_redirect( add_query_arg( array( 'error' => 'no-user-found' ), $redirect ) );
     94                                exit;
     95                        }
     96                }
     97
     98                if ( ! empty( $_REQUEST['action'] ) ) {
     99                        switch ( $_REQUEST['action'] ) {
     100                                case 'update-cross-locale-pte':
     101                                        check_admin_referer( 'update-cross-locale-pte_' . self::$user->ID );
     102                                        return self::update_cross_locale_pte();
     103                        }
     104
     105                        return self::render_edit_page();
     106                }
     107        }
     108
     109        /**
     110         * Render the Cross-Locale PTE overview page in the admin.
     111         */
     112        public static function render_admin_page() {
     113                if ( ! empty( $_REQUEST['user_id'] ) ) {
     114                        return self::render_edit_page( $_REQUEST['user_id'] );
     115                }
     116
     117                $feedback_message = '';
     118                $cross_locale_pte_users = self::get_all_users();
     119                require __DIR__ . '/views/cross-locale-pte.php';
     120        }
     121
     122        /**
     123         * Update the projects for the Cross-Locale PTE.
     124         */
     125        public static function update_cross_locale_pte() {
     126                global $wpdb;
     127
     128                $projects = array_map( 'strval', explode( ',', $_REQUEST['projects'] ) );
     129                $current_projects = self::get_users_projects( self::$user->ID );
     130
     131                $projects_to_remove = array_diff( $current_projects, $projects );
     132                $projects_to_add = array_diff( $projects, $current_projects );
     133
     134                $values_to_add = array();
     135                foreach ( $projects_to_add as $project_id ) {
     136                        $values_to_add[] = $wpdb->prepare( '(%d, %d, %s)',
     137                                self::$user->ID,
     138                                $project_id,
     139                                self::ALL_LOCALES
     140                        );
     141                }
     142
     143                if ( $values_to_add ) {
     144                        $wpdb->query( "
     145                                INSERT INTO {$wpdb->wporg_translation_editors}
     146                                ( `user_id`,`project_id`, `locale` )
     147                                VALUES " . implode( ', ', $values_to_add )
     148                        );
     149                }
     150
     151                $values_to_remove = array_map( 'intval', $projects_to_remove );
     152                if ( $values_to_remove ) {
     153                        $wpdb->query( $wpdb->prepare( "
     154                                DELETE FROM {$wpdb->wporg_translation_editors}
     155                                WHERE `user_id` = %d AND `locale` = %s
     156                                AND project_id IN (" . implode( ', ', $values_to_remove ) . ')',
     157                        self::$user->ID, self::ALL_LOCALES ) );
     158                }
     159
     160                wp_redirect( add_query_arg( array( 'user_id' => self::$user->ID ), menu_page_url( 'cross-locale-pte', false ) ) );
     161                exit;
     162        }
     163
     164        /**
     165         * Render the page to edit a single Cross-Locale PTE.
     166         */
     167        public static function render_edit_page() {
     168                $user = self::$user;
     169                $project_access_list = self::get_users_projects( $user->ID );
     170                $last_updated = get_blog_option( WPORG_TRANSLATE_BLOGID, 'wporg_projects_last_updated' );
     171
     172                wp_localize_script( 'rosetta-roles', '_rosettaProjectsSettings', array(
     173                        'l10n' => array(
     174                                'searchPlaceholder' => esc_attr__( 'Search...', 'rosetta' ),
     175                        ),
     176                        'lastUpdated' => $last_updated,
     177                        'accessList' => $project_access_list,
     178                ) );
     179
     180                $feedback_message = '';
     181                require __DIR__ . '/views/edit-cross-locale-pte.php';
     182        }
     183
     184        /**
     185         * Retrieves the projects for which a user has cross-locale PTE permissions.
     186         *
     187         * @param int $user_id User ID.
     188         * @return array List of project IDs.
     189         */
     190        public static function get_users_projects( $user_id ) {
     191                global $wpdb;
     192
     193                $projects = $wpdb->get_col( $wpdb->prepare( "
     194                        SELECT project_id FROM
     195                        {$wpdb->wporg_translation_editors}
     196                        WHERE user_id = %d AND locale = %s
     197                ", $user_id, self::ALL_LOCALES ) );
     198
     199                return $projects;
     200        }
     201        /**
     202         * Retrieves the projects for which a user has cross-locale PTE permissions.
     203         *
     204         * @return array List of User IDs.
     205         */
     206        public static function get_all_users() {
     207                global $wpdb;
     208
     209                $rows = $wpdb->get_results( $wpdb->prepare( "
     210                        SELECT te.user_id, te.project_id, p.name AS project_name FROM
     211                        {$wpdb->wporg_translation_editors} te
     212                        JOIN translate_projects p ON te.project_id = p.id
     213                        WHERE te.locale = %s
     214                ", self::ALL_LOCALES ) );
     215
     216                $user_ids = array();
     217                foreach ( $rows as $row ) {
     218                        if ( ! isset( $user_ids[ $row->user_id ] ) ) {
     219                                $user = get_user_by( 'id', $row->user_id );
     220                                if ( ! $user ) {
     221                                        continue;
     222                                }
     223                                $row->user_login = $user->user_login;
     224                                $row->email = $user->email;
     225                                $row->display_name = $user->display_name;
     226                                $row->projects = array( $row->project_id => $row->project_name );
     227                                $user_ids[ $row->user_id ] = $row;
     228                        } else {
     229                                $user_ids[ $row->user_id ]->projects[ $row->project_id ] = $row->project_name;
     230                        }
     231                }
     232
     233                return $user_ids;
     234        }
     235
     236        /**
     237         * Check for the Cross-Locale PTE permission for the project.
     238         *
     239         * @param int $user_id    The User ID.
     240         * @param int $project_id The Project ID.
     241         * @return string|bool The verdict.
     242         */
     243        public static function user_has_cross_locale_permission( $user_id, $project_id ) {
     244                static $cache = null;
     245
     246                if ( null === $cache ) {
     247                        $cache = array();
     248                }
     249
     250                $user_id = intval( $user_id );
     251                $project_id = intval( $project_id );
     252
     253                if ( isset( $cache[ $user_id ][ $project_id ] ) ) {
     254                        return $cache[ $user_id ][ $project_id ];
     255                }
     256
     257                if ( ! isset( $cache[ $user_id ] ) ) {
     258                        $cache[ $user_id ] = array();
     259                }
     260
     261                global $wpdb;
     262                $result = $wpdb->get_col( $wpdb->prepare( "
     263                        SELECT user_id FROM
     264                        {$wpdb->wporg_translation_editors}
     265                        WHERE user_id = %d AND project_id = %d AND locale = %s
     266                ", $user_id, $project_id, self::ALL_LOCALES ) );
     267
     268                if ( $result && intval( $result[0]->user_id ) === $user_id ) {
     269                        return $cache[ $user_id ][ $project_id ] = true;
     270                }
     271
     272                return $cache[ $user_id ][ $project_id ] = false;
     273        }
     274
     275        /**
     276         * Enforce not-overwriting current translation by others while importing.
     277         *
     278         * @param string         $status          The desired status.
     279         * @param GP_Translation $new_translation The new translation.
     280         * @param GP_Translation $old_translation The old translation.
     281         * @return string The new status.
     282         */
     283        public static function gp_translation_set_import_status( $status, $new_translation, $old_translation ) {
     284                if ( ! isset( $old_translation->translation_set_id ) ) {
     285                        return $status;
     286                }
     287
     288                if ( 'current' !== $old_translation->translation_status ) {
     289                        return $status;
     290                }
     291
     292                if ( GP::$permission->current_user_can( 'cross-pte', 'translation-set', $old_translation->translation_set_id ) ) {
     293                        // Set to waiting if a current translation exists by another user.
     294                        if ( intval( $old_translation->user_id ) !== intval( get_current_user_id() ) ) {
     295                                return 'waiting';
     296                        }
     297                }
     298                return $status;
     299        }
     300
     301        /**
     302         * The GlotPress filter for Cross-Locale PTE.
     303         *
     304         * A Cross-Locale PTE is defined through an entry in the permission table 'cross-pte' with the
     305         * object_id referring to a project id.
     306         * A user with this permission will have 'approve' rights for all translation-sets within this
     307         * project. Usually having approval rights for a translation-set also means that the user has
     308         * approval rights for all translations, but not a Cross-Locale PTE:
     309         * If a current translation exists by another user then overwriting (through UI or import) is not
     310         * possible.
     311         *
     312         * @param string|bool $verdict The verdict from an earlier filter.
     313         * @param array       $args    Arguments that describe the object to judge for.
     314         * @return string|bool The verdict for the object.
     315         */
     316        public static function gp_pre_can_user( $verdict, $args ) {
     317                if ( 'cross-pte' === $args['action'] ) {
     318                        $verdict = self::gp_pre_can_user_cross_pte( $verdict, $args );
     319
     320                        if ( is_bool( $verdict ) ) {
     321                                return $verdict;
     322                        }
     323                }
     324
     325                if ( 'approve' === $args['action'] ) {
     326                        if ( 'translation' === $args['object_type'] ) {
     327                                $verdict = self::gp_pre_can_user_approve_translation( $verdict, $args );
     328                        } elseif ( 'translation-set' === $args['object_type'] ) {
     329                                $verdict = self::gp_pre_can_user_approve_translation_set( $verdict, $args );
     330                        }
     331                }
     332
     333                return $verdict;
     334
     335        }
     336
     337        /**
     338         * A GlotPress sub-filter for the permission 'cross-lte'.
     339         *
     340         * @param string|bool $verdict The verdict from an earlier filter.
     341         * @param array       $args    Arguments that describe the object to judge for.
     342         * @return string|bool The verdict for the object.
     343         */
     344        public static function gp_pre_can_user_cross_pte( $verdict, $args ) {
     345                if ( GP::$permission->user_can( $args['user'], 'admin' ) ) {
     346                        // Admins shouldn't have this because it will end up restricting them.
     347                        return false;
     348                }
     349
     350                if ( 'translation-set' === $args['object_type'] ) {
     351                        if ( isset( $args['extra']['set']->id ) && intval( $args['extra']['set']->id ) === intval( $args['object_id'] ) ) {
     352                                $set = $args['extra']['set'];
     353                        } else {
     354                                $set = GP::$translation_set->get( $args['object_id'] );
     355                        }
     356
     357                        // Allow on all translation-sets within the project.
     358                        if ( $set ) {
     359                                return GP::$permission->user_can( $args['user'], 'cross-pte', 'project', $set->project_id );
     360                        }
     361                } elseif ( 'project' === $args['object_type'] ) {
     362                        return self::user_has_cross_locale_permission( $args['user'], $args['object_id'] );
     363                }
     364
     365                return $verdict;
     366        }
     367
     368        /**
     369         * A GlotPress sub-filter for the permission 'approve' and object 'translation'.
     370         *
     371         * @param string|bool $verdict The verdict from an earlier filter.
     372         * @param array       $args    Arguments that describe the object to judge for.
     373         * @return string|bool The verdict for the object.
     374         */
     375        public static function gp_pre_can_user_approve_translation( $verdict, $args ) {
     376                if ( isset( $args['extra']['translation']->translation_set_id ) && intval( $args['extra']['translation']->id ) === intval( $args['object_id'] ) ) {
     377                        $translation = $args['extra']['translation'];
     378                } else {
     379                        $translation = GP::$translation->get( $args['object_id'] );
     380                }
     381
     382                if ( ! $translation ) {
     383                        return $verdict;
     384                }
     385
     386                static $current_translation_by_user;
     387                $cache_key = $args['user']->ID . '_' . $translation->original_id;
     388
     389                if ( isset( $current_translation_by_user[ $cache_key ] ) ) {
     390                        return $current_translation_by_user[ $cache_key ];
     391                }
     392
     393                if ( GP::$permission->user_can( $args['user'], 'cross-pte', 'translation-set', $translation->translation_set_id ) ) {
     394                        $current_translation = GP::$translation->find_one( array( 'translation_set_id' => $translation->translation_set_id, 'original_id' => $translation->original_id, 'status' => 'current' ) );
     395                        if ( $current_translation && intval( $current_translation->user_id ) !== $args['user']->ID ) {
     396                                // Current translation was authored by someone else. Disallow setting to current.
     397                                return $current_translation_by_user[ $cache_key ] = false;
     398                        }
     399
     400                        // No current translation exists or it was translated by me: allow.
     401                        return $current_translation_by_user[ $cache_key ] = true;
     402                }
     403
     404                // Allows usage of the re-implementation below.
     405                if ( GP::$permission->user_can( $args['user'], 'approve', 'translation-set', $translation->translation_set_id ) ) {
     406                        return true;
     407                }
     408
     409                return $verdict;
     410        }
     411
     412        /**
     413         * A GlotPress sub-filter for the permission 'approve' and object 'translation-set'.
     414         *
     415         * @param string|bool $verdict The verdict from an earlier filter.
     416         * @param array       $args    Arguments that describe the object to judge for.
     417         * @return string|bool The verdict for the object.
     418         */
     419        public static function gp_pre_can_user_approve_translation_set( $verdict, $args ) {
     420                // Re-implementation of gp_route_translation_set_permissions_to_validator_permissions().
     421                if ( isset( $args['extra']['set']->id ) && intval( $args['extra']['set']->id ) === intval( $args['object_id'] ) ) {
     422                        $set = $args['extra']['set'];
     423                } else {
     424                        $set = GP::$translation_set->get( $args['object_id'] );
     425                }
     426
     427                if ( $set ) {
     428                        if ( GP::$permission->user_can( $args['user'], 'cross-pte', 'project', $set->project_id ) ) {
     429                                return true;
     430                        }
     431
     432                        return GP::$permission->user_can( $args['user'], 'approve', GP::$validator_permission->object_type, GP::$validator_permission->object_id( $set->project_id, $set->locale, $set->slug ) );
     433                }
     434
     435                return $verdict;
     436        }
     437}
  • global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js

    diff --git global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js
    index 7231623..9be2c8c 100644
     
    253253                        });
    254254
    255255                        $( '#project-loading' ).remove();
     256                        var firstActive = view.$el.find( 'li.active' );
     257                        if ( ! firstActive.length ) {
     258                                firstActive = view.$el.find( 'li input[type=radio]' );
     259                        }
     260                        if ( ! firstActive.length ) {
     261                                firstActive = view.$el.find( 'li:first-child' );
     262                        }
     263                        firstActive.closest( 'li' ).addClass( 'active' );
    256264
    257265                        this.views.ready();
    258266
  • global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php

    diff --git global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php
    index d15c603..f19d848 100644
    if ( ! class_exists( 'GP_Locales' ) ) { 
    1111        require_once GLOTPRESS_LOCALES_PATH;
    1212}
    1313
     14require __DIR__ . '/cross-locale-pte.php';
     15
    1416class Rosetta_Roles {
    1517        /**
    1618         * Endpoint for profiles.wordpress.org updates.
    class Rosetta_Roles { 
    7476                add_action( 'translation_editor_removed', array( $this, 'update_wporg_profile_badge' ) );
    7577
    7678                add_action( 'wp_ajax_rosetta-get-projects', array( $this, 'ajax_rosetta_get_projects' ) );
     79
     80                Cross_Locale_PTE::init_admin();
    7781        }
    7882
    7983        /**
    class Rosetta_Roles { 
    9296
    9397                add_action( 'load-' . $this->translation_editors_page, array( $this, 'load_translation_editors_page' ) );
    9498                add_action( 'load-' . $this->translation_editors_page, array( $this, 'register_screen_options' ) );
    95                 add_action( 'admin_print_scripts-' . $this->translation_editors_page, array( $this, 'enqueue_scripts' ) );
    96                 add_action( 'admin_footer-' . $this->translation_editors_page, array( $this, 'print_js_templates' ) );
    97                 add_action( 'admin_print_styles-' . $this->translation_editors_page, array( $this, 'enqueue_styles' ) );
     99                add_action( 'admin_print_scripts-' . $this->translation_editors_page, array( __CLASS__, 'enqueue_scripts' ) );
     100                add_action( 'admin_footer-' . $this->translation_editors_page, array( __CLASS__, 'print_js_templates' ) );
     101                add_action( 'admin_print_styles-' . $this->translation_editors_page, array( __CLASS__, 'enqueue_styles' ) );
    98102        }
    99103
    100104        /**
    101105         * Enqueues scripts.
    102106         */
    103         public function enqueue_scripts() {
     107        public static function enqueue_scripts() {
    104108                wp_enqueue_script( 'string_score', plugins_url( '/js/string_score.min.js', __FILE__ ), array(), '0.1.22', true );
    105                 wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery', 'wp-backbone', 'string_score' ), '9', true );
     109                wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery', 'wp-backbone', 'string_score' ), '10', true );
    106110        }
    107111
    108112        /**
    109113         * Enqueues styles.
    110114         */
    111         public function enqueue_styles() {
     115        public static function enqueue_styles() {
    112116                $suffix = is_rtl() ? '-rtl' : '';
    113117                wp_enqueue_style( 'rosetta-roles', plugins_url( "/css/rosetta-roles$suffix.css", __FILE__ ), array(), '4' );
    114118        }
    class Rosetta_Roles { 
    116120        /**
    117121         * Prints JavaScript templates.
    118122         */
    119         public function print_js_templates() {
     123        public static function print_js_templates() {
    120124                ?>
    121125                <script id="tmpl-project-checkbox" type="text/html">
    122126                        <# if ( ! data.checkedSubProjects ) {
  • new file global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/cross-locale-pte.php

    diff --git global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/cross-locale-pte.php global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/cross-locale-pte.php
    new file mode 100644
    index 0000000..231d18d
    - +  
     1<div class="wrap">
     2        <h2>
     3                <?php _e( 'Cross-Locale PTEs', 'rosetta' ); ?>
     4        </h2>
     5
     6        <?php echo $feedback_message; ?>
     7
     8        <p><?php _e( 'This is the list of our current Cross-Locale PTEs.', 'rosetta' ); ?></p>
     9        <table class="wp-list-table widefat fixed striped translation-editors">
     10                <thead>
     11                <tr>
     12                        <th scope="col" id="username" class="column-username column-primary">Username</th>
     13                        <th scope="col" id="name" class="column-name">Name</th>
     14                        <th scope="col" id="email" class="column-email">E-mail</th>
     15                        <th scope="col" id="projects" class="column-projects">Projects</th></tr>
     16                </thead>
     17
     18                <tbody id="the-list">
     19                <?php $url = menu_page_url( 'cross-locale-pte', false );  ?>
     20                <?php foreach ( $cross_locale_pte_users as $user_id => $user ) : ?>
     21                        <tr>
     22                                <td class="username column-username column-primary">
     23                                        <?php echo get_avatar( $user_id, 32 ); ?>
     24                                        <a href="<?php echo esc_url( add_query_arg( array( 'user_id' => $user_id ), $url ) ); ?>"><?php echo esc_html( $user->user_login ); ?></a>
     25                                </td>
     26                                <td class="name column-name">
     27                                        <?php echo esc_html( $user->display_name ); ?>
     28                                </td>
     29                                <td class="email column-email">
     30                                        <a href="mailto:<?php echo esc_attr( $user->email ); ?>"><?php echo esc_html( $user->email ); ?></a>
     31                                </td>
     32                                <td class="projects column-projects"><?php
     33                                        asort( $user->projects );
     34                                        echo implode( ', ', array_map( 'esc_html', $user->projects ) );
     35                                        ?>
     36                                </td>
     37                        </tr>
     38                <?php endforeach; ?>
     39                </tbody>
     40        </table>
     41
     42        <?php if ( current_user_can( Rosetta_Roles::MANAGE_TRANSLATION_EDITORS_CAP ) ) : ?>
     43                <h3><?php _e( 'Add Cross-Locale PTE', 'rosetta' ); ?></h3>
     44                <p><?php _e( 'Enter the email address or username of an existing user on wordpress.org.', 'rosetta' ); ?></p>
     45                <form action="" method="post">
     46                        <table class="form-table">
     47                                <tr>
     48                                        <th scope="row"><label for="user"><?php _e( 'E-mail or Username', 'rosetta' ); ?></label></th>
     49                                        <td><input type="text" class="regular-text" name="user" id="user"></td>
     50                                </tr>
     51                        </table>
     52                        <input type="hidden" name="action" value="cross-locale-pte">
     53                        <?php wp_nonce_field( 'cross-locale-pte', '_nonce_cross-locale-pte' ) ?>
     54                        <?php submit_button( __( 'Add Cross-Locale PTE', 'rosetta' ) ); ?>
     55                </form>
     56        <?php endif; ?>
     57</div>
  • new file global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/edit-cross-locale-pte.php

    diff --git global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/edit-cross-locale-pte.php global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/edit-cross-locale-pte.php
    new file mode 100644
    index 0000000..d672626
    - +  
     1<div class="wrap">
     2        <h2><?php _e( 'Edit Cross-Locale PTE', 'rosetta' ); ?></h2>
     3
     4        <?php echo $feedback_message; ?>
     5
     6        <p><?php
     7                printf(
     8                        /* translators: %s: username */
     9                        __( 'You are currently editing the user %s.', 'rosetta' ),
     10                        '<em>' . $user->user_login . '</em>'
     11                );
     12        ?></p>
     13
     14        <form method="post">
     15                <table class="form-table">
     16                        <tbody>
     17                                <tr>
     18                                        <th scope="row">
     19                                                <?php _e( 'Add cross-locale PTE access for:', 'rosetta' ); ?><br>
     20                                        </th>
     21                                        <td>
     22                                                <fieldset id="projects">
     23                                                        <legend class="screen-reader-text"><span><?php _e( 'Add cross-locale PTE access for:', 'rosetta' ); ?></span></legend>
     24
     25                                                        <ul id="projects-list" class="projects-list">
     26                                                                <li id="project-loading" class="loading">
     27                                                                        <?php _e( 'Loading&hellip;', 'rosetta' ); ?>
     28                                                                </li>
     29                                                        </ul>
     30                                                </fieldset>
     31                                                <p class="description"><?php _e( 'Each project includes sub projects and newly-added sub projects.', 'rosetta' ); ?></p>
     32                                        </td>
     33                                </tr>
     34                        </tbody>
     35                </table>
     36
     37                <input type="hidden" id="project-access-list" name="projects" value="<?php esc_attr( implode( ',', $project_access_list ) ); ?>">
     38                <input type="hidden" name="action" value="update-cross-locale-pte" />
     39                <input type="hidden" name="user_id" value="<?php echo esc_attr( $user->ID ); ?>" />
     40                <?php
     41                wp_nonce_field( 'update-cross-locale-pte_' . $user->ID );
     42                submit_button( _x( 'Update', 'translation editor', 'rosetta' ) );
     43                ?>
     44        </form>
     45</div>
  • wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php wordpress.org/public_html/wp-content/plugins/wporg-gp-rosetta-roles/inc/class-plugin.php
    index 3c990d0..1ed519e 100644
    class Plugin { 
    6767                add_filter( 'gp_pre_can_user', array( $this, 'pre_can_user' ), 9 , 2 );
    6868                add_action( 'gp_project_created', array( $this, 'project_created' ) );
    6969                add_action( 'gp_project_saved', array( $this, 'project_saved' ) );
     70                add_filter( 'gp_translation_set_import_status', array( 'Cross_Locale_PTE', 'gp_translation_set_import_status' ), 9, 3 );
    7071
    7172                if ( is_admin() ) {
    7273                        $users = new Admin\Translators();
    class Plugin { 
    9899                        return true;
    99100                }
    100101
     102                // Grant permissions to Cross-Locale PTEs
     103                $cross_locale_pte_verdict = Cross_Locale_PTE::gp_pre_can_user( $verdict, $args );
     104                if ( is_bool( $cross_locale_pte_verdict ) ) {
     105                        return $cross_locale_pte_verdict;
     106                }
     107
    101108                // No permissions for unknown object types.
    102109                if ( ! in_array( $args['object_type'], array( 'project|locale|set-slug', 'translation-set' ) ) ) {
    103110                        return false;