Co-Authors Plus - Version 3.0

Version Description

(Nov. 12, 2012) = * Create guest author profiles for bylines you'd like to assign without creating WordPress user accounts. Guest authors can have all of the same fields as normal users including display name, biography, and avatars. * Support for non-Latin characters in usernames and guest author names * wp-cli subcommands for creating, assigning, and reassigning co-authors * For themes using core template tags like the_author() or the_author_posts_link(), you enable Co-Authors Plus support with a simple filter * New author terms are now prefixed with 'cap-' to avoid collisions with global scope * Bug fix: Apply query filters to only post_types registered with the taxonomy. Props Tom Ransom * Filter coauthors_posts_link_single() with 'coauthors_posts_link'. Also adds rel="author". Props Amit Sannad and Gabriel Koen * Filter for the context and priorities of the Co-Authors meta boxes. Props Tom Kapler * Renamed the post meta box for selecting authors so it applies to many post types. Props John Blackbourn

Download this release

Release Info

Developer danielbachhuber
Plugin Icon wp plugin Co-Authors Plus
Version 3.0
Comparing to
See all releases

Code changes from version 2.6.4 to 3.0

co-authors-plus.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Co-Authors Plus
4
  Plugin URI: http://wordpress.org/extend/plugins/co-authors-plus/
5
  Description: Allows multiple authors to be assigned to a post. This plugin is an extended version of the Co-Authors plugin developed by Weston Ruter.
6
- Version: 2.6.4
7
  Author: Mohammad Jangda, Daniel Bachhuber, Automattic
8
  Copyright: 2008-2012 Shared and distributed between Mohammad Jangda, Daniel Bachhuber, Weston Ruter
9
 
@@ -24,70 +24,83 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
 
25
  */
26
 
27
- define( 'COAUTHORS_PLUS_VERSION', '2.6.4' );
28
 
29
  define( 'COAUTHORS_PLUS_PATH', dirname( __FILE__ ) );
30
  define( 'COAUTHORS_PLUS_URL', plugin_dir_url( __FILE__ ) );
31
 
32
  require_once( dirname( __FILE__ ) . '/template-tags.php' );
