<?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+
 * @link    http://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;

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

	/**
	 * Constructor.
	 *
	 * @since 1.7.0
	 */
	public function __construct() {

		self::$instance =& $this;

		/**
		 * 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', array( &$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, array( $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 = array(
			'one_zero'                 => array( $this, 'one_zero'                 ),
			'no_html'                  => array( $this, 'no_html'                  ),
			'absint'                   => array( $this, 'absint'                   ),
			'safe_html'                => array( $this, 'safe_html'                ),
			'requires_unfiltered_html' => array( $this, 'requires_unfiltered_html' ),
			'unfiltered_or_safe_html'  => array( $this, 'unfiltered_or_safe_html'  ),
			'url'                      => array( $this, 'url'                      ),
			'email_address'            => array( $this, '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;
		} elseif ( is_string( $this->options[$option] ) ) {
			// Single option value.
			return $this->do_filter( $this->options[$option], $new_value, get_option( $option ) );
		} elseif ( is_array( $this->options[$option] ) ) {
			// Array of sub-option values to loop through.
			$old_value = get_option( $option );
			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;
		} else {
			// Should never hit this.
			return $new_value;
		}

	}

	/**
	 * Returns a 1 or 0, for all truthy / falsy values.
	 *
	 * Uses double casting. First, we cast to bool, then to integer.
	 *
	 * @since 1.7.0
	 *
	 * @param mixed $new_value Should ideally be a 1 or 0 integer passed in.
	 * @return int `1` or `0`.
	 */
	public function one_zero( $new_value ) {

		return (int) (bool) $new_value;

	}

	/**
	 * Returns a positive integer value.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed $new_value Should ideally be a positive integer.
	 * @return int Positive integer.
	 */
	public function absint( $new_value ) {

		return absint( $new_value );

	}

	/**
	 * Removes HTML tags from string.
	 *
	 * @since 1.7.0
	 *
	 * @param string $new_value String, possibly with HTML in it.
	 * @return string String without HTML in it.
	 */
	public function no_html( $new_value ) {

		return strip_tags( $new_value );

	}

	/**
	 * Makes URLs safe
	 *
	 * @since 1.9.0
	 *
	 * @param string $new_value String, a URL, possibly unsafe.
	 * @return string String a safe URL.
	 */
	public function url( $new_value ) {

		return esc_url_raw( $new_value );

	}

	/**
	 * Makes Email Addresses safe, via sanitize_email()
	 *
	 * @since 2.1.0
	 *
	 * @param string $new_value String, an email address, possibly unsafe.
	 * @return string String a safe email address.
	 */
	public function email_address( $new_value ) {

		return sanitize_email( $new_value );

	}

	/**
	 * Removes unsafe HTML tags, via wp_kses_post().
	 *
	 * @since 1.7.0
	 *
	 * @param string $new_value String with potentially unsafe HTML in it.
	 * @return string String with only safe HTML in it.
	 */
	public function safe_html( $new_value ) {

		return wp_kses_post( $new_value );

	}

	/**
	 * Keeps the option from being updated if the user lacks unfiltered_html
	 * capability.
	 *
	 * @since 1.7.0
	 *
	 * @param string $new_value New value.
	 * @param string $old_value Previous value.
	 * @return string New or previous value, depending if user has correct
	 *                capability or not.
	 */
	public function requires_unfiltered_html( $new_value, $old_value ) {

		if ( current_user_can( 'unfiltered_html' ) ) {
			return $new_value;
		} else {
			return $old_value;
		}

	}

	/**
	 * Removes unsafe HTML tags when user does not have unfiltered_html
	 * capability.
	 *
	 * @since 2.4.0
	 *
	 * @param string $new_value New value.
	 * @param string $old_value Previous value.
	 * @return string New or safe HTML value, depending if user has correct
	 *                capability or not.
	 */
	public function unfiltered_or_safe_html( $new_value, $old_value ) {

		if ( current_user_can( 'unfiltered_html' ) ) {
			return $new_value;
		}

		return wp_kses_post( $new_value );

	}

}


/**
 * Registers an option sanitization filter.
 *
 * If the option is an "array" option type with "sub-options", you have to use the third param to specify the
 * sub-option or sub-options you want the filter to apply to. DO NOT call this without the third parameter on an option
 * that is an array option, because in that case it will apply that filter to the array(), not each member.
 *
 * Use the 'genesis_settings_sanitizer_init' action to be notified when this function is safe to use
 *
 * @since 1.7.0
 *
 * @param string       $filter    The filter to call (see Genesis_Settings_Sanitizer::$available_filters for options).
 * @param string       $option    The WordPress option name.
 * @param string|array $suboption Optional. The sub-option or sub-options you want to filter.
 * @return true True when complete.
 */
function genesis_add_option_filter( $filter, $option, $suboption = null ) {

	return Genesis_Settings_Sanitizer::$instance->add_filter( $filter, $option, $suboption );

}

add_action( 'admin_init', 'genesis_settings_sanitizer_init' );
/**
 * Instantiate the Sanitizer.
 *
 * @since 1.7.0
 */
function genesis_settings_sanitizer_init() {

	new Genesis_Settings_Sanitizer;

}
