PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 10.5.3
Jetpack – WP Security, Backup, Speed, & Growth v10.5.3
16.2-beta 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 All 501 releases
jetpack / modules / custom-css / custom-css.php
custom-css.php
1,676 lines 53.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Automattic\Jetpack\Assets;
4 use Automattic\Jetpack\Redirect;
5 use Automattic\Jetpack\Device_Detection\User_Agent_Info;
6
7 class Jetpack_Custom_CSS {
8 static function init() {
9 add_action( 'switch_theme', array( __CLASS__, 'reset' ) );
10 add_action( 'wp_restore_post_revision', array( __CLASS__, 'restore_revision' ), 10, 2 );
11
12 // Save revisions for posts of type safecss.
13 add_action( 'load-revision.php', array( __CLASS__, 'add_revision_redirect' ) );
14
15 // Override the edit link, the default link causes a redirect loop
16 add_filter( 'get_edit_post_link', array( __CLASS__, 'revision_post_link' ), 10, 3 );
17
18 // Overwrite the content width global variable if one is set in the custom css
19 add_action( 'template_redirect', array( __CLASS__, 'set_content_width' ) );
20 add_action( 'admin_init', array( __CLASS__, 'set_content_width' ) );
21
22 if ( ! is_admin() )
23 add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) );
24
25 define(
26 'SAFECSS_USE_ACE',
27 ! jetpack_is_mobile() &&
28 ! User_Agent_Info::is_ipad() &&
29 /**
30 * Should the Custom CSS module use ACE to process CSS.
31 * @see https://ace.c9.io/
32 *
33 * @module custom-css
34 *
35 * @since 1.7.0
36 *
37 * @param bool true Use ACE to process the Custom CSS. Default to true.
38 */
39 apply_filters( 'safecss_use_ace', true )
40 );
41
42 // Register safecss as a custom post_type
43 // Explicit capability definitions are largely unnecessary because the posts are manipulated in code via an options page, managing CSS revisions does check the capabilities, so let's ensure that the proper caps are checked.
44 register_post_type( 'safecss', array(
45 // These are the defaults
46 // 'exclude_from_search' => true,
47 // 'public' => false,
48 // 'publicly_queryable' => false,
49 // 'show_ui' => false,
50 'supports' => array( 'revisions' ),
51 'label' => 'Custom CSS',
52 'can_export' => false,
53 'rewrite' => false,
54 'capabilities' => array(
55 'edit_post' => 'edit_theme_options',
56 'read_post' => 'read',
57 'delete_post' => 'edit_theme_options',
58 'edit_posts' => 'edit_theme_options',
59 'edit_others_posts' => 'edit_theme_options',
60 'publish_posts' => 'edit_theme_options',
61 'read_private_posts' => 'read'
62 )
63 ) );
64
65 // Short-circuit WP if this is a CSS stylesheet request
66 if ( isset( $_GET['custom-css'] ) ) {
67 header( 'Content-Type: text/css', true, 200 );
68 header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000) . ' GMT' ); // 1 year
69 Jetpack_Custom_CSS::print_css();
70 exit;
71 }
72
73 add_action( 'admin_enqueue_scripts', array( 'Jetpack_Custom_CSS', 'enqueue_scripts' ) );
74
75 if ( isset( $_GET['page'] ) && 'editcss' == $_GET['page'] && is_admin() ) {
76 // Do migration routine if necessary
77 Jetpack_Custom_CSS::upgrade();
78
79 /**
80 * Allows additional work when migrating safecss from wp_options to wp_post.
81 *
82 * @module custom-css
83 *
84 * @since 1.7.0
85 */
86 do_action( 'safecss_migrate_post' );
87 }
88
89 /**
90 * Never embed the style in the head on wpcom.
91 * Yes, this filter should be added to an unsynced file on wpcom, but
92 * there is no good syntactically-correct location to put it yet.
93 * @link https://github.com/Automattic/jetpack/commit/a1be114e9179f64d147124727a58e2cf76c7e5a1#commitcomment-7763921
94 */
95 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
96 add_filter( 'safecss_embed_style', '__return_false' );
97 } else {
98 add_filter( 'safecss_embed_style', array( 'Jetpack_Custom_CSS', 'should_we_inline_custom_css' ), 10, 2 );
99 }
100
101 add_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 );
102
103 add_filter( 'jetpack_content_width', array( 'Jetpack_Custom_CSS', 'jetpack_content_width' ) );
104 add_filter( 'editor_max_image_size', array( 'Jetpack_Custom_CSS', 'editor_max_image_size' ), 10, 3 );
105
106 if ( !current_user_can( 'switch_themes' ) && !is_super_admin() )
107 return;
108
109 add_action( 'admin_menu', array( 'Jetpack_Custom_CSS', 'menu' ) );
110
111 if ( isset( $_POST['safecss'] ) && false == strstr( $_SERVER[ 'REQUEST_URI' ], 'options.php' ) ) {
112 check_admin_referer( 'safecss' );
113
114 $save_result = self::save( array(
115 'css' => stripslashes( $_POST['safecss'] ),
116 'is_preview' => isset( $_POST['action'] ) && $_POST['action'] == 'preview',
117 'preprocessor' => isset( $_POST['custom_css_preprocessor'] ) ? $_POST['custom_css_preprocessor'] : '',
118 'add_to_existing' => isset( $_POST['add_to_existing'] ) ? $_POST['add_to_existing'] == 'true' : true,
119 'content_width' => isset( $_POST['custom_content_width'] ) ? $_POST['custom_content_width'] : false,
120 ) );
121
122 if ( $_POST['action'] == 'preview' ) {
123 wp_safe_redirect( add_query_arg( 'csspreview', 'true', get_option( 'home' ) ) );
124 exit;
125 }
126
127 if ( $save_result )
128 add_action( 'admin_notices', array( 'Jetpack_Custom_CSS', 'saved_message' ) );
129 }
130
131 // Prevent content filters running on CSS when restoring revisions
132 if ( isset( $_REQUEST[ 'action' ] ) && 'restore' === $_REQUEST[ 'action' ] && false !== strstr( $_SERVER[ 'REQUEST_URI' ], 'revision.php' ) ) {
133 $parent_post = get_post( wp_get_post_parent_id( (int) $_REQUEST['revision'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
134 if ( $parent_post && ! is_wp_error( $parent_post ) && 'safecss' === $parent_post->post_type ) {
135 // Remove wp_filter_post_kses, this causes CSS escaping issues
136 remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
137 remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
138 remove_all_filters( 'content_save_pre' );
139 }
140 }
141
142 // Modify all internal links so that preview state persists
143 if ( Jetpack_Custom_CSS::is_preview() )
144 ob_start( array( 'Jetpack_Custom_CSS', 'buffer' ) );
145 }
146
147 /**
148 * Save new custom CSS. This should be the entry point for any third-party code using Jetpack_Custom_CSS
149 * to save CSS.
150 *
151 * @param array $args Array of arguments:
152 * string $css The CSS (or LESS or Sass)
153 * bool $is_preview Whether this CSS is preview or published
154 * string preprocessor Which CSS preprocessor to use
155 * bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it.
156 * int $content_width A custom $content_width to go along with this CSS.
157 * @return int The post ID of the saved Custom CSS post.
158 */
159 public static function save( $args = array() ) {
160 $defaults = array(
161 'css' => '',
162 'is_preview' => false,
163 'preprocessor' => '',
164 'add_to_existing' => true,
165 'content_width' => false,
166 );
167
168 $args = wp_parse_args( $args, $defaults );
169
170 if (
171 $args['content_width']
172 && (int) $args['content_width'] > 0
173 && (
174 ! isset( $GLOBALS['content_width'] )
175 || $args['content_width'] !== $GLOBALS['content_width']
176 )
177 ) {
178 $args['content_width'] = (int) $args['content_width'];
179 } else {
180 $args['content_width'] = false;
181 }
182
183 // Remove wp_filter_post_kses, this causes CSS escaping issues
184 remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
185 remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
186 remove_all_filters( 'content_save_pre' );
187
188 /**
189 * Fires prior to saving custom css values. Necessitated because the
190 * core WordPress save_pre filters were removed:
191 * - content_save_pre
192 * - content_filtered_save_pre
193 *
194 * @module custom-css
195 *
196 * @since 1.7.0
197 *
198 * @param array $args {
199 * Array of custom CSS arguments.
200 * @type string $css The CSS (or LESS or Sass).
201 * @type bool $is_preview Whether this CSS is preview or published.
202 * @type string preprocessor Which CSS preprocessor to use.
203 * @type bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it.
204 * @type int $content_width A custom $content_width to go along with this CSS.
205 * }
206 */
207 do_action( 'safecss_save_pre', $args );
208
209 $warnings = array();
210
211 safecss_class();
212 $csstidy = new csstidy();
213 $csstidy->optimise = new safecss( $csstidy );
214
215 $csstidy->set_cfg( 'remove_bslash', false );
216 $csstidy->set_cfg( 'compress_colors', false );
217 $csstidy->set_cfg( 'compress_font-weight', false );
218 $csstidy->set_cfg( 'optimise_shorthands', 0 );
219 $csstidy->set_cfg( 'remove_last_;', false );
220 $csstidy->set_cfg( 'case_properties', false );
221 $csstidy->set_cfg( 'discard_invalid_properties', true );
222 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
223 $csstidy->set_cfg( 'preserve_css', true );
224 $csstidy->set_cfg( 'template', dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' );
225
226 $css = $orig = $args['css'];
227
228 $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $prev = $css );
229 // prevent content: '\3434' from turning into '\\3434'
230 $css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css );
231
232 if ( $css != $prev )
233 $warnings[] = 'preg_replace found stuff';
234
235 // Some people put weird stuff in their CSS, KSES tends to be greedy
236 $css = str_replace( '<=', '&lt;=', $css );
237 // Why KSES instead of strip_tags? Who knows?
238 $css = wp_kses_split( $prev = $css, array(), array() );
239 $css = str_replace( '&gt;', '>', $css ); // kses replaces lone '>' with &gt;
240 // Why both KSES and strip_tags? Because we just added some '>'.
241 $css = strip_tags( $css );
242
243 if ( $css != $prev )
244 $warnings[] = 'kses found stuff';
245
246 // if we're not using a preprocessor
247 if ( ! $args['preprocessor'] ) {
248
249 /**
250 * Fires before parsing the css with CSSTidy, but only if
251 * the preprocessor is not configured for use.
252 *
253 * @module custom-css
254 *
255 * @since 1.7.0
256 *
257 * @param obj $csstidy The csstidy object.
258 * @param string $css Custom CSS.
259 * @param array $args Array of custom CSS arguments.
260 */
261 do_action( 'safecss_parse_pre', $csstidy, $css, $args );
262
263 $csstidy->parse( $css );
264
265 /**
266 * Fires after parsing the css with CSSTidy, but only if
267 * the preprocessor is not cinfigured for use.
268 *
269 * @module custom-css
270 *
271 * @since 1.7.0
272 *
273 * @param obj $csstidy The csstidy object.
274 * @param array $warnings Array of warnings.
275 * @param array $args Array of custom CSS arguments.
276 */
277 do_action( 'safecss_parse_post', $csstidy, $warnings, $args );
278
279 $css = $csstidy->print->plain();
280 }
281
282 if ( $args['add_to_existing'] )
283 $add_to_existing = 'yes';
284 else
285 $add_to_existing = 'no';
286
287 if ( $args['is_preview'] || Jetpack_Custom_CSS::is_freetrial() ) {
288 // Save the CSS
289 $safecss_revision_id = Jetpack_Custom_CSS::save_revision( $css, true, $args['preprocessor'] );
290
291 // Cache Buster
292 update_option( 'safecss_preview_rev', (int) get_option( 'safecss_preview_rev' ) + 1 );
293
294 update_metadata( 'post', $safecss_revision_id, 'custom_css_add', $add_to_existing );
295 update_metadata( 'post', $safecss_revision_id, 'content_width', $args['content_width'] );
296 update_metadata( 'post', $safecss_revision_id, 'custom_css_preprocessor', $args['preprocessor'] );
297
298 delete_option( 'safecss_add' );
299 delete_option( 'safecss_content_width' );
300
301 if ( $args['is_preview'] ) {
302 return $safecss_revision_id;
303 }
304
305 /**
306 * Fires after saving Custom CSS.
307 *
308 * @module custom-css
309 *
310 * @since 1.7.0
311 */
312 do_action( 'safecss_save_preview_post' );
313 }
314
315 // Save the CSS
316 $safecss_post_id = Jetpack_Custom_CSS::save_revision( $css, false, $args['preprocessor'] );
317
318 $safecss_post_revision = Jetpack_Custom_CSS::get_current_revision();
319
320 update_option( 'safecss_rev', (int) get_option( 'safecss_rev' ) + 1 );
321
322 update_post_meta( $safecss_post_id, 'custom_css_add', $add_to_existing );
323 update_post_meta( $safecss_post_id, 'content_width', $args['content_width'] );
324 update_post_meta( $safecss_post_id, 'custom_css_preprocessor', $args['preprocessor'] );
325
326 delete_option( 'safecss_add' );
327 delete_option( 'safecss_content_width' );
328
329 update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_add', $add_to_existing );
330 update_metadata( 'post', $safecss_post_revision['ID'], 'content_width', $args['content_width'] );
331 update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_preprocessor', $args['preprocessor'] );
332
333 delete_option( 'safecss_preview_add' );
334
335 return $safecss_post_id;
336 }
337
338 /**
339 * Get the published custom CSS post.
340 *
341 * @return array
342 */
343 static function get_post() {
344 $custom_css_post_id = Jetpack_Custom_CSS::post_id();
345
346 if ( $custom_css_post_id )
347 return get_post( $custom_css_post_id, ARRAY_A );
348
349 return array();
350 }
351
352 /**
353 * Get the post ID of the published custom CSS post.
354 *
355 * @return int|bool The post ID if it exists; false otherwise.
356 */
357 static function post_id() {
358 /**
359 * Filter the ID of the post where Custom CSS is stored, before the ID is retrieved.
360 *
361 * If the callback function returns a non-null value, then post_id() will immediately
362 * return that value, instead of retrieving the normal post ID.
363 *
364 * @module custom-css
365 *
366 * @since 3.8.1
367 *
368 * @param null null The ID to return instead of the normal ID.
369 */
370 $custom_css_post_id = apply_filters( 'jetpack_custom_css_pre_post_id', null );
371 if ( ! is_null( $custom_css_post_id ) ) {
372 return $custom_css_post_id;
373 }
374
375 $custom_css_post_id = wp_cache_get( 'custom_css_post_id' );
376
377 if ( false === $custom_css_post_id ) {
378 $custom_css_posts = get_posts( array(
379 'posts_per_page' => 1,
380 'post_type' => 'safecss',
381 'post_status' => 'publish',
382 'orderby' => 'date',
383 'order' => 'DESC'
384 ) );
385
386 if ( count( $custom_css_posts ) > 0 )
387 $custom_css_post_id = $custom_css_posts[0]->ID;
388 else
389 $custom_css_post_id = 0;
390
391 // Save post_id=0 to note that no safecss post exists.
392 wp_cache_set( 'custom_css_post_id', $custom_css_post_id );
393 }
394
395 if ( ! $custom_css_post_id )
396 return false;
397
398 return $custom_css_post_id;
399 }
400
401 /**
402 * Get the current revision of the original safecss record
403 *
404 * @return object
405 */
406 static function get_current_revision() {
407 $safecss_post = Jetpack_Custom_CSS::get_post();
408
409 if ( empty( $safecss_post ) ) {
410 return false;
411 }
412
413 $revisions = wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC' ) );
414
415 // Empty array if no revisions exist
416 if ( empty( $revisions ) ) {
417 // Return original post
418 return $safecss_post;
419 } else {
420 // Return the first entry in $revisions, this will be the current revision
421 $current_revision = get_object_vars( array_shift( $revisions ) );
422 return $current_revision;
423 }
424 }
425
426 /**
427 * Save new revision of CSS
428 * Checks to see if content was modified before really saving
429 *
430 * @param string $css
431 * @param bool $is_preview
432 * @return bool|int If nothing was saved, returns false. If a post
433 * or revision was saved, returns the post ID.
434 */
435 static function save_revision( $css, $is_preview = false, $preprocessor = '' ) {
436 $safecss_post = Jetpack_Custom_CSS::get_post();
437
438 $compressed_css = Jetpack_Custom_CSS::minify( $css, $preprocessor );
439
440 // If null, there was no original safecss record, so create one
441 if ( null == $safecss_post ) {
442 if ( ! $css )
443 return false;
444
445 $post = array();
446 $post['post_content'] = wp_slash( $css );
447 $post['post_title'] = 'safecss';
448 $post['post_status'] = 'publish';
449 $post['post_type'] = 'safecss';
450 $post['post_content_filtered'] = wp_slash( $compressed_css );
451
452 // Set excerpt to current theme, for display in revisions list
453 $current_theme = wp_get_theme();
454 $post['post_excerpt'] = $current_theme->Name;
455
456 // Insert the CSS into wp_posts
457 $post_id = wp_insert_post( $post );
458 wp_cache_set( 'custom_css_post_id', $post_id );
459 return $post_id;
460 }
461
462 // Update CSS in post array with new value passed to this function
463 $safecss_post['post_content'] = $css;
464 $safecss_post['post_content_filtered'] = $compressed_css;
465
466 // Set excerpt to current theme, for display in revisions list
467 $current_theme = wp_get_theme();
468 $safecss_post['post_excerpt'] = $current_theme->Name;
469
470 // Don't carry over last revision's timestamps, otherwise revisions all have matching timestamps
471 unset( $safecss_post['post_date'] );
472 unset( $safecss_post['post_date_gmt'] );
473 unset( $safecss_post['post_modified'] );
474 unset( $safecss_post['post_modified_gmt'] );
475
476 // Do not update post if we are only saving a preview
477 if ( false === $is_preview ) {
478 $safecss_post['post_content'] = wp_slash( $safecss_post['post_content'] );
479 $safecss_post['post_content_filtered'] = wp_slash( $safecss_post['post_content_filtered'] );
480 $post_id = wp_update_post( $safecss_post );
481 wp_cache_set( 'custom_css_post_id', $post_id );
482 return $post_id;
483 }
484 else if ( ! defined( 'DOING_MIGRATE' ) ) {
485 return _wp_put_post_revision( $safecss_post );
486 }
487 }
488
489 static function skip_stylesheet() {
490 /**
491 * Prevent the Custom CSS stylesheet from being enqueued.
492 *
493 * @module custom-css
494 *
495 * @since 2.2.1
496 *
497 * @param null Should the stylesheet be skipped. Default to null. Anything else will force the stylesheet to be skipped.
498 */
499 $skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null );
500
501 if ( null !== $skip_stylesheet ) {
502 return $skip_stylesheet;
503 } elseif ( Jetpack_Custom_CSS::is_customizer_preview() ) {
504 return false;
505 } else {
506 if ( Jetpack_Custom_CSS::is_preview() ) {
507 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
508
509 if ( $safecss_post )
510 return (bool) ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) == 'no' );
511 else
512 return (bool) ( get_option( 'safecss_preview_add' ) == 'no' );
513 }
514 else {
515 $custom_css_post_id = Jetpack_Custom_CSS::post_id();
516
517 if ( $custom_css_post_id ) {
518 $custom_css_add = get_post_meta( $custom_css_post_id, 'custom_css_add', true );
519
520 // It is possible for the CSS to be stored in a post but for the safecss_add option
521 // to have not been upgraded yet if the user hasn't opened their Custom CSS editor
522 // since October 2012.
523 if ( ! empty( $custom_css_add ) )
524 return (bool) ( $custom_css_add === 'no' );
525 }
526
527 return (bool) ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_add', '' ) == 'no' );
528 }
529 }
530 }
531
532 static function is_preview() {
533 return isset( $_GET['csspreview'] ) && $_GET['csspreview'] === 'true';
534 }
535
536 /**
537 * Currently this filter function gets called on
538 * 'template_redirect' action and
539 * 'admin_init' action
540 */
541 static function set_content_width(){
542 // Don't apply this filter on the Edit CSS page
543 if ( isset( $_GET ) && isset( $_GET['page'] ) && 'editcss' == $_GET['page'] && is_admin() ) {
544 return;
545 }
546
547 $GLOBALS['content_width'] = Jetpack::get_content_width();
548 }
549
550 /*
551 * False when the site has the Custom Design upgrade.
552 * Used only on WordPress.com.
553 */
554 static function is_freetrial() {
555 /**
556 * Determine if a WordPress.com site uses a Free trial of the Custom Design Upgrade.
557 * Used only on WordPress.com.
558 *
559 * @module custom-css
560 *
561 * @since 1.7.0
562 *
563 * @param bool false Does the site use a Free trial of the Custom Design Upgrade. Default to false.
564 */
565 return apply_filters( 'safecss_is_freetrial', false );
566 }
567
568 static function get_preprocessor_key() {
569 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
570 return get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true );
571 }
572
573 static function get_preprocessor() {
574 /** This filter is documented in modules/custom-css/custom-css.php */
575 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
576 $selected_preprocessor_key = self::get_preprocessor_key();
577 $selected_preprocessor = isset( $preprocessors[ $selected_preprocessor_key ] ) ? $preprocessors[ $selected_preprocessor_key ] : null;
578 return $selected_preprocessor;
579 }
580
581 static function get_css( $compressed = false ) {
582 /**
583 * Filter the Custom CSS returned.
584 * Can be used to return an error, or no CSS at all.
585 *
586 * @module custom-css
587 *
588 * @since 1.7.0
589 *
590 * @param bool false Should we return an error instead of the Custom CSS. Default to false.
591 */
592 $default_css = apply_filters( 'safecss_get_css_error', false );
593
594 if ( $default_css !== false )
595 return $default_css;
596
597 $option = ( Jetpack_Custom_CSS::is_preview() || Jetpack_Custom_CSS::is_freetrial() ) ? 'safecss_preview' : 'safecss';
598 $css = '';
599
600 if ( 'safecss' == $option ) {
601 // Don't bother checking for a migrated 'safecss' option if it never existed.
602 if ( false === get_option( 'safecss' ) || get_option( 'safecss_revision_migrated' ) ) {
603 $safecss_post = Jetpack_Custom_CSS::get_post();
604 if ( ! empty( $safecss_post ) ) {
605 $css = ( $compressed && $safecss_post['post_content_filtered'] ) ? $safecss_post['post_content_filtered'] : $safecss_post['post_content'];
606 }
607 } else {
608 $current_revision = Jetpack_Custom_CSS::get_current_revision();
609 if ( false === $current_revision ) {
610 $css = '';
611 } else {
612 $css = ( $compressed && $current_revision['post_content_filtered'] ) ? $current_revision['post_content_filtered'] : $current_revision['post_content'];
613 }
614 }
615
616 // Fix for un-migrated Custom CSS
617 if ( empty( $safecss_post ) ) {
618 $_css = get_option( 'safecss' );
619 if ( !empty( $_css ) ) {
620 $css = $_css;
621 }
622 }
623 }
624 else if ( 'safecss_preview' == $option ) {
625 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
626 $css = $safecss_post['post_content'];
627 $css = Jetpack_Custom_CSS::minify( $css, get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ) );
628 }
629
630 $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css );
631
632 /**
633 * Filter the Custom CSS returned from the editor.
634 *
635 * @module custom-css
636 *
637 * @since 1.7.0
638 *
639 * @param string $css Custom CSS.
640 */
641 $css = apply_filters( 'safecss_css', $css );
642
643 return $css;
644 }
645
646 static function replace_insecure_urls( $css ) {
647 if ( ! function_exists( '_sa_get_frontend_https_url_replacement_map' ) ) {
648 return $css;
649 }
650 list( $http_urls, $secure_urls ) = _sa_get_frontend_https_url_replacement_map();
651
652 return str_replace( $http_urls, $secure_urls, $css );
653 }
654
655 static function print_css() {
656
657 /**
658 * Fires right before printing the custom CSS inside the <head> element.
659 *
660 * @module custom-css
661 *
662 * @since 1.7.0
663 */
664 do_action( 'safecss_print_pre' );
665 $css = Jetpack_Custom_CSS::get_css( true );
666 echo self::replace_insecure_urls( $css );
667 }
668
669 static function should_we_inline_custom_css( $should_we, $css ) {
670 // If the CSS is less than 2,000 characters, inline it! otherwise return what was passed in.
671 return ( strlen( $css ) < 2000 ) ? true : $should_we;
672 }
673
674 static function link_tag() {
675 global $blog_id, $current_blog;
676
677 if (
678 /**
679 * Do not include any CSS on the page if the CSS includes an error.
680 * Setting this filter to true stops any Custom CSS from being enqueued.
681 *
682 * @module custom-css
683 *
684 * @since 1.7.0
685 *
686 * @param bool false Does the CSS include an error. Default to false.
687 */
688 apply_filters( 'safecss_style_error', false )
689 ) {
690 return;
691 }
692
693 if ( ! is_super_admin() && isset( $current_blog ) && ( 1 == $current_blog->spam || 1 == $current_blog->deleted ) )
694 return;
695
696 if ( Jetpack_Custom_CSS::is_customizer_preview() )
697 return;
698
699 $css = '';
700 $option = Jetpack_Custom_CSS::is_preview() ? 'safecss_preview' : 'safecss';
701
702 if ( 'safecss' == $option ) {
703 if ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_revision_migrated', '0' ) ) {
704 $safecss_post = Jetpack_Custom_CSS::get_post();
705
706 if ( ! empty( $safecss_post['post_content'] ) ) {
707 $css = $safecss_post['post_content'];
708 }
709 } else {
710 $current_revision = Jetpack_Custom_CSS::get_current_revision();
711
712 if ( ! empty( $current_revision['post_content'] ) ) {
713 $css = $current_revision['post_content'];
714 }
715 }
716
717 // Fix for un-migrated Custom CSS
718 if ( empty( $safecss_post ) ) {
719 $_css = Jetpack_Options::get_option_and_ensure_autoload( 'safecss', '' );
720 if ( !empty( $_css ) ) {
721 $css = $_css;
722 }
723 }
724 }
725
726 if ( 'safecss_preview' == $option ) {
727 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
728
729 if ( !empty( $safecss_post['post_content'] ) ) {
730 $css = $safecss_post['post_content'];
731 }
732 }
733
734 $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css );
735
736 if ( $css == '' )
737 return;
738
739 if (
740 /**
741 * Allow inserting CSS inline instead of through a separate file.
742 *
743 * @module custom-css
744 *
745 * @since 3.4.0
746 *
747 * @param bool false Should the CSS be added inline instead of through a separate file. Default to false.
748 * @param string $css Custom CSS.
749 */
750 apply_filters( 'safecss_embed_style', false, $css )
751 ) {
752
753 echo "\r\n" . '<style id="custom-css-css">' . Jetpack_Custom_CSS::get_css( true ) . "</style>\r\n";
754
755 } else {
756
757 $href = home_url( '/' );
758 $href = add_query_arg( 'custom-css', 1, $href );
759 $href = add_query_arg( 'csblog', $blog_id, $href );
760 $href = add_query_arg( 'cscache', 6, $href );
761 $href = add_query_arg( 'csrev', (int) get_option( $option . '_rev' ), $href );
762
763 /**
764 * Filter the Custom CSS link enqueued in the head.
765 *
766 * @module custom-css
767 *
768 * @since 1.7.0
769 *
770 * @param string $href Custom CSS link enqueued in the head.
771 * @param string $blog_id Blog ID.
772 */
773 $href = apply_filters( 'safecss_href', $href, $blog_id );
774
775 if ( Jetpack_Custom_CSS::is_preview() )
776 $href = add_query_arg( 'csspreview', 'true', $href );
777
778 ?>
779 <link rel="stylesheet" id="custom-css-css" type="text/css" href="<?php echo esc_url( $href ); ?>" />
780 <?php
781
782 }
783
784 /**
785 * Fires after creating the <link> in the <head> element for the custom css stylesheet.
786 *
787 * @module custom-css
788 *
789 * @since 2.2.2
790 */
791 do_action( 'safecss_link_tag_post' );
792 }
793
794 static function style_filter( $current ) {
795 if ( Jetpack_Custom_CSS::is_freetrial() && ( ! Jetpack_Custom_CSS::is_preview() || ! current_user_can( 'switch_themes' ) ) )
796 return $current;
797 else if ( Jetpack_Custom_CSS::skip_stylesheet() )
798 /**
799 * Filter the default blank Custom CSS URL.
800 *
801 * @module custom-css
802 *
803 * @since 2.2.1
804 *
805 * @param string $url Default blank Custom CSS URL.
806 */
807 return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) );
808
809 return $current;
810 }
811
812 static function buffer( $html ) {
813 $html = str_replace( '</body>', Jetpack_Custom_CSS::preview_flag(), $html );
814 return preg_replace_callback( '!href=([\'"])(.*?)\\1!', array( 'Jetpack_Custom_CSS', 'preview_links' ), $html );
815 }
816
817 static function preview_links( $matches ) {
818 if ( 0 !== strpos( $matches[2], get_option( 'home' ) ) )
819 return $matches[0];
820
821 $link = wp_specialchars_decode( $matches[2] );
822 $link = add_query_arg( 'csspreview', 'true', $link );
823 $link = esc_url( $link );
824 return "href={$matches[1]}$link{$matches[1]}";
825 }
826
827 /**
828 * Places a black bar above every preview page
829 */
830 static function preview_flag() {
831 if ( is_admin() )
832 return;
833
834 $message = esc_html__( 'Preview: changes must be saved or they will be lost', 'jetpack' );
835 /**
836 * Filter the Preview message displayed on the site when previewing custom CSS, before to save it.
837 *
838 * @module custom-css
839 *
840 * @since 1.7.0
841 *
842 * @param string $message Custom CSS preview message.
843 */
844 $message = apply_filters( 'safecss_preview_message', $message );
845
846 $preview_flag_js = "var flag = document.createElement('div');
847 flag.innerHTML = " . json_encode( $message ) . ";
848 flag.style.background = '#FF6600';
849 flag.style.color = 'white';
850 flag.style.textAlign = 'center';
851 flag.style.fontSize = '15px';
852 flag.style.padding = '2px';
853 flag.style.fontFamily = 'sans-serif';
854 document.body.style.paddingTop = '0px';
855 document.body.insertBefore(flag, document.body.childNodes[0]);
856 ";
857
858 /**
859 * Filter the Custom CSS preview message JS styling.
860 *
861 * @module custom-css
862 *
863 * @since 1.7.0
864 *
865 * @param string $preview_flag_js Custom CSS preview message JS styling.
866 */
867 $preview_flag_js = apply_filters( 'safecss_preview_flag_js', $preview_flag_js );
868 if ( $preview_flag_js ) {
869 $preview_flag_js = '<script type="text/javascript">
870 // <![CDATA[
871 ' . $preview_flag_js . '
872 // ]]>
873 </script>';
874 }
875
876 return $preview_flag_js;
877 }
878
879 static function menu() {
880 $parent = 'themes.php';
881 $title = __( 'Additional CSS', 'jetpack' );
882 $hook = add_theme_page( $title, $title, 'edit_theme_options', 'editcss', array( 'Jetpack_Custom_CSS', 'admin' ) );
883
884 add_action( "load-revision.php", array( 'Jetpack_Custom_CSS', 'prettify_post_revisions' ) );
885 add_action( "load-$hook", array( 'Jetpack_Custom_CSS', 'update_title' ) );
886 }
887
888 /**
889 * Adds a menu item in the appearance section for this plugin's administration
890 * page. Also adds hooks to enqueue the CSS and JS for the admin page.
891 */
892 static function update_title() {
893 global $title;
894 $title = __( 'CSS', 'jetpack' );
895 }
896
897 static function prettify_post_revisions() {
898 add_filter( 'the_title', array( 'Jetpack_Custom_CSS', 'post_title' ), 10, 2 );
899 }
900
901 static function post_title( $title, $post_id ) {
902 if ( !$post_id = (int) $post_id ) {
903 return $title;
904 }
905
906 if ( !$post = get_post( $post_id ) ) {
907 return $title;
908 }
909
910 if ( 'safecss' != $post->post_type ) {
911 return $title;
912 }
913
914 return __( 'Custom CSS Stylesheet', 'jetpack' );
915 }
916
917 static function enqueue_scripts( $hook ) {
918 if ( 'appearance_page_editcss' != $hook )
919 return;
920
921 wp_enqueue_script( 'postbox' );
922 wp_enqueue_script(
923 'custom-css-editor',
924 Assets::get_file_url_for_environment(
925 '_inc/build/custom-css/custom-css/js/css-editor.min.js',
926 'modules/custom-css/custom-css/js/css-editor.js'
927 ),
928 'jquery',
929 '20130325',
930 true
931 );
932 wp_enqueue_style( 'custom-css-editor', plugins_url( 'custom-css/css/css-editor.css', __FILE__ ) );
933
934 if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) {
935 wp_register_style( 'jetpack-css-codemirror', plugins_url( 'custom-css/css/codemirror.css', __FILE__ ), array(), '20120905' );
936 wp_enqueue_style( 'jetpack-css-use-codemirror', plugins_url( 'custom-css/css/use-codemirror.css', __FILE__ ), array( 'jetpack-css-codemirror' ), '20120905' );
937
938 wp_register_script( 'jetpack-css-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
939 wp_enqueue_script(
940 'jetpack-css-use-codemirror',
941 Assets::get_file_url_for_environment(
942 '_inc/build/custom-css/custom-css/js/use-codemirror.min.js',
943 'modules/custom-css/custom-css/js/use-codemirror.js'
944 ),
945 array( 'jquery', 'underscore', 'jetpack-css-codemirror' ),
946 '20131009',
947 true
948 );
949 }
950 }
951
952 static function saved_message() {
953 echo '<div id="message" class="updated fade"><p><strong>' . __( 'Stylesheet saved.', 'jetpack' ) . '</strong></p></div>';
954 }
955
956 static function admin() {
957 add_meta_box( 'submitdiv', __( 'Publish', 'jetpack' ), array( __CLASS__, 'publish_box' ), 'editcss', 'side' );
958 add_action( 'custom_css_submitbox_misc_actions', array( __CLASS__, 'content_width_settings' ) );
959
960 $safecss_post = Jetpack_Custom_CSS::get_post();
961
962 if ( ! empty( $safecss_post ) && 0 < $safecss_post['ID'] && wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1 ) ) )
963 add_meta_box( 'revisionsdiv', __( 'CSS Revisions', 'jetpack' ), array( __CLASS__, 'revisions_meta_box' ), 'editcss', 'side' );
964 ?>
965 <div class="wrap">
966 <?php
967
968 /**
969 * Fires right before the custom css page begins.
970 *
971 * @module custom-css
972 *
973 * @since 1.7.0
974 */
975 do_action( 'custom_design_header' );
976
977 ?>
978 <h1><?php _e( 'CSS Stylesheet Editor', 'jetpack' ); ?></h1>
979 <form id="safecssform" action="" method="post">
980 <?php wp_nonce_field( 'safecss' ) ?>
981 <?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
982 <?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
983 <input type="hidden" name="action" value="save" />
984 <div id="poststuff">
985 <p class="css-support">
986 <?php
987 /**
988 * Filter the intro text appearing above the Custom CSS Editor.
989 *
990 * @module custom-css
991 *
992 * @since 1.7.0
993 *
994 * @param string $str Intro text appearing above the Custom CSS editor.
995 */
996 echo apply_filters( 'safecss_intro_text', __( 'New to CSS? Start with a <a href="https://www.htmldog.com/guides/css/beginner/" rel="noopener noreferrer" target="_blank">beginner tutorial</a>. Questions?
997 Ask in the <a href="https://wordpress.org/support/forum/themes-and-templates" rel="noopener noreferrer" target="_blank">Themes and Templates forum</a>.', 'jetpack' ) );
998 ?></p>
999 <p class="css-support"><?php echo __( 'Note: Custom CSS will be reset when changing themes.', 'jetpack' ); ?></p>
1000
1001 <div id="post-body" class="metabox-holder columns-2">
1002 <div id="post-body-content">
1003 <div class="postarea">
1004 <textarea id="safecss" name="safecss"<?php if ( SAFECSS_USE_ACE ) echo ' class="hide-if-js"'; ?>><?php echo esc_textarea( Jetpack_Custom_CSS::get_css() ); ?></textarea>
1005 <div class="clear"></div>
1006 </div>
1007 </div>
1008 <div id="postbox-container-1" class="postbox-container">
1009 <?php do_meta_boxes( 'editcss', 'side', $safecss_post ); ?>
1010 </div>
1011 </div>
1012 <br class="clear" />
1013 </div>
1014 </form>
1015 </div>
1016 <?php
1017 }
1018
1019 /**
1020 * Content width setting callback
1021 */
1022 static function content_width_settings() {
1023 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
1024
1025 $custom_content_width = get_post_meta( $safecss_post['ID'], 'content_width', true );
1026
1027 // If custom content width hasn't been overridden and the theme has a content_width value, use that as a default.
1028 if ( $custom_content_width <= 0 && ! empty( $GLOBALS['content_width'] ) )
1029 $custom_content_width = $GLOBALS['content_width'];
1030
1031 if ( ! $custom_content_width || ( isset( $GLOBALS['content_width'] ) && $custom_content_width == $GLOBALS['content_width'] ) )
1032 $custom_content_width = '';
1033
1034 ?>
1035 <div class="misc-pub-section">
1036 <label><?php esc_html_e( 'Media Width:', 'jetpack' ); ?></label>
1037 <span id="content-width-display" data-default-text="<?php esc_attr_e( 'Default', 'jetpack' ); ?>" data-custom-text="<?php esc_attr_e( '%s px', 'jetpack' ); ?>"><?php echo $custom_content_width ? sprintf( esc_html__( '%s px', 'jetpack' ), $custom_content_width ) : esc_html_e( 'Default', 'jetpack' ); ?></span>
1038 <a class="edit-content-width hide-if-no-js" href="#content-width"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
1039 <div id="content-width-select" class="hide-if-js">
1040 <input type="hidden" name="custom_content_width" id="custom_content_width" value="<?php echo esc_attr( $custom_content_width ); ?>" />
1041 <p>
1042 <?php
1043
1044 printf( /* translators: %1$s is replaced with an input field for numbers. */
1045 __( 'Limit width to %1$s pixels for full size images. (<a href="%2$s" rel="noopener noreferrer" target="_blank">More info</a>.)', 'jetpack' ),
1046 '<input type="text" id="custom_content_width_visible" value="' . esc_attr( $custom_content_width ) . '" size="4" />',
1047 /**
1048 * Filter the Custom CSS limited width's support doc URL.
1049 *
1050 * @module custom-css
1051 *
1052 * @since 2.2.3
1053 *
1054 * @param string $url Custom CSS limited width's support doc URL.
1055 */
1056 esc_url(
1057 apply_filters( 'safecss_limit_width_link', Redirect::get_url( 'jetpack-support-custom-css', array( 'anchor' => 'limited-width' ) ) )
1058 )
1059 );
1060
1061 ?>
1062 </p>
1063 <?php
1064
1065 if (
1066 ! empty( $GLOBALS['content_width'] )
1067 && $custom_content_width != $GLOBALS['content_width']
1068 ) {
1069 $current_theme = wp_get_theme()->Name;
1070
1071 ?>
1072 <p>
1073 <?php
1074 echo esc_html(
1075 sprintf(
1076 /* translators: %1$s is the theme name, %2$d is an amount of pixels. */
1077 _n(
1078 'The default content width for the %1$s theme is %2$d pixel.',
1079 'The default content width for the %1$s theme is %2$d pixels.',
1080 (int) $GLOBALS['content_width'],
1081 'jetpack'
1082 ),
1083 $current_theme,
1084 (int) $GLOBALS['content_width']
1085 )
1086 );
1087 ?>
1088 </p>
1089 <?php
1090 }
1091
1092 ?>
1093 <a class="save-content-width hide-if-no-js button" href="#content-width"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
1094 <a class="cancel-content-width hide-if-no-js" href="#content-width"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
1095 </div>
1096 <script type="text/javascript">
1097 jQuery( function ( $ ) {
1098 var defaultContentWidth = <?php echo isset( $GLOBALS['content_width'] ) ? json_encode( (int) $GLOBALS['content_width'] ) : 0; ?>;
1099
1100 $( '.edit-content-width' ).bind( 'click', function ( e ) {
1101 e.preventDefault();
1102
1103 $( '#content-width-select' ).slideDown();
1104 $( this ).hide();
1105 } );
1106
1107 $( '.cancel-content-width' ).bind( 'click', function ( e ) {
1108 e.preventDefault();
1109
1110 $( '#content-width-select' ).slideUp( function () {
1111 $( '.edit-content-width' ).show();
1112 $( '#custom_content_width_visible' ).val( $( '#custom_content_width' ).val() );
1113 } );
1114 } );
1115
1116 $( '.save-content-width' ).bind( 'click', function ( e ) {
1117 e.preventDefault();
1118
1119 $( '#content-width-select' ).slideUp();
1120
1121 var newContentWidth = parseInt( $( '#custom_content_width_visible' ).val(), 10 );
1122
1123 if ( newContentWidth && newContentWidth != defaultContentWidth ) {
1124 $( '#content-width-display' ).text(
1125 $( '#content-width-display' )
1126 .data( 'custom-text' )
1127 .replace( '%s', $( '#custom_content_width_visible' ).val() )
1128 );
1129 }
1130 else {
1131 $( '#content-width-display' ).text( $( '#content-width-display' ).data( 'default-text' ) );
1132 }
1133
1134 $( '#custom_content_width' ).val( $( '#custom_content_width_visible' ).val() );
1135 $( '.edit-content-width' ).show();
1136 } );
1137 } );
1138 </script>
1139 </div>
1140 <?php
1141 }
1142
1143 static function publish_box() {
1144 ?>
1145 <div id="minor-publishing">
1146 <div id="misc-publishing-actions">
1147 <?php
1148
1149 /**
1150 * Filter the array of available Custom CSS preprocessors.
1151 *
1152 * @module custom-css
1153 *
1154 * @since 2.0.3
1155 *
1156 * @param array array() Empty by default.
1157 */
1158 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
1159
1160 if ( ! empty( $preprocessors ) ) {
1161 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
1162 $selected_preprocessor_key = get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true );
1163 $selected_preprocessor = isset( $preprocessors[$selected_preprocessor_key] ) ? $preprocessors[$selected_preprocessor_key] : null;
1164
1165 ?>
1166 <div class="misc-pub-section">
1167 <label><?php esc_html_e( 'Preprocessor:', 'jetpack' ); ?></label>
1168 <span id="preprocessor-display"><?php echo esc_html( $selected_preprocessor ? $selected_preprocessor['name'] : __( 'None', 'jetpack' ) ); ?></span>
1169 <a class="edit-preprocessor hide-if-no-js" href="#preprocessor"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
1170 <div id="preprocessor-select" class="hide-if-js">
1171 <input type="hidden" name="custom_css_preprocessor" id="custom_css_preprocessor" value="<?php echo esc_attr( $selected_preprocessor_key ); ?>" />
1172 <select id="preprocessor_choices">
1173 <option value=""><?php esc_html_e( 'None', 'jetpack' ); ?></option>
1174 <?php
1175
1176 foreach ( $preprocessors as $preprocessor_key => $preprocessor ) {
1177 ?>
1178 <option value="<?php echo esc_attr( $preprocessor_key ); ?>" <?php selected( $selected_preprocessor_key, $preprocessor_key ); ?>><?php echo esc_html( $preprocessor['name'] ); ?></option>
1179 <?php
1180 }
1181
1182 ?>
1183 </select>
1184 <a class="save-preprocessor hide-if-no-js button" href="#preprocessor"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
1185 <a class="cancel-preprocessor hide-if-no-js" href="#preprocessor"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
1186 </div>
1187 </div>
1188 <?php
1189 }
1190
1191 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
1192
1193 $add_css = ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) != 'no' );
1194
1195 ?>
1196 <div class="misc-pub-section">
1197 <label><?php esc_html_e( 'Mode:', 'jetpack' ); ?></label>
1198 <span id="css-mode-display"><?php echo esc_html( $add_css ? __( 'Add-on', 'jetpack' ) : __( 'Replacement', 'jetpack' ) ); ?></span>
1199 <a class="edit-css-mode hide-if-no-js" href="#css-mode"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
1200 <div id="css-mode-select" class="hide-if-js">
1201 <input type="hidden" name="add_to_existing" id="add_to_existing" value="<?php echo $add_css ? 'true' : 'false'; ?>" />
1202 <p>
1203 <label>
1204 <input type="radio" name="add_to_existing_display" value="true" <?php checked( $add_css ); ?>/>
1205 <?php _e( 'Add-on CSS <b>(Recommended)</b>', 'jetpack' ); ?>
1206 </label>
1207 <br />
1208 <label>
1209 <input type="radio" name="add_to_existing_display" value="false" <?php checked( ! $add_css ); ?>/>
1210 <?php printf(
1211 __( 'Replace <a href="%s">theme\'s CSS</a> <b>(Advanced)</b>', 'jetpack' ),
1212 /**
1213 * Filter the theme's stylesheet URL.
1214 *
1215 * @module custom-css
1216 *
1217 * @since 1.7.0
1218 *
1219 * @param string $url Active theme's stylesheet URL. Default to get_stylesheet_uri().
1220 */
1221 apply_filters( 'safecss_theme_stylesheet_url', get_stylesheet_uri() )
1222 ); ?>
1223 </label>
1224 </p>
1225 <a class="save-css-mode hide-if-no-js button" href="#css-mode"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
1226 <a class="cancel-css-mode hide-if-no-js" href="#css-mode"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
1227 </div>
1228 </div>
1229 <?php
1230
1231 /**
1232 * Allows addition of elements to the submit box for custom css on the wp-admin side.
1233 *
1234 * @module custom-css
1235 *
1236 * @since 2.0.3
1237 */
1238 do_action( 'custom_css_submitbox_misc_actions' );
1239
1240 ?>
1241 </div>
1242 </div>
1243 <div id="major-publishing-actions">
1244 <input type="button" class="button" id="preview" name="preview" value="<?php esc_attr_e( 'Preview', 'jetpack' ) ?>" />
1245 <div id="publishing-action">
1246 <input type="submit" class="button-primary" id="save" name="save" value="<?php ( Jetpack_Custom_CSS::is_freetrial() ) ? esc_attr_e( 'Save &amp; Buy Upgrade', 'jetpack' ) : esc_attr_e( 'Save Stylesheet', 'jetpack' ); ?>" />
1247 </div>
1248 </div>
1249 <?php
1250 }
1251
1252 /**
1253 * Render metabox listing CSS revisions and the themes that correspond to the revisions.
1254 * Called by safecss_admin
1255 *
1256 * @global $post
1257 * @param array $safecss_post
1258 * @uses wp_revisions_to_keep
1259 * @uses WP_Query
1260 * @uses wp_post_revision_title
1261 * @uses esc_html
1262 * @uses add_query_arg
1263 * @uses menu_page_url
1264 * @uses wp_reset_query
1265 * @return string
1266 */
1267 static function revisions_meta_box( $safecss_post ) {
1268
1269 $show_all_revisions = isset( $_GET['show_all_rev'] );
1270
1271 if ( function_exists( 'wp_revisions_to_keep' ) ) {
1272 $max_revisions = wp_revisions_to_keep( (object) $safecss_post );
1273 } else {
1274 $max_revisions = defined( 'WP_POST_REVISIONS' ) && is_numeric( WP_POST_REVISIONS ) ? (int) WP_POST_REVISIONS : 25;
1275 }
1276
1277 $posts_per_page = $show_all_revisions ? $max_revisions : 6;
1278
1279 $revisions = new WP_Query( array(
1280 'posts_per_page' => $posts_per_page,
1281 'post_type' => 'revision',
1282 'post_status' => 'inherit',
1283 'post_parent' => $safecss_post['ID'],
1284 'orderby' => 'date',
1285 'order' => 'DESC'
1286 ) );
1287
1288 if ( $revisions->have_posts() ) { ?>
1289 <ul class="post-revisions"><?php
1290
1291 global $post;
1292
1293 while ( $revisions->have_posts() ) :
1294 $revisions->the_post();
1295
1296 ?><li>
1297 <?php
1298 echo wp_post_revision_title( $post );
1299
1300 if ( ! empty( $post->post_excerpt ) )
1301 echo ' (' . esc_html( $post->post_excerpt ) . ')';
1302 ?>
1303 </li><?php
1304
1305 endwhile;
1306
1307 ?></ul><?php
1308
1309 if ( $revisions->found_posts > 6 && !$show_all_revisions ) {
1310 ?>
1311 <br>
1312 <a href="<?php echo add_query_arg( 'show_all_rev', 'true', menu_page_url( 'editcss', false ) ); ?>"><?php esc_html_e( 'Show all', 'jetpack' ); ?></a>
1313 <?php
1314 }
1315 }
1316
1317 wp_reset_query();
1318 }
1319
1320 /**
1321 * Hook in init at priority 11 to disable custom CSS.
1322 */
1323 static function disable() {
1324 remove_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 );
1325 remove_filter( 'stylesheet_uri', array( 'Jetpack_Custom_CSS', 'style_filter' ) );
1326 }
1327
1328 /**
1329 * Reset all aspects of Custom CSS on a theme switch so that changing
1330 * themes is a sure-fire way to get a clean start.
1331 */
1332 static function reset() {
1333 $safecss_post_id = Jetpack_Custom_CSS::save_revision( '' );
1334 $safecss_revision = Jetpack_Custom_CSS::get_current_revision();
1335
1336 update_option( 'safecss_rev', (int) get_option( 'safecss_rev' ) + 1 );
1337
1338 update_post_meta( $safecss_post_id, 'custom_css_add', 'yes' );
1339 update_post_meta( $safecss_post_id, 'content_width', false );
1340 update_post_meta( $safecss_post_id, 'custom_css_preprocessor', '' );
1341
1342 delete_option( 'safecss_add' );
1343 delete_option( 'safecss_content_width' );
1344
1345 update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', 'yes' );
1346 update_metadata( 'post', $safecss_revision['ID'], 'content_width', false );
1347 update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', '' );
1348
1349 delete_option( 'safecss_preview_add' );
1350 }
1351
1352 static function is_customizer_preview() {
1353 if ( isset ( $GLOBALS['wp_customize'] ) )
1354 return ! $GLOBALS['wp_customize']->is_theme_active();
1355
1356 return false;
1357 }
1358
1359 static function minify( $css, $preprocessor = '' ) {
1360 if ( ! $css )
1361 return '';
1362
1363 if ( $preprocessor ) {
1364 /** This filter is documented in modules/custom-css/custom-css.php */
1365 $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
1366
1367 if ( isset( $preprocessors[$preprocessor] ) ) {
1368 $css = call_user_func( $preprocessors[$preprocessor]['callback'], $css );
1369 }
1370 }
1371
1372 safecss_class();
1373 $csstidy = new csstidy();
1374 $csstidy->optimise = new safecss( $csstidy );
1375
1376 $csstidy->set_cfg( 'remove_bslash', false );
1377 $csstidy->set_cfg( 'compress_colors', true );
1378 $csstidy->set_cfg( 'compress_font-weight', true );
1379 $csstidy->set_cfg( 'remove_last_;', true );
1380 $csstidy->set_cfg( 'case_properties', true );
1381 $csstidy->set_cfg( 'discard_invalid_properties', true );
1382 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
1383 $csstidy->set_cfg( 'template', 'highest');
1384 $csstidy->parse( $css );
1385
1386 return $csstidy->print->plain();
1387 }
1388
1389 /**
1390 * When restoring a SafeCSS post revision, also copy over the
1391 * content_width and custom_css_add post metadata.
1392 */
1393 static function restore_revision( $_post_id, $_revision_id ) {
1394 $_post = get_post( $_post_id );
1395
1396 if ( 'safecss' != $_post->post_type )
1397 return;
1398
1399 $safecss_revision = Jetpack_Custom_CSS::get_current_revision();
1400
1401 $content_width = get_post_meta( $_revision_id, 'content_width', true );
1402 $custom_css_add = get_post_meta( $_revision_id, 'custom_css_add', true );
1403 $preprocessor = get_post_meta( $_revision_id, 'custom_css_preprocessor', true );
1404
1405 update_metadata( 'post', $safecss_revision['ID'], 'content_width', $content_width );
1406 update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', $custom_css_add );
1407 update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', $preprocessor );
1408
1409 delete_option( 'safecss_add' );
1410 delete_option( 'safecss_content_width' );
1411
1412 update_post_meta( $_post->ID, 'content_width', $content_width );
1413 update_post_meta( $_post->ID, 'custom_css_add', $custom_css_add );
1414 update_post_meta( $_post->ID, 'custom_css_preprocessor', $preprocessor );
1415
1416 delete_option( 'safecss_preview_add' );
1417 }
1418
1419 /**
1420 * Migration routine for moving safecss from wp_options to wp_posts to support revisions
1421 *
1422 * @return void
1423 */
1424 static function upgrade() {
1425 $css = get_option( 'safecss' );
1426
1427 if ( get_option( 'safecss_revision_migrated' ) ) {
1428 return false;
1429 }
1430
1431 // Check if CSS is stored in wp_options
1432 if ( $css ) {
1433 // Remove the async actions from publish_post
1434 remove_action( 'publish_post', 'queue_publish_post' );
1435
1436 $post = array();
1437 $post['post_content'] = $css;
1438 $post['post_title'] = 'safecss';
1439 $post['post_status'] = 'publish';
1440 $post['post_type'] = 'safecss';
1441
1442 // Insert the CSS into wp_posts
1443 $post_id = wp_insert_post( $post );
1444 // Check for errors
1445 if ( !$post_id or is_wp_error( $post_id ) )
1446 die( $post_id->get_error_message() );
1447
1448 // Delete safecss option
1449 delete_option( 'safecss' );
1450 }
1451
1452 unset( $css );
1453
1454 // Check if we have already done this
1455 if ( !get_option( 'safecss_revision_migrated' ) ) {
1456 define( 'DOING_MIGRATE', true );
1457
1458 // Get hashes of safecss post and current revision
1459 $safecss_post = Jetpack_Custom_CSS::get_post();
1460
1461 if ( empty( $safecss_post ) )
1462 return;
1463
1464 $safecss_post_hash = md5( $safecss_post['post_content'] );
1465 $current_revision = Jetpack_Custom_CSS::get_current_revision();
1466
1467 if ( null == $current_revision )
1468 return;
1469
1470 $current_revision_hash = md5( $current_revision['post_content'] );
1471
1472 // If hashes are not equal, set safecss post with content from current revision
1473 if ( $safecss_post_hash !== $current_revision_hash ) {
1474 Jetpack_Custom_CSS::save_revision( $current_revision['post_content'] );
1475 // Reset post_content to display the migrated revsion
1476 $safecss_post['post_content'] = $current_revision['post_content'];
1477 }
1478
1479 // Set option so that we dont keep doing this
1480 update_option( 'safecss_revision_migrated', time() );
1481 }
1482
1483 $newest_safecss_post = Jetpack_Custom_CSS::get_current_revision();
1484
1485 if ( $newest_safecss_post ) {
1486 if ( get_option( 'safecss_content_width' ) ) {
1487 // Add the meta to the post and the latest revision.
1488 update_post_meta( $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) );
1489 update_metadata( 'post', $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) );
1490
1491 delete_option( 'safecss_content_width' );
1492 }
1493
1494 if ( get_option( 'safecss_add' ) ) {
1495 update_post_meta( $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) );
1496 update_metadata( 'post', $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) );
1497
1498 delete_option( 'safecss_add' );
1499 }
1500 }
1501 }
1502
1503 /**
1504 * Adds a filter to the redirect location in `wp-admin/revisions.php`.
1505 */
1506 static function add_revision_redirect() {
1507 add_filter( 'wp_redirect', array( __CLASS__, 'revision_redirect' ) );
1508 }
1509
1510 /**
1511 * Filters the redirect location in `wp-admin/revisions.php`.
1512 *
1513 * @param string $location The path to redirect to.
1514 * @return string
1515 */
1516 static function revision_redirect( $location ) {
1517 $post = get_post();
1518
1519 if ( ! empty( $post->post_type ) && 'safecss' == $post->post_type ) {
1520 $location = 'themes.php?page=editcss';
1521
1522 if ( 'edit.php' == $location ) {
1523 $location = '';
1524 }
1525 }
1526
1527 return $location;
1528 }
1529
1530 static function revision_post_link( $post_link, $post_id, $context ) {
1531 if ( !$post_id = (int) $post_id ) {
1532 return $post_link;
1533 }
1534
1535 if ( !$post = get_post( $post_id ) ) {
1536 return $post_link;
1537 }
1538
1539 if ( 'safecss' != $post->post_type ) {
1540 return $post_link;
1541 }
1542
1543 $post_link = admin_url( 'themes.php?page=editcss' );
1544
1545 if ( 'display' == $context ) {
1546 return esc_url( $post_link );
1547 }
1548
1549 return esc_url_raw( $post_link );
1550 }
1551
1552 /**
1553 * When on the edit screen, make sure the custom content width
1554 * setting is applied to the large image size.
1555 */
1556 static function editor_max_image_size( $dims, $size = 'medium', $context = null ) {
1557 list( $width, $height ) = $dims;
1558
1559 if ( 'large' == $size && 'edit' == $context )
1560 $width = Jetpack::get_content_width();
1561
1562 return array( $width, $height );
1563 }
1564
1565 /**
1566 * Override the content_width with a custom value if one is set.
1567 */
1568 static function jetpack_content_width( $content_width ) {
1569 $custom_content_width = 0;
1570
1571 if ( Jetpack_Custom_CSS::is_preview() ) {
1572 $safecss_post = Jetpack_Custom_CSS::get_current_revision();
1573 $custom_content_width = (int) get_post_meta( $safecss_post['ID'], 'content_width', true );
1574 } else if ( ! Jetpack_Custom_CSS::is_freetrial() ) {
1575 $custom_css_post_id = Jetpack_Custom_CSS::post_id();
1576 if ( $custom_css_post_id )
1577 $custom_content_width = (int) get_post_meta( $custom_css_post_id, 'content_width', true );
1578 }
1579
1580 if ( $custom_content_width > 0 )
1581 $content_width = $custom_content_width;
1582
1583 return $content_width;
1584 }
1585 }
1586
1587 class Jetpack_Safe_CSS {
1588 static function filter_attr( $css, $element = 'div' ) {
1589 safecss_class();
1590
1591 $css = $element . ' {' . $css . '}';
1592
1593 $csstidy = new csstidy();
1594 $csstidy->optimise = new safecss( $csstidy );
1595 $csstidy->set_cfg( 'remove_bslash', false );
1596 $csstidy->set_cfg( 'compress_colors', false );
1597 $csstidy->set_cfg( 'compress_font-weight', false );
1598 $csstidy->set_cfg( 'discard_invalid_properties', true );
1599 $csstidy->set_cfg( 'merge_selectors', false );
1600 $csstidy->set_cfg( 'remove_last_;', false );
1601 $csstidy->set_cfg( 'css_level', 'CSS3.0' );
1602
1603 // Turn off css shorthands and leading zero removal as it breaks block validation.
1604 $csstidy->set_cfg( 'optimise_shorthands', 0 );
1605 $csstidy->set_cfg( 'preserve_leading_zeros', true );
1606
1607 $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css );
1608 $css = wp_kses_split( $css, array(), array() );
1609 $csstidy->parse( $css );
1610
1611 $css = $csstidy->print->plain();
1612
1613 $css = str_replace( array( "\n","\r","\t" ), '', $css );
1614
1615 preg_match( "/^{$element}\s*{(.*)}\s*$/", $css, $matches );
1616
1617 if ( empty( $matches[1] ) )
1618 return '';
1619
1620 return $matches[1];
1621 }
1622 }
1623
1624 if ( ! function_exists( 'safecss_class' ) ) :
1625 function safecss_class() {
1626 // Wrapped so we don't need the parent class just to load the plugin
1627 if ( class_exists('safecss') )
1628 return;
1629
1630 require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' );
1631
1632 class safecss extends csstidy_optimise {
1633
1634 function postparse() {
1635
1636 /**
1637 * Fires after parsing the css.
1638 *
1639 * @module custom-css
1640 *
1641 * @since 1.8.0
1642 *
1643 * @param obj $this CSSTidy object.
1644 */
1645 do_action( 'csstidy_optimize_postparse', $this );
1646
1647 return parent::postparse();
1648 }
1649
1650 function subvalue() {
1651
1652 /**
1653 * Fires before optimizing the Custom CSS subvalue.
1654 *
1655 * @module custom-css
1656 *
1657 * @since 1.8.0
1658 *
1659 * @param obj $this CSSTidy object.
1660 **/
1661 do_action( 'csstidy_optimize_subvalue', $this );
1662
1663 return parent::subvalue();
1664 }
1665 }
1666 }
1667 endif;
1668
1669 if ( ! function_exists( 'safecss_filter_attr' ) ) {
1670 function safecss_filter_attr( $css, $element = 'div' ) {
1671 return Jetpack_Safe_CSS::filter_attr( $css, $element );
1672 }
1673 }
1674
1675 include_once dirname( __FILE__ ) . '/custom-css/preprocessors.php';
1676