Completed
Push — ccss/deux ( eb0453...a32c08 )
by George
07:55
created

prettify_post_revisions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Class Jetpack_Custom_CSS_Enhancements
5
 */
6
class Jetpack_Custom_CSS_Enhancements {
7
	/**
8
	 * Set up the actions and filters needed for our compatability layer on top of core's Custom CSS implementation.
9
	 */
10
	public static function add_hooks() {
11
		add_action( 'init', array( __CLASS__, 'init' ) );
12
		add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
13
		add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'customize_controls_enqueue_scripts' ) );
14
		add_action( 'customize_register', array( __CLASS__, 'customize_register' ) );
15
		add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap' ), 20, 2 );
16
		add_action( 'customize_preview_init', array( __CLASS__, 'customize_preview_init' ) );
17
		add_filter( '_wp_post_revision_fields', array( __CLASS__, '_wp_post_revision_fields' ), 10, 2 );
18
		add_action( 'load-revision.php', array( __CLASS__, 'load_revision_php' ) );
19
20
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'wp_enqueue_scripts' ) );
21
22
		// Handle Sass/LESS
23
		add_filter( 'customize_value_custom_css', array( __CLASS__, 'customize_value_custom_css' ), 10, 2 );
24
		add_filter( 'customize_update_custom_css_post_content_args', array( __CLASS__, 'customize_update_custom_css_post_content_args' ), 10, 3 );
25
		add_filter( 'update_custom_css_data', array( __CLASS__, 'update_custom_css_data' ), 10, 2 );
26
27
		// Handle Sass/LESS
28
		add_filter( 'customize_value_custom_css', array( __CLASS__, 'customize_value_custom_css' ), 10, 2 );
29
		add_filter( 'customize_update_custom_css_post_content_args', array( __CLASS__, 'customize_update_custom_css_post_content_args' ), 10, 3 );
30
31
		// Stuff for stripping out the theme's default stylesheet...
32
		add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) );
33
		add_filter( 'safecss_skip_stylesheet', array( __CLASS__, 'preview_skip_stylesheet' ) );
34
35
		// Stuff for overriding content width...
36
		add_action( 'customize_preview_init', array( __CLASS__, 'preview_content_width' ) );
37
		add_filter( 'jetpack_content_width', array( __CLASS__, 'jetpack_content_width' ) );
38
		add_filter( 'editor_max_image_size', array( __CLASS__, 'editor_max_image_size' ), 10, 3 );
39
		add_action( 'template_redirect', array( __CLASS__, 'set_content_width' ) );
40
		add_action( 'admin_init', array( __CLASS__, 'set_content_width' ) );
41
42
		// Stuff?
43
	}
44
45
	/**
46
	 * Things that we do on init.
47
	 */
48
	public static function init() {
49
		$min = '.min';
0 ignored issues
show
Unused Code introduced by
$min is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
50
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
51
			$min = '';
0 ignored issues
show
Unused Code introduced by
$min is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
52
		}
53
54
		wp_register_style( 'jetpack-codemirror',      plugins_url( "custom-css/css/codemirror.css", __FILE__ ), array(), '20120905' );
55
		wp_register_style( 'jetpack-customizer-css',  plugins_url( 'custom-css/css/customizer-control.css', __FILE__ ), array( 'jetpack-codemirror' ), '20140728' );
56
		wp_register_script( 'jetpack-codemirror',     plugins_url( "custom-css/js/codemirror.min.js", __FILE__ ), array(), '3.16', true );
57
		wp_register_script( 'jetpack-customizer-css', plugins_url( 'custom-css/js/core-customizer-css.js', __FILE__ ), array(  'customize-controls', 'underscore', 'jetpack-codemirror' ), JETPACK__VERSION, true );
58
59
		wp_register_script( 'jetpack-customizer-css-preview', plugins_url( 'custom-css/js/core-customizer-css-preview.js', __FILE__ ), array( 'customize-selective-refresh' ), JETPACK__VERSION, true );
60
	}
61
62
	/**
63
	 * Things that we do on init when the Customize Preview is loading.
64
	 */
65
	public static function customize_preview_init() {
66
		add_filter( 'wp_get_custom_css', array( __CLASS__, 'customize_preview_wp_get_custom_css' ) );
67
	}
68
69
	/**
70
	 * Re-map the Edit CSS capability.
71
	 *
72
	 * Core, by default, restricts this to users that have `unfiltered_html` which
73
	 * would make the feature unusable in multi-site by non-super-admins, due to Core
74
	 * not shipping any solid sanitization.
75
	 *
76
	 * We're expanding who can use it, and then conditionally applying CSSTidy
77
	 * sanitization to users that do not have the `unfiltered_html` capability.
78
	 *
79
	 * @param $caps
80
	 * @param $cap
81
	 * @return array
82
	 */
83
	public static function map_meta_cap( $caps, $cap ) {
84
		if ( 'edit_css' === $cap ) {
85
			$caps = array( 'edit_theme_options' );
86
		}
87
		return $caps;
88
	}
