Changeset 1786
- Timestamp:
- 07/26/2015 04:42:45 PM (9 years ago)
- Location:
- sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles
- Files:
-
- 2 added
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/class-translation-editors-list-table.php
r1437 r1786 34 34 */ 35 35 public $projects; 36 37 /** 38 * Holds the list of a project tree. 39 * 40 * @var array 41 */ 42 public $project_tree; 36 43 37 44 /** … … 52 59 $this->project_access_meta_key = $args['project_access_meta_key']; 53 60 $this->projects = $args['projects']; 61 $this->project_tree = $args['project_tree']; 54 62 $this->user_can_promote = current_user_can( 'promote_users' ); 55 63 } … … 226 234 foreach ( $project_access_list as $project_id ) { 227 235 if ( $this->projects[ $project_id ] ) { 228 $projects[] = esc_html( $this->projects[ $project_id ]->name ); 236 $parent = $this->get_parent_project( $this->project_tree, $project_id ); 237 if ( $parent->id != $project_id ) { 238 $name = sprintf( 239 '%s → %s', 240 esc_html( $parent->name ), 241 esc_html( $this->projects[ $project_id ]->name ) 242 ); 243 } else { 244 $name = esc_html( $this->projects[ $project_id ]->name ); 245 } 246 $projects[] = $name; 229 247 } 230 248 } … … 232 250 echo implode( '<br>', $projects ); 233 251 } 252 253 /** 254 * Returns the parent project for a sub project. 255 * 256 * @param array $tree The project tree. 257 * @param int $child_id The project tree. 258 * @return object The parent project. 259 */ 260 private function get_parent_project( $tree, $child_id ) { 261 $parent = null; 262 foreach ( $tree as $project ) { 263 if ( $project->id == $child_id ) { 264 $parent = $project; 265 break; 266 } 267 268 if ( isset( $project->sub_projects ) ) { 269 $parent = $this->get_parent_project( $project->sub_projects, $child_id ); 270 if ( $parent ) { 271 $parent = $project; 272 break; 273 } 274 } 275 } 276 277 return $parent; 278 } 234 279 } -
sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/js/rosetta-roles.js
r1419 r1786 1 1 ( function( $ ) { 2 2 3 $(function() { 4 var $projects = $( 'input.project' ); 5 6 if ( $projects.length ) { 7 var $allProjects = $( '#project-all' ), 8 checked = []; 9 10 // Deselect "All" if a project is checked. 11 $projects.on( 'change', function() { 12 $allProjects.prop( 'checked', false ); 13 checked = []; 14 } ); 15 16 // (De)select projects if "All" is (de)selected. 17 $allProjects.on( 'change', function() { 18 if ( this.checked ) { 19 $projects.each( function( index, checkbox ) { 20 var $cb = $( checkbox ); 21 if ( $cb.prop( 'checked' ) ) { 22 checked.push( $cb.attr( 'id' ) ); 23 $cb.prop( 'checked', false ); 24 } 25 } ); 26 } else { 27 for ( i = 0; i < checked.length; i++ ) { 28 $( '#' + checked[ i ] ).prop( 'checked', true ); 29 } 30 checked = []; 3 var projects, l10n, $window = $(window); 4 5 projects = { model: {}, view: {}, controller: {} }; 6 7 projects.settings = window._rosettaProjectsSettings || {}; 8 l10n = projects.settings.l10n || {}; 9 delete projects.settings.l10n; 10 11 /** 12 * MODELS 13 */ 14 projects.model.Project = Backbone.Model.extend({ 15 defaults: { 16 id: 0, 17 name: '', 18 checked: false, 19 isActive: false, 20 checkedSubProjects: false 21 }, 22 23 initialize: function() { 24 var subProjects = this.get( 'sub_projects' ); 25 this.unset( 'sub_projects' ); 26 27 this.set( 'subProjects', new projects.model.subProjects( subProjects ) ); 28 this.set( 'checked', _.contains( projects.settings.accessList, parseInt( this.get( 'id' ), 10 ) ) ); 29 30 this.listenTo( this.get( 'subProjects' ), 'change:checked', this.updateChecked ); 31 32 this.checkForCheckedSubProjects(); 33 }, 34 35 updateChecked: function( model ) { 36 if ( model.get( 'checked' ) ) { 37 this.set( 'checked', false ); 38 } 39 40 this.checkForCheckedSubProjects(); 41 }, 42 43 checkForCheckedSubProjects: function() { 44 var checked = this.get( 'subProjects' ).findWhere({ 'checked': true }); 45 if ( checked ) { 46 this.set( 'checkedSubProjects', true ); 47 } else { 48 this.set( 'checkedSubProjects', false ); 49 } 50 } 51 }); 52 53 projects.model.Projects = Backbone.Collection.extend({ 54 model: projects.model.Project, 55 56 initialize: function() { 57 _.bindAll( this, 'disableActiveStates' ); 58 this.on( 'change:isActive', this.toggleActiveStates ); 59 $window.on( 'deactivate-all-projects.rosetta', this.disableActiveStates ); 60 }, 61 62 toggleActiveStates: function( model ) { 63 if ( ! model.get( 'isActive' ) ) { 64 return; 65 } 66 this.each( function( project ) { 67 if ( project.get( 'id' ) != model.get( 'id' ) ) { 68 project.set( 'isActive', false ); 31 69 } 32 } ); 33 34 // Deselect all checkboxes. 35 $( '#clear-all' ).on( 'click', function( event ) { 70 }); 71 72 $window.trigger( 'deactivate-other-projects.rosetta' ); 73 }, 74 75 disableActiveStates: function() { 76 this.each( function( project ) { 77 project.set( 'isActive', false ); 78 }); 79 } 80 }); 81 82 projects.model.subProject = Backbone.Model.extend({ 83 defaults: { 84 id: 0, 85 name: '', 86 checked: false, 87 isVisible: true 88 }, 89 90 initialize: function() { 91 this.set( 'checked', _.contains( projects.settings.accessList, parseInt( this.get( 'id' ), 10 ) ) ); 92 } 93 }); 94 95 projects.model.subProjects = Backbone.Collection.extend({ 96 model: projects.model.subProject, 97 98 // Search terms 99 terms: '', 100 101 initialize: function() { 102 this.on( 'uncheckall', this.uncheckall ); 103 }, 104 105 uncheckall: function() { 106 this.each( function( project ) { 107 project.set( 'checked', false ); 108 }); 109 }, 110 111 doSearch: function( value ) { 112 // Don't do anything if we've already done this search 113 if ( this.terms === value ) { 114 return; 115 } 116 117 this.terms = value; 118 119 if ( this.terms.length > 0 ) { 120 this.search( this.terms ); 121 } 122 123 if ( this.terms === '' ) { 124 this.each( function( project ) { 125 project.set( 'isVisible', true ); 126 }); 127 } 128 }, 129 130 // Performs a search within the collection 131 // @uses RegExp 132 search: function( term ) { 133 var match, name; 134 135 // Escape the term string for RegExp meta characters 136 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); 137 138 // Consider spaces as word delimiters and match the whole string 139 // so matching terms can be combined 140 term = term.replace( / /g, ')(?=.*' ); 141 match = new RegExp( '^(?=.*' + term + ').+', 'i' ); 142 143 // Find results 144 this.each( function( project ) { 145 name = project.get( 'name' ); 146 project.set( 'isVisible', match.test( name ) ); 147 }); 148 }, 149 }); 150 151 /** 152 * VIEWS 153 */ 154 projects.view.Frame = wp.Backbone.View.extend({ 155 el: '#projects-list', 156 157 render: function() { 158 var view = this; 159 wp.Backbone.View.prototype.render.apply( this, arguments ); 160 161 this.collection.each( function( project ) { 162 var projectView = new projects.view.Project({ 163 model: project 164 }); 165 166 projectView.render(); 167 view.$el.append( projectView.el ); 168 }); 169 170 this.views.ready(); 171 172 return this; 173 } 174 }); 175 176 projects.view.Checkbox = wp.Backbone.View.extend({ 177 className: 'project-checkbox', 178 template: wp.template( 'project-checkbox' ), 179 180 initialize: function() { 181 this.listenTo( this.model, 'change', this.render ); 182 }, 183 184 events: { 185 'click .input-checkbox': 'updateChecked', 186 'click .input-radio': 'setChecked' 187 }, 188 189 prepare: function() { 190 return _.pick( this.model.toJSON(), 'id', 'name', 'checked', 'checkedSubProjects' ); 191 }, 192 193 updateChecked: function() { 194 this.model.set( 'checked', this.$el.find( 'input' ).prop( 'checked' ) ); 195 }, 196 197 setChecked: function() { 198 this.model.set( 'checked', true ); 199 } 200 }); 201 202 projects.view.Project = wp.Backbone.View.extend({ 203 tagName: 'li', 204 205 events: { 206 'click': 'updateIsActive' 207 }, 208 209 initialize: function() { 210 this.listenTo( this.model, 'change:checked', this.propagateChange ); 211 this.listenTo( this.model, 'change:isActive', this.toggleActiveState ); 212 213 this.views.add( new projects.view.Checkbox({ 214 model: this.model 215 }) ); 216 217 this.views.add( new projects.view.SubProjects({ 218 model: this.model 219 }) ); 220 221 this.toggleActiveState(); 222 223 this.views.ready(); 224 }, 225 226 propagateChange: function() { 227 if ( this.model.get( 'checked' ) ) { 228 this.model.get( 'subProjects' ).trigger( 'uncheckall' ); 229 } 230 }, 231 232 toggleActiveState: function() { 233 if ( this.model.get( 'isActive' ) ) { 234 this.$el.addClass( 'active' ); 235 } else { 236 this.$el.removeClass( 'active' ); 237 } 238 }, 239 240 updateIsActive: function() { 241 this.model.set( 'isActive', true ); 242 }, 243 }); 244 245 projects.view.SubProjects = wp.Backbone.View.extend({ 246 tagName: 'div', 247 className: 'sub-projects-wrapper', 248 249 initialize: function() { 250 var collection = this.model.get( 'subProjects' ); 251 252 if ( collection.length > 5 ) { 253 this.views.add( new projects.view.Search({ 254 collection: collection 255 }) ); 256 } 257 258 this.views.add( new projects.view.SubProjectsList({ 259 collection: collection 260 }) ); 261 }, 262 }); 263 264 projects.view.SubProjectsList = wp.Backbone.View.extend({ 265 tagName: 'ul', 266 className: 'sub-projects-list', 267 268 initialize: function() { 269 var view = this; 270 this.collection.each( function( project ) { 271 view.views.add( new projects.view.SubProject({ 272 model: project 273 }) ); 274 }); 275 } 276 }); 277 278 projects.view.SubProject = wp.Backbone.View.extend({ 279 tagName: 'li', 280 281 initialize: function() { 282 this.views.add( new projects.view.Checkbox({ 283 model: this.model 284 }) ); 285 286 this.listenTo( this.model, 'change:isVisible', this.changeVisibility ); 287 }, 288 289 changeVisibility: function() { 290 if ( this.model.get( 'isVisible' ) ) { 291 this.$el.removeClass( 'hidden' ); 292 } else { 293 this.$el.addClass( 'hidden' ); 294 } 295 } 296 }); 297 298 projects.view.Search = wp.Backbone.View.extend({ 299 tagName: 'input', 300 className: 'sub-projects-search', 301 302 attributes: { 303 placeholder: l10n.searchPlaceholder, 304 type: 'search', 305 }, 306 307 events: { 308 'input': 'search', 309 'keyup': 'search', 310 'keydown': 'search', 311 }, 312 313 search: function( event ) { 314 // Prevent form submit 315 if ( event.type === 'keydown' && event.which === 13 ) { 36 316 event.preventDefault(); 37 38 checked = []; 39 $allProjects.prop( 'checked', false ); 40 $projects.prop( 'checked', false ); 41 } ); 42 } 317 } else if ( event.type === 'keydown' ) { 318 return; 319 } 320 321 // Clear on escape. 322 if ( event.type === 'keyup' && event.which === 27 ) { 323 event.target.value = ''; 324 } 325 326 this.doSearch( event ); 327 }, 328 329 doSearch: _.debounce( function( event ) { 330 this.collection.doSearch( event.target.value ); 331 }, 200 ) 332 }); 333 334 projects.init = function() { 335 projects.view.frame = new projects.view.Frame({ 336 collection: new projects.model.Projects( projects.settings.data ) 337 }).render(); 338 }; 339 340 $( projects.init ); 341 342 $( '#project-all' ).on( 'click', function() { 343 var $el = $( this ); 344 345 if ( $el.hasClass( 'active' ) ) { 346 return; 347 } 348 349 $el.addClass( 'active' ); 350 $window.trigger( 'deactivate-all-projects.rosetta' ); 351 }); 352 353 $window.on( 'deactivate-other-projects.rosetta', function() { 354 $( '#project-all' ).removeClass( 'active' ); 43 355 } ); 44 356 } )( jQuery ); -
sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/rosetta-roles.php
r1741 r1786 211 211 add_action( 'load-' . $this->translation_editors_page, array( $this, 'load_translation_editors_page' ) ); 212 212 add_action( 'admin_print_scripts-' . $this->translation_editors_page, array( $this, 'enqueue_scripts' ) ); 213 add_action( 'admin_footer-' . $this->translation_editors_page, array( $this, 'print_js_templates' ) ); 214 add_action( 'admin_print_styles-' . $this->translation_editors_page, array( $this, 'enqueue_styles' ) ); 213 215 } 214 216 … … 217 219 */ 218 220 public function enqueue_scripts() { 219 wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery' ), '1', true ); 221 wp_enqueue_script( 'rosetta-roles', plugins_url( '/js/rosetta-roles.js', __FILE__ ), array( 'jquery', 'wp-backbone' ), '2', true ); 222 } 223 224 /** 225 * Enqueues styles. 226 */ 227 public function enqueue_styles() { 228 wp_enqueue_style( 'rosetta-roles', plugins_url( '/css/rosetta-roles.css', __FILE__ ), array(), '2' ); 229 } 230 231 /** 232 * Prints JavaScript templates. 233 */ 234 public function print_js_templates() { 235 ?> 236 <script id="tmpl-project-checkbox" type="text/html"> 237 <# if ( ! data.checkedSubProjects ) { 238 #> 239 <label> 240 <input type="checkbox" class="input-checkbox" name="projects[]" value="{{data.id}}" 241 <# 242 if ( data.checked ) { 243 #> checked="checked"<# 244 } 245 #> 246 /> 247 {{data.name}} 248 </label> 249 <# } else { #> 250 <label> 251 <input type="radio" class="input-radio" checked="checked" /> {{data.name}} 252 </label> 253 <# } #> 254 </script> 255 <?php 220 256 } 221 257 … … 290 326 $this->notify_translation_editor_update( $user_details->ID, 'add' ); 291 327 328 $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key; 329 292 330 $projects = empty( $_REQUEST['projects'] ) ? '' : $_REQUEST['projects']; 293 331 if ( 'custom' === $projects ) { 332 update_user_meta( $user_details->ID, $meta_key, array() ); 294 333 $redirect = add_query_arg( 'user_id', $user_details->ID, $redirect ); 295 334 wp_redirect( add_query_arg( array( 'update' => 'user-added-custom-projects' ), $redirect ) ); … … 297 336 } 298 337 299 $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key;300 338 update_user_meta( $user_details->ID, $meta_key, array( 'all' ) ); 301 339 … … 393 431 $redirect = add_query_arg( 'user_id', $user_details->ID, $redirect ); 394 432 395 $all_projects = $this->get_translate_ top_level_projects();433 $all_projects = $this->get_translate_projects(); 396 434 $all_projects = wp_list_pluck( $all_projects, 'id' ); 397 435 $all_projects = array_map( 'intval', $all_projects ); … … 431 469 global $wpdb; 432 470 433 $projects = $this->get_translate_top_level_projects(); 471 $projects = $this->get_translate_projects(); 472 $project_tree = $this->get_project_tree( $projects, 0, 1 ); 473 474 // Sort the tree and remove array keys. 475 usort( $project_tree, array( $this, '_sort_name_callback' ) ); 476 foreach ( $project_tree as $key => $project ) { 477 if ( $project->sub_projects ) { 478 usort( $project->sub_projects, array( $this, '_sort_name_callback' ) ); 479 } 480 $project->sub_projects = array_values( $project->sub_projects ); 481 } 482 $project_tree = array_values( $project_tree ); 434 483 435 484 $meta_key = $wpdb->get_blog_prefix() . $this->project_access_meta_key; … … 438 487 $project_access_list = array(); 439 488 } 489 490 wp_localize_script( 'rosetta-roles', '_rosettaProjectsSettings', array( 491 'l10n' => array( 492 'searchPlaceholder' => esc_attr__( 'Search...', 'rosetta' ) 493 ), 494 'data' => $project_tree, 495 'accessList' => $project_access_list 496 ) ); 440 497 441 498 $feedback_message = $this->get_feedback_message(); … … 504 561 } 505 562 563 $projects = $this->get_translate_projects(); 564 $project_tree = $this->get_project_tree( $projects, 0, 1 ); 565 506 566 $args = array( 507 567 'user_role' => $this->translation_editor_role, 508 'projects' => $this->get_translate_top_level_projects(), 568 'projects' => $projects, 569 'project_tree' => $project_tree, 509 570 'project_access_meta_key' => $wpdb->get_blog_prefix() . $this->project_access_meta_key, 510 571 ); … … 535 596 536 597 /** 537 * Fetches all top levelprojects from translate.wordpress.org.598 * Fetches all projects from translate.wordpress.org. 538 599 * 539 600 * @return array List of projects. 540 601 */ 541 private function get_translate_ top_level_projects() {602 private function get_translate_projects() { 542 603 global $wpdb; 543 544 $cache = get_site_transient( 'translate-top-level-projects' ); 545 if ( false !== $cache) {546 return $ cache;604 static $projects = null; 605 606 if ( null !== $projects ) { 607 return $projects; 547 608 } 548 609 549 610 $_projects = $wpdb->get_results( " 550 SELECT id, name 611 SELECT id, name, parent_project_id 551 612 FROM translate_projects 552 WHERE parent_project_id IS NULL 553 ORDER BY name ASC 613 ORDER BY id ASC 554 614 " ); 555 615 … … 559 619 } 560 620 561 set_site_transient( 'translate-top-level-projects', $projects, DAY_IN_SECONDS );562 563 621 return $projects; 564 622 } 623 624 /** 625 * Transforms a flat array to a hierarchy tree. 626 * 627 * @param array $projects The projects. 628 * @param int $parent_id Optional. Parent ID. Default 0. 629 * @param int $max_depth Optional. Max depth to avoid endless recursion. Default 5. 630 * @return array The project tree. 631 */ 632 public function get_project_tree( $projects, $parent_id = 0, $max_depth = 5 ) { 633 if ( $max_depth < 0 ) { // Avoid an endless recursion. 634 return; 635 } 636 637 $tree = array(); 638 foreach ( $projects as $project ) { 639 if ( $project->parent_project_id == $parent_id ) { 640 $sub_projects = $this->get_project_tree( $projects, $project->id, $max_depth - 1 ); 641 if ( $sub_projects ) { 642 $project->sub_projects = $sub_projects; 643 } 644 645 $tree[ $project->id ] = $project; 646 } 647 } 648 return $tree; 649 } 650 651 private function _sort_name_callback( $a, $b ) { 652 return strnatcasecmp( $a->name, $b->name ); 653 } 565 654 } -
sites/trunk/global.wordpress.org/public_html/wp-content/mu-plugins/roles/views/edit-translation-editor.php
r1419 r1786 10 10 <th scope="row"> 11 11 <?php _e( 'Add editor access for:', 'rosetta' ); ?><br> 12 <small style="font-weight:normal"><a href="#clear-all" id="clear-all"><?php _ex( 'Clear All', 'Deselects all checkboxes', 'rosetta' ); ?></a></small>13 12 </th> 14 13 <td> 15 <fieldset >14 <fieldset id="projects"> 16 15 <legend class="screen-reader-text"><span><?php _e( 'Add editor access for:', 'rosetta' ); ?></span></legend> 17 <p> 18 <label for="project-all"> 19 <input name="projects[]" id="project-all" value="all" type="checkbox"<?php checked( in_array( 'all', $project_access_list ) ); ?>> <?php _e( 'All projects – If selected, translation editor will have validation permissions for all projects, including newly-added projects.', 'rosetta' ); ?> 20 </label> 21 </p> 22 <?php 23 foreach ( $projects as $project ) { 24 $project_id = esc_attr( $project->id ); 25 printf( 26 '<p><label for="project-%d"><input name="projects[]" id="project-%d" class="project" value="%d" type="checkbox"%s> %s</label></p>', 27 $project_id, 28 $project_id, 29 $project_id, 30 checked( in_array( $project->id, $project_access_list ), true, false ), 31 esc_html( $project->name ) 32 ); 33 } 34 ?> 35 <p class="description"><?php _e( 'Each project includes sub projects and newly-added sub projects.', 'rosetta' ); ?></p> 16 17 <ul id="projects-list" class="projects-list"> 18 <li id="project-all" class="active"> 19 <label> 20 <input name="projects[]"value="all" type="checkbox"<?php checked( in_array( 'all', $project_access_list ) ); ?>> <?php _e( 'All projects', 'rosetta' ); ?> 21 </label> 22 <div class="sub-projects-wrapper"> 23 <?php _e( 'The translation editor has validation permissions for all projects, including newly-added projects.', 'rosetta' ); ?> 24 </div> 25 </li> 26 </ul> 36 27 </fieldset> 28 <p class="description"><?php _e( 'Each project includes sub projects and newly-added sub projects.', 'rosetta' ); ?></p> 37 29 </td> 38 30 </tr>
Note: See TracChangeset
for help on using the changeset viewer.