PluginProbe
Edit Flow / 0.8
Edit Flow v0.8
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.8, at modules/custom-status/custom-status.php

1,804 lines 67.1 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 'settings_help_tab' => array(
57 'id' => 'ef-custom-status-overview',
58 'title' => __('Overview', 'edit-flow'),
59 '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'),
60 ),
61 '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' ),
62 );
63 $this->module = EditFlow()->register_module( 'custom_status', $args );
64
65 }
66
67 /**
68 * Initialize the EF_Custom_Status class if the module is active
69 */
70 function init() {
71 global $edit_flow;
72
73 // Register custom statuses as a taxonomy
74 $this->register_custom_statuses();
75
76 // Register our settings
77 add_action( 'admin_init', array( $this, 'register_settings' ) );
78
79 // Load CSS and JS resources that we probably need
80 add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
81 add_action( 'admin_notices', array( $this, 'no_js_notice' ) );
82 add_action( 'admin_print_scripts', array( $this, 'post_admin_header' ) );
83
84 // Methods for handling the actions of creating, making default, and deleting post stati
85 add_action( 'admin_init', array( $this, 'handle_add_custom_status' ) );
86 add_action( 'admin_init', array( $this, 'handle_edit_custom_status' ) );
87 add_action( 'admin_init', array( $this, 'handle_make_default_custom_status' ) );
88 add_action( 'admin_init', array( $this, 'handle_delete_custom_status' ) );
89 add_action( 'wp_ajax_update_status_positions', array( $this, 'handle_ajax_update_status_positions' ) );
90 add_action( 'wp_ajax_inline_save_status', array( $this, 'ajax_inline_save_status' ) );
91
92 // Hook to add the status column to Manage Posts
93
94 add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns') );
95 add_action( 'manage_posts_custom_column', array( $this, '_filter_manage_posts_custom_column') );
96
97 // 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)
98 add_filter( 'manage_pages_columns', array( $this, '_filter_manage_posts_columns' ) );
99 add_action( 'manage_pages_custom_column', array( $this, '_filter_manage_posts_custom_column' ) );
100
101 // These seven-ish methods are hacks for fixing bugs in WordPress core
102 add_action( 'admin_init', array( $this, 'check_timestamp_on_publish' ) );
103 add_filter( 'wp_insert_post_data', array( $this, 'fix_custom_status_timestamp' ), 10, 2 );
104 add_action( 'wp_insert_post', array( $this, 'fix_post_name' ), 10, 2 );
105 add_action( 'edit_form_after_title', array( $this, 'action_edit_form_after_title' ) );
106 add_filter( 'editable_slug', array( $this, 'fix_editable_slug' ) );
107 add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_one' ) );
108 add_filter( 'post_link', array( $this, 'fix_preview_link_part_two' ), 10, 2 );
109 add_filter( 'page_link', array( $this, 'fix_preview_link_part_two' ), 10, 2 );
110 add_filter( 'post_type_link', array( $this, 'fix_preview_link_part_two' ), 10, 2 );
111 add_filter( 'post_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 );
112 add_filter( 'page_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 );
113
114 }
115
116 /**
117 * Create the default set of custom statuses the first time the module is loaded
118 *
119 * @since 0.7
120 */
121 function install() {
122
123 $default_terms = array(
124 array(
125 'term' => __( 'Pitch', 'edit-flow' ),
126 'args' => array(
127 'slug' => 'pitch',
128 'description' => __( 'Idea proposed; waiting for acceptance.', 'edit-flow' ),
129 'position' => 1,
130 ),
131 ),
132 array(
133 'term' => __( 'Assigned', 'edit-flow' ),
134 'args' => array(
135 'slug' => 'assigned',
136 'description' => __( 'Post idea assigned to writer.', 'edit-flow' ),
137 'position' => 2,
138 ),
139 ),
140 array(
141 'term' => __( 'In Progress', 'edit-flow' ),
142 'args' => array(
143 'slug' => 'in-progress',
144 'description' => __( 'Writer is working on the post.', 'edit-flow' ),
145 'position' => 3,
146 ),
147 ),
148 array(
149 'term' => __( 'Draft', 'edit-flow' ),
150 'args' => array(
151 'slug' => 'draft',
152 'description' => __( 'Post is a draft; not ready for review or publication.', 'edit-flow' ),
153 'position' => 4,
154 ),
155 ),
156 array(
157 'term' => __( 'Pending Review' ),
158 'args' => array(
159 'slug' => 'pending',
160 'description' => __( 'Post needs to be reviewed by an editor.', 'edit-flow' ),
161 'position' => 5,
162 ),
163 ),
164 );
165
166 // Okay, now add the default statuses to the db if they don't already exist
167 foreach( $default_terms as $term ) {
168 if( !term_exists( $term['term'] ) )
169 $this->add_custom_status( $term['term'], $term['args'] );
170 }
171
172 }
173
174 /**
175 * Upgrade our data in case we need to
176 *
177 * @since 0.7
178 */
179 function upgrade( $previous_version ) {
180 global $edit_flow;
181
182 // Upgrade path to v0.7
183 if ( version_compare( $previous_version, '0.7' , '<' ) ) {
184 // Migrate dropdown visibility option
185 if ( $dropdown_visible = get_option( 'edit_flow_status_dropdown_visible' ) )
186 $dropdown_visible = 'on';
187 else
188 $dropdown_visible = 'off';
189 $edit_flow->update_module_option( $this->module->name, 'always_show_dropdown', $dropdown_visible );
190 delete_option( 'edit_flow_status_dropdown_visible' );
191 // Migrate default status option
192 if ( $default_status = get_option( 'edit_flow_custom_status_default_status' ) )
193 $edit_flow->update_module_option( $this->module->name, 'default_status', $default_status );
194 delete_option( 'edit_flow_custom_status_default_status' );
195
196 // Technically we've run this code before so we don't want to auto-install new data
197 $edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
198 }
199 // Upgrade path to v0.7.4
200 if ( version_compare( $previous_version, '0.7.4', '<' ) ) {
201 // Custom status descriptions become base64_encoded, instead of maybe json_encoded.
202 $this->upgrade_074_term_descriptions( self::taxonomy_key );
203 }
204
205 }
206
207 /**
208 * Makes the call to register_post_status to register the user's custom statuses.
209 * Also unregisters draft and pending, in case the user doesn't want them.
210 */
211 function register_custom_statuses() {
212 global $wp_post_statuses;
213
214 if ( $this->disable_custom_statuses_for_post_type() )
215 return;
216
217 // Register new taxonomy so that we can store all our fancy new custom statuses (or is it stati?)
218 if ( !taxonomy_exists( self::taxonomy_key ) ) {
219 $args = array( 'hierarchical' => false,
220 'update_count_callback' => '_update_post_term_count',
221 'label' => false,
222 'query_var' => false,
223 'rewrite' => false,
224 'show_ui' => false
225 );
226 register_taxonomy( self::taxonomy_key, 'post', $args );
227 }
228
229 if ( function_exists( 'register_post_status' ) ) {
230 // Users can delete draft and pending statuses if they want, so let's get rid of them
231 // They'll get re-added if the user hasn't "deleted" them
232 unset( $wp_post_statuses[ 'draft' ] );
233 unset( $wp_post_statuses[ 'pending' ] );
234
235 $custom_statuses = $this->get_custom_statuses();
236
237 // Unfortunately, register_post_status() doesn't accept a
238 // post type argument, so we have to register the post
239 // statuses for all post types. This results in
240 // all post statuses for a post type appearing at the top
241 // of manage posts if there is a post with the status
242 foreach ( $custom_statuses as $status ) {
243 register_post_status( $status->slug, array(
244 'label' => $status->name
245 , 'protected' => true
246 , '_builtin' => false
247 , 'label_count' => _n_noop( "{$status->name} <span class='count'>(%s)</span>", "{$status->name} <span class='count'>(%s)</span>" )
248 ) );
249 }
250 }
251 }
252
253 /**
254 * Whether custom post statuses should be disabled for this post type.
255 * Used to stop custom statuses from being registered for post types that don't support them.
256 *
257 * @since 0.7.5
258 *
259 * @return bool
260 */
261 function disable_custom_statuses_for_post_type( $post_type = null ) {
262 global $pagenow;
263
264 // Only allow deregistering on 'edit.php' and 'post.php'
265 if ( ! in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ) ) )
266 return false;
267
268 if ( is_null( $post_type ) )
269 $post_type = $this->get_current_post_type();
270
271 if ( $post_type && ! in_array( $post_type, $this->get_post_types_for_module( $this->module ) ) )
272 return true;
273
274 return false;
275 }
276
277 /**
278 * Enqueue Javascript resources that we need in the admin:
279 * - Primary use of Javascript is to manipulate the post status dropdown on Edit Post and Manage Posts
280 * - jQuery Sortable plugin is used for drag and dropping custom statuses
281 * - We have other custom code for Quick Edit and JS niceties
282 */
283 function action_admin_enqueue_scripts() {
284 global $pagenow;
285
286 if ( $this->disable_custom_statuses_for_post_type() )
287 return;
288
289 // Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit)
290 if ( $this->is_whitelisted_settings_view( $this->module->name ) ) {
291 wp_enqueue_script( 'jquery-ui-sortable' );
292 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 );
293 }
294
295 // Custom javascript to modify the post status dropdown where it shows up
296 if ( $this->is_whitelisted_page() ) {
297 wp_enqueue_script( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.js', array( 'jquery','post' ), EDIT_FLOW_VERSION, true );
298 wp_enqueue_style( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.css', false, EDIT_FLOW_VERSION, 'all' );
299 }
300 }
301
302 /**
303 * Displays a notice to users if they have JS disabled
304 * Javascript is needed for custom statuses to be fully functional
305 */
306 function no_js_notice() {
307 if( $this->is_whitelisted_page() ) :
308 ?>
309 <style type="text/css">
310 /* Hide post status dropdown by default in case of JS issues **/
311 label[for=post_status],
312 #post-status-display,
313 #post-status-select,
314 #publish {
315 display: none;
316 }
317 </style>
318 <div class="update-nag hide-if-js">
319 <?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' ); ?>
320 </div>
321 <?php
322 endif;
323 }
324
325 /**
326 * Check whether custom status stuff should be loaded on this page
327 *
328 * @todo migrate this to the base module class
329 */
330 function is_whitelisted_page() {
331 global $pagenow;
332
333 if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
334 return false;
335
336 $post_type_obj = get_post_type_object( $this->get_current_post_type() );
337
338 if( ! current_user_can( $post_type_obj->cap->edit_posts ) )
339 return false;
340
341 // 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
342 return in_array( $pagenow, array( 'post.php', 'edit.php', 'post-new.php', 'page.php', 'edit-pages.php', 'page-new.php' ) );
343 }
344
345 /**
346 * Adds all necessary javascripts to make custom statuses work
347 *
348 * @todo Support private and future posts on edit.php view
349 */
350 function post_admin_header() {
351 global $post, $edit_flow, $pagenow, $current_user;
352
353 if ( $this->disable_custom_statuses_for_post_type() )
354 return;
355
356 // Get current user
357 get_currentuserinfo() ;
358
359 // 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
360 if ( !empty( $post ) && $this->is_whitelisted_page() ) {
361
362 $custom_statuses = $this->get_custom_statuses();
363
364 // Get the status of the current post
365 if ( $post->ID == 0 || $post->post_status == 'auto-draft' || $pagenow == 'edit.php' ) {
366 // TODO: check to make sure that the default exists
367 $selected = $this->get_default_custom_status()->slug;
368
369 } else {
370 $selected = $post->post_status;
371 }
372
373 // Get the current post status name
374 $selected_name = '';
375
376 foreach ($custom_statuses as $status) {
377 if ($status->slug == $selected) {
378 $selected_name = $status->name;
379 }
380 }
381
382 $custom_statuses = apply_filters( 'ef_custom_status_list', $custom_statuses, $post );
383
384 // All right, we want to set up the JS var which contains all custom statuses
385 $all_statuses = array();
386
387 // The default statuses from WordPress
388 $all_statuses[] = array(
389 'name' => __( 'Published', 'edit-flow' ),
390 'slug' => 'publish',
391 'description' => '',
392 );
393 $all_statuses[] = array(
394 'name' => __( 'Privately Published', 'edit-flow' ),
395 'slug' => 'private',
396 'description' => '',
397 );
398 $all_statuses[] = array(
399 'name' => __( 'Scheduled', 'edit-flow' ),
400 'slug' => 'future',
401 'description' => '',
402 );
403
404 // Load the custom statuses
405 foreach( $custom_statuses as $status ) {
406 $all_statuses[] = array(
407 'name' => esc_js( $status->name ),
408 'slug' => esc_js( $status->slug ),
409 'description' => esc_js( $status->description ),
410 );
411 }
412
413 $always_show_dropdown = ( $this->module->options->always_show_dropdown == 'on' ) ? 1 : 0;
414
415 $post_type_obj = get_post_type_object( $this->get_current_post_type() );
416
417 // Now, let's print the JS vars
418 ?>
419 <script type="text/javascript">
420 var custom_statuses = <?php echo json_encode( $all_statuses ); ?>;
421 var ef_text_no_change = '<?php echo esc_js( __( "&mdash; No Change &mdash;" ) ); ?>';
422 var ef_default_custom_status = '<?php echo esc_js( $this->get_default_custom_status()->slug ); ?>';
423 var current_status = '<?php echo esc_js( $selected ); ?>';
424 var current_status_name = '<?php echo esc_js( $selected_name ); ?>';
425 var status_dropdown_visible = <?php echo esc_js( $always_show_dropdown ); ?>;
426 var current_user_can_publish_posts = <?php echo current_user_can( $post_type_obj->cap->publish_posts ) ? 1 : 0; ?>;
427 var current_user_can_edit_published_posts = <?php echo current_user_can( $post_type_obj->cap->edit_published_posts ) ? 1 : 0; ?>;
428 </script>
429
430 <?php
431
432 }
433
434 }
435
436 /**
437 * Adds a new custom status as a term in the wp_terms table.
438 * Basically a wrapper for the wp_insert_term class.
439 *
440 * The arguments decide how the term is handled based on the $args parameter.
441 * The following is a list of the available overrides and the defaults.
442 *
443 * 'description'. There is no default. If exists, will be added to the database
444 * along with the term. Expected to be a string.
445 *
446 * 'slug'. Expected to be a string. There is no default.
447 *
448 * @param int|string $term The status to add or update
449 * @param array|string $args Change the values of the inserted term
450 * @return array|WP_Error $response The Term ID and Term Taxonomy ID
451 */
452 function add_custom_status( $term, $args = array() ) {
453 $slug = ( ! empty( $args['slug'] ) ) ? $args['slug'] : sanitize_title( $term );
454 unset( $args['slug'] );
455 $encoded_description = $this->get_encoded_description( $args );
456 $response = wp_insert_term( $term, self::taxonomy_key, array( 'slug' => $slug, 'description' => $encoded_description ) );
457 return $response;
458
459 }
460
461 /**
462 * Update an existing custom status
463 *
464 * @param int @status_id ID for the status
465 * @param array $args Any arguments to be updated
466 * @return object $updated_status Newly updated status object
467 */
468 function update_custom_status( $status_id, $args = array() ) {
469 global $edit_flow;
470
471 $old_status = $this->get_custom_status_by( 'id', $status_id );
472 if ( !$old_status || is_wp_error( $old_status ) )
473 return new WP_Error( 'invalid', __( "Custom status doesn't exist.", 'edit-flow' ) );
474
475 // If the name was changed, we need to change the slug
476 if ( isset( $args['name'] ) && $args['name'] != $old_status->name )
477 $args['slug'] = sanitize_title( $args['name'] );
478
479 // Reassign posts to new status slug if the slug changed and isn't restricted
480 if ( isset( $args['slug'] ) && $args['slug'] != $old_status->slug && !$this->is_restricted_status( $old_status->slug ) ) {
481 $new_status = $args['slug'];
482 $this->reassign_post_status( $old_status->slug, $new_status );
483
484 $default_status = $this->get_default_custom_status()->slug;
485 if ( $old_status->slug == $default_status )
486 $edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
487 }
488 // We're encoding metadata that isn't supported by default in the term's description field
489 $args_to_encode = array();
490 $args_to_encode['description'] = ( isset( $args['description'] ) ) ? $args['description'] : $old_status->description;
491 $args_to_encode['position'] = ( isset( $args['position'] ) ) ? $args['position'] : $old_status->position;
492 $encoded_description = $this->get_encoded_description( $args_to_encode );
493 $args['description'] = $encoded_description;
494
495 $updated_status_array = wp_update_term( $status_id, self::taxonomy_key, $args );
496 $updated_status = $this->get_custom_status_by( 'id', $updated_status_array['term_id'] );
497
498 return $updated_status;
499
500 }
501
502 /**
503 * Deletes a custom status from the wp_terms table.
504 *
505 * Partly a wrapper for the wp_delete_term function.
506 * BUT, also reassigns posts that currently have the deleted status assigned.
507 */
508 function delete_custom_status( $status_id, $args = array(), $reassign = '' ) {
509 global $edit_flow;
510 // Reassign posts to alternate status
511
512 // Get slug for the old status
513 $old_status = $this->get_custom_status_by( 'id', $status_id )->slug;
514
515 if ( $reassign == $old_status )
516 return new WP_Error( 'invalid', __( 'Cannot reassign to the status you want to delete', 'edit-flow' ) );
517
518 if( !$this->is_restricted_status( $old_status ) ) {
519 $default_status = $this->get_default_custom_status()->slug;
520 // If new status in $reassign, use that for all posts of the old_status
521 if( !empty( $reassign ) )
522 $new_status = $this->get_custom_status_by( 'id', $reassign )->slug;
523 else
524 $new_status = $default_status;
525 if ( $old_status == $default_status && $this->get_custom_status_by( 'slug', 'draft' ) ) { // Deleting default status
526 $new_status = 'draft';
527 $edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
528 }
529
530 $this->reassign_post_status( $old_status, $new_status );
531
532 return wp_delete_term( $status_id, self::taxonomy_key, $args );
533 } else
534 return new WP_Error( 'restricted', __( 'Restricted status ', 'edit-flow' ) . '(' . $this->get_custom_status_by( 'id', $status_id )->name . ')' );
535
536 }
537
538 /**
539 * Get all custom statuses as an ordered array
540 *
541 * @param array|string $statuses
542 * @param array $args
543 * @return array $statuses All of the statuses
544 */
545 function get_custom_statuses( $args = array() ) {
546 global $wp_post_statuses;
547
548 $disable_custom_statuses = $this->disable_custom_statuses_for_post_type();
549 if ( ! $disable_custom_statuses ) {
550
551 // Handle if the requested taxonomy doesn't exist
552 $args = array_merge( array( 'hide_empty' => false ), $args );
553 $statuses = get_terms( self::taxonomy_key, $args );
554
555 if ( is_wp_error( $statuses ) || empty( $statuses ) )
556 $disable_custom_statuses = true;
557 }
558
559 // If this post type doesn't support custom statuses, we should return WP default in our format.
560 if ( $disable_custom_statuses ) {
561 $draft = isset( $wp_post_statuses['draft'] ) ? $wp_post_statuses['draft'] : new stdClass();
562 $draft->slug = 'draft';
563 $draft->position = 1;
564 $draft->name = 'Draft';
565
566 $pending = isset( $wp_post_statuses['pending'] ) ? $wp_post_statuses['pending'] : new stdClass();
567 $pending->slug = 'pending';
568 $pending->position = 2;
569 $pending->name = 'Pending Review';
570
571 return array(
572 $draft,
573 $pending
574 );
575 }
576
577 // Expand and order the statuses
578 $ordered_statuses = array();
579 $hold_to_end = array();
580 foreach ( $statuses as $key => $status ) {
581 // Unencode and set all of our psuedo term meta because we need the position if it exists
582 $unencoded_description = $this->get_unencoded_description( $status->description );
583 if ( is_array( $unencoded_description ) ) {
584 foreach( $unencoded_description as $key => $value ) {
585 $status->$key = $value;
586 }
587 }
588 // We require the position key later on (e.g. management table)
589 if ( !isset( $status->position ) )
590 $status->position = false;
591 // Only add the status to the ordered array if it has a set position and doesn't conflict with another key
592 // Otherwise, hold it for later
593 if ( $status->position && !array_key_exists( $status->position, $ordered_statuses ) ) {
594 $ordered_statuses[(int)$status->position] = $status;
595 } else {
596 $hold_to_end[] = $status;
597 }
598 }
599 // Sort the items numerically by key
600 ksort( $ordered_statuses, SORT_NUMERIC );
601 // Append all of the statuses that didn't have an existing position
602 foreach( $hold_to_end as $unpositioned_status )
603 $ordered_statuses[] = $unpositioned_status;
604 return $ordered_statuses;
605 }
606
607 /**
608 * Returns the a single status object based on ID, title, or slug
609 *
610 * @param string|int $string_or_int The status to search for, either by slug or ID
611 * @return object|WP_Error $status The object for the matching status
612 */
613 function get_custom_status_by( $field, $value ) {
614
615 $status = get_term_by( $field, $value, self::taxonomy_key );
616 if ( !$status || is_wp_error( $status ) )
617 return $status;
618 // Unencode and set all of our psuedo term meta because we need the position if it exists
619 $status->position = false;
620 $unencoded_description = $this->get_unencoded_description( $status->description );
621 if ( is_array( $unencoded_description ) ) {
622 foreach( $unencoded_description as $key => $value ) {
623 $status->$key = $value;
624 }
625 }
626 return $status;
627
628 }
629
630 /**
631 * Get the term object for the default custom post status
632 *
633 * @return object $default_status Default post status object
634 */
635 function get_default_custom_status() {
636 $default_status = $this->get_custom_status_by( 'slug', $this->module->options->default_status );
637 if ( ! $default_status )
638 $default_status = array_shift( $this->get_custom_statuses() );
639 return $default_status;
640
641 }
642
643 /**
644 * Assign new statuses to posts using value provided or the default
645 *
646 * @param string $old_status Slug for the old status
647 * @param string $new_status Slug for the new status
648 */
649 function reassign_post_status( $old_status, $new_status = '' ) {
650 global $wpdb;
651
652 if ( empty( $new_status ) )
653 $new_status = $this->get_default_custom_status()->slug;
654
655 // Make the database call
656 $result = $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'post_status' => $old_status ), array( '%s' ));
657 }
658
659 /**
660 * Insert new column header for post status after the title column
661 *
662 * @param array $posts_columns Columns currently shown on the Edit Posts screen
663 * @return array Same array as the input array with a "status" column added after the "title" column
664 */
665 function _filter_manage_posts_columns( $posts_columns ) {
666 // Return immediately if the supplied parameter isn't an array (which shouldn't happen in practice?)
667 // http://wordpress.org/support/topic/plugin-edit-flow-bug-shows-2-drafts-when-there-are-none-leads-to-error-messages
668 if ( !is_array( $posts_columns ) )
669 return $posts_columns;
670
671 // Only do it for the post types this module is activated for
672 if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
673 return $posts_columns;
674
675 $result = array();
676 foreach ( $posts_columns as $key => $value ) {
677 if ($key == 'title') {
678 $result[$key] = $value;
679 $result['status'] = __('Status', 'edit-flow');
680 } else $result[$key] = $value;
681 }
682 return $result;
683
684 }
685
686 /**
687 * Adds a Post's status to its row on the Edit page
688 *
689 * @param string $column_name
690 **/
691 function _filter_manage_posts_custom_column( $column_name ) {
692
693 if ( $column_name == 'status' ) {
694 global $post;
695 echo $this->get_post_status_friendly_name( $post->post_status );
696 }
697
698 }
699
700
701 /**
702 * Determines whether the slug indicated belongs to a restricted status or not
703 *
704 * @param string $slug Slug of the status
705 * @return bool $restricted True if restricted, false if not
706 */
707 function is_restricted_status( $slug ) {
708
709 switch( $slug ) {
710 case 'publish':
711 case 'private':
712 case 'future':
713 case 'new':
714 case 'inherit':
715 case 'auto-draft':
716 case 'trash':
717 $restricted = true;
718 break;
719
720 default:
721 $restricted = false;
722 break;
723 }
724 return $restricted;
725
726 }
727
728 /**
729 * Handles a form's POST request to add a custom status
730 *
731 * @since 0.7
732 */
733 function handle_add_custom_status() {
734
735 // Check that the current POST request is our POST request
736 if ( !isset( $_POST['submit'], $_GET['page'], $_POST['action'] )
737 || $_GET['page'] != $this->module->settings_slug || $_POST['action'] != 'add-new' )
738 return;
739
740 if ( !wp_verify_nonce( $_POST['_wpnonce'], 'custom-status-add-nonce' ) )
741 wp_die( $this->module->messages['nonce-failed'] );
742
743 // Validate and sanitize the form data
744 $status_name = sanitize_text_field( trim( $_POST['status_name'] ) );
745 $status_slug = sanitize_title( $status_name );
746 $status_description = stripslashes( wp_filter_nohtml_kses( trim( $_POST['status_description'] ) ) );
747
748 /**
749 * Form validation
750 * - Name is required and can't conflict with an existing name or slug
751 * - Description is optional
752 */
753 $_REQUEST['form-errors'] = array();
754 // Check if name field was filled in
755 if( empty( $status_name ) )
756 $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
757 // Check that the name isn't numeric
758 if ( (int)$status_name != 0 )
759 $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
760 // Check that the status name doesn't exceed 20 chars
761 if ( strlen( $status_name ) > 20 )
762 $_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
763 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
764 if ( term_exists( $status_slug ) )
765 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
766 // Check to make sure the name is not restricted
767 if ( $this->is_restricted_status( strtolower( $status_slug ) ) )
768 $_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
769
770 // If there were any form errors, kick out and return them
771 if ( count( $_REQUEST['form-errors'] ) ) {
772 $_REQUEST['error'] = 'form-error';
773 return;
774 }
775
776 // Try to add the status
777 $status_args = array(
778 'description' => $status_description,
779 'slug' => $status_slug,
780 );
781 $return = $this->add_custom_status( $status_name, $status_args );
782 if ( is_wp_error( $return ) )
783 wp_die( __( 'Could not add status: ', 'edit-flow' ) . $return->get_error_message() );
784 // Redirect if successful
785 $redirect_url = $this->get_link( array( 'message' => 'status-added' ) );
786 wp_redirect( $redirect_url );
787 exit;
788
789 }
790
791 /**
792 * Handles a POST request to edit an custom status
793 *
794 * @since 0.7
795 */
796 function handle_edit_custom_status() {
797 if ( !isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] )
798 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'edit-status' )
799 return;
800
801 if ( !wp_verify_nonce( $_POST['_wpnonce'], 'edit-status' ) )
802 wp_die( $this->module->messages['nonce-failed'] );
803
804 if ( !current_user_can( 'manage_options' ) )
805 wp_die( $this->module->messages['invalid-permissions'] );
806
807 if ( !$existing_status = $this->get_custom_status_by( 'id', (int)$_GET['term-id'] ) )
808 wp_die( $this->module->messsage['term-error'] );
809
810 $name = sanitize_text_field( trim( $_POST['name'] ) );
811 $description = stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) );
812
813 /**
814 * Form validation for editing custom status
815 *
816 * Details
817 * - 'name' is a required field and can't conflict with existing name or slug
818 * - 'description' is optional
819 */
820 $_REQUEST['form-errors'] = array();
821 // Check if name field was filled in
822 if( empty( $name ) )
823 $_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
824 // Check that the name isn't numeric
825 if ( is_numeric( $name ) )
826 $_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
827 // Check that the status name doesn't exceed 20 chars
828 if ( strlen( $name ) > 20 )
829 $_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
830 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
831 $term_exists = term_exists( sanitize_title( $name ) );
832 if ( $term_exists && $term_exists != $existing_status->term_id )
833 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
834 // Check to make sure the status doesn't already exist
835 $search_status = $this->get_custom_status_by( 'slug', sanitize_title( $name ) );
836 if ( $search_status && $search_status->term_id != $existing_status->term_id )
837 $_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing status. Please choose another.', 'edit-flow' );
838 // Check to make sure the name is not restricted
839 if ( $this->is_restricted_status( strtolower( sanitize_title( $name ) ) ) )
840 $_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
841
842 // Kick out if there are any errors
843 if ( count( $_REQUEST['form-errors'] ) ) {
844 $_REQUEST['error'] = 'form-error';
845 return;
846 }
847
848 // Try to add the new post status
849 $args = array(
850 'name' => $name,
851 'slug' => sanitize_title( $name ),
852 'description' => $description,
853 );
854 $return = $this->update_custom_status( $existing_status->term_id, $args );
855 if ( is_wp_error( $return ) )
856 wp_die( __( 'Error updating post status.', 'edit-flow' ) );
857
858 $redirect_url = $this->get_link( array( 'message' => 'status-updated' ) );
859 wp_redirect( $redirect_url );
860 exit;
861 }
862
863 /**
864 * Handles a GET request to make the identified status default
865 *
866 * @since 0.7
867 */
868 function handle_make_default_custom_status() {
869 global $edit_flow;
870
871 // Check that the current GET request is our GET request
872 if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
873 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'make-default' )
874 return;
875
876 // Check for proper nonce
877 if ( !wp_verify_nonce( $_GET['nonce'], 'make-default' ) )
878 wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
879
880 // Only allow users with the proper caps
881 if ( !current_user_can( 'manage_options' ) )
882 wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
883
884 $term_id = (int)$_GET['term-id'];
885 $term = $this->get_custom_status_by( 'id', $term_id );
886 if ( is_object( $term ) ) {
887 $edit_flow->update_module_option( $this->module->name, 'default_status', $term->slug );
888 // @todo How do we want to handle users who click the link from "Add New Status"
889 $redirect_url = $this->get_link( array( 'message' => 'default-status-changed' ) );
890 wp_redirect( $redirect_url );
891 exit;
892 } else {
893 wp_die( __( 'Status doesn&#39;t exist.', 'edit-flow' ) );
894 }
895
896 }
897
898 /**
899 * Handles a GET request to delete a specific term
900 *
901 * @since 0.7
902 */
903 function handle_delete_custom_status() {
904
905 // Check that this GET request is our GET request
906 if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
907 || $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'delete-status' )
908 return;
909
910 // Check for proper nonce
911 if ( !wp_verify_nonce( $_GET['nonce'], 'delete-status' ) )
912 wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
913
914 // Only allow users with the proper caps
915 if ( !current_user_can( 'manage_options' ) )
916 wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
917
918 // Check to make sure the status isn't already deleted
919 $term_id = (int)$_GET['term-id'];
920 $term = $this->get_custom_status_by( 'id', $term_id );
921 if( !$term )
922 wp_die( __( 'Status does not exist.', 'edit-flow' ) );
923
924 // Don't allow deletion of default status
925 if ( $term->slug == $this->get_default_custom_status()->slug )
926 wp_die( __( 'Cannot delete default status.', 'edit-flow' ) );
927
928 $return = $this->delete_custom_status( $term_id );
929 if ( is_wp_error( $return ) )
930 wp_die( __( 'Could not delete the status: ', 'edit-flow' ) . $return->get_error_message() );
931
932 $redirect_url = $this->get_link( array( 'message' => 'status-deleted' ) );
933 wp_redirect( $redirect_url );
934 exit;
935
936 }
937
938 /**
939 * Generate a link to one of the custom status actions
940 *
941 * @since 0.7
942 *
943 * @param array $args (optional) Action and any query args to add to the URL
944 * @return string $link Direct link to complete the action
945 */
946 function get_link( $args = array() ) {
947 if ( !isset( $args['action'] ) )
948 $args['action'] = '';
949 if ( !isset( $args['page'] ) )
950 $args['page'] = $this->module->settings_slug;
951 // Add other things we may need depending on the action
952 switch( $args['action'] ) {
953 case 'make-default':
954 case 'delete-status':
955 $args['nonce'] = wp_create_nonce( $args['action'] );
956 break;
957 default:
958 break;
959 }
960 return add_query_arg( $args, get_admin_url( null, 'admin.php' ) );
961 }
962
963 /**
964 * Handle an ajax request to update the order of custom statuses
965 *
966 * @since 0.7
967 */
968 function handle_ajax_update_status_positions() {
969
970 if ( !wp_verify_nonce( $_POST['custom_status_sortable_nonce'], 'custom-status-sortable' ) )
971 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
972
973 if ( !current_user_can( 'manage_options') )
974 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
975
976 if ( !isset( $_POST['status_positions'] ) || !is_array( $_POST['status_positions'] ) )
977 $this->print_ajax_response( 'error', __( 'Terms not set.', 'edit-flow' ) );
978
979 // Update each custom status with its new position
980 foreach ( $_POST['status_positions'] as $position => $term_id ) {
981
982 // Have to add 1 to the position because the index started with zero
983 $args = array(
984 'position' => (int)$position + 1,
985 );
986 $return = $this->update_custom_status( (int)$term_id, $args );
987 // @todo check that this was a valid return
988 }
989 $this->print_ajax_response( 'success', $this->module->messages['status-position-updated'] );
990 }
991
992 /**
993 * Handle an Inline Edit POST request to update status values
994 *
995 * @since 0.7
996 */
997 function ajax_inline_save_status() {
998 global $edit_flow;
999
1000 if ( !wp_verify_nonce( $_POST['inline_edit'], 'custom-status-inline-edit-nonce' ) )
1001 die( $this->module->messages['nonce-failed'] );
1002
1003 if ( !current_user_can( 'manage_options') )
1004 die( $this->module->messages['invalid-permissions'] );
1005
1006 $term_id = (int) $_POST['status_id'];
1007 $status_name = sanitize_text_field( trim( $_POST['name'] ) );
1008 $status_slug = sanitize_title( $_POST['name'] );
1009 $status_description = stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) );
1010
1011 // Check if name field was filled in
1012 if ( empty( $status_name ) ) {
1013 $change_error = new WP_Error( 'invalid', __( 'Please enter a name for the status.', 'edit-flow' ) );
1014 die( $change_error->get_error_message() );
1015 }
1016
1017 // Check that the name isn't numeric
1018 if( is_numeric( $status_name) ) {
1019 $change_error = new WP_Error( 'invalid', __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' ) );
1020 die( $change_error->get_error_message() );
1021 }
1022
1023 // Check that the status name doesn't exceed 20 chars
1024 if ( strlen( $status_name ) > 20 ) {
1025 $change_error = new WP_Error( 'invalid', __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' ) );
1026 die( $change_error->get_error_message() );
1027 }
1028
1029 // Check to make sure the name is not restricted
1030 if ( $edit_flow->custom_status->is_restricted_status( strtolower( $status_name ) ) ) {
1031 $change_error = new WP_Error( 'invalid', __( 'Status name is restricted. Please chose another name.', 'edit-flow' ) );
1032 die( $change_error->get_error_message() );
1033 }
1034
1035 // Check to make sure the status doesn't already exist
1036 if ( $this->get_custom_status_by( 'slug', $status_slug ) && ( $this->get_custom_status_by( 'id', $term_id )->slug != $status_slug ) ) {
1037 $change_error = new WP_Error( 'invalid', __( 'Status already exists. Please choose another name.', 'edit-flow' ) );
1038 die( $change_error->get_error_message() );
1039 }
1040
1041 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a fatal error
1042 $term_exists = term_exists( sanitize_title( $status_name ) );
1043 if ( $term_exists && $term_exists != $term_id ) {
1044 $change_error = new WP_Error( 'invalid', __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' ) );
1045 die( $change_error->get_error_message() );
1046 }
1047
1048 // get status_name & status_description
1049 $args = array(
1050 'name' => $status_name,
1051 'description' => $status_description,
1052 'slug' => $status_slug,
1053 );
1054 $return = $this->update_custom_status( $term_id, $args );
1055 if( !is_wp_error( $return ) ) {
1056 set_current_screen( 'edit-custom-status' );
1057 $wp_list_table = new EF_Custom_Status_List_Table();
1058 $wp_list_table->prepare_items();
1059 echo $wp_list_table->single_row( $return );
1060 die();
1061 } else {
1062 $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the status: <strong>%s</strong>', 'edit-flow' ), $status_name ) );
1063 die( $change_error->get_error_message() );
1064 }
1065
1066 }
1067
1068 /**
1069 * Register settings for notifications so we can partially use the Settings API
1070 * (We use the Settings API for form generation, but not saving)
1071 *
1072 * @since 0.7
1073 */
1074 function register_settings() {
1075
1076 add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name );
1077 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' );
1078 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' );
1079
1080 }
1081
1082 /**
1083 * Choose the post types that should be displayed on the calendar
1084 *
1085 * @since 0.7
1086 */
1087 function settings_post_types_option() {
1088 global $edit_flow;
1089 $edit_flow->settings->helper_option_custom_post_type( $this->module );
1090 }
1091
1092 /**
1093 * Option for whether the blog admin email address should be always notified or not
1094 *
1095 * @since 0.7
1096 */
1097 function settings_always_show_dropdown_option() {
1098 $options = array(
1099 'off' => __( 'Disabled', 'edit-flow' ),
1100 'on' => __( 'Enabled', 'edit-flow' ),
1101 );
1102 echo '<select id="always_show_dropdown" name="' . $this->module->options_group_name . '[always_show_dropdown]">';
1103 foreach ( $options as $value => $label ) {
1104 echo '<option value="' . esc_attr( $value ) . '"';
1105 echo selected( $this->module->options->always_show_dropdown, $value );
1106 echo '>' . esc_html( $label ) . '</option>';
1107 }
1108 echo '</select>';
1109 }
1110
1111 /**
1112 * Validate input from the end user
1113 *
1114 * @since 0.7
1115 */
1116 function settings_validate( $new_options ) {
1117
1118 // Whitelist validation for the post type options
1119 if ( !isset( $new_options['post_types'] ) )
1120 $new_options['post_types'] = array();
1121 $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support );
1122
1123 // Whitelist validation for the 'always_show_dropdown' optoins
1124 if ( !isset( $new_options['always_show_dropdown'] ) || $new_options['always_show_dropdown'] != 'on' )
1125 $new_options['always_show_dropdown'] = 'off';
1126
1127 return $new_options;
1128 }
1129
1130 /**
1131 * Primary configuration page for custom status class.
1132 * Shows form to add new custom statuses on the left and a
1133 * WP_List_Table with the custom status terms on the right
1134 */
1135 function print_configure_view() {
1136 global $edit_flow;
1137
1138 /** Full width view for editing a custom status **/
1139 if ( isset( $_GET['action'], $_GET['term-id'] ) && $_GET['action'] == 'edit-status' ): ?>
1140 <?php
1141 // Check whether the term exists
1142 $term_id = (int)$_GET['term-id'];
1143 $status = $this->get_custom_status_by( 'id', $term_id );
1144 if ( !$status ) {
1145 echo '<div class="error"><p>' . $this->module->messages['status-missing'] . '</p></div>';
1146 return;
1147 }
1148 $edit_status_link = $this->get_link( array( 'action' => 'edit-status', 'term-id' => $term_id ) );
1149
1150 $name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $status->name;
1151 $description = ( isset( $_POST['description'] ) ) ? strip_tags( stripslashes( $_POST['description'] ) ) : $status->description;
1152 ?>
1153
1154 <div id="ajax-response"></div>
1155 <form method="post" action="<?php echo esc_attr( $edit_status_link ); ?>" >
1156 <input type="hidden" name="term-id" value="<?php echo esc_attr( $term_id ); ?>" />
1157 <?php
1158 wp_original_referer_field();
1159 wp_nonce_field( 'edit-status' );
1160 ?>
1161 <table class="form-table">
1162 <tr class="form-field form-required">
1163 <th scope="row" valign="top"><label for="name"><?php _e( 'Custom Status', 'edit-flow' ); ?></label></th>
1164 <td><input name="name" id="name" type="text" value="<?php echo esc_attr( $name ); ?>" size="40" aria-required="true" />
1165 <?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>
1166 </td>
1167 </tr>
1168 <tr class="form-field">
1169 <th scope="row" valign="top"><?php _e( 'Slug', 'edit-flow' ); ?></th>
1170 <td>
1171 <input type="text" disabled="disabled" value="<?php echo esc_attr( $status->slug ); ?>" />
1172 <?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' ) ); ?>
1173 </td>
1174 </tr>
1175 <tr class="form-field">
1176 <th scope="row" valign="top"><label for="description"><?php _e( 'Description', 'edit-flow' ); ?></label></th>
1177 <td>
1178 <textarea name="description" id="description" rows="5" cols="50" style="width: 97%;"><?php echo esc_textarea( $description ); ?></textarea>
1179 <?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' ) ); ?>
1180 </td>
1181 </tr>
1182 </table>
1183 <p class="submit">
1184 <?php submit_button( __( 'Update Status', 'edit-flow' ), 'primary', 'submit', false ); ?>
1185 <a class="cancel-settings-link" href="<?php echo esc_url( $this->get_link() ); ?>"><?php _e( 'Cancel', 'edit-flow' ); ?></a>
1186 </p>
1187 </form>
1188
1189 <?php else: ?>
1190 <?php
1191 $wp_list_table = new EF_Custom_Status_List_Table();
1192 $wp_list_table->prepare_items();
1193 ?>
1194 <script type="text/javascript">
1195 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' ) ); ?>";
1196 </script>
1197 <div id="col-right">
1198 <div class="col-wrap">
1199 <?php $wp_list_table->display(); ?>
1200 <?php wp_nonce_field( 'custom-status-sortable', 'custom-status-sortable' ); ?>
1201 <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>
1202 </div>
1203 </div>
1204 <div id="col-left">
1205 <div class="col-wrap">
1206 <div class="form-wrap">
1207 <h3 class="nav-tab-wrapper">
1208 <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>
1209 <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>
1210 </h3>
1211 <?php if ( isset( $_GET['action'] ) && $_GET['action'] == 'change-options' ): ?>
1212 <form class="basic-settings" action="<?php echo esc_url( $this->get_link( array( 'action' => 'change-options' ) ) ); ?>" method="post">
1213 <?php settings_fields( $this->module->options_group_name ); ?>
1214 <?php do_settings_sections( $this->module->options_group_name ); ?>
1215 <?php echo '<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="' . esc_attr( $this->module->name ) . '" />'; ?>
1216 <?php submit_button(); ?>
1217 </form>
1218 <?php else: ?>
1219 <?php /** Custom form for adding a new Custom Status term **/ ?>
1220 <form class="add:the-list:" action="<?php echo esc_url( $this->get_link() ); ?>" method="post" id="addstatus" name="addstatus">
1221 <div class="form-field form-required">
1222 <label for="status_name"><?php _e( 'Name', 'edit-flow' ); ?></label>
1223 <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'] ) ?>" />
1224 <?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is used to identify the status. (Max: 20 characters)', 'edit-flow' ) ); ?>
1225 </div>
1226 <div class="form-field">
1227 <label for="status_description"><?php _e( 'Description', 'edit-flow' ); ?></label>
1228 <textarea cols="40" rows="5" id="status_description" name="status_description"><?php if ( !empty( $_POST['status_description'] ) ) echo esc_textarea( $_POST['status_description'] ) ?></textarea>
1229 <?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' ) ); ?>
1230 </div>
1231 <?php wp_nonce_field( 'custom-status-add-nonce' ); ?>
1232 <?php echo '<input id="action" name="action" type="hidden" value="add-new" />'; ?>
1233 <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>
1234 </form>
1235 <?php endif; ?>
1236 </div>
1237 </div>
1238 </div>
1239 <?php $wp_list_table->inline_edit(); ?>
1240 <?php endif; ?>
1241 <?php
1242 }
1243
1244 /**
1245 * This is a hack! hack! hack! until core is fixed/better supports custom statuses
1246 *
1247 * When publishing a post with a custom status, set the status to 'pending' temporarily
1248 * @see Works around this limitation: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-includes/post.php#L2694
1249 * @see Original thread: http://wordpress.org/support/topic/plugin-edit-flow-custom-statuses-create-timestamp-problem
1250 * @see Core ticket: http://core.trac.wordpress.org/ticket/18362
1251 */
1252 function check_timestamp_on_publish() {
1253 global $edit_flow, $pagenow, $wpdb;
1254
1255 if ( $this->disable_custom_statuses_for_post_type() )
1256 return;
1257
1258 // Handles the transition to 'publish' on edit.php
1259 if ( isset( $edit_flow ) && $pagenow == 'edit.php' && isset( $_REQUEST['bulk_edit'] ) ) {
1260 // For every post_id, set the post_status as 'pending' only when there's no timestamp set for $post_date_gmt
1261 if ( $_REQUEST['_status'] == 'publish' ) {
1262 $post_ids = array_map( 'intval', (array) $_REQUEST['post'] );
1263 foreach ( $post_ids as $post_id ) {
1264 $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $post_id, 'post_date_gmt' => '0000-00-00 00:00:00' ) );
1265 clean_post_cache( $post_id );
1266 }
1267 }
1268 }
1269
1270 // Handles the transition to 'publish' on post.php
1271 if ( isset( $edit_flow ) && $pagenow == 'post.php' && isset( $_POST['publish'] ) ) {
1272 // Set the post_status as 'pending' only when there's no timestamp set for $post_date_gmt
1273 if ( isset( $_POST['post_ID'] ) ) {
1274 $post_id = (int) $_POST['post_ID'];
1275 $ret = $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $post_id, 'post_date_gmt' => '0000-00-00 00:00:00' ) );
1276 clean_post_cache( $post_id );
1277 foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
1278 if ( !empty( $_POST['hidden_' . $timeunit] ) && $_POST['hidden_' . $timeunit] != $_POST[$timeunit] ) {
1279 $edit_date = '1';
1280 break;
1281 }
1282 }
1283 if ( $ret && empty( $edit_date ) ) {
1284 add_filter( 'pre_post_date', array( $this, 'helper_timestamp_hack' ) );
1285 add_filter( 'pre_post_date_gmt', array( $this, 'helper_timestamp_hack' ) );
1286 }
1287 }
1288 }
1289
1290 }
1291
1292 /**
1293 * Remove the editable_slug filter after it has run for the sample permalink box (slug editor)
1294 *
1295 * @since 0.7.7
1296 */
1297 function action_edit_form_after_title() {
1298 remove_filter( 'editable_slug', array( $this, 'fix_editable_slug' ) );
1299 }
1300
1301 /**
1302 * PHP < 5.3.x doesn't support anonymous functions
1303 * This helper is only used for the check_timestamp_on_publish method above
1304 *
1305 * @since 0.7.3
1306 */
1307 function helper_timestamp_hack() {
1308 return ( 'pre_post_date' == current_filter() ) ? current_time('mysql') : '';
1309 }
1310
1311 /**
1312 * This is a hack! hack! hack! until core is fixed/better supports custom statuses
1313 *
1314 * @since 0.6.5
1315 *
1316 * Normalize post_date_gmt if it isn't set to the past or the future
1317 * @see Works around this limitation: http://core.trac.wordpress.org/browser/tags/3.2.1/wp-includes/post.php#L2506
1318 * @see Original thread: http://wordpress.org/support/topic/plugin-edit-flow-custom-statuses-create-timestamp-problem
1319 * @see Core ticket: http://core.trac.wordpress.org/ticket/18362
1320 */
1321 function fix_custom_status_timestamp( $data, $postarr ) {
1322 global $edit_flow;
1323 // Don't run this if Edit Flow isn't active, or we're on some other page
1324 if ( $this->disable_custom_statuses_for_post_type()
1325 || ! isset( $edit_flow )
1326 || ! isset( $_POST ) )
1327 return $data;
1328 $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' );
1329 $ef_normalize_post_date_gmt = true;
1330
1331 //If the time isn't set, don't set it.
1332 if( empty( $_POST['aa'] ) ) {
1333 $data['post_date_gmt'] = '0000-00-00 00:00:00';
1334 return $data;
1335 }
1336
1337 //If it's been set, stay set.
1338 if( !empty( $postarr['post_date_gmt'] ) && $postarr['post_date_gmt'] != '0000-00-00 00:00:00' ) {
1339 return $data;
1340 }
1341
1342 foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
1343 if( $_POST['hidden_' . $timeunit] != $_POST[$timeunit] )
1344 $ef_normalize_post_date_gmt = false;
1345 }
1346
1347 if ( $ef_normalize_post_date_gmt )
1348 if ( in_array( $data['post_status'], $status_slugs ) ) {
1349 $data['post_date_gmt'] = '0000-00-00 00:00:00';
1350 }
1351
1352 return $data;
1353 }
1354
1355 /**
1356 * Another hack! hack! hack! until core better supports custom statuses
1357 *
1358 * @since 0.7.4
1359 *
1360 * Keep the post_name value empty for posts with custom statuses
1361 * Unless they've set it customly
1362 * @see https://github.com/danielbachhuber/Edit-Flow/issues/123
1363 * @see http://core.trac.wordpress.org/browser/tags/3.4.2/wp-includes/post.php#L2530
1364 * @see http://core.trac.wordpress.org/browser/tags/3.4.2/wp-includes/post.php#L2646
1365 */
1366 public function fix_post_name( $post_id, $post ) {
1367 global $pagenow;
1368
1369 // Only modify if we're using a pre-publish status on a supported custom post type
1370 $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' );
1371 if ( 'post.php' != $pagenow
1372 || ! in_array( $post->post_status, $status_slugs )
1373 || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) )
1374 return;
1375
1376 // The slug has been set by the meta box
1377 if ( ! empty( $_POST['post_name'] ) )
1378 return;
1379
1380 global $wpdb;
1381
1382 $wpdb->update( $wpdb->posts, array( 'post_name' => '' ), array( 'ID' => $post_id ) );
1383 clean_post_cache( $post_id );
1384 }
1385 /**
1386 * Another hack! hack! hack! until core better supports custom statuses
1387 *
1388 * @since 0.7.4
1389 *
1390 * If the post_name for the object is empty, set it to sanitize_title() of post_title
1391 * However, we only want to do this the first time it's called on the page (slug editor)
1392 * and not in the post metabox, because the latter will set the value
1393 */
1394 public function fix_editable_slug( $slug ) {
1395 global $pagenow;
1396
1397 // If 'all_admin_notices' hasn't happened yet, we're not even close to the UI yet.
1398 if ( ! did_action( 'all_admin_notices' ) )
1399 return $slug;
1400
1401 if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
1402 return $slug;
1403
1404 // Only do this on post.php
1405 if ( 'post.php' != $pagenow )
1406 return $slug;
1407
1408 $post = get_post( get_the_ID() );
1409
1410 // Only modify if we're using a pre-publish status on a supported custom post type
1411 $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' );
1412 if ( ! $post
1413 || ! in_array( $post->post_status, $status_slugs )
1414 || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) )
1415 || ! empty( $post->post_name ) )
1416 return $slug;
1417
1418 // For hierarchical post types, we only want to modify the last time 'editable_slug' filter runs.
1419 $ptype = get_post_type_object( $post->post_type );
1420 if ( $ptype->hierarchical ) {
1421 static $i;
1422 $i++;
1423 if ( $i > 1 )
1424 return sanitize_title( $post->post_title );
1425 else
1426 return $slug;
1427 }
1428
1429 $slug = sanitize_title( $post->post_title );
1430
1431 return $slug;
1432
1433 }
1434
1435 /**
1436 * Another hack! hack! hack! until core better supports custom statuses
1437 *
1438 * @since 0.7.4
1439 *
1440 * The preview link for an unpublished post should always be ?p=
1441 */
1442 public function fix_preview_link_part_one( $preview_link ) {
1443 global $pagenow;
1444
1445 $post = get_post( get_the_ID() );
1446
1447 // Only modify if we're using a pre-publish status on a supported custom post type
1448 $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' );
1449 if ( ! $post
1450 || ! is_admin()
1451 || 'post.php' != $pagenow
1452 || ! in_array( $post->post_status, $status_slugs )
1453 || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) )
1454 || strpos( $preview_link, 'preview_id' ) !== false )
1455 return $preview_link;
1456
1457 return $this->get_preview_link( $post );
1458 }
1459
1460 /**
1461 * Another hack! hack! hack! until core better supports custom statuses
1462 *
1463 * @since 0.7.4
1464 *
1465 * The preview link for an unpublished post should always be ?p=
1466 * The code used to trigger a post preview doesn't also apply the 'preview_post_link' filter
1467 * So we can't do a targeted filter. Instead, we can even more hackily filter get_permalink
1468 * @see http://core.trac.wordpress.org/ticket/19378
1469 */
1470 public function fix_preview_link_part_two( $permalink, $post ) {
1471 global $pagenow;
1472
1473 if ( is_int( $post ) )
1474 $post = get_post( $post );
1475
1476 //Should we be doing anything at all?
1477 if( !in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) )
1478 return $permalink;
1479
1480 //Is this published?
1481 if( in_array( $post->post_status, $this->published_statuses ) )
1482 return $permalink;
1483
1484 //Are we overriding the permalink? Don't do anything
1485 if( isset( $_POST['action'] ) && $_POST['action'] == 'sample-permalink' )
1486 return $permalink;
1487
1488 //Are we previewing the post from the normal post screen?
1489 if( ( $pagenow == 'post.php' || $pagenow == 'post-new.php' )
1490 && !isset( $_POST['wp-preview'] ) )
1491 return $permalink;
1492
1493 return $this->get_preview_link( $post );
1494 }
1495
1496 /**
1497 * Get the proper preview link for a post
1498 *
1499 * @since 0.8
1500 */
1501 private function get_preview_link( $post ) {
1502
1503 if ( 'page' == $post->post_type ) {
1504 $args = array(
1505 'page_id' => $post->ID,
1506 );
1507 } else if ( 'post' == $post->post_type ) {
1508 $args = array(
1509 'p' => $post->ID,
1510 );
1511 } else {
1512 $args = array(
1513 'p' => $post->ID,
1514 'post_type' => $post->post_type,
1515 );
1516 }
1517
1518 $args['preview_id'] = $post->ID;
1519 return add_query_arg( $args, home_url() );
1520 }
1521
1522 /**
1523 * Another hack! hack! hack! until core better supports custom statuses
1524 *
1525 * @since 0.7.4
1526 *
1527 * The preview link for an unpublished post should always be ?p=, even in the list table
1528 * @see http://core.trac.wordpress.org/ticket/19378
1529 */
1530 public function fix_post_row_actions( $actions, $post ) {
1531 global $pagenow;
1532
1533 // Only modify if we're using a pre-publish status on a supported custom post type
1534 $status_slugs = wp_list_pluck( $this->get_custom_statuses(), 'slug' );
1535 if ( 'edit.php' != $pagenow
1536 || ! in_array( $post->post_status, $status_slugs )
1537 || ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ) ) )
1538 return $actions;
1539
1540 // 'view' is only set if the user has permission to post
1541 if ( empty( $actions['view'] ) )
1542 return $actions;
1543
1544 if ( 'page' == $post->post_type ) {
1545 $args = array(
1546 'page_id' => $post->ID,
1547 );
1548 } else if ( 'post' == $post->post_type ) {
1549 $args = array(
1550 'p' => $post->ID,
1551 );
1552 } else {
1553 $args = array(
1554 'p' => $post->ID,
1555 'post_type' => $post->post_type,
1556 );
1557 }
1558 $args['preview'] = 'true';
1559 $preview_link = add_query_arg( $args, home_url() );
1560
1561 $actions['view'] = '<a href="' . esc_url( $preview_link ) . '" title="' . esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;' ), $post->post_title ) ) . '" rel="permalink">' . __( 'Preview' ) . '</a>';
1562 return $actions;
1563 }
1564 }
1565
1566 }
1567
1568 /**
1569 * Custom Statuses uses WordPress' List Table API for generating the custom status management table
1570 *
1571 * @since 0.7
1572 */
1573 class EF_Custom_Status_List_Table extends WP_List_Table
1574 {
1575
1576 var $callback_args;
1577 var $default_status;
1578
1579 /**
1580 * Construct the extended class
1581 */
1582 function __construct() {
1583
1584 parent::__construct( array(
1585 'plural' => 'custom statuses',
1586 'singular' => 'custom status',
1587 'ajax' => true
1588 ) );
1589
1590 }
1591
1592 /**
1593 * Pull in the data we'll be displaying on the table
1594 *
1595 * @since 0.7
1596 */
1597 function prepare_items() {
1598 global $edit_flow;
1599
1600 $columns = $this->get_columns();
1601 $hidden = array(
1602 'position',
1603 );
1604 $sortable = array();
1605 $this->_column_headers = array($columns, $hidden, $sortable);
1606
1607 $this->items = $edit_flow->custom_status->get_custom_statuses();
1608 $total_items = count( $this->items );
1609 $this->default_status = $edit_flow->custom_status->get_default_custom_status()->slug;
1610
1611 $this->set_pagination_args( array(
1612 'total_items' => $total_items,
1613 'per_page' => $total_items,
1614 ) );
1615 }
1616
1617 /**
1618 * Message to be displayed when there are no custom statuses. Should never be displayed, but we'll customize it
1619 * just in case.
1620 *
1621 * @since 0.7
1622 */
1623 function no_items() {
1624 _e( 'No custom statuses found.', 'edit-flow' );
1625 }
1626
1627 /**
1628 * Table shows (hidden) position, status name, status description, and the post count for each activated
1629 * post type
1630 *
1631 * @since 0.7
1632 *
1633 * @return array $columns Columns to be registered with the List Table
1634 */
1635 function get_columns() {
1636 global $edit_flow;
1637
1638 $columns = array(
1639 'position' => __( 'Position', 'edit-flow' ),
1640 'name' => __( 'Name', 'edit-flow' ),
1641 'description' => __( 'Description', 'edit-flow' ),
1642 );
1643
1644 $post_types = get_post_types( '', 'objects' );
1645 $supported_post_types = $edit_flow->helpers->get_post_types_for_module( $edit_flow->custom_status->module );
1646 foreach ( $post_types as $post_type )
1647 if ( in_array( $post_type->name, $supported_post_types ) )
1648 $columns[$post_type->name] = $post_type->label;
1649
1650 return $columns;
1651 }
1652
1653 /**
1654 * Fallback column callback.
1655 * Primarily used to display post count for each post type
1656 *
1657 * @since 0.7
1658 *
1659 * @param object $item Custom status as an object
1660 * @param string $column_name Name of the column as registered in $this->prepare_items()
1661 * @return string $output What will be rendered
1662 */
1663 function column_default( $item, $column_name ) {
1664 global $edit_flow;
1665
1666 // Handle custom post counts for different post types
1667 $post_types = get_post_types( '', 'names' );
1668 if ( in_array( $column_name, $post_types ) ) {
1669
1670 // @todo Cachify this
1671 $post_count = wp_cache_get( "ef_custom_status_count_$column_name" );
1672 if ( false === $post_count ) {
1673 $posts = wp_count_posts( $column_name );
1674 $post_status = $item->slug;
1675 // To avoid error notices when changing the name of non-standard statuses
1676 if ( isset( $posts->$post_status ) )
1677 $post_count = $posts->$post_status;
1678 else
1679 $post_count = 0;
1680 //wp_cache_set( "ef_custom_status_count_$column_name", $post_count );
1681 }
1682 $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 );
1683 return $output;
1684 }
1685
1686 }
1687
1688 /**
1689 * Hidden column for storing the status position
1690 *
1691 * @since 0.7
1692 *
1693 * @param object $item Custom status as an object
1694 * @return string $output What will be rendered
1695 */
1696 function column_position( $item ) {
1697 return esc_html( $item->position );
1698 }
1699
1700 /**
1701 * Displayed column showing the name of the status
1702 *
1703 * @since 0.7
1704 *
1705 * @param object $item Custom status as an object
1706 * @return string $output What will be rendered
1707 */
1708 function column_name( $item ) {
1709 global $edit_flow;
1710
1711 $item_edit_link = esc_url( $edit_flow->custom_status->get_link( array( 'action' => 'edit-status', 'term-id' => $item->term_id ) ) );
1712
1713 $output = '<strong><a href="' . $item_edit_link . '">' . esc_html( $item->name ) . '</a>';
1714 if ( $item->slug == $this->default_status )
1715 $output .= ' - ' . __( 'Default', 'edit-flow' );
1716 $output .= '</strong>';
1717
1718 // Don't allow for any of these status actions when adding a new custom status
1719 if ( isset( $_GET['action'] ) && $_GET['action'] == 'add' )
1720 return $output;
1721
1722 $actions = array();
1723 $actions['edit'] = "<a href='$item_edit_link'>" . __( 'Edit', 'edit-flow' ) . "</a>";
1724 $actions['inline hide-if-no-js'] = '<a href="#" class="editinline">' . __( 'Quick&nbsp;Edit' ) . '</a>';
1725 if ( $item->slug != $this->default_status )
1726 $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 ) ) );
1727
1728 if ( $item->slug != $this->default_status )
1729 $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 ) ) );
1730
1731 $output .= $this->row_actions( $actions, false );
1732 $output .= '<div class="hidden" id="inline_' . esc_attr( $item->term_id ) . '">';
1733 $output .= '<div class="name">' . esc_html( $item->name ) . '</div>';
1734 $output .= '<div class="description">' . esc_html( $item->description ) . '</div>';
1735 $output .= '</div>';
1736
1737 return $output;
1738
1739 }
1740
1741 /**
1742 * Displayed column showing the description of the status
1743 *
1744 * @since 0.7
1745 *
1746 * @param object $item Custom status as an object
1747 * @return string $output What will be rendered
1748 */
1749 function column_description( $item ) {
1750 return esc_html( $item->description );
1751 }
1752
1753 /**
1754 * Prepare and echo a single custom status row
1755 *
1756 * @since 0.7
1757 */
1758 function single_row( $item ) {
1759 static $alternate_class = '';
1760 $alternate_class = ( $alternate_class == '' ? ' alternate' : '' );
1761 $row_class = ' class="term-static' . $alternate_class . '"';
1762
1763 echo '<tr id="term-' . $item->term_id . '"' . $row_class . '>';
1764 echo $this->single_row_columns( $item );
1765 echo '</tr>';
1766 }
1767
1768 /**
1769 * Hidden form used for inline editing functionality
1770 *
1771 * @since 0.7
1772 */
1773 function inline_edit() {
1774 global $edit_flow;
1775 ?>
1776 <form method="get" action=""><table style="display: none"><tbody id="inlineedit">
1777 <tr id="inline-edit" class="inline-edit-row" style="display: none"><td colspan="<?php echo $this->get_column_count(); ?>" class="colspanchange">
1778 <fieldset><div class="inline-edit-col">
1779 <h4><?php _e( 'Quick Edit' ); ?></h4>
1780 <label>
1781 <span class="title"><?php _e( 'Name', 'edit-flow' ); ?></span>
1782 <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" maxlength="20" /></span>
1783 </label>
1784 <label>
1785 <span class="title"><?php _e( 'Description', 'edit-flow' ); ?></span>
1786 <span class="input-text-wrap"><input type="text" name="description" class="pdescription" value="" /></span>
1787 </label>
1788 </div></fieldset>
1789 <p class="inline-edit-save submit">
1790 <a accesskey="c" href="#inline-edit" title="<?php _e( 'Cancel' ); ?>" class="cancel button-secondary alignleft"><?php _e( 'Cancel' ); ?></a>
1791 <?php $update_text = __( 'Update Status', 'edit-flow' ); ?>
1792 <a accesskey="s" href="#inline-edit" title="<?php echo esc_attr( $update_text ); ?>" class="save button-primary alignright"><?php echo $update_text; ?></a>
1793 <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
1794 <span class="error" style="display:none;"></span>
1795 <?php wp_nonce_field( 'custom-status-inline-edit-nonce', 'inline_edit', false ); ?>
1796 <br class="clear" />
1797 </p>
1798 </td></tr>
1799 </tbody></table></form>
1800 <?php
1801 }
1802
1803 }
1804