Completed
Push — develop ( fe9689...674c42 )
by Aristeides
02:41
created

Kirki_Output::process_property_value()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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