89
90
	/**
91
	 * Handle our admin menu item and legacy page declaration.
92
	 */
93
	public static function admin_menu() {
94
		// Add in our legacy page to support old bookmarks and such.
95
		add_submenu_page( null, __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss', array( __CLASS__, 'admin_page' ) );
96
97
		// Add in our new page slug that will redirect to the customizer.
98
		$hook = add_theme_page( __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss-customizer-redirect', array( __CLASS__, 'admin_page' ) );
99
		add_action( "load-{$hook}", array( __CLASS__, 'customizer_redirect' ) );
100
	}
101
102
	/**
103
	 * Handle the redirect for the customizer.  This is necessary because
104
	 * we can't directly add customizer links to the admin menu.
105
	 *
106
	 * There is a core patch in trac that would make this unnecessary.
107
	 *
108
	 * @link https://core.trac.wordpress.org/ticket/39050
109
	 */
110
	public static function customizer_redirect() {
111
		wp_safe_redirect( self::customizer_link( array(
112
			'return_url' => wp_get_referer(),
113
		) ) );
114
	}
115
116
	/**
117
	 * Shows Preprocessor code in the Revisions screen, and ensures that post_content_filtered
118
	 * is maintained on revisions
119
	 *
120
	 * @param  array $fields  Post fields pertinent to revisions
121
	 * @return array          Modified array to include post_content_filtered
122
	 */
123
	public static function _wp_post_revision_fields( $fields, $post ) {
124
		// If we're passed in a revision, go get the main post instead.
125
		if ( 'revision' === $post['post_type'] ) {
126
			$main_post_id = wp_is_post_revision( $post['ID'] );
127
			$post = get_post( $main_post_id, ARRAY_A );
128
		}
129
		if ( 'custom_css' === $post['post_type'] ) {
130
			$fields['post_content'] = __( 'CSS', 'jetpack' );
131
			$fields['post_content_filtered'] = __( 'Preprocessor', 'jetpack' );
132
		}
133
		return $fields;
134
	}
135
136
	/**
137
	 * Get the published custom CSS post.
138
	 *
139
	 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
140
	 * @return WP_Post|null
141
	 */
142
	public static function get_css_post( $stylesheet = '' ) {
143
		return wp_get_custom_css_post( $stylesheet );
144
	}
145
146
	/**
147
	 * Get the ID of a Custom CSS post tying to a given stylesheet.
148
	 *
149
	 * @param string $stylesheet
150
	 * @return int
151
	 */
152
	public static function post_id( $stylesheet = '' ) {
153
		$post = self::get_css_post( $stylesheet );
154
		if ( $post instanceof WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
155
			return $post->ID;
156
		}
157
		return 0;
158
	}
159
160
	/**
161
	 * Partial for use in the Customizer.
162
	 */
163
	public static function echo_custom_css_partial() {
164
		echo wp_get_custom_css();
165
	}
166
167
	/**
168
	 * Admin page!
169
	 *
170
	 * This currently has two main uses -- firstly to display the css for an inactive
171
	 * theme if there are no revisions attached it to a legacy bug, and secondly to
172
	 * handle folks that have bookmarkes in their browser going to the old page for
173
	 * managing Custom CSS in Jetpack.
174
	 *
175
	 * If we ever add back in a non-Customizer CSS editor, this would be the place.
176
	 */
177
	public static function admin_page() {
178
		$post = null;
179
		$stylesheet = null;
180
		if ( isset( $_GET['id'] ) ) {
181
			$post_id = absint( $_GET['id'] );
182
			$post = get_post( $post_id );
183
			if ( $post instanceof WP_Post && 'custom_css' === $post->post_type ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
184
				$stylesheet = $post->post_title;
185
			}
186
		}
187
		?>
188
		<div class="wrap">
189
			<?php self::revisions_switcher_box( $stylesheet ); ?>
190
			<h1>
191
				<?php
192
				if ( $post ) {
193
					printf( 'Custom CSS for &#8220;%1$s&#8221;', wp_get_theme( $stylesheet )->Name );
194
				} else {
195
					esc_html_e( 'Custom CSS', 'jetpack' );
196
				}
197
				if ( current_user_can( 'customize' ) ) {
198
					printf(
199
						' <a class="page-title-action hide-if-no-customize" href="%1$s">%2$s</a>',
200
						esc_url( self::customizer_link() ),
201
						__( 'Manage with Live Preview', 'jetpack' )
202
					);
203
				}
204
				?>
205
			</h1>
206
			<p><?php esc_html_e( 'Custom CSS is now managed in the Customizer.', 'jetpack' ); ?></p>
207
			<?php if ( $post ) : ?>
208
				<div class="revisions">
209
					<h3><?php esc_html_e( 'CSS', 'jetpack' ); ?></h3>
210
					<textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content ); ?></textarea>
211
					<?php if ( $post->post_content_filtered ) : ?>
212
						<h3><?php esc_html_e( 'Preprocessor', 'jetpack' ); ?></h3>
213
						<textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content_filtered ); ?></textarea>
214
					<?php endif; ?>
215
				</div>
216
			<?php endif; ?>
217
		</div>
218
219
		<style>
220
			.other-themes-wrap {
221
				float: right;
222
				background-color: #fff;
223
				-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
224
				box-shadow: 0 1px 3px rgba(0,0,0,0.1);
225
				padding: 5px 10px;
226
				margin-bottom: 10px;
227
			}
228
			.other-themes-wrap label {
229
				display: block;
230
				margin-bottom: 10px;
231
			}
232
			.other-themes-wrap select {
233
				float: left;
234
				width: 77%;
235
			}
236
			.other-themes-wrap button {
237
				float: right;
238
				width: 20%;
239
			}
240
			.revisions {
241
				clear: both;
242
			}
243
			.revisions textarea {
244
				min-height: 300px;
245
				background: #fff;
246
			}
247
		</style>
248
		<script>
249
			(function($){
250
				var $switcher = $('.other-themes-wrap');
251
				$switcher.find('button').on('click', function(e){
252
					e.preventDefault();
253
					if ( $switcher.find('select').val() ) {
254
						window.location.href = $switcher.find('select').val();
255
					}
256
				});
257
			})(jQuery);
258
		</script>
259
		<?php
260
	}
