123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- <?php
- /**
- * Customize API: WP_Customize_Custom_CSS_Setting class
- *
- * This handles validation, sanitization and saving of the value.
- *
- * @package WordPress
- * @subpackage Customize
- * @since 4.7.0
- */
- /**
- * Custom Setting to handle WP Custom CSS.
- *
- * @since 4.7.0
- *
- * @see WP_Customize_Setting
- */
- final class WP_Customize_Custom_CSS_Setting extends WP_Customize_Setting {
- /**
- * The setting type.
- *
- * @since 4.7.0
- * @access public
- * @var string
- */
- public $type = 'custom_css';
- /**
- * Setting Transport
- *
- * @since 4.7.0
- * @access public
- * @var string
- */
- public $transport = 'postMessage';
- /**
- * Capability required to edit this setting.
- *
- * @since 4.7.0
- * @access public
- * @var string
- */
- public $capability = 'edit_css';
- /**
- * Stylesheet
- *
- * @since 4.7.0
- * @access public
- * @var string
- */
- public $stylesheet = '';
- /**
- * WP_Customize_Custom_CSS_Setting constructor.
- *
- * @since 4.7.0
- * @access public
- *
- * @throws Exception If the setting ID does not match the pattern `custom_css[$stylesheet]`.
- *
- * @param WP_Customize_Manager $manager The Customize Manager class.
- * @param string $id An specific ID of the setting. Can be a
- * theme mod or option name.
- * @param array $args Setting arguments.
- */
- public function __construct( $manager, $id, $args = array() ) {
- parent::__construct( $manager, $id, $args );
- if ( 'custom_css' !== $this->id_data['base'] ) {
- throw new Exception( 'Expected custom_css id_base.' );
- }
- if ( 1 !== count( $this->id_data['keys'] ) || empty( $this->id_data['keys'][0] ) ) {
- throw new Exception( 'Expected single stylesheet key.' );
- }
- $this->stylesheet = $this->id_data['keys'][0];
- }
- /**
- * Add filter to preview post value.
- *
- * @since 4.7.9
- * @access public
- *
- * @return bool False when preview short-circuits due no change needing to be previewed.
- */
- public function preview() {
- if ( $this->is_previewed ) {
- return false;
- }
- $this->is_previewed = true;
- add_filter( 'wp_get_custom_css', array( $this, 'filter_previewed_wp_get_custom_css' ), 9, 2 );
- return true;
- }
- /**
- * Filter `wp_get_custom_css` for applying the customized value.
- *
- * This is used in the preview when `wp_get_custom_css()` is called for rendering the styles.
- *
- * @since 4.7.0
- * @access private
- * @see wp_get_custom_css()
- *
- * @param string $css Original CSS.
- * @param string $stylesheet Current stylesheet.
- * @return string CSS.
- */
- public function filter_previewed_wp_get_custom_css( $css, $stylesheet ) {
- if ( $stylesheet === $this->stylesheet ) {
- $customized_value = $this->post_value( null );
- if ( ! is_null( $customized_value ) ) {
- $css = $customized_value;
- }
- }
- return $css;
- }
- /**
- * Fetch the value of the setting. Will return the previewed value when `preview()` is called.
- *
- * @since 4.7.0
- * @access public
- * @see WP_Customize_Setting::value()
- *
- * @return string
- */
- public function value() {
- if ( $this->is_previewed ) {
- $post_value = $this->post_value( null );
- if ( null !== $post_value ) {
- return $post_value;
- }
- }
- $id_base = $this->id_data['base'];
- $value = '';
- $post = wp_get_custom_css_post( $this->stylesheet );
- if ( $post ) {
- $value = $post->post_content;
- }
- if ( empty( $value ) ) {
- $value = $this->default;
- }
- /** This filter is documented in wp-includes/class-wp-customize-setting.php */
- $value = apply_filters( "customize_value_{$id_base}", $value, $this );
- return $value;
- }
- /**
- * Validate CSS.
- *
- * Checks for imbalanced braces, brackets, and comments.
- * Notifications are rendered when the customizer state is saved.
- *
- * @todo There are cases where valid CSS can be incorrectly marked as invalid when strings or comments include balancing characters. To fix, CSS tokenization needs to be used.
- *
- * @since 4.7.0
- * @access public
- *
- * @param string $css The input string.
- * @return true|WP_Error True if the input was validated, otherwise WP_Error.
- */
- public function validate( $css ) {
- $validity = new WP_Error();
- if ( preg_match( '#</?\w+#', $css ) ) {
- $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
- }
- $imbalanced = false;
- // Make sure that there is a closing brace for each opening brace.
- if ( ! $this->validate_balanced_characters( '{', '}', $css ) ) {
- $validity->add( 'imbalanced_curly_brackets', sprintf(
- /* translators: 1: {}, 2: }, 3: { */
- __( 'Your curly brackets %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ),
- '<code>{}</code>',
- '<code>}</code>',
- '<code>{</code>'
- ) );
- $imbalanced = true;
- }
- // Ensure brackets are balanced.
- if ( ! $this->validate_balanced_characters( '[', ']', $css ) ) {
- $validity->add( 'imbalanced_braces', sprintf(
- /* translators: 1: [], 2: ], 3: [ */
- __( 'Your brackets %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ),
- '<code>[]</code>',
- '<code>]</code>',
- '<code>[</code>'
- ) );
- $imbalanced = true;
- }
- // Ensure parentheses are balanced.
- if ( ! $this->validate_balanced_characters( '(', ')', $css ) ) {
- $validity->add( 'imbalanced_parentheses', sprintf(
- /* translators: 1: (), 2: ), 3: ( */
- __( 'Your parentheses %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ),
- '<code>()</code>',
- '<code>)</code>',
- '<code>(</code>'
- ) );
- $imbalanced = true;
- }
- // Ensure double quotes are equal.
- if ( ! $this->validate_equal_characters( '"', $css ) ) {
- $validity->add( 'unequal_double_quotes', sprintf(
- /* translators: 1: " (double quote) */
- __( 'Your double quotes %1$s are uneven. Make sure there is a closing %1$s for every opening %1$s.' ),
- '<code>"</code>'
- ) );
- $imbalanced = true;
- }
- /*
- * Make sure any code comments are closed properly.
- *
- * The first check could miss stray an unpaired comment closing figure, so if
- * The number appears to be balanced, then check for equal numbers
- * of opening/closing comment figures.
- *
- * Although it may initially appear redundant, we use the first method
- * to give more specific feedback to the user.
- */
- $unclosed_comment_count = $this->validate_count_unclosed_comments( $css );
- if ( 0 < $unclosed_comment_count ) {
- $validity->add( 'unclosed_comment', sprintf(
- /* translators: 1: number of unclosed comments, 2: */ */
- _n(
- 'There is %1$s unclosed code comment. Close each comment with %2$s.',
- 'There are %1$s unclosed code comments. Close each comment with %2$s.',
- $unclosed_comment_count
- ),
- $unclosed_comment_count,
- '<code>*/</code>'
- ) );
- $imbalanced = true;
- } elseif ( ! $this->validate_balanced_characters( '/*', '*/', $css ) ) {
- $validity->add( 'imbalanced_comments', sprintf(
- /* translators: 1: */, 2: /* */
- __( 'There is an extra %1$s, indicating an end to a comment. Be sure that there is an opening %2$s for every closing %1$s.' ),
- '<code>*/</code>',
- '<code>/*</code>'
- ) );
- $imbalanced = true;
- }
- if ( $imbalanced && $this->is_possible_content_error( $css ) ) {
- $validity->add( 'possible_false_positive', sprintf(
- /* translators: %s: content: ""; */
- __( 'Imbalanced/unclosed character errors can be caused by %s declarations. You may need to remove this or add it to a custom CSS file.' ),
- '<code>content: "";</code>'
- ) );
- }
- if ( empty( $validity->errors ) ) {
- $validity = parent::validate( $css );
- }
- return $validity;
- }
- /**
- * Store the CSS setting value in the custom_css custom post type for the stylesheet.
- *
- * @since 4.7.0
- * @access public
- *
- * @param string $css The input value.
- * @return int|false The post ID or false if the value could not be saved.
- */
- public function update( $css ) {
- if ( empty( $css ) ) {
- $css = '';
- }
- $r = wp_update_custom_css_post( $css, array(
- 'stylesheet' => $this->stylesheet,
- ) );
- if ( $r instanceof WP_Error ) {
- return false;
- }
- $post_id = $r->ID;
- // Cache post ID in theme mod for performance to avoid additional DB query.
- if ( $this->manager->get_stylesheet() === $this->stylesheet ) {
- set_theme_mod( 'custom_css_post_id', $post_id );
- }
- return $post_id;
- }
- /**
- * Ensure there are a balanced number of paired characters.
- *
- * This is used to check that the number of opening and closing
- * characters is equal.
- *
- * For instance, there should be an equal number of braces ("{", "}")
- * in the CSS.
- *
- * @since 4.7.0
- * @access private
- *
- * @param string $opening_char The opening character.
- * @param string $closing_char The closing character.
- * @param string $css The CSS input string.
- *
- * @return bool
- */
- private function validate_balanced_characters( $opening_char, $closing_char, $css ) {
- return substr_count( $css, $opening_char ) === substr_count( $css, $closing_char );
- }
- /**
- * Ensure there are an even number of paired characters.
- *
- * This is used to check that the number of a specific
- * character is even.
- *
- * For instance, there should be an even number of double quotes
- * in the CSS.
- *
- * @since 4.7.0
- * @access private
- *
- * @param string $char A character.
- * @param string $css The CSS input string.
- * @return bool Equality.
- */
- private function validate_equal_characters( $char, $css ) {
- $char_count = substr_count( $css, $char );
- return ( 0 === $char_count % 2 );
- }
- /**
- * Count unclosed CSS Comments.
- *
- * Used during validation.
- *
- * @see self::validate()
- *
- * @since 4.7.0
- * @access private
- *
- * @param string $css The CSS input string.
- * @return int Count.
- */
- private function validate_count_unclosed_comments( $css ) {
- $count = 0;
- $comments = explode( '/*', $css );
- if ( ! is_array( $comments ) || ( 1 >= count( $comments ) ) ) {
- return $count;
- }
- unset( $comments[0] ); // The first item is before the first comment.
- foreach ( $comments as $comment ) {
- if ( false === strpos( $comment, '*/' ) ) {
- $count++;
- }
- }
- return $count;
- }
- /**
- * Find "content:" within a string.
- *
- * Imbalanced/Unclosed validation errors may be caused
- * when a character is used in a "content:" declaration.
- *
- * This function is used to detect if this is a possible
- * cause of the validation error, so that if it is,
- * a notification may be added to the Validation Errors.
- *
- * Example:
- * .element::before {
- * content: "(\"";
- * }
- * .element::after {
- * content: "\")";
- * }
- *
- * Using ! empty() because strpos() may return non-boolean values
- * that evaluate to false. This would be problematic when
- * using a strict "false === strpos()" comparison.
- *
- * @since 4.7.0
- * @access private
- *
- * @param string $css The CSS input string.
- * @return bool
- */
- private function is_possible_content_error( $css ) {
- $found = preg_match( '/\bcontent\s*:/', $css );
- if ( ! empty( $found ) ) {
- return true;
- }
- return false;
- }
- }
|