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

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

1,719 lines 62.2 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 trait AdminInitTrait {
6
7 var $activation_function_ran;
8 var $all_pages = array();
9 var $user_memory_limit;
10
11 function init(){
12
13 // setup text domain
14 $this->loadTextDomain();
15
16 // get the directory paths
17 include dirname(__FILE__) .'/../get-dir-paths.inc.php';
18
19 // for media queries
20 $this->unq_base = uniqid('mtmq');
21
22 // Setup essential vars
23 // Page slugs include ".php" (e.g. tvr-microthemer.php). Do not use
24 // sanitize_key() — it strips dots and would skip initMicrothemerPage().
25 $raw_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
26 $page = in_array($raw_page, $this->getAllPluginPages(), true) ? $raw_page : false;
27 $this->preferences = get_option($this->preferencesName);
28
29 // setup reference to content class
30 if ($this->hasContentCapability()){
31 $this->contentClass = new Content\AdminContent($this);
32 }
33
34 // Set the app name
35 $this->setAppName();
36
37 // bail if server doesn't meet the minimum requirements
38 if (!$this->checkMinimumRequirements()){
39 return;
40 }
41
42 // hook minimal functionality on all admin pages
43 $this->hookAjax();
44 $this->hookAdminMenu();
45 $this->hookAdminBarShortcut();
46 $this->hookPluginUpdate();
47 $this->hookActivation();
48
49 // Load additional functionality on MT admin pages
50 if (in_array($page, $this->getAllPluginPages()) ) {
51 $this->initMicrothemerPage($page);
52 }
53
54 // Non-MT page
55 // - note MT admin area styling only works for admin level priviledges, without some rejigging
56 else {
57
58 // Pass in the context of loading assets or actually being able to edit the admin area with point and click
59 $context = !empty($this->preferences['admin_asset_editing']) ? 'edit' : 'respond';
60
61 new AssetAuth($context);
62 }
63
64 }
65
66 function initMicrothemerPage($page){
67
68 $this->new_version = (empty($this->preferences['version'])
69 || $this->preferences['version'] != $this->version)
70 || $this->contentRequiresSetup();
71
72 // if it's a new version, run the activation/upgrade function (if not done at activation hook)
73 // this will update the translations in the JS cached HTML
74 // and ensures the pre-update settings are saved in the history table
75 if ($this->new_version){
76 //$this->microthemer_activated_or_updated();
77 add_action('admin_init', array(&$this, 'microthemer_activated_or_updated'));
78 }
79
80 // get lang for non-english exceptions (e.g. showing English property labels too)
81 $this->locale = get_locale();
82
83 $this->dis_text = __('DISABLED', 'microthemer');
84 $this->level_map = array(
85 'section' => __('folder', 'microthemer'),
86 'selector' => __('selector', 'microthemer'),
87 'tab' => __('tab', 'microthemer'),
88 'tab-input' => __('tab', 'microthemer'),
89 'group' => __('group', 'microthemer'),
90 'pgtab' => __('settings', 'microthemer'),
91 'subgroup' => __('settings', 'microthemer'),
92 'property' => __('property', 'microthemer'),
93 'script' => __('Enqueued Script', 'microthemer')
94 );
95
96 // check if integratable plugins are active
97 add_action( 'admin_init', array(&$this, 'check_integrations'));
98
99 // setup vars that depend on WP being fully loaded
100 add_action( 'admin_init', array(&$this, 'setup_wp_dependent_vars'));
101
102 // we don't want the WP admin bar on any Microthemer pages
103 add_filter('show_admin_bar', '__return_false');
104
105 // loading URL for iframe
106 //$this->placeholderURLs = $this->get_placeholder_urls();
107
108 /* this may need work, ocassionally breaks: http://stackoverflow.com/questions/5441784/why-does-ob-startob-gzhandler-break-this-website
109 * $this->show_me = 'zlib.output_compression config: ('
110 . ini_get('zlib.output_compression')
111 . ') gzipping HTTP_ACCEPT_ENCODING: (' . $_SERVER['HTTP_ACCEPT_ENCODING']
112 . ') substr_count: ' . substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');*/
113 // only microthemer needs custom jQuery and gzipping
114
115 // enable gzipping on UI page if defined
116 if ( isset($_GET["page"]) and $_GET["page"] == basename(__FILE__) and $this->preferences["gzip"] == 1) {
117 if (session_id() === null &&
118 !empty($_SERVER['HTTP_ACCEPT_ENCODING']) &&
119 substr_count(isset($_SERVER["HTTP_ACCEPT_ENCODING"]) ? sanitize_text_field(wp_unslash($_SERVER["HTTP_ACCEPT_ENCODING"])) : "", "gzip")){
120 ob_start("ob_gzhandler");
121 } else {
122 ob_start();
123 }
124 }
125
126 // add scripts and styles
127 // Not necessary if this is an ajax call. XDEBUG_PROFILE showed add_js was adding sig time.
128 if ( empty($_GET['action']) or $_GET['action'] != 'mtui'){
129
130 add_action('admin_init', array(&$this, 'actionCookieUpdates') );
131 add_action('admin_init', array(&$this, 'add_no_cache_headers'), 1);
132 add_action('admin_enqueue_scripts', array(&$this, 'add_css'), PHP_INT_MAX);
133 add_action('admin_head', array(&$this, 'add_dyn_inline_css'));
134 add_action('admin_head', array(&$this, 'load_icon_font')); // icon font and layout
135 add_action('admin_enqueue_scripts', array(&$this, 'add_js'), PHP_INT_MAX);
136 //add_action( 'wp_body_open', array(&$this, 'add_svg_sprite') );
137
138 // fix compatibility issues due to a plugin loading scripts or styles on MT interface pages
139 add_action('admin_enqueue_scripts', array('Microthemer\Common', 'dequeue_rogue_assets'), 1000);
140 add_action('wp_enqueue_media', array('Microthemer\Common', 'dequeue_rogue_assets'), 1000);
141
142 } else {
143 //echo 'it is an ajax request';
144 }
145
146 }
147
148 // Stop the plugin if below requirements
149 function checkMinimumRequirements(){
150 return $this->required_version() && $this->check_user_memory_limit() && !defined('TVR_MICROBOTH');
151 }
152
153 // Hook into WordPress ajax action (for saving settings)
154 function hookAjax(){
155 add_action('wp_ajax_mtui', array(&$this, 'microthemer_ajax_actions'));
156
157 //echo '<pre>' . print_r($GLOBALS, 1 ) . '</pre>';
158 }
159
160 // add menu links (all WP admin pages need this)
161 function hookAdminMenu(){
162 add_action("admin_menu", array(&$this, "microthemer_dedicated_menu"));
163 }
164
165 // add shortcut to Microthemer if preference
166 function hookAdminBarShortcut(){
167
168 if ( !empty($this->preferences['admin_bar_shortcut']) ) {
169 add_action( 'admin_bar_menu', array(&$this, 'custom_toolbar_link'), 999999);
170 }
171 }
172
173 // activation hook for setting initial preferences (so e.g. Microthemer link appears in top toolbar)
174 function hookActivation(){
175
176 // just flag that Microthemer has been activated on code than runs before activation redirect
177 // we can't hook into all WordPress actions prior to the admin redirect
178 register_activation_hook( __FILE__, array(&$this, 'flag_microthemer_activation') );
179
180 // check if Microthemer has been activated and run any code that CAN tap into all WP functionality
181 add_action( 'admin_init', array(&$this, 'check_microthemer_activation_flag') );
182 }
183
184 function actionCookieUpdates(){
185
186 if (isset($_GET['page']) && $_GET['page'] === $this->microthemeruipage){
187 if (isset($_COOKIE['microthemer_draft_folder'])){
188 $this->deleteCookie('microthemer_draft_folder');
189 }
190 }
191 }
192
193
194 // user's subscription has expired and they are capped at a version
195 function is_capped_version($prefix = ''){
196 return !empty($this->preferences[$prefix . 'buyer_validated']) and !empty($this->preferences[$prefix . 'subscription']['capped_version']);
197 }
198
199 function supportAdminAssets(){
200 $p = $this->preferences;
201 return !empty($p['admin_asset_loading']) || !empty($p['admin_asset_editing']);
202 }
203
204 function getAllPluginPages(){
205
206 $this->all_pages = array(
207 $this->microthemeruipage,
208 $this->microthemespage,
209 $this->managesinglepage,
210 $this->docspage,
211 $this->fontspage,
212 $this->preferencespage,
213 $this->detachedpreviewpage
214 );
215
216 return $this->all_pages;
217 }
218
219 // @taken from ngg gallery: http://wordpress.org/extend/plugins/nextgen-gallery/
220 function required_version() {
221
222 global $wp_version;
223
224 // if requirements not met
225 if ( !version_compare($wp_version, $this->minimum_wordpress, '>=') ) {
226
227 add_action(
228 'admin_notices',
229 function(){
230 echo '<div id="message" class="error"><p><strong>' .
231 /* translators: 1: application name, 2: minimum WordPress version, 3: application name */
232 sprintf(esc_html__('Sorry, %1$s only runs on WordPress version %2$s or above. Deactivate %3$s to remove this message.', 'microthemer'), esc_html($this->appName), esc_html($this->minimum_wordpress), esc_html($this->appName)) .
233 '</strong></p></div>';
234 }
235 );
236
237 return false;
238 }
239
240 return true;
241 }
242
243 // check the user has a minimal amount of memory
244 function check_user_memory_limit() {
245
246 // get memory limit including unit
247 $subject = ini_get('memory_limit'); // e.g. 256M
248 $pattern = '/([\-0-9]+)/';
249 preg_match($pattern, $subject, $matches);
250 $this->user_memory_limit = $matches[0];
251 $unit = str_replace($matches[0], '', $subject);
252
253 // cautious memory check that will only throw error if memory is given in MB.
254 // Too many variables to safely accommodate all e.g. 0, -1, (int) 268435456, 3GB etc
255 if (($unit == 'M' || $unit == 'MB') && $this->user_memory_limit < 16){
256 // we don't have enough
257 add_action(
258 'admin_notices',
259 function() {
260 echo '<div id="message" class="error"><p><strong>' .
261 /* translators: 1: application name, 2: allocated memory in MB, 3: application name */
262 sprintf( esc_html__( 'Sorry, %1$s has a memory requirement of 16MB or higher to run. Your allocated memory is less than this (%2$sMB). Deactivate %3$s to remove this message. Or increase your memory limit.', 'microthemer' ), esc_html($this->appName), esc_html($this->user_memory_limit), esc_html($this->appName)) .
263 '</strong></p></div>';
264 }
265 );
266
267 return false;
268 }
269
270 return true;
271 }
272
273 // Microthemer dedicated menu
274 function microthemer_dedicated_menu() {
275
276 // for draft mode and preventing two users overwriting each other's edits
277 // get_current_user_id() needs to be here (hooked function)
278 $this->current_user_id = get_current_user_id();
279
280 add_menu_page(
281 /* translators: %s: application name */
282 sprintf(__('%s UI', 'microthemer'), $this->appName),
283 $this->appNameFull,
284 'administrator', $this->microthemeruipage, array(&$this,'microthemer_ui_page')
285 );
286
287 add_submenu_page('options.php',
288 __('Manage Design Packs', 'microthemer'),
289 __('Manage Design Packs', 'microthemer'),
290 'administrator', $this->microthemespage, array(&$this,'manage_micro_themes_page'));
291 add_submenu_page('options.php',
292 __('Manage Single Pack', 'microthemer'),
293 __('Manage Single Pack', 'microthemer'),
294 'administrator', $this->managesinglepage, array(&$this,'manage_single_page'));
295 add_submenu_page('options.php',
296 __('Microthemer Docs', 'microthemer'),
297 __('Documentation', 'microthemer'),
298 'administrator', $this->docspage, array(&$this,'microthemer_docs_page'));
299 add_submenu_page('options.php',
300 __('Google Fonts', 'microthemer'),
301 __('Google Fonts', 'microthemer'),
302 'administrator', $this->fontspage, array(&$this,'microthemer_fonts_page'));
303 add_submenu_page('options.php',
304 __('Detached Preview', 'microthemer'),
305 __('Detached Preview', 'microthemer'),
306 'administrator', $this->detachedpreviewpage, array(&$this,'microthemer_detached_preview_page'));
307 add_submenu_page($this->microthemeruipage,
308 __('Preferences', 'microthemer'),
309 __('Preferences', 'microthemer'),
310 'administrator', $this->preferencespage, array(&$this,'microthemer_preferences_page'));
311 }
312
313 // add a link to the WP Toolbar (this was copied from frontend class - use better method later)
314 function custom_toolbar_link($wp_admin_bar) {
315
316 if (!current_user_can('manage_options')){
317 return false;
318 }
319
320 if (!empty($this->preferences['top_level_shortcut'])){
321 $parent = false;
322 } else {
323 $parent = 'site-name';
324 }
325
326 // root URL to MT UI
327 $href = $this->wp_blog_admin_url . 'admin.php?page=' . $this->microthemeruipage;
328
329 // if admin edit post or page - MT should load that page
330 $front = $this->get_url_from_edit_screen();
331
332 if ($front){
333
334 $href.= '&mt_preview_url=' . rawurlencode($front['url'])
335 . '&mt_item_id=' . rawurlencode($front['postID'])
336 . '&mt_path_label=' . rawurlencode($front['title'])
337 .' &_wpnonce=' . wp_create_nonce( 'mt-preview-nonce' );
338
339 // not sure how to make a post
340 if ($front['post_status'] === 'auto-draft'){
341 $href.= '&auto_save_draft='.$front['postID'];
342 }
343
344 //wp_die('<pre>'.print_r($front, true).'</pre>');
345 }
346
347 $args = array(
348 'id' => 'wp-mcr-shortcut',
349 'title' => $this->appNameFull,
350 'parent' => $parent,
351 'href' => $href,
352 'meta' => array(
353 'class' => 'wp-mcr-shortcut',
354 /* translators: %s: application name */
355 'title' => sprintf(__('Jump to the %s interface', 'microthemer'), $this->appName)
356 )
357 );
358
359 $wp_admin_bar->add_node($args);
360 }
361
362 function get_url_from_edit_screen(){
363
364 global $post;
365
366 $url = false;
367
368 if ($post && function_exists('get_current_screen')) {
369
370 $current_screen = get_current_screen();
371 $post_type = $current_screen->post_type;
372 $isPostOrPage = ($post_type === 'post' || $post_type === 'page');
373 $isEditScreen = $isPostOrPage && isset($_GET['action'])
374 && $_GET['action'] === 'edit'
375 && !empty($_GET['post']);
376 $isAddScreen = $isPostOrPage && $current_screen->action === 'add';
377
378 //wp_die('<pre>'.print_r($post, true).'</pre>');
379
380 // if add new or saved draft use preview URL
381 if ($isAddScreen || $post->post_status !== 'publish'){
382 $url = get_preview_post_link($post->ID);
383 }
384
385 // get link for published post
386 else if ($isEditScreen){
387 $url = get_permalink( intval($_GET['post']) );
388 }
389
390 if ($url){
391 return array(
392 'url' => $url,
393 'post_status' => $post->post_status,
394 'title' => $post->post_title,
395 'postID' => $post->ID
396 );
397 }
398
399 //wp_die('<pre>'.print_r(get_current_screen()->id, true).'</pre>');
400 }
401
402 return false;
403 }
404
405 // When a WP plugin is activated, we can't hook into all WordPress functionality
406 // so the workaround they advise is to flag that activation happened, so when WP
407 // redirects to another admin page, the activation functionality can run on the admin_init hook
408 // https://developer.wordpress.org/reference/functions/register_activation_hook/
409 function flag_microthemer_activation(){
410 add_option('microthemer_activation', '1');
411 }
412
413 function check_microthemer_activation_flag(){
414
415 if ( get_option( 'microthemer_activation' ) == '1' ) {
416
417 // delete the flag
418 delete_option( 'microthemer_activation' );
419
420 // run the activation code
421 $this->microthemer_activated_or_updated();
422 }
423 }
424
425 // ensure preferences are set upon activation
426 function microthemer_activated_or_updated(){
427
428 if (!$this->activation_function_ran){
429
430 $pd_context = 'microthemer_activated_or_updated';
431
432 // we need to check_integrations here, so that the default MQs are set for a builder, if active
433 //$this->check_integrations(); // is causes an error - is_plugin_active is not defined - need to come back to this
434 /*wp_die('check_integrations result on activation: <pre>'.print_r(array(
435 'integrations' => $this->integrations,
436 'mqs' => $this->mq_sets
437 ), true).'</pre>' );*/
438
439 // setup program data arrays
440 // calls getPreferences() which also sets if nothing to get yet
441 // and creates a backup of the settings and preferences if a new version
442 include dirname(__FILE__) .'/../includes/program-data.php';
443
444 // if non-english, we need to write to program-data.js in current language
445 // log success of overwrite
446 // (this didn't work properly on some servers, maybe @fopen suppress would work, but this is safer)
447 $pref_array = array(
448 'inlineJsProgData' => ( strpos($this->locale, 'en_') === false ) //!$this->write_mt_version_specific_js('../js-min')
449 );
450
451 $this->savePreferences($pref_array);
452
453 // Ensure that all new preferences have been set, so the frontend can use new preferences
454 $this->getPreferences();
455
456
457 // todo save all lang strings in DB at this point to save CPU later, start with property-options.inc.php
458
459 // ensure micro-themes dir is created animation-events.js and stock.zip extracted
460 $this->setup_micro_themes_dir(true);
461
462 $this->activation_function_ran = true;
463
464 }
465
466 }
467
468 // plugin update stuff (premium builds only)
469 // The subscription update guard filters live in a separate file that is
470 // excluded from the wp.org build - hosted plugins must not alter core
471 // update behaviour, so those filters must be absent from that package.
472 function hookPluginUpdate(){
473
474 $guardFile = dirname(__FILE__) . '/SubscriptionUpdateGuard.php';
475
476 if (file_exists($guardFile)){
477 include_once $guardFile;
478 new SubscriptionUpdateGuard($this);
479 }
480 }
481
482 function installThemeoverAddon($addon){
483
484 $key = $addon === 'microthemer' ? 'css_addon' : 'content_addon';
485 $pref_array = array(
486 $key => 1
487 );
488
489 if ($addon === 'amender'){
490 $pref_array['pg_focus'] = 'html';
491 }
492
493 $this->savePreferences($pref_array);
494
495 return array(
496 'success' => 1
497 );
498 }
499
500 function microthemer_ajax_actions(){
501
502 if ( !current_user_can('manage_options') ){
503 wp_die( 'Access denied' );
504 }
505
506 // simple ajax operations that can be executed from any page, pointing to ui page
507 if (isset($_GET['mcth_simple_ajax'])) {
508
509 check_ajax_referer( 'mcth_simple_ajax', '_wpnonce' );
510
511 $revision_id = null;
512
513 // workspace preferences
514 if (isset($_POST['tvr_preferences_form'])) {
515 $this->process_preferences_form();
516 wp_die();
517 }
518
519 // if it's an options save request
520 if (isset($_GET['mt_action']) and $_GET['mt_action'] === 'mt_save_interface') {
521 $this->actionSaveInterface();
522 wp_die();
523 }
524
525 // If we're copying the revision from the server to indexedDB
526 if (isset($_GET['copy_revision_locally'])) {
527 $revision_id = !empty($_GET["revision_id"]) ? intval($_GET["revision_id"]) : null;
528 $recent_revisions = !empty($_GET["recent_revisions"]) ? intval($_GET["recent_revisions"]) : null;
529 $this->getRevisionForIndexedDB($revision_id, true, true, $recent_revisions);
530 wp_die();
531 }
532
533
534 // if it's a silent save request for updating ui options (e.g. last viewed selector)
535 if (isset($_GET['mt_action']) and $_GET['mt_action'] == 'mt_silent_save_interface') {
536 // large JSON settings payload (user-authored CSS/selectors); sanitize_text_field would
537 // corrupt it. Structure is validated by apply_save_package; nonce + capability gated above.
538 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
539 $savePackage = isset($_POST['savePackage']) ? $this->deep_unescape($_POST['savePackage'], 1, 1, 1) : array();
540 /*echo 'show_me from ajax save (before): <pre> ';
541 print_r($savePackage);
542 echo '</pre>';
543 return false;*/
544 $this->apply_save_package($savePackage, $this->options);
545 update_option($this->optionsName, $this->options);
546 wp_die();
547 }
548
549 // addon installation
550 if (!empty($_GET['install_tvr_addon'])) {
551 echo wp_json_encode($this->installThemeoverAddon(sanitize_key(wp_unslash($_GET["install_tvr_addon"]))));
552 wp_die();
553 }
554
555 // content actions
556 $this->contentMethod('ajaxActions');
557
558 if (isset($_GET['process_ai_prompt'])) {
559 $AI = new AI($this);
560 $AI->sendAIPrompt();
561 wp_die();
562 }
563
564 // $this->get_site_pages();
565 if (isset($_GET['get_site_pages'])) {
566
567 // MT posts search should only check title or slug so we get precise results (that appear in top 10 limit)
568 // And because MT will filter out results with no title match on JS side anyway
569 add_filter( 'posts_search', array(&$this, 'search_by_title_or_slug'), 10, 2 );
570
571 $searchTerm = isset($_GET['search_term'])
572 ? sanitize_text_field(wp_unslash($_GET["search_term"]))
573 : null;
574
575 echo wp_json_encode($this->get_site_pages($searchTerm));
576
577 wp_die();
578 }
579
580 // ajax - load selectors and/or selector options
581 /*if ( isset($_GET['mt_action']) and $_GET['mt_action'] == 'tvr_microthemer_ui_load_styles') {
582 //check_admin_referer('tvr_microthemer_ui_load_styles');
583 $section_name = sanitize_text_field(wp_unslash($_GET["tvr_load_section"]));
584 $css_selector = sanitize_text_field(wp_unslash($_GET["tvr_load_selector"]));
585 $array = $this->options[$section_name][$css_selector];
586 echo '<div id="tmp-wrap">';
587 echo $this->all_option_groups_html($section_name, $css_selector, $array); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- generated UI markup, values escaped in builder
588 echo '</div>';
589 // output pulled data to debug file
590 if ($this->debug_pulled_data){
591 $debug_file = $this->debug_dir . 'debug-pulled-data.txt';
592 $write_file = @fopen($debug_file, 'w');
593 $data = '';
594 $data.= esc_html__('Custom debug output', 'microthemer') . "\n\n";
595 $data.= $this->debug_custom;
596 $data.= "\n\n" . esc_html__('Last pulled data', 'microthemer') . "\n\n";
597 $data.= print_r($this->options[$section_name][$css_selector], true);
598 fwrite($write_file, $data);
599 fclose($write_file);
600 }
601 // kill the program - this action is always requested via ajax. no message necessary
602 wp_die();
603 }*/
604
605 // ajax - toggle draft mode
606 if (isset($_GET['draft_mode'])) {
607
608 $pref_array['draft_mode'] = intval($_GET['draft_mode']);
609
610 // ned to get current user id again as $this->current_user_id won't be set in ajax request
611 $current_user_id = get_current_user_id();
612
613 // save current user in array
614 if ($pref_array['draft_mode']){
615 $pref_array['draft_mode_uids'][$current_user_id] = $current_user_id;
616 } else {
617 // reset if draft mode is off
618 $pref_array['draft_mode_uids'] = array();
619 }
620 $this->savePreferences($pref_array);
621 wp_die();
622 }
623
624 if (isset($_GET['mt_publish_settings'])) {
625 echo $this->publishSettings(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- generated UI markup, values escaped in builder
626 wp_die();
627 }
628
629 // selname_code_synced
630 if (isset($_GET['load_sass_import'])) {
631
632 $path = sanitize_text_field(rawurldecode(wp_unslash($_GET["load_sass_import"])));
633 $imports = $this->get_sass_import_paths('@import "'.$path.'";', '');
634 $content = false;
635
636 if ($imports){
637 $content = $this->recursively_scan_import_files(
638 array(
639 'import' => $imports[0]
640 )
641 );
642 }
643
644 $response = array(
645 'error' => !$content,
646 'content' => $content
647 );
648
649 echo wp_json_encode($response);
650 wp_die();
651 }
652
653 // selname_code_synced
654 if (isset($_GET['selname_code_synced'])) {
655 $pref_array['selname_code_synced'] = intval($_GET['selname_code_synced']);
656 $this->savePreferences($pref_array);
657 wp_die();
658 }
659
660 // code_manual_resize
661 if (isset($_GET['code_manual_resize'])) {
662 $pref_array['code_manual_resize'] = intval($_GET['code_manual_resize']);
663 $this->savePreferences($pref_array);
664 wp_die();
665 }
666
667 // ace full page html
668 if (isset($_GET['wizard_expanded'])) {
669 $pref_array['wizard_expanded'] = intval($_GET['wizard_expanded']);
670 $this->savePreferences($pref_array);
671 wp_die();
672 }
673
674 // AI admin access
675 if (isset($_GET['ai_admin_access'])) {
676 $pref_array['ai_admin_access'] = intval($_GET['ai_admin_access']);
677 $this->savePreferences($pref_array);
678 wp_die();
679 }
680
681 // remember the state of the extra icons in the selectors menu
682 if (isset($_GET['show_extra_actions'])) {
683 $pref_array['show_extra_actions'] = intval($_GET['show_extra_actions']);
684 $this->savePreferences($pref_array);
685 wp_die();
686 }
687
688 // remember the grid highlight status
689 if (isset($_GET['grid_highlight'])) {
690 $pref_array['grid_highlight'] = intval($_GET['grid_highlight']);
691 $this->savePreferences($pref_array);
692 wp_die();
693 }
694
695 // remember show_sampled_values
696 if (isset($_GET['show_sampled_values'])) {
697 $pref_array['show_sampled_values'] = intval($_GET['show_sampled_values']);
698 $this->savePreferences($pref_array);
699 wp_die();
700 }
701
702 // remember show_sampled_variables
703 if (isset($_GET['show_sampled_variables'])) {
704 $pref_array['show_sampled_variables'] = intval($_GET['show_sampled_variables']);
705 $this->savePreferences($pref_array);
706 wp_die();
707 }
708
709 // mt_color_variables_css
710 if (isset($_POST['mt_color_variables_css'])) {
711 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- user-authored CSS; sanitize_text_field would corrupt it. Tags stripped, nonce + capability gated.
712 $pref_array['mt_color_variables_css'] = wp_strip_all_tags(wp_unslash($_POST['mt_color_variables_css']));
713 $this->savePreferences($pref_array);
714 wp_die();
715 }
716
717 // wizard footer/right dock
718 if (isset($_GET['dock_ai_right'])) {
719 $pref_array['dock_ai_right'] = intval($_GET['dock_ai_right']);
720 $this->savePreferences($pref_array);
721 wp_die();
722 }
723
724 // wizard footer/right dock
725 if (isset($_GET['dock_settings_right'])) {
726 $pref_array['dock_settings_right'] = intval($_GET['dock_settings_right']);
727 $this->savePreferences($pref_array);
728 wp_die();
729 }
730
731 // instant hover inspection
732 if (isset($_GET['hover_inspect'])) {
733 $pref_array['hover_inspect'] = intval($_GET['hover_inspect']);
734 $this->savePreferences($pref_array);
735 wp_die();
736 }
737
738 // auto folders
739 if (isset($_GET['auto_folders'])) {
740 $pref_array['auto_folders'] = intval($_GET['auto_folders']);
741 $this->savePreferences($pref_array);
742 wp_die();
743 }
744
745 // auto folders
746 if (isset($_GET['auto_folders_page'])) {
747 $pref_array['auto_folders_page'] = intval($_GET['auto_folders_page']);
748 $this->savePreferences($pref_array);
749 wp_die();
750 }
751
752 // ajax - update preview url after page navigation
753 if (isset($_GET['mt_preview_url'])) {
754 $this->maybe_set_preview_url();
755 // kill the program - this action is always requested via ajax. no message necessary
756 wp_die();
757 }
758
759 // ajax - update preview url after page navigation
760 if (isset($_GET['import_css_url'])) {
761 // update view_import_stylesheets list with possible new stylesheet
762 $this->update_css_import_urls(sanitize_text_field(rawurldecode(wp_unslash($_GET["import_css_url"]))));
763 wp_die();
764 }
765
766 // code editor focus
767 if (isset($_GET['show_code_editor'])) {
768 $pref_array = array();
769 $pref_array['show_code_editor'] = intval($_GET['show_code_editor']);
770 $this->savePreferences($pref_array);
771 // kill the program - this action is always requested via ajax. no message necessary
772 wp_die();
773 }
774
775 // ruler show/hide
776 if (isset($_GET['show_rulers'])) {
777 $pref_array = array();
778 $pref_array['show_rulers'] = intval($_GET['show_rulers']);
779 $this->savePreferences($pref_array);
780 // kill the program - this action is always requested via ajax. no message necessary
781 wp_die();
782 }
783
784 // dark theme
785 if (isset($_GET['mt_dark_mode'])) {
786 $pref_array = array();
787 $pref_array['mt_dark_mode'] = intval($_GET['mt_dark_mode']);
788 $this->savePreferences($pref_array);
789 wp_die();
790 }
791
792 // dark theme
793 if (isset($_GET['sync_browser_tabs'])) {
794 $pref_array = array();
795 $pref_array['sync_browser_tabs'] = intval($_GET['sync_browser_tabs']);
796 $this->savePreferences($pref_array);
797 wp_die();
798 }
799
800 // auto-load Elementor, BB, Oxygen
801 if (isset($_GET['autoload_elementor'])) {
802 $this->savePreferences(array(
803 'autoload_elementor' => intval($_GET['autoload_elementor'])
804 ));
805 wp_die();
806 } if (isset($_GET['autoload_FLBuilder'])) {
807 $this->savePreferences(array(
808 'autoload_FLBuilder' => intval($_GET['autoload_FLBuilder'])
809 ));
810 wp_die();
811 } if (isset($_GET['autoload_oxygen'])) {
812 $this->savePreferences(array(
813 'autoload_oxygen' => intval($_GET['autoload_oxygen'])
814 ));
815 wp_die();
816 }
817
818 // Auto-save mode
819 if (isset($_GET['auto_save_mode'])) {
820 $pref_array = array();
821 $pref_array['auto_save_mode'] = intval($_GET['auto_save_mode']);
822 $this->savePreferences($pref_array);
823 wp_die();
824 }
825
826 // Auto-publish mode
827 if (isset($_GET['auto_publish_mode'])) {
828 $pref_array = array();
829 $pref_array['auto_publish_mode'] = intval($_GET['auto_publish_mode']);
830 $this->savePreferences($pref_array);
831 wp_die();
832 }
833
834 if (isset($_GET['sticky_styles_toolbar'])) {
835 $this->savePreferences(array(
836 'sticky_styles_toolbar' => intval($_GET['sticky_styles_toolbar'])
837 ));
838 wp_die();
839 }
840
841 if (isset($_GET['pseudo_base_styles'])){
842 $this->savePreferences(array(
843 'pseudo_base_styles' => intval($_GET['pseudo_base_styles'])
844 ));
845 wp_die();
846 }
847
848 // specificity preference
849 if (isset($_GET['specificity_preference'])) {
850 $pref_array = array();
851 $pref_array['specificity_preference'] = intval($_GET['specificity_preference']);
852 $this->savePreferences($pref_array);
853 // kill the program - this action is always requested via ajax. no message necessary
854 wp_die();
855 }
856
857 // sidebar size
858 /* if (isset($_GET['sidebar_size'])) {
859 $pref_array = array();
860 $pref_array['sidebar_size'] = intval($_GET['sidebar_size']);
861 $pref_array["sidebar_size_category"] = sanitize_text_field(wp_unslash($_GET["sidebar_size"]));
862 $this->savePreferences($pref_array);
863 // kill the program - this action is always requested via ajax. no message necessary
864 wp_die();
865 }*/
866
867 // save new MT layout
868 if (isset($_GET['update_mt_layout'])) {
869 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
870 $data = json_decode( stripslashes($_POST['tvr_serialized_data']), true );
871 $this->savePreferences(array(
872 'layout' => $data
873 ));
874 wp_die();
875 }
876
877 // dock folders left
878 if (isset($_GET['dock_folders_left'])) {
879 $pref_array = array();
880 $pref_array['dock_folders_left'] = intval($_GET['dock_folders_left']);
881 $this->savePreferences($pref_array);
882 wp_die();
883 }
884
885 // dock styles left
886 if (isset($_GET['dock_styles_left'])) {
887 $pref_array = array();
888 $pref_array['dock_styles_left'] = intval($_GET['dock_styles_left']);
889 $this->savePreferences($pref_array);
890 wp_die();
891 }
892
893 // dock editor left
894 if (isset($_GET['dock_editor_left'])) {
895 $pref_array = array();
896 $pref_array['dock_editor_left'] = intval($_GET['dock_editor_left']);
897 $this->savePreferences($pref_array);
898 // kill the program - this action is always requested via ajax. no message necessary
899 wp_die();
900 }
901
902 // full_height_left_sidebar
903 if (isset($_GET['full_height_left_sidebar'])) {
904 $pref_array = array();
905 $pref_array['full_height_left_sidebar'] = intval($_GET['full_height_left_sidebar']);
906 $this->savePreferences($pref_array);
907 // kill the program - this action is always requested via ajax. no message necessary
908 wp_die();
909 }
910
911 // expand_device_tabs
912 if (isset($_GET['expand_device_tabs'])) {
913 $pref_array = array();
914 $pref_array['expand_device_tabs'] = intval($_GET['expand_device_tabs']);
915 $this->savePreferences($pref_array);
916 // kill the program - this action is always requested via ajax. no message necessary
917 wp_die();
918 }
919
920 // update multiple preferences in one go
921 if (isset($_GET['save_multiple_preferences']) && isset($_POST['pref_array'])) {
922
923 $pref_array = array();
924
925 // Mixed-type preference values (may hold user-authored CSS/JSON or nested
926 // arrays like lastMultiTab). Preserve key casing — sanitize_key() lowercases
927 // and would turn lastMultiTab into lastmultitab. Nonce + capability gated above.
928 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
929 foreach(wp_unslash($_POST['pref_array']) as $key => $value){
930 $safe_key = preg_replace('/[^A-Za-z0-9_\-]/', '', (string) $key);
931 if ($safe_key === ''){
932 continue;
933 }
934 $pref_array[$safe_key] = is_array($value)
935 ? $value
936 : (is_numeric($value) ? floatval($value) : $value);
937 }
938
939 $this->savePreferences($pref_array);
940 // kill the program - this action is always requested via ajax. no message necessary
941 wp_die();
942 }
943
944 // if it's an email error report request
945 elseif (isset($_GET['send_error_report'])){
946
947 $manual = (bool) intval($_POST['manual']);
948
949 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- structured diagnostic payload; fields are individually picked/cast in maybeSendReport. Nonce + capability gated.
950 $response = $this->maybeSendReport($_POST, $manual);
951
952 if ($manual || TVR_DEV_MODE){
953 echo wp_json_encode(array(
954 //'html'=> '<div id="microthemer-notice">'. $this->display_log() . '</div>',
955 'response' => $response,
956 'success' => !!$response
957 ));
958 }
959
960 wp_die();
961 }
962
963
964 // dock ALL options left
965 /* if (isset($_GET['dock_styles_left'])) {
966 $pref_array = array();
967 $pref_array['dock_styles_left'] = intval($_GET['dock_styles_left']);
968 $this->savePreferences($pref_array);
969 // kill the program - this action is always requested via ajax. no message necessary
970 wp_die();
971 }*/
972
973 // detach preview -
974 if (isset($_GET['detach_preview'])) {
975 $pref_array = array();
976 $pref_array['detach_preview'] = intval($_GET['detach_preview']);
977 $this->savePreferences($pref_array);
978 // kill the program - this action is always requested via ajax. no message necessary
979 wp_die();
980 }
981
982 // ruler show/hide
983 if (isset($_GET['show_text_labels'])) {
984 $pref_array = array();
985 $pref_array['show_text_labels'] = intval($_GET['show_text_labels']);
986 $this->savePreferences($pref_array);
987 wp_die();
988 }
989
990 // show/hide whole interface
991 if (isset($_GET['hide_interface'])) {
992 $pref_array = array();
993 $pref_array['hide_interface'] = intval($_GET['hide_interface']);
994 $this->savePreferences($pref_array);
995 // kill the program - this action is always requested via ajax. no message necessary
996 wp_die();
997 }
998
999 // active MQ tab
1000 if (isset($_GET['manual_recompile_all_css'])) {
1001 $pref_array = array();
1002 $pref_array['manual_recompile_all_css'] = sanitize_text_field(wp_unslash($_GET['manual_recompile_all_css']));
1003 $this->savePreferences($pref_array);
1004 // kill the program - this action is always requested via ajax. no message necessary
1005 wp_die();
1006 }
1007
1008 // remove_all_bricks_container_hacks
1009 if (isset($_GET['remove_all_bricks_container_hacks'])) {
1010 $pref_array = array();
1011 $pref_array['remove_all_bricks_container_hacks'] = intval($_GET['remove_all_bricks_container_hacks']);
1012 $this->savePreferences($pref_array);
1013 // kill the program - this action is always requested via ajax. no message necessary
1014 wp_die();
1015 }
1016
1017 // initial_view_set
1018 if (isset($_GET['initial_view_set'])) {
1019 $pref_array = array();
1020 $pref_array['initial_view_set'] = intval($_GET['initial_view_set']);
1021 $this->savePreferences($pref_array);
1022 wp_die();
1023 }
1024
1025 // active MQ tab
1026 if (isset($_GET['mq_device_focus'])) {
1027 $pref_array = array();
1028 $pref_array['mq_device_focus'] = sanitize_text_field(wp_unslash($_GET['mq_device_focus']));
1029 $this->savePreferences($pref_array);
1030 // kill the program - this action is always requested via ajax. no message necessary
1031 wp_die();
1032 }
1033
1034 // active MQ tab
1035 if (isset($_GET['rev_save_status'])) {
1036 $this->updateRevisionSaveStatus(
1037 intval($_GET['rev_id']),
1038 intval($_GET['rev_save_status'])
1039 );
1040 wp_die();
1041 }
1042
1043 // active CSS tab
1044 if (isset($_GET['css_focus'])) {
1045 $pref_array = array();
1046 $pref_array['css_focus'] = sanitize_text_field(wp_unslash($_GET['css_focus']));
1047 $this->savePreferences($pref_array);
1048 // kill the program - this action is always requested via ajax. no message necessary
1049 wp_die();
1050 }
1051
1052 // update_default_unit
1053 if (isset($_GET['update_default_unit'])) {
1054 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1055 $data = json_decode( stripslashes($_POST['tvr_serialized_data']), true );
1056 $this->preferences['my_props'][$data['group']]['pg_props'][$data['prop']]['default_unit'] = $data['unit'];
1057 $pref_array['my_props'] = $this->preferences['my_props'];
1058 $this->savePreferences($pref_array);
1059
1060 wp_die();
1061 }
1062
1063 // MT may update custom paths array via JS (e.g. path clear) and then post full array to replace current
1064 if (isset($_GET['update_custom_paths'])) {
1065 $pref_array['custom_paths'] = json_decode(
1066 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1067 stripslashes($_POST['tvr_serialized_data']),
1068 true
1069 );
1070 $this->savePreferences($pref_array);
1071 wp_die();
1072 }
1073
1074 // update draft folder session
1075 if (isset($_GET['update_draft_folder_session'])) {
1076 //add_action( 'init', array(&$this, 'updateDraftFolderCookie') );
1077 $this->updateDraftFolderCookie();
1078 }
1079
1080 // update_recent_custom_prefixes
1081 if (isset($_GET['update_recent_custom_prefixes'])) {
1082 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1083 $pref_array['recent_custom_prefixes'] = json_decode( stripslashes($_POST['tvr_serialized_data']), true );
1084 $this->savePreferences($pref_array);
1085 wp_die();
1086 }
1087
1088 // update suggested values
1089 if (isset($_GET['update_sug_values'])) {
1090
1091 $pref_array = array();
1092 $root_cat = sanitize_text_field(wp_unslash($_GET["update_sug_values"]));
1093
1094 // tap into WordPress native JSON functions
1095 /*if( !class_exists('Moxiecode_JSON') ) {
1096 require_once($this->thisplugindir . 'includes/class-json.php');
1097 }
1098
1099 $json_object = new Moxiecode_JSON();*/
1100
1101 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1102 $data = json_decode( stripslashes($_POST['tvr_serialized_data']), true );
1103
1104 // if we're setting suggested values for all properties
1105 if ($root_cat == 'all'){
1106 $this->preferences['my_props']['sug_values'] = $data['sug_values'];
1107 $this->preferences['my_props']['sug_variables'] = $data['sug_variables'];
1108 } elseif ($root_cat == 'synced_set') {
1109 // a set of fields in one go e.g. padding
1110 $this->preferences['my_props']['sug_values'] =
1111 array_merge($this->preferences['my_props']['sug_values'], $data['synced_set']);
1112 } else {
1113 // just setting suggestions for a type of property e.g. site_colors
1114
1115 if (!empty($data['specific'])){
1116 $this->preferences['my_props']['sug_values'][$root_cat] = $data['specific'];
1117 }
1118 }
1119
1120 // update variable if passed
1121 if (isset($data['sug_variables'])){
1122 $this->preferences['my_props']['sug_variables'] = $data['sug_variables'];
1123 $pref_array['default_sug_variables_set'] = 1;
1124 }
1125
1126 $pref_array['default_sug_values_set'] = 1;
1127 $pref_array['my_props'] = $this->preferences['my_props'];
1128 $this->savePreferences($pref_array);
1129
1130 //echo '<pre>posted array: '.print_r($data, true).'</pre>';
1131
1132 // kill the program - this action is always requested via ajax. no message necessary
1133 wp_die();
1134 }
1135
1136 // save google/typekit fonts config
1137 if (isset($_GET['save_font_config'])) {
1138
1139 // tap into WordPress native JSON functions
1140 /*if( !class_exists('Moxiecode_JSON') ) {
1141 require_once($this->thisplugindir . 'includes/class-json.php');
1142 }
1143
1144 $json_object = new Moxiecode_JSON();*/
1145
1146 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1147 $data = json_decode( stripslashes($_POST['tvr_serialized_data']), true );
1148 $pref_array = array();
1149 $key = $_GET['save_font_config'] == 'google' ? 'google' : 'typekit';
1150 $pref_array['font_config'][$key] = $data;
1151
1152 $this->savePreferences($pref_array);
1153
1154 //echo '<pre>posted array: '.print_r($data, true).'</pre>';
1155
1156 // kill the program - this action is always requested via ajax. no message necessary
1157 wp_die();
1158 }
1159
1160 // active property group
1161 if (isset($_GET['pg_focus'])) {
1162 $pref_array = array();
1163 $pref_array['pg_focus'] = sanitize_text_field(wp_unslash($_GET['pg_focus']));
1164 $this->savePreferences($pref_array);
1165 // kill the program - this action is always requested via ajax. no message necessary
1166 wp_die();
1167 }
1168
1169 // active generated_css_focus
1170 /*if (isset($_GET['generated_css_focus'])) {
1171 $pref_array = array();
1172 $pref_array['generated_css_focus'] = intval($_GET['generated_css_focus']);
1173 $this->savePreferences($pref_array);
1174 // kill the program - this action is always requested via ajax. no message necessary
1175 wp_die();
1176 }*/
1177
1178 // remember selector wizard tab
1179 if (isset($_GET['adv_wizard_tab'])) {
1180 $pref_array = array();
1181 $pref_array['adv_wizard_tab'] = sanitize_text_field(wp_unslash($_GET['adv_wizard_tab']));
1182 $this->savePreferences($pref_array);
1183 // kill the program - this action is always requested via ajax. no message necessary
1184 wp_die();
1185 }
1186
1187 // remember the program settings tab
1188 if (isset($_GET['program_settings_tab'])) {
1189 $pref_array = array();
1190 $pref_array['program_settings_tab'] = sanitize_text_field(wp_unslash($_GET['program_settings_tab']));
1191 $this->savePreferences($pref_array);
1192 // kill the program - this action is always requested via ajax. no message necessary
1193 wp_die();
1194 }
1195
1196 // remember selector wizard tab
1197 if (isset($_GET['grid_focus'])) {
1198 $pref_array = array();
1199 $pref_array['grid_focus'] = sanitize_text_field(wp_unslash($_GET['grid_focus']));
1200 $this->savePreferences($pref_array);
1201 // kill the program - this action is always requested via ajax. no message necessary
1202 wp_die();
1203 }
1204
1205 // remember transform tab
1206 if (isset($_GET['transform_focus'])) {
1207 $pref_array = array();
1208 $pref_array['transform_focus'] = sanitize_text_field(wp_unslash($_GET['transform_focus']));
1209 $this->savePreferences($pref_array);
1210 // kill the program - this action is always requested via ajax. no message necessary
1211 wp_die();
1212 }
1213
1214 // last viewed selector
1215 /*if (isset($_GET['last_viewed_selector'])) {
1216 $pref_array = array();
1217 $pref_array['last_viewed_selector'] = sanitize_text_field(wp_unslash($_GET['last_viewed_selector']));
1218 $this->savePreferences($pref_array);
1219 // kill the program - this action is always requested via ajax. no message necessary
1220 wp_die();
1221 }*/
1222
1223 // download pack
1224 if (!empty($_GET['mt_action']) and
1225 $_GET['mt_action'] == 'tvr_download_pack') {
1226 if (!empty($_GET['dir_name'])) {
1227 // first of all, copy any images from the media library
1228 $pack = sanitize_file_name(wp_unslash($_GET["dir_name"]));
1229 $dir = $this->micro_root_dir . $pack;
1230 $json_config_file = $dir . '/config.json';
1231 if ($library_images = $this->get_linked_library_images($json_config_file)){
1232 foreach($library_images as $key => $path){
1233 // strip site_url rather than home_url in this case coz using with ABSPATH
1234 $root_rel_path = $this->root_rel($path, false, true, true);
1235 $basename = basename($root_rel_path);
1236 $orig = rtrim(ABSPATH,"/"). $root_rel_path;
1237 $img_paths[] = $new = $dir . '/' . $basename;
1238 $replacements[$path] = $this->root_rel(
1239 $this->micro_root_url . $pack . '/' . $basename, false, true
1240 );
1241 if (!copy($orig, $new)){
1242 $this->log(
1243 esc_html__('Library image not downloaded', 'microthemer'),
1244 '<p>' . sprintf(/* translators: %s: image file path */ esc_html__('%s could not be copied to the zip download file', 'microthemer'), $root_rel_path) . '</p>',
1245 'warning'
1246 );
1247 $download_status = 0;
1248 }
1249 }
1250 // cache original config file data
1251 $orig_json_data = $this->get_file_data($json_config_file);
1252
1253 // update image paths in config.json for zip only (we'll restore shortly)
1254 $this->replace_json_paths($json_config_file, $replacements, $orig_json_data);
1255 }
1256
1257 // now zip the contents
1258 if (
1259 $this->create_zip(
1260 $this->micro_root_dir,
1261 $pack,
1262 $this->thisplugindir.'zip-exports/')
1263 ){
1264 $download_status = 1;
1265 } else {
1266 $download_status = 0;
1267 }
1268 }
1269 else {
1270 $download_status = 0;
1271 }
1272 // delete any media library images temporarily copied to the directory
1273 if ($library_images){
1274 // restore orgin config.json paths
1275 $this->write_file($json_config_file, $orig_json_data);
1276 // delete images
1277 foreach ($img_paths as $key => $path){
1278 if ( ! wp_delete_file( $path ) ){
1279 $this->log(
1280 esc_html__('Temporary image could not be deleted.', 'microthemer'),
1281 '<p>' . sprintf( /* translators: %s: image file path */ esc_html__('%s was temporarily copied to your theme pack before download but could not be deleted after the download operation finished.', 'microthemer'), $this->root_rel($root_rel_path) ) . '</p>',
1282 'warning'
1283 );
1284 }
1285 }
1286 }
1287 // phpcs:disable WordPress.Security.EscapeOutput -- display_log returns generated UI markup, values escaped in builder
1288 echo '
1289 <div id="microthemer-notice">'
1290 . $this->display_log() . '
1291 <span id="download-status" rel="'.esc_attr($download_status).'"></span>
1292 </div>';
1293 // phpcs:enable WordPress.Security.EscapeOutput
1294 wp_die();
1295 }
1296
1297 // delete pack
1298 if (!empty($_GET['mt_action']) and
1299 $_GET['mt_action'] == 'tvr_delete_micro_theme') {
1300 if (!empty($_GET['dir_name']) and $this->tvr_delete_micro_theme(sanitize_file_name(wp_unslash($_GET["dir_name"])))){
1301 $delete_status = 1;
1302 } else {
1303 $delete_status = 0;
1304 }
1305 // phpcs:disable WordPress.Security.EscapeOutput -- display_log returns generated UI markup, values escaped in builder
1306 echo '
1307 <div id="microthemer-notice">'
1308 . $this->display_log() . '
1309 <span id="delete-status" rel="'.esc_attr($delete_status).'"></span>
1310 </div>';
1311 // phpcs:enable WordPress.Security.EscapeOutput
1312 wp_die();
1313 }
1314
1315 // download remote css file
1316 if (!empty($_GET['mt_action']) and
1317 $_GET['mt_action'] == 'tvr_get_remote_css') {
1318 $config['allowed_ext'] = array('css');
1319 $r = $this->get_safe_url(esc_url_raw(rawurldecode(wp_unslash($_GET["url"]))), $config);
1320 // phpcs:disable WordPress.Security.EscapeOutput -- display_log returns generated UI markup; CSS content entity-encoded
1321 echo '
1322 <div id="microthemer-notice">'
1323 . $this->display_log() . '
1324 <div id="remote-css">'.(!empty($r['content']) ? esc_html($r['content']) : 0).'</div>
1325 </div>';
1326 // phpcs:enable WordPress.Security.EscapeOutput
1327 wp_die();
1328 }
1329
1330 // if it's an import request
1331 if ( !empty($_POST['import_pack_or_css']) ){
1332
1333 // if importing raw CSS
1334 if (!empty($_POST['stylesheet_import_json'])){
1335
1336 $context = esc_attr__('Raw CSS', 'microthemer');
1337 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1338 $json_str = stripslashes($_POST['stylesheet_import_json']);
1339 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- structured preferences array; fields validated individually below. Nonce + capability gated.
1340 $p = $_POST['tvr_preferences'];
1341
1342 // checkbox values must be explicitly evaluated
1343 $p['css_imp_only_selected'] = !empty($p['css_imp_only_selected']) ? 1 : 0;
1344
1345 // handle remote image import. See plugins that do this:
1346 // https://premium.wpmudev.org/blog/download-remote-images-into-wordpress/
1347 if (!empty($_POST['get_remote_images'])){
1348
1349 // Each entry is "pathInData,fullUrl" joined by "|". esc_url_raw on the
1350 // composite before splitting empties entries (relative path + https URL).
1351 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- each half sanitized below after split. Nonce + capability gated.
1352 $r_images = explode("|", wp_unslash($_POST["get_remote_images"]));
1353 $do_copy = false;
1354 $remote_images = array();
1355 $all_r = array();
1356 foreach ($r_images as $i => $both){
1357 $tmp = explode(',', $both, 2);
1358 // This is an exact CSS/JSON replacement key, not a filesystem path.
1359 // sanitize_text_field() strips valid URL-encoded segments such as "%20", preventing a match.
1360 $path_in_data = isset($tmp[0]) ? $tmp[0] : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- derived from the nonce/capability-gated composite above; used only as a JSON string-replacement key
1361 $full_url = isset($tmp[1]) ? esc_url_raw($tmp[1]) : '';
1362 if ($path_in_data === '' || $full_url === ''){
1363 continue;
1364 }
1365 // save to temp dir first
1366 $r = $this->get_safe_url($full_url, array(
1367 'allowed_ext' => array('jpg', 'jpeg', 'gif', 'png', 'svg'),
1368 'tmp_file' => 1
1369 ));
1370
1371 if ($r){
1372 $remote_images[$path_in_data] = $r['tmp_file'];
1373 $do_copy = true;
1374 //$all_r[++$i] = $r;
1375 }
1376
1377 }
1378
1379 // do image copy function
1380 if ($do_copy){
1381
1382 $updated_json_str = $this->import_pack_images_to_library(
1383 false,
1384 'custom',
1385 $json_str,
1386 $remote_images
1387 );
1388
1389 $json_str = $updated_json_str ? $updated_json_str : $json_str;
1390 }
1391
1392 }
1393
1394 // load the json file
1395 $this->load_json_file(false, 'custom', $context, $json_str);
1396
1397 // save the import preferences
1398 $this->savePreferences($p);
1399 }
1400
1401 // if importing an MT design pack
1402 else {
1403
1404
1405 $theme_name = sanitize_file_name(sanitize_title(htmlentities($_POST['import_from_pack_name'])));
1406
1407
1408 $json_file = $this->micro_root_dir . $theme_name . '/config.json';
1409
1410 // Radio values are translated UI strings ("Merge"/"Overwrite"), not machine keys.
1411 // sanitize_key() would lowercase them and break the Merge comparison.
1412 $context = sanitize_text_field(wp_unslash($_POST["tvr_import_method"]));
1413
1414 // import any background images that may need moving to the media library and update json
1415 $this->import_pack_images_to_library($json_file, $theme_name);
1416
1417 // load the json file
1418 $this->load_json_file($json_file, $theme_name, $context);
1419
1420 }
1421
1422 // signal that all selectors should be recompiled (to ensure latest data structure)
1423 $this->update_preference('manual_recompile_all_css', 1);
1424
1425 // update the revisions DB field
1426 $revision_id = $this->updateRevisions($this->options, $this->json_format_ua(
1427 'import-from-pack lg-icon',
1428 esc_html__('Import', 'microthemer') . ' ('.$context.'):&nbsp;',
1429 $this->readable_name($theme_name)
1430 ));
1431 if (!$revision_id) {
1432 $this->log('','','error', 'revisions');
1433 }
1434
1435 // save last message in database so that it can be displayed on page reload (just once)
1436 $this->cache_global_msg();
1437
1438 // Return the revision data for indexedDB
1439 $this->jsonResponse($this->getRevisionData($revision_id));
1440 }
1441
1442 // if it's a reset request
1443 elseif( isset($_GET['mt_action']) and $_GET['mt_action'] == 'tvr_ui_reset'){
1444 if ($this->resetUiOptions()) {
1445 $this->update_assets('customised');
1446 $item = esc_html__('Folders were reset', 'microthemer');
1447 $this->log(
1448 $item,
1449 '<p>' . esc_html__('The default empty folders have been reset.', 'microthemer') . '</p>',
1450 'notice'
1451 );
1452 // update the revisions DB field
1453 $revision_id = $this->updateRevisions($this->options, $this->json_format_ua(
1454 'folder-reset lg-icon',
1455 $item
1456 ));
1457 if (!$revision_id) {
1458 $this->log(
1459 esc_html__('Revision failed to save', 'microthemer'),
1460 '<p>' . esc_html__('The revisions table could not be updated.', 'microthemer') . '</p>',
1461 'notice'
1462 );
1463 }
1464 }
1465 // save last message in database so that it can be displayed on page reload (just once)
1466 $this->cache_global_msg();
1467
1468 $this->jsonResponse($this->getRevisionData($revision_id));
1469 }
1470
1471 // if it's a restore revision request
1472 if(isset($_GET['mt_action']) and $_GET['mt_action'] == 'restore_rev'){
1473 $rev_key = intval($_GET["tvr_rev"]);
1474 if ($this->restoreRevision($rev_key)) {
1475 $item = esc_html__('Previous settings restored', 'microthemer');
1476 $this->log(
1477 $item,
1478 '<p>' . esc_html__('Your settings were successfully restored from a previous save.', 'microthemer') . '</p>',
1479 'notice'
1480 );
1481 $this->update_assets('customised');
1482 // update the revisions DB field
1483 $revision_id = $this->updateRevisions($this->options, $this->json_format_ua(
1484 'display-revisions lg-icon',
1485 $item
1486 ));
1487 if (!$revision_id) {
1488 $this->log('','','error', 'revisions');
1489 }
1490 }
1491 else {
1492 $this->log(
1493 esc_html__('Settings restore failed', 'microthemer'),
1494 '<p>' . esc_html__('Data could not be restored from a previous save.', 'microthemer') . '</p>'
1495 );
1496 }
1497 // save last message in database so that it can be displayed on page reload (just once)
1498 $this->cache_global_msg();
1499
1500 // Return the revision data for indexedDB
1501 $this->jsonResponse($this->getRevisionData($revision_id));
1502 }
1503
1504 // if it's a get revision ajax request
1505 elseif(isset($_GET['mt_action']) and $_GET['mt_action'] == 'get_revisions'){
1506 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- generated revisions table markup, values escaped in builder
1507 echo '<div id="tmp-wrap">' . $this->getRevisions() . '</div>'; // outputs table
1508 wp_die();
1509 }
1510
1511
1512 /* PREFERENCES FUNCTIONS MOVED TO MAIN UI */
1513
1514 // update the MQs
1515 if (isset($_POST['tvr_media_queries_submit'])){
1516
1517 $orig_media_queries = $this->preferences['m_queries'];
1518
1519 // remove backslashes from $_POST
1520 $_POST = $this->deep_unescape($_POST, 0, 1, 1); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- unslash equivalent for nested arrays
1521 // get the initial scale and default width for the "All Devices" tab
1522 $pref_array["initial_scale"] = floatval($_POST["tvr_preferences"]["initial_scale"]);
1523 // May be blank (full width) or e.g. "480px" — do not intval() ('' → 0, strips units).
1524 $pref_array["all_devices_default_width"] = isset($_POST["tvr_preferences"]["all_devices_default_width"])
1525 ? sanitize_text_field(wp_unslash($_POST["tvr_preferences"]["all_devices_default_width"]))
1526 : '';
1527 // reset default media queries if all empty
1528 $action = '';
1529 if (empty($_POST['tvr_preferences']['m_queries'])) {
1530 $pref_array['m_queries'] = $this->default_mqs;
1531 $action = 'reset';
1532 } else {
1533 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- structured media query array (user-authored CSS values); validated by savePreferences. Nonce + capability gated.
1534 $pref_array['m_queries'] = $_POST['tvr_preferences']['m_queries'];
1535 $action = 'update';
1536 }
1537
1538 // are we merging/overwriting with a new media query set
1539 if (!empty($_POST['tvr_preferences']['load_mq_set'])){
1540 //print_r($this->mq_sets);
1541 $action = 'load_set';
1542 $new_set = sanitize_text_field($_POST["tvr_preferences"]["load_mq_set"]);
1543 $new_mq_set = $this->mq_sets[$new_set];
1544 $pref_array["overwrite_existing_mqs"] = intval($_POST["tvr_preferences"]["overwrite_existing_mqs"]);
1545 if (!empty($pref_array['overwrite_existing_mqs'])){
1546 $pref_array['m_queries'] = $new_mq_set;
1547 $load_action = esc_html__('replaced', 'microthemer');
1548 } else {
1549 $pref_array['m_queries'] = array_merge($pref_array['m_queries'], $new_mq_set);
1550 $load_action = esc_html__('was merged with', 'microthemer');
1551 }
1552 }
1553
1554 // format media query min/max width (height later) and units
1555 $pref_array['m_queries'] = $this->mq_min_max($pref_array);
1556
1557 // save and preset message
1558 $pref_array['num_saves'] = ++$this->preferences['num_saves'];
1559
1560 if (empty($this->preferences['auto_publish_mode'])){
1561 $pref_array['num_unpublished_saves'] = ++$this->preferences['num_unpublished_saves'];
1562 }
1563
1564 if ($this->savePreferences($pref_array)) {
1565
1566 switch ($action) {
1567 case 'reset':
1568 $this->log(
1569 esc_html__('Media queries reset', 'microthemer'),
1570 '<p>' . esc_html__('The default media queries were successfully reset.', 'microthemer') . '</p>',
1571 'notice'
1572 );
1573 break;
1574 case 'update':
1575 $this->log(
1576 esc_html__('Media queries updated', 'microthemer'),
1577 '<p>' . esc_html__('Your media queries were successfully updated.', 'microthemer') . '</p>',
1578 'notice'
1579 );
1580 break;
1581 case 'load_set':
1582 $this->log(
1583 esc_html__('Media query set loaded', 'microthemer'),
1584 '<p>' . sprintf( /* translators: 1: load action (merged/replaced), 2: media query set name */ esc_html__('A new media query set %1$s your existing media queries: %2$s', 'microthemer'), $load_action, esc_html(sanitize_text_field(wp_unslash($_POST["tvr_preferences"]["load_mq_set"]))) ) . '</p>',
1585 'notice'
1586 );
1587 break;
1588 }
1589
1590 // if the user deleted a media query, ensure data is cleaned from the ui data
1591 $this->clean_deleted_media_queries($orig_media_queries, $pref_array['m_queries']);
1592
1593 }
1594 // save last message in database so that it can be displayed on page reload (just once)
1595 $this->cache_global_msg();
1596
1597 // Return the revision data for indexedDB
1598 $this->jsonResponse($this->getRevisionData(
1599 $this->updateRevisions(
1600 $this->options,
1601 $this->json_format_ua(
1602 'mtif-devices lg-icon',
1603 esc_html__('Media queries updated', 'microthemer')
1604 )
1605 )
1606 ));
1607 }
1608
1609 // update the enqueued JS files
1610 if (isset($_POST['mt_enqueue_js_submit'])){
1611 // remove backslashes from $_POST
1612 $_POST = $this->deep_unescape($_POST, 0, 1, 1); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- unslash equivalent for nested arrays
1613 $pref_array["enq_js"] = isset($_POST["tvr_preferences"]["enq_js"]) ? $_POST["tvr_preferences"]["enq_js"] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- structured user-authored script config, validated by savePreferences, nonce + capability gated
1614 $pref_array['num_saves'] = ++$this->preferences['num_saves'];
1615
1616 if (empty($this->preferences['auto_publish_mode'])){
1617 $pref_array['num_unpublished_saves'] = ++$this->preferences['num_unpublished_saves'];
1618 }
1619
1620 // save and present message
1621 if ($this->savePreferences($pref_array)) {
1622 $this->log(
1623 esc_html__('Enqueued scripts were updated', 'microthemer'),
1624 '<p>' . esc_html__('Your enqueued scripts were successfully updated.', 'microthemer') . '</p>',
1625 'notice'
1626 );
1627 }
1628
1629 // save last message in database so that it can be displayed on page reload (just once)
1630 $this->cache_global_msg();
1631
1632 // Return the revision data for indexedDB
1633 $this->jsonResponse($this->getRevisionData(
1634 $this->updateRevisions(
1635 $this->options,
1636 $this->json_format_ua(
1637 'mtif-js lg-icon',
1638 esc_html__('Enqueued scripts updated', 'microthemer')
1639 )
1640 )
1641 ));
1642 }
1643
1644 // reset default preferences
1645 if (isset($_POST['tvr_preferences_reset'])) {
1646 check_admin_referer('tvr_preferences_reset');
1647 $pref_array = $this->default_preferences;
1648 if ($this->savePreferences($pref_array)) {
1649 $this->log(
1650 esc_html__('Preferences were reset', 'microthemer'),
1651 '<p>' . esc_html__('The default program preferences were reset.', 'microthemer') . '</p>',
1652 'notice'
1653 );
1654 }
1655 }
1656
1657 // save the conditional logic configuration
1658 /*if (isset($_GET['update_asset_loading'])) {
1659 $pref_array['asset_loading'] = json_decode(
1660 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1661 stripslashes($_POST['tvr_serialized_data']),
1662 true
1663 );
1664 $this->savePreferences($pref_array);
1665 wp_die();
1666 }*/
1667
1668
1669
1670 // css filter configs
1671 $filter_types = array('page_specific', 'pseudo_classes', 'pseudo_elements');
1672 foreach ($filter_types as $type){
1673 if (isset($_GET[$type])) {
1674 // Keys include CSS pseudos like ":hover" / "::before" — do not use sanitize_key().
1675 $pref_sub_key = sanitize_text_field(wp_unslash($_GET["pref_sub_key"]));
1676 //$value = $_GET['pref_sub_key'] === 'custom-prefix' ? $_GET[$type] : intval($_GET[$type]);
1677 $this->preferences[$type][$pref_sub_key] = intval($_GET[$type]);
1678 $pref_array[$type] = $this->preferences[$type];
1679
1680 if (isset($_GET['extraValue'])){
1681 // This is user-authored CSS selector syntax. sanitize_text_field() can strip
1682 // valid percent-encoded attribute values and silently change the selector.
1683 $extra_value = wp_unslash($_GET['extraValue']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- raw CSS selector fragment; nonce + capability gated above
1684 $pref_array[$pref_sub_key.'-extraValue'] = is_string($extra_value) ? $extra_value : '';
1685 }
1686
1687 $this->savePreferences( $pref_array );
1688 //echo '<pre>'. print_r($this->preferences[$type], true).'</pre>';
1689 wp_die();
1690 }
1691 }
1692
1693 // if we got to hear, the ajax request didn't work as intended, so warn
1694 echo 'Yo! The Ajax call failed to trigger any function. Sort it out.';
1695 wp_die();
1696
1697 }
1698
1699 }
1700
1701 function updateDraftFolderCookie(){
1702
1703 $key = 'microthemer_draft_folder';
1704
1705 if (isset($_GET['delete_value'])){
1706 $this->deleteCookie($key);
1707 } else {
1708 $this->pluginCookie(
1709 $key,
1710 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- JSON payload (may contain user-authored CSS/JS); structure enforced by json_decode, request nonce + capability gated
1711 stripslashes($_POST['tvr_serialized_data'])
1712 );
1713 }
1714
1715 wp_die();
1716 }
1717
1718 }
1719