CMB2

CMB2 - The core metabox object.

Defined (1)

The class is defined in the following location(s).

/includes/CMB2.php  
  1. class CMB2 extends CMB2_Base { 
  2.  
  3. /** 
  4. * The object properties name. 
  5. * @var string 
  6. * @since 2.2.3 
  7. */ 
  8. protected $properties_name = 'meta_box'; 
  9.  
  10. /** 
  11. * Metabox Config array 
  12. * @var array 
  13. * @since 0.9.0 
  14. */ 
  15. protected $meta_box = array(); 
  16.  
  17. /** 
  18. * Type of object registered for metabox. (e.g., post, user, or comment) 
  19. * @var string 
  20. * @since 1.0.0 
  21. */ 
  22. protected $mb_object_type = null; 
  23.  
  24. /** 
  25. * List of fields that are changed/updated on save 
  26. * @var array 
  27. * @since 1.1.0 
  28. */ 
  29. protected $updated = array(); 
  30.  
  31. /** 
  32. * Metabox Defaults 
  33. * @var array 
  34. * @since 1.0.1 
  35. */ 
  36. protected $mb_defaults = array( 
  37. 'id' => '',  
  38. 'title' => '',  
  39. 'type' => '',  
  40. 'object_types' => array(), // Post type 
  41. 'context' => 'normal',  
  42. 'priority' => 'high',  
  43. 'show_names' => true, // Show field names on the left 
  44. 'show_on_cb' => null, // Callback to determine if metabox should display. 
  45. 'show_on' => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb' 
  46. 'cmb_styles' => true, // Include CMB2 stylesheet 
  47. 'enqueue_js' => true, // Include CMB2 JS 
  48. 'fields' => array(),  
  49. 'hookup' => true,  
  50. 'save_fields' => true, // Will not save during hookup if false 
  51. 'closed' => false, // Default to metabox being closed? 
  52. 'taxonomies' => array(),  
  53. 'new_user_section' => 'add-new-user', // or 'add-existing-user' 
  54. 'new_term_section' => true,  
  55. ); 
  56.  
  57. /** 
  58. * Metabox field objects 
  59. * @var array 
  60. * @since 2.0.3 
  61. */ 
  62. protected $fields = array(); 
  63.  
  64. /** 
  65. * An array of hidden fields to output at the end of the form 
  66. * @var array 
  67. * @since 2.0.0 
  68. */ 
  69. protected $hidden_fields = array(); 
  70.  
  71. /** 
  72. * Array of key => value data for saving. Likely $_POST data. 
  73. * @var string 
  74. * @since 2.0.0 
  75. */ 
  76. protected $generated_nonce = ''; 
  77.  
  78. /** 
  79. * Whether there are fields to be shown in columns. Set in CMB2::add_field(). 
  80. * @var bool 
  81. * @since 2.2.2 
  82. */ 
  83. protected $has_columns = false; 
  84.  
  85. /** 
  86. * If taxonomy field is requesting to remove_default, we store the taxonomy here. 
  87. * @var array 
  88. * @since 2.2.3 
  89. */ 
  90. protected $tax_metaboxes_to_remove = array(); 
  91.  
  92. /** 
  93. * Get started 
  94. * @since 0.4.0 
  95. * @param array $config Metabox config array 
  96. * @param integer $object_id Optional object id 
  97. */ 
  98. public function __construct( $config, $object_id = 0 ) { 
  99.  
  100. if ( empty( $config['id'] ) ) { 
  101. wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) ); 
  102.  
  103. $this->meta_box = wp_parse_args( $config, $this->mb_defaults ); 
  104. $this->meta_box['fields'] = array(); 
  105.  
  106. $this->object_id( $object_id ); 
  107. $this->mb_object_type(); 
  108. $this->cmb_id = $config['id']; 
  109.  
  110. if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) { 
  111. $this->add_fields( $config['fields'] ); 
  112.  
  113. CMB2_Boxes::add( $this ); 
  114.  
  115. /** 
  116. * Hook during initiation of CMB2 object 
  117. * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id. 
  118. * @param array $cmb This CMB2 object 
  119. */ 
  120. do_action( "cmb2_init_{$this->cmb_id}", $this ); 
  121.  
  122. /** 
  123. * Loops through and displays fields 
  124. * @since 1.0.0 
  125. * @param int $object_id Object ID 
  126. * @param string $object_type Type of object being saved. (e.g., post, user, or comment) 
  127. */ 
  128. public function show_form( $object_id = 0, $object_type = '' ) { 
  129. $this->render_form_open( $object_id, $object_type ); 
  130.  
  131. foreach ( $this->prop( 'fields' ) as $field_args ) { 
  132. $this->render_field( $field_args ); 
  133.  
  134. $this->render_form_close( $object_id, $object_type ); 
  135.  
  136. /** 
  137. * Outputs the opening form markup and runs corresponding hooks: 
  138. * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}" 
  139. * @since 2.2.0 
  140. * @param integer $object_id Object ID 
  141. * @param string $object_type Object type 
  142. * @return void 
  143. */ 
  144. public function render_form_open( $object_id = 0, $object_type = '' ) { 
  145. $object_type = $this->object_type( $object_type ); 
  146. $object_id = $this->object_id( $object_id ); 
  147.  
  148. echo "\n<!-- Begin CMB2 Fields -->\n"; 
  149.  
  150. $this->nonce_field(); 
  151.  
  152. /** 
  153. * Hook before form table begins 
  154. * @param array $cmb_id The current box ID 
  155. * @param int $object_id The ID of the current object 
  156. * @param string $object_type The type of object you are working with. 
  157. * Usually `post` (this applies to all post-types). 
  158. * Could also be `comment`, `user` or `options-page`. 
  159. * @param array $cmb This CMB2 object 
  160. */ 
  161. do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this ); 
  162.  
  163. /** 
  164. * Hook before form table begins 
  165. * The first dynamic portion of the hook name, $object_type, is the type of object 
  166. * you are working with. Usually `post` (this applies to all post-types). 
  167. * Could also be `comment`, `user` or `options-page`. 
  168. * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id. 
  169. * @param array $cmb_id The current box ID 
  170. * @param int $object_id The ID of the current object 
  171. * @param array $cmb This CMB2 object 
  172. */ 
  173. do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this ); 
  174.  
  175. echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">'; 
  176.  
  177.  
  178. /** 
  179. * Defines the classes for the CMB2 form/wrap. 
  180. * @since 2.0.0 
  181. * @return string Space concatenated list of classes 
  182. */ 
  183. public function box_classes() { 
  184.  
  185. $classes = array( 'cmb2-wrap', 'form-table' ); 
  186.  
  187. // Use the callback to fetch classes. 
  188. if ( $added_classes = $this->get_param_callback_result( 'classes_cb' ) ) { 
  189. $added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes ); 
  190. $classes = array_merge( $classes, $added_classes ); 
  191.  
  192. if ( $added_classes = $this->prop( 'classes' ) ) { 
  193. $added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes ); 
  194. $classes = array_merge( $classes, $added_classes ); 
  195.  
  196. /** 
  197. * Globally filter box wrap classes 
  198. * @since 2.2.2 
  199. * @param string $classes Array of classes for the cmb2-wrap. 
  200. * @param CMB2 $cmb This CMB2 object. 
  201. */ 
  202. $classes = apply_filters( 'cmb2_wrap_classes', $classes, $this ); 
  203.  
  204. // Clean up. 
  205. $classes = array_map( 'strip_tags', array_filter( $classes ) ); 
  206.  
  207. // Make a string. 
  208. return implode( ' ', $classes ); 
  209.  
  210. /** 
  211. * Outputs the closing form markup and runs corresponding hooks: 
  212. * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}" 
  213. * @since 2.2.0 
  214. * @param integer $object_id Object ID 
  215. * @param string $object_type Object type 
  216. * @return void 
  217. */ 
  218. public function render_form_close( $object_id = 0, $object_type = '' ) { 
  219. $object_type = $this->object_type( $object_type ); 
  220. $object_id = $this->object_id( $object_id ); 
  221.  
  222. echo '</div></div>'; 
  223.  
  224. $this->render_hidden_fields(); 
  225.  
  226. /** 
  227. * Hook after form form has been rendered 
  228. * @param array $cmb_id The current box ID 
  229. * @param int $object_id The ID of the current object 
  230. * @param string $object_type The type of object you are working with. 
  231. * Usually `post` (this applies to all post-types). 
  232. * Could also be `comment`, `user` or `options-page`. 
  233. * @param array $cmb This CMB2 object 
  234. */ 
  235. do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this ); 
  236.  
  237. /** 
  238. * Hook after form form has been rendered 
  239. * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id. 
  240. * The first dynamic portion of the hook name, $object_type, is the type of object 
  241. * you are working with. Usually `post` (this applies to all post-types). 
  242. * Could also be `comment`, `user` or `options-page`. 
  243. * @param int $object_id The ID of the current object 
  244. * @param array $cmb This CMB2 object 
  245. */ 
  246. do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this ); 
  247.  
  248. echo "\n<!-- End CMB2 Fields -->\n"; 
  249.  
  250.  
  251. /** 
  252. * Renders a field based on the field type 
  253. * @since 2.2.0 
  254. * @param array $field_args A field configuration array. 
  255. * @return mixed CMB2_Field object if successful. 
  256. */ 
  257. public function render_field( $field_args ) { 
  258. $field_args['context'] = $this->prop( 'context' ); 
  259.  
  260. if ( 'group' == $field_args['type'] ) { 
  261.  
  262. if ( ! isset( $field_args['show_names'] ) ) { 
  263. $field_args['show_names'] = $this->prop( 'show_names' ); 
  264. $field = $this->render_group( $field_args ); 
  265.  
  266. } elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) { 
  267. // Save rendering for after the metabox 
  268. $field = $this->add_hidden_field( $field_args ); 
  269.  
  270. } else { 
  271.  
  272. $field_args['show_names'] = $this->prop( 'show_names' ); 
  273.  
  274. // Render default fields 
  275. $field = $this->get_field( $field_args )->render_field(); 
  276.  
  277. return $field; 
  278.  
  279. /** 
  280. * Render a repeatable group. 
  281. * @param array $args Array of field arguments for a group field parent. 
  282. * @return CMB2_Field|null Group field object. 
  283. */ 
  284. public function render_group( $args ) { 
  285.  
  286. if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) { 
  287. return; 
  288.  
  289. $field_group = $this->get_field( $args ); 
  290.  
  291. // If field is requesting to be conditionally shown 
  292. if ( ! $field_group || ! $field_group->should_show() ) { 
  293. return; 
  294.  
  295. $desc = $field_group->args( 'description' ); 
  296. $label = $field_group->args( 'name' ); 
  297. $group_val = (array) $field_group->value(); 
  298. $remove_disabled = count( $group_val ) <= 1 ? 'disabled="disabled" ' : ''; 
  299. $field_group->index = 0; 
  300.  
  301. $field_group->peform_param_callback( 'before_group' ); 
  302.  
  303. echo '<div class="cmb-row cmb-repeat-group-wrap ', $field_group->row_classes(), '" data-fieldtype="group"><div class="cmb-td"><div data-groupid="', $field_group->id(), '" id="', $field_group->id(), '_repeat" ', $this->group_wrap_attributes( $field_group ), '>'; 
  304.  
  305. if ( $desc || $label ) { 
  306. $class = $desc ? ' cmb-group-description' : ''; 
  307. echo '<div class="cmb-row', $class, '"><div class="cmb-th">'; 
  308. if ( $label ) { 
  309. echo '<h2 class="cmb-group-name">', $label, '</h2>'; 
  310. if ( $desc ) { 
  311. echo '<p class="cmb2-metabox-description">', $desc, '</p>'; 
  312. echo '</div></div>'; 
  313.  
  314. if ( ! empty( $group_val ) ) { 
  315. foreach ( $group_val as $group_key => $field_id ) { 
  316. $this->render_group_row( $field_group, $remove_disabled ); 
  317. $field_group->index++; 
  318. } else { 
  319. $this->render_group_row( $field_group, $remove_disabled ); 
  320.  
  321. if ( $field_group->args( 'repeatable' ) ) { 
  322. echo '<div class="cmb-row"><div class="cmb-td"><p class="cmb-add-row"><button type="button" data-selector="', $field_group->id(), '_repeat" data-grouptitle="', $field_group->options( 'group_title' ), '" class="cmb-add-group-row button">', $field_group->options( 'add_button' ), '</button></p></div></div>'; 
  323.  
  324. echo '</div></div></div>'; 
  325.  
  326. $field_group->peform_param_callback( 'after_group' ); 
  327.  
  328. return $field_group; 
  329.  
  330. /** 
  331. * Get the group wrap attributes, which are passed through a filter. 
  332. * @since 2.2.3 
  333. * @param CMB2_Field $field_group The group CMB2_Field object. 
  334. * @return string The attributes string. 
  335. */ 
  336. public function group_wrap_attributes( $field_group ) { 
  337. $classes = 'cmb-nested cmb-field-list cmb-repeatable-group'; 
  338. $classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable'; 
  339. $classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable'; 
  340.  
  341. $group_wrap_attributes = array( 
  342. 'class' => $classes,  
  343. 'style' => 'width:100%;',  
  344. ); 
  345.  
  346. /** 
  347. * Allow for adding additional HTML attributes to a group wrapper. 
  348. * The attributes will be an array of key => value pairs for each attribute. 
  349. * @since 2.2.2 
  350. * @param string $group_wrap_attributes Current attributes array. 
  351. * @param CMB2_Field $field_group The group CMB2_Field object. 
  352. */ 
  353. $group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group ); 
  354.  
  355. return CMB2_Utils::concat_attrs( $group_wrap_attributes ); 
  356.  
  357. /** 
  358. * Render a repeatable group row 
  359. * @since 1.0.2 
  360. * @param CMB2_Field $field_group CMB2_Field group field object 
  361. * @param string $remove_disabled Attribute string to disable the remove button 
  362. */ 
  363. public function render_group_row( $field_group, $remove_disabled ) { 
  364.  
  365. $field_group->peform_param_callback( 'before_group_row' ); 
  366. $closed_class = $field_group->options( 'closed' ) ? ' closed' : ''; 
  367.  
  368. echo ' 
  369. <div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">'; 
  370.  
  371. if ( $field_group->args( 'repeatable' ) ) { 
  372. echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row"></button>'; 
  373.  
  374. echo ' 
  375. <div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div> 
  376. <h3 class="cmb-group-title cmbhandle-title"><span>', $field_group->replace_hash( $field_group->options( 'group_title' ) ), '</span></h3> 
  377.  
  378. <div class="inside cmb-td cmb-nested cmb-field-list">'; 
  379. // Loop and render repeatable group fields 
  380. foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) { 
  381. if ( 'hidden' == $field_args['type'] ) { 
  382.  
  383. // Save rendering for after the metabox 
  384. $this->add_hidden_field( $field_args, $field_group ); 
  385.  
  386. } else { 
  387.  
  388. $field_args['show_names'] = $field_group->args( 'show_names' ); 
  389. $field_args['context'] = $field_group->args( 'context' ); 
  390.  
  391. $field = $this->get_field( $field_args, $field_group )->render_field(); 
  392. if ( $field_group->args( 'repeatable' ) ) { 
  393. echo ' 
  394. <div class="cmb-row cmb-remove-field-row"> 
  395. <div class="cmb-remove-row"> 
  396. <button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="button cmb-remove-group-row alignright">', $field_group->options( 'remove_button' ), '</button> 
  397. </div> 
  398. </div> 
  399. '; 
  400. echo ' 
  401. </div> 
  402. </div> 
  403. '; 
  404.  
  405. $field_group->peform_param_callback( 'after_group_row' ); 
  406.  
  407. /** 
  408. * Add a hidden field to the list of hidden fields to be rendered later 
  409. * @since 2.0.0 
  410. * @param array $field_args Array of field arguments to be passed to CMB2_Field 
  411. */ 
  412. public function add_hidden_field( $field_args, $field_group = null ) { 
  413. if ( isset( $field_args['field_args'] ) ) { 
  414. // For back-compatibility. 
  415. $field = new CMB2_Field( $field_args ); 
  416. } else { 
  417. $field = $this->get_new_field( $field_args, $field_group ); 
  418.  
  419. $type = new CMB2_Types( $field ); 
  420.  
  421. if ( $field_group ) { 
  422. $type->iterator = $field_group->index; 
  423.  
  424. $this->hidden_fields[] = $type; 
  425.  
  426. return $field; 
  427.  
  428. /** 
  429. * Loop through and output hidden fields 
  430. * @since 2.0.0 
  431. */ 
  432. public function render_hidden_fields() { 
  433. if ( ! empty( $this->hidden_fields ) ) { 
  434. foreach ( $this->hidden_fields as $hidden ) { 
  435. $hidden->render(); 
  436.  
  437. /** 
  438. * Returns array of sanitized field values (without saving them) 
  439. * @since 2.0.3 
  440. * @param array $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data). 
  441. */ 
  442. public function get_sanitized_values( array $data_to_sanitize ) { 
  443. $this->data_to_save = $data_to_sanitize; 
  444. $stored_id = $this->object_id(); 
  445.  
  446. // We do this So CMB will sanitize our data for us, but not save it 
  447. $this->object_id( '_' ); 
  448.  
  449. // Ensure temp. data store is empty 
  450. cmb2_options( 0 )->set(); 
  451.  
  452. // Process/save fields 
  453. $this->process_fields(); 
  454.  
  455. // Get data from temp. data store 
  456. $sanitized_values = cmb2_options( 0 )->get_options(); 
  457.  
  458. // Empty out temp. data store again 
  459. cmb2_options( 0 )->set(); 
  460.  
  461. // Reset the object id 
  462. $this->object_id( $stored_id ); 
  463.  
  464. return $sanitized_values; 
  465.  
  466. /** 
  467. * Loops through and saves field data 
  468. * @since 1.0.0 
  469. * @param int $object_id Object ID 
  470. * @param string $object_type Type of object being saved. (e.g., post, user, or comment) 
  471. * @param array $data_to_save Array of key => value data for saving. Likely $_POST data. 
  472. */ 
  473. public function save_fields( $object_id = 0, $object_type = '', $data_to_save = array() ) { 
  474.  
  475. // Fall-back to $_POST data 
  476. $this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST; 
  477. $object_id = $this->object_id( $object_id ); 
  478. $object_type = $this->object_type( $object_type ); 
  479.  
  480. $this->process_fields(); 
  481.  
  482. // If options page, save the updated options 
  483. if ( 'options-page' == $object_type ) { 
  484. cmb2_options( $object_id )->set(); 
  485.  
  486. $this->after_save(); 
  487.  
  488. /** 
  489. * Process and save form fields 
  490. * @since 2.0.0 
  491. */ 
  492. public function process_fields() { 
  493.  
  494. $this->pre_process(); 
  495.  
  496. // Remove the show_on properties so saving works 
  497. $this->prop( 'show_on', array() ); 
  498.  
  499. // save field ids of those that are updated 
  500. $this->updated = array(); 
  501.  
  502. foreach ( $this->prop( 'fields' ) as $field_args ) { 
  503. $this->process_field( $field_args ); 
  504.  
  505. /** 
  506. * Process and save a field 
  507. * @since 2.0.0 
  508. * @param array $field_args Array of field arguments 
  509. */ 
  510. public function process_field( $field_args ) { 
  511.  
  512. switch ( $field_args['type'] ) { 
  513.  
  514. case 'group': 
  515. if ( $this->save_group( $field_args ) ) { 
  516. $this->updated[] = $field_args['id']; 
  517.  
  518. break; 
  519.  
  520. case 'title': 
  521. // Don't process title fields 
  522. break; 
  523.  
  524. default: 
  525.  
  526. $field = $this->get_new_field( $field_args ); 
  527.  
  528. if ( $field->save_field_from_data( $this->data_to_save ) ) { 
  529. $this->updated[] = $field->id(); 
  530.  
  531. break; 
  532.  
  533.  
  534. public function pre_process() { 
  535. /** 
  536. * Fires before fields have been processed/saved. 
  537. * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id. 
  538. * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type 
  539. * Usually `post` (this applies to all post-types). 
  540. * Could also be `comment`, `user` or `options-page`. 
  541. * @param array $cmb This CMB2 object 
  542. * @param int $object_id The ID of the current object 
  543. */ 
  544. do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() ); 
  545.  
  546. public function after_save() { 
  547. $object_type = $this->object_type(); 
  548. $object_id = $this->object_id(); 
  549.  
  550. /** 
  551. * Fires after all fields have been saved. 
  552. * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type 
  553. * Usually `post` (this applies to all post-types). 
  554. * Could also be `comment`, `user` or `options-page`. 
  555. * @param int $object_id The ID of the current object 
  556. * @param array $cmb_id The current box ID 
  557. * @param string $updated Array of field ids that were updated. 
  558. * Will only include field ids that had values change. 
  559. * @param array $cmb This CMB2 object 
  560. */ 
  561. do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this ); 
  562.  
  563. /** 
  564. * Fires after all fields have been saved. 
  565. * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id. 
  566. * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type 
  567. * Usually `post` (this applies to all post-types). 
  568. * Could also be `comment`, `user` or `options-page`. 
  569. * @param int $object_id The ID of the current object 
  570. * @param string $updated Array of field ids that were updated. 
  571. * Will only include field ids that had values change. 
  572. * @param array $cmb This CMB2 object 
  573. */ 
  574. do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this ); 
  575.  
  576. /** 
  577. * Save a repeatable group 
  578. * @since 1.x.x 
  579. * @param array $args Field arguments array 
  580. * @return mixed Return of CMB2_Field::update_data() 
  581. */ 
  582. public function save_group( $args ) { 
  583. if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) { 
  584. return; 
  585.  
  586. return $this->save_group_field( $this->get_new_field( $args ) ); 
  587.  
  588. /** 
  589. * Save a repeatable group 
  590. * @since 1.x.x 
  591. * @param array $field_group CMB2_Field group field object 
  592. * @return mixed Return of CMB2_Field::update_data() 
  593. */ 
  594. public function save_group_field( $field_group ) { 
  595. $base_id = $field_group->id(); 
  596.  
  597. if ( ! isset( $this->data_to_save[ $base_id ] ) ) { 
  598. return; 
  599.  
  600. $old = $field_group->get_data(); 
  601. // Check if group field has sanitization_cb 
  602. $group_vals = $field_group->sanitization_cb( $this->data_to_save[ $base_id ] ); 
  603. $saved = array(); 
  604.  
  605. $field_group->index = 0; 
  606. $field_group->data_to_save = $this->data_to_save; 
  607.  
  608. foreach ( array_values( $field_group->fields() ) as $field_args ) { 
  609.  
  610. $field = $this->get_new_field( $field_args, $field_group ); 
  611. $sub_id = $field->id( true ); 
  612.  
  613. foreach ( (array) $group_vals as $field_group->index => $post_vals ) { 
  614.  
  615. // Get value 
  616. $new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] ) 
  617. ? $group_vals[ $field_group->index ][ $sub_id ] 
  618. : false; 
  619.  
  620. // Sanitize 
  621. $new_val = $field->sanitization_cb( $new_val ); 
  622.  
  623. if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) { 
  624. if ( $field->args( 'repeatable' ) ) { 
  625. $_new_val = array(); 
  626. foreach ( $new_val as $group_index => $grouped_data ) { 
  627. // Add the supporting data to the $saved array stack 
  628. $saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value']; 
  629. // Reset var to the actual value 
  630. $_new_val[ $group_index ] = $grouped_data['value']; 
  631. $new_val = $_new_val; 
  632. } else { 
  633. // Add the supporting data to the $saved array stack 
  634. $saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value']; 
  635. // Reset var to the actual value 
  636. $new_val = $new_val['value']; 
  637.  
  638. // Get old value 
  639. $old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] ) 
  640. ? $old[ $field_group->index ][ $sub_id ] 
  641. : false; 
  642.  
  643. $is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val ); 
  644. $is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) ); 
  645.  
  646. // Compare values and add to `$updated` array 
  647. if ( $is_updated || $is_removed ) { 
  648. $this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id; 
  649.  
  650. // Add to `$saved` array 
  651. $saved[ $field_group->index ][ $sub_id ] = $new_val; 
  652.  
  653.  
  654. $saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] ); 
  655.  
  656. $saved = CMB2_Utils::filter_empty( $saved ); 
  657.  
  658. return $field_group->update_data( $saved, true ); 
  659.  
  660. /** 
  661. * Get object id from global space if no id is provided 
  662. * @since 1.0.0 
  663. * @param integer $object_id Object ID 
  664. * @return integer $object_id Object ID 
  665. */ 
  666. public function object_id( $object_id = 0 ) { 
  667. global $pagenow; 
  668.  
  669. if ( $object_id ) { 
  670. $this->object_id = $object_id; 
  671. return $this->object_id; 
  672.  
  673. if ( $this->object_id ) { 
  674. return $this->object_id; 
  675.  
  676. // Try to get our object ID from the global space 
  677. switch ( $this->object_type() ) { 
  678. case 'user': 
  679. $object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id; 
  680. $object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id; 
  681. break; 
  682.  
  683. case 'comment': 
  684. $object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id; 
  685. $object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id; 
  686. break; 
  687.  
  688. case 'term': 
  689. $object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id; 
  690. break; 
  691.  
  692. default: 
  693. $object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id; 
  694. $object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id; 
  695. break; 
  696.  
  697. // reset to id or 0 
  698. $this->object_id = $object_id ? $object_id : 0; 
  699.  
  700. return $this->object_id; 
  701.  
  702. /** 
  703. * Sets the $object_type based on metabox settings 
  704. * @since 1.0.0 
  705. * @return string Object type 
  706. */ 
  707. public function mb_object_type() { 
  708. if ( null !== $this->mb_object_type ) { 
  709. return $this->mb_object_type; 
  710.  
  711. if ( $this->is_options_page_mb() ) { 
  712. $this->mb_object_type = 'options-page'; 
  713. return $this->mb_object_type; 
  714.  
  715. $registered_types = $this->prop( 'object_types' ); 
  716.  
  717. if ( ! $registered_types ) { 
  718. $this->mb_object_type = 'post'; 
  719. return $this->mb_object_type; 
  720.  
  721. $type = false; 
  722.  
  723. // check if 'object_types' is a string 
  724. if ( is_string( $registered_types ) ) { 
  725. $type = $registered_types; 
  726.  
  727. // if it's an array of one, extract it 
  728. elseif ( is_array( $registered_types ) && 1 === count( $registered_types ) ) { 
  729. $last = end( $registered_types ); 
  730. if ( is_string( $last ) ) { 
  731. $type = $last; 
  732. } elseif ( is_array( $registered_types ) ) { 
  733. $page_type = $this->current_object_type(); 
  734.  
  735. if ( in_array( $page_type, $registered_types, true ) ) { 
  736. $type = $page_type; 
  737.  
  738. // Get our object type 
  739. switch ( $type ) { 
  740.  
  741. case 'user': 
  742. case 'comment': 
  743. case 'term': 
  744. $this->mb_object_type = $type; 
  745. break; 
  746.  
  747. default: 
  748. $this->mb_object_type = 'post'; 
  749. break; 
  750.  
  751. return $this->mb_object_type; 
  752.  
  753. /** 
  754. * Determines if metabox is for an options page 
  755. * @since 1.0.1 
  756. * @return boolean True/False 
  757. */ 
  758. public function is_options_page_mb() { 
  759. return ( isset( $this->meta_box['show_on']['key'] ) && 'options-page' === $this->meta_box['show_on']['key'] || array_key_exists( 'options-page', $this->meta_box['show_on'] ) ); 
  760.  
  761. /** 
  762. * Returns the object type 
  763. * @since 1.0.0 
  764. * @return string Object type 
  765. */ 
  766. public function object_type( $object_type = '' ) { 
  767. if ( $object_type ) { 
  768. $this->object_type = $object_type; 
  769. return $this->object_type; 
  770.  
  771. if ( $this->object_type ) { 
  772. return $this->object_type; 
  773.  
  774. $this->object_type = $this->current_object_type(); 
  775.  
  776. return $this->object_type; 
  777.  
  778. /** 
  779. * Get the object type for the current page, based on the $pagenow global. 
  780. * @since 2.2.2 
  781. * @return string Page object type name. 
  782. */ 
  783. public function current_object_type() { 
  784. global $pagenow; 
  785. $type = 'post'; 
  786.  
  787. if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) { 
  788. $type = 'user'; 
  789.  
  790. if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) { 
  791. $type = 'comment'; 
  792.  
  793. if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) { 
  794. $type = 'term'; 
  795.  
  796. return $type; 
  797.  
  798. /** 
  799. * Set metabox property. 
  800. * @since 2.2.2 
  801. * @param string $property Metabox config property to retrieve 
  802. * @param mixed $value Value to set if no value found 
  803. * @return mixed Metabox config property value or false 
  804. */ 
  805. public function set_prop( $property, $value ) { 
  806. $this->meta_box[ $property ] = $value; 
  807.  
  808. return $this->prop( $property ); 
  809.  
  810. /** 
  811. * Get metabox property and optionally set a fallback 
  812. * @since 2.0.0 
  813. * @param string $property Metabox config property to retrieve 
  814. * @param mixed $fallback Fallback value to set if no value found 
  815. * @return mixed Metabox config property value or false 
  816. */ 
  817. public function prop( $property, $fallback = null ) { 
  818. if ( array_key_exists( $property, $this->meta_box ) ) { 
  819. return $this->meta_box[ $property ]; 
  820. } elseif ( $fallback ) { 
  821. return $this->meta_box[ $property ] = $fallback; 
  822.  
  823. /** 
  824. * Get a field object 
  825. * @since 2.0.3 
  826. * @param string|array|CMB2_Field $field Metabox field id or field config array or CMB2_Field object 
  827. * @param CMB2_Field $field_group (optional) CMB2_Field object (group parent) 
  828. * @return CMB2_Field|false CMB2_Field object (or false) 
  829. */ 
  830. public function get_field( $field, $field_group = null ) { 
  831. if ( is_a( $field, 'CMB2_Field' ) ) { 
  832. return $field; 
  833.  
  834. $field_id = is_string( $field ) ? $field : $field['id']; 
  835.  
  836. $parent_field_id = ! empty( $field_group ) ? $field_group->id() : ''; 
  837. $ids = $this->get_field_ids( $field_id, $parent_field_id, true ); 
  838.  
  839. if ( ! $ids ) { 
  840. return false; 
  841.  
  842. list( $field_id, $sub_field_id ) = $ids; 
  843.  
  844. $index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' ); 
  845. if ( array_key_exists( $index, $this->fields ) ) { 
  846. return $this->fields[ $index ]; 
  847.  
  848. $this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) ); 
  849.  
  850. return $this->fields[ $index ]; 
  851.  
  852. /** 
  853. * Handles determining which type of arguments to pass to CMB2_Field 
  854. * @since 2.0.7 
  855. * @param mixed $field_id Field (or group field) ID 
  856. * @param mixed $field_args Array of field arguments 
  857. * @param mixed $sub_field_id Sub field ID (if field_group exists) 
  858. * @param mixed $field_group If a sub-field, will be the parent group CMB2_Field object 
  859. * @return array Array of CMB2_Field arguments 
  860. */ 
  861. public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) { 
  862.  
  863. // Check if group is passed and if fields were added in the old-school fields array 
  864. if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) { 
  865.  
  866. // Update the fields array w/ any modified properties inherited from the group field 
  867. $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args; 
  868.  
  869. return $this->get_default_args( $field_args, $field_group ); 
  870.  
  871. if ( is_array( $field_args ) ) { 
  872. $this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] ); 
  873.  
  874. return $this->get_default_args( $this->meta_box['fields'][ $field_id ] ); 
  875.  
  876. /** 
  877. * Get default field arguments specific to this CMB2 object. 
  878. * @since 2.2.0 
  879. * @param array $field_args Metabox field config array. 
  880. * @param CMB2_Field $field_group (optional) CMB2_Field object (group parent) 
  881. * @return array Array of field arguments. 
  882. */ 
  883. protected function get_default_args( $field_args, $field_group = null ) { 
  884. if ( $field_group ) { 
  885. $args = array( 
  886. 'field_args' => $field_args,  
  887. 'group_field' => $field_group,  
  888. ); 
  889. } else { 
  890. $args = array( 
  891. 'field_args' => $field_args,  
  892. 'object_type' => $this->object_type(),  
  893. 'object_id' => $this->object_id(),  
  894. 'cmb_id' => $this->cmb_id,  
  895. ); 
  896.  
  897. return $args; 
  898.  
  899. /** 
  900. * Get a new field object specific to this CMB2 object. 
  901. * @since 2.2.0 
  902. * @param array $field_args Metabox field config array. 
  903. * @param CMB2_Field $field_group (optional) CMB2_Field object (group parent) 
  904. * @return CMB2_Field CMB2_Field object 
  905. */ 
  906. protected function get_new_field( $field_args, $field_group = null ) { 
  907. return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) ); 
  908.  
  909. /** 
  910. * When fields are added in the old-school way, intitate them as they should be 
  911. * @since 2.1.0 
  912. * @param array $fields Array of fields to add 
  913. * @param mixed $parent_field_id Parent field id or null 
  914. */ 
  915. protected function add_fields( $fields, $parent_field_id = null ) { 
  916. foreach ( $fields as $field ) { 
  917.  
  918. $sub_fields = false; 
  919. if ( array_key_exists( 'fields', $field ) ) { 
  920. $sub_fields = $field['fields']; 
  921. unset( $field['fields'] ); 
  922.  
  923. $field_id = $parent_field_id 
  924. ? $this->add_group_field( $parent_field_id, $field ) 
  925. : $this->add_field( $field ); 
  926.  
  927. if ( $sub_fields ) { 
  928. $this->add_fields( $sub_fields, $field_id ); 
  929.  
  930. /** 
  931. * Add a field to the metabox 
  932. * @since 2.0.0 
  933. * @param array $field Metabox field config array 
  934. * @param int $position (optional) Position of metabox. 1 for first, etc 
  935. * @return mixed Field id or false 
  936. */ 
  937. public function add_field( array $field, $position = 0 ) { 
  938. if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) { 
  939. return false; 
  940.  
  941. if ( 'oembed' === $field['type'] ) { 
  942. // Initiate oembed Ajax hooks 
  943. cmb2_ajax(); 
  944.  
  945. if ( isset( $field['column'] ) && false !== $field['column'] ) { 
  946. $field = $this->define_field_column( $field ); 
  947.  
  948. if ( isset( $field['taxonomy'] ) && ! empty( $field['remove_default'] ) ) { 
  949. $this->tax_metaboxes_to_remove[ $field['taxonomy'] ] = $field['taxonomy']; 
  950.  
  951. $this->_add_field_to_array( 
  952. $field,  
  953. $this->meta_box['fields'],  
  954. $position 
  955. ); 
  956.  
  957. return $field['id']; 
  958.  
  959. /** 
  960. * Defines a field's column if requesting to be show in admin columns. 
  961. * @since 2.2.3 
  962. * @param array $field Metabox field config array. 
  963. * @return array Modified metabox field config array. 
  964. */ 
  965. protected function define_field_column( array $field ) { 
  966. $this->has_columns = true; 
  967.  
  968. $column = is_array( $field['column'] ) ? $field['column'] : array(); 
  969.  
  970. $field['column'] = wp_parse_args( $column, array( 
  971. 'name' => isset( $field['name'] ) ? $field['name'] : '',  
  972. 'position' => false,  
  973. ) ); 
  974.  
  975. return $field; 
  976.  
  977. /** 
  978. * Add a field to a group 
  979. * @since 2.0.0 
  980. * @param string $parent_field_id The field id of the group field to add the field 
  981. * @param array $field Metabox field config array 
  982. * @param int $position (optional) Position of metabox. 1 for first, etc 
  983. * @return mixed Array of parent/field ids or false 
  984. */ 
  985. public function add_group_field( $parent_field_id, array $field, $position = 0 ) { 
  986. if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) { 
  987. return false; 
  988.  
  989. $parent_field = $this->meta_box['fields'][ $parent_field_id ]; 
  990.  
  991. if ( 'group' !== $parent_field['type'] ) { 
  992. return false; 
  993.  
  994. if ( ! isset( $parent_field['fields'] ) ) { 
  995. $this->meta_box['fields'][ $parent_field_id ]['fields'] = array(); 
  996.  
  997. $this->_add_field_to_array( 
  998. $field,  
  999. $this->meta_box['fields'][ $parent_field_id ]['fields'],  
  1000. $position 
  1001. ); 
  1002.  
  1003. return array( $parent_field_id, $field['id'] ); 
  1004.  
  1005. /** 
  1006. * Add a field array to a fields array in desired position 
  1007. * @since 2.0.2 
  1008. * @param array $field Metabox field config array 
  1009. * @param array &$fields Array (passed by reference) to append the field (array) to 
  1010. * @param integer $position Optionally specify a position in the array to be inserted 
  1011. */ 
  1012. protected function _add_field_to_array( $field, &$fields, $position = 0 ) { 
  1013. if ( $position ) { 
  1014. CMB2_Utils::array_insert( $fields, array( $field['id'] => $field ), $position ); 
  1015. } else { 
  1016. $fields[ $field['id'] ] = $field; 
  1017.  
  1018. /** 
  1019. * Remove a field from the metabox 
  1020. * @since 2.0.0 
  1021. * @param string $field_id The field id of the field to remove 
  1022. * @param string $parent_field_id (optional) The field id of the group field to remove field from 
  1023. * @return bool True if field was removed 
  1024. */ 
  1025. public function remove_field( $field_id, $parent_field_id = '' ) { 
  1026. $ids = $this->get_field_ids( $field_id, $parent_field_id ); 
  1027.  
  1028. if ( ! $ids ) { 
  1029. return false; 
  1030.  
  1031. list( $field_id, $sub_field_id ) = $ids; 
  1032.  
  1033. unset( $this->fields[ implode( '', $ids ) ] ); 
  1034.  
  1035. if ( ! $sub_field_id ) { 
  1036. unset( $this->meta_box['fields'][ $field_id ] ); 
  1037. return true; 
  1038.  
  1039. if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) { 
  1040. unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ); 
  1041. if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) { 
  1042. unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ); 
  1043. return true; 
  1044.  
  1045. /** 
  1046. * Update or add a property to a field 
  1047. * @since 2.0.0 
  1048. * @param string $field_id Field id 
  1049. * @param string $property Field property to set/update 
  1050. * @param mixed $value Value to set the field property 
  1051. * @param string $parent_field_id (optional) The field id of the group field to remove field from 
  1052. * @return mixed Field id. Strict compare to false, as success can return a falsey value (like 0) 
  1053. */ 
  1054. public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) { 
  1055. $ids = $this->get_field_ids( $field_id, $parent_field_id ); 
  1056.  
  1057. if ( ! $ids ) { 
  1058. return false; 
  1059.  
  1060. list( $field_id, $sub_field_id ) = $ids; 
  1061.  
  1062. if ( ! $sub_field_id ) { 
  1063. $this->meta_box['fields'][ $field_id ][ $property ] = $value; 
  1064. return $field_id; 
  1065.  
  1066. $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value; 
  1067. return $field_id; 
  1068.  
  1069. /** 
  1070. * Check if field ids match a field and return the index/field id 
  1071. * @since 2.0.2 
  1072. * @param string $field_id Field id 
  1073. * @param string $parent_field_id (optional) Parent field id 
  1074. * @return mixed Array of field/parent ids, or false 
  1075. */ 
  1076. public function get_field_ids( $field_id, $parent_field_id = '' ) { 
  1077. $sub_field_id = $parent_field_id ? $field_id : ''; 
  1078. $field_id = $parent_field_id ? $parent_field_id : $field_id; 
  1079. $fields =& $this->meta_box['fields']; 
  1080.  
  1081. if ( ! array_key_exists( $field_id, $fields ) ) { 
  1082. $field_id = $this->search_old_school_array( $field_id, $fields ); 
  1083.  
  1084. if ( false === $field_id ) { 
  1085. return false; 
  1086.  
  1087. if ( ! $sub_field_id ) { 
  1088. return array( $field_id, $sub_field_id ); 
  1089.  
  1090. if ( 'group' !== $fields[ $field_id ]['type'] ) { 
  1091. return false; 
  1092.  
  1093. if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) { 
  1094. $sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] ); 
  1095.  
  1096. return false === $sub_field_id ? false : array( $field_id, $sub_field_id ); 
  1097.  
  1098. /** 
  1099. * When using the old array filter, it is unlikely field array indexes will be the field id 
  1100. * @since 2.0.2 
  1101. * @param string $field_id The field id 
  1102. * @param array $fields Array of fields to search 
  1103. * @return mixed Field index or false 
  1104. */ 
  1105. public function search_old_school_array( $field_id, $fields ) { 
  1106. $ids = wp_list_pluck( $fields, 'id' ); 
  1107. $index = array_search( $field_id, $ids ); 
  1108. return false !== $index ? $index : false; 
  1109.  
  1110. /** 
  1111. * Handles metabox property callbacks, and passes this $cmb object as property. 
  1112. * @since 2.2.3 
  1113. * @param callable $cb The callback method/function/closure 
  1114. * @return mixed Return of the callback function. 
  1115. */ 
  1116. protected function do_callback( $cb ) { 
  1117. return call_user_func( $cb, $this ); 
  1118.  
  1119. /** 
  1120. * Generate a unique nonce field for each registered meta_box 
  1121. * @since 2.0.0 
  1122. * @return string unique nonce hidden input 
  1123. */ 
  1124. public function nonce_field() { 
  1125. wp_nonce_field( $this->nonce(), $this->nonce(), false, true ); 
  1126.  
  1127. /** 
  1128. * Generate a unique nonce for each registered meta_box 
  1129. * @since 2.0.0 
  1130. * @return string unique nonce string 
  1131. */ 
  1132. public function nonce() { 
  1133. if ( $this->generated_nonce ) { 
  1134. return $this->generated_nonce; 
  1135. $this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id ); 
  1136. return $this->generated_nonce; 
  1137.  
  1138. /** 
  1139. * Magic getter for our object. 
  1140. * @param string $field 
  1141. * @throws Exception Throws an exception if the field is invalid. 
  1142. * @return mixed 
  1143. */ 
  1144. public function __get( $field ) { 
  1145. switch ( $field ) { 
  1146. case 'updated': 
  1147. case 'has_columns': 
  1148. case 'tax_metaboxes_to_remove': 
  1149. return $this->{$field}; 
  1150. default: 
  1151. return parent::__get( $field ); 
  1152.