CMB2

CMB2 - The core metabox object.

Defined (1)

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

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