Completed
Pull Request — develop (#1334)
by Aristeides
02:52
created

Kirki_Modules_PostMessage::script()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 18
nop 1
dl 0
loc 31
rs 8.439
c 0
b 0
f 0
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
	/**
24
	 * The script.
25
	 *
26
	 * @access protected
27
	 * @since 3.0.0
28
	 * @var string
29
	 */
30
	protected $script = '';
31
32
	/**
33
	 * Constructor.
34
	 *
35
	 * @access public
36
	 * @since 3.0.0
37
	 */
38
	public function __construct() {
39
		add_action( 'customize_preview_init', array( $this, 'postmessage' ) );
40
	}
41
42
	/**
43
	 * Enqueues the postMessage script
44
	 * and adds variables to it using the wp_localize_script function.
45
	 * The rest is handled via JS.
46
	 */
47
	public function postmessage() {
48
49
		wp_enqueue_script( 'kirki_auto_postmessage', trailingslashit( Kirki::$url ) . 'modules/postmessage/postmessage.js', array( 'jquery', 'customize-preview' ), false, true );
50
		$fields = Kirki::$fields;
51
		foreach ( $fields as $field ) {
52
			if ( isset( $field['transport'] ) && 'postMessage' === $field['transport'] && isset( $field['js_vars'] ) && ! empty( $field['js_vars'] ) && is_array( $field['js_vars'] ) && isset( $field['settings'] ) ) {
53
				$this->script .= $this->script( $field );
54
			}
55
		}
56
		$this->script = apply_filters( 'kirki/postmessage/script', $this->script );
57
		wp_add_inline_script( 'kirki_auto_postmessage', $this->script, 'after' );
58
59
	}
60
61
	/**
62
	 * Generates script for a single field.
63
	 *
64
	 * @access protected
65
	 * @since 3.0.0
66
	 * @param array $args The arguments.
67
	 */
68
	protected function script( $args ) {
69
70
		$script = 'wp.customize(\'' . $args['settings'] . '\',function(value){value.bind(function(newval){';
71
		// append unique style tag if not exist
72
		// The style ID.
73
		$style_id = 'kirki-postmessage-' . str_replace( array( '[', ']' ), '', $args['settings'] );
74
		$script .= 'if(!jQuery(\'' . $style_id . '\').size()){jQuery(\'head\').append(\'<style id="' . $style_id . '"></style>\');}';
75
76
		// Add anything we need before the main script.
77
		$script .= $this->before_script( $args );
78
		// Loop through the js_vars and generate the script.
79
		foreach ( $args['js_vars'] as $key => $js_var ) {
80
			$js_var['index_key'] = $key;
81
			$callback = $this->get_callback( $args );
82
			if ( is_callable( $callback ) ) {
83
				$field['scripts'][ $key ] = call_user_func_array( $callback, array( $js_var, $args ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$field was never initialized. Although not strictly required by PHP, it is generally a good practice to add $field = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
84
				continue;
85
			}
86
			$field['scripts'][ $key ] = $this->script_var( $js_var, $args );
0 ignored issues
show
Bug introduced by
The variable $field does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Unused Code introduced by
The call to Kirki_Modules_PostMessage::script_var() has too many arguments starting with $args.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
87
		}
88
		$combo_extra_script = '';
89
		$combo_css_script   = '';
90
		foreach ( $field['scripts'] as $script_array ) {
91
			$combo_extra_script .= $script_array['script'];
92
			$combo_css_script   .= ( 'css' !== $combo_css_script ) ? $script_array['css'] : '';
93
		}
94
		$text = ( 'css' === $combo_css_script ) ? 'css' : '\'' . $combo_css_script . '\'';
95
		$script .= $combo_extra_script . 'jQuery(\'#' . $style_id . '\').text(' . $text . ');';
96
		$script .= '});});';
97
		return $script;
98
	}
99
100
	/**
101
	 * Generates script for a single js_var.
102
	 *
103
	 * @access protected
104
	 * @since 3.0.0
105
	 * @param array $args  The arguments for this js_var.
106
	 */
107
	protected function script_var( $args ) {
108
		$script = '';
109
		$property_script = '';
110
111
		$value_key = 'newval' . $args['index_key'];
112
		$property_script .= $value_key . '=newval;';
113
114
		$args = $this->get_args( $args );
115
116
		// Apply callback to the value if a callback is defined.
117
		if ( ! empty( $args['js_callback'][0] ) ) {
118
			$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
119
		}
120
121
		// Apply the value_pattern.
122
		if ( '' !== $args['value_pattern'] ) {
123
			$script .= $this->value_pattern_replacements( $value_key, $args );
124
		}
125
126
		// Tweak to add url() for background-images.
127
		if ( 'background-image' === $args['property'] ) {
128
			$script .= 'if(-1===' . $value_key . '.indexOf(\'url(\')){' . $value_key . '=\'url("\'+' . $value_key . '+\'");}';
129
		}
130
131
		// Apply prefix.
132
		$value = $value_key;
133
		if ( '' !== $args['prefix'] ) {
134
			$value = $args['prefix'] . '+' . $value_key;
135
		}
136
137
		return array(
138
			'script' => $property_script . $script,
139
			'css'    => $args['element'] . '{' . $args['property'] . ':\'+' . $value . '+\'' . $args['units'] . $args['suffix'] . ';}',
140
		);
141
	}
142
143
	/**
144
	 * Processes script generation for fields that save an array.
145
	 *
146
	 * @access protected
147
	 * @since 3.0.0
148
	 * @param array $args  The arguments for this js_var.
149
	 */
150
	protected function script_var_array( $args ) {
151
152
		$script = 'css=\'\';';
153
		$property_script = '';
154
155
		// Define choice.
156
		$choice  = ( isset( $args['choice'] ) && '' !== $args['choice'] ) ? $args['choice'] : '';
157
		$script .= ( '' !== $choice ) ? 'choice=\'' . $choice . '\';' : '';
158
159
		$value_key = 'newval' . $args['index_key'];
160
		$property_script .= $value_key . '=newval;';
161
162
		$args = $this->get_args( $args );
163
164
		// Apply callback to the value if a callback is defined.
165
		if ( ! empty( $args['js_callback'][0] ) ) {
166
			$script .= $value_key . '=' . $args['js_callback'][0] . '(' . $value_key . ',' . $args['js_callback'][1] . ');';
167
		}
168
		$script .= '_.each(' . $value_key . ', function(subValue,subKey){';
169
170
		// Apply the value_pattern.
171
		if ( '' !== $args['value_pattern'] ) {
172
			$script .= $this->value_pattern_replacements( 'subValue', $args );
173
		}
174
175
		// Tweak to add url() for background-images.
176
		if ( '' === $choice || 'background-image' === $choice ) {
177
			$script .= 'if(\'background-image\'===\'' . $args['property'] . '\'||\'background-image\'===subKey){';
178
			$script .= 'if(-1===subValue.indexOf(\'url(\')){subValue=\'url("\'+subValue+\'")\';}';
179
			$script .= '}';
180
		}
181
182
		// Apply prefix.
183
		$value = $value_key;
184
		if ( '' !== $args['prefix'] ) {
185
			$value = '\'' . $args['prefix'] . '\'+subValue';
186
		}
187
188
		// Mostly used for padding, margin & position properties.
189
		$direction_script  = 'if(_.contains([\'top\',\'bottom\',\'left\',\'right\'],subKey)){';
190
		$direction_script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . '-\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';}';
191
		// Allows us to apply this just for a specific choice in the array of the values.
192
		if ( '' !== $choice ) {
193
			$choice_is_direction = ( false !== strpos( $choice, 'top' ) || false !== strpos( $choice, 'bottom' ) || false !== strpos( $choice, 'left' ) || false !== strpos( $choice, 'right' ) );
194
			$script .= 'choice=\'' . $choice . '\';';
195
			$script .= 'if(\'' . $choice . '\'===subKey){';
196
			$script .= ( $choice_is_direction ) ? $direction_script . 'else{' : '';
197
			$script .= 'css+=\'' . $args['element'] . '{' . $args['property'] . ':\'+subValue+\';}\';';
198
			$script .= ( $choice_is_direction ) ? '}' : '';
199
			$script .= '}';
200
		} else {
201
			$script .= $direction_script . 'else{';
202
203
			// This is where most object-based fields will go.
204
			$script .= 'css+=\'' . $args['element'] . '{\'+subKey+\':\'+subValue+\'' . $args['units'] . $args['suffix'] . ';}\';';
205
			$script .= '}';
206
		}
207
		$script .= '});';
208
209
		return array(
210
			'script' => $property_script . $script,
211
			'css'    => 'css',
212
		);
213
	}
214
215
	/**
216
	 * Processes script generation for typography fields.
217
	 *
218
	 * @access protected
219
	 * @since 3.0.0
220
	 * @param array $args  The arguments for this js_var.
221
	 */
222
	protected function script_var_typography( $args ) {
223
224
		$script = '';
225
		$css    = '';
226
227
		// Load the font using WenFontloader.
228
		// This is a bit ugly because wp_add_inline_script doesn't allow adding <script> directly.
229
		$webfont_loader = 'sc=\'a\';jQuery(\'head\').append(sc.replace(\'a\',\'<\')+\'script>if(!_.isUndefined(WebFont)){WebFont.load({google:{families:["\'+fontFamily+\':\'+variant+subsetsString+\'"]}});}\'+sc.replace(\'a\',\'<\')+\'/script>\');';
230
231
		// Add the css.
232
		$css_build_array = array(
233
			'font-family'    => 'fontFamily',
234
			'font-size'      => 'fontSize',
235
			'line-height'    => 'lineHeight',
236
			'letter-spacing' => 'letterSpacing',
237
			'word-spacing'   => 'wordSpacing',
238
			'text-align'     => 'textAlign',
239
			'text-transform' => 'textTransform',
240
			'color'          => 'color',
241
			'font-weight'    => 'fontWeight',
242
			'font-style'     => 'fontStyle',
243
		);
244
		$choice_condition = ( isset( $args['choice'] ) && '' !== $args['choice'] && isset( $css_build_array[ $args['choice'] ] ) );
245
		$script .= ( ! $choice_condition ) ? $webfont_loader : '';
246
		foreach ( $css_build_array as $property => $var ) {
247
			if ( $choice_condition && $property !== $args['choice'] ) {
248
				continue;
249
			}
250
			$script .= ( $choice_condition && 'font-family' === $args['choice'] ) ? $webfont_loader : '';
251
			$css .= 'css+=(\'\'!==' . $var . ')?\'' . $args['element'] . '\'+\'{' . $property . ':\'+' . $var . '+\'}\':\'\';';
252
		}
253
254
		$script .= $css;
255
		return array(
256
			'script' => $script,
257
			'css'    => 'css',
258
		);
259
	}
260
261
	/**
262
	 * Adds anything we need before the main script.
263
	 *
264
	 * @access private
265
	 * @since 3.0.0
266
	 * @param array $args The field args.
267
	 * @return string;
0 ignored issues
show
Documentation introduced by
The doc-type string; could not be parsed: Expected "|" or "end of type", but got ";" at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
268
	 */
269
	private function before_script( $args ) {
270
271
		$script = '';
272
273
		if ( isset( $args['type'] ) ) {
274
			switch ( $args['type'] ) {
275
				case 'kirki-typography':
276
					$script .= 'fontFamily=(_.isUndefined(newval[\'font-family\']))?\'\':newval[\'font-family\'];';
277
					$script .= 'variant=(_.isUndefined(newval.variant))?400:newval.variant;';
278
					$script .= 'subsets=(_.isUndefined(newval.subsets))?[]:newval.subsets;';
279
					$script .= 'subsetsString=(_.isObject(newval.subsets))?\':\'+newval.subsets.join(\',\'):\'\';';
280
					$script .= 'fontSize=(_.isUndefined(newval[\'font-size\']))?\'\':newval[\'font-size\'];';
281
					$script .= 'lineHeight=(_.isUndefined(newval[\'line-height\']))?\'\':newval[\'line-height\'];';
282
					$script .= 'letterSpacing=(_.isUndefined(newval[\'letter-spacing\']))?\'\':newval[\'letter-spacing\'];';
283
					$script .= 'wordSpacing=(_.isUndefined(newval[\'word-spacing\']))?\'\':newval[\'word-spacing\'];';
284
					$script .= 'textAlign=(_.isUndefined(newval[\'text-align\']))?\'\':newval[\'text-align\'];';
285
					$script .= 'textTransform=(_.isUndefined(newval[\'text-transform\']))?\'\':newval[\'text-transform\'];';
286
					$script .= 'color=(_.isUndefined(newval.color))?\'\':newval.color;';
287
					$script .= 'fontWeight=(!_.isObject(variant.match(/\d/g)))?400:variant.match(/\d/g).join(\'\');';
288
					$script .= 'fontStyle=(-1!==newval.variant.indexOf(\'italic\'))?\'italic\':\'normal\';';
289
					$script .= 'css=\'\';';
290
					break;
291
			}
292
		}
293
		return $script;
294
	}
295
296
	/**
297
	 * Sanitizes the arguments and makes sure they are all there.
298
	 *
299
	 * @access private
300
	 * @since 3.0.0
301
	 * @param array $args The arguments.
302
	 * @return array
303
	 */
304
	private function get_args( $args ) {
305
306
		// Make sure everything is defined to avoid "undefined index" errors.
307
		$args = wp_parse_args( $args, array(
308
			'element'       => '',
309
			'property'      => '',
310
			'prefix'        => '',
311
			'suffix'        => '',
312
			'units'         => '',
313
			'js_callback'   => array( '', '' ),
314
			'value_pattern' => '',
315
		));
316
317
		// Element should be a string.
318
		if ( is_array( $args['element'] ) ) {
319
			$args['element'] = implode( ',', $args['element'] );
320
		}
321
322
		// Make sure arguments that are passed-on to callbacks are strings.
323
		if ( is_array( $args['js_callback'] ) && isset( $args['js_callback'][1] ) && is_array( $args['js_callback'][1] ) ) {
324
			$args['js_callback'][1] = wp_json_encode( $args['js_callback'][1] );
325
		}
326
		return $args;
327
328
	}
329
330
	/**
331
	 * Returns script for value_pattern & replacements.
332
	 *
333
	 * @access private
334
	 * @since 3.0.0
335
	 * @param string $value   The value placeholder.
336
	 * @param array  $js_vars The js_vars argument.
337
	 * @return string         The script.
338
	 */
339
	private function value_pattern_replacements( $value, $js_vars ) {
340
		$script = '';
341
		$alias  = $value;
342
		if ( isset( $js_vars['replacements'] ) ) {
343
			$script .= 'settings=window.wp.customize.get();';
344
			foreach ( $js_vars['replacements'] as $search => $replace ) {
345
				$replace = '\'+settings["' . $replace . '"]+\'';
346
				$value = str_replace( $search, $replace, $js_vars['value_pattern'] );
347
				$value = trim( $value, '+' );
348
			}
349
		}
350
		$value_compiled = str_replace( '$', '\'+' . $alias . '+\'', $value );
351
		$value_compiled = trim( $value_compiled, '+' );
352
		return $script . $alias . '=\'' . $value_compiled . '\';';
353
	}
354
355
	/**
356
	 * Get the callback function/method we're going to use for this field.
357
	 *
358
	 * @access private
359
	 * @since 3.0.0
360
	 * @param array $args The field args.
361
	 * @return string|array A callable function or method.
362
	 */
363
	protected function get_callback( $args ) {
364
365
		switch ( $args['type'] ) {
366
			case 'kirki-background':
367
			case 'kirki-dimensions':
368
			case 'kirki-multicolor':
369
			case 'kirki-sortable':
370
				$callback = array( $this, 'script_var_array' );
371
				break;
372
			case 'kirki-typography':
373
				$callback = array( $this, 'script_var_typography' );
374
				break;
375
			default:
376
				$callback = array( $this, 'script_var' );
377
		}
378
		return $callback;
379
	}
380
}
381