Completed
Push — develop ( 2c9622...ec0795 )
by Aristeides
02:53
created

Kirki_Output::process_output()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 8
nc 17
nop 2
dl 0
loc 11
rs 8.2222
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
								$replacement = ( is_array( $options ) && isset( $options[ $replace ] ) ) ? $options[ $replace ] : get_option( $replace );
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
								break;
121
							case 'site_option':
122
								$replacement = ( is_array( $options ) && isset( $options[ $replace ] ) ) ? $options[ $replace ] : get_site_option( $replace );
123
								break;
124
							case 'user_meta':
125
								$user_id = get_current_user_id();
126
								if ( $user_id ) {
127
									$replacement = get_user_meta( $user_id, $replace, true );
128
								}
129
								break;
130
							default:
131
								$replacement = get_theme_mod( $replace );
132
						}
133
						$replacement = ( false === $replacement ) ? '' : $replacement;
134
						$value = str_replace( $search, $replacement, $value );
135
					}
136
				}
137
			}
138
		}
139
140
		return $value;
141
142
	}
143
144
	/**
145
	 * Parses the output arguments.
146
	 * Calls the process_output method for each of them.
147
	 *
148
	 * @access protected
149
	 */
150
	protected function parse_output() {
151
		foreach ( $this->output as $output ) {
152
			$skip = false;
153
154
			// Apply any sanitization callbacks defined.
155
			$value = $this->apply_sanitize_callback( $output, $this->value );
156
157
			// Skip if value is empty.
158
			if ( '' === $this->value ) {
159
				$skip = true;
160
			}
161
162
			// No need to proceed this if the current value is the same as in the "exclude" value.
163
			if ( isset( $output['exclude'] ) && false !== $output['exclude'] && is_array( $output['exclude'] ) ) {
164
				foreach ( $output['exclude'] as $exclude ) {
165
					if ( is_array( $value ) && is_array( $exclude ) ) {
166
						$diff1 = array_diff( $value, $exclude );
167
						$diff2 = array_diff( $exclude, $value );
168
169
						if ( empty( $diff1 ) && empty( $diff2 ) ) {
170
							$skip = true;
171
						}
172
					}
173
					if ( $skip ) {
174
						continue;
175
					}
176
177
					// Skip if value is defined as excluded.
178
					if ( $exclude === $value ) {
179
						$skip = true;
180
					}
181
				}
182
			}
183
			if ( $skip ) {
184
				continue;
185
			}
186
187
			// Apply any value patterns defined.
188
			$value = $this->apply_value_pattern( $output, $value );
189
190
			if ( isset( $output['element'] ) && is_array( $output['element'] ) ) {
191
				$output['element'] = array_unique( $output['element'] );
192
				sort( $output['element'] );
193
				$output['element'] = implode( ',', $output['element'] );
194
			}
195
196
			$value = $this->process_value( $value, $output );
197
			$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 196 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...
198
		} // End foreach().
199
	}
200
201
	/**
202
	 * Parses an output and creates the styles array for it.
203
	 *
204
	 * @access protected
205
	 * @param array  $output The field output.
206
	 * @param string $value  The value.
207
	 *
208
	 * @return void
209
	 */
210
	protected function process_output( $output, $value ) {
211
		if ( ! isset( $output['element'] ) || ! isset( $output['property'] ) ) {
212
			return;
213
		}
214
		$output['media_query'] = ( isset( $output['media_query'] ) ) ? $output['media_query'] : 'global';
215
		$output['prefix']      = ( isset( $output['prefix'] ) )      ? $output['prefix']      : '';
216
		$output['units']       = ( isset( $output['units'] ) )       ? $output['units']       : '';
217
		$output['suffix']      = ( isset( $output['suffix'] ) )      ? $output['suffix']      : '';
218
219
		$this->styles[ $output['media_query'] ][ $output['element'] ][ $output['property'] ] = $output['prefix'] . $value . $output['units'] . $output['suffix'];
220
	}
221
222
	/**
223
	 * Some CSS properties are unique.
224
	 * We need to tweak the value to make everything works as expected.
225
	 *
226
	 * @access protected
227
	 * @param string $property The CSS property.
228
	 * @param string $value    The value.
229
	 *
230
	 * @return array
231
	 */
232
	protected function process_property_value( $property, $value ) {
233
		$properties = apply_filters( "kirki/{$this->config_id}/output/property-classnames", array(
234
			'font-family'         => 'Kirki_Output_Property_Font_Family',
235
			'background-image'    => 'Kirki_Output_Property_Background_Image',
236
			'background-position' => 'Kirki_Output_Property_Background_Position',
237
		) );
238
		if ( array_key_exists( $property, $properties ) ) {
239
			$classname = $properties[ $property ];
240
			$obj = new $classname( $property, $value );
241
			return $obj->get_value();
242
		}
243
		return $value;
244
	}
245
246
	/**
247
	 * Returns the value.
248
	 *
249
	 * @access protected
250
	 * @param string|array $value The value.
251
	 * @param array        $output The field "output".
252
	 * @return string|array
253
	 */
254
	protected function process_value( $value, $output ) {
255
		if ( isset( $output['property'] ) ) {
256
			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 254 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...
257
		}
258
		return $value;
259
	}
260
261
	/**
262
	 * Exploses the private $styles property to the world
263
	 *
264
	 * @access protected
265
	 * @return array
266
	 */
267
	public function get_styles() {
268
		return $this->styles;
269
	}
270
}
271