* @copyright 2013-2022 (c) Michael Uno * @license MIT * @package AdminPageFramework */ if ( ! class_exists( 'Select2CustomFieldType' ) ) : /** * A filed of the `select2` field type lets the user select items from a predefined list by typing the item name and the items possibly be fetched with AJAX. * * This class defines the `select2` field type. * *

Field Definition Arguments

*

Field Type Specific Arguments

* * * @since 3.8.7 * @version 0.2.0 * @supports IE8 or above. (uses JSON object) * @requires Admin Page Framework 3.8.20 */ class Select2CustomFieldType extends AdminPageFramework_FieldType_select { /** * Defines the field type slugs used for this field type. */ public $aFieldTypeSlugs = array( 'select2', ); /** * Defines the default key-values of this field type settings. * * @remark $_aDefaultKeys holds shared default key-values defined in the base class. */ protected $aDefaultKeys = array( 'label' => array(), 'icon' => array(), 'is_multiple' => false, 'attributes' => array( 'select' => array( 'size' => 1, 'autofocusNew' => null, 'multiple' => null, // set 'multiple' for multiple selections. If 'is_multiple' is set, it takes the precedence. 'required' => null, ), 'optgroup' => array(), 'option' => array(), ), /** * @see https://select2.github.io/options.html */ 'options' => array( 'width' => 'auto', // 'maximum-selection-length' => 2, ), // If a callback is set, the select list will be generated dynamically with Ajax. // The callback function must return an array of select list. 'callback' => array( 'search' => null, 'new_tag' => null, ), // [0.2.0+] 'selector' => array(), 'is_global' => false, // when the `selector` argument has a value, this indicates whether the element is within the residing section or not. If true, it searches the entire document. Otherwise, within the section. ); protected function construct() {} /** * Loads the field type necessary components. */ public function setUp() {} /** * Returns an array holding the urls of enqueuing scripts. * @return array */ protected function getEnqueuingScripts() { $_bDebugMode = $this->isDebugMode(); return array( array( 'handle_id' => 'select2', 'src' => $_bDebugMode ? dirname( __FILE__ ) . '/select2/js/select2.full.js' : dirname( __FILE__ ) . '/select2/js/select2.full.min.js', 'in_footer' => true, 'dependencies' => array( 'jquery' ), ), array( 'handle_id' => 'AdminPageFrameworkSelect2FieldType', 'src' => dirname( __FILE__ ) . '/js/loader.js', 'in_footer' => true, 'dependencies' => array( 'jquery', 'select2' ), 'translation' => array( 'debugMode' => $_bDebugMode, 'ajaxURL' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( get_class( $this ) ), ), ), ); } /** * @return array */ protected function getEnqueuingStyles() { return array( $this->isDebugMode() ? dirname( __FILE__ ) . '/select2/css/select2.css' : dirname( __FILE__ ) . '/select2/css/select2.min.css', dirname( __FILE__ ) . '/css/style.css', ); } /** * Returns the output of the field type. * * @return string */ public function getField( $aField ) { $_sInputForEncodedValue = ''; if ( is_callable( $this->getElement( $aField, array( 'callback', 'search' ) ) ) ) { $_sInputForEncodedValue = $this->_getChildInputByKey( 'encoded', $aField ); $aField[ 'attributes' ] = $this->_getAttributesUpdatedForAJAX( $aField ); } $_aOptions = $this->_getSelect2OptionsFormatted( $aField[ 'options' ], $aField ); $_bHasIcons = ! empty( $aField[ 'icon' ] ); $aField[ 'attributes' ][ 'select' ] = array( 'data-has-icons' => $_bHasIcons, 'data-type' => 'select2', 'data-field_id' => $aField[ 'field_id' ], // checked in the background with the `doOnFieldRegistration()` method using AJAX. 'data-section_id' => $aField[ 'section_id' ], // checked in the background with the `doOnFieldRegistration()` method using AJAX. ) + $this->getDataAttributeArray( $_aOptions ) + $this->getElementAsArray( $aField, array( 'attributes', 'select', ) ); // [0.2.0+] $_aSelectors = $this->getAsArray( $aField[ 'selector' ] ); if ( ! empty( $_aSelectors ) ) { $_sSelectors = implode( ',', $_aSelectors ); $aField[ 'attributes' ][ 'select' ] = array( // Embed all the selectors for the field so that the other members can be referred when showing an item. // This is especially needed for repeatable sections. 'data-selectors' => $_sSelectors, 'data-global' => ( integer ) $aField[ 'is_global' ], ) + $this->getElementAsArray( $aField, array( 'attributes', 'select' ) ); foreach( $this->getAsArray( $aField[ 'label' ] ) as $_sKey => $_sLabel ) { // If the user sets the 'selectors' argument, its value will be used; otherwise, the label key will be used. $_sSelector = $this->getElement( $_aSelectors, array( $_sKey ), $_sKey ); $aField[ 'attributes' ][ 'option' ][ $_sKey ] = array( 'data-toggle' => $_sSelector, ) + $this->getElementAsArray( $aField, array( 'attributes', 'option', $_sKey ) ); } } $_sIconDefinitions = $this->___getIconDefinitions( $this->getAsArray( $aField[ 'icon' ] ) ); return parent::getField( $aField ) // the select field . $_sInputForEncodedValue // a nested input that stores an encoded selection value. . $_sIconDefinitions // icons ; } /** * @param array $aIcons * * @return string * @since 0.1.0 */ private function ___getIconDefinitions( array $aIcons ) { $_sOutput = ''; foreach( $aIcons as $_sSelectOptionValue => $_sIconHTML ) { $_sOutput .= "" . $_sIconHTML . ""; } return $_sOutput ? "
" . $_sOutput . "
" : ''; } /** * @return string */ private function _getChildInputByKey( $sKey, $aField ) { $_aAttributes = array( 'name' => $aField[ 'attributes' ][ 'name' ] . '[' . $sKey . ']', 'id' => $aField[ 'attributes' ][ 'id' ] . '_' . $sKey, 'data-' . $sKey => true, 'type' => 'hidden', 'value' => ( string ) $this->getElement( $aField, array( 'value', $sKey ), '' ), 'style' => 'width: 100%', // for debugging ); return "getAttributes( $_aAttributes ) . " />"; } /** * For AJAX enabled fields, the stored field data structure becomes different. * * Nested elements of `encoded` and `value` will be added. The selection IDs will be stored in the `value`. * The `encoded` element will store the text and id of the user's selection. * * @return array */ private function _getAttributesUpdatedForAJAX( $aField ) { $_aAttributes = $aField[ 'attributes' ]; $_aAttributes[ 'name' ] = $_aAttributes[ 'name' ] . "[value]"; $_aAttributes[ 'id' ] = $_aAttributes[ 'id' ] . "_value"; return $_aAttributes; } /** * @return array */ private function _getSelect2OptionsFormatted( $aOptions, $aField ) { // Format camel-cased key names. foreach( $aOptions as $_sKey => $_mValue ) { if( ! preg_match( '/([a-zA-Z])(?=[A-Z])/', $_sKey ) ) { continue; } $_sDashed = $this->_getCamelCaseToDashed( $_sKey ); $aOptions[ $_sDashed ] = $_mValue; unset( $aOptions[ $_sKey ] ); } $aOptions[ 'search_callback' ] = is_callable( $this->getElement( $aField, array( 'callback', 'search' ) ) ); $_bNewTagCallbackCallable = is_callable( $this->getElement( $aField, array( 'callback', 'new_tag' ) ) ); $aOptions[ 'new_tag_callback' ] = $_bNewTagCallbackCallable; if ( $_bNewTagCallbackCallable ) { $aOptions[ 'tags' ] = true; } return $aOptions; } /** * @return string */ private function _getCamelCaseToDashed( $sString ) { return strtolower( preg_replace( '/([a-zA-Z])(?=[A-Z])/', '$1-', $sString ) ); } /** * Calls back the callback function if it is set. * * Called when the field type is registered. */ protected function doOnFieldRegistration( $aFieldset ) { $_aQueries = $_REQUEST; if ( ! $this->___shouldProceedToAjaxRequest( $_aQueries, $aFieldset ) ) { return; } unset( $_aQueries[ 'doing_select2_ajax' ] ); $_asCallable = $this->___getAjaxCallback( $_aQueries, $aFieldset ); if ( ! is_callable( $_asCallable ) ) { return; } // Will exit in the function. wp_send_json( call_user_func_array( $_asCallable, // callable array( $this->getArrayMappedRecursive( 'sanitize_text_field', $_aQueries ), // param 1 $aFieldset // param 2 ) ) ); } /** * @return boolean|callable False when a callback is not found. Otherwise, the found callable. */ private function ___getAjaxCallback( $aRequest, $aFieldset ) { if ( isset( $aRequest[ 'q' ] ) ) { return $this->getElement( $aFieldset, array( 'callback', 'search' ), false ); } if ( isset( $aRequest[ 'tag' ] ) ) { return $this->getElement( $aFieldset, array( 'callback', 'new_tag' ), false ); } return false; } /** * @return boolean */ private function ___shouldProceedToAjaxRequest( $aRequest, $aFieldset ) { if ( ! isset( $aRequest[ 'doing_select2_ajax' ], $aRequest[ 'field_id' ], $aRequest[ 'section_id' ] ) ) { return false; } if ( ! wp_verify_nonce( $aRequest[ 'doing_select2_ajax' ], get_class( $this ) ) ) { return false; } if ( $aFieldset[ 'field_id' ] !== $aRequest[ 'field_id' ] ) { return false; } // @deprecated 0.0.4 Bug fix - this is for nested repeated fields to process properly // in meta boxes, if the section id is '_default', fieldset may not have that key // if ( $aFieldset[ 'section_id' ] !== $aRequest[ 'section_id' ] ) { // return false; // } return true; } } endif;