@@ -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