Passed
Pull Request — master (#817)
by Kiran
05:40
created

Super_Duper_Bricks_Element::set_control_groups()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 11
nc 4
nop 0
dl 0
loc 18
rs 8.8333
c 1
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
//		$this->scripts  = [ 'bricksIsotope' ];
21
22
		parent::__construct($element);
23
	}
24
25
	/**
26
	 * Set the element name.
27
	 *
28
	 * @return array|string|string[]|null
29
	 */
30
	public function get_label() {
31
		$escaped_text = esc_attr( $this->widget->name );
32
		return str_replace( ' &gt; ', ' > ', $escaped_text ); // keep our > but have it safe
33
	}
34
35
	/**
36
	 * Bricks function to set the controls
37
	 *
38
	 * @return void
39
	 */
40
	public function set_controls() {
41
		$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

41
		/** @scrutinizer ignore-call */ 
42
  $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...
42
43
		if (!empty($args)) {
44
			$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...
45
		}
46
47
	}
48
49
	/**
50
	 * Set the bricks control groups from the GD ones.
51
	 *
52
	 * @return void
53
	 */
54
	public function set_control_groups() {
55
		$args = $this->sd_get_arguments();
56
57
		$groups = array();
58
		if(!empty($args)) {
59
			foreach ($args as $k => $v) {
60
				$g_slug = !empty($v['group']) ? sanitize_title( $v['group'] ) : '';
61
				if($g_slug && empty($groups[$g_slug])) {
62
					$groups[$g_slug] = array(
63
						'title' => esc_html( $v['group'] ),
64
						'tab' => 'content',
65
					);
66
				}
67
			}
68
		}
69
70
		if(!empty($groups)) {
71
			$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...
72
		}
73
74
	}
75
76
	/**
77
	 * Get the setting input arguments.
78
	 *
79
	 * @return mixed
80
	 */
81
	public function sd_get_arguments() {
82
		$args = $this->widget->set_arguments();
83
		$arg_keys_subtract = $this->sd_remove_arguments();
84
85
		if ( ! empty( $arg_keys_subtract ) ) {
86
			foreach($arg_keys_subtract as $key ){
87
				unset($args[$key]);
88
			}
89
		}
90
91
		return $args;
92
	}
93
94
95
	/**
96
	 * Simply use our own render function for the output.
97
	 *
98
	 * @return void
99
	 */
100
	public function render() {
101
		$settings   = $this->settings;
102
103
104
		// we might need to add a placeholder here for previews.
105
		echo $this->widget->output($settings);
106
	}
107
108
	/**
109
	 * Convert SD arguments to Bricks arguments.
110
	 *
111
	 * @param $widget
112
	 *
113
	 * @return array
114
	 */
115
	public function sd_convert_arguments()
116
	{
117
		$bricks_args = array();
118
119
		$args = $this->sd_get_arguments();
120
121
		if (!empty($args)) {
122
			foreach ($args as $key => $arg) {
123
124
				// convert title
125
				if (!empty($arg['title'])) {
126
					$arg['label'] = $arg['title'];
127
					unset($arg['title']);
128
				}
129
130
				// set fields not to use dynamic data
131
				$arg['hasDynamicData'] = false;
132
133
				if (!empty($arg['group'])) {
134
					$arg['group'] =  sanitize_title($arg['group']);
135
				}
136
137
				$arg['rerender'] = true;
138
139
				// required
140
				if(!empty($arg['element_require'])) {
141
					$arg['required'] = $this->sd_convert_required($arg['element_require']);
142
					unset($arg['element_require']);
143
				}
144
145
				$bricks_args[$key] = $arg;
146
147
			}
148
149
		}
150
151
		return $bricks_args;
152
153
	}
154
155
	/**
156
	 * Convert the SD element_required to the Bricks required syntax.
157
	 *
158
	 * @param $element_require
159
	 * @return array
160
	 */
