PluginProbe
PublishPress Statuses – Custom Post Status and Workflow / trunk
PublishPress Statuses – Custom Post Status and Workflow vtrunk
1.3.6 1.3.4 1.3.3 1.3.2 trunk 1.0.4.1 1.0.5 1.0.6 1.0.6.2 1.0.6.3 1.0.6.4 1.0.6.5 1.0.6.6 1.0.6.7 1.0.6.8 1.0.6.9 1.0.7 1.0.8 1.0.9 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.14-rc All 38 releases
publishpress-statuses / StatusHandler.php

StatusHandler.php in PublishPress Statuses – Custom Post Status and Workflow trunk, at StatusHandler.php

1,083 lines 42.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace PublishPress_Statuses;
3
4 // Custom Status management: Handle status add / edit requests
5 class StatusHandler {
6 /**
7 * Handles a form's POST request to add a custom status
8 *
9 */
10 public static function handleAddCustomStatus()
11 {
12 global $current_user;
13
14 check_admin_referer('custom-status-add-nonce');
15
16 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
17 wp_die(esc_html__('Sorry, you do not have permission to edit custom statuses.', 'publishpress-statuses'));
18 }
19
20 // Validate and sanitize the form data
21 $status_label = !empty($_POST['status_label']) ? sanitize_text_field(trim(sanitize_text_field(wp_unslash($_POST['status_label'])))) : '';
22
23 $status_name = !empty($_POST['slug']) ? sanitize_title(wp_unslash($_POST['slug'])) : '';
24
25 $status_description = !empty($_POST['description']) ? stripslashes(wp_filter_nohtml_kses(trim(sanitize_text_field(wp_unslash($_POST['description']))))) : '';
26
27 $status_color = !empty($_POST['status_color']) ? sanitize_hex_color(wp_unslash($_POST['status_color'])) : '';
28
29 $status_icon = !empty($_POST['icon']) ? str_replace('dashicons|', '', sanitize_key($_POST['icon'])) : '';
30
31 $taxonomy = (!empty($_POST['taxonomy'])) ? sanitize_key($_POST['taxonomy']) : \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
32
33 if ($taxonomy
34 && !in_array(
35 $taxonomy,
36 apply_filters(
37 'publishpress_statuses_taxonomies',
38 [\PublishPress_Statuses::TAXONOMY_PRE_PUBLISH, \PublishPress_Statuses::TAXONOMY_PRIVACY]
39 )
40 )) {
41 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
42 }
43
44 /**
45 * Form validation
46 * - Name is required and can't conflict with an existing name or slug
47 * - Description is optional
48 */
49 $form_errors = [];
50
51 // Check if name field was filled in
52 if (empty($status_label)) {
53 $form_errors['label'] = __('Please enter a name for the status', 'publishpress-statuses');
54 }
55
56 if (empty($status_name)) {
57 $form_errors['label'] = __('Please enter a slug for the status', 'publishpress-statuses');
58 } else {
59 $status_name = apply_filters('publishpress_statuses_add_status', sanitize_key($status_name), $taxonomy);
60 }
61
62 // Check that the name isn't numeric
63 if (is_numeric($status_name)) {
64 $form_errors['label'] = __(
65 'Please enter a valid, non-numeric name for the status.',
66 'publishpress-statuses'
67 );
68 }
69 // Check that the status name doesn't exceed 20 chars
70 $name_is_valid = true;
71 if (function_exists('mb_strlen')) {
72 if (mb_strlen($status_label) > 20) {
73 $name_is_valid = false;
74 }
75 } else {
76 if (strlen($status_label) > 20) {
77 $name_is_valid = false;
78 }
79 }
80 if (! $name_is_valid) {
81 $form_errors['label'] = __(
82 'Status name cannot exceed 20 characters. Please try a shorter name.',
83 'publishpress-statuses'
84 );
85
86 return;
87 }
88
89 $name_is_valid = true;
90 if (function_exists('mb_strlen')) {
91 if (mb_strlen($status_name) > 20) {
92 $name_is_valid = false;
93 }
94 } else {
95 if (strlen($status_name) > 20) {
96 $name_is_valid = false;
97 }
98 }
99 if (! $name_is_valid) {
100 $form_errors['label'] = __(
101 'Status slug cannot exceed 20 characters. Please try a shorter name.',
102 'publishpress-statuses'
103 );
104
105 return;
106 }
107
108 // Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug
109 if (get_term_by('slug', $status_name, \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH)) {
110 $form_errors['label'] = __(
111 'Name conflicts with existing status. Please choose another.',
112 'publishpress-statuses'
113 );
114 }
115 // Check to make sure the name is not restricted
116 if (self::is_restricted_status(strtolower($status_name))) {
117 $form_errors['label'] = __(
118 'Status name is restricted. Please choose another name.',
119 'publishpress-statuses'
120 );
121 }
122
123 // If there were any form errors, kick out and return them
124 if (count($form_errors)) {
125 \PublishPress_Statuses::instance()->form_errors = $form_errors;
126 \PublishPress_Statuses::instance()->last_error = 'form-error';
127 return;
128 }
129
130 // Try to add the status
131 $status_args = [
132 'description' => $status_description,
133 'slug' => $status_name,
134 'color' => $status_color,
135 'icon' => $status_icon,
136 ];
137
138 $return = \PublishPress_Statuses::instance()->addStatus($taxonomy, $status_label, $status_args);
139
140 if (is_wp_error($return)) {
141 wp_die(esc_html__('Could not add status: ', 'publishpress-statuses') . esc_html($return->get_error_message()));
142 }
143
144 global $wp_roles;
145
146 if (!empty($wp_roles) && is_object($wp_roles) && !empty($wp_roles->roles)) {
147 foreach($wp_roles->role_objects as $role_name => $role) {
148
149 // Mirror Planner behavior of enabling standard WP roles to assign statuses, but also grant to other roles based on post / page capabilities
150 if (in_array($role_name, ['administrator', 'author', 'editor', 'contributor']) || $role->has_cap('edit_posts') || $role->has_cap('edit_pages')) {
151 $cap_name = 'status_change_' . str_replace('-', '_', $status_name);
152
153 if (empty($role->capabilties[$cap_name])) {
154 $role->add_cap($cap_name);
155 }
156 }
157 }
158 }
159
160 delete_option('publishpress_statuses_num_roles');
161
162 $redirect_args = ['action' => 'edit-status', 'name' => $status_name, 'message' => 'status-added'];
163
164 if ($status_type = \PP_Statuses_Functions::REQUEST_key('status_type')) {
165 $redirect_args['status_type'] = $status_type;
166 }
167
168 if (!empty(\PublishPress_Statuses::instance()->options->new_statuses_main_workflow)) {
169 $defaulted_to_main = (array) get_option('publishpress_statuses_defaulted_to_main', []);
170 $defaulted_to_main[$status_name] = true;
171 update_option('publishpress_statuses_defaulted_to_main', $defaulted_to_main);
172
173 if ($status_positions = get_option('publishpress_status_positions')) {
174 $status_positions = array_values($status_positions);
175
176 if ('post_status' == $taxonomy) {
177 if ($alternate_pos = array_search('_pre-publish-alternate', $status_positions)) {
178 $status_positions = array_merge(
179 array_slice($status_positions, 0, $alternate_pos - 1),
180 [$status_name],
181 array_slice($status_positions, $alternate_pos)
182 );
183
184 update_option('publishpress_status_positions', $status_positions);
185 }
186 } elseif ('pp_revision_status' == $taxonomy) {
187 if ($alternate_pos) {
188 $status_positions = array_merge(
189 array_slice($status_positions, 0, $alternate_pos - 1),
190 [$status_name],
191 array_slice($status_positions, $alternate_pos)
192 );
193
194 update_option('publishpress_status_positions', $status_positions);
195 }
196 }
197 }
198 }
199
200 // Redirect if successful
201 $redirect_url = \PublishPress_Statuses::getLink($redirect_args);
202
203 wp_redirect($redirect_url);
204 exit;
205 }
206
207 /**
208 * Handles a GET request to delete a specific term
209 *
210 */
211 public static function handleDeleteCustomStatus()
212 {
213 // Check for proper nonce
214 check_admin_referer('delete-status');
215
216 // Only allow users with the proper caps
217 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
218 wp_die(esc_html__('Sorry, you do not have permission to edit custom statuses.', 'publishpress-statuses'));
219 }
220
221 // Check to make sure the status isn't already deleted
222 $name = !empty($_GET['name']) ? sanitize_key($_GET['name']) : '';
223 $term = \PublishPress_Statuses::getStatusBy('id', $name);
224 if (! $term) {
225 wp_die(esc_html__('Status does not exist.', 'publishpress-statuses'));
226 }
227
228 $return = self::deleteCustomStatus($name);
229 if (is_wp_error($return)) {
230 wp_die(esc_html__('Could not delete the status: ', 'publishpress-statuses') . esc_html($return->get_error_message()));
231 }
232
233 $redirect_url = \PublishPress_Statuses::getLink(['message' => 'status-deleted']);
234 wp_redirect($redirect_url);
235
236 exit;
237 }
238
239 /**
240 * Handles a POST request to edit a custom status
241 *
242 */
243 public static function handleEditCustomStatus()
244 {
245 check_admin_referer('edit-status');
246
247 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
248 wp_die(esc_html(__('Sorry, you are not allowed to access this page.')));
249 }
250
251 $name = (!empty($_REQUEST['name'])) ? sanitize_key($_REQUEST['name']) : '';
252
253 if (!$existing_status = \PublishPress_Statuses::getStatusBy('slug', $name)) {
254 wp_die(esc_html__("Post status doesn't exist.", 'publishpress-statuses'));
255 }
256
257 $color = !empty($_POST['status_color']) ? sanitize_hex_color(wp_unslash($_POST['status_color'])) : '';
258 $icon = !empty($_POST['icon']) ? sanitize_text_field(wp_unslash($_POST['icon'])) : '';
259 $icon = str_replace('dashicons|', '', $icon);
260
261 $status_obj = $existing_status;
262
263 // Prime the term_meta records if they don't already exist
264 // Doing this in advance prevents seletions from being overridden by defaults.
265 if (!empty($status_obj->_builtin)) {
266 $taxonomy = \PublishPress_Statuses::TAXONOMY_CORE_STATUS;
267
268 } elseif (in_array($name, ['_pre-publish-alternate', '_disabled'])) {
269 $taxonomy = \PublishPress_Statuses::TAXONOMY_PSEUDO_STATUS;
270
271 } elseif (!empty($status_obj->private)) {
272 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRIVACY;
273
274 } else {
275 if (!$taxonomy = apply_filters('publishpress_statuses_taxonomy', '', $status_obj)) {
276 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
277 }
278 }
279
280 if (!$term = get_term_by('slug', $name, $taxonomy)) {
281 \PublishPress_Statuses::instance()->addStatus($taxonomy, $status_obj->label, ['slug' => $name]);
282 }
283
284 $form_errors = [];
285
286 // Try to edit the post status
287 $args = [
288 'name' => $name,
289 'color' => $color,
290 'icon' => $icon,
291 ];
292
293 $status_post_types = !empty($status_obj->post_type) ? $status_obj->post_type : [];
294
295 if (!empty($_REQUEST['pp_status_all_types'])) {
296 $args['post_type'] = [];
297
298 } else {
299 $set_post_types = !empty($_REQUEST['pp_status_post_types']) ? array_map('intval', $_REQUEST['pp_status_post_types']) : false;
300
301 if ($set_post_types) {
302 if ($add_types = array_filter($set_post_types)) {
303 $status_post_types = array_unique(array_merge($status_post_types, array_map('sanitize_key', array_keys($add_types))));
304 }
305
306 if ($remove_types = array_diff($set_post_types, ['1', true, 1])) {
307 $status_post_types = array_diff($status_post_types, array_keys($remove_types));
308 }
309
310 $args['post_type'] = $status_post_types;
311 }
312 }
313
314 if (isset($_REQUEST['roles_set_status'])) {
315 $cap_name = str_replace('-', '_', "status_change_{$status_obj->name}");
316
317 $roles_set_status = array_map('intval', $_REQUEST['roles_set_status']);
318
319 foreach ($roles_set_status as $role_name => $set_val) {
320 $role_name = sanitize_key($role_name);
321 $set_val = boolval($set_val);
322
323 if (!\PP_Statuses_Functions::isEditableRole($role_name)) {
324 continue;
325 }
326
327 if ($role = get_role($role_name)) {
328 if ($set_val && empty($role->capabilities[$cap_name])) {
329 $role->add_cap($cap_name);
330 $changed = true;
331
332 } elseif (!$set_val && !empty($role->capabilities[$cap_name])) {
333 $role->remove_cap($cap_name);
334 $changed = true;
335 }
336 }
337
338 }
339
340 if (!empty($changed)) {
341 \PublishPress_Statuses::updateStatusNumRoles($status_obj->name, ['force_refresh' => true]);
342 }
343 }
344
345 $status_obj = get_post_status_object($name);
346
347 if (!\PP_Statuses_Functions::empty_REQUEST('return_module')) {
348 $arr = ['message' => 'status-updated'];
349 $arr['page'] = 'pp-modules-settings';
350 $arr['settings_module'] = \PP_Statuses_Functions::REQUEST_key('return_module');
351
352 $redirect_url = \PublishPress_Statuses::getLink($arr);
353 } else {
354 $arr = ['message' => 'status-updated'];
355 $arr['page'] = 'publishpress-statuses';
356 $arr['action'] = 'edit-status';
357 $arr['name'] = $name;
358
359 if (!empty($_REQUEST['pp_tab'])) {
360 $arr['pp_tab'] = str_replace('pp-', '', sanitize_key($_REQUEST['pp_tab']));
361 }
362
363 $arr = apply_filters('publishpress_status_edit_redirect_args', $arr, $status_obj);
364
365 $redirect_url = \PublishPress_Statuses::getLink($arr);
366 }
367
368 // work around bug in status capabilities library (displaying Set capability checkbox for disabled post types)
369 if (isset($_REQUEST['status_caps'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
370 foreach (array_keys($_REQUEST['status_caps']) as $role_name) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
371 if (isset($_REQUEST['status_caps'][$role_name]["status_change_{$status_obj->name}"])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
372 unset($_REQUEST['status_caps'][$role_name]["status_change_{$status_obj->name}"]); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
373 }
374 }
375 }
376
377 if (!empty($_POST['pp_statuses_enable_status_post_caps'])) {
378 update_option('cme_custom_status_control', true);
379 update_option('cme_custom_status_postmeta_caps', true);
380 }
381
382 do_action('publishpress_statuses_edit_status', $existing_status->name, $args);
383
384 $return = self::updateCustomStatus($existing_status->name, $args);
385
386 if (is_wp_error($return)) {
387 wp_die(esc_html__('Error updating post status.', 'publishpress-statuses'));
388 }
389
390 wp_redirect($redirect_url);
391 exit;
392 }
393
394 /**
395 * Handles a POST request to edit a custom status
396 *
397 */
398 public static function handleEditCustomStatusLabels()
399 {
400 check_admin_referer('edit-status');
401
402 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
403 wp_die(esc_html(__('Sorry, you are not allowed to access this page.')));
404 }
405
406 $name = !empty($_REQUEST['name']) ? trim(sanitize_text_field(wp_unslash($_REQUEST['name']))) : '';
407
408 if (!$existing_status = \PublishPress_Statuses::getStatusBy('name', sanitize_key($name))) {
409 wp_die(esc_html__("Post status doesn't exist.", 'publishpress-statuses'));
410 }
411
412 $status_obj = $existing_status;
413
414 // Prime the term_meta records if they don't already exist
415 // Doing this in advance prevents seletions from being overridden by defaults.
416 if (!empty($status_obj->_builtin)) {
417 $taxonomy = \PublishPress_Statuses::TAXONOMY_CORE_STATUS;
418
419 } elseif (in_array($name, ['_pre-publish-alternate', '_disabled'])) {
420 $taxonomy = \PublishPress_Statuses::TAXONOMY_PSEUDO_STATUS;
421
422 } elseif (!empty($status_obj->private)) {
423 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRIVACY;
424
425 } else {
426 if (!$taxonomy = apply_filters('publishpress_statuses_taxonomy', '', $status_obj)) {
427 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
428 }
429 }
430
431 if (!$term = get_term_by('slug', $name, $taxonomy)) {
432 \PublishPress_Statuses::instance()->addStatus($taxonomy, $status_obj->label, ['slug' => $name]);
433 }
434
435 $form_errors = [];
436
437 if (isset($_REQUEST['description'])) {
438 $description = stripslashes(wp_filter_nohtml_kses(trim(sanitize_text_field(wp_unslash($_REQUEST['description'])))));
439 }
440
441 if (isset($_REQUEST['status_label'])) {
442 /**
443 * Form validation for editing custom status
444 *
445 * Details
446 * - 'name' is a required field and can't conflict with existing name or slug
447 * - 'description' is optional
448 */
449
450 $label = !empty($_POST['status_label']) ? trim(sanitize_text_field(wp_unslash($_POST['status_label']))) : '';
451
452 // Check if name field was filled in
453 if (empty($label)) {
454 $form_errors['status_label'] = __('Please enter a name for the status', 'publishpress-statuses');
455 }
456
457 // Check that the name isn't numeric
458 if (is_numeric($label)) {
459 $form_errors['status_label'] = __(
460 'Please enter a valid, non-numeric name for the status.',
461 'publishpress-statuses'
462 );
463 }
464 // Check that the status name doesn't exceed 20 chars
465 $name_is_valid = true;
466
467 if (function_exists('mb_strlen')) {
468 if (mb_strlen($label) > 20) {
469 $name_is_valid = false;
470 }
471 } else {
472 if (strlen($label) > 20) {
473 $name_is_valid = false;
474 }
475 }
476
477 if (! $name_is_valid) {
478 $form_errors['status_label'] = __(
479 'Status name cannot exceed 20 characters. Please try a shorter name.',
480 'publishpress-statuses'
481 );
482 }
483
484 if (!empty($form_errors)) {
485 \PublishPress_Statuses::instance()->form_errors = $form_errors;
486 }
487 }
488
489 // Try to edit the post status
490 $args = [
491 'name' => $name,
492 ];
493
494 if (isset($label)) {
495 $args['label'] = $label;
496 }
497
498 if (isset($description)) {
499 $args['description'] = $description;
500 }
501
502
503 $labels = [];
504 $labels['save_as'] = !empty($_REQUEST['status_save_as_label']) ? sanitize_text_field(wp_unslash($_REQUEST['status_save_as_label'])) : '';
505 $labels['publish'] = !empty($_REQUEST['status_publish_label']) ? sanitize_text_field(wp_unslash($_REQUEST['status_publish_label'])) : '';
506 $args['labels'] = (object) $labels;
507
508 $name = apply_filters('publishpress_statuses_sanitize_status_name', sanitize_key($name), $taxonomy);
509
510 $status_obj = get_post_status_object($name);
511
512 if (!\PP_Statuses_Functions::empty_REQUEST('return_module')) {
513 $arr = ['message' => 'status-updated'];
514 $arr['page'] = 'pp-modules-settings';
515 $arr['settings_module'] = \PP_Statuses_Functions::REQUEST_key('return_module');
516
517 $redirect_url = \PublishPress_Statuses::getLink($arr);
518 } else {
519 $arr = ['message' => 'status-updated'];
520 $arr['page'] = 'publishpress-statuses';
521 $arr['action'] = 'edit-status-labels';
522 $arr['name'] = $name;
523
524 if (!empty($_REQUEST['pp_tab'])) {
525 $arr['pp_tab'] = str_replace('pp-', '', sanitize_key($_REQUEST['pp_tab']));
526 }
527
528 $arr = apply_filters('publishpress_status_edit_redirect_args', $arr, $status_obj);
529
530 $redirect_url = \PublishPress_Statuses::getLink($arr);
531 }
532
533 do_action('publishpress_statuses_edit_status_labels', $existing_status->name, $args);
534
535 $return = self::updateCustomStatus($existing_status->name, $args);
536
537 if (is_wp_error($return)) {
538 wp_die(esc_html__('Error updating post status labels.', 'publishpress-statuses'));
539 }
540
541 wp_redirect($redirect_url);
542 exit;
543 }
544
545 /**
546 * Update an existing custom status
547 *
548 * @param int @status_id ID for the status
549 * @param array $args Any arguments to be updated
550 *
551 * @return object|WP_Error|false $updated_status Newly updated status object
552 */
553 public static function updateCustomStatus($name, $args = [])
554 {
555 if (in_array($name, ['_pre-publish-alternate', '_disabled'])) {
556 return;
557 }
558
559 $status_obj = \PublishPress_Statuses::getStatusBy('slug', $name);
560
561 if (! $status_obj || is_wp_error($status_obj)) {
562 return new \WP_Error('invalid', __("Custom status ($name) doesn't exist.", 'publishpress-statuses'));
563 }
564
565 // Reset our internal object cache
566 \PublishPress_Statuses::instance()->clearStatusCache();
567
568 $updatedStatusId = $name;
569
570
571 // We're encoding metadata that isn't supported by default in the term's description field
572 $args_to_encode = [];
573
574 if (!empty($status_obj->_builtin)) {
575 $taxonomy = \PublishPress_Statuses::TAXONOMY_CORE_STATUS;
576
577 } elseif (in_array($name, ['_pre-publish-alternate', '_disabled'])) {
578 $taxonomy = \PublishPress_Statuses::TAXONOMY_PSEUDO_STATUS;
579
580 } elseif (!empty($status_obj->private)) {
581 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRIVACY;
582
583 } else {
584 if (!$taxonomy = apply_filters('publishpress_statuses_taxonomy', '', $status_obj)) {
585 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
586 }
587 }
588
589 // Also re-encode any existing properties, since the plugin that defined it may be temporarily deactivated.
590 if (!$term = get_term_by('slug', $name, $taxonomy)) {
591 if ($term_id = \PublishPress_Statuses::instance()->addStatus($taxonomy, $status_obj->label, ['slug' => $name])) {
592 $term = get_term_by('slug', $name, $taxonomy);
593 $updated_status_array = (array) $term;
594 }
595 }
596
597 if (!empty($status_obj)) {
598 $label_storage = \PublishPress_Statuses::instance()->options->label_storage;
599
600 switch ($label_storage) {
601 case 'user':
602 if (!empty($status_obj->pp_builtin) || !empty($status_obj->_builtin)
603 || in_array($name, ['draft', 'pending', 'publish', 'private', 'future'])
604 ) {
605 $label_locked = true;
606 }
607
608 break;
609
610 default:
611 if ((!empty($status_obj->_builtin) && ('pending' != $name))
612 || in_array($name, ['draft', 'publish', 'private', 'future'])
613 ) {
614 $label_locked = true;
615 }
616 }
617
618 if (!isset($args['label'])) {
619 $label_locked = true;
620 }
621 }
622
623 if ($term) {
624 $term_meta_fields = apply_filters('publishpress_statuses_meta_fields', ['labels', 'post_type', 'roles', 'status_parent', 'color', 'icon']);
625
626 if (!empty($label_locked)) {
627 $term_meta_fields = array_diff($term_meta_fields, ['labels']);
628 }
629
630 foreach ($args as $field => $set_value) {
631 if (in_array($field, $term_meta_fields)) {
632 if (is_array($args[$field])) {
633 $meta_val = [];
634
635 foreach ($set_value as $k => $val) {
636 $meta_val[$k] = sanitize_textarea_field($val);
637 }
638 } elseif (is_object($set_value)) {
639 $meta_val = \get_object_vars($set_value);
640
641 foreach($meta_val as $k => $val) {
642 $meta_val[$k] = sanitize_text_field(wp_unslash($val));
643 }
644
645 $meta_val = (object) $meta_val;
646 } else {
647 $meta_val = sanitize_textarea_field($set_value);
648 }
649
650 $result = update_term_meta($term->term_id, $field, $meta_val);
651
652 if (is_wp_error($result)) {
653 return $result;
654 }
655 }
656 }
657
658 $args = array_intersect_key(
659 $args,
660 array_fill_keys(['term_id', 'name', 'slug', 'label', 'term_group', 'term_taxonomy_id', 'taxonomy', 'description', 'parent'], true)
661 );
662
663 $args['description'] = (isset($args['description'])) ? $args['description'] : $term->description;
664
665 if (!empty($args['name'])) {
666 $args['slug'] = $args['name'];
667 }
668
669 if (!empty($args['label'])) {
670 $args['name'] = $args['label'];
671 }
672
673 // temp (@todo: test status slug rename
674 unset($args['slug']);
675
676 if (!empty($status_obj->_builtin) && ('pending' != $name)) {
677 $args['name'] = $status_obj->label;
678 }
679
680 if (!empty($label_locked)) {
681 $args = array_diff_key($args, array_fill_keys(['label', 'labels', 'name'], true));
682 }
683
684 $updated_status_array = wp_update_term($term->term_id, $taxonomy, $args);
685
686 if (is_wp_error($updated_status_array)) {
687 return $updated_status_array;
688 }
689 }
690
691 if (!$term || !is_array($updated_status_array) || !isset($updated_status_array['term_id'])) {
692 $term_id = (!empty($term)) ? $term->term_id : 0;
693 return new \WP_Error('custom-status-term_id', esc_html__("Error while updating the status ($name)", 'publishpress-statuses'));
694 }
695
696 $updatedStatusId = $updated_status_array['term_id'];
697
698 return \PublishPress_Statuses::getStatusBy('id', $updatedStatusId);
699 }
700
701 private function statusDeleted($status_name, $reassign_status = '') {
702
703 }
704
705
706 /**
707 * Deletes a custom status from the wp_terms table.
708 *
709 * Reassigns posts that currently have the deleted status assigned.
710 */
711 public static function deleteCustomStatus($old_status, $args = [], $reassign_status = '')
712 {
713 if ($reassign_status == $old_status) {
714 return new \WP_Error('invalid', __('Cannot reassign to the status you want to delete', 'publishpress-statuses'));
715 }
716
717 // Reset our internal object cache
718 \PublishPress_Statuses::instance()->clearStatusCache();
719
720 if (! self::is_restricted_status($old_status)) {
721 $default_status = \PublishPress_Statuses::DEFAULT_STATUS;
722
723 // If new status in $reassign, use that for all posts of the old_status
724 if (!empty($reassign_status)) {
725 if ($_status = \PublishPress_Statuses::getStatusBy('id', $reassign_status)) {
726 $new_status = $_status->name;
727 }
728 }
729
730 if (empty($new_status)) {
731 $new_status = $default_status;
732 }
733
734 if ($old_status == $default_status) {
735 $new_status = 'draft';
736
737 // @todo: If we support a custom default status, set it to draft
738 }
739
740 self::reassign_post_status($old_status, $new_status);
741
742 if (!$status_obj = \PublishPress_Statuses::getStatusBy('name', $old_status)) {
743 return false;
744 }
745
746 if (!empty($status_obj->private)) {
747 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRIVACY;
748 } elseif (!empty($status_obj->taxonomy)) {
749 $taxonomy = $status_obj->taxonomy;
750 } else {
751 $taxonomy = \PublishPress_Statuses::TAXONOMY_PRE_PUBLISH;
752 }
753
754 if ($term = get_term_by('slug', $old_status, $taxonomy)) {
755 return wp_delete_term($term->term_id, $taxonomy, $args);
756 }
757
758 } else {
759 return new \WP_Error(
760 'restricted',
761 __('Restricted status ', 'publishpress-statuses') . '(' . \PublishPress_Statuses::getStatusBy(
762 'id',
763 $old_status
764 )->label . ')'
765 );
766 }
767 }
768
769 public static function handleAjaxDeleteStatus() {
770 check_ajax_referer('custom-status-sortable');
771
772 if ($status_name = \PP_Statuses_Functions::REQUEST_key('delete_status')) {
773 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
774 self::printAjaxResponse('error', esc_html(__('Sorry, you are not allowed to access this page.')));
775 }
776
777 if ($status = \PublishPress_Statuses::getStatusBy('slug', $status_name)) {
778 if (!empty($status->_builtin) || !empty($status->pp_builtin)) {
779 self::printAjaxResponse('error', esc_html(__('Sorry, you are not allowed to access this page.')));
780 return;
781 }
782
783 $return = self::deleteCustomStatus($status_name);
784
785 if (is_wp_error($return)) {
786 self::printAjaxResponse('error', __('Could not delete the status: ', 'publishpress-statuses'));
787 } else {
788 self::printAjaxResponse('success', __('Status deleted', 'publishpress-statuses'));
789 }
790 } else {
791 self::printAjaxResponse('error', esc_html__('Status does not exist.', 'publishpress-statuses'));
792 }
793 }
794 }
795
796 /**
797 * Handle an ajax request to update the order of custom statuses
798 *
799 * @since 0.7
800 */
801 public static function handleAjaxUpdateStatusPositions()
802 {
803 check_ajax_referer('custom-status-sortable');
804
805 if (!current_user_can('manage_options') && !current_user_can('pp_manage_statuses')) {
806 self::printAjaxResponse('error', esc_html(__('Sorry, you are not allowed to access this page.')));
807 }
808
809 if (!isset($_POST['status_positions']) || !is_array($_POST['status_positions'])) {
810 self::printAjaxResponse('error', __('Status positions were not sent.', 'publishpress-statuses'));
811 }
812
813 update_option('publishpress_status_positions',
814 array_values(
815 array_filter(
816 array_map('sanitize_key', $_POST['status_positions'])
817 )
818 )
819 );
820
821 // @todo: update 'publishpress_disabled_statuses' based on ordering relative to '_disabled'
822
823 if (!empty($_REQUEST['status_hierarchy'])) {
824 $status_parents = [];
825
826 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
827 foreach ($_REQUEST['status_hierarchy'] as $arr) { // (elements of multi-dim array sanitized below)
828 $status_name = str_replace('status_row_', '', sanitize_key($arr['id']));
829
830 $status_parents[$status_name] = '';
831
832 if (!empty($arr['children']) && !empty($status_name)) {
833 foreach ($arr['children'] as $child_arr) {
834 $child_status_name = str_replace('status_row_', '', sanitize_key($child_arr['id']));
835
836 $status_obj = get_post_status_object($child_status_name);
837 if (!empty($status_obj) && !empty($status_obj->private)) {
838 continue;
839 }
840
841 $status_parents[$child_status_name] = $status_name;
842 }
843 }
844 }
845
846 $statuses = \PublishPress_Statuses::getPostStati(
847 [],
848 ['output' => 'object'],
849 ['show_disabled' => true, 'context' => 'load']
850 );
851
852 // Update any modified status_parent value as an term meta value
853 foreach ($status_parents as $status_name => $edited_status_parent) {
854 $current_status_parent = (!empty($statuses[$status_name]) && !empty($statuses[$status_name]->status_parent))
855 ? $statuses[$status_name] : '';
856
857 if ($edited_status_parent == $current_status_parent) {
858 continue; // no change
859 }
860
861 $result = self::updateCustomStatus($status_name, ['status_parent' => $edited_status_parent]);
862
863 if (is_wp_error($result)) {
864 self::printAjaxResponse('error', $result->get_error_message());
865 }
866 }
867 }
868
869 self::printAjaxResponse('success', esc_html__('Status order updated.', 'publishpress-statuses'));
870 }
871
872 /**
873 * Take a status and a message, JSON encode and print
874 *
875 * @param string $status Whether it was a 'success' or an 'error'
876 * @param string $message
877 * @param array $data
878 *
879 * @since 0.7
880 *
881 */
882 public static function printAjaxResponse($status, $message = '', $data = null)
883 {
884 \PP_Statuses_Functions::printAjaxResponse($status, $message, $data);
885 }
886
887 /**
888 * Determines whether the slug indicated belongs to a restricted status or not
889 *
890 * @param string $slug Slug of the status
891 *
892 * @return bool $restricted True if restricted, false if not
893 */
894 private static function is_restricted_status($slug)
895 {
896 switch ($slug) {
897 case 'publish':
898 case 'private':
899 case 'future':
900 case 'pending':
901 case 'draft':
902 case 'new':
903 case 'inherit':
904 case 'auto-draft':
905 case 'trash':
906 $restricted = true;
907 break;
908
909 default:
910 $restricted = false;
911 break;
912 }
913
914 return $restricted;
915 }
916
917 public static function settings_validate_and_save()
918 {
919 if (!wp_verify_nonce(\PP_Statuses_Functions::POST_key('_wpnonce'), 'edit-publishpress-settings')
920 || !current_user_can('manage_options')
921 ) {
922 wp_die(esc_html__('Cheatin&#8217; uh?'));
923 }
924
925 if (!isset($_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['_wp_http_referer'], $_POST['publishpress_module_name'], $_POST['submit']) || !is_admin()) {
926 return false;
927 }
928
929 if (($_POST['action'] != 'update') ||
930 (!in_array('publishpress_statuses', (array)$_POST['publishpress_module_name']))
931 ) {
932 return false;
933 }
934
935 $module = \PublishPress_Statuses::instance();
936
937 $new_options = [];
938
939 $options = array_merge($module->default_options, ['privacy_statuses_enabled' => 1]);
940
941 foreach ($options as $option_name => $current_val) {
942 if ('loaded_once' == $option_name) {
943 continue;
944 }
945
946 if (isset($_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name])) {
947 switch ($option_name) {
948 case 'privacy_statuses_enabled' :
949 update_option('presspermit_privacy_statuses_enabled', intval($_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name]));
950 break;
951
952 case 'post_types':
953 $new_options[$option_name] = array_intersect_key(
954 array_map('intval', (array) $_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name]),
955 \PublishPress_Statuses::instance()->get_supported_post_types()
956 );
957
958 break;
959
960 case 'default_privacy':
961 case 'force_default_privacy':
962 $new_options[$option_name] = array_intersect_key(
963 array_map('sanitize_key', (array) $_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name]),
964 \PublishPress_Statuses::instance()->get_supported_post_types()
965 );
966
967 break;
968
969 case 'force_editor_detection':
970 case 'label_storage':
971 case 'moderation_statuses_default_by_sequence':
972 $new_options[$option_name] = sanitize_key($_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name]);
973 break;
974
975 default:
976 $new_options[$option_name] = (int) $_POST[\PublishPress_Statuses::SETTINGS_SLUG][$option_name];
977 }
978 } else {
979 $new_options[$option_name] = $current_val;
980 }
981 }
982
983 // Cast our object and save the data.
984 update_option('publishpress_custom_status_options', (object) $new_options);
985
986 // Import / Backup Operations
987 if (\PP_Statuses_Functions::is_POST('publishpress_statuses_import_operation', 'do_status_control_import')) {
988 update_option('pp_statuses_force_status_control_import', true);
989 update_option('pp_statuses_force_planner_import', true);
990
991 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_import_operation', 'do_planner_import')) {
992 update_option('pp_statuses_force_planner_import', true);
993
994 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_import_operation', 'do_planner_import_only')) {
995 update_option('pp_statuses_skip_status_control_import', true);
996 update_option('pp_statuses_force_planner_import', true);
997
998 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'backup_status_properties')) {
999 update_option('pp_statuses_set_backup_props', true);
1000
1001 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_colors')) {
1002 update_option('pp_statuses_restore_backup_colors', true);
1003
1004 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_icons')) {
1005 update_option('pp_statuses_restore_backup_icons', true);
1006
1007 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_labels')) {
1008 update_option('pp_statuses_restore_backup_labels', true);
1009
1010 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_post_types')) {
1011 update_option('pp_statuses_restore_backup_post_types', true);
1012
1013 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_colors_auto')) {
1014 update_option('pp_statuses_restore_autobackup_colors', true);
1015
1016 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_icons_auto')) {
1017 update_option('pp_statuses_restore_autobackup_icons', true);
1018
1019 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_labels_auto')) {
1020 update_option('pp_statuses_restore_autobackup_labels', true);
1021
1022 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'restore_status_post_types_auto')) {
1023 update_option('pp_statuses_restore_autobackup_post_types', true);
1024
1025 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_colors')) {
1026 update_option('pp_statuses_default_colors', true);
1027
1028 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_icons')) {
1029 update_option('pp_statuses_default_icons', true);
1030
1031 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_labels')) {
1032 update_option('pp_statuses_default_labels', true);
1033
1034 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_post_types')) {
1035 update_option('pp_statuses_default_post_types', true);
1036
1037 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_colors_planner')) {
1038 update_option('pp_statuses_default_planner_colors', true);
1039
1040 } elseif (\PP_Statuses_Functions::is_POST('publishpress_statuses_backup_operation', 'default_status_icons_planner')) {
1041 update_option('pp_statuses_default_planner_icons', true);
1042 }
1043
1044 // Redirect back to the settings page that was submitted without any previous messages
1045 $goback = add_query_arg('message', 'settings-updated', remove_query_arg(['message'], wp_get_referer()));
1046
1047 if (!empty($_REQUEST['pp_tab']) && ('workflow' != $_REQUEST['pp_tab'])) {
1048 $goback = add_query_arg('pp_tab', esc_attr(wp_unslash($_REQUEST['pp_tab'])), $goback); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1049 }
1050
1051 wp_safe_redirect($goback);
1052
1053 exit;
1054 }
1055
1056 /**
1057 * Assign a new status to all posts currently set to another specified status.
1058 *
1059 * @param string $old_status Slug for the old status
1060 * @param string $new_status Slug for the new status
1061 */
1062 private static function reassign_post_status($old_status, $new_status = '')
1063 {
1064 global $wpdb;
1065
1066 if (empty($new_status)) {
1067 $new_status = \PublishPress_Statuses::DEFAULT_STATUS;
1068 }
1069
1070 // phpcs Note: Direct DB query for efficient and reliable update of all existing $old_status posts to $new_status
1071
1072 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1073 $result = $wpdb->update(
1074 $wpdb->posts,
1075 ['post_status' => $new_status],
1076 ['post_status' => $old_status],
1077 ['%s']
1078 );
1079
1080 wp_cache_flush();
1081 }
1082 }
1083