261
262
	/**
263
	 * Build the URL to deep link to the Customizer.
264
	 *
265
	 * You can modify the return url via $args.
266
	 *
267
	 * @param array $args
268
	 * @return string
269
	 */
270
	public static function customizer_link( $args = array() ) {
271
		$args = wp_parse_args( $args, array(
272
			'return_url' => urlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
273
		) );
274
275
		return add_query_arg(
276
			array(
277
				array(
278
					'autofocus' => array(
279
						'section' => 'custom_css'
280
					),
281
				),
282
				'return' => $args['return_url'],
283
			),
284
			admin_url( 'customize.php' )
285
		);
286
	}
287
288
	/**
289
	 * Handle the enqueueing and localizing for scripts to be used in the Customizer.
290
	 */
291
	public static function customize_controls_enqueue_scripts() {
292
		wp_enqueue_style( 'jetpack-customizer-css' );
293
		wp_enqueue_script( 'jetpack-customizer-css' );
294
295
		$content_help = __( 'Set a different content width for full size images.', 'jetpack' );
296
		if ( ! empty( $GLOBALS['content_width'] ) ) {
297
			$content_help .= sprintf( __( ' The default content width for the <strong>%s</strong> theme is %d pixels.', 'jetpack' ), wp_get_theme()->Name, intval( $GLOBALS['content_width'] ) );
298
		}
299
300
		wp_localize_script( 'jetpack-customizer-css', '_jp_css_settings', array(
301
			/** This filter is documented in modules/custom-css/custom-css.php */
302
			'useRichEditor' => ! jetpack_is_mobile() && apply_filters( 'safecss_use_ace', true ),
303
			'areThereCssRevisions' => self::are_there_css_revisions(),
304
			'revisionsUrl' => self::get_revisions_url(),
305
			'cssHelpUrl' => '//en.support.wordpress.com/custom-design/editing-css/',
306
			'l10n' => array(
307
				'mode'           => __( 'Start Fresh', 'jetpack' ),
308
				'mobile'         => __( 'On Mobile', 'jetpack' ),
309
				'contentWidth'   => $content_help,
310
				'revisions'      => _x( 'See full history', 'Toolbar button to see full CSS revision history', 'jetpack' ),
311
				'css_help_title' => _x( 'Help', 'Toolbar button to get help with custom CSS', 'jetpack' )
312
			)
313
		));
314
	}
315
316
	/**
317
	 * Check whether there are CSS Revisions for a given theme.
318
	 *
319
	 * Going forward, there should always be, but this was necessitated
320
	 * early on by https://core.trac.wordpress.org/ticket/30854
321
	 *
322
	 * @param string $stylesheet
323
	 * @return bool|null|WP_Post
324
	 */
325
	public static function are_there_css_revisions( $stylesheet = '' ) {
326
		$post = wp_get_custom_css_post( $stylesheet );
327
		if ( empty( $post ) ) {
328
			return $post;
329
		}
330
		return (bool) wp_get_post_revisions( $post );
331
	}
332
333
	/**
334
	 * Core doesn't have a function to get the revisions url for a given post ID.
335
	 *
336
	 * @param string $stylesheet
337
	 * @return null|string|void
338
	 */
339
	public static function get_revisions_url( $stylesheet = '' ) {
340
		$post = wp_get_custom_css_post( $stylesheet );
341
342
		// If we have any currently saved customizations...
343
		if ( $post instanceof WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
344
			$revisions = wp_get_post_revisions( $post->ID, array( 'posts_per_page' => 1 ) );
345
			if ( empty( $revisions ) || is_wp_error( $revisions ) ) {
346
				return admin_url( 'themes.php?page=editcss' );
347
			}
348
			$revision = reset( $revisions );
349
			return get_edit_post_link( $revision->ID );
350
		}
351
352
		return admin_url( 'themes.php?page=editcss' );
353
	}
