| 1 |
<?php |
| 2 |
/** |
| 3 |
* Editorial Metadata module for Edit Flow. |
| 4 |
* |
| 5 |
* This class gives publishers arbitrary structured content details to go along with every post. |
| 6 |
* |
| 7 |
* @package EditFlow |
| 8 |
* |
| 9 |
* @author sbressler, danielbachhuber |
| 10 |
* |
| 11 |
* Ways to test and play with this class: |
| 12 |
* 1) Create a new term by selecting Editorial Metadata from the Edit Flow settings |
| 13 |
* 2) Edit an existing term (slug, description, etc.) |
| 14 |
* 3) Create a post and assign metadata to it |
| 15 |
* 4) Look at the list of terms again - the count should go up! |
| 16 |
* 5) Play with adding more metadata to a post |
| 17 |
* 6) Clear the metadata for a single term in a post and watch the count go down! |
| 18 |
* 6) Delete a term and note the metadata disappears from posts |
| 19 |
* 7) Re-add the term (same slug) and the metadata returns! |
| 20 |
* |
| 21 |
* Improvements to make: |
| 22 |
* |
| 23 |
* @todo Abstract the permissions check for management to class level |
| 24 |
*/ |
| 25 |
|
| 26 |
if ( ! class_exists( 'EF_Editorial_Metadata' ) ) { |
| 27 |
|
| 28 |
/** |
| 29 |
* Editorial Metadata module class. |
| 30 |
* |
| 31 |
* Allows publishers to add arbitrary structured content details to posts. |
| 32 |
*/ |
| 33 |
class EF_Editorial_Metadata extends EF_Module { |
| 34 |
|
| 35 |
/** |
| 36 |
* The name of the taxonomy we're going to register for editorial metadata. |
| 37 |
*/ |
| 38 |
// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase |
| 39 |
const metadata_taxonomy = 'ef_editorial_meta'; |
| 40 |
// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase |
| 41 |
const metadata_postmeta_key = '_ef_editorial_meta'; |
| 42 |
|
| 43 |
/** |
| 44 |
* Module name. |
| 45 |
* |
| 46 |
* @var string |
| 47 |
*/ |
| 48 |
public $module_name = 'editorial_metadata'; |
| 49 |
|
| 50 |
/** |
| 51 |
* Cache for editorial metadata terms. |
| 52 |
* |
| 53 |
* @var array |
| 54 |
*/ |
| 55 |
private $editorial_metadata_terms_cache = array(); |
| 56 |
|
| 57 |
/** |
| 58 |
* Construct the EF_Editorial_Metadata class. |
| 59 |
*/ |
| 60 |
public function __construct() { |
| 61 |
|
| 62 |
$this->module_url = $this->get_module_url( __FILE__ ); |
| 63 |
// Register the module with Edit Flow. |
| 64 |
$args = array( |
| 65 |
'title' => __( 'Editorial Metadata', 'edit-flow' ), |
| 66 |
'short_description' => __( 'Track details about your posts in progress.', 'edit-flow' ), |
| 67 |
'extended_description' => __( 'Log details on every assignment using configurable editorial metadata. It’s completely customizable; create fields for everything from due date to location to contact information to role assignments.', 'edit-flow' ), |
| 68 |
'module_url' => $this->module_url, |
| 69 |
'img_url' => $this->module_url . 'lib/editorial_metadata_s128.png', |
| 70 |
'slug' => 'editorial-metadata', |
| 71 |
'default_options' => array( |
| 72 |
'enabled' => 'on', |
| 73 |
'post_types' => array( |
| 74 |
'post' => 'on', |
| 75 |
'page' => 'off', |
| 76 |
), |
| 77 |
), |
| 78 |
'messages' => array( |
| 79 |
'term-added' => __( 'Metadata term added.', 'edit-flow' ), |
| 80 |
'term-updated' => __( 'Metadata term updated.', 'edit-flow' ), |
| 81 |
'term-missing' => __( "Metadata term doesn't exist.", 'edit-flow' ), |
| 82 |
'term-deleted' => __( 'Metadata term deleted.', 'edit-flow' ), |
| 83 |
'term-position-updated' => __( 'Term order updated.', 'edit-flow' ), |
| 84 |
'term-visibility-changed' => __( 'Term visibility changed.', 'edit-flow' ), |
| 85 |
), |
| 86 |
'configure_page_cb' => 'print_configure_view', |
| 87 |
'settings_help_tab' => array( |
| 88 |
'id' => 'ef-editorial-metadata-overview', |
| 89 |
'title' => __( 'Overview', 'edit-flow' ), |
| 90 |
'content' => __( '<p>Keep track of important details about your content with editorial metadata. This feature allows you to create as many date, text, number, etc. fields as you like, and then use them to store information like contact details, required word count, or the location of an interview.</p><p>Once you’ve set your fields up, editorial metadata integrates with both the calendar and the story budget. Make an editorial metadata item visible to have it appear to the rest of your team. Keep it hidden to restrict the information between the writer and their editor.</p>', 'edit-flow' ), |
| 91 |
), |
| 92 |
'settings_help_sidebar' => __( '<p><strong>For more information:</strong></p><p><a href="https://editflow.org/features/editorial-metadata/">Editorial Metadata Documentation</a></p><p><a href="https://wordpress.org/support/plugin/edit-flow/">Edit Flow Forum</a></p><p><a href="https://github.com/Automattic/Edit-Flow">Edit Flow on GitHub</a></p>', 'edit-flow' ), |
| 93 |
); |
| 94 |
EditFlow()->register_module( $this->module_name, $args ); |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* Initialize the module. Conditionally loads if the module is enabled. |
| 99 |
*/ |
| 100 |
public function init() { |
| 101 |
|
| 102 |
// Register the taxonomy we use for Editorial Metadata with WordPress core. |
| 103 |
$this->register_taxonomy(); |
| 104 |
|
| 105 |
// Register post meta for REST API support (enables Gutenberg saving). |
| 106 |
$this->register_metadata_for_rest_api(); |
| 107 |
|
| 108 |
// Anything that needs to happen in the admin. |
| 109 |
add_action( 'admin_init', array( $this, 'action_admin_init' ) ); |
| 110 |
|
| 111 |
// Register our settings. |
| 112 |
add_action( 'admin_init', array( $this, 'register_settings' ) ); |
| 113 |
|
| 114 |
// Actions relevant to the configuration view (adding, editing, or sorting existing Editorial Metadata). |
| 115 |
add_action( 'admin_init', array( $this, 'handle_add_editorial_metadata' ) ); |
| 116 |
add_action( 'admin_init', array( $this, 'handle_edit_editorial_metadata' ) ); |
| 117 |
add_action( 'admin_init', array( $this, 'handle_change_editorial_metadata_visibility' ) ); |
| 118 |
add_action( 'admin_init', array( $this, 'handle_delete_editorial_metadata' ) ); |
| 119 |
add_action( 'wp_ajax_inline_save_term', array( $this, 'handle_ajax_inline_save_term' ) ); |
| 120 |
add_action( 'wp_ajax_update_term_positions', array( $this, 'handle_ajax_update_term_positions' ) ); |
| 121 |
|
| 122 |
add_action( 'add_meta_boxes', array( $this, 'handle_post_metaboxes' ) ); |
| 123 |
add_action( 'save_post', array( $this, 'save_meta_box' ), 10, 2 ); |
| 124 |
|
| 125 |
// Add Editorial Metadata columns to the Manage Posts view. |
| 126 |
$supported_post_types = $this->get_post_types_for_module( $this->module ); |
| 127 |
foreach ( $supported_post_types as $post_type ) { |
| 128 |
add_filter( "manage_{$post_type}_posts_columns", array( $this, 'filter_manage_posts_columns' ) ); |
| 129 |
add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'action_manage_posts_custom_column' ), 10, 2 ); |
| 130 |
} |
| 131 |
|
| 132 |
// Add Editorial Metadata to the calendar if the calendar is activated. |
| 133 |
if ( $this->module_enabled( 'calendar' ) ) { |
| 134 |
add_filter( 'ef_calendar_item_information_fields', array( $this, 'filter_calendar_item_fields' ), 10, 2 ); |
| 135 |
} |
| 136 |
|
| 137 |
// Add Editorial Metadata columns to the Story Budget if it exists. |
| 138 |
if ( $this->module_enabled( 'story_budget' ) ) { |
| 139 |
add_filter( 'ef_story_budget_term_columns', array( $this, 'filter_story_budget_term_columns' ) ); |
| 140 |
// Register an action to handle this data later. |
| 141 |
add_filter( 'ef_story_budget_term_column_value', array( $this, 'filter_story_budget_term_column_values' ), 10, 3 ); |
| 142 |
} |
| 143 |
|
| 144 |
// Load necessary scripts and stylesheets. |
| 145 |
add_action( 'admin_enqueue_scripts', array( $this, 'add_admin_scripts' ) ); |
| 146 |
} |
| 147 |
|
| 148 |
/** |
| 149 |
* Load default editorial metadata the first time the module is loaded. |
| 150 |
* |
| 151 |
* @since 0.7 |
| 152 |
*/ |
| 153 |
public function install() { |
| 154 |
// Our default metadata fields. |
| 155 |
$default_metadata = array( |
| 156 |
array( |
| 157 |
'name' => __( 'First Draft Date', 'edit-flow' ), |
| 158 |
'slug' => 'first-draft-date', |
| 159 |
'type' => 'date', |
| 160 |
'description' => __( 'When the first draft needs to be ready.', 'edit-flow' ), |
| 161 |
), |
| 162 |
array( |
| 163 |
'name' => __( 'Assignment', 'edit-flow' ), |
| 164 |
'slug' => 'assignment', |
| 165 |
'type' => 'paragraph', |
| 166 |
'description' => __( 'What the post needs to cover.', 'edit-flow' ), |
| 167 |
), |
| 168 |
array( |
| 169 |
'name' => __( 'Needs Photo', 'edit-flow' ), |
| 170 |
'slug' => 'needs-photo', |
| 171 |
'type' => 'checkbox', |
| 172 |
'description' => __( 'Checked if this post needs a photo.', 'edit-flow' ), |
| 173 |
), |
| 174 |
array( |
| 175 |
'name' => __( 'Word Count', 'edit-flow' ), |
| 176 |
'slug' => 'word-count', |
| 177 |
'type' => 'number', |
| 178 |
'description' => __( 'Required post length in words.', 'edit-flow' ), |
| 179 |
), |
| 180 |
); |
| 181 |
// Load the metadata fields if the slugs don't conflict. |
| 182 |
foreach ( $default_metadata as $args ) { |
| 183 |
if ( ! term_exists( $args['slug'], self::metadata_taxonomy ) ) { |
| 184 |
$this->insert_editorial_metadata_term( $args ); |
| 185 |
} |
| 186 |
} |
| 187 |
} |
| 188 |
|
| 189 |
/** |
| 190 |
* Upgrade our data in case we need to. |
| 191 |
* |
| 192 |
* @since 0.7 |
| 193 |
* |
| 194 |
* @param string $previous_version The previous version number. |
| 195 |
*/ |
| 196 |
public function upgrade( $previous_version ) { |
| 197 |
global $edit_flow; |
| 198 |
|
| 199 |
// Upgrade path to v0.7. |
| 200 |
if ( version_compare( $previous_version, '0.7', '<' ) ) { |
| 201 |
// Technically we've run this code before so we don't want to auto-install new data. |
| 202 |
$edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); |
| 203 |
} |
| 204 |
// Upgrade path to v0.7.4. |
| 205 |
if ( version_compare( $previous_version, '0.7.4', '<' ) ) { |
| 206 |
// Editorial metadata descriptions become base64_encoded, instead of maybe json_encoded. |
| 207 |
$this->upgrade_074_term_descriptions( self::metadata_taxonomy ); |
| 208 |
} |
| 209 |
} |
| 210 |
|
| 211 |
/** |
| 212 |
* Anything that needs to happen on the 'admin_init' hook. |
| 213 |
* |
| 214 |
* @since 0.7.4 |
| 215 |
*/ |
| 216 |
public function action_admin_init() { |
| 217 |
|
| 218 |
// Parse the query when we're ordering by an editorial metadata term. |
| 219 |
add_action( 'parse_query', array( $this, 'action_parse_query' ) ); |
| 220 |
} |
| 221 |
|
| 222 |
/** |
| 223 |
* Generate select HTML for all of the metadata types. |
| 224 |
* |
| 225 |
* @param object $description The term description object. |
| 226 |
*/ |
| 227 |
public function get_select_html( $description ) { |
| 228 |
$current_metadata_type = $description->type; |
| 229 |
$metadata_types = $this->get_supported_metadata_types(); |
| 230 |
?> |
| 231 |
<select id="<?php echo esc_attr( self::metadata_taxonomy ); ?>'_type" name="<?php echo esc_attr( self::metadata_taxonomy ); ?>'_type"> |
| 232 |
<?php foreach ( $metadata_types as $metadata_type => $metadata_type_name ) : ?> |
| 233 |
<option value="<?php echo esc_attr( $metadata_type ); ?>" <?php selected( $metadata_type, $current_metadata_type ); ?>><?php echo esc_html( $metadata_type_name ); ?></option> |
| 234 |
<?php endforeach; ?> |
| 235 |
</select> |
| 236 |
<?php |
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* Prepare an array of supported editorial metadata types. |
| 241 |
* |
| 242 |
* @return array $supported_metadata_types All of the supported metadata. |
| 243 |
*/ |
| 244 |
public function get_supported_metadata_types() { |
| 245 |
$supported_metadata_types = array( |
| 246 |
'checkbox' => __( 'Checkbox', 'edit-flow' ), |
| 247 |
'date' => __( 'Date', 'edit-flow' ), |
| 248 |
'location' => __( 'Location', 'edit-flow' ), |
| 249 |
'number' => __( 'Number', 'edit-flow' ), |
| 250 |
'paragraph' => __( 'Paragraph', 'edit-flow' ), |
| 251 |
'text' => __( 'Text', 'edit-flow' ), |
| 252 |
'user' => __( 'User', 'edit-flow' ), |
| 253 |
); |
| 254 |
return $supported_metadata_types; |
| 255 |
} |
| 256 |
|
| 257 |
/** |
| 258 |
* Enqueue relevant admin Javascript. |
| 259 |
*/ |
| 260 |
public function add_admin_scripts() { |
| 261 |
global $current_screen, $pagenow; |
| 262 |
|
| 263 |
// Add the metabox date picker JS and CSS. |
| 264 |
$current_post_type = $this->get_current_post_type(); |
| 265 |
$supported_post_types = $this->get_post_types_for_module( $this->module ); |
| 266 |
if ( in_array( $current_post_type, $supported_post_types, true ) ) { |
| 267 |
$this->enqueue_datepicker_resources(); |
| 268 |
|
| 269 |
// Now add the rest of the metabox CSS. |
| 270 |
wp_enqueue_style( 'edit_flow-editorial_metadata-styles', $this->module_url . 'lib/editorial-metadata.css', false, EDIT_FLOW_VERSION, 'all' ); |
| 271 |
} |
| 272 |
// A bit of custom CSS for the Manage Posts view if we have viewable metadata. |
| 273 |
if ( 'edit' === $current_screen->base && in_array( $current_post_type, $supported_post_types, true ) ) { |
| 274 |
$terms = $this->get_editorial_metadata_terms(); |
| 275 |
$viewable_terms = array(); |
| 276 |
foreach ( $terms as $term ) { |
| 277 |
if ( $term->viewable ) { |
| 278 |
$viewable_terms[] = $term; |
| 279 |
} |
| 280 |
} |
| 281 |
if ( ! empty( $viewable_terms ) ) { |
| 282 |
$css_rules = array( |
| 283 |
'.wp-list-table.fixed .column-author' => array( |
| 284 |
'min-width: 7em;', |
| 285 |
'width: auto;', |
| 286 |
), |
| 287 |
'.wp-list-table.fixed .column-tags' => array( |
| 288 |
'min-width: 7em;', |
| 289 |
'width: auto;', |
| 290 |
), |
| 291 |
'.wp-list-table.fixed .column-categories' => array( |
| 292 |
'min-width: 7em;', |
| 293 |
'width: auto;', |
| 294 |
), |
| 295 |
); |
| 296 |
foreach ( $viewable_terms as $viewable_term ) { |
| 297 |
switch ( $viewable_term->type ) { |
| 298 |
case 'checkbox': |
| 299 |
case 'number': |
| 300 |
case 'date': |
| 301 |
$css_rules[ '.wp-list-table.fixed .column-' . $this->module->slug . '-' . $viewable_term->slug ] = array( |
| 302 |
'min-width: 6em;', |
| 303 |
); |
| 304 |
break; |
| 305 |
case 'location': |
| 306 |
case 'text': |
| 307 |
case 'user': |
| 308 |
$css_rules[ '.wp-list-table.fixed .column-' . $this->module->slug . '-' . $viewable_term->slug ] = array( |
| 309 |
'min-width: 7em;', |
| 310 |
); |
| 311 |
break; |
| 312 |
case 'paragraph': |
| 313 |
$css_rules[ '.wp-list-table.fixed .column-' . $this->module->slug . '-' . $viewable_term->slug ] = array( |
| 314 |
'min-width: 8em;', |
| 315 |
); |
| 316 |
break; |
| 317 |
} |
| 318 |
} |
| 319 |
// Allow users to filter out rules if there's something wonky. |
| 320 |
$css_rules = apply_filters( 'ef_editorial_metadata_manage_posts_css_rules', $css_rules ); |
| 321 |
|
| 322 |
$css_lines = array(); |
| 323 |
foreach ( (array) $css_rules as $css_property => $rules ) { |
| 324 |
// Strip any HTML tags that a misbehaving filter hook may have |
| 325 |
// smuggled into the selector or declarations; this prevents |
| 326 |
// an injected `</style>` sequence from breaking out of the |
| 327 |
// style block into arbitrary HTML. |
| 328 |
$safe_property = wp_strip_all_tags( (string) $css_property ); |
| 329 |
$safe_rules = implode( ' ', array_map( 'wp_strip_all_tags', (array) $rules ) ); |
| 330 |
$css_lines[] = $safe_property . ' {' . $safe_rules . '}'; |
| 331 |
} |
| 332 |
wp_add_inline_style( 'edit_flow-editorial_metadata-styles', implode( "\n", $css_lines ) ); |
| 333 |
} |
| 334 |
} |
| 335 |
|
| 336 |
// Load Javascript specific to the editorial metadata configuration view. |
| 337 |
if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { |
| 338 |
wp_enqueue_script( 'jquery-ui-sortable' ); |
| 339 |
wp_enqueue_script( 'edit-flow-editorial-metadata-configure', EDIT_FLOW_URL . 'modules/editorial-metadata/lib/editorial-metadata-configure.js', array( 'jquery', 'jquery-ui-sortable', 'edit-flow-settings-js' ), EDIT_FLOW_VERSION, true ); |
| 340 |
} |
| 341 |
} |
| 342 |
|
| 343 |
/** |
| 344 |
* Register the post metadata taxonomy. |
| 345 |
*/ |
| 346 |
public function register_taxonomy() { |
| 347 |
|
| 348 |
// We need to make sure taxonomy is registered for all of the post types that support it. |
| 349 |
$supported_post_types = $this->get_post_types_for_module( $this->module ); |
| 350 |
|
| 351 |
register_taxonomy( self::metadata_taxonomy, $supported_post_types, |
| 352 |
array( |
| 353 |
'public' => false, |
| 354 |
'labels' => array( |
| 355 |
'name' => _x( 'Editorial Metadata', 'taxonomy general name', 'edit-flow' ), |
| 356 |
'singular_name' => _x( 'Editorial Metadata', 'taxonomy singular name', 'edit-flow' ), |
| 357 |
'search_items' => __( 'Search Editorial Metadata', 'edit-flow' ), |
| 358 |
'popular_items' => __( 'Popular Editorial Metadata', 'edit-flow' ), |
| 359 |
'all_items' => __( 'All Editorial Metadata', 'edit-flow' ), |
| 360 |
'edit_item' => __( 'Edit Editorial Metadata', 'edit-flow' ), |
| 361 |
'update_item' => __( 'Update Editorial Metadata', 'edit-flow' ), |
| 362 |
'add_new_item' => __( 'Add New Editorial Metadata', 'edit-flow' ), |
| 363 |
'new_item_name' => __( 'New Editorial Metadata', 'edit-flow' ), |
| 364 |
), |
| 365 |
'rewrite' => false, |
| 366 |
) |
| 367 |
); |
| 368 |
} |
| 369 |
|
| 370 |
/** |
| 371 |
* Register editorial metadata post meta keys for REST API support. |
| 372 |
* |
| 373 |
* This enables Gutenberg to save metadata values directly through the REST API |
| 374 |
* when the post is saved, rather than relying on legacy metabox form submission. |
| 375 |
*/ |
| 376 |
private function register_metadata_for_rest_api() { |
| 377 |
$terms = $this->get_editorial_metadata_terms(); |
| 378 |
$supported_post_types = $this->get_post_types_for_module( $this->module ); |
| 379 |
|
| 380 |
foreach ( $terms as $term ) { |
| 381 |
$meta_key = $this->get_postmeta_key( $term ); |
| 382 |
|
| 383 |
// Mirror the sanitisation applied on the classic (metabox) save path so the |
| 384 |
// REST/Gutenberg write path cannot store unsanitised values. Paragraph fields |
| 385 |
// keep their line breaks; every other type is treated as a single line. |
| 386 |
$sanitize_callback = ( 'paragraph' === $term->type ) ? 'sanitize_textarea_field' : 'sanitize_text_field'; |
| 387 |
|
| 388 |
foreach ( $supported_post_types as $post_type ) { |
| 389 |
register_post_meta( |
| 390 |
$post_type, |
| 391 |
$meta_key, |
| 392 |
array( |
| 393 |
'show_in_rest' => true, |
| 394 |
'single' => true, |
| 395 |
'type' => 'string', |
| 396 |
'sanitize_callback' => $sanitize_callback, |
| 397 |
'auth_callback' => function ( $allowed, $meta_key, $post_id ) { |
| 398 |
return current_user_can( 'edit_post', $post_id ); |
| 399 |
}, |
| 400 |
) |
| 401 |
); |
| 402 |
} |
| 403 |
} |
| 404 |
} |
| 405 |
|
| 406 |
/***************************************************** |
| 407 |
* Post meta box generation and processing |
| 408 |
****************************************************/ |
| 409 |
|
| 410 |
/** |
| 411 |
* Load the post metaboxes for all of the post types that are supported. |
| 412 |
*/ |
| 413 |
public function handle_post_metaboxes() { |
| 414 |
$title = __( 'Editorial Metadata', 'edit-flow' ); |
| 415 |
|
| 416 |
$supported_post_types = $this->get_post_types_for_module( $this->module ); |
| 417 |
foreach ( $supported_post_types as $post_type ) { |
| 418 |
add_meta_box( self::metadata_taxonomy, $title, array( $this, 'display_meta_box' ), $post_type, 'side' ); |
| 419 |
} |
| 420 |
} |
| 421 |
|
| 422 |
/** |
| 423 |
* Displays HTML output for Editorial Metadata post meta box. |
| 424 |
* |
| 425 |
* @param object $post Current post. |
| 426 |
*/ |
| 427 |
public function display_meta_box( $post ) { |
| 428 |
echo "<div id='" . esc_attr( self::metadata_taxonomy ) . "_meta_box'>"; |
| 429 |
// Add nonce for verification upon save. |
| 430 |
echo "<input type='hidden' name='" . esc_attr( self::metadata_taxonomy ) . "_nonce' value='" . esc_attr( wp_create_nonce( 'ef-save-metabox' ) ) . "' />"; |
| 431 |
|
| 432 |
if ( current_user_can( 'manage_options' ) ) { |
| 433 |
// Make the metabox title include a link to edit the Editorial Metadata terms. Logic similar to how Core dashboard widgets work. |
| 434 |
$url = add_query_arg( 'page', 'ef-editorial-metadata-settings', get_admin_url( null, 'admin.php' ) ); |
| 435 |
echo '<p><a href="' . esc_url( $url ) . '">' . esc_html__( 'Configure', 'edit-flow' ) . '</a></p>'; |
| 436 |
} |
| 437 |
|
| 438 |
$terms = $this->get_editorial_metadata_terms(); |
| 439 |
if ( ! count( $terms ) ) { |
| 440 |
$message = __( 'No editorial metadata available.', 'edit-flow' ); |
| 441 |
if ( current_user_can( 'manage_options' ) ) { |
| 442 |
/* translators: 1: The link to add editorial metadata fields */ |
| 443 |
$message .= sprintf( __( ' <a href="%s">Add fields to get started</a>.', 'edit-flow' ), $this->get_link() ); |
| 444 |
} else { |
| 445 |
$message .= esc_html__( ' Encourage your site administrator to configure your editorial workflow by adding editorial metadata.', 'edit-flow' ); |
| 446 |
} |
| 447 |
echo '<p>' . wp_kses( $message, 'a' ) . '</p>'; |
| 448 |
} else { |
| 449 |
foreach ( $terms as $term ) { |
| 450 |
$postmeta_key = $this->get_postmeta_key( $term ); |
| 451 |
// Raw stored value; each field type below escapes it for its own output context. |
| 452 |
$current_metadata = $this->get_postmeta_value( $term, $post->ID ); |
| 453 |
$type = $term->type; |
| 454 |
$description = $term->description; |
| 455 |
if ( $description ) { |
| 456 |
$description_span = "<span class='description'>$description</span>"; |
| 457 |
} else { |
| 458 |
$description_span = ''; |
| 459 |
} |
| 460 |
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is for the escaping of type. |
| 461 |
echo "<div class='" . esc_attr( self::metadata_taxonomy ) . ' ' . esc_attr( self::metadata_taxonomy ) . "_$type'>"; |
| 462 |
switch ( $type ) { |
| 463 |
case 'date': |
| 464 |
$date_value = ''; |
| 465 |
$time_value = ''; |
| 466 |
$hidden_value = ''; |
| 467 |
if ( ! empty( $current_metadata ) ) { |
| 468 |
$timestamp = intval( $current_metadata ); |
| 469 |
$date_value = date_i18n( 'M d Y', $timestamp ); |
| 470 |
$hidden_value = date( 'Y-m-d', $timestamp ); |
| 471 |
// Only show time if it's not midnight (00:00). |
| 472 |
if ( date( 'Hi', $timestamp ) !== '0000' ) { |
| 473 |
$time_value = date( 'H:i', $timestamp ); |
| 474 |
$hidden_value .= ' ' . $time_value; |
| 475 |
} else { |
| 476 |
$hidden_value .= ' 00:00'; |
| 477 |
} |
| 478 |
} |
| 479 |
echo '<label for="' . esc_attr( $postmeta_key ) . '_date">' . esc_html( $term->name ) . '</label>'; |
| 480 |
if ( $description_span ) { |
| 481 |
echo '<label for="' . esc_attr( $postmeta_key ) . '_date">' . wp_kses_post( $description_span ) . '</label>'; |
| 482 |
} |
| 483 |
echo '<div class="ef-datetime-wrapper">'; |
| 484 |
// The date input has name="$postmeta_key" to pass the empty check in save_meta_box(). |
| 485 |
// The hidden field has the machine-readable value used for actual parsing. |
| 486 |
echo '<input id="' . esc_attr( $postmeta_key ) . '_date" name="' . esc_attr( $postmeta_key ) . '" type="text" class="date-pick" value="' . esc_attr( $date_value ) . '" placeholder="' . esc_attr__( 'Select date', 'edit-flow' ) . '" />'; |
| 487 |
echo '<input id="' . esc_attr( $postmeta_key ) . '_time" type="time" class="time-pick" value="' . esc_attr( $time_value ) . '" />'; |
| 488 |
echo '</div>'; |
| 489 |
echo '<input type="hidden" id="' . esc_attr( $postmeta_key ) . '_hidden" name="' . esc_attr( $postmeta_key ) . '_hidden" value="' . esc_attr( $hidden_value ) . '" />'; |
| 490 |
break; |
| 491 |
case 'location': |
| 492 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . '</label>'; |
| 493 |
if ( $description_span ) { |
| 494 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . wp_kses_post( $description_span ) . '</label>'; |
| 495 |
} |
| 496 |
echo '<input id="' . esc_attr( $postmeta_key ) . '" name="' . esc_attr( $postmeta_key ) . '" type="text" value="' . esc_attr( $current_metadata ) . '" />'; |
| 497 |
if ( ! empty( $current_metadata ) ) { |
| 498 |
// http_build_query() (unlike add_query_arg()) URL-encodes the value, |
| 499 |
// so spaces, "&" and "=" cannot alter the URL structure. |
| 500 |
$google_maps_url = 'https://maps.google.com/?' . http_build_query( |
| 501 |
array( |
| 502 |
'q' => $current_metadata, |
| 503 |
't' => 'm', |
| 504 |
) |
| 505 |
); |
| 506 |
$google_maps_text = sprintf( |
| 507 |
/* translators: %s: the location entered in the editorial metadata field. */ |
| 508 |
__( 'View “%s” on Google Maps', 'edit-flow' ), |
| 509 |
$current_metadata |
| 510 |
); |
| 511 |
// Escape late: the label is translatable and filterable (gettext), so escape the |
| 512 |
// whole composed string at output rather than trusting either source. |
| 513 |
echo '<div><a href="' . esc_url( $google_maps_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $google_maps_text ) . '</a></div>'; |
| 514 |
} |
| 515 |
break; |
| 516 |
case 'text': |
| 517 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . wp_kses_post( $description_span ) . '</label>'; |
| 518 |
echo '<input id="' . esc_attr( $postmeta_key ) . '" name="' . esc_attr( $postmeta_key ) . '"type=text value="' . esc_attr( $current_metadata ) . '" />'; |
| 519 |
break; |
| 520 |
case 'paragraph': |
| 521 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . wp_kses_post( $description_span ) . '</label>'; |
| 522 |
echo '<textarea id="' . esc_attr( $postmeta_key ) . '" name="' . esc_attr( $postmeta_key ) . '">' . esc_textarea( $current_metadata ) . '</textarea>'; |
| 523 |
break; |
| 524 |
case 'checkbox': |
| 525 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . wp_kses_post( $description_span ) . '</label>'; |
| 526 |
echo '<input id="' . esc_attr( $postmeta_key ) . '" name="' . esc_attr( $postmeta_key ) . '" type=checkbox value=1 "' . checked( $current_metadata, 1, false ) . '" />'; |
| 527 |
break; |
| 528 |
case 'user': |
| 529 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . wp_kses_post( $description_span ) . '</label>'; |
| 530 |
$user_dropdown_args = array( |
| 531 |
'show_option_all' => __( '-- Select a user --', 'edit-flow' ), |
| 532 |
'name' => $postmeta_key, |
| 533 |
'selected' => $current_metadata, |
| 534 |
); |
| 535 |
$user_dropdown_args = apply_filters( 'ef_editorial_metadata_user_dropdown_args', $user_dropdown_args ); |
| 536 |
wp_dropdown_users( $user_dropdown_args ); |
| 537 |
break; |
| 538 |
case 'number': |
| 539 |
echo '<label for="' . esc_attr( $postmeta_key ) . '">' . esc_html( $term->name ) . wp_kses_post( $description_span ) . '</label>'; |
| 540 |
echo '<input id="' . esc_attr( $postmeta_key ) . '" name="' . esc_attr( $postmeta_key ) . '"type=text value="' . esc_attr( $current_metadata ) . '" />'; |
| 541 |
break; |
| 542 |
default: |
| 543 |
echo '<p>' . esc_html__( 'This editorial metadata type is not yet supported.', 'edit-flow' ) . '</p>'; |
| 544 |
} |
| 545 |
echo '</div>'; |
| 546 |
echo "<div class='clear'></div>"; |
| 547 |
} // Done iterating through metadata terms. |
| 548 |
} |
| 549 |
echo '</div>'; |
| 550 |
} |
| 551 |
|
| 552 |
/** |
| 553 |
* Show date or datetime. |
| 554 |
* |
| 555 |
* @since 0.8 |
| 556 |
* |
| 557 |
* @param int $current_date The timestamp to display. |
| 558 |
* @return string The formatted date string. |
| 559 |
*/ |
| 560 |
private function show_date_or_datetime( $current_date ) { |
| 561 |
|
| 562 |
if ( date( 'Hi', $current_date ) == '0000' ) { |
| 563 |
return date_i18n( 'M d Y', $current_date ); |
| 564 |
} else { |
| 565 |
return date_i18n( 'M d Y H:i', $current_date ); |
| 566 |
} |
| 567 |
} |
| 568 |
|
| 569 |
/** |
| 570 |
* Save editorial metadata values from POST data. |
| 571 |
* Called from save_post hook for Classic Editor form submissions. |
| 572 |
* |
| 573 |
* Note: In Gutenberg, metadata is saved via REST API (see register_metadata_for_rest_api). |
| 574 |
* |
| 575 |
* @param int $post_id Post ID. |
| 576 |
*/ |
| 577 |
private function save_meta_box_data( $post_id ) { |
| 578 |
// Verify nonce. |
| 579 |
$nonce_key = self::metadata_taxonomy . '_nonce'; |
| 580 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 581 |
if ( ! isset( $_POST[ $nonce_key ] ) || ! wp_verify_nonce( $_POST[ $nonce_key ], 'ef-save-metabox' ) ) { |
| 582 |
return; |
| 583 |
} |
| 584 |
|
| 585 |
// Verify user can edit. |
| 586 |
if ( ! current_user_can( 'edit_post', $post_id ) ) { |
| 587 |
return; |
| 588 |
} |
| 589 |
|
| 590 |
$terms = $this->get_editorial_metadata_terms(); |
| 591 |
|
| 592 |
foreach ( $terms as $term ) { |
| 593 |
$key = $this->get_postmeta_key( $term ); |
| 594 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized based on type below. |
| 595 |
$new_metadata = isset( $_POST[ $key ] ) ? $_POST[ $key ] : ''; |
| 596 |
$type = $term->type; |
| 597 |
|
| 598 |
if ( empty( $new_metadata ) ) { |
| 599 |
delete_post_meta( $post_id, $key ); |
| 600 |
} else { |
| 601 |
if ( 'date' === $type ) { |
| 602 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by DateTime::createFromFormat below. |
| 603 |
$date_to_parse = isset( $_POST[ $key . '_hidden' ] ) ? $_POST[ $key . '_hidden' ] : ''; |
| 604 |
$date = DateTime::createFromFormat( 'Y-m-d H:i', $date_to_parse ); |
| 605 |
|
| 606 |
if ( false !== $date ) { |
| 607 |
$new_metadata = $date->getTimestamp(); |
| 608 |
} else { |
| 609 |
$new_metadata = strtotime( $new_metadata ); |
| 610 |
} |
| 611 |
} |
| 612 |
|
| 613 |
if ( 'number' === $type ) { |
| 614 |
$new_metadata = (int) $new_metadata; |
| 615 |
} |
| 616 |
|
| 617 |
$new_metadata = wp_strip_all_tags( $new_metadata ); |
| 618 |
update_post_meta( $post_id, $key, $new_metadata ); |
| 619 |
} |
| 620 |
|
| 621 |
do_action( 'ef_editorial_metadata_field_updated', $key, $new_metadata, $post_id, $type ); |
| 622 |
} |
| 623 |
} |
| 624 |
|
| 625 |
/** |
| 626 |
* Save any values in the editorial metadata post meta box. |
| 627 |
* |
| 628 |
* @param int $id Unique ID for the post being saved. |
| 629 |
* @param object $post Post object. |
| 630 |
*/ |
| 631 |
public function save_meta_box( $id, $post ) { |
| 632 |
// Skip autosave. |
| 633 |
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { |
| 634 |
return $id; |
| 635 |
} |
| 636 |
|
| 637 |
// Skip if post type not supported. |
| 638 |
if ( ! in_array( $post->post_type, $this->get_post_types_for_module( $this->module ), true ) ) { |
| 639 |
return $id; |
| 640 |
} |
| 641 |
|
| 642 |
// Save the data. |
| 643 |
$this->save_meta_box_data( $id ); |
| 644 |
} |
| 645 |
|
| 646 |
/** |
| 647 |
* Generate a unique key based on the term. |
| 648 |
* |
| 649 |
* @param object $term Term object. |
| 650 |
* @return string $postmeta_key Unique key. |
| 651 |
*/ |
| 652 |
public function get_postmeta_key( $term ) { |
| 653 |
$key = self::metadata_postmeta_key; |
| 654 |
$type = $term->type; |
| 655 |
$prefix = "{$key}_{$type}"; |
| 656 |
$postmeta_key = "{$prefix}_" . ( is_object( $term ) ? $term->slug : $term ); |
| 657 |
return $postmeta_key; |
| 658 |
} |
| 659 |
|
| 660 |
/** |
| 661 |
* Returns the value for the given metadata. |
| 662 |
* |
| 663 |
* @param object|string|int $term The term object, slug or ID for the metadata field term. |
| 664 |
* @param int $post_id The ID of the post. |
| 665 |
* @return mixed The metadata value. |
| 666 |
*/ |
| 667 |
public function get_postmeta_value( $term, $post_id ) { |
| 668 |
if ( ! is_object( $term ) ) { |
| 669 |
if ( is_int( $term ) ) { |
| 670 |
$term = $this->get_editorial_metadata_term_by( 'id', $term ); |
| 671 |
} else { |
| 672 |
$term = $this->get_editorial_metadata_term_by( 'slug', $term ); |
| 673 |
} |
| 674 |
} |
| 675 |
$postmeta_key = $this->get_postmeta_key( $term ); |
| 676 |
return get_metadata( 'post', $post_id, $postmeta_key, true ); |
| 677 |
} |
| 678 |
|
| 679 |
/** |
| 680 |
* Get all of the editorial metadata terms as objects and sort by position. |
| 681 |
* |
| 682 |
* @todo Figure out what we should do with the filter. |
| 683 |
* |
| 684 |
* @param array $filter_args Filter to specific arguments. |
| 685 |
* @return array $ordered_terms The terms as they should be ordered. |
| 686 |
*/ |
| 687 |
public function get_editorial_metadata_terms( $filter_args = array() ) { |
| 688 |
|
| 689 |
// Try to fetch from internal object cache. |
| 690 |
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used for cache key generation only. |
| 691 |
$arg_hash = md5( serialize( $filter_args ) ); |
| 692 |
if ( isset( $this->editorial_metadata_terms_cache[ $arg_hash ] ) ) { |
| 693 |
return $this->editorial_metadata_terms_cache[ $arg_hash ]; |
| 694 |
} |
| 695 |
|
| 696 |
$terms = get_terms( array( |
| 697 |
'taxonomy' => self::metadata_taxonomy, |
| 698 |
'orderby' => apply_filters( 'ef_editorial_metadata_term_order', 'name' ), |
| 699 |
'hide_empty' => false, |
| 700 |
)); |
| 701 |
|
| 702 |
$ordered_terms = array(); |
| 703 |
$hold_to_end = array(); |
| 704 |
// Order the terms. |
| 705 |
foreach ( $terms as $key => $term ) { |
| 706 |
|
| 707 |
// Unencode and set all of our pseudo term meta because we need the position and viewable if they exist. |
| 708 |
// First do an array_merge() on the term object to make sure the keys exist, then array_merge() |
| 709 |
// any values that may already exist. |
| 710 |
$unencoded_description = $this->get_unencoded_description( $term->description ); |
| 711 |
$defaults = array( |
| 712 |
'description' => '', |
| 713 |
'viewable' => false, |
| 714 |
'position' => false, |
| 715 |
); |
| 716 |
$term = array_merge( $defaults, (array) $term ); |
| 717 |
if ( is_array( $unencoded_description ) ) { |
| 718 |
$term = array_merge( $term, $unencoded_description ); |
| 719 |
} |
| 720 |
$term = (object) $term; |
| 721 |
// We used to store the description field in a funny way. |
| 722 |
if ( isset( $term->desc ) ) { |
| 723 |
$term->description = $term->desc; |
| 724 |
unset( $term->desc ); |
| 725 |
} |
| 726 |
// Only add the term to the ordered array if it has a set position and doesn't conflict with another key. |
| 727 |
// Otherwise, hold it for later. |
| 728 |
if ( $term->position && ! array_key_exists( $term->position, $ordered_terms ) ) { |
| 729 |
$ordered_terms[ (int) $term->position ] = $term; |
| 730 |
} else { |
| 731 |
$hold_to_end[] = $term; |
| 732 |
} |
| 733 |
} |
| 734 |
// Sort the items numerically by key. |
| 735 |
ksort( $ordered_terms, SORT_NUMERIC ); |
| 736 |
// Append all of the terms that didn't have an existing position. |
| 737 |
foreach ( $hold_to_end as $unpositioned_term ) { |
| 738 |
$ordered_terms[] = $unpositioned_term; |
| 739 |
} |
| 740 |
|
| 741 |
// If filter arguments were passed, do our filtering. |
| 742 |
$ordered_terms = wp_filter_object_list( $ordered_terms, $filter_args ); |
| 743 |
|
| 744 |
// Set the internal object cache. |
| 745 |
$this->editorial_metadata_terms_cache[ $arg_hash ] = $ordered_terms; |
| 746 |
|
| 747 |
return $ordered_terms; |
| 748 |
} |
| 749 |
|
| 750 |
/** |
| 751 |
* Returns a term for single metadata field. |
| 752 |
* |
| 753 |
* @param string $field The field to match (id, slug, or name). |
| 754 |
* @param int|string $value The value to match against the field. |
| 755 |
* @return object|false $term Term's object representation or false if not found. |
| 756 |
*/ |
| 757 |
public function get_editorial_metadata_term_by( $field, $value ) { |
| 758 |
|
| 759 |
if ( ! in_array( $field, array( 'id', 'slug', 'name' ) ) ) { |
| 760 |
return false; |
| 761 |
} |
| 762 |
|
| 763 |
if ( 'id' == $field ) { |
| 764 |
$field = 'term_id'; |
| 765 |
} |
| 766 |
|
| 767 |
$terms = $this->get_editorial_metadata_terms(); |
| 768 |
$term = wp_filter_object_list( $terms, array( $field => $value ) ); |
| 769 |
|
| 770 |
if ( ! empty( $term ) ) { |
| 771 |
return array_shift( $term ); |
| 772 |
} else { |
| 773 |
return false; |
| 774 |
} |
| 775 |
} |
| 776 |
|
| 777 |
/** |
| 778 |
* Register editorial metadata fields as columns in the manage posts view. |
| 779 |
* |
| 780 |
* Only adds columns for the currently active post types - logic controlled in $this->init(). |
| 781 |
* |
| 782 |
* @since 0.7 |
| 783 |
* @uses apply_filters( 'manage_posts_columns' ) in wp-admin/includes/class-wp-posts-list-table.php |
| 784 |
* |
| 785 |
* @param array $posts_columns Existing post columns prepared by WP_List_Table. |
| 786 |
* @return array $posts_columns Previous post columns with the new values. |
| 787 |
*/ |
| 788 |
public function filter_manage_posts_columns( $posts_columns ) { |
| 789 |
$screen = get_current_screen(); |
| 790 |
if ( $screen ) { |
| 791 |
add_filter( "manage_{$screen->id}_sortable_columns", array( $this, 'filter_manage_posts_sortable_columns' ) ); |
| 792 |
$terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); |
| 793 |
foreach ( $terms as $term ) { |
| 794 |
// Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions. |
| 795 |
$key = $this->module->slug . '-' . $term->slug; |
| 796 |
$posts_columns[ $key ] = $term->name; |
| 797 |
} |
| 798 |
} |
| 799 |
return $posts_columns; |
| 800 |
} |
| 801 |
|
| 802 |
/** |
| 803 |
* Register any viewable date editorial metadata as a sortable column. |
| 804 |
* |
| 805 |
* @since 0.7.4 |
| 806 |
* |
| 807 |
* @param array $sortable_columns Any existing sortable columns (e.g. Title). |
| 808 |
* @return array $sortable_columns Sortable columns with editorial metadata date fields added. |
| 809 |
*/ |
| 810 |
public function filter_manage_posts_sortable_columns( $sortable_columns ) { |
| 811 |
|
| 812 |
$terms = $this->get_editorial_metadata_terms( array( |
| 813 |
'viewable' => true, |
| 814 |
'type' => 'date', |
| 815 |
) ); |
| 816 |
foreach ( $terms as $term ) { |
| 817 |
// Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions. |
| 818 |
$key = $this->module->slug . '-' . $term->slug; |
| 819 |
$sortable_columns[ $key ] = $key; |
| 820 |
} |
| 821 |
return $sortable_columns; |
| 822 |
} |
| 823 |
|
| 824 |
/** |
| 825 |
* If we're ordering by a sortable column, let's modify the query. |
| 826 |
* |
| 827 |
* @since 0.7.4 |
| 828 |
* |
| 829 |
* @param WP_Query $query The query object. |
| 830 |
*/ |
| 831 |
public function action_parse_query( $query ) { |
| 832 |
|
| 833 |
if ( is_admin() && false !== stripos( get_query_var( 'orderby' ), $this->module->slug ) ) { |
| 834 |
$term_slug = sanitize_key( str_replace( $this->module->slug . '-', '', get_query_var( 'orderby' ) ) ); |
| 835 |
$term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); |
| 836 |
$meta_key = $this->get_postmeta_key( $term ); |
| 837 |
set_query_var( 'meta_key', $meta_key ); |
| 838 |
set_query_var( 'orderby', 'meta_value_num' ); |
| 839 |
} |
| 840 |
} |
| 841 |
|
| 842 |
/** |
| 843 |
* Handle the output of an editorial metadata custom column. |
| 844 |
* |
| 845 |
* Logic for the post types this is called on is controlled in $this->init(). |
| 846 |
* |
| 847 |
* @since 0.7 |
| 848 |
* @uses do_action( 'manage_posts_custom_column' ) in wp-admin/includes/class-wp-posts-list-table.php |
| 849 |
* |
| 850 |
* @param string $column_name Unique string for the column. |
| 851 |
* @param int $post_id ID for the post of the row. |
| 852 |
*/ |
| 853 |
public function action_manage_posts_custom_column( $column_name, $post_id ) { |
| 854 |
|
| 855 |
$terms = $this->get_editorial_metadata_terms(); |
| 856 |
// We're looking for the proper term to display its saved value. |
| 857 |
foreach ( $terms as $term ) { |
| 858 |
$key = $this->module->slug . '-' . $term->slug; |
| 859 |
if ( $column_name != $key ) { |
| 860 |
continue; |
| 861 |
} |
| 862 |
|
| 863 |
$current_metadata = $this->get_postmeta_value( $term, $post_id ); |
| 864 |
echo esc_html( $this->generate_editorial_metadata_term_output( $term, $current_metadata ) ); |
| 865 |
} |
| 866 |
} |
| 867 |
|
| 868 |
/** |
| 869 |
* If the Edit Flow Calendar is enabled, add viewable Editorial Metadata terms. |
| 870 |
* |
| 871 |
* @since 0.7 |
| 872 |
* @uses apply_filters( 'ef_calendar_item_information_fields' ) |
| 873 |
* |
| 874 |
* @param array $calendar_fields Additional data fields to include on the calendar. |
| 875 |
* @param int $post_id Unique ID for the post data we're building. |
| 876 |
* @return array $calendar_fields Calendar fields with our viewable Editorial Metadata added. |
| 877 |
*/ |
| 878 |
public function filter_calendar_item_fields( $calendar_fields, $post_id ) { |
| 879 |
|
| 880 |
|
| 881 |
// Make sure we respect which post type we're on. |
| 882 |
if ( ! in_array( get_post_type( $post_id ), $this->get_post_types_for_module( $this->module ) ) ) { |
| 883 |
return $calendar_fields; |
| 884 |
} |
| 885 |
|
| 886 |
$terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); |
| 887 |
|
| 888 |
foreach ( $terms as $term ) { |
| 889 |
$key = $this->module->slug . '-' . $term->slug; |
| 890 |
|
| 891 |
// Default values. |
| 892 |
$current_metadata = $this->get_postmeta_value( $term, $post_id ); |
| 893 |
$term_data = array( |
| 894 |
'label' => $term->name, |
| 895 |
'value' => $this->generate_editorial_metadata_term_output( $term, $current_metadata ), |
| 896 |
); |
| 897 |
$term_data['editable'] = true; |
| 898 |
$term_data['type'] = $term->type; |
| 899 |
$calendar_fields[ $key ] = $term_data; |
| 900 |
} |
| 901 |
return $calendar_fields; |
| 902 |
} |
| 903 |
|
| 904 |
/** |
| 905 |
* If the Edit Flow Story Budget is enabled, register our viewable terms as columns. |
| 906 |
* |
| 907 |
* @since 0.7 |
| 908 |
* @uses apply_filters( 'ef_story_budget_term_columns' ) |
| 909 |
* |
| 910 |
* @param array $term_columns The existing columns on the story budget. |
| 911 |
* @return array $term_columns Term columns with viewable Editorial Metadata terms. |
| 912 |
*/ |
| 913 |
public function filter_story_budget_term_columns( $term_columns ) { |
| 914 |
|
| 915 |
$terms = $this->get_editorial_metadata_terms( array( 'viewable' => true ) ); |
| 916 |
foreach ( $terms as $term ) { |
| 917 |
// Prefixing slug with module slug because it isn't stored prefixed and we want to avoid collisions. |
| 918 |
$key = $this->module->slug . '-' . $term->slug; |
| 919 |
// Switch to underscores. |
| 920 |
$key = str_replace( '-', '_', $key ); |
| 921 |
$term_columns[ $key ] = $term->name; |
| 922 |
} |
| 923 |
return $term_columns; |
| 924 |
} |
| 925 |
|
| 926 |
/** |
| 927 |
* If the Edit Flow Story Budget is enabled, generate metadata column values. |
| 928 |
* |
| 929 |
* @since 0.7 |
| 930 |
* @uses apply_filters( 'ef_story_budget_term_column_value' ) |
| 931 |
* |
| 932 |
* @param string $column_name Name of the column, as registered with EF_Story_Budget::register_term_columns. |
| 933 |
* @param object $post The post we're displaying. |
| 934 |
* @param object $parent_term The parent term for the term column. |
| 935 |
* @return string The column value or the original column name. |
| 936 |
*/ |
| 937 |
public function filter_story_budget_term_column_values( $column_name, $post, $parent_term ) { |
| 938 |
|
| 939 |
$local_column_name = str_replace( '_', '-', $column_name ); |
| 940 |
// Don't accidentally handle values not our own. |
| 941 |
if ( false === strpos( $local_column_name, $this->module->slug ) ) { |
| 942 |
return $column_name; |
| 943 |
} |
| 944 |
|
| 945 |
$term_slug = str_replace( $this->module->slug . '-', '', $local_column_name ); |
| 946 |
$term = $this->get_editorial_metadata_term_by( 'slug', $term_slug ); |
| 947 |
|
| 948 |
// Don't allow non-viewable term data to be displayed. |
| 949 |
if ( ! $term->viewable ) { |
| 950 |
return $column_name; |
| 951 |
} |
| 952 |
|
| 953 |
$current_metadata = $this->get_postmeta_value( $term, $post->ID ); |
| 954 |
$output = $this->generate_editorial_metadata_term_output( $term, $current_metadata ); |
| 955 |
|
| 956 |
return $output; |
| 957 |
} |
| 958 |
|
| 959 |
/** |
| 960 |
* Generate the presentational output for an editorial metadata term. |
| 961 |
* |
| 962 |
* @since 0.8 |
| 963 |
* |
| 964 |
* @param object $term The editorial metadata term. |
| 965 |
* @param mixed $pm_value The post meta value. |
| 966 |
* @return string $html How the term should be rendered. |
| 967 |
*/ |
| 968 |
private function generate_editorial_metadata_term_output( $term, $pm_value ) { |
| 969 |
|
| 970 |
$output = ''; |
| 971 |
switch ( $term->type ) { |
| 972 |
case 'date': |
| 973 |
if ( empty( $pm_value ) ) { |
| 974 |
break; |
| 975 |
} |
| 976 |
|
| 977 |
// All day vs. day and time. |
| 978 |
$date = date( get_option( 'date_format' ), $pm_value ); |
| 979 |
$time = date( get_option( 'time_format' ), $pm_value ); |
| 980 |
if ( '0000' == date( 'Hi', $pm_value ) ) { |
| 981 |
$pm_value = $date; |
| 982 |
} else { |
| 983 |
/* translators: 1: date, 2: time */ |
| 984 |
$pm_value = sprintf( __( '%1$s at %2$s', 'edit-flow' ), $date, $time ); |
| 985 |
} |
| 986 |
$output = esc_html( $pm_value ); |
| 987 |
break; |
| 988 |
case 'location': |
| 989 |
case 'text': |
| 990 |
case 'number': |
| 991 |
case 'paragraph': |
| 992 |
if ( $pm_value ) { |
| 993 |
$output = esc_html( $pm_value ); |
| 994 |
} |
| 995 |
break; |
| 996 |
case 'checkbox': |
| 997 |
if ( $pm_value ) { |
| 998 |
$output = __( 'Yes', 'edit-flow' ); |
| 999 |
} else { |
| 1000 |
$output = __( 'No', 'edit-flow' ); |
| 1001 |
} |
| 1002 |
break; |
| 1003 |
case 'user': |
| 1004 |
if ( empty( $pm_value ) ) { |
| 1005 |
break; |
| 1006 |
} |
| 1007 |
$userdata = get_user_by( 'id', $pm_value ); |
| 1008 |
if ( is_object( $userdata ) ) { |
| 1009 |
$output = esc_html( $userdata->display_name ); |
| 1010 |
} |
| 1011 |
break; |
| 1012 |
default: |
| 1013 |
break; |
| 1014 |
} |
| 1015 |
return $output; |
| 1016 |
} |
| 1017 |
|
| 1018 |
/** |
| 1019 |
* Update an existing editorial metadata term if the term_id exists. |
| 1020 |
* |
| 1021 |
* @since 0.7 |
| 1022 |
* |
| 1023 |
* @param int $term_id The term's unique ID. |
| 1024 |
* @param array $args Any values that need to be updated for the term. |
| 1025 |
* @return object|WP_Error $updated_term The updated term or a WP_Error object if something disastrous happened. |
| 1026 |
*/ |
| 1027 |
public function update_editorial_metadata_term( $term_id, $args ) { |
| 1028 |
|
| 1029 |
$new_args = array(); |
| 1030 |
$old_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); |
| 1031 |
if ( $old_term ) { |
| 1032 |
$old_args = array( |
| 1033 |
'position' => $old_term->position, |
| 1034 |
'name' => $old_term->name, |
| 1035 |
'slug' => $old_term->slug, |
| 1036 |
'description' => $old_term->description, |
| 1037 |
'type' => $old_term->type, |
| 1038 |
'viewable' => $old_term->viewable, |
| 1039 |
); |
| 1040 |
} |
| 1041 |
$new_args = array_merge( $old_args, $args ); |
| 1042 |
|
| 1043 |
// We're encoding metadata that isn't supported by default in the term's description field. |
| 1044 |
$args_to_encode = array( |
| 1045 |
'description' => $new_args['description'], |
| 1046 |
'position' => $new_args['position'], |
| 1047 |
'type' => $new_args['type'], |
| 1048 |
'viewable' => $new_args['viewable'], |
| 1049 |
); |
| 1050 |
$encoded_description = $this->get_encoded_description( $args_to_encode ); |
| 1051 |
$new_args['description'] = $encoded_description; |
| 1052 |
|
| 1053 |
$updated_term = wp_update_term( $term_id, self::metadata_taxonomy, $new_args ); |
| 1054 |
|
| 1055 |
// Reset the internal object cache. |
| 1056 |
$this->editorial_metadata_terms_cache = array(); |
| 1057 |
|
| 1058 |
$updated_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); |
| 1059 |
return $updated_term; |
| 1060 |
} |
| 1061 |
|
| 1062 |
/** |
| 1063 |
* Insert a new editorial metadata term. |
| 1064 |
* |
| 1065 |
* @todo Handle conflicts with existing terms at that position (if relevant). |
| 1066 |
* |
| 1067 |
* @since 0.7 |
| 1068 |
* |
| 1069 |
* @param array $args Arguments for creating the term. |
| 1070 |
* @return object|WP_Error The new term or a WP_Error object. |
| 1071 |
*/ |
| 1072 |
public function insert_editorial_metadata_term( $args ) { |
| 1073 |
|
| 1074 |
|
| 1075 |
// Term is always added to the end of the list. |
| 1076 |
$default_position = count( $this->get_editorial_metadata_terms() ) + 2; |
| 1077 |
$defaults = array( |
| 1078 |
'position' => $default_position, |
| 1079 |
'name' => '', |
| 1080 |
'slug' => '', |
| 1081 |
'description' => '', |
| 1082 |
'type' => '', |
| 1083 |
'viewable' => false, |
| 1084 |
); |
| 1085 |
$args = array_merge( $defaults, $args ); |
| 1086 |
$term_name = $args['name']; |
| 1087 |
unset( $args['name'] ); |
| 1088 |
|
| 1089 |
// We're encoding metadata that isn't supported by default in the term's description field. |
| 1090 |
$args_to_encode = array( |
| 1091 |
'description' => $args['description'], |
| 1092 |
'position' => $args['position'], |
| 1093 |
'type' => $args['type'], |
| 1094 |
'viewable' => $args['viewable'], |
| 1095 |
); |
| 1096 |
$encoded_description = $this->get_encoded_description( $args_to_encode ); |
| 1097 |
$args['description'] = $encoded_description; |
| 1098 |
|
| 1099 |
$inserted_term = wp_insert_term( $term_name, self::metadata_taxonomy, $args ); |
| 1100 |
|
| 1101 |
// Reset the internal object cache. |
| 1102 |
$this->editorial_metadata_terms_cache = array(); |
| 1103 |
|
| 1104 |
return $inserted_term; |
| 1105 |
} |
| 1106 |
|
| 1107 |
/** |
| 1108 |
* Settings and other management code. |
| 1109 |
*/ |
| 1110 |
|
| 1111 |
/** |
| 1112 |
* Delete an existing editorial metadata term. |
| 1113 |
* |
| 1114 |
* @since 0.7 |
| 1115 |
* |
| 1116 |
* @param int $term_id The term we want deleted. |
| 1117 |
* @return bool $result Whether or not the term was deleted. |
| 1118 |
*/ |
| 1119 |
public function delete_editorial_metadata_term( $term_id ) { |
| 1120 |
$result = wp_delete_term( $term_id, self::metadata_taxonomy ); |
| 1121 |
|
| 1122 |
// Reset the internal object cache. |
| 1123 |
$this->editorial_metadata_terms_cache = array(); |
| 1124 |
|
| 1125 |
return $result; |
| 1126 |
} |
| 1127 |
|
| 1128 |
/** |
| 1129 |
* Generate a link to one of the editorial metadata actions. |
| 1130 |
* |
| 1131 |
* @since 0.7 |
| 1132 |
* |
| 1133 |
* @param array $args (optional) Action and any query args to add to the URL. |
| 1134 |
* @return string $link Direct link to complete the action. |
| 1135 |
*/ |
| 1136 |
public function get_link( $args = array() ) { |
| 1137 |
if ( ! isset( $args['action'] ) ) { |
| 1138 |
$args['action'] = ''; |
| 1139 |
} |
| 1140 |
if ( ! isset( $args['page'] ) ) { |
| 1141 |
$args['page'] = $this->module->settings_slug; |
| 1142 |
} |
| 1143 |
// Add other things we may need depending on the action. |
| 1144 |
switch ( $args['action'] ) { |
| 1145 |
case 'make-viewable': |
| 1146 |
case 'make-hidden': |
| 1147 |
case 'delete-term': |
| 1148 |
$args['nonce'] = wp_create_nonce( $args['action'] ); |
| 1149 |
break; |
| 1150 |
default: |
| 1151 |
break; |
| 1152 |
} |
| 1153 |
return add_query_arg( $args, get_admin_url( null, 'admin.php' ) ); |
| 1154 |
} |
| 1155 |
|
| 1156 |
/** |
| 1157 |
* Handles a request to add a new piece of editorial metadata. |
| 1158 |
*/ |
| 1159 |
public function handle_add_editorial_metadata() { |
| 1160 |
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Nonce verified below. |
| 1161 |
if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) |
| 1162 |
|| $_GET['page'] != $this->module->settings_slug || 'add-term' != $_POST['form-action'] ) { |
| 1163 |
return; |
| 1164 |
} |
| 1165 |
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended |
| 1166 |
|
| 1167 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1168 |
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-add-nonce' ) ) { |
| 1169 |
wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); |
| 1170 |
} |
| 1171 |
|
| 1172 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1173 |
wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); |
| 1174 |
} |
| 1175 |
|
| 1176 |
// Sanitize all of the user-entered values. |
| 1177 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_text_field(). |
| 1178 |
$term_name = isset( $_POST['metadata_name'] ) ? sanitize_text_field( trim( $_POST['metadata_name'] ) ) : ''; |
| 1179 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_title(). |
| 1180 |
$term_slug = ( ! empty( $_POST['metadata_slug'] ) ) ? sanitize_title( $_POST['metadata_slug'] ) : sanitize_title( $term_name ); |
| 1181 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by wp_filter_nohtml_kses(). |
| 1182 |
$term_description = isset( $_POST['metadata_description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['metadata_description'] ) ) ) : ''; |
| 1183 |
$term_type = isset( $_POST['metadata_type'] ) ? sanitize_key( $_POST['metadata_type'] ) : ''; |
| 1184 |
|
| 1185 |
EditFlow()->settings->form_errors = array(); |
| 1186 |
|
| 1187 |
/** |
| 1188 |
* Form validation for adding new editorial metadata term. |
| 1189 |
* |
| 1190 |
* Details: |
| 1191 |
* - "name", "slug", and "type" are required fields |
| 1192 |
* - "description" can accept a limited amount of HTML, and is optional |
| 1193 |
*/ |
| 1194 |
// Field is required. |
| 1195 |
if ( empty( $term_name ) ) { |
| 1196 |
EditFlow()->settings->form_errors['name'] = __( 'Please enter a name for the editorial metadata.', 'edit-flow' ); |
| 1197 |
} |
| 1198 |
// Field is required. |
| 1199 |
if ( empty( $term_slug ) ) { |
| 1200 |
EditFlow()->settings->form_errors['slug'] = __( 'Please enter a slug for the editorial metadata.', 'edit-flow' ); |
| 1201 |
} |
| 1202 |
if ( term_exists( $term_slug ) ) { |
| 1203 |
EditFlow()->settings->form_errors['name'] = __( 'Name conflicts with existing term. Please choose another.', 'edit-flow' ); |
| 1204 |
} |
| 1205 |
// Check to ensure a term with the same name doesn't exist. |
| 1206 |
if ( $this->get_editorial_metadata_term_by( 'name', $term_name, self::metadata_taxonomy ) ) { |
| 1207 |
EditFlow()->settings->form_errors['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); |
| 1208 |
} |
| 1209 |
// Check to ensure a term with the same slug doesn't exist. |
| 1210 |
if ( $this->get_editorial_metadata_term_by( 'slug', $term_slug ) ) { |
| 1211 |
EditFlow()->settings->form_errors['slug'] = __( 'Slug already in use. Please choose another.', 'edit-flow' ); |
| 1212 |
} |
| 1213 |
// Check to make sure the status doesn't already exist as another term because otherwise we'd get a weird slug. |
| 1214 |
// Check that the term name doesn't exceed 200 chars. |
| 1215 |
if ( strlen( $term_name ) > 200 ) { |
| 1216 |
EditFlow()->settings->form_errors['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); |
| 1217 |
} |
| 1218 |
// Metadata type needs to pass our whitelist check. |
| 1219 |
$metadata_types = $this->get_supported_metadata_types(); |
| 1220 |
if ( empty( $_POST['metadata_type'] ) || ! isset( $metadata_types[ $_POST['metadata_type'] ] ) ) { |
| 1221 |
EditFlow()->settings->form_errors['type'] = __( 'Please select a valid metadata type.', 'edit-flow' ); |
| 1222 |
} |
| 1223 |
// Metadata viewable needs to be a valid Yes or No. |
| 1224 |
$term_viewable = false; |
| 1225 |
if ( isset( $_POST['metadata_viewable'] ) && 'yes' == $_POST['metadata_viewable'] ) { |
| 1226 |
$term_viewable = true; |
| 1227 |
} |
| 1228 |
|
| 1229 |
// Kick out if there are any errors. |
| 1230 |
if ( count( EditFlow()->settings->form_errors ) ) { |
| 1231 |
$_REQUEST['error'] = 'form-error'; |
| 1232 |
return; |
| 1233 |
} |
| 1234 |
|
| 1235 |
// Try to add the status. |
| 1236 |
$args = array( |
| 1237 |
'name' => $term_name, |
| 1238 |
'description' => $term_description, |
| 1239 |
'slug' => $term_slug, |
| 1240 |
'type' => $term_type, |
| 1241 |
'viewable' => $term_viewable, |
| 1242 |
); |
| 1243 |
$return = $this->insert_editorial_metadata_term( $args ); |
| 1244 |
if ( is_wp_error( $return ) ) { |
| 1245 |
wp_die( esc_html__( 'Error adding term.', 'edit-flow' ) ); |
| 1246 |
} |
| 1247 |
|
| 1248 |
$redirect_url = add_query_arg( array( |
| 1249 |
'page' => $this->module->settings_slug, |
| 1250 |
'message' => 'term-added', |
| 1251 |
), get_admin_url( null, 'admin.php' ) ); |
| 1252 |
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. |
| 1253 |
wp_redirect( $redirect_url ); |
| 1254 |
exit; |
| 1255 |
} |
| 1256 |
|
| 1257 |
/** |
| 1258 |
* Handles a request to edit an editorial metadata |
| 1259 |
*/ |
| 1260 |
public function handle_edit_editorial_metadata() { |
| 1261 |
if ( ! isset( $_POST['submit'], $_GET['page'], $_GET['action'], $_GET['term-id'] ) |
| 1262 |
|| $_GET['page'] != $this->module->settings_slug || 'edit-term' != $_GET['action'] ) { |
| 1263 |
return; |
| 1264 |
} |
| 1265 |
|
| 1266 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1267 |
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'editorial-metadata-edit-nonce' ) ) { |
| 1268 |
wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); |
| 1269 |
} |
| 1270 |
|
| 1271 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1272 |
wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); |
| 1273 |
} |
| 1274 |
|
| 1275 |
$existing_term = $this->get_editorial_metadata_term_by( 'id', (int) $_GET['term-id'] ); |
| 1276 |
if ( ! $existing_term ) { |
| 1277 |
wp_die( esc_html( $this->module->messages['term-missing'] ) ); |
| 1278 |
} |
| 1279 |
|
| 1280 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_text_field(). |
| 1281 |
$new_name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; |
| 1282 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by wp_filter_nohtml_kses(). |
| 1283 |
$new_description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; |
| 1284 |
|
| 1285 |
/** |
| 1286 |
* Form validation for editing editorial metadata term |
| 1287 |
* |
| 1288 |
* Details |
| 1289 |
* - "name", "slug", and "type" are required fields |
| 1290 |
* - "description" can accept a limited amount of HTML, and is optional |
| 1291 |
*/ |
| 1292 |
EditFlow()->settings->form_errors = array(); |
| 1293 |
// Check if name field was filled in. |
| 1294 |
if ( empty( $new_name ) ) { |
| 1295 |
EditFlow()->settings->form_errors['name'] = __( 'Please enter a name for the editorial metadata', 'edit-flow' ); |
| 1296 |
} |
| 1297 |
|
| 1298 |
// Check that the name isn't numeric. |
| 1299 |
if ( is_numeric( $new_name ) ) { |
| 1300 |
EditFlow()->settings->form_errors['name'] = __( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ); |
| 1301 |
} |
| 1302 |
|
| 1303 |
$term_exists = term_exists( sanitize_title( $new_name ) ); |
| 1304 |
if ( $term_exists && (int) $term_exists !== (int) $existing_term->term_id ) { |
| 1305 |
EditFlow()->settings->form_errors['name'] = __( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ); |
| 1306 |
} |
| 1307 |
|
| 1308 |
// Check to ensure a term with the same name doesn't exist. |
| 1309 |
$search_term = $this->get_editorial_metadata_term_by( 'name', $new_name ); |
| 1310 |
if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { |
| 1311 |
EditFlow()->settings->form_errors['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); |
| 1312 |
} |
| 1313 |
// Or that the term name doesn't map to an existing term's slug. |
| 1314 |
$search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $new_name ) ); |
| 1315 |
if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { |
| 1316 |
EditFlow()->settings->form_errors['name'] = __( 'Name conflicts with slug for another term. Please choose something else.', 'edit-flow' ); |
| 1317 |
} |
| 1318 |
|
| 1319 |
// Check that the term name doesn't exceed 200 chars. |
| 1320 |
if ( strlen( $new_name ) > 200 ) { |
| 1321 |
EditFlow()->settings->form_errors['name'] = __( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ); |
| 1322 |
} |
| 1323 |
// Make sure the viewable state is valid. |
| 1324 |
$new_viewable = false; |
| 1325 |
if ( isset( $_POST['viewable'] ) && 'yes' == $_POST['viewable'] ) { |
| 1326 |
$new_viewable = true; |
| 1327 |
} |
| 1328 |
|
| 1329 |
// Kick out if there are any errors. |
| 1330 |
if ( count( EditFlow()->settings->form_errors ) ) { |
| 1331 |
$_REQUEST['error'] = 'form-error'; |
| 1332 |
return; |
| 1333 |
} |
| 1334 |
|
| 1335 |
// Try to add the metadata term. |
| 1336 |
$args = array( |
| 1337 |
'name' => $new_name, |
| 1338 |
'description' => $new_description, |
| 1339 |
'viewable' => $new_viewable, |
| 1340 |
); |
| 1341 |
$return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); |
| 1342 |
if ( is_wp_error( $return ) ) { |
| 1343 |
wp_die( esc_html__( 'Error updating term.', 'edit-flow' ) ); |
| 1344 |
} |
| 1345 |
|
| 1346 |
$redirect_url = add_query_arg( array( |
| 1347 |
'page' => $this->module->settings_slug, |
| 1348 |
'message' => 'term-updated', |
| 1349 |
), get_admin_url( null, 'admin.php' ) ); |
| 1350 |
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. |
| 1351 |
wp_redirect( $redirect_url ); |
| 1352 |
exit; |
| 1353 |
} |
| 1354 |
|
| 1355 |
/** |
| 1356 |
* Handle a $_GET request to change the visibility of an Editorial Metadata term. |
| 1357 |
* |
| 1358 |
* @since 0.7 |
| 1359 |
*/ |
| 1360 |
public function handle_change_editorial_metadata_visibility() { |
| 1361 |
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified below. |
| 1362 |
// Check that the current GET request is our GET request. |
| 1363 |
if ( ! isset( $_GET['page'], $_GET['action'], $_GET['term-id'], $_GET['nonce'] ) |
| 1364 |
|| $_GET['page'] !== $this->module->settings_slug || ! in_array( $_GET['action'], array( 'make-viewable', 'make-hidden' ), true ) ) { |
| 1365 |
return; |
| 1366 |
} |
| 1367 |
// phpcs:enable WordPress.Security.NonceVerification.Recommended |
| 1368 |
|
| 1369 |
// Check for proper nonce. |
| 1370 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1371 |
if ( ! isset( $_GET['nonce'] ) || ( ! wp_verify_nonce( $_GET['nonce'], 'make-viewable' ) && ! wp_verify_nonce( $_GET['nonce'], 'make-hidden' ) ) ) { |
| 1372 |
wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); |
| 1373 |
} |
| 1374 |
|
| 1375 |
// Only allow users with the proper caps. |
| 1376 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1377 |
wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); |
| 1378 |
} |
| 1379 |
|
| 1380 |
$term_id = (int) $_GET['term-id']; |
| 1381 |
$args = array(); |
| 1382 |
if ( 'make-viewable' == $_GET['action'] ) { |
| 1383 |
$args['viewable'] = true; |
| 1384 |
} elseif ( 'make-hidden' == $_GET['action'] ) { |
| 1385 |
$args['viewable'] = false; |
| 1386 |
} |
| 1387 |
|
| 1388 |
$return = $this->update_editorial_metadata_term( $term_id, $args ); |
| 1389 |
if ( is_wp_error( $return ) ) { |
| 1390 |
wp_die( esc_html__( 'Error updating term.', 'edit-flow' ) ); |
| 1391 |
} |
| 1392 |
|
| 1393 |
$redirect_url = $this->get_link( array( 'message' => 'term-visibility-changed' ) ); |
| 1394 |
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. |
| 1395 |
wp_redirect( $redirect_url ); |
| 1396 |
exit; |
| 1397 |
} |
| 1398 |
|
| 1399 |
/** |
| 1400 |
* Handle the request to update a given Editorial Metadata term via inline edit. |
| 1401 |
* |
| 1402 |
* @since 0.7 |
| 1403 |
*/ |
| 1404 |
public function handle_ajax_inline_save_term() { |
| 1405 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1406 |
if ( ! isset( $_POST['inline_edit'] ) || ! wp_verify_nonce( $_POST['inline_edit'], 'editorial-metadata-inline-edit-nonce' ) ) { |
| 1407 |
wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); |
| 1408 |
} |
| 1409 |
|
| 1410 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1411 |
wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); |
| 1412 |
} |
| 1413 |
|
| 1414 |
$term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : 0; |
| 1415 |
$existing_term = $this->get_editorial_metadata_term_by( 'id', $term_id ); |
| 1416 |
if ( ! $existing_term ) { |
| 1417 |
wp_die( esc_html( $this->module->messages['term-missing'] ) ); |
| 1418 |
} |
| 1419 |
|
| 1420 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_text_field(). |
| 1421 |
$metadata_name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; |
| 1422 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by wp_filter_nohtml_kses(). |
| 1423 |
$metadata_description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; |
| 1424 |
|
| 1425 |
/** |
| 1426 |
* Form validation for editing editorial metadata term. |
| 1427 |
*/ |
| 1428 |
// Check if name field was filled in. |
| 1429 |
if ( empty( $metadata_name ) ) { |
| 1430 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Please enter a name for the editorial metadata', 'edit-flow' ) ); |
| 1431 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1432 |
} |
| 1433 |
|
| 1434 |
// Check that the name isn't numeric. |
| 1435 |
if ( is_numeric( $metadata_name ) ) { |
| 1436 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Please enter a valid, non-numeric name for the editorial metadata.', 'edit-flow' ) ); |
| 1437 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1438 |
} |
| 1439 |
|
| 1440 |
// Check that the term name doesn't exceed 200 chars. |
| 1441 |
if ( strlen( $metadata_name ) > 200 ) { |
| 1442 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Name cannot exceed 200 characters. Please try a shorter name.', 'edit-flow' ) ); |
| 1443 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1444 |
} |
| 1445 |
|
| 1446 |
// Check to make sure the status doesn't already exist as another term because otherwise we'd get a fatal error. |
| 1447 |
$term_exists = term_exists( sanitize_title( $metadata_name ) ); |
| 1448 |
if ( $term_exists && (int) $term_exists !== (int) $term_id ) { |
| 1449 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Metadata name conflicts with existing term. Please choose another.', 'edit-flow' ) ); |
| 1450 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1451 |
} |
| 1452 |
|
| 1453 |
// Check to ensure a term with the same name doesn't exist. |
| 1454 |
$search_term = $this->get_editorial_metadata_term_by( 'name', $metadata_name ); |
| 1455 |
if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { |
| 1456 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Name already in use. Please choose another.', 'edit-flow' ) ); |
| 1457 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1458 |
} |
| 1459 |
|
| 1460 |
// Or that the term name doesn't map to an existing term's slug. |
| 1461 |
$search_term = $this->get_editorial_metadata_term_by( 'slug', sanitize_title( $metadata_name ) ); |
| 1462 |
if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { |
| 1463 |
$change_error = new WP_Error( 'invalid', esc_html__( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); |
| 1464 |
wp_die( esc_html( $change_error->get_error_message() ) ); |
| 1465 |
} |
| 1466 |
|
| 1467 |
// Prepare the term name and description for saving. |
| 1468 |
$args = array( |
| 1469 |
'name' => $metadata_name, |
| 1470 |
'description' => $metadata_description, |
| 1471 |
); |
| 1472 |
$return = $this->update_editorial_metadata_term( $existing_term->term_id, $args ); |
| 1473 |
if ( ! is_wp_error( $return ) ) { |
| 1474 |
set_current_screen( 'edit-editorial-metadata' ); |
| 1475 |
$wp_list_table = new EF_Editorial_Metadata_List_Table(); |
| 1476 |
$wp_list_table->prepare_items(); |
| 1477 |
// single_row() echoes its own output; column_* callbacks are |
| 1478 |
// responsible for escaping, as per WP_List_Table's contract. |
| 1479 |
$wp_list_table->single_row( $return ); |
| 1480 |
wp_die(); |
| 1481 |
} else { |
| 1482 |
/* Translators: 1: the name of the term that could not be found */ |
| 1483 |
$change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the term: <strong>%s</strong>', 'edit-flow' ), esc_html( $metadata_name ) ) ); |
| 1484 |
wp_die( wp_kses( $change_error->get_error_message(), array( 'strong' => array() ) ) ); |
| 1485 |
} |
| 1486 |
} |
| 1487 |
|
| 1488 |
/** |
| 1489 |
* Handle the ajax request to update all of the term positions. |
| 1490 |
* |
| 1491 |
* @since 0.7 |
| 1492 |
*/ |
| 1493 |
public function handle_ajax_update_term_positions() { |
| 1494 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1495 |
if ( ! isset( $_POST['editorial_metadata_sortable_nonce'] ) || ! wp_verify_nonce( $_POST['editorial_metadata_sortable_nonce'], 'editorial-metadata-sortable' ) ) { |
| 1496 |
$this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] ); |
| 1497 |
return; |
| 1498 |
} |
| 1499 |
|
| 1500 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1501 |
$this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); |
| 1502 |
return; |
| 1503 |
} |
| 1504 |
|
| 1505 |
if ( ! isset( $_POST['term_positions'] ) || ! is_array( $_POST['term_positions'] ) ) { |
| 1506 |
$this->print_ajax_response( 'error', __( 'Terms not set.', 'edit-flow' ) ); |
| 1507 |
return; |
| 1508 |
} |
| 1509 |
|
| 1510 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are cast to int below. |
| 1511 |
foreach ( $_POST['term_positions'] as $position => $term_id ) { |
| 1512 |
|
| 1513 |
// Have to add 1 to the position because the index started with zero. |
| 1514 |
$args = array( |
| 1515 |
'position' => (int) $position + 1, |
| 1516 |
); |
| 1517 |
$return = $this->update_editorial_metadata_term( (int) $term_id, $args ); |
| 1518 |
// @todo Check that this was a valid return. |
| 1519 |
} |
| 1520 |
$this->print_ajax_response( 'success', $this->module->messages['term-position-updated'] ); |
| 1521 |
} |
| 1522 |
|
| 1523 |
/** |
| 1524 |
* Handles a request to delete an editorial metadata term. |
| 1525 |
*/ |
| 1526 |
public function handle_delete_editorial_metadata() { |
| 1527 |
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified below. |
| 1528 |
if ( ! isset( $_GET['page'], $_GET['action'], $_GET['term-id'] ) |
| 1529 |
|| $_GET['page'] != $this->module->settings_slug || 'delete-term' != $_GET['action'] ) { |
| 1530 |
return; |
| 1531 |
} |
| 1532 |
// phpcs:enable WordPress.Security.NonceVerification.Recommended |
| 1533 |
|
| 1534 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). |
| 1535 |
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'delete-term' ) ) { |
| 1536 |
wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); |
| 1537 |
} |
| 1538 |
|
| 1539 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 1540 |
wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); |
| 1541 |
} |
| 1542 |
|
| 1543 |
$existing_term = $this->get_editorial_metadata_term_by( 'id', (int) $_GET['term-id'] ); |
| 1544 |
if ( ! $existing_term ) { |
| 1545 |
wp_die( esc_html( $this->module->messages['term-missing'] ) ); |
| 1546 |
} |
| 1547 |
|
| 1548 |
$result = $this->delete_editorial_metadata_term( $existing_term->term_id ); |
| 1549 |
if ( ! $result || is_wp_error( $result ) ) { |
| 1550 |
wp_die( esc_html__( 'Error deleting term.', 'edit-flow' ) ); |
| 1551 |
} |
| 1552 |
|
| 1553 |
$redirect_url = add_query_arg( array( |
| 1554 |
'page' => $this->module->settings_slug, |
| 1555 |
'message' => 'term-deleted', |
| 1556 |
), get_admin_url( null, 'admin.php' ) ); |
| 1557 |
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. |
| 1558 |
wp_redirect( $redirect_url ); |
| 1559 |
exit; |
| 1560 |
} |
| 1561 |
|
| 1562 |
/** |
| 1563 |
* Register settings for notifications so we can partially use the Settings API |
| 1564 |
* (We use the Settings API for form generation, but not saving) |
| 1565 |
* |
| 1566 |
* @since 0.7 |
| 1567 |
* @uses add_settings_section(), add_settings_field() |
| 1568 |
*/ |
| 1569 |
public function register_settings() { |
| 1570 |
add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); |
| 1571 |
add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); |
| 1572 |
} |
| 1573 |
|
| 1574 |
/** |
| 1575 |
* Choose the post types for editorial metadata |
| 1576 |
* |
| 1577 |
* @since 0.7 |
| 1578 |
*/ |
| 1579 |
public function settings_post_types_option() { |
| 1580 |
global $edit_flow; |
| 1581 |
$edit_flow->settings->helper_option_custom_post_type( $this->module ); |
| 1582 |
} |
| 1583 |
|
| 1584 |
/** |
| 1585 |
* Validate data entered by the user. |
| 1586 |
* |
| 1587 |
* @since 0.7 |
| 1588 |
* |
| 1589 |
* @param array $new_options New values that have been entered by the user. |
| 1590 |
* @return array $new_options Form values after they've been sanitized. |
| 1591 |
*/ |
| 1592 |
public function settings_validate( $new_options ) { |
| 1593 |
|
| 1594 |
// Whitelist validation for the post type options. |
| 1595 |
if ( ! isset( $new_options['post_types'] ) ) { |
| 1596 |
$new_options['post_types'] = array(); |
| 1597 |
} |
| 1598 |
$new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); |
| 1599 |
|
| 1600 |
return $new_options; |
| 1601 |
} |
| 1602 |
|
| 1603 |
/** |
| 1604 |
* Prepare and display the configuration view for editorial metadata. |
| 1605 |
* |
| 1606 |
* There are four primary components: |
| 1607 |
* - Form to add a new Editorial Metadata term |
| 1608 |
* - Form generated by the settings API for managing Editorial Metadata options |
| 1609 |
* - Table of existing Editorial Metadata terms with ability to take actions on each |
| 1610 |
* - Full page width view for editing a single Editorial Metadata term |
| 1611 |
* |
| 1612 |
* @since 0.7 |
| 1613 |
*/ |
| 1614 |
public function print_configure_view() { |
| 1615 |
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is a view function only, data is escaped on output. |
| 1616 |
global $edit_flow; |
| 1617 |
$wp_list_table = new EF_Editorial_Metadata_List_Table(); |
| 1618 |
$wp_list_table->prepare_items(); |
| 1619 |
?> |
| 1620 |
<script type="text/javascript"> |
| 1621 |
var ef_confirm_delete_term_string = "<?php echo esc_js( __( 'Are you sure you want to delete this term? Any metadata for this term will remain but will not be visible unless this term is re-added.', 'edit-flow' ) ); ?>"; |
| 1622 |
</script> |
| 1623 |
<?php if ( ! isset( $_GET['action'] ) || ( isset( $_GET['action'] ) && 'edit-term' != $_GET['action'] ) ) : ?> |
| 1624 |
<div id="col-right"> |
| 1625 |
<div class="col-wrap"> |
| 1626 |
<form id="" action="" method="post"> |
| 1627 |
<?php $wp_list_table->display(); ?> |
| 1628 |
<?php wp_nonce_field( 'editorial-metadata-sortable', 'editorial-metadata-sortable' ); ?> |
| 1629 |
</form> |
| 1630 |
</div> |
| 1631 |
</div><!-- /col-right --> |
| 1632 |
<?php $wp_list_table->inline_edit(); ?> |
| 1633 |
<?php endif; ?> |
| 1634 |
|
| 1635 |
<?php if ( isset( $_GET['action'], $_GET['term-id'] ) && 'edit-term' == $_GET['action'] ) : ?> |
| 1636 |
<?php /** Full page width view for editing a given editorial metadata term. **/ ?> |
| 1637 |
<?php |
| 1638 |
// Check whether the term exists. |
| 1639 |
$term_id = (int) $_GET['term-id']; |
| 1640 |
$term = $this->get_editorial_metadata_term_by( 'id', $term_id ); |
| 1641 |
if ( ! $term ) { |
| 1642 |
echo '<div class="error"><p>' . esc_html( $this->module->messages['term-missing'] ) . '</p></div>'; |
| 1643 |
return; |
| 1644 |
} |
| 1645 |
$metadata_types = $this->get_supported_metadata_types(); |
| 1646 |
$type = $term->type; |
| 1647 |
$edit_term_link = $this->get_link( array( |
| 1648 |
'action' => 'edit-term', |
| 1649 |
'term-id' => $term->term_id, |
| 1650 |
) ); |
| 1651 |
|
| 1652 |
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are used for form re-population, escaped on output. |
| 1653 |
$name = ( isset( $_POST['name'] ) ) ? stripslashes( $_POST['name'] ) : $term->name; |
| 1654 |
$description = ( isset( $_POST['description'] ) ) ? stripslashes( $_POST['description'] ) : $term->description; |
| 1655 |
if ( $term->viewable ) { |
| 1656 |
$viewable = 'yes'; |
| 1657 |
} else { |
| 1658 |
$viewable = 'no'; |
| 1659 |
} |
| 1660 |
$viewable = ( isset( $_POST['viewable'] ) ) ? stripslashes( $_POST['viewable'] ) : $viewable; |
| 1661 |
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 1662 |
?> |
| 1663 |
|
| 1664 |
<form method="post" action="<?php echo esc_url( $edit_term_link ); ?>" > |
| 1665 |
<input type="hidden" name="action" value="editedtag" /> |
| 1666 |
<input type="hidden" name="tag_id" value="<?php echo esc_attr( $term->term_id ); ?>" /> |
| 1667 |
<input type="hidden" name="taxonomy" value="<?php echo esc_attr( self::metadata_taxonomy ); ?>" /> |
| 1668 |
<?php |
| 1669 |
wp_original_referer_field(); |
| 1670 |
wp_nonce_field( 'editorial-metadata-edit-nonce' ); |
| 1671 |
?> |
| 1672 |
<table class="form-table"> |
| 1673 |
<tr class="form-field form-required"> |
| 1674 |
<th scope="row" valign="top"><label for="name"><?php _e( 'Name', 'edit-flow' ); ?></label></th> |
| 1675 |
<td><input name="name" id="name" type="text" value="<?php echo esc_attr( $name ); ?>" size="40" aria-required="true" /> |
| 1676 |
<?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is for labeling the metadata field.', 'edit-flow' ) ); ?> |
| 1677 |
</tr> |
| 1678 |
<tr class="form-field"> |
| 1679 |
<th scope="row" valign="top"><?php _e( 'Slug', 'edit-flow' ); ?></th> |
| 1680 |
<td> |
| 1681 |
<input type="text" disabled="disabled" value="<?php echo esc_attr( $term->slug ); ?>" /> |
| 1682 |
<p class="description"><?php _e( 'The slug cannot be changed once the term has been created.', 'edit-flow' ); ?></p> |
| 1683 |
</td> |
| 1684 |
</tr> |
| 1685 |
<tr class="form-field"> |
| 1686 |
<th scope="row" valign="top"><label for="description"><?php _e( 'Description', 'edit-flow' ); ?></label></th> |
| 1687 |
<td> |
| 1688 |
<textarea name="description" id="description" rows="5" cols="50" style="width: 97%;"><?php echo esc_html( $description ); ?></textarea> |
| 1689 |
<?php $edit_flow->settings->helper_print_error_or_description( 'description', __( 'The description can be used to communicate with your team about what the metadata is for.', 'edit-flow' ) ); ?> |
| 1690 |
</td> |
| 1691 |
</tr> |
| 1692 |
<tr class="form-field"> |
| 1693 |
<th scope="row" valign="top"><?php _e( 'Type', 'edit-flow' ); ?></th> |
| 1694 |
<td> |
| 1695 |
<input type="text" disabled="disabled" value="<?php echo esc_attr( $metadata_types[ $type ] ); ?>" /> |
| 1696 |
<p class="description"><?php _e( 'The metadata type cannot be changed once created.', 'edit-flow' ); ?></p> |
| 1697 |
</td> |
| 1698 |
</tr> |
| 1699 |
<tr class="form-field"> |
| 1700 |
<th scope="row" valign="top"><?php _e( 'Viewable', 'edit-flow' ); ?></th> |
| 1701 |
<td> |
| 1702 |
<?php |
| 1703 |
$metadata_viewable_options = array( |
| 1704 |
'no' => __( 'No', 'edit-flow' ), |
| 1705 |
'yes' => __( 'Yes', 'edit-flow' ), |
| 1706 |
); |
| 1707 |
?> |
| 1708 |
<select id="viewable" name="viewable"> |
| 1709 |
<?php foreach ( $metadata_viewable_options as $metadata_viewable_key => $metadata_viewable_value ) : ?> |
| 1710 |
<option value="<?php echo esc_attr( $metadata_viewable_key ); ?>" <?php selected( $viewable, $metadata_viewable_key ); ?>><?php echo esc_html( $metadata_viewable_value ); ?></option> |
| 1711 |
<?php endforeach; ?> |
| 1712 |
</select> |
| 1713 |
<?php $edit_flow->settings->helper_print_error_or_description( 'viewable', __( 'When viewable, metadata can be seen on views other than the edit post view (e.g. calendar, manage posts, story budget, etc.)', 'edit-flow' ) ); ?> |
| 1714 |
</td> |
| 1715 |
</tr> |
| 1716 |
<input type="hidden" name="<?php echo esc_attr( self::metadata_taxonomy ); ?>'_type" value="<?php echo esc_attr( $type ); ?>" /> |
| 1717 |
</table> |
| 1718 |
<p class="submit"> |
| 1719 |
<?php submit_button( __( 'Update Metadata Term', 'edit-flow' ), 'primary', 'submit', false ); ?> |
| 1720 |
<a class="cancel-settings-link" href="<?php echo esc_url( add_query_arg( 'page', $this->module->settings_slug, get_admin_url( null, 'admin.php' ) ) ); ?>"><?php _e( 'Cancel', 'edit-flow' ); ?></a> |
| 1721 |
</p> |
| 1722 |
</form> |
| 1723 |
|
| 1724 |
<?php else : ?> |
| 1725 |
<?php /** If not in full-screen edit term mode, we can create new terms or change options **/ ?> |
| 1726 |
<div id="col-left"> |
| 1727 |
<div class="col-wrap"> |
| 1728 |
<div class="form-wrap"> |
| 1729 |
<h3 class="nav-tab-wrapper"> |
| 1730 |
<?php $add_new_nav_class = ! isset( $_GET['action'] ) || 'change-options' != $_GET['action'] ? 'nav-tab-active' : ''; ?> |
| 1731 |
<a href="<?php echo esc_url( add_query_arg( array( 'page' => $this->module->settings_slug ), get_admin_url( null, 'admin.php' ) ) ); ?>" class="nav-tab <?php echo esc_attr( $add_new_nav_class ); ?>"><?php esc_html_e( 'Add New', 'edit-flow' ); ?></a> |
| 1732 |
<?php $options_nav_class = isset( $_GET['action'] ) && 'change-options' == $_GET['action'] ? 'nav-tab-active' : ''; ?> |
| 1733 |
<a href=" |
| 1734 |
<?php |
| 1735 |
echo esc_url( add_query_arg( array( |
| 1736 |
'page' => $this->module->settings_slug, |
| 1737 |
'action' => 'change-options', |
| 1738 |
), get_admin_url( null, 'admin.php' ) ) ); |
| 1739 |
?> |
| 1740 |
" class="nav-tab <?php echo esc_attr( $options_nav_class ); ?>"><?php esc_html_e( 'Options', 'edit-flow' ); ?></a> |
| 1741 |
</h3> |
| 1742 |
<?php if ( isset( $_GET['action'] ) && 'change-options' == $_GET['action'] ) : ?> |
| 1743 |
<?php /** Basic form built on WP Settings API for outputting Editorial Metadata options **/ ?> |
| 1744 |
<form class="basic-settings" action=" |
| 1745 |
<?php |
| 1746 |
echo esc_url( add_query_arg( array( |
| 1747 |
'page' => $this->module->settings_slug, |
| 1748 |
'action' => 'change-options', |
| 1749 |
), get_admin_url( null, 'admin.php' ) ) ); |
| 1750 |
?> |
| 1751 |
" method="post"> |
| 1752 |
<?php settings_fields( $this->module->options_group_name ); ?> |
| 1753 |
<?php do_settings_sections( $this->module->options_group_name ); ?> |
| 1754 |
<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="<?php echo esc_attr( $this->module->name ); ?>" /> |
| 1755 |
<?php submit_button(); ?> |
| 1756 |
</form> |
| 1757 |
<?php else : ?> |
| 1758 |
<?php /** Custom form for adding a new Editorial Metadata term. **/ ?> |
| 1759 |
<?php // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Form fields escaped with esc_attr() or compared against whitelists. ?> |
| 1760 |
<form class="add:the-list:" action="<?php echo esc_url( add_query_arg( array( 'page' => $this->module->settings_slug ), get_admin_url( null, 'admin.php' ) ) ); ?>" method="post" id="addmetadata" name="addmetadata"> |
| 1761 |
<div class="form-field form-required"> |
| 1762 |
<label for="metadata_name"><?php _e( 'Name', 'edit-flow' ); ?></label> |
| 1763 |
<input type="text" aria-required="true" size="20" maxlength="200" id="metadata_name" name="metadata_name" value="<?php echo ( empty( $_POST['metadata_name'] ) ? '' : esc_attr( wp_unslash( $_POST['metadata_name'] ) ) ); ?>" /> |
| 1764 |
<?php $edit_flow->settings->helper_print_error_or_description( 'name', __( 'The name is for labeling the metadata field.', 'edit-flow' ) ); ?> |
| 1765 |
</div> |
| 1766 |
<div class="form-field form-required"> |
| 1767 |
<label for="metadata_slug"><?php _e( 'Slug', 'edit-flow' ); ?></label> |
| 1768 |
<input type="text" aria-required="true" size="20" maxlength="200" id="metadata_slug" name="metadata_slug" value="<?php echo ( empty( $_POST['metadata_slug'] ) ? '' : esc_attr( wp_unslash( $_POST['metadata_slug'] ) ) ); ?>" /> |
| 1769 |
<?php $edit_flow->settings->helper_print_error_or_description( 'slug', __( 'The "slug" is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.', 'edit-flow' ) ); ?> |
| 1770 |
</div> |
| 1771 |
<div class="form-field"> |
| 1772 |
<label for="metadata_description"><?php _e( 'Description', 'edit-flow' ); ?></label> |
| 1773 |
<textarea cols="40" rows="5" id="metadata_description" name="metadata_description"><?php echo ( empty( $_POST['metadata_description'] ) ? '' : esc_textarea( sanitize_textarea_field( wp_unslash( $_POST['metadata_description'] ) ) ) ); ?></textarea> |
| 1774 |
<?php $edit_flow->settings->helper_print_error_or_description( 'description', __( 'The description can be used to communicate with your team about what the metadata is for.', 'edit-flow' ) ); ?> |
| 1775 |
</div> |
| 1776 |
<div class="form-field form-required"> |
| 1777 |
<label for="metadata_type"><?php _e( 'Type', 'edit-flow' ); ?></label> |
| 1778 |
<?php |
| 1779 |
$metadata_types = $this->get_supported_metadata_types(); |
| 1780 |
// Select the previously selected metadata type if a valid one exists. |
| 1781 |
$current_metadata_type = ( isset( $_POST['metadata_type'] ) && in_array( $_POST['metadata_type'], array_keys( $metadata_types ), true ) ) ? $_POST['metadata_type'] : false; |
| 1782 |
?> |
| 1783 |
<select id="metadata_type" name="metadata_type"> |
| 1784 |
<?php foreach ( $metadata_types as $metadata_type => $metadata_type_name ) : ?> |
| 1785 |
<option value="<?php echo esc_attr( $metadata_type ); ?>" <?php selected( $metadata_type, $current_metadata_type ); ?>><?php echo esc_html( $metadata_type_name ); ?></option> |
| 1786 |
<?php endforeach; ?> |
| 1787 |
</select> |
| 1788 |
<?php $edit_flow->settings->helper_print_error_or_description( 'type', __( 'Indicate the type of editorial metadata.', 'edit-flow' ) ); ?> |
| 1789 |
</div> |
| 1790 |
<div class="form-field form-required"> |
| 1791 |
<label for="metadata_viewable"><?php _e( 'Viewable', 'edit-flow' ); ?></label> |
| 1792 |
<?php |
| 1793 |
$metadata_viewable_options = array( |
| 1794 |
'no' => __( 'No', 'edit-flow' ), |
| 1795 |
'yes' => __( 'Yes', 'edit-flow' ), |
| 1796 |
); |
| 1797 |
$current_metadata_viewable = ( isset( $_POST['metadata_viewable'] ) && in_array( $_POST['metadata_viewable'], array_keys( $metadata_viewable_options ), true ) ) ? $_POST['metadata_viewable'] : 'no'; |
| 1798 |
?> |
| 1799 |
<select id="metadata_viewable" name="metadata_viewable"> |
| 1800 |
<?php foreach ( $metadata_viewable_options as $metadata_viewable_key => $metadata_viewable_value ) : ?> |
| 1801 |
<option value="<?php echo esc_attr( $metadata_viewable_key ); ?>" <?php selected( $current_metadata_viewable, $metadata_viewable_key ); ?>><?php echo esc_html( $metadata_viewable_value ); ?></option> |
| 1802 |
<?php endforeach; ?> |
| 1803 |
</select> |
| 1804 |
<?php $edit_flow->settings->helper_print_error_or_description( 'viewable', __( 'When viewable, metadata can be seen on views other than the edit post view (e.g. calendar, manage posts, story budget, etc.)', 'edit-flow' ) ); ?> |
| 1805 |
</div> |
| 1806 |
<?php wp_nonce_field( 'editorial-metadata-add-nonce' ); ?> |
| 1807 |
<input type="hidden" id="form-action" name="form-action" value="add-term" /> |
| 1808 |
<?php submit_button( __( 'Add New Metadata Term', 'edit-flow' ) ); ?> |
| 1809 |
</form> |
| 1810 |
<?php // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?> |
| 1811 |
<?php endif; ?> |
| 1812 |
</div> |
| 1813 |
</div> |
| 1814 |
</div> |
| 1815 |
|
| 1816 |
<?php |
| 1817 |
endif; |
| 1818 |
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 1819 |
} |
| 1820 |
} |
| 1821 |
|
| 1822 |
} |
| 1823 |
|
| 1824 |
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound |
| 1825 |
|
| 1826 |
/** |
| 1827 |
* Management interface for Editorial Metadata. Extends WP_List_Table class. |
| 1828 |
*/ |
| 1829 |
class EF_Editorial_Metadata_List_Table extends WP_List_Table { |
| 1830 |
|
| 1831 |
/** |
| 1832 |
* Callback arguments. |
| 1833 |
* |
| 1834 |
* @var array |
| 1835 |
*/ |
| 1836 |
protected $callback_args; |
| 1837 |
|
| 1838 |
/** |
| 1839 |
* Taxonomy name. |
| 1840 |
* |
| 1841 |
* @var string |
| 1842 |
*/ |
| 1843 |
protected $taxonomy; |
| 1844 |
|
| 1845 |
/** |
| 1846 |
* Taxonomy object. |
| 1847 |
* |
| 1848 |
* @var object |
| 1849 |
*/ |
| 1850 |
protected $tax; |
| 1851 |
|
| 1852 |
/** |
| 1853 |
* Construct the class |
| 1854 |
*/ |
| 1855 |
public function __construct() { |
| 1856 |
global $edit_flow; |
| 1857 |
|
| 1858 |
$this->taxonomy = EF_Editorial_Metadata::metadata_taxonomy; |
| 1859 |
|
| 1860 |
$this->tax = get_taxonomy( $this->taxonomy ); |
| 1861 |
|
| 1862 |
$columns = $this->get_columns(); |
| 1863 |
$hidden = array( |
| 1864 |
'position', |
| 1865 |
); |
| 1866 |
$sortable = array(); |
| 1867 |
|
| 1868 |
$this->_column_headers = array( $columns, $hidden, $sortable ); |
| 1869 |
|
| 1870 |
parent::__construct( array( |
| 1871 |
'plural' => 'editorial metadata', |
| 1872 |
'singular' => 'editorial metadata', |
| 1873 |
) ); |
| 1874 |
} |
| 1875 |
|
| 1876 |
/** |
| 1877 |
* Prepare the items to be displayed on the list table |
| 1878 |
* |
| 1879 |
* @since 0.7 |
| 1880 |
*/ |
| 1881 |
public function prepare_items() { |
| 1882 |
global $edit_flow; |
| 1883 |
$this->items = $edit_flow->editorial_metadata->get_editorial_metadata_terms(); |
| 1884 |
|
| 1885 |
$this->set_pagination_args( array( |
| 1886 |
'total_items' => count( $this->items ), |
| 1887 |
'per_page' => count( $this->items ), |
| 1888 |
) ); |
| 1889 |
} |
| 1890 |
|
| 1891 |
/** |
| 1892 |
* Message to be displayed when there is no editorial metadata |
| 1893 |
* |
| 1894 |
* @since 0.7 |
| 1895 |
*/ |
| 1896 |
public function no_items() { |
| 1897 |
_e( 'No editorial metadata found.', 'edit-flow' ); |
| 1898 |
} |
| 1899 |
|
| 1900 |
/** |
| 1901 |
* Register the columns to appear in the table |
| 1902 |
* |
| 1903 |
* @since 0.7 |
| 1904 |
*/ |
| 1905 |
public function get_columns() { |
| 1906 |
|
| 1907 |
$columns = array( |
| 1908 |
'position' => __( 'Position', 'edit-flow' ), |
| 1909 |
'name' => __( 'Name', 'edit-flow' ), |
| 1910 |
'type' => __( 'Metadata Type', 'edit-flow' ), |
| 1911 |
'description' => __( 'Description', 'edit-flow' ), |
| 1912 |
'viewable' => __( 'Viewable', 'edit-flow' ), |
| 1913 |
); |
| 1914 |
return $columns; |
| 1915 |
} |
| 1916 |
|
| 1917 |
/** |
| 1918 |
* Prepare a single row of Editorial Metadata. |
| 1919 |
* |
| 1920 |
* @since 0.7 |
| 1921 |
* |
| 1922 |
* @param object $term The current term we're displaying. |
| 1923 |
* @param int $level Level is always zero because it isn't a parent-child tax. |
| 1924 |
*/ |
| 1925 |
public function single_row( $term, $level = 0 ) { |
| 1926 |
static $alternate_class = ''; |
| 1927 |
$alternate_class = ( '' == $alternate_class ? ' alternate' : '' ); |
| 1928 |
|
| 1929 |
printf( '<tr id="term-%d" class="term-static%s">', (int) $term->term_id, esc_attr( $alternate_class ) ); |
| 1930 |
// single_row_columns() echoes its own output; the column_* callbacks are |
| 1931 |
// responsible for escaping, as per WP_List_Table's contract. |
| 1932 |
$this->single_row_columns( $term ); |
| 1933 |
echo '</tr>'; |
| 1934 |
} |
| 1935 |
|
| 1936 |
/** |
| 1937 |
* Handle the column output when there's no method for it. |
| 1938 |
* |
| 1939 |
* @since 0.7 |
| 1940 |
* |
| 1941 |
* @param object $item Editorial Metadata term as an object. |
| 1942 |
* @param string $column_name How the column was registered at birth. |
| 1943 |
*/ |
| 1944 |
public function column_default( $item, $column_name ) { |
| 1945 |
|
| 1946 |
switch ( $column_name ) { |
| 1947 |
case 'position': |
| 1948 |
case 'type': |
| 1949 |
case 'description': |
| 1950 |
return esc_html( $item->$column_name ); |
| 1951 |
case 'viewable': |
| 1952 |
if ( $item->viewable ) { |
| 1953 |
return __( 'Yes', 'edit-flow' ); |
| 1954 |
} else { |
| 1955 |
return __( 'No', 'edit-flow' ); |
| 1956 |
} |
| 1957 |
break; |
| 1958 |
default: |
| 1959 |
break; |
| 1960 |
} |
| 1961 |
} |
| 1962 |
|
| 1963 |
/** |
| 1964 |
* Column for displaying the term's name and associated actions. |
| 1965 |
* |
| 1966 |
* @since 0.7 |
| 1967 |
* |
| 1968 |
* @param object $item Editorial Metadata term as an object. |
| 1969 |
*/ |
| 1970 |
public function column_name( $item ) { |
| 1971 |
global $edit_flow; |
| 1972 |
$item_edit_link = esc_url( $edit_flow->editorial_metadata->get_link( array( |
| 1973 |
'action' => 'edit-term', |
| 1974 |
'term-id' => $item->term_id, |
| 1975 |
) ) ); |
| 1976 |
$item_delete_link = esc_url( $edit_flow->editorial_metadata->get_link( array( |
| 1977 |
'action' => 'delete-term', |
| 1978 |
'term-id' => $item->term_id, |
| 1979 |
) ) ); |
| 1980 |
|
| 1981 |
$out = '<strong><a class="row-title" href="' . $item_edit_link . '">' . esc_html( $item->name ) . '</a></strong>'; |
| 1982 |
|
| 1983 |
$actions = array(); |
| 1984 |
$actions['edit'] = "<a href='$item_edit_link'>" . __( 'Edit', 'edit-flow' ) . '</a>'; |
| 1985 |
$actions['inline hide-if-no-js'] = '<a href="#" class="editinline">' . __( 'Quick Edit', 'edit-flow' ) . '</a>'; |
| 1986 |
if ( $item->viewable ) { |
| 1987 |
$actions['change-visibility make-hidden'] = '<a title="' . esc_attr( __( 'Hidden metadata can only be viewed on the edit post view.', 'edit-flow' ) ) . '" href="' . esc_url( $edit_flow->editorial_metadata->get_link( array( |
| 1988 |
'action' => 'make-hidden', |
| 1989 |
'term-id' => $item->term_id, |
| 1990 |
) ) ) . '">' . __( 'Make Hidden', 'edit-flow' ) . '</a>'; |
| 1991 |
} else { |
| 1992 |
$actions['change-visibility make-viewable'] = '<a title="' . esc_attr( __( 'When viewable, metadata can be seen on views other than the edit post view (e.g. calendar, manage posts, story budget, etc.)', 'edit-flow' ) ) . '" href="' . esc_url( $edit_flow->editorial_metadata->get_link( array( |
| 1993 |
'action' => 'make-viewable', |
| 1994 |
'term-id' => $item->term_id, |
| 1995 |
) ) ) . '">' . __( 'Make Viewable', 'edit-flow' ) . '</a>'; |
| 1996 |
} |
| 1997 |
$actions['delete delete-status'] = "<a href='$item_delete_link'>" . __( 'Delete', 'edit-flow' ) . '</a>'; |
| 1998 |
|
| 1999 |
$out .= $this->row_actions( $actions, false ); |
| 2000 |
$out .= '<div class="hidden" id="inline_' . $item->term_id . '">'; |
| 2001 |
$out .= '<div class="name">' . esc_html( $item->name ) . '</div>'; |
| 2002 |
$out .= '<div class="description">' . esc_html( $item->description ) . '</div>'; |
| 2003 |
$out .= '</div>'; |
| 2004 |
|
| 2005 |
return $out; |
| 2006 |
} |
| 2007 |
|
| 2008 |
/** |
| 2009 |
* Admins can use the inline edit capability to quickly make changes to the title or description. |
| 2010 |
* |
| 2011 |
* @since 0.7 |
| 2012 |
*/ |
| 2013 |
public function inline_edit() { |
| 2014 |
|
| 2015 |
?> |
| 2016 |
<form method="get" action=""><table style="display: none"><tbody id="inlineedit"> |
| 2017 |
<tr id="inline-edit" class="inline-edit-row" style="display: none"><td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>" class="colspanchange"> |
| 2018 |
<fieldset><div class="inline-edit-col"> |
| 2019 |
<h4><?php _e( 'Quick Edit', 'edit-flow' ); ?></h4> |
| 2020 |
<label> |
| 2021 |
<span class="title"><?php _e( 'Name', 'edit-flow' ); ?></span> |
| 2022 |
<span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" maxlength="200" /></span> |
| 2023 |
</label> |
| 2024 |
<label> |
| 2025 |
<span class="title"><?php _e( 'Description', 'edit-flow' ); ?></span> |
| 2026 |
<span class="input-text-wrap"><input type="text" name="description" class="pdescription" value="" /></span> |
| 2027 |
</label> |
| 2028 |
</div></fieldset> |
| 2029 |
<p class="inline-edit-save submit"> |
| 2030 |
<a accesskey="c" href="#inline-edit" title="<?php _e( 'Cancel', 'edit-flow' ); ?>" class="cancel button-secondary alignleft"><?php _e( 'Cancel', 'edit-flow' ); ?></a> |
| 2031 |
<?php $update_text = __( 'Update Metadata Term', 'edit-flow' ); ?> |
| 2032 |
<a accesskey="s" href="#inline-edit" title="<?php echo esc_attr( $update_text ); ?>" class="save button-primary alignright"><?php echo esc_html( $update_text ); ?></a> |
| 2033 |
<img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" /> |
| 2034 |
<span class="error" style="display:none;"></span> |
| 2035 |
<?php wp_nonce_field( 'editorial-metadata-inline-edit-nonce', 'inline_edit', false ); ?> |
| 2036 |
<br class="clear" /> |
| 2037 |
</p> |
| 2038 |
</td></tr> |
| 2039 |
</tbody></table></form> |
| 2040 |
<?php |
| 2041 |
} |
| 2042 |
} |
| 2043 |
|