Completed
Push — develop ( 23532a...da90a3 )
by Aristeides
03:21
created

Kirki_Modules_PostMessage   D

Complexity

Total Complexity 119

Size/Duplication

Total Lines 517
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 517
rs 4.8717
c 0
b 0
f 0
wmc 119
lcom 2
cbo 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A get_instance() 0 6 2
C postmessage() 0 24 12
F script() 0 77 18
B script_html_var() 0 16 5
C script_var() 0 38 11
F script_var_array() 0 63 14
C script_var_typography() 0 61 36
A script_var_image() 0 7 1
B before_script() 0 28 3
B get_args() 0 25 5
A value_pattern_replacements() 0 20 4
B get_callback() 0 20 7

How to fix   Complexity   

Complex Class

Complex classes like Kirki_Modules_PostMessage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Kirki_Modules_PostMessage, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Automatic postMessage scripts calculation for Kirki controls.
4
 *
5
 * @package     Kirki
6
 * @category    Modules
7
 * @author      Aristeides Stathopoulos
8
 * @copyright   Copyright (c) 2017, Aristeides Stathopoulos
9
 * @license     http://opensource.org/licenses/https://opensource.org/licenses/MIT
10
 * @since       3.0.0
11
 */
12
13
// Exit if accessed directly.
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
/**
19
 * Adds styles to the customizer.
20
 */
