WP_Customize_Custom_CSS_Setting

Custom Setting to handle WP Custom CSS.

Defined (1)

The class is defined in the following location(s).

/wp-includes/customize/class-wp-customize-custom-css-setting.php  
  1. final class WP_Customize_Custom_CSS_Setting extends WP_Customize_Setting { 
  2.  
  3. /** 
  4. * The setting type. 
  5. * @since 4.7.0 
  6. * @access public 
  7. * @var string 
  8. */ 
  9. public $type = 'custom_css'; 
  10.  
  11. /** 
  12. * Setting Transport 
  13. * @since 4.7.0 
  14. * @access public 
  15. * @var string 
  16. */ 
  17. public $transport = 'postMessage'; 
  18.  
  19. /** 
  20. * Capability required to edit this setting. 
  21. * @since 4.7.0 
  22. * @access public 
  23. * @var string 
  24. */ 
  25. public $capability = 'edit_css'; 
  26.  
  27. /** 
  28. * Stylesheet 
  29. * @since 4.7.0 
  30. * @access public 
  31. * @var string 
  32. */ 
  33. public $stylesheet = ''; 
  34.  
  35. /** 
  36. * WP_Customize_Custom_CSS_Setting constructor. 
  37. * @since 4.7.0 
  38. * @access public 
  39. * @throws Exception If the setting ID does not match the pattern `custom_css[$stylesheet]`. 
  40. * @param WP_Customize_Manager $manager The Customize Manager class. 
  41. * @param string $id An specific ID of the setting. Can be a 
  42. * theme mod or option name. 
  43. * @param array $args Setting arguments. 
  44. */ 
  45. public function __construct( $manager, $id, $args = array() ) { 
  46. parent::__construct( $manager, $id, $args ); 
  47. if ( 'custom_css' !== $this->id_data['base'] ) { 
  48. throw new Exception( 'Expected custom_css id_base.' ); 
  49. if ( 1 !== count( $this->id_data['keys'] ) || empty( $this->id_data['keys'][0] ) ) { 
  50. throw new Exception( 'Expected single stylesheet key.' ); 
  51. $this->stylesheet = $this->id_data['keys'][0]; 
  52.  
  53. /** 
  54. * Add filter to preview post value. 
  55. * @since 4.7.9 
  56. * @access public 
  57. * @return bool False when preview short-circuits due no change needing to be previewed. 
  58. */ 
  59. public function preview() { 
  60. if ( $this->is_previewed ) { 
  61. return false; 
  62. $this->is_previewed = true; 
  63. add_filter( 'wp_get_custom_css', array( $this, 'filter_previewed_wp_get_custom_css' ), 9, 2 ); 
  64. return true; 
  65.  
  66. /** 
  67. * Filter `wp_get_custom_css` for applying the customized value. 
  68. * This is used in the preview when `wp_get_custom_css()` is called for rendering the styles. 
  69. * @since 4.7.0 
  70. * @access private 
  71. * @see wp_get_custom_css() 
  72. * @param string $css Original CSS. 
  73. * @param string $stylesheet Current stylesheet. 
  74. * @return string CSS. 
  75. */ 
  76. public function filter_previewed_wp_get_custom_css( $css, $stylesheet ) { 
  77. if ( $stylesheet === $this->stylesheet ) { 
  78. $customized_value = $this->post_value( null ); 
  79. if ( ! is_null( $customized_value ) ) { 
  80. $css = $customized_value; 
  81. return $css; 
  82.  
  83. /** 
  84. * Fetch the value of the setting. Will return the previewed value when `preview()` is called. 
  85. * @since 4.7.0 
  86. * @access public 
  87. * @see WP_Customize_Setting::value() 
  88. * @return string 
  89. */ 
  90. public function value() { 
  91. if ( $this->is_previewed ) { 
  92. $post_value = $this->post_value( null ); 
  93. if ( null !== $post_value ) { 
  94. return $post_value; 
  95. $id_base = $this->id_data['base']; 
  96. $value = ''; 
  97. $post = wp_get_custom_css_post( $this->stylesheet ); 
  98. if ( $post ) { 
  99. $value = $post->post_content; 
  100. if ( empty( $value ) ) { 
  101. $value = $this->default; 
  102.  
  103. /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 
  104. $value = apply_filters( "customize_value_{$id_base}", $value, $this ); 
  105.  
  106. return $value; 
  107.  
  108. /** 
  109. * Validate CSS. 
  110. * Checks for imbalanced braces, brackets, and comments. 
  111. * Notifications are rendered when the customizer state is saved. 
  112. * @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. 
  113. * @since 4.7.0 
  114. * @access public 
  115. * @param string $css The input string. 
  116. * @return true|WP_Error True if the input was validated, otherwise WP_Error. 
  117. */ 
  118. public function validate( $css ) { 
  119. $validity = new WP_Error(); 
  120.  
  121. if ( preg_match( '#</?\w+#', $css ) ) { 
  122. $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); 
  123.  
  124. $imbalanced = false; 
  125.  
  126. // Make sure that there is a closing brace for each opening brace. 
  127. if ( ! $this->validate_balanced_characters( '{', '}', $css ) ) { 
  128. $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>.' ) ); 
  129. $imbalanced = true; 
  130.  
  131. // Ensure brackets are balanced. 
  132. if ( ! $this->validate_balanced_characters( '[', ']', $css ) ) { 
  133. $validity->add( 'imbalanced_braces', __( 'Your brackets <code>[]</code> are imbalanced. Make sure there is a closing <code>]</code> for every opening <code>[</code>.' ) ); 
  134. $imbalanced = true; 
  135.  
  136. // Ensure parentheses are balanced. 
  137. if ( ! $this->validate_balanced_characters( '(', ')', $css ) ) { 
  138. $validity->add( 'imbalanced_parentheses', __( 'Your parentheses <code>()</code> are imbalanced. Make sure there is a closing <code>)</code> for every opening <code>(</code>.' ) ); 
  139. $imbalanced = true; 
  140.  
  141. // Ensure double quotes are equal. 
  142. if ( ! $this->validate_equal_characters( '"', $css ) ) { 
  143. $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>.' ) ); 
  144. $imbalanced = true; 
  145.  
  146. /** 
  147. * Make sure any code comments are closed properly. 
  148. * The first check could miss stray an unpaired comment closing figure, so if 
  149. * The number appears to be balanced, then check for equal numbers 
  150. * of opening/closing comment figures. 
  151. * Although it may initially appear redundant, we use the first method 
  152. * to give more specific feedback to the user. 
  153. */ 
  154. $unclosed_comment_count = $this->validate_count_unclosed_comments( $css ); 
  155. if ( 0 < $unclosed_comment_count ) { 
  156. $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 ) ); 
  157. $imbalanced = true; 
  158. } elseif ( ! $this->validate_balanced_characters( '/*', '*/', $css ) ) { 
  159. $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>.' ) ); 
  160. $imbalanced = true; 
  161. if ( $imbalanced && $this->is_possible_content_error( $css ) ) { 
  162. $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.' ) ); 
  163.  
  164. if ( empty( $validity->errors ) ) { 
  165. $validity = parent::validate( $css ); 
  166. return $validity; 
  167.  
  168. /** 
  169. * Store the CSS setting value in the custom_css custom post type for the stylesheet. 
  170. * @since 4.7.0 
  171. * @access public 
  172. * @param string $css The input value. 
  173. * @return int|false The post ID or false if the value could not be saved. 
  174. */ 
  175. public function update( $css ) { 
  176. if ( empty( $css ) ) { 
  177. $css = ''; 
  178.  
  179. $r = wp_update_custom_css_post( $css, array( 
  180. 'stylesheet' => $this->stylesheet,  
  181. ) ); 
  182.  
  183. if ( $r instanceof WP_Error ) { 
  184. return false; 
  185. $post_id = $r->ID; 
  186.  
  187. // Cache post ID in theme mod for performance to avoid additional DB query. 
  188. if ( $this->manager->get_stylesheet() === $this->stylesheet ) { 
  189. set_theme_mod( 'custom_css_post_id', $post_id ); 
  190.  
  191. return $post_id; 
  192.  
  193. /** 
  194. * Ensure there are a balanced number of paired characters. 
  195. * This is used to check that the number of opening and closing 
  196. * characters is equal. 
  197. * For instance, there should be an equal number of braces ("{", "}") 
  198. * in the CSS. 
  199. * @since 4.7.0 
  200. * @access private 
  201. * @param string $opening_char The opening character. 
  202. * @param string $closing_char The closing character. 
  203. * @param string $css The CSS input string. 
  204. * @return bool 
  205. */ 
  206. private function validate_balanced_characters( $opening_char, $closing_char, $css ) { 
  207. return substr_count( $css, $opening_char ) === substr_count( $css, $closing_char ); 
  208.  
  209. /** 
  210. * Ensure there are an even number of paired characters. 
  211. * This is used to check that the number of a specific 
  212. * character is even. 
  213. * For instance, there should be an even number of double quotes 
  214. * in the CSS. 
  215. * @since 4.7.0 
  216. * @access private 
  217. * @param string $char A character. 
  218. * @param string $css The CSS input string. 
  219. * @return bool Equality. 
  220. */ 
  221. private function validate_equal_characters( $char, $css ) { 
  222. $char_count = substr_count( $css, $char ); 
  223. return ( 0 === $char_count % 2 ); 
  224.  
  225. /** 
  226. * Count unclosed CSS Comments. 
  227. * Used during validation. 
  228. * @see self::validate() 
  229. * @since 4.7.0 
  230. * @access private 
  231. * @param string $css The CSS input string. 
  232. * @return int Count. 
  233. */ 
  234. private function validate_count_unclosed_comments( $css ) { 
  235. $count = 0; 
  236. $comments = explode( '/*', $css ); 
  237.   
  238. if ( ! is_array( $comments ) || ( 1 >= count( $comments ) ) ) { 
  239. return $count; 
  240.   
  241. unset( $comments[0] ); // The first item is before the first comment. 
  242. foreach ( $comments as $comment ) { 
  243. if ( false === strpos( $comment, '*/' ) ) { 
  244. $count++; 
  245. return $count; 
  246.  
  247. /** 
  248. * Find "content:" within a string. 
  249. * Imbalanced/Unclosed validation errors may be caused 
  250. * when a character is used in a "content:" declaration. 
  251. * This function is used to detect if this is a possible 
  252. * cause of the validation error, so that if it is,  
  253. * a notification may be added to the Validation Errors. 
  254. * Example: 
  255. * .element::before { 
  256. * content: "(\""; 
  257. * } 
  258. * .element::after { 
  259. * content: "\")"; 
  260. * } 
  261. * Using ! empty() because strpos() may return non-boolean values 
  262. * that evaluate to false. This would be problematic when 
  263. * using a strict "false === strpos()" comparison. 
  264. * @since 4.7.0 
  265. * @access private 
  266. * @param string $css The CSS input string. 
  267. * @return bool 
  268. */ 
  269. private function is_possible_content_error( $css ) { 
  270. $found = preg_match( '/\bcontent\s*:/', $css ); 
  271. if ( ! empty( $found ) ) { 
  272. return true; 
  273. return false;