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

Kirki_Output   C

Complexity

Total Complexity 75

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 321
rs 5.5056
c 0
b 0
f 0
wmc 75
lcom 1
cbo 1

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
B apply_sanitize_callback() 0 19 7
C apply_value_pattern() 0 24 10
C apply_pattern_replace() 0 54 22
C parse_output() 0 57 20
D process_output() 0 25 10
A process_property_value() 0 13 2
A process_value() 0 6 2
A get_styles() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Kirki_Output 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_Output, and based on these observations, apply Extract Interface, too.

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
83
			if ( is_array( $value ) && isset( $output['choice'] ) && isset( $this->value[ $output['choice'] ] ) ) {
84
				$value[ $output['choice'] ] = call_user_func( $output['sanitize_callback'], $this->value[ $output['choice'] ] );
85
				return $value;
86
			}
87
			return call_user_func( $output['sanitize_callback'], $this->value );
88
		}
89
90
		return $value;
91
92
	}
93
94
	/**
95
	 * If we have a value_pattern defined, apply it to the value.
96
	 *
97
	 * @param array        $output The output args.
98
	 * @param string|array $value  The value.
99
	 * @return string|array
100
	 */
101
	protected function apply_value_pattern( $output, $value ) {
102
103
		if ( isset( $output['value_pattern'] ) && ! empty( $output['value_pattern'] ) && is_string( $output['value_pattern'] ) ) {
104
			if ( ! is_array( $value ) ) {
105
				$value = str_replace( '$', $value, $output['value_pattern'] );
106
			}
107
			if ( is_array( $value ) ) {
108
				foreach ( array_keys( $value ) as $value_k ) {
109
					if ( ! is_string( $value[ $value_k ] ) ) {
110
						continue;
111
					}
112
					if ( isset( $output['choice'] ) ) {
113
						if ( $output['choice'] === $value_k ) {
114
							$value[ $output['choice'] ] = str_replace( '$', $value[ $output['choice'] ], $output['value_pattern'] );
115
						}
116
						continue;
117
					}
118
					$value[ $value_k ] = str_replace( '$', $value[ $value_k ], $output['value_pattern'] );
119
				}
120
			}
121
			$value = $this->apply_pattern_replace( $output, $value );
122
		} // End if().
123
		return $value;
124
	}
125
126
	/**
127
	 * If we have a value_pattern defined, apply it to the value.
128
	 *
129
	 * @param array        $output The output args.
130
	 * @param string|array $value  The value.
131
	 * @return string|array
132
	 */
133
	protected function apply_pattern_replace( $output, $value ) {
134
		if ( isset( $output['pattern_replace'] ) && is_array( $output['pattern_replace'] ) ) {
135
			$option_type = ( '' !== Kirki::get_config_param( $this->config_id, 'option_type' ) ) ? Kirki::get_config_param( $this->config_id, 'option_type' ) : 'theme_mod';
136
			$option_name = Kirki::get_config_param( $this->config_id, 'option_name' );
137
			$options     = array();
138
			if ( $option_name ) {
139
				$options = ( 'site_option' === $option_type ) ? get_site_option( $option_name ) : get_option( $option_name );
140
			}
141
			foreach ( $output['pattern_replace'] as $search => $replace ) {
142
				$replacement = '';
143
				switch ( $option_type ) {
144
					case 'option':
145
						if ( is_array( $options ) ) {
146
							if ( $option_name ) {
147
								$subkey = str_replace( array( $option_name, '[', ']' ), '', $replace );
148
								$replacement = ( isset( $options[ $subkey ] ) ) ? $options[ $subkey ] : '';
149
								break;
150
							}
151
							$replacement = ( isset( $options[ $replace ] ) ) ? $options[ $replace ] : '';
152
							break;
153
						}
154
						$replacement = get_option( $replace );
155
						break;
156
					case 'site_option':
157
						$replacement = ( is_array( $options ) && isset( $options[ $replace ] ) ) ? $options[ $replace ] : get_site_option( $replace );
158
						break;
159
					case 'user_meta':
160
						$user_id = get_current_user_id();
161
						if ( $user_id ) {
162
							// @codingStandardsIgnoreLine
163
							$replacement = get_user_meta( $user_id, $replace, true );
164
						}
165
						break;
166
					default:
167
						$replacement = get_theme_mod( $replace );
168
				}
169
				$replacement = ( false === $replacement ) ? '' : $replacement;
170
				if ( is_array( $value ) ) {
171
					foreach ( $value as $k => $v ) {
172
						if ( isset( $output['choice'] ) ) {
173
							if ( $k === $output['choice'] ) {
174
								$value[ $k ] = str_replace( $search, $replacement, $v );
175
							}
176
							continue;
177
						}
178
						$value[ $k ] = str_replace( $search, $replacement, $v );
179
					}
180
					return $value;
181
				}
182
				$value = str_replace( $search, $replacement, $value );
183
			} // End foreach().
184
		} // End if().
185
		return $value;
186
	}
187
188
	/**
189
	 * Parses the output arguments.
190
	 * Calls the process_output method for each of them.
191
	 *
192
	 * @access protected
193
	 */
