type ) { case 'sortlist' : if ( ! empty( $this->label ) ) : ?> label ); ?> description ) ) : ?> description ); ?> sublabel ) ) : ?> sublabel ); ?> id ) ); $choices = $this->choices; $rawvalue = $this->value(); $valuearray = array(); // We do not use JSON.stringify( $optionsform.serializeArray() ) and PHP json_decode(). // This is because when using a dafult value, it is passed through get_theme_mod which creates a sprintf error ( since default string has a lot of % signs from http_build_query(): php equivalent of jquery.serialize() ). We dont face this issue if the value is stored, as sprintf is only applied on default in get_theme_mod. Also note, this error is only visible in customizer view. // Hence we use a mix. For normal stored values and js computation, we use jquery.serialize() and PHP parse_str(). // However, for defaults, we still use json_encode instead of http_build_query due to above reason // If revert to stringify + json_decode(): // Known bug: error in saving (") in textareas when using stringify + json_decode() // Make sure hidden input has value marked with single inverted (') to work with stringify which uses " in the string. if ( !empty( $rawvalue ) ) { // We either have a default, or value stored $value_json = json_decode( $rawvalue, true ); // We cant use parse_str because parse_str on json_encoded string still gives an array. However json_decode on jquery.serialize string returns null if ( !empty( $value_json ) && is_array( $value_json ) ) { // json_encoded default value $valuearray = $value_json; } else { parse_str($rawvalue, $valuearray); } } // Check if stored value / default value parsed properly to an array if ( !empty( $valuearray ) && is_array( $valuearray ) ) { $valuearray = $valuearray[ esc_attr( $id ) ]; // Check choices if any missing (useful if a child theme adds choices after this setting has been saved atleast once in database) foreach ( $choices as $choiceid => $choicelabel ) if ( !array_key_exists( $choiceid, $valuearray ) ) $valuearray[ $choiceid ]['sortitem_hide'] = ''; // Also check if a choice has been removed (by child theme) but its value still exists in stored value foreach ( $valuearray as $choiceid => $choicevalues ) if ( empty( $choices[ $choiceid ] ) ) unset( $valuearray[ $choiceid ] ); } else { // no value stored, no default=> create empty array foreach ( $choices as $choiceid => $choicelabel ) $valuearray[ $choiceid ] = $valuearray[ $choiceid ]['sortitem_hide'] = ''; } $display = ( isset( $this->attributes['display-label'] ) && $this->attributes['display-label'] === false ) ? false : true; // default: true $display = ( !empty( $this->options ) && is_array( $this->options ) ) ? $display : true; // always true if no options $hideable = ( !empty( $this->attributes['hideable'] ) ); // default: false $hideable = ( !$display ) ? false : $hideable; // always false if display-label false $open = ( isset( $this->attributes['open-state'] ) ) ? ( ( $this->attributes['open-state'] === true ) ? 'data-openstate="all"' : 'data-openstate="' . esc_attr( $this->attributes['open-state'] ) . '"' ) : ''; // default: false $open = ( !$display ) ? 'data-openstate="all"' : $open; // always true if display-label false $sortable = ( !empty( $this->attributes['sortable'] ) ); // default: false $sortable = ( !$display ) ? false : $sortable; // always false if display-label false ?> link(); ?> type="hidden"/> add_control( new Hoot_Customize_Sortlist_Control( $wp_customize, $id, $setting ) ); } endif; } add_action( 'hoot_customize_control_interface', 'hoot_customize_sortlist_control_interface', 10, 3 ); endif; /** * Modify the settings array and prepare sortlist settings * * @since 3.0.0 * @param array $value * @param string $key * @param array $setting * @param int $count * @return void */ function hoot_customize_prepare_sortlist_settings( $value, $key, $setting, $count ) { if ( $setting['type'] == 'sortlist' ) { $newdefault = array(); foreach ( $setting['choices'] as $choiceid => $choicelabel ) { if ( isset( $setting['default'][$choiceid] ) ) { $newdefault[$choiceid] = array_merge( array( 'sortitem_hide' => '' ), $setting['default'][$choiceid] ); } else { $newdefault[$choiceid]['sortitem_hide'] = ''; } } if ( !empty( $newdefault ) ) $setting['default'] = json_encode( array( $key => $newdefault ) ); elseif ( isset( $setting['default'] ) ) unset( $setting['default'] ); $value[ $key ] = $setting; } return $value; } add_filter( 'hoot_customize_prepare_settings', 'hoot_customize_prepare_sortlist_settings', 10, 4 ); /** * Add sanitization function * * @since 3.0.0 * @param string $callback * @param string $type * @param array $setting * @param string $name name (id) of the setting * @return string */ function hoot_customize_sanitize_sortlist_callback( $callback, $type, $setting, $name ) { if ( $type == 'sortlist' || $type == 'repeatable' ) $callback = 'hoot_sanitize_customize_sortlist'; return $callback; } add_filter( 'hoot_customize_sanitize_callback', 'hoot_customize_sanitize_sortlist_callback', 5, 4 ); /** * Sanitize sortlist value. * * @since 3.0.0 * @param string $value The value to sanitize. * @param mixed $setting 'WP_Customize_Setting' Object (called by Customizer) or Setting Name (called by hoot_get_mod) * @return string The sanitized value. */ function hoot_sanitize_customize_sortlist( $value, $setting ) { parse_str( $value, $valuearray ); if ( !empty( $valuearray ) && is_array( $valuearray ) ) { $return = array(); $id = array_keys( $valuearray ); $id = current( $id ); // use $id instead of reset( $id ) : reset returns false if empty, only 1 element, so we good to go // Get Choice Options if exist $name = ''; if ( is_object( $setting ) ) $name = $setting->id; elseif ( is_string( $setting ) ) $name = $setting; $hoot_customize = Hoot_Customize::get_instance(); $settings = $hoot_customize->get_options('settings'); $choices = ( isset( $settings[$name]['choices'] ) ) ? $settings[$name]['choices'] : array(); $options = ( isset( $settings[$name]['options'] ) ) ? $settings[$name]['options'] : array(); // Build return array foreach ( $valuearray[ $id ] as $choiceid => $choicevalues ) : // Sanitize choice name if ( !empty( $choices[$choiceid] ) ): // Sanitize choice values: hidden $return[$id][$choiceid]['sortitem_hide'] = ( isset( $choicevalues['sortitem_hide'] ) ) ? $choicevalues['sortitem_hide'] : 0; // Sanitize choice values: options if ( !empty( $options[$choiceid] ) ){ foreach ( $options[$choiceid] as $optionid => $optionarray ) { if ( !empty( $optionarray['type'] ) ) { switch( $optionarray['type'] ): // @todo : use sanitization.php functions case 'text': case 'textarea': global $allowedtags; $return[$id][$choiceid][$optionid] = ( isset( $choicevalues[$optionid] ) ) ? wp_kses( $choicevalues[$optionid] , $allowedtags ) : ''; break; case 'checkbox': $return[$id][$choiceid][$optionid] = ( !empty( $choicevalues[$optionid] ) ) ? 1 : 0; break; case 'select': case 'radio': case 'radioimage': $return[$id][$choiceid][$optionid] = ( !array_key_exists( $choicevalues[$optionid], $optionarray['choices'] ) ) ? $choicevalues[$optionid] : $choicevalues[$optionid]; break; endswitch; } } } endif; endforeach; // Same as jquery.serialize ( though it also converts entities. example: & to & ) // If using JSON.stringify, use json_encode instead return http_build_query( $return ); } else { return ''; } } /** * Utility function to map a sortlist option value to array * * @since 3.0.0 * @access public * @param array $value jquery.serialize() string * @param bool $returnid return id instead of the value array * @return array|false */ function hoot_sortlist( $rawvalue, $returnid = false ) { $valuearray = array(); if ( !empty( $rawvalue ) ) { // We either have a default, or value stored $value_json = json_decode( $rawvalue, true ); // We cant use parse_str because parse_str on json_encoded string still gives an array. However json_decode on jquery.serialize string returns null if ( !empty( $value_json ) && is_array( $value_json ) ) { // json_encoded default value $valuearray = $value_json; } else { parse_str($rawvalue, $valuearray); } } if ( !empty( $valuearray ) && is_array( $valuearray ) ) { $return = array(); $id = array_keys( $valuearray ); $id = current( $id ); // use $id instead of reset( $id ) : reset returns false if empty, only 1 element, so we good to go if ( isset( $valuearray[ $id ] ) ) { if ( !$returnid ) return $valuearray[ $id ]; else return $id; } else { return false; } } else { return false; } }