Skip to content

Commit 2d5a616

Browse files
committed
Merge branch 'release/2.34.0'
2 parents c25e8b3 + cb70bd2 commit 2d5a616

File tree

11 files changed

+299
-63
lines changed

11 files changed

+299
-63
lines changed

changelog.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
== Changelog ==
22

3+
= 2.34.0 – 21 February 2026 =
4+
* ACF: Improved widget compatibility for array return format.
5+
* AIOSEO, Layout Block: Prevented the AIOSEO SiteOrigin integration from loading in the Layout Block editor.
6+
* Events Manager: Fixed query context handling and duplication processing to prevent duplicate output and preserve Page Builder data.
7+
* Security: Hardened post loop template resolution to prevent local file inclusion risks.
8+
* WooCommerce: Prevented Shop page content from leaking into Terms and Conditions content rendering.
9+
* Added a setting to display empty columns with backgrounds in responsive layouts.
10+
311
= 2.33.5 – 07 January 2026 =
412
* Added contextual menu action hooks for row and widget menus.
513

compat/acf-widgets.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function enqueue_assets() {
4242
*/
4343
public function store_acf_widget_fields_instance( $the_widget, $instance ) {
4444
if ( ! empty( $instance['acf'] ) ) {
45+
$fields = array();
4546
$field_groups = acf_get_field_groups( array(
4647
'widget' => $the_widget->id_base,
4748
) );
@@ -70,15 +71,15 @@ public function load_panels_widget_field_data( $value, $post_id, $widget_field )
7071
$fields = acf_get_store( 'so_fields' );
7172
$instance = acf_get_store( 'so_widget_instance' );
7273

73-
if ( ! empty( $fields ) ) {
74+
if ( ! empty( $fields ) && ! empty( $instance ) ) {
7475
foreach ( $fields->data as $field ) {
7576
if (
76-
$widget_field['type'] != 'repeater' ||
77+
$widget_field['type'] != 'repeater' &&
7778
$widget_field['type'] != 'checkbox'
7879
) {
7980
if (
8081
$field['key'] == $widget_field['key'] &&
81-
! empty( $instance->data[ $field['key'] ] )
82+
array_key_exists( $field['key'], $instance->data )
8283
) {
8384
return $instance->data[ $field['key'] ];
8485
}
@@ -87,6 +88,8 @@ public function load_panels_widget_field_data( $value, $post_id, $widget_field )
8788
}
8889
}
8990
}
91+
92+
return $value;
9093
}
9194

9295
/**
@@ -112,7 +115,7 @@ private function generate_fields_array( $fields ) {
112115
}
113116
}
114117

115-
if ( $fields != '' ) {
118+
if ( $fields !== '' && $fields !== null ) {
116119
return $fields;
117120
}
118121
}

compat/events-manager.php

Lines changed: 105 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,117 @@
77
return;
88
}
99

10-
$em_pb_removed = false;
11-
12-
/**
13-
* Disable Page Builder for Events Manager post types.
14-
*
15-
* This function checks if the current post is an Events Manager post type
16-
* and if Page Builder is enabled for it. If both conditions are met, it
17-
* disables Page Builder for the content. This is done to prevent Page Builder
18-
* from interfering with the Events Manager content, and vice versa.
19-
*
20-
* `loop_start` is used due to when the Events Manager plugin sets up its
21-
* content replacement.
22-
*
23-
* @return void
24-
*/
25-
function siteorigin_panels_event_manager_loop_start() {
26-
$em_post_types = array( 'event-recurring', 'event' );
27-
28-
// Is the current post an $em_post_types post?
29-
$post_type = get_post_type();
30-
if ( ! in_array( $post_type, $em_post_types ) ) {
31-
return;
10+
class SiteOrigin_Panels_Compat_Events_Manager {
11+
private $is_pb_removed = false;
12+
private $is_duplicating = false;
13+
14+
public static function single() {
15+
static $single;
16+
17+
return empty( $single ) ? $single = new self() : $single;
3218
}
3319

34-
// Is Page Builder enabled for Events Manager post types?
35-
$pb_post_types = siteorigin_panels_setting( 'post-types' );
36-
if ( empty( $pb_post_types ) || ! array_intersect( $em_post_types, $pb_post_types ) ) {
37-
return;
20+
public function __construct() {
21+
add_action( 'loop_start', array( $this, 'loop_start' ) );
22+
add_action( 'loop_end', array( $this, 'loop_end' ) );
23+
24+
add_action( 'em_event_duplicate_pre', array( $this, 'duplicate_pre' ) );
25+
add_filter( 'em_event_get_event_meta', array( $this, 'filter_duplicate_meta' ) );
26+
add_filter( 'em_event_duplicate', array( $this, 'duplicate_copy_panels_data' ), 10, 2 );
3827
}
3928

40-
global $em_pb_removed;
41-
$em_pb_removed = true;
29+
/**
30+
* Disable Page Builder for Events Manager post types.
31+
*
32+
* @return void
33+
*/
34+
public function loop_start() {
35+
$em_post_types = array( 'event-recurring', 'event' );
4236

43-
add_filter( 'siteorigin_panels_filter_content_enabled', '__return_false' );
44-
}
45-
add_action( 'loop_start', 'siteorigin_panels_event_manager_loop_start' );
37+
$post_type = get_post_type();
38+
if ( ! in_array( $post_type, $em_post_types ) ) {
39+
return;
40+
}
41+
42+
$pb_post_types = siteorigin_panels_setting( 'post-types' );
43+
if ( empty( $pb_post_types ) || ! array_intersect( $em_post_types, $pb_post_types ) ) {
44+
return;
45+
}
4646

47-
/**
48-
* Re-enable Page Builder for `the_content` filter if it
49-
* was disabled at the start of the loop.
50-
*/
51-
function siteorigin_panels_event_manager_loop_end() {
52-
global $em_pb_removed;
47+
$this->is_pb_removed = true;
48+
add_filter( 'siteorigin_panels_filter_content_enabled', '__return_false' );
49+
}
50+
51+
/**
52+
* Re-enable Page Builder for `the_content` filter if it
53+
* was disabled at the start of the loop.
54+
*
55+
* @return void
56+
*/
57+
public function loop_end() {
58+
if ( $this->is_pb_removed ) {
59+
remove_filter( 'siteorigin_panels_filter_content_enabled', '__return_false' );
60+
$this->is_pb_removed = false;
61+
}
62+
}
5363

54-
if ( $em_pb_removed ) {
55-
remove_filter( 'siteorigin_panels_filter_content_enabled', '__return_false' );
64+
/**
65+
* Flag Events Manager duplication so we can avoid unsafe SQL inserts for panels_data.
66+
*
67+
* @return void
68+
*/
69+
public function duplicate_pre() {
70+
$this->is_duplicating = true;
71+
}
72+
73+
/**
74+
* Remove Page Builder data from Events Manager's raw SQL duplication payload.
75+
*
76+
* @param array $event_meta Event post meta.
77+
*
78+
* @return array
79+
*/
80+
public function filter_duplicate_meta( $event_meta ) {
81+
if ( ! $this->is_duplicating || ! is_array( $event_meta ) ) {
82+
return $event_meta;
83+
}
84+
85+
unset( $event_meta['panels_data'] );
86+
87+
return $event_meta;
88+
}
89+
90+
/**
91+
* Copy Page Builder data to the duplicated event using safe WordPress APIs.
92+
*
93+
* @param mixed $duplicated_event The duplicated event object, or false on failure.
94+
* @param mixed $source_event The original source event object.
95+
*
96+
* @return mixed
97+
*/
98+
public function duplicate_copy_panels_data( $duplicated_event, $source_event ) {
99+
$this->is_duplicating = false;
100+
101+
if (
102+
empty( $duplicated_event ) ||
103+
! is_object( $duplicated_event ) ||
104+
! is_object( $source_event ) ||
105+
empty( $duplicated_event->post_id ) ||
106+
empty( $source_event->post_id )
107+
) {
108+
return $duplicated_event;
109+
}
110+
111+
$source_panels_data = get_post_meta( (int) $source_event->post_id, 'panels_data', true );
112+
if ( empty( $source_panels_data ) ) {
113+
return $duplicated_event;
114+
}
115+
116+
$source_panels_data = map_deep( $source_panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) );
117+
update_post_meta( (int) $duplicated_event->post_id, 'panels_data', $source_panels_data );
118+
119+
return $duplicated_event;
56120
}
57121
}
58-
add_action( 'loop_end', 'siteorigin_panels_event_manager_loop_end' );
122+
123+
SiteOrigin_Panels_Compat_Events_Manager::single();

compat/layout-block.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,26 @@ public function register_layout_block() {
4242
}
4343

4444
public function enqueue_layout_block_editor_assets() {
45-
if ( SiteOrigin_Panels_Admin::is_block_editor() || is_customize_preview() ) {
45+
$is_block_editor = SiteOrigin_Panels_Admin::is_block_editor();
46+
47+
if ( $is_block_editor || is_customize_preview() ) {
48+
if ( $is_block_editor && function_exists( 'aioseo' ) ) {
49+
$aioseo = aioseo();
50+
if (
51+
is_object( $aioseo ) &&
52+
isset( $aioseo->standalone ) &&
53+
is_object( $aioseo->standalone ) &&
54+
isset( $aioseo->standalone->pageBuilderIntegrations ) &&
55+
is_array( $aioseo->standalone->pageBuilderIntegrations ) &&
56+
isset( $aioseo->standalone->pageBuilderIntegrations['siteorigin'] ) &&
57+
is_object( $aioseo->standalone->pageBuilderIntegrations['siteorigin'] )
58+
) {
59+
remove_action(
60+
'siteorigin_panel_enqueue_admin_scripts',
61+
array( $aioseo->standalone->pageBuilderIntegrations['siteorigin'], 'enqueue' )
62+
);
63+
}
64+
}
4665
$panels_admin = SiteOrigin_Panels_Admin::single();
4766
$panels_admin->enqueue_admin_scripts();
4867
$panels_admin->enqueue_admin_styles();

inc/installer

inc/renderer-legacy.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,14 @@ public function generate_css( $post_id, $panels_data = false, $layout_data = fal
111111
'padding' => 0,
112112
), $panels_mobile_width );
113113

114-
// Hide empty cells on mobile
115-
$css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
116-
'display' => 'none',
117-
), $panels_mobile_width );
114+
// Hide empty columns on mobile unless "Display Empty Columns With Background" is enabled.
115+
if ( ! siteorigin_panels_setting( 'display-empty-rows-with-background' ) ) {
116+
foreach ( $layout_data as $ri => $row ) {
117+
$css->add_row_css( $post_id, $ri, ' .panel-grid-cell-empty', array(
118+
'display' => 'none',
119+
), $panels_mobile_width );
120+
}
121+
}
118122

119123
// Hide empty cells on mobile
120124
$css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(

inc/renderer.php

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,61 @@ public static function single() {
1616
return empty( $single ) ? $single = new self() : $single;
1717
}
1818

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+
1974
/**
2075
* Add CSS that needs to go inline.
2176
*/
@@ -91,6 +146,7 @@ public function generate_css( $post_id, $panels_data = false, $layout_data = fal
91146
// Filter the bottom margin for this row with the arguments
92147
$panels_margin_bottom = apply_filters( 'siteorigin_panels_css_row_margin_bottom', $settings['margin-bottom'] . 'px', $row, $ri, $panels_data, $post_id );
93148
$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 );
94150

95151
if ( SiteOrigin_Panels_Styles::single()->has_overlay( $row ) ) {
96152
$css->add_row_css( $post_id, $ri, array(
@@ -420,10 +476,14 @@ public function generate_css( $post_id, $panels_data = false, $layout_data = fal
420476
'padding' => 0,
421477
), $panels_mobile_width );
422478

423-
// Hide empty cells on mobile
424-
$css->add_row_css( $post_id, false, ' .panel-grid-cell-empty', array(
425-
'display' => 'none',
426-
), $panels_mobile_width );
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+
}
427487

428488
// Hide empty cells on mobile
429489
$css->add_row_css( $post_id, false, ' .panel-grid-cell-mobile-last', array(
@@ -505,7 +565,7 @@ public function render( $post_id = false, $enqueue_css = true, $panels_data = fa
505565
if ( empty( $post_id ) ) {
506566
$post_id = get_the_ID();
507567

508-
if ( class_exists( 'WooCommerce' ) && is_shop() ) {
568+
if ( SiteOrigin_Panels::should_use_woocommerce_shop_page_id() ) {
509569
$post_id = wc_get_page_id( 'shop' );
510570
}
511571
}
@@ -761,7 +821,7 @@ public function the_widget( $widget_info, $instance, $grid_index, $cell_index, $
761821
if ( empty( $post_id ) ) {
762822
$post_id = get_the_ID();
763823

764-
if ( class_exists( 'WooCommerce' ) && is_shop() ) {
824+
if ( SiteOrigin_Panels::should_use_woocommerce_shop_page_id() ) {
765825
$post_id = wc_get_page_id( 'shop' );
766826
}
767827
}
@@ -1081,6 +1141,9 @@ private function render_row( $post_id, $ri, & $row, & $panels_data ) {
10811141

10821142
$row_classes = array( 'panel-grid' );
10831143
$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+
}
10841147

10851148
if ( SiteOrigin_Panels_Styles::single()->has_overlay( $row ) ) {
10861149
$row_classes[] = 'panel-has-overlay';

0 commit comments

Comments
 (0)