PluginProbe
Gallery Box / trunk
Gallery Box vtrunk
trunk 1.3.1 1.3.3 1.4.0 1.4.1 1.4.2 1.5.1 1.7.35
gallery-box / admin / src / CMB2 / includes / CMB2.php

CMB2.php in Gallery Box trunk, at admin/src/CMB2/includes/CMB2.php

1,831 lines 55.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CMB2 - The core metabox object
4 *
5 * @category WordPress_Plugin
6 * @package CMB2
7 * @author CMB2 team
8 * @license GPL-2.0+
9 * @link https://cmb2.io
10 *
11 * @property-read string $cmb_id
12 * @property-read array $meta_box
13 * @property-read array $updated
14 * @property-read bool $has_columns
15 * @property-read array $tax_metaboxes_to_remove
16 */
17
18 /**
19 * The main CMB2 object for storing box data/properties.
20 */
21 class CMB2 extends CMB2_Base {
22
23 /**
24 * The object properties name.
25 *
26 * @var string
27 * @since 2.2.3
28 */
29 protected $properties_name = 'meta_box';
30
31 /**
32 * Metabox Config array
33 *
34 * @var array
35 * @since 0.9.0
36 */
37 protected $meta_box = array();
38
39 /**
40 * Type of object registered for metabox. (e.g., post, user, or comment)
41 *
42 * @var string
43 * @since 1.0.0
44 */
45 protected $mb_object_type = null;
46
47 /**
48 * List of fields that are changed/updated on save
49 *
50 * @var array
51 * @since 1.1.0
52 */
53 protected $updated = array();
54
55 /**
56 * Metabox Defaults
57 *
58 * @var array
59 * @since 1.0.1
60 */
61 protected $mb_defaults = array(
62 'id' => '',
63 'title' => '',
64 // Post type slug, or 'user', 'term', 'comment', or 'options-page'.
65 'object_types' => array(),
66
67 /**
68 * The context within the screen where the boxes should display. Available contexts vary
69 * from screen to screen. Post edit screen contexts include 'normal', 'side', and 'advanced'.
70 *
71 * For placement in locations outside of a metabox, other options include:
72 * 'form_top', 'before_permalink', 'after_title', 'after_editor'
73 *
74 * Comments screen contexts include 'normal' and 'side'. Default is 'normal'.
75 */
76 'context' => 'normal',
77 'priority' => 'high', // Or 10 for options pages.
78 'show_names' => true, // Show field names on the left.
79 'show_on_cb' => null, // Callback to determine if metabox should display.
80 'show_on' => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb'.
81 'cmb_styles' => true, // Include CMB2 stylesheet.
82 'enqueue_js' => true, // Include CMB2 JS.
83 'fields' => array(),
84
85 /**
86 * Handles hooking CMB2 forms/metaboxes into the post/attachement/user/options-page screens
87 * and handles hooking in and saving those fields.
88 */
89 'hookup' => true,
90 'save_fields' => true, // Will not save during hookup if false.
91 'closed' => false, // Default metabox to being closed.
92 'taxonomies' => array(),
93 'new_user_section' => 'add-new-user', // or 'add-existing-user'.
94 'new_term_section' => true,
95 'show_in_rest' => false,
96 'classes' => null, // Optionally add classes to the CMB2 wrapper.
97 'classes_cb' => '', // Optionally add classes to the CMB2 wrapper (via a callback).
98
99 /*
100 * The following parameter is for post alternate-context metaboxes only.
101 *
102 * To output the fields 'naked' (without a postbox wrapper/style), then
103 * add a `'remove_box_wrap' => true` to your metabox registration array.
104 */
105 'remove_box_wrap' => false,
106
107 /*
108 * The following parameter is any additional arguments passed as $callback_args
109 * to add_meta_box, if/when applicable.
110 *
111 * CMB2 does not use these arguments in the add_meta_box callback, however, these args
112 * are parsed for certain special properties, like determining Gutenberg/block-editor
113 * compatibility.
114 *
115 * Examples:
116 *
117 * - Make sure default editor is used as metabox is not compatible with block editor
118 * [ '__block_editor_compatible_meta_box' => false/true ]
119 *
120 * - Or declare this box exists for backwards compatibility
121 * [ '__back_compat_meta_box' => false ]
122 *
123 * More: https://wordpress.org/gutenberg/handbook/extensibility/meta-box/
124 */
125 'mb_callback_args' => null,
126
127 /*
128 * The following parameters are for options-page metaboxes,
129 * and several are passed along to add_menu_page()/add_submenu_page()
130 */
131
132 // 'menu_title' => null, // Falls back to 'title' (above). Do not define here so we can set a fallback.
133 'message_cb' => '', // Optionally define the options-save message (via a callback).
134 'option_key' => '', // The actual option key and admin menu page slug.
135 'parent_slug' => '', // Used as first param in add_submenu_page().
136 'capability' => 'manage_options', // Cap required to view options-page.
137 'icon_url' => '', // Menu icon. Only applicable if 'parent_slug' is left empty.
138 'position' => null, // Menu position. Only applicable if 'parent_slug' is left empty.
139
140 'admin_menu_hook' => 'admin_menu', // Alternately 'network_admin_menu' to add network-level options page.
141 'display_cb' => false, // Override the options-page form output (CMB2_Hookup::options_page_output()).
142 'save_button' => '', // The text for the options-page save button. Defaults to 'Save'.
143 'disable_settings_errors' => false, // On settings pages (not options-general.php sub-pages), allows disabling.
144 'tab_group' => '', // Tab-group identifier, enables options page tab navigation.
145 // 'tab_title' => null, // Falls back to 'title' (above). Do not define here so we can set a fallback.
146 // 'autoload' => true, // Defaults to true, the options-page option will be autloaded.
147 );
148
149 /**
150 * Metabox field objects
151 *
152 * @var array
153 * @since 2.0.3
154 */
155 protected $fields = array();
156
157 /**
158 * An array of hidden fields to output at the end of the form
159 *
160 * @var array
161 * @since 2.0.0
162 */
163 protected $hidden_fields = array();
164
165 /**
166 * Array of key => value data for saving. Likely $_POST data.
167 *
168 * @var string
169 * @since 2.0.0
170 */
171 protected $generated_nonce = '';
172
173 /**
174 * Whether there are fields to be shown in columns. Set in CMB2::add_field().
175 *
176 * @var bool
177 * @since 2.2.2
178 */
179 protected $has_columns = false;
180
181 /**
182 * If taxonomy field is requesting to remove_default, we store the taxonomy here.
183 *
184 * @var array
185 * @since 2.2.3
186 */
187 protected $tax_metaboxes_to_remove = array();
188
189 /**
190 * Get started
191 *
192 * @since 0.4.0
193 * @param array $config Metabox config array.
194 * @param integer $object_id Optional object id.
195 */
196 public function __construct( $config, $object_id = 0 ) {
197
198 if ( empty( $config['id'] ) ) {
199 wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) );
200 }
201
202 $this->cmb_id = $config['id'];
203 $this->meta_box = wp_parse_args( $config, $this->mb_defaults );
204 $this->meta_box['fields'] = array();
205
206 // Ensures object_types is an array.
207 $this->set_prop( 'object_types', $this->box_types() );
208 $this->object_id( $object_id );
209
210 if ( $this->is_options_page_mb() ) {
211
212 // Check initial priority.
213 if ( empty( $config['priority'] ) ) {
214
215 // If not explicitly defined, Reset the priority to 10
216 // Fixes https://github.com/CMB2/CMB2/issues/1410.
217 $this->meta_box['priority'] = 10;
218 }
219
220 $this->init_options_mb();
221 }
222
223 $this->mb_object_type();
224
225 if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) {
226 $this->add_fields( $config['fields'] );
227 }
228
229 CMB2_Boxes::add( $this );
230
231 /**
232 * Hook during initiation of CMB2 object
233 *
234 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
235 *
236 * @param array $cmb This CMB2 object
237 */
238 do_action( "cmb2_init_{$this->cmb_id}", $this );
239
240 // Hook in the hookup... how meta.
241 add_action( "cmb2_init_hookup_{$this->cmb_id}", array( 'CMB2_Hookup', 'maybe_init_and_hookup' ) );
242
243 // Hook in the rest api functionality.
244 add_action( "cmb2_init_hookup_{$this->cmb_id}", array( 'CMB2_REST', 'maybe_init_and_hookup' ) );
245 }
246
247 /**
248 * Loops through and displays fields
249 *
250 * @since 1.0.0
251 * @param int $object_id Object ID.
252 * @param string $object_type Type of object being saved. (e.g., post, user, or comment).
253 *
254 * @return CMB2
255 */
256 public function show_form( $object_id = 0, $object_type = '' ) {
257 $this->render_form_open( $object_id, $object_type );
258
259 foreach ( $this->prop( 'fields' ) as $field_args ) {
260 $this->render_field( $field_args );
261 }
262
263 return $this->render_form_close( $object_id, $object_type );
264 }
265
266 /**
267 * Outputs the opening form markup and runs corresponding hooks:
268 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
269 *
270 * @since 2.2.0
271 * @param integer $object_id Object ID.
272 * @param string $object_type Object type.
273 *
274 * @return CMB2
275 */
276 public function render_form_open( $object_id = 0, $object_type = '' ) {
277 $object_type = $this->object_type( $object_type );
278 $object_id = $this->object_id( $object_id );
279
280 echo "\n<!-- Begin CMB2 Fields -->\n";
281
282 $this->nonce_field();
283
284 /**
285 * Hook before form table begins
286 *
287 * @param array $cmb_id The current box ID.
288 * @param int $object_id The ID of the current object.
289 * @param string $object_type The type of object you are working with.
290 * Usually `post` (this applies to all post-types).
291 * Could also be `comment`, `user` or `options-page`.
292 * @param array $cmb This CMB2 object.
293 */
294 do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
295
296 /**
297 * Hook before form table begins
298 *
299 * The first dynamic portion of the hook name, $object_type, is the type of object
300 * you are working with. Usually `post` (this applies to all post-types).
301 * Could also be `comment`, `user` or `options-page`.
302 *
303 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
304 *
305 * @param array $cmb_id The current box ID
306 * @param int $object_id The ID of the current object
307 * @param array $cmb This CMB2 object
308 */
309 do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
310
311 echo '<div class="', esc_attr( $this->box_classes() ), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
312
313 return $this;
314 }
315
316 /**
317 * Defines the classes for the CMB2 form/wrap.
318 *
319 * @since 2.0.0
320 * @return string Space concatenated list of classes
321 */
322 public function box_classes() {
323
324 $classes = array( 'cmb2-wrap', 'form-table' );
325
326 // Use the callback to fetch classes.
327 if ( $added_classes = $this->get_param_callback_result( 'classes_cb' ) ) {
328 $added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
329 $classes = array_merge( $classes, $added_classes );
330 }
331
332 if ( $added_classes = $this->prop( 'classes' ) ) {
333 $added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
334 $classes = array_merge( $classes, $added_classes );
335 }
336
337 /**
338 * Add our context classes for non-standard metaboxes.
339 *
340 * @since 2.2.4
341 */
342 if ( $this->is_alternate_context_box() ) {
343 $context = array();
344
345 // Include custom class if requesting no title.
346 if ( ! $this->prop( 'title' ) && ! $this->prop( 'remove_box_wrap' ) ) {
347 $context[] = 'cmb2-context-wrap-no-title';
348 }
349
350 // Include a generic context wrapper.
351 $context[] = 'cmb2-context-wrap';
352
353 // Include a context-type based context wrapper.
354 $context[] = 'cmb2-context-wrap-' . $this->prop( 'context' );
355
356 // Include an ID based context wrapper as well.
357 $context[] = 'cmb2-context-wrap-' . $this->prop( 'id' );
358
359 // And merge all the classes back into the array.
360 $classes = array_merge( $classes, $context );
361 }
362
363 /**
364 * Globally filter box wrap classes
365 *
366 * @since 2.2.2
367 *
368 * @param string $classes Array of classes for the cmb2-wrap.
369 * @param CMB2 $cmb This CMB2 object.
370 */
371 $classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
372
373 $split = array();
374 foreach ( array_filter( $classes ) as $class ) {
375 foreach ( explode( ' ', $class ) as $_class ) {
376 // Clean up & sanitize.
377 $split[] = sanitize_html_class( strip_tags( $_class ) );
378 }
379 }
380 $classes = $split;
381
382 // Remove any duplicates.
383 $classes = array_unique( $classes );
384
385 // Make it a string.
386 return implode( ' ', $classes );
387 }
388
389 /**
390 * Outputs the closing form markup and runs corresponding hooks:
391 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
392 *
393 * @since 2.2.0
394 * @param integer $object_id Object ID.
395 * @param string $object_type Object type.
396 *
397 * @return CMB2
398 */
399 public function render_form_close( $object_id = 0, $object_type = '' ) {
400 $object_type = $this->object_type( $object_type );
401 $object_id = $this->object_id( $object_id );
402
403 echo '</div></div>';
404
405 $this->render_hidden_fields();
406
407 /**
408 * Hook after form form has been rendered
409 *
410 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
411 *
412 * The first dynamic portion of the hook name, $object_type, is the type of object
413 * you are working with. Usually `post` (this applies to all post-types).
414 * Could also be `comment`, `user` or `options-page`.
415 *
416 * @param int $object_id The ID of the current object
417 * @param array $cmb This CMB2 object
418 */
419 do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
420
421 /**
422 * Hook after form form has been rendered
423 *
424 * @param array $cmb_id The current box ID.
425 * @param int $object_id The ID of the current object.
426 * @param string $object_type The type of object you are working with.
427 * Usually `post` (this applies to all post-types).
428 * Could also be `comment`, `user` or `options-page`.
429 * @param array $cmb This CMB2 object.
430 */
431 do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
432
433 echo "\n<!-- End CMB2 Fields -->\n";
434
435 return $this;
436 }
437
438 /**
439 * Renders a field based on the field type
440 *
441 * @since 2.2.0
442 * @param array $field_args A field configuration array.
443 * @return mixed CMB2_Field object if successful.
444 */
445 public function render_field( $field_args ) {
446 $field_args['context'] = $this->prop( 'context' );
447
448 if ( 'group' === $field_args['type'] ) {
449
450 if ( ! isset( $field_args['show_names'] ) ) {
451 $field_args['show_names'] = $this->prop( 'show_names' );
452 }
453 $field = $this->render_group( $field_args );
454
455 } elseif ( 'hidden' === $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
456 // Save rendering for after the metabox.
457 $field = $this->add_hidden_field( $field_args );
458
459 } else {
460
461 $field_args['show_names'] = $this->prop( 'show_names' );
462
463 // Render default fields.
464 $field = $this->get_field( $field_args )->render_field();
465 }
466
467 return $field;
468 }
469
470 /**
471 * Render a group of fields.
472 *
473 * @param array|CMB2_Field $args Array of field arguments for a group field parent or the group parent field.
474 * @return CMB2_Field|null Group field object.
475 */
476 public function render_group( $args ) {
477 $field_group = false;
478
479 if ( $args instanceof CMB2_Field ) {
480 $field_group = 'group' === $args->type() ? $args : false;
481 } elseif ( isset( $args['id'], $args['fields'] ) && is_array( $args['fields'] ) ) {
482 $field_group = $this->get_field( $args );
483 }
484
485 if ( ! $field_group ) {
486 return;
487 }
488
489 $field_group->render_context = 'edit';
490 $field_group->peform_param_callback( 'render_row_cb' );
491
492 return $field_group;
493 }
494
495 /**
496 * The default callback to render a group of fields.
497 *
498 * @since 2.2.6
499 *
500 * @param array $field_args Array of field arguments for the group field parent.
501 * @param CMB2_Field $field_group The CMB2_Field group object.
502 *
503 * @return CMB2_Field|null Group field object.
504 */
505 public function render_group_callback( $field_args, $field_group ) {
506
507 // If field is requesting to be conditionally shown.
508 if ( ! $field_group || ! $field_group->should_show() ) {
509 return;
510 }
511
512 $field_group->index = 0;
513
514 $field_group->peform_param_callback( 'before_group' );
515
516 $desc = $field_group->args( 'description' );
517 $label = $field_group->args( 'name' );
518 $group_val = (array) $field_group->value();
519
520 echo '<div class="cmb-row cmb-repeat-group-wrap ', esc_attr( $field_group->row_classes() ), '" data-fieldtype="group"><div class="cmb-td"><div data-groupid="', esc_attr( $field_group->id() ), '" id="', esc_attr( $field_group->id() ), '_repeat" ', $this->group_wrap_attributes( $field_group ), '>';
521
522 if ( $desc || $label ) {
523 $class = $desc ? ' cmb-group-description' : '';
524 echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
525 if ( $label ) {
526 echo '<h2 class="cmb-group-name">', $label, '</h2>';
527 }
528 if ( $desc ) {
529 echo '<p class="cmb2-metabox-description">', $desc, '</p>';
530 }
531 echo '</div></div>';
532 }
533
534 if ( ! empty( $group_val ) ) {
535 foreach ( $group_val as $group_key => $field_id ) {
536 $this->render_group_row( $field_group );
537 $field_group->index++;
538 }
539 } else {
540 $this->render_group_row( $field_group );
541 }
542
543 if ( $field_group->args( 'repeatable' ) ) {
544 echo '<div class="cmb-row"><div class="cmb-td"><p class="cmb-add-row"><button type="button" data-selector="', esc_attr( $field_group->id() ), '_repeat" data-grouptitle="', esc_attr( $field_group->options( 'group_title' ) ), '" class="cmb-add-group-row button-secondary">', $field_group->options( 'add_button' ), '</button></p></div></div>';
545 }
546
547 echo '</div></div></div>';
548
549 $field_group->peform_param_callback( 'after_group' );
550
551 return $field_group;
552 }
553
554 /**
555 * Get the group wrap attributes, which are passed through a filter.
556 *
557 * @since 2.2.3
558 * @param CMB2_Field $field_group The group CMB2_Field object.
559 * @return string The attributes string.
560 */
561 public function group_wrap_attributes( $field_group ) {
562 $classes = 'cmb-nested cmb-field-list cmb-repeatable-group';
563 $classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
564 $classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
565
566 $group_wrap_attributes = array(
567 'class' => $classes,
568 'style' => 'width:100%;',
569 );
570
571 /**
572 * Allow for adding additional HTML attributes to a group wrapper.
573 *
574 * The attributes will be an array of key => value pairs for each attribute.
575 *
576 * @since 2.2.2
577 *
578 * @param string $group_wrap_attributes Current attributes array.
579 * @param CMB2_Field $field_group The group CMB2_Field object.
580 */
581 $group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group );
582
583 $atts = array();
584 foreach ( $group_wrap_attributes as $att => $att_value ) {
585 if ( ! CMB2_Utils::is_data_attribute( $att ) ) {
586 $att_value = htmlspecialchars( $att_value, ENT_COMPAT );
587 }
588
589 $atts[ sanitize_html_class( $att ) ] = sanitize_text_field( $att_value );
590 }
591
592 return CMB2_Utils::concat_attrs( $atts );
593 }
594
595 /**
596 * Render a repeatable group row
597 *
598 * @since 1.0.2
599 * @param CMB2_Field $field_group CMB2_Field group field object.
600 *
601 * @return CMB2
602 */
603 public function render_group_row( $field_group ) {
604
605 $field_group->peform_param_callback( 'before_group_row' );
606 $closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
607 $confirm_deletion = $field_group->options( 'remove_confirm' );
608 $confirm_deletion = ! empty( $confirm_deletion ) ? $confirm_deletion : '';
609
610 echo '
611 <div id="cmb-group-', $field_group->id(), '-', $field_group->index, '" class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
612
613 if ( $field_group->args( 'repeatable' ) ) {
614 echo '<button type="button" data-selector="', $field_group->id(), '_repeat" data-confirm="', esc_attr( $confirm_deletion ), '" class="dashicons-before dashicons-no-alt cmb-remove-group-row" title="', esc_attr( $field_group->options( 'remove_button' ) ), '"></button>';
615 }
616
617 echo '
618 <div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div>
619 <h3 class="cmb-group-title cmbhandle-title"><span>', $field_group->replace_hash( $field_group->options( 'group_title' ) ), '</span></h3>
620
621 <div class="inside cmb-td cmb-nested cmb-field-list">';
622 // Loop and render repeatable group fields.
623 foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
624 if ( 'hidden' === $field_args['type'] ) {
625
626 // Save rendering for after the metabox.
627 $this->add_hidden_field( $field_args, $field_group );
628
629 } else {
630
631 $field_args['show_names'] = $field_group->args( 'show_names' );
632 $field_args['context'] = $field_group->args( 'context' );
633
634 $this->get_field( $field_args, $field_group )->render_field();
635 }
636 }
637
638 if ( $field_group->args( 'repeatable' ) ) {
639 echo '
640 <div class="cmb-row cmb-remove-field-row">
641 <div class="cmb-remove-row">
642 <button type="button" data-selector="', $field_group->id(), '_repeat" data-confirm="', esc_attr( $confirm_deletion ), '" class="cmb-remove-group-row cmb-remove-group-row-button alignright button-secondary">', $field_group->options( 'remove_button' ), '</button>
643 </div>
644 </div>
645 ';
646 }
647 echo '
648 </div>
649 </div>
650 ';
651
652 $field_group->peform_param_callback( 'after_group_row' );
653
654 return $this;
655 }
656
657 /**
658 * Add a hidden field to the list of hidden fields to be rendered later.
659 *
660 * @since 2.0.0
661 *
662 * @param array $field_args Array of field arguments to be passed to CMB2_Field.
663 * @param CMB2_Field|null $field_group CMB2_Field group field object.
664 * @return CMB2_Field
665 */
666 public function add_hidden_field( $field_args, $field_group = null ) {
667 if ( isset( $field_args['field_args'] ) ) {
668 // For back-compatibility.
669 $field = new CMB2_Field( $field_args );
670 } else {
671 $field = $this->get_new_field( $field_args, $field_group );
672 }
673
674 $types = new CMB2_Types( $field );
675
676 if ( $field_group ) {
677 $types->iterator = $field_group->index;
678 }
679
680 $this->hidden_fields[] = $types;
681
682 return $field;
683 }
684
685 /**
686 * Loop through and output hidden fields
687 *
688 * @since 2.0.0
689 *
690 * @return CMB2
691 */
692 public function render_hidden_fields() {
693 if ( ! empty( $this->hidden_fields ) ) {
694 foreach ( $this->hidden_fields as $hidden ) {
695 $hidden->render();
696 }
697 }
698
699 return $this;
700 }
701
702 /**
703 * Returns array of sanitized field values (without saving them)
704 *
705 * @since 2.0.3
706 * @param array $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
707 * @return mixed
708 */
709 public function get_sanitized_values( array $data_to_sanitize ) {
710 $this->data_to_save = $data_to_sanitize;
711 $stored_id = $this->object_id();
712
713 // We do this So CMB will sanitize our data for us, but not save it.
714 $this->object_id( '_' );
715
716 // Ensure temp. data store is empty.
717 cmb2_options( 0 )->set();
718
719 // We want to get any taxonomy values back.
720 add_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
721
722 // Process/save fields.
723 $this->process_fields();
724
725 // Put things back the way they were.
726 remove_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
727
728 // Get data from temp. data store.
729 $sanitized_values = cmb2_options( 0 )->get_options();
730
731 // Empty out temp. data store again.
732 cmb2_options( 0 )->set();
733
734 // Reset the object id.
735 $this->object_id( $stored_id );
736
737 return $sanitized_values;
738 }
739
740 /**
741 * Loops through and saves field data
742 *
743 * @since 1.0.0
744 * @param int $object_id Object ID.
745 * @param string $object_type Type of object being saved. (e.g., post, user, or comment).
746 * @param array $data_to_save Array of key => value data for saving. Likely $_POST data.
747 *
748 * @return CMB2
749 */
750 public function save_fields( $object_id = 0, $object_type = '', $data_to_save = array() ) {
751
752 // Fall-back to $_POST data.
753 $this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
754 $object_id = $this->object_id( $object_id );
755 $object_type = $this->object_type( $object_type );
756
757 $this->process_fields();
758
759 // If options page, save the updated options.
760 if ( 'options-page' === $object_type ) {
761 cmb2_options( $object_id )->set();
762 }
763
764 return $this->after_save();
765 }
766
767 /**
768 * Process and save form fields
769 *
770 * @since 2.0.0
771 *
772 * @return CMB2
773 */
774 public function process_fields() {
775
776 $this->pre_process();
777
778 // Remove the show_on properties so saving works.
779 $this->prop( 'show_on', array() );
780
781 // save field ids of those that are updated.
782 $this->updated = array();
783
784 foreach ( $this->prop( 'fields' ) as $field_args ) {
785 $this->process_field( $field_args );
786 }
787
788 return $this;
789 }
790
791 /**
792 * Process and save a field
793 *
794 * @since 2.0.0
795 * @param array $field_args Array of field arguments.
796 *
797 * @return CMB2
798 */
799 public function process_field( $field_args ) {
800
801 switch ( $field_args['type'] ) {
802
803 case 'group':
804 if ( $this->save_group( $field_args ) ) {
805 $this->updated[] = $field_args['id'];
806 }
807
808 break;
809
810 case 'title':
811 // Don't process title fields.
812 break;
813
814 default:
815 $field = $this->get_new_field( $field_args );
816
817 if ( $field->save_field_from_data( $this->data_to_save ) ) {
818 $this->updated[] = $field->id();
819 }
820
821 break;
822 }
823
824 return $this;
825 }
826
827 /**
828 * Fires the "cmb2_{$object_type}_process_fields_{$cmb_id}" action hook.
829 *
830 * @since 2.2.2
831 *
832 * @return CMB2
833 */
834 public function pre_process() {
835 $object_type = $this->object_type();
836
837 /**
838 * Fires before fields have been processed/saved.
839 *
840 * The dynamic portion of the hook name, $object_type, refers to the
841 * metabox/form's object type
842 * Usually `post` (this applies to all post-types).
843 * Could also be `comment`, `user` or `options-page`.
844 *
845 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
846 *
847 * @param array $cmb This CMB2 object
848 * @param int $object_id The ID of the current object
849 */
850 do_action( "cmb2_{$object_type}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
851
852 return $this;
853 }
854
855 /**
856 * Fires the "cmb2_save_{$object_type}_fields" and
857 * "cmb2_save_{$object_type}_fields_{$cmb_id}" action hooks.
858 *
859 * @since 2.x.x
860 *
861 * @return CMB2
862 */
863 public function after_save() {
864 $object_type = $this->object_type();
865 $object_id = $this->object_id();
866
867 /**
868 * Fires after all fields have been saved.
869 *
870 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
871 * Usually `post` (this applies to all post-types).
872 * Could also be `comment`, `user` or `options-page`.
873 *
874 * @param int $object_id The ID of the current object
875 * @param array $cmb_id The current box ID
876 * @param string $updated Array of field ids that were updated.
877 * Will only include field ids that had values change.
878 * @param array $cmb This CMB2 object
879 */
880 do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
881
882 /**
883 * Fires after all fields have been saved.
884 *
885 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
886 *
887 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
888 * Usually `post` (this applies to all post-types).
889 * Could also be `comment`, `user` or `options-page`.
890 *
891 * @param int $object_id The ID of the current object
892 * @param string $updated Array of field ids that were updated.
893 * Will only include field ids that had values change.
894 * @param array $cmb This CMB2 object
895 */
896 do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
897
898 return $this;
899 }
900
901 /**
902 * Save a repeatable group
903 *
904 * @since 1.x.x
905 * @param array $args Field arguments array.
906 * @return mixed Return of CMB2_Field::update_data().
907 */
908 public function save_group( $args ) {
909 if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
910 return;
911 }
912
913 return $this->save_group_field( $this->get_new_field( $args ) );
914 }
915
916 /**
917 * Save a repeatable group
918 *
919 * @since 1.x.x
920 * @param CMB2_Field $field_group CMB2_Field group field object.
921 * @return mixed Return of CMB2_Field::update_data().
922 */
923 public function save_group_field( $field_group ) {
924 $base_id = $field_group->id();
925
926 if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
927 return;
928 }
929
930 $old = $field_group->get_data();
931 // Check if group field has sanitization_cb.
932 $group_vals = $field_group->sanitization_cb( $this->data_to_save[ $base_id ] );
933 $saved = array();
934
935 $field_group->index = 0;
936 $field_group->data_to_save = $this->data_to_save;
937
938 foreach ( array_values( $field_group->fields() ) as $field_args ) {
939 if ( 'title' === $field_args['type'] ) {
940 // Don't process title fields.
941 continue;
942 }
943
944 $field = $this->get_new_field( $field_args, $field_group );
945 $sub_id = $field->id( true );
946 if ( empty( $saved[ $field_group->index ] ) ) {
947 $saved[ $field_group->index ] = array();
948 }
949
950 foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
951
952 // Get value.
953 $new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
954 ? $group_vals[ $field_group->index ][ $sub_id ]
955 : false;
956
957 // Sanitize.
958 $new_val = $field->sanitization_cb( $new_val );
959
960 if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
961 if ( $field->args( 'repeatable' ) ) {
962 $_new_val = array();
963 foreach ( $new_val as $group_index => $grouped_data ) {
964 // Add the supporting data to the $saved array stack.
965 $saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
966 // Reset var to the actual value.
967 $_new_val[ $group_index ] = $grouped_data['value'];
968 }
969 $new_val = $_new_val;
970 } else {
971 // Add the supporting data to the $saved array stack.
972 $saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
973 // Reset var to the actual value.
974 $new_val = $new_val['value'];
975 }
976 }
977
978 // Get old value.
979 $old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
980 ? $old[ $field_group->index ][ $sub_id ]
981 : false;
982
983 $is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val );
984 $is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) );
985
986 // Compare values and add to `$updated` array.
987 if ( $is_updated || $is_removed ) {
988 $this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
989 }
990
991 // Add to `$saved` array.
992 $saved[ $field_group->index ][ $sub_id ] = $new_val;
993
994 }// End foreach.
995
996 $saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] );
997 }// End foreach.
998
999 $saved = CMB2_Utils::filter_empty( $saved );
1000
1001 return $field_group->update_data( $saved, true );
1002 }
1003
1004 /**
1005 * Get object id from global space if no id is provided
1006 *
1007 * @since 1.0.0
1008 * @param integer|string $object_id Object ID.
1009 * @return integer|string $object_id Object ID.
1010 */
1011 public function object_id( $object_id = 0 ) {
1012 global $pagenow;
1013
1014 if ( $object_id ) {
1015 $this->object_id = $object_id;
1016 return $this->object_id;
1017 }
1018
1019 if ( $this->object_id ) {
1020 return $this->object_id;
1021 }
1022
1023 // Try to get our object ID from the global space.
1024 switch ( $this->object_type() ) {
1025 case 'user':
1026 $object_id = isset( $_REQUEST['user_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['user_id'] ) ) : $object_id;
1027 $object_id = ! $object_id && 'user-new.php' !== $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
1028 break;
1029
1030 case 'comment':
1031 $object_id = isset( $_REQUEST['c'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['c'] ) ) : $object_id;
1032 $object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
1033 break;
1034
1035 case 'term':
1036 $object_id = isset( $_REQUEST['tag_ID'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['tag_ID'] ) ) : $object_id;
1037 break;
1038
1039 case 'options-page':
1040 $key = $this->doing_options_page();
1041 if ( ! empty( $key ) ) {
1042 $object_id = $key;
1043 }
1044 break;
1045
1046 default:
1047 $object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
1048 $object_id = isset( $_REQUEST['post'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['post'] ) ) : $object_id;
1049 break;
1050 }
1051
1052 /**
1053 * Filter the object id.
1054 *
1055 * @since {{next}}
1056 *
1057 * @param integer|string $object_id Object ID.
1058 * @param CMB2 $cmb This CMB2 object.
1059 */
1060 $object_id = apply_filters( 'cmb2_set_object_id', $object_id, $this );
1061
1062 // reset to id or 0.
1063 $this->object_id = ! empty( $object_id ) ? $object_id : 0;
1064
1065 return $this->object_id;
1066 }
1067
1068 /**
1069 * Sets the $object_type based on metabox settings
1070 *
1071 * @since 1.0.0
1072 * @return string Object type.
1073 */
1074 public function mb_object_type() {
1075 if ( null !== $this->mb_object_type ) {
1076 return $this->mb_object_type;
1077 }
1078
1079 $found_type = '';
1080
1081 if ( $this->is_options_page_mb() ) {
1082 $found_type = 'options-page';
1083 } else {
1084 $registered_types = $this->box_types();
1085
1086 // if it's an array of one, extract it.
1087 if ( 1 === count( $registered_types ) ) {
1088 $last = end( $registered_types );
1089 if ( is_string( $last ) ) {
1090 $found_type = $last;
1091 }
1092 } else {
1093 $current_object_type = $this->current_object_type();
1094 if ( in_array( $current_object_type, $registered_types, true ) ) {
1095 $found_type = $current_object_type;
1096 }
1097 }
1098 }
1099
1100 // Get our object type.
1101 $supported_types = array( 'post', 'user', 'comment', 'term', 'options-page' );
1102 $mb_object_type = in_array( $found_type, $supported_types, true )
1103 ? $found_type
1104 : 'post';
1105
1106 /**
1107 * Filter the metabox object type.
1108 *
1109 * @since {{next}}
1110 *
1111 * @param string $mb_object_type The metabox object type.
1112 * @param string $found_type The found object type.
1113 * @param CMB2 $cmb This CMB2 object.
1114 */
1115 $this->mb_object_type = apply_filters( 'cmb2_set_box_object_type', $mb_object_type, $found_type, $this );
1116
1117 return $this->mb_object_type;
1118 }
1119
1120 /**
1121 * Gets the box 'object_types' array based on box settings.
1122 *
1123 * @since 2.2.3
1124 * @param array $fallback Fallback value.
1125 *
1126 * @return array Object types.
1127 */
1128 public function box_types( $fallback = array() ) {
1129 return CMB2_Utils::ensure_array( $this->prop( 'object_types' ), $fallback );
1130 }
1131
1132 /**
1133 * Check if given object_type(s) matches any of the registered object types or
1134 * taxonomies for this box.
1135 *
1136 * @since 2.7.0
1137 * @param string|array $object_types The object type(s) to check.
1138 * @param array $fallback Fallback object_types value.
1139 *
1140 * @return bool Whether given object type(s) are registered to this box.
1141 */
1142 public function is_box_type( $object_types = array(), $fallback = array() ) {
1143 $object_types = (array) $object_types;
1144 $box_types = $this->box_types( $fallback );
1145
1146 if ( in_array( 'term', $box_types, true ) ) {
1147 $taxonomies = CMB2_Utils::ensure_array( $this->prop( 'taxonomies' ) );
1148 $box_types = array_merge( $box_types, $taxonomies );
1149 }
1150
1151 $found = array_intersect( $object_types, $box_types );
1152
1153 return ! empty( $found );
1154 }
1155
1156 /**
1157 * Initates the object types and option key for an options page metabox.
1158 *
1159 * @since 2.2.5
1160 *
1161 * @return void
1162 */
1163 public function init_options_mb() {
1164 $keys = $this->options_page_keys();
1165 $types = $this->box_types();
1166
1167 if ( empty( $keys ) ) {
1168 $keys = '';
1169 $types = $this->deinit_options_mb( $types );
1170 } else {
1171
1172 // Make sure 'options-page' is one of the object types.
1173 $types[] = 'options-page';
1174 }
1175
1176 // Set/Reset the option_key property.
1177 $this->set_prop( 'option_key', $keys );
1178
1179 // Reset the object types.
1180 $this->set_prop( 'object_types', array_unique( $types ) );
1181 }
1182
1183 /**
1184 * If object-page initiation failed, remove traces options page setup.
1185 *
1186 * @since 2.2.5
1187 *
1188 * @param array $types Array of types.
1189 * @return array
1190 */
1191 protected function deinit_options_mb( $types ) {
1192 if ( isset( $this->meta_box['show_on']['key'] ) && 'options-page' === $this->meta_box['show_on']['key'] ) {
1193 unset( $this->meta_box['show_on']['key'] );
1194 }
1195
1196 if ( array_key_exists( 'options-page', $this->meta_box['show_on'] ) ) {
1197 unset( $this->meta_box['show_on']['options-page'] );
1198 }
1199
1200 $index = array_search( 'options-page', $types );
1201
1202 if ( false !== $index ) {
1203 unset( $types[ $index ] );
1204 }
1205
1206 return $types;
1207 }
1208
1209 /**
1210 * Determines if metabox is for an options page
1211 *
1212 * @since 1.0.1
1213 * @return boolean True/False.
1214 */
1215 public function is_options_page_mb() {
1216 return (
1217 // 'show_on' values checked for back-compatibility.
1218 $this->is_old_school_options_page_mb()
1219 || in_array( 'options-page', $this->box_types() )
1220 );
1221 }
1222
1223 /**
1224 * Determines if metabox uses old-schoold options page config.
1225 *
1226 * @since 2.2.5
1227 * @return boolean True/False.
1228 */
1229 public function is_old_school_options_page_mb() {
1230 return (
1231 // 'show_on' values checked for back-compatibility.
1232 isset( $this->meta_box['show_on']['key'] ) && 'options-page' === $this->meta_box['show_on']['key']
1233 || array_key_exists( 'options-page', $this->meta_box['show_on'] )
1234 );
1235 }
1236
1237 /**
1238 * Determine if we are on an options page (or saving the options page).
1239 *
1240 * @since 2.2.5
1241 *
1242 * @return bool
1243 */
1244 public function doing_options_page() {
1245 $found_key = false;
1246 $keys = $this->options_page_keys();
1247
1248 if ( empty( $keys ) ) {
1249 return $found_key;
1250 }
1251
1252 if ( ! empty( $_GET['page'] ) && in_array( $_GET['page'], $keys ) ) {
1253 $found_key = $_GET['page'];
1254 }
1255
1256 if ( ! empty( $_POST['action'] ) && in_array( $_POST['action'], $keys ) ) {
1257 $found_key = $_POST['action'];
1258 }
1259
1260 return $found_key ? $found_key : false;
1261 }
1262
1263 /**
1264 * Get the options page key.
1265 *
1266 * @since 2.2.5
1267 * @return string|array
1268 */
1269 public function options_page_keys() {
1270 $key = '';
1271 if ( ! $this->is_options_page_mb() ) {
1272 return $key;
1273 }
1274
1275 $values = null;
1276 if ( ! empty( $this->meta_box['show_on']['value'] ) ) {
1277 $values = $this->meta_box['show_on']['value'];
1278 } elseif ( ! empty( $this->meta_box['show_on']['options-page'] ) ) {
1279 $values = $this->meta_box['show_on']['options-page'];
1280 } elseif ( $this->prop( 'option_key' ) ) {
1281 $values = $this->prop( 'option_key' );
1282 }
1283
1284 if ( $values ) {
1285 $key = $values;
1286 }
1287
1288 if ( ! is_array( $key ) ) {
1289 $key = array( $key );
1290 }
1291
1292 return $key;
1293 }
1294
1295 /**
1296 * Returns the object type
1297 *
1298 * @since 1.0.0
1299 * @param string $object_type Type of object being saved. (e.g., post, user, or comment). Optional.
1300 * @return string Object type.
1301 */
1302 public function object_type( $object_type = '' ) {
1303 if ( $object_type ) {
1304 $this->object_type = $object_type;
1305 return $this->object_type;
1306 }
1307
1308 if ( $this->object_type ) {
1309 return $this->object_type;
1310 }
1311
1312 $this->object_type = $this->current_object_type();
1313
1314 return $this->object_type;
1315 }
1316
1317 /**
1318 * Get the object type for the current page, based on the $pagenow global.
1319 *
1320 * @since 2.2.2
1321 * @return string Page object type name.
1322 */
1323 public function current_object_type() {
1324 global $pagenow;
1325 $type = 'post';
1326
1327 if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
1328 $type = 'user';
1329 }
1330
1331 if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
1332 $type = 'comment';
1333 }
1334
1335 if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
1336 $type = 'term';
1337 }
1338
1339 if ( defined( 'DOING_AJAX' ) && isset( $_POST['action'] ) && 'add-tag' === $_POST['action'] ) {
1340 $type = 'term';
1341 }
1342
1343 if (
1344 in_array( $pagenow, array( 'admin.php', 'admin-post.php' ), true )
1345 && $this->doing_options_page()
1346 ) {
1347 $type = 'options-page';
1348 }
1349
1350 return $type;
1351 }
1352
1353 /**
1354 * Set metabox property.
1355 *
1356 * @since 2.2.2
1357 * @param string $property Metabox config property to retrieve.
1358 * @param mixed $value Value to set if no value found.
1359 * @return mixed Metabox config property value or false.
1360 */
1361 public function set_prop( $property, $value ) {
1362 $this->meta_box[ $property ] = $value;
1363
1364 return $this->prop( $property );
1365 }
1366
1367 /**
1368 * Get metabox property and optionally set a fallback
1369 *
1370 * @since 2.0.0
1371 * @param string $property Metabox config property to retrieve.
1372 * @param mixed $fallback Fallback value to set if no value found.
1373 * @return mixed Metabox config property value or false.
1374 */
1375 public function prop( $property, $fallback = null ) {
1376 if ( array_key_exists( $property, $this->meta_box ) ) {
1377 return $this->meta_box[ $property ];
1378 } elseif ( $fallback ) {
1379 return $this->meta_box[ $property ] = $fallback;
1380 }
1381 }
1382
1383 /**
1384 * Get a field object
1385 *
1386 * @since 2.0.3
1387 * @param string|array|CMB2_Field $field Metabox field id or field config array or CMB2_Field object.
1388 * @param CMB2_Field|null $field_group (optional) CMB2_Field object (group parent).
1389 * @param bool $reset_cached (optional) Reset the internal cache for this field object.
1390 * Use sparingly.
1391 *
1392 * @return CMB2_Field|false CMB2_Field object (or false).
1393 */
1394 public function get_field( $field, $field_group = null, $reset_cached = false ) {
1395 if ( $field instanceof CMB2_Field ) {
1396 return $field;
1397 }
1398
1399 $field_id = is_string( $field ) ? $field : $field['id'];
1400
1401 $parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
1402 $ids = $this->get_field_ids( $field_id, $parent_field_id );
1403
1404 if ( ! $ids ) {
1405 return false;
1406 }
1407
1408 list( $field_id, $sub_field_id ) = $ids;
1409
1410 $index = $field_id . ( $sub_field_id ? '|' . $sub_field_id : '' ) . ( $field_group ? '|' . $field_group->index : '' );
1411
1412 if ( array_key_exists( $index, $this->fields ) && ! $reset_cached ) {
1413 return $this->fields[ $index ];
1414 }
1415
1416 $this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
1417
1418 return $this->fields[ $index ];
1419 }
1420
1421 /**
1422 * Handles determining which type of arguments to pass to CMB2_Field
1423 *
1424 * @since 2.0.7
1425 * @param mixed $field_id Field (or group field) ID.
1426 * @param mixed $field_args Array of field arguments.
1427 * @param mixed $sub_field_id Sub field ID (if field_group exists).
1428 * @param CMB2_Field|null $field_group If a sub-field, will be the parent group CMB2_Field object.
1429 * @return array Array of CMB2_Field arguments.
1430 */
1431 public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
1432
1433 // Check if group is passed and if fields were added in the old-school fields array.
1434 if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
1435
1436 // Update the fields array w/ any modified properties inherited from the group field.
1437 $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
1438
1439 return $this->get_default_args( $field_args, $field_group );
1440 }
1441
1442 if ( is_array( $field_args ) ) {
1443 $this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
1444 }
1445
1446 return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
1447 }
1448
1449 /**
1450 * Get default field arguments specific to this CMB2 object.
1451 *
1452 * @since 2.2.0
1453 * @param array $field_args Metabox field config array.
1454 * @param CMB2_Field $field_group (optional) CMB2_Field object (group parent).
1455 * @return array Array of field arguments.
1456 */
1457 protected function get_default_args( $field_args, $field_group = null ) {
1458 if ( $field_group ) {
1459 $args = array(
1460 'field_args' => $field_args,
1461 'group_field' => $field_group,
1462 );
1463 } else {
1464 $args = array(
1465 'field_args' => $field_args,
1466 'object_type' => $this->object_type(),
1467 'object_id' => $this->object_id(),
1468 'cmb_id' => $this->cmb_id,
1469 );
1470 }
1471
1472 return $args;
1473 }
1474
1475 /**
1476 * When fields are added in the old-school way, intitate them as they should be
1477 *
1478 * @since 2.1.0
1479 * @param array $fields Array of fields to add.
1480 * @param mixed $parent_field_id Parent field id or null.
1481 *
1482 * @return CMB2
1483 */
1484 protected function add_fields( $fields, $parent_field_id = null ) {
1485 foreach ( $fields as $field ) {
1486
1487 $sub_fields = false;
1488 if ( array_key_exists( 'fields', $field ) ) {
1489 $sub_fields = $field['fields'];
1490 unset( $field['fields'] );
1491 }
1492
1493 $field_id = $parent_field_id
1494 ? $this->add_group_field( $parent_field_id, $field )
1495 : $this->add_field( $field );
1496
1497 if ( $sub_fields ) {
1498 $this->add_fields( $sub_fields, $field_id );
1499 }
1500 }
1501
1502 return $this;
1503 }
1504
1505 /**
1506 * Add a field to the metabox
1507 *
1508 * @since 2.0.0
1509 * @param array $field Metabox field config array.
1510 * @param int $position (optional) Position of metabox. 1 for first, etc.
1511 * @return string|false Field id or false.
1512 */
1513 public function add_field( array $field, $position = 0 ) {
1514 if ( ! array_key_exists( 'id', $field ) ) {
1515 return false;
1516 }
1517
1518 $this->_add_field_to_array(
1519 $field,
1520 $this->meta_box['fields'],
1521 $position
1522 );
1523
1524 return $field['id'];
1525 }
1526
1527 /**
1528 * Add a field to a group
1529 *
1530 * @since 2.0.0
1531 * @param string $parent_field_id The field id of the group field to add the field.
1532 * @param array $field Metabox field config array.
1533 * @param int $position (optional) Position of metabox. 1 for first, etc.
1534 * @return mixed Array of parent/field ids or false.
1535 */
1536 public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1537 if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1538 return false;
1539 }
1540
1541 $parent_field = $this->meta_box['fields'][ $parent_field_id ];
1542
1543 if ( 'group' !== $parent_field['type'] ) {
1544 return false;
1545 }
1546
1547 if ( ! isset( $parent_field['fields'] ) ) {
1548 $this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1549 }
1550
1551 $this->_add_field_to_array(
1552 $field,
1553 $this->meta_box['fields'][ $parent_field_id ]['fields'],
1554 $position
1555 );
1556
1557 return array( $parent_field_id, $field['id'] );
1558 }
1559
1560 /**
1561 * Perform some field-type-specific initiation actions.
1562 *
1563 * @since 2.7.0
1564 * @param array $field Metabox field config array.
1565 * @return void
1566 */
1567 protected function field_actions( $field ) {
1568 switch ( $field['type'] ) {
1569 case 'file':
1570 case 'file_list':
1571 // Initiate attachment JS hooks.
1572 add_filter( 'wp_prepare_attachment_for_js', array( 'CMB2_Type_File_Base', 'prepare_image_sizes_for_js' ), 10, 3 );
1573 break;
1574
1575 case 'oembed':
1576 // Initiate oembed Ajax hooks.
1577 cmb2_ajax();
1578 break;
1579
1580 case 'group':
1581 if ( empty( $field['render_row_cb'] ) ) {
1582 $field['render_row_cb'] = array( $this, 'render_group_callback' );
1583 }
1584 break;
1585 case 'colorpicker':
1586 // https://github.com/JayWood/CMB2_RGBa_Picker
1587 // Dequeue the rgba_colorpicker custom field script if it is used,
1588 // since we now enqueue our own more current version.
1589 add_action( 'admin_enqueue_scripts', array( 'CMB2_Type_Colorpicker', 'dequeue_rgba_colorpicker_script' ), 99 );
1590 break;
1591 }
1592
1593 if ( isset( $field['column'] ) && false !== $field['column'] ) {
1594 $field = $this->define_field_column( $field );
1595 }
1596
1597 if ( isset( $field['taxonomy'] ) && ! empty( $field['remove_default'] ) ) {
1598 $this->tax_metaboxes_to_remove[ $field['taxonomy'] ] = $field['taxonomy'];
1599 }
1600
1601 return $field;
1602 }
1603
1604 /**
1605 * Defines a field's column if requesting to be show in admin columns.
1606 *
1607 * @since 2.2.3
1608 * @param array $field Metabox field config array.
1609 * @return array Modified metabox field config array.
1610 */
1611 protected function define_field_column( array $field ) {
1612 $this->has_columns = true;
1613
1614 $column = is_array( $field['column'] ) ? $field['column'] : array();
1615
1616 $field['column'] = wp_parse_args( $column, array(
1617 'name' => isset( $field['name'] ) ? $field['name'] : '',
1618 'position' => false,
1619 ) );
1620
1621 return $field;
1622 }
1623
1624 /**
1625 * Add a field array to a fields array in desired position
1626 *
1627 * @since 2.0.2
1628 * @param array $field Metabox field config array.
1629 * @param array $fields Array (passed by reference) to append the field (array) to.
1630 * @param integer $position Optionally specify a position in the array to be inserted.
1631 */
1632 protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1633 $field = $this->field_actions( $field );
1634
1635 if ( $position ) {
1636 CMB2_Utils::array_insert( $fields, array( $field['id'] => $field ), $position );
1637 } else {
1638 $fields[ $field['id'] ] = $field;
1639 }
1640 }
1641
1642 /**
1643 * Remove a field from the metabox
1644 *
1645 * @since 2.0.0
1646 * @param string $field_id The field id of the field to remove.
1647 * @param string $parent_field_id (optional) The field id of the group field to remove field from.
1648 * @return bool True if field was removed.
1649 */
1650 public function remove_field( $field_id, $parent_field_id = '' ) {
1651 $ids = $this->get_field_ids( $field_id, $parent_field_id );
1652
1653 if ( ! $ids ) {
1654 return false;
1655 }
1656
1657 list( $field_id, $sub_field_id ) = $ids;
1658
1659 unset( $this->fields[ implode( '', $ids ) ] );
1660
1661 if ( ! $sub_field_id ) {
1662 unset( $this->meta_box['fields'][ $field_id ] );
1663 return true;
1664 }
1665
1666 if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1667 unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1668 }
1669 if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1670 unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1671 }
1672
1673 return true;
1674 }
1675
1676 /**
1677 * Update or add a property to a field
1678 *
1679 * @since 2.0.0
1680 * @param string $field_id Field id.
1681 * @param string $property Field property to set/update.
1682 * @param mixed $value Value to set the field property.
1683 * @param string $parent_field_id (optional) The field id of the group field to remove field from.
1684 * @return mixed Field id. Strict compare to false, as success can return a falsey value (like 0).
1685 */
1686 public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1687 $ids = $this->get_field_ids( $field_id, $parent_field_id );
1688
1689 if ( ! $ids ) {
1690 return false;
1691 }
1692
1693 list( $field_id, $sub_field_id ) = $ids;
1694
1695 if ( ! $sub_field_id ) {
1696 $this->meta_box['fields'][ $field_id ][ $property ] = $value;
1697 return $field_id;
1698 }
1699
1700 $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1701 return $field_id;
1702 }
1703
1704 /**
1705 * Check if field ids match a field and return the index/field id
1706 *
1707 * @since 2.0.2
1708 * @param string $field_id Field id.
1709 * @param string $parent_field_id (optional) Parent field id.
1710 * @return mixed Array of field/parent ids, or false.
1711 */
1712 public function get_field_ids( $field_id, $parent_field_id = '' ) {
1713 $sub_field_id = $parent_field_id ? $field_id : '';
1714 $field_id = $parent_field_id ? $parent_field_id : $field_id;
1715 $fields =& $this->meta_box['fields'];
1716
1717 if ( ! array_key_exists( $field_id, $fields ) ) {
1718 $field_id = $this->search_old_school_array( $field_id, $fields );
1719 }
1720
1721 if ( false === $field_id ) {
1722 return false;
1723 }
1724
1725 if ( ! $sub_field_id ) {
1726 return array( $field_id, $sub_field_id );
1727 }
1728
1729 if ( 'group' !== $fields[ $field_id ]['type'] ) {
1730 return false;
1731 }
1732
1733 if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1734 $sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1735 }
1736
1737 return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1738 }
1739
1740 /**
1741 * When using the old array filter, it is unlikely field array indexes will be the field id.
1742 *
1743 * @since 2.0.2
1744 * @param string $field_id The field id.
1745 * @param array $fields Array of fields to search.
1746 * @return mixed Field index or false.
1747 */
1748 public function search_old_school_array( $field_id, $fields ) {
1749 $ids = wp_list_pluck( $fields, 'id' );
1750 $index = array_search( $field_id, $ids );
1751 return false !== $index ? $index : false;
1752 }
1753
1754 /**
1755 * Handles metabox property callbacks, and passes this $cmb object as property.
1756 *
1757 * @since 2.2.3
1758 * @param callable $cb The callback method/function/closure.
1759 * @param mixed $additional_params Any additoinal parameters which should be passed to the callback.
1760 * @return mixed Return of the callback function.
1761 */
1762 public function do_callback( $cb, $additional_params = null ) {
1763 return call_user_func( $cb, $this, $additional_params );
1764 }
1765
1766 /**
1767 * Generate a unique nonce field for each registered meta_box
1768 *
1769 * @since 2.0.0
1770 * @return void
1771 */
1772 public function nonce_field() {
1773 wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1774 }
1775
1776 /**
1777 * Generate a unique nonce for each registered meta_box
1778 *
1779 * @since 2.0.0
1780 * @return string unique nonce string.
1781 */
1782 public function nonce() {
1783 if ( ! $this->generated_nonce ) {
1784 $this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1785 }
1786
1787 return $this->generated_nonce;
1788 }
1789
1790 /**
1791 * Checks if field-saving updated any fields.
1792 *
1793 * @since 2.2.5
1794 *
1795 * @return bool
1796 */
1797 public function was_updated() {
1798 return ! empty( $this->updated );
1799 }
1800
1801 /**
1802 * Whether this box is an "alternate context" box. This means the box has a 'context' property defined as:
1803 * 'form_top', 'before_permalink', 'after_title', or 'after_editor'.
1804 *
1805 * @since 2.2.4
1806 * @return bool
1807 */
1808 public function is_alternate_context_box() {
1809 return $this->prop( 'context' ) && in_array( $this->prop( 'context' ), array( 'form_top', 'before_permalink', 'after_title', 'after_editor' ), true );
1810 }
1811
1812 /**
1813 * Magic getter for our object.
1814 *
1815 * @param string $property Object property.
1816 * @throws Exception Throws an exception if the field is invalid.
1817 * @return mixed
1818 */
1819 public function __get( $property ) {
1820 switch ( $property ) {
1821 case 'updated':
1822 case 'has_columns':
1823 case 'tax_metaboxes_to_remove':
1824 return $this->{$property};
1825 default:
1826 return parent::__get( $property );
1827 }
1828 }
1829
1830 }
1831