21
class Kirki_Modules_PostMessage {
22
	/**
23
	 * The object instance.
24
	 *
25
	 * @static
26
	 * @access private
27
	 * @since 3.0.0
28
	 * @var object
29
	 */
30
	private static $instance;
31
32
	/**
33
	 * The script.
34
	 *
35
	 * @access protected
36
	 * @since 3.0.0
37
	 * @var string
38
	 */
39
	protected $script = '';
40
41
	/**
42
	 * Constructor.
43
	 *
44
	 * @access protected
45
	 * @since 3.0.0
46
	 */
47
	protected function __construct() {
48
		add_action( 'customize_preview_init', array( $this, 'postmessage' ) );
49
	}
50
51
	/**
52
	 * Gets an instance of this object.
53
	 * Prevents duplicate instances which avoid artefacts and improves performance.
54
	 *
55
	 * @static
56
	 * @access public
57
	 * @since 3.0.0
58
	 * @return object
59
	 */
60
	public static function get_instance() {
61
		if ( ! self::$instance ) {
62
			self::$instance = new self();
63
		}
64
		return self::$instance;
65
	}
66
67
	/**
68
	 * Enqueues the postMessage script
69
	 * and adds variables to it using the wp_localize_script function.
70
	 * The rest is handled via JS.
71
	 */
72
	public function postmessage() {
73
74
		wp_enqueue_script( 'kirki_auto_postmessage', trailingslashit( Kirki::$url ) . 'modules/postmessage/postmessage.js', array( 'jquery', 'customize-preview' ), false, true );
75
		$fields = Kirki::$fields;
76
		$script_id = '';
77
		foreach ( $fields as $field ) {
78
			if ( ! isset( $field['settings'] ) || ! is_string( $field['settings'] ) ) {
79
				continue;
80
			}
81
			$script_id .= $field['settings'];
82
		}
83
		$this->script = get_transient( 'kirki_auto_postmessage' . md5( $script_id ) );
84
		if ( ! $this->script ) {
85
			foreach ( $fields as $field ) {
86
				if ( isset( $field['transport'] ) && 'postMessage' === $field['transport'] && isset( $field['js_vars'] ) && ! empty( $field['js_vars'] ) && is_array( $field['js_vars'] ) && isset( $field['settings'] ) ) {
87
					$this->script .= $this->script( $field );
88
				}
89
			}
90
			$this->script = apply_filters( 'kirki/postmessage/script', $this->script );
91
			set_transient( 'kirki_auto_postmessage' . md5( $script_id ), $this->script, 30 * MINUTE_IN_SECONDS );
92
		}
93
		wp_add_inline_script( 'kirki_auto_postmessage', $this->script, 'after' );
94
95
	}
96
97
	/**
98
	 * Generates script for a single field.
99
	 *
100
	 * @access protected
101
	 * @since 3.0.0
102
	 * @param array $args The arguments.
103
	 */
104
	protected function script( $args ) {
105
106
		$script = 'wp.customize(\'' . $args['settings'] . '\',function(value){value.bind(function(newval){';
107
108
		$add_css = false;
109
		foreach ( $args['js_vars'] as $js_var ) {
110
			if ( ! isset( $js_var['function'] ) || 'html' !== $js_var['function'] ) {
111
				$add_css = true;
112
			}
113
		}
114
115
		if ( $add_css ) {
116
117
			// append unique style tag if not exist
118
			// The style ID.
119
			$style_id = 'kirki-postmessage-' . str_replace( array( '[', ']' ), '', $args['settings'] );
120
			$script .= 'if(null===document.getElementById(\'' . $style_id . '\')||\'undefined\'===typeof document.getElementById(\'' . $style_id . '\')){jQuery(\'head\').append(\'<style id="' . $style_id . '"></style>\');}';
121
		}
122
123
		// Add anything we need before the main script.
124
		$script .= $this->before_script( $args );
125
126
		$field = array(
127
			'scripts' => array(),
128
		);
129
		// Loop through the js_vars and generate the script.
130
		foreach ( $args['js_vars'] as $key => $js_var ) {
131
132
			// Skip styles if "exclude" is defined and value is excluded.
133
			if ( isset( $js_var['exclude'] ) ) {
134
				$js_var['exclude'] = (array) $js_var['exclude'];
135
				$script .= 'exclude=false;';
136
				foreach ( $js_var['exclude'] as $exclussion ) {
137
					$script .= "if(newval=='{$exclussion}'||(''==='{$exclussion}'&&_.isObject(newval)&&_.isEmpty(newval))){exclude=true;}";
138
				}
139
			}
140
			if ( isset( $js_var['element'] ) ) {
141
				// Array to string.
142
				if ( is_array( $js_var['element'] ) ) {
143
					$js_var['element'] = array_unique( $js_var['element'] );
144
					$js_var['element'] = implode( ',', $js_var['element'] );
145
				}
146
				// Replace single quotes with double quotes to avoid issues with the compiled JS.
147
				$js_var['element'] = str_replace( '\'', '"', $js_var['element'] );
148
			}
149
			if ( isset( $js_var['function'] ) && 'html' === $js_var['function'] ) {
150
				$script .= $this->script_html_var( $js_var );
151
				continue;
152
			}
153
			$js_var['index_key'] = $key;
154
			$callback = $this->get_callback( $args );
155
			if ( is_callable( $callback ) ) {
156
				$field['scripts'][ $key ] = call_user_func_array( $callback, array( $js_var, $args ) );
157
				continue;
158
			}
159
			$field['scripts'][ $key ] = $this->script_var( $js_var );
160
		}
161
		$combo_extra_script = '';
162
		$combo_css_script   = '';
163
		foreach ( $field['scripts'] as $script_array ) {
164
			$combo_extra_script .= $script_array['script'];
165
			$combo_css_script   .= ( 'css' !== $combo_css_script ) ? $script_array['css'] : '';
166
		}
167
		$text = ( 'css' === $combo_css_script ) ? 'css' : '\'' . $combo_css_script . '\'';
168
169
		$script .= $combo_extra_script;
170
		$script .= "var cssContent={$text};";
171
		if ( isset( $js_var['exclude'] ) ) {
172
			$script .= 'if(true===exclude){cssContent="";}';
173
		}
174
		if ( $add_css ) {
175
			$script .= "jQuery('#{$style_id}').text(cssContent);";
176
			$script .= "jQuery('#{$style_id}').appendTo('head');";
177
		}
178
		$script .= '});});';
179
		return $script;
180
	}
181
182
	/**
183
	 * Generates script for a single js_var when using "html" as function.
184
	 *
185
	 * @access protected
186
	 * @since 3.0.0
187
	 * @param array $args  The arguments for this js_var.
188
	 */
189
	protected function script_html_var( $args ) {
190
191
		$script  = ( isset( $args['choice'] ) ) ? "newval=newval['{$args['choice']}'];" : '';
192
193
		// Apply the value_pattern.
194
		if ( isset( $args['value_pattern'] ) && '' !== $args['value_pattern'] ) {
195
			$script .= $this->value_pattern_replacements( 'newval', $args );
196
		}
197
198
		if ( isset( $args['attr'] ) ) {
199
			$script .= "jQuery('{$args['element']}').attr('{$args['attr']}',newval);";
200
			return $script;
201
		}
202
		$script .= "jQuery('{$args['element']}').html(newval);";
203
		return $script;
204
	}
205
206
	/**
207
	 * Generates script for a single js_var.
208
	 *
209
	 * @access protected
210
	 * @since 3.0.0
211
	 * @param array $args  The arguments for this js_var.
212
	 */
213
	protected function script_var( $args ) {
214
		$script = '';
215
		$property_script = '';
216
217
		$value_key = 'newval' . $args['index_key'];
218
		$property_script .= $value_key . '=newval;';
219
220
		$args = $this->get_args( $args );
221
222
		// Apply callback to the value if a callback is defined.
223
		if ( ! empty( $args['js_callback'] ) && is_array( $args['js_callback'] ) && isset( $args['js_callback'][0] ) && ! empty( $args['js_callback'][0] ) ) {
224
			$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
225
		}
226
227
		// Apply the value_pattern.
228
		if ( '' !== $args['value_pattern'] ) {
229
			$script .= $this->value_pattern_replacements( $value_key, $args );
230
		}
231
232
		// Tweak to add url() for background-images.
233
		if ( 'background-image' === $args['property'] && ( ! isset( $args['value_pattern'] ) || false === strpos( $args['value_pattern'], 'gradient' ) ) ) {
234
			$script .= 'if(-1===' . $value_key . '.indexOf(\'url(\')){' . $value_key . '=\'url("\'+' . $value_key . '+\'")\';}';
235
		}
236
237
		// Apply prefix.
238
		$value = $value_key;
239
		if ( '' !== $args['prefix'] ) {
240
			$value = "'" . $args['prefix'] . "'+" . $value_key;
241
		}
242
		$css = $args['element'] . '{' . $args['property'] . ':\'+' . $value . '+\'' . $args['units'] . $args['suffix'] . ';}';
243
		if ( isset( $args['media_query'] ) ) {
244
			$css = $args['media_query'] . '{' . $css . '}';
245
		}
246
		return array(
247
			'script' => $property_script . $script,
248
			'css'    => $css,
249
		);
250
	}
251
252
	/**
253
	 * Processes script generation for fields that save an array.
254
	 *
255
	 * @access protected
256
	 * @since 3.0.0
257
	 * @param array $args  The arguments for this js_var.
258
	 */
259
	protected function script_var_array( $args ) {
260
261
		$script = ( 0 === $args['index_key'] ) ? 'css=\'\';' : '';
262
		$property_script = '';
263
264
		// Define choice.
265
		$choice  = ( isset( $args['choice'] ) && '' !== $args['choice'] ) ? $args['choice'] : '';
266
267
		$value_key = 'newval' . $args['index_key'];
268
		$property_script .= $value_key . '=newval;';
269
270
		$args = $this->get_args( $args );
271
272
		// Apply callback to the value if a callback is defined.
273
		if ( ! empty( $args['js_callback'] ) && is_array( $args['js_callback'] ) && isset( $args['js_callback'][0] ) && ! empty( $args['js_callback'][0] ) ) {
274
			$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
275
		}
276
		$script .= '_.each(' . $value_key . ', function(subValue,subKey){';
277
278
		// Apply the value_pattern.
279
		if ( '' !== $args['value_pattern'] ) {
280
			$script .= $this->value_pattern_replacements( 'subValue', $args );
281
		}
282
283
		// Tweak to add url() for background-images.
284
		if ( '' === $choice || 'background-image' === $choice ) {
285
			$script .= 'if(\'background-image\'===\'' . $args['property'] . '\'||\'background-image\'===subKey){';
286
			$script .= 'if(-1===subValue.indexOf(\'url(\')){subValue=\'url("\'+subValue+\'")\';}';
287
			$script .= '}';
288
		}
289
290
		// Apply prefix.
291
		$value = $value_key;
292
		if ( '' !== $args['prefix'] ) {
293
			$value = '\'' . $args['prefix'] . '\'+subValue';
294
		}
295
296
		// Allows us to apply this just for a specific choice in the array of the values.
297
		if ( '' !== $choice ) {
298
			$script .= 'if(\'' . $choice . '\'===subKey){';
299
			$script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . ':\'+subValue+\';}\';';
300
			$script .= '}';
301
		} else {
302
			// Mostly used for padding, margin & position properties.
303
			$script .= 'if(_.contains([\'top\',\'bottom\',\'left\',\'right\'],subKey)){';
304
			$script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . '-\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';}';
305
			$script .= 'else{';
306
307
			// This is where most object-based fields will go.
308
			$script .= 'css+=\'' . $args['element'] . '{\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';';
309
			$script .= '}';
310
		}
311
		$script .= '});';
312
313
		if ( isset( $args['media_query'] ) ) {
314
			$script .= 'css=\'' . $args['media_query'] . '{\'+css+\'}\';';
315
		}
316
317
		return array(
318
			'script' => $property_script . $script,
319
			'css'    => 'css',
320
		);
321
	}
