Completed
Pull Request — master (#1454)
by Aristeides
04:48 queued 02:03
created

Kirki_Output::apply_pattern_replace()   D

Complexity

Conditions 20
Paths 103

Size

Total Lines 48
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 37
nc 103
nop 2
dl 0
loc 48
rs 4.8444
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Handles CSS output for fields.
4
 *
5
 * @package     Kirki
6
 * @subpackage  Controls
7
 * @copyright   Copyright (c) 2017, Aristeides Stathopoulos
8
 * @license     http://opensource.org/licenses/https://opensource.org/licenses/MIT
9
 * @since       2.2.0
10
 */
11
12
/**
13
 * Handles field CSS output.
14
 */
15
class Kirki_Output {
16
17
	/**
18
	 * The Kirki configuration used in the field.
19
	 *
20
	 * @access protected
21
	 * @var string
22
	 */
23
	protected $config_id = 'global';
24
25
	/**
26
	 * The field's `output` argument.
27
	 *
28
	 * @access protected
29
	 * @var array
30
	 */
31
	protected $output = array();
32
33
	/**
34
	 * An array of the generated styles.
35
	 *
36
	 * @access protected
37
	 * @var array
38
	 */
39
	protected $styles = array();
40
41
	/**
42
	 * The value.
43
	 *
44
	 * @access protected
45
	 * @var string|array
46
	 */
47
	protected $value;
48
49
	/**
50
	 * The class constructor.
51
	 *
52
	 * @access public
53
	 * @param string       $config_id The config ID.
54
	 * @param array        $output    The output argument.
55
	 * @param string|array $value     The value.
56
	 */
57
	public function __construct( $config_id, $output, $value ) {
58
59
		$this->config_id = $config_id;
60
		$this->value     = $value;
61
		$this->output    = $output;
62
63
		$this->parse_output();
64
	}
65
66
	/**
67
	 * If we have a sanitize_callback defined, apply it to the value.
68
	 *
69
	 * @param array        $output The output args.
70
	 * @param string|array $value  The value.
71
	 *
72
	 * @return string|array
73
	 */
74
	protected function apply_sanitize_callback( $output, $value ) {
75
76
		if ( isset( $output['sanitize_callback'] ) && null !== $output['sanitize_callback'] ) {
77
78
			// If the sanitize_callback is invalid, return the value.
79
			if ( ! is_callable( $output['sanitize_callback'] ) ) {
80
				return $value;
81
			}
82
			return call_user_func( $output['sanitize_callback'], $this->value );
83
		}
84
85
		return $value;
86
87
	}
88
89
	/**
90
	 * If we have a value_pattern defined, apply it to the value.
91
	 *
92
	 * @param array        $output The output args.
93
	 * @param string|array $value  The value.
94
	 * @return string|array
95
	 */
96
	protected function apply_value_pattern( $output, $value ) {
97
98
		if ( isset( $output['value_pattern'] ) && ! empty( $output['value_pattern'] ) && is_string( $output['value_pattern'] ) ) {
99
			if ( ! is_array( $value ) ) {
100
				$value = str_replace( '$', $value, $output['value_pattern'] );
101
			}
102
			if ( is_array( $value ) ) {
103
				foreach ( array_keys( $value ) as $value_k ) {
104
					if ( ! is_string( $value[ $value_k ] ) ) {
105
						continue;
106
					}
107
					if ( isset( $output['choice'] ) ) {
108
						if ( $output['choice'] === $value_k ) {
109
							$value[ $output['choice'] ] = str_replace( '$', $value[ $output['choice'] ], $output['value_pattern'] );
110
						}
111
						continue;
112
					}
113
					$value[ $value_k ] = str_replace( '$', $value[ $value_k ], $output['value_pattern'] );
114
				}
115
			}
116
			$value = $this->apply_pattern_replace( $output, $value );
117
		} // End if().
118
		return $value;
119
	}
120
121
	/**
122
	 * If we have a value_pattern defined, apply it to the value.
123
	 *
124
	 * @param array        $output The output args.
125
	 * @param string|array $value  The value.
126
	 * @return string|array
127
	 */
128
	protected function apply_pattern_replace( $output, $value ) {
129
		if ( isset( $output['pattern_replace'] ) && is_array( $output['pattern_replace'] ) ) {
130
			$option_type = ( '' !== Kirki::get_config_param( $this->config_id, 'option_type' ) ) ? Kirki::get_config_param( $this->config_id, 'option_type' ) : 'theme_mod';
131
			$option_name = Kirki::get_config_param( $this->config_id, 'option_name' );
132
			$options     = array();
133
			if ( $option_name ) {
134
				$options = ( 'site_option' === $option_type ) ? get_site_option( $option_name ) : get_option( $option_name );
135
			}
136
			foreach ( $output['pattern_replace'] as $search => $replace ) {
137
				$replacement = '';
138
				switch ( $option_type ) {
139
					case 'option':
140
						if ( is_array( $options ) ) {
141
							if ( $option_name ) {
142
								$subkey = str_replace( array( $option_name, '[', ']' ), '', $replace );
143
								$replacement = ( isset( $options[ $subkey ] ) ) ? $options[ $subkey ] : '';
144
								break;
145
							}
146
							$replacement = ( isset( $options[ $replace ] ) ) ? $options[ $replace ] : '';
147
							break;
148
						}
149
						$replacement = get_option( $replace );
150
						break;
151
					case 'site_option':
152
						$replacement = ( is_array( $options ) && isset( $options[ $replace ] ) ) ? $options[ $replace ] : get_site_option( $replace );
153
						break;
154
					case 'user_meta':
155
						$user_id = get_current_user_id();
156
						if ( $user_id ) {
157
							// @codingStandardsIgnoreLine
158
							$replacement = get_user_meta( $user_id, $replace, true );
159
						}
160
						break;
161
					default:
162
						$replacement = get_theme_mod( $replace );
163
				}
164
				$replacement = ( false === $replacement ) ? '' : $replacement;
165
				if ( is_array( $value ) ) {
166
					foreach ( $value as $k => $v ) {
167
						$value[ $k ] = str_replace( $search, $replacement, $value[ $v ] );
168
					}
169
					return $value;
170
				}
171
				$value = str_replace( $search, $replacement, $value );
172
			} // End foreach().
173
		} // End if().
174
		return $value;
175
	}
176
177
	/**
178
	 * Parses the output arguments.
179
	 * Calls the process_output method for each of them.
180
	 *
181
	 * @access protected
182
	 */
183
	protected function parse_output() {
184
		foreach ( $this->output as $output ) {
185
			$skip = false;
186
187
			// Apply any sanitization callbacks defined.
188
			$value = $this->apply_sanitize_callback( $output, $this->value );
189
190
			// Skip if value is empty.
191
			if ( '' === $this->value ) {
192
				$skip = true;
193
			}
194
195
			// No need to proceed this if the current value is the same as in the "exclude" value.
196
			if ( isset( $output['exclude'] ) && is_array( $output['exclude'] ) ) {
197
				foreach ( $output['exclude'] as $exclude ) {
198
					if ( is_array( $value ) ) {
199
						if ( is_array( $exclude ) ) {
200
							$diff1 = array_diff( $value, $exclude );
201
							$diff2 = array_diff( $exclude, $value );
202
203
							if ( empty( $diff1 ) && empty( $diff2 ) ) {
204
								$skip = true;
205
							}
206
						}
207
						// If 'choice' is defined check for sub-values too.
208
						// Fixes https://github.com/aristath/kirki/issues/1416.
209
						if ( isset( $output['choice'] ) && isset( $value[ $output['choice'] ] ) && $exclude == $value[ $output['choice'] ] ) {
210
							$skip = true;
211
						}
212
					}
213
					if ( $skip ) {
214
						continue;
215
					}
216
217
					// Skip if value is defined as excluded.
218
					if ( $exclude === $value ) {
219
						$skip = true;
220
					}
221
				}
222
			}
223
			if ( $skip ) {
224
				continue;
225
			}
226
227
			// Apply any value patterns defined.
228
			$value = $this->apply_value_pattern( $output, $value );
229
230
			if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
231
				$output['element'] = array_unique( $output['element'] );
232
				sort( $output['element'] );
233
				$output['element'] = implode( ',', $output['element'] );
234
			}
235
236
			$value = $this->process_value( $value, $output );
237
			$this->process_output( $output, $value );
0 ignored issues
show
Bug introduced by
It seems like $value defined by $this->process_value($value, $output) on line 236 can also be of type array; however, Kirki_Output::process_output() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
238
		} // End foreach().
239
	}
240
241
	/**
242
	 * Parses an output and creates the styles array for it.
243
	 *
244
	 * @access protected
245
	 * @param array  $output The field output.
246
	 * @param string $value  The value.
247
	 *
248
	 * @return void
249
	 */
250
	protected function process_output( $output, $value ) {
251
		if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
252
			return;
253
		}
254
		$output['media_query'] = ( isset( $output['media_query'] ) ) ? $output['media_query'] : 'global';
255
		$output['prefix']      = ( isset( $output['prefix'] ) ) ? $output['prefix'] : '';
256
		$output['units']       = ( isset( $output['units'] ) ) ? $output['units'] : '';
257
		$output['suffix']      = ( isset( $output['suffix'] ) ) ? $output['suffix'] : '';
258
259
		// Properties that can accept multiple values.
260
		// Useful for example for gradients where all browsers use the "background-image" property
261
		// and the browser prefixes go in the value_pattern arg.
262
		$accepts_multiple = array(
263
			'background-image',
264
			'background',
265
		);
266
		if ( in_array( $output['property'], $accepts_multiple, true ) ) {
267
			if ( isset( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) && ! is_array( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) ) {
268
				$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = (array) $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ];
269
			}
270
			$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ][] = $output['prefix'] . $value . $output['units'] . $output['suffix'];
