Completed
Push — develop ( a960b6...86b8b3 )
by Aristeides
03:47 queued 01:14
created

Kirki_Output::process_value()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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