354
355
	/**
356
	 * Get a map of all theme names and theme stylesheets for mapping stuff.
357
	 *
358
	 * @return array
359
	 */
360
	public static function get_themes() {
361
		$themes = wp_get_themes( array( 'errors' => null ) );
362
		$all = array();
363
		foreach ( $themes as $theme ) {
364
			$all[ $theme->name ] = $theme->stylesheet;
365
		}
366
		return $all;
367
	}
368
369
	/**
370
	 * When we need to get all themes that have Custom CSS saved.
371
	 *
372
	 * @return array
373
	 */
374
	public static function get_all_themes_with_custom_css() {
375
		$themes = self::get_themes();
376
		$custom_css = get_posts( array(
377
			'post_type'   => 'custom_css',
378
			'post_status' => get_post_stati(),
379
			'number'      => -1,
380
			'order'       => 'DESC',
381
			'orderby'     => 'modified',
382
		) );
383
		$return = array();
384
385
		foreach ( $custom_css as $post ) {
386
			$stylesheet = $post->post_title;
387
			$label      = array_search( $stylesheet, $themes );
388
389
			if ( ! $label ) {
390
				continue;
391
			}
392
393
			$return[ $stylesheet ] = array(
394
				'label' => $label,
395
				'post'  => $post,
396
			);
397
		}
398
399
		return $return;
400
	}
401
402
	/**
403
	 * Handle the enqueueing of scripts for customize previews.
404
	 */
405
	public static function wp_enqueue_scripts() {
406
		if ( is_customize_preview() ) {
407
			wp_enqueue_script( 'jetpack-customizer-css-preview' );
408
			wp_localize_script( 'jetpack-customizer-css-preview', 'jpCustomizerCssPreview', array(
409
				/** This filter is documented in modules/custom-css/custom-css.php */
410
				'preprocessors' => apply_filters( 'jetpack_custom_css_preprocessors', array() ),
411
			));
412
		}
413
	}
414
415
	/**
416
	 * Sanitize the CSS for users without `unfiltered_html`.
417
	 *
418
	 * @param $css
419
	 * @param array $args
420
	 * @return mixed|string
421
	 */
422
	public static function sanitize_css( $css, $args = array() ) {
423
		$args = wp_parse_args( $args, array(
424
			'force'        => false,
425
			'preprocessor' => null,
426
		) );
427
428
		if ( $args['force'] || ! current_user_can( 'unfiltered_html' ) ) {
429
430
			$warnings = array();
431
432
			safecss_class();
433
			$csstidy = new csstidy();
434
			$csstidy->optimise = new safecss( $csstidy );
0 ignored issues
show
Documentation introduced by
$csstidy is of type object<csstidy>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
436
			$csstidy->set_cfg( 'remove_bslash',              false );
437
			$csstidy->set_cfg( 'compress_colors',            false );
438
			$csstidy->set_cfg( 'compress_font-weight',       false );
439
			$csstidy->set_cfg( 'optimise_shorthands',        0 );
440
			$csstidy->set_cfg( 'remove_last_;',              false );
441
			$csstidy->set_cfg( 'case_properties',            false );
442
			$csstidy->set_cfg( 'discard_invalid_properties', true );
443
			$csstidy->set_cfg( 'css_level',                  'CSS3.0' );
444
			$csstidy->set_cfg( 'preserve_css',               true );
445
			$csstidy->set_cfg( 'template',                   dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' );
446
447
			// Test for some preg_replace stuff.
448
			{
449
				$prev = $css;
450
				$css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css );
451
				// prevent content: '\3434' from turning into '\\3434'
452
				$css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css );
453
				if ( $css !== $prev ) {
454
					$warnings[] = 'preg_replace found stuff';
455
				}
456
			}
457
458
			// Some people put weird stuff in their CSS, KSES tends to be greedy
459
			$css = str_replace( '<=', '&lt;=', $css );
460
461
			// Test for some kses stuff.
462
			{
463
				$prev = $css;
464
				// Why KSES instead of strip_tags?  Who knows?
465
				$css = wp_kses_split( $css, array(), array() );
466
				$css = str_replace( '&gt;', '>', $css ); // kses replaces lone '>' with &gt;
467
				// Why both KSES and strip_tags?  Because we just added some '>'.
468
				$css = strip_tags( $css );
469
470
				if ( $css != $prev ) {
471
					$warnings[] = 'kses found stuff';
472
				}
473
			}
474
475
			// if we're not using a preprocessor
476 View Code Duplication
			if ( ! $args['preprocessor'] ) {
477
478
				/**
479
				 * Fires before parsing the css with CSSTidy, but only if
480
				 * the preprocessor is not configured for use.
481
				 *
482
				 * @module custom-css
483
				 *
484
				 * @since 1.7.0
485
				 *
486
				 * @param obj $csstidy The csstidy object.
487
				 * @param string $css Custom CSS.
488
				 * @param array $args Array of custom CSS arguments.
489
				 */
490
				do_action( 'safecss_parse_pre', $csstidy, $css, $args );
491
492
				$csstidy->parse( $css );
493
494
				/**
495
				 * Fires after parsing the css with CSSTidy, but only if
496
				 * the preprocessor is not configured for use.
497
				 *
498
				 * @module custom-css
499
				 *
500
				 * @since 1.7.0
501
				 *
502
				 * @param obj $csstidy The csstidy object.
503
				 * @param array $warnings Array of warnings.
504
				 * @param array $args Array of custom CSS arguments.
505
				 */
506
				do_action( 'safecss_parse_post', $csstidy, $warnings, $args );
507
508
				$css = $csstidy->print->plain();
509
			}
510
		}
