PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / assets / php / template-catalog-screen.php

template-catalog-screen.php in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at assets/php/template-catalog-screen.php

110 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /** Searchable native template library. Server filtering remains usable without JS. */
3 defined('ABSPATH') || exit;
4 add_action('admin_enqueue_scripts',function($hook){if($hook!=='blockenberg_page_blockenberg-templates')return;$base=dirname(__DIR__,2);$url=plugins_url('',$base.'/blockenberg.php');foreach(array('css','js') as $ext){$f='/assets/'.$ext.'/template-catalog.'.$ext;if($ext==='css')wp_enqueue_style('bkbg-template-catalog',$url.$f,array(),filemtime($base.$f));else wp_enqueue_script('bkbg-template-catalog',$url.$f,array(),filemtime($base.$f),true);}});
5
6 /** Preview access is independent of import entitlement; never use a source Elementor URL. */
7 function bkbg_catalog_preview_url($entry) {
8 $key = $entry['key'] ?? '';
9 if (!is_string($key) || !preg_match('/^[a-z0-9][a-z0-9-]{0,159}$/D', $key)) return '';
10 $public = 'https://templates.blockenberg.com/template-library/previews/20261009-v2/' . rawurlencode($key) . '/';
11 $preview = $entry['previewUrl'] ?? '';
12 // Release IDs match the publisher: 1–80 ASCII letters/digits/underscores/hyphens,
13 // starting with a letter/digit. Exact URL matching rejects traversal and extra parameters.
14 $pattern = '~\Ahttps://templates\.blockenberg\.com/template-library/previews/[A-Za-z0-9][A-Za-z0-9_-]{0,79}/' . preg_quote($key, '~') . '/\z~';
15 // Older cached catalogs may still name King previews. Use the native versioned fallback.
16 return is_string($preview) && preg_match($pattern, $preview) ? $preview : $public;
17 }
18
19 /** Search titles first, then tags, with the same word/key behavior as King Addons. */
20 function bkbg_catalog_filter_entries($entries, $search, $category, $collection, $plan, $chosen) {
21 $normalize = static function ($value) {
22 return trim(preg_replace('/\s+/u', ' ', (string) $value));
23 };
24 $search = $normalize($search);
25 $terms = $search === '' ? array() : explode(' ', $search);
26 $titles = $tag_matches = array();
27 foreach ($entries as $entry) {
28 if ($category !== '' && $category !== strtolower(trim($entry['category'] ?? ''))) continue;
29 if ($collection !== '' && $collection !== (string) ($entry['collection'] ?? '')) continue;
30 if ($plan !== '' && $plan !== ($entry['plan'] ?? '')) continue;
31 $tags = array_map(static function ($tag) { return strtolower(trim($tag)); }, $entry['tags'] ?? array());
32 if ($chosen && array_diff($chosen, $tags)) continue;
33 if ($search !== '' && strcasecmp($entry['key'], $search) === 0) return array($entry);
34 if (!$terms) { $titles[] = $entry; continue; }
35 $title = $normalize($entry['title'] ?? '');
36 $title_match = $tag_match = false;
37 foreach ($terms as $term) {
38 if (stripos($title, $term) !== false) $title_match = true;
39 foreach ($tags as $tag) if (stripos($tag, $term) !== false) { $tag_match = true; break; }
40 }
41 if ($title_match) $titles[] = $entry;
42 elseif ($tag_match) $tag_matches[] = $entry;
43 }
44 return array_merge($titles, $tag_matches);
45 }
46
47 function bkbg_template_catalog_screen(){
48 if(!current_user_can('manage_options'))wp_die('Insufficient permissions.');
49 $manifest=bkbg_template_manifest();$names=bkbg_library_catalog()['collections'];$categories=array();$collections=array();$tags=array();$entries=array();
50 foreach($manifest as $key=>$entry){$entry['key']=$key;$cat=strtolower(trim($entry['category']??''));if($cat)$categories[$cat]=($categories[$cat]??0)+1;$coll=(string)($entry['collection']??'');if($coll)$collections[$coll][]=$entry;foreach($entry['tags']??array() as $tag){$tag=strtolower(trim($tag));if($tag)$tags[$tag]=($tags[$tag]??0)+1;}$entries[]=$entry;}
51 ksort($categories);ksort($tags);uksort($collections,function($a,$b)use($names){return strnatcasecmp($names[$a]??$a,$names[$b]??$b);});
52 $read=static function($key){return isset($_GET[$key])&&is_string($_GET[$key])?sanitize_text_field(wp_unslash($_GET[$key])):'';};
53 $search=$read('s');$category=strtolower($read('category'));$collection=$read('collection');$plan=$read('plan');$view=$read('view')==='collections'?'collections':'templates';$chosen=array();foreach((array)($_GET['tags']??array()) as $tag)if(is_string($tag))$chosen[]=strtolower(sanitize_text_field(wp_unslash($tag)));
54 $filtered=bkbg_catalog_filter_entries($entries,$search,$category,$collection,$plan,$chosen);
55 $groups=array_filter($collections,function($rows,$id)use($view,$search,$names){return $view!=='collections'||$search===''||stripos(($names[$id]??'').' '.implode(' ',array_column($rows,'title')),$search)!==false;},ARRAY_FILTER_USE_BOTH);
56 $total=$view==='collections'?count($groups):count($filtered);$per=$view==='collections'?8:24;$pages=max(1,(int)ceil($total/$per));$current=min($pages,max(1,(int)$read('paged')));$visible=$view==='collections'?array_slice($groups,($current-1)*$per,$per,true):array_slice($filtered,($current-1)*$per,$per);
57 $base=admin_url('admin.php');$home=add_query_arg('page','blockenberg-templates',$base);$args=array('page'=>'blockenberg-templates','view'=>$view,'s'=>$search,'category'=>$category,'collection'=>$collection,'plan'=>$plan,'tags'=>$chosen);
58 ?>
59 <div class="wrap bkbg-template-catalog" id="bkbg-template-catalog" data-import-url="<?php echo esc_url(admin_url('admin-ajax.php'));?>">
60 <header class="bkbg-catalog-header">
61 <div class="bkbg-catalog-brand"><span class="bkbg-catalog-mark" aria-hidden="true"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><rect x="2" y="2" width="8" height="8" rx="2"/><rect x="14" y="2" width="8" height="8" rx="2"/><rect x="2" y="14" width="8" height="8" rx="2"/><rect x="14" y="14" width="8" height="8" rx="2"/></svg></span><span class="bkbg-catalog-eyebrow">Blockenberg</span></div>
62 <div class="bkbg-catalog-intro"><div><h1>Template Library</h1></div>
63 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php'));?>" class="bkbg-catalog-refresh"><input type="hidden" name="action" value="bkbg_refresh_template_library"><?php wp_nonce_field('bkbg_refresh_template_library');?><button type="submit" class="button"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 7v5h-5M4 17v-5h5"/><path d="M6.1 7a7 7 0 0 1 11.6-1L20 9M4 15l2.3 3A7 7 0 0 0 17.9 17"/></svg>Refresh library</button></form>
64 </div>
65 <nav class="bkbg-catalog-tabs" aria-label="Library views"><?php foreach(array('templates'=>count($entries),'collections'=>count($collections)) as $v=>$n):?><a class="<?php echo $view===$v?'is-active':'';?>" href="<?php echo esc_url(add_query_arg(array('view'=>$v),$home));?>" <?php if($view===$v)echo 'aria-current="page"';?>><?php echo esc_html(ucfirst($v));?><span><?php echo (int)$n;?></span></a><?php endforeach;?></nav>
66 </header>
67 <form method="get" action="<?php echo esc_url($base);?>" id="bkbg-catalog-filters"><input type="hidden" name="page" value="blockenberg-templates"><input type="hidden" name="view" value="<?php echo esc_attr($view);?>">
68 <div class="bkbg-catalog-layout">
69 <?php if($view==='templates'):?><aside class="bkbg-catalog-sidebar" aria-label="Template filters"><div class="bkbg-catalog-filter-heading"><strong>Filters</strong></div>
70 <label for="bkbg-catalog-category">Category</label><select name="category" id="bkbg-catalog-category"><option value="">All categories (<?php echo count($entries);?>)</option><?php foreach($categories as $v=>$n):?><option value="<?php echo esc_attr($v);?>" <?php selected($category,$v);?>><?php echo esc_html(ucwords($v).' ('.$n.')');?></option><?php endforeach;?></select>
71 <label for="bkbg-catalog-collection">Collection</label><select name="collection" id="bkbg-catalog-collection"><option value="">All collections (<?php echo count($collections);?>)</option><?php foreach($collections as $v=>$rows):?><option value="<?php echo esc_attr($v);?>" <?php selected($collection,(string)$v);?>><?php echo esc_html(($names[$v]??'Collection '.$v).' ('.count($rows).')');?></option><?php endforeach;?></select>
72 <fieldset class="bkbg-catalog-access"><legend>Access</legend><div class="bkbg-catalog-access-options"><?php foreach(array(''=>'All templates','free'=>'Free','premium'=>'Pro') as $v=>$label):?><label class="bkbg-catalog-choice"><input type="radio" name="plan" value="<?php echo esc_attr($v);?>" <?php checked($plan,$v);?>><?php echo esc_html($label);?></label><?php endforeach;?></div></fieldset>
73 <details class="bkbg-catalog-tags" <?php if($chosen)echo 'open';?>><summary>Tags <span><?php echo count($tags);?></span></summary><div class="bkbg-catalog-tag-list"><?php foreach($tags as $v=>$n):?><label class="bkbg-catalog-choice"><input type="checkbox" name="tags[]" value="<?php echo esc_attr($v);?>" <?php checked(in_array($v,$chosen,true));?>><span><?php echo esc_html(ucfirst($v));?></span><small><?php echo (int)$n;?></small></label><?php endforeach;?></div></details>
74 <button class="button bkbg-catalog-apply" type="submit">Apply filters</button>
75 <div class="bkbg-catalog-filter-footer"><a href="<?php echo esc_url($home);?>" class="bkbg-catalog-reset"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10a9 9 0 1 1 2.6 8.4M3 4v6h6"/></svg><span>Reset filters and search</span></a></div>
76 </aside><?php endif;?>
77 <section class="bkbg-catalog-content"><div class="bkbg-catalog-search"><label for="bkbg-catalog-search" class="screen-reader-text">Search <?php echo esc_html($view);?></label><svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><circle cx="10" cy="10" r="6.5"/><path d="m15 15 5 5"/></svg><input id="bkbg-catalog-search" type="search" name="s" value="<?php echo esc_attr($search);?>" placeholder="Search <?php echo esc_attr($view);?>…"><button class="button" type="submit">Search</button></div>
78 <div id="bkbg-catalog-results" aria-live="polite" aria-atomic="false"><div class="bkbg-catalog-result-heading"><p><?php echo esc_html(number_format_i18n($total).' '.$view);?><?php if($collection&&$view==='templates')echo ' · '.esc_html($names[$collection]??'Collection '.$collection);?></p><span>Page <?php echo (int)$current;?> of <?php echo (int)$pages;?></span></div>
79 <?php if($view==='collections'):?><div class="bkbg-catalog-collections"><?php foreach($visible as $id=>$rows):$link=add_query_arg(array('collection'=>$id),$home);?><article class="bkbg-catalog-collection"><div class="bkbg-catalog-collection-heading"><div><h2><?php echo esc_html($names[$id]??'Collection '.$id);?></h2><p><?php echo count($rows);?> matching page designs</p></div><a class="button" href="<?php echo esc_url($link);?>">View collection →</a></div><div class="bkbg-catalog-collection-previews"><?php foreach(array_slice($rows,0,5) as $e):?><a href="<?php echo esc_url($link);?>" aria-label="<?php echo esc_attr($e['title']);?>"><img src="<?php echo esc_url($e['thumbnail']);?>" alt="<?php echo esc_attr($e['title']);?>" loading="lazy" decoding="async"><span><?php echo esc_html($e['title']);?></span></a><?php endforeach;?></div></article><?php endforeach;?></div>
80 <?php else:?><div class="bkbg-catalog-grid"><?php foreach($visible as $index=>$e):$ready=($e['status']??'')==='ready';$locked=($e['plan']??'')!=='free'&&!bkbg_library_pro_enabled();$preview=bkbg_catalog_preview_url($e);?><article class="bkbg-catalog-card"><div class="bkbg-catalog-image"><img src="<?php echo esc_url($e['thumbnail']??'');?>" alt="<?php echo esc_attr($e['title']);?>" loading="lazy" decoding="async" width="560" height="700"><span class="bkbg-catalog-plan <?php echo ($e['plan']??'')==='free'?'is-free':'';?>"><?php echo ($e['plan']??'')==='free'?'FREE':'PRO';?></span><a class="bkbg-catalog-preview-overlay" href="<?php echo esc_url($preview);?>" data-bkbg-preview data-preview-key="<?php echo esc_attr($e['key']);?>" data-preview-title="<?php echo esc_attr($e['title']);?>" aria-haspopup="dialog" aria-controls="bkbg-template-preview" aria-label="<?php echo esc_attr('Preview '.$e['title']);?>">Preview template</a></div><div class="bkbg-catalog-card-body"><span class="bkbg-catalog-category"><?php echo esc_html(ucfirst($e['category']??''));?></span><h2><?php echo esc_html($e['title']);?></h2><div class="bkbg-catalog-actions"><a href="<?php echo esc_url($preview);?>" data-bkbg-preview data-preview-key="<?php echo esc_attr($e['key']);?>" data-preview-title="<?php echo esc_attr($e['title']);?>" aria-haspopup="dialog" aria-controls="bkbg-template-preview" aria-label="<?php echo esc_attr('Preview '.$e['title']);?>">Preview</a><?php if($locked):?><a class="button button-primary" href="<?php echo esc_url(apply_filters('bkbg_templates_upgrade_url','https://blockenberg.com/'));?>" target="_blank" rel="noopener">Unlock Pro ↗</a><?php else:?><button type="submit" form="<?php echo esc_attr('bkbg-catalog-import-'.$index);?>" class="button button-primary" <?php disabled(!$ready);?> aria-label="<?php echo esc_attr('Import '.$e['title']);?>">Import →</button><?php endif;?></div><?php if(!$ready):?><p><?php echo esc_html(implode(' ',$e['diagnostics']??array('Unavailable')));?></p><?php endif;?></div></article><?php endforeach;?></div><?php endif;?>
81 <?php if(!$total):?><div class="bkbg-catalog-empty"><h2>No matches yet</h2><p>Try another search or clear your filters.</p><a href="<?php echo esc_url($home);?>">Reset filters</a></div><?php endif;?>
82 <?php if($pages>1):?><nav class="bkbg-catalog-pagination" aria-label="Library pages"><?php echo wp_kses_post(paginate_links(array('base'=>str_replace('999999999','%#%',add_query_arg($args+array('paged'=>999999999),$base)),'format'=>'','current'=>$current,'total'=>$pages,'mid_size'=>1,'end_size'=>1)));?></nav><?php endif;?>
83 </div>
84 </section>
85 </div>
86 </form>
87 <div id="bkbg-catalog-import-forms" hidden><?php if($view==='templates')foreach($visible as $index=>$e):?><form id="<?php echo esc_attr('bkbg-catalog-import-'.$index);?>" method="post" action="<?php echo esc_url(admin_url('admin-post.php'));?>"><input type="hidden" name="action" value="bkbg_import_template"><input type="hidden" name="key" value="<?php echo esc_attr($e['key']);?>"><?php wp_nonce_field('bkbg_import_template');?></form><?php endforeach;?></div>
88 <dialog id="bkbg-template-preview" class="bkbg-preview" aria-modal="true" aria-labelledby="bkbg-preview-title" aria-describedby="bkbg-preview-help">
89 <header class="bkbg-preview-toolbar">
90 <h2 id="bkbg-preview-title">Template preview</h2>
91 <div class="bkbg-preview-devices" role="group" aria-label="Preview device">
92 <button type="button" data-preview-device="desktop" aria-pressed="true" aria-label="Desktop full width" title="Desktop · Full width"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="13" rx="2"/><path d="M8 21h8M12 16v5"/></svg></button>
93 <button type="button" data-preview-device="tablet" aria-pressed="false" aria-label="Tablet 768px" title="Tablet · 768px"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2"/><path d="M11 18h2"/></svg></button>
94 <button type="button" data-preview-device="mobile" aria-pressed="false" aria-label="Mobile 375px" title="Mobile · 375px"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><rect x="7" y="2" width="10" height="20" rx="2"/><path d="M11 18h2"/></svg></button>
95 </div>
96 <span class="bkbg-preview-size" data-preview-size aria-hidden="true">Full width</span>
97 <a class="bkbg-preview-open-page" data-preview-fallback href="#" target="_blank" rel="noopener noreferrer" aria-label="Open preview in a new tab" title="Open preview in a new tab"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3h7v7M21 3l-9 9M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5"/></svg></a>
98 <button type="button" class="bkbg-preview-close" data-preview-close aria-label="Close template preview"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button>
99 </header>
100 <p class="bkbg-preview-status" role="status" aria-live="polite"></p>
101 <div class="bkbg-preview-stage" data-preview-stage>
102 <div class="bkbg-preview-loading" aria-hidden="true"><span></span>Loading preview</div>
103 <iframe class="bkbg-preview-frame" title="Native Blockenberg template preview" src="about:blank" width="100%" sandbox="allow-scripts allow-same-origin allow-forms" referrerpolicy="strict-origin-when-cross-origin"></iframe>
104 </div>
105 <p id="bkbg-preview-help" class="screen-reader-text">Scroll inside the preview. Press Escape or Close to return to the library.</p>
106 </dialog>
107 </div>
108 <?php
109 }
110