Skip to content

Commit 6111507

Browse files
committed
Merge branch 'release/2.33.0'
2 parents bf585d0 + acab41e commit 6111507

File tree

10 files changed

+453
-124
lines changed

10 files changed

+453
-124
lines changed

build-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ module.exports = {
7272
'!readme.txt', // Not the readme.txt file. It is copied by the 'version' task.
7373
'!readme.md', // Ignore the readme.md file. It is for the github repo.
7474
'!{js/siteorigin-panels,js/siteorigin-panels/**}',
75-
'!inc/installer/inc/github-plugin-updater.php', // Exclude Installer Standalone Updater.
75+
'!{inc/installer/github-updater,inc/installer/github-updater/**}', // Exclude Installer GitHub Updater directory.
7676
]
7777
},
7878
i18n: {

changelog.txt

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

3+
= 2.33.0 – 29 July 2025 =
4+
* Layout Directory: Added category and niche filtering, search functionality, and pagination support.
5+
* Mode Switcher: Hidden when parent dialog exists for cleaner interface.
6+
* WPML: Updated configuration for improved compatibility.
7+
38
= 2.32.1 – 29 June 2025 =
49
* Mode Switcher: Restored mobile CSS hide functionality for improved responsive behavior.
510
* Mode Switcher: Removed legacy CSS and moved .so-mode to .so-toolbar for cleaner structure.

css/admin.less

Lines changed: 106 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,63 +1297,66 @@
12971297
.save-buttons {
12981298
display: flex;
12991299

1300-
.button-primary.so-close,
1301-
.button-primary.so-save,
1302-
.button-primary.so-saveinline {
1303-
border-bottom-right-radius: 0;
1304-
border-top-right-radius: 0;
1305-
margin-right: -1px;
1306-
}
1307-
1308-
.so-button-mode {
1309-
border-bottom-left-radius: 0;
1310-
border-top-left-radius: 0;
1311-
border-left: 1px solid rgba(0, 0, 0, 0.1);
1300+
&:has(.so-button-mode ) {
1301+
1302+
.button-primary.so-close,
1303+
.button-primary.so-save,
1304+
.button-primary.so-saveinline {
1305+
border-bottom-right-radius: 0;
1306+
border-top-right-radius: 0;
1307+
margin-right: -1px;
1308+
}
13121309

1313-
// Prevent JavaScript from hiding this button when switching modes.
1314-
&.so-mode {
1315-
display: flex !important;
1310+
.so-button-mode {
1311+
border-bottom-left-radius: 0;
1312+
border-top-left-radius: 0;
1313+
border-left: 1px solid rgba(0, 0, 0, 0.1);
1314+
1315+
// Prevent JavaScript from hiding this button when switching modes.
1316+
&.so-mode {
1317+
display: flex !important;
1318+
}
13161319
}
1317-
}
13181320

1319-
.so-mode {
1320-
align-items: center;
1321-
border-bottom-left-radius: 0;
1322-
border-top-left-radius: 0;
1323-
display: flex;
1324-
justify-content: center;
1321+
.so-mode {
1322+
align-items: center;
1323+
border-bottom-left-radius: 0;
1324+
border-top-left-radius: 0;
1325+
display: flex;
1326+
justify-content: center;
13251327

1326-
&:before {
1327-
content: "\f11c";
1328-
height: auto;
1329-
line-height: 0;
1330-
transform: rotate(90deg);
1331-
}
1328+
&:before {
1329+
content: "\f11c";
1330+
height: auto;
1331+
line-height: 0;
1332+
transform: rotate(90deg);
1333+
}
13321334

1333-
.dashicons {
1335+
.dashicons {
1336+
}
13341337
}
1335-
}
13361338

1337-
.so-mode-list {
1338-
background: #fff;
1339-
border: 1px solid #ededed;
1340-
font-size: 12px;
1341-
position: absolute;
1342-
right: 14px;
1343-
top: -49%;
1339+
.so-mode-list {
1340+
background: #fff;
1341+
border: 1px solid #ededed;
1342+
font-size: 12px;
1343+
position: absolute;
1344+
right: 14px;
1345+
top: -49%;
13441346

1345-
li {
1346-
cursor: pointer;
1347-
padding: 6px 8px;
1348-
margin: 0;
1347+
li {
1348+
cursor: pointer;
1349+
padding: 6px 8px;
1350+
margin: 0;
13491351

1350-
&:hover {
1351-
background: #f5f5f5;
1352+
&:hover {
1353+
background: #f5f5f5;
1354+
}
13521355
}
1353-
}
13541356

1355-
.so-close-mode {
1356-
border-top: 1px solid #f5f5f5;
1357+
.so-close-mode {
1358+
border-top: 1px solid #f5f5f5;
1359+
}
13571360
}
13581361
}
13591362
}
@@ -2148,10 +2151,63 @@
21482151
flex-flow: row wrap;
21492152
}
21502153

2154+
.so-directory-items-filters {
2155+
display: flex;
2156+
gap: 24px;
2157+
2158+
& > div {
2159+
display: flex;
2160+
}
2161+
2162+
ul {
2163+
margin: 0;
2164+
}
2165+
2166+
span,
2167+
li {
2168+
display: inline-block;
2169+
padding: 5px 7.5px;
2170+
}
2171+
2172+
span {
2173+
font-weight: 600;
2174+
}
2175+
2176+
li {
2177+
cursor: pointer;
2178+
text-decoration: underline;
2179+
text-transform: capitalize;
2180+
2181+
&:focus,
2182+
&:hover {
2183+
background: #f3f3f3;
2184+
text-decoration: none;
2185+
}
2186+
2187+
&.so-active-filter {
2188+
background: #f3f3f3;
2189+
cursor: default;
2190+
text-decoration: none;
2191+
}
2192+
}
2193+
}
2194+
21512195
// For a list of directory items
21522196
.so-directory-items {
21532197

2198+
&.so-empty {
2199+
2200+
.so-no-results {
2201+
display: block;
2202+
}
2203+
2204+
.so-directory-pages {
2205+
display: none;
2206+
}
2207+
}
2208+
21542209
.so-no-results {
2210+
display: none;
21552211
margin: 20px 0;
21562212
padding: 0 5px;
21572213
}
@@ -2161,6 +2217,10 @@
21612217
.box-sizing(border-box);
21622218
padding: 6px;
21632219

2220+
&.so-hidden {
2221+
display: none;
2222+
}
2223+
21642224
.so-directory-item-wrapper {
21652225
display: flex;
21662226
flex-flow: column nowrap;

inc/admin-layouts.php

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -265,39 +265,33 @@ public function action_get_prebuilt_layouts() {
265265

266266
$return['max_num_pages'] = 1;
267267
} elseif ( substr( $type, 0, 10 ) == 'directory-' ) {
268-
$return['title'] = __( 'Layouts Directory', 'siteorigin-panels' );
269-
270-
// This is a query of the prebuilt layout directory
271-
$query = array();
272-
273-
if ( ! empty( $search ) ) {
274-
$query['search'] = $search;
275-
}
276-
$query['page'] = $page_num;
277-
278268
$directory_id = str_replace( 'directory-', '', $type );
279-
$directories = $this->get_directories();
280-
$directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
281-
282-
if ( empty( $directory ) ) {
283-
return false;
284-
}
285-
286-
// If the user isn't searching, check if we have a cached
287-
// version of the results.
269+
// If the user isn't searching, check if we have a cached version of the results.
288270
if ( empty( $search ) ) {
289271
$cache = get_transient( 'siteorigin_panels_layouts_directory_cache_' . $directory_id .'_page_' . $page_num );
290-
291272
if ( ! empty( $cache ) ) {
292273
$return = $cache;
293274
}
294275
}
295276

296277
if ( empty( $return['items'] ) ) {
278+
$return['title'] = __( 'Layouts Directory', 'siteorigin-panels' );
279+
280+
// This is a query of the prebuilt layout directory
281+
$query = array(
282+
'search' => $search,
283+
'page' => $page_num,
284+
);
285+
$directories = $this->get_directories();
286+
$directory = ! empty( $directories[ $directory_id ] ) ? $directories[ $directory_id ] : false;
287+
288+
if ( empty( $directory ) ) {
289+
return false;
290+
}
297291
$url = add_query_arg( $query, $directory[ 'url' ] . 'wp-admin/admin-ajax.php?action=query_layouts' );
298292

299-
if ( ! empty( $directory['args'] ) && is_array( $directory['args'] ) ) {
300-
$url = add_query_arg( $directory['args'], $url );
293+
if ( ! empty( $directory[ 'args' ] ) && is_array( $directory[ 'args' ] ) ) {
294+
$url = add_query_arg( $directory[ 'args' ], $url );
301295
}
302296

303297
$url = apply_filters( 'siteorigin_panels_layouts_directory_url', $url );
@@ -315,13 +309,42 @@ public function action_get_prebuilt_layouts() {
315309
$item['id'] = esc_html( $item['slug'] );
316310
$item['type'] = esc_html( $type );
317311

312+
// Always process category and niche classes for filtering.
313+
$item['access'] = ! empty( $item['access'] ) ? esc_html( $item['access'] ) : '';
314+
315+
// Convert category and niche names to CSS class format.
316+
if ( ! empty( $item['category'] ) ) {
317+
$item['category'] = 'so-' . sanitize_title( $item['category'] );
318+
}
319+
320+
if ( ! empty( $item['niches'] ) ) {
321+
$niche_names = json_decode( $item['niches'] );
322+
if ( is_array( $niche_names ) ) {
323+
$formatted_niches = array_map( function( $niche ) {
324+
return 'so-' . sanitize_title( $niche );
325+
}, $niche_names );
326+
$item['niches'] = ' ' . implode( ' ', $formatted_niches );
327+
}
328+
}
329+
330+
// Set the CSS class to be the category + niches.
331+
$item['class'] = trim( $item['category'] . ( ! empty( $item['niches'] ) ? $item['niches'] : '' ) );
332+
318333
if ( empty( $item['screenshot'] ) && ! empty( $item['preview'] ) ) {
319334
$preview_url = add_query_arg( 'screenshot', 'true', $item[ 'preview' ] );
320335
$item['screenshot'] = esc_url( 'https://s.wordpress.com/mshots/v1/' . urlencode( $preview_url ) . '?w=700' );
321336
}
322337

323338
$return['items'][] = $item;
324339
}
340+
341+
if ( ! empty( $results['niches'] ) ) {
342+
// Convert the categories and niches to the expected format for filtering.
343+
// The layout-viewer returns them as slug => name, but we need to format them.
344+
// so the filter buttons work with the CSS classes
345+
$return['niches'] = $this->format_filter_terms( $results['niches'] );
346+
$return['categories'] = $this->format_filter_terms( $results['categories'] );
347+
}
325348
}
326349

327350
$return['max_num_pages'] = $results['max_num_pages'];
@@ -332,6 +355,7 @@ public function action_get_prebuilt_layouts() {
332355
}
333356
}
334357
}
358+
$no_search_title = true;
335359
} elseif ( strpos( $type, 'clone_' ) !== false ) {
336360
$post_type = str_replace( 'clone_', '', $type );
337361
$post_types_editable_by_user = SiteOrigin_Panels_Admin_Layouts::single()->post_types();
@@ -389,15 +413,50 @@ public function action_get_prebuilt_layouts() {
389413
}
390414

391415
// Add the search part to the title
392-
if ( ! empty( $search ) ) {
416+
if ( ! empty( $search ) && empty( $no_search_title ) ) {
393417
$return['title'] .= __( ' - Results For:', 'siteorigin-panels' ) . ' <em>' . esc_html( $search ) . '</em>';
394418
}
395419

396-
echo wp_json_encode( apply_filters( 'siteorigin_panels_layouts_result', $return, $type ) );
420+
$return = apply_filters( 'siteorigin_panels_layouts_result', $return, $type );
421+
422+
echo wp_json_encode( $return );
397423

398424
wp_die();
399425
}
400426

427+
/**
428+
* Escapes the keys and values of an array using the `esc_html` function.
429+
*
430+
* @param array $results The array to escape.
431+
* @return array The escaped array.
432+
*/
433+
private function escape_results( $results = array() ) {
434+
$escaped_values = array();
435+
foreach ( $results as $key => $value ) {
436+
$escaped_key = esc_html( $key );
437+
$escaped_value = esc_html( $value );
438+
$escaped_values[ $escaped_key ] = $escaped_value;
439+
}
440+
return $escaped_values;
441+
}
442+
443+
/**
444+
* Format filter terms for category/niche filtering.
445+
* Converts from layout-viewer format (slug => name) to filtering format.
446+
*
447+
* @param array $terms The terms array from layout-viewer API.
448+
* @return array Formatted terms for filtering.
449+
*/
450+
private function format_filter_terms( $terms = array() ) {
451+
$formatted_terms = array();
452+
foreach ( $terms as $slug => $name ) {
453+
// The layout-viewer API returns slug => name where slug already has 'so-' prefix.
454+
// Use the slug as-is since it already matches the CSS classes on layout items.
455+
$formatted_terms[ $slug ] = esc_html( $name );
456+
}
457+
return $formatted_terms;
458+
}
459+
401460
private function delete_file( $file ) {
402461
if ( ! empty( $file ) && file_exists( $file ) ) {
403462
@unlink( $file );
@@ -504,13 +563,17 @@ public function action_get_prebuilt_layout() {
504563

505564
$response = wp_remote_get( $url );
506565

507-
if ( $response['response']['code'] == 200 ) {
508-
// For now, we'll just pretend to load this
566+
if ( is_wp_error( $response ) ) {
567+
wp_send_json_error( array(
568+
'error' => true,
569+
'message' => 'WordPress error: ' . $response->get_error_message(),
570+
) );
571+
} elseif ( $response['response']['code'] == 200 ) {
509572
$panels_data = json_decode( $response['body'], true );
510573
} else {
511574
wp_send_json_error( array(
512575
'error' => true,
513-
'message' => __( 'There was a problem fetching the layout. Please try again later.', 'siteorigin-panels' ),
576+
'message' => 'HTTP Error ' . $response['response']['code'] . ': There was a problem fetching the layout. Please try again later.',
514577
) );
515578
}
516579
}
@@ -616,7 +679,7 @@ public function action_export_layout() {
616679
header( 'content-type: application/json' );
617680
header( "Content-Disposition: attachment; filename=$filename.json" );
618681

619-
echo wp_json_encode( $export_data );
682+
echo wp_json_encode( $decoded_export_data );
620683

621684
wp_die();
622685
}

0 commit comments

Comments
 (0)