PluginProbe ʕ •ᴥ•ʔ
Page Builder by SiteOrigin / 2.34.3
Page Builder by SiteOrigin v2.34.3
2.34.3 2.34.2 2.29.5 2.29.6 2.29.7 2.29.8 2.29.9 2.3 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.31.4 2.31.5 2.31.6 2.31.7 2.31.8 2.32.0 2.32.1 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.34.0 2.34.1 2.4 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 2.4.22 2.4.23 2.4.24 2.4.25 2.4.3 2.4.4 2.4.5 2.4.6 2.4.8 2.4.9 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.14 2.5.15 2.5.16 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0 2.8.1 2.8.2 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 trunk 2.10.0 2.10.1 2.10.10 2.10.11 2.10.12 2.10.13 2.10.14 2.10.15 2.10.16 2.10.17 2.10.2 2.10.3 2.10.4 2.10.5 2.10.6 2.10.7 2.10.8 2.10.9 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.12.0 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.13.0 2.13.1 2.13.2 2.14.0 2.14.1 2.14.2 2.14.3 2.15.0 2.15.1 2.15.2 2.15.3 2.16.0 2.16.1 2.16.10 2.16.11 2.16.12 2.16.13 2.16.14 2.16.15 2.16.16 2.16.17 2.16.18 2.16.19 2.16.2 2.16.3 2.16.4 2.16.5 2.16.6 2.16.7 2.16.8 2.16.9 2.17.0 2.18.0 2.18.1 2.18.2 2.18.3 2.18.4 2.19.0 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.21.1 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.26.1 2.26.2 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.10 2.29.11 2.29.12 2.29.13 2.29.14 2.29.15 2.29.16 2.29.17 2.29.18 2.29.19 2.29.2 2.29.20 2.29.21 2.29.22 2.29.3 2.29.4
siteorigin-panels / inc / renderer.php
siteorigin-panels / inc Last commit date
data 3 years ago installer 3 months ago widgets 3 months ago admin-dashboard.php 2 years ago admin-layouts.php 10 months ago admin-widget-dialog.php 1 year ago admin-widgets-bundle.php 11 months ago admin.php 1 month ago compatibility.php 1 month ago css-builder.php 3 years ago functions.php 3 years ago home.php 3 years ago live-editor.php 2 years ago post-content-filters.php 2 years ago renderer-legacy.php 3 months ago renderer.php 1 month ago revisions.php 3 years ago settings.php 3 months ago sidebars-emulator.php 2 years ago styles-admin.php 11 months ago styles.php 11 months ago widget-shortcode.php 2 years ago
renderer.php
1359 lines
1 <?php
2
3 class SiteOrigin_Panels_Renderer {
4 private $inline_css;
5 private $container;
6 private $side = array( 'top', 'right', 'bottom', 'left' );
7
8 public function __construct() {
9 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ), 1 );
10 $this->inline_css = null;
11 }
12
13 public static function single() {
14 static $single;
15
16 return empty( $single ) ? $single = new self() : $single;
17 }
18
19 /**
20 * Determine whether the current row contains no widgets.
21 *
22 * @param array $row The row data.
23 *
24 * @return bool
25 */
26 private function is_empty_row( $row ) {
27 if ( empty( $row['cells'] ) || ! is_array( $row['cells'] ) ) {
28 return true;
29 }
30
31 foreach ( $row['cells'] as $cell ) {
32 if ( ! empty( $cell['widgets'] ) ) {
33 return false;
34 }
35 }
36
37 return true;
38 }
39
40 /**
41 * Determine whether the current row has a background style set.
42 *
43 * @param array $row The row data.
44 *
45 * @return bool
46 */
47 private function row_has_background( $row ) {
48 if ( empty( $row['style'] ) || ! is_array( $row['style'] ) ) {
49 return false;
50 }
51
52 return (
53 ! empty( $row['style']['background'] ) ||
54 ! empty( $row['style']['background_image_attachment'] ) ||
55 ! empty( $row['style']['background_image_attachment_fallback'] )
56 );
57 }
58
59 /**
60 * Determine whether an empty row with a background should remain visible.
61 *
62 * @param array $row The row data.
63 *
64 * @return bool
65 */
66 private function should_display_empty_background_row( $row ) {
67 return (
68 siteorigin_panels_setting( 'display-empty-rows-with-background' ) &&
69 $this->is_empty_row( $row ) &&
70 $this->row_has_background( $row )
71 );
72 }
73
74 /**
75 * Add CSS that needs to go inline.
76 */
77 public function add_inline_css( $post_id, $css ) {
78 if ( is_null( $this->inline_css ) ) {
79 // Initialize the inline CSS array and add actions to handle printing.
80 $this->inline_css = array();
81 $css_output_set = false;
82 $output_css = siteorigin_panels_setting( 'output-css-header' );
83
84 if ( is_admin() || SiteOrigin_Panels_Admin::is_block_editor() || $output_css == 'auto' ) {
85 add_action( 'wp_head', array( $this, 'print_inline_css' ), 12 );
86 add_action( 'wp_footer', array( $this, 'print_inline_css' ) );
87 $css_output_set = true;
88 }
89
90 // The CSS can only be output in the header if the page is powered by the Classic Editor.
91 // $post_id won't be a number if the current page is powered by the Block Editor.
92 if ( ! $css_output_set && $output_css == 'header' && is_numeric( $post_id ) ) {
93 add_action( 'wp_head', array( $this, 'print_inline_css' ), 12 );
94 $css_output_set = true;
95 }
96
97 if ( ! $css_output_set ) {
98 add_action( 'wp_footer', array( $this, 'print_inline_css' ) );
99 }
100 }
101
102 $this->inline_css[ $post_id ] = $css;
103
104 // Enqueue the front styles, if they haven't already been enqueued
105 if ( ! wp_style_is( 'siteorigin-panels-front', 'enqueued' ) ) {
106 wp_enqueue_style( 'siteorigin-panels-front' );
107 }
108 }
109
110 /**
111 * Generate the CSS for the page layout.
112 *
113 * @return string
114 */
115 public function generate_css( $post_id, $panels_data = false, $layout_data = false ) {
116 // Exit if we don't have panels data
117 if ( empty( $panels_data ) ) {
118 $panels_data = get_post_meta( $post_id, 'panels_data', true );
119 $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
120
121 if ( empty( $panels_data ) ) {
122 return '';
123 }
124 }
125
126 if ( empty( $layout_data ) ) {
127 $layout_data = $this->get_panels_layout_data( $panels_data );
128 $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
129 }
130
131 if ( empty( $this->container ) ) {
132 $this->container = SiteOrigin_Panels::container_settings();
133 }
134
135 // Get some of the default settings
136 $settings = siteorigin_panels_setting();
137 $panels_tablet_width = $settings['tablet-width'];
138 $panels_mobile_width = $settings['mobile-width'];
139 $panels_margin_bottom_last_row = $settings['margin-bottom-last-row'];
140
141 $css = new SiteOrigin_Panels_Css_Builder();
142
143 $ci = 0;
144
145 foreach ( $layout_data as $ri => $row ) {
146 // Filter the bottom margin for this row with the arguments
147 $panels_margin_bottom = apply_filters( 'siteorigin_panels_css_row_margin_bottom', $settings['margin-bottom'] . 'px', $row, $ri, $panels_data, $post_id );
148 $panels_mobile_margin_bottom = apply_filters( 'siteorigin_panels_css_row_mobile_margin_bottom', $settings['row-mobile-margin-bottom'] . 'px', $row, $ri, $panels_data, $post_id );
149 $display_empty_background_row = $this->should_display_empty_background_row( $row );
150
151 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $row ) ) {
152 $css->add_row_css( $post_id, $ri, array(
153 '.panel-has-style > .panel-row-style',
154 ), array(
155 'position' => 'relative',
156 ) );
157
158 // Prevent display issue with fixed backgrounds on iOS.
159 if ( $row['style']['background_display'] === 'fixed' ) {
160 $css->add_row_css( $post_id, $ri, array(
161 '.panel-has-overlay > .panel-row-style > .panel-background-overlay',
162 ), array(
163 'background-attachment' => 'scroll !important',
164 ), $panels_tablet_width );
165 }
166 }
167
168 if ( empty( $row['cells'] ) ) {
169 continue;
170 }
171
172 // Let other themes and plugins change the gutter.
173 $gutter = apply_filters( 'siteorigin_panels_css_row_gutter', $settings['margin-sides'] . 'px', $row, $ri, $panels_data );
174 preg_match( '/([0-9\.,]+)(.*)/', $gutter, $gutter_parts );
175
176 $cell_count = count( $row['cells'] );
177
178 // If the CSS Container Breaker is enabled, and this row is using it,
179 // we need to remove the cell widths on mobile.
180 $css_container_cutoff = $this->container['css_override'] && isset( $row['style']['row_stretch'] ) && $row['style']['row_stretch'] == 'full' ? ':' . ( $panels_mobile_width + 1 ) : 1920;
181
182 if (
183 $this->container['css_override'] &&
184 ! $this->container['full_width'] &&
185 ! empty( $row['style'] ) &&
186 ! empty( $row['style']['row_stretch'] ) &&
187 (
188 $row['style']['row_stretch'] == 'full' ||
189 $row['style']['row_stretch'] == 'full-width-stretch' ||
190 $row['style']['row_stretch'] == 'full-stretched' ||
191 $row['style']['row_stretch'] == 'full-stretched-padded'
192 )
193 ) {
194 $this->container['full_width'] = true;
195 }
196
197 // Add the cell sizing
198 foreach ( $row['cells'] as $ci => $cell ) {
199 $weight = apply_filters( 'siteorigin_panels_css_cell_weight', $cell['weight'], $row, $ri, $cell, $ci - 1, $panels_data, $post_id );
200 $rounded_width = round( $weight * 100, 4 ) . '%';
201 $calc_width = 'calc(' . $rounded_width . ' - ( ' . ( 1 - $weight ) . ' * ' . $gutter . ' ) )';
202
203 // Add the width and ensure we have correct formatting for CSS.
204 $css->add_cell_css( $post_id, $ri, $ci, '', array(
205 'width' => array(
206 // For some locales PHP uses ',' for decimal separation.
207 // This seems to happen when a plugin calls `setlocale(LC_ALL, 'de_DE');` or `setlocale(LC_NUMERIC, 'de_DE');`
208 // This should prevent issues with column sizes in these cases.
209 str_replace( ',', '.', $rounded_width ),
210 str_replace( ',', '.', (int) $gutter ? $calc_width : '' ), // Exclude if there's a zero gutter
211 ),
212 ), $css_container_cutoff );
213
214 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $cell ) ) {
215 $css->add_cell_css( $post_id, $ri, $ci, '', array(
216 'position' => 'relative',
217 ) );
218
219 // Prevent display issue with fixed backgrounds on iOS.
220 if ( $cell['style']['background_display'] === 'fixed' ) {
221 $css->add_cell_css( $post_id, $ri, $ci, array(
222 '.panel-has-overlay > .panel-cell-style > .panel-background-overlay',
223 ), array(
224 'background-attachment' => 'scroll !important',
225 ), $panels_tablet_width );
226 }
227 }
228
229 // Add in any widget specific CSS
230 foreach ( $cell['widgets'] as $wi => $widget ) {
231 $widget_style_data = ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array();
232 $widget_css = apply_filters(
233 'siteorigin_panels_css_widget_css',
234 array(),
235 $widget_style_data,
236 $row,
237 $ri,
238 $cell,
239 $ci - 1,
240 $widget,
241 $wi,
242 $panels_data,
243 $post_id
244 );
245
246 $css->add_widget_css(
247 $post_id,
248 $ri,
249 $ci,
250 $wi,
251 '',
252 $widget_css,
253 1920,
254 true
255 );
256
257 $panels_tablet_widget_tablet_margin = apply_filters(
258 'siteorigin_panels_css_tablet_mobile_margin',
259 ! empty( $widget['panels_info']['style']['tablet_margin'] ) ? $widget['panels_info']['style']['tablet_margin'] : false,
260 $widget,
261 $wi,
262 $panels_data,
263 $post_id
264 );
265
266 if ( ! empty( $panels_tablet_widget_tablet_margin ) ) {
267 $css->add_widget_css(
268 $post_id,
269 $ri,
270 $ci,
271 $wi,
272 '',
273 array(
274 'margin' => $panels_tablet_widget_tablet_margin . ( siteorigin_panels_setting( 'inline-styles' ) ? ' !important' : '' ),
275 ),
276 $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ),
277 true
278 );
279 }
280
281 $panels_mobile_widget_mobile_margin = apply_filters(
282 'siteorigin_panels_css_widget_mobile_margin',
283 ! empty( $widget['panels_info']['style']['mobile_margin'] ) ? $widget['panels_info']['style']['mobile_margin'] : false,
284 $widget,
285 $wi,
286 $panels_data,
287 $post_id
288 );
289
290 if ( empty( $panels_mobile_widget_mobile_margin ) && ! empty( $settings['widget-mobile-margin-bottom'] ) ) {
291 $panels_mobile_widget_mobile_margin = '0 0 ' . $settings[ 'widget-mobile-margin-bottom'] . 'px';
292 }
293
294 if ( ! empty( $panels_mobile_widget_mobile_margin ) ) {
295 $css->add_widget_css(
296 $post_id,
297 $ri,
298 $ci,
299 $wi,
300 '',
301 array(
302 'margin' => $panels_mobile_widget_mobile_margin . ( siteorigin_panels_setting( 'inline-styles' ) ? ' !important' : '' ),
303 ),
304 $panels_mobile_width,
305 true
306 );
307 }
308
309 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $widget['panels_info'] ) ) {
310 $css->add_widget_css(
311 $post_id,
312 $ri,
313 $ci,
314 $wi,
315 '',
316 array(
317 'position' => 'relative',
318 )
319 );
320
321 // Prevent display issue with fixed backgrounds on iOS.
322 if ( $widget['panels_info']['style']['background_display'] === 'fixed' ) {
323 $css->add_widget_css(
324 $post_id,
325 $ri,
326 $ci,
327 $wi,
328 '.panel-has-overlay > .panel-widget-style > .panel-background-overlay',
329 array(
330 'background-attachment' => 'scroll !important',
331 ),
332 $panels_tablet_width
333 );
334 }
335 }
336 }
337 }
338
339 if ( ! siteorigin_panels_setting( 'inline-styles' ) ) {
340 if (
341 $ri != count( $layout_data ) - 1 ||
342 ! empty( $row['style']['bottom_margin'] ) ||
343 ! empty( $panels_margin_bottom_last_row )
344 ) {
345 $css->add_row_css( $post_id, $ri, '', array(
346 'margin-bottom' => $panels_margin_bottom,
347 ) );
348 }
349 }
350
351 $collapse_order = ! empty( $row['style']['collapse_order'] ) ? $row['style']['collapse_order'] : ( ! is_rtl() ? 'left-top' : 'right-top' );
352
353 // Let other themes and plugins change the row collapse point.
354 $collapse_point = apply_filters( 'siteorigin_panels_css_row_collapse_point', '', $row, $ri, $panels_data );
355
356 if ( $settings['responsive'] && empty( $row['style']['collapse_behaviour'] ) ) {
357 // The default collapse behaviour
358 if (
359 $settings['tablet-layout'] &&
360 $cell_count >= 3 &&
361 $panels_tablet_width > $panels_mobile_width &&
362 empty( $collapse_point )
363 ) {
364 // Tablet responsive css for the row
365
366 $css->add_row_css( $post_id, $ri, array(
367 '.panel-no-style',
368 '.panel-has-style > .panel-row-style',
369 ), array(
370 '-ms-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
371 '-webkit-flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
372 'flex-wrap' => $collapse_order == 'left-top' ? 'wrap' : 'wrap-reverse',
373 ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
374
375 $css->add_cell_css( $post_id, $ri, false, '', array(
376 '-ms-flex' => '0 1 50%',
377 '-webkit-flex' => '0 1 50%',
378 'flex' => '0 1 50%',
379 'margin-right' => '0',
380 'margin-bottom' => $panels_margin_bottom,
381 ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
382
383 $remove_bottom_margin = ':nth-';
384
385 if ( $collapse_order == 'left-top' ) {
386 $remove_bottom_margin .= 'last-child(' . ( count( $row['cells'] ) % 2 == 0 ? '-n+2' : '1' ) . ')';
387 } else {
388 $remove_bottom_margin .= 'child(-n+2)';
389 }
390
391 if ( ! empty( $gutter_parts[1] ) ) {
392 // Tablet responsive css for cells
393
394 $css->add_cell_css( $post_id, $ri, false, ':nth-child(even)', array(
395 'padding-left' => ( (float) $gutter_parts[1] / 2 . $gutter_parts[2] ),
396 ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
397
398 $css->add_cell_css( $post_id, $ri, false, ':nth-child(odd)', array(
399 'padding-right' => ( (float) $gutter_parts[1] / 2 . $gutter_parts[2] ),
400 ), $panels_tablet_width . ':' . ( $panels_mobile_width + 1 ) );
401 }
402 }
403
404 // Mobile Responsive
405 $collapse_point = ! empty( $collapse_point ) ? $collapse_point : $panels_mobile_width;
406 // Uses rows custom collapse point or sets mobile collapse point set on settings page.
407 $css->add_row_css( $post_id, $ri, array(
408 '.panel-no-style',
409 '.panel-has-style > .panel-row-style',
410 // When CSS override is enabled, a full width row has a special wrapper so need to account for that.
411 $this->container['css_override'] && isset( $row['style']['row_stretch'] ) && $row['style']['row_stretch'] == 'full' ? ' .so-panels-full-wrapper' : '',
412 ), array(
413 '-webkit-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
414 '-ms-flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
415 'flex-direction' => $collapse_order == 'left-top' ? 'column' : 'column-reverse',
416 ), $collapse_point );
417
418 // Uses rows custom collapse point or sets mobile collapse point set on settings page.
419 $css->add_cell_css( $post_id, $ri, false, '', array(
420 'width' => '100%',
421 'margin-right' => 0,
422 ), $collapse_point );
423
424 foreach ( $row['cells'] as $ci => $cell ) {
425 if ( ( $collapse_order == 'left-top' && $ci != $cell_count - 1 ) || ( $collapse_order == 'right-top' && $ci !== 0 ) ) {
426 $css->add_cell_css( $post_id, $ri, $ci, '', array(
427 'margin-bottom' => apply_filters(
428 'siteorigin_panels_css_cell_mobile_margin_bottom',
429 $settings['mobile-cell-margin'] . 'px',
430 $cell,
431 $ci,
432 $row,
433 $ri,
434 $panels_data,
435 $post_id
436 ),
437 ), $collapse_point );
438 }
439 }
440
441 if (
442 $settings['tablet-layout'] &&
443 $panels_tablet_width > $collapse_point &&
444 ! empty( $row['style']['tablet_bottom_margin'] )
445 ) {
446 $tablet_bottom_margin = $row['style']['tablet_bottom_margin'];
447 if ( siteorigin_panels_setting( 'inline-styles' ) ) {
448 $tablet_bottom_margin .= ' !important';
449 }
450
451 $css->add_row_css( $post_id, $ri, '', array(
452 'margin-bottom' => $tablet_bottom_margin,
453 ), "$panels_tablet_width:$collapse_point" );
454 }
455
456 if ( $panels_mobile_margin_bottom != $panels_margin_bottom && ! empty( $panels_mobile_margin_bottom ) ) {
457 $css->add_row_css( $post_id, $ri, '', array(
458 'margin-bottom' => $panels_mobile_margin_bottom,
459 ), $collapse_point );
460 }
461 } // End of responsive code
462 }
463
464 // Add the bottom margins.
465 if ( ! siteorigin_panels_setting( 'inline-styles' ) ) {
466 $css->add_widget_css( $post_id, false, false, false, '', array(
467 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_margin_bottom', $settings['margin-bottom'] . 'px', false, false, $panels_data, $post_id ),
468 ) );
469 $css->add_widget_css( $post_id, false, false, false, ':last-of-type', array(
470 'margin-bottom' => apply_filters( 'siteorigin_panels_css_cell_last_margin_bottom', '0px', false, false, $panels_data, $post_id ),
471 ) );
472 }
473
474 if ( $settings['responsive'] ) {
475 $css->add_cell_css( $post_id, false, false, '', array(
476 'padding' => 0,
477 ), $panels_mobile_width );
478
479 // Hide empty columns on mobile unless "Display Empty Columns With Background" is enabled.
480 if ( ! siteorigin_panels_setting( 'display-empty-rows-with-background' ) ) {
481 foreach ( $layout_data as $ri => $row ) {
482 $css->add_row_css( $post_id, $ri, ' .panel-grid-cell-empty', array(
483 'display' => 'none',
484 ), $panels_mobile_width );
485 }
486 }
487
488 // Hide empty cells on mobile
489 $css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
490 'margin-bottom' => '0px',
491 ), $panels_mobile_width );
492 }
493
494 // Do we need to remove the theme container on this page?
495 if (
496 $this->container['css_override'] &&
497 $this->container['full_width'] && // Does this layout have full width layouts?
498 ! defined( 'siteorigin_css_override' )
499 ) {
500 // Prevent this CSS from being added again.
501 define( 'siteorigin_css_override', true );
502
503 $css->add_css(
504 esc_html( $this->container['selector'] ),
505 array(
506 'max-width' => 'none',
507 // Clear horizontal spacing from container to prevent any indents.
508 'padding-right' => '0',
509 'padding-left' => '0',
510 'margin-right' => '0',
511 'margin-left' => '0',
512 ),
513 1920
514 );
515
516 $css->add_css(
517 '.so-panels-full-wrapper, .panel-grid.panel-no-style, .panel-row-style:not([data-stretch-type])',
518 array(
519 'max-width' => esc_attr( $this->container['width'] ),
520 'margin' => '0 auto',
521 ),
522 1920
523 );
524
525 // Allow .so-panels-full-wrapper to handle columns correctly.
526 $css->add_css(
527 '.so-panels-full-wrapper',
528 array(
529 'display' => 'flex',
530 'flex-wrap' => 'nowrap',
531 'justify-content' => 'space-between',
532 'align-items' => 'flex-start',
533 'width' => '100%',
534 ),
535 1920
536 );
537
538 // Ensure cells inside of .so-panels-full-wrapper are full width when collapsed.
539 $css->add_css(
540 '.so-panels-full-wrapper .panel-grid-cell',
541 array(
542 'width' => '100%',
543 ),
544 siteorigin_panels_setting( 'mobile-width' )
545 );
546 }
547
548 // Let other plugins and components filter the CSS object.
549 $css = apply_filters( 'siteorigin_panels_css_object', $css, $panels_data, $post_id, $layout_data );
550
551 return $css->get_css();
552 }
553
554 /**
555 * Render the panels.
556 *
557 * @param int|string|bool $post_id The Post ID or 'home'.
558 * @param bool $enqueue_css Should we also enqueue the layout CSS.
559 * @param array|bool $panels_data Existing panels data. By default load from settings or post meta.
560 * @param array $layout_data Reformatted panels_data that includes data about the render.
561 *
562 * @return string
563 */
564 public function render( $post_id = false, $enqueue_css = true, $panels_data = false, & $layout_data = array(), $is_preview = false ) {
565 if ( empty( $post_id ) ) {
566 $post_id = get_the_ID();
567
568 if ( SiteOrigin_Panels_Compat_WooCommerce::should_use_shop_page_id() ) {
569 $post_id = wc_get_page_id( 'shop' );
570 }
571 }
572
573 global $siteorigin_panels_current_post;
574 // If $panels_data is empty, and the current post being processed is the same as the last one, don't process it.
575 if (
576 empty( $panels_data ) &&
577 ! empty( $siteorigin_panels_current_post ) &&
578 apply_filters( 'siteorigin_panels_renderer_current_post_check', true ) &&
579 $siteorigin_panels_current_post == $post_id
580 ) {
581 trigger_error( __( 'Prevented SiteOrigin layout from repeated rendering.', 'siteorigin-panels' ) );
582
583 return;
584 }
585
586 $old_current_post = $siteorigin_panels_current_post;
587 $siteorigin_panels_current_post = $post_id;
588
589 // Try get the cached panel from in memory cache.
590 global $siteorigin_panels_cache;
591
592 if ( ! empty( $siteorigin_panels_cache ) && ! empty( $siteorigin_panels_cache[ $post_id ] ) ) {
593 return $siteorigin_panels_cache[ $post_id ];
594 }
595
596 if ( empty( $panels_data ) ) {
597 $panels_data = $this->get_panels_data_for_post( $post_id );
598
599 if ( $panels_data === false ) {
600 return false;
601 }
602 } elseif ( is_string( $panels_data ) ) {
603 // If $panels_data is a string, it's likely json, try decoding it.
604 $panels_data = json_decode( $panels_data, true );
605 }
606
607 $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id );
608
609 if ( empty( $panels_data ) || empty( $panels_data['grids'] ) ) {
610 return '';
611 }
612
613 if ( empty( $this->container ) ) {
614 $this->container = SiteOrigin_Panels::container_settings();
615 }
616
617 if ( $is_preview ) {
618 $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] = true;
619 }
620
621 if ( empty( $layout_data ) ) {
622 $layout_data = $this->get_panels_layout_data( $panels_data );
623 $layout_data = apply_filters( 'siteorigin_panels_layout_data', $layout_data, $post_id );
624 }
625
626 ob_start();
627
628 // Add the panel layout wrapper
629 $layout_classes = apply_filters( 'siteorigin_panels_layout_classes', array( 'panel-layout' ), $post_id, $panels_data );
630
631 if ( is_rtl() ) {
632 $layout_classes[] = 'panel-is-rtl';
633 }
634
635
636 $layout_attributes = apply_filters( 'siteorigin_panels_layout_attributes', array(
637 'id' => 'pl-' . $post_id,
638 'class' => implode( ' ', $layout_classes ),
639 ), $post_id, $panels_data );
640
641 $this->render_element( 'div', $layout_attributes );
642
643 echo apply_filters( 'siteorigin_panels_before_content', '', $panels_data, $post_id );
644
645 foreach ( $layout_data as $ri => & $row ) {
646 if ( apply_filters( 'siteorigin_panels_output_row', true, $row, $ri, $panels_data, $post_id ) ) {
647 $this->render_row( $post_id, $ri, $row, $panels_data );
648 }
649 }
650
651 echo apply_filters( 'siteorigin_panels_after_content', '', $panels_data, $post_id );
652
653 echo '</div>';
654
655 do_action( 'siteorigin_panels_after_render', $panels_data, $post_id );
656
657 $html = ob_get_clean();
658
659 if ( $enqueue_css && ! isset( $this->inline_css[ $post_id ] ) ) {
660 wp_enqueue_style( 'siteorigin-panels-front' );
661 $this->add_inline_css( $post_id, $this->generate_css( $post_id, $panels_data, $layout_data ) );
662 }
663
664 // Reset the current post
665 $siteorigin_panels_current_post = $old_current_post;
666
667 $rendered_layout = apply_filters( 'siteorigin_panels_render', $html, $post_id, ! empty( $post ) ? $post : null );
668
669 if ( $is_preview ) {
670 $widget_css = '@import url(' . esc_url( SiteOrigin_Panels::front_css_url() ) . '); ';
671 $widget_css .= SiteOrigin_Panels::renderer()->generate_css( $post_id, $panels_data, $layout_data );
672 $widget_css = preg_replace( '/\s+/', ' ', $widget_css );
673 $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
674 $rendered_layout .= "\n\n" .
675 "<style$type_attr class='panels-style' data-panels-style-for-post='" . esc_attr( $post_id ) . "'>" .
676 $widget_css .
677 '</style>';
678 }
679
680 unset( $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] );
681
682 return $rendered_layout;
683 }
684
685 /**
686 * Echo the style wrapper and return if there was a wrapper
687 *
688 * @param string $name The name of the style wrapper
689 * @param array $style The style wrapper args. Used as an argument for siteorigin_panels_{$name}_style_attributes
690 * @param string|bool $for An identifier of what this style wrapper is for
691 *
692 * @return bool Is there a style wrapper
693 */
694 private function start_style_wrapper( $name, $style = array(), $for = false ) {
695 $attributes = array();
696
697 if ( empty( $attributes['class'] ) ) {
698 $attributes['class'] = array();
699 }
700
701 if ( empty( $attributes['style'] ) ) {
702 $attributes['style'] = '';
703 }
704
705 // Check if Page Builder is set to output certain styles inline and if it is, do so.
706 if ( siteorigin_panels_setting( 'inline-styles' ) ) {
707 if ( ! empty( $style['padding'] ) ) {
708 SiteOrigin_Panels_Styles::single()->full_width_stretched_legacy_padding( $style, 'padding' );
709 $attributes['style'] .= 'padding: ' . $style['padding'] . ';';
710 }
711
712 if ( $name !== 'widget' && ! empty( $style['margin'] ) ) {
713 $attributes['style'] .= 'margin: ' . $style['margin'] . ';';
714 }
715
716 if ( ! empty( $style['border_color'] ) ) {
717 $border_thickness = ! empty( $style['border_thickness'] ) ? $style['border_thickness'] : '1px 1px 1px 1px';
718
719 // Does this have legacy border thickness?
720 if ( strpos( $border_thickness, ' ' ) === false ) {
721 $attributes['style'] .= 'border: ' . $border_thickness . ' solid ' . $style['border_color'] . ';';
722 } else {
723 $border_thickness_split = explode( ' ', $border_thickness );
724 foreach ( $border_thickness_split as $i => $border_thickness_part ) {
725 $attributes['style'] .= 'border-' . $this->side[ $i ] . ': ' . $border_thickness_part . ' solid ' . $style['border_color'] .';';
726 }
727 }
728 }
729 }
730
731 // Get everything related to the style wrapper
732 $attributes = apply_filters( 'siteorigin_panels_' . $name . '_style_attributes', $attributes, $style );
733 $attributes = apply_filters( 'siteorigin_panels_general_style_attributes', $attributes, $style );
734
735 $standard_css = array();
736 $standard_css = apply_filters( 'siteorigin_panels_' . $name . '_style_css', $standard_css, $style );
737 $standard_css = apply_filters( 'siteorigin_panels_general_style_css', $standard_css, $style );
738
739 $tablet_css = array();
740 $tablet_css = siteorigin_panels_setting( 'tablet-layout' ) ? apply_filters( 'siteorigin_panels_' . $name . '_style_tablet_css', $tablet_css, $style ) : '';
741 $tablet_css = apply_filters( 'siteorigin_panels_general_style_tablet_css', $tablet_css, $style );
742
743 $mobile_css = array();
744 $mobile_css = apply_filters( 'siteorigin_panels_' . $name . '_style_mobile_css', $mobile_css, $style );
745 $mobile_css = apply_filters( 'siteorigin_panels_general_style_mobile_css', $mobile_css, $style );
746
747 // Remove anything we didn't actually use
748 if ( empty( $attributes['class'] ) ) {
749 unset( $attributes['class'] );
750 }
751
752 if ( empty( $attributes['style'] ) ) {
753 unset( $attributes['style'] );
754 }
755
756 $style_wrapper = '';
757
758 if ( ! empty( $attributes ) || ! empty( $standard_css ) || ! empty( $tablet_css ) || ! empty( $mobile_css ) ) {
759 if ( empty( $attributes['class'] ) ) {
760 $attributes['class'] = array();
761 }
762 $attributes['class'][] = 'panel-' . $name . '-style';
763
764 if ( ! empty( $for ) ) {
765 $attributes['class'][] = 'panel-' . $name . '-style-for-' . sanitize_html_class( $for );
766 }
767 $attributes['class'] = array_unique( $attributes['class'] );
768
769 // Filter and sanitize the classes
770 $attributes['class'] = apply_filters( 'siteorigin_panels_' . $name . '_style_classes', $attributes['class'], $attributes, $style );
771 $attributes['class'] = array_map( 'sanitize_html_class', $attributes['class'] );
772
773 $style_wrapper = '<div ';
774
775 foreach ( $attributes as $name => $value ) {
776 // Attributes start with _ are used for internal communication between filters, so are not added to the HTML
777 // We don't make use of this in our styling, so its left as a mechanism for other plugins.
778 if ( substr( $name, 0, 1 ) === '_' ) {
779 continue;
780 }
781
782 if ( is_array( $value ) ) {
783 $style_wrapper .= $this->sanitize_attribute_key( $name ) . '="' . esc_attr( implode( ' ', array_unique( $value ) ) ) . '" ';
784 } else {
785 $style_wrapper .= $this->sanitize_attribute_key( $name ) . '="' . esc_attr( $value ) . '" ';
786 }
787 }
788 $style_wrapper .= '>';
789
790 return $style_wrapper;
791 }
792
793 return $style_wrapper;
794 }
795
796 /**
797 * Render the widget.
798 *
799 * @param array $widget_info The widget info.
800 * @param array $instance The widget instance
801 * @param int $grid_index The grid index.
802 * @param int $cell_index The cell index.
803 * @param int $widget_index The index of this widget.
804 * @param bool $is_first Is this the first widget in the cell.
805 * @param bool $is_last Is this the last widget in the cell.
806 * @param bool $post_id
807 * @param string $style_wrapper The start of the style wrapper
808 */
809 public function the_widget( $widget_info, $instance, $grid_index, $cell_index, $widget_index, $is_first, $is_last, $post_id = false, $style_wrapper = '' ) {
810 // Set widget class to $widget
811 $widget_class = $widget_info['class'];
812 $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
813
814 // Load the widget from the widget factory and give themes and plugins a chance to provide their own
815 $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
816 $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class, $instance );
817
818 // Allow other themes/plugins to override the instance.
819 $instance = apply_filters( 'siteorigin_panels_widget_instance', $instance, $the_widget, $widget_class );
820
821 if ( empty( $post_id ) ) {
822 $post_id = get_the_ID();
823
824 if ( SiteOrigin_Panels_Compat_WooCommerce::should_use_shop_page_id() ) {
825 $post_id = wc_get_page_id( 'shop' );
826 }
827 }
828
829 $classes = array( 'so-panel' );
830
831 if ( siteorigin_panels_setting( 'add-widget-class' ) ) {
832 $classes[] = 'widget';
833 }
834
835 if ( ! empty( $the_widget ) && ! empty( $the_widget->id_base ) ) {
836 $classes[] = 'widget_' . $the_widget->id_base;
837 }
838
839 if ( ! empty( $the_widget ) && is_array( $the_widget->widget_options ) && ! empty( $the_widget->widget_options['classname'] ) ) {
840 $classes[] = $the_widget->widget_options['classname'];
841 }
842
843 if ( $is_first ) {
844 $classes[] = 'panel-first-child';
845 }
846
847 if ( $is_last ) {
848 $classes[] = 'panel-last-child';
849 }
850
851 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $widget_info ) ) {
852 $classes[] = 'panel-has-overlay';
853 }
854
855 $id = 'panel-' . $post_id . '-' . $grid_index . '-' . $cell_index . '-' . $widget_index;
856
857 // Filter and sanitize the classes
858 $classes = apply_filters( 'siteorigin_panels_widget_classes', $classes, $widget_class, $instance, $widget_info );
859 $classes = explode( ' ', implode( ' ', $classes ) );
860 $classes = array_filter( $classes );
861 $classes = array_unique( $classes );
862 $classes = array_map( 'sanitize_html_class', $classes );
863
864 $title_html = siteorigin_panels_setting( 'title-html' );
865
866 if ( strpos( $title_html, '{{title}}' ) !== false ) {
867 list( $before_title, $after_title ) = explode( '{{title}}', $title_html, 2 );
868 } else {
869 $before_title = '<h3 class="widget-title">';
870 $after_title = '</h3>';
871 }
872
873 // Attributes of the widget wrapper.
874 $attributes = array(
875 'id' => $id,
876 'class' => implode( ' ', $classes ),
877 'data-index' => $widget_info['widget_index'],
878 );
879
880 if ( siteorigin_panels_setting( 'inline-styles' ) ) {
881 $attributes['style'] = '';
882 if ( ! empty( $widget_info['style']['margin'] ) ) {
883 $attributes['style'] .= 'margin: ' . esc_attr( $widget_info['style']['margin'] ) . ';';
884 }
885
886 if (
887 ! $is_last &&
888 empty( $widget_info['style']['margin'] )
889 ) {
890 $widget_bottom_margin = apply_filters(
891 'siteorigin_panels_css_cell_margin_bottom',
892 siteorigin_panels_setting( 'margin-bottom' ) . 'px',
893 false,
894 false,
895 array(),
896 $post_id
897 );
898
899 if (
900 ! empty( $widget_bottom_margin ) &&
901 $widget_bottom_margin !== '0px'
902 ) {
903 $attributes['style'] .= 'margin-bottom: ' . $widget_bottom_margin;
904 }
905 }
906
907 if ( empty( $attributes['style'] ) ) {
908 unset( $attributes['style'] );
909 }
910 }
911
912 $attributes = apply_filters( 'siteorigin_panels_widget_attributes', $attributes, $widget_info );
913
914 $before_widget = '<div ';
915
916 foreach ( $attributes as $k => $v ) {
917 $before_widget .= $this->sanitize_attribute_key( $k ) . '="' . esc_attr( $v ) . '" ';
918 }
919 $before_widget .= '>';
920
921 $args = array(
922 'before_widget' => $before_widget,
923 'after_widget' => '</div>',
924 'before_title' => $before_title,
925 'after_title' => $after_title,
926 'widget_id' => 'widget-' . $grid_index . '-' . $cell_index . '-' . $widget_index,
927 );
928
929 // Let other themes and plugins change the arguments that go to the widget class.
930 $args = apply_filters( 'siteorigin_panels_widget_args', $args );
931
932 // If there is a style wrapper, add it.
933 if ( ! empty( $style_wrapper ) ) {
934 $args['before_widget'] = $args['before_widget'] . $style_wrapper;
935 $args['after_widget'] = '</div>' . $args['after_widget'];
936 }
937
938 // This allows other themes and plugins to add HTML inside of the widget before and after the contents.
939 $args['before_widget'] .= apply_filters( 'siteorigin_panels_inside_widget_before', '', $widget_info );
940 $args['after_widget'] = apply_filters( 'siteorigin_panels_inside_widget_after', '', $widget_info ) . $args['after_widget'];
941
942 // This gives other plugins the chance to take over rendering of widgets
943 $widget_html = apply_filters( 'siteorigin_panels_the_widget_html', '', $the_widget, $args, $instance );
944
945 if ( ! empty( $widget_html ) ) {
946 echo $args['before_widget'];
947 echo $widget_html;
948 echo $args['after_widget'];
949 } elseif ( ! empty( $the_widget ) && is_a( $the_widget, 'WP_Widget' ) ) {
950 $the_widget->widget( $args, $instance );
951 } else {
952 // This gives themes a chance to display some sort of placeholder for missing widgets
953 echo apply_filters( 'siteorigin_panels_missing_widget', $args['before_widget'] . $args['after_widget'], $widget_class, $args, $instance );
954 }
955 }
956
957 /**
958 * Print inline CSS in the header and footer.
959 */
960 public function print_inline_css( $return_css = false ) {
961 if ( ! empty( $this->inline_css ) ) {
962 $the_css = '';
963
964 foreach ( $this->inline_css as $post_id => $css ) {
965 if ( empty( $css ) ) {
966 continue;
967 }
968 $the_css .= '/* Layout ' . esc_attr( $post_id ) . ' */ ';
969 $the_css .= $css;
970 }
971
972 // Reset the inline CSS
973 $this->inline_css = null;
974
975 switch ( current_filter() ) {
976 case 'wp_head':
977 $css_id = 'head';
978 break;
979
980 case 'wp_footer':
981 $css_id = 'footer';
982 break;
983
984 default:
985 $css_id = sanitize_html_class( current_filter() );
986 break;
987 }
988
989 // Allow third party developers to change the inline styles or remove them completely.
990 $the_css = apply_filters( 'siteorigin_panels_inline_styles', $the_css );
991
992 if ( ! empty( $the_css ) ) {
993 if ( $return_css ) {
994 ob_start();
995 }
996
997 printf( '<style media="all" id="siteorigin-panels-layouts-%s">%s</style>', esc_attr( $css_id ), $the_css );
998
999 if ( $return_css ) {
1000 return ob_get_clean();
1001 }
1002 }
1003 }
1004 }
1005
1006 /**
1007 * Enqueue the required styles
1008 */
1009 public function enqueue_styles() {
1010 // Register the style to support possible lazy loading
1011 wp_register_style(
1012 'siteorigin-panels-front',
1013 esc_url( SiteOrigin_Panels::front_css_url() ),
1014 array(),
1015 SITEORIGIN_PANELS_VERSION
1016 );
1017 }
1018
1019 /**
1020 * Retrieve panels data for a post or a prebuilt layout or the home page layout.
1021 *
1022 * @param string $post_id
1023 *
1024 * @return array
1025 */
1026 private function get_panels_data_for_post( $post_id ) {
1027 if ( SiteOrigin_Panels::is_live_editor() ) {
1028 if (
1029 current_user_can( 'edit_post', $post_id ) &&
1030 ! empty( $_POST['live_editor_panels_data'] ) &&
1031 $_POST['live_editor_post_ID'] == $post_id
1032 ) {
1033 $panels_data = json_decode( wp_unslash( $_POST['live_editor_panels_data'] ), true );
1034
1035 if ( ! empty( $panels_data['widgets'] ) ) {
1036 $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'] );
1037 }
1038 }
1039 } elseif ( strpos( $post_id, 'prebuilt:' ) === 0 ) {
1040 list( $null, $prebuilt_id ) = explode( ':', $post_id, 2 );
1041 $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
1042 $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : array();
1043 } elseif ( $post_id == 'home' ) {
1044 $page_id = get_option( 'page_on_front' );
1045
1046 if ( empty( $page_id ) ) {
1047 $page_id = get_option( 'siteorigin_panels_home_page_id' );
1048 }
1049
1050 $panels_data = ! empty( $page_id ) ? get_post_meta( $page_id, 'panels_data', true ) : null;
1051
1052 if ( is_null( $panels_data ) ) {
1053 // Load the default layout
1054 $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
1055 $prebuilt_id = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
1056
1057 $panels_data = ! empty( $layouts[ $prebuilt_id ] ) ? $layouts[ $prebuilt_id ] : current( $layouts );
1058 }
1059 }
1060
1061 if ( ! empty( $post_id ) && empty( $panels_data ) ) {
1062 if ( post_password_required( $post_id ) ) {
1063 return false;
1064 }
1065 $panels_data = get_post_meta( $post_id, 'panels_data', true );
1066 }
1067
1068 return $panels_data;
1069 }
1070
1071 /**
1072 * Transform flat panels data into a hierarchical structure.
1073 *
1074 * @param array $panels_data Flat panels data containing `grids`, `grid_cells`, and `widgets`.
1075 *
1076 * @return array Hierarchical structure of rows => cells => widgets.
1077 */
1078 public function get_panels_layout_data( $panels_data ) {
1079 $layout_data = array();
1080
1081 foreach ( $panels_data['grids'] as $grid ) {
1082 $layout_data[] = array(
1083 'style' => ! empty( $grid['style'] ) ? $grid['style'] : array(),
1084 'ratio' => ! empty( $grid['ratio'] ) ? $grid['ratio'] : '',
1085 'ratio_direction' => ! empty( $grid['ratio_direction'] ) ? $grid['ratio_direction'] : '',
1086 'color_label' => ! empty( $grid['color_label'] ) ? $grid['color_label'] : '',
1087 'label' => ! empty( $grid['label'] ) ? $grid['label'] : '',
1088 'cells' => array(),
1089 );
1090 }
1091
1092 foreach ( $panels_data['grid_cells'] as $cell ) {
1093 $layout_data[ $cell['grid'] ]['cells'][] = array(
1094 'widgets' => array(),
1095 'style' => ! empty( $cell['style'] ) ? $cell['style'] : array(),
1096 'weight' => (float) $cell['weight'],
1097 );
1098 }
1099
1100 foreach ( $panels_data['widgets'] as $i => $widget ) {
1101 $widget['panels_info']['widget_index'] = $i;
1102 $row_index = (int) $widget['panels_info']['grid'];
1103 $cell_index = (int) $widget['panels_info']['cell'];
1104 $layout_data[ $row_index ]['cells'][ $cell_index ]['widgets'][] = $widget;
1105 }
1106
1107 return $layout_data;
1108 }
1109
1110 /**
1111 * Outputs the given HTML tag with the given attributes.
1112 *
1113 * @param string $tag The HTML element to render.
1114 * @param array $attributes The attributes for the HTML element.
1115 */
1116 private function render_element( $tag, $attributes ) {
1117 echo '<' . esc_html( $tag );
1118
1119 foreach ( $attributes as $name => $value ) {
1120 if ( $value ) {
1121 echo ' ' . $this->sanitize_attribute_key( $name ) . '="' . esc_attr( $value ) . '" ';
1122 }
1123 }
1124 echo '>';
1125 }
1126
1127 /**
1128 * Render everything for the given row, including:
1129 * - filters before and after row,
1130 * - row style wrapper,
1131 * - row element wrapper with attributes,
1132 * - child cells
1133 *
1134 * @param string $post_id The ID of the post containing this layout.
1135 * @param int $ri The index of this row.
1136 * @param array $row The model containing this row's data and child cells.
1137 * @param array $panels_data A copy of panels_data for filters.
1138 */
1139 private function render_row( $post_id, $ri, & $row, & $panels_data ) {
1140 $row_style_wrapper = $this->start_style_wrapper( 'row', ! empty( $row['style'] ) ? $row['style'] : array(), $post_id . '-' . $ri );
1141
1142 $row_classes = array( 'panel-grid' );
1143 $row_classes[] = ! empty( $row_style_wrapper ) ? 'panel-has-style' : 'panel-no-style';
1144 if ( $this->should_display_empty_background_row( $row ) ) {
1145 $row_classes[] = 'panel-empty-row-has-background';
1146 }
1147
1148 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $row ) ) {
1149 $row_classes[] = 'panel-has-overlay';
1150 }
1151
1152 $row_classes = apply_filters( 'siteorigin_panels_row_classes', $row_classes, $row );
1153
1154 $row_attributes = array(
1155 'id' => 'pg-' . $post_id . '-' . $ri,
1156 'class' => implode( ' ', $row_classes ),
1157 );
1158
1159 if ( siteorigin_panels_setting( 'inline-styles' ) ) {
1160 $panels_margin_bottom = apply_filters( 'siteorigin_panels_css_row_margin_bottom', siteorigin_panels_setting( 'margin-bottom' ) . 'px', $row, $ri, $panels_data, $post_id );
1161
1162 if (
1163 ! empty( $row['style']['bottom_margin'] ) ||
1164 $ri != count( $panels_data['grids'] ) - 1 ||
1165 ! empty( siteorigin_panels_setting( 'margin-bottom-last-row' ) )
1166 ) {
1167 $row_attributes['style'] = 'margin-bottom: ' . $panels_margin_bottom;
1168 }
1169 }
1170
1171 $row_attributes = apply_filters( 'siteorigin_panels_row_attributes', $row_attributes, $row );
1172
1173 // This allows other themes and plugins to add html before the row
1174 echo apply_filters( 'siteorigin_panels_before_row', '', $row, $row_attributes );
1175
1176 $this->render_element( 'div', $row_attributes );
1177
1178 if ( ! empty( $row_style_wrapper ) ) {
1179 echo $row_style_wrapper;
1180 }
1181
1182 if (
1183 $this->container['css_override'] &&
1184 isset( $row['style']['row_stretch'] ) &&
1185 $row['style']['row_stretch'] == 'full'
1186 ) {
1187 $this->render_element( 'div', array(
1188 'class' => 'so-panels-full-wrapper',
1189 ) );
1190 }
1191
1192 // This allows other themes and plugins to add HTML inside of the row before the row contents.
1193 echo apply_filters( 'siteorigin_panels_inside_row_before', '', $row );
1194
1195 if ( method_exists( $this, 'modify_row_cells' ) ) {
1196 // This gives other renderers a chance to change the cell order
1197 $row['cells'] = $cells = $this->modify_row_cells( $row['cells'], $row );
1198 }
1199
1200 foreach ( $row['cells'] as $ci => & $cell ) {
1201 $this->render_cell( $post_id, $ri, $ci, $cell, $row['cells'], $panels_data );
1202 }
1203
1204 // This allows other themes and plugins to add HTML inside of the row after the row contents.
1205 echo apply_filters( 'siteorigin_panels_inside_row_after', '', $row );
1206
1207 if (
1208 $this->container['css_override'] &&
1209 isset( $row['style']['row_stretch'] ) &&
1210 $row['style']['row_stretch'] == 'full'
1211 ) {
1212 echo '</div>';
1213 }
1214
1215 // Close the style wrapper
1216 if ( ! empty( $row_style_wrapper ) ) {
1217 echo '</div>';
1218 }
1219
1220 echo '</div>';
1221
1222 // This allows other themes and plugins to add html after the row
1223 echo apply_filters( 'siteorigin_panels_after_row', '', $row, $row_attributes );
1224 }
1225
1226 /**
1227 * Render everything for the given cell, including:
1228 * - filters before and after cell,
1229 * - cell element wrapper with attributes,
1230 * - style wrapper,
1231 * - child widgets
1232 *
1233 * @param string $post_id The ID of the post containing this layout.
1234 * @param int $ri The index of this cell's parent row.
1235 * @param int $ci The index of this cell.
1236 * @param array $cell The model containing this cell's data and child widgets.
1237 * @param array $cells The array of cells containing this cell.
1238 * @param array $panels_data A copy of panels_data for filters
1239 */
1240 private function render_cell( $post_id, $ri, $ci, & $cell, $cells, & $panels_data ) {
1241 $cell_classes = array( 'panel-grid-cell' );
1242
1243 if ( empty( $cell['widgets'] ) ) {
1244 $cell_classes[] = 'panel-grid-cell-empty';
1245 }
1246
1247 if ( $ci == count( $cells ) - 2 && count( $cells[ $ci + 1 ]['widgets'] ) == 0 ) {
1248 $cell_classes[] = 'panel-grid-cell-mobile-last';
1249 }
1250
1251 // Themes can add their own styles to cells
1252 $cell_classes = apply_filters( 'siteorigin_panels_cell_classes', $cell_classes, $cell );
1253
1254 if ( SiteOrigin_Panels_Styles::single()->has_overlay( $cell ) ) {
1255 $cell_classes[] = 'panel-has-overlay';
1256 }
1257
1258 // Legacy filter, use `siteorigin_panels_cell_classes` instead
1259 $cell_classes = apply_filters( 'siteorigin_panels_row_cell_classes', $cell_classes, $panels_data, $cell );
1260
1261 $cell_attributes = apply_filters( 'siteorigin_panels_cell_attributes', array(
1262 'id' => 'pgc-' . $post_id . '-' . $ri . '-' . $ci,
1263 'class' => implode( ' ', $cell_classes ),
1264 ), $cell );
1265
1266 // Legacy filter, use `siteorigin_panels_cell_attributes` instead
1267 $cell_attributes = apply_filters( 'siteorigin_panels_row_cell_attributes', $cell_attributes, $panels_data, $cell );
1268
1269 echo apply_filters( 'siteorigin_panels_before_cell', '', $cell, $cell_attributes );
1270
1271 $this->render_element( 'div', $cell_attributes );
1272
1273 $grid = $panels_data['grids'][ $ri ];
1274
1275 if ( empty( $cell['style']['class'] ) && ! empty( $grid['style']['cell_class'] ) ) {
1276 $cell['style']['class'] = $grid['style']['cell_class'];
1277 }
1278
1279 $cell_style = ! empty( $cell['style'] ) ? $cell['style'] : array();
1280 $cell_style_wrapper = $this->start_style_wrapper( 'cell', $cell_style, $post_id . '-' . $ri . '-' . $ci );
1281
1282 if ( ! empty( $cell_style_wrapper ) ) {
1283 echo $cell_style_wrapper;
1284 }
1285 // This allows other themes and plugins to add HTML inside of the cell before its contents.
1286 echo apply_filters( 'siteorigin_panels_inside_cell_before', '', $cell );
1287
1288 foreach ( $cell['widgets'] as $wi => & $widget ) {
1289 $is_last = ( $wi == count( $cell['widgets'] ) - 1 );
1290
1291 if ( apply_filters( 'siteorigin_panels_output_widget', true, $widget, $ri, $ci, $wi, $panels_data, $post_id ) ) {
1292 $this->render_widget( $post_id, $ri, $ci, $wi, $widget, $is_last );
1293 }
1294 }
1295
1296 // This allows other themes and plugins to add HTML inside of the cell after its contents.
1297 echo apply_filters( 'siteorigin_panels_inside_cell_after', '', $cell );
1298
1299 if ( ! empty( $cell_style_wrapper ) ) {
1300 echo '</div>';
1301 }
1302 echo '</div>';
1303
1304 echo apply_filters( 'siteorigin_panels_after_cell', '', $cell, $cell_attributes );
1305 }
1306
1307 /**
1308 * Gets the style wrapper for this widget and passes it through to `the_widget` along with other required parameters.
1309 *
1310 * @param string $post_id The ID of the post containing this layout.
1311 * @param int $ri The index of this widget's ancestor row.
1312 * @param int $ci The index of this widget's parent cell.
1313 * @param int $wi The index of this widget.
1314 * @param array $widget The model containing this widget's data.
1315 * @param bool $is_last Whether this is the last widget in the parent cell.
1316 */
1317 private function render_widget( $post_id, $ri, $ci, $wi, & $widget, $is_last ) {
1318 $widget_style_wrapper = $this->start_style_wrapper(
1319 'widget',
1320 ! empty( $widget['panels_info']['style'] ) ? $widget['panels_info']['style'] : array(),
1321 $post_id . '-' . $ri . '-' . $ci . '-' . $wi
1322 );
1323
1324 $this->the_widget(
1325 $widget['panels_info'],
1326 $widget,
1327 $ri,
1328 $ci,
1329 $wi,
1330 $wi == 0,
1331 $is_last,
1332 $post_id,
1333 $widget_style_wrapper
1334 );
1335 }
1336
1337 public function front_css_url() {
1338 return siteorigin_panels_url( 'css/front-flex' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' );
1339 }
1340
1341 function sanitize_attribute_key( $attr = null ) {
1342 if ( empty( $attr ) ) {
1343 return 'invalid-attribute';
1344 }
1345
1346 $attr = sanitize_key( strtolower( $attr ) );
1347
1348 // "On" prefixed attributes are too risky to allow.
1349 if (
1350 empty( $attr ) ||
1351 strpos( $attr, 'on' ) === 0
1352 ) {
1353 return 'invalid-attribute';
1354 };
1355
1356 return $attr;
1357 }
1358 }
1359