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.php

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

210 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /** Editable native template catalogue. Elementor is not required. */
3 defined('ABSPATH') || exit;
4 require_once __DIR__.'/template-library-client.php';
5 function bkbg_template_manifest(){return bkbg_library_catalog()['templates'];}
6 function bkbg_template_keys(){return array_keys(array_filter(bkbg_template_manifest(),function($e){return ($e['status']??'')==='ready';}));}
7 function bkbg_template_dir(){return dirname(__DIR__,2).'/templates/';}
8 function bkbg_template_url(){return plugins_url('templates/',dirname(__DIR__,2).'/blockenberg.php');}
9 function bkbg_template_data($key,$authoring_preview=false){return bkbg_library_template_bundle($key,$authoring_preview);}
10 function bkbg_template_resolve($value){
11 if(is_string($value))return str_replace('{{assets}}/',bkbg_library_assets_url(),$value);
12 if(is_array($value))foreach($value as &$v)$v=bkbg_template_resolve($v);
13 return $value;
14 }
15 function bkbg_template_serialize($node){
16 $name=$node['blockName'];$attrs=bkbg_template_resolve($node['attrs']);
17 $json=wp_json_encode($attrs,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
18 $json=str_replace(array('--','<','>','&'),array('\\u002d\\u002d','\\u003c','\\u003e','\\u0026'),$json);
19 $short=str_replace('core/','',$name);
20 if(empty($node['innerBlocks']))return '<!-- wp:'.$short.' '.$json.' /-->';
21 $content='';foreach($node['innerBlocks'] as $child)$content.=bkbg_template_serialize($child);
22 return '<!-- wp:'.$short.' '.$json.' -->'.$content.'<!-- /wp:'.$short.' -->';
23 }
24 function bkbg_template_import($key,$progress=null){
25 $notify=function($percent,$message)use($progress){if(is_callable($progress))$progress($percent,$message);};
26 $notify(5,__('Downloading and verifying template…','blockenberg'));
27 $data=bkbg_template_data($key);if(is_wp_error($data))return $data;if(!$data||empty($data['blocks'])||!is_array($data['blocks']))return new WP_Error('missing_template','Template is not available.');
28 $valid=bkbg_template_validate_nodes($data['blocks']);if(is_wp_error($valid))return $valid;
29 $notify(20,__('Creating your draft…','blockenberg'));
30 $content='';foreach($data['blocks'] as $node)$content.=bkbg_template_serialize($node);
31 $id=wp_insert_post(wp_slash(array('post_type'=>'page','post_status'=>'draft','post_title'=>$data['title'].' — Blockenberg','post_name'=>wp_unique_post_slug('bkbg-'.$key,0,'publish','page',0),'post_content'=>$content)),true);
32 if(is_wp_error($id))return $id;
33 $total=bkbg_template_media_count($data);$done=0;
34 $notify(25,__('Preparing images…','blockenberg'));
35 $data=bkbg_template_prepare_media($data,$id,function()use(&$done,$total,$notify){$done++;$notify(25+(int)floor(65*$done/max(1,$total)),sprintf(__('Preparing images: %1$d of %2$d','blockenberg'),$done,$total));});
36 $notify(95,__('Saving editable blocks…','blockenberg'));$content='';foreach($data['blocks'] as $node)$content.=bkbg_template_serialize($node);
37 $updated=wp_update_post(wp_slash(array('ID'=>$id,'post_content'=>$content)),true);if(is_wp_error($updated))return $updated;
38 update_post_meta($id,'_bkbg_template_key',$key);update_post_meta($id,'_bkbg_template_canvas','1');
39 $notify(100,__('Your draft is ready.','blockenberg'));
40 return $id;
41 }
42 function bkbg_template_classes($a){
43 $class=$a['classes']??'';return implode(' ',array_map('sanitize_html_class',preg_split('/\s+/',trim($class))));
44 }
45 function bkbg_template_image($image){
46 $out='<img';foreach(array('src','alt','width','height','class','loading','decoding','fetchpriority','srcset','sizes') as $k){
47 if($k==='alt'){$out.=' alt="'.esc_attr($image['alt']??'').'"';continue;}
48 if(!isset($image[$k])||$image[$k]==='')continue;
49 $v=$k==='src'?esc_url($image[$k]):esc_attr($image[$k]);$out.=' '.$k.'="'.$v.'"';
50 }return $out.' />';
51 }
52 function bkbg_template_override_css($a){
53 $id='bkbg-tpl-element-'.sanitize_html_class($a['uid']??'');$selector='.'. $id.' > .bkbg-tpl-widget-container';$out='';
54 foreach(array(''=>'','Tablet'=>'@media(max-width:1024px)','Mobile'=>'@media(max-width:767px)') as $suffix=>$media){
55 $props=array();foreach(array('fontSize'=>'font-size','lineHeight'=>'line-height','letterSpacing'=>'letter-spacing','textColor'=>'color','textAlign'=>'text-align','fontWeight'=>'font-weight') as $key=>$css){
56 $value=$a[$key.$suffix]??'';if($value==='')continue;
57 if(in_array($key,array('fontSize','letterSpacing'),true))$value=(float)$value.'px';
58 elseif($key==='lineHeight')$value=(float)$value;
59 elseif($key==='fontWeight')$value=(int)$value;
60 elseif($key==='textAlign'&&!in_array($value,array('left','center','right','justify'),true))continue;
61 elseif($key==='textColor'&&!preg_match('/^(#[a-f0-9]{3,8}|rgba?\([0-9.,%\s]+\)|[a-z]+)$/i',$value))continue;
62 $props[]=$css.':'.$value.'!important';
63 }
64 if($props){$rule=$selector.','.$selector.' .bkbg-tpl-heading-title,'.$selector.' .bkbg-catalog-styled-text-builder-items,'.$selector.' *{'.implode(';',$props).'}';$out.=$media?$media.'{'.$rule.'}':$rule;}
65 }
66 if(!empty($a['imageWidth']))$out.=$selector.' img{width:'.max(1,min(3000,(float)$a['imageWidth'])).'px!important}';
67 return $out?'<style>'.$out.'</style>':'';
68 }
69 function bkbg_template_render($a,$content,$block){
70 $name=$block->name;$classes=bkbg_template_classes($a);$uid=sanitize_html_class($a['uid']??'');
71 if($name==='blockenberg/template-page'){
72 $key=$a['templateKey']??'';if(!preg_match('/^[a-z0-9][a-z0-9-]{0,159}$/D',$key))return '';
73 return '<main '.get_block_wrapper_attributes(array('class'=>'bkbg-template-page','data-bkbg-template'=>$key)).'><div class="'.esc_attr($a['sourceClasses']??'').'">'.$content.'</div></main>';
74 }
75 if($name==='blockenberg/template-layout'){
76 $tag=($a['tag']??'')==='section'?'section':'div';$attrs=get_block_wrapper_attributes(array('class'=>$classes,'data-bkbg-id'=>$uid,'data-bkbg-type'=>$a['kind']??'','style'=>safecss_filter_attr($a['inlineStyle']??'')));
77 $overlay=!empty($a['overlay'])?'<div class="bkbg-tpl-background-overlay"></div>':'';
78 return '<'.$tag.' '.$attrs.'>'.(($a['kind']??'')==='column'?'':bkbg_template_background($a).$overlay).'<div class="'.esc_attr($a['boxClasses']??'').'">'.(($a['kind']??'')==='column'?bkbg_template_background($a).$overlay:'').$content.'</div>'.bkbg_template_shapes($a).'</'.$tag.'>';
79 }
80 $body='';
81 if($name==='blockenberg/template-text')$body=wp_kses_post($a['content']??'');
82 elseif($name==='blockenberg/template-heading'){
83 $tag=in_array($a['headingTag']??'',array('h1','h2','h3','h4','h5','h6','p','div'),true)?$a['headingTag']:'h2';
84 $body='<'.$tag.' class="'.esc_attr($a['contentClasses']??'').'">'.wp_kses_post($a['content']??'').'</'.$tag.'>';
85 }elseif($name==='blockenberg/template-image'){
86 $body=bkbg_template_image($a['image']??array());if(!empty($a['link']))$body='<a href="'.esc_url($a['link']).'">'.$body.'</a>';
87 }elseif($name==='blockenberg/template-button'){
88 $target=($a['target']??'')==='_blank'?' target="_blank" rel="noopener noreferrer"':'';
89 $icon=($a['buttonIcon']??'')==='play'?'<span class="bkbg-tpl-button-icon" aria-hidden="true"><svg viewBox="0 0 448 512" width="1em" height="1em" fill="currentColor"><path d="M0 0L448 256L0 512Z"/></svg></span>':'';
90 $body='<div class="bkbg-tpl-button-wrapper"><a class="'.esc_attr($a['buttonClasses']??'').'" href="'.esc_url($a['url']??'#').'"'.$target.'><span class="bkbg-tpl-button-content-wrapper">'.$icon.'<span class="bkbg-tpl-button-text">'.wp_kses_post($a['content']??'').'</span></span></a></div>';
91 }elseif(in_array($name,array('blockenberg/template-styled-text','blockenberg/template-marquee'),true)){
92 $marquee=$name==='blockenberg/template-marquee';$items='';
93 foreach($a['segments']??array() as $segment){
94 $inner=isset($segment['image'])?bkbg_template_image($segment['image']):'<span>'.wp_kses_post($segment['text']??'').'</span>';
95 $items.='<span class="'.esc_attr($segment['classes']??'').'"><span class="bkbg-catalog-'.($marquee?'auto-scrolling-text':'styled-text').'-inner">'.$inner.'</span></span>';
96 }
97 if(!$marquee)$body='<div class="bkbg-catalog-styled-text-builder-items">'.$items.'</div>';
98 else $body='<div class="bkbg-catalog-auto-scrolling-text-items-wrapper"><div class="bkbg-catalog-auto-scrolling-text-items bkbg-catalog-auto-scrolling-text-items-'.$uid.'"><div class="bkbg-catalog-auto-scrolling-text-wrapper bkbg-catalog-auto-scrolling-text-wrapper-'.$uid.' bkbg-template-marquee-track" data-bkbg-marquee="'.max(1,min(200,(float)($a['speed']??60))).'" data-pause="'.(!empty($a['pauseOnHover'])?'1':'0').'">'.$items.'</div></div></div>';
99 }
100 return '<div '.get_block_wrapper_attributes(array('class'=>$classes,'data-bkbg-id'=>$uid)).'><div class="bkbg-tpl-widget-container">'.$body.'</div></div>'.bkbg_template_override_css($a);
101 }
102 add_action('init',function(){
103 $base=dirname(__DIR__,2);$url=plugins_url('', $base.'/blockenberg.php');
104 wp_register_script('bkbg-template-editor-styles',$url.'/assets/js/template-editor-styles.js',array(),filemtime($base.'/assets/js/template-editor-styles.js'),false);
105 wp_register_script('bkbg-template-pilot-schemas',$url.'/assets/js/template-pilot-schemas.js',array(),filemtime($base.'/assets/js/template-pilot-schemas.js'),false);
106 wp_register_script('bkbg-template-pilot-editor',$url.'/assets/js/template-pilot.js',array('wp-blocks','wp-block-editor','wp-element','wp-components','wp-i18n','bkbg-motion-editor','bkbg-template-pilot-schemas','bkbg-inspector-tabs','bkbg-template-editor-styles'),filemtime($base.'/assets/js/template-pilot.js'),false);
107 wp_register_style('bkbg-template-pilot',$url.'/assets/css/template-pilot.css',array(),filemtime($base.'/assets/css/template-pilot.css'));
108 wp_register_script('bkbg-template-pilot-front',$url.'/assets/js/template-pilot-front.js',array(),filemtime($base.'/assets/js/template-pilot-front.js'),true);
109 },5);
110 function bkbg_template_content_keys($blocks){
111 $keys=array();foreach($blocks as $block){
112 if(($block['blockName']??'')==='blockenberg/template-page')$keys[]=$block['attrs']['templateKey']??'';
113 if(!empty($block['innerBlocks']))$keys=array_merge($keys,bkbg_template_content_keys($block['innerBlocks']));
114 }return array_values(array_unique(array_filter($keys)));
115 }
116 add_action('enqueue_block_assets',function(){
117 $post=get_post();if(!$post&&is_admin()&&!empty($_GET['post']))$post=get_post(absint($_GET['post']));
118 if(!$post)return;
119 $visit=function($blocks)use(&$visit){foreach($blocks as $block){
120 if(($block['blockName']??'')==='blockenberg/template-page')bkbg_library_enqueue_template_style($block['attrs']['templateKey']??'', $block['attrs']['templateStyleUrl']??'');
121 if(!empty($block['innerBlocks']))$visit($block['innerBlocks']);
122 }};$visit(parse_blocks($post->post_content));
123 });
124 add_filter('template_include',function($template){
125 if(is_singular('page')&&get_post_meta(get_the_ID(),'_bkbg_template_canvas',true)==='1'&&has_block('blockenberg/template-page'))return __DIR__.'/template-canvas.php';
126 return $template;
127 });
128 add_action('admin_menu',function(){add_submenu_page('blockenberg',__('Template Library','blockenberg'),__('Template Library','blockenberg'),'manage_options','blockenberg-templates','bkbg_template_catalog_screen');},25);
129 require_once __DIR__.'/template-catalog-screen.php';
130 add_action('admin_post_bkbg_import_template',function(){
131 if(!current_user_can('manage_options'))wp_die('Insufficient permissions.');check_admin_referer('bkbg_import_template');
132 $id=bkbg_template_import(sanitize_key(wp_unslash($_POST['key']??'')));if(is_wp_error($id))wp_die(esc_html($id->get_error_message()));
133 wp_safe_redirect(admin_url('post.php?post='.(int)$id.'&action=edit'));exit;
134 });
135
136
137 /** Validate bundled block data before creating a draft. */
138 function bkbg_template_validate_nodes($nodes,$depth=0){
139 if($depth>40)return new WP_Error('template_depth','Template structure is too deeply nested.');
140 foreach($nodes as $node){
141 $name=$node['blockName']??'';
142 if(strpos($name,'blockenberg/template-')!==0||!WP_Block_Type_Registry::get_instance()->is_registered($name))return new WP_Error('template_block','Unknown template block.');
143 if(!is_array($node['attrs']??null)||!is_array($node['innerBlocks']??null))return new WP_Error('template_schema','Invalid template structure.');
144 $nested=bkbg_template_validate_nodes($node['innerBlocks'],$depth+1);if(is_wp_error($nested))return $nested;
145 }return true;
146 }
147 /** Images are imported only when a user imports a template, with source deduplication. */
148 function bkbg_template_media_count($value){
149 if(!is_array($value))return 0;
150 if(isset($value['src'])&&is_string($value['src'])&&$value['src']!==''){
151 $source=$value['src'];$token=strpos($source,'{{assets}}/')===0;$host=strtolower(wp_parse_url($source,PHP_URL_HOST)??'');
152 if(!$token&&!bkbg_library_asset_url_allowed($source)&&$host!=='kingaddons.com'&&!preg_match('/(^|\.)kingaddons\.com$/',$host))return 0;
153 $name=$token?basename(substr($source,11)):basename(wp_parse_url($source,PHP_URL_PATH)??'');$mime=wp_check_filetype($name)['type'];
154 return $mime&&strpos($mime,'image/')===0&&$mime!=='image/svg+xml'?1:0;
155 }
156 $count=0;foreach($value as $child)$count+=bkbg_template_media_count($child);return $count;
157 }
158 function bkbg_template_prepare_media($value,$post_id,$progress=null){
159 if(!is_array($value))return $value;
160 if(isset($value['src'])&&is_string($value['src'])&&$value['src']!==''){
161 $source=$value['src'];$token=strpos($source,'{{assets}}/')===0;
162 $host=strtolower(wp_parse_url($source,PHP_URL_HOST)??'');
163 if(!$token&&!bkbg_library_asset_url_allowed($source)&&$host!=='kingaddons.com'&&!preg_match('/(^|\.)kingaddons\.com$/',$host))return $value;
164 $name=$token?basename(substr($source,11)):basename(wp_parse_url($source,PHP_URL_PATH)??'');
165 $mime=wp_check_filetype($name)['type'];if(!$mime||strpos($mime,'image/')!==0||$mime==='image/svg+xml')return $value;
166 try {
167 $meta=$token?'_bkbg_template_asset':'_bkbg_template_source';$identity=$token?$name:hash('sha256',$source);
168 $ids=get_posts(array('post_type'=>'attachment','post_status'=>'inherit','fields'=>'ids','numberposts'=>1,'meta_key'=>$meta,'meta_value'=>$identity));
169 $id=$ids?(int)$ids[0]:0;
170 if(!$id){
171 if($token)$source=bkbg_library_assets_url().$name;
172 {
173 $response=wp_safe_remote_get($source,array('timeout'=>20,'limit_response_size'=>20*MB_IN_BYTES));
174 if(is_wp_error($response)||wp_remote_retrieve_response_code($response)!==200)return $value;
175 $bytes=wp_remote_retrieve_body($response);
176 if(!$bytes||strpos(strtolower(substr($bytes,0,100)),'<html')!==false)return $value;
177 }
178 $upload=wp_upload_bits('blockenberg-'.substr(hash('sha256',$source),0,16).'-'.$name,null,$bytes);
179 if(!empty($upload['error']))return $value;
180 $actual=wp_check_filetype_and_ext($upload['file'],$name);if(empty($actual['type'])||strpos($actual['type'],'image/')!==0){wp_delete_file($upload['file']);return $value;}
181 $id=wp_insert_attachment(array('post_mime_type'=>$actual['type'],'post_title'=>'Blockenberg — '.$name,'post_status'=>'inherit'),$upload['file'],$post_id,true);
182 if(is_wp_error($id))return $value;
183 require_once ABSPATH.'wp-admin/includes/image.php';
184 wp_update_attachment_metadata($id,wp_generate_attachment_metadata($id,$upload['file']));
185 update_post_meta($id,$meta,$identity);update_post_meta($id,'_wp_attachment_image_alt',$value['alt']??'');
186 }
187 $value['id']=$id;$value['src']=wp_get_attachment_url($id);
188 // Responsive candidates must follow the new local attachment.
189 if(isset($value['srcset'])){$set=wp_get_attachment_image_srcset($id,'full');if($set)$value['srcset']=$set;else unset($value['srcset']);}
190 return $value;
191 } finally {if(is_callable($progress))$progress();}
192 }
193 foreach($value as &$child)$child=bkbg_template_prepare_media($child,$post_id,$progress);
194 return $value;
195 }
196
197 /** Stream actual completed import stages; the normal form remains a no-JS fallback. */
198 add_action('wp_ajax_bkbg_import_template',function(){
199 if(!current_user_can('manage_options'))wp_send_json_error(array('message'=>__('Insufficient permissions.','blockenberg')),403);
200 check_ajax_referer('bkbg_import_template','_wpnonce');
201 $key=sanitize_key(wp_unslash($_POST['key']??''));
202 while(ob_get_level()>0){if(!@ob_end_clean())break;}
203 nocache_headers();header('Content-Type: application/x-ndjson; charset=utf-8');header('X-Accel-Buffering: no');
204 $emit=function($data){echo wp_json_encode($data)."\n";flush();};
205 $id=bkbg_template_import($key,function($percent,$message)use($emit){$emit(array('type'=>'progress','percent'=>$percent,'message'=>$message));});
206 if(is_wp_error($id))$emit(array('type'=>'error','message'=>$id->get_error_message()));
207 else $emit(array('type'=>'complete','postId'=>(int)$id,'editUrl'=>admin_url('post.php?post='.(int)$id.'&action=edit')));
208 exit;
209 });
210