511
		return $css;
512
	}
513
514
	/**
515
	 * Override $content_width in customizer previews.
516
	 */
517
	public static function preview_content_width() {
518
		global $wp_customize;
519
		if ( ! is_customize_preview() ) {
520
			return;
521
		}
522
523
		$setting = $wp_customize->get_setting( 'jetpack_custom_css[content_width]' );
524
		if ( ! $setting ) {
525
			return;
526
		}
527
528
		$customized_content_width = (int) $setting->post_value();
529
		if ( ! empty( $customized_content_width ) ) {
530
			$GLOBALS['content_width'] = $customized_content_width;
531
		}
532
	}
533
534
	/**
535
	 * Filter the current theme's stylesheet for potentially nullifying it.
536
	 *
537
	 * @param $current
538
	 * @return mixed|void
539
	 */
540
	static function style_filter( $current ) {
541
		if ( is_admin() ) {
542
			return $current;
543
		} elseif ( self::is_freetrial() && ( ! self::is_preview() || ! current_user_can( 'switch_themes' ) ) ) {
544
			return $current;
545
		} elseif ( self::skip_stylesheet() ) {
546
			/** This filter is documented in modules/custom-css/custom-css.php */
547
			return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) );
548
		}
549
550
		return $current;
551
	}
552
553
	/**
554
	 * Determine whether or not we should have the theme skip its main stylesheet.
555
	 *
556
	 * @return mixed The truthiness of this value determines whether the stylesheet should be skipped.
557
	 */
558
	static function skip_stylesheet() {
559
		/** This filter is documented in modules/custom-css/custom-css.php */
560
		$skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null );
561
		if ( ! is_null( $skip_stylesheet ) ) {
562
			return $skip_stylesheet;
563
		}
564
565
		$jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
566
		if ( isset( $jetpack_custom_css['replace'] ) ) {
567
			return $jetpack_custom_css['replace'];
568
		}
569
570
		return false;
571
	}
572
573
	/**
574
	 * Override $content_width in customizer previews.
575
	 *
576
	 * Runs on `safecss_skip_stylesheet` filter.
577
	 */
578
	public static function preview_skip_stylesheet( $skip_value ) {
579
		global $wp_customize;
580
		if ( ! is_customize_preview() ) {
581
			return $skip_value;
582
		}
583
584
		$setting = $wp_customize->get_setting( 'jetpack_custom_css[replace]' );
585
		if ( ! $setting ) {
586
			return $skip_value;
587
		}
588
589
		$customized_replace = $setting->post_value();
590
		if ( null !== $customized_replace ) {
591
			return $customized_replace;
592
		}
593
594
		return $skip_value;
595
	}
596
597
	/**
598
	 * Add Custom CSS section and controls.
599
	 */
