Completed
Push — develop ( c4733d...62be89 )
by Aristeides
02:38 queued 27s
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
	 * @param array        $field     The field.
57
	 */
58
	public function __construct( $config_id, $output, $value, $field ) {
59
60
		$this->config_id = $config_id;
61
		$this->value     = $value;
62
		$this->output    = $output;
63
		$this->field     = $field;
0 ignored issues
show
Bug introduced by
The property field does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

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