161
	public function sd_convert_required($element_require) {
162
		$bricks_required = [];
163
164
		// Handle logical OR (||) for multiple values
165
		if (strpos($element_require, '||') !== false) {
166
			preg_match('/\[%(.+?)%\] *== *"(.*?)"/', $element_require, $matches);
167
			if ($matches) {
168
				$control_id = $matches[1];
169
				preg_match_all('/\[%.*?%\] *== *"(.*?)"/', $element_require, $value_matches);
170
				$values = $value_matches[1];
171
				$bricks_required[] = [$control_id, '=', $values];
172
			}
173
			return $bricks_required;
174
		}
175
176
		// Match individual conditions
177
		preg_match_all('/(!)?\[%(.*?)%\](?:\s*([!=<>]=?)\s*(".*?"|\'.*?\'|\d+))?/', $element_require, $matches, PREG_SET_ORDER);
178
179
		foreach ($matches as $match) {
180
			$is_negation = isset($match[1]) && $match[1] === '!';
181
			$control_id = $match[2];
182
			$operator = isset($match[3]) ? str_replace('==', '=', $match[3]) : ($is_negation ? '=' : '!=');
183
			$value = isset($match[4]) ? trim($match[4], '"\'') : ($is_negation ? '' : '');
184
185
			// Adjust for negation without explicit operator
186
			if ($is_negation && !isset($match[3])) {
187
				$operator = '=';
188
				$value = '';
189
			}
190
191
			$bricks_required[] = [$control_id, $operator, $value];
192
		}
193
194
		return $bricks_required;
195
	}
196
197
198
	/**
199
	 * A way to remove some settings by keys.
200
	 *
201
	 * @return array
202
	 */
203
	public function sd_remove_arguments()
204
	{
205
		return array();
206
	}
207
208
}
209
210
211
/**
212
 * 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.
213
 */