194
	protected function parse_output() {
195
		foreach ( $this->output as $output ) {
196
			$skip = false;
197
198
			// Apply any sanitization callbacks defined.
199
			$value = $this->apply_sanitize_callback( $output, $this->value );
200
201
			// Skip if value is empty.
202
			if ( '' === $this->value ) {
203
				$skip = true;
204
			}
205
206
			// No need to proceed this if the current value is the same as in the "exclude" value.
207
			if ( isset( $output['exclude'] ) && is_array( $output['exclude'] ) ) {
208
				foreach ( $output['exclude'] as $exclude ) {
209
					if ( is_array( $value ) ) {
210
						if ( is_array( $exclude ) ) {
211
							$diff1 = array_diff( $value, $exclude );
212
							$diff2 = array_diff( $exclude, $value );
213
214
							if ( empty( $diff1 ) && empty( $diff2 ) ) {
215
								$skip = true;
216
							}
217
						}
218
						// If 'choice' is defined check for sub-values too.
219
						// Fixes https://github.com/aristath/kirki/issues/1416.
220
						if ( isset( $output['choice'] ) && isset( $value[ $output['choice'] ] ) && $exclude == $value[ $output['choice'] ] ) {
221
							$skip = true;
222
						}
223
					}
224
					if ( $skip ) {
225
						continue;
226
					}
227
228
					// Skip if value is defined as excluded.
229
					if ( $exclude === $value || ( '' === $exclude && empty( $value ) ) ) {
230
						$skip = true;
231
					}
232
				}
233
			}
234
			if ( $skip ) {
235
				continue;
236
			}
237
238
			// Apply any value patterns defined.
239
			$value = $this->apply_value_pattern( $output, $value );
240
241
			if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
242
				$output['element'] = array_unique( $output['element'] );
243
				sort( $output['element'] );
244
				$output['element'] = implode( ',', $output['element'] );
245
			}
246
247
			$value = $this->process_value( $value, $output );
248
			$this->process_output( $output, $value );
249
		} // End foreach().
250
	}
251
252
	/**
253
	 * Parses an output and creates the styles array for it.
254
	 *
255
	 * @access protected
256
	 * @param array        $output The field output.
257
	 * @param string|array $value  The value.
258
	 *
259
	 * @return void
260
	 */
261
	protected function process_output( $output, $value ) {
262
		if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
263
			return;
264
		}
265
		$output['media_query'] = ( isset( $output['media_query'] ) ) ? $output['media_query'] : 'global';
266
		$output['prefix']      = ( isset( $output['prefix'] ) ) ? $output['prefix'] : '';
267
		$output['units']       = ( isset( $output['units'] ) ) ? $output['units'] : '';
268
		$output['suffix']      = ( isset( $output['suffix'] ) ) ? $output['suffix'] : '';
269
270
		// Properties that can accept multiple values.
271
		// Useful for example for gradients where all browsers use the "background-image" property
272
		// and the browser prefixes go in the value_pattern arg.
273
		$accepts_multiple = array(
274
			'background-image',
275
			'background',
276
		);
277
		if ( in_array( $output['property'], $accepts_multiple, true ) ) {
278
			if ( isset( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) && ! is_array( $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] ) ) {
279
				$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = (array) $this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ];
280
			}
281
			$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ][] = $output['prefix'] . $value . $output['units'] . $output['suffix'];
282
			return;
283
		}
284
		$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $this->process_property_value( $output['property'], $value ) . $output['units'] . $output['suffix'];
285
	}
286
287
	/**
288
	 * Some CSS properties are unique.
289
	 * We need to tweak the value to make everything works as expected.
290
	 *
291
	 * @access protected
292
	 * @param string       $property The CSS property.
293
	 * @param string|array $value    The value.
294
	 *
295
	 * @return array
296
	 */
297
	protected function process_property_value( $property, $value ) {
298
		$properties = apply_filters( "kirki/{$this->config_id}/output/property-classnames", array(
299
			'font-family'         => 'Kirki_Output_Property_Font_Family',
300
			'background-image'    => 'Kirki_Output_Property_Background_Image',
301
			'background-position' => 'Kirki_Output_Property_Background_Position',
302
		) );
303
		if ( array_key_exists( $property, $properties ) ) {
304
			$classname = $properties[ $property ];
305
			$obj = new $classname( $property, $value );
306
			return $obj->get_value();
307
		}
308
		return $value;
309
	}
310
311
	/**
312
	 * Returns the value.
313
	 *
314
	 * @access protected
315
	 * @param string|array $value The value.
316
	 * @param array        $output The field "output".
317
	 * @return string|array
318
	 */
319
	protected function process_value( $value, $output ) {
320
		if ( isset( $output['property'] ) ) {
321
			return $this->process_property_value( $output['property'], $value );
322
		}
323
		return $value;
324
	}
325
326
	/**
327
	 * Exploses the private $styles property to the world
328
	 *
329
	 * @access protected
330
	 * @return array
331
	 */
332
	public function get_styles() {
333
		return $this->styles;
334
	}
335
}
336