Skip to content

Instantly share code, notes, and snippets.

@anhskohbo
Last active January 4, 2018 15:57
Show Gist options
  • Save anhskohbo/75ddb64b5f3294f192ec5ac602fb5df3 to your computer and use it in GitHub Desktop.
Save anhskohbo/75ddb64b5f3294f192ec5ac602fb5df3 to your computer and use it in GitHub Desktop.
CMB2 With Multidimensional Support

With this way, you must extends CMB2 class:

A class will be handle multi-dimensional data (almost code is taken from WP_Customize_Setting class)

$metabox = new Our_CMB2(array(
  'id' => 'our-metabox',
  // ....
));

$metabox->add_field(array(
  // ...
));
<?php
class Multidimensional {
/**
* Return item in array with multidimensional support.
*
* @param array $array
* @param string $keys
* @param mixed $default
* @return string
*/
public static function find( $array, $keys, $default = null ) {
if ( isset( $array[ $keys ] ) ) {
return $array[ $keys ];
}
$id_data = static::split( $keys );
if ( empty( $id_data['keys'] ) || empty( $array[ $id_data['base'] ] ) ) {
return $default;
}
return static::get( $array[ $id_data['base'] ], $id_data['keys'], $default );
}
/**
* Get split id_data for multidimensional.
*
* @param string $id
* @return array
*/
public static function split( $id ) {
$id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $id ) );
$id_data['base'] = array_shift( $id_data['keys'] );
return $id_data;
}
/**
* Will attempt to fetch a specific value from a multidimensional array.
*
* @param array $root
* @param string $keys
* @param mixed $default
* @return mixed|null
*/
public static function get( $root, $keys, $default = null ) {
if ( empty( $keys ) ) { // If there are no keys, test the root.
return isset( $root ) ? $root : $default;
}
$result = static::multidimensional( $root, $keys );
return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
}
/**
* Will attempt to check if a specific value in a multidimensional array is set.
*
* @param array $root
* @param string $keys
* @return bool
*/
public static function has( $root, $keys ) {
$result = static::get( $root, $keys );
return isset( $result );
}
/**
* Will attempt to replace a specific value in a multidimensional array.
*
* @param array $root
* @param string $keys
* @return mixed
*/
public static function delete( &$root, $keys ) {
$result = static::multidimensional( $root, $keys );
if ( isset( $result ) ) {
unset( $result['node'][ $result['key'] ] );
}
return $root;
}
/**
* Will attempt to replace a specific value in a multidimensional array.
*
* @param array $root
* @param string $keys
* @param mixed $value
* @return mixed
*/
public static function replace( $root, $keys, $value ) {
if ( ! isset( $value ) ) {
return $root;
} elseif ( empty( $keys ) ) { // If there are no keys, we're replacing the root.
return $value;
}
$result = static::multidimensional( $root, $keys, true );
if ( isset( $result ) ) {
$result['node'][ $result['key'] ] = $value;
}
return $root;
}
/**
* Multidimensional helper function.
*
* @param array $root
* @param string $keys
* @param bool $create
* @return array|void
*/
protected static function multidimensional( &$root, $keys, $create = false ) {
if ( $create && empty( $root ) ) {
$root = array();
}
if ( ! isset( $root ) || empty( $keys ) ) {
return;
}
$last = array_pop( $keys );
$node = &$root;
foreach ( $keys as $key ) {
if ( $create && ! isset( $node[ $key ] ) ) {
$node[ $key ] = array();
}
if ( ! is_array( $node ) || ! isset( $node[ $key ] ) ) {
return;
}
$node = &$node[ $key ];
}
if ( $create ) {
if ( ! is_array( $node ) ) {
// Account for an array overriding a string or object value.
$node = array();
}
if ( ! isset( $node[ $last ] ) ) {
$node[ $last ] = array();
}
}
if ( ! isset( $node[ $last ] ) ) {
return;
}
return array(
'root' => &$root,
'node' => &$node,
'key' => $last,
);
}
}
<?php
class Our_CMB2 extends CMB2 {
/**
* Overrite CMB2 process and save form fields.
*/
public function process_fields() {
$this->pre_process();
// Remove the show_on properties so saving works
$this->prop( 'show_on', array() );
// save field ids of those that are updated
$this->updated = array();
foreach ( $this->prop( 'fields' ) as $field_args ) {
// Magic is here!
// Fake $this->data_to_save for support multi-dimensional.
// If see a multidimensional-like, just add a parsed data with name
// same as field ID. So we can easy access to multi-dimensional data.
if ( strpos( $args['id'], '[' ) ) {
$this->data_to_save[ $args['id'] ] = Multidimensional::find( $this->data_to_save, $args['id'], '' );
}
$this->process_field( $field_args );
}
}
}
<?php
class Our_CMB2_Hooks {
/**
* Init hooks.
*/
public function init() {
// Support multidimensional fields.
add_filter( 'cmb2_override_meta_save', array( $this, 'multidimensional_save_data' ), 10, 4 );
add_filter( 'cmb2_override_meta_value', array( $this, 'multidimensional_get_data' ), 10, 4 );
add_filter( 'cmb2_override_meta_remove', array( $this, 'multidimensional_remove_data' ), 10, 4 );
}
/**
* Filter whether to override saving of meta value.
*
* @param null|bool $check Whether to allow updating metadata for the given type.
* @param array $args Array of data about current field including.
* @param array $field_args All field argument.
* @param CMB2_Field $field This field objec.
* @return mixed
*/
public function multidimensional_save_data( $check, $args, $field_args, $field ) {
$no_override = null;
$id_data = Multidimensional::split( $args['field_id'] );
// Tell CMB2 that we no override value.
if ( empty( $id_data['keys'] ) ) {
return $no_override;
}
// Options page updating...
if ( 'options-page' === $args['type'] || empty( $args['id'] ) ) {
$options = cmb2_options( $args['id'] )->get( $id_data['base'], array() );
$repace_value = $args['value'];
if ( ! $args['single'] ) {
$repace_value = Multidimensional::get( $options, $id_data['keys'], array() );
$repace_value[] = $args['value'];
$repace_value = array_unique( $repace_value );
}
$update_values = Multidimensional::replace( $options, $id_data['keys'], $repace_value );
return cmb2_options( $args['id'] )->update( $id_data['base'], $update_values, false, true );
}
// Update metadata...
// NOTE: Maybe have bugs in this case, see: CMB2_Field::update_data().
$metadata = get_metadata( $args['type'], $args['id'], $id_data['base'], true );
// Delete meta if we have an empty array.
if ( is_array( $args['value'] ) && empty( $args['value'] ) ) {
$update_values = Multidimensional::delete( $metadata, $id_data['keys'] );
} else {
$update_values = Multidimensional::replace( $metadata, $id_data['keys'], $args['value'] );
}
return update_metadata( $args['type'], $args['id'], $id_data['base'], $update_values );
}
/**
* Filter whether to override saving of meta value.
*
* @param null|bool $delete Whether to allow metadata deletion of the given type.
* @param array $args Array of data about current field.
* @param int|string $field_args All field arguments.
* @param CMB2_Field $field CMB2_Field object.
* @return mixed
*/
public function multidimensional_remove_data( $delete, $args, $field_args, $field ) {
$no_override = null;
$id_data = Multidimensional::split( $args['field_id'] );
// Tell CMB2 that we no override value.
if ( empty( $id_data['keys'] ) ) {
return $no_override;
}
// Handler delete options.
if ( 'options-page' === $args['type'] || empty( $args['id'] ) ) {
$options = cmb2_options( $args['id'] )->get( $id_data['base'], array() );
Multidimensional::delete( $options, $id_data['keys'] );
return cmb2_options( $args['id'] )->update( $id_data['base'], $options, false, true );
}
// Handler delete metadata.
$metadata = get_metadata( $args['type'], $args['id'], $id_data['base'], true );
Multidimensional::delete( $metadata, $id_data['keys'] );
return update_metadata( $args['type'], $args['id'], $id_data['base'], $metadata );
}
/**
* Filter whether to override saving of meta value.
*
* @param null|bool $value The meta value should return.
* @param int|string $object_id Object ID.
* @param array $args An array of arguments for retrieving data.
* @param CMB2_Field $field CMB2_Field object.
* @return mixed
*/
public function multidimensional_get_data( $value, $object_id, $args, $field ) {
$no_override = 'cmb2_field_no_override_val';
$id_data = Multidimensional::split( $args['field_id'] );
// Tell CMB2 that we no override value.
if ( empty( $id_data['keys'] ) ) {
return $no_override;
}
if ( 'options-page' === $args['type'] ) {
$metadata = cmb2_options( $args['id'] )->get( $id_data['base'] );
} else {
// NOTE: Maybe have bugs in this case, see: CMB2_Field::get_data().
$metadata = get_metadata( $args['type'], $args['id'], $id_data['base'], true );
}
return Multidimensional::get( $metadata, $id_data['keys'] );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment