Passed
Push — master ( a4951a...eea87a )
by Stiofan
07:14
created

sd_maybe_convert_values()   A

Complexity

Conditions 6
Paths 2

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 2
nop 1
dl 0
loc 23
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
4
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5
6
7
class Super_Duper_Bricks_Element extends \Bricks\Element {
0 ignored issues
show
Bug introduced by
The type Bricks\Element was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
9
	public $widget;
10
11
	public function __construct( $element = null ) {
12
13
14
		$block_icon = !empty($this->widget->options['block-icon']) ? $this->widget->options['block-icon'] : '';
15
16
17
		$this->category = !empty($this->widget->options['textdomain']) ? esc_attr( $this->widget->options['textdomain'] ) : 'Super Duper';
0 ignored issues
show
Bug Best Practice introduced by
The property category does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
18
		$this->name     = $this->widget->id_base;
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
19
		$this->icon     = (strpos($block_icon, 'fa') === 0) ? esc_attr($this->widget->options['block-icon']) : 'fas fa-globe-americas';
0 ignored issues
show
Bug Best Practice introduced by
The property icon does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
20
21
		parent::__construct($element);
22
	}
23
24
	/**
25
	 * Set the element name.
26
	 *
27
	 * @return array|string|string[]|null
28
	 */
29
	public function get_label() {
30
		$escaped_text = esc_attr( $this->widget->name );
31
		return str_replace( ' &gt; ', ' > ', $escaped_text ); // keep our > but have it safe
32
	}
33
34
	/**
35
	 * Bricks function to set the controls
36
	 *
37
	 * @return void
38
	 */
39
	public function set_controls() {
40
		$args = $this->sd_convert_arguments($this->widget);
0 ignored issues
show
Unused Code introduced by
The call to Super_Duper_Bricks_Element::sd_convert_arguments() has too many arguments starting with $this->widget. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

40
		/** @scrutinizer ignore-call */ 
41
  $args = $this->sd_convert_arguments($this->widget);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
41
42
		if (!empty($args)) {
43
			$this->controls = $this->controls + $args;
0 ignored issues
show
Bug Best Practice introduced by
The property controls does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
44
		}
45
46
	}
47
48
	/**
49
	 * Set the bricks control groups from the GD ones.
50
	 *
51
	 * @return void
52
	 */
53
	public function set_control_groups() {
54
		$args = $this->sd_get_arguments();
55
56
		$groups = array();
57
		if(!empty($args)) {
58
			foreach ($args as $k => $v) {
59
				$g_slug = !empty($v['group']) ? sanitize_title( $v['group'] ) : '';
60
				if($g_slug && empty($groups[$g_slug])) {
61
					$groups[$g_slug] = array(
62
						'title' => esc_html( $v['group'] ),
63
						'tab' => 'content',
64
					);
65
				}
66
			}
67
		}
68
69
		if(!empty($groups)) {
70
			$this->control_groups = $this->control_groups + $groups;
0 ignored issues
show
Bug Best Practice introduced by
The property control_groups does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
71
		}
72
73
	}
74
75
	/**
76
	 * Get the setting input arguments.
77
	 *
78
	 * @return mixed
79
	 */
80
	public function sd_get_arguments() {
81
		$args = $this->widget->set_arguments();
82
		$arg_keys_subtract = $this->sd_remove_arguments();
83
84
		if ( ! empty( $arg_keys_subtract ) ) {
85
			foreach($arg_keys_subtract as $key ){
86
				unset($args[$key]);
87
			}
88
		}
89
90
		return $args;
91
	}
92
93
94
	/**
95
	 * Simply use our own render function for the output.
96
	 *
97
	 * @return void
98
	 */
99
	public function render() {
100
		$settings   = $this->sd_maybe_convert_values( $this->settings );
101
102
103
		// set the AyeCode UI calss on the wrapper
104
		$this->set_attribute( '_root', 'class', 'bsui' );
105
106
		// we might need to add a placeholder here for previews.
107
108
		// add the bricks attributes to wrapper
109
		echo "<div {$this->render_attributes( '_root' )}>";
110
		echo $this->widget->output($settings);
111
		echo '</div>';
112
	}
113
114
	/**
115
	 * Values can never be arrays so convert if bricks setting make it an array.
116
	 *
117
	 * @param $settings
118
	 * @return mixed
119
	 */
120
	public function sd_maybe_convert_values( $settings ) {
121
122
123
		if (!empty($settings)) {
124
			foreach( $settings as $k => $v ) {
125
				if(is_array($v)) {
126
					$value = '';
127
					// is color
128
					if (isset($v['hex'])) {
129
						$value = $v['hex'];
130
					} elseif (isset($v['icon'])) {
131
						$value = $v['icon'];
132
					}
133
134
135
					// set the value
136
					$settings[$k] = $value;
137
				}
138
139
			}
140
		}
141
142
		return $settings;
143
	}
144
145
	/**
146
	 * Convert SD arguments to Bricks arguments.
147
	 *
148
	 * @param $widget
149
	 *
150
	 * @return array
151
	 */
152
	public function sd_convert_arguments()
153
	{
154
		$bricks_args = array();
155
156
		$args = $this->sd_get_arguments();
157
158
		if (!empty($args)) {
159
			foreach ($args as $key => $arg) {
160
161
				// convert title
162
				if (!empty($arg['title'])) {
163
					$arg['label'] = $arg['title'];
164
					unset($arg['title']);
165
				}
166
167
				// set fields not to use dynamic data
168
				$arg['hasDynamicData'] = false;
169
170
				if (!empty($arg['group'])) {
171
					$arg['group'] =  sanitize_title($arg['group']);
172
				}
173
174
				$arg['rerender'] = true;
175
176
				// required
177
				if(!empty($arg['element_require'])) {
178
					$arg['required'] = $this->sd_convert_required($arg['element_require']);
179
					unset($arg['element_require']);
180
				}
181
182
				// icons
183
				if ('icon' === $key) {
184
					$arg['type'] = 'icon';
185
				}
186
187
				$bricks_args[$key] = $arg;
188
189
			}
190
191
		}
192
193
		return $bricks_args;
194
195
	}
196
197
	/**
198
	 * Convert the SD element_required to the Bricks required syntax.
199
	 *
200
	 * @param $element_require
201
	 * @return array
202
	 */
203
	public function sd_convert_required($element_require) {
204
		$bricks_required = [];
205
206
		// Handle logical OR (||) for multiple values
207
		if (strpos($element_require, '||') !== false) {
208
			preg_match('/\[%(.+?)%\] *== *"(.*?)"/', $element_require, $matches);
209
			if ($matches) {
210
				$control_id = $matches[1];
211
				preg_match_all('/\[%.*?%\] *== *"(.*?)"/', $element_require, $value_matches);
212
				$values = $value_matches[1];
213
				$bricks_required[] = [$control_id, '=', $values];
214
			}
215
			return $bricks_required;
216
		}
217
218
		// Match individual conditions
219
		preg_match_all('/(!)?\[%(.*?)%\](?:\s*([!=<>]=?)\s*(".*?"|\'.*?\'|\d+))?/', $element_require, $matches, PREG_SET_ORDER);
220
221
		foreach ($matches as $match) {
222
			$is_negation = isset($match[1]) && $match[1] === '!';
223
			$control_id = $match[2];
224
			$operator = isset($match[3]) ? str_replace('==', '=', $match[3]) : ($is_negation ? '=' : '!=');
225
			$value = isset($match[4]) ? trim($match[4], '"\'') : ($is_negation ? '' : '');
226
227
			// Adjust for negation without explicit operator
228
			if ($is_negation && !isset($match[3])) {
229
				$operator = '=';
230
				$value = '';
231
			}
232
233
			$bricks_required[] = [$control_id, $operator, $value];
234
		}
235
236
		return $bricks_required;
237
	}
238
239
240
	/**
241
	 * A way to remove some settings by keys.
242
	 *
243
	 * @return array
244
	 */
245
	public function sd_remove_arguments()
246
	{
247
		return array();
248
	}
249
250
}
251
252
253
/**
254
 * This implements the desktop, tablet and mobile breakpoints views with our fields that are hidden on these types and adda the icon after the label to show which it applies to.
255
 */
