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

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