322
323
	/**
324
	 * Processes script generation for typography fields.
325
	 *
326
	 * @access protected
327
	 * @since 3.0.0
328
	 * @param array $args  The arguments for this js_var.
329
	 * @param array $field The field arguments.
330
	 */
331
	protected function script_var_typography( $args, $field ) {
332
333
		$script = '';
334
		$css    = '';
335
336
		// Load the font using WenFontloader.
337
		// This is a bit ugly because wp_add_inline_script doesn't allow adding <script> directly.
338
		$webfont_loader = 'sc=\'a\';jQuery(\'head\').append(sc.replace(\'a\',\'<\')+\'script>if(!_.isUndefined(WebFont)&&fontFamily){WebFont.load({google:{families:["\'+fontFamily.replace( /\"/g, \'&quot;\' )+\':\'+variant+subsetsString+\'"]}});}\'+sc.replace(\'a\',\'<\')+\'/script>\');';
339
340
		// Add the css.
341
		$css_build_array = array(
342
			'font-family'    => 'fontFamily',
343
			'font-size'      => 'fontSize',
344
			'line-height'    => 'lineHeight',
345
			'letter-spacing' => 'letterSpacing',
346
			'word-spacing'   => 'wordSpacing',
347
			'text-align'     => 'textAlign',
348
			'text-transform' => 'textTransform',
349
			'color'          => 'color',
350
			'font-weight'    => 'fontWeight',
351
			'font-style'     => 'fontStyle',
352
		);
353
		$choice_condition = ( isset( $args['choice'] ) && '' !== $args['choice'] && isset( $css_build_array[ $args['choice'] ] ) );
354
		$script .= ( ! $choice_condition ) ? $webfont_loader : '';
355
		foreach ( $css_build_array as $property => $var ) {
356
			if ( $choice_condition && $property !== $args['choice'] ) {
357
				continue;
358
			}
359
			// Fixes https://github.com/aristath/kirki/issues/1436.
360
			if ( ! isset( $field['default'] ) || (
361
				( 'font-family' === $property && ! isset( $field['default']['font-family'] ) ) ||
362
				( 'font-size' === $property && ! isset( $field['default']['font-size'] ) ) ||
363
				( 'line-height' === $property && ! isset( $field['default']['line-height'] ) ) ||
364
				( 'letter-spacing' === $property && ! isset( $field['default']['letter-spacing'] ) ) ||
365
				( 'word-spacing' === $property && ! isset( $field['default']['word-spacing'] ) ) ||
366
				( 'text-align' === $property && ! isset( $field['default']['text-align'] ) ) ||
367
				( 'text-transform' === $property && ! isset( $field['default']['text-transform'] ) ) ||
368
				( 'color' === $property && ! isset( $field['default']['color'] ) ) ||
369
				( 'font-weight' === $property && ! isset( $field['default']['variant'] ) && ! isset( $field['default']['font-weight'] ) ) ||
370
				( 'font-style' === $property && ! isset( $field['default']['variant'] ) && ! isset( $field['default']['font-style'] ) )
371
				) ) {
372
				continue;
373
			}
374
			$script .= ( $choice_condition && 'font-family' === $args['choice'] ) ? $webfont_loader : '';
375
376
			if ( 'font-family' === $property || ( isset( $args['choice'] ) && 'font-family' === $args['choice'] ) ) {
377
				$css .= 'fontFamilyCSS=fontFamily;if(0<fontFamily.indexOf(\' \')&&-1===fontFamily.indexOf(\'"\')){fontFamilyCSS=\'"\'+fontFamily+\'"\';}';
378
				$var = 'fontFamilyCSS';
379
			}
380
			$css .= 'css+=(\'\'!==' . $var . ')?\'' . $args['element'] . '\'+\'{' . $property . ':\'+' . $var . '+\';}\':\'\';';
381
		}
382
383
		$script .= $css;
384
		if ( isset( $args['media_query'] ) ) {
385
			$script .= 'css=\'' . $args['media_query'] . '{\'+css+\'}\';';
386
		}
387
		return array(
388
			'script' => $script,
389
			'css'    => 'css',
390
		);
391
	}