600
	public static function customize_register( $wp_customize ) {
601
602
		// SETTINGS
603
604
		$wp_customize->add_setting( 'jetpack_custom_css[preprocessor]', array(
605
			'default' => '',
606
			'transport' => 'postMessage',
607
			'sanitize_callback' => array( __CLASS__, 'sanitize_preprocessor' ),
608
		) );
609
610
		$wp_customize->add_setting( 'jetpack_custom_css[replace]', array(
611
			'default' => false,
612
			'transport' => 'refresh',
613
		) );
614
615
		$wp_customize->add_setting( 'jetpack_custom_css[content_width]', array(
616
			'default' => '',
617
			'transport' => 'refresh',
618
			'sanitize_callback' => array( __CLASS__, 'intval_base10' ),
619
		) );
620
621
		// Add custom sanitization to the core css customizer setting.
622
		foreach ( $wp_customize->settings() as $setting ) {
623
			if ( $setting instanceof WP_Customize_Custom_CSS_Setting ) {
0 ignored issues
show
Bug introduced by
The class WP_Customize_Custom_CSS_Setting does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
624
				add_filter( "customize_sanitize_{$setting->id}", array( __CLASS__, 'sanitize_css_callback' ), 10, 2 );
625
			}
626
		}
627
628
		// CONTROLS
629
630
		// Overwrite the Core Control.
631
		$core_custom_css = $wp_customize->get_control( 'custom_css' );
632
		if ( $core_custom_css ) {
633
			$wp_customize->remove_control( 'custom_css' );
634
			$core_custom_css->type = 'jetpackCss';
635
			$wp_customize->add_control( $core_custom_css );
636
		}
637
638
		$wp_customize->selective_refresh->add_partial( 'custom_css', array(
639
			'type'                => 'custom_css',
640
			'selector'            => '#wp-custom-css',
641
			'container_inclusive' => false,
642
			'fallback_refresh'    => false,
643
			'settings'            => array(
644
				'custom_css[' . $wp_customize->get_stylesheet() . ']',
645
				'jetpack_custom_css[preprocessor]',
646
			),
647
			'render_callback' => array( __CLASS__, 'echo_custom_css_partial' ),
648
		) );
649
650
		$wp_customize->add_control( 'wpcom_custom_css_content_width_control', array(
651
			'type'     => 'text',
652
			'label'    => __( 'Media Width', 'jetpack' ),
653
			'section'  => 'custom_css',
654
			'settings' => 'jetpack_custom_css[content_width]',
655
		) );
656
657
		$wp_customize->add_control( 'jetpack_css_mode_control', array(
658
			'type'     => 'checkbox',
659
			'label'    => __( 'Don\'t use the theme\'s original CSS.', 'jetpack' ),
660
			'section'  => 'custom_css',
661
			'settings' => 'jetpack_custom_css[replace]',
662
		) );
663
664
		/**
665
		 * An action to grab on to if another Jetpack Module would like to add its own controls.
666
		 *
667
		 * @module custom-css
668
		 *
669
		 * @since 4.?.?
670
		 *
671
		 * @param $wp_customize The WP_Customize object.
672
		 */
673
		do_action( 'jetpack_custom_css_customizer_controls', $wp_customize );
674
675
		/** This filter is documented in modules/custom-css/custom-css.php */
676
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
677
		if ( ! empty( $preprocessors ) ) {
678
			$preprocessor_choices = array(
679
				'' => __( 'None', 'jetpack' ),
680
			);
681
682
			foreach ( $preprocessors as $preprocessor_key => $processor ) {
683
				$preprocessor_choices[$preprocessor_key] = $processor['name'];
684
			}
685
686
			$wp_customize->add_control( 'jetpack_css_preprocessors_control', array(
687
				'type'     => 'select',
688
				'choices'  => $preprocessor_choices,
689
				'label'    => __( 'Preprocessor', 'jetpack' ),
690
				'section'  => 'custom_css',
691
				'settings' => 'jetpack_custom_css[preprocessor]',
692
			) );
693
		}
694
695
	}
696
697
	/**
698
	 * The callback to handle sanitizing the CSS.  Takes different arguments, hence the proxy function.
699
	 *
700
	 * @param $css
701
	 * @param $setting
702
	 * @return mixed|string
703
	 */
704
	public static function sanitize_css_callback( $css, $setting ) {
0 ignored issues
show
Unused Code introduced by
The parameter $setting is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
705
		global $wp_customize;
706
		return self::sanitize_css( $css, array(
707
			'preprocessor' => $wp_customize->get_setting('jetpack_custom_css[preprocessor]')->value(),
708
		) );
709
	}
710
711
	/**
712
	 * Flesh out for wpcom.
713
	 *
714
	 * @todo
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
715
	 *
716
	 * @return bool
717
	 */
718
	public static function is_freetrial() {
719
		return false;
720
	}
721
722
	/**
723
	 * Flesh out for wpcom.
724
	 *
725
	 * @todo
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
726
	 *
727
	 * @return bool
728
	 */
729
	public static function is_preview() {
730
		return false;
731
	}
732
733
	/**
734
	 * Output the custom css for customize preview.
735
	 *
736
	 * @param $css
737
	 * @return mixed
738
	 */
739
	public static function customize_preview_wp_get_custom_css( $css ) {
740
		global $wp_customize;
741
742
		$preprocessor = $wp_customize->get_setting('jetpack_custom_css[preprocessor]')->value();
743
744
		// If it's empty, just return.
745
		if ( empty( $preprocessor ) ) {
746
			return $css;
747
		}
748
749
		/** This filter is documented in modules/custom-css/custom-css.php */
750
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
751
		if ( isset( $preprocessors[ $preprocessor ] ) ) {
752
			return call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
753
		}
754
755
		return $css;
756
	}
757
758
	/**
759
	 * @param $css
760
	 * @param $setting
761
	 * @return string
762
	 */
763
	public static function customize_value_custom_css( $css, $setting ) {
764
		// Find the current preprocessor
765
		$jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
766
		if ( isset( $jetpack_custom_css['preprocessor'] ) ) {
767
			$preprocessor = $jetpack_custom_css['preprocessor'];
768
		}
769
770
		// If it's not supported, just return.
771
		/** This filter is documented in modules/custom-css/custom-css.php */
772
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
773
		if ( ! isset( $preprocessors[ $preprocessor ] ) ) {
774
			return $css;
775
		}
776
777
		// Swap it for the `post_content_filtered` instead.
778
		$post = wp_get_custom_css_post( $setting->stylesheet );
779
		if ( $post && ! empty( $post->post_content_filtered ) ) {
780
			$css = $post->post_content_filtered;
781
		}
782
783
		return $css;
784
	}
