$type variable passed to the factory() method * * @see factory * @var string */ public $type; /** * Field value * * @var mixed */ protected $value; /** * Default field value * * @var mixed */ protected $default_value; /** * Sanitized field name used as input name attribute during field render * * @see factory() * @see set_name() * @var string */ protected $name; /** * The base field name which is used in the container. * * @see set_base_name() * @var string */ protected $base_name; /** * Field name used as label during field render * * @see factory() * @see set_label() * @var string */ protected $label; /** * Additional text containing information and guidance for the user * * @see help_text() * @var string */ protected $help_text; /** * Field DataStore instance to which save, load and delete calls are delegated * * @see set_datastore() * @see get_datastore() * @var Datastore_Interface */ protected $store; /** * The type of the container this field is in * * @see get_context() * @var string */ protected $context; /** * Whether or not this value should be auto loaded. Applicable to theme options only. * * @see set_autoload() * @var bool **/ protected $autoload = false; /** * Whether or not this field will be initialized when the field is in the viewport (visible). * * @see set_lazyload() * @var bool **/ protected $lazyload = false; /** * The width of the field. * * @see set_width() * @var int **/ protected $width = 0; /** * Custom CSS classes. * * @see add_class() * @var array **/ protected $classes = array(); /** * Whether or not this field is required. * * @see set_required() * @var bool **/ protected $required = false; /** * Prefix to be prepended to the field name during load, save, delete and render * * @var string **/ protected $name_prefix = '_'; /** * Stores the field conditional logic rules. * * @var array **/ protected $conditional_logic = array(); /** * Create a new field of type $type and name $name and label $label. * * @param string $type * @param string $name lower case and underscore-delimited * @param string $label (optional) Automatically generated from $name if not present * @return object $field **/ public static function factory( $type, $name, $label = null ) { // backward compatibility: `file` type used to be called `attachment` if ( $type === 'attachment' ) { $type = 'file'; } $type = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $type ) ) ); $class = __NAMESPACE__ . '\\' . $type . '_Field'; if ( ! class_exists( $class ) ) { Incorrect_Syntax_Exception::raise( 'Unknown field "' . $type . '".' ); $class = __NAMESPACE__ . '\\Broken_Field'; } $field = new $class( $name, $label ); $field->type = $type; return $field; } /** * An alias of factory(). * * @see Field::factory() **/ public static function make( $type, $name, $label = null ) { return self::factory( $type, $name, $label ); } /** * Create a field from a certain type with the specified label. * @param string $name Field name * @param string $label Field label */ protected function __construct( $name, $label ) { $this->set_name( $name ); $this->set_label( $label ); $this->set_base_name( $name ); // Pick random ID $random_string = md5( mt_rand() . $this->get_name() . $this->get_label() ); $random_string = substr( $random_string, 0, 5 ); // 5 chars should be enough $this->id = 'carbon-' . $random_string; $this->init(); } /** * Boot the field once the container is attached. **/ public function boot() { $this->admin_init(); $this->add_template( $this->get_type(), array( $this, 'template' ) ); add_action( 'admin_footer', array( get_class(), 'admin_hook_scripts' ), 5 ); add_action( 'admin_footer', array( get_class(), 'admin_hook_styles' ), 5 ); add_action( 'admin_footer', array( get_class( $this ), 'admin_enqueue_scripts' ), 5 ); } /** * Perform instance initialization after calling setup() **/ public function init() {} /** * Instance initialization when in the admin area. * Called during field boot. **/ public function admin_init() {} /** * Enqueue admin scripts. * Called once per field type. **/ public static function admin_enqueue_scripts() {} /** * Prints the main Underscore template **/ public function template() { } /** * Returns all the Backbone templates * * @return array **/ public function get_templates() { return $this->templates; } /** * Adds a new Backbone template **/ public function add_template( $name, $callback ) { $this->templates[ $name ] = $callback; } /** * Delegate load to the field DataStore instance **/ public function load() { $this->store->load( $this ); if ( $this->get_value() === false ) { $this->set_value( $this->default_value ); } } /** * Delegate save to the field DataStore instance **/ public function save() { return $this->store->save( $this ); } /** * Delegate delete to the field DataStore instance **/ public function delete() { return $this->store->delete( $this ); } /** * Load the field value from an input array based on it's name * * @param array $input (optional) Array of field names and values. Defaults to $_POST **/ public function set_value_from_input( $input = null ) { if ( is_null( $input ) ) { $input = $_POST; } if ( ! isset( $input[ $this->name ] ) ) { $this->set_value( null ); } else { $this->set_value( stripslashes_deep( $input[ $this->name ] ) ); } } /** * Assign DataStore instance for use during load, save and delete * * @param object $store * @return object $this **/ public function set_datastore( Datastore_Interface $store ) { $this->store = $store; return $this; } /** * Return the DataStore instance used by the field * * @return object $store **/ public function get_datastore() { return $this->store; } /** * Assign the type of the container this field is in * * @param string * @return object $this **/ public function set_context( $context ) { $this->context = $context; return $this; } /** * Return the type of the container this field is in * * @return string **/ public function get_context() { return $this->context; } /** * Directly modify the field value * * @param mixed $value **/ public function set_value( $value ) { $this->value = $value; } /** * Set default field value * * @param mixed $default_value **/ public function set_default_value( $default_value ) { $this->default_value = $default_value; return $this; } /** * Get default field value * * @return mixed **/ public function get_default_value() { return $this->default_value; } /** * Return the field value * * @return mixed **/ public function get_value() { return $this->value; } /** * Set field name. * Use only if you are completely aware of what you are doing. * * @param string $name Field name, either sanitized or not **/ public function set_name( $name ) { $name = preg_replace( '~\s+~', '_', mb_strtolower( $name ) ); if ( empty( $name ) ) { Incorrect_Syntax_Exception::raise( 'Field name can\'t be empty' ); } if ( $this->name_prefix && strpos( $name, $this->name_prefix ) !== 0 ) { $name = $this->name_prefix . $name; } $this->name = $name; } /** * Return the field name * * @return string **/ public function get_name() { return $this->name; } /** * Set field base name as defined in the container. **/ public function set_base_name( $name ) { $this->base_name = $name; } /** * Return the field base name. * * @return string **/ public function get_base_name() { return $this->base_name; } /** * Set field name prefix. Calling this method will update the current field * name and the conditional logic fields. * * @param string $prefix * @return object $this **/ public function set_prefix( $prefix ) { $escaped_prefix = preg_quote( $this->name_prefix, '~' ); $this->name = preg_replace( '~^' . $escaped_prefix . '~', '', $this->name ); $this->name_prefix = $prefix; $this->name = $this->name_prefix . $this->name; return $this; } /** * Set field label. * * @param string $label If null, the label will be generated from the field name **/ public function set_label( $label ) { // Try to guess field label from it's name if ( is_null( $label ) ) { // remove the leading underscore(if it's there) $label = preg_replace( '~^_~', '', $this->name ); // remove the leading "crb_"(if it's there) $label = preg_replace( '~^crb_~', '', $label ); // split the name into words and make them capitalized $label = mb_convert_case( str_replace( '_', ' ', $label ), MB_CASE_TITLE ); } $this->label = $label; } /** * Return field label. * * @return string **/ public function get_label() { return $this->label; } /** * Set additional text to be displayed during field render, * containing information and guidance for the user * * @return object $this **/ public function set_help_text( $help_text ) { $this->help_text = $help_text; return $this; } /** * Alias for set_help_text() * * @see set_help_text() * @return object $this **/ public function help_text( $help_text ) { return $this->set_help_text( $help_text ); } /** * Return the field help text * * @return object $this **/ public function get_help_text() { return $this->help_text; } /** * Whether or not this value should be auto loaded. Applicable to theme options only. * * @param bool $autoload * @return object $this **/ public function set_autoload( $autoload ) { $this->autoload = $autoload; return $this; } /** * Return whether or not this value should be auto loaded. * * @return bool **/ public function get_autoload() { return $this->autoload; } /** * Whether or not this field will be initialized when the field is in the viewport (visible). * * @param bool $lazyload * @return object $this **/ public function set_lazyload( $lazyload ) { $this->lazyload = $lazyload; return $this; } /** * Return whether or not this field should be lazyloaded. * * @return bool **/ public function get_lazyload() { return $this->lazyload; } /** * Set the field width. * * @param int $width * @return object $this **/ public function set_width( $width ) { $this->width = (int) $width; return $this; } /** * Get the field width. * * @return int $width **/ public function get_width() { return $this->width; } /** * Add custom CSS class to the field html container. * * @param string|array $classes * @return object $this **/ public function add_class( $classes ) { if ( ! is_array( $classes ) ) { $classes = array_values( array_filter( explode( ' ', $classes ) ) ); } $this->classes = array_map( 'sanitize_html_class', $classes ); return $this; } /** * Get the field custom CSS classes. * * @return array **/ public function get_classes() { return $this->classes; } /** * Whether this field is mandatory for the user * * @param bool $required * @return object $this **/ public function set_required( $required ) { $this->required = $required; return $this; } /** * HTML id attribute getter. * @return string */ public function get_id() { return $this->id; } /** * HTML id attribute setter * @param string $id */ public function set_id( $id ) { $this->id = $id; } /** * Return whether this field is mandatory for the user * * @return bool **/ public function is_required() { return $this->required; } /** * Returns the type of the field based on the class. * The class is stripped by the "CarbonFields" prefix. * Also the "Field" suffix is removed. * Then underscores and backslashes are removed. * * @return string */ public function get_type() { $class = get_class( $this ); return $this->clean_type( $class ); } /** * Cleans up an object class for usage as HTML class * * @return string */ protected function clean_type( $type ) { $remove = array( '_', '\\', 'CarbonFields', 'Field', ); $clean_class = str_replace( $remove, '', $type ); return $clean_class; } /** * Return an array of html classes to be used for the field container * * @return array */ public function get_html_class() { $html_classes = array(); $object_class = get_class( $this ); $html_classes[] = $this->get_type(); $parent_class = $object_class; while ( $parent_class = get_parent_class( $parent_class ) ) { $clean_class = $this->clean_type( $parent_class ); if ( $clean_class ) { $html_classes[] = $clean_class; } } return $html_classes; } /** * Allows the value of a field to be processed after loading. * Can be implemented by the extending class if necessary. * * @return array */ public function process_value() { } /** * Returns an array that holds the field data, suitable for JSON representation. * This data will be available in the Underscore template and the Backbone Model. * * @param bool $load Should the value be loaded from the database or use the value from the current instance. * @return array */ public function to_json( $load ) { if ( $load ) { $this->load(); } $this->process_value(); $field_data = array( 'id' => $this->get_id(), 'type' => $this->get_type(), 'label' => $this->get_label(), 'name' => $this->get_name(), 'base_name' => $this->get_base_name(), 'value' => $this->get_value(), 'default_value' => $this->get_default_value(), 'help_text' => $this->get_help_text(), 'context' => $this->get_context(), 'required' => $this->is_required(), 'lazyload' => $this->get_lazyload(), 'width' => $this->get_width(), 'classes' => $this->get_classes(), 'conditional_logic' => $this->get_conditional_logic(), ); return $field_data; } /** * Set the field visibility conditional logic. * * @param array */ public function set_conditional_logic( $rules ) { $this->conditional_logic = $this->parse_conditional_rules( $rules ); return $this; } /** * Get the conditional logic rules * * @return array */ public function get_conditional_logic() { return $this->conditional_logic; } /** * Validate and parse the conditional logic rules. * * @param array $rules * @return array */ protected function parse_conditional_rules( $rules ) { if ( ! is_array( $rules ) ) { Incorrect_Syntax_Exception::raise( 'Conditional logic rules argument should be an array.' ); } $allowed_operators = array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'CONTAINS' ); $allowed_relations = array( 'AND', 'OR' ); $parsed_rules = array( 'relation' => 'AND', 'rules' => array(), ); foreach ( $rules as $key => $rule ) { // Check if we have a relation key if ( $key === 'relation' ) { $relation = strtoupper( $rule ); if ( ! in_array( $relation, $allowed_relations ) ) { Incorrect_Syntax_Exception::raise( 'Invalid relation type ' . $rule . '. ' . 'The rule should be one of the following: "' . implode( '", "', $allowed_relations ) . '"' ); } $parsed_rules['relation'] = $relation; continue; } // Check if the rule is valid if ( ! is_array( $rule ) || empty( $rule['field'] ) ) { Incorrect_Syntax_Exception::raise( 'Invalid conditional logic rule format. ' . 'The rule should be an array with the "field" key set.' ); } // Check the compare operator if ( empty( $rule['compare'] ) ) { $rule['compare'] = '='; } if ( ! in_array( $rule['compare'], $allowed_operators ) ) { Incorrect_Syntax_Exception::raise( 'Invalid conditional logic compare operator: ' . $rule['compare'] . '
Allowed operators are: ' . implode( ', ', $allowed_operators ) . '' ); } if ( $rule['compare'] === 'IN' || $rule['compare'] === 'NOT IN' ) { if ( ! is_array( $rule['value'] ) ) { Incorrect_Syntax_Exception::raise( 'Invalid conditional logic value format. ' . 'An array is expected, when using the "' . $rule['compare'] . '" operator.' ); } } // Check the value if ( ! isset( $rule['value'] ) ) { $rule['value'] = ''; } $parsed_rules['rules'][] = $rule; } return $parsed_rules; } /** * Hook administration scripts. */ public static function admin_hook_scripts() { wp_enqueue_media(); wp_enqueue_script( 'carbon-fields', \Carbon_Fields\URL . '/assets/js/fields.js', array( 'carbon-app', 'carbon-containers' ) ); wp_localize_script( 'carbon-fields', 'crbl10n', array( 'title' => __( 'Files', 'carbon-fields' ), 'geocode_zero_results' => __( 'The address could not be found. ', 'carbon-fields' ), 'geocode_not_successful' => __( 'Geocode was not successful for the following reason: ', 'carbon-fields' ), 'max_num_items_reached' => __( 'Maximum number of items reached (%s items)', 'carbon-fields' ), 'max_num_rows_reached' => __( 'Maximum number of rows reached (%s rows)', 'carbon-fields' ), 'cannot_create_more_rows' => __( 'Cannot create more than %s rows', 'carbon-fields' ), 'enter_name_of_new_sidebar' => __( 'Please enter the name of the new sidebar:', 'carbon-fields' ), 'remove_sidebar_confirmation' => __( 'Are you sure you wish to remove this sidebar?', 'carbon-fields' ), 'add_sidebar' => __( 'Add Sidebar', 'carbon-fields' ), 'complex_no_rows' => __( 'There are no %s yet. Click here to add one.', 'carbon-fields' ), 'complex_add_button' => __( 'Add %s', 'carbon-fields' ), 'complex_min_num_rows_not_reached' => __( 'Minimum number of rows not reached (%d %s)', 'carbon-fields' ), 'message_form_validation_failed' => __( 'Please fill out all fields correctly. ', 'carbon-fields' ), 'message_required_field' => __( 'This field is required. ', 'carbon-fields' ), 'message_choose_option' => __( 'Please choose an option. ', 'carbon-fields' ), ) ); } /** * Hook administration styles. */ public static function admin_hook_styles() { wp_enqueue_style( 'thickbox' ); } } // END Field