<?php
/**
 * Genesis Framework.
 *
 * WARNING: This file is part of the core Genesis Framework. DO NOT edit this file under any circumstances.
 * Please do all modifications in the form of a child theme.
 *
 * @package Genesis\Sanitizer
 * @author  StudioPress
 * @license GPL-2.0-or-later
 * @link    https://my.studiopress.com/themes/genesis/
 */

/**
 * Settings sanitization class. Ensures saved values are of the expected type.
 *
 * @since 1.7.0
 *
 * @package Genesis\Sanitizer
 */
class Genesis_Settings_Sanitizer {

	/**
	 * Hold instance of self so methods can be accessed statically.
	 *
	 * @since 1.7.0
	 *
	 * @var Genesis_Settings_Sanitizer
	 */
	public static $instance;

	/**
	 * The sanitizer object.
	 *
	 * @since 2.6.1
	 *
	 * @var Genesis_Sanitizer Sanitizer object.
	 */
	public $sanitizer;

	/**
	 * Holds list of all options as array.
	 *
	 * @since 1.7.0
	 *
	 * @var array Options
	 */
	public $options = [];

	/**
	 * Constructor.
	 *
	 * @since 1.7.0
	 *
	 * @param Genesis_Sanitizer $sanitizer Sanitizer object.
	 */
	public function __construct( Genesis_Sanitizer $sanitizer ) {

		self::$instance =& $this;

		$this->sanitizer = $sanitizer;

		/**
		 * Fires when Genesis_Settings_Sanitizer is initialized.
		 *
		 * @since 1.7.0
		 *
		 * @param Genesis_Settings_Sanitizer $this The Genesis_Settings_Sanitizer object.
		 */
		do_action_ref_array( 'genesis_settings_sanitizer_init', [ &$this ] );

	}

	/**
	 * Add sanitization filters to options.
	 *
	 * Associates a sanitization filter to each option (or sub options if they
	 * exist) before adding a reference to run the option through that
	 * sanitizer at the right time.
	 *
	 * @since 1.7.0
	 *
	 * @param string       $filter    Sanitization filter type.
	 * @param string       $option    Option key.
	 * @param array|string $suboption Optional. Sub-option key.
	 * @return bool True when complete.
	 */
	public function add_filter( $filter, $option, $suboption = null ) {

		if ( is_array( $suboption ) ) {
			foreach ( $suboption as $so ) {
				$this->options[ $option ][ $so ] = $filter;
			}
		} elseif ( null === $suboption ) {
			$this->options[ $option ] = $filter;
		} else {
			$this->options[ $option ][ $suboption ] = $filter;
		}

		add_filter( 'sanitize_option_' . $option, [ $this, 'sanitize' ], 10, 2 );

		return true;

	}

	/**
	 * Checks sanitization filter exists, and if so, passes the value through it.
	 *
	 * @since 1.7.0
	 *
	 * @param string $filter    Sanitization filter type.
	 * @param string $new_value New value.
	 * @param string $old_value Previous value.
	 * @return mixed Filtered value, or submitted value if value is unfiltered.
	 */
	public function do_filter( $filter, $new_value, $old_value ) {

		$available_filters = $this->get_available_filters();

		if ( ! array_key_exists( $filter, $available_filters ) ) {
			return $new_value;
		}

		return call_user_func( $available_filters[ $filter ], $new_value, $old_value );

	}

	/**
	 * Return array of known sanitization filter types.
	 *
	 * Array can be filtered via 'genesis_available_sanitizer_filters' to let
	 * child themes and plugins add their own sanitization filters.
	 *
	 * @since 1.7.0
	 *
	 * @return array Keys of sanitization types, and values of the
	 *               filter function name as a callback.
	 */
	public function get_available_filters() {

		$default_filters = [
			'one_zero'                 => [ $this->sanitizer, 'one_zero' ],
			'no_html'                  => [ $this->sanitizer, 'no_html' ],
			'absint'                   => [ $this->sanitizer, 'absint' ],
			'safe_html'                => [ $this->sanitizer, 'safe_html' ],
			'requires_unfiltered_html' => [ $this->sanitizer, 'requires_unfiltered_html' ],
			'unfiltered_or_safe_html'  => [ $this->sanitizer, 'unfiltered_or_safe_html' ],
			'url'                      => [ $this->sanitizer, 'url' ],
			'email_address'            => [ $this->sanitizer, 'email_address' ],
		];

		/**
		 * Filter the available sanitization filter types.
		 *
		 * @since 1.7.0
		 *
		 * @param array $default_filters Array with keys of sanitization types, and values of the filter function name as a callback
		 */
		return apply_filters( 'genesis_available_sanitizer_filters', $default_filters );

	}

	/**
	 * Sanitize a value, via the sanitization filter type associated with an
	 * option.
	 *
	 * @since 1.7.0
	 *
	 * @param mixed  $new_value New value.
	 * @param string $option    Name of the option.
	 *
	 * @return mixed Filtered, or unfiltered value.
	 */
	public function sanitize( $new_value, $option ) {

		if ( ! isset( $this->options[ $option ] ) ) {
			// We are not filtering this option at all.
			return $new_value;
		}

		if ( is_string( $this->options[ $option ] ) ) {
			// Single option value.
			return $this->do_filter( $this->options[ $option ], $new_value, get_option( $option ) );
		}

		if ( is_array( $this->options[ $option ] ) ) {
			// Array of sub-option values to loop through.
			$old_value = get_option( $option );
			if ( ! is_array( $old_value ) ) {
				$old_value = [];
			}

			foreach ( $this->options[ $option ] as $suboption => $filter ) {
				$old_value[ $suboption ] = isset( $old_value[ $suboption ] ) ? $old_value[ $suboption ] : '';
				$new_value[ $suboption ] = isset( $new_value[ $suboption ] ) ? $new_value[ $suboption ] : '';
				$new_value[ $suboption ] = $this->do_filter( $filter, $new_value[ $suboption ], $old_value[ $suboption ] );
			}
			return $new_value;
		}

		// Should never hit this.
		return $new_value;
	}

}
