src/fields/field-manager.php
<?php
/**
* Field_Manager class
*
* @package Leaves_And_Love\Plugin_Lib
* @since 1.0.0
*/
namespace Leaves_And_Love\Plugin_Lib\Fields;
use Leaves_And_Love\Plugin_Lib\Service;
use Leaves_And_Love\Plugin_Lib\Fields\Interfaces\Field_Manager_Interface;
use Leaves_And_Love\Plugin_Lib\Traits\Container_Service_Trait;
use Leaves_And_Love\Plugin_Lib\Traits\Args_Service_Trait;
use Leaves_And_Love\Plugin_Lib\Translations\Translations_Field_Manager;
use Leaves_And_Love\Plugin_Lib\AJAX;
use Leaves_And_Love\Plugin_Lib\Assets;
use Leaves_And_Love\Plugin_Lib\Error_Handler;
use WP_Error;
use Exception;
if ( ! class_exists( 'Leaves_And_Love\Plugin_Lib\Fields\Field_Manager' ) ) :
/**
* Manager class for fields
*
* @since 1.0.0
*/
class Field_Manager extends Service implements Field_Manager_Interface {
use Container_Service_Trait, Args_Service_Trait;
/**
* Instance ID of this field manager. Used internally.
*
* @since 1.0.0
* @var string
*/
protected $instance_id = '';
/**
* Array of fields that are part of this manager, grouped by their `$section`.
*
* @since 1.0.0
* @var array
*/
protected $field_instances = array();
/**
* Section lookup map for field identifiers.
*
* @since 1.0.0
* @var array
*/
protected $section_lookup = array();
/**
* Array of current values.
*
* @since 1.0.0
* @var array
*/
protected $current_values = array();
/**
* Field manager instances.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $instances = array();
/**
* Instance count of field managers per prefix.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $prefix_count = array();
/**
* Array of registered field types, as `$type => $class_name` pairs.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $field_types = array();
/**
* Internal flag whether default types have been registered.
*
* @since 1.0.0
* @static
* @var bool
*/
protected static $defaults_registered = false;
/**
* Internal flag for whether the current enqueue run is the first one.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $first_enqueue_run = true;
/**
* Internal flags for enqueueing field assets.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $enqueued = array();
/**
* Internal flags for JS templates printed.
*
* @since 1.0.0
* @static
* @var array
*/
protected static $templates_printed = array();
/**
* The AJAX API service definition.
*
* @since 1.0.0
* @static
* @var string
*/
protected static $service_ajax = AJAX::class;
/**
* The Assets API service definition.
*
* @since 1.0.0
* @static
* @var string
*/
protected static $service_assets = Assets::class;
/**
* The Assets API service definition for the library itself.
*
* @since 1.0.0
* @static
* @var string
*/
protected static $service_library_assets = Assets::class;
/**
* Translations to print to the user.
*
* @since 1.0.0
* @static
* @var Translations_Field_Manager
*/
protected static $translations;
/**
* Constructor.
*
* @since 1.0.0
*
* @param string $prefix Prefix.
* @param array $services {
* Array of service instances.
*
* @type AJAX $ajax The AJAX API class instance.
* @type Assets $assets The Assets API class instance.
* @type Error_Handler $error_handler The error handler instance.
* }
* @param array $args {
* Array of arguments.
*
* @type callable $get_value_callback Callback to get current values.
* @type array $get_value_callback_args Arguments to pass to the `$get_value_callback`.
* A placeholder `{id}` can be used to indicate that
* this argument should be replaced by the field ID.
* @type callable $update_value_callback Callback to update the current values with new ones.
* @type array $update_value_callback_args Arguments to pass to the `$update_value_callback`.
* One of these arguments must be a placeholder `{value}`.
* Another placeholder `{id}` can also be used to indicate
* that this argument should be replaced by the field ID.
* @type string $name_prefix The name prefix to create name attributes for fields.
* @type string $render_mode Render mode. Default 'form-table'.
* @type string $field_required_markup HTML markup to render after a label for a required field.
* Default empty string.
* @type bool $skip_js_initialization Whether to skip field initialization in JavaScript.
* Default false.
* }
*/
public function __construct( $prefix, $services, $args ) {
if ( ! isset( $services['library_assets'] ) ) {
$services['library_assets'] = Assets::get_library_instance();
}
$this->set_prefix( $prefix );
$this->set_services( $services );
$this->set_args( $args );
if ( ! isset( self::$prefix_count[ $prefix ] ) ) {
self::$prefix_count[ $prefix ] = 1;
} else {
self::$prefix_count[ $prefix ]++;
}
$this->instance_id = $prefix . self::$prefix_count[ $prefix ];
self::$instances[ $this->instance_id ] = $this;
self::register_default_field_types();
}
/**
* Adds a new field.
*
* @since 1.0.0
*
* @param string $id Field identifier. Must be unique for this field manager.
* @param string $type Identifier of the type.
* @param array $args Optional. Field arguments. See the field class constructor for supported
* arguments. Default empty array.
* @return bool True on success, false on failure.
*/
public function add( $id, $type, $args = array() ) {
if ( ! self::is_field_type_registered( $type ) ) {
return false;
}
if ( isset( $this->section_lookup[ $id ] ) ) {
return false;
}
$section = isset( $args['section'] ) ? $args['section'] : '';
$class_name = self::get_registered_field_type( $type );
$field_instance = new $class_name( $this, $id, $args );
$this->section_lookup[ $id ] = $section;
if ( ! isset( $this->field_instances[ $section ] ) ) {
$this->field_instances[ $section ] = array();
}
$this->field_instances[ $section ][ $id ] = $field_instance;
return true;
}
/**
* Gets a specific field.
*
* @since 1.0.0
*
* @param string $id Field identifier.
* @return Field|null Field instance, or null if it does not exist.
*/
public function get( $id ) {
if ( ! $this->exists( $id ) ) {
return null;
}
return $this->field_instances[ $this->section_lookup[ $id ] ][ $id ];
}
/**
* Checks whether a specific field exists.
*
* @since 1.0.0
*
* @param string $id Field identifier.
* @return bool True if the field exists, false otherwise.
*/
public function exists( $id ) {
if ( ! isset( $this->section_lookup[ $id ] ) ) {
return false;
}
return true;
}
/**
* Removes an existing field.
*
* @since 1.0.0
*
* @param string $id Field identifier.
* @return bool True on success, false on failure.
*/
public function remove( $id ) {
if ( ! $this->exists( $id ) ) {
return false;
}
unset( $this->field_instances[ $this->section_lookup[ $id ] ][ $id ] );
unset( $this->section_lookup[ $id ] );
return true;
}
/**
* Enqueues the necessary assets for a list of fields.
*
* @since 1.0.0
*/
public function enqueue() {
if ( ! $this->enqueued( '_core' ) ) {
$this->library_assets()->register_style(
'fields',
'assets/dist/css/fields.css',
array(
'ver' => \Leaves_And_Love_Plugin_Loader::VERSION,
'enqueue' => true,
)
);
$this->library_assets()->register_script(
'fields',
'assets/dist/js/fields.js',
array(
'deps' => array( 'jquery', 'underscore', 'backbone', 'wp-util' ),
'ver' => \Leaves_And_Love_Plugin_Loader::VERSION,
'in_footer' => true,
'enqueue' => true,
)
);
$this->enqueued( '_core', true );
}
$prefixed_script_handle = str_replace( '_', '-', $this->library_assets()->get_prefix() ) . 'fields';
$script = wp_scripts()->registered[ $prefixed_script_handle ];
if ( ! isset( $script->extra['plugin_lib_data'] ) ) {
$script->extra['plugin_lib_data'] = array(
'field_managers' => array(),
);
}
if ( ! isset( $script->extra['plugin_lib_templates'] ) ) {
$script->extra['plugin_lib_templates'] = array();
}
$values = $this->get_values();
$field_instances = $this->get_fields();
/** This is run to verify there are no circular dependencies. */
$this->resolve_dependency_order( $field_instances );
$field_data = array();
$type_templates = array();
foreach ( $field_instances as $id => $field_instance ) {
$type = $field_instance->slug;
if ( ! $this->enqueued( $type ) ) {
list( $new_dependencies, $new_data ) = $field_instance->enqueue();
if ( ! empty( $new_dependencies ) ) {
$script->deps = array_merge( $script->deps, $new_dependencies );
}
if ( ! empty( $new_data ) ) {
$script->extra['plugin_lib_data'] = array_merge( $script->extra['plugin_lib_data'], $new_data );
}
$this->enqueued( $type, true );
}
if ( ! $this->templates_printed( $type ) ) {
$type_template = array();
ob_start();
$field_instance->print_label_template();
$type_template['label'] = ob_get_clean();
ob_start();
$field_instance->print_content_template();
$type_template['content'] = ob_get_clean();
ob_start();
$field_instance->print_repeatable_item_template();
$type_template['repeatable_item'] = ob_get_clean();
$type_templates[ $type ] = $type_template;
$this->templates_printed( $type, true );
}
$value = isset( $values[ $id ] ) ? $values[ $id ] : $field_instance->default;
$field_data[ $id ] = $field_instance->to_json( $value );
}
if ( ! empty( $field_data ) ) {
$script->extra['plugin_lib_data']['field_managers'][ $this->instance_id ] = array(
'skip_initialization' => $this->skip_js_initialization,
'fields' => $field_data,
);
}
if ( ! empty( $type_templates ) ) {
$script->extra['plugin_lib_templates'] = array_merge( $script->extra['plugin_lib_templates'], $type_templates );
}
if ( self::$first_enqueue_run ) {
$data_hook_name = is_admin() ? 'admin_enqueue_scripts' : 'wp_enqueue_scripts';
$templates_hook_name = is_admin() ? 'admin_footer' : 'wp_footer';
add_action(
$data_hook_name,
function() use ( &$script ) {
$output = 'var pluginLibFieldsAPIData = ' . wp_json_encode( $script->extra['plugin_lib_data'] ) . ';';
wp_scripts()->add_inline_script( $script->handle, $output, 'before' );
},
9999,
0
);
add_action(
$templates_hook_name,
function() use ( &$script ) {
foreach ( $script->extra['plugin_lib_templates'] as $type => $templates ) {
?>
<script type="text/html" id="<?php echo esc_attr( 'tmpl-plugin-lib-field-' . $type . '-label' ); ?>">
<?php echo $templates['label']; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>
</script>
<script type="text/html" id="<?php echo esc_attr( 'tmpl-plugin-lib-field-' . $type . '-content' ); ?>">
<?php echo $templates['content']; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>
</script>
<script type="text/html" id="<?php echo esc_attr( 'tmpl-plugin-lib-field-' . $type . '-repeatable-item' ); ?>">
<?php echo $templates['repeatable_item']; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>
</script>
<?php
}
},
1,
0
);
self::$first_enqueue_run = false;
}
}
/**
* Checks whether dependencies for a specific type have been enqueued.
*
* @since 1.0.0
*
* @param string $type Type to check for.
* @param bool|null $set Optional. A boolean in case the value should be set. Default null.
* @return bool True if the dependencies have been enqueued at the time of calling the function,
* false otherwise.
*/
public function enqueued( $type, $set = null ) {
$result = isset( self::$enqueued[ $type ] ) && self::$enqueued[ $type ];
if ( null !== $set ) {
self::$enqueued[ $type ] = (bool) $set;
}
return $result;
}
/**
* Checks whether templates for a specific type have been printed.
*
* @since 1.0.0
*
* @param string $type Type to check for.
* @param bool|null $set Optional. A boolean in case the value should be set. Default null.
* @return bool True if the templates have been printed at the time of calling the function,
* false otherwise.
*/
public function templates_printed( $type, $set = null ) {
$result = isset( self::$templates_printed[ $type ] ) && self::$templates_printed[ $type ];
if ( null !== $set ) {
self::$templates_printed[ $type ] = (bool) $set;
}
return $result;
}
/**
* Renders a list of fields.
*
* @since 1.0.0
*
* @param string|array|null $sections Optional. Section identifier(s), to only render
* fields that belong to this section. Default null.
* @param callable|null $render_callback Optional. Callback to use for rendering a single
* field. It will be passed the field instance and
* the field's current value. Default is the callback
* specified through the class' $render_mode argument.
*/
public function render( $sections = null, $render_callback = null ) {
$field_instances = $this->get_fields( $sections );
if ( ! $render_callback || ! is_callable( $render_callback ) ) {
switch ( $this->render_mode ) {
case 'divs':
$render_callback = array( $this, 'render_divs_row' );
break;
case 'form-table':
default:
$render_callback = array( $this, 'render_form_table_row' );
}
}
$values = $this->get_values();
foreach ( $field_instances as $id => $field_instance ) {
$value = isset( $values[ $id ] ) ? $values[ $id ] : $field_instance->default;
call_user_func( $render_callback, $field_instance, $value );
}
}
/**
* Gets the current values for all fields of this manager.
*
* @since 1.0.0
*
* @return array Array of values as `$id => $current_value` pairs.
*/
public function get_values() {
if ( empty( $this->current_values ) ) {
$field_instances = $this->get_fields();
$id_key = array_search( '{id}', $this->get_value_callback_args, true );
if ( false !== $id_key ) {
$values = array();
foreach ( $field_instances as $id => $field_instance ) {
$args = $this->get_value_callback_args;
$args[ $id_key ] = $id;
$values[ $id ] = call_user_func_array( $this->get_value_callback, $args );
}
$this->current_values = $values;
} else {
$this->current_values = call_user_func_array( $this->get_value_callback, $this->get_value_callback_args );
}
}
return $this->current_values;
}
/**
* Updates the current values for a list of fields.
*
* @since 1.0.0
*
* @param array $values New values to validate and store, as `$id => $new_value` pairs.
* @param string|array|null $sections Optional. Section identifier(s), to only update values for
* fields that belong to this section. Default null.
* @return bool|WP_Error True on success, or an error object if some fields produced validation errors.
* All fields that are not part of this error object have been updated successfully.
*/
public function update_values( $values, $sections = null ) {
$field_instances = $this->get_fields( $sections );
$field_instances = $this->resolve_dependency_order( $field_instances );
$validated_values = $this->get_values();
$errors = new WP_Error();
$value_key = array_search( '{value}', $this->update_value_callback_args, true );
$id_key = array_search( '{id}', $this->update_value_callback_args, true );
if ( false !== $id_key ) {
foreach ( $field_instances as $id => $field_instance ) {
$validated_value = $this->validate_value( $field_instance, $values, $errors );
if ( is_wp_error( $validated_value ) ) {
continue;
}
$this->current_values[ $id ] = $validated_value;
$args = $this->update_value_callback_args;
$args[ $id_key ] = $id;
$args[ $value_key ] = $validated_value;
$update_result = call_user_func_array( $this->update_value_callback, $args );
$this->process_update_result( $update_result, $errors );
}
} else {
foreach ( $field_instances as $id => $field_instance ) {
$validated_value = $this->validate_value( $field_instance, $values, $errors );
if ( is_wp_error( $validated_value ) ) {
continue;
}
$this->current_values[ $id ] = $validated_value;
$validated_values[ $id ] = $validated_value;
}
$args = $this->update_value_callback_args;
$args[ $value_key ] = $validated_values;
$update_result = call_user_func_array( $this->update_value_callback, $args );
$this->process_update_result( $update_result, $errors );
}
if ( ! empty( $errors->errors ) ) {
return $errors;
}
return true;
}
/**
* Returns an array of fields that are part of this field manager.
*
* @since 1.0.0
*
* @param string|array|null $sections Optional. Section identifier(s), to only return
* fields that belong to this section. Default null.
* @return array Array of fields as `$id => $instance` pairs.
*/
public function get_fields( $sections = null ) {
if ( null !== $sections ) {
$sections = (array) $sections;
} else {
$sections = array_keys( $this->field_instances );
}
$all_field_instances = array();
foreach ( $this->field_instances as $section => $field_instances ) {
if ( ! in_array( $section, $sections, true ) ) {
continue;
}
$all_field_instances = array_merge( $all_field_instances, $field_instances );
}
return $all_field_instances;
}
/**
* Creates the id attribute for a given field identifier.
*
* @since 1.0.0
*
* @param string $id Field identifier.
* @param int|string|null $index Optional. Index of the field, in case it is a repeatable field.
* Default null.
* @return string Field id attribute.
*/
public function make_id( $id, $index = null ) {
$field_id = str_replace( '_', '-', $id );
$instance_id = $this->get_instance_id();
if ( $instance_id ) {
$field_id = $instance_id . '_' . $field_id;
}
if ( null !== $index ) {
if ( '%index%' === $index ) {
$field_id .= '-%indexPlus1%';
} else {
$field_id .= '-' . ( $index + 1 );
}
}
return $field_id;
}
/**
* Creates the name attribute for a given field identifier.
*
* @since 1.0.0
*
* @param string $id Field identifier.
* @param int|string|null $index Optional. Index of the field, in case it is a repeatable field.
* Default null.
* @return string Field name attribute.
*/
public function make_name( $id, $index = null ) {
$name_prefix = $this->name_prefix;
$field_name = $id;
if ( ! empty( $this->name_prefix ) ) {
$field_name = $this->name_prefix . '[' . $field_name . ']';
}
if ( null !== $index ) {
$field_name .= '[' . $index . ']';
}
return $field_name;
}
/**
* Gets the HTML markup to indicate that a field is required.
*
* @since 1.0.0
*
* @return string HTML markup.
*/
public function get_field_required_markup() {
if ( empty( $this->field_required_markup ) ) {
return '<em>' . $this->get_message( 'field_required_indicator' ) . '</em>';
}
return $this->field_required_markup;
}
/**
* Returns the ID of this instance.
*
* @since 1.0.0
*
* @return string|null Instance ID.
*/
public function get_instance_id() {
return $this->instance_id;
}
/**
* Returns a specific manager message.
*
* @since 1.0.0
*
* @param string $identifier Identifier for the message.
* @param bool $noop Optional. Whether this is a noop message. Default false.
* @return string|array Translated message, or array if $noop, or empty string if
* invalid identifier.
*/
public function get_message( $identifier, $noop = false ) {
return self::$translations->get( $identifier, $noop );
}
/**
* Renders a field in form table mode.
*
* @since 1.0.0
*
* @param Field $field Field instance.
* @param mixed $value Current field value.
*/
protected function render_form_table_row( $field, $value ) {
?>
<tr<?php echo $field->get_wrap_attrs(); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>>
<th scope="row">
<?php $field->render_label(); ?>
</th>
<td>
<?php $field->render_content( $value ); ?>
</td>
</tr>
<?php
}
/**
* Renders a field in simple div markup with classes.
*
* @since 1.0.0
*
* @param Field $field Field instance.
* @param mixed $value Current field value.
*/
protected function render_divs_row( $field, $value ) {
?>
<div<?php echo $field->get_wrap_attrs(); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>>
<div class="plugin-lib-label-wrap">
<?php $field->render_label(); ?>
</div>
<div class="plugin-lib-control-wrap">
<?php $field->render_content( $value ); ?>
</div>
</div>
<?php
}
/**
* Validates a value.
*
* The $errors object passed will automatically receive any occurring errors.
*
* @since 1.0.0
*
* @param Field $field Field instance.
* @param array $values Array of all values to validate.
* @param WP_Error $errors Error object to possibly fill.
* @return mixed|WP_Error Validated value on success, error object on failure.
*/
protected function validate_value( $field, $values, $errors ) {
$value = isset( $values[ $field->id ] ) ? $values[ $field->id ] : null;
$validated_value = $field->validate( $value );
if ( is_wp_error( $validated_value ) ) {
$error = $validated_value;
$error_data = $error->get_error_data();
if ( isset( $error_data['validated'] ) ) {
$validated_value = $error_data['validated'];
}
$errors->add( $error->get_error_code(), $error->get_error_message(), $error->get_error_data() );
}
return $validated_value;
}
/**
* Processes a callback result, merging into an existing WP_Error as necessary.
*
* @since 1.0.2
*
* @param mixed $result Callback result. Is only treated if it is a WP_Error or a boolean.
* @param WP_Error $errors Error object to merge errors into.
*/
protected function process_update_result( $result, WP_Error $errors ) {
if ( is_wp_error( $result ) ) {
foreach ( $result->errors as $error_code => $error_messages ) {
foreach ( $error_messages as $error_message ) {
$errors->add( $error_code, $error_message );
}
}
return;
}
if ( is_bool( $result ) && ! $result ) {
$errors->add( 'values_cannot_update', $this->get_message( 'field_cannot_update' ) );
}
}
/**
* Sorts field instances by their dependencies so that those can be resolved in the correct order.
*
* @since 1.0.0
*
* @param array $field_instances Array of field instances.
* @return array Array of field instances sorted by their dependencies.
*/
protected function resolve_dependency_order( $field_instances ) {
$resolved = array();
foreach ( $field_instances as $id => $field_instance ) {
$resolved = $this->resolve_dependency_order_for_instance( $field_instance, $field_instances, $resolved, array() );
}
return $resolved;
}
/**
* Recursive helper method for sorting field instances by their dependencies.
*
* @since 1.0.0
*
* @param Field $field_instance Field instance to recursively add its dependencies and itself.
* @param array $all_instances All field instances in the collection to sort.
* @param array $resolved Results array to append to.
* @param array $queued_ids Array of field identifiers that are currently queued for appending.
* This allows to detect circular dependencies.
* @return array Modified results array.
*
* @throws Exception Thrown if circular dependency is detected.
*/
protected function resolve_dependency_order_for_instance( $field_instance, $all_instances, $resolved, $queued_ids ) {
if ( isset( $resolved[ $field_instance->id ] ) ) {
return $resolved;
}
$dependency_resolver = $field_instance->dependency_resolver;
if ( ! $dependency_resolver ) {
$resolved[ $field_instance->id ] = $field_instance;
return $resolved;
}
$dependency_ids = $dependency_resolver->get_dependency_field_identifiers();
if ( empty( $dependency_ids ) ) {
$resolved[ $field_instance->id ] = $field_instance;
return $resolved;
}
$queued_ids[] = $field_instance->id;
foreach ( $dependency_ids as $dependency_id ) {
if ( ! isset( $all_instances[ $dependency_id ] ) ) {
continue;
}
if ( in_array( $dependency_id, $queued_ids, true ) ) {
throw new Exception( sprintf( 'Circular dependency detected in plugin-lib between fields “%1$s” and “%2$s”!', $field_instance->id, $dependency_id ) );
}
$resolved = $this->resolve_dependency_order_for_instance( $all_instances[ $dependency_id ], $all_instances, $resolved, $queued_ids );
}
$resolved[ $field_instance->id ] = $field_instance;
return $resolved;
}
/**
* Registers a field type.
*
* @since 1.0.0
* @static
*
* @param string $type Identifier of the type.
* @param string $class_name Name of the field type class.
* @return bool True on success, false on failure.
*/
public static function register_field_type( $type, $class_name ) {
self::register_default_field_types();
if ( self::is_field_type_registered( $type ) ) {
return false;
}
// Do not allow registration of an existing class as a different type.
if ( in_array( $class_name, self::$field_types, true ) ) {
return false;
}
self::$field_types[ $type ] = $class_name;
return true;
}
/**
* Retrieves the class name for a registered field type.
*
* @since 1.0.0
* @static
*
* @param string $type Identifier of the type.
* @return string Class name, or empty string if the type is not registered.
*/
public static function get_registered_field_type( $type ) {
self::register_default_field_types();
if ( ! self::is_field_type_registered( $type ) ) {
return '';
}
return self::$field_types[ $type ];
}
/**
* Checks whether a field type is registered.
*
* @since 1.0.0
* @static
*
* @param string $type Identifier of the type.
* @return bool True if the type is registered, false otherwise.
*/
public static function is_field_type_registered( $type ) {
self::register_default_field_types();
return isset( self::$field_types[ $type ] );
}
/**
* Unregisters a field type.
*
* @since 1.0.0
* @static
*
* @param string $type Identifier of the type.
* @return bool True on success, false on failure.
*/
public static function unregister_field_type( $type ) {
self::register_default_field_types();
if ( ! self::is_field_type_registered( $type ) ) {
return false;
}
unset( self::$field_types[ $type ] );
return true;
}
/**
* Registers the default field types.
*
* @since 1.0.0
* @static
*/
protected static function register_default_field_types() {
if ( self::$defaults_registered ) {
return;
}
self::$defaults_registered = true;
$default_field_types = array(
'text' => Text::class,
'email' => Email::class,
'url' => URL::class,
'textarea' => Textarea::class,
'wysiwyg' => WYSIWYG::class,
'number' => Number::class,
'range' => Range::class,
'checkbox' => Checkbox::class,
'select' => Select::class,
'multiselect' => Multiselect::class,
'radio' => Radio::class,
'multibox' => Multibox::class,
'autocomplete' => Autocomplete::class,
'datetime' => Datetime::class,
'color' => Color::class,
'media' => Media::class,
'map' => Map::class,
'group' => Group::class,
);
foreach ( $default_field_types as $type => $class_name ) {
self::register_field_type( $type, $class_name );
}
}
/**
* Sets the translations instance.
*
* @since 1.0.0
*
* @param Translations_Field_Manager $translations Translations instance.
*/
public static function set_translations( $translations ) {
self::$translations = $translations;
}
/**
* Gets an option.
*
* Default callback used for the `$get_value_callback` argument.
*
* @since 1.0.0
* @static
*
* @param string $id Field identifier.
* @return mixed Current value, or null if not set.
*/
protected static function get_option( $id ) {
return get_option( $id, null );
}
/**
* Updates an option.
*
* Default callback used for the `$update_value_callback` argument.
*
* @since 1.0.0
* @static
*
* @param string $id Field identifier.
* @param mixed $value New value to set.
* @return bool True on success, false on failure.
*/
protected static function update_option( $id, $value ) {
return update_option( $id, $value );
}
/**
* Parses the get value callback.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return callable The parsed value.
*/
protected static function parse_arg_get_value_callback( $value ) {
if ( ! is_callable( $value ) ) {
return array( __CLASS__, 'get_option' );
}
return $value;
}
/**
* Parses the get value callback args.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return array The parsed value.
*/
protected static function parse_arg_get_value_callback_args( $value ) {
if ( ! is_array( $value ) ) {
return array( '{id}' );
}
return $value;
}
/**
* Parses the update value callback.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return callable The parsed value.
*/
protected static function parse_arg_update_value_callback( $value ) {
if ( ! is_callable( $value ) ) {
return array( __CLASS__, 'update_option' );
}
return $value;
}
/**
* Parses the update value callback args.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return array The parsed value.
*/
protected static function parse_arg_update_value_callback_args( $value ) {
if ( ! is_array( $value ) ) {
return array( '{id}', '{value}' );
}
/* A '{value}' element must always be present. This is the worst way to verify it. */
if ( ! in_array( '{value}', $value, true ) ) {
$value[] = '{value}';
}
return $value;
}
/**
* Parses the name prefix.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return string The parsed value.
*/
protected static function parse_arg_name_prefix( $value ) {
if ( ! is_string( $value ) ) {
return '';
}
return $value;
}
/**
* Parses the render mode.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return string The parsed value.
*/
protected static function parse_arg_render_mode( $value ) {
$valid_modes = array( 'form-table' );
if ( ! is_string( $value ) || ! in_array( $value, $valid_modes, true ) ) {
return 'form-table';
}
return $value;
}
/**
* Parses the HTML markup to indicate that a field is required.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return string The parsed value.
*/
protected static function parse_arg_field_required_markup( $value ) {
if ( ! $value ) {
return '';
}
return trim( (string) $value );
}
/**
* Parses whether JavaScript initialization should be skipped.
*
* @since 1.0.0
* @static
*
* @param mixed $value The input value.
* @return string The parsed value.
*/
protected static function parse_arg_skip_js_initialization( $value ) {
return (bool) $value;
}
}
endif;