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