Completed
Push — develop ( 956be6...d87e60 )
by Aristeides
03:13
created

Kirki_Output::get_styles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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