392
393
	/**
394
	 * Processes script generation for typography fields.
395
	 *
396
	 * @access protected
397
	 * @since 3.0.0
398
	 * @param array $args  The arguments for this js_var.
399
	 */
400
	protected function script_var_image( $args ) {
401
		$return = $this->script_var( $args );
402
		return array(
403
			'script' => 'newval=(!_.isUndefined(newval.url))?newval.url:newval;' . $return['script'],
404
			'css'    => $return['css'],
405
		);
406
	}
407
408
	/**
409
	 * Adds anything we need before the main script.
410
	 *
411
	 * @access private
412
	 * @since 3.0.0
413
	 * @param array $args The field args.
414
	 * @return string
415
	 */
416
	private function before_script( $args ) {
417
418
		$script = '';
419
420
		if ( isset( $args['type'] ) ) {
421
			switch ( $args['type'] ) {
422
				case 'kirki-typography':
423
					$script .= 'fontFamily=(_.isUndefined(newval[\'font-family\']))?\'\':newval[\'font-family\'];';
424
					$script .= 'variant=(_.isUndefined(newval.variant))?\'400\':newval.variant;';
425
					$script .= 'subsets=(_.isUndefined(newval.subsets))?[]:newval.subsets;';
426
					$script .= 'subsetsString=(_.isObject(newval.subsets))?\':\'+newval.subsets.join(\',\'):\'\';';
427
					$script .= 'fontSize=(_.isUndefined(newval[\'font-size\']))?\'\':newval[\'font-size\'];';
428
					$script .= 'lineHeight=(_.isUndefined(newval[\'line-height\']))?\'\':newval[\'line-height\'];';
429
					$script .= 'letterSpacing=(_.isUndefined(newval[\'letter-spacing\']))?\'\':newval[\'letter-spacing\'];';
430
					$script .= 'wordSpacing=(_.isUndefined(newval[\'word-spacing\']))?\'\':newval[\'word-spacing\'];';
431
					$script .= 'textAlign=(_.isUndefined(newval[\'text-align\']))?\'\':newval[\'text-align\'];';
432
					$script .= 'textTransform=(_.isUndefined(newval[\'text-transform\']))?\'\':newval[\'text-transform\'];';
433
					$script .= 'color=(_.isUndefined(newval.color))?\'\':newval.color;';
434
435
					$script .= 'fw=(!_.isString(newval.variant))?\'400\':newval.variant.match(/\d/g);';
436
					$script .= 'fontWeight=(!_.isObject(fw))?400:fw.join(\'\');';
437
					$script .= 'fontStyle=(-1!==variant.indexOf(\'italic\'))?\'italic\':\'normal\';';
438
					$script .= 'css=\'\';';
439
					break;
440
			}
441
		}
442
		return $script;
443
	}
