inc/WPDLib/FieldTypes/Radio.php
<?php
/**
* WPDLib\FieldTypes\Radio class
*
* @package WPDLib
* @subpackage FieldTypes
* @author Felix Arntz <felix-arntz@leaves-and-love.net>
* @since 0.5.0
*/
namespace WPDLib\FieldTypes;
use WPDLib\FieldTypes\Manager as FieldManager;
use WPDLib\Util\Util;
use WP_Error as WPError;
if ( ! defined( 'ABSPATH' ) ) {
die();
}
if ( ! class_exists( 'WPDLib\FieldTypes\Radio' ) ) {
/**
* Class for a radio group field.
*
* The class is also used as base class for select fields, multiselect fields and multibox fields.
*
* @since 0.5.0
*/
class Radio extends Base {
/**
* Class constructor.
*
* For an overview of the supported arguments, please read the Field Types Reference.
*
* @since 0.5.0
* @param string $type the field type
* @param array $args array of field type arguments
*/
public function __construct( $type, $args ) {
$args = wp_parse_args( $args, array(
'options' => array(),
) );
parent::__construct( $type, $args );
if ( is_array( $this->args['options'] ) && 1 === count( $this->args['options'] ) ) {
if ( isset( $this->args['options']['posts'] ) || isset( $this->args['options']['terms'] ) || isset( $this->args['options']['users'] ) ) {
add_action( 'wp_loaded', array( $this, 'parse_options' ) );
}
}
}
/**
* Displays the input control for the field.
*
* @since 0.5.0
* @param string|array $val the current value of the field
* @param bool $echo whether to echo the output (default is true)
* @return string the HTML output of the field control
*/
public function display( $val, $echo = true ) {
$args = array(
'id' => $this->args['id'],
'class' => $this->args['class'],
);
$name = $this->get_sanitized_name();
$single_type = 'radio';
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
$single_type = 'checkbox';
}
$output = '<div' . FieldManager::make_html_attributes( $args, false, false ) . '>';
foreach ( $this->args['options'] as $value => $label ) {
$output .= $this->display_item( $value, $label, $single_type, $args['id'], $name, $val, false );
}
$output .= '</div>';
if ( $echo ) {
echo $output;
}
return $output;
}
/**
* Validates a value for the field.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @return string|array|WP_Error the validated field value or an error object
*/
public function validate( $val = null ) {
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
return $this->validate_multi( $val );
}
return $this->validate_single( $val );
}
/**
* Checks whether a value for the field is considered empty.
*
* This function is needed to check whether a required field has been properly filled.
*
* @since 0.5.0
* @param string|array $val the current value of the field
* @return bool whether the value is considered empty
*/
public function is_empty( $val ) {
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
return count( (array) $val ) < 1;
}
return empty( $val );
}
/**
* Parses a value for the field.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @param bool|array $formatted whether to also format the value (default is false)
* @return string|array the correctly parsed value
*/
public function parse( $val, $formatted = false ) {
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
return $this->parse_multi( $val, $formatted );
}
return $this->parse_single( $val, $formatted );
}
/**
* Displays a single item in the group.
*
* @since 0.5.0
* @param string $value the value of the item
* @param string|array $label the label of the item
* @param string $id the overall field's ID attribute
* @param string $name the overall field's name attribute
* @param string|array $current the current value of the field
* @param bool $echo whether to echo the output (default is true)
* @return string the HTML output of the item
*/
protected function display_item( $value, $label, $single_type, $id, $name, $current = '', $echo = true ) {
$option_atts = array(
'id' => $id . '-' . $value,
'name' => $name,
'value' => $value,
'checked' => $this->is_value_checked_or_selected( $current, $value ),
'readonly' => $this->args['readonly'],
'disabled' => $this->args['disabled'],
);
$option_atts = array_merge( $option_atts, $this->data_atts );
$additional_output = $additional_class = '';
if ( is_array( $label ) ) {
list( $additional_output, $additional_class, $label ) = $this->maybe_generate_additional_item_output( $option_atts['id'], $label, $option_atts['checked'] );
}
$output = '<div class="wpdlib-' . $single_type . $additional_class . '">';
$output .= '<input type="' . $single_type . '"' . FieldManager::make_html_attributes( $option_atts, false, false ) . ' />';
$output .= $additional_output;
if ( ! empty( $label ) ) {
$output .= ' <label for="' . $option_atts['id'] . '">' . esc_html( $label ) . '</label>';
}
$output .= '</div>';
if ( $echo ) {
echo $output;
}
return $output;
}
/**
* Generates output for a label in array format.
*
* Instead of just a default text label, you can also use an image or a color as label.
* To do that the label must be provided as an array which can have the following keys:
* - image (the URL to an image to use for the label)
* - color (a hex color string to use for the label)
* - label (the text to display)
*
* @since 0.5.0
* @param string|array $val the current value of the field
* @param bool $echo whether to echo the output (default is true)
* @return string the HTML output of the field control
*/
protected function maybe_generate_additional_item_output( $id, $data, $checked = false ) {
$output = $class = $label = '';
if ( isset( $data['image'] ) || isset( $data['color'] ) ) {
$output = '<div id="' . $id . '-asset"';
if ( $checked ) {
$output .= ' class="checked"';
}
if ( isset( $data['image'] ) ) {
$output .= ' style="background-image:url(\'' . esc_url( $data['image'] ) . '\');"';
} else {
$output .= ' style="background-color:#' . ltrim( $data['color'], '#' ) . ';"';
}
$output .= '></div>';
$class .= ' box';
}
if ( isset( $data['label'] ) ) {
$label = $data['label'];
}
return array( $output, $class, $label );
}
/**
* Validates a value for the field.
*
* This function is used for radio groups / selects.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @return string|WP_Error the validated field value or an error object
*/
protected function validate_single( $val = null ) {
if ( ! $val ) {
if ( count( $this->args['options'] ) > 0 ) {
return key( $this->args['options'] );
}
return '';
}
if ( ! isset( $this->args['options'][ $val ] ) ) {
$key = array_search( $val, $this->args['options'] );
if ( '' !== $key ) {
return new WPError( 'invalid_' . $this->type . '_option', sprintf( __( '%s is not a valid option.', 'wpdlib' ), FieldManager::format( $val, 'string', 'output' ) ) );
} else {
$val = $key;
}
}
return $val;
}
/**
* Validates a value for the field.
*
* This function is used for multiboxes (checkbox groups) / multiselects.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @return array|WP_Error the validated field value or an error object
*/
protected function validate_multi( $val = null ) {
if ( ! $val ) {
return array();
}
if ( ! is_array( $val ) ) {
if ( $val ) {
$val = array( $val );
} else {
$val = array();
}
}
for ( $i = 0; $i < count( $val ); $i++ ) {
if ( ! isset( $this->args['options'][ $val[ $i ] ] ) ) {
$key = array_search( $val[ $i ], $this->args['options'] );
if ( '' !== $key ) {
return new WPError( 'invalid_' . $this->type . '_option', sprintf( __( '%s is not a valid option.', 'wpdlib' ), FieldManager::format( $val[ $i ], 'string', 'output' ) ) );
} else {
$val[ $i ] = $key;
}
}
}
return $val;
}
/**
* Parses a value for the field.
*
* This function is used for radio groups / selects.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @param bool|array $formatted whether to also format the value (default is false)
* @return string the correctly parsed value
*/
protected function parse_single( $val, $formatted = false ) {
if ( $formatted ) {
if ( ! is_array( $formatted ) ) {
$formatted = array();
}
$formatted = wp_parse_args( $formatted, array(
'mode' => 'text',
) );
return $this->format_item( $val, $formatted );
}
return FieldManager::format( $val, 'string', 'input' );
}
/**
* Parses a value for the field.
*
* This function is used for multiboxes (checkbox groups) / multiselects.
*
* @since 0.5.0
* @param mixed $val the current value of the field
* @param bool|array $formatted whether to also format the value (default is false)
* @return string the correctly parsed value
*/
protected function parse_multi( $val, $formatted = false ) {
$parsed = array();
if ( $formatted ) {
if ( ! is_array( $formatted ) ) {
$formatted = array();
}
$formatted = wp_parse_args( $formatted, array(
'mode' => 'text',
'list' => false,
) );
$list_separator = ( 'html' == $formatted['mode'] ) ? ' ' : ', ';
foreach ( (array) $val as $_val) {
$_item = $this->format_item( $_val, $formatted );
if ( false === strpos( $_item, '<img' ) && false === strpos( $_item, '<div' ) ) {
$list_separator = ', ';
}
$parsed[] = $_item;
}
if ( $formatted['list'] ) {
return implode( $list_separator, $parsed );
}
} else {
foreach ( (array) $val as $_val) {
$parsed[] = FieldManager::format( $_val, 'string', 'input' );
}
}
return $parsed;
}
/**
* Formats a single item for output.
*
* The argument passed as $val is the value of a specific field option.
*
* The 'mode' key in the $args array specifies in which format it should be returned:
* - html (if option has an image, it will be printed inside an IMG tag; if it has a color, it will be printed as a div with that color as background)
* - text (if option has an image, it will be printed as the plain URL; if it has a color, it will be printed as a hex color string)
*
* @since 0.5.0
* @param string|array $val the field option value to format
* @param array $args arguments on how to format
* @return string the correctly parsed value
*/
protected function format_item( $val, $args = array() ) {
$skip_formatting = false;
if ( isset( $this->args['options'][ $val ] ) ) {
if ( is_array( $this->args['options'][ $val ] ) ) {
if ( isset( $this->args['options'][ $val ]['label'] ) && ! empty( $this->args['options'][ $val ]['label'] ) ) {
$val = $this->args['options'][ $val ]['label'];
} elseif ( isset( $this->args['options'][ $val ]['image'] ) ) {
if ( 'html' == $args['mode'] ) {
$val = '<img src="' . esc_url( $this->args['options'][ $val ]['image'] ) . '" style="display: inline-block;width:64px;height:auto;">';
$skip_formatting = true;
} else {
$val = esc_url( $this->args['options'][ $val ]['image'] );
}
} elseif ( isset( $this->args['options'][ $val ]['color'] ) ) {
if ( 'html' == $args['mode'] ) {
$val = '<div style="display:inline-block;width:64px;height:48px;background-color:' . $this->args['options'][ $val ]['color'] . ';"></div>';
$skip_formatting = true;
} else {
$val = $this->args['options'][ $val ]['color'];
}
}
} else {
$val = $this->args['options'][ $val ];
}
}
if ( $skip_formatting ) {
return $val;
}
return FieldManager::format( $val, 'string', 'output' );
}
/**
* Returns whether a specific choice should be checked / selected.
*
* @since 0.5.0
* @param string|array $val the field option value
* @param string $choice the current choice to determine the checked / selected status for
* @return bool whether the choice should be marked as checked / selected
*/
protected function is_value_checked_or_selected( $val, $choice ) {
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
if ( ! is_array( $val ) ) {
$val = array( $val );
}
return in_array( $choice, $val );
}
return $val == $choice;
}
/**
* Returns the sanitized name attribute for the field.
*
* This function is used to append parentheses to the name if the field is a multibox or multiselect.
*
* @since 0.5.0
* @return string the sanitized field name attribute
*/
protected function get_sanitized_name() {
$name = $this->args['name'];
if ( isset( $this->args['multiple'] ) && $this->args['multiple'] ) {
if ( '[]' != substr( $name, -2 ) ) {
$name .= '[]';
}
}
return $name;
}
/**
* Transforms options of a specific type into actual options.
*
* Instead of specifying an array of options in the `$value => $label` format,
* you may also specify an array that only contains one of the following key value pairs:
* - `'posts' => $post_type` (will be transformed into a dropdown of posts of the post type $post_type)
* - `'terms' => $taxonomy` (will be transformed into a dropdown of terms of the taxonomy $taxonomy)
* - `'users' => $user_role (will be transformed into a dropdown of users of the role $user_role)
*
* Note that instead of providing a value of the above, you could also simply specify a boolean true
* to get a dropdown of ALL posts / terms / users respectively.
*
* @since 0.5.0
*/
public function parse_options() {
if ( isset( $this->args['options']['posts'] ) ) {
$this->args['options'] = Util::get_posts_options( $this->args['options']['posts'] );
} elseif ( isset( $this->args['options']['terms'] ) ) {
$this->args['options'] = Util::get_terms_options( $this->args['options']['terms'] );
} elseif ( isset( $this->args['options']['users'] ) ) {
$this->args['options'] = Util::get_users_options( $this->args['options']['users'] );
}
}
}
}