PluginProbe
Edit Flow / 0.7.3
Edit Flow v0.7.3
0.11.1 0.11.0 0.7.2 0.7.3 0.7.4 0.7.5 0.7.6 0.8 0.8.1 0.8.2 0.9 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 trunk 0.1.5 0.10.0 0.10.1 0.10.2 All 44 releases
edit-flow / modules / custom-status / custom-status.php

custom-status.php in Edit Flow 0.7.3, at modules/custom-status/custom-status.php

1,484 lines 57.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class EF_Custom_Status
4 * Custom statuses make it simple to define the different stages in your publishing workflow.
5 *
6 * @todo for v0.7
7 * - Improve the copy
8 * - Thoroughly test what happens when the default post statuses 'Draft' and 'Pending Review' no longer exist
9 * - Ensure all of the form processing uses our messages functionality
10 */
11 if ( !class_exists( 'EF_Custom_Status' ) ) {
12
13 class EF_Custom_Status extends EF_Module {
14
15 var $module;
16
17 // This is taxonomy name used to store all our custom statuses
18 const taxonomy_key = 'post_status';
19
20 /**
21 * Register the module with Edit Flow but don't do anything else
22 */
23 function __construct() {
24 global $edit_flow;
25
26 $this->module_url = $this->get_module_url( __FILE__ );
27 // Register the module with Edit Flow
28 $args = array(
29 'title' => __( 'Custom Statuses', 'edit-flow' ),
30 'short_description' => __( 'Create custom post statuses to define the stages of your workflow.', 'edit-flow' ),
31 'extended_description' => __( 'Create your own post statuses to add structure your publishing workflow. You can change existing or add new ones anytime, and drag and drop to change their order.', 'edit-flow' ),
32 'module_url' => $this->module_url,
33 'img_url' => $this->module_url . 'lib/custom_status_s128.png',
34 'slug' => 'custom-status',
35 'default_options' => array(
36 'enabled' => 'on',
37 'default_status' => 'pitch',
38 'always_show_dropdown' => 'off',
39 'post_types' => array(
40 'post' => 'on',
41 'page' => 'on',
42 ),
43 ),
44 'post_type_support' => 'ef_custom_statuses', // This has been plural in all of our docs
45 'configure_page_cb' => 'print_configure_view',
46 'configure_link_text' => __( 'Edit Statuses', 'edit-flow' ),
47 'messages' => array(
48 'status-added' => __( 'Post status created.', 'edit-flow' ),
49 'status-missing' => __( "Post status doesn't exist.", 'edit-flow' ),
50 'default-status-changed' => __( 'Default post status has been changed.', 'edit-flow'),
51 'term-updated' => __( "Post status updated.", 'edit-flow' ),
52 'status-deleted' => __( 'Post status deleted.', 'edit-flow' ),
53 'status-position-updated' => __( "Status order updated.", 'edit-flow' ),
54 ),
55 'autoload' => false,
56 'load_frontend' => true, // Load on the frontend so that previewing posts with custom statuses works
57 'settings_help_tab' => array(
58 'id' => 'ef-custom-status-overview',
59 'title' => __('Overview', 'edit-flow'),
60 'content' => __('<p>Edit Flow’s custom statuses allow you to define the most important stages of your editorial workflow. Out of the box, WordPress only offers “Draft” and “Pending Review” as post states. With custom statuses, you can create your own post states like “In Progress”, “Pitch”, or “Waiting for Edit” and keep or delete the originals. You can also drag and drop statuses to set the best order for your workflow.</p><p>Custom statuses are fully integrated into the rest of Edit Flow and the WordPress admin. On the calendar and story budget, you can filter your view to see only posts of a specific post state. Furthermore, email notifications can be sent to a specific group of users when a post changes state.</p>', 'edit-flow'),
61 ),
62 'settings_help_sidebar' => __( '<p><strong>For more information:</strong></p><p><a href="http://editflow.org/features/custom-statuses/">Custom Status Documentation</a></p><p><a href="http://wordpress.org/tags/edit-flow?forum_id=10">Edit Flow Forum</a></p><p><a href="https://github.com/danielbachhuber/Edit-Flow">Edit Flow on Github</a></p>', 'edit-flow' ),
63 );
64 $this->module = $edit_flow->register_module( 'custom_status', $args );
65
66 }
67
68 /**
69 * Initialize the EF_Custom_Status class if the module is active
70 */
71 function init() {
72 global $edit_flow;
73
74 // Register custom statuses as a taxonomy
75 $this->register_custom_statuses();
76
77 // Register our settings
78 add_action( 'admin_init', array( $this, 'register_settings' ) );
79
80 // Load CSS and JS resources that we probably need
81 add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
82 add_action( 'admin_notices', array( $this, 'no_js_notice' ) );
83 add_action( 'admin_print_scripts', array( $this, 'post_admin_header' ) );
84
85 // Methods for handling the actions of creating, making default, and deleting post stati
86 add_action( 'admin_init', array( $this, 'handle_add_custom_status' ) );
87 add_action( 'admin_init', array( $this, 'handle_edit_custom_status' ) );
88 add_action( 'admin_init', array( $this, 'handle_make_default_custom_status' ) );
89 add_action( 'admin_init', array( $this, 'handle_delete_custom_status' ) );
90 add_action( 'wp_ajax_update_status_positions', array( $this, 'handle_ajax_update_status_positions' ) );
91 add_action( 'wp_ajax_inline_save_status', array( $this, 'ajax_inline_save_status' ) );
92
93 // Hook to add the status column to Manage Posts
94
95 add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns') );
96 add_action( 'manage_posts_custom_column', array( $this, '_filter_manage_posts_custom_column') );
97
98 // We need these for pages (http://core.trac.wordpress.org/browser/tags/3.3.1/wp-admin/includes/class-wp-posts-list-table.php#L283)
99 add_filter( 'manage_pages_columns', array( $this, '_filter_manage_posts_columns' ) );
100 add_action( 'manage_pages_custom_column', array( $this, '_filter_manage_posts_custom_column' ) );
101
102 // These two methods are hacks for fixing bugs in WordPress core
103 add_action( 'admin_init', array( $this, 'check_timestamp_on_publish' ) );
104 add_filter( 'wp_insert_post_data', array( $this, 'fix_custom_status_timestamp' ) );
105
106 }
107
108 /**
109 * Create the default set of custom statuses the first time the module is loaded
110 *
111 * @since 0.7
112 */
113 function install() {
114
115 $default_terms = array(
116 array(
117 'term' => __( 'Draft', 'edit-flow' ),
118 'args' => array(
119 'slug' => 'draft',
120 'description' => __( 'Post is a draft; not ready for review or publication.', 'edit-flow' ),
121 ),
122 ),
123 array(
124 'term' => __( 'Pending Review' ),
125 'args' => array(
126 'slug' => 'pending',
127 'description' => __( 'Post needs to be reviewed by an editor.', 'edit-flow' ),
128 ),
129 ),
130 array(
131 'term' => __( 'Pitch', 'edit-flow' ),
132 'args' => array(
133 'slug' => 'pitch',
134 'description' => __( 'Idea proposed; waiting for acceptance.', 'edit-flow' ),
135 ),
136 ),
137 array(
138 'term' => __( 'Assigned', 'edit-flow' ),
139 'args' => array(
140 'slug' => 'assigned',
141 'description' => __( 'Post idea assigned to writer.', 'edit-flow' ),
142 ),
143 ),
144 array(
145 'term' => __( 'In Progress', 'edit-flow' ),
146 'args' => array(
147 'slug' => 'in-progress',
148 'description' => __( 'Writer is working on the post.', 'edit-flow' ),
149 ),
150 ),
151 );
152
153 // Okay, now add the default statuses to the db if they don't already exist
154 foreach( $default_terms as $term ) {
155 if( !term_exists( $term['term'] ) )
156 $this->add_custom_status( $term['term'], $term['args'] );
157 }
158
159 }
160
161 /**
162 * Upgrade our data in case we need to
163 *
164 * @since 0.7
165 */
166 function upgrade( $previous_version ) {
167 global $edit_flow;
168
169 // Upgrade path to v0.7
170 if ( version_compare( $previous_version, '0.7' , '<' ) ) {
171 // Migrate dropdown visibility option
172 if ( $dropdown_visible = get_option( 'edit_flow_status_dropdown_visible' ) )
173 $dropdown_visible = 'on';
174 else
175 $dropdown_visible = 'off';
176 $edit_flow->update_module_option( $this->module->name, 'always_show_dropdown', $dropdown_visible );
177 delete_option( 'edit_flow_status_dropdown_visible' );
178 // Migrate default status option
179 if ( $default_status = get_option( 'edit_flow_custom_status_default_status' ) )
180 $edit_flow->update_module_option( $this->module->name, 'default_status', $default_status );
181 delete_option( 'edit_flow_custom_status_default_status' );
182
183 // Technically we've run this code before so we don't want to auto-install new data
184 $edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
185 }
186
187 }
188
189 /**
190 * Makes the call to register_post_status to register the user's custom statuses.
191 * Also unregisters draft and pending, in case the user doesn't want them.
192 */
193 function register_custom_statuses() {
194 global $wp_post_statuses;
195
196 // Register new taxonomy so that we can store all our fancy new custom statuses (or is it stati?)
197 if ( !taxonomy_exists( self::taxonomy_key ) ) {
198 $args = array( 'hierarchical' => false,
199 'update_count_callback' => '_update_post_term_count',
200 'label' => false,
201 'query_var' => false,
202 'rewrite' => false,
203 'show_ui' => false
204 );
205 register_taxonomy( self::taxonomy_key, 'post', $args );
206 }
207
208 if ( function_exists( 'register_post_status' ) ) {
209 // Users can delete draft and pending statuses if they want, so let's get rid of them
210 // They'll get re-added if the user hasn't "deleted" them
211 unset( $wp_post_statuses[ 'draft' ] );
212 unset( $wp_post_statuses[ 'pending' ] );
213
214 $custom_statuses = $this->get_custom_statuses();
215
216 // Unfortunately, register_post_status() doesn't accept a
217 // post type argument, so we have to register the post
218 // statuses for all post types. This results in
219 // all post statuses for a post type appearing at the top
220 // of manage posts if there is a post with the status
221 foreach ( $custom_statuses as $status ) {
222 register_post_status( $status->slug, array(
223 'label' => $status->name
224 , 'protected' => true
225 , '_builtin' => false
226 , 'label_count' => _n_noop( "{$status->name} <span class='count'>(%s)</span>", "{$status->name} <span class='count'>(%s)</span>" )
227 ) );
228 }
229 }
230 }
231
232 /**
233 * Enqueue Javascript resources that we need in the admin:
234 * - Primary use of Javascript is to manipulate the post status dropdown on Edit Post and Manage Posts
235 * - jQuery Sortable plugin is used for drag and dropping custom statuses
236 * - We have other custom code for Quick Edit and JS niceties
237 */
238 function action_admin_enqueue_scripts() {
239 // Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit)
240 if ( $this->is_whitelisted_settings_view( $this->module->name ) ) {
241 wp_enqueue_script( 'jquery-ui-sortable' );
242 wp_enqueue_script( 'edit-flow-custom-status-configure', $this->module_url . 'lib/custom-status-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true );
243 }
244
245 // Custom javascript to modify the post status dropdown where it shows up
246 if ( $this->is_whitelisted_page() ) {
247 wp_enqueue_script( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.js', array( 'jquery','post' ), EDIT_FLOW_VERSION, true );
248 wp_enqueue_style( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.css', false, EDIT_FLOW_VERSION, 'all' );
249 }
250 }
251
252 /**
253 * Displays a notice to users if they have JS disabled
254 * Javascript is needed for custom statuses to be fully functional
255 */
256 function no_js_notice() {
257 if( $this->is_whitelisted_page() ) :
258 ?>
259 <style type="text/css">
260 /* Hide post status dropdown by default in case of JS issues **/
261 label[for=post_status],
262 #post-status-display,
263 #post-status-select,
264 #publish {
265 display: none;
266 }
267 </style>
268 <div class="update-nag hide-if-js">
269 <?php _e( '<strong>Note:</strong> Your browser does not support JavaScript or has JavaScript disabled. You will not be able to access or change the post status.', 'edit-flow' ); ?>
270 </div>
271 <?php
272 endif;
273 }
274
275 /**
276 * Check whether custom status stuff should be loaded on this page
277 *
278 * @todo migrate this to the base module class
279 */
280 function is_whitelisted_page() {
281 global $pagenow;
282
283 if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
284 return false;
285
286 if( ! current_user_can('edit_posts') )
287 return false;
288
289 // Only add the script to Edit Post and Edit Page pages -- don't want to bog down the rest of the admin with unnecessary javascript
290 return in_array( $pagenow, array( 'post.php', 'edit.php', 'post-new.php', 'page.php', 'edit-pages.php', 'page-new.php' ) );
291 }
292
293 /**
294 * Adds all necessary javascripts to make custom statuses work
295 *
296 * @todo Support private and future posts on edit.php view
297 */
298 function post_admin_header() {
299 global $post, $edit_flow, $pagenow, $current_user;
300
301 // Get current user
302 get_currentuserinfo() ;
303
304 // Only add the script to Edit Post and Edit Page pages -- don't want to bog down the rest of the admin with unnecessary javascript
305 if ( !empty( $post ) && $this->is_whitelisted_page() ) {
306
307 $custom_statuses = $this->get_custom_statuses();
308
309 // Get the status of the current post
310 if ( $post->ID == 0 || $post->post_status == 'auto-draft' || $pagenow == 'edit.php' ) {
311 // TODO: check to make sure that the default exists
312 $selected = $this->get_default_custom_status()->slug;
313
314 } else {
315 $selected = $post->post_status;
316 }
317
318 // Get the current post status name
319 $selected_name = '';
320
321 foreach ($custom_statuses as $status) {
322 if ($status->slug == $selected) {
323 $selected_name = $status->name;
324 }
325 }
326
327 $custom_statuses = apply_filters( 'ef_custom_status_list', $custom_statuses, $post );
328
329 // All right, we want to set up the JS var which contains all custom statuses
330 $all_statuses = array();
331
332 // The default statuses from WordPress
333 $all_statuses[] = array(
334 'name' => __( 'Published', 'edit-flow' ),
335 'slug' => 'publish',
336 'description' => '',
337 );
338 $all_statuses[] = array(
339 'name' => __( 'Privately Published', 'edit-flow' ),
340 'slug' => 'private',
341 'description' => '',
342 );
343 $all_statuses[] = array(
344 'name' => __( 'Scheduled', 'edit-flow' ),
345 'slug' => 'future',
346 'description' => '',
347 );
348
349 // Load the custom statuses
350 foreach( $custom_statuses as $status ) {
351 $all_statuses[] = array(
352 'name' => esc_js( $status->name ),
353 'slug' => esc_js( $status->slug ),
354 'description' => esc_js( $status->description ),
355 );
356 }
357
358 $always_show_dropdown = ( $this->module->options->always_show_dropdown == 'on' ) ? 1 : 0;
359
360 // Now, let's print the JS vars
361 ?>
362 <script type="text/javascript">
363 var custom_statuses = <?php echo json_encode( $all_statuses ); ?>;
364 var ef_text_no_change = '<?php echo esc_js( __( "&mdash; No Change &mdash;" ) ); ?>';
365 var ef_default_custom_status = '<?php echo esc_js( $this->get_default_custom_status()->slug ); ?>';
366 var current_status = '<?php echo esc_js( $selected ); ?>';
367 var current_status_name = '<?php echo esc_js( $selected_name ); ?>';
368 var status_dropdown_visible = <?php echo esc_js( $always_show_dropdown ); ?>;
369 var current_user_can_publish_posts = <?php echo current_user_can( 'publish_posts' ) ? 1 : 0; ?>;
370 var current_user_can_edit_published_posts = <?php echo current_user_can( 'edit_published_posts' ) ? 1 : 0; ?>;
371 </script>
372
373 <?php
374
375 }
376
377 }
378
379 /**
380 * Adds a new custom status as a term in the wp_terms table.
381 * Basically a wrapper for the wp_insert_term class.
382 *
383 * The arguments decide how the term is handled based on the $args parameter.
384 * The following is a list of the available overrides and the defaults.
385 *
386 * 'description'. There is no default. If exists, will be added to the database
387 * along with the term. Expected to be a string.
388 *
389 * 'slug'. Expected to be a string. There is no default.
390 *
391 * @param int|string $term The status to add or update
392 * @param array|string $args Change the values of the inserted term
393 * @return array|WP_Error $response The Term ID and Term Taxonomy ID
394 */
395 function add_custom_status( $term, $args = array() ) {
396 // Don't reallly need to encode the description here because our helper method handles plain strings
397 $response = wp_insert_term( $term, self::taxonomy_key, $args );
398 return $response;
399
400 }
401
402 /**
403 * Update an existing custom status
404 *
405 * @param int @status_id ID for the status
406 * @param array $args Any arguments to be updated
407 * @return object $updated_status Newly updated status object
408 */
409 function update_custom_status( $status_id, $args = array() ) {
410 global $edit_flow;
411
412 $old_status = $this->get_custom_status_by( 'id', $status_id );
413 if ( !$old_status || is_wp_error( $old_status ) )
414 return new WP_Error( 'invalid', __( "Custom status doesn't exist.", 'edit-flow' ) );
415
416 // If the name was changed, we need to change the slug
417 if ( isset( $args['name'] ) && $args['name'] != $old_status->name )
418 $args['slug'] = sanitize_title( $args['name'] );
419
420 // Reassign posts to new status slug if the slug changed and isn't restricted
421 if ( isset( $args['slug'] ) && $args['slug'] != $old_status->slug && !$this->is_restricted_status( $old_status->slug ) ) {
422 $new_status = $args['slug'];
423 $this->reassign_post_status( $old_status->slug, $new_status );
424
425 $default_status = $this->get_default_custom_status()->slug;
426 if ( $old_status->slug == $default_status )
427 $edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
428 }
429 // We're encoding metadata that isn't supported by default in the term's description field
430 $args_to_encode = array();
431 $args_to_encode['description'] = ( isset( $args['description'] ) ) ? $args['description'] : $old_status->description;
432 $args_to_encode['position'] = ( isset( $args['position'] ) ) ? $args['position'] : $old_status->position;
433 $encoded_description = $this->get_encoded_description( $args_to_encode );
434 $args['description'] = $encoded_description;
435
436 $updated_status_array = wp_update_term( $status_id, self::taxonomy_key, $args );
437 $updated_status = $this->get_custom_status_by( 'id', $updated_status_array['term_id'] );
438
439 return $updated_status;
440
441 }
442
443 /**
444 * Deletes a custom status from the wp_terms table.
445 *
446 * Partly a wrapper for the wp_delete_term function.
447 * BUT, also reassigns posts that currently have the deleted status assigned.
448 */
449 function delete_custom_status( $status_id, $args = array(), $reassign = '' ) {
450 global $edit_flow;
451 // Reassign posts to alternate status
452
453 // Get slug for the old status
454 $old_status = $this->get_custom_status_by( 'id', $status_id )->slug;
455
456 if ( $reassign == $old_status )
457 return new WP_Error( 'invalid', __( 'Cannot reassign to the status you want to delete', 'edit-flow' ) );
458
459 if( !$this->is_restricted_status( $old_status ) ) {
460 $default_status = $this->get_default_custom_status()->slug;
461 // If new status in $reassign, use that for all posts of the old_status
462 if( !empty( $reassign ) )
463 $new_status = $this->get_custom_status_by( 'id', $reassign )->slug;
464 else
465 $new_status = $default_status;
466 if ( $old_status == $default_status && $this->get_custom_status_by( 'slug', 'draft' ) ) { // Deleting default status
467 $new_status = 'draft';
468 $edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
469 }
470
471 $this->reassign_post_status( $old_status, $new_status );
472
473 return wp_delete_term( $status_id, self::taxonomy_key, $args );
474 } else
475 return new WP_Error( 'restricted', __( 'Restricted status ', 'edit-flow' ) . '(' . $this->get_custom_status_by( 'id', $status_id )->name . ')' );
476
477 }
478
479 /**
480 * Get all custom statuses as an ordered array
481 *
482 * @param array|string $statuses
483 * @param array $args
484 * @return object $statuses All of the statuses
485 */
486 function get_custom_statuses( $args = array() ) {
487
488 $default = array(
489 'hide_empty' => false,
490 );
491 $args = array_merge( $default, $args );
492 $statuses = get_terms( self::taxonomy_key, $args );
493 // Expand and order the statuses
494 $ordered_statuses = array();
495 $hold_to_end = array();
496 foreach ( $statuses as $key => $status ) {
497 // Unencode and set all of our psuedo term meta because we need the position if it exists
498 $unencoded_description = $this->get_unencoded_description( $status->description );
499 if ( is_array( $unencoded_description ) ) {
500 foreach( $unencoded_description as $key => $value ) {
501 $status->$key = $value;
502 }
503 }
504 // We require the position key later on (e.g. management table)
505 if ( !isset( $status->position ) )
506 $status->position = false;
507 // Only add the status to the ordered array if it has a set position and doesn't conflict with another key
508 // Otherwise, hold it for later
509 if ( $status->position && !array_key_exists( $status->position, $ordered_statuses ) ) {
510 $ordered_statuses[(int)$status->position] = $status;
511 } else {
512 $hold_to_end[] = $status;
513 }
514 }
515 // Sort the items numerically by key
516 ksort( $ordered_statuses, SORT_NUMERIC );
517 // Append all of the statuses that didn't have an existing position
518 foreach( $hold_to_end as $unpositioned_status )
519 $ordered_statuses[] = $unpositioned_status;
520 return $ordered_statuses;
521 }
522
523 /**
524 * Returns the a single status object based on ID, title, or slug
525 *
526 * @param string|int $string_or_int The status to search for, either by slug or ID
527 * @return object|WP_Error $status The object for the matching status
528 */
529 function get_custom_status_by( $field, $value ) {
530
531 $status = get_term_by( $field, $value, self::taxonomy_key );
532 if ( !$status || is_wp_error( $status ) )
533 return $status;
534 // Unencode and set all of our psuedo term meta because we need the position if it exists
535 $status->position = false;
536 $unencoded_description = $this->get_unencoded_description( $status->description );
537 if ( is_array( $unencoded_description ) ) {
538 foreach( $unencoded_description as $key => $value ) {
539 $status->$key = $value;
540 }
541 }
542 return $status;
543
544 }
545
546 /**
547 * Get the term object for the default custom post status
548 *
549 * @return object $default_status Default post status object
550 */
551 function get_default_custom_status() {
552 $default_status = $this->get_custom_status_by( 'slug', $this->module->options->default_status );
553 return $default_status;
554
555 }
556
557 /**
558 * Assign new statuses to posts using value provided or the default
559 *
560 * @param string $old_status Slug for the old status
561 * @param string $new_status Slug for the new status
562 */
563 function reassign_post_status( $old_status, $new_status = '' ) {
564 global $wpdb;
565
566 if ( empty( $new_status ) )
567 $new_status = $this->get_default_custom_status()->slug;
568
569 // Make the database call
570 $result = $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'post_status' => $old_status ), array( '%s' ));
571 }
572
573 /**
574 * Insert new column header for post status after the title column
575 *
576 * @param array $posts_columns Columns currently shown on the Edit Posts screen
577 * @return array Same array as the input array with a "status" column added after the "title" column
578 */
579 function _filter_manage_posts_columns( $posts_columns ) {
580 // Return immediately if the supplied parameter isn't an array (which shouldn't happen in practice?)
581 // http://wordpress.org/support/topic/plugin-edit-flow-bug-shows-2-drafts-when-there-are-none-leads-to-error-messages
582 if ( !is_array( $posts_columns ) )
583 return $posts_columns;
584
585 // Only do it for the post types this module is activated for
586 if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
587 return $posts_columns;
588
589 $result = array();
590 foreach ( $posts_columns as $key => $value ) {
591 if ($key == 'title') {
592 $result[$key] = $value;
593 $result['status'] = __('Status', 'edit-flow');
594 } else $result[$key] = $value;
595 }
596 return $result;
597
598 }
599
600 /**
601 * Adds a Post's status to its row on the Edit page
602 *
603 * @param string $column_name
604 **/
605 function _filter_manage_posts_custom_column( $column_name ) {
606
607 if ( $column_name == 'status' ) {
608 global $post;
609 echo $this->get_post_status_friendly_name( $post->post_status );
610 }
611
612 }
613
614
615 /**
616 * Determines whether the slug indicated belongs to a restricted status or not
617 *
618 * @param string $slug Slug of the status
619 * @return bool $restricted True if restricted, false if not
620 */
621 function is_restricted_status( $slug ) {
622
623 switch( $slug ) {
624 case 'publish':
625 case 'private':
626 case 'future':
627 case 'new':
628 case 'inherit':
629 case 'auto-draft':
630 case 'trash':
631 $restricted = true;
632 break;
633
634 default:
635 $restricted = false;
636 break;
637 }
638 return $restricted;
639
640 }
641
642 /**
643 * Handles a form's POST request to add a custom status
644 *
645 * @since 0.7
646 */
647 function handle_add_custom_status() {
648
649 // Check that the current POST request is our POST request
650 if ( !isset( $_POST['submit'], $_GET['page'], $_POST['action'] )
651 || $_GET['page'] != $this->module->settings_slug || $_POST['action'] != 'add-new' )
652 return;
653
654 if ( !wp_verify_nonce( $_POST['_wpnonce'], 'custom-status-add-nonce' ) )
655 wp_die( $this->module->messages['nonce-failed'] );
656
657 // Validate and sanitize the form data
658 $status_name = strip_tags( trim( $_POST['status_name'] ) );
659 $status_slug = sanitize_title( $status_name );
660 $status_description = strip_tags( trim( $_POST['status_description'] ) );
661
662 /**
663 * Form validation
664 * - Name is required and can't conflict with an existing name or slug
665 * - Description is optional
666 */
667 $_REQUEST['form-errors'] = array();
668 // Check if name field was filled in
669 if( empty( $status_name ) )
670 $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
671 // Check that the name isn't numeric
672 if ( (int)$status_name != 0 )
673 $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
674 // Check that the status name doesn't exceed 20 chars
675 if ( strlen( $status_name ) > 20 )
676 $_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
677 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
678 if ( term_exists( $status_slug ) )
679 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
680 // Check to make sure the name is not restricted
681 if ( $this->is_restricted_status( strtolower( $status_slug ) ) )
682 $_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
683
684 // If there were any form errors, kick out and return them
685 if ( count( $_REQUEST['form-errors'] ) ) {
686 $_REQUEST['error'] = 'form-error';
687 return;
688 }
689
690 // Try to add the status
691 $status_args = array(
692 'description' => $status_description,
693 'slug' => $status_slug,
694 );
695 $return = $this->add_custom_status( $status_name, $status_args );
696 if ( is_wp_error( $return ) )
697 wp_die( __( 'Could not add status: ', 'edit-flow' ) . $return->get_error_message() );
698 // Redirect if successful
699 $redirect_url = $this->get_link( array( 'message' => 'status-added' ) );
700 wp_redirect( $redirect_url );
701 exit;
702
703 }
704
705 /**
706 * Handles a POST request to edit an custom status
707 *
708 * @since 0.7
709 */
710 function handle_edit_custom_status() {
711 if ( !isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] )
712 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'edit-status' )
713 return;
714
715 if ( !wp_verify_nonce( $_POST['_wpnonce'], 'edit-status' ) )
716 wp_die( $this->module->messages['nonce-failed'] );
717
718 if ( !current_user_can( 'manage_options' ) )
719 wp_die( $this->module->messages['invalid-permissions'] );
720
721 if ( !$existing_status = $this->get_custom_status_by( 'id', (int)$_GET['term-id'] ) )
722 wp_die( $this->module->messsage['term-error'] );
723
724 $name = strip_tags( trim( $_POST['name'] ) );
725 $description = strip_tags( trim( $_POST['description'] ) );
726
727 /**
728 * Form validation for editing custom status
729 *
730 * Details
731 * - 'name' is a required field and can't conflict with existing name or slug
732 * - 'description' is optional
733 */
734 $_REQUEST['form-errors'] = array();
735 // Check if name field was filled in
736 if( empty( $name ) )
737 $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
738 // Check that the name isn't numeric
739 if ( is_numeric( $name ) )
740 $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
741 // Check that the status name doesn't exceed 20 chars
742 if ( strlen( $name ) > 20 )
743 $_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
744 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
745 $term_exists = term_exists( sanitize_title( $name ) );
746 if ( $term_exists && $term_exists != $existing_status->term_id )
747 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
748 // Check to make sure the status doesn't already exist
749 $search_status = $this->get_custom_status_by( 'slug', sanitize_title( $name ) );
750 if ( $search_status && $search_status->term_id != $existing_status->term_id )
751 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing status. Please choose another.', 'edit-flow' );
752 // Check to make sure the name is not restricted
753 if ( $this->is_restricted_status( strtolower( sanitize_title( $name ) ) ) )
754 $_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
755
756 // Kick out if there are any errors
757 if ( count( $_REQUEST['form-errors'] ) ) {
758 $_REQUEST['error'] = 'form-error';
759 return;
760 }
761
762 // Try to add the new post status
763 $args = array(
764 'name' => $name,
765 'slug' => sanitize_title( $name ),
766 'description' => $description,
767 );
768 $return = $this->update_custom_status( $existing_status->term_id, $args );
769 if ( is_wp_error( $return ) )
770 wp_die( __( 'Error updating post status.', 'edit-flow' ) );
771
772 $redirect_url = $this->get_link( array( 'message' => 'status-updated' ) );
773 wp_redirect( $redirect_url );
774 exit;
775 }
776
777 /**
778 * Handles a GET request to make the identified status default
779 *
780 * @since 0.7
781 */
782 function handle_make_default_custom_status() {
783 global $edit_flow;
784
785 // Check that the current GET request is our GET request
786 if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
787 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'make-default' )
788 return;
789
790 // Check for proper nonce
791 if ( !wp_verify_nonce( $_GET['nonce'], 'make-default' ) )
792 wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
793
794 // Only allow users with the proper caps
795 if ( !current_user_can( 'manage_options' ) )
796 wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
797
798 $term_id = (int)$_GET['term-id'];
799 $term = $this->get_custom_status_by( 'id', $term_id );
800 if ( is_object( $term ) ) {
801 $edit_flow->update_module_option( $this->module->name, 'default_status', $term->slug );
802 // @todo How do we want to handle users who click the link from "Add New Status"
803 $redirect_url = $this->get_link( array( 'message' => 'default-status-changed' ) );
804 wp_redirect( $redirect_url );
805 exit;
806 } else {
807 wp_die( __( 'Status doesn&#39;t exist.', 'edit-flow' ) );
808 }
809
810 }
811
812 /**
813 * Handles a GET request to delete a specific term
814 *
815 * @since 0.7
816 */
817 function handle_delete_custom_status() {
818
819 // Check that this GET request is our GET request
820 if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
821 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'delete-status' )
822 return;
823
824 // Check for proper nonce
825 if ( !wp_verify_nonce( $_GET['nonce'], 'delete-status' ) )
826 wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
827
828 // Only allow users with the proper caps
829 if ( !current_user_can( 'manage_options' ) )
830 wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
831
832 // Check to make sure the status isn't already deleted
833 $term_id = (int)$_GET['term-id'];
834 $term = $this->get_custom_status_by( 'id', $term_id );
835 if( !$term )
836 wp_die( __( 'Status does not exist.', 'edit-flow' ) );
837
838 // Don't allow deletion of default status
839 if ( $term->slug == $this->get_default_custom_status()->slug )
840 wp_die( __( 'Cannot delete default status.', 'edit-flow' ) );
841
842 $return = $this->delete_custom_status( $term_id );
843 if ( is_wp_error( $return ) )
844 wp_die( __( 'Could not delete the status: ', 'edit-flow' ) . $return->get_error_message() );
845
846 $redirect_url = $this->get_link( array( 'message' => 'status-deleted' ) );
847 wp_redirect( $redirect_url );
848 exit;
849
850 }
851
852 /**
853 * Generate a link to one of the custom status actions
854 *
855 * @since 0.7
856 *
857 * @param array $args (optional) Action and any query args to add to the URL
858 * @return string $link Direct link to complete the action
859 */
860 function get_link( $args = array() ) {
861 if ( !isset( $args['action'] ) )
862 $args['action'] = '';
863 if ( !isset( $args['page'] ) )
864 $args['page'] = $this->module->settings_slug;
865 // Add other things we may need depending on the action
866 switch( $args['action'] ) {
867 case 'make-default':
868 case 'delete-status':
869 $args['nonce'] = wp_create_nonce( $args['action'] );
870 break;
871 default:
872 break;
873 }
874 return add_query_arg( $args, get_admin_url( null, 'admin.php' ) );
875 }
876
877 /**
878 * Handle an ajax request to update the order of custom statuses
879 *
880 * @since 0.7
881 */
882 function handle_ajax_update_status_positions() {
883
884 if ( !wp_verify_nonce( $_POST['custom_status_sortable_nonce'], 'custom-status-sortable' ) )
885 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
886
887 if ( !current_user_can( 'manage_options') )
888 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
889
890 if ( !isset( $_POST['status_positions'] ) || !is_array( $_POST['status_positions'] ) )
891 $this->print_ajax_response( 'error', __( 'Terms not set.', 'edit-flow' ) );
892
893 // Update each custom status with its new position
894 foreach ( $_POST['status_positions'] as $position => $term_id ) {
895
896 // Have to add 1 to the position because the index started with zero
897 $args = array(
898 'position' => (int)$position + 1,
899 );
900 $return = $this->update_custom_status( (int)$term_id, $args );
901 // @todo check that this was a valid return
902 }
903 $this->print_ajax_response( 'success', $this->module->messages['status-position-updated'] );
904 }
905
906 /**
907 * Handle an Inline Edit POST request to update status values
908 *
909 * @since 0.7
910 */
911 function ajax_inline_save_status() {
912 global $edit_flow;
913
914 if ( !wp_verify_nonce( $_POST['inline_edit'], 'custom-status-inline-edit-nonce' ) )
915 die( $this->module->messages['nonce-failed'] );
916
917 if ( !current_user_can( 'manage_options') )
918 die( $this->module->messages['invalid-permissions'] );
919
920 $term_id = (int) $_POST['status_id'];
921 $status_name = strip_tags( trim( $_POST['name'] ) );
922 $status_slug = sanitize_title( $_POST['name'] );
923 $status_description = strip_tags( trim( $_POST['description'] ) );
924
925 // Check if name field was filled in
926 if ( empty( $status_name ) ) {
927 $change_error = new WP_Error( 'invalid', __( 'Please enter a name for the status.', 'edit-flow' ) );
928 die( $change_error->get_error_message() );
929 }
930
931 // Check that the name isn't numeric
932 if( is_numeric( $status_name) ) {
933 $change_error = new WP_Error( 'invalid', __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' ) );
934 die( $change_error->get_error_message() );
935 }
936
937 // Check that the status name doesn't exceed 20 chars
938 if ( strlen( $status_name ) > 20 ) {
939 $change_error = new WP_Error( 'invalid', __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' ) );
940 die( $change_error->get_error_message() );
941 }
942
943 // Check to make sure the name is not restricted
944 if ( $edit_flow->custom_status->is_restricted_status( strtolower( $status_name ) ) ) {
945 $change_error = new WP_Error( 'invalid', __( 'Status name is restricted. Please chose another name.', 'edit-flow' ) );
946 die( $change_error->get_error_message() );
947 }
948
949 // Check to make sure the status doesn't already exist
950 if ( $this->get_custom_status_by( 'slug', $status_slug ) && ( $this->get_custom_status_by( 'id', $term_id )->slug != $status_slug ) ) {
951 $change_error = new WP_Error( 'invalid', __( 'Status already exists. Please choose another name.', 'edit-flow' ) );
952 die( $change_error->get_error_message() );
953 }
954
955 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a fatal error
956 $term_exists = term_exists( sanitize_title( $status_name ) );
957 if ( $term_exists && $term_exists != $term_id ) {
958 $change_error = new WP_Error( 'invalid', __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' ) );
959 die( $change_error->get_error_message() );
960 }
961
962 // get status_name & status_description
963 $args = array(
964 'name' => $status_name,
965 'description' => $status_description,
966 'slug' => $status_slug,
967 );
968 $return = $this->update_custom_status( $term_id, $args );
969 if( !is_wp_error( $return ) ) {
970 set_current_screen( 'edit-custom-status' );
971 $wp_list_table = new EF_Custom_Status_List_Table();
972 $wp_list_table->prepare_items();
973 echo $wp_list_table->single_row( $return );
974 die();
975 } else {
976 $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the status: <strong>%s</strong>', 'edit-flow' ), $status_name ) );
977 die( $change_error->get_error_message() );
978 }
979
980 }
981
982 /**
983 * Register settings for notifications so we can partially use the Settings API
984 * (We use the Settings API for form generation, but not saving)
985 *
986 * @since 0.7
987 */
988 function register_settings() {
989
990 add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name );
991 add_settings_field( 'post_types', __( 'Use on these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
992 add_settings_field( 'always_show_dropdown', __( 'Always show dropdown:', 'edit-flow' ), array( $this, 'settings_always_show_dropdown_option'), $this->module->options_group_name, $this->module->options_group_name . '_general' );
993
994 }
995
996 /**
997 * Choose the post types that should be displayed on the calendar
998 *
999 * @since 0.7
1000 */
1001 function settings_post_types_option() {
1002 global $edit_flow;
1003 $edit_flow->settings->helper_option_custom_post_type( $this->module );
1004 }
1005
1006 /**
1007 * Option for whether the blog admin email address should be always notified or not
1008 *
1009 * @since 0.7
1010 */
1011 function settings_always_show_dropdown_option() {
1012 $options = array(
1013 'off' => __( 'Disabled', 'edit-flow' ),
1014 'on' => __( 'Enabled', 'edit-flow' ),
1015 );
1016 echo '<select id="always_show_dropdown" name="' . $this->module->options_group_name . '[always_show_dropdown]">';
1017 foreach ( $options as $value => $label ) {
1018 echo '<option value="' . esc_attr( $value ) . '"';
1019 echo selected( $this->module->options->always_show_dropdown, $value );
1020 echo '>' . esc_html( $label ) . '</option>';
1021 }
1022 echo '</select>';
1023 }
1024
1025 /**
1026 * Validate input from the end user
1027 *
1028 * @since 0.7
1029 */
1030 function settings_validate( $new_options ) {
1031
1032 // Whitelist validation for the post type options
1033 if ( !isset( $new_options['post_types'] ) )
1034 $new_options['post_types'] = array();
1035 $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support );
1036
1037 // Whitelist validation for the 'always_show_dropdown' optoins
1038 if ( !isset( $new_options['always_show_dropdown'] ) || $new_options['always_show_dropdown'] != 'on' )
1039 $new_options['always_show_dropdown'] = 'off';
1040
1041 return $new_options;
1042 }
1043
1044 /**
1045 * Primary configuration page for custom status class.
1046 * Shows form to add new custom statuses on the left and a
1047 * WP_List_Table with the custom status terms on the right
1048 */
1049 function print_configure_view() {
1050 global $edit_flow;
1051
1052 /** Full width view for editing a custom status **/
1053 if ( isset( $_GET['action'], $_GET['term-id'] ) && $_GET['action'] == 'edit-status' ): ?>
1054 <?php
1055 // Check whether the term exists
1056 $term_id = (int)$_GET['term-id'];
1057 $status = $this->get_custom_status_by( 'id', $term_id );
1058 if ( !$status ) {
1059 echo '<div class="error"><p>' . $this->module->messages['status-missing'] . '</p></div>';
1060 return;
1061 }
1062 $edit_status_link = $this->get_link( array( 'action' => 'edit-status', 'term-id' => $term_id ) );
1063
1064 $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $status->name;
1065 $description = ( isset( $_POST['description'] ) ) ? stripslashes( strip_tags( $_POST['description'] ) ) : $status->description;
1066 ?>
1067
1068 <div id="ajax-response"></div>
1069 <form method="post" action="<?php echo esc_attr( $edit_status_link ); ?>" >
1070 <input type="hidden" name="term-id" value="<?php echo esc_attr( $term_id ); ?>" />
1071 <?php
1072 wp_original_referer_field();
1073 wp_nonce_field( 'edit-status' );
1074 ?>
1075 <table class="form-table">
1076 <tr class="form-field form-required">
1077 <th scope="row" valign="top"><label for="name"><?php _e( 'Custom Status', 'edit-flow' ); ?></label></th>
1078 <td><input name="name" id="name" type="text" value="<?php echo esc_attr( $name ); ?>" size="40" aria-required="true" />
1079 <?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>
1080 </td>
1081 </tr>
1082 <tr class="form-field">
1083 <th scope="row" valign="top"><?php _e( 'Slug', 'edit-flow' ); ?></th>
1084 <td>
1085 <input type="text" disabled="disabled" value="<?php echo esc_attr( $status->slug ); ?>" />
1086 <?php $edit_flow->settings->helper_print_error_or_description( 'slug', __( 'The slug is the unique ID for the status and is changed when the name is changed.', 'edit-flow' ) ); ?>
1087 </td>
1088 </tr>
1089 <tr class="form-field">
1090 <th scope="row" valign="top"><label for="description"><?php _e( 'Description', 'edit-flow' ); ?></label></th>
1091 <td>
1092 <textarea name="description" id="description" rows="5" cols="50" style="width: 97%;"><?php echo esc_textarea( $description ); ?></textarea>
1093 <?php $edit_flow->settings->helper_print_error_or_description( 'description', __( 'The description is primarily for administrative use, to give you some context on what the custom status is to be used for.', 'edit-flow' ) ); ?>
1094 </td>
1095 </tr>
1096 </table>
1097 <p class="submit">
1098 <?php submit_button( __( 'Update Status', 'edit-flow' ), 'primary', 'submit', false ); ?>
1099 <a class="cancel-settings-link" href="<?php echo esc_url( $this->get_link() ); ?>"><?php _e( 'Cancel', 'edit-flow' ); ?></a>
1100 </p>
1101 </form>
1102
1103 <?php else: ?>
1104 <?php
1105 $wp_list_table = new EF_Custom_Status_List_Table();
1106 $wp_list_table->prepare_items();
1107 ?>
1108 <script type="text/javascript">
1109 var ef_confirm_delete_status_string = "<?php echo esc_js( __( 'Are you sure you want to delete the post status? All posts with this status will be assigned to the default status.', 'edit-flow' ) ); ?>";
1110 </script>
1111 <div id="col-right">
1112 <div class="col-wrap">
1113 <?php $wp_list_table->display(); ?>
1114 <?php wp_nonce_field( 'custom-status-sortable', 'custom-status-sortable' ); ?>
1115 <p class="description" style="padding-top:10px;"><?php _e( 'Deleting a post status will assign all posts to the default post status.', 'edit-flow' ); ?></p>
1116 </div>
1117 </div>
1118 <div id="col-left">
1119 <div class="col-wrap">
1120 <div class="form-wrap">
1121 <h3 class="nav-tab-wrapper">
1122 <a href="<?php echo esc_url( $this->get_link() ); ?>" class="nav-tab<?php if ( !isset( $_GET['action'] ) || $_GET['action'] != 'change-options' ) echo ' nav-tab-active'; ?>"><?php _e( 'Add New', 'edit-flow' ); ?></a>
1123 <a href="<?php echo esc_url( $this->get_link( array( 'action' => 'change-options' ) ) ); ?>" class="nav-tab<?php if ( isset( $_GET['action'] ) && $_GET['action'] == 'change-options' ) echo ' nav-tab-active'; ?>"><?php _e( 'Options', 'edit-flow' ); ?></a>
1124 </h3>
1125 <?php if ( isset( $_GET['action'] ) && $_GET['action'] == 'change-options' ): ?>
1126 <form class="basic-settings" action="<?php echo esc_url( $this->get_link( array( 'action' => 'change-options' ) ) ); ?>" method="post">
1127 <?php settings_fields( $this->module->options_group_name ); ?>
1128 <?php do_settings_sections( $this->module->options_group_name ); ?>
1129 <?php echo '<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="' . esc_attr( $this->module->name ) . '" />'; ?>
1130 <?php submit_button(); ?>
1131 </form>
1132 <?php else: ?>
1133 <?php /** Custom form for adding a new Custom Status term **/ ?>
1134 <form class="add:the-list:" action="<?php echo esc_url( $this->get_link() ); ?>" method="post" id="addstatus" name="addstatus">
1135 <div class="form-field form-required">
1136 <label for="status_name"><?php _e( 'Name', 'edit-flow' ); ?></label>
1137 <input type="text" aria-required="true" size="20" maxlength="20" id="status_name" name="status_name" value="<?php if ( !empty( $_POST['status_name'] ) ) echo esc_attr( $_POST['status_name'] ) ?>" />
1138 <?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>
1139 </div>
1140 <div class="form-field">
1141 <label for="status_description"><?php _e( 'Description', 'edit-flow' ); ?></label>
1142 <textarea cols="40" rows="5" id="status_description" name="status_description"><?php if ( !empty( $_POST['status_description'] ) ) echo esc_textarea( $_POST['status_description'] ) ?></textarea>
1143 <?php $edit_flow->settings->helper_print_error_or_description( 'description', __( 'The description is primarily for administrative use, to give you some context on what the custom status is to be used for.', 'edit-flow' ) ); ?>
1144 </div>
1145 <?php wp_nonce_field( 'custom-status-add-nonce' ); ?>
1146 <?php echo '<input id="action" name="action" type="hidden" value="add-new" />'; ?>
1147 <p class="submit"><?php submit_button( __( 'Add New Status', 'edit-flow' ), 'primary', 'submit', false ); ?><a class="cancel-settings-link" href="<?php echo EDIT_FLOW_SETTINGS_PAGE; ?>"><?php _e( 'Back to Edit Flow', 'edit-flow' ); ?></a></p>
1148 </form>
1149 <?php endif; ?>
1150 </div>
1151 </div>
1152 </div>
1153 <?php $wp_list_table->inline_edit(); ?>
1154 <?php endif; ?>
1155 <?php
1156 }
1157
1158 /**
1159 * This is a hack! hack! hack! until core is fixed/better supports custom statuses
1160 *
1161 * When publishing a post with a custom status, set the status to 'pending' temporarily
1162 * @see Works around this limitation: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-includes/post.php#L2694
1163 * @see Original thread: http://wordpress.org/support/topic/plugin-edit-flow-custom-statuses-create-timestamp-problem
1164 * @see Core ticket: http://core.trac.wordpress.org/ticket/18362
1165 */
1166 function check_timestamp_on_publish() {
1167 global $edit_flow, $pagenow, $wpdb;
1168
1169 // Handles the transition to 'publish' on edit.php
1170 if ( isset( $edit_flow ) && $pagenow == 'edit.php' && isset( $_REQUEST['bulk_edit'] ) ) {
1171 // For every post_id, set the post_status as 'pending' only when there's no timestamp set for $post_date_gmt
1172 if ( $_REQUEST['_status'] == 'publish' ) {
1173 $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
1174 foreach ( $post_ids as $post_id ) {
1175 $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $post_id, 'post_date_gmt' => '0000-00-00 00:00:00' ) );
1176 }
1177 }
1178 }
1179
1180 // Handles the transition to 'publish' on post.php
1181 if ( isset( $edit_flow ) && $pagenow == 'post.php' && isset( $_POST['publish'] ) ) {
1182 // Set the post_status as 'pending' only when there's no timestamp set for $post_date_gmt
1183 if ( isset( $_POST['post_ID'] ) ) {
1184 $post_id = (int) $_POST['post_ID'];
1185 $ret = $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $post_id, 'post_date_gmt' => '0000-00-00 00:00:00' ) );
1186 foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
1187 if ( !empty( $_POST['hidden_' . $timeunit] ) && $_POST['hidden_' . $timeunit] != $_POST[$timeunit] ) {
1188 $edit_date = '1';
1189 break;
1190 }
1191 }
1192 if ( $ret && empty( $edit_date ) ) {
1193 add_filter( 'pre_post_date', array( $this, 'helper_timestamp_hack' ) );
1194 add_filter( 'pre_post_date_gmt', array( $this, 'helper_timestamp_hack' ) );
1195 }
1196 }
1197 }
1198
1199 }
1200 /**
1201 * PHP < 5.3.x doesn't support anonymous functions
1202 * This helper is only used for the check_timestamp_on_publish method above
1203 *
1204 * @since 0.7.3
1205 */
1206 function helper_timestamp_hack() {
1207 return ( 'pre_post_date' == current_filter() ) ? current_time('mysql') : '';
1208 }
1209 /**
1210 * This is a hack! hack! hack! until core is fixed/better supports custom statuses
1211 *
1212 * @since 0.6.5
1213 *
1214 * Normalize post_date_gmt if it isn't set to the past or the future
1215 * @see Works around this limitation: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-includes/post.php#L2506
1216 * @see Original thread: http://wordpress.org/support/topic/plugin-edit-flow-custom-statuses-create-timestamp-problem
1217 * @see Core ticket: http://core.trac.wordpress.org/ticket/18362
1218 */
1219 function fix_custom_status_timestamp( $data ) {
1220 global $edit_flow, $pagenow;
1221 // Don't run this if Edit Flow isn't active, or we're on some other page
1222 if ( !isset( $edit_flow ) || $pagenow != 'post.php' || !isset( $_POST ) )
1223 return $data;
1224 $custom_statuses = get_terms( 'post_status', array( 'get' => 'all' ) );
1225 $status_slugs = array();
1226 foreach( $custom_statuses as $custom_status ) {
1227 $status_slugs[] = $custom_status->slug;
1228 }
1229 $ef_normalize_post_date_gmt = true;
1230 // We're only going to normalize the post_date_gmt if the user hasn't set a custom date in the metabox
1231 // and the current post_date_gmt isn't already future or past-ized
1232 foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
1233 if ( !empty( $_POST['hidden_' . $timeunit] ) && (($_POST['hidden_' . $timeunit] != $_POST[$timeunit] ) || ( $_POST['hidden_' . $timeunit] != $_POST['cur_' . $timeunit] )) ) {
1234 $ef_normalize_post_date_gmt = false;
1235 break;
1236 }
1237 }
1238 if ( $ef_normalize_post_date_gmt )
1239 if ( in_array( $data['post_status'], $status_slugs ) )
1240 $data['post_date_gmt'] = '0000-00-00 00:00:00';
1241 return $data;
1242 }
1243
1244 }
1245
1246 }
1247
1248 /**
1249 * Custom Statuses uses WordPress' List Table API for generating the custom status management table
1250 *
1251 * @since 0.7
1252 */
1253 class EF_Custom_Status_List_Table extends WP_List_Table
1254 {
1255
1256 var $callback_args;
1257 var $default_status;
1258
1259 /**
1260 * Construct the extended class
1261 */
1262 function __construct() {
1263
1264 parent::__construct( array(
1265 'plural' => 'custom statuses',
1266 'singular' => 'custom status',
1267 'ajax' => true
1268 ) );
1269
1270 }
1271
1272 /**
1273 * Pull in the data we'll be displaying on the table
1274 *
1275 * @since 0.7
1276 */
1277 function prepare_items() {
1278 global $edit_flow;
1279
1280 $columns = $this->get_columns();
1281 $hidden = array(
1282 'position',
1283 );
1284 $sortable = array();
1285 $this->_column_headers = array($columns, $hidden, $sortable);
1286
1287 $this->items = $edit_flow->custom_status->get_custom_statuses();
1288 $total_items = count( $this->items );
1289 $this->default_status = $edit_flow->custom_status->get_default_custom_status()->slug;
1290
1291 $this->set_pagination_args( array(
1292 'total_items' => $total_items,
1293 'per_page' => $total_items,
1294 ) );
1295 }
1296
1297 /**
1298 * Message to be displayed when there are no custom statuses. Should never be displayed, but we'll customize it
1299 * just in case.
1300 *
1301 * @since 0.7
1302 */
1303 function no_items() {
1304 _e( 'No custom statuses found.', 'edit-flow' );
1305 }
1306
1307 /**
1308 * Table shows (hidden) position, status name, status description, and the post count for each activated
1309 * post type
1310 *
1311 * @since 0.7
1312 *
1313 * @return array $columns Columns to be registered with the List Table
1314 */
1315 function get_columns() {
1316 global $edit_flow;
1317
1318 $columns = array(
1319 'position' => __( 'Position', 'edit-flow' ),
1320 'name' => __( 'Name', 'edit-flow' ),
1321 'description' => __( 'Description', 'edit-flow' ),
1322 );
1323
1324 $post_types = get_post_types( '', 'objects' );
1325 $supported_post_types = $edit_flow->helpers->get_post_types_for_module( $edit_flow->custom_status->module );
1326 foreach ( $post_types as $post_type )
1327 if ( in_array( $post_type->name, $supported_post_types ) )
1328 $columns[$post_type->name] = $post_type->label;
1329
1330 return $columns;
1331 }
1332
1333 /**
1334 * Fallback column callback.
1335 * Primarily used to display post count for each post type
1336 *
1337 * @since 0.7
1338 *
1339 * @param object $item Custom status as an object
1340 * @param string $column_name Name of the column as registered in $this->prepare_items()
1341 * @return string $output What will be rendered
1342 */
1343 function column_default( $item, $column_name ) {
1344 global $edit_flow;
1345
1346 // Handle custom post counts for different post types
1347 $post_types = get_post_types( '', 'names' );
1348 if ( in_array( $column_name, $post_types ) ) {
1349
1350 // @todo Cachify this
1351 $post_count = wp_cache_get( "ef_custom_status_count_$column_name" );
1352 if ( false === $post_count ) {
1353 $posts = wp_count_posts( $column_name );
1354 $post_status = $item->slug;
1355 // To avoid error notices when changing the name of non-standard statuses
1356 if ( isset( $posts->$post_status ) )
1357 $post_count = $posts->$post_status;
1358 else
1359 $post_count = 0;
1360 //wp_cache_set( "ef_custom_status_count_$column_name", $post_count );
1361 }
1362 $output = sprintf( '<a title="See all %1$ss saved as \'%2$s\'" href="%3$s">%4$s</a>', $column_name, $item->name, $edit_flow->helpers->filter_posts_link( $item->slug, $column_name ), $post_count );
1363 return $output;
1364 }
1365
1366 }
1367
1368 /**
1369 * Hidden column for storing the status position
1370 *
1371 * @since 0.7
1372 *
1373 * @param object $item Custom status as an object
1374 * @return string $output What will be rendered
1375 */
1376 function column_position( $item ) {
1377 return esc_html( $item->position );
1378 }
1379
1380 /**
1381 * Displayed column showing the name of the status
1382 *
1383 * @since 0.7
1384 *
1385 * @param object $item Custom status as an object
1386 * @return string $output What will be rendered
1387 */
1388 function column_name( $item ) {
1389 global $edit_flow;
1390
1391 $item_edit_link = esc_url( $edit_flow->custom_status->get_link( array( 'action' => 'edit-status', 'term-id' => $item->term_id ) ) );
1392
1393 $output = '<strong><a href="' . $item_edit_link . '">' . esc_html( $item->name ) . '</a>';
1394 if ( $item->slug == $this->default_status )
1395 $output .= ' - ' . __( 'Default', 'edit-flow' );
1396 $output .= '</strong>';
1397
1398 // Don't allow for any of these status actions when adding a new custom status
1399 if ( isset( $_GET['action'] ) && $_GET['action'] == 'add' )
1400 return $output;
1401
1402 $actions = array();
1403 $actions['edit'] = "<a href='$item_edit_link'>" . __( 'Edit', 'edit-flow' ) . "</a>";
1404 $actions['inline hide-if-no-js'] = '<a href="#" class="editinline">' . __( 'Quick&nbsp;Edit' ) . '</a>';
1405 if ( $item->slug != $this->default_status )
1406 $actions['make_default'] = sprintf( '<a href="%1$s">' . __( 'Make&nbsp;Default', 'edit-flow' ) . '</a>', $edit_flow->custom_status->get_link( array( 'action' => 'make-default', 'term-id' => $item->term_id ) ) );
1407
1408 if ( $item->slug != $this->default_status )
1409 $actions['delete delete-status'] = sprintf( '<a href="%1$s">' . __( 'Delete', 'edit-flow' ) . '</a>', $edit_flow->custom_status->get_link( array( 'action' => 'delete-status', 'term-id' => $item->term_id ) ) );
1410
1411 $output .= $this->row_actions( $actions, false );
1412 $output .= '<div class="hidden" id="inline_' . esc_attr( $item->term_id ) . '">';
1413 $output .= '<div class="name">' . esc_html( $item->name ) . '</div>';
1414 $output .= '<div class="description">' . esc_html( $item->description ) . '</div>';
1415 $output .= '</div>';
1416
1417 return $output;
1418
1419 }
1420
1421 /**
1422 * Displayed column showing the description of the status
1423 *
1424 * @since 0.7
1425 *
1426 * @param object $item Custom status as an object
1427 * @return string $output What will be rendered
1428 */
1429 function column_description( $item ) {
1430 return esc_html( $item->description );
1431 }
1432
1433 /**
1434 * Prepare and echo a single custom status row
1435 *
1436 * @since 0.7
1437 */
1438 function single_row( $item ) {
1439 static $alternate_class = '';
1440 $alternate_class = ( $alternate_class == '' ? ' alternate' : '' );
1441 $row_class = ' class="term-static' . $alternate_class . '"';
1442
1443 echo '<tr id="term-' . $item->term_id . '"' . $row_class . '>';
1444 echo $this->single_row_columns( $item );
1445 echo '</tr>';
1446 }
1447
1448 /**
1449 * Hidden form used for inline editing functionality
1450 *
1451 * @since 0.7
1452 */
1453 function inline_edit() {
1454 global $edit_flow;
1455 ?>
1456 <form method="get" action=""><table style="display: none"><tbody id="inlineedit">
1457 <tr id="inline-edit" class="inline-edit-row" style="display: none"><td colspan="<?php echo $this->get_column_count(); ?>" class="colspanchange">
1458 <fieldset><div class="inline-edit-col">
1459 <h4><?php _e( 'Quick Edit' ); ?></h4>
1460 <label>
1461 <span class="title"><?php _e( 'Name', 'edit-flow' ); ?></span>
1462 <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" maxlength="20" /></span>
1463 </label>
1464 <label>
1465 <span class="title"><?php _e( 'Description', 'edit-flow' ); ?></span>
1466 <span class="input-text-wrap"><input type="text" name="description" class="pdescription" value="" /></span>
1467 </label>
1468 </div></fieldset>
1469 <p class="inline-edit-save submit">
1470 <a accesskey="c" href="#inline-edit" title="<?php _e( 'Cancel' ); ?>" class="cancel button-secondary alignleft"><?php _e( 'Cancel' ); ?></a>
1471 <?php $update_text = __( 'Update Status', 'edit-flow' ); ?>
1472 <a accesskey="s" href="#inline-edit" title="<?php echo esc_attr( $update_text ); ?>" class="save button-primary alignright"><?php echo $update_text; ?></a>
1473 <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
1474 <span class="error" style="display:none;"></span>
1475 <?php wp_nonce_field( 'custom-status-inline-edit-nonce', 'inline_edit', false ); ?>
1476 <br class="clear" />
1477 </p>
1478 </td></tr>
1479 </tbody></table></form>
1480 <?php
1481 }
1482
1483 }
1484