PluginProbe
Edit Flow / 0.10.2
Edit Flow v0.10.2
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 / common / php / class-module.php

class-module.php in Edit Flow 0.10.2, at common/php/class-module.php

705 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base class for Edit Flow modules.
4 *
5 * @package EditFlow
6 */
7
8 if ( ! class_exists( 'EF_Module' ) ) {
9
10 /**
11 * Base class any Edit Flow module should extend.
12 */
13 class EF_Module {
14
15 /**
16 * Published post statuses.
17 *
18 * @var array
19 */
20 public $published_statuses = array(
21 'publish',
22 'future',
23 'private',
24 );
25
26 /**
27 * URL to the module directory.
28 *
29 * @var string
30 */
31 public $module_url;
32
33 /**
34 * Module data object.
35 *
36 * @var object
37 */
38 public $module;
39
40 /**
41 * Constructor.
42 */
43 public function __construct() {}
44
45 /**
46 * Returns whether the current module is enabled.
47 *
48 * @since 0.9.1
49 *
50 * @return bool True if the module is enabled, false otherwise.
51 */
52 public function is_enabled() {
53 return 'on' === $this->module->options->enabled;
54 }
55
56 /**
57 * Returns whether the module with the given name is enabled.
58 *
59 * @since 0.7
60 *
61 * @param string $slug Slug of the module to check.
62 * @return bool True if the module is enabled, false otherwise.
63 */
64 public function module_enabled( $slug ) {
65 global $edit_flow;
66
67 return isset( $edit_flow->$slug ) && $edit_flow->$slug->is_enabled();
68 }
69
70 /**
71 * Returns whether analytics has been enabled or not.
72 *
73 * It's only enabled if the site is a production WPVIP site.
74 *
75 * @since 0.10.0
76 *
77 * @return bool True if analytics is enabled, false otherwise.
78 */
79 public function is_analytics_enabled() {
80 // Check if the site is a production WPVIP site and only then enable it.
81 $is_analytics_enabled = $this->is_vip_site( true );
82
83 // Filter to disable it.
84 $is_analytics_enabled = apply_filters( 'ef_should_analytics_be_enabled', $is_analytics_enabled );
85
86 return $is_analytics_enabled;
87 }
88
89 /**
90 * Check if the site is a WPVIP site.
91 *
92 * @since 0.10.0
93 *
94 * @param bool $only_production Whether to only allow production sites to be considered WPVIP sites.
95 * @return bool True if it is a WPVIP site, false otherwise.
96 */
97 protected function is_vip_site( $only_production = false ) {
98 $is_vip_site = defined( 'VIP_GO_ENV' )
99 && defined( 'WPCOM_SANDBOXED' ) && constant( 'WPCOM_SANDBOXED' ) === false
100 && defined( 'FILES_CLIENT_SITE_ID' );
101
102 if ( $only_production ) {
103 $is_vip_site = $is_vip_site && defined( 'VIP_GO_ENV' ) && 'production' === constant( 'VIP_GO_ENV' );
104 }
105
106 return $is_vip_site;
107 }
108
109 /**
110 * Gets an array of allowed post types for a module.
111 *
112 * @return array Post-type-slug => post-type-label.
113 */
114 public function get_all_post_types() {
115
116 $allowed_post_types = array(
117 'post' => __( 'Post', 'edit-flow' ),
118 'page' => __( 'Page', 'edit-flow' ),
119 );
120 $custom_post_types = $this->get_supported_post_types_for_module();
121
122 foreach ( $custom_post_types as $custom_post_type => $args ) {
123 $allowed_post_types[ $custom_post_type ] = $args->label;
124 }
125 return $allowed_post_types;
126 }
127
128 /**
129 * Cleans up the 'on' and 'off' for post types on a given module (so we don't get warnings all over).
130 *
131 * For every post type that doesn't explicitly have the 'on' value, turn it 'off'.
132 * If add_post_type_support() has been used anywhere (legacy support), inherit the state.
133 *
134 * @since 0.7
135 *
136 * @param array $module_post_types Current state of post type options for the module.
137 * @param string $post_type_support What the feature is called for post_type_support (e.g. 'ef_calendar').
138 * @return array The setting for each post type, normalized based on rules.
139 */
140 public function clean_post_type_options( $module_post_types = array(), $post_type_support = null ) {
141 $normalized_post_type_options = array();
142 $all_post_types = array_keys( $this->get_all_post_types() );
143 foreach ( $all_post_types as $post_type ) {
144 if ( ( isset( $module_post_types[ $post_type ] ) && 'on' == $module_post_types[ $post_type ] ) || post_type_supports( $post_type, $post_type_support ) ) {
145 $normalized_post_type_options[ $post_type ] = 'on';
146 } else {
147 $normalized_post_type_options[ $post_type ] = 'off';
148 }
149 }
150 return $normalized_post_type_options;
151 }
152
153 /**
154 * Get all of the possible post types that can be used with a given module.
155 *
156 * @since 0.7.2
157 *
158 * @param object $module The full module.
159 * @return array An array of post type objects.
160 */
161 public function get_supported_post_types_for_module( $module = null ) {
162
163 $pt_args = array(
164 '_builtin' => false,
165 'public' => true,
166 );
167 $pt_args = apply_filters( 'edit_flow_supported_module_post_types_args', $pt_args, $module );
168 return get_post_types( $pt_args, 'objects' );
169 }
170
171 /**
172 * Collect all of the active post types for a given module.
173 *
174 * @since 0.7
175 *
176 * @param object $module Module's data.
177 * @return array All of the post types that are 'on'.
178 */
179 public function get_post_types_for_module( $module ) {
180
181 $post_types = array();
182 if ( isset( $module->options->post_types ) && is_array( $module->options->post_types ) ) {
183 foreach ( $module->options->post_types as $post_type => $value ) {
184 if ( 'on' == $value ) {
185 $post_types[] = $post_type;
186 }
187 }
188 }
189 return $post_types;
190 }
191
192 /**
193 * Get all of the currently available post statuses.
194 *
195 * This should be used in favor of calling $edit_flow->custom_status->get_custom_statuses() directly.
196 *
197 * @since 0.7
198 *
199 * @return array All of the post statuses that aren't a published state.
200 */
201 public function get_post_statuses() {
202 global $edit_flow;
203
204 if ( $this->module_enabled( 'custom_status' ) ) {
205 return $edit_flow->custom_status->get_custom_statuses();
206 } else {
207 return $this->get_core_post_statuses();
208 }
209 }
210
211 /**
212 * Get core's 'draft' and 'pending' post statuses, but include our special attributes.
213 *
214 * @since 0.8.1
215 *
216 * @return array
217 */
218 protected function get_core_post_statuses() {
219
220 return array(
221 (object) array(
222 'name' => __( 'Draft', 'edit-flow' ),
223 'description' => '',
224 'slug' => 'draft',
225 'position' => 1,
226 ),
227 (object) array(
228 'name' => __( 'Pending Review', 'edit-flow' ),
229 'description' => '',
230 'slug' => 'pending',
231 'position' => 2,
232 ),
233 );
234 }
235
236 /**
237 * Gets the name of the default custom status. If custom statuses are disabled,
238 * returns 'draft'.
239 *
240 * @return string Name of the status.
241 */
242 public function get_default_post_status() {
243
244 // Check if custom status module is enabled.
245 $custom_status_module = EditFlow()->custom_status->module->options;
246
247 if ( 'on' == $custom_status_module->enabled ) {
248 return $custom_status_module->default_status;
249 } else {
250 return 'draft';
251 }
252 }
253
254 /**
255 * Filter to all posts with a given post status (can be a custom status or a built-in status) and optional custom post type.
256 *
257 * @since 0.7
258 *
259 * @param string $slug The slug for the post status to which to filter.
260 * @param string $post_type Optional post type to which to filter.
261 * @return string An edit.php link to all posts with the given post status and, optionally, the given post type.
262 */
263 public function filter_posts_link( $slug, $post_type = 'post' ) {
264 $filter_link = add_query_arg( 'post_status', $slug, get_admin_url( null, 'edit.php' ) );
265 if ( 'post' != $post_type && in_array( $post_type, get_post_types( '', 'names' ) ) ) {
266 $filter_link = add_query_arg( 'post_type', $post_type, $filter_link );
267 }
268 return $filter_link;
269 }
270
271 /**
272 * Enqueue any resources (CSS or JS) associated with datepicker functionality.
273 *
274 * @since 0.7
275 */
276 public function enqueue_datepicker_resources() {
277
278 wp_enqueue_script( 'jquery-ui-datepicker' );
279
280 // Build script dependencies. Add wp-data for Gutenberg integration if available.
281 $dependencies = array( 'jquery', 'jquery-ui-datepicker' );
282 if ( function_exists( 'use_block_editor_for_post' ) && use_block_editor_for_post( get_post() ) ) {
283 $dependencies[] = 'wp-data';
284 }
285
286 wp_enqueue_script( 'edit_flow-date_picker', EDIT_FLOW_URL . 'common/js/ef_date.js', $dependencies, EDIT_FLOW_VERSION, true );
287 wp_add_inline_script( 'edit_flow-date_picker', sprintf( 'var ef_week_first_day = %s;', wp_json_encode( get_option( 'start_of_week' ) ) ), 'before' );
288
289 // Now styles.
290 wp_enqueue_style( 'jquery-ui-datepicker', EDIT_FLOW_URL . 'common/css/jquery.ui.datepicker.css', array( 'wp-jquery-ui-dialog' ), EDIT_FLOW_VERSION, 'screen' );
291 wp_enqueue_style( 'jquery-ui-theme', EDIT_FLOW_URL . 'common/css/jquery.ui.theme.css', false, EDIT_FLOW_VERSION, 'screen' );
292 }
293
294 /**
295 * Checks for the current post type.
296 *
297 * @since 0.7
298 *
299 * @return string|null The post type we've found, or null if no post type.
300 */
301 public function get_current_post_type() {
302 global $post, $typenow, $pagenow, $current_screen;
303 // get_post() needs a variable.
304 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reading post type from REQUEST for context detection, not processing form data.
305 $post_id = isset( $_REQUEST['post'] ) ? (int) $_REQUEST['post'] : false;
306
307 if ( $post && $post->post_type ) {
308 $post_type = $post->post_type;
309 } elseif ( $typenow ) {
310 $post_type = $typenow;
311 } elseif ( $current_screen && ! empty( $current_screen->post_type ) ) {
312 $post_type = $current_screen->post_type;
313 } elseif ( isset( $_REQUEST['post_type'] ) ) {
314 $post_type = sanitize_key( $_REQUEST['post_type'] );
315 } elseif ( 'post.php' == $pagenow
316 && $post_id
317 && ! empty( get_post( $post_id )->post_type ) ) {
318 $post_type = get_post( $post_id )->post_type;
319 } elseif ( 'edit.php' == $pagenow && empty( $_REQUEST['post_type'] ) ) {
320 $post_type = 'post';
321 } else {
322 $post_type = null;
323 }
324 // phpcs:enable WordPress.Security.NonceVerification.Recommended
325
326 return $post_type;
327 }
328
329 /**
330 * Wrapper for the get_user_meta() function so we can replace it if we need to.
331 *
332 * @since 0.7
333 *
334 * @param int $user_id Unique ID for the user.
335 * @param string $key Key to search against.
336 * @param bool $string Whether or not to return just one value.
337 * @return string|bool|array Whatever the stored value was.
338 */
339 public function get_user_meta( $user_id, $key, $string = true ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound -- Legacy parameter name.
340
341 $response = null;
342 $response = apply_filters( 'ef_get_user_meta', $response, $user_id, $key, $string );
343 if ( ! is_null( $response ) ) {
344 return $response;
345 }
346
347 return get_user_meta( $user_id, $key, $string );
348 }
349
350 /**
351 * Wrapper for the update_user_meta() function so we can replace it if we need to.
352 *
353 * @since 0.7
354 *
355 * @param int $user_id Unique ID for the user.
356 * @param string $key Key to search against.
357 * @param string|bool|array $value The value to store.
358 * @param string|bool|array $previous Previous value to replace.
359 * @return bool Whether we were successful in saving.
360 */
361 public function update_user_meta( $user_id, $key, $value, $previous = null ) {
362
363 $response = null;
364 $response = apply_filters( 'ef_update_user_meta', $response, $user_id, $key, $value, $previous );
365 if ( ! is_null( $response ) ) {
366 return $response;
367 }
368
369 return update_user_meta( $user_id, $key, $value, $previous );
370 }
371
372 /**
373 * Take a status and a message, JSON encode and print.
374 *
375 * @since 0.7
376 *
377 * @param string $status Whether it was a 'success' or an 'error'.
378 * @param string $message Optional message to include.
379 * @param int $http_code HTTP response code.
380 */
381 protected function print_ajax_response( $status, $message = '', $http_code = 200 ) {
382 header( 'Content-type: application/json;' );
383 http_response_code( $http_code );
384 echo wp_json_encode(
385 array(
386 'status' => $status,
387 'message' => $message,
388 )
389 );
390 wp_die();
391 }
392
393 /**
394 * Whether or not the current page is a post management page.
395 *
396 * A post management page is where the module's functionality is actually
397 * needed, such as post editing pages (post.php, post-new.php) or post listing
398 * pages (edit.php) for supported post types.
399 *
400 * @since 0.7
401 * @since 0.10.0 Actually implemented instead of returning true. Renamed from
402 * is_whitelisted_functional_view().
403 *
404 * @see https://github.com/Automattic/Edit-Flow/issues/351
405 *
406 * @param string $module_name (Optional) Module name to check against.
407 * @return bool Whether the current page is a post management page for the module.
408 */
409 public function is_post_management_page( $module_name = null ) {
410 global $pagenow, $edit_flow;
411
412 // Only load on post editing and listing pages.
413 $functional_pages = [ 'post.php', 'post-new.php', 'edit.php' ];
414 if ( ! in_array( $pagenow, $functional_pages, true ) ) {
415 return false;
416 }
417
418 // Get the current post type.
419 $current_post_type = $this->get_current_post_type();
420 if ( ! $current_post_type ) {
421 return false;
422 }
423
424 // If a module name is specified, check if this post type is supported by that module.
425 if ( $module_name && isset( $edit_flow->modules->$module_name ) ) {
426 $module = $edit_flow->modules->$module_name;
427 $supported_post_types = $this->get_post_types_for_module( $module );
428 if ( ! in_array( $current_post_type, $supported_post_types, true ) ) {
429 return false;
430 }
431 }
432
433 return true;
434 }
435
436 /**
437 * Whether or not the current page is an Edit Flow settings view (either main or module).
438 *
439 * Determination is based on $pagenow, $_GET['page'], and the module's $settings_slug.
440 * If there's no module name specified, it will return true against all Edit Flow settings views.
441 *
442 * @since 0.7
443 *
444 * @param string $module_name Optional module name to check against.
445 * @return bool Return true if it is.
446 */
447 public function is_whitelisted_settings_view( $module_name = null ) {
448 global $pagenow, $edit_flow;
449
450 // All of the settings views are based on admin.php and a $_GET['page'] parameter.
451 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Checking page parameter for context, not processing form data.
452 if ( 'admin.php' != $pagenow || ! isset( $_GET['page'] ) ) {
453 return false;
454 }
455
456 // Load all of the modules that have a settings slug/ callback for the settings page.
457 foreach ( $edit_flow->modules as $mod_name => $mod_data ) {
458 if ( isset( $mod_data->options->enabled ) && 'on' == $mod_data->options->enabled && $mod_data->configure_page_cb ) {
459 $settings_view_slugs[] = $mod_data->settings_slug;
460 }
461 }
462
463 // The current page better be in the array of registered settings view slugs.
464 if ( ! in_array( $_GET['page'], $settings_view_slugs ) ) {
465 return false;
466 }
467
468 if ( $module_name && $edit_flow->modules->$module_name->settings_slug != $_GET['page'] ) {
469 return false;
470 }
471 // phpcs:enable WordPress.Security.NonceVerification.Recommended
472
473 return true;
474 }
475
476
477 /**
478 * This is a hack, Hack, HACK!!!
479 *
480 * Encode all of the given arguments as a serialized array, and then base64_encode.
481 * Used to store extra data in a term's description field.
482 *
483 * @since 0.7
484 *
485 * @param array $args The arguments to encode.
486 * @return string Arguments encoded in base64.
487 */
488 public function get_encoded_description( $args = array() ) {
489 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Required for term description storage.
490 return base64_encode( maybe_serialize( $args ) );
491 }
492
493 /**
494 * If given an encoded string from a term's description field,
495 * return an array of values. Otherwise, return the original string.
496 *
497 * @since 0.7
498 *
499 * @param string $string_to_unencode Possibly encoded string.
500 * @return array Array if string was encoded, otherwise the string as the 'description' field.
501 */
502 public function get_unencoded_description( $string_to_unencode ) {
503 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Required for term description retrieval.
504 return maybe_unserialize( base64_decode( $string_to_unencode ) );
505 }
506
507 /**
508 * Get the publicly accessible URL for the module based on the filename.
509 *
510 * @since 0.7
511 *
512 * @param string $file File path for the module.
513 * @return string Publicly accessible URL for the module.
514 */
515 public function get_module_url( $file ) {
516 $module_url = plugins_url( '/', $file );
517 return trailingslashit( $module_url );
518 }
519
520 /**
521 * Displays a list of users that can be selected!
522 *
523 * @since 0.7
524 *
525 * @todo Add pagination support for blogs with billions of users.
526 *
527 * @param array|null $selected Selected users.
528 * @param array|null $args Optional arguments for the form.
529 */
530 public function users_select_form( $selected = null, $args = null ) {
531
532 // Set up arguments.
533 $defaults = array(
534 'list_class' => 'ef-users-select-form',
535 'input_id' => 'ef-selected-users',
536 );
537 $parsed_args = wp_parse_args( $args, $defaults );
538 extract( $parsed_args, EXTR_SKIP );
539
540 $args = array(
541 'capability' => 'publish_posts',
542 'fields' => array(
543 'ID',
544 'display_name',
545 'user_nicename',
546 'user_email',
547 ),
548 'orderby' => 'display_name',
549 );
550 $args = apply_filters( 'ef_users_select_form_get_users_args', $args );
551
552 $users = get_users( $args );
553
554 if ( ! is_array( $selected ) ) {
555 $selected = array();
556 }
557 ?>
558
559 <?php if ( ! empty( $users ) ) : ?>
560 <ul class="<?php echo esc_attr( $list_class ); ?>">
561 <?php
562 foreach ( $users as $user ) :
563 $checked = ( in_array( $user->ID, $selected ) ) ? 'checked="checked"' : '';
564 // Add a class to checkbox of current user so we know not to add them in notified list during notifiedMessage() js function.
565 $current_user_class = ( get_current_user_id() == $user->ID ) ? 'class="post_following_list-current_user" ' : '';
566 ?>
567 <li>
568 <label for="<?php echo esc_attr( $input_id . '-' . $user->ID ); ?>">
569 <div class="ef-user-subscribe-actions">
570 <?php do_action( 'ef_user_subscribe_actions', $user->ID, $checked ); ?>
571 <input type="checkbox" id="<?php echo esc_attr( $input_id . '-' . $user->ID ); ?>" name="<?php echo esc_attr( $input_id ); ?>[]" value="<?php echo esc_attr( $user->ID ); ?>"
572 <?php
573 echo esc_attr( $checked );
574 echo esc_attr( $current_user_class );
575 ?>
576 />
577 </div>
578
579 <span class="ef-user_displayname"><?php echo esc_html( $user->display_name ); ?></span>
580 <?php
581 /**
582 * Filters the secondary user identifier shown in the notifications list.
583 *
584 * By default, shows user_nicename for unique identification without exposing email.
585 * Return user_email to show email addresses, or empty string to hide.
586 *
587 * @since 0.10.1
588 *
589 * @param string $identifier The secondary identifier to display.
590 * @param object $user The user object.
591 */
592 $secondary_identifier = apply_filters( 'ef_user_secondary_identifier', $user->user_nicename, $user );
593 if ( ! empty( $secondary_identifier ) ) :
594 ?>
595 <span class="ef-user_useremail"><?php echo esc_html( $secondary_identifier ); ?></span>
596 <?php endif; ?>
597 </label>
598 </li>
599 <?php endforeach; ?>
600 </ul>
601 <?php endif; ?>
602 <?php
603 }
604
605 /**
606 * Adds an array of capabilities to a role.
607 *
608 * @since 0.7
609 *
610 * @param string $role A standard WP user role like 'administrator' or 'author'.
611 * @param array $caps One or more user caps to add.
612 */
613 public function add_caps_to_role( $role, $caps ) {
614
615 // In some contexts, we don't want to add caps to roles.
616 if ( apply_filters( 'ef_kill_add_caps_to_role', false, $role, $caps ) ) {
617 return;
618 }
619
620 global $wp_roles;
621
622 if ( $wp_roles->is_role( $role ) ) {
623 $role = get_role( $role );
624 foreach ( $caps as $cap ) {
625 $role->add_cap( $cap );
626 }
627 }
628 }
629
630 /**
631 * Add settings help menus to our module screens if the values exist.
632 *
633 * Auto-registered in Edit_Flow::register_module().
634 *
635 * @since 0.7
636 */
637 public function action_settings_help_menu() {
638
639 $screen = get_current_screen();
640
641 if ( ! method_exists( $screen, 'add_help_tab' ) ) {
642 return;
643 }
644
645 if ( 'edit-flow_page_' . $this->module->settings_slug != $screen->id ) {
646 return;
647 }
648
649 // Make sure we have all of the required values for our tab.
650 if ( isset( $this->module->settings_help_tab['id'], $this->module->settings_help_tab['title'], $this->module->settings_help_tab['content'] ) ) {
651 $screen->add_help_tab( $this->module->settings_help_tab );
652
653 if ( isset( $this->module->settings_help_sidebar ) ) {
654 $screen->set_help_sidebar( $this->module->settings_help_sidebar );
655 }
656 }
657 }
658
659 /**
660 * Upgrade the term descriptions for all of the terms in a given taxonomy.
661 *
662 * @param string $taxonomy The taxonomy to upgrade.
663 */
664 public function upgrade_074_term_descriptions( $taxonomy ) {
665 $args = array(
666 'hide_empty' => false,
667 );
668 // This is migration code, so it's being left as is for now.
669 // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found
670 $terms = get_terms( $taxonomy, $args );
671 foreach ( $terms as $term ) {
672 // If we can detect that this term already follows the new scheme, let's skip it.
673 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Required for term description retrieval.
674 $maybe_serialized = base64_decode( $term->description );
675 if ( is_serialized( $maybe_serialized ) ) {
676 continue;
677 }
678
679 $description_args = array();
680 // This description has been JSON-encoded, so let's decode it.
681 if ( 0 === strpos( $term->description, '{' ) ) {
682 $string_to_unencode = stripslashes( htmlspecialchars_decode( $term->description ) );
683 $unencoded_array = json_decode( $string_to_unencode, true );
684 // Only continue processing if it actually was an array. Otherwise, set to the original string.
685 if ( is_array( $unencoded_array ) ) {
686 foreach ( $unencoded_array as $key => $value ) {
687 // html_entity_decode only works on strings but sometimes we store nested arrays.
688 if ( ! is_array( $value ) ) {
689 $description_args[ $key ] = html_entity_decode( $value, ENT_QUOTES );
690 } else {
691 $description_args[ $key ] = $value;
692 }
693 }
694 }
695 } else {
696 $description_args['description'] = $term->description;
697 }
698 $new_description = $this->get_encoded_description( $description_args );
699 wp_update_term( $term->term_id, $taxonomy, array( 'description' => $new_description ) );
700 }
701 }
702 }
703
704 }
705