214
add_action( 'wp_enqueue_scripts', function() {
215
216
	// Check if we're in the Bricks Editor
217
	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

217
	if ( isset( $_GET['bricks'] ) && $_GET['bricks'] && /** @scrutinizer ignore-call */ bricks_is_builder_main() ) {
Loading history...
218
		// Add inline script to the 'bricks-builder' script
219
		wp_add_inline_script(
220
			'bricks-builder',
221
			"
222
223
(function () {
224
    // Function to get the current breakpoint from the #bricks-preview class
225
    function getCurrentBreakpoint() {
226
        const bricksPreview = document.getElementById('bricks-preview');
227
        if (!bricksPreview) return null;
228
229
        // Check which breakpoint class is active
230
        if (bricksPreview.classList.contains('desktop')) {
231
            return 'desktop';
232
        } else if (bricksPreview.classList.contains('tablet_portrait')) {
233
            return 'tablet';
234
        } else if (bricksPreview.classList.contains('mobile_landscape') || bricksPreview.classList.contains('mobile_portrait')) {
235
            return 'phone';
236
        }
237
        return null;
238
    }
239
240
    // Function to group fields by base key
241
    function groupFields() {
242
        const controls = document.querySelectorAll('[data-controlkey]');
243
        const grouped = {};
244
245
        controls.forEach((control) => {
246
            const controlKey = control.getAttribute('data-controlkey');
247
            const baseKey = controlKey.replace(/(_sm|_md|_lg)$/, ''); // Remove _sm, _md, or _lg suffix
248
249
            if (!grouped[baseKey]) {
250
                grouped[baseKey] = { base: null, md: null, lg: null };
251
            }
252
253
            if (controlKey.endsWith('_lg')) {
254
                grouped[baseKey].lg = control;
255
            } else if (controlKey.endsWith('_md')) {
256
                grouped[baseKey].md = control;
257
            } else {
258
                grouped[baseKey].base = control; // No suffix is treated as base (sm)
259
            }
260
        });
261
262
        return grouped;
263
    }
264
265
    // Function to update visibility of controls
266
    function updateControlVisibility() {
267
        const breakpoint = getCurrentBreakpoint();
268
        const groupedFields = groupFields();
269
270
        Object.keys(groupedFields).forEach((baseKey) => {
271
            const { base, md, lg } = groupedFields[baseKey];
272
273
            // Skip fields that have no `_md` or `_lg` counterparts
274
            if (!md && !lg) {
275
                if (base) base.style.display = ''; // Ensure base field is always visible
276
                return;
277
            }
278
279
            // Apply hide/show logic based on the breakpoint
280
            if (breakpoint === 'desktop') {
281
                if (base) base.style.display = 'none';
282
                if (md) md.style.display = 'none';
283
                if (lg) lg.style.display = ''; // Show _lg
284
            } else if (breakpoint === 'tablet') {
285
                if (base) base.style.display = 'none';
286
                if (md) md.style.display = ''; // Show _md
287
                if (lg) lg.style.display = 'none';
288
            } else if (breakpoint === 'phone') {
289
                if (base) base.style.display = ''; // Show base (sm)
290
                if (md) md.style.display = 'none';
291
                if (lg) lg.style.display = 'none';
292
            }
293
        });
294
    }
295
296
    // Observe changes in the #bricks-panel-element content
297
    const panelElementObserver = new MutationObserver(() => {
298
        updateControlVisibility();
299
    });
300
301
    const bricksPanelElement = document.getElementById('bricks-panel-element');
302
    if (bricksPanelElement) {
303
        panelElementObserver.observe(bricksPanelElement, { childList: true, subtree: true });
304
    }
305
306
    // Also observe changes to the #bricks-preview element for breakpoint changes
307
    const bricksPreviewObserver = new MutationObserver(() => {
308
        updateControlVisibility();
309
    });
310
311
    const bricksPreview = document.getElementById('bricks-preview');
312
    if (bricksPreview) {
313
        bricksPreviewObserver.observe(bricksPreview, { attributes: true, attributeFilter: ['class'] });
314
    }
315
316
    // Run on initial load
317
    updateControlVisibility();
318
})();
319
320
321
(function () {
322
    // Function to get the current breakpoint from the #bricks-preview class
323
    function getCurrentBreakpoint() {
324
        const bricksPreview = document.getElementById('bricks-preview');
325
        if (!bricksPreview) return null;
326
327
        if (bricksPreview.classList.contains('desktop')) {
328
            return 'desktop';
329
        } else if (bricksPreview.classList.contains('tablet_portrait')) {
330
            return 'tablet';
331
        } else if (bricksPreview.classList.contains('mobile_landscape') || bricksPreview.classList.contains('mobile_portrait')) {
332
            return 'phone';
333
        }
334
        return null;
335
    }
336
337
    // SVG icons
338
   const icons = {
339
    lg: `
340
        <span class=\"bricks-svg-wrapper\" data-name=\"laptop\" style=\"padding-top:3px;\" title=\"Desktop\">
341
            <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 14 14\" class=\"bricks-svg\">
342
                <g id=\"laptop--device-laptop-electronics-computer-notebook\">
343
                    <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>
344
                    <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>
345
                </g>
346
            </svg>
347
        </span>
348
    `,
349
    md: `
350
        <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>
351
    `,
352
    sm: `
353
        <span class=\"bricks-svg-wrapper\" data-name=\"phone-portrait\" style=\"padding-top:3px;\" title=\"Mobile\">
354
            <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 14 14\" class=\"bricks-svg\">
355
                <g id=\"phone-mobile-phone--android-phone-mobile-device-smartphone-iphone\">
356
                    <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>
357
                    <path id=\"Vector_2\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6.5 11h1\" stroke-width=\"1\"></path>
358
                </g>
359
            </svg>
360
        </span>
361
    `,
362
};
363
364
365
    // Function to group fields by base key
366
    function groupFields() {
367
        const controls = document.querySelectorAll('[data-controlkey]');
368
        const grouped = {};
369
370
        controls.forEach((control) => {
371
            const controlKey = control.getAttribute('data-controlkey');
372
            const baseKey = controlKey.replace(/(_sm|_md|_lg)$/, ''); // Remove _sm, _md, or _lg suffix
373
374
            if (!grouped[baseKey]) {
375
                grouped[baseKey] = { base: null, md: null, lg: null };
376
            }
377
378
            if (controlKey.endsWith('_lg')) {
379
                grouped[baseKey].lg = control;
380
            } else if (controlKey.endsWith('_md')) {
381
                grouped[baseKey].md = control;
382
            } else {
383
                grouped[baseKey].base = control; // No suffix is treated as base (sm)
384
            }
385
        });
386
387
        return grouped;
388
    }
389
390
    // Function to add icons to labels
391
    function addIconsToLabels() {
392
        const groupedFields = groupFields();
393
394
        Object.keys(groupedFields).forEach((baseKey) => {
395
            const { base, md, lg } = groupedFields[baseKey];
396
397
            // Skip fields that do not have `_md` or `_lg` counterparts
398
            if (!md && !lg) return;
399
400
            if (base) appendIcon(base, 'sm');
401
            if (md) appendIcon(md, 'md');
402
            if (lg) appendIcon(lg, 'lg');
403
        });
404
    }
405
406
    // Append an icon to the label of a control
407
    function appendIcon(control, type) {
408
        const label = control.querySelector('label');
409
        if (label && !label.querySelector('.bricks-svg-wrapper')) {
410
            label.insertAdjacentHTML('beforeend', icons[type]);
411
        }
412
    }
413
414
    // Observe changes in the #bricks-panel-element content
415
    const panelElementObserver = new MutationObserver(() => {
416
        addIconsToLabels(); // Ensure icons are added when the panel updates
417
    });
418
419
    const bricksPanelElement = document.getElementById('bricks-panel-element');
420
    if (bricksPanelElement) {
421
        panelElementObserver.observe(bricksPanelElement, { childList: true, subtree: true });
422
    }
423
424
    // Initial run to add icons
425
    addIconsToLabels();
426
})();
427
"
428
		);
429
	}
430
});
431