PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.1.1
Kirki – Freeform Page Builder, Website Builder & Customizer v6.1.1
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / customizer / packages / modules / css / src / CSS.php
kirki / customizer / packages / modules / css / src Last commit date
CSS 5 months ago CSS.php 2 months ago
CSS.php
502 lines
1 <?php
2 /**
3 * Handles the CSS Output of fields.
4 *
5 * @package Kirki
6 * @category Modules
7 * @author Themeum
8 * @copyright Copyright (c) 2023, Themeum
9 * @license https://opensource.org/licenses/MIT
10 * @since 3.0.0
11 */
12
13 namespace Kirki\Module;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 use Kirki\Compatibility\Kirki;
20 use Kirki\Util\Helper;
21 use Kirki\Compatibility\Values;
22 use Kirki\Module\CSS\Generator;
23
24 /**
25 * The Module object.
26 */
27 class CSS
28 {
29
30 /**
31 * The class instance.
32 *
33 * @static
34 * @access private
35 * @since 1.0
36 * @var object
37 */
38 private static $instance;
39
40 /**
41 * The CSS array
42 *
43 * @access public
44 * @var array
45 */
46 public static $css_array = array();
47
48 /**
49 * An array of fields to be processed.
50 *
51 * @static
52 * @access protected
53 * @since 1.0
54 * @var array
55 */
56 protected static $fields = array();
57
58 /**
59 * Field option types.
60 *
61 * @static
62 * @access protected
63 * @since 1.0
64 * @var array
65 */
66 protected static $field_option_types = array();
67
68 /**
69 * The default handle for kirki's styles enqueue.
70 *
71 * @since 4.0
72 * @access private
73 * @static
74 *
75 * @var string
76 */
77 private static $css_handle = 'kirki-styles';
78
79 /**
80 * The default id for kirki's inline style tag.
81 *
82 * @since 4.0.23
83 * @access private
84 * @static
85 *
86 * @var string
87 */
88 private static $inline_styles_id = 'kirki-inline-styles';
89
90 /**
91 * Get the one, true instance of this class.
92 * Prevents performance issues since this is instantiated in a filter.
93 *
94 * @static
95 * @access public
96 * @since 1.0
97 * @return object
98 */
99 public static function get_instance()
100 {
101 if (null === self::$instance) {
102 self::$instance = new self();
103 }
104 return self::$instance;
105 }
106
107 /**
108 * Constructor
109 *
110 * @access public
111 */
112 public function __construct()
113 {
114
115 add_action('kirki_field_init', array($this, 'field_init'), 10, 2);
116 add_action('init', array($this, 'init'));
117
118 }
119
120 /**
121 * Init.
122 *
123 * @access public
124 */
125 public function init()
126 {
127
128 new \Kirki\Module\Webfonts();
129
130 add_action('wp', array($this, 'print_styles_action'));
131
132 if (!apply_filters('kirki_output_inline_styles', true)) {
133 $config = apply_filters('kirki_config', array());
134 $priority = 999;
135
136 if (isset($config['styles_priority'])) {
137 $priority = absint($config['styles_priority']);
138 }
139
140 } else {
141 add_action('wp_head', array($this, 'print_styles_inline'), 999);
142 }
143
144 }
145
146 /**
147 * Runs when a field gets added.
148 * Adds fields to this object so their styles can later be generated.
149 *
150 * @access public
151 * @since 1.0
152 * @param array $args The field args.
153 * @param Object $object The field object.
154 * @return void
155 */
156 public function field_init($args, $object)
157 {
158
159 if (!isset($args['output'])) {
160 $args['output'] = array();
161 }
162
163 self::$field_option_types[$args['settings']] = isset($args['option_type']) ? $args['option_type'] : 'theme_mod';
164
165 if (!is_array($args['output'])) {
166 /* translators: The field ID where the error occurs. */
167 _doing_it_wrong(__METHOD__, sprintf(esc_html__('"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki'), esc_html($args['settings'])), '3.0.10');
168 $args['output'] = array(
169 array(
170 'element' => $args['output'],
171 ),
172 );
173 }
174
175 // Convert to array of arrays if needed.
176 if (isset($args['output']['element'])) {
177 /* translators: The field ID where the error occurs. */
178 _doing_it_wrong(__METHOD__, sprintf(esc_html__('"output" invalid format in field %s. The "output" argument should be defined as an array of arrays.', 'kirki'), esc_html($args['settings'])), '3.0.10');
179 $args['output'] = array($args['output']);
180 }
181
182 if (empty($args['output'])) {
183 return;
184 }
185
186 foreach ($args['output'] as $key => $output) {
187 if (empty($output) || !isset($output['element'])) {
188 unset($args['output'][$key]);
189 continue;
190 }
191 if (!isset($output['sanitize_callback']) && isset($output['callback'])) {
192 $args['output'][$key]['sanitize_callback'] = $output['callback'];
193 }
194
195 // Convert element arrays to strings.
196 if (isset($output['element']) && is_array($output['element'])) {
197 $args['output'][$key]['element'] = array_unique($args['output'][$key]['element']);
198 sort($args['output'][$key]['element']);
199
200 // Trim each element in the array.
201 foreach ($args['output'][$key]['element'] as $index => $element) {
202 $args['output'][$key]['element'][$index] = trim($element);
203 }
204 $args['output'][$key]['element'] = implode(',', $args['output'][$key]['element']);
205 }
206
207 // Fix for https://github.com/aristath/kirki/issues/1659#issuecomment-346229751.
208 $args['output'][$key]['element'] = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $args['output'][$key]['element']);
209 $args['output'][$key]['element'] = trim(preg_replace('/\s+/', ' ', $args['output'][$key]['element']));
210 }
211
212 if (!isset($args['type']) && isset($object->type)) {
213 $args['type'] = $object->type;
214 }
215
216 self::$fields[] = $args;
217
218 }
219
220 /**
221 * Print styles inline.
222 *
223 * @access public
224 * @since 3.0.36
225 * @return void
226 */
227 public function print_styles_inline()
228 {
229
230 $should_print = true;
231
232 if (defined('KIRKI_NO_OUTPUT') && true === KIRKI_NO_OUTPUT) {
233 $should_print = false;
234 }
235
236 ob_start();
237 $this->print_styles();
238 $inline_styles = ob_get_clean();
239
240 /**
241 * If KIRKI_NO_OUTPUT constant is defined (and is true), but typography field is defined, then print it.
242 * Otherwise, the typography field might be broken (missing font-family) if the font-face is not outputted.
243 */
244 if (!$should_print && false !== stripos($inline_styles, '@font-face')) {
245 $should_print = true;
246 }
247
248 if (!$should_print) {
249 return;
250 }
251
252 $inline_styles_id = apply_filters('kirki_inline_styles_id', self::$inline_styles_id);
253
254 echo '<style id="' . esc_attr($inline_styles_id) . '">';
255 echo wp_strip_all_tags( $inline_styles ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
256 echo '</style>';
257
258 }
259
260 /**
261 * Enqueue the styles.
262 *
263 * @access public
264 * @since 3.0.36
265 * @return void
266 */
267 public function enqueue_styles()
268 {
269
270 $args = array(
271 'action' => apply_filters('kirki_styles_action_handle', self::$css_handle),
272 );
273
274 if (is_admin()) {
275 global $current_screen;
276
277 /**
278 * This `enqueue_styles` method is also hooked into `enqueue_block_editor_assets`.
279 * It needs to be excluded from customize control page.
280 *
281 * Why not simply excluding all admin area except gutenberg editing interface?
282 * Because it would be nice to let the possibility open
283 * if a 3rd party plugin will output gutenberg styles somewhere in admin area.
284 *
285 * Example of possibility:
286 * In the future, Ultimate Dashboard Pro's admin page feature might supports Gutenberg.
287 */
288 if (is_object($current_screen) && property_exists($current_screen, 'id') && 'customize' === $current_screen->id) {
289 return;
290 }
291
292 if (property_exists($current_screen, 'is_block_editor') && 1 === (int) $current_screen->is_block_editor) {
293 $args['editor'] = '1';
294 }
295 }
296
297 // Enqueue the dynamic stylesheet.
298 wp_enqueue_style(
299 self::$css_handle,
300 add_query_arg( $args, home_url() ),
301 array(),
302 '4.0'
303 );
304
305 }
306
307 /**
308 * Prints the styles as an enqueued file.
309 *
310 * @access public
311 * @since 3.0.36
312 * @return void
313 */
314 public function print_styles_action()
315 {
316
317 /**
318 * Note to code reviewers:
319 * There is no need for a nonce check here, we're only checking if this is a valid request or not.
320 */
321
322 // phpcs:ignore WordPress.Security.NonceVerification
323 if (empty($_GET['action']) || apply_filters('kirki_styles_action_handle', self::$css_handle) !== $_GET['action']) {
324 return;
325 }
326
327 // This is a stylesheet.
328 header('Content-type: text/css');
329 $this->print_styles();
330 exit;
331
332 }
333
334 /**
335 * Prints the styles.
336 *
337 * @access public
338 */
339 public function print_styles()
340 {
341
342 // Go through all configs.
343 $configs = Kirki::$config;
344
345 foreach ($configs as $config_id => $args) {
346 if (defined('KIRKI_NO_OUTPUT') && true === KIRKI_NO_OUTPUT) {
347 continue;
348 }
349
350 if (isset($args['disable_output']) && true === $args['disable_output']) {
351 continue;
352 }
353
354 $styles = self::loop_controls($config_id);
355 $styles = apply_filters("kirki_{$config_id}_dynamic_css", $styles);
356
357 if (!empty($styles)) {
358 /**
359 * Note to code reviewers:
360 *
361 * Though all output should be run through an escaping function, this is pure CSS.
362 *
363 * When used in the print_styles_action() method the PHP header() call makes the browser interpret it as such.
364 * No code, script or anything else can be executed from inside a stylesheet.
365 *
366 * When using in the print_styles_inline() method the wp_strip_all_tags call we use below
367 * strips anything that has the possibility to be malicious, and since this is inslide a <style> tag
368 * it can only be interpreted by the browser as such.
369 * wp_strip_all_tags() excludes the possibility of someone closing the <style> tag and then opening something else.
370 */
371 echo wp_strip_all_tags($styles); // phpcs:ignore WordPress.Security.EscapeOutput
372 }
373 }
374
375 do_action('kirki_dynamic_css');
376
377 }
378
379 /**
380 * Loop through all fields and create an array of style definitions.
381 *
382 * @static
383 * @access public
384 * @param string $config_id The configuration ID.
385 */
386 public static function loop_controls($config_id)
387 {
388
389 // Get an instance of the Generator class.
390 // This will make sure google fonts and backup fonts are loaded.
391 Generator::get_instance();
392
393 $fields = self::get_fields_by_config($config_id);
394
395 // Compatibility with v3 API.
396 if (class_exists('\Kirki\Compatibility\Kirki')) {
397 $fields = array_merge(\Kirki\Compatibility\Kirki::$fields, $fields);
398 }
399
400 $css = array();
401
402 // Early exit if no fields are found.
403 if (empty($fields)) {
404 return;
405 }
406
407 foreach ($fields as $field) {
408
409 // Only process fields that belong to $config_id.
410 if (isset($field['kirki_config']) && $config_id !== $field['kirki_config']) {
411 continue;
412 }
413
414 if (true === apply_filters("kirki_{$config_id}_css_skip_hidden", true)) {
415
416 // Only continue if field dependencies are met.
417 if ((isset($field['required']) && !empty($field['required'])) || (isset($field['active_callback']) && !empty($field['active_callback']))) {
418 $valid = true;
419
420 // If $field is using active_callback instead of required.
421 if (!isset($field['required']) || empty($field['required'])) {
422 if (isset($field['active_callback']) && !empty($field['active_callback'])) {
423 // The "active_callback" or "required" accepts array or callable as the value.
424 if (is_array($field['active_callback']) || is_callable($field['active_callback'])) {
425 $field['required'] = $field['active_callback'];
426 }
427 }
428 }
429
430 // At this point, we know that the "required" is set and is not empty.
431 if (is_array($field['required'])) {
432 foreach ($field['required'] as $requirement) {
433 if (isset($requirement['setting']) && isset($requirement['value']) && isset($requirement['operator']) && isset(self::$field_option_types[$requirement['setting']])) {
434 $controller_value = Values::get_value($config_id, $requirement['setting']);
435
436 if (!Helper::compare_values($controller_value, $requirement['value'], $requirement['operator'])) {
437 $valid = false;
438 }
439 }
440 }
441 } elseif (is_string($field['required'])) {
442 $valid = '__return_true' === $field['required'] ? true : false;
443 } elseif (is_callable($field['required'])) {
444 $valid = call_user_func($field['required']);
445 }
446
447 if (!$valid) {
448 continue;
449 }
450 }
451 }
452
453 // Only continue if $field['output'] is set.
454 if (isset($field['output']) && !empty($field['output'])) {
455 $css = Helper::array_replace_recursive($css, Generator::css($field));
456
457 // Add the globals.
458 if (isset(self::$css_array[$config_id]) && !empty(self::$css_array[$config_id])) {
459 Helper::array_replace_recursive($css, self::$css_array[$config_id]);
460 }
461 }
462 }
463
464 $css = apply_filters("kirki_{$config_id}_styles", $css);
465
466 if (is_array($css)) {
467 return Generator::styles_parse(Generator::add_prefixes($css));
468 }
469
470 }
471
472 /**
473 * Gets fields from self::$fields by config-id.
474 *
475 * @static
476 * @access private
477 * @since 1.0
478 * @param string $config_id The config-ID.
479 * @return array
480 */
481 private static function get_fields_by_config($config_id)
482 {
483
484 $fields = array();
485
486 foreach (self::$fields as $field) {
487 if (
488 (isset($field['kirki_config']) && $config_id === $field['kirki_config']) ||
489 (
490 ('global' === $config_id || !$config_id) &&
491 (!isset($field['kirki_config']) || 'global' === $field['kirki_config'] || !$field['kirki_config'])
492 )
493 ) {
494 $fields[] = $field;
495 }
496 }
497
498 return $fields;
499
500 }
501
502 }