forked from Automattic/Edit-Flow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcustom-status.php
1995 lines (1705 loc) · 73.8 KB
/
custom-status.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* class EF_Custom_Status
* Custom statuses make it simple to define the different stages in your publishing workflow.
*
* @todo for v0.7
* - Improve the copy
* - Thoroughly test what happens when the default post statuses 'Draft' and 'Pending Review' no longer exist
* - Ensure all of the form processing uses our messages functionality
*/
if ( !class_exists( 'EF_Custom_Status' ) ) {
class EF_Custom_Status extends EF_Module {
var $module;
private $custom_statuses_cache = array();
/**
* Define the hooks that need to be unhooked/rehooked to make the module Gutenberg-ready.
*
* @var array
*/
protected $compat_hooks = [
'admin_enqueue_scripts' => 'action_admin_enqueue_scripts',
];
// This is taxonomy name used to store all our custom statuses
const taxonomy_key = 'post_status';
/**
* Register the module with Edit Flow but don't do anything else
*/
function __construct() {
$this->module_url = $this->get_module_url( __FILE__ );
// Register the module with Edit Flow
$args = array(
'title' => __( 'Custom Statuses', 'edit-flow' ),
'short_description' => __( 'Create custom post statuses to define the stages of your workflow.', 'edit-flow' ),
'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' ),
'module_url' => $this->module_url,
'img_url' => $this->module_url . 'lib/custom_status_s128.png',
'slug' => 'custom-status',
'default_options' => array(
'enabled' => 'on',
'default_status' => 'pitch',
'always_show_dropdown' => 'off',
'post_types' => array(
'post' => 'on',
'page' => 'on',
),
),
'post_type_support' => 'ef_custom_statuses', // This has been plural in all of our docs
'configure_page_cb' => 'print_configure_view',
'configure_link_text' => __( 'Edit Statuses', 'edit-flow' ),
'messages' => array(
'status-added' => __( 'Post status created.', 'edit-flow' ),
'status-missing' => __( "Post status doesn't exist.", 'edit-flow' ),
'default-status-changed' => __( 'Default post status has been changed.', 'edit-flow'),
'term-updated' => __( "Post status updated.", 'edit-flow' ),
'status-deleted' => __( 'Post status deleted.', 'edit-flow' ),
'status-position-updated' => __( "Status order updated.", 'edit-flow' ),
),
'autoload' => false,
'settings_help_tab' => array(
'id' => 'ef-custom-status-overview',
'title' => __('Overview', 'edit-flow'),
'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'),
),
'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' ),
);
$this->module = EditFlow()->register_module( 'custom_status', $args );
}
/**
* Initialize the EF_Custom_Status class if the module is active
*/
function init() {
global $edit_flow;
// Register custom statuses as a taxonomy
$this->register_custom_statuses();
// Register our settings
add_action( 'admin_init', array( $this, 'register_settings' ) );
// Load CSS and JS resources that we probably need
add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
add_action( 'admin_notices', array( $this, 'no_js_notice' ) );
add_action( 'admin_print_scripts', array( $this, 'post_admin_header' ) );
// Methods for handling the actions of creating, making default, and deleting post stati
add_action( 'admin_init', array( $this, 'handle_add_custom_status' ) );
add_action( 'admin_init', array( $this, 'handle_edit_custom_status' ) );
add_action( 'admin_init', array( $this, 'handle_make_default_custom_status' ) );
add_action( 'admin_init', array( $this, 'handle_delete_custom_status' ) );
add_action( 'wp_ajax_update_status_positions', array( $this, 'handle_ajax_update_status_positions' ) );
add_action( 'wp_ajax_inline_save_status', array( $this, 'ajax_inline_save_status' ) );
// Hook to add the status column to Manage Posts
add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns') );
add_action( 'manage_posts_custom_column', array( $this, '_filter_manage_posts_custom_column') );
// 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)
add_filter( 'manage_pages_columns', array( $this, '_filter_manage_posts_columns' ) );
add_action( 'manage_pages_custom_column', array( $this, '_filter_manage_posts_custom_column' ) );
// These seven-ish methods are hacks for fixing bugs in WordPress core
add_action( 'admin_init', array( $this, 'check_timestamp_on_publish' ) );
add_filter( 'wp_insert_post_data', array( $this, 'fix_custom_status_timestamp' ), 10, 2 );
add_action( 'wp_insert_post', array( $this, 'fix_post_name' ), 10, 2 );
add_filter( 'preview_post_link', array( $this, 'fix_preview_link_part_one' ) );
add_filter( 'post_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 );
add_filter( 'page_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 );
add_filter( 'post_type_link', array( $this, 'fix_preview_link_part_two' ), 10, 3 );
add_filter( 'get_sample_permalink', array( $this, 'fix_get_sample_permalink' ), 10, 5 );
add_filter( 'get_sample_permalink_html', array( $this, 'fix_get_sample_permalink_html' ), 10, 5);
add_filter( 'post_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 );
add_filter( 'page_row_actions', array( $this, 'fix_post_row_actions' ), 10, 2 );
// Pagination for custom post statuses when previewing posts
add_filter( 'wp_link_pages_link', array( $this, 'modify_preview_link_pagination_url' ), 10, 2 );
// Filter through Post States and run a function to check if they are also a Status
add_filter( 'display_post_states', array( $this, 'check_if_post_state_is_status' ), 10, 2 );
}
/**
* Create the default set of custom statuses the first time the module is loaded
*
* @since 0.7
*/
function install() {
$default_terms = array(
array(
'term' => __( 'Pitch', 'edit-flow' ),
'args' => array(
'slug' => 'pitch',
'description' => __( 'Idea proposed; waiting for acceptance.', 'edit-flow' ),
'position' => 1,
),
),
array(
'term' => __( 'Assigned', 'edit-flow' ),
'args' => array(
'slug' => 'assigned',
'description' => __( 'Post idea assigned to writer.', 'edit-flow' ),
'position' => 2,
),
),
array(
'term' => __( 'In Progress', 'edit-flow' ),
'args' => array(
'slug' => 'in-progress',
'description' => __( 'Writer is working on the post.', 'edit-flow' ),
'position' => 3,
),
),
array(
'term' => __( 'Draft', 'edit-flow' ),
'args' => array(
'slug' => 'draft',
'description' => __( 'Post is a draft; not ready for review or publication.', 'edit-flow' ),
'position' => 4,
),
),
array(
'term' => __( 'Pending Review' ),
'args' => array(
'slug' => 'pending',
'description' => __( 'Post needs to be reviewed by an editor.', 'edit-flow' ),
'position' => 5,
),
),
);
// Okay, now add the default statuses to the db if they don't already exist
foreach( $default_terms as $term ) {
if( !term_exists( $term['term'], self::taxonomy_key ) )
$this->add_custom_status( $term['term'], $term['args'] );
}
}
/**
* Upgrade our data in case we need to
*
* @since 0.7
*/
function upgrade( $previous_version ) {
global $edit_flow;
// Upgrade path to v0.7
if ( version_compare( $previous_version, '0.7' , '<' ) ) {
// Migrate dropdown visibility option
if ( $dropdown_visible = get_option( 'edit_flow_status_dropdown_visible' ) )
$dropdown_visible = 'on';
else
$dropdown_visible = 'off';
$edit_flow->update_module_option( $this->module->name, 'always_show_dropdown', $dropdown_visible );
delete_option( 'edit_flow_status_dropdown_visible' );
// Migrate default status option
if ( $default_status = get_option( 'edit_flow_custom_status_default_status' ) )
$edit_flow->update_module_option( $this->module->name, 'default_status', $default_status );
delete_option( 'edit_flow_custom_status_default_status' );
// Technically we've run this code before so we don't want to auto-install new data
$edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
}
// Upgrade path to v0.7.4
if ( version_compare( $previous_version, '0.7.4', '<' ) ) {
// Custom status descriptions become base64_encoded, instead of maybe json_encoded.
$this->upgrade_074_term_descriptions( self::taxonomy_key );
}
}
/**
* Makes the call to register_post_status to register the user's custom statuses.
* Also unregisters draft and pending, in case the user doesn't want them.
*/
function register_custom_statuses() {
global $wp_post_statuses;
if ( $this->disable_custom_statuses_for_post_type() )
return;
// Register new taxonomy so that we can store all our fancy new custom statuses (or is it stati?)
if ( !taxonomy_exists( self::taxonomy_key ) ) {
$args = array( 'hierarchical' => false,
'update_count_callback' => '_update_post_term_count',
'label' => false,
'query_var' => false,
'rewrite' => false,
'show_ui' => false
);
register_taxonomy( self::taxonomy_key, 'post', $args );
}
if ( function_exists( 'register_post_status' ) ) {
// Users can delete draft and pending statuses if they want, so let's get rid of them
// They'll get re-added if the user hasn't "deleted" them
unset( $wp_post_statuses[ 'draft' ] );
unset( $wp_post_statuses[ 'pending' ] );
$custom_statuses = $this->get_custom_statuses();
// Unfortunately, register_post_status() doesn't accept a
// post type argument, so we have to register the post
// statuses for all post types. This results in
// all post statuses for a post type appearing at the top
// of manage posts if there is a post with the status
foreach ( $custom_statuses as $status ) {
register_post_status( $status->slug, array(
'label' => $status->name
, 'protected' => true
, '_builtin' => false
, 'label_count' => _n_noop( "{$status->name} <span class='count'>(%s)</span>", "{$status->name} <span class='count'>(%s)</span>" )
) );
}
}
}
/**
* Whether custom post statuses should be disabled for this post type.
* Used to stop custom statuses from being registered for post types that don't support them.
*
* @since 0.7.5
*
* @return bool
*/
function disable_custom_statuses_for_post_type( $post_type = null ) {
global $pagenow;
// Only allow deregistering on 'edit.php' and 'post.php'
if ( ! in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ) ) )
return false;
if ( is_null( $post_type ) )
$post_type = $this->get_current_post_type();
if ( $post_type && ! in_array( $post_type, $this->get_post_types_for_module( $this->module ) ) )
return true;
return false;
}
/**
* Enqueue Javascript resources that we need in the admin:
* - Primary use of Javascript is to manipulate the post status dropdown on Edit Post and Manage Posts
* - jQuery Sortable plugin is used for drag and dropping custom statuses
* - We have other custom code for Quick Edit and JS niceties
*/
function action_admin_enqueue_scripts() {
global $pagenow;
if ( $this->disable_custom_statuses_for_post_type() )
return;
// Load Javascript we need to use on the configuration views (jQuery Sortable and Quick Edit)
if ( $this->is_whitelisted_settings_view( $this->module->name ) ) {
wp_enqueue_script( 'jquery-ui-sortable' );
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 );
}
// Custom javascript to modify the post status dropdown where it shows up
if ( $this->is_whitelisted_page() ) {
wp_enqueue_script( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.js', array( 'jquery','post' ), EDIT_FLOW_VERSION, true );
wp_enqueue_style( 'edit_flow-custom_status', $this->module_url . 'lib/custom-status.css', false, EDIT_FLOW_VERSION, 'all' );
wp_localize_script('edit_flow-custom_status', '__ef_localize_custom_status', array(
'no_change' => esc_html__( "— No Change —", 'edit-flow' ),
'published' => esc_html__( 'Published', 'edit-flow' ),
'save_as' => esc_html__( 'Save as', 'edit-flow' ),
'save' => esc_html__( 'Save', 'edit-flow' ),
'edit' => esc_html__( 'Edit', 'edit-flow' ),
'ok' => esc_html__( 'OK', 'edit-flow' ),
'cancel' => esc_html__( 'Cancel', 'edit-flow' ),
));
}
}
/**
* Displays a notice to users if they have JS disabled
* Javascript is needed for custom statuses to be fully functional
*/
function no_js_notice() {
if( $this->is_whitelisted_page() ) :
?>
<style type="text/css">
/* Hide post status dropdown by default in case of JS issues **/
label[for=post_status],
#post-status-display,
#post-status-select,
#publish {
display: none;
}
</style>
<div class="update-nag hide-if-js">
<?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' ); ?>
</div>
<?php
endif;
}
/**
* Check whether custom status stuff should be loaded on this page
*
* @todo migrate this to the base module class
*/
function is_whitelisted_page() {
global $pagenow;
if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
return false;
$post_type_obj = get_post_type_object( $this->get_current_post_type() );
if( ! current_user_can( $post_type_obj->cap->edit_posts ) )
return false;
// 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
return in_array( $pagenow, array( 'post.php', 'edit.php', 'post-new.php', 'page.php', 'edit-pages.php', 'page-new.php' ) );
}
/**
* Adds all necessary javascripts to make custom statuses work
*
* @todo Support private and future posts on edit.php view
*/
function post_admin_header() {
global $post, $edit_flow, $pagenow, $current_user;
if ( $this->disable_custom_statuses_for_post_type() )
return;
// Get current user
wp_get_current_user() ;
// 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
if ( $this->is_whitelisted_page() ) {
$custom_statuses = $this->get_custom_statuses();
// $selected can be empty, but must be set because it's used as a JS variable
$selected = '';
$selected_name = '';
if( ! empty( $post ) ) {
// Get the status of the current post
if ( $post->ID == 0 || $post->post_status == 'auto-draft' || $pagenow == 'edit.php' ) {
// TODO: check to make sure that the default exists
$selected = $this->get_default_custom_status()->slug;
} else {
$selected = $post->post_status;
}
// Get the label of current status
foreach ( $custom_statuses as $status ) {
if ( $status->slug == $selected ) {
$selected_name = $status->name;
}
}
}
$custom_statuses = apply_filters( 'ef_custom_status_list', $custom_statuses, $post );
// All right, we want to set up the JS var which contains all custom statuses
$all_statuses = array();
// The default statuses from WordPress
$all_statuses[] = array(
'name' => __( 'Published', 'edit-flow' ),
'slug' => 'publish',
'description' => '',
);
$all_statuses[] = array(
'name' => __( 'Privately Published', 'edit-flow' ),
'slug' => 'private',
'description' => '',
);
$all_statuses[] = array(
'name' => __( 'Scheduled', 'edit-flow' ),
'slug' => 'future',
'description' => '',
);
// Load the custom statuses
foreach( $custom_statuses as $status ) {
$all_statuses[] = array(
'name' => esc_js( $status->name ),
'slug' => esc_js( $status->slug ),
'description' => esc_js( $status->description ),
);
}
$always_show_dropdown = ( $this->module->options->always_show_dropdown == 'on' ) ? 1 : 0;
$post_type_obj = get_post_type_object( $this->get_current_post_type() );
// Now, let's print the JS vars
?>
<script type="text/javascript">
var custom_statuses = <?php echo json_encode( $all_statuses ); ?>;
var ef_default_custom_status = '<?php echo esc_js( $this->get_default_custom_status()->slug ); ?>';
var current_status = '<?php echo esc_js( $selected ); ?>';
var current_status_name = '<?php echo esc_js( $selected_name ); ?>';
var status_dropdown_visible = <?php echo esc_js( $always_show_dropdown ); ?>;
var current_user_can_publish_posts = <?php echo current_user_can( $post_type_obj->cap->publish_posts ) ? 1 : 0; ?>;
var current_user_can_edit_published_posts = <?php echo current_user_can( $post_type_obj->cap->edit_published_posts ) ? 1 : 0; ?>;
</script>
<?php
}
}
/**
* Adds a new custom status as a term in the wp_terms table.
* Basically a wrapper for the wp_insert_term class.
*
* The arguments decide how the term is handled based on the $args parameter.
* The following is a list of the available overrides and the defaults.
*
* 'description'. There is no default. If exists, will be added to the database
* along with the term. Expected to be a string.
*
* 'slug'. Expected to be a string. There is no default.
*
* @param int|string $term The status to add or update
* @param array|string $args Change the values of the inserted term
* @return array|WP_Error $response The Term ID and Term Taxonomy ID
*/
function add_custom_status( $term, $args = array() ) {
$slug = ( ! empty( $args['slug'] ) ) ? $args['slug'] : sanitize_title( $term );
unset( $args['slug'] );
$encoded_description = $this->get_encoded_description( $args );
$response = wp_insert_term( $term, self::taxonomy_key, array( 'slug' => $slug, 'description' => $encoded_description ) );
// Reset our internal object cache
$this->custom_statuses_cache = array();
return $response;
}
/**
* Update an existing custom status
*
* @param int @status_id ID for the status
* @param array $args Any arguments to be updated
* @return object $updated_status Newly updated status object
*/
function update_custom_status( $status_id, $args = array() ) {
global $edit_flow;
$old_status = $this->get_custom_status_by( 'id', $status_id );
if ( !$old_status || is_wp_error( $old_status ) )
return new WP_Error( 'invalid', __( "Custom status doesn't exist.", 'edit-flow' ) );
// Reset our internal object cache
$this->custom_statuses_cache = array();
// Prevent user from changing draft name or slug
if ( 'draft' === $old_status->slug
&& (
( isset( $args['name'] ) && $args['name'] !== $old_status->name )
||
( isset( $args['slug'] ) && $args['slug'] !== $old_status->slug )
) ) {
return new WP_Error( 'invalid', __( 'Changing the name and slug of "Draft" is not allowed', 'edit-flow' ) );
}
// If the name was changed, we need to change the slug
if ( isset( $args['name'] ) && $args['name'] != $old_status->name )
$args['slug'] = sanitize_title( $args['name'] );
// Reassign posts to new status slug if the slug changed and isn't restricted
if ( isset( $args['slug'] ) && $args['slug'] != $old_status->slug && !$this->is_restricted_status( $old_status->slug ) ) {
$new_status = $args['slug'];
$this->reassign_post_status( $old_status->slug, $new_status );
$default_status = $this->get_default_custom_status()->slug;
if ( $old_status->slug == $default_status )
$edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
}
// We're encoding metadata that isn't supported by default in the term's description field
$args_to_encode = array();
$args_to_encode['description'] = ( isset( $args['description'] ) ) ? $args['description'] : $old_status->description;
$args_to_encode['position'] = ( isset( $args['position'] ) ) ? $args['position'] : $old_status->position;
$encoded_description = $this->get_encoded_description( $args_to_encode );
$args['description'] = $encoded_description;
$updated_status_array = wp_update_term( $status_id, self::taxonomy_key, $args );
$updated_status = $this->get_custom_status_by( 'id', $updated_status_array['term_id'] );
return $updated_status;
}
/**
* Deletes a custom status from the wp_terms table.
*
* Partly a wrapper for the wp_delete_term function.
* BUT, also reassigns posts that currently have the deleted status assigned.
*/
function delete_custom_status( $status_id, $args = array(), $reassign = '' ) {
global $edit_flow;
// Reassign posts to alternate status
// Get slug for the old status
$old_status = $this->get_custom_status_by( 'id', $status_id )->slug;
if ( $reassign == $old_status )
return new WP_Error( 'invalid', __( 'Cannot reassign to the status you want to delete', 'edit-flow' ) );
// Reset our internal object cache
$this->custom_statuses_cache = array();
if( !$this->is_restricted_status( $old_status ) && 'draft' !== $old_status ) {
$default_status = $this->get_default_custom_status()->slug;
// If new status in $reassign, use that for all posts of the old_status
if( !empty( $reassign ) )
$new_status = $this->get_custom_status_by( 'id', $reassign )->slug;
else
$new_status = $default_status;
if ( $old_status == $default_status && $this->get_custom_status_by( 'slug', 'draft' ) ) { // Deleting default status
$new_status = 'draft';
$edit_flow->update_module_option( $this->module->name, 'default_status', $new_status );
}
$this->reassign_post_status( $old_status, $new_status );
return wp_delete_term( $status_id, self::taxonomy_key, $args );
} else
return new WP_Error( 'restricted', __( 'Restricted status ', 'edit-flow' ) . '(' . $this->get_custom_status_by( 'id', $status_id )->name . ')' );
}
/**
* Get all custom statuses as an ordered array
*
* @param array|string $statuses
* @param array $args
* @return array $statuses All of the statuses
*/
function get_custom_statuses( $args = array() ) {
global $wp_post_statuses;
if ( $this->disable_custom_statuses_for_post_type() ) {
return $this->get_core_post_statuses();
}
// Internal object cache for repeat requests
$arg_hash = md5( serialize( $args ) );
if ( ! empty( $this->custom_statuses_cache[ $arg_hash ] ) ) {
return $this->custom_statuses_cache[ $arg_hash ];
}
// Handle if the requested taxonomy doesn't exist
$args = array_merge( array( 'hide_empty' => false ), $args );
$statuses = get_terms( self::taxonomy_key, $args );
if ( is_wp_error( $statuses ) || empty( $statuses ) ) {
$statuses = array();
}
// Expand and order the statuses
$ordered_statuses = array();
$hold_to_end = array();
foreach ( $statuses as $key => $status ) {
// Unencode and set all of our psuedo term meta because we need the position if it exists
$unencoded_description = $this->get_unencoded_description( $status->description );
if ( is_array( $unencoded_description ) ) {
foreach( $unencoded_description as $key => $value ) {
$status->$key = $value;
}
}
// We require the position key later on (e.g. management table)
if ( !isset( $status->position ) )
$status->position = false;
// Only add the status to the ordered array if it has a set position and doesn't conflict with another key
// Otherwise, hold it for later
if ( $status->position && !array_key_exists( $status->position, $ordered_statuses ) ) {
$ordered_statuses[(int)$status->position] = $status;
} else {
$hold_to_end[] = $status;
}
}
// Sort the items numerically by key
ksort( $ordered_statuses, SORT_NUMERIC );
// Append all of the statuses that didn't have an existing position
foreach( $hold_to_end as $unpositioned_status )
$ordered_statuses[] = $unpositioned_status;
$this->custom_statuses_cache[ $arg_hash ] = $ordered_statuses;
return $ordered_statuses;
}
/**
* Returns the a single status object based on ID, title, or slug
*
* @param string|int $string_or_int The status to search for, either by slug, name or ID
* @return object|WP_Error $status The object for the matching status
*/
function get_custom_status_by( $field, $value ) {
if ( ! in_array( $field, array( 'id', 'slug', 'name' ) ) )
return false;
if ( 'id' == $field )
$field = 'term_id';
$custom_statuses = $this->get_custom_statuses();
$custom_status = wp_filter_object_list( $custom_statuses, array( $field => $value ) );
if ( ! empty( $custom_status ) )
return array_shift( $custom_status );
else
return false;
}
/**
* Get the term object for the default custom post status
*
* @return object $default_status Default post status object
*/
function get_default_custom_status() {
$default_status = $this->get_custom_status_by( 'slug', $this->module->options->default_status );
if ( ! $default_status ) {
$custom_statuses = $this->get_custom_statuses();
$default_status = array_shift( $custom_statuses );
}
return $default_status;
}
/**
* Assign new statuses to posts using value provided or the default
*
* @param string $old_status Slug for the old status
* @param string $new_status Slug for the new status
*/
function reassign_post_status( $old_status, $new_status = '' ) {
global $wpdb;
if ( empty( $new_status ) )
$new_status = $this->get_default_custom_status()->slug;
// Make the database call
$result = $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'post_status' => $old_status ), array( '%s' ));
}
/**
* Insert new column header for post status after the title column
*
* @param array $posts_columns Columns currently shown on the Edit Posts screen
* @return array Same array as the input array with a "status" column added after the "title" column
*/
function _filter_manage_posts_columns( $posts_columns ) {
// Return immediately if the supplied parameter isn't an array (which shouldn't happen in practice?)
// http://wordpress.org/support/topic/plugin-edit-flow-bug-shows-2-drafts-when-there-are-none-leads-to-error-messages
if ( !is_array( $posts_columns ) )
return $posts_columns;
// Only do it for the post types this module is activated for
if ( !in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $this->module ) ) )
return $posts_columns;
$result = array();
foreach ( $posts_columns as $key => $value ) {
if ($key == 'title') {
$result[$key] = $value;
$result['status'] = __('Status', 'edit-flow');
} else $result[$key] = $value;
}
return $result;
}
/**
* Adds a Post's status to its row on the Edit page
*
* @param string $column_name
**/
function _filter_manage_posts_custom_column( $column_name ) {
if ( $column_name == 'status' ) {
global $post;
$post_status_obj = get_post_status_object( get_post_status( $post->ID ) );
echo esc_html( $post_status_obj->label );
}
}
/**
* Check if Post State is a Status and display if it is not.
*
* @param array $post_states An array of post display states.
*/
function check_if_post_state_is_status($post_states) {
global $post;
$statuses = get_post_status_object(get_post_status($post->ID));
foreach ( $post_states as $state ) {
if ( $state !== $statuses->label ) {
echo '<span class="show"></span>';
}
}
return $post_states;
}
/**
* Determines whether the slug indicated belongs to a restricted status or not
*
* @param string $slug Slug of the status
* @return bool $restricted True if restricted, false if not
*/
function is_restricted_status( $slug ) {
switch( $slug ) {
case 'publish':
case 'private':
case 'future':
case 'new':
case 'inherit':
case 'auto-draft':
case 'trash':
$restricted = true;
break;
default:
$restricted = false;
break;
}
return $restricted;
}
/**
* Handles a form's POST request to add a custom status
*
* @since 0.7
*/
function handle_add_custom_status() {
// Check that the current POST request is our POST request
if ( !isset( $_POST['submit'], $_GET['page'], $_POST['action'] )
|| $_GET['page'] != $this->module->settings_slug || $_POST['action'] != 'add-new' )
return;
if ( !wp_verify_nonce( $_POST['_wpnonce'], 'custom-status-add-nonce' ) )
wp_die( $this->module->messages['nonce-failed'] );
// Validate and sanitize the form data
$status_name = sanitize_text_field( trim( $_POST['status_name'] ) );
$status_slug = sanitize_title( $status_name );
$status_description = stripslashes( wp_filter_nohtml_kses( trim( $_POST['status_description'] ) ) );
/**
* Form validation
* - Name is required and can't conflict with an existing name or slug
* - Description is optional
*/
$_REQUEST['form-errors'] = array();
// Check if name field was filled in
if( empty( $status_name ) )
$_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
// Check that the name isn't numeric
if ( (int)$status_name != 0 )
$_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
// Check that the status name doesn't exceed 20 chars
if ( strlen( $status_name ) > 20 )
$_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
// Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
if ( term_exists( $status_slug, self::taxonomy_key ) )
$_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
// Check to make sure the name is not restricted
if ( $this->is_restricted_status( strtolower( $status_slug ) ) )
$_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
// If there were any form errors, kick out and return them
if ( count( $_REQUEST['form-errors'] ) ) {
$_REQUEST['error'] = 'form-error';
return;
}
// Try to add the status
$status_args = array(
'description' => $status_description,
'slug' => $status_slug,
);
$return = $this->add_custom_status( $status_name, $status_args );
if ( is_wp_error( $return ) )
wp_die( __( 'Could not add status: ', 'edit-flow' ) . $return->get_error_message() );
// Redirect if successful
$redirect_url = $this->get_link( array( 'message' => 'status-added' ) );
wp_redirect( $redirect_url );
exit;
}
/**
* Handles a POST request to edit an custom status
*
* @since 0.7
*/
function handle_edit_custom_status() {
if ( !isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] )
|| $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'edit-status' )
return;
if ( !wp_verify_nonce( $_POST['_wpnonce'], 'edit-status' ) )
wp_die( $this->module->messages['nonce-failed'] );
if ( !current_user_can( 'manage_options' ) )
wp_die( $this->module->messages['invalid-permissions'] );
if ( !$existing_status = $this->get_custom_status_by( 'id', (int)$_GET['term-id'] ) )
wp_die( $this->module->messages['status-missing'] );
$name = sanitize_text_field( trim( $_POST['name'] ) );
$description = stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) );
/**
* Form validation for editing custom status
*
* Details
* - 'name' is a required field and can't conflict with existing name or slug
* - 'description' is optional
*/
$_REQUEST['form-errors'] = array();
// Check if name field was filled in
if( empty( $name ) )
$_REQUEST['form-errors']['name'] = __( 'Please enter a name for the status', 'edit-flow' );
// Check that the name isn't numeric
if ( is_numeric( $name ) )
$_REQUEST['form-errors']['name'] = __( 'Please enter a valid, non-numeric name for the status.', 'edit-flow' );
// Check that the status name doesn't exceed 20 chars
if ( strlen( $name ) > 20 )
$_REQUEST['form-errors']['name'] = __( 'Status name cannot exceed 20 characters. Please try a shorter name.', 'edit-flow' );
// Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
$term_exists = term_exists( sanitize_title( $name ), self::taxonomy_key );
if ( $term_exists && isset( $term_exists['term_id'] ) && $term_exists['term_id'] != $existing_status->term_id )
$_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing term. Please choose another.', 'edit-flow' );
// Check to make sure the status doesn't already exist
$search_status = $this->get_custom_status_by( 'slug', sanitize_title( $name ) );
if ( $search_status && $search_status->term_id != $existing_status->term_id )
$_REQUEST['form-errors']['name'] = __( 'Status name conflicts with existing status. Please choose another.', 'edit-flow' );
// Check to make sure the name is not restricted
if ( $this->is_restricted_status( strtolower( sanitize_title( $name ) ) ) )
$_REQUEST['form-errors']['name'] = __( 'Status name is restricted. Please choose another name.', 'edit-flow' );
// Kick out if there are any errors
if ( count( $_REQUEST['form-errors'] ) ) {
$_REQUEST['error'] = 'form-error';
return;
}
// Try to add the new post status
$args = array(
'name' => $name,
'slug' => sanitize_title( $name ),
'description' => $description,
);
$return = $this->update_custom_status( $existing_status->term_id, $args );
if ( is_wp_error( $return ) )
wp_die( __( 'Error updating post status.', 'edit-flow' ) );
$redirect_url = $this->get_link( array( 'message' => 'status-updated' ) );
wp_redirect( $redirect_url );
exit;
}
/**
* Handles a GET request to make the identified status default
*
* @since 0.7
*/
function handle_make_default_custom_status() {
global $edit_flow;
// Check that the current GET request is our GET request
if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
|| $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'make-default' )
return;
// Check for proper nonce
if ( !wp_verify_nonce( $_GET['nonce'], 'make-default' ) )
wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
// Only allow users with the proper caps
if ( !current_user_can( 'manage_options' ) )
wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
$term_id = (int)$_GET['term-id'];
$term = $this->get_custom_status_by( 'id', $term_id );
if ( is_object( $term ) ) {
$edit_flow->update_module_option( $this->module->name, 'default_status', $term->slug );
// @todo How do we want to handle users who click the link from "Add New Status"
$redirect_url = $this->get_link( array( 'message' => 'default-status-changed' ) );
wp_redirect( $redirect_url );
exit;
} else {
wp_die( __( 'Status doesn't exist.', 'edit-flow' ) );
}
}
/**
* Handles a GET request to delete a specific term
*
* @since 0.7
*/
function handle_delete_custom_status() {
// Check that this GET request is our GET request
if ( !isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] )
|| $_GET['page'] != $this->module->settings_slug || $_GET['action'] != 'delete-status' )
return;
// Check for proper nonce
if ( !wp_verify_nonce( $_GET['nonce'], 'delete-status' ) )
wp_die( __( 'Invalid nonce for submission.', 'edit-flow' ) );
// Only allow users with the proper caps
if ( !current_user_can( 'manage_options' ) )
wp_die( __( 'Sorry, you do not have permission to edit custom statuses.', 'edit-flow' ) );
// Check to make sure the status isn't already deleted
$term_id = (int)$_GET['term-id'];
$term = $this->get_custom_status_by( 'id', $term_id );
if( !$term )
wp_die( __( 'Status does not exist.', 'edit-flow' ) );
// Don't allow deletion of default status
if ( $term->slug == $this->get_default_custom_status()->slug )
wp_die( __( 'Cannot delete default status.', 'edit-flow' ) );
$return = $this->delete_custom_status( $term_id );
if ( is_wp_error( $return ) )
wp_die( __( 'Could not delete the status: ', 'edit-flow' ) . $return->get_error_message() );
$redirect_url = $this->get_link( array( 'message' => 'status-deleted' ) );
wp_redirect( $redirect_url );
exit;
}
/**
* Generate a link to one of the custom status actions
*
* @since 0.7