PluginProbe
Microthemer Lite – Visual Editor to Customize CSS / trunk
Microthemer Lite – Visual Editor to Customize CSS vtrunk
trunk 5.0.0.2
microthemer / src / Common.php

Common.php in Microthemer Lite – Visual Editor to Customize CSS trunk, at src/Common.php

539 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Microthemer;
4
5 class Common {
6
7 public static $live_post_content = '';
8 public static $live_template = false;
9
10 public static function get_protocol() {
11 $isSSL = is_ssl(); // (!empty($_SERVER["HTTPS"]) and $_SERVER["HTTPS"] == "on");
12 return 'http' . ($isSSL ? 's' : '') . '://';
13 }
14
15 public static function get_custom_code() {
16 return array(
17 'hand_coded_css' => array (
18 'tab-key' => 'all-browsers',
19 'label' => esc_html__('CSS', 'microthemer'),
20 //'label' => esc_html__('CSS', 'microthemer'),
21 'type' => 'css'
22 ),
23 'js' => array (
24 'tab-key' => 'js',
25 'label' => esc_html__('JS', 'microthemer'),
26 'type' => 'javascript'
27 ),
28 );
29 }
30
31 // add a param to an existing url if it doesn't exist, using the correct joining char
32 public static function append_url_param($url, $param, $val = false){
33
34 // bail if already present
35 if (strpos($url, $param) !== false){
36 return $url;
37 }
38
39 // we do need to add param, so determine joiner
40 $joiner = strpos($url, '?') !== false ? '&': '?';
41
42 // is there param val?
43 $param = $val ? $param.'='.$val : $param;
44
45 // return new url
46 return $url . $joiner . $param;
47
48 }
49
50 // strip a single parameter from an url (adapted from JS function)
51 public static function strip_url_param($url, $param, $withVal = true){
52
53 $param = $withVal ? $param . '(?:=[a-z0-9]+)?' : $param;
54 $pattern = '/(?:&|\?)' . $param . '/';
55 $url = preg_replace($pattern, '', $url);
56
57 // check we don't have an any params that start with & instead of ?
58 if (strpos($url, '&') !== false && strpos($url, '?') === false){
59 preg_replace('/&/', '?', $url, 1); // just replaces the first instance of & with ?
60 }
61
62 return $url;
63 }
64
65 // &preview= and ?preview= cause problems - strip everything after (more heavy handed than above function)
66 public static function strip_preview_params($url){
67 //$url = explode('preview=', $url); // which didn't support regex (for e.g. elementor)
68 $url = preg_split('/(?:elementor-)?preview=/', $url, -1);
69 $url = rtrim($url[0], '?&');
70 return $url;
71 }
72
73 public static function params_to_strip(){
74 return array(
75
76 // wordpress params
77 array(
78 'param' => '_wpnonce',
79 'withVal' => true,
80 ),
81 array(
82 'param' => 'ver',
83 'withVal' => true,
84 ),
85
86 // MT params
87 array(
88 'param' => 'mt_nonlog',
89 'withVal' => false,
90 ),
91 array(
92 'param' => 'mto2_edit_link',
93 'withVal' => true,
94 ),
95
96 // Amender
97 array(
98 'param' => 'dyn_amends',
99 'withVal' => false,
100 ),
101 array(
102 'param' => 'debug_amends',
103 'withVal' => false,
104 ),
105 array(
106 'param' => 'no_amends',
107 'withVal' => false,
108 ),
109 array(
110 'param' => 'time_amends',
111 'withVal' => false,
112 ),
113 array(
114 'param' => 'elementor-preview',
115 'withVal' => true,
116 ),
117 array(
118 'param' => 'brizy-edit-iframe', // brizy
119 'withVal' => false,
120 ),
121 array(
122 'param' => 'preview_id', // brizy
123 'withVal' => true,
124 ),
125 array(
126 'param' => 'preview_nonce', // brizy
127 'withVal' => true,
128 ),
129 array(
130 'param' => 'et_fb', // strip Divi param which causes iframe to break out of parent
131 'withVal' => true,
132 ),
133 array(
134 'param' => 'fl_builder', // strip beaver builder
135 'withVal' => false,
136 ),
137 // oxygen params
138 array(
139 'param' => 'ct_builder',
140 'withVal' => true,
141 'unless' => array('ct_template') // ct_template also requires ct_builder to work
142 ),
143 array(
144 'param' => 'ct_inner',
145 'withVal' => true,
146 ),
147 /* Keep as necessary for showing specific content
148 * array(
149 'param' => 'ct_template',
150 'withVal' => true,
151 ),*/
152 array(
153 'param' => 'oxygen_iframe',
154 'withVal' => true,
155 ),
156
157
158 // MT cache parameter: nomtcache
159 array(
160 'param' => 'nomtcache',
161 'withVal' => true,
162 ),
163
164 // MT logic parameters: test_logic, test_all, get_simple_stylesheets, get_front_data
165 array(
166 'param' => 'test_logic',
167 'withVal' => true,
168 ),
169 array(
170 'param' => 'test_all',
171 'withVal' => true,
172 ),
173 array(
174 'param' => 'get_simple_stylesheets',
175 'withVal' => true,
176 ),
177 array(
178 'param' => 'get_front_data',
179 'withVal' => true,
180 ),
181
182 // elementor doesn't pass a parameter to the frontend it runs on the admin side
183
184 );
185 }
186
187 // we don't strip params that are required when another param is present
188 public static function has_excluded_param($url, $array){
189
190 $unless = !empty($array['unless']) ? $array['unless'] : false;
191 if ($unless){
192 foreach ($unless as $i => $excl){
193 if (strpos($url, $excl) !== false){
194 return true;
195 }
196 }
197 }
198
199 return false;
200 }
201
202 // strip preview= and page builder parameters
203 public static function strip_page_builder_and_other_params($url, $strip_preview = true){
204
205 // strip preview params (regular and elementor)
206 //$url = Common::strip_preview_params($url); // test what happens
207
208 $other_params = Common::params_to_strip();
209
210 foreach ($other_params as $key => $array){
211
212 // we don't strip params that are required when another param is present
213 if (Common::has_excluded_param($url, $array)){
214 continue;
215 }
216
217 $url = Common::strip_url_param($url, $array['param'], $array['withVal']);
218 }
219
220 // strip brizy
221 /*$url = Common::strip_url_param($url, 'brizy-edit-iframe', false);
222
223 // strip Divi param which causes iframe to break out of parent
224 $url = Common::strip_url_param($url, 'et_fb', true); // this has issue with divi builder
225
226 // strip beaver builder - NO, we're currently checking fl_builder for JS logic.
227 $url = Common::strip_url_param($url, 'fl_builder', false);*/
228
229 return $url;
230
231 }
232
233 // we are adding user google fonts on admin side too so they can be shown in UI (todo)
234 public static function add_user_google_fonts($p){
235
236 // use g_url_with_subsets value generated when writing stylesheet
237 $google_url = !empty($p['g_url_with_subsets'])
238 ? $p['g_url_with_subsets']
239
240 // fallback to g_url if user has yet to save settings since g_url_with_subsets was added
241 : (!empty($p['gfont_subset']) ? $p['g_url'].$p['gfont_subset'] : $p['g_url']);
242
243 if (!empty($google_url)){
244 Common::mt_enqueue_or_add(!empty($p['stylesheet_order']), 'microthemer_g_font', $google_url);
245 }
246
247 }
248
249 public static function mt_enqueue_or_add($add, $handle, $url, $in_footer = false, $data_key = false, $data_val = false){
250
251 global $wp_styles;
252
253 // special case for loading CSS after Oxygen
254 if ($add){
255
256 $wp_styles->add($handle, $url);
257 $wp_styles->enqueue(array($handle));
258
259 if ($data_key){
260 $wp_styles->add_data($handle, $data_key, $data_val);
261 }
262
263 // allow CSS to load in footer if O2 is active so MT comes after O2 even when O2 active without O2
264 // Note this didn't work on my local install, but did on a customer who reported issue with Agency Tools
265 // so better to use a more deliberate action hook e.g. wp_footer
266 // Ideally, O2 would enqueue a placeholder stylesheet and replace rather than append to head
267 /*if ( !defined( 'SHOW_CT_BUILDER' ) ) {
268 $wp_styles->do_items($handle);
269 }*/
270
271 // (feels a bit risky, but can add if MT loading before O2 when active by itself causes issue for people)
272 $wp_styles->do_items($handle);
273 }
274
275 else {
276 wp_register_style($handle, $url, false);
277 wp_enqueue_style($handle);
278 }
279
280 }
281
282 // dequeue rougue styles or scripts loading on MT UI page that cause issues for it
283 public static function dequeue_rogue_assets(){
284
285 $conflict_styles = array(
286
287 // admin 2020 plugin assets
288 'uikitcss',
289 'ma_admin_head_css',
290 'ma_admin_editor_css',
291 'ma_admin_menu_css',
292 'ma_admin_mobile_css',
293 'custom_wp_admin_css',
294 'ma_admin_media_css',
295
296 // for UIPress
297 'uip-app',
298 'uip-app-rtl',
299 'uip-icons',
300 'uip-font',
301
302 // TK shortcodes
303 'tk-shortcodes',
304 'tk-shortcodes-fa'
305
306
307 );
308
309 // for UIPress
310 /*if ( class_exists('uipress') ){
311 $conflict_styles = array_merge($conflict_styles, array(
312
313 ));
314 }*/
315
316 foreach ($conflict_styles as $style_handle){
317 wp_dequeue_style($style_handle);
318 }
319 }
320
321 // get the template file WordPress is loading e.g. page, single, single-with-sidebar
322 /*public static function getCurrentTemplateSlug(){
323
324 global $post;
325
326 $slug = get_page_template_slug();
327
328 // get_page_template_slug sets an empty string for single and page, normalise if so
329 if ($slug === '' && !empty($post->post_type)){
330 return $post->post_type === 'post' && is_single()
331 ? 'single'
332 : $post->post_type;
333 }
334
335 return $slug;
336 }*/
337
338
339
340 /////////// GUTENBERG BLOCK FUNCTIONS //////////////////
341 ///
342 /// the save_post action hook runs on the frontend surprisingly,
343 /// so we need these functions available to both main classes
344
345 // In order to efficiently check if a particular template is loading in a Block theme,
346 // Microthemer creates a map of templates and the sub-templates they use
347 // This map will be added if it doesn't exist for the current theme, or a preference to update it is set
348 public static function maybeUpdateTemplateCache($micro_root_dir, $themeSlug = null, $force = false){
349
350 // bail if it's not a block theme
351 if (!wp_is_block_theme()){
352 return false;
353 }
354
355 // default to the current theme
356 $themeSlug = $themeSlug ?: get_stylesheet();
357 $templateMapFile = $micro_root_dir . 'mt/cache/themes/'.$themeSlug.'/'.Helper::templateMapName().'.json';
358
359 // bail if the file already exists and if we're not forcing a refresh
360 if (!$force && file_exists($templateMapFile)){
361 return false;
362 }
363
364 // get the default / user-customised templates/parts array from the database
365 $resolvedTemplates = get_block_templates();
366 $resolvedTemplateParts = get_block_templates(array(), 'wp_template_part');
367 $registeredPatterns = \WP_Block_Patterns_Registry::get_instance()->get_all_registered();
368 $block_query = new \WP_Query(array(
369 'post_type' => array('wp_block', 'wp_navigation'),
370 'posts_per_page' => -1
371 ));
372 $allPatterns = array_merge($registeredPatterns, $block_query->posts);
373
374 // loop each template and parse the blocks
375 $map = array();
376
377 // templates
378 foreach ($resolvedTemplates as $template){
379 Common::updateTemplateMap($template, $map, 'wp_template', $themeSlug);
380 }
381
382 // template parts
383 foreach ($resolvedTemplateParts as $templatePart){
384 Common::updateTemplateMap($templatePart, $map, 'wp_template_part', $themeSlug);
385 }
386
387 // template file patterns and DB patterns/navigation stored in the DB
388 foreach ($allPatterns as $pattern){
389
390 $item = (object) $pattern;
391
392 $type = !isset($item->post_type) || $item->post_type === 'wp_block'
393 ? 'wp_pattern'
394 : $item->post_type; // e.g. wp_navigation
395
396 Common::updateTemplateMap($item, $map, $type, $themeSlug);
397 }
398
399 // write to a json file
400 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Template map under uploads/micro-themes; direct FS avoids WP_Filesystem FTP prompts.
401 $write_file = @fopen($templateMapFile, 'w');
402
403 if (!$write_file){
404 return false; // prevent a write error if permissions don't allow this for some reason.
405 }
406
407 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- See fopen note above.
408 fwrite($write_file, wp_json_encode($map));
409 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- See fopen note above.
410 fclose($write_file);
411
412 /*wp_die('<pre>'.print_r([
413 'map' => $map,
414 //'reg_patterns' => $registeredPatterns,
415 //'all_patterns' => $allPatterns,
416 ], true).'</pre>');*/
417
418 }
419
420 public static function updateLiveTemplateData($micro_root_dir){
421
422 Common::$live_post_content = wp_unslash($_POST["live_post_content"]); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- user-authored page HTML for live preview rendering only; kses would corrupt builder markup. Capability gated.
423
424 if (isset($_POST['live_template'])){
425 Common::$live_template = wp_unslash($_POST["live_template"]); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- template slug used for preview lookup, compared against known templates
426 }
427
428
429 if (Helper::$doDebug){
430 Helper::debug('updateLiveTemplateData');
431 }
432
433 // update the live map
434 Common::maybeUpdateTemplateCache($micro_root_dir, null, true);
435 }
436
437 // update a single item in the templateMap
438 /*public static function updateTemplateMapItem($map){
439
440 }*/
441
442 // mark
443 public static function updateTemplateMap($item, &$map, $type = 'wp_template', $themeSlug = ''){
444
445 $isDBPost = isset($item->post_content);
446 $slug = $isDBPost
447 ? $item->ID // user pattern/navigation
448 : (isset($item->slug)
449 ? $item->slug // template
450 : $item->name); // theme pattern
451 $slug = Helper::removeRedundantThemePrefix($slug, $themeSlug);
452 $content = $isDBPost ? $item->post_content : $item->content;
453
454 // use live data in place of DB stored data if we're performing a test of client-side Gutenberg content
455 extract(Helper::extractUrlParams($themeSlug));
456 if (Helper::isLiveContentTest() && $fseType === $type && $postId == $slug){
457 $content = Common::$live_post_content;
458 if (Helper::$doDebug){
459 Helper::debug('Live content used to update the live map ('.$fseType.'): ' . $postId);
460 }
461
462 }
463
464 $store = array(
465 'wp_template_part' => array(),
466 'wp_pattern' => array(),
467 'wp_navigation' => array(),
468 );
469 $entry = array();
470
471 // extract template parts and patterns from the content
472 $blocks = parse_blocks($content);
473 Common::extractTemplatePartsPatterns($blocks, $store, $themeSlug);
474
475 if (!empty($item->area)){
476 $entry['area'] = $item->area;
477 } if (count($store['wp_template_part'])){
478 $entry['wp_template_part'] = $store['wp_template_part'];
479 } if (count($store['wp_pattern'])){
480 $entry['wp_pattern'] = $store['wp_pattern'];
481 } if (count($store['wp_navigation'])){
482 $entry['wp_navigation'] = $store['wp_navigation'];
483 }
484
485 // debug
486 /*$entry['content'] = esc_html($item->content);
487 $entry['blocks'] = $blocks;*/
488
489 $map[$type][$slug] = count($entry) ? $entry : 1;
490
491 }
492
493 public static function extractTemplatePartsPatterns($blocks, &$store, $themeSlug){
494
495 foreach ($blocks as $block){
496
497 $name = $block['blockName'];
498 $ref = isset($block['attrs']['ref']) ? $block['attrs']['ref'] : null;
499
500 // template part
501 if ($name === 'core/template-part'){
502 $key = Helper::removeRedundantThemePrefix($block['attrs']['slug'], $themeSlug);
503 $store['wp_template_part'][$key] = 1;
504 }
505
506 // pattern defined in the theme/patterns directory or a user-defined pattern/navigation
507 elseif ($name === 'core/pattern' || $ref){
508 $storeKey = $name === 'core/navigation'
509 ? 'wp_navigation'
510 : 'wp_pattern';
511 $key = isset($block['attrs']['slug'])
512 ? Helper::removeRedundantThemePrefix($block['attrs']['slug'], $themeSlug)
513 : Helper::maybeMakeNumber($ref);
514
515 $store[$storeKey][$key] = 1;
516 }
517
518 // recurse
519 if (count($block['innerBlocks'])){
520 Common::extractTemplatePartsPatterns($block['innerBlocks'], $store, $themeSlug);
521 }
522 }
523 }
524
525 public static function debugMemory(){
526 echo '<pre>';
527 $vars = get_defined_vars();
528 foreach($vars as $name=>$var)
529 {
530 echo '<strong>' . esc_html($name) . '</strong>: ' . (int) strlen(serialize($var)) . '<br />';
531 }
532 echo '</pre>';
533 //exit();
534 }
535
536
537
538 }
539