785
786
	/**
787
	 * @param $args
788
	 * @param $css
789
	 * @param $setting
790
	 * @return mixed
791
	 */
792
	public static function customize_update_custom_css_post_content_args( $args, $css, $setting ) {
0 ignored issues
show
Unused Code introduced by
The parameter $setting is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
793
		// Find the current preprocessor
794
		$jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
795
		if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
796
			return $args;
797
		}
798
799
		$preprocessor = $jetpack_custom_css['preprocessor'];
800
		/** This filter is documented in modules/custom-css/custom-css.php */
801
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
802
803
		// If it's empty, just return.
804
		if ( empty( $preprocessor ) ) {
805
			return $args;
806
		}
807
808 View Code Duplication
		if ( isset( $preprocessors[ $preprocessor ] ) ) {
809
			$args['post_content_filtered'] = $css;
810
			$args['post_content'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
811
		}
812
813
		return $args;
814
	}
815
816
	/**
817
	 * Filter to handle the processing of preprocessed css on save.
818
	 *
819
	 * @param $args
820
	 * @param $stylesheet
821
	 * @return mixed
822
	 */
823
	public static function update_custom_css_data( $args, $stylesheet ) {
0 ignored issues
show
Unused Code introduced by
The parameter $stylesheet is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
824
		// Find the current preprocessor
825
		$jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
826
		if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
827
			return $args;
828
		}
829
830
		/** This filter is documented in modules/custom-css/custom-css.php */
831
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
832
		$preprocessor = $jetpack_custom_css['preprocessor'];
833
834
		// If we have a preprocessor specified ...
835
		if ( isset( $preprocessors[ $preprocessor ] ) ) {
836
			// And no other preprocessor has run ...
837
			if ( empty( $args['preprocessed'] ) ) {
838
				$args['preprocessed'] = $args['css'];
839
				$args['css'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $args['css'] );
840
			} else {
841
				trigger_error( 'Jetpack CSS Preprocessor specified, but something else has already modified the argument.', E_USER_WARNING );
842
			}
843
		}
844
845
		return $args;
846
	}
847
848
	/**
849
	 * When on the edit screen, make sure the custom content width
850
	 * setting is applied to the large image size.
851
	 *
852
	 * @param array $dims
853
	 * @param string $size
854
	 * @param null $context
855
	 * @return array
856
	 */
857 View Code Duplication
	static function editor_max_image_size( $dims, $size = 'medium', $context = null ) {
858
		list( $width, $height ) = $dims;
859
860
		if ( 'large' === $size && 'edit' === $context ) {
861
			$width = Jetpack::get_content_width();
862
		}
863
864
		return array( $width, $height );
865
	}
866
867
	/**
868
	 * Override the content_width with a custom value if one is set.
869
	 *
870
	 * @param $content_width
871
	 * @return int
872
	 */
873
	static function jetpack_content_width( $content_width ) {
874
		$custom_content_width = 0;
875
876
		$jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
877
		if ( isset( $jetpack_custom_css['content_width'] ) ) {
878
			$custom_content_width = $jetpack_custom_css['content_width'];
879
		}
880
881
		if ( $custom_content_width > 0 ) {
882
			return $custom_content_width;
883
		}
884
885
		return $content_width;
886
	}
887
888
	/**
889
	 * Currently this filter function gets called on
890
	 * 'template_redirect' action and
891
	 * 'admin_init' action
892
	 */
893 View Code Duplication
	static function set_content_width(){
894
		// Don't apply this filter on the Edit CSS page
895
		if ( isset( $_GET['page'] ) && 'editcss' === $_GET['page'] && is_admin() ) {
896
			return;
897
		}
898
899
		$GLOBALS['content_width'] = Jetpack::get_content_width();
900
	}
901
902
	/**
903
	 * Make sure the preprocessor we're saving is one we know about.
904
	 *
905
	 * @param $preprocessor The preprocessor to sanitize.
906
	 * @return null|string
907
	 */
908
	public static function sanitize_preprocessor( $preprocessor ) {
909
		/** This filter is documented in modules/custom-css/custom-css.php */
910
		$preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
911
		if ( empty( $preprocessor ) || array_key_exists( $preprocessor, $preprocessors ) ) {
912
			return $preprocessor;
913
		}
914
		return null;
915
	}
916
917
	/**
918
	 * Get the base10 intval.
919
	 *
920
	 * This is used as a setting's sanitize_callback; we can't use just plain
921
	 * intval because the second argument is not what intval() expects.
922
	 *
923
	 * @access public
924
	 *
925
	 * @param mixed $value Number to convert.
926
	 * @return int Integer.
927
	 */
928
	public static function intval_base10( $value ) {
929
		return intval( $value, 10 );
930
	}
931
932
	/**
933
	 * Add a footer action on revision.php to print some customizations for the theme switcher.
934
	 */
935
	public static function load_revision_php() {
936
		add_action( 'admin_footer', array( __CLASS__, 'revision_admin_footer' ) );
937
	}
938
939
	/**
940
	 * Print the theme switcher on revision.php and move it into place.
941
	 */