33
 
 
 
 
 
 
34
  class coauthors_plus {
35
-
36
- // Name for the taxonomy we're using to store coauthors
 
37
  var $coauthor_taxonomy = 'author';
38
-
39
  var $coreauthors_meta_box_name = 'authordiv';
40
  var $coauthors_meta_box_name = 'coauthorsdiv';
41
-
 
42
  var $gravatar_size = 25;
43
-
44
  var $_pages_whitelist = array( 'post.php', 'post-new.php' );
45
-
 
 
 
 
 
 
46
  /**
47
  * __construct()
48
  */
49
  function __construct() {
50
 
51
- load_plugin_textdomain( 'co-authors-plus', null, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
 
 
52
 
53
  // Load admin_init function
54
  add_action( 'admin_init', array( $this,'admin_init' ) );
55
-
56
- // Register new taxonomy so that we can store all our authors
57
- register_taxonomy( $this->coauthor_taxonomy, 'post', array('hierarchical' => false, 'update_count_callback' => array( &$this, '_update_users_posts_count' ), 'label' => false, 'query_var' => false, 'rewrite' => false, 'sort' => true, 'show_ui' => false ) );
58
-
59
  // Modify SQL queries to include coauthors
60
- add_filter( 'posts_where', array( $this, 'posts_where_filter' ) );
61
- add_filter( 'posts_join', array( $this, 'posts_join_filter' ) );
62
- add_filter( 'posts_groupby', array( $this, 'posts_groupby_filter' ) );
63
-
64
  // Action to set users when a post is saved
65
  add_action( 'save_post', array( $this, 'coauthors_update_post' ), 10, 2 );
66
  // Filter to set the post_author field when wp_insert_post is called
67
  add_filter( 'wp_insert_post_data', array( $this, 'coauthors_set_post_author_field' ), 10, 2 );
68
-
69
  // Action to reassign posts when a user is deleted
70
  add_action( 'delete_user', array( $this, 'delete_user_action' ) );
71
-
72
  add_filter( 'get_usernumposts', array( $this, 'filter_count_user_posts' ), 10, 2 );
73
-
74
  // Action to set up author auto-suggest
75
  add_action( 'wp_ajax_coauthors_ajax_suggest', array( $this, 'ajax_suggest' ) );
76
-
77
  // Filter to allow coauthors to edit posts
78
- add_filter( 'user_has_cap', array( $this, 'add_coauthor_cap' ), 10, 3 );
79
 
80
  // Handle the custom author meta box
81
  add_action( 'add_meta_boxes', array( $this, 'add_coauthors_box' ) );
82
  add_action( 'add_meta_boxes', array( $this, 'remove_authors_box' ) );
83
-
84
- // Removes the author dropdown from the post quick edit
85
- add_action( 'load-edit.php', array( $this, 'remove_quick_edit_authors_box' ) );
86
 
87
  // Restricts WordPress from blowing away term order on bulk edit
88
- add_filter( 'wp_get_object_terms', array( &$this, 'filter_wp_get_object_terms' ), 10, 4 );
89
-
90
  // Fix for author info not properly displaying on author pages
 
91
  add_action( 'the_post', array( $this, 'fix_author_page' ) );
92
 
93
  // Support for Edit Flow's calendar and story budget
@@ -98,18 +111,68 @@ class coauthors_plus {
98
 
99
  function coauthors_plus() {
100
  $this->__construct();
101
- }
102
-
103
  /**
104
- * Initialize the plugin for the admin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  */
106
  function admin_init() {
107
  global $pagenow;
108
 
109
- // Hook into load to initialize custom columns
110
- if( $this->is_valid_page() ) {
111
- add_action( 'load-' . $pagenow, array( $this, 'admin_load_page' ) );
112
- }
113
 
114
  // Hooks to add additional coauthors to author column to Edit page
115
  add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns' ) );
@@ -119,101 +182,141 @@ class coauthors_plus {
119
 
120
  // Hooks to modify the published post number count on the Users WP List Table
121
  add_filter( 'manage_users_columns', array( $this, '_filter_manage_users_columns' ) );
122
- add_filter( 'manage_users_custom_column', array( &$this, '_filter_manage_users_custom_column' ), 10, 3 );
123
-
 
 
 
124
  }
125
-
126
- function admin_load_page() {
127
-
128
- // Add the main JS script and CSS file
129
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
130
-
131
- // Add necessary JS variables
132
- add_action( 'admin_print_scripts', array( $this, 'js_vars' ) );
133
 
 
 
 
 
 
 
 
 
 
134
  }
135
-
136
- /**
137
- * Checks to see if the post_type supports authors
 
 
 
 
138
  */
139
- function authors_supported( $post_type ) {
140
-
141
- if( ! function_exists( 'post_type_supports' ) && in_array( $post_type, array( 'post', 'page' ) ) )
142
- return true;
143
-
144
- // Hacky way to prevent issues on the Edit page
145
- if( isset( $this->_edit_page_post_type_supported ) && $this->_edit_page_post_type_supported )
146
- return true;
147
-
148
- if( post_type_supports( $post_type, 'author' ) )
149
- return true;
150
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  return false;
 
152
  }
153
-
154
  /**
155
- * Gets the current global post type if one is set
 
 
 
 
 
 
156
  */
157
- function get_current_post_type() {
158
- global $post, $typenow, $current_screen;
159
-
160
- // "Cache" it!
161
- if( isset( $this->_current_post_type ) )
162
- return $this->_current_post_type;
163
-
164
- if( $post && $post->post_type )
165
- $post_type = $post->post_type;
166
- elseif( $typenow )
167
- $post_type = $typenow;
168
- elseif( $current_screen && isset( $current_screen->post_type ) )
169
- $post_type = $current_screen->post_type;
170
- elseif( isset( $_REQUEST['post_type'] ) )
171
- $post_type = sanitize_key( $_REQUEST['post_type'] );
172
- else
173
- $post_type = '';
174
-
175
- if( $post_type )
176
- $this->_current_post_type = $post_type;
177
-
178
- return $post_type;
179
  }
180
-
181
  /**
182
  * Removes the standard WordPress Author box.
183
  * We don't need it because the Co-Authors one is way cooler.
184
  */
185
  function remove_authors_box() {
186
-
187
- $post_type = $this->get_current_post_type();
188
-
189
- if( $this->authors_supported( $post_type ) )
190
- remove_meta_box( $this->coreauthors_meta_box_name, $post_type, 'normal' );
191
  }
192
-
193
  /**
194
  * Adds a custom Authors box
195
  */
196
  function add_coauthors_box() {
197
-
198
- $post_type = $this->get_current_post_type();
199
-
200
- if( $this->authors_supported( $post_type ) && $this->current_user_can_set_authors() )
201
- add_meta_box($this->coauthors_meta_box_name, __('Post Authors', 'co-authors-plus'), array( &$this, 'coauthors_meta_box' ), $post_type, 'normal', 'high');
202
  }
203
-
204
  /**
205
  * Callback for adding the custom author box
206
  */
207
  function coauthors_meta_box( $post ) {
208
- global $post;
209
-
210
  $post_id = $post->ID;
211
-
212
- if( !$post_id || $post_id == 0 || !$post->post_author )
213
- $coauthors = array( wp_get_current_user() );
214
- else
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  $coauthors = get_coauthors();
216
-
 
217
  $count = 0;
218
  if( !empty( $coauthors ) ) :
219
  ?>
@@ -234,66 +337,76 @@ class coauthors_plus {
234
  <?php
235
  endforeach;
236
  ?>
237
- </ul>
238
  <div class="clear"></div>
239
  <p><?php _e( '<strong>Note:</strong> To edit post authors, please enable javascript or use a javascript-capable browser', 'co-authors-plus' ); ?></p>
240
  </div>
241
  <?php
242
  endif;
243
  ?>
244
-
245
  <div id="coauthors-edit" class="hide-if-no-js">
246
  <p><?php _e( 'Click on an author to change them. Drag to change their order. Click on <strong>Remove</strong> to remove them.', 'co-authors-plus' ); ?></p>
247
  </div>
248
-
249
  <?php wp_nonce_field( 'coauthors-edit', 'coauthors-nonce' ); ?>
250
-
251
  <?php
252
  }
253
-
254
  /**
255
  * Removes the author dropdown from the post quick edit
256
- * It's a bit hacky, but the only way I can figure out :(
257
  */
258
  function remove_quick_edit_authors_box() {
259
- $post_type = $this->get_current_post_type();
260
- if( post_type_supports( $post_type, 'author' )) {
261
- $this->_edit_page_post_type_supported = true;
262
- remove_post_type_support( $post_type, 'author' );
263
- }
264
  }
265
-
266
  /**
267
  * Add coauthors to author column on edit pages
 
268
  * @param array $post_columns
269
  */
270
- function _filter_manage_posts_columns($posts_columns) {
 
271
  $new_columns = array();
272
-
 
 
273
  foreach ($posts_columns as $key => $value) {
274
  $new_columns[$key] = $value;
275
  if( $key == 'title' )
276
  $new_columns['coauthors'] = __( 'Authors', 'co-authors-plus' );
277
-
278
  if ( $key == 'author' )
279
  unset($new_columns[$key]);
280
  }
281
  return $new_columns;
282
- } // END: _filter_manage_posts_columns
283
-
284
  /**
285
  * Insert coauthors into post rows on Edit Page
 
286
  * @param string $column_name
287
- **/
288
- function _filter_manage_posts_custom_column($column_name) {
289
  if ($column_name == 'coauthors') {
290
  global $post;
291
  $authors = get_coauthors( $post->ID );
292
-
293
  $count = 1;
294
  foreach( $authors as $author ) :
 
 
 
 
 
 
295
  ?>
296
- <a href="<?php echo esc_url( get_admin_url( null, 'edit.php?author=' . $author->ID ) ); ?>"><?php echo esc_html( $author->display_name ); ?></a><?php echo ( $count < count( $authors ) ) ? ',' : ''; ?>
297
  <?php
298
  $count++;
299
  endforeach;
@@ -324,8 +437,9 @@ class coauthors_plus {
324
  return $value;
325
  // We filter count_user_posts() so it provides an accurate number
326
  $numposts = count_user_posts( $user_id );
 
327
  if ( $numposts > 0 ) {
328
- $value .= "<a href='edit.php?author=$user_id' title='" . esc_attr__( 'View posts by this author' ) . "' class='edit'>";
329
  $value .= $numposts;
330
  $value .= '</a>';
331
  } else {
@@ -337,125 +451,174 @@ class coauthors_plus {
337
  /**
338
  * When we update the terms at all, we should update the published post count for each author
339
  */
340
- function _update_users_posts_count( $terms, $taxonomy ) {
341
  global $wpdb;
342
 
343
- $object_types = (array) $taxonomy->object_type;
 
344
 
345
- foreach ( $object_types as &$object_type ) {
346
- list( $object_type ) = explode( ':', $object_type );
 
347
  }
 
 
348
 
349
- if ( $object_types )
350
- $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
351
-
352
- $object_types = array_unique( $object_types );
353
-
354
- foreach( (array)$terms as $term_taxonomy_id ) {
355
- $count = 0;
356
- if ( 0 == $term_taxonomy_id )
357
- continue;
358
- // Get the post IDs for all published posts with this co-author
359
- $query = $wpdb->prepare( "SELECT $wpdb->posts.ID FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status = 'publish' AND post_type IN ('" . implode("', '", $object_types ) . "') AND term_taxonomy_id = %d", $term_taxonomy_id );
360
- $all_coauthor_posts = $wpdb->get_results( $query );
361
-
362
- // Find the term_id from the term_taxonomy_id, and then get the user's user_login from that
363
- $query = $wpdb->prepare( "SELECT $wpdb->terms.slug FROM $wpdb->term_taxonomy INNER JOIN $wpdb->terms ON $wpdb->terms.term_id = $wpdb->term_taxonomy.term_id WHERE $wpdb->term_taxonomy.term_taxonomy_id = %d", $term_taxonomy_id );
364
- $term_slug = $wpdb->get_var( $query );
365
- $author = get_user_by( 'login', $term_slug );
366
-
367
- // Get all of the post IDs where the user is the primary author
368
- $query = $wpdb->prepare( "SELECT $wpdb->posts.ID FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ('" . implode("', '", $object_types ) . "') AND post_author = %d;", $author->ID );
369
- $all_author_posts = $wpdb->get_results( $query );
370
-
371
- // Dedupe the post IDs and then provide a final count
372
- $all_posts = array();
373
- foreach( $all_coauthor_posts as $coauthor_post ) {
374
- $all_posts[] = $coauthor_post->ID;
375
- }
376
- foreach( $all_author_posts as $author_post ) {
377
- $all_posts[] = $author_post->ID;
378
- }
379
- $count = count( array_unique( $all_posts ) );
380
 
381
- // Save the count to the term's count column
382
- $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term_taxonomy_id ) );
383
- }
384
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
386
-
387
  /**
388
  * Modify the author query posts SQL to include posts co-authored
389
  */
390
- function posts_join_filter( $join ){
391
- global $wpdb, $wp_query;
392
-
393
- if( is_author() ){
 
 
 
 
394
  // Check to see that JOIN hasn't already been added. Props michaelingp and nbaxley
395
  $term_relationship_join = " INNER JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)";
396
  $term_taxonomy_join = " INNER JOIN {$wpdb->term_taxonomy} ON ( {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )";
397
-
398
  if( strpos( $join, trim( $term_relationship_join ) ) === false ) {
399
- $join .= $term_relationship_join;
400
  }
401
  if( strpos( $join, trim( $term_taxonomy_join ) ) === false ) {
402
- $join .= $term_taxonomy_join;
403
  }
404
  }
405
-
406
  return $join;
407
  }
408
-
409
  /**
410
- * Modify
411
  */
412
- function posts_where_filter( $where ){
413
- global $wpdb, $wp_query;
414
-
415
- if( is_author() ) {
416
- $author = get_userdata( $wp_query->query_vars['author'] );
417
- $term = get_term_by( 'name', $author->user_login, $this->coauthor_taxonomy );
418
-
419
- if( $author ) {
420
- $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(\d+))/', '($1 OR (' . $wpdb->term_taxonomy . '.taxonomy = \''. $this->coauthor_taxonomy.'\' AND '. $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\'))', $where, 1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
 
423
  }
424
  return $where;
425
  }
426
-
427
  /**
428
- *
429
  */
430
- function posts_groupby_filter( $groupby ) {
431
  global $wpdb;
432
-
433
- if( is_author() ) {
434
- $groupby = $wpdb->posts .'.ID';
 
 
 
 
 
 
 
435
  }
436
  return $groupby;
437
  }
438
-
439
  /**
440
  * Filters post data before saving to db to set post_author
441
  */
442
  function coauthors_set_post_author_field( $data, $postarr ) {
443
-
444
  // Bail on autosave
445
  if ( defined( 'DOING_AUTOSAVE' ) && !DOING_AUTOSAVE )
446
  return $data;
447
-
448
  // Bail on revisions
449
- if( $data['post_type'] == 'revision' )
450
  return $data;
451
 
452
- // @todo this should check the nonce before changing the value
453
-
454
  if( isset( $_REQUEST['coauthors-nonce'] ) && isset( $_POST['coauthors'] ) && is_array( $_POST['coauthors'] ) ) {
455
- $author = sanitize_user( $_POST['coauthors'][0] );
456
- if( $author ) {
457
- $author_data = get_user_by( 'login', $author );
458
- $data['post_author'] = $author_data->ID;
 
 
 
 
 
459
  }
460
  }
461
 
@@ -464,8 +627,20 @@ class coauthors_plus {
464
  // 'post_author' is set to current user if the $_REQUEST value doesn't exist
465
  if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'inline-save' ) {
466
  $coauthors = get_coauthors( $postarr['ID'] );
467
- if ( is_array( $coauthors ) )
468
- $data['post_author'] = $coauthors[0]->ID;
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
470
 
471
  // If for some reason we don't have the coauthors fields set
@@ -473,65 +648,59 @@ class coauthors_plus {
473
  $user = wp_get_current_user();
474
  $data['post_author'] = $user->ID;
475
  }
476
-
 
 
 
477
  return $data;
478
  }
479
-
480
  /**
481
- * Update a post's co-authors
 
482
  * @param $post_ID
483
- * @return
484
  */
485
  function coauthors_update_post( $post_id, $post ) {
486
  $post_type = $post->post_type;
487
-
488
  if ( defined( 'DOING_AUTOSAVE' ) && !DOING_AUTOSAVE )
489
  return;
490
-
491
  if( isset( $_POST['coauthors-nonce'] ) && isset( $_POST['coauthors'] ) ) {
492
  check_admin_referer( 'coauthors-edit', 'coauthors-nonce' );
493
-
494
  if( $this->current_user_can_set_authors() ){
495
  $coauthors = (array) $_POST['coauthors'];
496
- $coauthors = array_map( 'sanitize_user', $coauthors );
497
  return $this->add_coauthors( $post_id, $coauthors );
498
  }
499
  }
500
  }
501
-
502
  /**
503
- * Add a user as coauthor for a post
504
  */
505
  function add_coauthors( $post_id, $coauthors, $append = false ) {
506
  global $current_user;
507
-
508
  $post_id = (int) $post_id;
509
  $insert = false;
510
-
511
  // if an array isn't returned, create one and populate with default author
512
  if ( !is_array( $coauthors ) || 0 == count( $coauthors ) || empty( $coauthors ) ) {
513
  $coauthors = array( $current_user->user_login );
514
  }
515
-
516
  // Add each co-author to the post meta
517
- foreach( array_unique( $coauthors ) as $author ){
518
-
519
- // Name and slug of term are the username;
520
- $name = $author;
521
-
522
- // Add user as a term if they don't exist
523
- if( !term_exists( $name, $this->coauthor_taxonomy ) ) {
524
- $args = array( 'slug' => sanitize_title( $name ) );
525
- $insert = wp_insert_term( $name, $this->coauthor_taxonomy, $args );
526
- }
527
- }
528
-
529
- // Add authors as post terms
530
- if( !is_wp_error( $insert ) ) {
531
- $set = wp_set_post_terms( $post_id, $coauthors, $this->coauthor_taxonomy, $append );
532
  }
 
533
  }
534
-
535
  /**
536
  * Action taken when user is deleted.
537
  * - User term is removed from all associated posts
@@ -540,9 +709,9 @@ class coauthors_plus {
540
  */
541
  function delete_user_action($delete_id){
542
  global $wpdb;
543
-
544
  $reassign_id = absint( $_POST['reassign_user'] );
545
-
546
  // If reassign posts, do that -- use coauthors_update_post
547
  if($reassign_id) {
548
  // Get posts belonging to deleted author
@@ -558,7 +727,7 @@ class coauthors_plus {
558
  }
559
  }
560
  }
561
-
562
  $delete_user = get_user_by( 'id', $delete_id );
563
  if ( is_object( $delete_user ) ) {
564
  // Delete term
@@ -577,55 +746,55 @@ class coauthors_plus {
577
  if ( !isset( $_REQUEST['bulk_edit'] ) || $taxonomies != "'author'" )
578
  return $terms;
579
 
580
- global $wpdb;
581
- $orderby = 'ORDER BY tr.term_order';
582
  $order = 'ASC';
583
  $object_ids = (int)$object_ids;
584
  $query = $wpdb->prepare( "SELECT t.slug, t.term_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN (%s) AND tr.object_id IN (%s) $orderby $order", $taxonomies, $object_ids );
585
- $raw_coauthors = $wpdb->get_results( $query );
586
  $terms = array();
587
- foreach( $raw_coauthors as $author ) {
588
- $terms[] = $author->slug;
589
  }
590
 
591
  return $terms;
592
-
593
  }
594
-
595
  /**
596
- * Filter the count_users_posts() core function
597
  */
598
  function filter_count_user_posts( $count, $user_id ) {
599
  $user = get_userdata( $user_id );
600
-
601
- $term = get_term_by( 'slug', $user->user_login, $this->coauthor_taxonomy );
602
-
603
  // Only modify the count if the author already exists as a term
604
  if( $term && !is_wp_error( $term ) ) {
605
  $count = $term->count;
606
  }
607
-
608
  return $count;
609
  }
610
-
611
  /**
612
  * Checks to see if the current user can set authors or not
613
  */
614
  function current_user_can_set_authors( ) {
615
  global $post, $typenow;
616
-
617
- // TODO: Enable Authors to set Co-Authors
618
-
619
- $post_type = $this->get_current_post_type();
620
  // TODO: need to fix this; shouldn't just say no if don't have post_type
621
  if( ! $post_type ) return false;
622
-
623
  $post_type_object = get_post_type_object( $post_type );
624
- $can_set_authors = current_user_can( $post_type_object->cap->edit_others_posts );
625
-
 
 
 
626
  return apply_filters( 'coauthors_plus_edit_authors', $can_set_authors );
627
  }
628
-
629
  /**
630
  * Fix for author info not properly displaying on author pages
631
  *
@@ -633,41 +802,42 @@ class coauthors_plus {
633
  * the first author is NOT the same as the author for the archive,
634
  * the query_var is changed.
635
  *
 
636
  */
637
- function fix_author_page( &$post ) {
638
-
639
- if( is_author() ) {
640
- global $wp_query, $authordata;
641
-
642
- // Get the id of the author whose page we're on
643
- $author_id = $wp_query->get( 'author' );
644
-
645
- // Check that the the author matches the first author of the first post
646
- if( $author_id != $authordata->ID ) {
647
- // The IDs don't match, so we need to force the $authordata to the one we want
648
- $authordata = get_userdata( $author_id );
649
  }
650
  }
651
  }
652
-
653
  /**
654
  * Main function that handles search-as-you-type for adding authors
655
  */
656
  function ajax_suggest() {
657
-
658
  if( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'coauthors-search' ) )
659
  die();
660
-
661
  if( empty( $_REQUEST['q'] ) )
662
  die();
663
 
664
  $search = sanitize_text_field( strtolower( $_REQUEST['q'] ) );
665
- $ignore = array_map( 'sanitize_user', explode( ',', $_REQUEST['existing_authors'] ) );
666
 
667
  $authors = $this->search_authors( $search, $ignore );
668
-
669
  foreach( $authors as $author ) {
670
- echo $author->ID ." | ". $author->user_login ." | ". $author->display_name ." | ". $author->user_email ."\n";
671
  }
672
 
673
  die();
@@ -676,9 +846,13 @@ class coauthors_plus {
676
 
677
  /**
678
  * Get matching authors based on a search value
679
- */
680
  function search_authors( $search = '', $ignored_authors = array() ) {
681
 
 
 
 
 
682
  $args = array(
683
  'count_total' => false,
684
  'search' => sprintf( '*%s*', $search ),
@@ -694,13 +868,39 @@ class coauthors_plus {
694
  $found_users = get_users( $args );
695
  remove_filter( 'pre_user_query', array( $this, 'filter_pre_user_query' ) );
696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  // Allow users to always filter out certain users if needed (e.g. administrators)
698
  $ignored_authors = apply_filters( 'coauthors_edit_ignored_authors', $ignored_authors );
699
  foreach( $found_users as $key => $found_user ) {
700
  // Make sure the user is contributor and above (or a custom cap)
701
  if ( in_array( $found_user->user_login, $ignored_authors ) )
702
  unset( $found_users[$key] );
703
- else if ( false === $found_user->has_cap( apply_filters( 'coauthors_edit_author_cap', 'edit_posts' ) ) )
704
  unset( $found_users[$key] );
705
  }
706
  return (array) $found_users;
@@ -716,25 +916,34 @@ class coauthors_plus {
716
  return $user_query;
717
  }
718
 
 
 
 
 
 
 
 
 
 
 
 
719
  /**
720
  * Functions to add scripts and css
721
  */
722
  function enqueue_scripts($hook_suffix) {
723
  global $pagenow, $post;
724
-
725
- $post_type = $this->get_current_post_type();
726
-
727
- if ( !$this->is_valid_page() || !$this->authors_supported( $post_type ) || !$this->current_user_can_set_authors() )
728
  return;
729
-
730
  wp_enqueue_script( 'jquery' );
731
  wp_enqueue_script( 'jquery-ui-sortable' );
732
  wp_enqueue_style( 'co-authors-plus-css', COAUTHORS_PLUS_URL . 'css/co-authors-plus.css', false, COAUTHORS_PLUS_VERSION, 'all' );
733
  wp_enqueue_script( 'co-authors-plus-js', COAUTHORS_PLUS_URL . 'js/co-authors-plus.js', array('jquery', 'suggest'), COAUTHORS_PLUS_VERSION, true);
734
-
735
  $js_strings = array(
736
  'edit_label' => __( 'Edit', 'co-authors-plus' ),
737
- 'delete_label' => __( 'Remove', 'co-authors-plus' ),
738
  'confirm_delete' => __( 'Are you sure you want to remove this author?', 'co-authors-plus' ),
739
  'input_box_title' => __( 'Click to change this author, or drag to change their position', 'co-authors-plus' ),
740
  'search_box_text' => __( 'Search for an author', 'co-authors-plus' ),
@@ -742,112 +951,195 @@ class coauthors_plus {
742
  );
743
  wp_localize_script( 'co-authors-plus-js', 'coAuthorsPlusStrings', $js_strings );
744
 
745
- }
746
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  /**
748
  * Adds necessary javascript variables to admin pages
749
  */
750
  function js_vars() {
751
-
752
- $post_type = $this->get_current_post_type();
753
-
754
- if( $this->is_valid_page() && $this->authors_supported( $post_type ) && $this->current_user_can_set_authors() ) {
755
- ?>
756
  <script type="text/javascript">
757
  // AJAX link used for the autosuggest
758
  var coAuthorsPlus_ajax_suggest_link = '<?php echo add_query_arg(
759
  array(
760
  'action' => 'coauthors_ajax_suggest',
761
- 'post_type' => $post_type,
762
  ),
763
  wp_nonce_url( 'admin-ajax.php', 'coauthors-search' )
764
  ); ?>';
765
  </script>
766
- <?php
767
- }
768
- } // END: js_vars()
769
-
770
  /**
771
  * Helper to only add javascript to necessary pages. Avoids bloat in admin.
772
  */
773
  function is_valid_page() {
774
  global $pagenow;
775
-
776
- return in_array( $pagenow, $this->_pages_whitelist );
777
- }
778
-
779
  function get_post_id() {
780
  global $post;
781
  $post_id = 0;
782
-
783
  if ( is_object( $post ) ) {
784
  $post_id = $post->ID;
785
  }
786
-
787
  if( ! $post_id ) {
788
  if ( isset( $_GET['post'] ) )
789
  $post_id = (int) $_GET['post'];
790
  elseif ( isset( $_POST['post_ID'] ) )
791
  $post_id = (int) $_POST['post_ID'];
792
  }
793
-
794
  return $post_id;
795
  }
796
-
797
  /**
798
  * Allows coauthors to edit the post they're coauthors of
799
- * Pieces of code borrowed from http://pastebin.ca/1909968
800
- *
801
  */
802
- function add_coauthor_cap( $allcaps, $caps, $args ) {
803
-
804
- // Load the post data:
805
  $user_id = isset( $args[1] ) ? $args[1] : 0;
806
  $post_id = isset( $args[2] ) ? $args[2] : 0;
807
-
808
- if( ! $post_id )
809
- $post_id = $this->get_post_id();
810
-
811
- if( ! $post_id )
812
- return $allcaps;
813
-
814
- $post = get_post( $post_id );
815
-
816
- if( ! $post )
817
- return $allcaps;
818
-
819
- $post_type_object = get_post_type_object( $post->post_type );
820
 
821
- // Bail out if there's no post type object
822
- if ( ! is_object( $post_type_object ) )
823
  return $allcaps;
824
 
825
- // Bail out if we're not asking about a post
826
- if ( ! in_array( $args[0], array( $post_type_object->cap->edit_post, $post_type_object->cap->edit_others_posts ) ) )
827
- return $allcaps;
828
-
829
- // Bail out for users who can already edit others posts
830
- if ( isset( $allcaps[$post_type_object->cap->edit_others_posts] ) && $allcaps[$post_type_object->cap->edit_others_posts] )
831
  return $allcaps;
832
-
833
- // Bail out for users who can't publish posts if the post is already published
834
- if ( 'publish' == $post->post_status && ( ! isset( $allcaps[$post_type_object->cap->edit_published_posts] ) || ! $allcaps[$post_type_object->cap->edit_published_posts] ) )
835
  return $allcaps;
836
-
837
- // Finally, double check that the user is a coauthor of the post
838
- if( is_coauthor_for_post( $user_id, $post_id ) ) {
839
- foreach($caps as $cap) {
840
- $allcaps[$cap] = true;
841
- }
842
- }
843
-
 
844
  return $allcaps;
845
  }
846
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  /**
848
  * Filter Edit Flow's 'ef_calendar_item_information_fields' to add co-authors
849
  *
850
- * @see https://github.com/danielbachhuber/Co-Authors-Plus/issues/2
851
  */
852
  function filter_ef_calendar_item_information_fields( $information_fields, $post_id ) {
853
 
@@ -869,7 +1161,7 @@ class coauthors_plus {
869
  /**
870
  * Filter Edit Flow's 'ef_story_budget_term_column_value' to add co-authors to the story budget
871
  *
872
- * @see https://github.com/danielbachhuber/Co-Authors-Plus/issues/2
873
  */
874
  function filter_ef_story_budget_term_column_value( $column_name, $post, $parent_term ) {
875
 
@@ -984,9 +1276,9 @@ function wp_notify_postauthor( $comment_id, $comment_type = '' ) {
984
  if ( isset($reply_to) )
985
  $message_headers .= $reply_to . "\n";
986
 
987
- $notify_message = apply_filters('comment_notification_text', $notify_message, $comment_id);
988
- $subject = apply_filters('comment_notification_subject', $subject, $comment_id);
989
- $message_headers = apply_filters('comment_notification_headers', $message_headers, $comment_id);
990
 
991
  @wp_mail( $author->user_email, $subject, $notify_message, $message_headers );
992
  }
@@ -1079,4 +1371,4 @@ function wp_notify_moderator( $comment_id ) {
1079
 
1080
  return true;
1081
  }
1082
- endif;
3
  Plugin Name: Co-Authors Plus
4
  Plugin URI: http://wordpress.org/extend/plugins/co-authors-plus/
5
  Description: Allows multiple authors to be assigned to a post. This plugin is an extended version of the Co-Authors plugin developed by Weston Ruter.
6
+ Version: 3.0
7
  Author: Mohammad Jangda, Daniel Bachhuber, Automattic
8
  Copyright: 2008-2012 Shared and distributed between Mohammad Jangda, Daniel Bachhuber, Weston Ruter
9
 
24
 
25
  */
26
 
27
+ define( 'COAUTHORS_PLUS_VERSION', '3.0' );
28
 
29
  define( 'COAUTHORS_PLUS_PATH', dirname( __FILE__ ) );
30
  define( 'COAUTHORS_PLUS_URL', plugin_dir_url( __FILE__ ) );
31
 
32
  require_once( dirname( __FILE__ ) . '/template-tags.php' );
33
 
34
+ require_once( dirname( __FILE__ ) . '/php/class-coauthors-template-filters.php' );
35
+
36
+ if ( defined('WP_CLI') && WP_CLI )
37
+ require_once( dirname( __FILE__ ) . '/php/class-wp-cli.php' );
38
+
39
  class coauthors_plus {
40
+
41
+ // Name for the taxonomy we're using to store relationships
42
+ // and the post type we're using to store co-authors
43
  var $coauthor_taxonomy = 'author';
44
+
45
  var $coreauthors_meta_box_name = 'authordiv';
46
  var $coauthors_meta_box_name = 'coauthorsdiv';
47
+ var $force_guest_authors = false;
48
+
49
  var $gravatar_size = 25;
50
+
51
  var $_pages_whitelist = array( 'post.php', 'post-new.php' );
52
+
53
+ var $supported_post_types = array();
54
+
55
+ var $ajax_search_fields = array( 'display_name', 'first_name', 'last_name', 'user_login', 'ID', 'user_email' );
56
+
57
+ var $having_terms = '';
58
+
59
  /**
60
  * __construct()
61
  */
62
  function __construct() {
63
 
64
+ // Register our models
65
+ add_action( 'init', array( $this, 'action_init' ) );
66
+ add_action( 'init', array( $this, 'action_init_late' ), 100 );
67
 
68
  // Load admin_init function
69
  add_action( 'admin_init', array( $this,'admin_init' ) );
70
+
 
 
 
71
  // Modify SQL queries to include coauthors
72
+ add_filter( 'posts_where', array( $this, 'posts_where_filter' ), 10, 2 );
73
+ add_filter( 'posts_join', array( $this, 'posts_join_filter' ), 10, 2 );
74
+ add_filter( 'posts_groupby', array( $this, 'posts_groupby_filter' ), 10, 2 );
75
+
76
  // Action to set users when a post is saved
77
  add_action( 'save_post', array( $this, 'coauthors_update_post' ), 10, 2 );
78
  // Filter to set the post_author field when wp_insert_post is called
79
  add_filter( 'wp_insert_post_data', array( $this, 'coauthors_set_post_author_field' ), 10, 2 );
80
+
81
  // Action to reassign posts when a user is deleted
82
  add_action( 'delete_user', array( $this, 'delete_user_action' ) );
83
+
84
  add_filter( 'get_usernumposts', array( $this, 'filter_count_user_posts' ), 10, 2 );
85
+
86
  // Action to set up author auto-suggest
87
  add_action( 'wp_ajax_coauthors_ajax_suggest', array( $this, 'ajax_suggest' ) );
88
+
89
  // Filter to allow coauthors to edit posts
90
+ add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 3 );
91
 
92
  // Handle the custom author meta box
93
  add_action( 'add_meta_boxes', array( $this, 'add_coauthors_box' ) );
94
  add_action( 'add_meta_boxes', array( $this, 'remove_authors_box' ) );
95
+
96
+ // Removes the author dropdown from the post quick edit
97
+ add_action( 'admin_head', array( $this, 'remove_quick_edit_authors_box' ) );
98
 
99
  // Restricts WordPress from blowing away term order on bulk edit
100
+ add_filter( 'wp_get_object_terms', array( $this, 'filter_wp_get_object_terms' ), 10, 4 );
101
+
102
  // Fix for author info not properly displaying on author pages
103
+ add_action( 'template_redirect', array( $this, 'fix_author_page' ) );
104
  add_action( 'the_post', array( $this, 'fix_author_page' ) );
105
 
106
  // Support for Edit Flow's calendar and story budget
111
 
112
  function coauthors_plus() {
113
  $this->__construct();
114
+ }
115
+
116
  /**
117
+ * Register the taxonomy used to managing relationships,
118
+ * and the custom post type to store our author data
119
+ */
120
+ function action_init() {
121
+
122
+ // Allow Co-Authors Plus to be easily translated
123
+ load_plugin_textdomain( 'co-authors-plus', null, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
124
+
125
+ // Load the Guest Authors functionality if needed
126
+ if ( $this->is_guest_authors_enabled() ) {
127
+ require_once( dirname( __FILE__ ) . '/php/class-coauthors-guest-authors.php' );
128
+ $this->guest_authors = new CoAuthors_Guest_Authors;
129
+ if ( apply_filters( 'coauthors_guest_authors_force', false ) ) {
130
+ $this->force_guest_authors = true;
131
+ }
132
+ }
133
+
134
+ // Maybe automatically apply our template tags
135
+ if ( apply_filters( 'coauthors_auto_apply_template_tags', false ) ) {
136
+ new CoAuthors_Template_Filters;
137
+ }
138
+
139
+ }
140
+
141
+ /**
142
+ * Register the 'author' taxonomy and add post type support
143
+ */
144
+ function action_init_late() {
145
+
146
+ // Register new taxonomy so that we can store all of the relationships
147
+ $args = array(
148
+ 'hierarchical' => false,
149
+ 'update_count_callback' => array( $this, '_update_users_posts_count' ),
150
+ 'label' => false,
151
+ 'query_var' => false,
152
+ 'rewrite' => false,
153
+ 'public' => false,
154
+ 'sort' => true,
155
+ 'show_ui' => false
156
+ );
157
+ $post_types_with_authors = array_values( get_post_types() );
158
+ foreach( $post_types_with_authors as $key => $name ) {
159
+ if ( ! post_type_supports( $name, 'author' ) )
160
+ unset( $post_types_with_authors[$key] );
161
+ }
162
+ $this->supported_post_types = apply_filters( 'coauthors_supported_post_types', $post_types_with_authors );
163
+ register_taxonomy( $this->coauthor_taxonomy, $this->supported_post_types, $args );
164
+ }
165
+
166
+ /**
167
+ * Initialize the plugin for the admin
168
  */
169
  function admin_init() {
170
  global $pagenow;
171
 
172
+ // Add the main JS script and CSS file
173
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
174
+ // Add necessary JS variables
175
+ add_action( 'admin_head', array( $this, 'js_vars' ) );
176
 
177
  // Hooks to add additional coauthors to author column to Edit page
178
  add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns' ) );
182
 
183
  // Hooks to modify the published post number count on the Users WP List Table
184
  add_filter( 'manage_users_columns', array( $this, '_filter_manage_users_columns' ) );
185
+ add_filter( 'manage_users_custom_column', array( $this, '_filter_manage_users_custom_column' ), 10, 3 );
186
+
187
+ // Apply some targeted filters
188
+ add_action( 'load-edit.php', array( $this, 'load_edit' ) );
189
+
190
  }
 
 
 
 
 
 
 
 
191
 
192
+ /**
193
+ * Check whether the guest authors functionality is enabled or not
194
+ * Guest authors can be disabled entirely with:
195
+ * add_filter( 'coauthors_guest_authors_enabled', '__return_false' )
196
+ *
197
+ * @since 3.0
198
+ */
199
+ function is_guest_authors_enabled() {
200
+ return apply_filters( 'coauthors_guest_authors_enabled', true );
201
  }
202
+
203
+ /**
204
+ * Get a co-author object by a specific type of key
205
+ *
206
+ * @param string $key Key to search by (slug,email)
207
+ * @param string $value Value to search for
208
+ * @param object|false $coauthor The co-author on success, false on failure
209
  */
210
+ function get_coauthor_by( $key, $value ) {
211
+
212
+ // If Guest Authors are enabled, prioritize those profiles
213
+ if ( $this->is_guest_authors_enabled() ) {
214
+ $guest_author = $this->guest_authors->get_guest_author_by( $key, $value );
215
+ if ( is_object( $guest_author ) ) {
216
+ return $guest_author;
217
+ }
218
+ }
219
+
220
+ switch( $key ) {
221
+ case 'id':
222
+ case 'login':
223
+ case 'user_login':
224
+ case 'email':
225
+ case 'user_nicename':
226
+ case 'user_email':
227
+ if ( 'user_login' == $key )
228
+ $key = 'login';
229
+ if ( 'user_email' == $key )
230
+ $key = 'email';
231
+ if ( 'user_nicename' == $key )
232
+ $key = 'slug';
233
+ // Ensure we aren't doing the lookup by the prefixed value
234
+ if ( 'login' == $key || 'slug' == $key )
235
+ $value = preg_replace( '#^cap\-#', '', $value );
236
+ $user = get_user_by( $key, $value );
237
+ if ( !$user || !is_user_member_of_blog( $user->ID ) )
238
+ return false;
239
+ $user->type = 'wpuser';
240
+ // However, if guest authors are enabled and there's a guest author linked to this
241
+ // user account, we want to use that instead
242
+ if ( $this->is_guest_authors_enabled() ) {
243
+ $guest_author = $this->guest_authors->get_guest_author_by( 'linked_account', $user->user_login );
244
+ if ( is_object( $guest_author ) )
245
+ $user = $guest_author;
246
+ }
247
+ return $user;
248
+ break;
249
+ }
250
  return false;
251
+
252
  }
253
+
254
  /**
255
+ * Whether or not Co-Authors Plus is enabled for this post type
256
+ * Must be called after init
257
+ *
258
+ * @since 3.0
259
+ *
260
+ * @param string $post_type The name of the post type we're considering
261
+ * @return bool Whether or not it's enabled
262
  */
263
+ function is_post_type_enabled( $post_type = null ) {
264
+
265
+ if ( ! $post_type )
266
+ $post_type = get_post_type();
267
+
268
+ return (bool) in_array( $post_type, $this->supported_post_types );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
270
+
271
  /**
272
  * Removes the standard WordPress Author box.
273
  * We don't need it because the Co-Authors one is way cooler.
274
  */
275
  function remove_authors_box() {
276
+
277
+ if ( $this->is_post_type_enabled() )
278
+ remove_meta_box( $this->coreauthors_meta_box_name, get_post_type(), 'normal' );
 
 
279
  }
280
+
281
  /**
282
  * Adds a custom Authors box
283
  */
284
  function add_coauthors_box() {
285
+
286
+ if( $this->is_post_type_enabled() && $this->current_user_can_set_authors() )
287
+ add_meta_box( $this->coauthors_meta_box_name, __('Authors', 'co-authors-plus'), array( $this, 'coauthors_meta_box' ), get_post_type(), apply_filters( 'coauthors_meta_box_context', 'normal'), apply_filters( 'coauthors_meta_box_priority', 'high'));
 
 
288
  }
289
+
290
  /**
291
  * Callback for adding the custom author box
292
  */
293
  function coauthors_meta_box( $post ) {
294
+ global $post, $coauthors_plus, $current_screen;
295
+
296
  $post_id = $post->ID;
297
+
298
+ // @daniel, $post_id and $post->post_author are always set when a new post is created due to auto draft,
299
+ // and the else case below was always able to properly assign users based on wp_posts.post_author,
300
+ // but that's not possible with force_guest_authors = true.
301
+ if( !$post_id || $post_id == 0 || ( !$post->post_author && !$coauthors_plus->force_guest_authors ) || ( $current_screen->base == 'post' && $current_screen->action == 'add' ) ) {
302
+ $coauthors = array();
303
+ // If guest authors is enabled, try to find a guest author attached to this user ID
304
+ if ( $this->is_guest_authors_enabled() ) {
305
+ $coauthor = $coauthors_plus->guest_authors->get_guest_author_by( 'linked_account', wp_get_current_user()->user_login );
306
+ if ( $coauthor ) {
307
+ $coauthors[] = $coauthor;
308
+ }
309
+ }
310
+ // If the above block was skipped, or if it failed to find a guest author, use the current
311
+ // logged in user, so long as force_guest_authors is false. If force_guest_authors = true, we are
312
+ // OK with having an empty authoring box.
313
+ if ( !$coauthors_plus->force_guest_authors && empty( $coauthors ) ) {
314
+ $coauthors[] = wp_get_current_user();
315
+ }
316
+ } else {
317
  $coauthors = get_coauthors();
318
+ }
319
+
320
  $count = 0;
321
  if( !empty( $coauthors ) ) :
322
  ?>
337
  <?php
338
  endforeach;
339
  ?>
340
+ </ul>
341
  <div class="clear"></div>
342
  <p><?php _e( '<strong>Note:</strong> To edit post authors, please enable javascript or use a javascript-capable browser', 'co-authors-plus' ); ?></p>
343
  </div>
344
  <?php
345
  endif;
346
  ?>
347
+
348
  <div id="coauthors-edit" class="hide-if-no-js">
349
  <p><?php _e( 'Click on an author to change them. Drag to change their order. Click on <strong>Remove</strong> to remove them.', 'co-authors-plus' ); ?></p>
350
  </div>
351
+
352
  <?php wp_nonce_field( 'coauthors-edit', 'coauthors-nonce' ); ?>
353
+
354
  <?php
355
  }
356
+
357
  /**
358
  * Removes the author dropdown from the post quick edit
359
+ * It's a bit hacky, but the only way I can figure out :(
360
  */
361
  function remove_quick_edit_authors_box() {
362
+ global $pagenow;
363
+
364
+ if ( 'edit.php' == $pagenow && $this->is_post_type_enabled() )
365
+ remove_post_type_support( get_post_type(), 'author' );
 
366
  }
367
+
368
  /**
369
  * Add coauthors to author column on edit pages
370
+ *
371
  * @param array $post_columns
372
  */
373
+ function _filter_manage_posts_columns( $posts_columns ) {
374
+
375
  $new_columns = array();
376
+ if ( ! $this->is_post_type_enabled() )
377
+ return $posts_columns;
378
+
379
  foreach ($posts_columns as $key => $value) {
380
  $new_columns[$key] = $value;
381
  if( $key == 'title' )
382
  $new_columns['coauthors'] = __( 'Authors', 'co-authors-plus' );
383
+
384
  if ( $key == 'author' )
385
  unset($new_columns[$key]);
386
  }
387
  return $new_columns;
388
+ }
389
+
390
  /**
391
  * Insert coauthors into post rows on Edit Page
392
+ *
393
  * @param string $column_name
394
+ */
395
+ function _filter_manage_posts_custom_column( $column_name ) {
396
  if ($column_name == 'coauthors') {
397
  global $post;
398
  $authors = get_coauthors( $post->ID );
399
+
400
  $count = 1;
401
  foreach( $authors as $author ) :
402
+ $args = array(
403
+ 'author_name' => $author->user_nicename,
404
+ );
405
+ if ( 'post' != $post->post_type )
406
+ $args['post_type'] = $post->post_type;
407
+ $author_filter_url = add_query_arg( $args, admin_url( 'edit.php' ) );
408
  ?>
409
+ <a href="<?php echo esc_url( $author_filter_url ); ?>"><?php echo esc_html( $author->display_name ); ?></a><?php echo ( $count < count( $authors ) ) ? ',' : ''; ?>
410
  <?php
411
  $count++;
412
  endforeach;
437
  return $value;
438
  // We filter count_user_posts() so it provides an accurate number
439
  $numposts = count_user_posts( $user_id );
440
+ $user = get_user_by( 'id', $user_id );
441
  if ( $numposts > 0 ) {
442
+ $value .= "<a href='edit.php?author_name=$user->user_nicename' title='" . esc_attr__( 'View posts by this author', 'co-authors-plus' ) . "' class='edit'>";
443
  $value .= $numposts;
444
  $value .= '</a>';
445
  } else {
451
  /**
452
  * When we update the terms at all, we should update the published post count for each author
453
  */
454
+ function _update_users_posts_count( $tt_ids, $taxonomy ) {
455
  global $wpdb;
456
 
457
+ $tt_ids = implode( ', ', array_map( 'intval', $tt_ids ) );
458
+ $term_ids = $wpdb->get_results( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)" );
459
 
460
+ foreach( (array)$term_ids as $term_id_result ) {
461
+ $term = get_term_by( 'id', $term_id_result->term_id, $this->coauthor_taxonomy );
462
+ $this->update_author_term_post_count( $term );
463
  }
464
+ $tt_ids = explode( ', ', $tt_ids );
465
+ clean_term_cache( $tt_ids, '', false );
466
 
467
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
 
469
+ /**
470
+ * Update the post count associated with an author term
471
+ *
472
+ * @since 3.0
473
+ *
474
+ * @param object $term The co-author term
475
+ */
476
+ public function update_author_term_post_count( $term ) {
477
+ global $wpdb;
478
+
479
+ $coauthor = $this->get_coauthor_by( 'user_nicename', $term->slug );
480
+ if ( ! $coauthor )
481
+ return new WP_Error( 'missing-coauthor', __( 'No co-author exists for that term', 'co-authors-plus' ) );
482
+
483
+ $query = "SELECT COUNT({$wpdb->posts}.ID) FROM {$wpdb->posts}";
484
+
485
+ $query .= " LEFT JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)";
486
+ $query .= " LEFT JOIN {$wpdb->term_taxonomy} ON ( {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )";
487
+
488
+ $having_terms_and_authors = $having_terms = $wpdb->prepare( "{$wpdb->term_taxonomy}.term_id = %d", $term->term_id );
489
+ if ( 'wpuser' == $coauthor->type )
490
+ $having_terms_and_authors .= $wpdb->prepare( " OR {$wpdb->posts}.post_author = %d", $coauthor->ID );
491
+
492
+ $query .= " WHERE ({$having_terms_and_authors}) AND {$wpdb->posts}.post_type = 'post' AND {$wpdb->posts}.post_status = 'publish'";
493
+
494
+ $query .= $wpdb->prepare( " GROUP BY {$wpdb->posts}.ID HAVING MAX( IF( {$wpdb->term_taxonomy}.taxonomy = '%s', IF( {$having_terms},2,1 ),0 ) ) <> 1 ", $this->coauthor_taxonomy );
495
+
496
+ $count = $wpdb->query( $query );
497
+ $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $count ), array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
498
+
499
+ wp_cache_delete( 'author-term-' . $coauthor->user_nicename, 'co-authors-plus' );
500
  }
501
+
502
  /**
503
  * Modify the author query posts SQL to include posts co-authored
504
  */
505
+ function posts_join_filter( $join, $query ){
506
+ global $wpdb;
507
+
508
+ if( $query->is_author() ) {
509
+
510
+ if ( !empty( $query->query_vars['post_type'] ) && !is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) )
511
+ return $join;
512
+
513
  // Check to see that JOIN hasn't already been added. Props michaelingp and nbaxley
514
  $term_relationship_join = " INNER JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)";
515
  $term_taxonomy_join = " INNER JOIN {$wpdb->term_taxonomy} ON ( {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )";
516
+
517
  if( strpos( $join, trim( $term_relationship_join ) ) === false ) {
518
+ $join .= str_replace( "INNER JOIN", "LEFT JOIN", $term_relationship_join );
519
  }
520
  if( strpos( $join, trim( $term_taxonomy_join ) ) === false ) {
521
+ $join .= str_replace( "INNER JOIN", "LEFT JOIN", $term_taxonomy_join );
522
  }
523
  }
524
+
525
  return $join;
526
  }
527
+
528
  /**
529
+ * Modify the author query posts SQL to include posts co-authored
530
  */
531
+ function posts_where_filter( $where, $query ){
532
+ global $wpdb;
533
+
534
+ if ( $query->is_author() ) {
535
+
536
+ if ( !empty( $query->query_vars['post_type'] ) && !is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) )
537
+ return $where;
 
 
538
 
539
+ if ( $query->get( 'author_name' ) )
540
+ $author_name = sanitize_title( $query->get( 'author_name' ) );
541
+ else
542
+ $author_name = get_userdata( $query->get( 'author' ) )->user_nicename;
543
+
544
+ $terms = array();
545
+ $coauthor = $this->get_coauthor_by( 'user_nicename', $author_name );
546
+ if ( $author_term = $this->get_author_term( $coauthor ) )
547
+ $terms[] = $author_term;
548
+ // If this coauthor has a linked account, we also need to get posts with those terms
549
+ if ( ! empty( $coauthor->linked_account ) ) {
550
+ $linked_account = get_user_by( 'login', $coauthor->linked_account );
551
+ if ( $guest_author_term = $this->get_author_term( $linked_account ) )
552
+ $terms[] = $guest_author_term;
553
+ }
554
+
555
+ // Whether or not to include the original 'post_author' value in the query
556
+ if ( $this->force_guest_authors )
557
+ $maybe_both = false;
558
+ else
559
+ $maybe_both = apply_filters( 'coauthors_plus_should_query_post_author', true );
560
+
561
+ $maybe_both_query = $maybe_both ? '$1 OR' : '';
562
+
563
+ if ( !empty( $terms ) ) {
564
+ $terms_implode = '';
565
+ $this->having_terms = '';
566
+ foreach( $terms as $term ) {
567
+ $terms_implode .= '(' . $wpdb->term_taxonomy . '.taxonomy = \''. $this->coauthor_taxonomy.'\' AND '. $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\') OR ';
568
+ $this->having_terms .= ' ' . $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\' OR ';
569
+ }
570
+ $terms_implode = rtrim( $terms_implode, ' OR' );
571
+ $this->having_terms = rtrim( $this->having_terms, ' OR' );
572
+ $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(\d+))/', '(' . $maybe_both_query . ' ' . $terms_implode . ')', $where, 1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND
573
  }
574
+
575
  }
576
  return $where;
577
  }
578
+
579
  /**
580
+ * Modify the author query posts SQL to include posts co-authored
581
  */
582
+ function posts_groupby_filter( $groupby, $query ) {
583
  global $wpdb;
584
+
585
+ if( $query->is_author() ) {
586
+
587
+ if ( !empty( $query->query_vars['post_type'] ) && !is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) )
588
+ return $groupby;
589
+
590
+ if ( $this->having_terms ) {
591
+ $having = 'MAX( IF( ' . $wpdb->term_taxonomy . '.taxonomy = \''. $this->coauthor_taxonomy.'\', IF( ' . $this->having_terms . ',2,1 ),0 ) ) <> 1 ';
592
+ $groupby = $wpdb->posts . '.ID HAVING ' . $having;
593
+ }
594
  }
595
  return $groupby;
596
  }
597
+
598
  /**
599
  * Filters post data before saving to db to set post_author
600
  */
601
  function coauthors_set_post_author_field( $data, $postarr ) {
602
+
603
  // Bail on autosave
604
  if ( defined( 'DOING_AUTOSAVE' ) && !DOING_AUTOSAVE )
605
  return $data;
606
+
607
  // Bail on revisions
608
+ if ( $data['post_type'] == 'revision' )
609
  return $data;
610
 
611
+ // This action happens when a post is saved while editing a post
 
612
  if( isset( $_REQUEST['coauthors-nonce'] ) && isset( $_POST['coauthors'] ) && is_array( $_POST['coauthors'] ) ) {
613
+ $author = sanitize_text_field( $_POST['coauthors'][0] );
614
+ if ( $author ) {
615
+ $author_data = $this->get_coauthor_by( 'user_nicename', $author );
616
+ // If it's a guest author and has a linked account, store that information in post_author
617
+ // because it'll be the valid user ID
618
+ if ( 'guest-author' == $author_data->type && ! empty( $author_data->linked_account ) ) {
619
+ $data['post_author'] = get_user_by( 'login', $author_data->linked_account )->ID;
620
+ } else if ( $author_data->type == 'wpuser' )
621
+ $data['post_author'] = $author_data->ID;
622
  }
623
  }
624
 
627
  // 'post_author' is set to current user if the $_REQUEST value doesn't exist
628
  if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'inline-save' ) {
629
  $coauthors = get_coauthors( $postarr['ID'] );
630
+ if ( is_array( $coauthors ) ) {
631
+ $coauthor = $this->get_coauthor_by( 'user_nicename', $coauthors[0]->user_nicename );
632
+ if ( 'guest-author' == $coauthor->type && ! empty( $coauthor->linked_account ) ) {
633
+ $data['post_author'] = get_user_by( 'user_login', $coauthor->linked_account )->ID;
634
+ } else if ( $coauthor->type == 'wpuser' )
635
+ $data['post_author'] = $coauthor->ID;
636
+ // Refresh their post publish count too
637
+ if ( 'publish' == $postarr['post_status'] || 'publish' == get_post_status( $postarr['ID'] ) ) {
638
+ foreach( $coauthors as $coauthor ) {
639
+ if ( $author_term = $this->get_author_term( $coauthor ) )
640
+ $this->update_author_term_post_count( $author_term );
641
+ }
642
+ }
643
+ }
644
  }
645
 
646
  // If for some reason we don't have the coauthors fields set
648
  $user = wp_get_current_user();
649
  $data['post_author'] = $user->ID;
650
  }
651
+
652
+ // Allow the 'post_author' to be forced to generic user if it doesn't match any users on the post
653
+ $data['post_author'] = apply_filters( 'coauthors_post_author_value', $data['post_author'], $postarr['ID'] );
654
+
655
  return $data;
656
  }
657
+
658
  /**
659
+ * Update a post's co-authors on the 'save_post' hook
660
+ *
661
  * @param $post_ID
 
662
  */
663
  function coauthors_update_post( $post_id, $post ) {
664
  $post_type = $post->post_type;
665
+
666
  if ( defined( 'DOING_AUTOSAVE' ) && !DOING_AUTOSAVE )
667
  return;
668
+
669
  if( isset( $_POST['coauthors-nonce'] ) && isset( $_POST['coauthors'] ) ) {
670
  check_admin_referer( 'coauthors-edit', 'coauthors-nonce' );
671
+
672
  if( $this->current_user_can_set_authors() ){
673
  $coauthors = (array) $_POST['coauthors'];
674
+ $coauthors = array_map( 'sanitize_text_field', $coauthors );
675
  return $this->add_coauthors( $post_id, $coauthors );
676
  }
677
  }
678
  }
679
+
680
  /**
681
+ * Add one or more co-authors as bylines for a post
682
  */
683
  function add_coauthors( $post_id, $coauthors, $append = false ) {
684
  global $current_user;
685
+
686
  $post_id = (int) $post_id;
687
  $insert = false;
688
+
689
  // if an array isn't returned, create one and populate with default author
690
  if ( !is_array( $coauthors ) || 0 == count( $coauthors ) || empty( $coauthors ) ) {
691
  $coauthors = array( $current_user->user_login );
692
  }
693
+
694
  // Add each co-author to the post meta
695
+ foreach( array_unique( $coauthors ) as $key => $author_name ){
696
+
697
+ $author = $this->get_coauthor_by( 'user_login', $author_name );
698
+ $term = $this->update_author_term( $author );
699
+ $coauthors[$key] = $term->slug;
 
 
 
 
 
 
 
 
 
 
700
  }
701
+ wp_set_post_terms( $post_id, $coauthors, $this->coauthor_taxonomy, $append );
702
  }
703
+
704
  /**
705
  * Action taken when user is deleted.
706
  * - User term is removed from all associated posts
709
  */
710
  function delete_user_action($delete_id){
711
  global $wpdb;
712
+
713
  $reassign_id = absint( $_POST['reassign_user'] );
714
+
715
  // If reassign posts, do that -- use coauthors_update_post
716
  if($reassign_id) {
717
  // Get posts belonging to deleted author
727
  }
728
  }
729
  }
730
+
731
  $delete_user = get_user_by( 'id', $delete_id );
732
  if ( is_object( $delete_user ) ) {
733
  // Delete term
746
  if ( !isset( $_REQUEST['bulk_edit'] ) || $taxonomies != "'author'" )
747
  return $terms;
748
 
749
+ global $wpdb;
750
+ $orderby = 'ORDER BY tr.term_order';
751
  $order = 'ASC';
752
  $object_ids = (int)$object_ids;
753
  $query = $wpdb->prepare( "SELECT t.slug, t.term_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN (%s) AND tr.object_id IN (%s) $orderby $order", $taxonomies, $object_ids );
754
+ $raw_coauthors = $wpdb->get_results( $query );
755
  $terms = array();
756
+ foreach( $raw_coauthors as $author ) {
757
+ $terms[] = $author->slug;
758
  }
759
 
760
  return $terms;
761
+
762
  }
763
+
764
  /**
765
+ * Filter the count_users_posts() core function to include our correct count
766
  */
767
  function filter_count_user_posts( $count, $user_id ) {
768
  $user = get_userdata( $user_id );
769
+
770
+ $term = $this->get_author_term( $user );
 
771
  // Only modify the count if the author already exists as a term
772
  if( $term && !is_wp_error( $term ) ) {
773
  $count = $term->count;
774
  }
775
+
776
  return $count;
777
  }
778
+
779
  /**
780
  * Checks to see if the current user can set authors or not
781
  */
782
  function current_user_can_set_authors( ) {
783
  global $post, $typenow;
784
+
785
+ $post_type = get_post_type();
 
 
786
  // TODO: need to fix this; shouldn't just say no if don't have post_type
787
  if( ! $post_type ) return false;
788
+
789
  $post_type_object = get_post_type_object( $post_type );
790
+ $current_user = wp_get_current_user();
791
+ if ( ! $current_user )
792
+ return false;
793
+ $can_set_authors = isset( $current_user->allcaps['edit_others_posts'] ) ? $current_user->allcaps['edit_others_posts'] : false;
794
+
795
  return apply_filters( 'coauthors_plus_edit_authors', $can_set_authors );
796
  }
797
+
798
  /**
799
  * Fix for author info not properly displaying on author pages
800
  *
802
  * the first author is NOT the same as the author for the archive,
803
  * the query_var is changed.
804
  *
805
+ * Also, we have to do some hacky WP_Query modification for guest authors
806
  */
807
+ function fix_author_page() {
808
+
809
+ if ( !is_author() )
810
+ return;
811
+
812
+ global $wp_query, $authordata;
813
+
814
+ if ( $author_name = sanitize_title( get_query_var( 'author_name' ) ) ) {
815
+ $authordata = $this->get_coauthor_by( 'user_nicename', $author_name );
816
+ if ( is_object( $authordata ) ) {
817
+ $wp_query->queried_object = $authordata;
818
+ $wp_query->queried_object_id = $authordata->ID;
819
  }
820
  }
821
  }
822
+
823
  /**
824
  * Main function that handles search-as-you-type for adding authors
825
  */
826
  function ajax_suggest() {
827
+
828
  if( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'coauthors-search' ) )
829
  die();
830
+
831
  if( empty( $_REQUEST['q'] ) )
832
  die();
833
 
834
  $search = sanitize_text_field( strtolower( $_REQUEST['q'] ) );
835
+ $ignore = array_map( 'sanitize_text_field', explode( ',', $_REQUEST['existing_authors'] ) );
836
 
837
  $authors = $this->search_authors( $search, $ignore );
838
+
839
  foreach( $authors as $author ) {
840
+ echo $author->ID ." | ". $author->user_login ." | ". $author->display_name ." | ". $author->user_email ."\n";
841
  }
842
 
843
  die();
846
 
847
  /**
848
  * Get matching authors based on a search value
849
+ */
850
  function search_authors( $search = '', $ignored_authors = array() ) {
851
 
852
+ // Since 2.7, we're searching against the term description for the fields
853
+ // instead of the user details. If the term is missing, we probably need to
854
+ // backfill with user details. Let's do this first... easier than running
855
+ // an upgrade script that could break on a lot of users
856
  $args = array(
857
  'count_total' => false,
858
  'search' => sprintf( '*%s*', $search ),
868
  $found_users = get_users( $args );
869
  remove_filter( 'pre_user_query', array( $this, 'filter_pre_user_query' ) );
870
 
871
+ foreach( $found_users as $found_user ) {
872
+ $term = $this->get_author_term( $found_user );
873
+ if ( empty( $term ) || empty( $term->description ) ) {
874
+ $this->update_author_term( $found_user );
875
+ }
876
+ }
877
+
878
+ $args = array(
879
+ 'search' => $search,
880
+ 'get' => 'all',
881
+ 'number' => 10,
882
+ );
883
+ add_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ) );
884
+ $found_terms = get_terms( $this->coauthor_taxonomy, $args );
885
+ remove_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ) );
886
+ if ( empty( $found_terms ) )
887
+ return array();
888
+
889
+ // Get the co-author objects
890
+ $found_users = array();
891
+ foreach( $found_terms as $found_term ) {
892
+ $found_user = $this->get_coauthor_by( 'user_nicename', $found_term->slug );
893
+ if ( !empty( $found_user ) )
894
+ $found_users[$found_user->user_login] = $found_user;
895
+ }
896
+
897
  // Allow users to always filter out certain users if needed (e.g. administrators)
898
  $ignored_authors = apply_filters( 'coauthors_edit_ignored_authors', $ignored_authors );
899
  foreach( $found_users as $key => $found_user ) {
900
  // Make sure the user is contributor and above (or a custom cap)
901
  if ( in_array( $found_user->user_login, $ignored_authors ) )
902
  unset( $found_users[$key] );
903
+ else if ( $found_user->type == 'wpuser' && false === $found_user->has_cap( apply_filters( 'coauthors_edit_author_cap', 'edit_posts' ) ) )
904
  unset( $found_users[$key] );
905
  }
906
  return (array) $found_users;
916
  return $user_query;
917
  }
918
 
919
+ /**
920
+ * Modify get_terms() to LIKE against the term description instead of the term name
921
+ *
922
+ * @since 3.0
923
+ */
924
+ function filter_terms_clauses( $pieces ) {
925
+
926
+ $pieces['where'] = str_replace( 't.name LIKE', 'tt.description LIKE', $pieces['where'] );
927
+ return $pieces;
928
+ }
929
+
930
  /**
931
  * Functions to add scripts and css
932
  */
933
  function enqueue_scripts($hook_suffix) {
934
  global $pagenow, $post;
935
+
936
+ if ( !$this->is_valid_page() || ! $this->is_post_type_enabled() || !$this->current_user_can_set_authors() )
 
 
937
  return;
938
+
939
  wp_enqueue_script( 'jquery' );
940
  wp_enqueue_script( 'jquery-ui-sortable' );
941
  wp_enqueue_style( 'co-authors-plus-css', COAUTHORS_PLUS_URL . 'css/co-authors-plus.css', false, COAUTHORS_PLUS_VERSION, 'all' );
942
  wp_enqueue_script( 'co-authors-plus-js', COAUTHORS_PLUS_URL . 'js/co-authors-plus.js', array('jquery', 'suggest'), COAUTHORS_PLUS_VERSION, true);
943
+
944
  $js_strings = array(
945
  'edit_label' => __( 'Edit', 'co-authors-plus' ),
946
+ 'delete_label' => __( 'Remove', 'co-authors-plus' ),
947
  'confirm_delete' => __( 'Are you sure you want to remove this author?', 'co-authors-plus' ),
948
  'input_box_title' => __( 'Click to change this author, or drag to change their position', 'co-authors-plus' ),
949
  'search_box_text' => __( 'Search for an author', 'co-authors-plus' ),
951
  );
952
  wp_localize_script( 'co-authors-plus-js', 'coAuthorsPlusStrings', $js_strings );
953
 
954
+ }
955
+
956
+ /**
957
+ * load-edit.php is when the screen has been set up
958
+ */
959
+ function load_edit() {
960
+
961
+ $screen = get_current_screen();
962
+ if ( in_array( $screen->post_type, $this->supported_post_types ) )
963
+ add_filter( 'views_' . $screen->id, array( $this, 'filter_views' ) );
964
+ }
965
+
966
+ /**
967
+ * Filter the view links that appear at the top of the Manage Posts view
968
+ *
969
+ * @since 3.0
970
+ */
971
+ function filter_views( $views ) {
972
+
973
+ if ( array_key_exists( 'mine', $views ) )
974
+ return $views;
975
+
976
+ $views = array_reverse( $views );
977
+ $all_view = array_pop( $views );
978
+ $mine_args = array(
979
+ 'author_name' => wp_get_current_user()->user_nicename,
980
+ );
981
+ if ( 'post' != get_post_type() )
982
+ $mine_args['post_type'] = get_post_type();
983
+ if ( ! empty( $_REQUEST['author_name'] ) && wp_get_current_user()->user_nicename == $_REQUEST['author_name'] )
984
+ $class = ' class="current"';
985
+ else
986
+ $class = '';
987
+ $views['mine'] = $view_mine = '<a' . $class . ' href="' . add_query_arg( $mine_args, admin_url( 'edit.php' ) ) . '">' . __( 'Mine', 'co-authors-plus' ) . '</a>';
988
+
989
+ $views['all'] = str_replace( $class, '', $all_view );
990
+ $views = array_reverse( $views );
991
+
992
+ return $views;
993
+ }
994
+
995
  /**
996
  * Adds necessary javascript variables to admin pages
997
  */
998
  function js_vars() {
999
+
1000
+ if ( ! $this->is_valid_page() || ! $this->is_post_type_enabled() || ! $this-> current_user_can_set_authors() )
1001
+ return;
1002
+ ?>
 
1003
  <script type="text/javascript">
1004
  // AJAX link used for the autosuggest
1005
  var coAuthorsPlus_ajax_suggest_link = '<?php echo add_query_arg(
1006
  array(
1007
  'action' => 'coauthors_ajax_suggest',
1008
+ 'post_type' => get_post_type(),
1009
  ),
1010
  wp_nonce_url( 'admin-ajax.php', 'coauthors-search' )
1011
  ); ?>';
1012
  </script>
1013
+ <?php
1014
+ }
1015
+
 
1016
  /**
1017
  * Helper to only add javascript to necessary pages. Avoids bloat in admin.
1018
  */
1019
  function is_valid_page() {
1020
  global $pagenow;
1021
+
1022
+ return (bool)in_array( $pagenow, $this->_pages_whitelist );
1023
+ }
1024
+
1025
  function get_post_id() {
1026
  global $post;
1027
  $post_id = 0;
1028
+
1029
  if ( is_object( $post ) ) {
1030
  $post_id = $post->ID;
1031
  }
1032
+
1033
  if( ! $post_id ) {
1034
  if ( isset( $_GET['post'] ) )
1035
  $post_id = (int) $_GET['post'];
1036
  elseif ( isset( $_POST['post_ID'] ) )
1037
  $post_id = (int) $_POST['post_ID'];
1038
  }
1039
+
1040
  return $post_id;
1041
  }
1042
+
1043
  /**
1044
  * Allows coauthors to edit the post they're coauthors of
 
 
1045
  */
1046
+ function filter_user_has_cap( $allcaps, $caps, $args ) {
1047
+
1048
+ $cap = $args[0];
1049
  $user_id = isset( $args[1] ) ? $args[1] : 0;
1050
  $post_id = isset( $args[2] ) ? $args[2] : 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
1051
 
1052
+ $obj = get_post_type_object( get_post_type( $post_id ) );
1053
+ if ( ! $obj )
1054
  return $allcaps;
1055
 
1056
+ $caps_to_modify = array(
1057
+ $obj->cap->edit_post,
1058
+ 'edit_post', // Need to filter this too, unfortunately: http://core.trac.wordpress.org/ticket/22415
1059
+ $obj->cap->edit_others_posts, // This as well: http://core.trac.wordpress.org/ticket/22417
1060
+ );
1061
+ if ( ! in_array( $cap, $caps_to_modify ) )
1062
  return $allcaps;
1063
+
1064
+ // We won't be doing any modification if they aren't already a co-author on the post
1065
+ if( ! is_coauthor_for_post( $user_id, $post_id ) )
1066
  return $allcaps;
1067
+
1068
+ $current_user = wp_get_current_user();
1069
+ if ( 'publish' == get_post_status( $post_id ) && ! empty( $current_user->allcaps[$obj->cap->edit_published_posts] ) )
1070
+ $allcaps[$obj->cap->edit_published_posts] = true;
1071
+ elseif ( 'private' == get_post_status( $post_id ) && ! empty( $current_user->allcaps[$obj->cap->edit_private_posts] ) )
1072
+ $allcaps[$obj->cap->edit_private_posts] = true;
1073
+
1074
+ $allcaps[$obj->cap->edit_others_posts] = true;
1075
+
1076
  return $allcaps;
1077
  }
1078
 
1079
+ /**
1080
+ * Get the author term for a given co-author
1081
+ *
1082
+ * @since 3.0
1083
+ *
1084
+ * @param object $coauthor The co-author object
1085
+ * @return object|false $author_term The author term on success
1086
+ */
1087
+ public function get_author_term( $coauthor ) {
1088
+
1089
+ if ( ! is_object( $coauthor ) )
1090
+ return;
1091
+
1092
+ $cache_key = 'author-term-' . $coauthor->user_nicename;
1093
+ if ( false !== ( $term = wp_cache_get( $cache_key, 'co-authors-plus' ) ) )
1094
+ return $term;
1095
+
1096
+ // See if the prefixed term is available, otherwise default to just the nicename
1097
+ $term = get_term_by( 'slug', 'cap-' . $coauthor->user_nicename, $this->coauthor_taxonomy );
1098
+ if ( ! $term ) {
1099
+ $term = get_term_by( 'slug', $coauthor->user_nicename, $this->coauthor_taxonomy );
1100
+ }
1101
+ wp_cache_set( $cache_key, $term, 'co-authors-plus' );
1102
+ return $term;
1103
+ }
1104
+
1105
+ /**
1106
+ * Update the author term for a given co-author
1107
+ *
1108
+ * @since 3.0
1109
+ *
1110
+ * @param object $coauthor The co-author object (user or guest author)
1111
+ * @return object|false $success Term object if successful, false if not
1112
+ */
1113
+ public function update_author_term( $coauthor ) {
1114
+
1115
+ if ( ! is_object( $coauthor ) )
1116
+ return false;
1117
+
1118
+ // Update the taxonomy term to include details about the user for searching
1119
+ $search_values = array();
1120
+ foreach( $this->ajax_search_fields as $search_field ) {
1121
+ $search_values[] = $coauthor->$search_field;
1122
+ }
1123
+ $term_description = implode( ' ', $search_values );
1124
+
1125
+ if ( $term = $this->get_author_term( $coauthor ) ) {
1126
+ wp_update_term( $term->term_id, $this->coauthor_taxonomy, array( 'description' => $term_description ) );
1127
+ } else {
1128
+ $coauthor_slug = 'cap-' . $coauthor->user_nicename;
1129
+ $args = array(
1130
+ 'slug' => $coauthor_slug,
1131
+ 'description' => $term_description,
1132
+ );
1133
+ $new_term = wp_insert_term( $coauthor->user_login, $this->coauthor_taxonomy, $args );
1134
+ }
1135
+ wp_cache_delete( 'author-term-' . $coauthor->user_nicename, 'co-authors-plus' );
1136
+ return $this->get_author_term( $coauthor );
1137
+ }
1138
+
1139
  /**
1140
  * Filter Edit Flow's 'ef_calendar_item_information_fields' to add co-authors
1141
  *
1142
+ * @see https://github.com/Automattic/Co-Authors-Plus/issues/2
1143
  */
1144
  function filter_ef_calendar_item_information_fields( $information_fields, $post_id ) {
1145
 
1161
  /**
1162
  * Filter Edit Flow's 'ef_story_budget_term_column_value' to add co-authors to the story budget
1163
  *
1164
+ * @see https://github.com/Automattic/Co-Authors-Plus/issues/2
1165
  */
1166
  function filter_ef_story_budget_term_column_value( $column_name, $post, $parent_term ) {
1167
 
1276
  if ( isset($reply_to) )
1277
  $message_headers .= $reply_to . "\n";
1278
 
1279
+ $notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment_id );
1280
+ $subject = apply_filters( 'comment_notification_subject', $subject, $comment_id );
1281
+ $message_headers = apply_filters( 'comment_notification_headers', $message_headers, $comment_id );
1282
 
1283
  @wp_mail( $author->user_email, $subject, $notify_message, $message_headers );
1284
  }
1371
 
1372
  return true;
1373
  }
1374
+ endif;
css/guest-authors.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ .co-authors.wp-list-table {
2
+ }
3
+
4
+ .co-authors.wp-list-table .column-display_name img.avatar {
5
+ float: left;
6
+ margin-right: 10px;
7
+ margin-top: 1px;
8
+ }
js/guest-authors.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function($){
2
+ $('.reassign-option').on('click',function(){
3
+ $('#wpbody-content input#submit').addClass('button-primary').removeAttr('disabled');
4
+ });
5
+ $('#leave-assigned-to').select2({
6
+ minimumInputLength: 2,
7
+ width: 'copy',
8
+ multiple: false,
9
+ ajax: {
10
+ url: ajaxurl,
11
+ dataType: 'json',
12
+ data: function( term, page ) {
13
+ return {
14
+ q: term,
15
+ action: 'search_coauthors_to_assign'
16
+ };
17
+ },
18
+ results: function( data, page ) {
19
+ return { results: data };
20
+ }
21
+ },
22
+ formatResult: function( object, container, query ) {
23
+ return object.display_name;
24
+ },
25
+ formatSelection: function( object, container ) {
26
+ return object.display_name;
27
+ }
28
+ }).on('change', function() {
29
+ $('#reassign-another').trigger('click');
30
+ });
31
+ });
js/jquery.aceditable.dev.js DELETED
@@ -1,1176 +0,0 @@
1
- /*
2
- * jQuery ContentEditable AC Autocompletion Plugin
3
- *
4
- * A signifigant fork of the Original AutoComplete by J�rn Zaeffererrequest
5
- *
6
- * Copyright (c) 2009 J�rn Zaeffererrequest and Aaron Raddon
7
- *
8
- * Dual licensed under the MIT and GPL licenses:
9
- * http://www.opensource.org/licenses/mit-license.php
10
- * http://www.gnu.org/licenses/gpl.html
11
- *
12
- * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
13
- */
14
-
15
- (function($) {
16
-
17
- if (window['log'] == undefined){
18
- window['log'] = {
19
- toggle: function() {},
20
- move: function() {},
21
- resize: function() {},
22
- clear: function() {},
23
- debug: function() {},
24
- info: function() {},
25
- warn: function() {},
26
- error: function() {},
27
- profile: function() {}
28
- };
29
- }
30
-
31
- $.fn.extend({
32
- autocomplete: function(urlOrData, options) {
33
- var isUrl = typeof urlOrData == "string";
34
- options = $.extend({
35
- formatEditableResult: function(row) { return '<a contenteditable="false" href="#" tabindex="-1" >@' + row[options.jsonterm] + '</a>&nbsp;';},
36
- formatResult: function(row) { return row[options.jsonterm];},
37
- formatItem: function(row) { return row[options.jsonterm]; }
38
- }, $.Autocompleter.defaults, {
39
- url: isUrl ? urlOrData : null,
40
- data: isUrl ? null : urlOrData,
41
- delay: isUrl ? $.Autocompleter.defaults.delay : 10,
42
- max: options && !options.scroll ? 10 : 150
43
- }, options);
44
-
45
- // if highlight is set to false, replace it with a do-nothing function
46
- options.highlight = options.highlight || function(value) { return value; };
47
-
48
- // if the formatMatch option is not specified, then use formatItem for backwards compatibility
49
- options.formatMatch = options.formatMatch || options.formatItem;
50
-
51
- return this.each(function() {
52
- new $.Autocompleter(this, options);
53
- });
54
- },
55
- result: function(handler) {
56
- return this.bind("result", handler);
57
- },
58
- search: function(handler) {
59
- return this.trigger("search", [handler]);
60
- },
61
- flushCache: function() {
62
- return this.trigger("flushCache");
63
- },
64
- setOptions: function(options){
65
- return this.trigger("setOptions", [options]);
66
- },
67
- unautocomplete: function() {
68
- return this.trigger("unautocomplete");
69
- },
70
- notfound: function() {
71
- return this.trigger("ace_notfound");
72
- }
73
- });
74
-
75
- $.Autocompleter = function(input, options) {
76
-
77
- var KEY = {
78
- UP: 38,
79
- DOWN: 40,
80
- DEL: 46,
81
- TAB: 9,
82
- RETURN: 13,
83
- SHIFT: 16,
84
- ESC: 27,
85
- COMMA: 188,
86
- PAGEUP: 33,
87
- PAGEDOWN: 34,
88
- BACKSPACE: 8,
89
- SPACE: 32,
90
- LEFT: 37,
91
- RIGHT: 39,
92
- AT: 50,
93
- POUND:34,
94
- DOLLAR:52,
95
- SEMIC:59
96
- };
97
- // Create $ object for input element
98
- var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
99
-
100
- var timeout;
101
- var previousValue = "";
102
- var cache = $.Autocompleter.Cache(options);
103
- var hasFocus = 0;
104
- var cursorStart = 0;
105
- var editable = null;
106
- var editableSelection, editableRange;
107
- var lastKeyPressCode;
108
- var preKeyPressCode;
109
- var prePreKeyPressCode;
110
- var hotKey = "@";
111
- var autocActive = options.hotkeymode ? false : true;
112
- var config = {
113
- mouseDownOnSelect: false
114
- };
115
- var select = $.Autocompleter.Select(options, input, selectCurrent, config);
116
-
117
- var blockSubmit;
118
-
119
- if (options.hotkeymode) {
120
- options.multiple = false;
121
- }
122
-
123
- // prevent form submit in opera when selecting with return key
124
- $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
125
- if (blockSubmit) {
126
- blockSubmit = false;
127
- return false;
128
- }
129
- });
130
- if (input.value == undefined && options.width < 1){
131
- editable = $(input)[0];
132
- options.width = $(input).parent().parent().width();
133
- options.left = $(input).parent().parent().offset().left;
134
- }
135
-
136
- // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
137
- $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
138
- // a keypress means the input has focus
139
- // avoids issue where input had focus before the autocomplete was applied
140
- hasFocus = 1;
141
- var k=event.keyCode || event.which; // keyCode == 0 in Gecko/FF on keypress
142
- //log.debug("keypress: " + k);
143
- if (k == KEY.RETURN){
144
- log.debug("in k = " + k + ' options.supressReturn =' + options.supressReturn)
145
- }
146
- // track history, probably should push/pop an array
147
- prePreKeyPressCode = preKeyPressCode;
148
- preKeyPressCode = lastKeyPressCode;
149
- lastKeyPressCode = k;
150
- if (options.hotkeymode && autocActive === false){
151
- if (k == KEY.AT && event.shiftKey && (KEY.SPACE == prePreKeyPressCode || prePreKeyPressCode == undefined)){
152
- autocActive = true;
153
- log.debug("AutoComplete now active in Hotkey mode")
154
- clearTimeout(timeout);
155
- timeout = setTimeout(onChange, options.delay);
156
- }
157
- return;
158
- }
159
- switch(k) {
160
-
161
- case KEY.UP:
162
- event.preventDefault();
163
- if ( select.visible() ) {
164
- select.prev();
165
- } else {
166
- onChange(0, true);
167
- }
168
- break;
169
-
170
- case KEY.DOWN:
171
- event.preventDefault();
172
- if ( select.visible() ) {
173
- select.next();
174
- } else {
175
- onChange(0, true);
176
- }
177
- break;
178
-
179
- case KEY.PAGEUP:
180
- event.preventDefault();
181
- if ( select.visible() ) {
182
- select.pageUp();
183
- } else {
184
- onChange(0, true);
185
- }
186
- break;
187
-
188
- case KEY.PAGEDOWN:
189
- event.preventDefault();
190
- if ( select.visible() ) {
191
- select.pageDown();
192
- } else {
193
- onChange(0, true);
194
- }
195
- break;
196
-
197
- // matches also semicolon
198
- case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
199
- case KEY.TAB:
200
- case KEY.RETURN:
201
- case KEY.RIGHT:
202
- case KEY.SEMIC:
203
- if (k == KEY.RETURN)
204
- event.preventDefault();
205
- if( selectCurrent() ) {
206
- // stop default to prevent a form submit, Opera needs special handling
207
- log.debug("in k = " + k + ' options.supressReturn =' + options.supressReturn)
208
- if (k == KEY.RETURN && options.supressReturn)
209
- event.preventDefault();
210
- blockSubmit = true;
211
- hideResultsNow();
212
- if (options.hotkeymode || options.multiple === true){
213
- log.debug("in return false")
214
- return false;
215
- }
216
- } else {
217
- log.debug("nada found? trigger notfound?");
218
- $input.trigger("ace_notfound", {});
219
- hideResultsNow();
220
- if (k == KEY.RETURN && options.supressReturn)
221
- event.preventDefault();
222
- }
223
- if (k == KEY.TAB || k == KEY.RETURN){
224
- log.debug("was tab")
225
- }
226
- break;
227
-
228
- case KEY.ESC:
229
- select.hide();
230
- break;
231
-
232
- default:
233
- if( autocActive === true ) {
234
- clearTimeout(timeout);
235
- timeout = setTimeout(onChange, options.delay);
236
- }
237
- break;
238
- }
239
- }).focus(function(){
240
- // track whether the field has focus, we shouldn't process any
241
- // results if the field no longer has focus
242
- log.debug("has focus")
243
- hasFocus++;
244
- if( autocActive === true && options.hotkeymode) {
245
- onChange(0, true);
246
- } else if (!options.hotkeymode) {
247
- if (!autocActive)
248
- autocActive = true;
249
- onChange(0, true);
250
- }
251
- }).blur(function() {
252
- hasFocus = 0;
253
- if (!config.mouseDownOnSelect) {
254
- hideResults();
255
- }
256
- }).click(function() {
257
- // show select when clicking in a focused field
258
- if ( hasFocus++ > 1 && !select.visible() ) {
259
- onChange(0, true);
260
- } else {
261
- log.debug('hasfocus = ' + hasFocus);
262
- log.debug('hasfocus = ' + (hasFocus > 1));
263
- log.debug('visible()' + (!select.visible()))
264
- log.debug('visible()' + select.visible())
265
- }
266
- }).bind('result',function(){
267
- hasFocus = 0;
268
- hideResults();
269
- }).bind("search", function() {
270
- // TODO why not just specifying both arguments?
271
- var fn = (arguments.length > 1) ? arguments[1] : null;
272
- function findValueCallback(q, data) {
273
- var result;
274
- if( data && data.length ) {
275
- for (var i=0; i < data.length; i++) {
276
- if( data[i].result.toLowerCase() == q.toLowerCase() ) {
277
- result = data[i];
278
- break;
279
- }
280
- }
281
- }
282
- if( typeof fn == "function" ) {
283
- fn(result);
284
- } else {
285
- $input.trigger("result", result && [result.data, result.value]);
286
- }
287
- }
288
- $.each(trimWords(smartVal()), function(i, value) {
289
- request(value, findValueCallback, findValueCallback);
290
- });
291
- }).bind("flushCache", function() {
292
- cache.flush();
293
- }).bind("setOptions", function() {
294
- $.extend(options, arguments[1]);
295
- // if we've updated the data, repopulate
296
- if ( "data" in arguments[1] )
297
- cache.populate();
298
- }).bind("unautocomplete", function() {
299
- select.unbind();
300
- $input.unbind();
301
- $(input.form).unbind(".autocomplete");
302
- });
303
-
304
-
305
- function selectCurrent() {
306
- var selected = select.selected();
307
- if( !selected )
308
- return false;
309
-
310
- var v = selected.result;
311
- previousValue = v;
312
-
313
- if ( options.multiple ) {
314
- var words = trimWords(smartVal());
315
- if ( words.length > 1 ) {
316
- var seperator = options.multipleSeparator.length;
317
- var cursorAt = $(input).selection().start;
318
- var wordAt, progress = 0;
319
- $.each(words, function(i, word) {
320
- progress += word.length;
321
- if (cursorAt <= progress) {
322
- wordAt = i;
323
- return false;
324
- }
325
- progress += seperator;
326
- });
327
- words[wordAt] = v;
328
- // TODO this should set the cursor to the right position, but it gets overriden somewhere
329
- //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
330
- v = words.join( options.multipleSeparator );
331
- }
332
- v += options.multipleSeparator;
333
-
334
- } else if ( options.hotkeymode) {
335
- autocActive = false;
336
- var cur = smartVal();
337
- cur = cur.substring(0,cursorStart -1);
338
- log.info("found Data! " + selected.data[0] + ' ' + selected.data[1])
339
- v = cur + options.formatResult(selected.data);
340
- if (input.value == undefined) {
341
- v = v + '<span id="cursorStart">�</span>';
342
- }
343
-
344
- //storeContentEditableCursor();
345
- }
346
- smartVal(v);
347
- hideResultsNow();
348
- $input.trigger("result", [selected.data, selected.value]);
349
- if ( options.hotkeymode && input.value == undefined)
350
- editableReturnCursor();
351
- return true;
352
- }
353
-
354
- function setupContentEditable(){
355
- if(editableSelection.getRangeAt !== undefined) {
356
- //ok
357
- // Get range (Safari 2)
358
- } else if(
359
- document.createRange &&
360
- editableSelection.anchorNode &&
361
- editableSelection.anchorOffset &&
362
- editableSelection.focusNode &&
363
- editableSelection.focusOffset
364
- ) {
365
- var temp = '';
366
- }
367
- }
368
-
369
- function storeContentEditableCursor() {
370
- // editable is the contentEditable div
371
-
372
- // Don't capture selection outside editable region
373
- var isOrContainsAnchor = false,
374
- isOrContainsFocus = false,
375
- sel = window.getSelection(),
376
- parentAnchor = sel.anchorNode,
377
- parentFocus = sel.focusNode;
378
-
379
- while(parentAnchor && parentAnchor != document.documentElement) {
380
- if(parentAnchor == editable) {
381
- isOrContainsAnchor = true;
382
- }
383
- parentAnchor = parentAnchor.parentNode;
384
- }
385
-
386
- while(parentFocus && parentFocus != document.documentElement) {
387
- if(parentFocus == editable) {
388
- isOrContainsFocus = true;
389
- }
390
- parentFocus = parentFocus.parentNode;
391
- }
392
-
393
- if(!isOrContainsAnchor || !isOrContainsFocus) {
394
- return;
395
- }
396
-
397
- editableSelection = window.getSelection();
398
-
399
- //editableSelection, editableRange;
400
- // Get range (standards)
401
- if(editableSelection.getRangeAt !== undefined) {
402
- editableRange = editableSelection.getRangeAt(0);
403
- //log.debug("in contenteditable keyup")
404
- // Get range (Safari 2)
405
- } else if(
406
- document.createRange &&
407
- editableSelection.anchorNode &&
408
- editableSelection.anchorOffset &&
409
- editableSelection.focusNode &&
410
- editableSelection.focusOffset
411
- ) {
412
- editableRange = document.createRange();
413
- editableRange.setStart(selection.anchorNode, editableSelection.anchorOffset);
414
- editableRange.setEnd(selection.focusNode, editableSelection.focusOffset);
415
- } else {
416
- // Failure here, not handled by the rest of the script.
417
- // Probably IE or some older browser
418
- // TODO: gracefully degrate to textarea?
419
- }
420
-
421
- var cursorStartSpan = document.createElement('span'),
422
- collapsed = !!editableRange.collapsed;
423
-
424
- cursorStartSpan.id = 'cursorStart';
425
- cursorStartSpan.appendChild(document.createTextNode('�'));
426
-
427
- // Insert beginning cursor marker
428
- editableRange.insertNode(cursorStartSpan);
429
-
430
- // Insert end cursor marker if any text is selected
431
- if(!collapsed) {
432
- var cursorEnd = document.createElement('span');
433
- cursorEnd.id = 'cursorEnd';
434
- editableRange.collapse();
435
- editableRange.insertNode(cursorEnd);
436
- }
437
- }
438
-
439
- function editableReturnCursor(){
440
- // Slight delay will avoid the initial selection
441
- // (at start or of contents depending on browser) being mistaken
442
- setTimeout(function() {
443
- var cursorStartSpan = document.getElementById('cursorStart'),
444
- cursorEnd = document.getElementById('cursorEnd');
445
-
446
- if (window.getSelection) { // Firefox, Safari, Opera
447
- editableSelection = window.getSelection();
448
- var range = document.createRange();
449
- range.selectNode(cursorStartSpan);
450
- // Select range
451
- editableSelection.removeAllRanges();
452
- editableSelection.addRange(range);
453
- // Delete cursor marker
454
- document.execCommand('delete', false, null);
455
- } else {
456
- if (document.body.createTextRange) { // Internet Explorer
457
- var rangeToSelect = document.body.createTextRange();
458
- rangeToSelect.moveToElementText(cursorStartSpan);
459
- rangeToSelect.select();
460
- document.selection.clear();
461
- }
462
- }
463
-
464
-
465
- // Register selection again
466
- //captureSelection();
467
- }, 10);
468
- }
469
-
470
- function onChange(crap, skipPrevCheck) {
471
- if( lastKeyPressCode == KEY.DEL ) {
472
- select.hide();
473
- return;
474
- }
475
-
476
- var currentValue = smartVal();
477
- if ( !skipPrevCheck && currentValue == previousValue ){
478
- return;
479
- }
480
-
481
- currentValue = findSearchTerm(currentValue);
482
- previousValue = currentValue;
483
- log.debug("onChange curVal = " + currentValue)
484
-
485
- if ( currentValue.length >= options.minChars) {
486
- $input.addClass(options.loadingClass);
487
- if (!options.matchCase)
488
- currentValue = currentValue.toLowerCase();
489
- request(currentValue, receiveData, hideResultsNow);
490
- } else {
491
- log.debug("in else")
492
- stopLoading();
493
- if (options.startmsg != null) {
494
- select.emptyList();
495
- select.display({}, null);
496
- select.show();
497
- } else {
498
- select.hide();
499
- }
500
- }
501
- };
502
-
503
- function trimWords(value) {
504
- if (!value)
505
- return [""];
506
- if (!options.multiple && !options.hotkeymode) {
507
- return [$.trim(value)];
508
- } else if (options.multiple) {
509
- return $.map(value.split(options.multipleSeparator), function(word) {
510
- return $.trim(value).length ? $.trim(word) : null;
511
- });
512
- } else if (options.hotkeymode) {
513
- log.error("should not get here, remove this section, not user")
514
- return fake.raise.error;
515
- }
516
- }
517
-
518
- // find word currently being searched for, anything after
519
- // previous results, or non query text
520
- function findSearchTerm(value) {
521
- if ( !options.multiple && !options.hotkeymode) {
522
- return value;
523
- } else if (options.multiple) {
524
- var words = trimWords(value);
525
- if (words.length == 1)
526
- return words[0];
527
- var cursorAt = $(input).selection().start;
528
- if (cursorAt == value.length) {
529
- words = trimWords(value)
530
- } else {
531
- words = trimWords(value.replace(value.substring(cursorAt), ""));
532
- }
533
- return words[words.length - 1];
534
- // hotkeymode
535
- } else {
536
- if (value && value.lastIndexOf('@') >= 0){
537
- cursorStart = value.lastIndexOf('@') + 1;
538
- } else {
539
- log.error("found no @ in hotkeymode?" + value)
540
- cursorStart = value.length + 2;
541
- }
542
- value = value.substring(cursorStart);
543
- log.info("findSearchTerm: cursorStart,val " + cursorStart + ', ' + value)
544
- return value; //.trim();
545
- }
546
- }
547
-
548
- // fills in the input box w/the first match (assumed to be the best match)
549
- // q: the term entered
550
- // sValue: the first matching result
551
- function autoFill(q, sValue){
552
- // autofill in the complete box w/the first match as long as the user hasn't entered in more data
553
- // if the last user key pressed was backspace, don't autofill
554
- if( options.autoFill && (findSearchTerm(smartVal()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
555
- // fill in the value (keep the case the user has typed)
556
- smartVal(smartVal() + sValue.substring(findSearchTerm(previousValue).length));
557
- // select the portion of the value not typed by the user (so the next character will erase)
558
- $(input).selection(previousValue.length, previousValue.length + sValue.length);
559
- }
560
- };
561
-
562
- // replace .val() with something to handle content-editable fields
563
- function smartVal(val) {
564
- var field = $input[0];
565
- //if form input field, vs contenteditable div
566
- if (field.value != undefined){
567
- if (val != undefined) {
568
- return $input.val(val);
569
- } else {
570
- return $input.val();
571
- }
572
- } else {
573
- if (val != undefined) {
574
- return $input.html(val);
575
- } else {
576
- val = $input.html();
577
- if (val != undefined && val.length != undefined && val.length > 0) {
578
- val = $.trim(val);
579
- } else {
580
- val = ''
581
- }
582
- var endval = '', li = 0;
583
- // replace <br> or &nbsp; BUT, only in last bit (10) or past hotkey
584
- // contenteditable appends <br> to end a lot, or the last space as &nbsp;
585
- if (val.lastIndexOf('@') > 0 || val.length > 10){
586
- li = val.lastIndexOf('@') > 0 ? val.lastIndexOf('@') + 1 : val.length - 10;
587
- endval = val.substring(li);
588
- val = val.substring(0,val.length - endval.length);
589
- //log.debug('used substring @ ' + val)
590
- } else {
591
- endval = val;
592
- val = '';
593
- }
594
- // only clean up end of markup, where they are doing insertion?
595
- endval = endval.replace("<br>",'').replace("<br/>",'').replace("&nbsp;",' ');
596
- val = val + endval;
597
- //log.debug("smartVal endval = " + escape(endval) + ' val= ' + escape(val))
598
- return val;
599
- }
600
- }
601
- }
602
-
603
- function hideResults() {
604
- clearTimeout(timeout);
605
- timeout = setTimeout(hideResultsNow, 200);
606
- };
607
-
608
- function hideResultsNow() {
609
- var wasVisible = select.visible();
610
- autocActive = false;
611
- select.hide();
612
- clearTimeout(timeout);
613
- stopLoading();
614
- if (options.mustMatch) {
615
- // call search and run callback
616
- $input.search(
617
- function (result){
618
- // if no value found, clear the input box
619
- if( !result ) {
620
- if (options.multiple) {
621
- var words = trimWords(smartVal()).slice(0, -1);
622
- smartVal( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
623
- } else if (options.hotkeymode) {
624
- smartVal( "" );
625
- $input.trigger("result", null);
626
- } else {
627
- smartVal( "" );
628
- $input.trigger("result", null);
629
- }
630
- }
631
- }
632
- );
633
- }
634
- };
635
-
636
- function receiveData(q, data) {
637
- if ( data && data.length && hasFocus ) {
638
- stopLoading();
639
- select.display(data, q);
640
- autoFill(q, data[0].value);
641
- select.show();
642
- } else {
643
- hideResultsNow();
644
- }
645
- };
646
-
647
- function request(term, success, failure) {
648
- log.debug("in request term = " + term)
649
- if (!options.matchCase)
650
- term = term.toLowerCase();
651
-
652
- log.debug("in request term2 = " + term)
653
- var data = cache.load(term);
654
- log.debug("in request term3 = " + data)
655
- // recieve the cached data
656
- if (data && data.length) {
657
- log.debug('found cache, not loading ' + term)
658
- success(term, data);
659
- return;
660
- // if an AJAX url has been supplied, try loading the data now
661
- } else if( (typeof options.url == "string") && (options.url.length > 0) ){
662
-
663
- var extraParams = {
664
- timestamp: +new Date()
665
- };
666
- $.each(options.extraParams, function(key, param) {
667
- extraParams[key] = typeof param == "function" ? param() : param;
668
- });
669
- var found = false;
670
- log.debug("calling ajax, term = " + term, ' dataType = ' + options.dataType)
671
- $.ajax({
672
- // try to leverage ajaxQueue plugin to abort previous requests
673
- mode: "abort",
674
- // limit abortion to this input
675
- port: "autocomplete" + input.name,
676
- dataType: options.dataType,
677
- url: options.url,
678
- data: $.extend({
679
- q: term,
680
- limit: options.max
681
- }, extraParams),
682
- success: function(data) {
683
- if (data.length > 0){
684
- found = true;
685
- var parsed = options.parse && options.parse(data) || parse_json(data);
686
- cache.add(term, parsed);
687
- success(term, parsed);
688
- }
689
- }
690
- });
691
- if (found === true){
692
- log.debug("returning?")
693
- return;
694
- }
695
- }
696
- log.debug("after load in request?")
697
- select.emptyList();
698
- if (options.noresultsmsg != null) {
699
- stopLoading();
700
- select.display({}, term);
701
- select.show();
702
- } else {
703
- // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
704
- failure(term);
705
- }
706
- };
707
-
708
- function parse_json(json){
709
- var parsed = [];
710
- log.debug("parsing json, len=" + json.length)
711
- for ( var i = 0, ol = json.length; i < ol; i++ ) {
712
- parsed[i] = {
713
- data: json[i],
714
- value: json[i][options.jsonterm],
715
- result: options.formatResult && options.formatResult(json[i]) || json[i][options.jsonterm]
716
- };
717
- }
718
- return parsed;
719
- }
720
-
721
- function stopLoading() {
722
- $input.removeClass(options.loadingClass);
723
- };
724
-
725
- };
726
-
727
- $.Autocompleter.defaults = {
728
- inputClass: "ac_input",
729
- resultsClass: "ac_results",
730
- loadingClass: "ac_loading",
731
- minChars: 1,
732
- live:false,
733
- startmsg: 'Start typing to get options...',
734
- msgonenter:false,
735
- endmsg: null,
736
- noresultsmsg: null,
737
- delay: 400,
738
- matchCase: false,
739
- matchSubset: true,
740
- matchContains: false,
741
- cacheLength: 10,
742
- supressReturn: false,
743
- max: 100,
744
- mustMatch: false,
745
- extraParams: {},
746
- jsonterm: 0, // 'name' ??
747
- formatResult: null,//placeholder, function in above options
748
- formatItem: null, //placeholder, function in above options
749
- dataType: 'json',
750
- selectFirst: true,
751
- formatMatch: null,
752
- autoFill: false,
753
- width: 0,
754
- left: 0,
755
- multiple: false,
756
- multipleSeparator: ", ",
757
- hotkeymode: false,
758
- highlight: function(value, term) {
759
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
760
- },
761
- scroll: true,
762
- scrollHeight: 180
763
- };
764
-
765
- $.Autocompleter.Cache = function(options) {
766
-
767
- var data = {};
768
- var length = 0;
769
-
770
- function matchSubset(s, sub) {
771
- if (!options.matchCase)
772
- s = s.toLowerCase();
773
- var i = s.indexOf(sub);
774
- if (options.matchContains == "word"){
775
- i = s.toLowerCase().search("\\b" + sub.toLowerCase());
776
- }
777
- if (i == -1) return false;
778
- return i == 0 || options.matchContains;
779
- };
780
-
781
- function add(q, value) {
782
- if (length > options.cacheLength){
783
- flush();
784
- }
785
- if (!data[q]){
786
- length++;
787
- }
788
- data[q] = value;
789
- }
790
-
791
- function populate(){
792
- if( !options.data ) return false;
793
- // track the matches
794
- var stMatchSets = {},
795
- nullData = 0;
796
-
797
- // no url was specified, we need to adjust the cache length to make sure it fits the local data store
798
- if( !options.url ) options.cacheLength = 1;
799
-
800
- // track all options for minChars = 0
801
- stMatchSets[""] = [];
802
-
803
- // loop through the array and create a lookup structure
804
- for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
805
- var rawValue = options.data[i];
806
- // if rawValue is a string, make an array otherwise just reference the array
807
- rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
808
-
809
- var value = options.formatMatch(rawValue, i+1, options.data.length);
810
- if ( value === false || value === undefined)
811
- continue;
812
-
813
- var firstChar = value.charAt(0).toLowerCase();
814
- // if no lookup array for this character exists, look it up now
815
- if( !stMatchSets[firstChar] )
816
- stMatchSets[firstChar] = [];
817
-
818
- // if the match is a string
819
- var row = {
820
- value: value,
821
- data: rawValue,
822
- result: options.formatResult && options.formatResult(rawValue) || value
823
- };
824
-
825
- // push the current match into the set list
826
- stMatchSets[firstChar].push(row);
827
-
828
- // keep track of minChars zero items
829
- if ( nullData++ < options.max ) {
830
- stMatchSets[""].push(row);
831
- }
832
- };
833
-
834
- // add the data items to the cache
835
- $.each(stMatchSets, function(i, value) {
836
- // increase the cache size
837
- options.cacheLength++;
838
- // add to the cache
839
- add(i, value);
840
- });
841
- }
842
-
843
- // populate any existing data
844
- setTimeout(populate, 25);
845
-
846
- function flush(){
847
- data = {};
848
- length = 0;
849
- }
850
-
851
- return {
852
- flush: flush,
853
- add: add,
854
- populate: populate,
855
- load: function(q) {
856
- if (!options.cacheLength || !length)
857
- return null;
858
- /*
859
- * if dealing w/local data and matchContains than we must make sure
860
- * to loop through all the data collections looking for matches
861
- */
862
- if( !options.url && options.matchContains ){
863
- // track all matches
864
- var csub = [];
865
- // loop through all the data grids for matches
866
- for( var k in data ){
867
- // don't search through the stMatchSets[""] (minChars: 0) cache
868
- // this prevents duplicates
869
- if( k.length > 0 ){
870
- var c = data[k];
871
- //log.debug("629 " + c + ' ' + k + ' ' + q)
872
- $.each(c, function(i, x) {
873
- // if we've got a match, add it to the array
874
- if (matchSubset(x.value, q)) {
875
- csub.push(x);
876
- }
877
- });
878
- }
879
- }
880
- return csub;
881
- } else
882
- // if the exact item exists, use it
883
- if (data[q]){
884
- return data[q];
885
- } else
886
- if (options.matchSubset) {
887
- for (var i = q.length - 1; i >= options.minChars; i--) {
888
- var c = data[q.substr(0, i)];
889
- if (c) {
890
- var csub = [];
891
- $.each(c, function(i, x) {
892
- if (matchSubset(x.value, q)) {
893
- csub[csub.length] = x;
894
- }
895
- });
896
- return csub;
897
- }
898
- }
899
- }
900
- return null;
901
- }
902
- };
903
- };
904
-
905
- $.Autocompleter.Select = function (options, input, select, config) {
906
- var CLASSES = {
907
- ACTIVE: "ac_over"
908
- };
909
-
910
- var listItems,
911
- active = -1,
912
- data,
913
- term = "",
914
- needsInit = true,
915
- element,
916
- list;
917
-
918
- // Create results
919
- function init() {
920
- if (!needsInit)
921
- return;
922
- element = $("<div/>")
923
- .hide()
924
- .addClass(options.resultsClass)
925
- .css("position", "absolute")
926
- .appendTo(document.body);
927
- list = $("<ul/>").appendTo(element).css({
928
- width: typeof options.width == "string" || options.width > 0 ? options.width -2 : $(input).width() -2
929
- }).mouseover( function(event) {
930
- if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
931
- active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
932
- $(target(event)).addClass(CLASSES.ACTIVE);
933
- }
934
- }).click(function(event) {
935
- $(target(event)).addClass(CLASSES.ACTIVE);
936
- select();
937
- // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
938
- input.focus();
939
- return false;
940
- }).mousedown(function() {
941
- config.mouseDownOnSelect = true;
942
- }).mouseup(function() {
943
- config.mouseDownOnSelect = false;
944
- });
945
-
946
- if( options.width > 0 )
947
- element.css("width", options.width);
948
-
949
- needsInit = false;
950
- }
951
-
952
- function target(event) {
953
- var element = event.target;
954
- while(element && element.tagName != "LI")
955
- element = element.parentNode;
956
- // more fun with IE, sometimes event.target is empty, just ignore it then
957
- if(!element)
958
- return [];
959
- return element;
960
- }
961
-
962
- function moveSelect(step) {
963
- listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
964
- movePosition(step);
965
- var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
966
- if(options.scroll) {
967
- var offset = 0;
968
- listItems.slice(0, active).each(function() {
969
- offset += this.offsetHeight;
970
- });
971
- if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
972
- list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
973
- } else if(offset < list.scrollTop()) {
974
- list.scrollTop(offset);
975
- }
976
- }
977
- };
978
-
979
- function movePosition(step) {
980
- active += step;
981
- if (active < 0) {
982
- active = listItems.size() - 1;
983
- } else if (active >= listItems.size()) {
984
- active = 0;
985
- }
986
- }
987
-
988
- function limitNumberOfItems(available) {
989
- return options.max && options.max < available
990
- ? options.max
991
- : available;
992
- }
993
-
994
- function fillList(q) {
995
- list.empty();
996
- var max = limitNumberOfItems(data.length);
997
- for (var i=0; i < max; i++) {
998
- if (!data[i])
999
- continue;
1000
- var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
1001
- if ( formatted === false )
1002
- continue;
1003
- var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
1004
- $.data(li, "ac_data", data[i]);
1005
- }
1006
- listItems = list.find("li");
1007
- if ( options.selectFirst ) {
1008
- listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
1009
- active = 0;
1010
- }
1011
- if (options.startmsg && (max == 0 || max == undefined) && q == null) {
1012
- var li = $("<li/>").html( options.startmsg )
1013
- .click(function(){
1014
- $(input).trigger("ac_start_msg_click");
1015
- }).addClass("start_msg").appendTo(list)[0];
1016
- $.data(li, "start_msg", data[max + 1]);
1017
- }
1018
- if (options.noresultsmsg && (max == 0 || max == undefined) && q != null) {
1019
- var val_noresult = options.noresultsmsg;
1020
- if( typeof options.noresultsmsg == "function" ) {
1021
- val_noresult = fn(q);
1022
- } else if (options.noresultsmsg.indexOf('{q}' > 0)) {
1023
- val_noresult = val_noresult.replace('{q}',q);
1024
- }
1025
- var li = $("<li/>").html( val_noresult )
1026
- .click(function(){
1027
- $(input).trigger("ac_noresult_click",q);
1028
- }).addClass("noresult_msg").appendTo(list)[0];
1029
- $.data(li, "noresult_msg", data[max + 1]);
1030
- }
1031
- if (options.endmsg && (max != undefined && max > 0)) {
1032
- var li = $("<li/>").html( options.endmsg )
1033
- .click(function(){
1034
- $(input).trigger("ac_end_message_click",q);
1035
- }).addClass("end_msg").appendTo(list)[0];
1036
- $.data(li, "end_msg", data[max + 1]);
1037
- }
1038
- // apply bgiframe if available
1039
- if ( $.fn.bgiframe )
1040
- list.bgiframe();
1041
- }
1042
-
1043
- return {
1044
- display: function(d, q) {
1045
- init();
1046
- data = d;
1047
- term = q;
1048
- fillList(q);
1049
- },
1050
- next: function() {
1051
- moveSelect(1);
1052
- },
1053
- prev: function() {
1054
- moveSelect(-1);
1055
- },
1056
- pageUp: function() {
1057
- if (active != 0 && active - 8 < 0) {
1058
- moveSelect( -active );
1059
- } else {
1060
- moveSelect(-8);
1061
- }
1062
- },
1063
- pageDown: function() {
1064
- if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
1065
- moveSelect( listItems.size() - 1 - active );
1066
- } else {
1067
- moveSelect(8);
1068
- }
1069
- },
1070
- hide: function() {
1071
- log.info("hiding?")
1072
- element && element.hide();
1073
- listItems && listItems.removeClass(CLASSES.ACTIVE);
1074
- active = -1;
1075
- },
1076
- visible : function() {
1077
- return element && element.is(":visible");
1078
- },
1079
- current: function() {
1080
- return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
1081
- },
1082
- show: function() {
1083
- var offset = $(input).offset();
1084
- element.css({
1085
- width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
1086
- top: offset.top + input.offsetHeight,
1087
- left: options.left > 0 ? options.left : offset.left
1088
- }).show();
1089
-
1090
- if(options.scroll) {
1091
- list.scrollTop(0);
1092
- list.css({
1093
- maxHeight: options.scrollHeight,
1094
- overflow: 'auto'
1095
- });
1096
-
1097
- if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
1098
- var listHeight = 0;
1099
- listItems.each(function() {
1100
- listHeight += this.offsetHeight;
1101
- });
1102
- var scrollbarsVisible = listHeight > options.scrollHeight;
1103
- list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
1104
- if (!scrollbarsVisible) {
1105
- // IE doesn't recalculate width when scrollbar disappears
1106
- listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
1107
- }
1108
- }
1109
- }
1110
- },
1111
- selected: function() {
1112
- var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
1113
- return selected && selected.length && $.data(selected[0], "ac_data");
1114
- },
1115
- emptyList: function (){
1116
- list && list.empty();
1117
- },
1118
- unbind: function() {
1119
- element && element.remove();
1120
- }
1121
- };
1122
- };
1123
- $.fn.selection = function(start, end) {
1124
- if (start !== undefined) {
1125
- log.debug("in selection, no start/end")
1126
- return this.each(function() {
1127
- if( this.createTextRange ){
1128
- var selRange = this.createTextRange();
1129
- if (end === undefined || start == end) {
1130
- selRange.move("character", start);
1131
- selRange.select();
1132
- } else {
1133
- selRange.collapse(true);
1134
- selRange.moveStart("character", start);
1135
- selRange.moveEnd("character", end);
1136
- selRange.select();
1137
- }
1138
- } else if( this.setSelectionRange ){
1139
- this.setSelectionRange(start, end);
1140
- } else if( this.selectionStart ){
1141
- this.selectionStart = start;
1142
- this.selectionEnd = end;
1143
- }
1144
- });
1145
- }
1146
- var field = this[0];
1147
- if ( field.createTextRange ) {
1148
- log.debug("in selection with createTextRange")
1149
- var range = document.selection.createRange(),
1150
- orig = field.value,
1151
- teststring = "<->",
1152
- textLength = range.text.length;
1153
- range.text = teststring;
1154
- var caretAt = field.value.indexOf(teststring);
1155
- field.value = orig;
1156
- this.selection(caretAt, caretAt + textLength);
1157
- return {
1158
- start: caretAt,
1159
- end: caretAt + textLength
1160
- }
1161
- } else if( field.selectionStart !== undefined ){
1162
- return {
1163
- start: field.selectionStart,
1164
- end: field.selectionEnd
1165
- }
1166
- } else {
1167
- if(field.getRangeAt !== undefined) {
1168
- log.error("ah, getRangeAt, ?")
1169
- } else {
1170
- log.error("ah, none of above, ?")
1171
- }
1172
-
1173
- }
1174
- };
1175
-
1176
- })(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/co-authors-plus-de_DE.mo ADDED
Binary file
co-authors-plus.pot → languages/co-authors-plus-de_DE.po RENAMED
@@ -8,90 +8,103 @@ msgstr ""
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
12
- "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
- "Language-Team: LANGUAGE <LL@li.org>\n"
 
 
14
 
15
  #: co-authors-plus.php:39
16
  msgid " and "
17
- msgstr ""
18
 
19
  #: co-authors-plus.php:216
20
  msgid "Post Authors"
21
- msgstr ""
22
 
23
  #: co-authors-plus.php:254
24
  msgid ""
25
  "<strong>Note:</strong> To edit post authors, please enable javascript or use "
26
  "a javascript-capable browser"
27
  msgstr ""
 
 
28
 
29
  #: co-authors-plus.php:261 co-authors-plus.php:743
30
  msgid ""
31
  "Click on an author to change them. Drag to change their order. Click on "
32
  "<strong>Remove</strong> to remove them."
33
  msgstr ""
 
 
34
 
35
  #: co-authors-plus.php:291
36
  msgid "Authors"
37
- msgstr ""
38
 
39
  #: co-authors-plus.php:327
40
  msgid "Posts"
41
- msgstr ""
42
 
43
  #: co-authors-plus.php:343
44
  msgid "View posts by this author"
45
- msgstr ""
46
 
47
  #: co-authors-plus.php:738
48
  msgid "Edit"
49
- msgstr ""
50
 
51
  #: co-authors-plus.php:739
52
  msgid "Remove"
53
- msgstr ""
54
 
55
  #: co-authors-plus.php:740
56
  msgid "Are you sure you want to remove this author?"
57
- msgstr ""
58
 
59
  #: co-authors-plus.php:741
60
  msgid "Click to change this author, or drag to change their position"
61
  msgstr ""
 
 
62
 
63
  #: co-authors-plus.php:742
64
  msgid "Search for an author"
65
- msgstr ""
66
 
67
  #: template-tags.php:71
68
  msgid ""
69
  "No post ID provided for CoAuthorsIterator constructor. Are you not in a loop "
70
  "or is $post not set?"
71
  msgstr ""
 
 
72
 
73
  #: template-tags.php:189 template-tags.php:329
74
  msgid "Posts by %s"
75
- msgstr ""
76
 
77
  #: template-tags.php:230
78
  msgid "Visit %s&#8217;s website"
79
- msgstr ""
80
 
81
  #. Plugin Name of the plugin/theme
82
  msgid "Co-Authors Plus"
83
- msgstr ""
84
 
85
  #. Plugin URI of the plugin/theme
86
  msgid "http://wordpress.org/extend/plugins/co-authors-plus/"
87
- msgstr ""
88
 
89
  #. Description of the plugin/theme
90
  msgid ""
91
  "Allows multiple authors to be assigned to a post. This plugin is an extended "
92
  "version of the Co-Authors plugin developed by Weston Ruter."
93
  msgstr ""
 
 
 
94
 
95
  #. Author of the plugin/theme
96
  msgid "Mohammad Jangda, Daniel Bachhuber"
97
- msgstr ""
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-08-20 23:57+0100\n"
12
+ "Last-Translator: \n"
13
+ "Language-Team: \n"
14
+ "Language: deutsch\n"
15
+ "X-Poedit-SourceCharset: UTF-8\n"
16
 
17
  #: co-authors-plus.php:39
18
  msgid " and "
19
+ msgstr "und"
20
 
21
  #: co-authors-plus.php:216
22
  msgid "Post Authors"
23
+ msgstr "Artikel Autoren"
24
 
25
  #: co-authors-plus.php:254
26
  msgid ""
27
  "<strong>Note:</strong> To edit post authors, please enable javascript or use "
28
  "a javascript-capable browser"
29
  msgstr ""
30
+ "<strong>Notiz:</strong> Um Artikel Autoren zu ändern, bitte JavaScript "
31
+ "aktivieren oder einen JavaScript fähigen Browser nutzen"
32
 
33
  #: co-authors-plus.php:261 co-authors-plus.php:743
34
  msgid ""
35
  "Click on an author to change them. Drag to change their order. Click on "
36
  "<strong>Remove</strong> to remove them."
37
  msgstr ""
38
+ "Klicke auf einen Autor um sie zu ändern. Durch verschieben änderst du ihre "
39
+ "Reihenfolge. Klicke auf <strong>Entfernen</strong> um sie zu entfernen."
40
 
41
  #: co-authors-plus.php:291
42
  msgid "Authors"
43
+ msgstr "Autoren"
44
 
45
  #: co-authors-plus.php:327
46
  msgid "Posts"
47
+ msgstr "Artikel"
48
 
49
  #: co-authors-plus.php:343
50
  msgid "View posts by this author"
51
+ msgstr "Schaue dir Artikel von diesem Autor an"
52
 
53
  #: co-authors-plus.php:738
54
  msgid "Edit"
55
+ msgstr "Bearbeiten"
56
 
57
  #: co-authors-plus.php:739
58
  msgid "Remove"
59
+ msgstr "Entfernen"
60
 
61
  #: co-authors-plus.php:740
62
  msgid "Are you sure you want to remove this author?"
63
+ msgstr "Bist du sicher, dass du diesen Autor entfernen möchtest?"
64
 
65
  #: co-authors-plus.php:741
66
  msgid "Click to change this author, or drag to change their position"
67
  msgstr ""
68
+ "Klicken um diesen Autor zu ändern oder verschiebe sie um ihre Position zu "
69
+ "ändern"
70
 
71
  #: co-authors-plus.php:742
72
  msgid "Search for an author"
73
+ msgstr "Suche nach einem Autor"
74
 
75
  #: template-tags.php:71
76
  msgid ""
77
  "No post ID provided for CoAuthorsIterator constructor. Are you not in a loop "
78
  "or is $post not set?"
79
  msgstr ""
80
+ "Keine Artikel ID für den CoAuthorsIterator Konstruktor verfügbar. Bist du "
81
+ "nicht im Loop oder wurde $post nicht gesetzt?"
82
 
83
  #: template-tags.php:189 template-tags.php:329
84
  msgid "Posts by %s"
85
+ msgstr "Artikel von %s"
86
 
87
  #: template-tags.php:230
88
  msgid "Visit %s&#8217;s website"
89
+ msgstr "Besuche %s&#8217;s Website"
90
 
91
  #. Plugin Name of the plugin/theme
92
  msgid "Co-Authors Plus"
93
+ msgstr "Co-Authors Plus"
94
 
95
  #. Plugin URI of the plugin/theme
96
  msgid "http://wordpress.org/extend/plugins/co-authors-plus/"
97
+ msgstr "http://wordpress.org/extend/plugins/co-authors-plus/"
98
 
99
  #. Description of the plugin/theme
100
  msgid ""
101
  "Allows multiple authors to be assigned to a post. This plugin is an extended "
102
  "version of the Co-Authors plugin developed by Weston Ruter."
103
  msgstr ""
104
+ "Erlaubt es mehrere Autoren einem Artikel zuzuweisen. Dieses Plugin ist eine "
105
+ "erweiterte Version des Co-Authors Plugins, dass von Weston Ruter entwickelt "
106
+ "wurde."
107
 
108
  #. Author of the plugin/theme
109
  msgid "Mohammad Jangda, Daniel Bachhuber"
110
+ msgstr "Mohammad Jangda, Daniel Bachhuber"
languages/co-authors-plus.pot CHANGED
@@ -1,80 +1,555 @@
1
- # Copyright (C) 2010 Co-Authors Plus
2
  # This file is distributed under the same license as the Co-Authors Plus package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Co-Authors Plus 2.6.1\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/co-authors-plus\n"
7
- "POT-Creation-Date: 2011-12-30 05:00:31+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #: co-authors-plus.php:39
16
- msgid " and "
17
- msgstr ""
18
-
19
- #: co-authors-plus.php:216
20
- msgid "Post Authors"
21
  msgstr ""
22
 
23
- #: co-authors-plus.php:254
24
  msgid ""
25
  "<strong>Note:</strong> To edit post authors, please enable javascript or use "
26
  "a javascript-capable browser"
27
  msgstr ""
28
 
29
- #: co-authors-plus.php:261 co-authors-plus.php:743
30
  msgid ""
31
  "Click on an author to change them. Drag to change their order. Click on "
32
  "<strong>Remove</strong> to remove them."
33
  msgstr ""
34
 
35
- #: co-authors-plus.php:291
36
- msgid "Authors"
37
- msgstr ""
38
-
39
- #: co-authors-plus.php:327
40
  msgid "Posts"
41
  msgstr ""
42
 
43
- #: co-authors-plus.php:343
44
  msgid "View posts by this author"
45
  msgstr ""
46
 
47
- #: co-authors-plus.php:738
 
 
 
 
48
  msgid "Edit"
49
  msgstr ""
50
 
51
- #: co-authors-plus.php:739
52
  msgid "Remove"
53
  msgstr ""
54
 
55
- #: co-authors-plus.php:740
56
  msgid "Are you sure you want to remove this author?"
57
  msgstr ""
58
 
59
- #: co-authors-plus.php:741
60
  msgid "Click to change this author, or drag to change their position"
61
  msgstr ""
62
 
63
- #: co-authors-plus.php:742
64
  msgid "Search for an author"
65
  msgstr ""
66
 
67
- #: template-tags.php:71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  msgid ""
69
  "No post ID provided for CoAuthorsIterator constructor. Are you not in a loop "
70
  "or is $post not set?"
71
  msgstr ""
72
 
73
- #: template-tags.php:189 template-tags.php:329
 
 
 
 
74
  msgid "Posts by %s"
75
  msgstr ""
76
 
77
- #: template-tags.php:230
78
  msgid "Visit %s&#8217;s website"
79
  msgstr ""
80
 
@@ -93,5 +568,5 @@ msgid ""
93
  msgstr ""
94
 
95
  #. Author of the plugin/theme
96
- msgid "Mohammad Jangda, Daniel Bachhuber"
97
  msgstr ""
1
+ # Copyright (C) 2012 Co-Authors Plus
2
  # This file is distributed under the same license as the Co-Authors Plus package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Co-Authors Plus 3.0-beta1\n"
6
  "Report-Msgid-Bugs-To: http://wordpress.org/tag/co-authors-plus\n"
7
+ "POT-Creation-Date: 2012-11-12 05:25:35+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
+ #: co-authors-plus.php:314 co-authors-plus.php:408 co-authors-plus.php:1173
16
+ msgid "Authors"
 
 
 
 
17
  msgstr ""
18
 
19
+ #: co-authors-plus.php:369
20
  msgid ""
21
  "<strong>Note:</strong> To edit post authors, please enable javascript or use "
22
  "a javascript-capable browser"
23
  msgstr ""
24
 
25
+ #: co-authors-plus.php:376 co-authors-plus.php:973
26
  msgid ""
27
  "Click on an author to change them. Drag to change their order. Click on "
28
  "<strong>Remove</strong> to remove them."
29
  msgstr ""
30
 
31
+ #: co-authors-plus.php:450 php/class-coauthors-wp-list-table.php:137
 
 
 
 
32
  msgid "Posts"
33
  msgstr ""
34
 
35
+ #: co-authors-plus.php:467
36
  msgid "View posts by this author"
37
  msgstr ""
38
 
39
+ #: co-authors-plus.php:502
40
+ msgid "No co-author exists for that term"
41
+ msgstr ""
42
+
43
+ #: co-authors-plus.php:968 php/class-coauthors-wp-list-table.php:188
44
  msgid "Edit"
45
  msgstr ""
46
 
47
+ #: co-authors-plus.php:969
48
  msgid "Remove"
49
  msgstr ""
50
 
51
+ #: co-authors-plus.php:970
52
  msgid "Are you sure you want to remove this author?"
53
  msgstr ""
54
 
55
+ #: co-authors-plus.php:971
56
  msgid "Click to change this author, or drag to change their position"
57
  msgstr ""
58
 
59
+ #: co-authors-plus.php:972
60
  msgid "Search for an author"
61
  msgstr ""
62
 
63
+ #: co-authors-plus.php:1008
64
+ msgid "Mine"
65
+ msgstr ""
66
+
67
+ #: co-authors-plus.php:1245
68
+ msgid "New comment on your post \"%s\""
69
+ msgstr ""
70
+
71
+ #. translators: 1: comment author, 2: author IP, 3: author domain
72
+ #: co-authors-plus.php:1247 co-authors-plus.php:1364
73
+ msgid "Author : %1$s (IP: %2$s , %3$s)"
74
+ msgstr ""
75
+
76
+ #: co-authors-plus.php:1248 co-authors-plus.php:1365
77
+ msgid "E-mail : %s"
78
+ msgstr ""
79
+
80
+ #: co-authors-plus.php:1249 co-authors-plus.php:1259 co-authors-plus.php:1268
81
+ #: co-authors-plus.php:1351 co-authors-plus.php:1358 co-authors-plus.php:1366
82
+ msgid "URL : %s"
83
+ msgstr ""
84
+
85
+ #: co-authors-plus.php:1250 co-authors-plus.php:1367
86
+ msgid "Whois : http://whois.arin.net/rest/ip/%s"
87
+ msgstr ""
88
+
89
+ #: co-authors-plus.php:1251 co-authors-plus.php:1368
90
+ msgid "Comment: "
91
+ msgstr ""
92
+
93
+ #: co-authors-plus.php:1252
94
+ msgid "You can see all comments on this post here: "
95
+ msgstr ""
96
+
97
+ #. translators: 1: blog name, 2: post title
98
+ #: co-authors-plus.php:1254
99
+ msgid "[%1$s] Comment: \"%2$s\""
100
+ msgstr ""
101
+
102
+ #: co-authors-plus.php:1256
103
+ msgid "New trackback on your post \"%s\""
104
+ msgstr ""
105
+
106
+ #. translators: 1: website name, 2: author IP, 3: author domain
107
+ #. translators: 1: comment author, 2: author IP, 3: author domain
108
+ #: co-authors-plus.php:1258 co-authors-plus.php:1267
109
+ msgid "Website: %1$s (IP: %2$s , %3$s)"
110
+ msgstr ""
111
+
112
+ #: co-authors-plus.php:1260 co-authors-plus.php:1269
113
+ msgid "Excerpt: "
114
+ msgstr ""
115
+
116
+ #: co-authors-plus.php:1261
117
+ msgid "You can see all trackbacks on this post here: "
118
+ msgstr ""
119
+
120
+ #. translators: 1: blog name, 2: post title
121
+ #: co-authors-plus.php:1263
122
+ msgid "[%1$s] Trackback: \"%2$s\""
123
+ msgstr ""
124
+
125
+ #: co-authors-plus.php:1265
126
+ msgid "New pingback on your post \"%s\""
127
+ msgstr ""
128
+
129
+ #: co-authors-plus.php:1270
130
+ msgid "You can see all pingbacks on this post here: "
131
+ msgstr ""
132
+
133
+ #. translators: 1: blog name, 2: post title
134
+ #: co-authors-plus.php:1272
135
+ msgid "[%1$s] Pingback: \"%2$s\""
136
+ msgstr ""
137
+
138
+ #: co-authors-plus.php:1275
139
+ msgid "Permalink: %s"
140
+ msgstr ""
141
+
142
+ #: co-authors-plus.php:1277 co-authors-plus.php:1374
143
+ msgid "Trash it: %s"
144
+ msgstr ""
145
+
146
+ #: co-authors-plus.php:1279 co-authors-plus.php:1376
147
+ msgid "Delete it: %s"
148
+ msgstr ""
149
+
150
+ #: co-authors-plus.php:1280 co-authors-plus.php:1377
151
+ msgid "Spam it: %s"
152
+ msgstr ""
153
+
154
+ #: co-authors-plus.php:1348
155
+ msgid "A new trackback on the post \"%s\" is waiting for your approval"
156
+ msgstr ""
157
+
158
+ #: co-authors-plus.php:1350 co-authors-plus.php:1357
159
+ msgid "Website : %1$s (IP: %2$s , %3$s)"
160
+ msgstr ""
161
+
162
+ #: co-authors-plus.php:1352
163
+ msgid "Trackback excerpt: "
164
+ msgstr ""
165
+
166
+ #: co-authors-plus.php:1355
167
+ msgid "A new pingback on the post \"%s\" is waiting for your approval"
168
+ msgstr ""
169
+
170
+ #: co-authors-plus.php:1359
171
+ msgid "Pingback excerpt: "
172
+ msgstr ""
173
+
174
+ #: co-authors-plus.php:1362
175
+ msgid "A new comment on the post \"%s\" is waiting for your approval"
176
+ msgstr ""
177
+
178
+ #: co-authors-plus.php:1372
179
+ msgid "Approve it: %s"
180
+ msgstr ""
181
+
182
+ #: co-authors-plus.php:1379
183
+ msgid ""
184
+ "Currently %s comment is waiting for approval. Please visit the moderation "
185
+ "panel:"
186
+ msgid_plural ""
187
+ "Currently %s comments are waiting for approval. Please visit the moderation "
188
+ "panel:"
189
+ msgstr[0] ""
190
+ msgstr[1] ""
191
+
192
+ #: co-authors-plus.php:1383
193
+ msgid "[%1$s] Please moderate: \"%2$s\""
194
+ msgstr ""
195
+
196
+ #: php/class-coauthors-guest-authors.php:78
197
+ msgid "Guest Author"
198
+ msgstr ""
199
+
200
+ #: php/class-coauthors-guest-authors.php:79
201
+ msgid "Guest Authors"
202
+ msgstr ""
203
+
204
+ #: php/class-coauthors-guest-authors.php:80
205
+ msgid "All Guest Authors"
206
+ msgstr ""
207
+
208
+ #: php/class-coauthors-guest-authors.php:81
209
+ msgid "Add New Guest Author"
210
+ msgstr ""
211
+
212
+ #: php/class-coauthors-guest-authors.php:82
213
+ msgid "Edit Guest Author"
214
+ msgstr ""
215
+
216
+ #: php/class-coauthors-guest-authors.php:83
217
+ msgid "New Guest Author"
218
+ msgstr ""
219
+
220
+ #: php/class-coauthors-guest-authors.php:84
221
+ msgid "View Guest Author"
222
+ msgstr ""
223
+
224
+ #: php/class-coauthors-guest-authors.php:85
225
+ msgid "Search Guest Authors"
226
+ msgstr ""
227
+
228
+ #: php/class-coauthors-guest-authors.php:86
229
+ msgid "No guest authors found"
230
+ msgstr ""
231
+
232
+ #: php/class-coauthors-guest-authors.php:87
233
+ msgid "No guest authors found in Trash"
234
+ msgstr ""
235
+
236
+ #: php/class-coauthors-guest-authors.php:88
237
+ msgid "Update Guest Author"
238
+ msgstr ""
239
+
240
+ #: php/class-coauthors-guest-authors.php:89
241
+ msgid "About the guest author"
242
+ msgstr ""
243
+
244
+ #: php/class-coauthors-guest-authors.php:98
245
+ msgctxt "co-authors-plus"
246
+ msgid "Add New"
247
+ msgstr ""
248
+
249
+ #: php/class-coauthors-guest-authors.php:142
250
+ #: php/class-coauthors-guest-authors.php:148
251
+ msgid "Guest author updated. <a href=\"%s\">View profile</a>"
252
+ msgstr ""
253
+
254
+ #: php/class-coauthors-guest-authors.php:143
255
+ msgid "Custom field updated."
256
+ msgstr ""
257
+
258
+ #: php/class-coauthors-guest-authors.php:144
259
+ msgid "Custom field deleted."
260
+ msgstr ""
261
+
262
+ #: php/class-coauthors-guest-authors.php:145
263
+ msgid "Guest author updated."
264
+ msgstr ""
265
+
266
+ #. translators: %s: date and time of the revision
267
+ #: php/class-coauthors-guest-authors.php:147
268
+ msgid "Guest author restored to revision from %s"
269
+ msgstr ""
270
+
271
+ #: php/class-coauthors-guest-authors.php:149
272
+ msgid "Guest author saved."
273
+ msgstr ""
274
+
275
+ #: php/class-coauthors-guest-authors.php:150
276
+ msgid ""
277
+ "Guest author submitted. <a target=\"_blank\" href=\"%s\">Preview profile</a>"
278
+ msgstr ""
279
+
280
+ #: php/class-coauthors-guest-authors.php:151
281
+ msgid ""
282
+ "Guest author scheduled for: <strong>%1$s</strong>. <a target=\"_blank\" href="
283
+ "\"%2$s\">Preview profile</a>"
284
+ msgstr ""
285
+
286
+ #. translators: Publish box date format, see http:php.net/date
287
+ #: php/class-coauthors-guest-authors.php:153
288
+ msgid "M j, Y @ G:i"
289
+ msgstr ""
290
+
291
+ #: php/class-coauthors-guest-authors.php:154
292
+ msgid ""
293
+ "Guest author updated. <a target=\"_blank\" href=\"%s\">Preview profile</a>"
294
+ msgstr ""
295
+
296
+ #: php/class-coauthors-guest-authors.php:171
297
+ #: php/class-coauthors-guest-authors.php:205
298
+ #: php/class-coauthors-guest-authors.php:388
299
+ msgid "Doin' something fishy, huh?"
300
+ msgstr ""
301
+
302
+ #: php/class-coauthors-guest-authors.php:174
303
+ #: php/class-coauthors-guest-authors.php:209
304
+ msgid "You don't have permission to perform this action."
305
+ msgstr ""
306
+
307
+ #: php/class-coauthors-guest-authors.php:214
308
+ #: php/class-coauthors-guest-authors.php:393
309
+ msgid "Guest author can't be deleted because it doesn't exist."
310
+ msgstr ""
311
+
312
+ #: php/class-coauthors-guest-authors.php:228
313
+ msgid "Co-author does not exists. Try again?"
314
+ msgstr ""
315
+
316
+ #: php/class-coauthors-guest-authors.php:236
317
+ msgid "Please make sure to pick an option."
318
+ msgstr ""
319
+
320
+ #: php/class-coauthors-guest-authors.php:348
321
+ msgid "Guest author deleted."
322
+ msgstr ""
323
+
324
+ #: php/class-coauthors-guest-authors.php:370
325
+ msgid "Save"
326
+ msgstr ""
327
+
328
+ #: php/class-coauthors-guest-authors.php:371
329
+ msgid "Unique Slug"
330
+ msgstr ""
331
+
332
+ #: php/class-coauthors-guest-authors.php:373
333
+ msgid "Name"
334
+ msgstr ""
335
+
336
+ #: php/class-coauthors-guest-authors.php:374
337
+ msgid "Contact Info"
338
+ msgstr ""
339
+
340
+ #: php/class-coauthors-guest-authors.php:397
341
+ msgid "Delete %s"
342
+ msgstr ""
343
+
344
+ #: php/class-coauthors-guest-authors.php:398
345
+ msgid "You have specified this guest author for deletion:"
346
+ msgstr ""
347
+
348
+ #: php/class-coauthors-guest-authors.php:400
349
+ msgid "What should be done with posts assigned to this guest author?"
350
+ msgstr ""
351
+
352
+ #: php/class-coauthors-guest-authors.php:401
353
+ msgid ""
354
+ "Note: If you'd like to delete the guest author and all of their posts, you "
355
+ "should delete their posts first and then come back to delete the guest "
356
+ "author."
357
+ msgstr ""
358
+
359
+ #: php/class-coauthors-guest-authors.php:410
360
+ msgid "Reassign to another co-author:"
361
+ msgstr ""
362
+
363
+ #: php/class-coauthors-guest-authors.php:416
364
+ msgid "Leave posts assigned to the mapped user, %s."
365
+ msgstr ""
366
+
367
+ #: php/class-coauthors-guest-authors.php:421
368
+ msgid "Remove byline from posts (but leave each post in its current status)."
369
+ msgstr ""
370
+
371
+ #: php/class-coauthors-guest-authors.php:424
372
+ msgid "Confirm Deletion"
373
+ msgstr ""
374
+
375
+ #: php/class-coauthors-guest-authors.php:433
376
+ msgid "Add New"
377
+ msgstr ""
378
+
379
+ #: php/class-coauthors-guest-authors.php:490
380
+ msgid "WordPress User Mapping"
381
+ msgstr ""
382
+
383
+ #: php/class-coauthors-guest-authors.php:492
384
+ msgid "-- Not mapped --"
385
+ msgstr ""
386
+
387
+ #: php/class-coauthors-guest-authors.php:594
388
+ msgid "Guest authors cannot be created without display names."
389
+ msgstr ""
390
+
391
+ #: php/class-coauthors-guest-authors.php:601
392
+ msgid ""
393
+ "Guest authors cannot be created with the same user_login value as a user. "
394
+ "Try creating a profile from the user instead"
395
+ msgstr ""
396
+
397
+ #: php/class-coauthors-guest-authors.php:606
398
+ msgid "Display name conflicts with another guest author display name."
399
+ msgstr ""
400
+
401
+ #: php/class-coauthors-guest-authors.php:750
402
+ msgid "ID"
403
+ msgstr ""
404
+
405
+ #: php/class-coauthors-guest-authors.php:756
406
+ #: php/class-coauthors-wp-list-table.php:132
407
+ msgid "Display Name"
408
+ msgstr ""
409
+
410
+ #: php/class-coauthors-guest-authors.php:762
411
+ #: php/class-coauthors-wp-list-table.php:133
412
+ msgid "First Name"
413
+ msgstr ""
414
+
415
+ #: php/class-coauthors-guest-authors.php:767
416
+ #: php/class-coauthors-wp-list-table.php:134
417
+ msgid "Last Name"
418
+ msgstr ""
419
+
420
+ #: php/class-coauthors-guest-authors.php:772
421
+ msgid "Slug"
422
+ msgstr ""
423
+
424
+ #: php/class-coauthors-guest-authors.php:779
425
+ #: php/class-coauthors-wp-list-table.php:135
426
+ msgid "E-mail"
427
+ msgstr ""
428
+
429
+ #: php/class-coauthors-guest-authors.php:784
430
+ #: php/class-coauthors-wp-list-table.php:136
431
+ msgid "Linked Account"
432
+ msgstr ""
433
+
434
+ #: php/class-coauthors-guest-authors.php:789
435
+ msgid "Website"
436
+ msgstr ""
437
+
438
+ #: php/class-coauthors-guest-authors.php:794
439
+ msgid "AIM"
440
+ msgstr ""
441
+
442
+ #: php/class-coauthors-guest-authors.php:799
443
+ msgid "Yahoo IM"
444
+ msgstr ""
445
+
446
+ #: php/class-coauthors-guest-authors.php:804
447
+ msgid "Jabber / Google Talk"
448
+ msgstr ""
449
+
450
+ #: php/class-coauthors-guest-authors.php:809
451
+ msgid "Biographical Info"
452
+ msgstr ""
453
+
454
+ #: php/class-coauthors-guest-authors.php:938
455
+ msgid "%s is a required field"
456
+ msgstr ""
457
+
458
+ #: php/class-coauthors-guest-authors.php:944
459
+ msgid "user_login cannot duplicate existing guest author or mapped user"
460
+ msgstr ""
461
+
462
+ #: php/class-coauthors-guest-authors.php:989
463
+ msgid "Guest author does not exist"
464
+ msgstr ""
465
+
466
+ #: php/class-coauthors-guest-authors.php:1001
467
+ msgid "Reassignment co-author does not exist"
468
+ msgstr ""
469
+
470
+ #: php/class-coauthors-guest-authors.php:1033
471
+ msgid "No user exists with that ID"
472
+ msgstr ""
473
+
474
+ #: php/class-coauthors-guest-authors.php:1088
475
+ msgid "Edit Profile"
476
+ msgstr ""
477
+
478
+ #: php/class-coauthors-guest-authors.php:1096
479
+ msgid "Create Profile"
480
+ msgstr ""
481
+
482
+ #: php/class-coauthors-wp-list-table.php:19
483
+ msgid "Co-Authors"
484
+ msgstr ""
485
+
486
+ #: php/class-coauthors-wp-list-table.php:20
487
+ msgid "Co-Author"
488
+ msgstr ""
489
+
490
+ #: php/class-coauthors-wp-list-table.php:68
491
+ msgid "Show all"
492
+ msgstr ""
493
+
494
+ #: php/class-coauthors-wp-list-table.php:69
495
+ msgid "With linked account"
496
+ msgstr ""
497
+
498
+ #: php/class-coauthors-wp-list-table.php:70
499
+ msgid "Without linked account"
500
+ msgstr ""
501
+
502
+ #: php/class-coauthors-wp-list-table.php:122
503
+ msgid "No matching guest authors were found."
504
+ msgstr ""
505
+
506
+ #: php/class-coauthors-wp-list-table.php:189
507
+ msgid "Delete"
508
+ msgstr ""
509
+
510
+ #: php/class-coauthors-wp-list-table.php:190
511
+ msgid "View Posts"
512
+ msgstr ""
513
+
514
+ #: php/class-coauthors-wp-list-table.php:238
515
+ msgid "Filter"
516
+ msgstr ""
517
+
518
+ #: php/class-wp-cli.php:167
519
+ msgid "Please specify a valid user_login"
520
+ msgstr ""
521
+
522
+ #: php/class-wp-cli.php:170
523
+ msgid "Please specify a valid co-author login"
524
+ msgstr ""
525
+
526
+ #: php/class-wp-cli.php:177
527
+ msgid "Skipping - Post #%d already has co-authors assigned: %s"
528
+ msgstr ""
529
+
530
+ #: php/class-wp-cli.php:182
531
+ msgid "Updating - Adding %s's byline to post #%d"
532
+ msgstr ""
533
+
534
+ #: php/class-wp-cli.php:187
535
+ msgid "All done! %d posts were affected."
536
+ msgstr ""
537
+
538
+ #: template-tags.php:79
539
  msgid ""
540
  "No post ID provided for CoAuthorsIterator constructor. Are you not in a loop "
541
  "or is $post not set?"
542
  msgstr ""
543
 
544
+ #: template-tags.php:133
545
+ msgid " and "
546
+ msgstr ""
547
+
548
+ #: template-tags.php:205 template-tags.php:353
549
  msgid "Posts by %s"
550
  msgstr ""
551
 
552
+ #: template-tags.php:254
553
  msgid "Visit %s&#8217;s website"
554
  msgstr ""
555
 
568
  msgstr ""
569
 
570
  #. Author of the plugin/theme
571
+ msgid "Mohammad Jangda, Daniel Bachhuber, Automattic"
572
  msgstr ""
lib/select2/select2.css ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
3
+ */
4
+ .select2-container {
5
+ position: relative;
6
+ display: inline-block;
7
+ /* inline-block for ie7 */
8
+ zoom: 1;
9
+ *display: inline;
10
+ vertical-align: top;
11
+ }
12
+
13
+ .select2-container,
14
+ .select2-drop,
15
+ .select2-search,
16
+ .select2-search input{
17
+ /*
18
+ Force border-box so that % widths fit the parent
19
+ container without overlap because of margin/padding.
20
+
21
+ More Info : http://www.quirksmode.org/css/box.html
22
+ */
23
+ -moz-box-sizing: border-box; /* firefox */
24
+ -ms-box-sizing: border-box; /* ie */
25
+ -webkit-box-sizing: border-box; /* webkit */
26
+ -khtml-box-sizing: border-box; /* konqueror */
27
+ box-sizing: border-box; /* css3 */
28
+ }
29
+
30
+ .select2-container .select2-choice {
31
+ background-color: #fff;
32
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
33
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
34
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
35
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
36
+ background-image: -ms-linear-gradient(top, #eeeeee 0%, #ffffff 50%);
37
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#ffffff', GradientType = 0);
38
+ background-image: linear-gradient(top, #eeeeee 0%, #ffffff 50%);
39
+ -webkit-border-radius: 4px;
40
+ -moz-border-radius: 4px;
41
+ border-radius: 4px;
42
+ -moz-background-clip: padding;
43
+ -webkit-background-clip: padding-box;
44
+ background-clip: padding-box;
45
+ border: 1px solid #aaa;
46
+ display: block;
47
+ overflow: hidden;
48
+ white-space: nowrap;
49
+ position: relative;
50
+ height: 26px;
51
+ line-height: 26px;
52
+ padding: 0 0 0 8px;
53
+ color: #444;
54
+ text-decoration: none;
55
+ }
56
+
57
+ .select2-container.select2-drop-above .select2-choice
58
+ {
59
+ border-bottom-color: #aaa;
60
+ -webkit-border-radius:0px 0px 4px 4px;
61
+ -moz-border-radius:0px 0px 4px 4px;
62
+ border-radius:0px 0px 4px 4px;
63
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
64
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
65
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
66
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
67
+ background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
68
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
69
+ background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
70
+ }
71
+
72
+ .select2-container .select2-choice span {
73
+ margin-right: 26px;
74
+ display: block;
75
+ overflow: hidden;
76
+ white-space: nowrap;
77
+ -o-text-overflow: ellipsis;
78
+ -ms-text-overflow: ellipsis;
79
+ text-overflow: ellipsis;
80
+ }
81
+
82
+ .select2-container .select2-choice abbr {
83
+ display: block;
84
+ position: absolute;
85
+ right: 26px;
86
+ top: 8px;
87
+ width: 12px;
88
+ height: 12px;
89
+ font-size: 1px;
90
+ background: url('select2.png') right top no-repeat;
91
+ cursor: pointer;
92
+ text-decoration: none;
93
+ border:0;
94
+ outline: 0;
95
+ }
96
+ .select2-container .select2-choice abbr:hover {
97
+ background-position: right -11px;
98
+ cursor: pointer;
99
+ }
100
+
101
+ .select2-drop {
102
+ background: #fff;
103
+ color: #000;
104
+ border: 1px solid #aaa;
105
+ border-top: 0;
106
+ position: absolute;
107
+ top: 100%;
108
+ -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
109
+ -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
110
+ -o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
111
+ box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
112
+ z-index: 9999;
113
+ width:100%;
114
+ margin-top:-1px;
115
+
116
+ -webkit-border-radius: 0 0 4px 4px;
117
+ -moz-border-radius: 0 0 4px 4px;
118
+ border-radius: 0 0 4px 4px;
119
+ }
120
+
121
+ .select2-drop.select2-drop-above {
122
+ -webkit-border-radius: 4px 4px 0px 0px;
123
+ -moz-border-radius: 4px 4px 0px 0px;
124
+ border-radius: 4px 4px 0px 0px;
125
+ margin-top:1px;
126
+ border-top: 1px solid #aaa;
127
+ border-bottom: 0;
128
+
129
+ -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
130
+ -moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
131
+ -o-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
132
+ box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
133
+ }
134
+
135
+ .select2-container .select2-choice div {
136
+ -webkit-border-radius: 0 4px 4px 0;
137
+ -moz-border-radius: 0 4px 4px 0;
138
+ border-radius: 0 4px 4px 0;
139
+ -moz-background-clip: padding;
140
+ -webkit-background-clip: padding-box;
141
+ background-clip: padding-box;
142
+ background: #ccc;
143
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
144
+ background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
145
+ background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
146
+ background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
147
+ background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
148
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#cccccc', endColorstr = '#eeeeee', GradientType = 0);
149
+ background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
150
+ border-left: 1px solid #aaa;
151
+ position: absolute;
152
+ right: 0;
153
+ top: 0;
154
+ display: block;
155
+ height: 100%;
156
+ width: 18px;
157
+ }
158
+
159
+ .select2-container .select2-choice div b {
160
+ background: url('select2.png') no-repeat 0 1px;
161
+ display: block;
162
+ width: 100%;
163
+ height: 100%;
164
+ }
165
+
166
+ .select2-search {
167
+ display: inline-block;
168
+ white-space: nowrap;
169
+ z-index: 10000;
170
+ min-height: 26px;
171
+ width: 100%;
172
+ margin: 0;
173
+ padding-left: 4px;
174
+ padding-right: 4px;
175
+ }
176
+
177
+ .select2-search-hidden {
178
+ display: block;
179
+ position: absolute;
180
+ left: -10000px;
181
+ }
182
+
183
+ .select2-search input {
184
+ background: #fff url('select2.png') no-repeat 100% -22px;
185
+ background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
186
+ background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
187
+ background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
188
+ background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
189
+ background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
190
+ background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
191
+ padding: 4px 20px 4px 5px;
192
+ outline: 0;
193
+ border: 1px solid #aaa;
194
+ font-family: sans-serif;
195
+ font-size: 1em;
196
+ width:100%;
197
+ margin:0;
198
+ height:auto !important;
199
+ min-height: 26px;
200
+ -webkit-box-shadow: none;
201
+ -moz-box-shadow: none;
202
+ box-shadow: none;
203
+ border-radius: 0;
204
+ -moz-border-radius: 0;
205
+ -webkit-border-radius: 0;
206
+ }
207
+
208
+ .select2-drop.select2-drop-above .select2-search input
209
+ {
210
+ margin-top:4px;
211
+ }
212
+
213
+ .select2-search input.select2-active {
214
+ background: #fff url('spinner.gif') no-repeat 100%;
215
+ background: url('spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
216
+ background: url('spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
217
+ background: url('spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
218
+ background: url('spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
219
+ background: url('spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
220
+ background: url('spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
221
+ }
222
+
223
+
224
+ .select2-container-active .select2-choice,
225
+ .select2-container-active .select2-choices {
226
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
227
+ -moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
228
+ -o-box-shadow : 0 0 5px rgba(0,0,0,.3);
229
+ box-shadow : 0 0 5px rgba(0,0,0,.3);
230
+ border: 1px solid #5897fb;
231
+ outline: none;
232
+ }
233
+
234
+ .select2-dropdown-open .select2-choice {
235
+ border: 1px solid #aaa;
236
+ border-bottom-color: transparent;
237
+ -webkit-box-shadow: 0 1px 0 #fff inset;
238
+ -moz-box-shadow : 0 1px 0 #fff inset;
239
+ -o-box-shadow : 0 1px 0 #fff inset;
240
+ box-shadow : 0 1px 0 #fff inset;
241
+ background-color: #eee;
242
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
243
+ background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
244
+ background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
245
+ background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
246
+ background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
247
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
248
+ background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
249
+ -webkit-border-bottom-left-radius : 0;
250
+ -webkit-border-bottom-right-radius: 0;
251
+ -moz-border-radius-bottomleft : 0;
252
+ -moz-border-radius-bottomright: 0;
253
+ border-bottom-left-radius : 0;
254
+ border-bottom-right-radius: 0;
255
+ }
256
+
257
+ .select2-dropdown-open .select2-choice div {
258
+ background: transparent;
259
+ border-left: none;
260
+ }
261
+ .select2-dropdown-open .select2-choice div b {
262
+ background-position: -18px 1px;
263
+ }
264
+
265
+ /* results */
266
+ .select2-results {
267
+ margin: 4px 4px 4px 0;
268
+ padding: 0 0 0 4px;
269
+ position: relative;
270
+ overflow-x: hidden;
271
+ overflow-y: auto;
272
+ max-height: 200px;
273
+ }
274
+
275
+ .select2-results ul.select2-result-sub {
276
+ margin: 0 0 0 0;
277
+ }
278
+
279
+ .select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
280
+ .select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
281
+ .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
282
+ .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
283
+ .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
284
+ .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
285
+ .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
286
+
287
+ .select2-results li {
288
+ list-style: none;
289
+ display: list-item;
290
+ }
291
+
292
+ .select2-results li.select2-result-with-children > .select2-result-label {
293
+ font-weight: bold;
294
+ }
295
+
296
+ .select2-results .select2-result-label {
297
+ padding: 3px 7px 4px;
298
+ margin: 0;
299
+ cursor: pointer;
300
+ }
301
+
302
+ .select2-results .select2-highlighted {
303
+ background: #3875d7;
304
+ color: #fff;
305
+ }
306
+ .select2-results li em {
307
+ background: #feffde;
308
+ font-style: normal;
309
+ }
310
+ .select2-results .select2-highlighted em {
311
+ background: transparent;
312
+ }
313
+ .select2-results .select2-no-results,
314
+ .select2-results .select2-searching,
315
+ .select2-results .select2-selection-limit {
316
+ background: #f4f4f4;
317
+ display: list-item;
318
+ }
319
+
320
+ /*
321
+ disabled look for already selected choices in the results dropdown
322
+ .select2-results .select2-disabled.select2-highlighted {
323
+ color: #666;
324
+ background: #f4f4f4;
325
+ display: list-item;
326
+ cursor: default;
327
+ }
328
+ .select2-results .select2-disabled {
329
+ background: #f4f4f4;
330
+ display: list-item;
331
+ cursor: default;
332
+ }
333
+ */
334
+ .select2-results .select2-disabled {
335
+ display: none;
336
+ }
337
+
338
+ .select2-more-results.select2-active {
339
+ background: #f4f4f4 url('spinner.gif') no-repeat 100%;
340
+ }
341
+
342
+ .select2-more-results {
343
+ background: #f4f4f4;
344
+ display: list-item;
345
+ }
346
+
347
+ /* disabled styles */
348
+
349
+ .select2-container.select2-container-disabled .select2-choice {
350
+ background-color: #f4f4f4;
351
+ background-image: none;
352
+ border: 1px solid #ddd;
353
+ cursor: default;
354
+ }
355
+
356
+ .select2-container.select2-container-disabled .select2-choice div {
357
+ background-color: #f4f4f4;
358
+ background-image: none;
359
+ border-left: 0;
360
+ }
361
+
362
+
363
+ /* multiselect */
364
+
365
+ .select2-container-multi .select2-choices {
366
+ background-color: #fff;
367
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
368
+ background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
369
+ background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
370
+ background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
371
+ background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
372
+ background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
373
+ border: 1px solid #aaa;
374
+ margin: 0;
375
+ padding: 0;
376
+ cursor: text;
377
+ overflow: hidden;
378
+ height: auto !important;
379
+ height: 1%;
380
+ position: relative;
381
+ }
382
+
383
+ .select2-container-multi .select2-choices {
384
+ min-height: 26px;
385
+ }
386
+
387
+ .select2-container-multi.select2-container-active .select2-choices {
388
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
389
+ -moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
390
+ -o-box-shadow : 0 0 5px rgba(0,0,0,.3);
391
+ box-shadow : 0 0 5px rgba(0,0,0,.3);
392
+ border: 1px solid #5897fb;
393
+ outline: none;
394
+ }
395
+ .select2-container-multi .select2-choices li {
396
+ float: left;
397
+ list-style: none;
398
+ }
399
+ .select2-container-multi .select2-choices .select2-search-field {
400
+ white-space: nowrap;
401
+ margin: 0;
402
+ padding: 0;
403
+ }
404
+
405
+ .select2-container-multi .select2-choices .select2-search-field input {
406
+ color: #666;
407
+ background: transparent !important;
408
+ font-family: sans-serif;
409
+ font-size: 100%;
410
+ height: 15px;
411
+ padding: 5px;
412
+ margin: 1px 0;
413
+ outline: 0;
414
+ border: 0;
415
+ -webkit-box-shadow: none;
416
+ -moz-box-shadow : none;
417
+ -o-box-shadow : none;
418
+ box-shadow : none;
419
+ }
420
+
421
+ .select2-container-multi .select2-choices .select2-search-field input.select2-active {
422
+ background: #fff url('spinner.gif') no-repeat 100% !important;
423
+ }
424
+
425
+ .select2-default {
426
+ color: #999 !important;
427
+ }
428
+
429
+ .select2-container-multi .select2-choices .select2-search-choice {
430
+ -webkit-border-radius: 3px;
431
+ -moz-border-radius : 3px;
432
+ border-radius : 3px;
433
+ -moz-background-clip : padding;
434
+ -webkit-background-clip: padding-box;
435
+ background-clip : padding-box;
436
+ background-color: #e4e4e4;
437
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 );
438
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
439
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
440
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
441
+ background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
442
+ background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
443
+ background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
444
+ -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
445
+ -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
446
+ box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
447
+ color: #333;
448
+ border: 1px solid #aaaaaa;
449
+ line-height: 13px;
450
+ padding: 3px 5px 3px 18px;
451
+ margin: 3px 0 3px 5px;
452
+ position: relative;
453
+ cursor: default;
454
+ }
455
+ .select2-container-multi .select2-choices .select2-search-choice span {
456
+ cursor: default;
457
+ }
458
+ .select2-container-multi .select2-choices .select2-search-choice-focus {
459
+ background: #d4d4d4;
460
+ }
461
+
462
+ .select2-search-choice-close {
463
+ display: block;
464
+ position: absolute;
465
+ right: 3px;
466
+ top: 4px;
467
+ width: 12px;
468
+ height: 13px;
469
+ font-size: 1px;
470
+ background: url('select2.png') right top no-repeat;
471
+ outline: none;
472
+ }
473
+
474
+ .select2-container-multi .select2-search-choice-close {
475
+ left: 3px;
476
+ }
477
+
478
+
479
+ .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
480
+ background-position: right -11px;
481
+ }
482
+ .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
483
+ background-position: right -11px;
484
+ }
485
+
486
+ /* disabled styles */
487
+
488
+ .select2-container-multi.select2-container-disabled .select2-choices{
489
+ background-color: #f4f4f4;
490
+ background-image: none;
491
+ border: 1px solid #ddd;
492
+ cursor: default;
493
+ }
494
+
495
+ .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
496
+ background-image: none;
497
+ background-color: #f4f4f4;
498
+ border: 1px solid #ddd;
499
+ padding: 3px 5px 3px 5px;
500
+ }
501
+
502
+ .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {
503
+ display: none;
504
+ }
505
+ /* end multiselect */
506
+
507
+ .select2-result-selectable .select2-match,
508
+ .select2-result-unselectable .select2-result-selectable .select2-match { text-decoration: underline; }
509
+ .select2-result-unselectable .select2-match { text-decoration: none; }
510
+
511
+ .select2-offscreen { position: absolute; left: -10000px; }
512
+
513
+ /* Retina-ize icons */
514
+
515
+ @media only screen and (-webkit-min-device-pixel-ratio: 1.5) {
516
+ .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b {
517
+ background-image: url(select2x2.png) !important;
518
+ background-repeat: no-repeat !important;
519
+ background-size: 60px 40px !important;
520
+ }
521
+ .select2-search input {
522
+ background-position: 100% -21px !important;
523
+ }
524
+ }
lib/select2/select2.min.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright 2012 Igor Vaynberg
3
+
4
+ Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in
7
+ compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software distributed under the License is
12
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and limitations under the License.
14
+ */
15
+ (function(e){"undefined"==typeof e.fn.each2&&e.fn.extend({each2:function(g){for(var i=e([0]),m=-1,s=this.length;++m<s&&(i.context=i[0]=this[m])&&!1!==g.call(i[0],m,i););return this}})})(jQuery);
16
+ (function(e,g){function i(a,b){var c=0,d=b.length,j;if("undefined"===typeof a)return-1;if(a.constructor===String)for(;c<d;c+=1){if(0===a.localeCompare(b[c]))return c}else for(;c<d;c+=1)if(j=b[c],j.constructor===String){if(0===j.localeCompare(a))return c}else if(j===a)return c;return-1}function m(a,b){return a===b?!0:a===g||b===g||null===a||null===b?!1:a.constructor===String?0===a.localeCompare(b):b.constructor===String?0===b.localeCompare(a):!1}function s(a,b){var c,d,j;if(null===a||1>a.length)return[];
17
+ c=a.split(b);d=0;for(j=c.length;d<j;d+=1)c[d]=e.trim(c[d]);return c}function A(a,b,c){var c=c||g,d;return function(){var j=arguments;window.clearTimeout(d);d=window.setTimeout(function(){b.apply(c,j)},a)}}function l(a){a.preventDefault();a.stopPropagation()}function B(a,b,c){var d=a.toUpperCase().indexOf(b.toUpperCase()),b=b.length;0>d?c.push(a):(c.push(a.substring(0,d)),c.push("<span class='select2-match'>"),c.push(a.substring(d,d+b)),c.push("</span>"),c.push(a.substring(d+b,a.length)))}function C(a){var b,
18
+ c=0,d=null,j=a.quietMillis||100;return function(h){window.clearTimeout(b);b=window.setTimeout(function(){var b=c+=1,j=a.data,n=a.transport||e.ajax,f=a.traditional||!1,g=a.type||"GET",j=j.call(this,h.term,h.page,h.context);null!==d&&d.abort();d=n.call(null,{url:a.url,dataType:a.dataType,data:j,type:g,traditional:f,success:function(d){b<c||(d=a.results(d,h.page),h.callback(d))}})},j)}}function D(a){var b=a,c,d=function(a){return""+a.text};e.isArray(b)||(d=b.text,e.isFunction(d)||(c=b.text,d=function(a){return a[c]}),
19
+ b=b.results);return function(a){var c=a.term,f={results:[]},k;if(c==="")a.callback({results:b});else{k=function(b,f){var g,t,b=b[0];if(b.children){g={};for(t in b)b.hasOwnProperty(t)&&(g[t]=b[t]);g.children=[];e(b.children).each2(function(a,b){k(b,g.children)});g.children.length&&f.push(g)}else a.matcher(c,d(b))&&f.push(b)};e(b).each2(function(a,b){k(b,f.results)});a.callback(f)}}}function E(a){return e.isFunction(a)?a:function(b){var c=b.term,d={results:[]};e(a).each(function(){var a=this.text!==
20
+ g,e=a?this.text:this;if(""===c||b.matcher(c,e))d.results.push(a?this:{id:this,text:this})});b.callback(d)}}function u(a){if(e.isFunction(a))return!0;if(!a)return!1;throw Error("formatterName must be a function or a falsy value");}function v(a){return e.isFunction(a)?a():a}function F(a){var b=0;e.each(a,function(a,d){d.children?b+=F(d.children):b++});return b}function H(a,b,c,d){var e=a,h=!1,f,k,n,o;if(!d.createSearchChoice||!d.tokenSeparators||1>d.tokenSeparators.length)return g;for(;;){h=-1;k=0;
21
+ for(n=d.tokenSeparators.length;k<n&&!(o=d.tokenSeparators[k],h=a.indexOf(o),0<=h);k++);if(0>h)break;f=a.substring(0,h);a=a.substring(h+o.length);if(0<f.length&&(f=d.createSearchChoice(f,b),f!==g&&null!==f&&d.id(f)!==g&&null!==d.id(f))){h=!1;k=0;for(n=b.length;k<n;k++)if(m(d.id(f),d.id(b[k]))){h=!0;break}h||c(f)}}if(0!=e.localeCompare(a))return a}function x(a,b){var c=function(){};c.prototype=new a;c.prototype.constructor=c;c.prototype.parent=a.prototype;c.prototype=e.extend(c.prototype,b);return c}
22
+ if(window.Select2===g){var f,w,y,z,G,q;f={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){a=a.which?a.which:a;switch(a){case f.LEFT:case f.RIGHT:case f.UP:case f.DOWN:return!0}return!1},isControl:function(a){switch(a.which){case f.SHIFT:case f.CTRL:case f.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){a=a.which?a.which:a;return 112<=a&&123>=a}};var I=1;G=function(){return I++};
23
+ e(document).delegate("body","mousemove",function(a){e.data(document,"select2-lastpos",{x:a.pageX,y:a.pageY})});e(document).ready(function(){e(document).delegate("body","mousedown touchend",function(a){var b=e(a.target).closest("div.select2-container").get(0),c;b?e(document).find("div.select2-container-active").each(function(){this!==b&&e(this).data("select2").blur()}):(b=e(a.target).closest("div.select2-drop").get(0),e(document).find("div.select2-drop-active").each(function(){this!==b&&e(this).data("select2").blur()}));
24
+ b=e(a.target);c=b.attr("for");"LABEL"===a.target.tagName&&(c&&0<c.length)&&(b=e("#"+c),b=b.data("select2"),b!==g&&(b.focus(),a.preventDefault()))})});w=x(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(a){var b,c;this.opts=a=this.prepareOpts(a);this.id=a.id;a.element.data("select2")!==g&&null!==a.element.data("select2")&&this.destroy();this.enabled=!0;this.container=this.createContainer();this.containerId="s2id_"+(a.element.attr("id")||"autogen"+G());this.containerSelector=
25
+ "#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1");this.container.attr("id",this.containerId);var d=!1,j;this.body=function(){!1===d&&(j=a.element.closest("body"),d=!0);return j};a.element.attr("class")!==g&&this.container.addClass(a.element.attr("class").replace(/validate\[[\S ]+] ?/,""));this.container.css(v(a.containerCss));this.container.addClass(v(a.containerCssClass));this.opts.element.data("select2",this).hide().before(this.container);this.container.data("select2",
26
+ this);this.dropdown=this.container.find(".select2-drop");this.dropdown.addClass(v(a.dropdownCssClass));this.dropdown.data("select2",this);this.results=b=this.container.find(".select2-results");this.search=c=this.container.find("input.select2-input");c.attr("tabIndex",this.opts.element.attr("tabIndex"));this.resultsPage=0;this.context=null;this.initContainer();this.initContainerWidth();this.results.bind("mousemove",function(a){var b=e.data(document,"select2-lastpos");(b===g||b.x!==a.pageX||b.y!==a.pageY)&&
27
+ e(a.target).trigger("mousemove-filtered",a)});this.dropdown.delegate(".select2-results","mousemove-filtered",this.bind(this.highlightUnderEvent));var h=this.results,f=A(80,function(a){h.trigger("scroll-debounced",a)});h.bind("scroll",function(a){0<=i(a.target,h.get())&&f(a)});this.dropdown.delegate(".select2-results","scroll-debounced",this.bind(this.loadMoreIfNeeded));e.fn.mousewheel&&b.mousewheel(function(a,c,d,e){c=b.scrollTop();0<e&&0>=c-e?(b.scrollTop(0),l(a)):0>e&&b.get(0).scrollHeight-b.scrollTop()+
28
+ e<=b.height()&&(b.scrollTop(b.get(0).scrollHeight-b.height()),l(a))});c.bind("keydown",function(){e.data(c,"keyup-change-value")===g&&e.data(c,"keyup-change-value",c.val())});c.bind("keyup",function(){var a=e.data(c,"keyup-change-value");a!==g&&c.val()!==a&&(e.removeData(c,"keyup-change-value"),c.trigger("keyup-change"))});c.bind("keyup-change",this.bind(this.updateResults));c.bind("focus",function(){c.addClass("select2-focused");" "===c.val()&&c.val("")});c.bind("blur",function(){c.removeClass("select2-focused")});
29
+ this.dropdown.delegate(".select2-results","mouseup",this.bind(function(a){0<e(a.target).closest(".select2-result-selectable:not(.select2-disabled)").length?(this.highlightUnderEvent(a),this.selectHighlighted(a)):this.focusSearch();l(a)}));this.dropdown.bind("click mouseup mousedown",function(a){a.stopPropagation()});e.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource());(a.element.is(":disabled")||a.element.is("[readonly='readonly']"))&&this.disable()},destroy:function(){var a=
30
+ this.opts.element.data("select2");a!==g&&(a.container.remove(),a.dropdown.remove(),a.opts.element.removeData("select2").unbind(".select2").show())},prepareOpts:function(a){var b,c,d;b=a.element;"select"===b.get(0).tagName.toLowerCase()&&(this.select=c=a.element);c&&e.each("id multiple ajax query createSearchChoice initSelection data tags".split(" "),function(){if(this in a)throw Error("Option '"+this+"' is not allowed for Select2 when attached to a <select> element.");});a=e.extend({},{populateResults:function(b,
31
+ c,d){var f,n=this.opts.id,o=this;f=function(b,c,j){var h,l,i,m,r,p,q;h=0;for(l=b.length;h<l;h=h+1){i=b[h];m=n(i)!==g;r=i.children&&i.children.length>0;p=e("<li></li>");p.addClass("select2-results-dept-"+j);p.addClass("select2-result");p.addClass(m?"select2-result-selectable":"select2-result-unselectable");r&&p.addClass("select2-result-with-children");p.addClass(o.opts.formatResultCssClass(i));m=e("<div></div>");m.addClass("select2-result-label");q=a.formatResult(i,m,d);q!==g&&m.html(o.opts.escapeMarkup(q));
32
+ p.append(m);if(r){r=e("<ul></ul>");r.addClass("select2-result-sub");f(i.children,r,j+1);p.append(r)}p.data("select2-data",i);c.append(p)}};f(c,b,0)}},e.fn.select2.defaults,a);"function"!==typeof a.id&&(d=a.id,a.id=function(a){return a[d]});if(c)a.query=this.bind(function(a){var c={results:[],more:false},d=a.term,f,n,o;o=function(b,c){var e;if(b.is("option"))a.matcher(d,b.text(),b)&&c.push({id:b.attr("value"),text:b.text(),element:b.get(),css:b.attr("class")});else if(b.is("optgroup")){e={text:b.attr("label"),
33
+ children:[],element:b.get(),css:b.attr("class")};b.children().each2(function(a,b){o(b,e.children)});e.children.length>0&&c.push(e)}};f=b.children();if(this.getPlaceholder()!==g&&f.length>0){n=f[0];e(n).text()===""&&(f=f.not(n))}f.each2(function(a,b){o(b,c.results)});a.callback(c)}),a.id=function(a){return a.id},a.formatResultCssClass=function(a){return a.css};else if(!("query"in a))if("ajax"in a){if((c=a.element.data("ajax-url"))&&0<c.length)a.ajax.url=c;a.query=C(a.ajax)}else"data"in a?a.query=D(a.data):
34
+ "tags"in a&&(a.query=E(a.tags),a.createSearchChoice=function(a){return{id:a,text:a}},a.initSelection=function(b,c){var d=[];e(s(b.val(),a.separator)).each(function(){var b=this,c=this,j=a.tags;e.isFunction(j)&&(j=j());e(j).each(function(){if(m(this.id,b)){c=this.text;return false}});d.push({id:b,text:c})});c(d)});if("function"!==typeof a.query)throw"query function not defined for Select2 "+a.element.attr("id");return a},monitorSource:function(){this.opts.element.bind("change.select2",this.bind(function(){!0!==
35
+ this.opts.element.data("select2-change-triggered")&&this.initSelection()}))},triggerChange:function(a){a=a||{};a=e.extend({},a,{type:"change",val:this.val()});this.opts.element.data("select2-change-triggered",!0);this.opts.element.trigger(a);this.opts.element.data("select2-change-triggered",!1);this.opts.element.click();this.opts.blurOnChange&&this.opts.element.blur()},enable:function(){this.enabled||(this.enabled=!0,this.container.removeClass("select2-container-disabled"))},disable:function(){this.enabled&&
36
+ (this.close(),this.enabled=!1,this.container.addClass("select2-container-disabled"))},opened:function(){return this.container.hasClass("select2-dropdown-open")},positionDropdown:function(){var a=this.container.offset(),b=this.container.outerHeight(),c=this.container.outerWidth(),d=this.dropdown.outerHeight(),j=e(window).scrollTop()+document.documentElement.clientHeight,b=a.top+b,f=a.left,j=b+d<=j,g=a.top-d>=this.body().scrollTop(),k=this.dropdown.hasClass("select2-drop-above"),n;"static"!==this.body().css("position")&&
37
+ (n=this.body().offset(),b-=n.top,f-=n.left);k?(k=!0,!g&&j&&(k=!1)):(k=!1,!j&&g&&(k=!0));k?(b=a.top-d,this.container.addClass("select2-drop-above"),this.dropdown.addClass("select2-drop-above")):(this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above"));a=e.extend({top:b,left:f,width:c},v(this.opts.dropdownCss));this.dropdown.css(a)},shouldOpen:function(){var a;if(this.opened())return!1;a=e.Event("open");this.opts.element.trigger(a);return!a.isDefaultPrevented()},
38
+ clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above");this.dropdown.removeClass("select2-drop-above")},open:function(){if(!this.shouldOpen())return!1;window.setTimeout(this.bind(this.opening),1);return!0},opening:function(){var a=this.containerId,b=this.containerSelector,c="scroll."+a,d="resize."+a;this.container.parents().each(function(){e(this).bind(c,function(){var a=e(b);0==a.length&&e(this).unbind(c);a.select2("close")})});e(window).bind(d,function(){var a=
39
+ e(b);0==a.length&&e(window).unbind(d);a.select2("close")});this.clearDropdownAlignmentPreference();" "===this.search.val()&&this.search.val("");this.container.addClass("select2-dropdown-open").addClass("select2-container-active");this.updateResults(!0);this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body());this.dropdown.show();this.positionDropdown();this.dropdown.addClass("select2-drop-active");this.ensureHighlightVisible();this.focusSearch()},close:function(){if(this.opened()){var a=
40
+ this;this.container.parents().each(function(){e(this).unbind("scroll."+a.containerId)});e(window).unbind("resize."+this.containerId);this.clearDropdownAlignmentPreference();this.dropdown.hide();this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");this.results.empty();this.clearSearch();this.opts.element.trigger(e.Event("close"))}},clearSearch:function(){},ensureHighlightVisible:function(){var a=this.results,b,c,d,f;c=this.highlight();0>c||(0==c?a.scrollTop(0):
41
+ (b=a.find(".select2-result-selectable"),d=e(b[c]),f=d.offset().top+d.outerHeight(),c===b.length-1&&(b=a.find("li.select2-more-results"),0<b.length&&(f=b.offset().top+b.outerHeight())),b=a.offset().top+a.outerHeight(),f>b&&a.scrollTop(a.scrollTop()+(f-b)),d=d.offset().top-a.offset().top,0>d&&a.scrollTop(a.scrollTop()+d)))},moveHighlight:function(a){for(var b=this.results.find(".select2-result-selectable"),c=this.highlight();-1<c&&c<b.length;){var c=c+a,d=e(b[c]);if(d.hasClass("select2-result-selectable")&&
42
+ !d.hasClass("select2-disabled")){this.highlight(c);break}}},highlight:function(a){var b=this.results.find(".select2-result-selectable").not(".select2-disabled");if(0===arguments.length)return i(b.filter(".select2-highlighted")[0],b.get());a>=b.length&&(a=b.length-1);0>a&&(a=0);b.removeClass("select2-highlighted");e(b[a]).addClass("select2-highlighted");this.ensureHighlightVisible()},countSelectableResults:function(){return this.results.find(".select2-result-selectable").not(".select2-disabled").length},
43
+ highlightUnderEvent:function(a){a=e(a.target).closest(".select2-result-selectable");if(0<a.length&&!a.is(".select2-highlighted")){var b=this.results.find(".select2-result-selectable");this.highlight(b.index(a))}else 0==a.length&&this.results.find(".select2-highlighted").removeClass("select2-highlighted")},loadMoreIfNeeded:function(){var a=this.results,b=a.find("li.select2-more-results"),c,d=this.resultsPage+1,e=this,f=this.search.val(),g=this.context;0!==b.length&&(c=b.offset().top-a.offset().top-
44
+ a.height(),0>=c&&(b.addClass("select2-active"),this.opts.query({term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),!0===c.more?(b.detach().appendTo(a).text(e.opts.formatLoadMore(d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d)})})))},tokenize:function(){},updateResults:function(a){function b(){f.scrollTop(0);d.removeClass("select2-active");
45
+ k.positionDropdown()}function c(a){f.html(k.opts.escapeMarkup(a));b()}var d=this.search,f=this.results,h=this.opts,i,k=this;if(!(!0!==a&&(!1===this.showSearchInput||!this.opened()))){d.addClass("select2-active");if(1<=h.maximumSelectionSize&&(i=this.data(),e.isArray(i)&&i.length>=h.maximumSelectionSize&&u(h.formatSelectionTooBig,"formatSelectionTooBig"))){c("<li class='select2-selection-limit'>"+h.formatSelectionTooBig(h.maximumSelectionSize)+"</li>");return}d.val().length<h.minimumInputLength&&u(h.formatInputTooShort,
46
+ "formatInputTooShort")?c("<li class='select2-no-results'>"+h.formatInputTooShort(d.val(),h.minimumInputLength)+"</li>"):(c("<li class='select2-searching'>"+h.formatSearching()+"</li>"),i=this.tokenize(),i!=g&&null!=i&&d.val(i),this.resultsPage=1,h.query({term:d.val(),page:this.resultsPage,context:null,matcher:h.matcher,callback:this.bind(function(i){var l;this.opened()&&((this.context=i.context===g?null:i.context,this.opts.createSearchChoice&&""!==d.val()&&(l=this.opts.createSearchChoice.call(null,
47
+ d.val(),i.results),l!==g&&null!==l&&k.id(l)!==g&&null!==k.id(l)&&0===e(i.results).filter(function(){return m(k.id(this),k.id(l))}).length&&i.results.unshift(l)),0===i.results.length&&u(h.formatNoMatches,"formatNoMatches"))?c("<li class='select2-no-results'>"+h.formatNoMatches(d.val())+"</li>"):(f.empty(),k.opts.populateResults.call(this,f,i.results,{term:d.val(),page:this.resultsPage,context:null}),!0===i.more&&u(h.formatLoadMore,"formatLoadMore")&&(f.append("<li class='select2-more-results'>"+k.opts.escapeMarkup(h.formatLoadMore(this.resultsPage))+
48
+ "</li>"),window.setTimeout(function(){k.loadMoreIfNeeded()},10)),this.postprocessResults(i,a),b()))})}))}},cancel:function(){this.close()},blur:function(){this.close();this.container.removeClass("select2-container-active");this.dropdown.removeClass("select2-drop-active");this.search[0]===document.activeElement&&this.search.blur();this.clearSearch();this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){this.search.show();this.search.focus();
49
+ window.setTimeout(this.bind(function(){this.search.show();this.search.focus();this.search.val(this.search.val())}),10)},selectHighlighted:function(){var a=this.highlight(),b=this.results.find(".select2-highlighted").not(".select2-disabled"),c=b.closest(".select2-result-selectable").data("select2-data");c&&(b.addClass("select2-disabled"),this.highlight(a),this.onSelect(c))},getPlaceholder:function(){return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||
50
+ this.opts.placeholder},initContainerWidth:function(){var a=function(){var a,c,d,f;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth()?"auto":this.opts.element.outerWidth()+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){a=this.opts.element.attr("style");if(a!==g){a=a.split(";");d=0;for(f=a.length;d<f;d+=1)if(c=a[d].replace(/\s/g,"").match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/),null!==c&&1<=c.length)return c[1]}return"resolve"===
51
+ this.opts.width?(a=this.opts.element.css("width"),0<a.indexOf("%")?a:0===this.opts.element.outerWidth()?"auto":this.opts.element.outerWidth()+"px"):null}return e.isFunction(this.opts.width)?this.opts.width():this.opts.width}.call(this);null!==a&&this.container.attr("style","width: "+a)}});y=x(w,{createContainer:function(){return e("<div></div>",{"class":"select2-container"}).html(" <a href='#' onclick='return false;' class='select2-choice'> <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr> <div><b></b></div></a> <div class='select2-drop select2-offscreen'> <div class='select2-search'> <input type='text' autocomplete='off' class='select2-input'/> </div> <ul class='select2-results'> </ul></div>")},
52
+ opening:function(){this.search.show();this.parent.opening.apply(this,arguments);this.dropdown.removeClass("select2-offscreen")},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show())},focus:function(){this.close();this.selection.focus()},isFocused:function(){return this.selection[0]===document.activeElement},cancel:function(){this.parent.cancel.apply(this,arguments);this.selection.focus()},
53
+ initContainer:function(){var a,b=this.dropdown;this.selection=a=this.container.find(".select2-choice");this.search.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(this.opened())switch(a.which){case f.UP:case f.DOWN:this.moveHighlight(a.which===f.UP?-1:1);l(a);break;case f.TAB:case f.ENTER:this.selectHighlighted();l(a);break;case f.ESC:this.cancel(a),l(a)}else a.which===f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC||!1===
54
+ this.opts.openOnEnter&&a.which===f.ENTER||this.open()}));this.search.bind("focus",this.bind(function(){this.selection.attr("tabIndex","-1")}));this.search.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.selection.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("mousedown",this.bind(function(){this.opened()?(this.close(),this.selection.focus()):this.enabled&&this.open()}));b.bind("mousedown",
55
+ this.bind(function(){this.search.focus()}));a.bind("focus",this.bind(function(){this.container.addClass("select2-container-active");this.search.attr("tabIndex","-1")}));a.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.search.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(!(a.which===
56
+ f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC)&&!(!1===this.opts.openOnEnter&&a.which===f.ENTER))if(a.which==f.DELETE)this.opts.allowClear&&this.clear();else{this.open();if(a.which!==f.ENTER&&!(48>a.which)){var b=String.fromCharCode(a.which).toLowerCase();a.shiftKey&&(b=b.toUpperCase());this.search.focus();this.search.val(b)}l(a)}}));a.delegate("abbr","mousedown",this.bind(function(a){this.enabled&&(this.clear(),l(a),this.close(),this.triggerChange(),this.selection.focus())}));this.setPlaceholder();
57
+ this.search.bind("focus",this.bind(function(){this.container.addClass("select2-container-active")}))},clear:function(){this.opts.element.val("");this.selection.find("span").empty();this.selection.removeData("select2-data");this.setPlaceholder()},initSelection:function(){if(""===this.opts.element.val())this.close(),this.setPlaceholder();else{var a=this;this.opts.initSelection.call(null,this.opts.element,function(b){b!==g&&null!==b&&(a.updateSelection(b),a.close(),a.setPlaceholder())})}},prepareOpts:function(){var a=
58
+ this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=a.find(":selected");e.isFunction(c)&&c({id:d.attr("value"),text:d.text()})});return a},setPlaceholder:function(){var a=this.getPlaceholder();""===this.opts.element.val()&&a!==g&&!(this.select&&""!==this.select.find("option:first").text())&&(this.selection.find("span").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.selection.find("abbr").hide())},
59
+ postprocessResults:function(a,b){var c=0,d=this,f=!0;this.results.find(".select2-result-selectable").each2(function(a,b){if(m(d.id(b.data("select2-data")),d.opts.element.val()))return c=a,!1});this.highlight(c);!0===b&&(f=this.showSearchInput=F(a.results)>=this.opts.minimumResultsForSearch,this.dropdown.find(".select2-search")[f?"removeClass":"addClass"]("select2-search-hidden"),e(this.dropdown,this.container)[f?"addClass":"removeClass"]("select2-with-searchbox"))},onSelect:function(a){var b=this.opts.element.val();
60
+ this.opts.element.val(this.id(a));this.updateSelection(a);this.close();this.selection.focus();m(b,this.id(a))||this.triggerChange()},updateSelection:function(a){var b=this.selection.find("span");this.selection.data("select2-data",a);b.empty();a=this.opts.formatSelection(a,b);a!==g&&b.append(this.opts.escapeMarkup(a));this.selection.removeClass("select2-default");this.opts.allowClear&&this.getPlaceholder()!==g&&this.selection.find("abbr").show()},val:function(){var a,b=null,c=this;if(0===arguments.length)return this.opts.element.val();
61
+ a=arguments[0];if(this.select)this.select.val(a).find(":selected").each2(function(a,c){b={id:c.attr("value"),text:c.text()};return!1}),this.updateSelection(b),this.setPlaceholder();else{if(this.opts.initSelection===g)throw Error("cannot call val() if initSelection() is not defined");a?(this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){c.opts.element.val(!a?"":c.id(a));c.updateSelection(a);c.setPlaceholder()})):this.clear()}},clearSearch:function(){this.search.val("")},
62
+ data:function(a){var b;if(0===arguments.length)return b=this.selection.data("select2-data"),b==g&&(b=null),b;!a||""===a?this.clear():(this.opts.element.val(!a?"":this.id(a)),this.updateSelection(a))}});z=x(w,{createContainer:function(){return e("<div></div>",{"class":"select2-container select2-container-multi"}).html(" <ul class='select2-choices'> <li class='select2-search-field'> <input type='text' autocomplete='off' class='select2-input'> </li></ul><div class='select2-drop select2-drop-multi' style='display:none;'> <ul class='select2-results'> </ul></div>")},
63
+ prepareOpts:function(){var a=this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=[];a.find(":selected").each2(function(a,b){d.push({id:b.attr("value"),text:b.text()})});e.isFunction(c)&&c(d)});return a},initContainer:function(){var a;this.searchContainer=this.container.find(".select2-search-field");this.selection=a=this.container.find(".select2-choices");this.search.bind("keydown",this.bind(function(b){if(this.enabled){if(b.which===
64
+ f.BACKSPACE&&""===this.search.val()){this.close();var c;c=a.find(".select2-search-choice-focus");if(0<c.length){this.unselect(c.first());this.search.width(10);l(b);return}c=a.find(".select2-search-choice");0<c.length&&c.last().addClass("select2-search-choice-focus")}else a.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");if(this.opened())switch(b.which){case f.UP:case f.DOWN:this.moveHighlight(b.which===f.UP?-1:1);l(b);return;case f.ENTER:case f.TAB:this.selectHighlighted();
65
+ l(b);return;case f.ESC:this.cancel(b);l(b);return}if(!(b.which===f.TAB||f.isControl(b)||f.isFunctionKey(b)||b.which===f.BACKSPACE||b.which===f.ESC)&&!(!1===this.opts.openOnEnter&&b.which===f.ENTER))this.open(),(b.which===f.PAGE_UP||b.which===f.PAGE_DOWN)&&l(b)}}));this.search.bind("keyup",this.bind(this.resizeSearch));this.search.bind("blur",this.bind(function(a){this.container.removeClass("select2-container-active");this.search.removeClass("select2-focused");this.clearSearch();a.stopImmediatePropagation()}));
66
+ this.container.delegate(".select2-choices","mousedown",this.bind(function(a){this.enabled&&!(0<e(a.target).closest(".select2-search-choice").length)&&(this.clearPlaceholder(),this.open(),this.focusSearch(),a.preventDefault())}));this.container.delegate(".select2-choices","focus",this.bind(function(){this.enabled&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())}));this.clearSearch()},enable:function(){this.enabled||(this.parent.enable.apply(this,
67
+ arguments),this.search.removeAttr("disabled"))},disable:function(){this.enabled&&(this.parent.disable.apply(this,arguments),this.search.attr("disabled",!0))},initSelection:function(){""===this.opts.element.val()&&(this.updateSelection([]),this.close(),this.clearSearch());if(this.select||""!==this.opts.element.val()){var a=this;this.opts.initSelection.call(null,this.opts.element,function(b){if(b!==g&&b!==null){a.updateSelection(b);a.close();a.clearSearch()}})}},clearSearch:function(){var a=this.getPlaceholder();
68
+ a!==g&&0===this.getVal().length&&!1===this.search.hasClass("select2-focused")?(this.search.val(a).addClass("select2-default"),this.resizeSearch()):this.search.val(" ").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")?this.search.val("").removeClass("select2-default"):" "===this.search.val()&&this.search.val("")},opening:function(){this.parent.opening.apply(this,arguments);this.clearPlaceholder();this.resizeSearch();this.focusSearch()},close:function(){this.opened()&&
69
+ this.parent.close.apply(this,arguments)},focus:function(){this.close();this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(a){var b=[],c=[],d=this;e(a).each(function(){0>i(d.id(this),b)&&(b.push(d.id(this)),c.push(this))});a=c;this.selection.find(".select2-search-choice").remove();e(a).each(function(){d.addSelectedChoice(this)});d.postprocessResults()},tokenize:function(){var a=this.search.val(),a=this.opts.tokenizer(a,this.data(),this.bind(this.onSelect),
70
+ this.opts);null!=a&&a!=g&&(this.search.val(a),0<a.length&&this.open())},onSelect:function(a){this.addSelectedChoice(a);this.select&&this.postprocessResults();this.opts.closeOnSelect?(this.close(),this.search.width(10)):0<this.countSelectableResults()?(this.search.width(10),this.resizeSearch(),this.positionDropdown()):this.close();this.triggerChange({added:a});this.focusSearch()},cancel:function(){this.close();this.focusSearch()},addSelectedChoice:function(a){var b=e("<li class='select2-search-choice'> <div></div> <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a></li>"),
71
+ c=this.id(a),d=this.getVal(),f;f=this.opts.formatSelection(a,b);b.find("div").replaceWith("<div>"+this.opts.escapeMarkup(f)+"</div>");b.find(".select2-search-choice-close").bind("mousedown",l).bind("click dblclick",this.bind(function(a){this.enabled&&(e(a.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(e(a.target));this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");this.close();this.focusSearch()})).dequeue(),
72
+ l(a))})).bind("focus",this.bind(function(){this.enabled&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))}));b.data("select2-data",a);b.insertBefore(this.searchContainer);d.push(c);this.setVal(d)},unselect:function(a){var b=this.getVal(),c,d,a=a.closest(".select2-search-choice");if(0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data");d=i(this.id(c),b);0<=d&&(b.splice(d,1),this.setVal(b),this.select&&
73
+ this.postprocessResults());a.remove();this.triggerChange({removed:c})},postprocessResults:function(){var a=this.getVal(),b=this.results.find(".select2-result-selectable"),c=this.results.find(".select2-result-with-children"),d=this;b.each2(function(b,c){var e=d.id(c.data("select2-data"));0<=i(e,a)?c.addClass("select2-disabled").removeClass("select2-result-selectable"):c.removeClass("select2-disabled").addClass("select2-result-selectable")});c.each2(function(a,b){0==b.find(".select2-result-selectable").length?
74
+ b.addClass("select2-disabled"):b.removeClass("select2-disabled")});b.each2(function(a,b){if(!b.hasClass("select2-disabled")&&b.hasClass("select2-result-selectable"))return d.highlight(0),!1})},resizeSearch:function(){var a,b,c,d,f=this.search.outerWidth()-this.search.width();a=this.search;q||(c=a[0].currentStyle||window.getComputedStyle(a[0],null),q=e("<div></div>").css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,
75
+ fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),e("body").append(q));q.text(a.val());a=q.width()+10;b=this.search.offset().left;c=this.selection.width();d=this.selection.offset().left;b=c-(b-d)-f;b<a&&(b=c-f);40>b&&(b=c-f);this.search.width(b)},getVal:function(){var a;if(this.select)return a=this.select.val(),null===a?[]:a;a=this.opts.element.val();return s(a,this.opts.separator)},setVal:function(a){var b;this.select?this.select.val(a):(b=
76
+ [],e(a).each(function(){0>i(this,b)&&b.push(this)}),this.opts.element.val(0===b.length?"":b.join(this.opts.separator)))},val:function(){var a,b=[],c=this;if(0===arguments.length)return this.getVal();if(a=arguments[0])if(this.setVal(a),this.select)this.select.find(":selected").each(function(){b.push({id:e(this).attr("value"),text:e(this).text()})}),this.updateSelection(b);else{if(this.opts.initSelection===g)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,
77
+ function(a){var b=e(a).map(c.id);c.setVal(b);c.updateSelection(a);c.clearSearch()})}else this.opts.element.val(""),this.updateSelection([]);this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");this.search.width(0);this.searchContainer.hide()},onSortEnd:function(){var a=[],b=this;this.searchContainer.show();this.searchContainer.appendTo(this.searchContainer.parent());this.resizeSearch();
78
+ this.selection.find(".select2-search-choice").each(function(){a.push(b.opts.id(e(this).data("select2-data")))});this.setVal(a);this.triggerChange()},data:function(a){var b=this,c;if(0===arguments.length)return this.selection.find(".select2-search-choice").map(function(){return e(this).data("select2-data")}).get();a||(a=[]);c=e.map(a,function(a){return b.opts.id(a)});this.setVal(c);this.updateSelection(a);this.clearSearch()}});e.fn.select2=function(){var a=Array.prototype.slice.call(arguments,0),b,
79
+ c,d,f,h="val destroy opened open close focus isFocused container onSortStart onSortEnd enable disable positionDropdown data".split(" ");this.each(function(){if(0===a.length||"object"===typeof a[0])b=0===a.length?{}:e.extend({},a[0]),b.element=e(this),"select"===b.element.get(0).tagName.toLowerCase()?f=b.element.attr("multiple"):(f=b.multiple||!1,"tags"in b&&(b.multiple=f=!0)),c=f?new z:new y,c.init(b);else if("string"===typeof a[0]){if(0>i(a[0],h))throw"Unknown method: "+a[0];d=g;c=e(this).data("select2");
80
+ if(c!==g&&(d="container"===a[0]?c.container:c[a[0]].apply(c,a.slice(1)),d!==g))return!1}else throw"Invalid arguments to select2 plugin: "+a;});return d===g?this:d};e.fn.select2.defaults={width:"copy",closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c){b=[];B(a.text,c.term,b);return b.join("")},formatSelection:function(a){return a?a.text:g},formatResultCssClass:function(){return g},formatNoMatches:function(){return"No matches found"},
81
+ formatInputTooShort:function(a,b){return"Please enter "+(b-a.length)+" more characters"},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return 0<=b.toUpperCase().indexOf(a.toUpperCase())},separator:",",tokenSeparators:[],tokenizer:H,
82
+ escapeMarkup:function(a){return a&&"string"===typeof a?a.replace(/&/g,"&amp;"):a},blurOnChange:!1};window.Select2={query:{ajax:C,local:D,tags:E},util:{debounce:A,markMatch:B},"class":{"abstract":w,single:y,multi:z}}}})(jQuery);
lib/select2/select2.png ADDED
Binary file
lib/select2/select2x2.png ADDED
Binary file
lib/select2/spinner.gif ADDED
Binary file
php/class-coauthors-guest-authors.php ADDED
@@ -0,0 +1,1214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Co-Authors Guest Authors
4
+ *
5
+ * Key idea: Create guest authors to assign as bylines on a post without having
6
+ * to give them access to the dashboard through a WP_User account
7
+ */
8
+
9
+ class CoAuthors_Guest_Authors
10
+ {
11
+
12
+ var $post_type = 'guest-author';
13
+ var $parent_page = 'users.php';
14
+ var $list_guest_authors_cap = 'list_users';
15
+
16
+ public static $cache_group = 'coauthors-plus-guest-authors';
17
+
18
+ /**
19
+ * Initialize our Guest Authors class and establish common hooks
20
+ */
21
+ function __construct() {
22
+ global $coauthors_plus;
23
+
24
+ // Add the guest author management menu
25
+ add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
26
+
27
+ // WP List Table for breaking out our Guest Authors
28
+ require_once( dirname( __FILE__ ) . '/class-coauthors-wp-list-table.php' );
29
+
30
+ // Get a co-author based on a query
31
+ add_action( 'wp_ajax_search_coauthors_to_assign', array( $this, 'handle_ajax_search_coauthors_to_assign' ) );
32
+
33
+ // Any CSS or JS
34
+ add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
35
+
36
+ // Extra notices
37
+ add_action( 'admin_notices', array( $this, 'action_admin_notices' ) );
38
+
39
+ // Handle actions to create or delete guest author accounts
40
+ add_action( 'admin_init', array( $this, 'handle_create_guest_author_action' ) );
41
+ add_action( 'admin_init', array( $this, 'handle_delete_guest_author_action' ) );
42
+
43
+ // Redirect if the user is mapped to a guest author
44
+ add_action( 'parse_request', array( $this, 'action_parse_request' ) );
45
+
46
+ // Filter author links and such
47
+ add_filter( 'author_link', array( $this, 'filter_author_link' ), 10, 3 );
48
+
49
+ // Validate new guest authors
50
+ add_filter( 'wp_insert_post_empty_content', array( $this, 'filter_wp_insert_post_empty_content' ), 10, 2 );
51
+
52
+ // Add metaboxes for our guest author management interface
53
+ add_action( 'add_meta_boxes', array( $this, 'action_add_meta_boxes' ), 10, 2 );
54
+ add_action( 'wp_insert_post_data', array( $this, 'manage_guest_author_filter_post_data' ), 10, 2 );
55
+ add_action( 'save_post', array( $this, 'manage_guest_author_save_meta_fields' ), 10, 2 );
56
+
57
+ // Empty associated caches when the guest author profile is updated
58
+ add_filter( 'update_post_metadata', array( $this, 'filter_update_post_metadata' ), 10, 5 );
59
+
60
+ // Modify the messages that appear when saving or creating
61
+ add_filter( 'post_updated_messages', array( $this, 'filter_post_updated_messages' ) );
62
+
63
+ // Allow admins to create or edit guest author profiles from the Manage Users listing
64
+ add_filter( 'user_row_actions', array( $this, 'filter_user_row_actions' ), 10, 2 );
65
+
66
+ // Add support for featured thumbnails that we can use for guest author avatars
67
+ add_action( 'after_setup_theme', array( $this, 'action_after_setup_theme' ) );
68
+ add_filter( 'get_avatar', array( $this, 'filter_get_avatar' ),10 ,5 );
69
+
70
+ // Allow users to change where this is placed in the WordPress admin
71
+ $this->parent_page = apply_filters( 'coauthors_guest_author_parent_page', $this->parent_page );
72
+
73
+ // Allow users to change the required cap for modifying guest authors
74
+ $this->list_guest_authors_cap = apply_filters( 'coauthors_guest_author_manage_cap', $this->list_guest_authors_cap );
75
+
76
+ // Set up default labels, but allow themes to modify
77
+ $this->labels = apply_filters( 'coauthors_guest_author_labels', array(
78
+ 'singular' => __( 'Guest Author', 'co-authors-plus' ),
79
+ 'plural' => __( 'Guest Authors', 'co-authors-plus' ),
80
+ 'all_items' => __( 'All Guest Authors', 'co-authors-plus' ),
81
+ 'add_new_item' => __( 'Add New Guest Author', 'co-authors-plus' ),
82
+ 'edit_item' => __( 'Edit Guest Author', 'co-authors-plus' ),
83
+ 'new_item' => __( 'New Guest Author', 'co-authors-plus' ),
84
+ 'view_item' => __( 'View Guest Author', 'co-authors-plus' ),
85
+ 'search_items' => __( 'Search Guest Authors', 'co-authors-plus' ),
86
+ 'not_found' => __( 'No guest authors found', 'co-authors-plus' ),
87
+ 'not_found_in_trash' => __( 'No guest authors found in Trash', 'co-authors-plus' ),
88
+ 'update_item' => __( 'Update Guest Author', 'co-authors-plus' ),
89
+ 'metabox_about' => __( 'About the guest author', 'co-authors-plus' ),
90
+ ) );
91
+
92
+ // Register a post type to store our authors that aren't WP.com users
93
+ $args = array(
94
+ 'label' => $this->labels['singular'],
95
+ 'labels' => array(
96
+ 'name' => $this->labels['plural'],
97
+ 'singular_name' => $this->labels['singular'],
98
+ 'add_new' => _x( 'Add New', 'co-authors-plus' ),
99
+ 'all_items' => $this->labels['all_items'],
100
+ 'add_new_item' => $this->labels['add_new_item'],
101
+ 'edit_item' => $this->labels['edit_item'],
102
+ 'new_item' => $this->labels['new_item'],
103
+ 'view_item' => $this->labels['view_item'],
104
+ 'search_items' => $this->labels['search_items'],
105
+ 'not_found' => $this->labels['not_found'],
106
+ 'not_found_in_trash' => $this->labels['not_found_in_trash'],
107
+ ),
108
+ 'public' => true,
109
+ 'publicly_queryable' => false,
110
+ 'exclude_from_search' => true,
111
+ 'show_in_menu' => false,
112
+ 'supports' => array(
113
+ 'thumbnail',
114
+ ),
115
+ 'taxonomies' => array(
116
+ $coauthors_plus->coauthor_taxonomy,
117
+ ),
118
+ 'rewrite' => false,
119
+ 'query_var' => false,
120
+ );
121
+ register_post_type( $this->post_type, $args );
122
+
123
+ // Hacky way to remove the title and the editor
124
+ remove_post_type_support( $this->post_type, 'title' );
125
+ remove_post_type_support( $this->post_type, 'editor' );
126
+
127
+ }
128
+
129
+ /**
130
+ * Filter the messages that appear when saving or updating a guest author
131
+ *
132
+ * @since 3.0
133
+ */
134
+ function filter_post_updated_messages( $messages ) {
135
+ global $post;
136
+
137
+ $guest_author = $this->get_guest_author_by( 'ID', $post->ID );
138
+ $guest_author_link = $this->filter_author_link( '', $guest_author->ID, $guest_author->user_nicename );
139
+
140
+ $messages[$this->post_type] = array(
141
+ 0 => '', // Unused. Messages start at index 1.
142
+ 1 => sprintf( __( 'Guest author updated. <a href="%s">View profile</a>', 'co-authors-plus' ), esc_url( $guest_author_link ) ),
143
+ 2 => __( 'Custom field updated.', 'co-authors-plus' ),
144
+ 3 => __( 'Custom field deleted.', 'co-authors-plus' ),
145
+ 4 => __( 'Guest author updated.', 'co-authors-plus' ),
146
+ /* translators: %s: date and time of the revision */
147
+ 5 => isset($_GET['revision']) ? sprintf( __( 'Guest author restored to revision from %s', 'co-authors-plus' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
148
+ 6 => sprintf( __( 'Guest author updated. <a href="%s">View profile</a>', 'co-authors-plus' ), esc_url( $guest_author_link ) ),
149
+ 7 => __( 'Guest author saved.', 'co-authors-plus' ),
150
+ 8 => sprintf( __( 'Guest author submitted. <a target="_blank" href="%s">Preview profile</a>', 'co-authors-plus' ), esc_url( add_query_arg( 'preview', 'true', $guest_author_link ) ) ),
151
+ 9 => sprintf( __( 'Guest author scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview profile</a>', 'co-authors-plus' ),
152
+ // translators: Publish box date format, see http://php.net/date
153
+ date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( $guest_author_link ) ),
154
+ 10 => sprintf( __('Guest author updated. <a target="_blank" href="%s">Preview profile</a>', 'co-authors-plus' ), esc_url( add_query_arg( 'preview', 'true', $guest_author_link ) ) ),
155
+ );
156
+ return $messages;
157
+ }
158
+
159
+ /**
160
+ * Handle the admin action to create a guest author based
161
+ * on an existing WordPress user
162
+ *
163
+ * @since 3.0
164
+ */
165
+ function handle_create_guest_author_action() {
166
+
167
+ if ( !isset( $_GET['action'], $_GET['nonce'], $_GET['user_id'] ) || $_GET['action'] != 'cap-create-guest-author' )
168
+ return;
169
+
170
+ if ( !wp_verify_nonce( $_GET['nonce'], 'create-guest-author' ) )
171
+ wp_die( __( "Doin' something fishy, huh?", 'co-authors-plus' ) );
172
+
173
+ if ( ! current_user_can( $this->list_guest_authors_cap ) )
174
+ wp_die( __( "You don't have permission to perform this action.", 'co-authors-plus' ) );
175
+
176
+ $user_id = intval( $_GET['user_id'] );
177
+
178
+ // Create the guest author
179
+ $post_id = $this->create_guest_author_from_user_id( $user_id );
180
+ if ( is_wp_error( $post_id ) )
181
+ wp_die( $post_id->get_error_message() );
182
+
183
+ // Redirect to the edit Guest Author screen
184
+ $edit_link = get_edit_post_link( $post_id, 'redirect' );
185
+ $redirect_to = add_query_arg( 'message', 'guest-author-created', $edit_link );
186
+ wp_safe_redirect( $redirect_to );
187
+ exit;
188
+
189
+ }
190
+
191
+ /**
192
+ * Handle the admin action to delete a guest author and possibly reassign their posts
193
+ *
194
+ * @since 3.0
195
+ */
196
+ function handle_delete_guest_author_action() {
197
+ global $coauthors_plus;
198
+
199
+ if ( !isset( $_POST['action'], $_POST['reassign'], $_POST['_wpnonce'], $_POST['id'] ) || 'delete-guest-author' != $_POST['action'] )
200
+ return;
201
+
202
+ // Verify the user is who they say they are
203
+ if ( !wp_verify_nonce( $_POST['_wpnonce'], 'delete-guest-author' ) )
204
+ wp_die( __( "Doin' something fishy, huh?", 'co-authors-plus' ) );
205
+
206
+ // Make sure they can perform the action
207
+ if ( ! current_user_can( $this->list_guest_authors_cap ) )
208
+ wp_die( __( "You don't have permission to perform this action.", 'co-authors-plus' ) );
209
+
210
+ // Make sure the guest author actually exists
211
+ $guest_author = $this->get_guest_author_by( 'ID', (int)$_POST['id'] );
212
+ if ( ! $guest_author )
213
+ wp_die( __( "Guest author can't be deleted because it doesn't exist.", 'co-authors-plus' ) );
214
+
215
+ // Perform the reassignment if needed
216
+ $guest_author_term = $coauthors_plus->get_author_term( $guest_author );
217
+ switch( $_POST['reassign'] ) {
218
+ // Leave assigned to the current linked account
219
+ case 'leave-assigned':
220
+ $reassign_to = $guest_author->linked_account;
221
+ break;
222
+ // Reassign to a different user
223
+ case 'reassign-another':
224
+ $user_nicename = sanitize_title( $_POST['leave-assigned-to'] );
225
+ $reassign_to = $coauthors_plus->get_coauthor_by( 'user_nicename', $user_nicename );
226
+ if ( ! $reassign_to )
227
+ wp_die( __( 'Co-author does not exists. Try again?', 'co-authors-plus' ) );
228
+ $reassign_to = $reassign_to->user_login;
229
+ break;
230
+ // Remove the byline, but don't delete the post
231
+ case 'remove-byline':
232
+ $reassign_to = false;
233
+ break;
234
+ default:
235
+ wp_die( __( "Please make sure to pick an option.", 'co-authors-plus' ) );
236
+ break;
237
+ }
238
+
239
+ $retval = $this->delete( $guest_author->ID, $reassign_to );
240
+
241
+ $args = array(
242
+ 'page' => 'view-guest-authors',
243
+ );
244
+ if ( is_wp_error( $retval ) )
245
+ $args['message'] = 'delete-error';
246
+ else
247
+ $args['message'] = 'guest-author-deleted';
248
+
249
+ // Redirect to safety
250
+ $redirect_to = add_query_arg( $args, admin_url( $this->parent_page ) );
251
+ wp_safe_redirect( $redirect_to );
252
+ exit;
253
+ }
254
+
255
+ /**
256
+ * Given a search query, suggest some co-authors that might match it
257
+ *
258
+ * @since 3.0
259
+ */
260
+ function handle_ajax_search_coauthors_to_assign() {
261
+ global $coauthors_plus;
262
+
263
+ if ( ! current_user_can( $this->list_guest_authors_cap ) )
264
+ die();
265
+
266
+ $search = sanitize_text_field( $_GET['q'] );
267
+
268
+ $results = wp_list_pluck( $coauthors_plus->search_authors( $search ), 'user_login' );
269
+ $retval = array();
270
+ foreach( $results as $user_login ) {
271
+ $coauthor = $coauthors_plus->get_coauthor_by( 'user_login', $user_login );
272
+ $retval[] = (object)array(
273
+ 'display_name' => $coauthor->display_name,
274
+ 'user_login' => $coauthor->user_login,
275
+ 'id' => $coauthor->user_nicename,
276
+ );
277
+ }
278
+ echo json_encode( $retval );
279
+ die();
280
+ }
281
+
282
+
283
+ /**
284
+ * Some redirection we need to do for linked accounts
285
+ *
286
+ * @todo support author ID query vars
287
+ */
288
+ function action_parse_request( $query ) {
289
+
290
+ if ( !isset( $query->query_vars['author_name'] ) )
291
+ return $query;
292
+
293
+ $coauthor = $this->get_guest_author_by( 'linked_account', sanitize_title( $query->query_vars['author_name'] ) );
294
+ if ( is_object( $coauthor ) && $query->query_vars['author_name'] != $coauthor->linked_account ) {
295
+ global $wp_rewrite;
296
+ $link = $wp_rewrite->get_author_permastruct();
297
+
298
+ if ( empty($link) ) {
299
+ $file = home_url( '/' );
300
+ $link = $file . '?author_name=' . $coauthor->user_login;
301
+ } else {
302
+ $link = str_replace('%author%', $coauthor->user_login, $link);
303
+ $link = home_url( user_trailingslashit( $link ) );
304
+ }
305
+ wp_safe_redirect( $link );
306
+ exit;
307
+ }
308
+
309
+ return $query;
310
+ }
311
+
312
+ /**
313
+ * Add the admin menus for seeing all co-authors
314
+ *
315
+ * @since 3.0
316
+ */
317
+ function action_admin_menu() {
318
+
319
+ add_submenu_page( $this->parent_page, $this->labels['plural'], $this->labels['plural'], $this->list_guest_authors_cap, 'view-guest-authors', array( $this, 'view_guest_authors_list' ) );
320
+
321
+ }
322
+
323
+ /**
324
+ * Enqueue any scripts or styles used for Guest Authors
325
+ *
326
+ * @since 3.0
327
+ */
328
+ function action_admin_enqueue_scripts() {
329
+ global $pagenow;
330
+ // Enqueue our guest author CSS on the related pages
331
+ if ( $this->parent_page == $pagenow && isset( $_GET['page'] ) && $_GET['page'] == 'view-guest-authors' ) {
332
+ wp_enqueue_script( 'jquery-select2', COAUTHORS_PLUS_URL . 'lib/select2/select2.min.js', array( 'jquery' ), COAUTHORS_PLUS_VERSION );
333
+ wp_enqueue_style( 'cap-jquery-select2-css', COAUTHORS_PLUS_URL . 'lib/select2/select2.css', false, COAUTHORS_PLUS_VERSION );
334
+
335
+ wp_enqueue_style( 'guest-authors-css', COAUTHORS_PLUS_URL . 'css/guest-authors.css', false, COAUTHORS_PLUS_VERSION );
336
+ wp_enqueue_script( 'guest-authors-js', COAUTHORS_PLUS_URL . 'js/guest-authors.js', false, COAUTHORS_PLUS_VERSION );
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Show some extra notices to the user
342
+ *
343
+ * @since 3.0
344
+ */
345
+ function action_admin_notices() {
346
+ global $pagenow;
347
+
348
+ if ( $this->parent_page != $pagenow || ! isset( $_REQUEST['message'] ) )
349
+ return;
350
+
351
+ switch( $_REQUEST['message'] ) {
352
+ case 'guest-author-deleted':
353
+ $message = __( 'Guest author deleted.', 'co-authors-plus' );
354
+ break;
355
+ default:
356
+ $message = false;
357
+ break;
358
+ }
359
+
360
+ if ( $message )
361
+ echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
362
+
363
+ }
364
+
365
+ /**
366
+ * Register the metaboxes used for Guest Authors
367
+ *
368
+ * @since 3.0
369
+ */
370
+ function action_add_meta_boxes() {
371
+ global $coauthors_plus;
372
+
373
+ if ( get_post_type() == $this->post_type ) {
374
+ // Remove the submitpost metabox because we have our own
375
+ remove_meta_box( 'submitdiv', $this->post_type, 'side' );
376
+ remove_meta_box( 'slugdiv', $this->post_type, 'normal' );
377
+ add_meta_box( 'coauthors-manage-guest-author-save', __( 'Save', 'co-authors-plus'), array( $this, 'metabox_manage_guest_author_save' ), $this->post_type, 'side', 'default' );
378
+ add_meta_box( 'coauthors-manage-guest-author-slug', __( 'Unique Slug', 'co-authors-plus'), array( $this, 'metabox_manage_guest_author_slug' ), $this->post_type, 'side', 'default' );
379
+ // Our metaboxes with co-author details
380
+ add_meta_box( 'coauthors-manage-guest-author-name', __( 'Name', 'co-authors-plus'), array( $this, 'metabox_manage_guest_author_name' ), $this->post_type, 'normal', 'default' );
381
+ add_meta_box( 'coauthors-manage-guest-author-contact-info', __( 'Contact Info', 'co-authors-plus'), array( $this, 'metabox_manage_guest_author_contact_info' ), $this->post_type, 'normal', 'default' );
382
+ add_meta_box( 'coauthors-manage-guest-author-bio', $this->labels['metabox_about'], array( $this, 'metabox_manage_guest_author_bio' ), $this->post_type, 'normal', 'default' );
383
+ }
384
+ }
385
+
386
+ /**
387
+ * View a list table of all guest authors
388
+ *
389
+ * @since 3.0
390
+ */
391
+ function view_guest_authors_list() {
392
+
393
+ // Allow guest authors to be deleted
394
+ if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' == $_GET['action'] ) {
395
+ // Make sure the user is who they say they are
396
+ if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'guest-author-delete' ) )
397
+ wp_die( __( "Doin' something fishy, huh?", 'co-authors-plus' ) );
398
+
399
+ // Make sure the guest author actually exists
400
+ $guest_author = $this->get_guest_author_by( 'ID', (int)$_GET['id'] );
401
+ if ( ! $guest_author )
402
+ wp_die( __( "Guest author can't be deleted because it doesn't exist.", 'co-authors-plus' ) );
403
+
404
+ echo '<div class="wrap">';
405
+ echo '<div class="icon32" id="icon-users"><br/></div>';
406
+ echo '<h2>' . sprintf( __( 'Delete %s', 'co-authors-plus ' ), $this->labels['plural'] ) . '</h2>';
407
+ echo '<p>' . __( 'You have specified this guest author for deletion:', 'co-authors-plus' ) . '</p>';
408
+ echo '<p>#' . $guest_author->ID . ': ' . esc_html( $guest_author->display_name ) . '</p>';
409
+ echo '<p>' . __( "What should be done with posts assigned to this guest author?", 'co-authors-plus' ) . '</p>';
410
+ echo '<p class="description">' . __( "Note: If you'd like to delete the guest author and all of their posts, you should delete their posts first and then come back to delete the guest author.", 'co-authors-plus' ) . '</p>';
411
+ echo '<form method="POST" action="' . esc_url( add_query_arg( 'page', 'view-guest-authors', admin_url( $this->parent_page ) ) ) . '">';
412
+ // Hidden stuffs
413
+ echo '<input type="hidden" name="action" value="delete-guest-author" />';
414
+ wp_nonce_field( 'delete-guest-author' );
415
+ echo '<input type="hidden" name="id" value="' . esc_attr( (int)$_GET['id'] ) . '" />';
416
+ echo '<fieldset><ul style="list-style-type:none;">';
417
+ // Reassign to another user
418
+ echo '<li class="hide-if-no-js"><label for="reassign-another">';
419
+ echo '<input type="radio" id="reassign-another" name="reassign" class="reassign-option" value="reassign-another" />&nbsp;&nbsp;' . __( 'Reassign to another co-author:', 'co-authors-plus' ) . '&nbsp;&nbsp;</label>';
420
+ echo '<input type="hidden" id="leave-assigned-to" name="leave-assigned-to" style="width:200px;" />';
421
+ echo '</li>';
422
+ // Leave mapped to a linked account
423
+ if ( get_user_by( 'login', $guest_author->linked_account ) ) {
424
+ echo '<li><label for="leave-assigned">';
425
+ echo '<input type="radio" id="leave-assigned" class="reassign-option" name="reassign" value="leave-assigned" />&nbsp;&nbsp;' . sprintf( __( 'Leave posts assigned to the mapped user, %s.', 'co-authors-plus' ), $guest_author->linked_account );
426
+ echo '</label></li>';
427
+ }
428
+ // Remove bylines from the posts
429
+ echo '<li><label for="remove-byline">';
430
+ echo '<input type="radio" id="remove-byline" class="reassign-option" name="reassign" value="remove-byline" />&nbsp;&nbsp;' . __( 'Remove byline from posts (but leave each post in its current status).', 'co-authors-plus' );
431
+ echo '</label></li>';
432
+ echo '</ul></fieldset>';
433
+ submit_button( __( 'Confirm Deletion', 'co-authors-plus' ), 'secondary', 'submit', true, array( 'disabled' => 'disabled' ) );
434
+ echo '</form>';
435
+ echo '</div>';
436
+ } else {
437
+ echo '<div class="wrap">';
438
+ echo '<div class="icon32" id="icon-users"><br/></div>';
439
+ echo '<h2>' . $this->labels['plural'];
440
+ // @todo caps check for creating a new user
441
+ $add_new_link = admin_url( "post-new.php?post_type=$this->post_type" );
442
+ echo '<a href="' . $add_new_link . '" class="add-new-h2">' . esc_html( __( 'Add New', 'co-authors-plus' ) ) . '</a>';
443
+ echo '</h2>';
444
+ $cap_list_table = new CoAuthors_WP_List_Table();
445
+ $cap_list_table->prepare_items();
446
+ echo '<form id="guest-authors-filter" action="" method="GET">';
447
+ echo '<input type="hidden" name="page" value="view-guest-authors" />';
448
+ $cap_list_table->display();
449
+ echo '</form>';
450
+ echo '</div>';
451
+ }
452
+
453
+ }
454
+
455
+ /**
456
+ * Metabox for saving or updating a Guest Author
457
+ *
458
+ * @since 3.0
459
+ */
460
+ function metabox_manage_guest_author_save() {
461
+ global $post, $coauthors_plus;
462
+
463
+ if ( in_array( $post->post_status, array( 'pending', 'publish', 'draft' ) ) )
464
+ $button_text = $this->labels['update_item'];
465
+ else
466
+ $button_text = $this->labels['add_new_item'];
467
+ submit_button( $button_text, 'primary', 'publish', false );
468
+
469
+ // Secure all of our requests
470
+ wp_nonce_field( 'guest-author-nonce', 'guest-author-nonce' );
471
+
472
+ }
473
+
474
+ /**
475
+ * Metabox for editing this guest author's slug or changing the linked account
476
+ *
477
+ * @since 3.0
478
+ */
479
+ function metabox_manage_guest_author_slug() {
480
+ global $post;
481
+
482
+ $pm_key = $this->get_post_meta_key( 'user_login' );
483
+ $existing_slug = get_post_meta( $post->ID, $pm_key, true );
484
+
485
+ echo '<input type="text" disabled="disabled" name="' . esc_attr( $pm_key ) . '" value="' . esc_attr( urldecode( $existing_slug ) ) . '" />';
486
+
487
+ // Taken from grist_authors.
488
+ $linked_account_key = $this->get_post_meta_key( 'linked_account' );
489
+ $linked_account = get_post_meta( $post->ID, $linked_account_key, true );
490
+ if ( $user = get_user_by( 'login', $linked_account ) )
491
+ $linked_account_id = $user->ID;
492
+ else
493
+ $linked_account_id = -1;
494
+
495
+ // If user_login is the same as linked account, don't let the association be removed
496
+ if ( $linked_account == $existing_slug )
497
+ add_filter( 'wp_dropdown_users', array( $this, 'filter_wp_dropdown_users_to_disable' ) );
498
+
499
+ $linked_account_user_ids = wp_list_pluck( $this->get_all_linked_accounts(), 'ID' );
500
+ if ( false !== ( $key = array_search( $linked_account_id, $linked_account_user_ids ) ) )
501
+ unset( $linked_account_user_ids[$key] );
502
+
503
+ echo '<p><label>' . __( 'WordPress User Mapping', 'co-authors-plus' ) . '</label> ';
504
+ wp_dropdown_users( array(
505
+ 'show_option_none' => __( '-- Not mapped --', 'co-authors-plus' ),
506
+ 'name' => esc_attr( $this->get_post_meta_key( 'linked_account' ) ),
507
+ // If we're adding an author or if there is no post author (0), then use -1 (which is show_option_none).
508
+ // We then take -1 on save and convert it back to 0. (#blamenacin)
509
+ 'selected' => $linked_account_id,
510
+ // Don't let user accounts to be linked to more than one guest author
511
+ 'exclude' => $linked_account_user_ids,
512
+ ) );
513
+ echo '</p>';
514
+
515
+ remove_filter( 'wp_dropdown_users', array( $this, 'filter_wp_dropdown_users_to_disable' ) );
516
+ }
517
+
518
+ /**
519
+ * Make a wp_dropdown_users disabled
520
+ * Only applied if the user_login value for the guest author matches its linked account
521
+ *
522
+ * @since 3.0
523
+ */
524
+ public function filter_wp_dropdown_users_to_disable( $output ) {
525
+ return str_replace( '<select ', '<select disabled="disabled" ', $output );
526
+ }
527
+
528
+ /**
529
+ * Metabox to display all of the pertient names for a Guest Author without a user account
530
+ *
531
+ * @since 3.0
532
+ */
533
+ function metabox_manage_guest_author_name() {
534
+ global $post;
535
+
536
+ $fields = $this->get_guest_author_fields( 'name' );
537
+ echo '<table class="form-table"><tbody>';
538
+ foreach( $fields as $field ) {
539
+ $pm_key = $this->get_post_meta_key( $field['key'] );
540
+ $value = get_post_meta( $post->ID, $pm_key, true );
541
+ echo '<tr><th>';
542
+ echo '<label for="' . esc_attr( $pm_key ) . '">' . $field['label'] . '</label>';
543
+ echo '</th><td>';
544
+ echo '<input type="text" name="' . esc_attr( $pm_key ) . '" value="' . esc_attr( $value ) . '" class="regular-text" />';
545
+ echo '</td></tr>';
546
+ }
547
+ echo '</tbody></table>';
548
+
549
+ }
550
+
551
+ /**
552
+ * Metabox to display all of the pertient contact details for a Guest Author without a user account
553
+ *
554
+ * @since 3.0
555
+ */
556
+ function metabox_manage_guest_author_contact_info() {
557
+ global $post;
558
+
559
+ $fields = $this->get_guest_author_fields( 'contact-info' );
560
+ echo '<table class="form-table"><tbody>';
561
+ foreach( $fields as $field ) {
562
+ $pm_key = $this->get_post_meta_key( $field['key'] );
563
+ $value = get_post_meta( $post->ID, $pm_key, true );
564
+ echo '<tr><th>';
565
+ echo '<label for="' . esc_attr( $pm_key ) . '">' . $field['label'] . '</label>';
566
+ echo '</th><td>';
567
+ echo '<input type="text" name="' . esc_attr( $pm_key ) . '" value="' . esc_attr( $value ) . '" class="regular-text" />';
568
+ echo '</td></tr>';
569
+ }
570
+ echo '</tbody></table>';
571
+
572
+ }
573
+
574
+ /**
575
+ * Metabox to edit the bio and other biographical details of the Guest Author
576
+ *
577
+ * @since 3.0
578
+ */
579
+ function metabox_manage_guest_author_bio() {
580
+ global $post;
581
+
582
+ $fields = $this->get_guest_author_fields( 'about' );
583
+ echo '<table class="form-table"><tbody>';
584
+ foreach( $fields as $field ) {
585
+ $pm_key = $this->get_post_meta_key( $field['key'] );
586
+ $value = get_post_meta( $post->ID, $pm_key, true );
587
+ echo '<tr><th>';
588
+ echo '<label for="' . esc_attr( $pm_key ) . '">' . $field['label'] . '</label>';
589
+ echo '</th><td>';
590
+ echo '<textarea style="width:300px;margin-bottom:6px;" name="' . esc_attr( $pm_key ) . '">' . esc_textarea( $value ) . '</textarea>';
591
+ echo '</td></tr>';
592
+ }
593
+ echo '</tbody></table>';
594
+
595
+ }
596
+
597
+ /**
598
+ * When a guest author is created or updated, we need to properly create
599
+ * the post_name based on some data provided by the user
600
+ *
601
+ * @since 3.0
602
+ */
603
+ function manage_guest_author_filter_post_data( $post_data, $original_args ) {
604
+
605
+ if ( $post_data['post_type'] != $this->post_type )
606
+ return $post_data;
607
+
608
+ // @todo caps check
609
+ if ( !isset( $_POST['guest-author-nonce'] ) || !wp_verify_nonce( $_POST['guest-author-nonce'], 'guest-author-nonce' ) )
610
+ return $post_data;
611
+
612
+ $post_data['post_title'] = sanitize_text_field( $_POST['cap-display_name'] );
613
+ $slug = sanitize_title( get_post_meta( $original_args['ID'], $this->get_post_meta_key( 'user_login' ), true ) );
614
+ if ( ! $slug )
615
+ $slug = sanitize_title( $_POST['cap-display_name'] );
616
+ // Uh oh, no guest authors without slugs
617
+ if ( ! $slug )
618
+ wp_die( __( 'Guest authors cannot be created without display names.', 'co-authors-plus' ) );
619
+ $post_data['post_name'] = $this->get_post_meta_key( $slug );
620
+
621
+ // Guest authors can't be created with the same user_login as a user
622
+ $user_nicename = str_replace( 'cap-', '', $slug );
623
+ $user = get_user_by( 'slug', $user_nicename );
624
+ if ( $user && $user->user_login != get_post_meta( $original_args['ID'], $this->get_post_meta_key( 'linked_account' ), true ) )
625
+ wp_die( __( 'Guest authors cannot be created with the same user_login value as a user. Try creating a profile from the user instead', 'co-authors-plus' ) );
626
+
627
+ // Guest authors can't have the same post_name value
628
+ $guest_author = $this->get_guest_author_by( 'post_name', $post_data['post_name'] );
629
+ if ( $guest_author && $guest_author->ID != $original_args['ID'] )
630
+ wp_die( __( 'Display name conflicts with another guest author display name.', 'co-authors-plus' ) );
631
+
632
+ return $post_data;
633
+ }
634
+
635
+ /**
636
+ * Save the various meta fields associated with our guest author model
637
+ *
638
+ * @since 3.0
639
+ */
640
+ function manage_guest_author_save_meta_fields( $post_id, $post ) {
641
+ global $coauthors_plus;
642
+
643
+ if ( $post->post_type != $this->post_type )
644
+ return;
645
+
646
+ // @todo caps check
647
+ if ( !isset( $_POST['guest-author-nonce'] ) || !wp_verify_nonce( $_POST['guest-author-nonce'], 'guest-author-nonce' ) )
648
+ return;
649
+
650
+ // Save our data to post meta
651
+ $author_fields = $this->get_guest_author_fields();
652
+ foreach( $author_fields as $author_field ) {
653
+
654
+ $key = $this->get_post_meta_key( $author_field['key'] );
655
+ // 'user_login' should only be saved on post update if it doesn't exist
656
+ if ( 'user_login' == $author_field['key'] && !get_post_meta( $post_id, $key, true ) ) {
657
+ $display_name_key = $this->get_post_meta_key( 'display_name' );
658
+ $temp_slug = sanitize_title( $_POST[$display_name_key] );
659
+ update_post_meta( $post_id, $key, $temp_slug );
660
+ continue;
661
+ }
662
+ if ( 'linked_account' == $author_field['key'] ) {
663
+ $linked_account_key = $this->get_post_meta_key( 'linked_account' );
664
+ if ( ! empty( $_POST[$linked_account_key] ) )
665
+ $user_id = intval( $_POST[$linked_account_key] );
666
+ else
667
+ continue;
668
+ $user = get_user_by( 'id', $user_id );
669
+ if ( $user_id > 0 && is_object( $user ) )
670
+ $user_login = $user->user_login;
671
+ else
672
+ $user_login = '';
673
+ update_post_meta( $post_id, $key, $user_login );
674
+ continue;
675
+ }
676
+ if ( !isset( $_POST[$key] ) )
677
+ continue;
678
+ if ( isset( $author_field['sanitize_function'] ) && function_exists( $author_field['sanitize_function'] ) )
679
+ $value = $author_field['sanitize_function']( $_POST[$key] );
680
+ else
681
+ $value = sanitize_text_field( $_POST[$key] );
682
+ update_post_meta( $post_id, $key, $value );
683
+ }
684
+
685
+ $author = $this->get_guest_author_by( 'ID', $post_id );
686
+ $author_term = $coauthors_plus->update_author_term( $author );
687
+ // Add the author as a post term
688
+ wp_set_post_terms( $post_id, array( $author_term->slug ), $coauthors_plus->coauthor_taxonomy, false );
689
+ }
690
+
691
+ /**
692
+ * Return a simulated WP_User object based on the post ID
693
+ * of a guest author
694
+ *
695
+ * @since 3.0
696
+ *
697
+ * @param string $key Key to search by (login,email)
698
+ * @param string $value Value to search for
699
+ * @param object|false $coauthor The guest author on success, false on failure
700
+ */
701
+ function get_guest_author_by( $key, $value ) {
702
+ global $wpdb;
703
+
704
+ $cache_key = md5( 'guest-author-' . $key . '-' . $value );
705
+ if ( false !== ( $retval = wp_cache_get( $cache_key, self::$cache_group ) ) )
706
+ return $retval;
707
+
708
+ switch( $key ) {
709
+ case 'ID':
710
+ case 'id':
711
+ $query = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID=%d", $value );
712
+ $post_id = $wpdb->get_var( $query );
713
+ if ( empty( $post_id ) )
714
+ return false;
715
+ break;
716
+ case 'user_nicename':
717
+ case 'post_name':
718
+ $value = $this->get_post_meta_key( $value );
719
+ $query = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_name=%s AND post_type = %s", $value, $this->post_type );
720
+ $post_id = $wpdb->get_var( $query );
721
+ if ( empty( $post_id ) )
722
+ return false;
723
+ break;
724
+ case 'login':
725
+ case 'user_login':
726
+ case 'linked_account':
727
+ if ( 'login' == $key )
728
+ $key = 'user_login';
729
+ // Ensure we aren't doing the lookup by the prefixed value
730
+ if ( 'user_login' == $key )
731
+ $value = preg_replace( '#^cap\-#', '', $value );
732
+ $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%s;", $this->get_post_meta_key( $key ), $value );
733
+ $post_id = $wpdb->get_var( $query );
734
+ if ( empty( $post_id ) ) {
735
+ if ( 'user_login' == $key )
736
+ return $this->get_guest_author_by( 'post_name', $value ); // fallback to post_name in case the guest author isn't a linked account
737
+ return false;
738
+ }
739
+ break;
740
+ default:
741
+ $post_id = false;
742
+ break;
743
+ }
744
+
745
+ if ( !$post_id )
746
+ return false;
747
+
748
+ $guest_author = array(
749
+ 'ID' => $post_id,
750
+ );
751
+
752
+ // Load the guest author fields
753
+ $fields = $this->get_guest_author_fields();
754
+ foreach( $fields as $field ) {
755
+ $key = $field['key'];
756
+ $pm_key = $this->get_post_meta_key( $field['key'] );
757
+ $guest_author[$key] = get_post_meta( $post_id, $pm_key, true );
758
+ }
759
+ // Support for non-Latin characters. They're stored as urlencoded slugs
760
+ $guest_author['user_login'] = urldecode( $guest_author['user_login'] );
761
+
762
+ // Hack to model the WP_User object
763
+ $guest_author['user_nicename'] = sanitize_title( $guest_author['user_login'] );
764
+ $guest_author['type'] = 'guest-author';
765
+
766
+ wp_cache_set( $cache_key, (object)$guest_author, self::$cache_group );
767
+
768
+ return (object)$guest_author;
769
+ }
770
+
771
+ /**
772
+ * Get all of the meta fields that can be associated with a guest author
773
+ *
774
+ * @since 3.0
775
+ */
776
+ function get_guest_author_fields( $groups = 'all' ) {
777
+
778
+ $groups = (array)$groups;
779
+ $global_fields = array(
780
+ // Hidden (included in object, no UI elements)
781
+ array(
782
+ 'key' => 'ID',
783
+ 'label' => __( 'ID', 'co-authors-plus' ),
784
+ 'group' => 'hidden',
785
+ ),
786
+ // Name
787
+ array(
788
+ 'key' => 'display_name',
789
+ 'label' => __( 'Display Name', 'co-authors-plus'),
790
+ 'group' => 'name',
791
+ 'required' => true,
792
+ ),
793
+ array(
794
+ 'key' => 'first_name',
795
+ 'label' => __( 'First Name', 'co-authors-plus'),
796
+ 'group' => 'name',
797
+ ),
798
+ array(
799
+ 'key' => 'last_name',
800
+ 'label' => __( 'Last Name', 'co-authors-plus'),
801
+ 'group' => 'name',
802
+ ),
803
+ array(
804
+ 'key' => 'user_login',
805
+ 'label' => __( 'Slug', 'co-authors-plus'),
806
+ 'group' => 'slug',
807
+ 'required' => true,
808
+ ),
809
+ // Contact info
810
+ array(
811
+ 'key' => 'user_email',
812
+ 'label' => __( 'E-mail', 'co-authors-plus' ),
813
+ 'group' => 'contact-info',
814
+ ),
815
+ array(
816
+ 'key' => 'linked_account',
817
+ 'label' => __( 'Linked Account', 'co-authors-plus' ),
818
+ 'group' => 'slug',
819
+ ),
820
+ array(
821
+ 'key' => 'website',
822
+ 'label' => __( 'Website', 'co-authors-plus' ),
823
+ 'group' => 'contact-info',
824
+ ),
825
+ array(
826
+ 'key' => 'aim',
827
+ 'label' => __( 'AIM', 'co-authors-plus' ),
828
+ 'group' => 'contact-info',
829
+ ),
830
+ array(
831
+ 'key' => 'yahooim',
832
+ 'label' => __( 'Yahoo IM', 'co-authors-plus' ),
833
+ 'group' => 'contact-info',
834
+ ),
835
+ array(
836
+ 'key' => 'jabber',
837
+ 'label' => __( 'Jabber / Google Talk', 'co-authors-plus' ),
838
+ 'group' => 'contact-info',
839
+ ),
840
+ array(
841
+ 'key' => 'description',
842
+ 'label' => __( 'Biographical Info', 'co-authors-plus' ),
843
+ 'group' => 'about',
844
+ 'sanitize_function' => 'wp_filter_post_kses',
845
+ ),
846
+ );
847
+ $fields_to_return = array();
848
+ foreach( $global_fields as $single_field ) {
849
+ if ( in_array( $single_field['group'], $groups ) || $groups[0] == 'all' && $single_field['group'] != 'hidden' )
850
+ $fields_to_return[] = $single_field;
851
+ }
852
+
853
+ return apply_filters( 'coauthors_guest_author_fields', $fields_to_return, $groups );
854
+
855
+ }
856
+
857
+ /**
858
+ * Gets a postmeta key by prefixing it with 'cap-'
859
+ * if not yet prefixed
860
+ *
861
+ * @since 3.0
862
+ */
863
+ function get_post_meta_key( $key ) {
864
+
865
+ if ( 0 !== stripos( $key, 'cap-' ) )
866
+ $key = 'cap-' . $key;
867
+
868
+ return $key;
869
+ }
870
+
871
+ /**
872
+ * Get all of the user accounts that have been linked
873
+ *
874
+ * @since 3.0
875
+ */
876
+ function get_all_linked_accounts( $force = false ) {
877
+ global $wpdb;
878
+
879
+ $cache_key = 'all-linked-accounts';
880
+ $retval = wp_cache_get( $cache_key, self::$cache_group );
881
+
882
+ if ( true === $force || false === $retval ) {
883
+ $user_logins = $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value !=''", $this->get_post_meta_key( 'linked_account' ) ) );
884
+ $users = array();
885
+ foreach( $user_logins as $user_login ) {
886
+ $user = get_user_by( 'login', $user_login );
887
+ if ( ! $user )
888
+ continue;
889
+ $users[] = array(
890
+ 'ID' => $user->ID,
891
+ 'user_login' => $user->user_login,
892
+ );
893
+ }
894
+ $retval = $users;
895
+ wp_cache_set( $cache_key, $retval, self::$cache_group );
896
+ }
897
+ return ( $retval ) ? $retval : array();
898
+ }
899
+
900
+ /**
901
+ * Filter update post metadata
902
+ * Clean caches when any of the values have been changed
903
+ *
904
+ * @since 3.0
905
+ */
906
+ function filter_update_post_metadata( $retnull, $object_id, $meta_key, $meta_value, $prev_value ) {
907
+
908
+ if ( $this->post_type != get_post_type( $object_id ) )
909
+ return null;
910
+
911
+ // If the linked_account is changing, invalidate the cache of all linked accounts
912
+ // Don't regenerate though, as we haven't saved the new value
913
+ $linked_account_key = $this->get_post_meta_key( 'linked_account' );
914
+ if ( $linked_account_key == $meta_key && $meta_value != get_post_meta( $object_id, $linked_account_key, true ) ) {
915
+ $this->delete_guest_author_cache( $object_id );
916
+ }
917
+
918
+ // If one of the guest author meta values has changed, we'll need to invalidate all keys
919
+ if ( false !== strpos( $meta_key, 'cap-' ) && $meta_value != get_post_meta( $object_id, $meta_key, true ) ) {
920
+ $this->delete_guest_author_cache( $object_id );
921
+ }
922
+
923
+ return null;
924
+ }
925
+
926
+ /**
927
+ * Delete all of the cache values associated with a guest author
928
+ *
929
+ * @since 3.0
930
+ *
931
+ * @param int|object $guest_author The guest author ID or object
932
+ */
933
+ public function delete_guest_author_cache( $id_or_object ) {
934
+
935
+ if ( is_object( $id_or_object ) )
936
+ $guest_author = $id_or_object;
937
+ else
938
+ $guest_author = $this->get_guest_author_by( 'ID', $id_or_object );
939
+
940
+ // Delete the lookup cache associated with each old co-author value
941
+ $keys = wp_list_pluck( $this->get_guest_author_fields(), 'key' );
942
+ $keys = array_merge( $keys, array( 'login', 'post_name', 'user_nicename', 'ID' ) );
943
+ foreach( $keys as $key ) {
944
+ if ( 'post_name' == $key )
945
+ $key = 'user_nicename';
946
+ else if ( 'login' == $key )
947
+ $key = 'user_login';
948
+ $cache_key = md5( 'guest-author-' . $key . '-' . $guest_author->$key );
949
+ wp_cache_delete( $cache_key, self::$cache_group );
950
+ }
951
+
952
+ // Delete the 'all-linked-accounts' cache
953
+ wp_cache_delete( 'all-linked-accounts', self::$cache_group );
954
+
955
+ }
956
+
957
+
958
+ /**
959
+ * Create a guest author
960
+ *
961
+ * @since 3.0
962
+ */
963
+ function create( $args ) {
964
+ global $coauthors_plus;
965
+
966
+ // Validate the arguments that have been passed
967
+ $fields = $this->get_guest_author_fields();
968
+ foreach( $fields as $field ) {
969
+
970
+ // Make sure required fields are there
971
+ if ( isset( $field['required'] ) && $field['required'] && empty( $args[$field['key']] ) ) {
972
+ return new WP_Error( 'field-required', sprintf( __( '%s is a required field', 'co-authors-plus' ), $field['key'] ) );
973
+ }
974
+
975
+ // The user login field shouldn't collide with any existing users
976
+ if ( 'user_login' == $field['key'] && $existing_coauthor = $coauthors_plus->get_coauthor_by( 'user_login', $args['user_login'] ) ) {
977
+ if ( 'guest-author' == $existing_coauthor->type )
978
+ return new WP_Error( 'duplicate-field', __( 'user_login cannot duplicate existing guest author or mapped user', 'co-authors-plus' ) );
979
+ }
980
+
981
+ }
982
+
983
+ // Create the primary post object
984
+ $new_post = array(
985
+ 'post_title' => $args['display_name'],
986
+ 'post_name' => sanitize_title( $this->get_post_meta_key( $args['user_login'] ) ),
987
+ 'post_type' => $this->post_type,
988
+ );
989
+ $post_id = wp_insert_post( $new_post, true );
990
+ if ( is_wp_error( $post_id ) )
991
+ return $post_id;
992
+
993
+ // Add all of the fields for the new guest author
994
+ foreach( $fields as $field ) {
995
+ $key = $field['key'];
996
+ if ( empty( $args[$key] ) )
997
+ continue;
998
+ $pm_key = $this->get_post_meta_key( $key );
999
+ update_post_meta( $post_id, $pm_key, $args[$key] );
1000
+ }
1001
+
1002
+ // Make sure the author term exists and that we're assigning it to this post type
1003
+ $author_term = $coauthors_plus->update_author_term( $this->get_guest_author_by( 'ID', $post_id ) );
1004
+ wp_set_post_terms( $post_id, array( $author_term->slug ), $coauthors_plus->coauthor_taxonomy, false );
1005
+
1006
+ return $post_id;
1007
+ }
1008
+
1009
+ /**
1010
+ * Delete a guest author
1011
+ *
1012
+ * @since 3.0
1013
+ *
1014
+ * @param int $post_id The ID for the guest author profile
1015
+ * @param string $reassign_to User login value for the co-author to reassign posts to
1016
+ * @return bool|WP_Error $success True on success, WP_Error on a failure
1017
+ */
1018
+ public function delete( $id, $reassign_to = false ) {
1019
+ global $coauthors_plus;
1020
+
1021
+ $guest_author = $this->get_guest_author_by( 'ID', $id );
1022
+ if ( ! $guest_author )
1023
+ return new WP_Error( 'guest-author-missing', __( 'Guest author does not exist', 'co-authors-plus' ) );
1024
+
1025
+ $guest_author_term = $coauthors_plus->get_author_term( $guest_author );
1026
+
1027
+ if ( $reassign_to ) {
1028
+
1029
+ // We're reassigning the guest author's posts user to its linked account
1030
+ if ( $guest_author->linked_account == $reassign_to )
1031
+ $reassign_to_author = get_user_by( 'login', $reassign_to );
1032
+ else
1033
+ $reassign_to_author = $coauthors_plus->get_coauthor_by( 'user_login', $reassign_to );
1034
+ if ( ! $reassign_to_author )
1035
+ return new WP_Error( 'reassign-to-missing', __( 'Reassignment co-author does not exist', 'co-authors-plus' ) );
1036
+
1037
+ $reassign_to_term = $coauthors_plus->get_author_term( $reassign_to_author );
1038
+ // In the case where the guest author and its linked account shared the same term, we don't want to reassign
1039
+ if ( $guest_author_term->term_id != $reassign_to_term->term_id )
1040
+ wp_delete_term( $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy, array( 'default' => $reassign_to_term->term_id, 'force_default' => true ) );
1041
+
1042
+ } else {
1043
+ wp_delete_term( $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy );
1044
+ }
1045
+
1046
+ // Delete the guest author profile
1047
+ wp_delete_post( $guest_author->ID, true );
1048
+
1049
+ // Make sure all of the caches are reset
1050
+ $this->delete_guest_author_cache( $guest_author );
1051
+ return true;
1052
+ }
1053
+
1054
+
1055
+ /**
1056
+ * Create a guest author from an existing WordPress user
1057
+ *
1058
+ * @since 3.0
1059
+ *
1060
+ * @param int $user_id ID for a WordPress user
1061
+ * @return int|WP_Error $retval ID for the new guest author on success, WP_Error on failure
1062
+ */
1063
+ function create_guest_author_from_user_id( $user_id ) {
1064
+
1065
+ $user = get_user_by( 'id', $user_id );
1066
+ if ( ! $user )
1067
+ return new WP_Error( 'invalid-user', __( 'No user exists with that ID', 'co-authors-plus' ) );
1068
+
1069
+ $guest_author = array();
1070
+ foreach( $this->get_guest_author_fields() as $field ) {
1071
+ $key = $field['key'];
1072
+ if ( ! empty( $user->$key ) )
1073
+ $guest_author[$key] = $user->$key;
1074
+ else
1075
+ $guest_author[$key] = '';
1076
+ }
1077
+ // Don't need the old user ID
1078
+ unset( $guest_author['ID'] );
1079
+ // Retain the user mapping and try to produce an unique user_login based on the name.
1080
+ $guest_author['linked_account'] = $guest_author['user_login'];
1081
+ if ( ! empty( $guest_author['display_name'] ) && $guest_author['display_name'] != $guest_author['user_login'] )
1082
+ $guest_author['user_login'] = sanitize_title( $guest_author['display_name'] );
1083
+ else if ( ! empty( $guest_author['first_name'] ) && ! empty( $guest_author['last_name'] ) )
1084
+ $guest_author['user_login'] = sanitize_title( $guest_author['first_name'] . ' ' . $guest_author['last_name'] );
1085
+
1086
+ $retval = $this->create( $guest_author );
1087
+ return $retval;
1088
+ }
1089
+
1090
+ /**
1091
+ * Guest authors must have Display Names
1092
+ *
1093
+ * @since 3.0
1094
+ */
1095
+ function filter_wp_insert_post_empty_content( $maybe_empty, $postarr ) {
1096
+
1097
+ if ( $this->post_type != $postarr['post_type'] )
1098
+ return $maybe_empty;
1099
+
1100
+ if ( empty( $postarr['post_title'] ) )
1101
+ return true;
1102
+
1103
+ return $maybe_empty;
1104
+ }
1105
+
1106
+ /**
1107
+ * On the User Management view, add action links to create or edit
1108
+ * guest author profiles
1109
+ *
1110
+ * @since 3.0
1111
+ *
1112
+ * @param array $actions The existing actions to perform on a user
1113
+ * @param object $user_object A WP_User object
1114
+ * @return array $actions Modified actions
1115
+ */
1116
+ function filter_user_row_actions( $actions, $user_object ) {
1117
+
1118
+ if ( ! current_user_can( $this->list_guest_authors_cap ) )
1119
+ return $actions;
1120
+
1121
+ $new_actions = array();
1122
+ if ( $guest_author = $this->get_guest_author_by( 'linked_account', $user_object->user_login ) ) {
1123
+ $edit_guest_author_link = get_edit_post_link( $guest_author->ID );
1124
+ $new_actions['edit-guest-author'] = '<a href="' . esc_url( $edit_guest_author_link ) . '">' . __( 'Edit Profile', 'co-authors-plus' ) . '</a>';
1125
+ } else {
1126
+ $query_args = array(
1127
+ 'action' => 'cap-create-guest-author',
1128
+ 'user_id' => $user_object->ID,
1129
+ 'nonce' => wp_create_nonce( 'create-guest-author' ),
1130
+ );
1131
+ $create_guest_author_link = add_query_arg( $query_args, admin_url( $this->parent_page ) );
1132
+ $new_actions['create-guest-author'] = '<a href="' . esc_url( $create_guest_author_link ) . '">' . __( 'Create Profile', 'co-authors-plus' ) . '</a>';
1133
+ }
1134
+
1135
+ return $new_actions + $actions;
1136
+ }
1137
+
1138
+ /**
1139
+ * Anything to do after the theme has been set up
1140
+ *
1141
+ * @since 3.0
1142
+ */
1143
+ function action_after_setup_theme() {
1144
+ add_theme_support( 'post-thumbnails', array( $this->post_type ) );
1145
+
1146
+ // Some of the common sizes used by get_avatar
1147
+ $this->avatar_sizes = array(
1148
+ 32,
1149
+ 64,
1150
+ 96,
1151
+ 128
1152
+ );
1153
+ $this->avatar_sizes = apply_filters( 'coauthors_guest_author_avatar_sizes', $this->avatar_sizes );
1154
+ foreach( $this->avatar_sizes as $size ) {
1155
+ add_image_size( 'guest-author-' . $size, $size, $size, true );
1156
+ }
1157
+ }
1158
+
1159
+ /**
1160
+ * Filter 'get_avatar' to replace with our own avatar if one exists
1161
+ *
1162
+ * @since 3.0
1163
+ */
1164
+ function filter_get_avatar( $avatar, $id_or_email, $size, $default ) {
1165
+
1166
+ if ( is_object( $id_or_email ) || !is_email( $id_or_email ) )
1167
+ return $avatar;
1168
+
1169
+ // See if this matches a guest author
1170
+ $guest_author = $this->get_guest_author_by( 'user_email', $id_or_email );
1171
+ if ( ! $guest_author )
1172
+ return $avatar;
1173
+
1174
+ // See if the guest author as an avatar
1175
+ if ( ! has_post_thumbnail( $guest_author->ID ) )
1176
+ return $avatar;
1177
+
1178
+ $args = array(
1179
+ 'class' => "avatar avatar-{$size} photo",
1180
+ );
1181
+ if ( in_array( $size, $this->avatar_sizes ) )
1182
+ $size = 'guest-author-' . $size;
1183
+ else
1184
+ $size = array( $size, $size );
1185
+ $avatar = get_the_post_thumbnail( $guest_author->ID, $size, $args );
1186
+
1187
+ return $avatar;
1188
+ }
1189
+
1190
+ /**
1191
+ * Filter the URL used in functions like the_author_posts_link()
1192
+ *
1193
+ * @since 3.0
1194
+ */
1195
+ function filter_author_link( $link, $author_id, $author_nicename ) {
1196
+
1197
+ // If we're using this at the top of the loop on author.php,
1198
+ // our queried object should be set correctly
1199
+ if ( !$author_nicename && is_author() && get_queried_object() )
1200
+ $author_nicename = get_queried_object()->user_nicename;
1201
+
1202
+ if ( empty($link) ) {
1203
+ $link = add_query_arg( 'author_name', $author_nicename, home_url() );
1204
+ } else {
1205
+ global $wp_rewrite;
1206
+ $link = $wp_rewrite->get_author_permastruct();
1207
+ $link = str_replace('%author%', $author_nicename, $link);
1208
+ $link = home_url( user_trailingslashit( $link ) );
1209
+ }
1210
+ return $link;
1211
+
1212
+ }
1213
+
1214
+ }
php/class-coauthors-template-filters.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * For themes where it's easily doable, add support for Co-Authors Plus on the frontend
4
+ * by filtering the common template tags
5
+ */
6
+
7
+ class CoAuthors_Template_Filters {
8
+
9
+ function __construct() {
10
+ add_filter( 'the_author', array( $this, 'filter_the_author' ) );
11
+ add_filter( 'the_author_posts_link', array( $this, 'filter_the_author_posts_link' ) );
12
+ }
13
+
14
+ function filter_the_author() {
15
+ return coauthors( null, null, null, null, false );
16
+ }
17
+
18
+ function filter_the_author_posts_link() {
19
+ return coauthors_posts_links( null, null, null, null, false );
20
+ }
21
+ }
php/class-coauthors-wp-list-table.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ //Our class extends the WP_List_Table class, so we need to make sure that it's there
3
+
4
+ require_once( ABSPATH . 'wp-admin/includes/screen.php' );
5
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
6
+
7
+ /**
8
+ * List all of the available Co-Authors within the system
9
+ */
10
+ class CoAuthors_WP_List_Table extends WP_List_Table {
11
+
12
+ var $is_search = false;
13
+
14
+ function __construct() {
15
+ if( !empty( $_REQUEST['s'] ) )
16
+ $this->is_search = true;
17
+
18
+ parent::__construct( array(
19
+ 'plural' => __( 'Co-Authors', 'co-authors-plus' ),
20
+ 'singular' => __( 'Co-Author', 'co-authors-plus' ),
21
+ ) );
22
+ }
23
+
24
+ /**
25
+ * Perform Co-Authors Query
26
+ */
27
+ function prepare_items() {
28
+ global $coauthors_plus;
29
+
30
+ $columns = $this->get_columns();
31
+ $hidden = array();
32
+ $sortable = array(
33
+ 'display_name' => array( 'display_name', 'ASC' ),
34
+ 'first_name' => array( 'first_name', 'ASC' ),
35
+ 'last_name' => array( 'last_name', 'ASC' ),
36
+ );
37
+ $this->_column_headers = array( $columns, $hidden, $sortable );
38
+
39
+ $paged = ( isset( $_REQUEST['paged'] ) ) ? intval( $_REQUEST['paged'] ) : 1;
40
+ $per_page = 20;
41
+
42
+ $args = array(
43
+ 'paged' => $paged,
44
+ 'posts_per_page' => $per_page,
45
+ 'post_type' => $coauthors_plus->guest_authors->post_type,
46
+ 'post_status' => 'any',
47
+ 'orderby' => 'title',
48
+ 'order' => 'ASC',
49
+ );
50
+
51
+ if ( isset( $_REQUEST['orderby'] ) ) {
52
+ switch( $_REQUEST['orderby'] ) {
53
+ case 'display_name':
54
+ $args['orderby'] = 'title';
55
+ break;
56
+ case 'first_name':
57
+ case 'last_name':
58
+ $args['orderby'] = 'meta_value';
59
+ $args['meta_key'] = $coauthors_plus->guest_authors->get_post_meta_key( $_REQUEST['orderby'] );
60
+ break;
61
+ }
62
+ }
63
+ if ( isset( $_REQUEST['order'] ) && in_array( strtoupper( $_REQUEST['order'] ), array( 'ASC', 'DESC' ) ) ) {
64
+ $args['order'] = strtoupper( $_REQUEST['order'] );
65
+ }
66
+
67
+ $this->filters = array(
68
+ 'show-all' => __( 'Show all', 'co-authors-plus' ),
69
+ 'with-linked-account' => __( 'With linked account', 'co-authors-plus' ),
70
+ 'without-linked-account' => __( 'Without linked account', 'co-authors-plus' ),
71
+ );
72
+
73
+ if ( isset( $_REQUEST['filter'] ) && array_key_exists( $_REQUEST['filter'], $this->filters ) ) {
74
+ $this->active_filter = sanitize_key( $_REQUEST['filter'] );
75
+ } else {
76
+ $this->active_filter = 'show-all';
77
+ }
78
+
79
+ switch( $this->active_filter ) {
80
+ case 'with-linked-account':
81
+ case 'without-linked-account':
82
+ $args['meta_key'] = $coauthors_plus->guest_authors->get_post_meta_key( 'linked_account' );
83
+ if ( 'with-linked-account' == $this->active_filter )
84
+ $args['meta_compare'] = '!=';
85
+ else
86
+ $args['meta_compare'] = '=';
87
+ $args['meta_value'] = '0';
88
+ break;
89
+ }
90
+
91
+ if( $this->is_search )
92
+ add_filter( 'posts_where', array( $this, 'filter_query_for_search' ) );
93
+
94
+ $author_posts = new WP_Query( $args );
95
+ $items = array();
96
+ foreach( $author_posts->get_posts() as $author_post ) {
97
+ $items[] = $coauthors_plus->guest_authors->get_guest_author_by( 'id', $author_post->ID );
98
+ }
99
+
100
+ if( $this->is_search )
101
+ remove_filter( 'posts_where', array( $this, 'filter_query_for_search' ) );
102
+
103
+ $this->items = $items;
104
+
105
+ $this->set_pagination_args( array(
106
+ 'total_items' => $author_posts->found_posts,
107
+ 'per_page' => $per_page,
108
+ ) );
109
+ }
110
+
111
+ function filter_query_for_search( $where ) {
112
+ global $wpdb;
113
+ $var = '%' . sanitize_text_field( $_REQUEST['s'] ) . '%';
114
+ $where .= $wpdb->prepare( ' AND (post_title LIKE %s OR post_name LIKE %s )', $var, $var);
115
+ return $where;
116
+ }
117
+
118
+ /**
119
+ * Either there are no guest authors, or the search doesn't match any
120
+ */
121
+ function no_items() {
122
+ _e( 'No matching guest authors were found.', 'co-authors-plus' );
123
+ }
124
+
125
+ /**
126
+ * Generate the columns of information to be displayed on our list table
127
+ */
128
+ function get_columns() {
129
+ $columns = array(
130
+ 'display_name' => __( 'Display Name', 'co-authors-plus' ),
131
+ 'first_name' => __( 'First Name', 'co-authors-plus' ),
132
+ 'last_name' => __( 'Last Name', 'co-authors-plus' ),
133
+ 'user_email' => __( 'E-mail', 'co-authors-plus' ),
134
+ 'linked_account' => __( 'Linked Account', 'co-authors-plus' ),
135
+ 'posts' => __( 'Posts', 'co-authors-plus' ),
136
+ );
137
+ return $columns;
138
+ }
139
+
140
+ /**
141
+ * Render a single row
142
+ */
143
+ function single_row( $item ) {
144
+ static $alternate_class = '';
145
+ $alternate_class = ( $alternate_class == '' ? ' alternate' : '' );
146
+ $row_class = ' class="guest-author-static' . $alternate_class . '"';
147
+
148
+ echo '<tr id="guest-author-' . $item->ID . '"' . $row_class . '>';
149
+ echo $this->single_row_columns( $item );
150
+ echo '</tr>';
151
+ }
152
+
153
+ /**
154
+ * Render columns, some are overridden below
155
+ */
156
+ function column_default( $item, $column_name ) {
157
+
158
+ switch( $column_name ) {
159
+ case 'first_name':
160
+ case 'last_name':
161
+ return $item->$column_name;
162
+ case 'user_email':
163
+ return '<a href="' . esc_attr( 'mailto:' . $item->user_email ) . '">' . esc_html( $item->user_email ) . '</a>';
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Render display name, e.g. author name
169
+ */
170
+ function column_display_name( $item ) {
171
+
172
+ $item_edit_link = get_edit_post_link( $item->ID );
173
+ $args = array(
174
+ 'action' => 'delete',
175
+ 'id' => $item->ID,
176
+ '_wpnonce' => wp_create_nonce( 'guest-author-delete' ),
177
+ );
178
+ $item_delete_link = add_query_arg( $args, menu_page_url( 'view-guest-authors', false ) );
179
+ $item_view_link = get_author_posts_url( $item->ID, $item->user_nicename );
180
+
181
+ $output = get_avatar( $item->user_email, 32 );
182
+ // @todo caps check to see whether the user can edit. Otherwise, just show the name
183
+ $output .= '<a href="' . esc_url( $item_edit_link ) . '">' . esc_html( $item->display_name ) . '</a>';
184
+
185
+ $actions = array();
186
+ $actions['edit'] = '<a href="' . esc_url( $item_edit_link ) . '">' . __( 'Edit', 'co-authors-plus' ) . '</a>';
187
+ $actions['delete'] = '<a href="' . esc_url( $item_delete_link ) . '">' . __( 'Delete', 'co-authors-plus' ) . '</a>';
188
+ $actions['view'] = '<a href="' . esc_url( $item_view_link ) . '">' . __( 'View Posts', 'co-authors-plus' ) . '</a>';
189
+ $actions = apply_filters( 'coauthors_guest_author_row_actions', $actions, $item );
190
+ $output .= $this->row_actions( $actions, false );
191
+
192
+ return $output;
193
+ }
194
+
195
+ /**
196
+ * Render linked account
197
+ */
198
+ function column_linked_account( $item ) {
199
+ if ( $item->linked_account ) {
200
+ $account = get_user_by( 'login', $item->linked_account );
201
+ if ( $account ) {
202
+ if ( current_user_can( 'edit_users' ) ) {
203
+ return '<a href="' . admin_url( 'user-edit.php?user_id=' . $account->ID ) . '">' . esc_html( $item->linked_account ) . '</a>';
204
+ }
205
+ return $item->linked_account;
206
+ }
207
+ }
208
+ return '';
209
+ }
210
+
211
+ /**
212
+ * Render the published post count column
213
+ */
214
+ function column_posts( $item ) {
215
+ global $coauthors_plus;
216
+ $term = $coauthors_plus->get_author_term( $item );
217
+ if ( ! $term )
218
+ return '';
219
+ return '<a href="' . esc_url( add_query_arg( 'author_name', $item->user_login, admin_url( 'edit.php' ) ) ) . '">' . $term->count . '</a>';
220
+ }
221
+
222
+ /**
223
+ * Allow users to filter the guest authors by various criteria
224
+ */
225
+ function extra_tablenav( $which ) {
226
+
227
+ ?><div class="alignleft actions"><?php
228
+ if ( 'top' == $which ) {
229
+ if ( !empty( $this->filters ) ) {
230
+ echo '<select name="filter">';
231
+ foreach( $this->filters as $key => $value ) {
232
+ echo '<option value="' . esc_attr( $key ) . '" ' . selected( $this->active_filter, $key, false ) . '>' . esc_attr( $value ) . '</option>';
233
+ }
234
+ echo '</select>';
235
+ }
236
+ submit_button( __( 'Filter', 'co-authors-plus' ), 'secondary', false, false );
237
+ }
238
+ ?></div><?php
239
+ }
240
+
241
+ function display() {
242
+ global $coauthors_plus;
243
+ $this->search_box( $coauthors_plus->guest_authors->labels['search_items'], 'guest-authors' );
244
+ parent::display();
245
+ }
246
+
247
+ }
php/class-wp-cli.php ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Co-Authors Plus commands for the WP-CLI framework
4
+ *
5
+ * @package wp-cli
6
+ * @since 3.0
7
+ * @see https://github.com/wp-cli/wp-cli
8
+ */
9
+ WP_CLI::add_command( 'co-authors-plus', 'CoAuthorsPlus_Command' );
10
+
11
+ class CoAuthorsPlus_Command extends WP_CLI_Command {
12
+
13
+ /**
14
+ * Subcommand to create guest authors based on users
15
+ *
16
+ * @since 3.0
17
+ *
18
+ * @subcommand create-guest-authors
19
+ */
20
+ public function create_guest_authors( $args, $assoc_args ) {
21
+ global $coauthors_plus;
22
+
23
+ $defaults = array(
24
+ // There are no arguments at this time
25
+ );
26
+ $this->args = wp_parse_args( $assoc_args, $defaults );
27
+
28
+ $users = get_users();
29
+ $created = 0;
30
+ $skipped = 0;
31
+ foreach( $users as $user ) {
32
+
33
+ $result = $coauthors_plus->guest_authors->create_guest_author_from_user_id( $user->ID );
34
+ if ( is_wp_error( $result ) ) {
35
+ $skipped++;
36
+ } else {
37
+ $created++;
38
+ }
39
+ }
40
+
41
+ WP_CLI::line( "All done! Here are your results:" );
42
+ WP_CLI::line( "- {$created} guest author profiles were created" );
43
+ WP_CLI::line( "- {$skipped} users already had guest author profiles" );
44
+
45
+ }
46
+
47
+ /**
48
+ * Subcommand to assign coauthors to a post based on a given meta key
49
+ *
50
+ * @since 3.0
51
+ *
52
+ * @subcommand assign-coauthors
53
+ * @synopsis [--meta_key=<key>] [--post_type=<ptype>]
54
+ */
55
+ public function assign_coauthors( $args, $assoc_args ) {
56
+ global $coauthors_plus;
57
+
58
+ $defaults = array(
59
+ 'meta_key' => '_original_import_author',
60
+ 'post_type' => 'post',
61
+ 'order' => 'ASC',
62
+ 'orderby' => 'ID',
63
+ 'posts_per_page' => 100,
64
+ 'paged' => 1,
65
+ 'append_coauthors' => false,
66
+ );
67
+ $this->args = wp_parse_args( $assoc_args, $defaults );
68
+
69
+ // For global use and not a part of WP_Query
70
+ $append_coauthors = $this->args['append_coauthors'];
71
+ unset( $this->args['append_coauthors'] );
72
+
73
+ $posts_total = 0;
74
+ $posts_already_associated = 0;
75
+ $posts_missing_coauthor = 0;
76
+ $posts_associated = 0;
77
+ $missing_coauthors = array();
78
+
79
+ $posts = new WP_Query( $this->args );
80
+ while( $posts->post_count ) {
81
+
82
+ foreach( $posts->posts as $single_post ) {
83
+ $posts_total++;
84
+
85
+ // See if the value in the post meta field is the same as any of the existing coauthors
86
+ $original_author = get_post_meta( $single_post->ID, $this->args['meta_key'], true );
87
+ $existing_coauthors = get_coauthors( $single_post->ID );
88
+ $already_associated = false;
89
+ foreach( $existing_coauthors as $existing_coauthor ) {
90
+ if ( $original_author == $existing_coauthor->user_login )
91
+ $already_associated = true;
92
+ }
93
+ if ( $already_associated ) {
94
+ $posts_already_associated++;
95
+ WP_CLI::line( $posts_total . ': Post #' . $single_post->ID . ' already has "' . $original_author . '" associated as a coauthor' );
96
+ continue;
97
+ }
98
+
99
+ // Make sure this original author exists as a co-author
100
+ if ( !$coauthors_plus->get_coauthor_by( 'user_login', $original_author ) ) {
101
+ $posts_missing_coauthor++;
102
+ $missing_coauthors[] = $original_author;
103
+ WP_CLI::line( $posts_total . ': Post #' . $single_post->ID . ' does not have "' . $original_author . '" associated as a coauthor but there is not a coauthor profile' );
104
+ continue;
105
+ }
106
+
107
+ // Assign the coauthor to the post
108
+ $coauthors_plus->add_coauthors( $single_post->ID, array( $original_author ), $append_coauthors );
109
+ WP_CLI::line( $posts_total . ': Post #' . $single_post->ID . ' has been assigned "' . $original_author . '" as the author' );
110
+ $posts_associated++;
111
+ clean_post_cache( $single_post->ID );
112
+ }
113
+
114
+ $this->args['paged']++;
115
+ $this->stop_the_insanity();
116
+ $posts = new WP_Query( $this->args );
117
+ }
118
+
119
+ WP_CLI::line( "All done! Here are your results:" );
120
+ if ( $posts_already_associated )
121
+ WP_CLI::line( "- {$posts_already_associated} posts already had the coauthor assigned" );
122
+ if ( $posts_missing_coauthor ) {
123
+ WP_CLI::line( "- {$posts_missing_coauthor} posts reference coauthors that don't exist. These are:" );
124
+ WP_CLI::line( " " . implode( ', ', array_unique( $missing_coauthors ) ) );
125
+ }
126
+ if ( $posts_associated )
127
+ WP_CLI::line( "- {$posts_associated} posts now have the proper coauthor" );
128
+
129
+ }
130
+
131
+ /**
132
+ * Assign posts associated with a WordPress user to a co-author
133
+ * Only apply the changes if there aren't yet co-authors associated with the post
134
+ *
135
+ * @since 3.0
136
+ *
137
+ * @subcommand assign-user-to-coauthor
138
+ * @synopsis --user_login=<user-login> --coauthor=<coauthor>
139
+ */
140
+ public function assign_user_to_coauthor( $args, $assoc_args ) {
141
+ global $coauthors_plus, $wpdb;
142
+
143
+ $defaults = array(
144
+ 'user_login' => '',
145
+ 'coauthor' => '',
146
+ );
147
+ $assoc_args = wp_parse_args( $assoc_args, $defaults );
148
+
149
+ $user = get_user_by( 'login', $assoc_args['user_login'] );
150
+ $coauthor = $coauthors_plus->get_coauthor_by( 'login', $assoc_args['coauthor'] );
151
+
152
+ if ( ! $user )
153
+ WP_CLI::error( __( 'Please specify a valid user_login', 'co-authors-plus' ) );
154
+
155
+ if ( ! $coauthor )
156
+ WP_CLI::error( __( 'Please specify a valid co-author login', 'co-authors-plus' ) );
157
+
158
+ $post_types = implode( "','", $coauthors_plus->supported_post_types );
159
+ $posts = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author=%d AND post_type IN ('$post_types')", $user->ID ) );
160
+ $affected = 0;
161
+ foreach( $posts as $post_id ) {
162
+ if ( $coauthors = wp_get_post_terms( $post_id, $coauthors_plus->coauthor_taxonomy ) ) {
163
+ WP_CLI::line( sprintf( __( "Skipping - Post #%d already has co-authors assigned: %s", 'co-authors-plus' ), $post_id, implode( ', ', wp_list_pluck( $coauthors, 'slug' ) ) ) );
164
+ continue;
165
+ }
166
+
167
+ $coauthors_plus->add_coauthors( $post_id, array( $coauthor->user_login ) );
168
+ WP_CLI::line( sprintf( __( "Updating - Adding %s's byline to post #%d", 'co-authors-plus' ), $coauthor->user_login, $post_id ) );
169
+ $affected++;
170
+ if ( $affected && $affected % 20 == 0 )
171
+ sleep( 5 );
172
+ }
173
+ WP_CLI::success( sprintf( __( "All done! %d posts were affected.", 'co-authors-plus' ), $affected ) );
174
+
175
+ }
176
+
177
+ /**
178
+ * Subcommand to reassign co-authors based on some given format
179
+ * This will look for terms with slug 'x' and rename to term with slug and name 'y'
180
+ * This subcommand can be helpful for cleaning up after an import if the usernames
181
+ * for authors have changed. During the import process, 'author' terms will be
182
+ * created with the old user_login value. We can use this to migrate to the new user_login
183
+ *
184
+ * @todo support reassigning by CSV
185
+ *
186
+ * @since 3.0
187
+ *
188
+ * @subcommand reassign-terms
189
+ * @synopsis [--author-mapping=<file>] [--old_term=<slug>] [--new_term=<slug>]
190
+ */
191
+ public function reassign_terms( $args, $assoc_args ) {
192
+ global $coauthors_plus;
193
+
194
+ $defaults = array(
195
+ 'author_mapping' => null,
196
+ 'old_term' => null,
197
+ 'new_term' => null,
198
+ );
199
+ $this->args = wp_parse_args( $assoc_args, $defaults );
200
+
201
+ $author_mapping = $this->args['author_mapping'];
202
+ $old_term = $this->args['old_term'];
203
+ $new_term = $this->args['new_term'];
204
+
205
+ // Get the reassignment data
206
+ if ( $author_mapping && file_exists( $author_mapping ) ) {
207
+ require_once( $author_mapping );
208
+ $authors_to_migrate = $cli_user_map;
209
+ } else if ( $author_mapping ) {
210
+ WP_CLI::error( "author_mapping doesn't exist: " . $author_mapping );
211
+ exit;
212
+ }
213
+
214
+ // Alternate reassigment approach
215
+ if ( $old_term && $new_term ) {
216
+ $authors_to_migrate = array(
217
+ $old_term => $new_term,
218
+ );
219
+ }
220
+
221
+ // For each author to migrate, check whether the term exists,
222
+ // whether the target term exists, and only do the migration if both are met
223
+ $results = (object)array(
224
+ 'old_term_missing' => 0,
225
+ 'new_term_exists' => 0,
226
+ 'success' => 0,
227
+ );
228
+ foreach( $authors_to_migrate as $old_user => $new_user ) {
229
+
230
+ if ( is_numeric( $new_user ) )
231
+ $new_user = get_user_by( 'id', $new_user )->user_login;
232
+
233
+ // The old user should exist as a term
234
+ $old_term = $coauthors_plus->get_author_term( $coauthors_plus->get_coauthor_by( 'login', $old_user ) );
235
+ if ( !$old_term ) {
236
+ WP_CLI::line( "Error: Term '{$old_user}' doesn't exist, skipping" );
237
+ $results->old_term_missing++;
238
+ continue;
239
+ }
240
+
241
+ // If the new user exists as a term already, we want to reassign all posts to that
242
+ // new term and delete the original
243
+ // Otherwise, simply rename the old term
244
+ $new_term = $coauthors_plus->get_author_term( $coauthors_plus->get_coauthor_by( 'login', $new_user ) );
245
+ if ( is_object( $new_term ) ) {
246
+ $args = array(
247
+ 'default' => $new_term->term_id,
248
+ 'force_default' => true,
249
+ );
250
+ wp_delete_term( $old_term->term_id, $coauthors_plus->coauthor_taxonomy, $args );
251
+ WP_CLI::line( "Success: There's already a '{$new_user}' term for '{$old_user}'. Reassigning posts and then deleting the term" );
252
+ $results->new_term_exists++;
253
+ } else {
254
+ $args = array(
255
+ 'slug' => $new_user,
256
+ 'name' => $new_user,
257
+ );
258
+ wp_update_term( $old_term->term_id, $coauthors_plus->coauthor_taxonomy, $args );
259
+ WP_CLI::line( "Success: Converted '{$old_user}' term to '{$new_user}'" );
260
+ $results->success++;
261
+ }
262
+ clean_term_cache( $old_term->term_id, $coauthors_plus->coauthor_taxonomy );
263
+ }
264
+
265
+ WP_CLI::line( "Reassignment complete. Here are your results:" );
266
+ WP_CLI::line( "- $results->success authors were successfully reassigned terms" );
267
+ WP_CLI::line( "- $results->new_term_exists authors had their old term merged to their new term" );
268
+ WP_CLI::line( "- $results->old_term_missing authors were missing old terms" );
269
+
270
+ }
271
+
272
+ /**
273
+ * List all of the posts without assigned co-authors terms
274
+ *
275
+ * @since 3.0
276
+ *
277
+ * @subcommand list-posts-without-terms
278
+ * @synopsis [--post_type=<ptype>]
279
+ */
280
+ public function list_posts_without_terms( $args, $assoc_args ) {
281
+ global $coauthors_plus;
282
+
283
+ $defaults = array(
284
+ 'post_type' => 'post',
285
+ 'order' => 'ASC',
286
+ 'orderby' => 'ID',
287
+ 'year' => '',
288
+ 'posts_per_page' => 300,
289
+ 'paged' => 1,
290
+ 'no_found_rows' => true,
291
+ 'update_meta_cache' => false,
292
+ );
293
+ $this->args = wp_parse_args( $assoc_args, $defaults );
294
+
295
+ $posts = new WP_Query( $this->args );
296
+ while( $posts->post_count ) {
297
+
298
+ foreach( $posts->posts as $single_post ) {
299
+
300
+ $terms = wp_get_post_terms( $single_post->ID, $coauthors_plus->coauthor_taxonomy );
301
+ if ( empty( $terms ) ) {
302
+ $saved = array(
303
+ $single_post->ID,
304
+ addslashes( $single_post->post_title ),
305
+ get_permalink( $single_post->ID ),
306
+ $single_post->post_date,
307
+ );
308
+ WP_CLI::line( '"' . implode( '","', $saved ) . '"' );
309
+ }
310
+ }
311
+
312
+ $this->stop_the_insanity();
313
+
314
+ $this->args['paged']++;
315
+ $posts = new WP_Query( $this->args );
316
+ }
317
+
318
+ }
319
+
320
+ /**
321
+ * Migrate author terms without prefixes to ones with prefixes
322
+ * Pre-3.0, all author terms didn't have a 'cap-' prefix, which means
323
+ * they can easily collide with terms in other taxonomies
324
+ *
325
+ * @since 3.0
326
+ *
327
+ * @subcommand migrate-author-terms
328
+ */
329
+ public function migrate_author_terms( $args, $assoc_args ) {
330
+ global $coauthors_plus;
331
+
332
+ $author_terms = get_terms( $coauthors_plus->coauthor_taxonomy, array( 'hide_empty' => false ) );
333
+ WP_CLI::line( "Now migrating up to " . count( $author_terms ) . " terms" );
334
+ foreach( $author_terms as $author_term ) {
335
+ // Term is already prefixed. We're good.
336
+ if ( preg_match( '#^cap\-#', $author_term->slug, $matches ) ) {
337
+ WP_CLI::line( "Term {$author_term->slug} ({$author_term->term_id}) is already prefixed, skipping" );
338
+ continue;
339
+ }
340
+ // A prefixed term was accidentally created, and the old term needs to be merged into the new (WordPress.com VIP)
341
+ if ( $prefixed_term = get_term_by( 'slug', 'cap-' . $author_term->slug, $coauthors_plus->coauthor_taxonomy ) ) {
342
+ WP_CLI::line( "Term {$author_term->slug} ({$author_term->term_id}) has a new term too: $prefixed_term->slug ($prefixed_term->term_id). Merging" );
343
+ $args = array(
344
+ 'default' => $author_term->term_id,
345
+ 'force_default' => true,
346
+ );
347
+ wp_delete_term( $prefixed_term->term_id, $coauthors_plus->coauthor_taxonomy, $args );
348
+ }
349
+
350
+ // Term isn't prefixed, doesn't have a sibling, and should be updated
351
+ WP_CLI::line( "Term {$author_term->slug} ({$author_term->term_id}) isn't prefixed, adding one" );
352
+ $args = array(
353
+ 'slug' => 'cap-' . $author_term->slug,
354
+ );
355
+ wp_update_term( $author_term->term_id, $coauthors_plus->coauthor_taxonomy, $args );
356
+ }
357
+ WP_CLI::success( "All done! Grab a cold one (Affogatto)" );
358
+ }
359
+
360
+ /**
361
+ * Update the post count and description for each author
362
+ *
363
+ * @since 3.0
364
+ *
365
+ * @subcommand update-author-terms
366
+ */
367
+ public function update_author_terms() {
368
+ global $coauthors_plus;
369
+ $author_terms = get_terms( $coauthors_plus->coauthor_taxonomy, array( 'hide_empty' => false ) );
370
+ WP_CLI::line( "Now updating " . count( $author_terms ) . " terms" );
371
+ foreach( $author_terms as $author_term ) {
372
+ $old_count = $author_term->count;
373
+ $coauthor = $coauthors_plus->get_coauthor_by( 'user_nicename', $author_term->slug );
374
+ $coauthors_plus->update_author_term( $coauthor );
375
+ $coauthors_plus->update_author_term_post_count( $author_term );
376
+ wp_cache_delete( $author_term->term_id, $coauthors_plus->coauthor_taxonomy );
377
+ $new_count = get_term_by( 'id', $author_term->term_id, $coauthors_plus->coauthor_taxonomy )->count;
378
+ WP_CLI::line( "Term {$author_term->slug} ({$author_term->term_id}) changed from {$old_count} to {$new_count} and the description was refreshed" );
379
+ }
380
+ WP_CLI::success( "All done" );
381
+ }
382
+
383
+ /**
384
+ * Clear all of the caches for memory management
385
+ */
386
+ private function stop_the_insanity() {
387
+ global $wpdb, $wp_object_cache;
388
+
389
+ $wpdb->queries = array(); // or define( 'WP_IMPORTING', true );
390
+
391
+ if ( !is_object( $wp_object_cache ) )
392
+ return;
393
+
394
+ $wp_object_cache->group_ops = array();
395
+ $wp_object_cache->stats = array();
396
+ $wp_object_cache->memcache_debug = array();
397
+ $wp_object_cache->cache = array();
398
+
399
+ if( is_callable( $wp_object_cache, '__remoteset' ) )
400
+ $wp_object_cache->__remoteset(); // important
401
+ }
402
+
403
+ }
readme.txt CHANGED
@@ -1,34 +1,56 @@
1
  === Co-Authors Plus ===
2
  Contributors: batmoo, danielbachhuber, automattic
3
- Donate link: http://digitalize.ca/donate
4
  Tags: authors, users, multiple authors, coauthors, multi-author, publishing
5
- Tested up to: 3.3.2
6
- Requires at least: 3.1
7
- Stable tag: 2.6.4
8
 
9
- Allows multiple authors to be assigned to posts, pages, and custom post types via a search-as-you-type input box
10
 
11
  == Description ==
12
 
13
- Allows multiple authors to be assigned to posts, pages, or custom post types via the search-as-you-type inputs. Template tags allow listing of co-authors anywhere you'd normally list the author. Co-authored posts appear on a co-author's archive page and in their feed. Additionally, co-authors may edit the posts they are associated with, and co-authors who are contributors may only edit posts if they have not been published (as is core behavior).
 
 
 
 
14
 
15
  This plugin is an almost complete rewrite of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/) (2007). The original plugin was inspired by the 'Multiple Authors' plugin by Mark Jaquith (2005).
16
 
17
- > *See "Other Notes" section for Template Tags and usage information*
 
 
 
 
 
 
 
 
18
 
19
  == Changelog ==
20
 
21
- = 2012-05-07 / 2.6.4 =
 
 
 
 
 
 
 
 
 
 
 
22
  * Bug fix: Properly filter the user query so users can AJAX search against the display name field again
23
  * If https is used for the admin, also use the secure Gravatar URL. Props [rmcfrazier](https://github.com/rmcfrazier)
24
 
25
- = 2012-04-30 / 2.6.3 =
26
  * AJAX user search is back to searching against user login, display name, email address and user ID. The method introduced in v2.6.2 didn't scale well
27
  * French translation courtesy of Sylvain Bérubé
28
  * Spanish translation courtesy of Alejandro Arcos
29
  * Bug fix: Resolved incorrect caps check against user editing an already published post. [See forum thread](http://wordpress.org/support/topic/multiple-authors-cant-edit-pages?replies=17#post-2741243)
30
 
31
- = 2012-03-06 / 2.6.2 =
32
  * AJAX user search matches against first name, last name, and nickname fields too, in addition to display name, user login, and email address
33
  * Comment moderation and approved notifications are properly sent to all co-authors with the correct caps
34
  * Filter required capability for user to be returned in an AJAX search with 'coauthors_edit_author_cap'
@@ -39,11 +61,11 @@ This plugin is an almost complete rewrite of the Co-Authors plugin originally de
39
  * Bug fix: If a user has already been added as an author to a post, don't show them in the AJAX search again
40
  * Bug fix: Allow output constants to be defined in a theme's functions.php file and include filters you can use instead
41
 
42
- = 2011-12-30 / 2.6.1 =
43
 
44
  * Fix mangled usernames because of sanitize_key http://wordpress.org/support/topic/plugin-co-authors-plus-26-not-working-with-wp-33
45
 
46
- = 2011-12-22 / 2.6 =
47
 
48
  * Sortable authors: Drag and drop the order of the authors as you'd like them to appear ([props kingkool68](http://profiles.wordpress.org/users/kingkool68/))
49
  * Search for authors by display name (instead of nicename which was essentially the same as user_login)
@@ -51,22 +73,22 @@ This plugin is an almost complete rewrite of the Co-Authors plugin originally de
51
  * Bumped requirements to WordPress 3.1
52
  * Bug fix: Update the published post count for each user more reliably
53
 
54
- = 2011-08-14 / 2.5.3 =
55
 
56
  * Bug fix: Removed extra comma when only two authors were listed. If you used the COAUTHORS_DEFAULT_BETWEEN_LAST constant, double-check what you have
57
 
58
- = 2011-04-23 / 2.5.2 =
59
 
60
  * Bug: Couldn't query terms and authors at the same time (props nbaxley)
61
  * Bug: Authors with empty fields (e.g. first name) were displaying blank in some cases
62
  * Bug: authors with spaces in usernames not getting saved (props MLmsw, Ruben S. and others!)
63
  * Bug: revisions getting wrong user attached (props cliquenoir!)
64
 
65
- = 2011-03-26 / 2.5.1 =
66
 
67
  * Fix with author post count (throwing errors)
68
 
69
- = 2011-03-26 / 2.5 =
70
 
71
  * Custom Post Type Support
72
  * Compatibility with WP 3.0 and 3.1
@@ -74,17 +96,17 @@ This plugin is an almost complete rewrite of the Co-Authors plugin originally de
74
  * Lots and lots and lots of bug fixes
75
  * Thanks to everyone who submitted bugs, fixes, and suggestions! And for your patience!
76
 
77
- = 2009-10-16 / 2.1.1 =
78
 
79
  * Fix for coauthors not being added if their username is different from display name
80
  * Fixes to readme.txt (fixes for textual and punctuation errors, language clarification, minor formatting changes) courtesy of [Waldo Jaquith](http://www.vqronline.org)
81
 
82
- = 2009-10-11 / 2.1 =
83
 
84
  * Fixed issues related to localization. Thanks to Jan Zombik <zombik@students.uni-mainz.de> for the fixes.
85
  * Added set_time_limit to update function to get around timeout issues when upgrading plugin
86
 
87
- = 2009-10-11 / 2.0 =
88
 
89
  * Plugin mostly rewritten to make use of taxonomy instead of post_meta
90
  * Can now see all authors of a post under the author column from Edit Posts page
@@ -95,7 +117,7 @@ This plugin is an almost complete rewrite of the Co-Authors plugin originally de
95
  * FIX: Issues with wp_coauthors_list function
96
  * FIX: Issues with coauthored posts not showing up on author archives
97
 
98
- = 2009-06-16 / 1.2.0 =
99
 
100
  * FIX: Added compatibility for WordPress 2.8
101
  * FIX: Added new template tags (get_the_coauthor_meta & the_coauthor_meta) to fix issues related to displaying author info on author archive pages. See [Other Notes](http://wordpress.org/extend/plugins/co-authors-plus/other_notes/) for details.
@@ -104,140 +126,45 @@ This plugin is an almost complete rewrite of the Co-Authors plugin originally de
104
  * FIX: Plugin now used WordPress native AJAX calls to tighten security
105
  * DOCS: Added details about the new template tags
106
 
107
- = 2009-04-26 / 1.1.5 =
108
 
109
  * FIX: Not searching Updated SQL query for autosuggest to search through first name, last name, and nickname
110
  * FIX: When editing an author, and clicking on a suggested author, the original author was not be removed
111
  * DOCS: Added code comments to javascript; more still to be added
112
  * DOCS: Updated readme information
113
 
114
- = 2009-04-25 / 1.1.4 =
115
 
116
  * Disabled "New Author" output in suggest box, for now
117
  * Hopefully fixed SVN issue (if you're having trouble with the plugin, please delete the plugin and reinstall)
118
 
119
- = 2009-04-23 / 1.1.3 =
120
 
121
  * Add blur event to disable input box
122
  * Limit only one edit at a time.
123
  * Checked basic cross-browser compatibility (Firefox 3 OS X, Safari 3 OS X, IE7 Vista).
124
  * Add suggest javascript plugin to Edit Page.
125
 
126
- = 2009-04-19 / 1.1.2 =
127
 
128
  * Disabled form submit when enter pressed.
129
 
130
- = 2009-04-15 / 1.1.1 =
131
 
132
  * Changed SQL query to return only contributor-level and above users.
133
 
134
- = 2009-04-14: 1.1.0 =
135
 
136
  * Initial beta release.
137
 
138
-
139
  == Installation ==
140
 
141
  1. IMPORTANT: Please disable the original Co-Authors plugin (if you are using it) before installing Co-Authors Plus
142
  1. Extract the coauthors-plus.zip file and upload its contents to the `/wp-content/plugins/` directory. Alternately, you can install directly from the Plugin directory within your WordPress Install.
143
  1. Activate the plugin through the "Plugins" menu in WordPress.
144
- 1. Place the appropriate coauthors template tags in your template.
145
  1. Add co-authors to your posts and pages.
146
 
147
-
148
- == Basic Usage and Other Notes ==
149
-
150
- * Contributor-level and above can be added as co-authors.
151
- * As per WordPress design, when an editor creates a new Post or Page, they are by default added as an author. However, they can be replaced by clicking on their name and typing in the name of the new author.
152
- * The search-as-you-type box starts searching once two letters have been added, and executes a new search with every subsequent letter.
153
- * The search-as-you-type box searches through the following user fields: a) user login; b) user nicename; c) display name; d) user email; e) first name; f) last name; and g) nickname.
154
-
155
-
156
- == Template Tags ==
157
-
158
- New template tags enable listing of co-authors:
159
-
160
- * <code>coauthors()</code>
161
- * <code>coauthors_posts_links()</code>
162
- * <code>coauthors_firstnames()</code>
163
- * <code>coauthors_lastnames()</code>
164
- * <code>coauthors_nicknames()</code>
165
- * <code>coauthors_links()</code>
166
- * <code>coauthors_IDs()</code>
167
-
168
- These template tags correspond to their "<code>the_author*</code>" equivalents; take special note of the pluralization.
169
- Each of these template tags accept four optional arguments:
170
-
171
- 1. <code>between</code>: default ", "
172
- 1. <code>betweenLast</code>: default " and "
173
- 1. <code>before</code>: default ""
174
- 1. <code>after</code>: default ""
175
-
176
- To use them, simply modify the code surrounding all instances of <code>the_author*()</code> to something like the following example:
177
-
178
- if(function_exists('coauthors_posts_links'))
179
- coauthors_posts_links();
180
- else
181
- the_author_posts_link();
182
-
183
- The result of this would be formatted like "John Smith, Jane Doe and Joe Public".
184
-
185
- Note that as of this writing, WordPress does provide a means of extending <code>wp_list_authors()</code>, so
186
- included in this plugin is the function <code>coauthors_wp_list_authors()</code> modified
187
- to take into account co-authored posts; the same arguments are accepted.
188
-
189
- Sometimes you may need fine-grained control over the display of a posts's authors, and in this case you may use
190
- the <code>CoAuthorsIterator</code> class. This class may be instantiated anywhere you may place <code>the_author()</code>
191
- or everywhere if the post ID is provided to the constructor. The instantiated class has the following methods:
192
-
193
- 1. <code>iterate()</code>: advances <code>$authordata</code> to the next co-author; returns <code>false</code> and restores the original <code>$authordata</code> if there are no more authors to iterate.
194
- 1. <code>get_position()</code>: returns the zero-based index of the current author; returns -1 if the iterator is invalid.
195
- 1. <code>is_last()</code>: returns <code>true</code> if the current author is the last.
196
- 1. <code>is_first()</code>: returns <code>true</code> if the current author is the first.
197
- 1. <code>count()</code>: returns the total number of authors.
198
- 1. <code>get_all()</code>: returns an array of all of the authors' user data.
199
-
200
- For example:
201
-
202
- $i = new CoAuthorsIterator();
203
- print $i->count() == 1 ? 'Author: ' : 'Authors: ';
204
- $i->iterate();
205
- the_author();
206
- while($i->iterate()){
207
- print $i->is_last() ? ' and ' : ', ';
208
- the_author();
209
- }
210
-
211
- = the coauthor meta =
212
-
213
- * <code>get_the_coauthor_meta( $field )</code> (2.8 only)
214
- * <code>the_coauthor_meta( $field )</code> (2.8 only)
215
-
216
- Note: The $field variable corresponds with the same values accepted by the [the author meta](http://codex.wordpress.org/Template_Tags/the_author_meta) function.
217
-
218
- = get coauthors =
219
-
220
- * <code>get_coauthors( [$post_id], [$args] )</code>
221
-
222
- This function returns an array of coauthors for the specified post, or if used inside the Loop, the current post active in the Loop. the $args parameter is an array that allows you to specify the order in which the authors should be returned.
223
-
224
- = is coauthor for post =
225
-
226
- * <code>is_coauthor_for_post( $user, $post_id )</code>
227
-
228
- This function allows you to check whether the specified user is coauthor for a post. The $user attribute can be the user ID or username.
229
-
230
-
231
- == Frequently Asked Questions ==
232
-
233
- = What is the main difference between Co-Authors and Co-Authors Plus? =
234
-
235
- The most notable difference is the replacement of the standard WordPress authors drop-downs with search-as-you-type/auto-suggest/whatever-you-call-them input boxes. As a result, major bits of the JavaScript code was changed to be more jQuery-friendly. Eventually, I hope to include the ability to add new Users from within the Edit Post/Page screen and possibly Gravatar support.
236
-
237
- = What happens to posts and pages when I delete a user assigned to a post or page as a coauthor? =
238
-
239
- When a user is deleted from WordPress, they will be removed from all posts for which they are co-authors. If you chose to reassign their posts to another user, that user will be set as the coauthor instead.
240
-
241
  == Screenshots ==
242
- 1. "Post Author(s)" box with multiple authors added
243
- 2. Search-as-you-type input box that looks up authors as you type in their name. Fields displayed are: ID, Display Name, and Email.
1
  === Co-Authors Plus ===
2
  Contributors: batmoo, danielbachhuber, automattic
 
3
  Tags: authors, users, multiple authors, coauthors, multi-author, publishing
4
+ Tested up to: 3.4.2
5
+ Requires at least: 3.3
6
+ Stable tag: 3.0
7
 
8
+ Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box
9
 
10
  == Description ==
11
 
12
+ Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box. Co-authored posts appear on a co-author's archive page and in their feed. Co-authors may edit the posts they are associated with, and co-authors who are contributors may only edit posts if they have not been published (as is core behavior).
13
+
14
+ Add writers as bylines without creating WordPress user accounts. Simply [create a guest author profile](http://vip.wordpress.com/documentation/add-guest-bylines-to-your-content-with-co-authors-plus/) for the writer and assign the byline as you normally would.
15
+
16
+ On the frontend, use the [Co-Authors Plus template tags](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) to list co-authors anywhere you'd normally list the author.
17
 
18
  This plugin is an almost complete rewrite of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/) (2007). The original plugin was inspired by the 'Multiple Authors' plugin by Mark Jaquith (2005).
19
 
20
+ == Frequently Asked Questions ==
21
+
22
+ = How do I add Co-Authors Plus support to my theme? =
23
+
24
+ If you've just installed Co-Authors Plus, you might notice that the bylines are being added in the backend but aren't appearing on the frontend. You'll need to [add the template tags to your theme](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) before the bylines will appear.
25
+
26
+ = What happens to posts and pages when I delete a user assigned to a post or page as a coauthor? =
27
+
28
+ When a user is deleted from WordPress, they will be removed from all posts for which they are co-authors. If you chose to reassign their posts to another user, that user will be set as the coauthor instead.
29
 
30
  == Changelog ==
31
 
32
+ = 3.0 (Nov. 12, 2012) =
33
+ * Create guest author profiles for bylines you'd like to assign without creating WordPress user accounts. Guest authors can have all of the same fields as normal users including display name, biography, and avatars.
34
+ * Support for non-Latin characters in usernames and guest author names
35
+ * wp-cli subcommands for creating, assigning, and reassigning co-authors
36
+ * For themes using core template tags like the_author() or the_author_posts_link(), you enable Co-Authors Plus support with a simple filter
37
+ * New author terms are now prefixed with 'cap-' to avoid collisions with global scope
38
+ * Bug fix: Apply query filters to only post_types registered with the taxonomy. Props [Tom Ransom](https://github.com/1bigidea)
39
+ * Filter coauthors_posts_link_single() with 'coauthors_posts_link'. Also adds rel="author". Props [Amit Sannad](https://github.com/asannad) and [Gabriel Koen](https://github.com/mintindeed)
40
+ * Filter for the context and priorities of the Co-Authors meta boxes. Props [Tomáš Kapler](https://github.com/tkapler)
41
+ * Renamed the post meta box for selecting authors so it applies to many post types. Props [John Blackbourn](https://github.com/johnbillion)
42
+
43
+ = 2.6.4 (May 7, 2012) =
44
  * Bug fix: Properly filter the user query so users can AJAX search against the display name field again
45
  * If https is used for the admin, also use the secure Gravatar URL. Props [rmcfrazier](https://github.com/rmcfrazier)
46
 
47
+ = 2.6.3 (Apr. 30, 2012) =
48
  * AJAX user search is back to searching against user login, display name, email address and user ID. The method introduced in v2.6.2 didn't scale well
49
  * French translation courtesy of Sylvain Bérubé
50
  * Spanish translation courtesy of Alejandro Arcos
51
  * Bug fix: Resolved incorrect caps check against user editing an already published post. [See forum thread](http://wordpress.org/support/topic/multiple-authors-cant-edit-pages?replies=17#post-2741243)
52
 
53
+ = 2.6.2 (Mar. 6, 2012) =
54
  * AJAX user search matches against first name, last name, and nickname fields too, in addition to display name, user login, and email address
55
  * Comment moderation and approved notifications are properly sent to all co-authors with the correct caps
56
  * Filter required capability for user to be returned in an AJAX search with 'coauthors_edit_author_cap'
61
  * Bug fix: If a user has already been added as an author to a post, don't show them in the AJAX search again
62
  * Bug fix: Allow output constants to be defined in a theme's functions.php file and include filters you can use instead
63
 
64
+ = 2.6.1 (Dec. 30, 2011) =
65
 
66
  * Fix mangled usernames because of sanitize_key http://wordpress.org/support/topic/plugin-co-authors-plus-26-not-working-with-wp-33
67
 
68
+ = 2.6 (Dec. 22, 2011) =
69
 
70
  * Sortable authors: Drag and drop the order of the authors as you'd like them to appear ([props kingkool68](http://profiles.wordpress.org/users/kingkool68/))
71
  * Search for authors by display name (instead of nicename which was essentially the same as user_login)
73
  * Bumped requirements to WordPress 3.1
74
  * Bug fix: Update the published post count for each user more reliably
75
 
76
+ = 2.5.3 (Aug. 14, 2011) =
77
 
78
  * Bug fix: Removed extra comma when only two authors were listed. If you used the COAUTHORS_DEFAULT_BETWEEN_LAST constant, double-check what you have
79
 
80
+ = 2.5.2 (Apr. 23, 2011) =
81
 
82
  * Bug: Couldn't query terms and authors at the same time (props nbaxley)
83
  * Bug: Authors with empty fields (e.g. first name) were displaying blank in some cases
84
  * Bug: authors with spaces in usernames not getting saved (props MLmsw, Ruben S. and others!)
85
  * Bug: revisions getting wrong user attached (props cliquenoir!)
86
 
87
+ = 2.5.1 (Mar. 26, 2011) =
88
 
89
  * Fix with author post count (throwing errors)
90
 
91
+ = 2.5 (Mar. 26, 2011) =
92
 
93
  * Custom Post Type Support
94
  * Compatibility with WP 3.0 and 3.1
96
  * Lots and lots and lots of bug fixes
97
  * Thanks to everyone who submitted bugs, fixes, and suggestions! And for your patience!
98
 
99
+ = 2.1.1 (Oct. 16, 2009) =
100
 
101
  * Fix for coauthors not being added if their username is different from display name
102
  * Fixes to readme.txt (fixes for textual and punctuation errors, language clarification, minor formatting changes) courtesy of [Waldo Jaquith](http://www.vqronline.org)
103
 
104
+ = 2.1 (Oct. 11, 2009) =
105
 
106
  * Fixed issues related to localization. Thanks to Jan Zombik <zombik@students.uni-mainz.de> for the fixes.
107
  * Added set_time_limit to update function to get around timeout issues when upgrading plugin
108
 
109
+ = 2.0 (Oct. 11, 2009) =
110
 
111
  * Plugin mostly rewritten to make use of taxonomy instead of post_meta
112
  * Can now see all authors of a post under the author column from Edit Posts page
117
  * FIX: Issues with wp_coauthors_list function
118
  * FIX: Issues with coauthored posts not showing up on author archives
119
 
120
+ = 1.2.0 (Jun. 16, 2012) =
121
 
122
  * FIX: Added compatibility for WordPress 2.8
123
  * FIX: Added new template tags (get_the_coauthor_meta & the_coauthor_meta) to fix issues related to displaying author info on author archive pages. See [Other Notes](http://wordpress.org/extend/plugins/co-authors-plus/other_notes/) for details.
126
  * FIX: Plugin now used WordPress native AJAX calls to tighten security
127
  * DOCS: Added details about the new template tags
128
 
129
+ = 1.1.5 (Apr. 26, 2009) =
130
 
131
  * FIX: Not searching Updated SQL query for autosuggest to search through first name, last name, and nickname
132
  * FIX: When editing an author, and clicking on a suggested author, the original author was not be removed
133
  * DOCS: Added code comments to javascript; more still to be added
134
  * DOCS: Updated readme information
135
 
136
+ = 1.1.4 (Apr. 25, 2009) =
137
 
138
  * Disabled "New Author" output in suggest box, for now
139
  * Hopefully fixed SVN issue (if you're having trouble with the plugin, please delete the plugin and reinstall)
140
 
141
+ = 1.1.3 (Apr. 23, 2009) =
142
 
143
  * Add blur event to disable input box
144
  * Limit only one edit at a time.
145
  * Checked basic cross-browser compatibility (Firefox 3 OS X, Safari 3 OS X, IE7 Vista).
146
  * Add suggest javascript plugin to Edit Page.
147
 
148
+ = 1.1.2 (Apr. 19, 2009) =
149
 
150
  * Disabled form submit when enter pressed.
151
 
152
+ = 1.1.1 (Apr. 15, 2009) =
153
 
154
  * Changed SQL query to return only contributor-level and above users.
155
 
156
+ = 1.1.0 (Apr. 14, 2009) =
157
 
158
  * Initial beta release.
159
 
 
160
  == Installation ==
161
 
162
  1. IMPORTANT: Please disable the original Co-Authors plugin (if you are using it) before installing Co-Authors Plus
163
  1. Extract the coauthors-plus.zip file and upload its contents to the `/wp-content/plugins/` directory. Alternately, you can install directly from the Plugin directory within your WordPress Install.
164
  1. Activate the plugin through the "Plugins" menu in WordPress.
165
+ 1. Place the appropriate [co-authors template tags](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) in your template.
166
  1. Add co-authors to your posts and pages.
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  == Screenshots ==
169
+ 1. "Authors" box with multiple authors added
170
+ 2. Guest authors allow you to assign bylines without creating WordPress user accounts
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file
template-tags.php CHANGED
@@ -18,12 +18,13 @@ function get_coauthors( $post_id = 0, $args = array() ) {
18
 
19
  if ( is_array( $coauthor_terms ) && !empty( $coauthor_terms ) ) {
20
  foreach( $coauthor_terms as $coauthor ) {
21
- $post_author = get_user_by( 'login', $coauthor->name );
 
22
  // In case the user has been deleted while plugin was deactivated
23
  if ( !empty( $post_author ) )
24
  $coauthors[] = $post_author;
25
  }
26
- } else {
27
  if ( $post ) {
28
  $post_author = get_userdata( $post->post_author );
29
  } else {
@@ -31,7 +32,7 @@ function get_coauthors( $post_id = 0, $args = array() ) {
31
  }
32
  if ( !empty( $post_author ) )
33
  $coauthors[] = $post_author;
34
- }
35
  }
36
  return $coauthors;
37
  }
@@ -56,7 +57,7 @@ function is_coauthor_for_post( $user, $post_id = 0 ) {
56
  }
57
 
58
  foreach( $coauthors as $coauthor ) {
59
- if ( $user == $coauthor->user_login )
60
  return true;
61
  }
62
  return false;
@@ -165,7 +166,7 @@ function coauthors__echo( $tag, $type = 'tag', $separators = array(), $tag_args
165
  $output .= $separators['between'];
166
 
167
  if ( $i->is_last() && $i->count() > 1 ) {
168
- $output = rtrim( $output, ' ' );
169
  $output .= ' ' . $separators['betweenLast'];
170
  }
171
 
@@ -198,11 +199,19 @@ function coauthors_posts_links( $between = null, $betweenLast = null, $before =
198
  ), null, $echo );
199
  }
200
  function coauthors_posts_links_single( $author ) {
 
 
 
 
 
 
 
201
  return sprintf(
202
- '<a href="%1$s" title="%2$s">%3$s</a>',
203
- get_author_posts_url( $author->ID, $author->user_nicename ),
204
- esc_attr( sprintf( __( 'Posts by %s', 'co-authors-plus' ), get_the_author() ) ),
205
- get_the_author()
 
206
  );
207
  }
208
 
18
 
19
  if ( is_array( $coauthor_terms ) && !empty( $coauthor_terms ) ) {
20
  foreach( $coauthor_terms as $coauthor ) {
21
+ $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug );
22
+ $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug );
23
  // In case the user has been deleted while plugin was deactivated
24
  if ( !empty( $post_author ) )
25
  $coauthors[] = $post_author;
26
  }
27
+ } else if ( !$coauthors_plus->force_guest_authors ) {
28
  if ( $post ) {
29
  $post_author = get_userdata( $post->post_author );
30
  } else {
32
  }
33
  if ( !empty( $post_author ) )
34
  $coauthors[] = $post_author;
35
+ } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has.
36
  }
37
  return $coauthors;
38
  }
57
  }
58
 
59
  foreach( $coauthors as $coauthor ) {
60
+ if ( $user == $coauthor->user_login || $user == $coauthor->linked_account )
61
  return true;
62
  }
63
  return false;
166
  $output .= $separators['between'];
167
 
168
  if ( $i->is_last() && $i->count() > 1 ) {
169
+ $output = rtrim( $output, " {$separators['between']}" );
170
  $output .= ' ' . $separators['betweenLast'];
171
  }
172
 
199
  ), null, $echo );
200
  }
201
  function coauthors_posts_links_single( $author ) {
202
+ $args = array(
203
+ 'href' => get_author_posts_url( $author->ID, $author->user_nicename ),
204
+ 'rel' => 'author',
205
+ 'title' => sprintf( __( 'Posts by %s', 'co-authors-plus' ), get_the_author() ),
206
+ 'text' => get_the_author(),
207
+ );
208
+ $args = apply_filters( 'coauthors_posts_link', $args, $author );
209
  return sprintf(
210
+ '<a href="%1$s" title="%2$s" rel="%3$s">%4$s</a>',
211
+ esc_url( $args['href'] ),
212
+ esc_attr( $args['title'] ),
213
+ esc_attr( $args['rel'] ),
214
+ esc_html( $args['text'] )
215
  );
216
  }
217
 
upgrade.php CHANGED
@@ -3,9 +3,6 @@ function coauthors_plus_upgrade( $from ) {
3
  // TODO: handle upgrade failures
4
 
5
  if( $from < 2.0 ) coauthors_plus_upgrade_20();
6
-
7
- // Update to the current global version
8
- coauthors_plus_update_version(COAUTHORS_PLUS_VERSION);
9
  }
10
 
11
  /**
@@ -16,7 +13,7 @@ function coauthors_plus_upgrade_20 () {
16
  global $coauthors_plus;
17
 
18
  // Get all posts with meta_key _coauthor
19
- $all_posts = get_posts(array('numberposts' => '-1'));
20
 
21
  if(is_array($all_posts)) {
22
  foreach($all_posts as $single_post) {
@@ -37,7 +34,7 @@ function coauthors_plus_upgrade_20 () {
37
  //echo '<p>Has Legacy coauthors';
38
  foreach($legacy_coauthors as $legacy_coauthor) {
39
  $legacy_coauthor_login = get_user_by( 'id', (int)$legacy_coauthor );
40
- if ( is_object( $legacy_coauthor_login ) ) $coauthors[] = $legacy_coauthor_login->user_login;
41
  }
42
  } else {
43
  // No Legacy coauthors
@@ -46,10 +43,4 @@ function coauthors_plus_upgrade_20 () {
46
 
47
  }
48
  }
49
- coauthors_plus_update_version( '2.0' );
50
- }
51
-
52
- function coauthors_plus_update_version( $version ) {
53
- global $coauthors_plus;
54
- update_option($co_authors_plus->get_plugin_option_fullname('version'), $version);
55
  }
3
  // TODO: handle upgrade failures
4
 
5
  if( $from < 2.0 ) coauthors_plus_upgrade_20();
 
 
 
6
  }
7
 
8
  /**
13
  global $coauthors_plus;
14
 
15
  // Get all posts with meta_key _coauthor
16
+ $all_posts = get_posts(array('numberposts' => '-1', 'meta_key' => '_coauthor'));
17
 
18
  if(is_array($all_posts)) {
19
  foreach($all_posts as $single_post) {
34
  //echo '<p>Has Legacy coauthors';
35
  foreach($legacy_coauthors as $legacy_coauthor) {
36
  $legacy_coauthor_login = get_user_by( 'id', (int)$legacy_coauthor );
37
+ if ( is_object( $legacy_coauthor_login ) && ! in_array( $legacy_coauthor_login->user_login, $coauthors ) ) $coauthors[] = $legacy_coauthor_login->user_login;
38
  }
39
  } else {
40
  // No Legacy coauthors
43
 
44
  }
45
  }
 
 
 
 
 
 
46
  }