444
445
	/**
446
	 * Sanitizes the arguments and makes sure they are all there.
447
	 *
448
	 * @access private
449
	 * @since 3.0.0
450
	 * @param array $args The arguments.
451
	 * @return array
452
	 */
453
	private function get_args( $args ) {
454
455
		// Make sure everything is defined to avoid "undefined index" errors.
456
		$args = wp_parse_args( $args, array(
457
			'element'       => '',
458
			'property'      => '',
459
			'prefix'        => '',
460
			'suffix'        => '',
461
			'units'         => '',
462
			'js_callback'   => array( '', '' ),
463
			'value_pattern' => '',
464
		));
465
466
		// Element should be a string.
467
		if ( is_array( $args['element'] ) ) {
468
			$args['element'] = implode( ',', $args['element'] );
469
		}
470
471
		// Make sure arguments that are passed-on to callbacks are strings.
472
		if ( is_array( $args['js_callback'] ) && isset( $args['js_callback'][1] ) && is_array( $args['js_callback'][1] ) ) {
473
			$args['js_callback'][1] = wp_json_encode( $args['js_callback'][1] );
474
		}
475
		return $args;
476
477
	}
478
479
	/**
480
	 * Returns script for value_pattern & replacements.
481
	 *
482
	 * @access private
483
	 * @since 3.0.0
484
	 * @param string $value   The value placeholder.
485
	 * @param array  $js_vars The js_vars argument.
486
	 * @return string         The script.
487
	 */