271
			return;
272
		}
273
		$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $value . $output['units'] . $output['suffix'];
274
	}
275
276
	/**
277
	 * Some CSS properties are unique.
278
	 * We need to tweak the value to make everything works as expected.
279
	 *
280
	 * @access protected
281
	 * @param string $property The CSS property.
282
	 * @param string $value    The value.
283
	 *
284
	 * @return array
285
	 */
286
	protected function process_property_value( $property, $value ) {
287
		$properties = apply_filters( "kirki/{$this->config_id}/output/property-classnames", array(
288
			'font-family'         => 'Kirki_Output_Property_Font_Family',
289
			'background-image'    => 'Kirki_Output_Property_Background_Image',
290
			'background-position' => 'Kirki_Output_Property_Background_Position',
291
		) );
292
		if ( array_key_exists( $property, $properties ) ) {
293
			$classname = $properties[ $property ];
294
			$obj = new $classname( $property, $value );
295
			return $obj->get_value();
296
		}
297
		return $value;
298
	}
299
300
	/**
301
	 * Returns the value.
302
	 *
303
	 * @access protected
304
	 * @param string|array $value The value.
305
	 * @param array        $output The field "output".
306
	 * @return string|array
307
	 */
308
	protected function process_value( $value, $output ) {
309
		if ( isset( $output['property'] ) ) {
310
			return $this->process_property_value( $output['property'], $value );
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 308 can also be of type array; however, Kirki_Output::process_property_value() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
311
		}
312
		return $value;
313
	}
314
315
	/**
316
	 * Exploses the private $styles property to the world
317
	 *
318
	 * @access protected
319
	 * @return array
320
	 */
321
	public function get_styles() {
322
		return $this->styles;
323
	}
324
}
325