256
add_action( 'wp_enqueue_scripts', function() {
257
258
	// Check if we're in the Bricks Editor
259
	if ( isset( $_GET['bricks'] ) && $_GET['bricks'] && bricks_is_builder_main() ) {
0 ignored issues
show
Bug introduced by
The function bricks_is_builder_main was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
	if ( isset( $_GET['bricks'] ) && $_GET['bricks'] && /** @scrutinizer ignore-call */ bricks_is_builder_main() ) {
Loading history...
260
		// Add inline script to the 'bricks-builder' script
261
		wp_add_inline_script(
262
			'bricks-builder',
263
			"
264
265
(function () {
266
    // Function to get the current breakpoint from the #bricks-preview class
267
    function getCurrentBreakpoint() {
268
        const bricksPreview = document.getElementById('bricks-preview');
269
        if (!bricksPreview) return null;
270
271
        // Check which breakpoint class is active
272
        if (bricksPreview.classList.contains('desktop')) {
273
            return 'desktop';
274
        } else if (bricksPreview.classList.contains('tablet_portrait')) {
275
            return 'tablet';
276
        } else if (bricksPreview.classList.contains('mobile_landscape') || bricksPreview.classList.contains('mobile_portrait')) {
277
            return 'phone';
278
        }
279
        return null;
280
    }
281
282
    // Function to group fields by base key
283
    function groupFields() {
284
        const controls = document.querySelectorAll('[data-controlkey]');
285
        const grouped = {};
286
287
        controls.forEach((control) => {
288
            const controlKey = control.getAttribute('data-controlkey');
289
            const baseKey = controlKey.replace(/(_sm|_md|_lg)$/, ''); // Remove _sm, _md, or _lg suffix
290
291
            if (!grouped[baseKey]) {
292
                grouped[baseKey] = { base: null, md: null, lg: null };
293
            }
294
295
            if (controlKey.endsWith('_lg')) {
296
                grouped[baseKey].lg = control;
297
            } else if (controlKey.endsWith('_md')) {
298
                grouped[baseKey].md = control;
299
            } else {
300
                grouped[baseKey].base = control; // No suffix is treated as base (sm)
301
            }
302
        });
303
304
        return grouped;
305
    }
306
307
    // Function to update visibility of controls
308
    function updateControlVisibility() {
309
        const breakpoint = getCurrentBreakpoint();
310
        const groupedFields = groupFields();
311
312
        Object.keys(groupedFields).forEach((baseKey) => {
313
            const { base, md, lg } = groupedFields[baseKey];
314
315
            // Skip fields that have no `_md` or `_lg` counterparts
316
            if (!md && !lg) {
317
                if (base) base.style.display = ''; // Ensure base field is always visible
318
                return;
319
            }
320
321
            // Apply hide/show logic based on the breakpoint
322
            if (breakpoint === 'desktop') {
323
                if (base) base.style.display = 'none';
324
                if (md) md.style.display = 'none';
325
                if (lg) lg.style.display = ''; // Show _lg
326
            } else if (breakpoint === 'tablet') {
327
                if (base) base.style.display = 'none';
328
                if (md) md.style.display = ''; // Show _md
329
                if (lg) lg.style.display = 'none';
330
            } else if (breakpoint === 'phone') {
331
                if (base) base.style.display = ''; // Show base (sm)
332
                if (md) md.style.display = 'none';
333
                if (lg) lg.style.display = 'none';
334
            }
335
        });
336
    }
337
338
    // Observe changes in the #bricks-panel-element content
339
    const panelElementObserver = new MutationObserver(() => {
340
        updateControlVisibility();
341
    });
342
343
    const bricksPanelElement = document.getElementById('bricks-panel-element');
344
    if (bricksPanelElement) {
345
        panelElementObserver.observe(bricksPanelElement, { childList: true, subtree: true });
346
    }
347
348
    // Also observe changes to the #bricks-preview element for breakpoint changes
349
    const bricksPreviewObserver = new MutationObserver(() => {
350
        updateControlVisibility();
351
    });
352
353
    const bricksPreview = document.getElementById('bricks-preview');
354
    if (bricksPreview) {
355
        bricksPreviewObserver.observe(bricksPreview, { attributes: true, attributeFilter: ['class'] });
356
    }
357
358
    // Run on initial load
359
    updateControlVisibility();
360
})();
361
362
363
(function () {
364
    // Function to get the current breakpoint from the #bricks-preview class
365
    function getCurrentBreakpoint() {
366
        const bricksPreview = document.getElementById('bricks-preview');
367
        if (!bricksPreview) return null;
368
369
        if (bricksPreview.classList.contains('desktop')) {
370
            return 'desktop';
371
        } else if (bricksPreview.classList.contains('tablet_portrait')) {
372
            return 'tablet';
373
        } else if (bricksPreview.classList.contains('mobile_landscape') || bricksPreview.classList.contains('mobile_portrait')) {
374
            return 'phone';
375
        }
376
        return null;
377
    }
378
379
    // SVG icons
380
   const icons = {
381
    lg: `
382
        <span class=\"bricks-svg-wrapper\" data-name=\"laptop\" style=\"padding-top:3px;\" title=\"Desktop\">
383
            <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 14 14\" class=\"bricks-svg\">
384
                <g id=\"laptop--device-laptop-electronics-computer-notebook\">
385
                    <path id=\"Vector\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.08 1.61H10.9136C11.4549 1.61 11.8936 2.0488 11.8936 2.59V7.98H2.1V2.59C2.1 2.002 2.492 1.61 3.08 1.61Z\" stroke-width=\"1\"></path>
386
                    <path id=\"Vector 3945\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M0.6957 11.2566L2.1 7.98H11.9L13.3042 11.2566C13.3477 11.3578 13.37 11.4667 13.37 11.5769C13.37 12.0259 13.0059 12.39 12.5569 12.39H1.4431C0.994 12.39 0.63 12.0259 0.63 11.5769C0.63 11.4667 0.6524 11.3578 0.6957 11.2566Z\" stroke-width=\"1\"></path>
387
                </g>
388
            </svg>
389
        </span>
390
    `,
391
    md: `
392
        <span class=\"bricks-svg-wrapper\" data-name=\"tablet-portrait\" style=\"padding-top:3px;\" title=\"Tablet\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 14 14\" class=\"bricks-svg\"><g id=\"one-handed-holding-tablet-handheld\"><path id=\"Rectangle 2038\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9.64593 1.23658C9.47168 1.089 9.24623 1 9 1H2c-0.55228 0 -1 0.44771 -1 1v9.0938c0 0.5522 0.44772 1 1 1h3.75\" stroke-width=\"1\"></path><path id=\"vector 296\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m12.3106 13 0.6383 -3.15223c0.0742 -0.36672 0.0675 -0.7452 -0.0197 -1.10906l-0.9088 -3.79119c-0.1682 -0.70134 -0.7013 -1.25797 -1.3954 -1.45681l-0.6221 -0.17821 -0.0002 5.23879c0 0.35407 -0.35839 0.59595 -0.68734 0.46392l-1.6994 -0.68209c-0.3105 -0.12463 -0.66467 -0.06608 -0.91839 0.15183 -0.3824 0.32842 -0.41818 0.90721 -0.07914 1.28012l1.24302 1.36723L8.89958 13\" stroke-width=\"1\"></path></g></svg></span>
393
    `,
394
    sm: `
395
        <span class=\"bricks-svg-wrapper\" data-name=\"phone-portrait\" style=\"padding-top:3px;\" title=\"Mobile\">
396
            <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 14 14\" class=\"bricks-svg\">
397
                <g id=\"phone-mobile-phone--android-phone-mobile-device-smartphone-iphone\">
398
                    <path id=\"Vector\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M10.5 0.5h-7c-0.55228 0 -1 0.447715 -1 1v11c0 0.5523 0.44772 1 1 1h7c0.5523 0 1 -0.4477 1 -1v-11c0 -0.552285 -0.4477 -1 -1 -1Z\" stroke-width=\"1\"></path>
399
                    <path id=\"Vector_2\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6.5 11h1\" stroke-width=\"1\"></path>
400
                </g>
401
            </svg>
402
        </span>
403
    `,
404
};
405
406
407
    // Function to group fields by base key
408
    function groupFields() {
409
        const controls = document.querySelectorAll('[data-controlkey]');
410
        const grouped = {};
411
412
        controls.forEach((control) => {
413
            const controlKey = control.getAttribute('data-controlkey');
414
            const baseKey = controlKey.replace(/(_sm|_md|_lg)$/, ''); // Remove _sm, _md, or _lg suffix
415
416
            if (!grouped[baseKey]) {
417
                grouped[baseKey] = { base: null, md: null, lg: null };
418
            }
419
420
            if (controlKey.endsWith('_lg')) {
421
                grouped[baseKey].lg = control;
422
            } else if (controlKey.endsWith('_md')) {
423
                grouped[baseKey].md = control;
424
            } else {
425
                grouped[baseKey].base = control; // No suffix is treated as base (sm)
426
            }
427
        });
428
429
        return grouped;
430
    }
431
432
    // Function to add icons to labels
433
    function addIconsToLabels() {
434
        const groupedFields = groupFields();
435
436
        Object.keys(groupedFields).forEach((baseKey) => {
437
            const { base, md, lg } = groupedFields[baseKey];
438
439
            // Skip fields that do not have `_md` or `_lg` counterparts
440
            if (!md && !lg) return;
441
442
            if (base) appendIcon(base, 'sm');
443
            if (md) appendIcon(md, 'md');
444
            if (lg) appendIcon(lg, 'lg');
445
        });
446
    }
447
448
    // Append an icon to the label of a control
449
    function appendIcon(control, type) {
450
        const label = control.querySelector('label');
451
        if (label && !label.querySelector('.bricks-svg-wrapper')) {
452
            label.insertAdjacentHTML('beforeend', icons[type]);
453
        }
454
    }
455
456
    // Observe changes in the #bricks-panel-element content
457
    const panelElementObserver = new MutationObserver(() => {
458
        addIconsToLabels(); // Ensure icons are added when the panel updates
459
    });
460
461
    const bricksPanelElement = document.getElementById('bricks-panel-element');
462
    if (bricksPanelElement) {
463
        panelElementObserver.observe(bricksPanelElement, { childList: true, subtree: true });
464
    }
465
466
    // Initial run to add icons
467
    addIconsToLabels();
468
})();
469
"
470
		);
471
	}
472
});
473