group = $args['group_field']; $this->object_id = $this->group->object_id; $this->object_type = $this->group->object_type; $this->cmb_id = $this->group->cmb_id; } else { $this->object_id = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0; $this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post'; if ( isset( $args['cmb_id'] ) ) { $this->cmb_id = $args['cmb_id']; } } $this->args = $this->_set_field_defaults( $args['field_args'] ); if ( $this->object_id ) { $this->value = $this->get_data(); } } /** * Non-existent methods fallback to checking for field arguments of the same name * * @since 1.1.0 * @param string $name Method name. * @param array $arguments Array of passed-in arguments. * @return mixed Value of field argument */ public function __call( $name, $arguments ) { if ( 'string' === $name ) { return call_user_func_array( array( $this, 'get_string' ), $arguments ); } $key = isset( $arguments[0] ) ? $arguments[0] : ''; return $this->args( $name, $key ); } /** * Retrieves the field id * * @since 1.1.0 * @param boolean $raw Whether to retrieve pre-modidifed id. * @return string Field id */ public function id( $raw = false ) { $id = $raw ? '_id' : 'id'; return $this->args( $id ); } /** * Get a field argument * * @since 1.1.0 * @param string $key Argument to check. * @param string $_key Sub argument to check. * @return mixed Argument value or false if non-existent */ public function args( $key = '', $_key = '' ) { $arg = $this->_data( 'args', $key ); if ( in_array( $key, array( 'default', 'default_cb' ), true ) ) { $arg = $this->get_default(); } elseif ( $_key ) { $arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false; } return $arg; } /** * Retrieve a portion of a field property * * @since 1.1.0 * @param string $var Field property to check. * @param string $key Field property array key to check. * @return mixed Queried property value or false */ public function _data( $var, $key = '' ) { $vars = $this->{$var}; if ( $key ) { return array_key_exists( $key, $vars ) ? $vars[ $key ] : false; } return $vars; } /** * Get Field's value * * @since 1.1.0 * @param string $key If value is an array, is used to get array key->value. * @return mixed Field value or false if non-existent */ public function value( $key = '' ) { return $this->_data( 'value', $key ); } /** * Retrieves metadata/option data * * @since 1.0.1 * @param string $field_id Meta key/Option array key. * @param array $args Override arguments. * @return mixed Meta/Option value */ public function get_data( $field_id = '', $args = array() ) { if ( $field_id ) { $args['field_id'] = $field_id; } elseif ( $this->group ) { $args['field_id'] = $this->group->id(); } $a = $this->data_args( $args ); /** * Filter whether to override getting of meta value. * Returning a non 'cmb2_field_no_override_val' value * will effectively short-circuit the value retrieval. * * @since 2.0.0 * * @param mixed $value The value get_metadata() should * return - a single metadata value, * or an array of values. * * @param int $object_id Object ID. * * @param array $args { * An array of arguments for retrieving data * * @type string $type The current object type * @type int $id The current object ID * @type string $field_id The ID of the field being requested * @type bool $repeat Whether current field is repeatable * @type bool $single Whether current field is a single database row * } * * @param CMB2_Field object $field This field object */ $data = apply_filters( 'cmb2_override_meta_value', 'cmb2_field_no_override_val', $this->object_id, $a, $this ); /** * Filter and parameters are documented for 'cmb2_override_meta_value' filter (above). * * The dynamic portion of the hook, $field_id, refers to the current * field id paramater. Returning a non 'cmb2_field_no_override_val' value * will effectively short-circuit the value retrieval. * * @since 2.0.0 */ $data = apply_filters( "cmb2_override_{$a['field_id']}_meta_value", $data, $this->object_id, $a, $this ); // If no override, get value normally. if ( 'cmb2_field_no_override_val' === $data ) { $data = 'options-page' === $a['type'] ? cmb2_options( $a['id'] )->get( $a['field_id'] ) : get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) ); } if ( $this->group ) { $data = is_array( $data ) && isset( $data[ $this->group->index ][ $this->args( '_id' ) ] ) ? $data[ $this->group->index ][ $this->args( '_id' ) ] : false; } return $data; } /** * Updates metadata/option data. * * @since 1.0.1 * @param mixed $new_value Value to update data with. * @param bool $single Whether data is an array (add_metadata). * @return mixed */ public function update_data( $new_value, $single = true ) { $a = $this->data_args( array( 'single' => $single, ) ); $a['value'] = $a['repeat'] ? array_values( $new_value ) : $new_value; /** * Filter whether to override saving of meta value. * Returning a non-null value will effectively short-circuit the function. * * @since 2.0.0 * * @param null|bool $check Whether to allow updating metadata for the given type. * * @param array $args { * Array of data about current field including: * * @type string $value The value to set * @type string $type The current object type * @type int $id The current object ID * @type string $field_id The ID of the field being updated * @type bool $repeat Whether current field is repeatable * @type bool $single Whether current field is a single database row * } * * @param array $field_args All field arguments * * @param CMB2_Field object $field This field object */ $override = apply_filters( 'cmb2_override_meta_save', null, $a, $this->args(), $this ); /** * Filter and parameters are documented for 'cmb2_override_meta_save' filter (above). * * The dynamic portion of the hook, $a['field_id'], refers to the current * field id paramater. Returning a non-null value * will effectively short-circuit the function. * * @since 2.0.0 */ $override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this ); // If override, return that. if ( null !== $override ) { return $override; } // Options page handling (or temp data store). if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) { return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] ); } // Add metadata if not single. if ( ! $a['single'] ) { return add_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'], false ); } // Delete meta if we have an empty array. if ( is_array( $a['value'] ) && empty( $a['value'] ) ) { return delete_metadata( $a['type'], $a['id'], $a['field_id'], $this->value ); } // Update metadata. return update_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'] ); } /** * Removes/updates metadata/option data. * * @since 1.0.1 * @param string $old Old value. * @return mixed */ public function remove_data( $old = '' ) { $a = $this->data_args( array( 'old' => $old, ) ); /** * Filter whether to override removing of meta value. * Returning a non-null value will effectively short-circuit the function. * * @since 2.0.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param array $args Array of data about current field including: * 'type' : Current object type * 'id' : Current object ID * 'field_id' : Current Field ID * 'repeat' : Whether current field is repeatable * 'single' : Whether to save as a * single meta value * @param array $field_args All field arguments * @param CMB2_Field object $field This field object */ $override = apply_filters( 'cmb2_override_meta_remove', null, $a, $this->args(), $this ); /** * Filter whether to override removing of meta value. * * The dynamic portion of the hook, $a['field_id'], refers to the current * field id paramater. Returning a non-null value * will effectively short-circuit the function. * * @since 2.0.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param array $args Array of data about current field including: * 'type' : Current object type * 'id' : Current object ID * 'field_id' : Current Field ID * 'repeat' : Whether current field is repeatable * 'single' : Whether to save as a * single meta value * @param array $field_args All field arguments * @param CMB2_Field object $field This field object */ $override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this ); // If no override, remove as usual. if ( null !== $override ) { return $override; } // End if. // Option page handling. elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) { return cmb2_options( $a['id'] )->remove( $a['field_id'] ); } // Remove metadata. return delete_metadata( $a['type'], $a['id'], $a['field_id'], $old ); } /** * Data variables for get/set data methods * * @since 1.1.0 * @param array $args Override arguments. * @return array Updated arguments */ public function data_args( $args = array() ) { $args = wp_parse_args( $args, array( 'type' => $this->object_type, 'id' => $this->object_id, 'field_id' => $this->id( true ), 'repeat' => $this->args( 'repeatable' ), 'single' => ! $this->args( 'multiple' ), ) ); return $args; } /** * Checks if field has a registered sanitization callback * * @since 1.0.1 * @param mixed $meta_value Meta value. * @return mixed Possibly sanitized meta value */ public function sanitization_cb( $meta_value ) { if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) { // Remove empties. $meta_value = array_filter( $meta_value ); } // Check if the field has a registered validation callback. $cb = $this->maybe_callback( 'sanitization_cb' ); if ( false === $cb ) { // If requesting NO validation, return meta value. return $meta_value; } elseif ( $cb ) { // Ok, callback is good, let's run it. return call_user_func( $cb, $meta_value, $this->args(), $this ); } $sanitizer = new CMB2_Sanitize( $this, $meta_value ); $field_type = $this->type(); /** * Filter the value before it is saved. * * The dynamic portion of the hook name, $field_type, refers to the field type. * * Passing a non-null value to the filter will short-circuit saving * the field value, saving the passed value instead. * * @param bool|mixed $override_value Sanitization/Validation override value to return. * Default: null. false to skip it. * @param mixed $value The value to be saved to this field. * @param int $object_id The ID of the object where the value will be saved * @param array $field_args The current field's arguments * @param object $sanitizer This `CMB2_Sanitize` object */ $override_value = apply_filters( "cmb2_sanitize_{$field_type}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer ); if ( null !== $override_value ) { return $override_value; } // Sanitization via 'CMB2_Sanitize'. return $sanitizer->{$field_type}(); } /** * Process $_POST data to save this field's value * * @since 2.0.3 * @param array $data_to_save $_POST data to check. * @return array|int|bool Result of save, false on failure */ public function save_field_from_data( array $data_to_save ) { $this->data_to_save = $data_to_save; $meta_value = isset( $this->data_to_save[ $this->id( true ) ] ) ? $this->data_to_save[ $this->id( true ) ] : null; return $this->save_field( $meta_value ); } /** * Sanitize/store a value to this field * * @since 2.0.0 * @param array $meta_value Desired value to sanitize/store. * @return array|int|bool Result of save. false on failure */ public function save_field( $meta_value ) { $updated = false; $action = ''; $new_value = $this->sanitization_cb( $meta_value ); if ( ! $this->args( 'save_field' ) ) { // Nothing to see here. $action = 'disabled'; } elseif ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) { $this->remove_data(); $count = 0; if ( ! empty( $new_value ) ) { foreach ( $new_value as $add_new ) { if ( $this->update_data( $add_new, false ) ) { $count++; } } } $updated = $count ? $count : false; $action = 'repeatable'; } elseif ( ! CMB2_Utils::isempty( $new_value ) && $new_value !== $this->get_data() ) { $updated = $this->update_data( $new_value ); $action = 'updated'; } elseif ( CMB2_Utils::isempty( $new_value ) ) { $updated = $this->remove_data(); $action = 'removed'; } if ( $updated ) { $this->value = $this->get_data(); $this->escaped_value = null; } $field_id = $this->id( true ); /** * Hooks after save field action. * * @since 2.2.0 * * @param string $field_id the current field id paramater. * @param bool $updated Whether the metadata update action occurred. * @param string $action Action performed. Could be "repeatable", "updated", or "removed". * @param CMB2_Field object $field This field object */ do_action( 'cmb2_save_field', $field_id, $updated, $action, $this ); /** * Hooks after save field action. * * The dynamic portion of the hook, $field_id, refers to the * current field id paramater. * * @since 2.2.0 * * @param bool $updated Whether the metadata update action occurred. * @param string $action Action performed. Could be "repeatable", "updated", or "removed". * @param CMB2_Field object $field This field object */ do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this ); return $updated; } /** * Determine if current type is exempt from escaping * * @since 1.1.0 * @return bool True if exempt */ public function escaping_exception() { // These types cannot be escaped. return in_array( $this->type(), array( 'file_list', 'multicheck', 'text_datetime_timestamp_timezone', ) ); } /** * Determine if current type cannot be repeatable * * @since 1.1.0 * @param string $type Field type to check. * @return bool True if type cannot be repeatable */ public function repeatable_exception( $type ) { // These types cannot be repeatable. $internal_fields = array( // Use file_list instead. 'file' => 1, 'radio' => 1, 'title' => 1, 'wysiwyg' => 1, 'checkbox' => 1, 'radio_inline' => 1, 'taxonomy_radio' => 1, 'taxonomy_radio_inline' => 1, 'taxonomy_radio_hierarchical' => 1, 'taxonomy_select' => 1, 'taxonomy_multicheck' => 1, 'taxonomy_multicheck_inline' => 1, 'taxonomy_multicheck_hierarchical' => 1, ); /** * Filter field types that are non-repeatable. * * Note that this does *not* allow overriding the default non-repeatable types. * * @since 2.1.1 * * @param array $fields Array of fields designated as non-repeatable. Note that the field names are *keys*, * and not values. The value can be anything, because it is meaningless. Example: * array( 'my_custom_field' => 1 ) */ $all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields ); return isset( $all_fields[ $type ] ); } /** * Determine if current type has its own defaults field-arguments method. * * @since 2.2.6 * @param string $type Field type to check. * @return bool True if has own method. */ public function has_args_method( $type ) { // These types have their own arguments parser. $type_methods = array( 'group' => 'set_field_defaults_group', 'wysiwyg' => 'set_field_defaults_wysiwyg', ); if ( isset( $type_methods[ $type ] ) ) { return $type_methods[ $type ]; } $all_or_nothing_types = array_flip( apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline', 'taxonomy_radio_hierarchical', ), $this ) ); if ( isset( $all_or_nothing_types[ $type ] ) ) { return 'set_field_defaults_all_or_nothing_types'; } return false; } /** * Escape the value before output. Defaults to 'esc_attr()' * * @since 1.0.1 * @param callable|string $func Escaping function (if not esc_attr()). * @param mixed $meta_value Meta value. * @return mixed Final value. */ public function escaped_value( $func = 'esc_attr', $meta_value = '' ) { if ( null !== $this->escaped_value ) { return $this->escaped_value; } $meta_value = $meta_value ? $meta_value : $this->value(); // Check if the field has a registered escaping callback. if ( $cb = $this->maybe_callback( 'escape_cb' ) ) { // Ok, callback is good, let's run it. return call_user_func( $cb, $meta_value, $this->args(), $this ); } $field_type = $this->type(); /** * Filter the value for escaping before it is ouput. * * The dynamic portion of the hook name, $field_type, refers to the field type. * * Passing a non-null value to the filter will short-circuit the built-in * escaping for this field. * * @param bool|mixed $override_value Escaping override value to return. * Default: null. false to skip it. * @param mixed $meta_value The value to be output. * @param array $field_args The current field's arguments. * @param object $field This `CMB2_Field` object. */ $esc = apply_filters( "cmb2_types_esc_{$field_type}", null, $meta_value, $this->args(), $this ); if ( null !== $esc ) { return $esc; } if ( false === $cb || $this->escaping_exception() ) { // If requesting NO escaping, return meta value. return $this->val_or_default( $meta_value ); } // escaping function passed in? $func = $func ? $func : 'esc_attr'; $meta_value = $this->val_or_default( $meta_value ); if ( is_array( $meta_value ) ) { foreach ( $meta_value as $key => $value ) { $meta_value[ $key ] = call_user_func( $func, $value ); } } else { $meta_value = call_user_func( $func, $meta_value ); } $this->escaped_value = $meta_value; return $this->escaped_value; } /** * Return non-empty value or field default if value IS empty * * @since 2.0.0 * @param mixed $meta_value Field value. * @return mixed Field value, or default value */ public function val_or_default( $meta_value ) { return ! CMB2_Utils::isempty( $meta_value ) ? $meta_value : $this->get_default(); } /** * Offset a time value based on timezone * * @since 1.0.0 * @return string Offset time string */ public function field_timezone_offset() { return CMB2_Utils::timezone_offset( $this->field_timezone() ); } /** * Return timezone string * * @since 1.0.0 * @return string Timezone string */ public function field_timezone() { $value = ''; // Is timezone arg set? if ( $this->args( 'timezone' ) ) { $value = $this->args( 'timezone' ); } // End if. // Is there another meta key with a timezone stored as its value we should use? elseif ( $this->args( 'timezone_meta_key' ) ) { $value = $this->get_data( $this->args( 'timezone_meta_key' ) ); } return $value; } /** * Format the timestamp field value based on the field date/time format arg * * @since 2.0.0 * @param int $meta_value Timestamp. * @param string $format Either date_format or time_format. * @return string Formatted date */ public function format_timestamp( $meta_value, $format = 'date_format' ) { return date( stripslashes( $this->args( $format ) ), $meta_value ); } /** * Return a formatted timestamp for a field * * @since 2.0.0 * @param string $format Either date_format or time_format. * @param string|int $meta_value Optional meta value to check. * @return string Formatted date */ public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) { $meta_value = $meta_value ? $meta_value : $this->escaped_value(); $meta_value = CMB2_Utils::make_valid_time_stamp( $meta_value ); if ( empty( $meta_value ) ) { return ''; } return is_array( $meta_value ) ? array_map( array( $this, 'format_timestamp' ), $meta_value, $format ) : $this->format_timestamp( $meta_value, $format ); } /** * Get timestamp from text date * * @since 2.2.0 * @param string $value Date value. * @return mixed Unix timestamp representing the date. */ public function get_timestamp_from_value( $value ) { return CMB2_Utils::get_timestamp_from_value( $value, $this->args( 'date_format' ) ); } /** * Get field render callback and Render the field row * * @since 1.0.0 */ public function render_field() { $this->render_context = 'edit'; $this->peform_param_callback( 'render_row_cb' ); // For chaining. return $this; } /** * Default field render callback * * @since 2.1.1 */ public function render_field_callback() { // If field is requesting to not be shown on the front-end. if ( ! is_admin() && ! $this->args( 'on_front' ) ) { return; } // If field is requesting to be conditionally shown. if ( ! $this->should_show() ) { return; } $field_type = $this->type(); /** * Hook before field row begins. * * @param CMB2_Field $field The current field object. */ do_action( 'cmb2_before_field_row', $this ); /** * Hook before field row begins. * * The dynamic portion of the hook name, $field_type, refers to the field type. * * @param CMB2_Field $field The current field object. */ do_action( "cmb2_before_{$field_type}_field_row", $this ); $this->peform_param_callback( 'before_row' ); printf( "