CMB2

CMB2 - The core metabox object.

Defined (2)

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

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