PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.4
Jetpack – WP Security, Backup, Speed, & Growth v6.4
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / modules / custom-css / custom-css-4.7.php
custom-css-4.7.php
1,184 lines 36.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Alternate Custom CSS source for 4.7 compat.
4 *
5 * @since 4.4.2
6 *
7 * @package Jetpack
8 */
9
10 /**
11 * Class Jetpack_Custom_CSS_Enhancements
12 */
13 class Jetpack_Custom_CSS_Enhancements {
14 /**
15 * Set up the actions and filters needed for our compatability layer on top of core's Custom CSS implementation.
16 */
17 public static function add_hooks() {
18 add_action( 'init', array( __CLASS__, 'init' ) );
19 add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
20 add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'customize_controls_enqueue_scripts' ) );
21 add_action( 'customize_register', array( __CLASS__, 'customize_register' ) );
22 add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap' ), 20, 2 );
23 add_action( 'customize_preview_init', array( __CLASS__, 'customize_preview_init' ) );
24 add_filter( '_wp_post_revision_fields', array( __CLASS__, '_wp_post_revision_fields' ), 10, 2 );
25 add_action( 'load-revision.php', array( __CLASS__, 'load_revision_php' ) );
26
27 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'wp_enqueue_scripts' ) );
28
29 // Handle Sass/LESS.
30 add_filter( 'customize_value_custom_css', array( __CLASS__, 'customize_value_custom_css' ), 10, 2 );
31 add_filter( 'customize_update_custom_css_post_content_args', array( __CLASS__, 'customize_update_custom_css_post_content_args' ), 10, 3 );
32 add_filter( 'update_custom_css_data', array( __CLASS__, 'update_custom_css_data' ), 10, 2 );
33
34 // Handle Sass/LESS.
35 add_filter( 'customize_value_custom_css', array( __CLASS__, 'customize_value_custom_css' ), 10, 2 );
36 add_filter( 'customize_update_custom_css_post_content_args', array( __CLASS__, 'customize_update_custom_css_post_content_args' ), 10, 3 );
37
38 // Stuff for stripping out the theme's default stylesheet...
39 add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) );
40 add_filter( 'safecss_skip_stylesheet', array( __CLASS__, 'preview_skip_stylesheet' ) );
41
42 // Stuff for overriding content width...
43 add_action( 'customize_preview_init', array( __CLASS__, 'preview_content_width' ) );
44 add_filter( 'jetpack_content_width', array( __CLASS__, 'jetpack_content_width' ) );
45 add_filter( 'editor_max_image_size', array( __CLASS__, 'editor_max_image_size' ), 10, 3 );
46 add_action( 'template_redirect', array( __CLASS__, 'set_content_width' ) );
47 add_action( 'admin_init', array( __CLASS__, 'set_content_width' ) );
48
49 // Stuff?
50 }
51
52 /**
53 * Things that we do on init.
54 */
55 public static function init() {
56 $min = '.min';
57 if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
58 $min = '';
59 }
60
61 wp_register_style( 'jetpack-codemirror', plugins_url( 'custom-css/css/codemirror.css', __FILE__ ), array(), '20120905' );
62 $deps = array();
63 if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
64 // If Core < 4.9
65 $deps[] = 'jetpack-codemirror';
66 }
67 wp_register_style( 'jetpack-customizer-css', plugins_url( 'custom-css/css/customizer-control.css', __FILE__ ), $deps, '20140728' );
68 wp_register_script( 'jetpack-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
69 $deps = array( 'customize-controls', 'underscore' );
70 $src = Jetpack::get_file_url_for_environment(
71 '_inc/build/custom-css/custom-css/js/core-customizer-css.core-4.9.min.js',
72 'modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js'
73 );
74 if ( ! function_exists( 'wp_enqueue_code_editor' ) ) {
75 // If Core < 4.9
76 $deps[] = 'jetpack-codemirror';
77 $src = Jetpack::get_file_url_for_environment(
78 '_inc/build/custom-css/custom-css/js/core-customizer-css.min.js',
79 'modules/custom-css/custom-css/js/core-customizer-css.js'
80 );
81 }
82 wp_register_script( 'jetpack-customizer-css', $src, $deps, JETPACK__VERSION, true );
83
84 wp_register_script(
85 'jetpack-customizer-css-preview',
86 Jetpack::get_file_url_for_environment(
87 '_inc/build/custom-css/custom-css/js/core-customizer-css-preview.min.js',
88 'modules/custom-css/custom-css/js/core-customizer-css-preview.js'
89 ),
90 array( 'customize-selective-refresh' ),
91 JETPACK__VERSION,
92 true
93 );
94
95 remove_action( 'wp_head', 'wp_custom_css_cb', 11 ); // 4.7.0 had it at 11, 4.7.1 moved it to 101.
96 remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
97 add_action( 'wp_head', array( __CLASS__, 'wp_custom_css_cb' ), 101 );
98
99 if ( isset( $_GET['custom-css'] ) ) {
100 self::print_linked_custom_css();
101 }
102 }
103
104 /**
105 * Things that we do on init when the Customize Preview is loading.
106 */
107 public static function customize_preview_init() {
108 add_filter( 'wp_get_custom_css', array( __CLASS__, 'customize_preview_wp_get_custom_css' ) );
109 }
110
111 /**
112 * Print the current Custom CSS. This is for linking instead of printing directly.
113 */
114 public static function print_linked_custom_css() {
115 header( 'Content-type: text/css' );
116 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + YEAR_IN_SECONDS ) . ' GMT' );
117 echo wp_get_custom_css();
118 exit;
119 }
120
121 /**
122 * Re-map the Edit CSS capability.
123 *
124 * Core, by default, restricts this to users that have `unfiltered_html` which
125 * would make the feature unusable in multi-site by non-super-admins, due to Core
126 * not shipping any solid sanitization.
127 *
128 * We're expanding who can use it, and then conditionally applying CSSTidy
129 * sanitization to users that do not have the `unfiltered_html` capability.
130 *
131 * @param array $caps Returns the user's actual capabilities.
132 * @param string $cap Capability name.
133 *
134 * @return array $caps
135 */
136 public static function map_meta_cap( $caps, $cap ) {
137 if ( 'edit_css' === $cap ) {
138 $caps = array( 'edit_theme_options' );
139 }
140 return $caps;
141 }
142
143 /**
144 * Handle our admin menu item and legacy page declaration.
145 */
146 public static function admin_menu() {
147 // Add in our legacy page to support old bookmarks and such.
148 add_submenu_page( null, __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss', array( __CLASS__, 'admin_page' ) );
149
150 // Add in our new page slug that will redirect to the customizer.
151 $hook = add_theme_page( __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss-customizer-redirect', array( __CLASS__, 'admin_page' ) );
152 add_action( "load-{$hook}", array( __CLASS__, 'customizer_redirect' ) );
153 }
154
155 /**
156 * Handle the redirect for the customizer. This is necessary because
157 * we can't directly add customizer links to the admin menu.
158 *
159 * There is a core patch in trac that would make this unnecessary.
160 *
161 * @link https://core.trac.wordpress.org/ticket/39050
162 */
163 public static function customizer_redirect() {
164 wp_safe_redirect( self::customizer_link( array(
165 'return_url' => wp_get_referer(),
166 ) ) );
167 exit;
168 }
169
170 /**
171 * Shows Preprocessor code in the Revisions screen, and ensures that post_content_filtered
172 * is maintained on revisions
173 *
174 * @param array $fields Post fields pertinent to revisions.
175 * @param array $post A post array being processed for insertion as a post revision.
176 *
177 * @return array $fields Modified array to include post_content_filtered.
178 */
179 public static function _wp_post_revision_fields( $fields, $post ) {
180 // None of the fields in $post are required to be passed in this filter.
181 if ( ! isset( $post['post_type'], $post['ID'] ) ) {
182 return $fields;
183 }
184
185 // If we're passed in a revision, go get the main post instead.
186 if ( 'revision' === $post['post_type'] ) {
187 $main_post_id = wp_is_post_revision( $post['ID'] );
188 $post = get_post( $main_post_id, ARRAY_A );
189 }
190 if ( 'custom_css' === $post['post_type'] ) {
191 $fields['post_content'] = __( 'CSS', 'jetpack' );
192 $fields['post_content_filtered'] = __( 'Preprocessor', 'jetpack' );
193 }
194 return $fields;
195 }
196
197 /**
198 * Get the published custom CSS post.
199 *
200 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
201 * @return WP_Post|null
202 */
203 public static function get_css_post( $stylesheet = '' ) {
204 return wp_get_custom_css_post( $stylesheet );
205 }
206
207 /**
208 * Override Core's `wp_custom_css_cb` method to provide linking to custom css.
209 */
210 public static function wp_custom_css_cb() {
211 $styles = wp_get_custom_css();
212 if ( strlen( $styles ) > 2000 && ! is_customize_preview() ) :
213 // Add a cache buster to the url.
214 $url = home_url( '/' );
215 $url = add_query_arg( 'custom-css', substr( md5( $styles ), -10 ), $url );
216 ?>
217 <link rel="stylesheet" type="text/css" id="wp-custom-css" href="<?php echo esc_url( $url ); ?>" />
218 <?php elseif ( $styles || is_customize_preview() ) : ?>
219 <style type="text/css" id="wp-custom-css">
220 <?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
221 </style>
222 <?php endif;
223 }
224
225 /**
226 * Get the ID of a Custom CSS post tying to a given stylesheet.
227 *
228 * @param string $stylesheet Stylesheet name.
229 *
230 * @return int $post_id Post ID.
231 */
232 public static function post_id( $stylesheet = '' ) {
233 $post = self::get_css_post( $stylesheet );
234 if ( $post instanceof WP_Post ) {
235 return $post->ID;
236 }
237 return 0;
238 }
239
240 /**
241 * Partial for use in the Customizer.
242 */
243 public static function echo_custom_css_partial() {
244 echo wp_get_custom_css();
245 }
246
247 /**
248 * Admin page!
249 *
250 * This currently has two main uses -- firstly to display the css for an inactive
251 * theme if there are no revisions attached it to a legacy bug, and secondly to
252 * handle folks that have bookmarkes in their browser going to the old page for
253 * managing Custom CSS in Jetpack.
254 *
255 * If we ever add back in a non-Customizer CSS editor, this would be the place.
256 */
257 public static function admin_page() {
258 $post = null;
259 $stylesheet = null;
260 if ( isset( $_GET['id'] ) ) {
261 $post_id = absint( $_GET['id'] );
262 $post = get_post( $post_id );
263 if ( $post instanceof WP_Post && 'custom_css' === $post->post_type ) {
264 $stylesheet = $post->post_title;
265 }
266 }
267 ?>
268 <div class="wrap">
269 <?php self::revisions_switcher_box( $stylesheet ); ?>
270 <h1>
271 <?php
272 if ( $post ) {
273 printf( 'Custom CSS for &#8220;%1$s&#8221;', wp_get_theme( $stylesheet )->Name );
274 } else {
275 esc_html_e( 'Custom CSS', 'jetpack' );
276 }
277 if ( current_user_can( 'customize' ) ) {
278 printf(
279 ' <a class="page-title-action hide-if-no-customize" href="%1$s">%2$s</a>',
280 esc_url( self::customizer_link() ),
281 esc_html__( 'Manage with Live Preview', 'jetpack' )
282 );
283 }
284 ?>
285 </h1>
286 <p><?php esc_html_e( 'Custom CSS is now managed in the Customizer.', 'jetpack' ); ?></p>
287 <?php if ( $post ) : ?>
288 <div class="revisions">
289 <h3><?php esc_html_e( 'CSS', 'jetpack' ); ?></h3>
290 <textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content ); ?></textarea>
291 <?php if ( $post->post_content_filtered ) : ?>
292 <h3><?php esc_html_e( 'Preprocessor', 'jetpack' ); ?></h3>
293 <textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content_filtered ); ?></textarea>
294 <?php endif; ?>
295 </div>
296 <?php endif; ?>
297 </div>
298
299 <style>
300 .other-themes-wrap {
301 float: right;
302 background-color: #fff;
303 -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
304 box-shadow: 0 1px 3px rgba(0,0,0,0.1);
305 padding: 5px 10px;
306 margin-bottom: 10px;
307 }
308 .other-themes-wrap label {
309 display: block;
310 margin-bottom: 10px;
311 }
312 .other-themes-wrap select {
313 float: left;
314 width: 77%;
315 }
316 .other-themes-wrap button {
317 float: right;
318 width: 20%;
319 }
320 .revisions {
321 clear: both;
322 }
323 .revisions textarea {
324 min-height: 300px;
325 background: #fff;
326 }
327 </style>
328 <script>
329 (function($){
330 var $switcher = $('.other-themes-wrap');
331 $switcher.find('button').on('click', function(e){
332 e.preventDefault();
333 if ( $switcher.find('select').val() ) {
334 window.location.href = $switcher.find('select').val();
335 }
336 });
337 })(jQuery);
338 </script>
339 <?php
340 }
341
342 /**
343 * Build the URL to deep link to the Customizer.
344 *
345 * You can modify the return url via $args.
346 *
347 * @param array $args Array of parameters.
348 * @return string
349 */
350 public static function customizer_link( $args = array() ) {
351 $args = wp_parse_args( $args, array(
352 'return_url' => urlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
353 ) );
354
355 return add_query_arg(
356 array(
357 array(
358 'autofocus' => array(
359 'section' => 'custom_css',
360 ),
361 ),
362 'return' => $args['return_url'],
363 ),
364 admin_url( 'customize.php' )
365 );
366 }
367
368 /**
369 * Handle the enqueueing and localizing for scripts to be used in the Customizer.
370 */
371 public static function customize_controls_enqueue_scripts() {
372 wp_enqueue_style( 'jetpack-customizer-css' );
373 wp_enqueue_script( 'jetpack-customizer-css' );
374
375 $content_help = __( 'Set a different content width for full size images.', 'jetpack' );
376 if ( ! empty( $GLOBALS['content_width'] ) ) {
377 $content_help .= sprintf(
378 _n( ' The default content width for the <strong>%1$s</strong> theme is %2$d pixel.', ' The default content width for the <strong>%1$s</strong> theme is %2$d pixels.', intval( $GLOBALS['content_width'] ), 'jetpack' ),
379 wp_get_theme()->Name,
380 intval( $GLOBALS['content_width'] )
381 );
382 }
383
384 wp_localize_script( 'jetpack-customizer-css', '_jp_css_settings', array(
385 /** This filter is documented in modules/custom-css/custom-css.php */
386 'useRichEditor' => ! jetpack_is_mobile() && apply_filters( 'safecss_use_ace', true ),
387 'areThereCssRevisions' => self::are_there_css_revisions(),
388 'revisionsUrl' => self::get_revisions_url(),
389 'cssHelpUrl' => '//en.support.wordpress.com/custom-design/editing-css/',
390 'l10n' => array(
391 'mode' => __( 'Start Fresh', 'jetpack' ),
392 'mobile' => __( 'On Mobile', 'jetpack' ),
393 'contentWidth' => $content_help,
394 'revisions' => _x( 'See full history', 'Toolbar button to see full CSS revision history', 'jetpack' ),
395 'css_help_title' => _x( 'Help', 'Toolbar button to get help with custom CSS', 'jetpack' ),
396 ),
397 ));
398 }
399
400 /**
401 * Check whether there are CSS Revisions for a given theme.
402 *
403 * Going forward, there should always be, but this was necessitated
404 * early on by https://core.trac.wordpress.org/ticket/30854
405 *
406 * @param string $stylesheet Stylesheet name.
407 *
408 * @return bool|null|WP_Post
409 */
410 public static function are_there_css_revisions( $stylesheet = '' ) {
411 $post = wp_get_custom_css_post( $stylesheet );
412 if ( empty( $post ) ) {
413 return $post;
414 }
415 return (bool) wp_get_post_revisions( $post );
416 }
417
418 /**
419 * Core doesn't have a function to get the revisions url for a given post ID.
420 *
421 * @param string $stylesheet Stylesheet name.
422 *
423 * @return null|string|void
424 */
425 public static function get_revisions_url( $stylesheet = '' ) {
426 $post = wp_get_custom_css_post( $stylesheet );
427
428 // If we have any currently saved customizations...
429 if ( $post instanceof WP_Post ) {
430 $revisions = wp_get_post_revisions( $post->ID, array( 'posts_per_page' => 1 ) );
431 if ( empty( $revisions ) || is_wp_error( $revisions ) ) {
432 return admin_url( 'themes.php?page=editcss' );
433 }
434 $revision = reset( $revisions );
435 return get_edit_post_link( $revision->ID );
436 }
437
438 return admin_url( 'themes.php?page=editcss' );
439 }
440
441 /**
442 * Get a map of all theme names and theme stylesheets for mapping stuff.
443 *
444 * @return array
445 */
446 public static function get_themes() {
447 $themes = wp_get_themes( array( 'errors' => null ) );
448 $all = array();
449 foreach ( $themes as $theme ) {
450 $all[ $theme->name ] = $theme->stylesheet;
451 }
452 return $all;
453 }
454
455 /**
456 * When we need to get all themes that have Custom CSS saved.
457 *
458 * @return array
459 */
460 public static function get_all_themes_with_custom_css() {
461 $themes = self::get_themes();
462 $custom_css = get_posts( array(
463 'post_type' => 'custom_css',
464 'post_status' => get_post_stati(),
465 'number' => -1,
466 'order' => 'DESC',
467 'orderby' => 'modified',
468 ) );
469 $return = array();
470
471 foreach ( $custom_css as $post ) {
472 $stylesheet = $post->post_title;
473 $label = array_search( $stylesheet, $themes );
474
475 if ( ! $label ) {
476 continue;
477 }
478
479 $return[ $stylesheet ] = array(
480 'label' => $label,
481 'post' => $post,
482 );
483 }
484
485 return $return;
486 }
487
488 /**
489 * Handle the enqueueing of scripts for customize previews.
490 */
491 public static function wp_enqueue_scripts() {
492 if ( is_customize_preview() ) {
493 wp_enqueue_script( 'jetpack-customizer-css-preview' );
494 wp_localize_script( 'jetpack-customizer-css-preview', 'jpCustomizerCssPreview', array(
495 /** This filter is documented in modules/custom-css/custom-css.php */
496 'preprocessors' => apply_filters( 'jetpack_custom_css_preprocessors', array() ),
497 ));
498 }
499 }
500
501 /**
502 * Sanitize the CSS for users without `unfiltered_html`.
503 *
504 * @param string $css Input CSS.
505 * @param array $args Array of CSS options.
506 *
507 * @return mixed|string
508 */
509 public static function sanitize_css( $css, $args = array() ) {
510 $args = wp_parse_args( $args, array(
511 'force' => false,
512 'preprocessor' => null,
513 ) );
514
515 if ( $args['force'] || ! current_user_can( 'unfiltered_html' ) ) {
516
517 $warnings = array();
518
519 safecss_class();
520 $csstidy = new csstidy();
521 $csstidy->optimise = new safecss( $csstidy );
522
523 $csstidy->set_cfg( 'remove_bslash', false );
524 $csstidy->set_cfg( 'compress_colors', false );
525 $csstidy->set_cfg( 'compress_font-weight', false );
526 $csstidy->set_cfg( 'optimise_shorthands', 0 );
527 $csstidy->set_cfg( 'remove_last_;', false );
528 $csstidy->set_cfg( 'case_properties', false );
529 $csstidy->set_cfg( 'discard_invalid_properties', true );
530 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
531 $csstidy->set_cfg( 'preserve_css', true );
532 $csstidy->set_cfg( 'template', dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' );
533
534 // Test for some preg_replace stuff.
535 {
536 $prev = $css;
537 $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css );
538 // prevent content: '\3434' from turning into '\\3434'.
539 $css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css );
540 if ( $css !== $prev ) {
541 $warnings[] = 'preg_replace found stuff';
542 }
543 }
544
545 // Some people put weird stuff in their CSS, KSES tends to be greedy.
546 $css = str_replace( '<=', '&lt;=', $css );
547
548 // Test for some kses stuff.
549 {
550 $prev = $css;
551 // Why KSES instead of strip_tags? Who knows?
552 $css = wp_kses_split( $css, array(), array() );
553 $css = str_replace( '&gt;', '>', $css ); // kses replaces lone '>' with &gt;
554 // Why both KSES and strip_tags? Because we just added some '>'.
555 $css = strip_tags( $css );
556
557 if ( $css != $prev ) {
558 $warnings[] = 'kses found stuff';
559 }
560 }
561
562 // if we're not using a preprocessor.
563 if ( ! $args['preprocessor'] ) {
564
565 /** This action is documented in modules/custom-css/custom-css.php */
566 do_action( 'safecss_parse_pre', $csstidy, $css, $args );
567
568 $csstidy->parse( $css );
569
570 /** This action is documented in modules/custom-css/custom-css.php */
571 do_action( 'safecss_parse_post', $csstidy, $warnings, $args );
572
573 $css = $csstidy->print->plain();
574 }
575 }
576 return $css;
577 }
578
579 /**
580 * Override $content_width in customizer previews.
581 */
582 public static function preview_content_width() {
583 global $wp_customize;
584 if ( ! is_customize_preview() ) {
585 return;
586 }
587
588 $setting = $wp_customize->get_setting( 'jetpack_custom_css[content_width]' );
589 if ( ! $setting ) {
590 return;
591 }
592
593 $customized_content_width = (int) $setting->post_value();
594 if ( ! empty( $customized_content_width ) ) {
595 $GLOBALS['content_width'] = $customized_content_width;
596 }
597 }
598
599 /**
600 * Filter the current theme's stylesheet for potentially nullifying it.
601 *
602 * @param string $current Stylesheet URI for the current theme/child theme.
603 *
604 * @return mixed|void
605 */
606 static function style_filter( $current ) {
607 if ( is_admin() ) {
608 return $current;
609 } elseif ( self::is_freetrial() && ( ! self::is_preview() || ! current_user_can( 'switch_themes' ) ) ) {
610 return $current;
611 } elseif ( self::skip_stylesheet() ) {
612 /** This filter is documented in modules/custom-css/custom-css.php */
613 return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) );
614 }
615
616 return $current;
617 }
618
619 /**
620 * Determine whether or not we should have the theme skip its main stylesheet.
621 *
622 * @return mixed The truthiness of this value determines whether the stylesheet should be skipped.
623 */
624 static function skip_stylesheet() {
625 /** This filter is documented in modules/custom-css/custom-css.php */
626 $skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null );
627 if ( ! is_null( $skip_stylesheet ) ) {
628 return $skip_stylesheet;
629 }
630
631 $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
632 if ( isset( $jetpack_custom_css['replace'] ) ) {
633 return $jetpack_custom_css['replace'];
634 }
635
636 return false;
637 }
638
639 /**
640 * Override $content_width in customizer previews.
641 *
642 * Runs on `safecss_skip_stylesheet` filter.
643 *
644 * @param bool $skip_value Should the stylesheet be skipped.
645 *
646 * @return null|bool
647 */
648 public static function preview_skip_stylesheet( $skip_value ) {
649 global $wp_customize;
650 if ( ! is_customize_preview() ) {
651 return $skip_value;
652 }
653
654 $setting = $wp_customize->get_setting( 'jetpack_custom_css[replace]' );
655 if ( ! $setting ) {
656 return $skip_value;
657 }
658
659 $customized_replace = $setting->post_value();
660 if ( null !== $customized_replace ) {
661 return $customized_replace;
662 }
663
664 return $skip_value;
665 }
666
667 /**
668 * Add Custom CSS section and controls.
669 *
670 * @param WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
671 */
672 public static function customize_register( $wp_customize ) {
673
674 /**
675 * SETTINGS.
676 */
677
678 $wp_customize->add_setting( 'jetpack_custom_css[preprocessor]', array(
679 'default' => '',
680 'transport' => 'postMessage',
681 'sanitize_callback' => array( __CLASS__, 'sanitize_preprocessor' ),
682 ) );
683
684 $wp_customize->add_setting( 'jetpack_custom_css[replace]', array(
685 'default' => false,
686 'transport' => 'refresh',
687 ) );
688
689 $wp_customize->add_setting( 'jetpack_custom_css[content_width]', array(
690 'default' => '',
691 'transport' => 'refresh',
692 'sanitize_callback' => array( __CLASS__, 'intval_base10' ),
693 ) );
694
695 // Add custom sanitization to the core css customizer setting.
696 foreach ( $wp_customize->settings() as $setting ) {
697 if ( $setting instanceof WP_Customize_Custom_CSS_Setting ) {
698 add_filter( "customize_sanitize_{$setting->id}", array( __CLASS__, 'sanitize_css_callback' ), 10, 2 );
699 }
700 }
701
702 /**
703 * CONTROLS.
704 */
705
706 // Overwrite or Tweak the Core Control.
707 $core_custom_css = $wp_customize->get_control( 'custom_css' );
708 if ( $core_custom_css ) {
709 if ( $core_custom_css instanceof WP_Customize_Code_Editor_Control ) {
710 // In WP 4.9, we let the Core CodeMirror control keep running the show, but hook into it to tweak stuff.
711 $types = array(
712 'default' => 'text/css',
713 'less' => 'text/x-less',
714 'sass' => 'text/x-scss',
715 );
716 $preprocessor = $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value();
717 if ( isset( $types[ $preprocessor ] ) ) {
718 $core_custom_css->code_type = $types[ $preprocessor ];
719 }
720 } else {
721 // Core < 4.9 Fallback
722 $core_custom_css->type = 'jetpackCss';
723 }
724 }
725
726 $wp_customize->selective_refresh->add_partial( 'custom_css', array(
727 'type' => 'custom_css',
728 'selector' => '#wp-custom-css',
729 'container_inclusive' => false,
730 'fallback_refresh' => false,
731 'settings' => array(
732 'custom_css[' . $wp_customize->get_stylesheet() . ']',
733 'jetpack_custom_css[preprocessor]',
734 ),
735 'render_callback' => array( __CLASS__, 'echo_custom_css_partial' ),
736 ) );
737
738 $wp_customize->add_control( 'wpcom_custom_css_content_width_control', array(
739 'type' => 'text',
740 'label' => __( 'Media Width', 'jetpack' ),
741 'section' => 'custom_css',
742 'settings' => 'jetpack_custom_css[content_width]',
743 ) );
744
745 $wp_customize->add_control( 'jetpack_css_mode_control', array(
746 'type' => 'checkbox',
747 'label' => __( 'Don\'t use the theme\'s original CSS.', 'jetpack' ),
748 'section' => 'custom_css',
749 'settings' => 'jetpack_custom_css[replace]',
750 ) );
751
752 /**
753 * An action to grab on to if another Jetpack Module would like to add its own controls.
754 *
755 * @module custom-css
756 *
757 * @since 4.4.2
758 *
759 * @param $wp_customize The WP_Customize object.
760 */
761 do_action( 'jetpack_custom_css_customizer_controls', $wp_customize );
762
763 /** This filter is documented in modules/custom-css/custom-css.php */
764 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
765 if ( ! empty( $preprocessors ) ) {
766 $preprocessor_choices = array(
767 '' => __( 'None', 'jetpack' ),
768 );
769
770 foreach ( $preprocessors as $preprocessor_key => $processor ) {
771 $preprocessor_choices[ $preprocessor_key ] = $processor['name'];
772 }
773
774 $wp_customize->add_control( 'jetpack_css_preprocessors_control', array(
775 'type' => 'select',
776 'choices' => $preprocessor_choices,
777 'label' => __( 'Preprocessor', 'jetpack' ),
778 'section' => 'custom_css',
779 'settings' => 'jetpack_custom_css[preprocessor]',
780 ) );
781 }
782
783 }
784
785 /**
786 * The callback to handle sanitizing the CSS. Takes different arguments, hence the proxy function.
787 *
788 * @param mixed $css Value of the setting.
789 * @param WP_Customize_Setting $setting WP_Customize_Setting instance.
790 *
791 * @return mixed|string
792 */
793 public static function sanitize_css_callback( $css, $setting ) {
794 global $wp_customize;
795 return self::sanitize_css( $css, array(
796 'preprocessor' => $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value(),
797 ) );
798 }
799
800 /**
801 * Flesh out for wpcom.
802 *
803 * @todo
804 *
805 * @return bool
806 */
807 public static function is_freetrial() {
808 return false;
809 }
810
811 /**
812 * Flesh out for wpcom.
813 *
814 * @todo
815 *
816 * @return bool
817 */
818 public static function is_preview() {
819 return false;
820 }
821
822 /**
823 * Output the custom css for customize preview.
824 *
825 * @param string $css Custom CSS content.
826 *
827 * @return mixed
828 */
829 public static function customize_preview_wp_get_custom_css( $css ) {
830 global $wp_customize;
831
832 $preprocessor = $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value();
833
834 // If it's empty, just return.
835 if ( empty( $preprocessor ) ) {
836 return $css;
837 }
838
839 /** This filter is documented in modules/custom-css/custom-css.php */
840 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
841 if ( isset( $preprocessors[ $preprocessor ] ) ) {
842 return call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
843 }
844
845 return $css;
846 }
847
848 /**
849 * Add CSS preprocessing to our CSS if it is supported.
850 *
851 * @param mixed $css Value of the setting.
852 * @param WP_Customize_Setting $setting WP_Customize_Setting instance.
853 *
854 * @return string
855 */
856 public static function customize_value_custom_css( $css, $setting ) {
857 // Find the current preprocessor.
858 $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
859 if ( isset( $jetpack_custom_css['preprocessor'] ) ) {
860 $preprocessor = $jetpack_custom_css['preprocessor'];
861 }
862
863 // If it's not supported, just return.
864 /** This filter is documented in modules/custom-css/custom-css.php */
865 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
866 if ( ! isset( $preprocessors[ $preprocessor ] ) ) {
867 return $css;
868 }
869
870 // Swap it for the `post_content_filtered` instead.
871 $post = wp_get_custom_css_post( $setting->stylesheet );
872 if ( $post && ! empty( $post->post_content_filtered ) ) {
873 $css = $post->post_content_filtered;
874 }
875
876 return $css;
877 }
878
879 /**
880 * Store the original pre-processed CSS in `post_content_filtered`
881 * and then store processed CSS in `post_content`.
882 *
883 * @param array $args Content post args.
884 * @param string $css Original CSS being updated.
885 * @param WP_Customize_Custom_CSS_Setting $setting Custom CSS Setting.
886 *
887 * @return mixed
888 */
889 public static function customize_update_custom_css_post_content_args( $args, $css, $setting ) {
890 // Find the current preprocessor.
891 $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
892 if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
893 return $args;
894 }
895
896 $preprocessor = $jetpack_custom_css['preprocessor'];
897 /** This filter is documented in modules/custom-css/custom-css.php */
898 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
899
900 // If it's empty, just return.
901 if ( empty( $preprocessor ) ) {
902 return $args;
903 }
904
905 if ( isset( $preprocessors[ $preprocessor ] ) ) {
906 $args['post_content_filtered'] = $css;
907 $args['post_content'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
908 }
909
910 return $args;
911 }
912
913 /**
914 * Filter to handle the processing of preprocessed css on save.
915 *
916 * @param array $args Custom CSS options.
917 * @param string $stylesheet Original CSS to be updated.
918 *
919 * @return mixed
920 */
921 public static function update_custom_css_data( $args, $stylesheet ) {
922 // Find the current preprocessor.
923 $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
924 if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
925 return $args;
926 }
927
928 /** This filter is documented in modules/custom-css/custom-css.php */
929 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
930 $preprocessor = $jetpack_custom_css['preprocessor'];
931
932 // If we have a preprocessor specified ...
933 if ( isset( $preprocessors[ $preprocessor ] ) ) {
934 // And no other preprocessor has run ...
935 if ( empty( $args['preprocessed'] ) ) {
936 $args['preprocessed'] = $args['css'];
937 $args['css'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $args['css'] );
938 } else {
939 trigger_error( 'Jetpack CSS Preprocessor specified, but something else has already modified the argument.', E_USER_WARNING );
940 }
941 }
942
943 return $args;
944 }
945
946 /**
947 * When on the edit screen, make sure the custom content width
948 * setting is applied to the large image size.
949 *
950 * @param array $dims Array of image dimensions (width and height).
951 * @param string $size Size of the resulting image.
952 * @param null $context Context the image is being resized for. `edit` or `display`.
953 *
954 * @return array
955 */
956 static function editor_max_image_size( $dims, $size = 'medium', $context = null ) {
957 list( $width, $height ) = $dims;
958
959 if ( 'large' === $size && 'edit' === $context ) {
960 $width = Jetpack::get_content_width();
961 }
962
963 return array( $width, $height );
964 }
965
966 /**
967 * Override the content_width with a custom value if one is set.
968 *
969 * @param int $content_width Content Width value to be updated.
970 *
971 * @return int
972 */
973 static function jetpack_content_width( $content_width ) {
974 $custom_content_width = 0;
975
976 $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
977 if ( isset( $jetpack_custom_css['content_width'] ) ) {
978 $custom_content_width = $jetpack_custom_css['content_width'];
979 }
980
981 if ( $custom_content_width > 0 ) {
982 return $custom_content_width;
983 }
984
985 return $content_width;
986 }
987
988 /**
989 * Currently this filter function gets called on
990 * 'template_redirect' action and
991 * 'admin_init' action
992 */
993 static function set_content_width() {
994 // Don't apply this filter on the Edit CSS page.
995 if ( isset( $_GET['page'] ) && 'editcss' === $_GET['page'] && is_admin() ) {
996 return;
997 }
998
999 $GLOBALS['content_width'] = Jetpack::get_content_width();
1000 }
1001
1002 /**
1003 * Make sure the preprocessor we're saving is one we know about.
1004 *
1005 * @param string $preprocessor The preprocessor to sanitize.
1006 *
1007 * @return null|string
1008 */
1009 public static function sanitize_preprocessor( $preprocessor ) {
1010 /** This filter is documented in modules/custom-css/custom-css.php */
1011 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
1012 if ( empty( $preprocessor ) || array_key_exists( $preprocessor, $preprocessors ) ) {
1013 return $preprocessor;
1014 }
1015 return null;
1016 }
1017
1018 /**
1019 * Get the base10 intval.
1020 *
1021 * This is used as a setting's sanitize_callback; we can't use just plain
1022 * intval because the second argument is not what intval() expects.
1023 *
1024 * @access public
1025 *
1026 * @param mixed $value Number to convert.
1027 * @return int Integer.
1028 */
1029 public static function intval_base10( $value ) {
1030 return intval( $value, 10 );
1031 }
1032
1033 /**
1034 * Add a footer action on revision.php to print some customizations for the theme switcher.
1035 */
1036 public static function load_revision_php() {
1037 add_action( 'admin_footer', array( __CLASS__, 'revision_admin_footer' ) );
1038 }
1039
1040 /**
1041 * Print the theme switcher on revision.php and move it into place.
1042 */
1043 public static function revision_admin_footer() {
1044 $post = get_post();
1045 if ( 'custom_css' !== $post->post_type ) {
1046 return;
1047 }
1048 $stylesheet = $post->post_title;
1049 ?>
1050 <script type="text/html" id="tmpl-other-themes-switcher">
1051 <?php self::revisions_switcher_box( $stylesheet ); ?>
1052 </script>
1053 <style>
1054 .other-themes-wrap {
1055 float: right;
1056 background-color: #fff;
1057 -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1058 box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1059 padding: 5px 10px;
1060 margin-bottom: 10px;
1061 }
1062 .other-themes-wrap label {
1063 display: block;
1064 margin-bottom: 10px;
1065 }
1066 .other-themes-wrap select {
1067 float: left;
1068 width: 77%;
1069 }
1070 .other-themes-wrap button {
1071 float: right;
1072 width: 20%;
1073 }
1074 .revisions {
1075 clear: both;
1076 }
1077 /* Hide the back-to-post link */
1078 .long-header + a {
1079 display: none;
1080 }
1081 </style>
1082 <script>
1083 (function($){
1084 var switcher = $('#tmpl-other-themes-switcher').html(),
1085 qty = $( switcher ).find('select option').length,
1086 $switcher;
1087
1088 if ( qty >= 3 ) {
1089 $('h1.long-header').before( switcher );
1090 $switcher = $('.other-themes-wrap');
1091 $switcher.find('button').on('click', function(e){
1092 e.preventDefault();
1093 if ( $switcher.find('select').val() ) {
1094 window.location.href = $switcher.find('select').val();
1095 }
1096 })
1097 }
1098 })(jQuery);
1099 </script>
1100 <?php
1101 }
1102
1103 /**
1104 * The HTML for the theme revision switcher box.
1105 *
1106 * @param string $stylesheet Stylesheet name.
1107 */
1108 public static function revisions_switcher_box( $stylesheet = '' ) {
1109 $themes = self::get_all_themes_with_custom_css();
1110 ?>
1111 <div class="other-themes-wrap">
1112 <label for="other-themes"><?php esc_html_e( 'Select another theme to view its custom CSS.', 'jetpack' ); ?></label>
1113 <select id="other-themes">
1114 <option value=""><?php esc_html_e( 'Select a theme&hellip;', 'jetpack' ); ?></option>
1115 <?php
1116 foreach ( $themes as $theme_stylesheet => $data ) {
1117 $revisions = wp_get_post_revisions( $data['post']->ID, array( 'posts_per_page' => 1 ) );
1118 if ( ! $revisions ) {
1119 ?>
1120 <option value="<?php echo esc_url( add_query_arg( 'id', $data['post']->ID, menu_page_url( 'editcss', 0 ) ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
1121 <?php echo esc_html( $data['label'] ); ?>
1122 <?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
1123 <?php
1124 continue;
1125 }
1126 $revision = array_shift( $revisions );
1127 ?>
1128 <option value="<?php echo esc_url( get_edit_post_link( $revision->ID ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
1129 <?php echo esc_html( $data['label'] ); ?>
1130 <?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
1131 <?php
1132 }
1133 ?>
1134 </select>
1135 <button class="button" id="other_theme_custom_css_switcher"><?php esc_html_e( 'Switch', 'jetpack' ); ?></button>
1136 </div>
1137 <?php
1138 }
1139 }
1140
1141 Jetpack_Custom_CSS_Enhancements::add_hooks();
1142
1143 if ( ! function_exists( 'safecss_class' ) ) :
1144 /**
1145 * Load in the class only when needed. Makes lighter load by having one less class in memory.
1146 */
1147 function safecss_class() {
1148 // Wrapped so we don't need the parent class just to load the plugin.
1149 if ( class_exists( 'safecss' ) ) {
1150 return;
1151 }
1152
1153 require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' );
1154
1155 /**
1156 * Class safecss
1157 */
1158 class safecss extends csstidy_optimise {
1159
1160 /**
1161 * Optimises $css after parsing.
1162 */
1163 function postparse() {
1164
1165 /** This action is documented in modules/custom-css/custom-css.php */
1166 do_action( 'csstidy_optimize_postparse', $this );
1167
1168 return parent::postparse();
1169 }
1170
1171 /**
1172 * Optimises a sub-value.
1173 */
1174 function subvalue() {
1175
1176 /** This action is documented in modules/custom-css/custom-css.php */
1177 do_action( 'csstidy_optimize_subvalue', $this );
1178
1179 return parent::subvalue();
1180 }
1181 }
1182 }
1183 endif;
1184