488
	private function value_pattern_replacements( $value, $js_vars ) {
489
		$script = '';
490
		$alias  = $value;
491
		if ( ! isset( $js_vars['value_pattern'] ) ) {
492
			return $value;
493
		}
494
		$value = $js_vars['value_pattern'];
495
		if ( isset( $js_vars['pattern_replace'] ) ) {
496
			$script .= 'settings=window.wp.customize.get();';
497
			foreach ( $js_vars['pattern_replace'] as $search => $replace ) {
498
				$replace = '\'+settings["' . $replace . '"]+\'';
499
				$value = str_replace( $search, $replace, $js_vars['value_pattern'] );
500
				$value = trim( $value, '+' );
501
			}
502
		}
503
		$value_compiled = str_replace( '$', '\'+' . $alias . '+\'', $value );
504
		$value_compiled = trim( $value_compiled, '+' );
505
506
		return $script . $alias . '=\'' . $value_compiled . '\';';
507
	}
508
509
	/**
510
	 * Get the callback function/method we're going to use for this field.
511
	 *
512
	 * @access private
513
	 * @since 3.0.0
514
	 * @param array $args The field args.
515
	 * @return string|array A callable function or method.
516
	 */
517
	protected function get_callback( $args ) {
518
519
		switch ( $args['type'] ) {
520
			case 'kirki-background':
521
			case 'kirki-dimensions':
522
			case 'kirki-multicolor':
523
			case 'kirki-sortable':
524
				$callback = array( $this, 'script_var_array' );
525
				break;
526
			case 'kirki-typography':
527
				$callback = array( $this, 'script_var_typography' );
528
				break;
529
			case 'kirki-image':
530
				$callback = array( $this, 'script_var_image' );
531
				break;
532
			default:
533
				$callback = array( $this, 'script_var' );
534
		}
535
		return $callback;
536
	}
537
}
538