]> scripts.mit.edu Git - autoinstalls/wordpress.git/blobdiff - wp-includes/customize/class-wp-customize-custom-css-setting.php
WordPress 4.7-scripts
[autoinstalls/wordpress.git] / wp-includes / customize / class-wp-customize-custom-css-setting.php
diff --git a/wp-includes/customize/class-wp-customize-custom-css-setting.php b/wp-includes/customize/class-wp-customize-custom-css-setting.php
new file mode 100644 (file)
index 0000000..cb0f151
--- /dev/null
@@ -0,0 +1,373 @@
+<?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', __( 'Your curly brackets <code>{}</code> are imbalanced. Make sure there is a closing <code>}</code> for every opening <code>{</code>.' ) );
+                       $imbalanced = true;
+               }
+
+               // Ensure brackets are balanced.
+               if ( ! $this->validate_balanced_characters( '[', ']', $css ) ) {
+                       $validity->add( 'imbalanced_braces', __( 'Your brackets <code>[]</code> are imbalanced. Make sure there is a closing <code>]</code> for every opening <code>[</code>.' ) );
+                       $imbalanced = true;
+               }
+
+               // Ensure parentheses are balanced.
+               if ( ! $this->validate_balanced_characters( '(', ')', $css ) ) {
+                       $validity->add( 'imbalanced_parentheses', __( 'Your parentheses <code>()</code> are imbalanced. Make sure there is a closing <code>)</code> for every opening <code>(</code>.' ) );
+                       $imbalanced = true;
+               }
+
+               // Ensure single quotes are equal.
+               if ( ! $this->validate_equal_characters( '\'', $css ) ) {
+                       $validity->add( 'unequal_single_quotes', __( 'Your single quotes <code>\'</code> are uneven. Make sure there is a closing <code>\'</code> for every opening <code>\'</code>.' ) );
+                       $imbalanced = true;
+               }
+
+               // Ensure single quotes are equal.
+               if ( ! $this->validate_equal_characters( '"', $css ) ) {
+                       $validity->add( 'unequal_double_quotes', __( 'Your double quotes <code>"</code> are uneven. Make sure there is a closing <code>"</code> for every opening <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( _n( 'There is %s unclosed code comment. Close each comment with <code>*/</code>.', 'There are %s unclosed code comments. Close each comment with <code>*/</code>.', $unclosed_comment_count ), $unclosed_comment_count ) );
+                       $imbalanced = true;
+               } elseif ( ! $this->validate_balanced_characters( '/*', '*/', $css ) ) {
+                       $validity->add( 'imbalanced_comments', __( 'There is an extra <code>*/</code>, indicating an end to a comment.  Be sure that there is an opening <code>/*</code> for every closing <code>*/</code>.' ) );
+                       $imbalanced = true;
+               }
+               if ( $imbalanced && $this->is_possible_content_error( $css ) ) {
+                       $validity->add( 'possible_false_positive', __( 'Imbalanced/unclosed character errors can be caused by <code>content: "";</code> declarations. You may need to remove this or add it to a custom CSS file.' ) );
+               }
+
+               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;
+       }
+}