Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

368 lignes
10 KiB

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