Completed
Push — update/custom-css-oversanitize ( 02dabc...bc1957 )
by
unknown
52:05 queued 43:35
created

Jetpack_Custom_CSS_Enhancements::customizer_link()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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