felixarntz/options-definitely

View on GitHub
inc/WPOD/Components/Field.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
/**
 * WPOD\Components\Field class
 *
 * @package WPOD
 * @subpackage Components
 * @author Felix Arntz <felix-arntz@leaves-and-love.net>
 * @since 0.5.0
 */

namespace WPOD\Components;

use WPOD\Utility as Utility;
use WPDLib\Components\Manager as ComponentManager;
use WPDLib\Components\Base as Base;
use WPDLib\FieldTypes\Manager as FieldManager;
use WPDLib\Util\Error as UtilError;
use WP_Error as WPError;

if ( ! defined( 'ABSPATH' ) ) {
    die();
}

if ( ! class_exists( 'WPOD\Components\Field' ) ) {
    /**
     * Class for a field component.
     *
     * A field denotes a settings field, i.e. both the field option and the visual input in the WordPress admin.
     * Since WPOD stores all options inside an array (where the option name is the tab slug), the field slugs are used as array keys in that options array.
     *
     * @internal
     * @since 0.5.0
     */
    class Field extends Base {

        /**
         * @since 0.5.0
         * @var WPDLib\FieldTypes\Base Holds the field type object from WPDLib.
         */
        protected $_field = null;

        /**
         * Class constructor.
         *
         * @since 0.5.0
         * @param string $slug the field slug
         * @param array $args array of field properties
         */
        public function __construct( $slug, $args ) {
            parent::__construct( $slug, $args );
            $this->validate_filter = 'wpod_field_validated';
        }

        /**
         * Magic get method.
         *
         * This function exists to allow direct access to properties that are stored on the internal WPDLib\FieldTypes\Base object of the field.
         *
         * @since 0.5.0
         * @param string $property name of the property to find
         * @return mixed value of the property or null if it does not exist
         */
        public function __get( $property ) {
            $value = parent::__get( $property );
            if ( null === $value ) {
                $value = $this->_field->$property;
            }
            return $value;
        }

        /**
         * Registers the settings fields.
         *
         * @since 0.5.0
         * @param WPOD\Components\Tab $parent_tab the parent tab component of this field
         * @param WPOD\Components\Section $parent_section the parent section component of this field
         */
        public function register( $parent_tab = null, $parent_section = null ) {
            if ( null === $parent_section ) {
                $parent_section = $this->get_parent();
            }
            if ( null === $parent_tab ) {
                $parent_tab = $parent_section->get_parent();
            }

            add_settings_field( $this->slug, $this->args['title'], array( $this, 'render' ), $parent_tab->slug, $parent_section->slug );
        }

        /**
         * Renders the field.
         *
         * This function will show the input field(s) in the WordPress admin.
         *
         * @since 0.5.0
         */
        public function render() {
            $parent_section = $this->get_parent();
            $parent_tab = $parent_section->get_parent();

            /**
             * This action can be used to display additional content on top of this field.
             *
             * @since 0.5.0
             * @param string the slug of the current field
             * @param array the arguments array for the current field
             * @param string the slug of the current section
             * @param string the slug of the current tab
             */
            do_action( 'wpod_field_before', $this->slug, $this->args, $parent_section->slug, $parent_tab->slug );

            $option = wpod_get_option( $parent_tab->slug, $this->slug );

            $this->_field->display( $option );

            if ( ! empty( $this->args['description'] ) ) {
                echo '<br/><span class="description">' . $this->args['description'] . '</span>';
            }

            /**
             * This action can be used to display additional content at the bottom of this field.
             *
             * @since 0.5.0
             * @param string the slug of the current field
             * @param array the arguments array for the current field
             * @param string the slug of the current section
             * @param string the slug of the current tab
             */
            do_action( 'wpod_field_after', $this->slug, $this->args, $parent_section->slug, $parent_tab->slug );
        }

        /**
         * Validates the option for this field.
         *
         * @see WPOD\Components\Tab::validate_options()
         * @since 0.5.0
         * @param mixed $option the new option value to validate
         * @return mixed either the validated option or a WP_Error object
         */
        public function validate_option( $option = null, $skip_required = false ) {
            if ( $this->args['required'] && ! $skip_required ) {
                if ( $option === null || $this->_field->is_empty( $option ) ) {
                    return new WPError( 'invalid_empty_value', __( 'No value was provided for the required field.', 'options-definitely' ) );
                }
            }
            return $this->_field->validate( $option );
        }

        /**
         * Validates the arguments array.
         *
         * @since 0.5.0
         * @param WPOD\Components\Section $parent the parent component
         * @return bool|WPDLib\Util\Error an error object if an error occurred during validation, true if it was validated, false if it did not need to be validated
         */
        public function validate( $parent = null ) {
            $status = parent::validate( $parent );

            if ( $status === true ) {
                if ( is_array( $this->args['class'] ) ) {
                    $this->args['class'] = implode( ' ', $this->args['class'] );
                }

                if ( isset( $this->args['options'] ) && ! is_array( $this->args['options'] ) ) {
                    $this->args['options'] = array();
                }

                $parent_section = $this->get_parent();
                $parent_tab = $parent_section->get_parent();

                $this->args['id'] = $parent_tab->slug . '-' . $this->slug;
                $this->args['name'] = $parent_tab->slug . '[' . $this->slug . ']';

                $this->_field = FieldManager::get_instance( $this->args );
                if ( $this->_field === null ) {
                    return new UtilError( 'no_valid_field_type', sprintf( __( 'The field type %1$s assigned to the field component %2$s is not a valid field type.', 'options-definitely' ), $this->args['type'], $this->slug ), '', ComponentManager::get_scope() );
                }
                if ( null === $this->args['default'] ) {
                    $this->args['default'] = $this->_field->validate();
                }

                $this->args = Utility::validate_position_args( $this->args );
            }

            return $status;
        }

        /**
         * Returns the keys of the arguments array and their default values.
         *
         * Read the plugin guide for more information about the field arguments.
         *
         * @since 0.5.0
         * @return array
         */
        protected function get_defaults() {
            $defaults = array(
                'title'       => __( 'Field title', 'options-definitely' ),
                'description' => '',
                'type'        => 'text',
                'class'       => '',
                'default'     => null,
                'required'    => false,
                'position'    => null,
            );

            /**
             * This filter can be used by the developer to modify the default values for each field component.
             *
             * @since 0.5.0
             * @param array the associative array of default values
             */
            return apply_filters( 'wpod_field_defaults', $defaults );
        }

        /**
         * Returns whether this component supports multiple parents.
         *
         * @since 0.5.0
         * @return bool
         */
        protected function supports_multiparents() {
            return false;
        }

        /**
         * Returns whether this component supports global slugs.
         *
         * If it does not support global slugs, the function either returns false for the slug to be globally unique
         * or the class name of a parent component to ensure the slug is unique within that parent's scope.
         *
         * @since 0.5.0
         * @return bool|string
         */
        protected function supports_globalslug() {
            return 'WPOD\Components\Tab';
        }
    }
}