942
	public static function revision_admin_footer() {
943
		$post = get_post();
944
		if ( 'custom_css' !== $post->post_type ) {
945
			return;
946
		}
947
		$stylesheet = $post->post_title;
948
		?>
949
<script type="text/html" id="tmpl-other-themes-switcher">
950
	<?php self::revisions_switcher_box( $stylesheet ); ?>
951
</script>
952
<style>
953
.other-themes-wrap {
954
	float: right;
955
	background-color: #fff;
956
	-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
957
	box-shadow: 0 1px 3px rgba(0,0,0,0.1);
958
	padding: 5px 10px;
959
	margin-bottom: 10px;
960
}
961
.other-themes-wrap label {
962
	display: block;
963
	margin-bottom: 10px;
964
}
965
.other-themes-wrap select {
966
	float: left;
967
	width: 77%;
968
}
969
.other-themes-wrap button {
970
	float: right;
971
	width: 20%;
972
}
973
.revisions {
974
	clear: both;
975
}
976
/* Hide the back-to-post link */
977
.long-header + a {
978
	display: none;
979
}
980
</style>
981
<script>
982
(function($){
983
	var switcher = $('#tmpl-other-themes-switcher').html(),
984
		qty = $( switcher ).find('select option').length,
985
		$switcher;
986
987
	if ( qty >= 3 ) {
988
		$('h1.long-header').before( switcher );
989
		$switcher = $('.other-themes-wrap');
990
		$switcher.find('button').on('click', function(e){
991
			e.preventDefault();
992
			if ( $switcher.find('select').val() ) {
993
				window.location.href = $switcher.find('select').val();
994
			}
995
		})
996
	}
997
})(jQuery);
998
</script>
999
		<?php
1000
	}
1001
1002
	/**
1003
	 * The HTML for the theme revision switcher box.
1004
	 *
1005
	 * @param string $stylesheet
1006
	 */
1007
	public static function revisions_switcher_box( $stylesheet = '' ) {
1008
		$themes = self::get_all_themes_with_custom_css();
1009
		?>
1010
		<div class="other-themes-wrap">
1011
			<label for="other-themes"><?php esc_html_e( 'Select another theme to view its custom CSS.', 'jetpack' ); ?></label>
1012
			<select id="other-themes">
1013
				<option value=""><?php esc_html_e( 'Select a theme&hellip;', 'jetpack' ); ?></option>
1014
				<?php
1015
				foreach ( $themes as $theme_stylesheet => $data ) {
1016
					$revisions = wp_get_post_revisions( $data['post']->ID, array( 'posts_per_page' => 1 ) );
1017
					if ( ! $revisions ) {
1018
						?>
1019
						<option value="<?php echo esc_url( add_query_arg( 'id', $data['post']->ID, menu_page_url( 'editcss', 0 ) ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
1020
							<?php echo esc_html( $data['label'] ); ?>
1021
							<?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
1022
						<?php
1023
						continue;
1024
					}
1025
					$revision = array_shift( $revisions );
1026
					?>
1027
					<option value="<?php echo esc_url( get_edit_post_link( $revision->ID ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
1028
						<?php echo esc_html( $data['label'] ); ?>
1029
						<?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
1030
					<?php
1031
				}
1032
				?>
1033
			</select>
1034
			<button class="button" id="other_theme_custom_css_switcher"><?php esc_html_e( 'Switch', 'jetpack' ); ?></button>
1035
		</div>
1036
		<?php
1037
	}
1038
}
1039
1040
Jetpack_Custom_CSS_Enhancements::add_hooks();
1041
1042 View Code Duplication
if ( ! function_exists( 'safecss_class' ) ) :
1043
/**
1044
 * Load in the class only when needed.  Makes lighter load by having one less class in memory.
1045
 */
1046
function safecss_class() {
1047
	// Wrapped so we don't need the parent class just to load the plugin
1048
	if ( class_exists('safecss') ) {
1049
		return;
1050
	}
1051
1052
	require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' );
1053
1054
	/**
1055
	 * Class safecss
1056
	 */
1057
	class safecss extends csstidy_optimise {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1058
1059
		function postparse() {
1060
1061
			/**
1062
			 * Fires after parsing the css.
1063
			 *
1064
			 * @module custom-css
1065
			 *
1066
			 * @since 1.8.0
1067
			 *
1068
			 * @param obj $this CSSTidy object.
1069
			 */
1070
			do_action( 'csstidy_optimize_postparse', $this );
1071
1072
			return parent::postparse();
1073
		}
1074
1075
		function subvalue() {
1076
1077
			/**
1078
			 * Fires before optimizing the Custom CSS subvalue.
1079
			 *
1080
			 * @module custom-css
1081
			 *
1082
			 * @since 1.8.0
1083
			 *
1084
			 * @param obj $this CSSTidy object.
1085
			 **/
1086
			do_action( 'csstidy_optimize_subvalue', $this );
1087
1088
			return parent::subvalue();
1089
		}
1090
	}
1091
}
1092
endif;
1093