<?php

namespace Microthemer;

class Admin {

    // Use traits to organise this god class into related functions
    // Then, convert the traits into classes one by one over time
    use PluginTrait,
        LogTrait,
	    AdminInitTrait,
        PreferencesTrait,
	    SettingsTrait,
	    FileTrait,
	    BlockTrait,
	    FrontAndBackTrait,
	    PackTrait;

	var $context = 'admin';
	var $contentClass;
    var $cssClass;
	var $locale = ''; // current language
	var $lang = array(); // lang strings
	var $time = 0;

	// set this to true if version saved in DB is different, other actions may follow if new v
	var $new_version = false;

	var $optimisation_test = false;
	var $optionsName = 'microthemer_ui_settings';

    var $modification = array();

	var $micro_ver_name = 'micro_revisions_version';
	var $html_mods_ver_name = 'micro_html_mods_version';
	var $globalmessage = array();
	var $outdatedTabIssue = 0;
	var $outdatedTabDebug = '';
	var $innoFirewall = false;
	var $ei = 0; // error index
	var $permissionshelp;


	//var $placeholderpage = 'tvr-placeholder.php';
	var $demo_video = 'https://themeover.com/videos/?name=gettingStarted';
	var $targeting_video = 'https://themeover.com/videos/?name=targeting';
	var $mt_admin_nonce = '';
	var $amender_demo_video = 'https://themeover.com/videos/?name=amenderGettingStarted';

	var $total_sections = 0;
	var $total_selectors = 0;
	var $sel_loop_count;
	var $sel_count = 0;
	var $sel_option_count = 0;
	var $group_spacer_count = 0;
	//var $sel_lookup = array();
	var $trial = true;
	var $initial_options_html = array();
	var $imported_images = array();

	var $integrations = array();
    var $blockBuilders = array();
	var $integrationsChecked = false;

	var $version_is_capped = false;

	// @var array $pages Stores all the plugin pages in an array

	// @var array $css_units Stores all the possible CSS units
	var $css_units = array();
	var $folder_item_types = array();
	var $aiData = array();
	//var $css_unit_sets = array();
	// @var array $options Stores the ui options for this plugin
	var $options = array();
	var $preferences = array();
    var $asset_loading_change = false;
    var $html_mods_changed = false;
	//var $placeholderURLs = array();
	var $serialised_post = array();
	var $propertyoptions = array();
	var $en_propertyoptions = array();
	var $property_option_groups = array();
	var $animatable = array();
	var $shorthand = array();
	var $auto_convert_map = array();
	var $legacy_groups = array();
	var $mob_preview = array();
	var $propAliases = array();
	var $cssFuncAliases = array();
	var $input_wrap_templates = array();
	var $suggested_screen_layouts = array();
	var $selector_variations = array();
	var $stylesheet_order_options = array();
	var $page_class_prefix_options = array();
	// @var array $options Stores the "to be merged" options in
	var $to_be_merged = array();
	var $dis_text = '';

	var $pre_update_preferences = array();
	// @var array $file_structure Stores the micro theme dir file structure
	var $file_structure = array();

	// temporarily keep track of the tabs that are available for the property group.
	// This saves additional processing at various stages
	var $current_pg_group_tabs = array();
	var $subgroup = '';
	// default preferences set in constructor
	var $default_preferences = array();
	var $default_preferences_exportable = array();
	var $default_preferences_resetable = array();
	var $default_preferences_dont_reset_or_export = array();
	// edge mode fixed settings
	var $edge_mode = array();

	// default media queries
	var $unq_base = '';
	var $builder_sync_tabs = array();
	var $default_folders = array();
	var $legacy_m_queries = array();
	var $default_mqs = array();
	var $bb_mqs = array();
	var $elementor_mqs = array();

    // Hard code for now, until Gutenberg makes them editable
    var $gutenberg_breakpoints = array(
            'tablet' => array(
                'newPreviewWidth' => 720,
                'max' => 768
            ),
            'mobile' => array(
	            'newPreviewWidth' => 360,
	            'max' => 480
            )
    );
	var $elementor_breakpoints = false;
	var $oxygen_mqs = array();
	var $oxygen_breakpoints = false;
	var $mq_sets = array();
	var $comb_devs = array(); // for storing all-devs + MQs in one array
	// set default custom code options (todo make use of this array throughout the program)
	var $custom_code = array();
	var $custom_code_flat = array();
	var $params_to_strip = array();
    var $level_map = '';
	var $actionHookOrder = 999999;

    // Previously deprecated
    var $longhand;

	// control debug output here

	var $debug_custom = '';
	var $debug_pulled_data = TVR_DEBUG_DATA;
	var $debug_current = TVR_DEBUG_DATA;
	var $debug_import = TVR_DEBUG_DATA;
	var $debug_merge = TVR_DEBUG_DATA;
	var $debug_save = TVR_DEBUG_DATA;
	var $debug_save_package = TVR_DEBUG_DATA;
	var $debug_selective_export = TVR_DEBUG_DATA;
	var $show_me = ''; // for quickly printing vars in the top toolbar
    var $setupError;
    var $maxUploadPrefSize = (1024 * 1024 * 10); // 10MB
    var $initial_preference_options;
    var $autoloadPreferencesList = array(
        'version',
        'num_saves',
	    'initial_scale',
	    'first_and_last',
	    'page_class_prefix',
	    'insert_custom_field_classes',
	    'g_fonts_used',
        'g_url_with_subsets',
        'gfont_subset',
	    'g_url',
	    'stylesheet_in_footer',
	    'stylesheet_order',
	    'global_styles_on_login',
	    'admin_asset_loading',
	    'admin_asset_editing',
        'tailwind',
        'content_addon',
	    'css_addon',
	    'asset_loading',
	    'asset_loading_published',
	    'global_stylesheet_required',
	    'global_stylesheet_required_published',
	    'load_js',
	    'load_js_published',
	    'active_events',
	    'enq_js',
	    'active_scripts_deps',
	    'active_scripts_footer',
        'draft_mode',
        'draft_mode_uids',
	    'admin_bar_preview',
        'top_level_shortcut',
        'sync_browser_tabs',
        'admin_bar_shortcut',
	    'top_level_shortcut',
	    'add_block_classes_all',
        'npm_dependencies',
        'npm_dependencies_published',
        'default_amender_event',
        'm_queries'
    );

	private $reporting = array(
		'max' => array(
			'fileSends' => 10,
			'dataSends' => 1,
            'auto' => array(
                'bytes' => (1024 * 1024 * 1.5), // 1.5MB
                'timeout' => 15
            ),
            'manual' => array(
				'bytes' => null, // we'll check max_post_size at point of send
				'timeout' => 120
			),
		)
	);

    private $errorsRequiringData = array(
		//'frontend.js|306|7|Uncaught ReferenceError: consol is not defined' => 1 // paste error key(s) here
        'microthemer.js|3164|15|Uncaught TypeError: $obj.attr is not a function',
        "deps.js|1|616362|Uncaught TypeError: Cannot read properties of undefined (reading 'split')"
	);

	function __construct(){
		add_action('init', array(&$this, 'init'));
	}

	function check_table_exists($table_name, $also_populated = false){

		global $wpdb;

		$exists = !empty(
		$wpdb->get_var(
			$wpdb->prepare("SHOW TABLES LIKE %s", $table_name)
		)
		);

		if (!$exists || !$also_populated){
			return $exists;
		}

		$wpdb->query("SELECT id FROM $table_name");

		return $exists && $wpdb->num_rows > 0;
	}


	function log_subscription_check($prefix = ''){

		$s = $this->preferences[$prefix . 'subscription'];
		$checks = $this->preferences[$prefix . 'subscription_checks'];
		$pref_array[$prefix . 'subscription_checks'] = $checks;
		$pref_array[$prefix . 'subscription_checks']['num']++;

		// last try, max attempts reached
		// could add a condition for max 3 days after renewal date, but holding off for now
		if ($pref_array[$prefix . 'subscription_checks']['num'] >= $pref_array[$prefix . 'subscription_checks']['max']){
			$pref_array[$prefix . 'subscription_checks']['stop_attempts'] = true;
			$this->savePreferences($pref_array);
			return 'subscription check failed';
		}

		// add some time before next check
		else {
			$extra_seconds = 12 * 60 * 60; // 12 hours

			$initial_time = !empty($checks['next_time']) ? $checks['next_time'] : $this->time;
			$pref_array[$prefix . 'subscription_checks']['next_time'] =
				$initial_time + ($pref_array[$prefix . 'subscription_checks']['num'] * $extra_seconds);
			$this->savePreferences($pref_array);
			return 'defer';
		}

	}

	// check subscription if past renewal_check date
	function maybe_check_subscription($prefix = ''){

		$p = $this->preferences;
		$s = $p[$prefix . 'subscription'];
		$checks = $p[$prefix . 'subscription_checks'];

		// Note: renewal_check is 2 days after their subscription expires (to safely allow for timezone diffs)
		$renewal_time = !empty($checks['next_time']) ? $checks['next_time'] : strtotime($s['renewal_check']);

		// remote check conditions
		$after_renewal_check = (!empty($s['renewal_check']) and $this->time > $renewal_time);
		$higher_than_capped = (!empty($s['capped_version']) and
		                       version_compare($s['capped_version'], $this->version) < 0);
		$retro_check_needed = empty($p[$prefix . 'retro_sub_check_done']);

		// if subscription check needed
		if (
			($after_renewal_check or $higher_than_capped or $retro_check_needed) and
			!empty($p[$prefix . 'buyer_email']) and
			!empty($p[$prefix . 'buyer_validated']) and
			empty($checks['stop_attempts'])
		){
			//$this->show_me.= 'doing auto remote check';
			// check if subscription is still active
			$this->get_validation_response($p[$prefix . 'buyer_email'], 'scheduled', $prefix);
		} else {
			//$this->show_me.= 'NOT doing auto remote check';
		}
	}

	function themeover_connection_url($email, $proxy = false, $prefix = ''){

		$domain = $this->home_url;

		$base_url = ($proxy)
			? 'https://validate.themeover.com/'
			: 'https://themeover.com/wp-content/tvr-auto-update/validate.php';

        /*$test_endpoint = 'https://themeover-updated.test/';
        if ($test_endpoint && TVR_DEV_MODE){
            $base_url = $test_endpoint;
        }*/

		$params = $prefix . 'email='.rawurlencode($email)
		          .'&domain='.$domain
		          .'&mt_version='.$this->version;

		 //Get local URL
        //$this->show_me = $base_url . '?' . $params;

		return $base_url.'?'.$params;

	}


	/**
	 * Connect to themeover directly or via proxy fallback
	 *
	 * @param      $url
	 * @param $email
	 * @param bool $proxy
	 *
	 * @return array
	 */
	function connect_to_themeover($url, $email, $proxy = false, $prefix = '') {

		$response = wp_remote_get($url, [
			'headers' => [
				'Accept' => 'application/json',
				'User-Agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(),
			],
			'timeout' => 15
		]);

		if (is_wp_error($response)) {
			// Optionally handle WP error here
			$responseString = $response->get_error_message();
			$responseCode = 0;
		} else {
			$responseString = wp_remote_retrieve_body($response);
			$responseCode = wp_remote_retrieve_response_code($response);
		}

		$decodedResponse = json_decode($responseString, true);

		// If we have a valid result, or already tried the proxy, return result
		if (!empty($decodedResponse['message']) || $proxy) {
			return array(
				'responseString' => $responseString,
				'url' => $url,
				'code' => $responseCode
			);
		}

		// Retry with proxy if initial attempt fails and proxy hasn't been tried
		return $this->connect_to_themeover(
			$this->themeover_connection_url($email, true, $prefix),
			$email,
			true,
			$prefix
		);
	}

	/*function connect_to_themeover($url, $email, $proxy = false, $prefix = ''){

		//$url = $this->themeover_connection_url($email, $proxy);
		//$responseString = wp_remote_fopen($url);
		$result = $this->wp_remote_fopen($url);
		$responseString = $result['body'];
		$responseCode = $result['code'];
		$response = json_decode($responseString, true);

		//$this->show_me.= 'The response from '. $url . ': '. $responseString;

		// if we have a valid result, or we have already tried the fallback proxy script, return result
		if (!empty($response['message']) or $proxy){
			return array(
				'responseString' => $responseString,
				'url' => $url,
				'code' => $responseCode
			);
		}

		// the initial connection was unsuccessful, possibly due to firewall rules, attempt proxy connection
		else {
			return $this->connect_to_themeover(
				$this->themeover_connection_url($email, true, $prefix),
                $email,
                true,
                $prefix
			);
		}

	}*/

	// check user can unlock / continue using MT
	function get_validation_response($email, $context = 'unlock', $prefix = ''){

		$pref_array = array(
			$prefix . 'buyer_email' => $email
		);
		$was_capped_version = $this->is_capped_version($prefix);
		$response = true;
		//$url = $this->themeover_connection_url($email);
		//$responseString = $this->connect_to_themeover($url, $email);

		$connection_details = $this->connect_to_themeover(
			$this->themeover_connection_url($email, false, $prefix),
			$email,
			$prefix
		);

		//wp_die('<pre>$connection_details: ' . print_r($connection_details, true) . '</pre>');

		$responseString = $connection_details['responseString'];
		$response_code = $connection_details['code'];
		$url = $connection_details['url'];

		//$this->show_me.= '<pre>$connection_details: ' . print_r($connection_details, true) . '</pre>';

		/*$this->innoFirewall = array_merge($connection_details, array(
					'debug' => array(
						'responseString' => $responseString,
						'decodedResponse' => json_decode($responseString, true),
						'altDecoded' => $this->json('decode', $responseString)
					)
				));*/

		// accommodate new json response format
		if ( strpos($responseString, '{') !== true ){
			$response = json_decode($responseString, true);
			$validation = !empty($response['unlock']) ? $response['unlock'] : true;
		} else {
			// old response format - ha! this will never happen, only older versions of MT get old response format
			$validation = $responseString && strlen($responseString) < 2;
		}

		/* Trigger firewall notification screen
		 * $response['message'] = ''; // sebtest
        $connection_details['url'] = 'https://validate.themeover.com/';
		*/

		// if no valid response, check for http issue
		if (empty($response['message'])){

			//$response_code = wp_remote_retrieve_response_code( wp_remote_get($url) ); // now collected in first request

			if ($response_code != 200){
				$response['message'] = 'connection error';
				if (empty($response_code) && !empty($responseString)){
					$response_code  = esc_html($responseString);
				}
			}

			// we may have got a HTML captcha page response, display this
			else {
				$response['message'] = 'possible firewall issue';
				$this->innoFirewall = array_merge($connection_details, array(
					'debug' => array(
						'responseString' => $responseString,
						'decodedResponse' => $response,
						//'altDecoded' => $this->json('decode', $responseString)
					)
				));
			}

			$response['code'] = $response_code;

			// if scheduled subscription check, log num tries and bail if deferring
			if ($context == 'scheduled'){
				$response['message'] = $this->log_subscription_check();
				if ($response['message'] == 'defer'){
					return true;
				}
			}


		}

		// valid response format
		else {

			// save subscription response from server (includes renewal_check date)
			$pref_array[$prefix . 'subscription'] = $response;

			// reset subscription checks if manual unlock attempted
			if ($context == 'unlock'){
				$pref_array[$prefix . 'subscription_checks'] = $this->subscription_check_defaults;
			}

		}

		$this->change_unlock_status($context, $validation, $pref_array, $response, $was_capped_version, $prefix);

        return $validation;
	}


	function change_unlock_status($context, $validation, $pref_array, $response, $was_capped_version, $prefix = ''){

		// regardless of unlock/lock no further need for retrospectively checking their subscription renewal
		$pref_array[$prefix . 'retro_sub_check_done'] = 1;
        $type = $prefix ? 'Amender' : 'Microthemer';

		/* validation success */
		if ($validation) {

			$pref_array[$prefix . 'buyer_validated'] = 1;

			if ($context == 'unlock'){
				if (!$this->preferences[$prefix . 'buyer_validated']) {
                    $pro_features = $type === 'Amender'
                        ? esc_html__('You can now publish your amendments.', 'microthemer')
                        : esc_html__('You can now use pro features.', 'microthemer');
					$this->log(
						$type.' ' . esc_html__('activation successful!', 'microthemer'),
						'<p>' . esc_html__('Your license has been successfully validated. ', 'microthemer') .
						$pro_features . '</p>',
						'notice'
					);
				} else {

					if ($was_capped_version){
						if (empty($response['capped_version'])){
							$this->log(
								$type . ' ' . esc_html__('updates enabled', 'microthemer'),
								'<p>' . esc_html__('You can now get the latest features.', 'microthemer') . '</p>',
								'notice'
							);
						} else {
							$this->log(
								$type . ' ' . esc_html__('updates still limited ', 'microthemer'),
								'<p>' . esc_html__('Your subscription must be renewed on themeover.com to enable modern features.', 'microthemer') . '</p>',
								'warning'
							);
						}
					}

					else {
						$this->log(
							$type . ' ' . esc_html__('already validated', 'microthemer'),
							'<p>' . esc_html__('Your license has already been validated. The pro version is currently active.', 'microthemer') . '</p>',
							'notice'
						);
					}
				}
			}
		}


		/* validation fail */
		else {

			// do checks on why validation failed here and report to user
			$pref_array[$prefix . 'buyer_validated'] = 0;

			// prevent future subscription checks as we're already in free trial mode
			$pref_array[$prefix . 'subscription']['renewal_check'] = false;

			$explain = '';
			$title_prefix = $type . ' ' . ($context == 'unlock' ? 'activation failed' : 'notice');

			// check for returned message to give clue about problem
			if (!empty($response['message'])){

				//$response['message'] = 'Message <b>Fom another</b> site';

				$title = $title_prefix . ' - ' . strip_tags($response['message']);

				switch ($response['message']) {

					case "missing info":
						$explain = "<p>The required unlock credentials were not provided.</p>";
						break;

					case "invalid credentials":
						$explain = '<p>The unlock credentials were invalid. Make sure you are entering 
                                        the license key shown in 
                                        <a target="_blank" href="https://themeover.com/my-account/">My Downloads</a></p>';
						break;

					case "subscription expired":
						$explain = '<p>Your subscription has expired. This means you can only  
                                        use Microthemer in free trial mode. To continue using Microthemer in 
                                        full capacity please renew or upgrade via  
                                        <a target="_blank" href="https://themeover.com/my-account/">My Downloads</a></p>';
						break;

					case "incorrect version":
						$explain = '<p>Your expired subscription does not allow you to use this version 
                                        ('.$this->version.') of Microthemer. You are eligible to use version '
						           .$response['capped_version'].', which you can download from  
                                        <a target="_blank" href="https://themeover.com/my-account/">My Downloads</a>. 
                                        You can also renew or upgrade your subscription from there if you want to 
                                        use this version of Microthemer.</p>';
						break;

					case "please upgrade":

						$limit = intval($response['limit']);
						$limit_lang = $limit === 1 ? 'domain' : 'domains';

						$explain = '<p>Domain limit ('.intval($response['limit']).') reached. Your license permits installing '.$type.' on '.intval($response['limit']).' '.$limit_lang.' in total, not '.intval($response['limit']).' '.$limit_lang.' at any one time.</p>';

						// extra text if they have already reached their limit
						/*if (count($response['domains']) > 3){
									$explain.= '<p>We started enforcing this restriction after learning that a
                                            few people have been unclear about the terms
                                            of the standard license. No worries if this includes you.</p>';
								}*/

						$explain.= '<p><a class="tvr-button" target="_blank" 
                                        href="https://themeover.com/my-account/">Please upgrade 
                                        to use '.$type.' on this domain</a></p>
                                        
                                        <h3>Domains you have installed '.$type.' on</h3>';

						// display domains
						$domains = '';
						foreach ($response['domains'] as $key => $arr){
							$domains.= '
                            <li>
                                <span class="domain-name">' . $arr['domain'] . '</span>
                                <span class="install-date">' . $arr['date'] . '</span>
                            </li>';
						}

						$explain.= '<ol>' . $domains . '</ol>';

						break;


					case "connection error":
					case "proxy connection error":
					case "subscription check failed":
						$code_message = !empty($response_code) ? 'HTTP response code: '.esc_html($response_code) : '';
						$explain = '<p>The connection to themeover.com was unsuccessful. 
                                '.$code_message.'</p>
                            
                                <p>The connection to Themeover\'s server may have failed due to an 
                                intermittent network error. Please ensure you are connected to the internet, 
                                if working from localhost. <span class="link show-dialog" 
                                rel="unlock-microthemer">Resubmitting your license key one 
                                more time</span> may do the trick</p>
                                
                                <p>Or try <b>disabling any security plugins</b> that may be 
                                blocking Microthemer\'s outbound connection. You can re-enable them after you 
                                unlock Microthemer</p>
                                
                                <p>Finally, security settings on your server may block all outgoing PHP 
                                connections to domains not on a trusted whitelist (e.g. sites that are not 
                                wordpress.org). Ask your web host about temporarily unblocking themeover.com.</p>';
						break;
                    default:
                        // If is not an MT response, but it includes a message
                        $explain = '<p>Please reference the following IP address if you need to <a target="_blank" href="https://themeover.com/support/contact/">contact support</a></p>
                        <p>IP Address: <b>'.esc_html(file_get_contents("http://ipecho.net/plain")).'</b></p>';
                        break;

				}

			}

			// unknown error
			else {
				$title = $title_prefix;
				$explain = '<p>Your license could not be validated. Make sure you are entering 
                                 the unlock code shown in <a target="_blank" href="https://themeover.com/my-account/">
                                 My Downloads</a>. If you are still stuck,  
                                <a target="_blank" href="https://themeover.com/support/contact/">please contact 
                                support for assistance</a></p>';
			}

			$this->log($title, $explain);
		}


		if (!$this->savePreferences($pref_array)) {
			$this->log(
				esc_html__('Unlock status not saved', 'microthemer'),
				'<p>' . esc_html__('Your validation status could not be saved. The program may need to be unlocked again.', 'microthemer') . '</p>'
			);
		}

		return $pref_array['subscription'];
	}

	function invalidLic(){

		$invalid = array(
			'c9f79e3d1874f6cc044513ab5357c63d'
		);

		$current = md5($this->preferences['buyer_email']);

		if ( in_array($current, $invalid) ){

			$pref_array = array(
				'buyer_email' => '',
				'buyer_validated' => 0,
				'used_invalid' => 1,
				'subscription' => array(
					'unlock' => false,
					'renewal_check' => false,
					'message' => ''
				)
			);

			$this->savePreferences($pref_array);

			return true;
		}

		return false;
	}

    function dialogSectionTabs($data){

        $html = '<div class="mt-dialog-section">
        <div class="dialog-section-tabs query-tabs">';

        // tabs
	    $i = 0;
	    foreach ($data as $key => $array){
            if (!empty($array['skip'])){
                continue;
            }
		    $active = $i === 0 ? 'active ' : '';
		    $html.= ' 
             <span class="'.$active.' mt-tab tab-'.$key.' dialog-section-tab dialog-section-tab-'.$i.' " rel="'.$i.'">
               '.$array['title'].'
             </span>';
            $i++;
	    }
	    $html.= '</div>
	    <div class="dialog-tab-fields">';

        // Content
	    $i = 0;
	    foreach ($data as $key => $array){
		    if (!empty($array['skip'])){
			    continue;
		    }
		    $active = $i === 0 ? 'show ' : '';
		    $html.= ' 
             <div class="'.$active.'  tab-content-'.$key.' dialog-section-tab-field dialog-section-tab-field-'.$i.' hidden">
               '.$array['content'].'
             </div>';
		    $i++;
	    }
	    $html.= '</div>
	    </div>';

        return $html;
    }

    function generateVideoButtons($videos){

        $html = '';

	    foreach ($videos as $slug => $title){

		    $thumb_class = isset($this->preferences['external_videos']['last_viewed'])
		                   && $slug === $this->preferences['external_videos']['last_viewed']
			    ? ' mt-last-viewed-video'
			    : '';

		    $pos_title = esc_attr__('Watch video', 'microthemer');
		    $neg_title = esc_attr__('Unmark video as watched', 'microthemer');
		    $tooltip = $pos_title;
		    if (!empty($this->preferences['external_videos']['watched'][$slug])){
			    $thumb_class.= ' mt-watched-video';
			    $tooltip = $neg_title;
		    }

		    $icon = $this->iconFont('play', array(
			    'class' => "external-video-watched-toggle",
			    'title' => $tooltip,
			    'data-pos' => $pos_title,
			    'data-neg' => $neg_title
		    ));

		    $html.= '
		    <a class="external-video-thumb'.$thumb_class.'" target="_blank" href="https://themeover.com/'.$slug.'/" data-slug="'.$slug.'">'
               .$icon.
               '<span class="video-label">'.$title.'</span>' .
           '</a>';
	    }

        return $html;
    }

	// set defaults for user's property preferences (this runs on every page load)
	function maybe_set_my_props_defaults(){

		$log = array(
			'update2' => false
		);

		// for resetting during development
		/*unset($this->preferences['layout']);
				$this->preferences['my_props'] = array();
				$this->preferences['default_sug_values_set'] = 0;*/

		foreach ($this->propertyoptions as $prop_group => $array){

			foreach ($array as $prop => $meta) {

				// we're only interested in props with default units or suggested values
				if ( !isset($meta['default_unit']) and empty($meta['sug_values']) ){
					continue;
				}

				// ensure that the default unit is set, this will cater for new props too
				if (isset($meta['default_unit']) and
				    (!isset($this->preferences['my_props'][$prop_group]['pg_props'][$prop]['default_unit']) or
				     $this->preferences['my_props'][$prop_group]['pg_props'][$prop]['default_unit'] === 'px (implicit)') // legacy system default
				){
					$log['update2'] = true;
					$default_unit = $meta['default_unit']; //$this->is_time_prop($prop) ? 's' : 'px (implicit)';
					$this->preferences['my_props'][$prop_group]['pg_props'][$prop]['default_unit'] = $default_unit;
				}

				// ensure that the suggested values array is set, this will cater for new props too
				$log = $this->prepare_sug_values($log, $meta, $prop, '');
				$log = $this->prepare_sug_values($log, $meta, $prop, '_extra');
			}
		}

		// Save if changes were made to my_props
		if ($log['update2']){

			$this->savePreferences(
				array(
					'my_props' =>  $this->preferences['my_props'],
					'units_added_to_suggestions' => $this->preferences['units_added_to_suggestions'],
                    'subgrid_added_to_suggestions' => $this->preferences['subgrid_added_to_suggestions']
				)
			);

			return true;
		}

		return false;
	}

	function unitCouldBeAdded($value, $unit){

		$isTimeUnit = ($unit === 's' || $unit === 'ms');

		return ( $isTimeUnit || ($value != 0 && $value !== '0') ) && is_numeric($value);
	}

	// Apply the default unit as set in MT to the suggestions (before we mostly had pixels and no unit)
	function ensureSuggestionsHaveUnits(&$suggestions, $meta){

		// if property supports units
		if (isset($meta['default_unit'])){

			$factoryDefaultUnit = $meta['default_unit'];

			foreach ($suggestions as $i => $value){

				if ( $this->unitCouldBeAdded($value, $factoryDefaultUnit) ){
					$suggestions[$i] = $value . $factoryDefaultUnit;
				}
			}
		}
	}

	function prepare_sug_values($log, $meta, $prop, $extra = ''){

		if ( !empty($meta['sug_values'.$extra]) ){

			// empty arrays by default
			$recent = $sampled = $saved = array();

			// copy MT default suggestions to sug_values key
			$sug_by_default = true; // do show suggestions by default
			$copiedSrc = ($sug_by_default and !empty($meta['select_options'.$extra]))
				? $meta['select_options'.$extra]
				: array();
			$this->ensureSuggestionsHaveUnits($copiedSrc, $meta);

			// note, this system allows EITHER root cat (only used for color) or prop
			$root_cat = !empty($meta['sug_values'.$extra]['root_cat'])
				? $meta['sug_values'.$extra]['root_cat']
				: $prop;

			/* New structure conversion - if root_cat is simple numerical array  */
			if (isset($this->preferences['my_props']['sug_values'][$root_cat]) and
			    (!count($this->preferences['my_props']['sug_values'][$root_cat]) or
			     is_int(key($this->preferences['my_props']['sug_values'][$root_cat])))
			){

				if ($root_cat == 'color') {

					// we only need to grab color once
					if (!empty($log['color_done'])){
						return $log;
					}

					$recent = $this->preferences['my_props']['sug_values'][$root_cat];
					if ( isset($this->preferences['my_props']['sug_values']['site_colors']) ){
						$sampled = $this->preferences['my_props']['sug_values']['site_colors'];
						unset($this->preferences['my_props']['sug_values']['site_colors']);
					} if ( isset($this->preferences['my_props']['sug_values']['saved_colors']) ){
						$saved = $this->preferences['my_props']['sug_values']['saved_colors'];
						unset($this->preferences['my_props']['sug_values']['saved_colors']);
					}

					$log['color_done'] = true;

				}

				// prepare arrays
				$this->preferences['my_props']['sug_values'][$root_cat] = array(
					'recent' => $recent,
					'sampled' => $sampled,
					'saved' => $saved,
					'copiedSrc' => $copiedSrc
				);

				$log['update2'] = true;
			}
			/* End new structure conversion*/

			// no conversion necessary
			else {

				// set root array if not already
				if (!isset($this->preferences['my_props']['sug_values'][$root_cat]) ){

					$this->preferences['my_props']['sug_values'][$root_cat] = array(
						'recent' => $recent,
						'sampled' => $sampled,
						'saved' => $saved,
						'copiedSrc' => $copiedSrc
					);

					// color will be set now, and doesn't need conversion true above
					if ($root_cat === 'color'){
						$log['color_done'] = true;
					}

					$log['update2'] = true;
				}

				// the root array is set
				else {

					// set copiedSrc if not already
					if (!isset($this->preferences['my_props']['sug_values'][$root_cat]['copiedSrc']) ){
						$this->preferences['my_props']['sug_values'][$root_cat]['copiedSrc'] = $copiedSrc;
						$log['update2'] = true;
					}

					// make sure grid-template-columns/rows have subgrid now it's widely supported
					if ( $this->preferences['subgrid_added_to_suggestions'] < 2 ){
                        if ($root_cat === 'grid_template_columns' || $root_cat === 'grid_template_rows'){
                            $this->preferences['subgrid_added_to_suggestions']++;
                            if (!in_array('subgrid', $copiedSrc)){
	                            array_unshift($copiedSrc, "subgrid");
	                            $this->preferences['my_props']['sug_values'][$root_cat]['copiedSrc'] = $copiedSrc;
                            }

	                        $log['update2'] = true;
                        }
					}

					// ensure units have been explicitly added (this came later)
					if ( empty($this->preferences['units_added_to_suggestions']) ){

						$sug_values = $this->preferences['my_props']['sug_values'][$root_cat];

						foreach($sug_values as $sug_key => $suggestions){
							if ($sug_key !== 'sampled' and count($suggestions)){

								$this->ensureSuggestionsHaveUnits(
									$this->preferences['my_props']['sug_values'][$root_cat][$sug_key], $meta
								);
							}
						}

						$this->preferences['units_added_to_suggestions'] = 1;
						$log['update2'] = true;
					}
				}
			}
		}

		return $log;
	}


	// check if an url is valid
	function is_valid_url( $url ) {
		if ( '' != $url ) {
			/* Using a HEAD request, we'll be able to know if the URL actually exists.
					 * the reason we're not using a GET request is because it might take (much) longer. */
			$response = wp_remote_head( $url, array( 'timeout' => 3 ) );
			/* We'll match these status codes against the HTTP response. */
			$accepted_status_codes = array( 200, 301, 302 );

			/* If no error occured and the status code matches one of the above, go on... */
			if ( ! is_wp_error( $response ) &&
			     in_array( wp_remote_retrieve_response_code( $response ), $accepted_status_codes ) ) {
				/* Target URL exists. Let's return the (working) URL */
				return $url;
			}
			/* If we have reached this point, it means that either the HEAD request didn't work or that the URL
					 * doesn't exist. This is a fallback so we don't show the malformed URL */
			return '';
		}
		return $url;
	}




	// load full set of suggested CSS units
	function update_my_prop_default_units($new_css_units){

		$first_in_group_val = '';

		foreach ($this->preferences['my_props'] as $prop_group => $array){

			if ($prop_group == 'sug_values' ||
			    empty($this->preferences['my_props'][$prop_group]['pg_props'])
			) {
				continue;
			}

			foreach ($this->preferences['my_props'][$prop_group]['pg_props'] as $prop => $arr){

				// skip props with no default unit
				if (!isset($this->propertyoptions[$prop_group][$prop]['default_unit'])
				    or $this->is_non_length_unit($this->propertyoptions[$prop_group][$prop]['default_unit'], $prop)
				){
					continue;
				}

				// get unit
				$new_unit = isset($new_css_units[$prop_group][$prop])
					? $new_css_units[$prop_group][$prop]
					: '';

				// correct for line-height
				if ($new_unit == 'none'){
					$new_unit = '';
				}

				// set all related the same
				$box_model_rel = false;

				if (!empty($this->propertyoptions[$prop_group][$prop]['rel'])){
					$box_model_rel = $this->propertyoptions[$prop_group][$prop]['rel'];
				} elseif (!empty($this->propertyoptions[$prop_group][$prop]['unit_rel'])){
					$box_model_rel = $this->propertyoptions[$prop_group][$prop]['unit_rel'];
				}

				/*if (!empty($this->propertyoptions[$prop_group][$prop]['sub_label'])){
							$first_in_group_val = $new_unit;
						}*/
				if (!empty($this->propertyoptions[$prop_group][$prop]['unit_sub_label'])){
					$first_in_group_val = $new_unit;
				} elseif (!empty($this->propertyoptions[$prop_group][$prop]['sub_label'])){
					$first_in_group_val = $new_unit;
				}

				if ($box_model_rel){
					$new_unit = $first_in_group_val;
				}

				$this->preferences['my_props'][$prop_group]['pg_props'][$prop]['default_unit'] = $new_unit;

			}
		}

		return $this->preferences['my_props'];
	}

	// ensure all preferences are defined
	function ensure_defined_preferences($full_preferences, $pd_context){

		//$this->preferences = array(); // for testing with fresh

		// copy previous preferences for history backup
		$this->pre_update_preferences = $this->preferences;

		// backup previous version settings as special history entry if new version
		if ($this->new_version && $pd_context == 'microthemer_activated_or_updated'){
			$this->pre_upgrade_backup();
		}

		// check if all preference are defined
		//unset($this->preferences['layout']); // for debugging
		$pref_array = array();
		$update = false;
		foreach ($full_preferences as $key => $value){
			if (!isset($this->preferences[$key])){

                // load_js_published is a new preference, but the default value depends on the value for 'load_js'
                if ($key === 'load_js_published' && isset($this->preferences['load_js'])){
                    $value = $this->preferences['load_js'];
                }

				$pref_array[$key] = $value;

				$update = true;
			}
		}

		// maybe reset the reporting stats for the day
        $today = date('Y-m-d');
		$pref_array['reporting'] = isset($this->preferences['reporting'])
            ? $this->preferences['reporting']
            : $full_preferences['reporting'];

        // fix missing property added later
        if (!isset($pref_array['reporting']['file']['unique_errors'])){
	        $pref_array['reporting']['file']['unique_errors'] = (object) array();
	        $update = true;
        }

        foreach(array('file', 'data') as $reportType){
	        $last_sent = $pref_array['reporting'][$reportType]['last_sent'];
            if ($last_sent != $today){

	            $pref_array['reporting'][$reportType] = array(
                   'last_sent' => $today,
                   'sends_today' => 0,
                );

                if ($reportType === 'file'){
	                $pref_array['reporting'][$reportType]['unique_errors'] = (object) array();
                }

	            $update = true;
            }
        }

		// save new preference definitions if found
		if ($update) {
			$this->savePreferences($pref_array);
		}

		// new CSS props will be added over time and the default unit etc must be assigned.
		$this->maybe_set_my_props_defaults();

		// convert user's non_section config to modern format with meta holding little values
		// meta always gets sent in ajax save
		$this->maybe_do_data_conversions_for_update();

		// ensure view_import_stylesheets list has current theme style.css (saves preferences too)
		$this->update_css_import_urls(get_stylesheet_directory_uri() . '/style.css', 'ensure');
	}

	// create a backup of the user's settings in history and as a design pack
	function pre_upgrade_backup(){

		// no need to backup if no settings have been saved
		global $wpdb;
		$this->maybeCreateOrUpdateRevsTable(); // only creates table if doesn't exist or needs updating
		$wpdb->get_results("select id from ".$wpdb->prefix . "micro_revisions");

		$previous_version = !empty($this->preferences['previous_version'])
			? $this->preferences['previous_version']
			: 'Previous version';

		if ($wpdb->num_rows > 0){

			// add settings before update to revision table, include preferences in this special revision.
			if (!$this->updateRevisions(
				$this->options,
				$this->json_format_ua(
					'display-preferences lg-icon',
					esc_html__($previous_version.' settings (before updating to '.$this->version.')',
						'microthemer')
				),
				true, // otherwise error on new installs
				$this->preferences, //$backup_preferences,
				true
			)) {
				$this->log('','','error', 'revisions');
			}

			// clean any pre-update packs MT created when it was using that system
			$this->clean_pre_upgrade_backup_packs();

			// export the old settings too, to ensure history doesn't get wiped
			// no, history will suffice as pre_upgrade only gets cleared when another upgrade happens
			// also this was creating mulitple packs as $alt_name was preventing overwrite
			//$this->update_json_file('Pre-upgrade backup settings', 'new', true, $this->preferences);
		}

	}

	// clean any pre-update packs MT created when it using that system
	function clean_pre_upgrade_backup_packs(){

		$pattern = '/pre-upgrade-backup-settings(?:-\d)?/';

		// loop packs
		foreach ($this->file_structure as $dir => $array) {

			// delete matches
			if (preg_match($pattern, $dir)) {
				$this->tvr_delete_micro_theme($dir);
			}
		}
	}




	// manually override user preferences here after code changes
	function maybe_manually_override_preferences(){

		$update_prefs = false;
		if (!empty($this->preferences['pseudo_classes']) and count($this->preferences['pseudo_classes'])){
			$this->preferences['pseudo_classes'] = array();
			$update_prefs = true;
		} if (!empty($this->preferences['pseudo_elements']) and count($this->preferences['pseudo_elements'])){
			$this->preferences['pseudo_elements'] = array();
			$update_prefs = true;
		}

        // Ensure the correct property group is active for the plugin type
        if (!$this->hasCSSCapability() && $this->preferences['pg_focus'] !== 'html'){
			$this->preferences['pg_focus'] = 'html';
			$update_prefs = true;
        } if (!$this->hasContentCapability() && $this->preferences['pg_focus'] === 'html'){
			$this->preferences['pg_focus'] = 'font';
			$update_prefs = true;
		}

		if ($update_prefs){
			$this->savePreferences($this->preferences);
		}

		// Version 7 has targeting enabled by default, but this used to be hard set to 0.
		// So now we hard set to 'on' for initial load unless the user wants it off by default;
		$this->preferences['hover_inspect'] = !empty($this->preferences['hover_inspect_off_initially']) ? 0 : 1;


	}

	// update viewed_import_stylesheets list array
	function update_css_import_urls($url, $context = 'make top'){

		// if url is already in the array, ensure it's at the top
		$curKey = array_search($url, $this->preferences['viewed_import_stylesheets']);
		if ($context == 'make top'){
			if ($curKey !== false){
				array_splice( $this->preferences['viewed_import_stylesheets'], $curKey, 1 );
			}
			array_unshift( $this->preferences['viewed_import_stylesheets'], $url );
		}

		// unless we're just ensuring e.g. the theme's style.css is in the list
		elseif ($context == 'ensure'){
			if ( !in_array($url, $this->preferences['viewed_import_stylesheets']) ){
				$this->preferences['viewed_import_stylesheets'][] = $url;
			}
		}

		// ensure only 20 items
		$i = 0;
		$pref_array['viewed_import_stylesheets'] = array();
		foreach ($this->preferences['viewed_import_stylesheets'] as $key => $css_url){
			if (++$i > 20) break;
			$pref_array['viewed_import_stylesheets'][] = $css_url;
		}

		//$pref_array['viewed_import_stylesheets'] = array();
		$this->savePreferences($pref_array);
	}


    function customScriptAtts($tag, $handle, $src){
        if ($handle === 'tvr_mod_firebase'){
	        $tag = str_replace('<script ', '<script type="module" ', $tag);
        }
	    return $tag;
    }

	// add js
	function add_js() {

		// allow style tag atts to be updated if they are async
		add_filter( 'script_loader_tag', array(&$this, 'customScriptAtts'), 10, 4 );

		// Run pre-wordPress 5.6 jQuery and jQuery UI (temp fix for users with sites that still have issues)
		$runLegacyJquery = !empty($this->preferences['wp55_jquery_version']);

		// jQuery UI scripts
		$jqueryUIScripts = array(
			'core',
			'widget',
			'mouse',
			'sortable',
			'position',
			'slider',
			'menu',
			'autocomplete',
			'button',
			'tooltip',
			'draggable',
			'resizable',
		);

		// core jQuery and migrate script
		$jquery_scripts = array(
			array(
				'h' => 'jquery',
				'alwaysInc' => 1,
				'dequeue' => $runLegacyJquery,
				'f' => $runLegacyJquery
					? '../js-min/legacy-jquery/jquery.js'
					: false,
				//'in_footer' => true
			),
			array(
				'h' => 'jquery-migrate',
				'dep' => 'jquery',
				'alwaysInc' => 1,
				'dequeue' => $runLegacyJquery,
				'f' => ($runLegacyJquery)
					? '../js-min/legacy-jquery/jquery-migrate-1.4.1-wp.js'
					: false,
				//'in_footer' => true
			)
		);

		$prevScript = false;
		foreach ($jqueryUIScripts as $jqi){

			$jqueryUIDeps = array('jquery', 'jquery-migrate');
			if (!empty($prevScript)){
				$jqueryUIDeps[] = 'jquery-ui-core';
				$jqueryUIDeps[] = 'jquery-ui-'.$prevScript;
			}

			$jquery_scripts[] = array(
				'h' => 'jquery-ui-'.$jqi,
				'dep' => $jqueryUIDeps,
				'alwaysInc' => 1,
				'dequeue' => $runLegacyJquery,
				'f' => $runLegacyJquery
					? '../js-min/legacy-jquery/jquery-ui/'.$jqi.'.min.js'
					: false,
				//'in_footer' => false

			);

			$prevScript = $jqi;
		}

        // If Amender, load TinyMCE
		$this->contentMethod('loadTinyMCE');

        // Always load media manager (for MT background images, or Amender image upload)
		wp_enqueue_media();

		// script map
		$mt_scripts = array(

			//array('h' => 'jquery', 'alwaysInc' => 1),

			// WP 5.5 removed the migrate helper, which caused some issues for the autocomplete menu
			// e.g. clearing a single suggestion also selected the cleared item, thus returning it to the suggestions
			//array('h' => 'mt-jquery-migrate', 'alwaysInc' => 1, 'f' => '../js-min/jquery-migrate-1.4.1-wp.js'),

			// jquery/ui
			/*array('h' => 'jquery-ui-core', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-position', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-sortable', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-slider', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-menu', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-autocomplete', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-button', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-tooltip', 'dep' => 'jquery', 'alwaysInc' => 1),

					// essential for gridstack
					array('h' => 'jquery-ui-widget', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-mouse', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-draggable', 'dep' => 'jquery', 'alwaysInc' => 1),
					array('h' => 'jquery-ui-resizable', 'dep' => 'jquery', 'alwaysInc' => 1),*/


			// mt core namespace
			array('h' => 'tvr_core', 'f' => 'mt-core.js'),
			array('h' => 'tvr_mcth_cssprops', 'f' => 'data/program-data.js'), // this will be dyn soon

			// js libraries (prefix name with mt- if I've edited the library)
			// use ace2, ace4 and have /ace as sub dir for easy globs in gulp file
			array('h' => 'tvr_ace', 'f' => 'lib/ace4/ace/ace.js'),
			array('h' => 'tvr_ace_lang', 'f' => 'lib/ace4/ace/ext-language_tools.js'),
			array('h' => 'tvr_ace_search', 'f' => 'lib/ace4/ace/ext-searchbox.js'),

			// this must be loaded in head so we can extend it
			//array('h' => 'tvr_ace_html', 'f' => 'lib/ace4/ace/mode-html.js'),

			// conditionally load emmet in build version as it's quite big - come back to this
			//array('h' => 'tvr_emmet', 'f' => 'lib/emmet/emmet.js'),
			//array('h' => 'tvr_ace_emmet', 'f' => 'lib/ace4/ace/ext-emmet.js'),

			//array('h' => 'tvr_gsap', 'f' => 'lib/gsap/gsap.min.js'), // not in use yet
			/*array('h' => 'tvr_widget', 'f' => '../src/js/mt-widget.js'),
					array('h' => 'tvr_transform', 'f' => '../src/js/mt-transform.js'),*/
			array('h' => 'tvr_gridstack', 'f' => 'lib/gridstack/gridstack.js'),
			array('h' => 'tvr_gridstack_ui', 'f' => 'lib/gridstack/gridstack.jQueryUI.js'),
			array('h' => 'tvr_extend_regexp', 'f' => 'lib/extend-native-regexp.js'),
			//array('h' => 'tvr_mcth_colorbox', 'f' => 'lib/colorbox/1.6.4/jquery.colorbox-min.js'),
			array('h' => 'tvr_spectrum', 'f' => 'lib/colorpicker/mt-spectrum.js', 'dep' => array( 'jquery' )),

			// https://github.com/beautify-web/js-beautify
			array('h' => 'tvr_html_beautify', 'f' => 'worker/beautify-html.min.js'),
			//array('h' => 'tvr_js_beautify', 'f' => 'lib/js-beautify/beautify.js'),
			array('h' => 'tvr_css_beautify', 'f' => 'lib/js-beautify/beautify-css.js'),

			array('h' => 'tvr_sprintf', 'f' => 'lib/sprintf/sprintf.min.js'),
			array('h' => 'tvr_parser', 'f' => 'lib/parser.js'),
			//array('h' => 'tvr_ast_query', 'f' => 'lib/query-ast.js'), // doesn't play well with gonz
			array('h' => 'tvr_scss_parser', 'f' => 'lib/gonzales.js'),
			array('h' => 'tvr_cssutilities', 'f' => 'lib/mt-cssutilities.js'), // mt-css-to-xpath
			array('h' => 'tvr_xpath', 'f' => 'lib/mt-css-to-xpath.js'),
			//array('h' => 'tvr_cssutilities', 'f' => 'lib/CSSUtilities.js'), // for comparing customised

			// try out a new sortable library as jquery seems buggy when there are lots of selectors
			//array('h' => 'tvr_sortable', 'f' => 'lib/sortable/mt-sortable.js'),
			array('h' => 'tvr_sortable', 'f' => 'lib/sortable/mt-sortable-1.13.js'),

			// custom modules
			array('h' => 'tvr_main_and_worker', 'f' => '../js/worker/mt-main-and-worker.js'),
			array('h' => 'tvr_utilities', 'f' => 'mod/mt-utilities.js'),
			array('h' => 'tvr_widget', 'f' => 'mod/mt-widget.js'),
			array('h' => 'tvr_layout', 'f' => 'mod/mt-layout.js', 'page' => array(
				$this->microthemeruipage
			)),
			//array('h' => 'tvr_transform', 'f' => '../src/js/mt-transform.js'),
			array('h' => 'tvr_init', 'f' => 'mod/mt-init.js'),
			array('h' => 'tvr_mod_ace', 'f' => 'mod/mt-ace.js'),
			array('h' => 'tvr_mod_integrate', 'f' => 'mod/mt-integrate.js'),
			array('h' => 'tvr_mod_loop', 'f' => 'mod/mt-loop.js'),
			array('h' => 'tvr_mod_dd', 'f' => 'mod/mt-dom-data.js'),
			array('h' => 'tvr_mod_save', 'f' => 'mod/mt-save.js'),
			array('h' => 'tvr_mod_sass', 'f' => 'mod/mt-sass.js'),
			array('h' => 'tvr_mod_grid', 'f' => 'mod/mt-grid.js'),
			array('h' => 'tvr_mod_folder', 'f' => 'mod/mt-folder.js'),
			array('h' => 'tvr_mod_html', 'f' => 'mod/mt-html.js'),

			array('h' => 'tvr_mod_firebase', 'f' => 'mod/mt-ai-deps.js', 'page' => array($this->microthemeruipage), 'in_footer' => 1, 'strategy' => 'defer'),
            array('h' => 'tvr_mod_ai', 'f' => 'mod/mt-ai.js', 'page' => array($this->microthemeruipage)),

            array('h' => 'tvr_mod_local', 'f' => 'mod/mt-local.js',
			      'page' => array(
				      $this->microthemeruipage,
				      $this->detachedpreviewpage
			      )),

			// page specific (non-min)
			array('h' => 'tvr_main_ui', 'f' => 'page/microthemer.js', 'page' => array($this->microthemeruipage)),
			array('h' => 'tvr_broadcast', 'f' => 'mod/mt-broadcast.js', 'page' => array($this->microthemeruipage)),
			array('h' => 'tvr_man', 'f' => 'page/packs.js', 'page' => 'other'),
			array('h' => 'tvr_fonts', 'f' => 'page/fonts.js', 'page' => array($this->fontspage)),
			array('h' => 'tvr_detached', 'f' => 'page/detached-preview.js', 'page' => array($this->detachedpreviewpage)),

			// min (deps.js combines all libraries and includes
			// apart from ace that didn't concat well with it's web workers for some reason
			array('h' => 'tvr_ace', 'f' => '../js-min/ace/ace.js', 'min' => 1),
			array('h' => 'tvr_ace_lang', 'f' => '../js-min/ace/ext-language_tools.js', 'min' => 1),
			array('h' => 'tvr_ace_search', 'f' => '../js-min/ace/ext-searchbox.js', 'min' => 1),

			// this must be loaded in head so we can extend it
			//array('h' => 'tvr_ace_html', 'f' => '../js-min/ace/mode-html.js', 'min' => 1),

			// page specific (min)
			array('h' => 'tvr_mod_firebase', 'f' => '../js-min/mt-ai-deps.js', 'page' => array($this->microthemeruipage), 'in_footer' => 1, 'strategy' => 'defer', 'min' => 1),
			array('h' => 'tvr_sassjs', 'f' => '../js-min/sass/sass.js', 'alwaysInc' => 1, 'ifSASS'),
			array('h' => 'tvr_deps', 'f' => '../js-min/deps.js', 'min' => 1),
			array('h' => 'tvr_mcth_cssprops', 'f' => '../js-min/program-data.js', 'min' => 1,
			      'skipScript' => !empty($this->preferences['inlineJsProgData']) ),
			array('h' => 'tvr_main_ui', 'f' => '../js-min/microthemer.js', 'min' => 1, 'page' => array($this->microthemeruipage)),
			array('h' => 'tvr_broadcast', 'f' => '../js-min/mt-broadcast.js', 'min' => 1, 'page' => array($this->microthemeruipage)),
			array('h' => 'tvr_man', 'f' => '../js-min/packs.js', 'min' => 1, 'page' => 'other'),
			array('h' => 'tvr_fonts', 'f' => '../js-min/fonts.js', 'min' => 1, 'page' => array($this->fontspage)),
			array('h' => 'tvr_detached', 'f' => '../js-min/detached-preview.js', 'min' => 1, 'page' => array($this->detachedpreviewpage)),

		);

		// combine jQuery and other scripts
		$scripts = array_merge($jquery_scripts, $mt_scripts);
		//$scripts = $mt_scripts;

		//wp_die('<pre>'.print_r($scripts, true).'</pre>');

		// output scripts based on various conditions
		$js_path = $this->thispluginurl.'js/';
		$v = '?v='.$this->version;
		foreach ($scripts as $key => $arr){
			$dep = !empty($arr['dep']) ? $arr['dep'] : false;
			$do_script = true;

			// filter out page specific scripts
			if (!empty($arr['page'])){
				if ( is_array($arr['page']) and !in_array($_GET['page'], $arr['page'])){
					$do_script = false;
				}
				if ($arr['page'] == 'other' and
				    ($_GET['page'] == $this->microthemeruipage or
				     $_GET['page'] == $this->fontspage or
				     $_GET['page'] == $this->detachedpreviewpage
				    )){
					$do_script = false;
				}
			}

			// only show correct script for dev/production
			if ( empty($arr['alwaysInc']) ) {
				if ((TVR_DEV_MODE and !empty($arr['min']))
				    or (!TVR_DEV_MODE and empty($arr['min']))
				    or !empty($arr['skipScript'])
				){
					$do_script = false;
				}
			}

			// always inc - check condition
			else {

				// skip sass.js if not enabled
				if ( $arr['alwaysInc'] === 'ifSASS' && !$this->preferences['allow_scss']){
					$do_script = false;
				}
			}

			// register/enqueue
			if ($do_script){
				if (!empty($arr['dequeue'])){
					//wp_dequeue_script( $arr['h'] );
					wp_scripts()->remove( $arr['h'] );
				}
				if (!empty($arr['f'])){
                    $file = $js_path . $arr['f'] . $v;
                    if (strpos($arr['f'], 'https') !== false){
	                    $file = $arr['f'];
                    }
					wp_register_script( $arr['h'], $file , $dep );
				}

                // support footer, deferred
                $args = array();
                if (!empty($arr['in_footer'])){
	                $args['in_footer'] = 1;
                } if (!empty($arr['strategy'])){
					$args['strategy'] = $arr['strategy'];
				}

				wp_enqueue_script( $arr['h'], '', $dep, $v, $args);
			}


		}

		// firebase config
		/*wp_localize_script('tvr_mod_firebase', 'tvrFirebaseConfig', array(
			'apiKey' => 'YOUR_API_KEY',
			'authDomain' => 'YOUR_PROJECT_ID.firebaseapp.com',
			'projectId' => 'YOUR_PROJECT_ID',
			'storageBucket' => 'YOUR_PROJECT_ID.appspot.com',
			'messagingSenderId' => 'YOUR_MESSAGING_SENDER_ID',
			'appId' => 'YOUR_APP_ID'
		));*/


		/*// for article
				$jqueryUIScripts = array(
					'core',
					'widget',
					'mouse',
					'sortable',
					'menu',
					'autocomplete',
				);

				$prevScript = false;

				foreach ($jqueryUIScripts as $scriptName){

					$jqueryUIDeps = array('jquery', 'jquery-migrate');

					if (!empty($prevScript)){
						$jqueryUIDeps[] = 'jquery-ui-core';
						$jqueryUIDeps[] = 'jquery-ui-'.$prevScript;
					}

					wp_scripts()->remove( $scriptName );

					wp_enqueue_script(
						$scriptName, '/wp-content/themes/my-theme/js/temp-fix/'.$scriptName.'.js',
						$jqueryUIDeps
					);

					$prevScript = $scriptName;
				}*/


		// load js strings for translation
		include_once $this->thisplugindir . 'includes/js-i18n.inc.php';

	}

	// initiate vars that are wp dependent
	function setup_wp_dependent_vars(){

		// ajax url
        $this->setAdminAjaxUrl();

		$pd_context = 'setup_wp_dependent_vars';

		// setup program data arrays (program data default MQs are dependent on which builder is active)
		include dirname(__FILE__) .'/../includes/program-data.php';

		// Write Microthemer version specific array data to JS file (can be static for each version).
		// This can be done in dev mode only (also, some servers don't like creating JS files)
		if (TVR_DEV_MODE){ // temp disable and false
			$this->write_mt_version_specific_js();
		}

		//$this->get_site_pages(); // for debug

	}

	// check_integrations (on the admin side)
	function check_integrations(){

		if (!$this->integrationsChecked){
			$check = array(
				'FLBuilder' => 'bb-plugin/fl-builder.php',
				'FLBuilder_lite' => 'beaver-builder-lite-version/fl-builder.php',
				'elementor' => 'elementor/elementor.php',
				'oxygen' => 'oxygen/functions.php',
			);

			// Block based builders
            $checkBlockBuilders = array(
	            'kadenceBlocks' => 'kadence-blocks/kadence-blocks.php',
	            'generateBlocks' => 'generateblocks/plugin.php',
	            'greenShift' => 'greenshift-animation-and-page-builder-blocks/plugin.php',
	            'maxiBlocks' => 'maxi-blocks/plugin.php',
            );

			// set config
			foreach ($check as $key => $plugin){
				if ( is_plugin_active( $plugin ) ) {
					// two versions of BB, try using same key
					$key = ($key === 'FLBuilder_lite') ? 'FLBuilder' : $key;
					$this->integrations[$key] =  1;
				}
			}

			foreach ($checkBlockBuilders as $key => $plugin){
				if ( is_plugin_active( $plugin ) ) {
					$this->blockBuilders[$key] =  1;
				}
			}

			// if BB, provide way to load a BB breakpoint set
			if ( !empty($this->integrations['FLBuilder']) ){

				$bb_global = get_option('_fl_builder_settings');
				$small = !empty($bb_global->responsive_breakpoint)
					? $bb_global->responsive_breakpoint : 768;
				$medium = !empty($bb_global->medium_breakpoint)
					? $bb_global->medium_breakpoint : 992;
				$large = !empty($bb_global->large_breakpoint)
					? $bb_global->large_breakpoint : 1200;
				$xl = $large + 1;

				//wp_die('<pre>'.print_r($bb_global, true).'</pre>');

				// append BB media query option to
				$this->bb_mqs = array(
					/*$this->unq_base.'bbxl' => array(
							    "label" => __('BB XL Only', 'microthemer'),
							    "query" => "@media (min-width: {$xl}px)",
							    "site_preview_width" => "builder.FLBuilder.xl"
						    ),*/
					$this->unq_base.'bb0' => array(
						"label" => __('BB Large', 'microthemer'),
						"query" => "@media (max-width: {$large}px)",
						"site_preview_width" => "builder.FLBuilder.large"
					),
					$this->unq_base.'bb1' => array(
						"label" => __('BB Medium', 'microthemer'),
						"query" => "@media (max-width: {$medium}px)",
						"site_preview_width" => "builder.FLBuilder.medium"
					),
					$this->unq_base.'bb2' => array(
						"label" => __('BB Small', 'microthemer'),
						"query" => "@media (max-width: {$small}px)",
						"site_preview_width" => "builder.FLBuilder.small"
					),
				);

				$this->concat_builder_sync_options($this->bb_mqs);

				$this->mq_sets[esc_html__('Beaver Builder MQs', 'microthemer')] = $this->bb_mqs;
			}

			// if Elementor, provide way to load an Elementor breakpoint set
			if ( !empty($this->integrations['elementor']) ){

				if (method_exists('\Elementor\Core\Responsive\Responsive', 'get_breakpoints')){

					$this->elementor_breakpoints = \Elementor\Core\Responsive\Responsive::get_breakpoints();
					// todo the above is Class/method is deprecated, need to switch to the below code (or something else) but formatting needs to be adjusted - haven't spent time on it yet.
                    // $this->elementor_breakpoints = \Elementor\Plugin::$instance->breakpoints->get_active_breakpoints();

					// elementor breakpoints to not match the preview screen. They are calculated from lg and md minus 1.
					$tablet_max = $this->elementor_breakpoints['lg'] - 1;
					$mobile_max = $this->elementor_breakpoints['md'] - 1;

					// Elementor media query option
					$this->elementor_mqs = array(
						/*// Not using Desktop label as Elmentor has that for full width. Yet 1025 is a custom setting.
								$this->unq_base.'elem1' => array(
								   "label" => __('Max: '.$breakpoints['lg'], 'microthemer'),
								   "query" => "@media (max-width: {$breakpoints['lg']}px)",
							   ),*/
						$this->unq_base.'elem2' => array(
							"label" => __('Elementor Tablet', 'microthemer'),
							"query" => "@media (max-width: {$tablet_max}px)",
							"site_preview_width" => "builder.elementor.tablet"
						),
						$this->unq_base.'elem3' => array(
							"label" => __('Elementor Mobile', 'microthemer'),
							"query" => "@media (max-width: {$mobile_max}px)",
							"site_preview_width" => "builder.elementor.mobile"
						),

					);

					$this->concat_builder_sync_options($this->elementor_mqs);

					$this->mq_sets[esc_html__('Elementor MQs', 'microthemer')] = $this->elementor_mqs;
				}


			}

			// get oxygen breakpoints $media_queries_list (global)
			/*global $media_queries_list_above, $media_queries_list;
	$this->show_me = '<pre>$media_queries_list: '.print_r($media_queries_list, true). '</pre>' .
					 '<pre>$media_queries_list_above: '.print_r($media_queries_list_above, true). '</pre>';*/

			// if Oxygen, provide way to load an Oxygen breakpoint set
			if ( !empty($this->integrations['oxygen']) ){

				global $media_queries_list, $media_queries_list_above;

				// get a copy of Oxygen global media query array so we don't update global instance
				// when setting maxSize on page-width key using $global_page_width
				// this is unset because oxygen allows different page widths
				// but MT doesn't support dynamic MQ tabs
				$mq_copy = $this->array_clone($media_queries_list);
				$mq_above_copy = $this->array_clone($media_queries_list_above);

				if ( isset($mq_copy) && function_exists('oxygen_vsb_get_page_width') ){

					$global_page_width = oxygen_vsb_get_page_width(true);
					$mq_copy['page-width']['maxSize'] = $global_page_width.'px';
					$tablet_max = $mq_copy['tablet']['maxSize'];
					$phone_landscape_max = $mq_copy['phone-landscape']['maxSize'];
					$phone_portrait_max = $mq_copy['phone-portrait']['maxSize'];
					$page_container_label = __('Page container', 'microthemer');
					$and_below_label = __('and below', 'microthemer');

					// Oxygen media query option
					$this->oxygen_mqs = array(

						$this->unq_base.'_oxy_page_width' => array(
							"label" => $page_container_label, //.' ('.$global_page_width.'px) '.$and_below_label,
							"query" => "@media (max-width: {$global_page_width}px)",
							"site_preview_width" => "builder.oxygen.page-width"
						),
						$this->unq_base.'_oxy_tablet' => array(
							"label" => $mq_copy['tablet']['title'],
							"query" => "@media (max-width: {$tablet_max})",
							"site_preview_width" => "builder.oxygen.tablet"
						),
						$this->unq_base.'_oxy_phone_landscape' => array(
							"label" => $mq_copy['phone-landscape']['title'],
							"query" => "@media (max-width: {$phone_landscape_max})",
							"site_preview_width" => "builder.oxygen.phone-landscape"
						),
						$this->unq_base.'_oxy_phone_portrait' => array(
							"label" => $mq_copy['phone-portrait']['title'],
							"query" => "@media (max-width: {$phone_portrait_max})",
							"site_preview_width" => "builder.oxygen.phone-portrait"
						),

					);

					$this->concat_builder_sync_options($this->oxygen_mqs);

					// make MQ set available
					$this->mq_sets[esc_html__('Oxygen MQs', 'microthemer')] = $this->oxygen_mqs;

					// save breakpoint data we might need later
					$this->oxygen_breakpoints = $mq_copy;

					//$this->show_me = '';
				}


			}

			// so it doesn't run twice when activating/updating
			$this->integrationsChecked = true;

		}

	}

	function concat_builder_sync_options($mqs){

		foreach ($mqs as $array){
            if (isset($array['site_preview_width'])){
	            $this->builder_sync_tabs[] = $array['site_preview_width'];
            }
		}
	}

	// clone an array - useful if we want to edit global array only locally
	function array_clone($array) {
		return array_map(function($element) {
			return ((is_array($element))
				? $this->array_clone($element)
				: ((is_object($element))
					? clone $element
					: $element
				)
			);
		}, $array);
	}

	// output meta tags to prevent browser back/forward cache from generating
	// false positive multiple tabs warning
	function add_no_cache_headers(){
		header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
		header("Cache-Control: post-check=0, pre-check=0", false);
		header("Pragma: no-cache");
	}

	// color variables and customisable panel sizes for left and right sidebars
	function add_dyn_inline_css(){

        //wp_die('layout <pre>' . print_r($this->preferences['layout'], 1) . '</pre>');

		$layout = $this->preferences['layout'];
		$inspCols = $layout['inspection_columns']['column_sizes'];

		// MT side columns are customisable, default to saved widths on load
		// Note, left and right columns are have position absolutes so resizing isn't jerky
		$column_sizes = $layout['left']['column_sizes'];
        $effective_num_columns = $layout['left']['effective_num_columns'];
		/*$min_column_sizes = $this->preferences['layout']['left']['min_column_sizes'];
				minmax('.$min_column_sizes[$i].'px, */
		$gridColumns = '';
		$gridColumnsFooter = '';
		foreach ($column_sizes as $i => $size) {

			$mainSize = $size.'px ';
			$footerSize = $mainSize;
            $columnNumber = $i+1;

			if ($columnNumber > intval($effective_num_columns)){
	            $mainSize = '0 ';
                if ($columnNumber > 1){
	                $footerSize = '0 ';;
                }
            }

			$gridColumns.= $mainSize;
			$gridColumnsFooter.= $footerSize;
		}

        // Right columns
		$rightColumn = $layout['right']['column_sizes'][1].'px';
        $effectiveRightColumn = $layout['right']['effective_num_columns'] > 0 ? $rightColumn : 0;
        $mainAndPlaceholder = '1fr 0 ';
		$gridColumns.= $mainAndPlaceholder . $effectiveRightColumn;
		$gridColumnsFooter.= $mainAndPlaceholder . $rightColumn;

		// color variables
		$colorVariablesRuleSet = !empty($this->preferences['mt_color_variables_css'])
			? '.sp-container .tile-color, .tvr-input-wrap .var-color-box, .mt-color-picker { '.strip_tags($this->preferences['mt_color_variables_css']).' }'
			: '';

		echo '
                <style id="mt_color_variables">'.$colorVariablesRuleSet.' </style>
                <style id="main-interface-columns">
                    .visual-view {
                        grid-template-columns: '.$gridColumns.';
                    }
                </style>
                <style id="mt-footer-columns">
                    #advanced-wizard {
                        grid-template-columns: '.$gridColumnsFooter.';
                    }
                </style>
                <style id="inspection-columns">
                    .wizard-panes { 
                        grid-template-columns: 1fr minmax(auto, '.$inspCols[0].'px) minmax(auto, '.$inspCols[1].'px);
                    }
                </style>';

	}

	/* function add_svg_sprite(){
				 echo $this->svg('sprite', array('id' => 'mt-svg-sprite', 'style' => 'display: none'), false);
			 }*/

	// add css
	function add_css() {

		// Google fonts
		//wp_register_style('tvrGFonts', '//fonts.googleapis.com/css2?family=Fira+Code:wght@600;700&family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,700&display=swap');
		wp_register_style('tvrGFonts', $this->thispluginurl.'local-google-font/load-mt-gf.css?v='.$this->version);
		wp_enqueue_style( 'tvrGFonts');

		// if dev mode, enqueue css libraries separately
		if (TVR_DEV_MODE){

			// color picker, colorbox, jquery ui styling
			wp_enqueue_style( 'spectrum-colorpicker',
				$this->thispluginurl . 'js/lib/colorpicker/spectrum.css?v=' . $this->version );
			/*wp_register_style( 'tvr_mcth_colorbox_styles',
						$this->thispluginurl.'css/colorbox.css?v='.$this->version );
					wp_enqueue_style( 'tvr_mcth_colorbox_styles' );*/
			wp_register_style( 'tvr_jqui_styles', $this->thispluginurl.'css/jquery-ui1.11.4.css?v='.$this->version );
			wp_enqueue_style( 'tvr_jqui_styles' );

			$main_css_file = $this->thispluginurl.'css/styles.css';
			$fonts_css_file = 'fonts.css';

		} else {

			//wp_register_style( 'tvr_mcth_colorbox_styles',
			//$this->thispluginurl.'js/lib/colorbox/1.3.19/colorbox.css?v='.$this->version );
			//wp_enqueue_style( 'tvr_mcth_colorbox_styles' );

			// in production, all css will be minified and concatenated to concat-styles.min.css
			$main_css_file = $this->thispluginurl.'css/concat-styles.min.css';
			$fonts_css_file = 'fonts.min.css';
		}

		// enqueue main stylesheet
		wp_register_style( 'tvr_mcth_styles', $main_css_file.'?v='.$this->version );
		wp_enqueue_style( 'tvr_mcth_styles' );

		// extra styles for fonts page
		if ($_GET['page'] === $this->fontspage){
			wp_register_style( 'tvr_font_styles', $this->thispluginurl.'css/'.$fonts_css_file.'?v='.$this->version );
			wp_enqueue_style( 'tvr_font_styles' );
		}

		// preferences page doesn't want toolbar hack, so add to stylesheet conditionally
		if ($_GET['page'] != $this->preferencespage){
			$custom_css = "
						html, html.wp-toolbar {
							padding-top:0
						}";
			wp_add_inline_style( 'tvr_mcth_styles', $custom_css );
		}

	}

	// preload icon font and have @font-face inline to ensure $this->version preloads correct file
	function load_icon_font($return = false){

		//$dir = $this->root_rel($this->thispluginurl, false, true). 'images/icon-font/';
		//$dir = $this->thispluginurl . 'images/icon-font/';

		$rel_path = 'images/icon-font/';

		// we use root rel so that this works with browser sync
		// and actual path = true so it works on local sub-dir install e.g. /wp-versions/really-fresh/
		//$dir = $this->root_rel($this->thispluginurl, false, true, true).$rel_path;

		// but if the user has a strange setup, stick to simple path
		/*$test_url = $this->site_url . $dir. 'microthemer.woff2';
				if (filter_var($test_url, FILTER_VALIDATE_URL) === FALSE) {
					$dir = $this->thispluginurl . $rel_path;
					//die('Not a valid URL ' . $dir. 'microthemer.woff2');
				}*/


		// the above options caused issues - and it seems to work with browser sync...
		$dir = $this->thispluginurl . $rel_path;

		$preload = '<link rel="preload" as="font" crossorigin="anonymous" href="'.$dir.'microthemer.woff2?v='.$this->version.'" />';
		$fontFace = "
                <style>
                     @font-face {
                      font-family: 'microthemer';
                      src: url(".$dir . 'microthemer.woff2?v='.$this->version.") format('woff2'),
                           url(".$dir . 'microthemer.woff?v='.$this->version.") format('woff');
                      font-weight: normal;
                      font-style: normal;
                    }   
                </style>";

		if ($return){
			return $fontFace;
		} else {
			echo $preload . "\n" . $fontFace;
		}
	}

	// build array for property/value input fields
	function getPropertyOptions() {
		$propertyOptions = array();
		$legacy_groups = array();
		$this->animatable = array(

			array(
				'label' => 'all',
				'category' => ''
			),
			array(
				'label' => 'none',
				'category' => ''
			)
		);
		include $this->thisplugindir . 'includes/property-options.inc.php';
		$this->propertyoptions = $propertyOptions;

		// populate $property_option_groups, $auto_convert_map, and animatable array
		foreach ($propertyOptions as $prop_group => $array){
			foreach ($array as $prop => $meta) {
				// pg group
				if ( !empty($meta['pg_label']) ){
					$pg_label = $meta['pg_label'];
					$this->property_option_groups[$prop_group] = $pg_label;
				}
				// auto convert
				if ( !empty($meta['auto']) ){
					$this->auto_convert_map[$prop] = $meta['auto'];
				}
				// animatable properties
				if ( !empty($meta['animatable']) ){
					$cssf = str_replace('_', '-', $prop);

					// adjust for shorthand
					if ($cssf == 'text-shadow-x'){
						$cssf = 'text-shadow';
					} else if ($cssf == 'box-shadow-x'){
						$cssf = 'box-shadow';
					}

					// include extra shorthand
					else if ($cssf == 'padding-top'){
						$this->animatable[] = array(
							'label' => 'padding',
							'category' => $pg_label
						);
					} else if ($cssf == 'margin-top'){
						$this->animatable[] = array(
							'label' => 'margin',
							'category' => $pg_label
						);
					} else if ($cssf == 'border-top-color'){
						$this->animatable[] = array(
							'label' => 'border-color',
							'category' => $pg_label
						);
					} else if ($cssf == 'border-top-width'){
						$this->animatable[] = array(
							'label' => 'border-width',
							'category' => $pg_label
						);
					} else if ($cssf == 'flex-grow'){
						$this->animatable[] = array(
							'label' => 'flex',
							'category' => $pg_label
						);
					}

					$this->animatable[] = array(
						'label' => $cssf,
						'category' => $pg_label
					);
				}
			}
		}
		$this->legacy_groups = $legacy_groups;

	}

	// update shorthand map array
	function update_shorthand_map($shorthand, $cssf, $prop_group, $prop, $propArr, $sh) {
		//$this->show_me.= print_r($sh[0], true);
		$shorthand[$sh[0]][$cssf] = array(
			'group' => $prop_group,
			'prop' => $prop,
			'index' => $sh[1],
			'config' => !empty($sh[2]) ? $sh[2] : array()
		);
		// signal if prop is !important carrier
		!empty($propArr['important_carrier']) ? $shorthand[$sh[0]][$cssf]['important_carrier'] = 1 : 0;
		// signal if prop can have multiple
		!empty($propArr['multiple']) ? $shorthand[$sh[0]][$cssf]['multiple'] = 1 : 0;
		// and if MT supports this
		!empty($propArr['multiple_sup']) ? $shorthand[$sh[0]][$cssf]['multiple_sup'] = 1 : 0;

		return $shorthand;
	}

	// update the array for checking match criteria when gathering interesting css values from site stylesheets
	function update_gc_css_match($gc_css_match, $type, $val){
		$gc_css_match[] = array(
			'type' => $type,
			'val' => $val
		);
		return $gc_css_match;
	}

	// create static JS file for property options etc that relate to the current version of MT
	function write_mt_version_specific_js($dir = 'js/data', $inline = false) {

		$write_file = '';

		if (!$inline){

			// Create new file if it doesn't already exist
			$js_file = $this->thisplugindir . $dir . '/program-data.js';
			$write_file = @fopen($js_file, 'w');

			if (!$write_file) {
				$this->log(
					esc_html__('Permission Error', 'microthemer'),
					'<p>' . sprintf( esc_html__('WordPress does not have permission to update: %s', 'microthemer'), $this->root_rel($js_file) . '. '.$this->permissionshelp ) . '</p>'
				);

				return 0;
			}
		}

		// some CSS properties need adjustment for jQuery .css() call
		// include any prop that needs special treatment for one reason or another
		$exceptions = array(
			'display' => array('display-flex'),
			'font-family' => 'google-font',
			'grid-template-areas' => 'grid-template-areas-add',
			'list-style-image' => 'list-style-image',
			'text-shadow' => array(
				'text-shadow-x',
				'text-shadow-y',
				'text-shadow-blur',
				'text-shadow-color'),
			'box-shadow' => array(
				'box-shadow-x',
				'box-shadow-y',
				'box-shadow-blur',
				'box-shadow-spread',
				'box-shadow-color',
				'box-shadow-inset'),
			'background-img-full' => array(
				'background-image',
				'gradient-angle',
				'gradient-a',
				'gradient-b',
				'gradient-b-pos',
				'gradient-c'
			),
			'background-position' => 'background-position',
			'background-position-custom' => array(
				'background-position-x',
				'background-position-y'
			),
			'background-repeat' => 'background-repeat',
			'background-attachment' => 'background-attachment',
			'background-size' => 'background-size',
			'background-clip' => 'background-clip',
			'border-top-left-radius' => 'radius-top-left',
			'border-top-right-radius' => 'radius-top-right',
			'border-bottom-right-radius' => 'radius-bottom-right',
			'border-bottom-left-radius' =>'radius-bottom-left',

			'keys' => array(
				'background-position-x' => array(
					'0%' => 'left',
					'100%' => 'right',
					'50%' => 'center'
				),
				'background-position-y' => array(
					'0%' => 'top',
					'100%' => 'bottom',
					'50%' => 'center'
				),
				'gradient-angle' => array(
					'180deg' => 'top to bottom',
					'0deg' => 'bottom to top',
					'90deg' => 'left to right',
					'-90deg' => 'right to left',
					'135deg' => 'top left to bottom right',
					'-45deg' => 'bottom right to top left',
					'-135deg' => 'top right to bottom left',
					'45deg' => 'bottom left to top right'
				),
				// webkit has a different interpretation of the degrees - doh!
				'webkit-gradient-angle' => array(
					'-90deg' => 'top to bottom',
					'90deg' => 'bottom to top',
					'0deg' => 'left to right',
					'180deg' => 'right to left',
					'-45deg' => 'top left to bottom right',
					'135deg' => 'bottom right to top left',
					'-135deg' => 'top right to bottom left',
					'45deg' => 'bottom left to top right'
				)
			)
		);


		$this->propAliases = array(

			'display-flex' => 'display',
			'column-gap-flex' => 'column-gap',
			'row-gap-flex' => 'row-gap',

			// properties that should go to grid prop in format cssf-grid
			'display-grid' => 'display',
			'justify-items-grid' => 'justify-items',
			'justify-content-grid' => 'justify-content',
			//'justify-self-grid' => 'justify-self', // use grid as default group for this
			'align-items-grid' => 'align-items',
			'align-content-grid' => 'align-content',
			'align-self-grid' => 'align-self',
			'order-grid' => 'order',
			'z-index-grid' => 'z-index',

			// caution order an z-index appear twice here!
			// this has implications for resolve_repeated_property_group (OK for now)

			// properties that should go to grid all fields
			'width-gridall' => 'width',
			'height-gridall' => 'height',
			'grid-area-gridall' => 'grid-area',
			'order-gridall' => 'order',
			'z-index-gridall' => 'z-index'

		);


		$this->cssFuncAliases = array(

			// transform css functions
			'rotate' => 'rotatez', // rotate does the same thing as rotateZ

			// filter css functions
			'opacity-function' => 'opacity',
		);

		// var for storing then writing json data to JS file
		$data = '';

		// shorthand properties in this array (like padding, font etc) also have longhand single props.
		// At the JS end, these single props can be got from tapping the browser's comp CSS
		// unlike only shorthand props in the $exceptions array above.
		$shorthand = array();

		// I should have left space for this in the $shorthand array, I will never learn
		$shorthand_prefixes = array();

		// longhand for checking against regular css properties
		// Also a general deposit for property data we want JavaScript to have access to
		$longhand = array();

		// And object for storing the subgroup keys e.g. just padding, rather than padding and margin
		$sub_group_keys = array();

		// also need to map subgroups to groups so pg_disabed[padding] will load group options
		$sub_group_to_group = array();

		// temporary reference map/storage for style values from site's stylesheets e.g. color palette
		// certain styles are saved to my_props
		$gathered_css = array(
			'eligable' => array(),
			'store' => array(
				//'site_colors' => array(),
				//'saved_colors' => array(),
			),
			'root_cat_keys' => array(),
		);

		// combo array for storing data for comboboxes
		$combo = array();

		// css props for passing jQuery.css() to get computed CSS
		$css_props = array();

		// we need a collection of props for pg tabs
		$pg_tab_props = array();

		// for mapping a pgtab control to a group
		$pg_tab_map = array();

		$unsupported_css_func_map = array(
			'matrix' => 'transform', // unsupported shorthand matrices that cannot be reliably converted to longhand functions
			'matrix3d' => 'transform',
			'rotate3d' => 'transform'
		);

		// for mapping css functions to the shorthand prop
		$css_func_map = array(
			'rotate' => 'transform', // alias for rotateZ
		);

		$sub_slug = '';

		// loop through property options, creating various JS key map arrays
		foreach ($this->propertyoptions as $prop_group => $array) {

			foreach ($array as $prop => $propArr) {

				// new sub group, update and save reference to array
				if (!empty($propArr['sub_slug'])){
					$sub_slug = $propArr['sub_slug'];
					$sub_group_keys[$sub_slug] = array();
					$sub_group_array = &$sub_group_keys[$sub_slug];

					$sub_group_to_group[$sub_slug] = $prop_group;
				}

				// store prop in sub_array
				$sub_group_array[] = $prop;

				// we loop the group props in a specified order so the CSS props are in the same place, so we need group too
				if ($sub_slug !== $prop_group){
					$sub_group_keys[$prop_group][] = $prop;
				}

				// this could be replaced with hardcoded values in property-options.inc.php
				$cssf = str_replace('_', '-', $prop);
				$css_props[$prop_group][] = array(
					'prop' => $prop,
					'cssf' => $cssf
				);

				// update tab control array and grid items map
				if (!empty($propArr['tab_control'])){

					// we're setting the valid syntax here, I think that makes sense as property group is included
					$valid_syntax = !empty($this->propAliases[$cssf])
						? $this->propAliases[$cssf]
						: $cssf;

					$pg_tab_props[$prop_group][$propArr['tab_control']][$prop] = $valid_syntax;
					$pg_tab_map[$propArr['tab_control']] = $prop_group;
				}

				// update shorthand map
				if (!empty($propArr['sh'])){
					// like with border, border-top, and border-color shorthands affecting 1 prop
					if (is_array($propArr['sh'][0])){
						foreach($propArr['sh'] as $n => $sub_sh){

							$shorthand = $this->update_shorthand_map($shorthand, $cssf, $prop_group,
								$prop, $propArr, $propArr['sh'][$n]);

							$shorthand_property = $propArr['sh'][$n][0];

							// also update $gathered_css while we're here
							if (!empty($propArr['sug_values'])){
								$gathered_css['eligable'][$shorthand_property] = 1;
							}

							if (isset($propArr['css_func'])){
								$css_func_map[$shorthand_property] = $prop_group;
							}

						}
					} else {
						// prop with just one shorthand available
						$shorthand = $this->update_shorthand_map($shorthand, $cssf, $prop_group,
							$prop, $propArr, $propArr['sh']);

						$shorthand_property = $propArr['sh'][0];

						// also update $gathered_css while we're here
						if (!empty($propArr['sug_values'])){
							$gathered_css['eligable'][$shorthand_property] = 1;
						}

						if (isset($propArr['css_func'])){
							$css_func_map[$shorthand_property] = $prop_group;
						}

						// update any shorthand prefixes
						if (!empty($propArr['sh'][2]['prefixes'])){
							$shorthand_prefixes[$shorthand_property]['prefixes'] = $propArr['sh'][2]['prefixes'];
						}

					}
				}

				// update longhand map (even with onlyShort)
				//if (empty($propArr['sh'][2]['onlyShort'])){
				$longhand[$cssf] = array(
					'group' => $prop_group,
					'prop' => $prop,
					//'multiple' => !empty($propArr['multiple']) ? 1 : 0,
				);

				// include any vendor prefixes for property
				!empty($propArr['prefixes'])
					? $longhand[$cssf]['prefixes'] = $propArr['prefixes']
					: false;

				// signal if prop can have multiple
				!empty($propArr['multiple']) ? $longhand[$cssf]['multiple'] = 1 : false;

				// signal MT factory default unit as unitless suggestions are based on that
				// the factory default can be used to convert suggested values based on the user's unit choice
				isset($propArr['default_unit'])
					? $longhand[$cssf]['fdu'] = $propArr['default_unit']
					: false;

				// signal if property has special units
				!empty($propArr['special_units'])
					? $longhand[$cssf]['special_units'] = $propArr['special_units']
					: false;

				// css function like rotateX or rotate3d for transform or filter
				if (isset($propArr['css_func'])){
					$longhand[$cssf]['css_func'] = $propArr['css_func'];
					$css_func_map[$prop] = $prop_group;
				}

				// css function like rotateX or rotate3d for transform or filter
				if (isset($propArr['defVal'])){
					$longhand[$cssf]['defVal'] = $propArr['defVal'];
				}

				// and if MT supports multiple
				!empty($propArr['multiple_sup']) ? $longhand[$cssf]['multiple_sup'] = 1 : 0;

				// attach tab_control data to prop
				!empty($propArr['tab_control'])
					? $longhand[$cssf]['tab_control'] = $propArr['tab_control']
					: false;

				// and attach shorthand so we can check this when resampling page for suggested styles
				if (!empty($propArr['sh'])){
					$longhand[$cssf]['sh'] = $propArr['sh'];
					// also put shorthand prefixes in longhand
				}


				// get sub_slug for checking disabled via JS (among things perhaps)
				$longhand[$cssf]['sub_slug'] = $sub_slug;

				// get combobox type for edge_mode (temp)
				//!empty($propArr['type']) ? $longhand[$cssf]['type'] = $propArr['type'] : 0;

				// get sug_values config for forcing recent / suggestions etc
				!empty($propArr['sug_values']) ? $longhand[$cssf]['sug_values'] = $propArr['sug_values'] : 0;

				//}

				// update the $gathered_css map
				if (!empty($propArr['sug_values'])){

					// straight property match e.g. font-size
					if (!empty($propArr['sug_values']['this'])){
						$gathered_css['eligable'][$cssf] = 1;
					}

					// populate $gathered_css keys and storage arrays ready for getting vals with JS
					$gc_root_cat = !empty($propArr['sug_values']['root_cat'])
						? $propArr['sug_values']['root_cat']
						: $prop;

					$gathered_css['root_cat_keys'][$prop] = $gc_root_cat;
					$gathered_css['store'][$gc_root_cat] = array();

					// create store for e.g. grid line names too
					if (!empty($propArr['sug_values_extra'])){
						$gc_root_cat_extra = $propArr['sug_values_extra']['root_cat'];
						$gathered_css['root_cat_keys'][$prop.'_extra'] = $gc_root_cat_extra;
						$gathered_css['store'][$gc_root_cat_extra] = array();
					}

				}

				// populate combobox array
				if (!empty($array[$prop]['select_options'])){
					$combo[$prop] = $array[$prop]['select_options'];
				} if (!empty($array[$prop]['select_options_extra'])){
					$combo[$prop.'_extra'] = $array[$prop]['select_options_extra'];
				}

				// exceptions for more complicated select items with categories
				else {

					// event options
					if ($prop == 'font_family'){
						$combo[$prop] = $this->system_fonts;
					}

					// HTML modification options
					elseif (!empty($this->modification[$prop])){
						$combo[$prop] = $this->modification[$prop];
					}

					// get preset animations from include file
					elseif ($prop == 'animation_name'){
						$animation_names = array();
						include $this->thisplugindir . 'includes/animation/animation-code.inc.php';
						$combo[$prop] = $animation_names;
					}

					// read stock background-image paths from json file created with svgstock gulp build
					elseif ($prop == 'background_image'){

						$background_image_json = file_get_contents(
							$this->thisplugindir . 'stock/json/'.$cssf.'.json'
						);

						if ($background_image_json){
							$combo[$prop] = json_decode($background_image_json, true);
						}

					}

					// read stock mask-image paths from json file created with svgstock gulp build
					elseif ($prop == 'mask_image'){

						$mask_image_json = file_get_contents(
							$this->thisplugindir . 'stock/json/'.$cssf.'.json'
						);
						$mask_images = $mask_image_json ? json_decode($mask_image_json, true) : false;

						// add the gradients too
						$angles = $exceptions['keys']['gradient-angle'];
						$linear_gradients = array();
						foreach ($angles as $deg => $label){
							$linear_gradients[] = array(
								'label' => '['.$label.']',
								'value' => 'linear-gradient('.$deg.', black 0%, rgba(0,0,0,0) 100%)'
							);
						}

						$css_gradients = $this->to_autocomplete_arr(array(
							__('Linear gradients', 'microthemer') => $linear_gradients,
							__('Radial & conic gradients', 'microthemer') => array(
								array(
									'label' => '[conic]',
									'value' => 'conic-gradient(from 0deg at 50% 50%, black 0% 25%, rgba(0,0,0,0) 50%, black 75% 100%)'
								),
								array(
									'label' => '[conic light ray]',
									'value' => 'conic-gradient(from 15deg at 0% 0%, black 0% 16%, rgba(0,0,0,0.2) 25%, black 75% 100%)'
								),

								array(
									'label' => '[conic checkerboard]',
									'value' => 'repeating-conic-gradient(rgba(0,0,0,0) 90deg, #000 0 180deg, rgba(0,0,0,0) 0 270deg, #000 0)'
								),
								array(
									'label' => '[radial fade out]',
									'value' => 'radial-gradient(at 50% 50%, black 0%, rgba(0,0,0,0) 100%)'
								),
								array(
									'label' => '[radial fade in]',
									'value' => 'radial-gradient(at 50% 50%, rgba(0,0,0,0) 0%, black 100%)'
								),
								array(
									'label' => '[radial rings]',
									'value' => 'repeating-radial-gradient(black, rgba(0,0,0,.7) 20%)'
								),

							)
						));


						if ($mask_image_json){
							$combo[$prop] = array_merge(
								$css_gradients,
								$mask_images
							);
						}

					}

					// get preset animations from include file
					elseif ($prop == 'clip_path'){
						$combo[$prop] = $this->to_autocomplete_arr(array(

							__('Angular sections', 'microthemer') => array(

								array(
									'label' => '[bottom center point]',
									'value' => 'polygon(0 0, 100% 0%, 100% 80%, 50% 100%, 0 80%)'
								),
								array(
									'label' => '[bottom ascend]',
									'value' => 'polygon(0 0, 100% 0, 100% 80%, 0% 100%)'
								),
								array(
									'label' => '[bottom descend]',
									'value' => 'polygon(0 0, 100% 0, 100% 100%, 0 80%)'
								),
								array(
									'label' => '[top ascend]',
									'value' => 'polygon(0 20%, 100% 0, 100% 100%, 0 100%)'
								),
								array(
									'label' => '[top descend]',
									'value' => 'polygon(0 0, 100% 20%, 100% 100%, 0 100%)'
								),
								array(
									'label' => '[top and bottom ascend]',
									'value' => 'polygon(0 20%, 100% 0, 100% 80%, 0 100%)'
								),
								array(
									'label' => '[top and bottom descend]',
									'value' => 'polygon(0 0, 100% 20%, 100% 100%, 0 79%)'
								),
								array(
									'label' => '[top ascend, bottom descend]',
									'value' => 'polygon(0 20%, 100% 0, 100% 100%, 0 79%)'
								),
								array(
									'label' => '[top descend, bottom ascend]',
									'value' => 'polygon(0 0, 100% 20%, 100% 80%, 0 100%)'
								),

							),



							__('Clippy shapes', 'microthemer') => array(

								array(
									'label' => '[arrow left]',
									'value' => 'polygon(40% 0%, 40% 20%, 100% 20%, 100% 80%, 40% 80%, 40% 100%, 0% 50%)'
								),
								array(
									'label' => '[arrow right ]',
									'value' => 'polygon(0% 20%, 60% 20%, 60% 0%, 100% 50%, 60% 100%, 60% 80%, 0% 80%)'
								),

								array(
									'label' => '[bevel]',
									'value' => 'polygon(20% 0%, 80% 0%, 100% 20%, 100% 80%, 80% 100%, 20% 100%, 0% 80%, 0% 20%)'
								),

								array(
									'label' => '[chevron left ]',
									'value' => 'polygon(100% 0%, 75% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%)'
								),
								array(
									'label' => '[chevron right]',
									'value' => 'polygon(75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%, 0% 0%)'
								),

								array(
									'label' => '[circle]',
									'value' => 'circle(50% at 50% 50%)'
								),

								array(
									'label' => '[close]',
									'value' => 'polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%)'
								),

								array(
									'label' => '[cross]',
									'value' => 'polygon(10% 25%, 35% 25%, 35% 0%, 65% 0%, 65% 25%, 90% 25%, 90% 50%, 65% 50%, 65% 100%, 35% 100%, 35% 50%, 10% 50%)'
								),

								array(
									'label' => '[custom]',
									'value' => 'polygon(18% 10%, 93% 37%, 77% 90%, 16% 89%, 83% 17%, 20% 42%)'
								),

								array(
									'label' => '[decagon]',
									'value' => 'polygon(50% 0%, 80% 10%, 100% 35%, 100% 70%, 80% 90%, 50% 100%, 20% 90%, 0% 70%, 0% 35%, 20% 10%)'
								),

								array(
									'label' => '[ellipse]',
									'value' => 'ellipse(50% 33% at 50% 50%)'
								),

								array(
									'label' => '[frame]',
									'value' => 'polygon(0% 0%, 0% 100%, 25% 100%, 25% 25%, 75% 25%, 75% 75%, 25% 75%, 25% 100%, 100% 100%, 100% 0%)'
								),

								array(
									'label' => '[heptagon]',
									'value' => 'polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 0% 60%, 10% 20%)'
								),

								array(
									'label' => '[hexagon]',
									'value' => 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)'
								),

								array(
									'label' => '[message]',
									'value' => 'polygon(0% 0%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%)'
								),

								array(
									'label' => '[nonagon]',
									'value' => 'polygon(50% 0%, 83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%)'
								),

								array(
									'label' => '[octagon]',
									'value' => 'polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)'
								),

								array(
									'label' => '[parallelogram]',
									'value' => 'polygon(25% 0%, 100% 0%, 75% 100%, 0% 100%)'
								),

								array(
									'label' => '[pentagon]',
									'value' => 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)'
								),

								array(
									'label' => '[point left]',
									'value' => 'polygon(25% 0%, 100% 0%, 100% 100%, 25% 100%, 0% 50%)'
								),
								array(
									'label' => '[point right]',
									'value' => 'polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%)'
								),

								array(
									'label' => '[rabbet]',
									'value' => 'polygon(0% 15%, 15% 15%, 15% 0%, 85% 0%, 85% 15%, 100% 15%, 100% 85%, 85% 85%, 85% 100%, 15% 100%, 15% 85%, 0% 85%)'
								),

								array(
									'label' => '[rectangle]',
									'value' => 'inset(0 0 0 0)'
								),

								array(
									'label' => '[rhombus]',
									'value' => 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)'
								),

								array(
									'label' => '[trapezoid]',
									'value' => 'polygon(20% 0%, 80% 0%, 100% 100%, 0% 100%)'
								),

								array(
									'label' => '[triangle]',
									'value' => 'polygon(50% 0%, 0% 100%, 100% 100%)'
								),
								array(
									'label' => '[star]',
									'value' => 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)'
								),
								array(
									'label' => '[wide star]',
									'value' => 'polygon(50% 0%, 70% 30%, 98% 35%, 80% 57%, 79% 91%, 50% 80%, 19% 92%, 20% 59%, 2% 35%, 30% 30%)'
								),


							),

							__('Valid syntax examples', 'microthemer') => array(
								'margin-box',
								'border-box',
								'padding-box',
								'content-box',
								'none',
								'inset(100px 50px)',
								'circle(50px at 0 100px)',
								'ellipse(50px 60px at 0 10% 20%)',
								'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)',
								// heart
								'path("M15,45 A30,30,0,0,1,75,45 A30,30,0,0,1,135,45 Q135,90,75,130 Q15,90,15,45 Z")',
								'url("#inlineSvgClipPathIDHere")',
							),

						));
					}

					// event options
					elseif ($prop == 'event'){
						$combo[$prop] = $this->browser_events;
					}

					// animatable properties
					elseif ($prop == 'transition_property'){
						$combo[$prop] = $this->animatable;
					}

				}


			}
		}

		//$this->show_me.= print_r($combo, true);

		$this->shorthand = $shorthand;
		$this->longhand = $longhand;

		// text/box-shadow need to be called as one
		$css_props['shadow'][] = array(
			'prop' => 'text_shadow',
			'cssf' => 'text-shadow'
		);
		$css_props['shadow'][] = array(
			'prop' => 'box_shadow',
			'cssf' => 'box-shadow'
		);
		$css_props['background'][] = array(
			'prop' => 'background_img_full',
			'cssf' => 'background-img-full'
		);
		// for storing full string (inc gradient)
		$css_props['background'][] = array(
			'prop' => 'extracted_gradient',
			'cssf' => 'extracted-gradient'
		);
		// for storing just gradient (for mixed-comp check)
		$css_props['gradient'][] = array(
			'prop' => 'background_image',
			'cssf' => 'background-image'
		);
		// gradient group needs this
		$css_props['gradient'][] = array(
			'prop' => 'background_img_full',
			'cssf' => 'background-img-full'
		);
		// for storing full string (inc gradient)
		$css_props['gradient'][] = array(
			'prop' => 'extracted_gradient',
			'cssf' => 'extracted-gradient'
		);

        // special folder search filters
		$combo['search_folders'] = array(
            array('label' => 'Affecting selectors', 'value' => ':affecting_selectors'),
			array('label' => 'Affecting selectors (inc inactive)', 'value' => ':affecting_selectors:including_inactive'),
        );

		// dev option for showing function times
		$combo['show_total_times'] = array('avg_time', 'total_time', 'calls');

		// set options for :lang(language) pseudo class
		$combo['lang_codes'] = $this->country_codes;

		// suggest some handy nth formulas
		$combo['nth_formulas'] = $this->nth_formulas;

        // Allow user to set the default modification point
        $combo['default_amender_event'] = array('DOMContentLoaded', 'serverHTMLReady');


		// ready combo for css_units
		$length_units = array();
		$unit_types = $this->lang['css_unit_types'];
		$length_units[$unit_types['none']] = $this->css_units[$unit_types['none']];
		$length_units[$unit_types['common']] = $this->css_units[$unit_types['common']];
		$length_units[$unit_types['other']] = $this->css_units[$unit_types['other']];
		$combo['css_length_units'] = $this->to_autocomplete_arr($length_units);

		// empty array for all sel suggestions
		$combo['all_sel_suggestions'] = array();

		// num history saves
		$combo['num_history_points'] = array(
			'50', '75', '100', '150', '200', '300'
		);

		// example below fold thresholds
		$combo['fold_threshold'] = array(
			'600', '900', '1200', '1440', '2160',
		);

		$combo['builder_sync_tabs'] = array_merge(
			array(
				'360', '568', '800',
                'builder.gutenberg.mobile',
                'builder.gutenberg.tablet'
			),
			$this->builder_sync_tabs
		);

        // AI combobox menus
        $combo['ai_actions'] = array(
            $this->aiData['actions']['generate']['label'],
	        $this->aiData['actions']['edit']['label'],
        );
        $combo['ai_scope'] = $this->to_autocomplete_arr(array(
	        $this->aiData['actions']['generate']['label'] => $this->aiData['actions']['generate']['scope'],
	        $this->aiData['actions']['edit']['label'] => $this->aiData['actions']['edit']['scope'],
        ));
		$combo['ai_commit_css'] = $this->aiData['actions']['generate']['commit']['type']['css'];
		$combo['ai_commit_html'] = $this->aiData['actions']['generate']['commit']['type']['html'];

		// options for choosing the stylesheet loading order
		$combo['stylesheet_order_options'] = $this->stylesheet_order_options;

		// customisable page prefix
		$combo['page_class_prefix_options'] = $this->page_class_prefix_options;

		// example scripts for enqueuing
		$combo['enq_js'] = array( 'jquery', 'jquery-form', 'jquery-color', 'jquery-masonry', 'masonry', 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-accordion', 'jquery-ui-autocomplete', 'jquery-ui-button', 'jquery-ui-datepicker', 'jquery-ui-dialog', 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-menu', 'jquery-ui-mouse', 'jquery-ui-position', 'jquery-ui-progressbar', 'jquery-ui-selectable', 'jquery-ui-resizable', 'jquery-ui-selectmenu', 'jquery-ui-sortable', 'jquery-ui-slider', 'jquery-ui-spinner', 'jquery-ui-tooltip', 'jquery-ui-tabs', 'jquery-effects-core', 'jquery-effects-blind', 'jquery-effects-bounce', 'jquery-effects-clip', 'jquery-effects-drop', 'jquery-effects-explode', 'jquery-effects-fade', 'jquery-effects-fold', 'jquery-effects-highlight', 'jquery-effects-pulsate', 'jquery-effects-scale', 'jquery-effects-shake', 'jquery-effects-slide', 'jquery-effects-transfer', 'wp-mediaelement', 'schedule', 'suggest', 'thickbox', 'hoverIntent', 'jquery-hotkeys', 'sack', 'quicktags', 'iris', 'json2', 'plupload', 'plupload-all', 'plupload-html4', 'plupload-html5', 'plupload-flash', 'plupload-silverlight', 'underscore', 'backbone' );
		sort($combo['enq_js']);


		// the full program data in one array
		$prog = array(
			'CSSUnits' =>  $this->css_units,
			'browser_event_keys' =>  $this->browser_event_keys,
			'selector_variations' => $this->selector_variations,
			'propExc' =>  $exceptions,
			'propAliases' =>  $this->propAliases,
			'cssFuncAliases' =>  $this->cssFuncAliases,
			'subGroupKeys' =>  $sub_group_keys,
			'subToGroup' =>  $sub_group_to_group,
			'sh' =>  $shorthand,
			'sh_prefixes' =>  $shorthand_prefixes,
			'lh' =>  $longhand,
			'gatheredCSS' =>  $gathered_css,
			'CSSProps' =>  $css_props,
			'PGs' =>  $this->property_option_groups,
			'autoMap' =>  $this->auto_convert_map,
			'combo' =>  $combo,
			'mobPreview' =>  $this->mob_preview,
			'CSSFilters' =>  $this->css_filters,
			'custom_code_flat' =>  $this->custom_code_flat,
			'pg_tab_props' =>  $pg_tab_props,
			'pg_tab_map' =>  $pg_tab_map,
			'css_func_map' =>  $css_func_map,
			'unsupported_css_func_map' =>  $unsupported_css_func_map,
			'params_to_strip' =>  $this->params_to_strip,
			'suggested_screen_layouts' => $this->suggested_screen_layouts,
            'exportable_preferences' => array_merge(
	            array_keys($this->default_preferences),
	            array_keys($this->default_preferences_exportable),
                // and some dynamically set preferences that should also copy over
                array(
                    "stylesheet_order",
                    "page_class_prefix",
                    "auto_folders",
                    "auto_folders_page"
                )
            ),
            // Help users find addons not indexed separately by NPM
            'npm_addons' => array(
                    'three' => array(
                            'browse' => 'https://cdn.jsdelivr.net/npm/three@{version}/examples/jsm/'
                    ),
                    'gsap' => array(
                            'browse' => 'https://cdn.jsdelivr.net/npm/gsap/'
                    )
            ),
            'native_packages' => json_decode(file_get_contents($this->thisplugindir . 'includes/native-packages.json'))
		);

		$data.= 'TvrMT.data.prog = ' . json_encode($prog) . ';' . "\n\n";
		$data.= 'TvrMT.data.templates = ' . json_encode($this->html_templates()) . ';' . "\n\n";

		// if the server can't overwrite an external JS file, add it inline with other dynamic JS
		if ($inline){
			return $data;
		}

		else {
			fwrite($write_file, $data);
			fclose($write_file);
		}

		return 1;

	}

	// HTML TEMPLATES
	function html_templates(){

		// full set of templates
		$templates = array(

			'notice' =>
				'<div id="notice-template" class="tvr-message tvr-template-notice tvr-warning test-class">
                            <span class="mt-notice-icon"></span>
                            <span class="mt-notice-text"></span>
                         </div>',

			'loaders' => $this->loaders_template(),

			'log_item' => $this->log_item_template(),

			'enq_js_menu_item' => $this->dyn_item($this->enq_js_structure, 'item', array('display_name' => 'item')),

			'mqs_menu_item' => $this->dyn_item($this->mq_structure, 'item', array('label' => 'item')),

			'sectionM' => $this->menu_section_html('selector_section', 'section_label'),

			'section' => $this->section_html('selector_section', array()),

			'selectorM' => $this->menu_selector_html('selector_section', 'selector_css', array('selector_code', 'selector_label'), 1),

			'selector' => $this->single_selector_html('selector_section', 'selector_css', '', true),

			'property_groups' => $this->property_group_templates(),

			'icon_inputs' => $this->icon_inputs(),

			'history_shortcut' => $this->iconFont('undo', array(
				'title' => esc_attr__('View history', 'microthemer'),
				'class' => 'short-status-icon display-revisions show-dialog',
				'rel' => 'display-revisions'
			)),

			'add_selector_form' => '',

			'edit_selector_form' => '',

			'input_wraps' => $this->input_wrap_templates, // var

			'frontend_markup' => $this->frontend_markup_template(),

			'frontend_overlay_template' => $this->frontend_overlay_template(),

			'icon_classes' => $this->iconFont('iconName', array('onlyClass' => 1)),

			'new_sel_radio' => $this->targeting_radio(),

			'existing_sel_radio' => $this->targeting_radio(true),

			'variation_radio' => $this->variation_radio(),

			'key_computed_info' => $this->key_computed_info(true),

			'svg_chev_right' => $this->svg_chev_right(),

			'toggle_switch' => $this->toggle('toggle_aspect', array(
				'toggle' => 0,
				'toggle_id' => 'toggle_id',
				'data-pos' => esc_attr__('enable_toggle_tooltip', 'microthemer'),
				'data-neg' => esc_attr__('disable_toggle_tooltip', 'microthemer'),
			)),

			'dependency_row' => $this->dependency_row(),
            'snippet_dependency_row' => $this->snippet_dependency_row(),
            'detected_package_item' => $this->detected_package_item()

			//'primary_context_menu_trigger' => $this->element_meta_template('primary'),

			//'oncanvas_feedback_template' =>$this->oncanvas_feedback_template()


		);

		return $templates; //$this->strip_tabs_and_line_breaks($templates); // not working but could be fixed maybe?

	}

	function targeting_radio($existing = false){

		$title = esc_html__('Choose suggestion', 'microthemer');
		$extra_class = '';

		if ($existing){
			$title = esc_html__('Choose selector', 'microthemer');
			$extra_class = ' existsel-radio';
		}

		return '
                 <span class="sugsel-radio targeting-sel-radio'. $extra_class . ' '.
		       $this->iconFont('radio-btn-unchecked', array('onlyClass' => 1)).'" 
                        title="'.$title.'">
                 </span>';

	}


	function variation_radio(){

		//  <input name="sel_variation" type="radio" value="1" />

		return '
				
                 <span class="alt-radio sel-variation-radio mt-variation-option '.
		       $this->iconFont('radio-btn-unchecked', array('onlyClass' => 1)).'" 
                        title="'.esc_html__('Choose variation', 'microthemer').'">
                 </span>';
	}

	function svg_chev_right(){
		return '<svg data-icon="BreadcrumbPartRight" aria-hidden="true" focusable="false" width="7" height="28" viewBox="0 0 7 28" class="bem-Svg left notch" style="display: block; color: rgb(235, 235, 235);"><path fill="currentColor" d="M6.5 14L.5 0H0v28h.5z"></path><path fill="#858585" d="M1 0H0l6 14-6 14h1l6-14z"></path></svg>';
	}
    


	function dependency_table(){

        $onLocal = !empty($this->preferences['npm_default_local']) ? ' on' : '';

        return '
        <div class="dependency-table-wrap">
            
             <div class="dependency-table-header">
                 <div class="search-dependencies tvr-input-wrap">
                    <input id="mt-npm-search" type="text" autocomplete="off" rel="npm_dependencies" data-autofill="" class="combobox has-arrows search-npm" name="search_npm" value="" placeholder="Search npm or enter jsDelivr URL">
                    <span class="combo-arrow tvr-field-arrow"></span>
                    <span class="mt-clear-field"></span>
                    <div class="cdn-local mt-binary-buttons'.$onLocal.'" data-run="mod.MThtml.npm.toggleDefaultDepDelivery">
                        <span class="binary-button-option" rel="0">CDN</span>
                        <span class="binary-button-option" rel="1">Local</span>
                    </div>
                    <div id="add-npm-dependency" class="tvr-button add-npm-dependency">Add</div>
                </div>
                <div class="dependency-notifications"></div>
             </div>
            
            
            <div class="dependency-columns">
                <div class="import-column">Default import</div>
                <div class="package-column">Package</div>
                <div class="size-column">Size</div>
                <div class="version-column">Version</div>
                <div class="about-column">Docs</div>
                <div class="delivery-column">Delivery</div>
                <div class="in-use-column">In use</div>
                <div class="delete-column">Delete</div>
            </div>
            <div class="dependency-rows"></div>
            
        </div>';
	}

	function dependency_row(){
		return '
        <div class="dependency-row" data-package="{value}">
            <div class="copy-import-syntax">
                 '.$this->iconFont('copy', array(
                    'class' => 'copy-import-syntax-icon',
                    'title' => esc_attr__('Copy import declaration', 'microthemer')
                )).'
            </div>
            <div class="import-field field-has-form">
                <span class="import-hint">import </span>
                <div class="npm-import-form edit-dep-form hidden">
                    <input type="text" class="npm-import-input" name="npm_import_input" value="{importSyntax}" />
                    <div class="tvr-button update-import-syntax update-npm-dependency">Update</div>
                </div>
                <span class="npm-import main-text">{importSyntax}</span>
                '.$this->iconFont('edit', array(
                    'class' => 'edit-import-syntax edit-dependency',
                    'title' => esc_attr__('Change default import syntax', 'microthemer')
                )).'
            </div>
            <div class="package-field main-text">
                <span class="import-hint">from </span>
                <a target="_blank" href="{linkToFile}" data-local="'.$this->micro_root_url . 'mt/js/draft/npm/{local}" data-cdn="https://cdn.jsdelivr.net/npm/{cdn}">"{value}"</a>
                 <span class="tvr-button tvr-gray browse-package-addons {hiddenAddons}" data-browse="{browseAddons}">Addons</span>
            </div>
            <div class="size-field">
               {size} KB
            </div>
            <div class="version-field field-has-form">
                <div class="npm-version-form edit-dep-form hidden">
                    <span class="tvr-input-wrap">
                         <input type="text" class="npm-version-input combobox has-arrows" name="npm_version_input" rel="npm_version_input"  value="{version}" />
                        <span class="combo-arrow"></span>
                    </span>
                   
                    <div class="tvr-button update-npm-version update-npm-dependency">Update</div>
                </div>
                <span class="npm-version main-text">{version}</span>
                '.$this->iconFont('edit', array(
                    'class' => 'edit-version edit-dependency',
                    'title' => esc_attr__('Change version', 'microthemer')
                )).'
            </div>
            <div class="about-field">
                '.$this->iconFont('docs', array(
                    'class' => 'package-readme-link',
                    'title' => esc_attr__('View Readme info', 'microthemer'),
                    'tag' => 'a',
                    'href' => "https://www.jsdelivr.com/package/npm/{rel}",
                    'target' => "_blank"
                )).'
            </div>
            <div class="delivery-field">
                <div class="cdn-local mt-binary-buttons" data-run="mod.MThtml.npm.toggleDepDelivery">
                    <span class="binary-button-option" rel="0">CDN</span>
                    <span class="binary-button-option" rel="1">Local</span>
                </div>
            </div>
            <div class="in-use-field">{inUse}</div>
            <div class="delete-field">
                '.$this->iconFont('bin', array(
                    'class' => 'delete-dependency',
                    'title' => esc_attr__('Delete library', 'microthemer')
                )).'
            </div>
        </div>';
	}

	function snippet_dependency_table(){
		return '
        <div class="snippet-dependency-table-wrap">
            
            <div class="dependency-columns">
                <div class="single-import-column">
                <span>Import</span>
                 '.$this->iconFont('add', array(
                    'class' => 'add-snippet-dependency-icon',
                    'title' => esc_attr__('Add package', 'microthemer'),
                    'data-label' => esc_attr__('add', 'microthemer')
                )).'
                </div>
                <div class="single-package-column">
                    <span title="Add any JavaScript packages this snippet needs (optional)">Package</span>
                    '.$this->iconFont('cog', array(
                        'rel' => 'mt-enqueue-js',
                        'class' => 'show-dialog',
                        'title' => esc_attr__('Manage installed packages', 'microthemer')
                    )).'
                </div>
            </div>
            
            <div class="dependency-rows"></div>
            
        </div>';
	}

	function snippet_dependency_row(){
		return '
        <div class="snippet-dependency-row" data-package="{value}" data-rel="{rel}">
          
            <div class="snippet-import-field">
                 <input type="text" class="snippet-dep-adjust snippet-import-input" placeholder="{importSyntaxPlaceholder}" value="{manualImportSyntax}" />
            </div>
            <div class="snippet-package-field">
                <span class="tvr-input-wrap">
                    <input type="text" class="snippet-dep-adjust snippet-select-package combobox has-arrows" placeholder="{autoPackName}" data-appto="#style-components" value="{manualPackName}" rel="current_npm_dependencies" />
                    <span class="combo-arrow"></span>
                </span>
            </div>
            <div class="snippet-remove-package-field">
                 '.$this->iconFont('disable', array(
				'class' => 'snippet-disable-package-icon {autoDisabled} {disabledHidden}',
				'title' => esc_attr__('Disable auto-added package', 'microthemer')
			    )).
               $this->iconFont('minus', array(
				'class' => 'snippet-remove-package-icon {removeHidden}',
				'title' => esc_attr__('Remove package', 'microthemer')
			)).'
            </div>
        </div>';
	}

	function detected_package_item(){
        return '
        <li class="detected-pacakge-item" data-package="{packName}" data-rel="{rel}">
            <div class="detected-package-result">
                <span class="mt-loader-icon hidden"></span>
                '.
               $this->iconFont( 'tick-box-unchecked', array(
                    'class' => 'fake-checkbox on',
                )). '' .
               $this->iconFont('check-circle', array(
		        'class' => 'detected-dependency-success hidden',
		        'title' => esc_attr__('Package added', 'microthemer')
                )).
               $this->iconFont('times-circle-regular', array(
                    'class' => 'detected-dependency-failed hidden',
                    'title' => esc_attr__('Package not added', 'microthemer')
                )).'
            </div>
            <div class="detected-package-name">
                <span>{packName} <span class="package-size-hint">({size} KB)</span></span>
            </div>
            
        </li>
        ';
	}

	function key_computed_info(){

		// define layers from outside going in
		$layers = array('margin', 'border-width', 'padding', 'content');

		// define side in order or grid-auto flow to make CSS simple
		$sides = array('top', 'left', 'right', 'bottom');

		// return the template which gets updated by JS in one DOM write
		return
			'<div id="box-model-layers">
                    '.$this->box_model_layers(0, 'margin', $layers, $sides).'
                </div>'
			.
			$this->key_computed_other();

	}

	function key_computed_other(){

		$info = array(
			'display' => array(
				'label' => 'display',
				'value' => '{display}'
			),
			'position' => array(
				'label' => 'position',
				'value' => '{position}'
			),
			'color' => array(
				'label' => 'color',
				'value' => '{color}'
			),
			'background-color' => array(
				'label' => 'background-color',
				'value' => '{background-color}'
			),
			'font-size' => array(
				'label' => 'font-size',
				'value' => '{font-size}'
			),
			'line-height' => array(
				'label' => 'line-height',
				'value' => '{line-height}'
			),
			'font-family' => array(
				'label' =>'font-family',
				'value' => '{font-family}'
			),
			// position
		);

		$html = '
	            <div id="key-element-info">
	                <div class="key-info-heading mt-expandable-heading expanded">'.esc_html__('Key information', 'microthemer').'</div>
                    <div class="key-element-info-inner mt-expandable-panel">';

		foreach ($info as $key => $array){
			$html.=
				'<span class="info-label '.$key.'-info-label">'.$array['label'].': </span>
                         <span class="info-value '.$key.'-info-value">'.$array['value'].'</span>';
		}

		$html.= '
                    </div>
                </div>';

		return $html;
	}

	function box_model_layers($i, $layer, $layers, $sides){

		$item = '<div class="mt-bm-layer mt-'.$layer.'-layer">';

		// inside content
		if ($layer === 'content'){
			$item.= '{width} x {height}';
		}

		// outer layer
		else {

			// color mask
			$item.= '<div class="tvr-mask"></div>';

			// label
			$layer_label = $layer === 'border-width' ? 'border' : $layer;
			$item.= '<div class="layer-label">'.$layer_label.'</div>';

			foreach($sides as $side) {

				$cssf = $layer === 'border-width'
					? 'border-' . $side . '-width'
					: $layer . '-' . $side;

				$item .= '<div class="mt-bm-cell mt-bm-' . $side . '-cell">{' . $cssf . '}</div>';


				// recursively call layer function if we have a next layer inside
				if ($side === 'left') {

					$i++;
					$next_layer = !empty($layers[$i]) ? $layers[$i] : false;

					if ($next_layer){
						$item.= $this->box_model_layers($i, $next_layer, $layers, $sides);
					}

				}

			}
		}

		$item.= '</div>';

		return $item;
	}

	function strip_tabs_and_line_breaks($string){
		// this does not work - not sure why.
		//preg_replace('/[\\t\\r\\n\n\t\r]+/g', "", $string);
		return $string;
	}

	function icon_inputs(){

		return array(
			'section' => array(
				'disabled'  => $this->icon_control(true, 'disabled', true, 'section', 'selector_section')
			),
			'selector' => array(
				'disabled'  => $this->icon_control(true, 'disabled', true, 'selector', 'selector_section',
					'selector_css')
			),
			'tab-input' => array(
				'tab_disabled'  => $this->icon_control(true, 'disabled', true, 'tab-input', 'selector_section',
					'selector_css', 'all-devices')
			),
			'group' => array(
				'pgtab_disabled'  => $this->icon_control(true, 'disabled', true,
					'group', 'selector_section', 'selector_css',
					'all-devices', 'group_slug', 'subgroup_slug', 'property_slug',
					'tvr_mcth', 'pgtab_slug'),

				'disabled'  => $this->icon_control(true, 'disabled', true, 'group', 'selector_section',
					'selector_css', 'all-devices', 'group_slug', 'subgroup_slug'),

				'flexitem'  => $this->icon_control(true, 'flexitem', true, 'group',
					'selector_section','selector_css', 'all-devices', 'group_slug'),

				'griditem'  => $this->icon_control(true, 'griditem', true, 'group',
					'selector_section','selector_css', 'all-devices', 'group_slug'),

				'nth_option' => '<li class="nth-item-option nth-item-option-0" data-nth="0">
                            <input class="nth-item-radio" type="radio" name="name_placeholder" value="0" />
                            '.$this->iconFont('radio-btn-unchecked', array('class' => 'fake-radio nth-radio-control')).'
                            <span class="nth-item-label">0</span>
                        </li>'
			),
			'subgroup' => array(
				'chained'  => $this->icon_control(true, 'chained', true, 'subgroup', 'selector_section',
					'selector_css', 'all-devices', 'group_slug', 'subgroup_slug')
			),
			'property' => array(
				'important'  => $this->icon_control(true, 'important', true, 'group', 'selector_section',
					'selector_css', 'all-devices', 'group_slug', 'subgroup_slug', 'property_slug'),
				'css_unit' => $this->icon_control(true, 'css_unit', true, 'property', 'selector_section',
					'selector_css', 'all-devices', 'group_slug', 'subgroup_slug', 'property_slug')
			)

		);


	}

	function loaders_template(){

		// loading gif common atts
		$loader_com = 'class="ajax-loader small" src="'.$this->thispluginurl.'/images/';

		return array(
			'default' => '<img id="loading-gif-template" '.$loader_com.'ajax-loader-green.gif" />',
			'wbg' => '<img id="loading-gif-template-wbg" '.$loader_com.'ajax-loader-wbg.gif" />',
			'mgbg' => '<img id="loading-gif-template-mgbg" '.$loader_com.'ajax-loader-mgbg.gif" />',
			'sec' => '<img id="loading-gif-template-sec" '.$loader_com.'sec-ajax-loader-green.gif" />',
			'sel' => '<img id="loading-gif-template-sel" '.$loader_com.'sel-ajax-loader-green.gif" />',
		);

	}

	function log_item_template(){

		// template for displaying save error and error report option
		$short = __('Error saving settings', 'microthemer');
		$long =
			'<p>' . sprintf(
				esc_html__('Please %s. The error report sends us information about your current Microthemer settings, server and browser information, and your WP admin email address. We use this information purely for replicating your issue and then contacting you with a solution.', 'microthemer'),
				'<span id="email-error" class="link">' . __('click this link to email an error report to Themeover', 'microthemer') . '</span>'
			) . '</p>
				<p>' . wp_kses(
				__('<b>Note:</b> reloading the page is normally a quick fix for now. However, unsaved changes will need to be redone.', 'microthemer'),
				array( 'b' => array() )
			). '</p>';


		return $this->display_log_item('error',
			array(
				'short'=> $short,
				'long'=> $long
			),
			0,
			'id="log-item-template"'
		);
	}

	function property_group_templates(){

		$pg_templates = array();

		// add property group templates
		foreach ($this->propertyoptions as $property_group_name => $property_group_array) {

			// we want root keys only for $property_group_array, to match propertyOptions format
			$array_keys = array_keys($property_group_array);
			$property_group_array_root = array();
			foreach($array_keys as $prop_slug){
				$property_group_array_root[$prop_slug] = '';
			}

			$pg_templates[$property_group_name] = $this->single_option_fields(
				'selector_section',
				'selector_css',
				array(),
				$property_group_array_root,
				$property_group_name,
				'',
				true
			);
		}

		return $pg_templates;
	}

	// frontend templates

	function frontend_markup_template(){

		// highlighting elements live on the frontend because we want them to be constrained by the frontend HTML boundary
		// but context menus live in the main UI window so avoid CSS conflicts and allow for overlap with the toolbars

		return '
				<div id="mt-stuff-ignore">
                   
                    <div id="mt-grid-highlight">
                        '.$this->oncanvas_grid().'
                    </div>
              
                    <div id="over-cont-hover">'.$this->element_meta_template('hover').'</div>
                        
                    <div id="mt-el-overlays"></div>'.

		       $this->frontend_overlay_template('primary').'
				       
				    <div id="mt-pos-layer"></div>  
				    <div id="mt-placeholder-layer"></div> 
                    
                </div>';
	}

	// the breadcrumb label and options for triggering the context menu are in thr iframe dom
	// so they move when the user scrolls. The actual context menu displays in the parent frame.
	function element_meta_template($forElementSelection){

		$suggestionsTrigger = $this->ui_toggle(
			'show_suggestions_menu',
			esc_attr__('Show targeting options', 'microthemer'),
			esc_attr__('Close targeting options', 'microthemer'),
			false,
			'show-suggestions-icon mt-ui-element mtif',
			false,
			array(
				'dataAtts' => array(
					'fhtml' => 1,
					'no-save' => 1,
					'forpopup' => 'contextMenu',
				),
				'inlineSVG' => 1
			)
		);

		//$suggestionsTrigger = $this->icon('');

		$onCanvasTrigger = $this->ui_toggle(
			'show_visual_controls_menu',
			esc_attr__('Enable visual controls', 'microthemer'),
			esc_attr__('Disable visual controls', 'microthemer'),
			true, // visual controls are on by default - but can be customised
			'show-visual-controls-icon mt-ui-element mtif',
			false,
			array(
				'dataAtts' => array(
					'fhtml' => 1,
					'no-save' => 1
				)
			)
		);

		$breadcrumbLabel = '';

		if ($forElementSelection === 'hover'){
			$breadcrumbLabel = '<span class="mt-breadcrumb-label mt-ui-element"></span>';
		}

		else if ($forElementSelection === 'primary'){
			$breadcrumbLabel = '<div id="mt-nested-els" class="mt-breadcrumb-nav mt-ui-element"></div>';
		}


		return '
				<div id="'.$forElementSelection.'-element-meta" class="mt-element-metas mt-ui-element">
			        '. $suggestionsTrigger . $onCanvasTrigger . $breadcrumbLabel .'
			    </div>';
	}

	function oncanvas_grid(){

		$gridlines = '';

		for ($x = 0; $x <= 24; $x++) {
			$gridlines.= '<div class="mtoc-line"></div>';
		}

		return '<div class="mt-oncanvas-grid-wrap"><div class="mt-oncanvas-grid">'.$gridlines.'</div></div>';
	}

	function oncanvas_feedback_template(){

		return '
				<div id="tvr-oncanvas-feedback">
                    <div class="tvr-adjusted-property">
                        <span class="tvr-adjusted-property-label"></span>
                        <span class="tvr-adjusted-property-value"></span>
                    </div>
                </div>';
	}

	function frontend_overlay_template($forElementSelection = false){

		// for normal overlays
		$id = 'over-cont-%s';
		$class = 'tvr-overlay tvr-container-overlay';
		$style = 'style="%s"';
		$elementMeta = '';
		$onCanvasControls = '';

		// inner padding/margin/border divs
		$inner = $this->frontend_overlay_template_inner($forElementSelection, $style);

		// for the fixed trigger el overlay with context menu and on-canvas controls
		if ($forElementSelection === 'primary'){
			$id = 'over-cont-'.$forElementSelection;
			$class = '';
			$elementMeta = $this->element_meta_template($forElementSelection);
			$inner = '<div id="mt-bm-shading">'.$inner.'</div>';

			// todo add controls for size and spacing, transform, position (grid adapt canvas, not tied to selected element),
			$onCanvasControls = $this->oncanvas_feedback_template($forElementSelection);
		}



		return
			'<div id="'.$id.'" class="'.$class.'">'.
			$inner . $onCanvasControls . $elementMeta
			.'</div>';
	}

	function frontend_overlay_template_inner($forElementSelection, $style){
		return '
			    <div class="tvr-overlay tvr-margin-overlay" '.$style.'>
                    <div class="tvr-mask"></div>
                </div>
                <div class="tvr-overlay tvr-border-overlay" '.$style.'>
                    <div class="tvr-mask"></div>
                </div>
                <div class="tvr-overlay tvr-padding-overlay" '.$style.'>
                    <div class="tvr-mask"></div>
                </div>
                <div class="tvr-overlay tvr-content-overlay" '.$style.'>
                    <div class="tvr-mask"></div>
                </div>';
	}

	// display the css filters
	function display_css_filters(){
		/*$html = '
				<div class="quick-opts first-quick-opts">
					<div class="quick-opts-inner">';*/

		$html = '';

		$i = 0;
		foreach ($this->css_filters as $key => $arr){
			if ($i === 0){
				$extra = $this->css_filter_list(
					$this->css_filters['pseudo_elements']['items'],
					'pseudo_elements',
					$this->css_filters['pseudo_elements']['label']
				);
			} elseif ($i === 1){
				++$i;
				continue;
			} else {
				$extra = '';
			}
			$html.= '
							<div class="mt-col mt-col'.(++$i).'">'
			        . $this->css_filter_list(
					$arr['items'],
					$key,
					$arr['label']
				). $extra . '
							</div>';
		}


		$html.= $this->iconFont('eraser', array(
			'class' => 'clear-css-filters',
			'innerHTML' => esc_html__('Clear all', 'microthemer'),
			'title' => esc_attr__('Clear all modifiers', 'microthemer'),
		));


		/*$clear_text = esc_html__('Clear all', 'microthemer');

				$html.= '
						<span class="clear-filters-wrap"
							  title="'.esc_html__('Clear all selector adjustments', 'microthemer').'">
							<span class="clear-icon clear-css-filters clear-css-filters-icon"></span>
							<span class="clear-css-filters clear-css-filters-text">'.$clear_text.'</span>
						</span>

					';*/

		return $html;

	}

	// display the targeting suggestions menu
	function targeting_suggestions($context){

		ob_start();
		?>

		<div id="mt-suggestions-<?php echo $context; ?>" class="scrollable-area mt-suggestions radio-set-container">

			<div class="mt-panel-header existing-selector-heading">
				<div class="mt-panel-title"><?php echo esc_html__('Resume an existing selector', 'microthemer'); ?></div>
				<span class="mtif mtif-times-circle-regular close-context-menu"></span>
			</div>

			<div class="mt-existing-selectors"></div>


			<div class="mt-panel-header create-new-selector-heading">
				<div class="mt-panel-title">

                            <span class="new-selector-text">
                                 <?php echo esc_html__('Create a new selector', 'microthemer'); ?>
                            </span>

					<span class="edit-selector-targeting-text">
                                <?php echo esc_html__('Edit selector targeting', 'microthemer'); ?>
                            </span>

				</div>
				<span class="mtif mtif-times-circle-regular close-context-menu"></span>
			</div>

			<div class="mt-panel-header additional-selector-heading">
				<div class="mt-ui-toggle uit-par mt-panel-title additional-selector-toggle"
				     data-aspect="create_additional_selector" data-no-save="1">
					<?php echo esc_html__('Create an additional selector', 'microthemer'); ?>
				</div>
			</div>

			<div class="new-selector-suggestions">

				<div class="mt-selector-adjustments">

					<ul class="fav-filters css-filter-list">
						<?php echo $this->fav_css_filters; ?>
					</ul>

					<?php
					$specificity_high = !empty($this->preferences['specificity_preference']) ? ' on' : '';
					?>
					<div class="mt-ui-toggle uit-par specificity-preference<?php echo $specificity_high; ?>" data-aspect="specificity_preference">
                                    <span class="specificity-label"
                                          title="<?php esc_attr_e('Sort by CSS specificity', 'microthemer'); ?>">
                                        <?php esc_html_e('Specificity', 'microthemer'); ?>:
                                    </span>
						<span class="specificity-low mt-ui-toggle"
						      title="<?php esc_attr_e('low CSS specificity (favor classes)', 'microthemer'); ?>">
                                        <?php esc_html_e('low', 'microthemer'); ?>
                                    </span>
						<span class="specificity-high mt-ui-toggle"
						      title="<?php esc_attr_e('high CSS specificity (favor ids)', 'microthemer'); ?>">
                                        <?php esc_html_e('high', 'microthemer'); ?>
                                    </span>
					</div>
				</div>

				<div class="code-suggestions mt-selector-suggestions"></div>

				<div class="code-suggestions-cta">

					<span><?php esc_html_e('Apply a new style change or', 'microthemer'); ?></span>
					<span class="tvr-button save-draft-selector"><?php esc_html_e('Save selector', 'microthemer'); ?></span>
					<div class="wizard-update-cur tvr-button">
						<?php esc_html_e('Update selector', 'microthemer'); ?>
					</div>
				</div>

			</div>

		</div>

		<?php

		return ob_get_clean();

	}

	// output a list of css filters (pseudo classes, elements, page-specific)
	function css_filter_list($filters, $type, $heading) {
		$html = '
				<div class="filter-heading">'.$heading.'</div>';
		$num_items = count($filters);
		$index = 0;

		// there are lots of pseudo, split into 3 columns
		if (($num_items >= 14)){
			$break = $num_items/2.5;
			$j = -1;
			foreach($filters as $k => $v){
				if (++$j > $break){
					++$index;
					$j = 0;
				}
				$split_filters[$index][$k] = $v;
			}
		} else {
			$split_filters[0] = $filters;
		}

		// loop through normalised $filters
		foreach ($split_filters as $i => $f){
			$html.= '
					<ul class="css-filter-list flist-'.$type.' cssfl-index-'.$i.'">';
			foreach($f as $key => $arr){
				$text = !empty($arr['text']) ? $arr['text'] : $key;
				$edClass = !empty($arr['editable']) ? ' filter-editable' : '';
				$li =
					$this->ui_toggle(
						$type,
						$arr['tip'],
						$arr['tip'],
						// left over enabled
						!empty($this->preferences[$type][$key]),
						'css-filter-item filter-'.$this->pseudo_class_format($text) . $edClass,
						false,
						array(
							'tag' => 'li',
							'dataAtts' => array(
								'filter' => $key,
								'type' => $type,
								'no-save' => $type === 'page_specific' ? 0 : 1
							),
							'text' => $text,
							'inner_icon' => $this->iconFont('tick-box-unchecked', array(
								'class' => 'alt-checkbox mt-ui-toggle'
							)),
							'pref_sub_key' => $text,
							'css_filter' => $arr
						)
					);
				$html.= $li;
				// save for favs list if required
				if (!empty($arr['common'])){
					// the title is a bit annoying on favourites.
					$this->fav_css_filters.= preg_replace('/title=\"([^"]*)\"/i', '', $li, 1);
				}
			}

			// provide an option to remember the choice
			//$html.= '<li class="filter-choice">'.esc_html__('More', 'microthemer').'</li>';

			$html.= '</ul>';
		}

		return $html;
	}

	// @return array - Retrieve the plugin options from the database.
	function getOptions() {
		// default options (html layout sections only - no default selectors)
		if (!$theOptions = get_option($this->optionsName)) {
			$theOptions = $this->default_folders;
			$theOptions['non_section']['hand_coded_css'] = '';
			// add_option rather than update_option (so autoload can be set to no)
			add_option($this->optionsName, $theOptions, '', 'no');
		}
		$this->options = $theOptions;
	}


	function pseudo_class_format($pseudo){
		return str_replace(array( ':', '(', ')' ), '', $pseudo);
	}

    // todo - we could store published autoload preferences by copying these upon publish
    // but then it can't conditionally autoload, so may not be worth the trade off.
    function copyToAutoloadValues($fullPreferences){

        $autoloadPreferences = array();

        foreach($this->autoloadPreferencesList as $key){

            // Some dynamic default prefs might not be set when this first runs
            if (isset($fullPreferences[$key])){
	            $autoloadPreferences[$key] = $fullPreferences[$key];
            }
        }

        return $autoloadPreferences;
    }

	// @return array - Retrieve the plugin preferences from the database.
	function getPreferences($special_checks = false, $pd_context = false) {

		$full_preferences = array_merge(
            $this->default_preferences,
            $this->default_preferences_dont_reset_or_export,
            $this->default_preferences_exportable,
            $this->default_preferences_resetable
        );

		// default preferences
		if (!$thePreferences = get_option($this->preferencesName)) {

			$thePreferences = $full_preferences;

			// add_option rather than update_option (so autoload can be set to no)
			add_option($this->preferencesName, $thePreferences, '', 'no');
		}

		// hard set this as version 7 is always in draft mode with manual publish
		// more formal conversion in DB pending
		$thePreferences['draft_mode'] = 1;

		// hard set this off as we're trialing not supporting this
		$thePreferences['dock_wizard_right'] = 0;

		$this->preferences = $thePreferences;

		// checks we only need to do once when this function is first called
		if ($special_checks){

			/*wp_die('the special '. !empty($this->preferences['version']) . ' '.$this->preferences['version']. ' '.$this->version. ' '.($this->preferences['version'] != $this->version));*/

			// check if this is a new version of Microthemer
			if ($this->new_version){ // empty($this->preferences['version']) || $this->preferences['version'] != $this->version){

				//$this->new_version = true;

				// maybe update revisions table
				$this->maybeCreateOrUpdateRevsTable();

                // maybe create the HTML mods table
                if ($this->hasContentCapability()){
	                $this->maybeCreateOrUpdateContentTable();
                }

				// signal that all selectors should be recompiled (to ensure latest data structure)
				$this->update_preference('manual_recompile_all_css', 1);
			}

			// ensure preferences are defined (for when I add new preferences that upgrading users won't have)
			$this->ensure_defined_preferences($full_preferences, $pd_context);

			// manually override user preferences after code changes
			$this->maybe_manually_override_preferences();
		}

		// Setup autoload preferences if necessary too
		if (!$autoloadPreferences = get_option($this->autoloadPreferencesName)) {
            add_option($this->autoloadPreferencesName, $this->copyToAutoloadValues($thePreferences)); // autoload = yes
		}

	}

	// Save the preferences


	function preferences_grid_items($opts){

        $html = '';
		// labels
		$yes_label = __('Yes', 'microthemer' );
		$no_label = __('No', 'microthemer' );

		foreach ($opts as $key => $array) {

			// skip edge mode if not available
			if ($key == 'edge_mode' and !$this->edge_mode['available']){
				continue;
			}

			// common
			$input_name = 'tvr_preferences['.$key.']';
			$array['link'] = ( !empty($array['link']) ) ? $array['link'] : '';

			// if radio
			if (empty($array['is_text'])){

				// ensure various vars are defined
				$li_class = 'fake-radio-parent';
				$array['label_no'] = ( !empty($array['label_no']) ) ? $array['label_no'] : '';
				$yes_val = ($key == 'draft_mode') ? $this->current_user_id : 1;
				$no_val = 0;

				if (!empty($this->preferences[$key])) {
					$yes_checked = 'checked="checked"';
					$yes_on = 'on';
					$no_checked = $no_on = '';
				} else {
					$no_checked = 'checked="checked"';
					$no_on = 'on';
					$yes_checked = $yes_on = '';
				}

				$form_options = '
                <span class="yes-wrap p-option-wrap">
                    <input type="radio" autocomplete="off" class="radio"
                       name="'.$input_name.'" value="'.$yes_val.'" '.$yes_checked.' />
                       '.$this->iconFont('radio-btn-unchecked', array('class' => 'fake-radio '.$yes_on)).'
                 
                    <span class="ef-label">'.$yes_label.'</span>
                </span>
                <span class="no-wrap p-option-wrap">
                    <input type="radio" autocomplete="off" class="radio"
                       name="'.$input_name.'" value="'.$no_val.'" '.$no_checked.' />
                     '.$this->iconFont('radio-btn-unchecked', array('class' => 'fake-radio '.$no_on)).'
                    <span class="ef-label">'.$no_label.'</span>
                </span>';

			}

			// else if input
			else {
				$li_class = 'mt-text-option';

				if (!empty($array['one_line'])){
					$li_class.= ' one-line';
				}

				$input_id = $input_class = $arrow_class = $class = $rel = $arrow = '';
				$input_value = ( !empty($this->preferences[$key]) ) ? $this->preferences[$key] : '';
				$extra_info = '';

				// does it need a custom id?
				if (!empty($array['input_id'])){
					$input_id = $array['input_id'];
				}
				// does it need a custom input class?
				if (!empty($array['input_class'])){
					$input_class = $array['input_class'];
				}
				// does it need a custom arrow class?
				if (!empty($array['arrow_class'])){
					$arrow_class = $array['arrow_class'];
				}
				// does it need a custom input name?
				if (!empty($array['input_name'])){
					$input_name = $array['input_name'];
				}
				// does it need a custom input value?
				if (!empty($array['input_value'])){
					$input_value = $array['input_value'];
				}
				// do we want to add a data attribute (quick and dirty way to support one att)
				if (!empty($array['extra_info'])){
					$extra_info = ' data-info="'. $array['extra_info'].'"';
				}

				if (!empty($array['prop'])){
					$extra_info.= ' data-prop="'. $array['prop'].'"';
				}

				// exception for css unit set (keep blank)
				if ($input_id == 'css_unit_set'){
					$input_value = '';
				}

				// is it a combobox?
				if (!empty($array['combobox'])){
					$class = 'combobox has-arrows';
					$rel = 'rel="'.$array['combobox'].'"';
					$arrow = '<span class="combo-arrow '.$arrow_class.'"></span>';
				}

				if (!empty($input_id)){
					$input_id = 'id="'.$input_id.'"';
				}

				$form_options = '
                <span class="tvr-input-wrap">
                    <input '.$input_id.' type="text" autocomplete="off" name="'.$input_name.'"  
                    class="'.$class . ' ' . $input_class.'" '.$rel . $extra_info.'
                    value="'. esc_attr($input_value).'" />'
                .$arrow .
                '</span>';
			}

			// sometimes we use empty cell after
			if (!empty($array['empty_before'])){
				$html.= '<li class="empty-cell empty-before-'.$key.'"></li>';
			}

			$li_class.= ' mt-pref-' . $key;

			// the option
			$html.= '
            <li class="'.$li_class.'">
                <label>
                    <span title="'.esc_attr($array['explain']).'">
                        '.esc_html($array['label']) . ' ' . $array['link'].'
                    </span>
                </label>
                '.$form_options.'
            </li>';

			// sometimes we use empty cell after
			if (!empty($array['empty_after'])){
				$html.= '<li class="empty-cell empty-after-'.$key.'"></li>';
			}
		}

        return $html;

	}

    // common function for outputting yes/no radios
	function preferences_grid($pref_cats, $settings_class){

		// ensure CSS recompile is off by default
		$this->preferences['manual_recompile_all_css'] = 0;

		// generate the HTML
		$html = '
        <ul id="'.$settings_class.'" class="mt-form-settings '.$settings_class.'">';

		foreach ($pref_cats as $cat_key => $cat_array){

			$html.= '
            <li class="empty-cell empty-before-'.$cat_key.'"></li>
            <li class="preference-category pref-cat-'.$cat_key.'">'.$cat_array['label'].'</li>';

			$html.= $this->preferences_grid_items($cat_array['items']);
		}

		$html.= '
        </ul>';

		return $html;
	}

	// common function for outputting yes/no radios
	function output_radio_input_lis($opts, $hidden = ''){

		foreach ($opts as $key => $array) {

			// ensure various vars are defined
			$array['label_no'] = ( !empty($array['label_no']) ) ? $array['label_no'] : '';
			$array['default'] = ( !empty($array['default']) ) ? $array['default'] : '';
			$array['link'] = ( !empty($array['link']) ) ? $array['link'] : '';
			$yes_val = ($key == 'draft_mode') ? $this->current_user_id : 1;

			// skip edge mode if not available
			if ($key == 'edge_mode' and !$this->edge_mode['available']){
				continue;
			}

			// ensure this setting is off by default
			$this->preferences['manual_recompile_all_css'] = 0;

			?>
			<li class="fake-radio-parent <?php echo $hidden; ?>" xmlns="http://www.w3.org/1999/html">
				<label>
							<span title="<?php echo esc_attr($array['explain']); ?>">
								<?php echo esc_html($array['label']) . $array['link']; ?>
							</span>
				</label>

				<span class="yes-wrap p-option-wrap">
							<input type='radio' autocomplete="off" class='radio'
							       name='tvr_preferences[<?php echo $key; ?>]' value='<?php echo $yes_val; ?>'
								<?php
								if ( !empty($this->preferences[$key])) {
									echo 'checked="checked"';
									$on = 'on';
								} else {
									$on = '';
								}
								?>
                            />
                            <?php
                            echo $this->iconFont('radio-btn-unchecked', array('class' => 'fake-radio '.$on))
                            ?>

							<span class="ef-label"><?php esc_html_e('Yes', 'microthemer'); ?></span>
						</span>
				<span class="no-wrap p-option-wrap">
							<input type='radio' autocomplete="off" class='radio' name='tvr_preferences[<?php echo $key; ?>]' value='0'
								<?php
								if ( (empty($this->preferences[$key]))
									// exception for mq set overwrite as this isn't stored as a global preference
									//and $key != 'overwrite_existing_mqs'
								) {
									echo 'checked="checked"';
									$on = 'on';
								} else {
									$on = '';
								}
								?>
                            />
                             <?php
                             echo $this->iconFont('radio-btn-unchecked', array('class' => 'fake-radio '.$on))
                             ?>
							<span class="ef-label"><?php esc_html_e('No', 'microthemer'); ?></span>
						</span>
			</li>
			<?php
		}
	}

	// common function for text inputs/combos
	function output_text_combo_lis($opts, $hidden = ''){
		foreach ($opts as $key => $array) {
			$input_id = $input_class = $arrow_class = $class = $rel = $arrow = '';
			$input_name = 'tvr_preferences['.$key.']';
			$input_value = ( !empty($this->preferences[$key]) ) ? $this->preferences[$key] : '';
			$extra_info = '';

			// does it need a custom id?
			if (!empty($array['input_id'])){
				$input_id = $array['input_id'];
			}
			// does it need a custom input class?
			if (!empty($array['input_class'])){
				$input_class = $array['input_class'];
			}
			// does it need a custom arrow class?
			if (!empty($array['arrow_class'])){
				$arrow_class = $array['arrow_class'];
			}
			// does it need a custom input name?
			if (!empty($array['input_name'])){
				$input_name = $array['input_name'];
			}
			// does it need a custom input value?
			if (!empty($array['input_value'])){
				$input_value = $array['input_value'];
			}
			// do we want to add a data attribute (quick and dirty way to support one att)
			if (!empty($array['extra_info'])){
				$extra_info = ' data-info="'. $array['extra_info'].'"';
			}

			// exception for css unit set (keep blank)
			if ($input_id == 'css_unit_set'){
				$input_value = '';
			}

			// is it a combobox?
			if (!empty($array['combobox'])){
				$class = 'combobox has-arrows';
				$rel = 'rel="'.$array['combobox'].'"';
				$arrow = '<span class="combo-arrow '.$arrow_class.'"></span>';
			}

			if (!empty($input_id)){
				$input_id = 'id="'.$input_id.'"';
			}

			?>
			<li class="tvr-input-wrap <?php echo $hidden; ?>">
				<label class="text-label">
							<span title="<?php echo esc_attr($array['explain']); ?>">
								<?php echo esc_html($array['label']); ?>
							</span>
				</label>
				<input type='text' autocomplete="off" name='<?php echo esc_attr($input_name); ?>'
					<?php echo $input_id; ?>
					   class="<?php echo $class . ' ' . $input_class; ?>" <?php echo $rel . $extra_info; ?>
					   value='<?php echo esc_attr($input_value); ?>' />
				<?php echo $arrow; ?>
			</li>
			<?php
		}
	}

    // the HTML modifications table
    // We copy the modifications for each folder to a row in this table so that retrieval is very quick for
    // whatever folders load on the current page - as opposed to loading full MT options data
    function maybeCreateOrUpdateContentTable() {
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_content";
		$table_ver_num = get_option($this->html_mods_ver_name);

		if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name
		    || $table_ver_num < $this->db_chg_in_ver) {

			$charset_collate = $wpdb->get_charset_collate();

			$sql = "CREATE TABLE $table_name (
			seq MEDIUMINT(9) NOT NULL,
			name VARCHAR(80) DEFAULT '' NOT NULL,
			slug VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '' NOT NULL,
			type VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '' NOT NULL,
			aspect VARCHAR(50) DEFAULT '' NOT NULL,
			published TINYINT(1) DEFAULT 0 NOT NULL,
			content LONGTEXT NOT NULL,
			modified_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
			meta TEXT NOT NULL,
			func_ref TINYINT(1) DEFAULT 0,
			PRIMARY KEY (slug(191), type(50), published),
			KEY micro_content_lookup (published, slug(191), type(50), seq),
			KEY type_idx (type(50)),
			KEY published_idx (published),
			KEY aspect_idx (aspect(50)),
			KEY seq_idx (seq),
			KEY func_ref_idx (func_ref),
			KEY name_idx (name)
		) $charset_collate;";

			require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
			dbDelta($sql);

			update_option($this->html_mods_ver_name, $this->version);
			return true;
		}

		return false;
	}


	// create revisions table if it doesn't exist
	function maybeCreateOrUpdateRevsTable() {
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_revisions";
		$micro_ver_num = get_option($this->micro_ver_name);

		// only execute following code if table doesn't exist.
		// dbDelta function wouldn't overwrite table,
		// But table version num shouldn't be updated with current plugin version if it already exists
		if( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name or
		    $micro_ver_num < $this->db_chg_in_ver) {

			$charset_collate = '';
			if (!empty($wpdb->charset)) {
				$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
			}
			if (!empty($wpdb->collate)) {
				$charset_collate .= " COLLATE $wpdb->collate";
			}

			$sql = "CREATE TABLE $table_name (
						id mediumint(9) NOT NULL AUTO_INCREMENT,
						time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
						user_action TEXT NOT NULL,
						data_size VARCHAR(10) DEFAULT '' NOT NULL,
						settings longtext NOT NULL,
						preferences longtext DEFAULT NULL,
						snippets longtext DEFAULT NULL,
                        meta longtext DEFAULT NULL,
						saved BOOLEAN DEFAULT 0,
						upgrade_backup BOOLEAN DEFAULT 0,
						UNIQUE KEY id (id)
						) $charset_collate;";

			//wp_die('the table should update' . $sql);

			require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
			dbDelta($sql);

			// store the table version in the wp_options table (useful for upgrading the DB)
			update_option($this->micro_ver_name, $this->version);

			return true;
		}
		else {
			return false;
		}
	}


	// format user action in same json format - see JS function: TvrUi.user_action() - this isn't very DRY. JS templating would be better.
	function json_format_ua($icon, $item, $val = false){
		$json = '{"items":["'.$item.'"],"val":"'.$val.'","icon":"'.$icon.'","main_class":"",';
		$json.= '"icon_html":"<span class=\"h-i no-click '.$icon.'\" ></span>",';
		$json.= '"html":"<span class=\"history-item history_'.$this->to_param($item).'\"><span class=\"his-items\"><span>'.$item.'</span></span>';
		if ($val){
			//$json.= '<span class=\"his-val\">'.htmlentities($val).'</span>'; // escHistory
			$json.= '<span class=\"his-val\">'.$val.'</span>';
		}
		$json.= '</span>"}';
		return $json;
	}

	// Update the Revisions Table
	function updateRevisions(
		&$save_data, $user_action = '', $tryCreate = true, $preferences = false, $upgrade_backup = false, $snippets = true, $meta = ''
	) {

		//debug_print_backtrace();
		//wp_die('user_action_debug: '. $user_action);

		// sometimes we don't want to log an action e.g. if editing a selector's code via the editor
		// the change will be shown in the code editor change history entry
		// and we don't want them to restore one without the other (selector code and editor content)
		if (is_null($user_action) or $user_action === 'null' or $user_action === 'false' or $user_action === false){
			return 'bail'; // false would generate an error message
		}

		$user_action = html_entity_decode($user_action);

		// create/update revisions table if it doesn't already exist or is out of date
		if ($tryCreate){
			$this->maybeCreateOrUpdateRevsTable();
		}

		// include the user's current media queries for restoring back
        if (!empty($save_data)){
	        $save_data['non_section']['active_queries'] = $this->preferences['m_queries'];

            // Also include a handful of other preferences that should overwrite the current ones
            if (empty($save_data['non_section']['meta']['merge_preferences'])){
	            $save_data['non_section']['meta']['merge_preferences'] = array();
            }
            $this->getRelevantPreferences(
                    $save_data['non_section']['meta']['merge_preferences'],
                    $this->preferences
            );
	        //wp_die('<pre>merge_preferences: '.print_r($save_data['non_section']['meta'], true).'</pre>');
        }

		// add the revision to the table
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_revisions";
		$serialized_data = $save_data ? serialize($save_data) : '';
        $serialized_preferences = ($preferences ? serialize($preferences) : '');
        $serialized_snippets = $snippets && $this->supportContent()
	        ? serialize($this->contentMethod('getSnippets', array(-1, 0, ARRAY_A)))
	        : '';
        $dataToSize = $serialized_data . $serialized_preferences;
		$data_size = round(strlen($dataToSize)/1024).'KB';
		// $wpdb->insert (columns and data should not be SQL escaped): https://developer.wordpress.org/reference/classes/wpdb/insert/
		$rows_affected = $wpdb->insert( $table_name, array(
			'time' => current_time('mysql', false), // use blogs local time - doesn't work on Nelson's site
			//'time' => $this->adjust_unix_timestamp_for_local(time(), 'mysql'), // nor does this
			//'time' => date_i18n('Y-m-d H:i:s'), // or this

			'user_action' => $user_action,
			'data_size' => $data_size,
			'settings' => $serialized_data,

			// pass in preferences when a revision should revert to workspace settings
			// adding this so users can rollback to a pre-speed version of MT in case of an upgrade issue
			'preferences' => $serialized_preferences,
			'upgrade_backup' => $upgrade_backup,
            'snippets' => $serialized_snippets,
            //'meta' => $meta
		));
		$revision_id = $wpdb->insert_id;

        // Store the current revision in the preferences
        if ($revision_id){
            $this->savePreferences(array(
                    'current_revision' => $revision_id
            ));
        }

		/*$this->log(
					esc_html__('$rows_affected: '.$rows_affected, 'microthemer'),
					'<pre>Hello' . print_r($user_action, true) . '</pre>'
				);*/

		//$this->show_me.= '<pre>$rows_affected:  '.$rows_affected.'</pre>';

		$default_num_revs = 50;
		$max_revisions = !empty($this->preferences['num_history_points'])
			? intval($this->preferences['num_history_points'])
			: $default_num_revs;

		// cap lowest and highest number of revisions
		if ($max_revisions > 300){
			$max_revisions = 300;
		} if ($max_revisions < 1){
			$max_revisions = 1;
		}

		$maybe_exclude_backups = !$upgrade_backup ? 'and upgrade_backup != 1' : '';

		// check if an old revision needs to be deleted
		$wpdb->get_results("select id from $table_name 
				where saved != 1 $maybe_exclude_backups order by id asc");

		// this will not delete saved or backups for regular saves. And wont delete saved backups ever.
		if ($wpdb->num_rows > $max_revisions) {
			$excess_rows = ($wpdb->num_rows - $max_revisions);
			$sql = "delete from $table_name 
                    where saved != 1 $maybe_exclude_backups order by id asc limit $excess_rows";
			$wpdb->query($sql);
		}

		return $revision_id;
	}

	function updateRevisionSaveStatus($rev_id, $rev_save_status){
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_revisions";
		$wpdb->query(
			$wpdb->prepare(
				"update $table_name set saved = %d where id = %d",
				$rev_save_status, $rev_id
			)
		);
	}

	// adjust unix time stamp for local time
	function adjust_unix_timestamp_for_local($unix_timestamp, $format = 'timestamp'){
		$mysql_format = get_date_from_gmt( date( 'Y-m-d H:i:s', $unix_timestamp ) );
		return $format === 'timesptamp'
			? strtotime($mysql_format)
			: $mysql_format;
		//return strtotime( get_date_from_gmt( date( 'Y-m-d H:i:s', $unix_timestamp ) ));
	}

	// custom function for time diff as we want seconds
	function human_time_diff( $from, $to = '' ) {

		if ( empty( $to ) ) {
			$to = current_time( 'timestamp', false); // use blogs local time
		}

		$diff = (int) abs( $to - $from );

		if ( $diff < 60 ) {
			//$since = $diff . ' secs';
			$since = sprintf( _n( '%s sec', '%s secs', $diff ), $diff );
		} elseif ( $diff < HOUR_IN_SECONDS ) {
			$mins = round( $diff / MINUTE_IN_SECONDS );
			if ( $mins <= 1 )
				$mins = 1;
			/* translators: min=minute */
			$since = sprintf( _n( '%s min', '%s mins', $mins ), $mins );
		} elseif ( $diff < DAY_IN_SECONDS && $diff >= HOUR_IN_SECONDS ) {
			$hours = round( $diff / HOUR_IN_SECONDS );
			if ( $hours <= 1 )
				$hours = 1;
			$since = sprintf( _n( '%s hour', '%s hours', $hours ), $hours );
		} elseif ( $diff < WEEK_IN_SECONDS && $diff >= DAY_IN_SECONDS ) {
			$days = round( $diff / DAY_IN_SECONDS );
			if ( $days <= 1 )
				$days = 1;
			$since = sprintf( _n( '%s day', '%s days', $days ), $days );
		} elseif ( $diff < MONTH_IN_SECONDS && $diff >= WEEK_IN_SECONDS ) {
			$weeks = round( $diff / WEEK_IN_SECONDS );
			if ( $weeks <= 1 )
				$weeks = 1;
			$since = sprintf( _n( '%s week', '%s weeks', $weeks ), $weeks );
		} elseif ( $diff < YEAR_IN_SECONDS && $diff >= MONTH_IN_SECONDS ) {
			$months = round( $diff / MONTH_IN_SECONDS );
			if ( $months <= 1 )
				$months = 1;
			$since = sprintf( _n( '%s month', '%s months', $months ), $months );
		} elseif ( $diff >= YEAR_IN_SECONDS ) {
			$years = round( $diff / YEAR_IN_SECONDS );
			if ( $years <= 1 )
				$years = 1;
			$since = sprintf( _n( '%s year', '%s years', $years ), $years );
		}
		return $since;
	}

	/**
	 * The definitive renderer. Renders the HTML for a new-format (version 2) history item,
	 * including complex icons, tooltips, and colored boxes.
	 * @param array $ua The decoded user action data array.
	 * @return array An array containing the final HTML pieces.
	 */
	function render_new_history_item($ua, $rev) {
		// These variables will be used to build the final output.
		$icon_html = '';
		$action_html_content = ''; // The content INSIDE the wrapper span
		$wrapper_tooltip_attr = '';
		$wrapper_main_class = 'history-item history_property';
		$extra_td_class = '';

		// 1. Build Icon HTML
		if (!empty($ua['icons']) && is_array($ua['icons'])) {
			$all_icon_spans = [];
			foreach ($ua['icons'] as $icon_data) {
				// ESCAPING: `esc_attr` makes the class attribute safe.
				$icon_class = isset($icon_data['iconClass']) ? esc_attr($icon_data['iconClass']) : '';
				// ESCAPING: `esc_html` makes any inner text safe.
				$inner_content = isset($icon_data['innerContent']) ? esc_html($icon_data['innerContent']) : '';
				$all_icon_spans[] = '<span class="h-i no-click ' . $icon_class . '">' . $inner_content . '</span>';
			}
			$icon_html = implode(' ', $all_icon_spans);
		}

		// 2. Build the display breadcrumbs
		$display_crumbs_html = '';
		if (!empty($ua['displayItems']) && is_array($ua['displayItems'])) {
			$item_spans = [];
			foreach ($ua['displayItems'] as $item) {
				// ESCAPING: `esc_html` makes each breadcrumb item safe before wrapping in a span.
				$item_spans[] = '<span>' . esc_html($item) . '</span>';
			}
			$display_crumbs_html = '<span class="his-items">' . implode('<span> » </span>', $item_spans) . '</span>';
		}

        /*if (is_array($ua['val'])){
	        wp_die('history rev <pre>' . print_r([$rev, $ua], true) . '</pre>');
        }*/

		// 3. Build the value HTML
		$value_html = '';
		if (isset($ua['val']) && $ua['val'] !== '' && !is_array($ua['val'])) {
			// ESCAPING: `htmlspecialchars` makes the raw user value safe to display.
            //wp_die('history rev <pre>' . print_r($ua, true) . '</pre>');

			$safe_val = htmlspecialchars($ua['val']);
			$value_html = '<span class="his-val">' . $safe_val . '</span>';

			if (!empty($ua['color'])) {
				// ESCAPING: `esc_attr` makes the color value safe for use in a `style` attribute.
				$safe_color = esc_attr($ua['color']);
				$trans_bg_url = '/wp-content/plugins/microthemer/images/trans-big-dark.png';
				$style_attr = 'style="background-image: linear-gradient(0deg, ' . $safe_color . ', ' . $safe_color . '), url(' . $trans_bg_url . ')"';
				$color_box_span = '<span class="colored-box" ' . $style_attr . '></span>';
				$color_val_span = '<span class="colored-val">' . $safe_val . '</span>';
				$value_html = '<span class="his-val">' . $color_box_span . $color_val_span . '</span>';
			}
		}

		// 4. Combine crumbs and value to create the inner content
		// All pieces are already escaped at this point.
		if (!empty($value_html)) {
			$colon = '<span> : </span>';
			$isValFirst = isset($ua['valFirst']) && ($ua['valFirst'] === true || $ua['valFirst'] === 'true');
			$action_html_content = $isValFirst ? $value_html . $colon . $display_crumbs_html : $display_crumbs_html . $colon . $value_html;
		} else {
			$action_html_content = $display_crumbs_html;
		}

		// 5. Build the wrapper span's attributes
		if (!empty($ua['pathTT']) && !empty($ua['fullPathItems']) && is_array($ua['fullPathItems'])) {
			$tooltip_crumbs = [];
			foreach ($ua['fullPathItems'] as $crumb) {
				// ESCAPING: `esc_html` makes each part of the tooltip data safe.
				$tooltip_crumbs[] = esc_html($crumb);
			}
			if (isset($ua['val'])) {
				$tooltip_crumbs[] = ': ' . esc_html($ua['val']);
			}
			$tooltip_content = implode(' » ', $tooltip_crumbs);
			// ESCAPING: `esc_attr` makes the final tooltip content safe for the title attribute.
			$wrapper_tooltip_attr = 'title="' . esc_attr($tooltip_content) . '"';
			$wrapper_main_class .= ' has-path-tt';
			$extra_td_class = ' has-path-tt';
		}

		// Construct the final HTML string. The `$action_html_content` is already safe.
		$final_action_html = '<span class="' . $wrapper_main_class . '" ' . $wrapper_tooltip_attr . '>' . $action_html_content . '</span>';

		return array(
			'icon_html'   => $icon_html,
			'action_html' => $final_action_html, // This is the complete, safe-to-render wrapper span
			'main_class'  => $extra_td_class
		);
	}

    // New get revisions code
	function getRevisions($summary = false) {

		$rev_table = '';

		// create/update revisions table if it doesn't already exist or is out of date
		$this->maybeCreateOrUpdateRevsTable();

		// get the full array of revisions
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_revisions";
		$limit = $summary ? ' limit 10' : '';
		//$revs = $wpdb->get_results("select id, user_action, data_size, date_format(time, '%D %b %Y %H:%i') as datetime");
		/*$revs = $wpdb->get_results("select id, user_action, data_size, unix_timestamp(time) as unix_timestamp
				from $table_name order by id desc");*/
		$revs = $wpdb->get_results("select id, user_action, data_size, time, saved from $table_name order by id desc".$limit);
		$total_rows = $wpdb->num_rows;

		// if no revisions, explain
		if ($total_rows == 0) {
			return '<span class="no-revisions-table">' .
			       esc_html__('No revisions have been created.', 'microthemer') .
			       '</span>';
		}

		$summary_class = '';

		if ($summary){
			$summary_class = ' summary-revisions-table';
			$rev_table .= '
                <div class="lastest-revisions-heading new-set-heading">'
			              .esc_html__('Latest revisions', 'microthemer')
			              .'</div>';
		}

		$rev_table .=
			'
            <table class="revisions-table'.$summary_class.'">
            <thead>
            <tr>
                <th class="rev-size">' . esc_html__('Size', 'microthemer') . '</th>
                <th class="rev-time">' . esc_html__('Time', 'microthemer') . '</th>
                <th class="rev-action" colspan="2">' . esc_html__('User Action', 'microthemer') . '</th>
                <th class="rev-restore">' . esc_html__('Restore', 'microthemer') . '</th>
                <th class="rev-save">' . esc_html__('Save', 'microthemer') . '</th>
            
            </tr>
            </thead>';

		$i = 0;
		foreach ($revs as $rev) {
			$local_timestamp = strtotime($rev->time);
			$time_ago = $this->human_time_diff($local_timestamp);
			$user_action_json = $rev->user_action;

			$rev_icon = '';
			$main_class = '';
			$legacy_new_class = 'legacy-hi';
			$tooltip_attr = '';
			$action_display_html = 'Unknown Action';

			if (preg_match("/[\n\r]+\t+/", $user_action_json)) {
				$user_action_json = preg_replace("/[\n\r]+\t+/", '', $user_action_json);
			}

			// (escHistory)
			//wp_die('history rev <pre>' . print_r($this->json('decode', $rev->user_action), true) . '</pre>');
			//wp_die('history rev <pre>' . print_r(json_decode($rev->user_action, true), true) . '</pre>');
			//$this->log('user action', $rev->user_action, 'warning', false);

			if (strpos($user_action_json, '{"') !== false) {
				//wp_die('history rev <pre>' . print_r([$rev], true) . '</pre>');
				$ua = $this->json('decode', $user_action_json);
				//wp_die('history rev <pre>' . print_r($ua, true) . '</pre>');

				if (isset($ua['data_version']) && $ua['data_version'] == 2) {
					// NEW FORMAT
					$legacy_new_class = 'new-hi refactored-hi';
					$rendered = $this->render_new_history_item($ua, $rev);
					$rev_icon = $rendered['icon_html'];
					// $action_display_html is assigned the pre-built, safe HTML wrapper span.
					$action_display_html = $rendered['action_html'];
					$main_class = $rendered['main_class'];
				}
				else if (is_array($ua)) {
					// OLD FORMAT
					$legacy_new_class = 'new-hi';
					$action_display_html = !empty($ua['html'])
						? $this->unescape_cus_quotes($ua['html'], true)
						: (!empty($ua['targ_and_val']) ? $this->unescape_cus_quotes($ua['targ_and_val'], true) : 'unknown');

					$rev_icon = !empty($ua['icon_html']) ? $ua['icon_html'] : '';
					$main_class = !empty($ua['main_class']) ? $ua['main_class'] : '';

					preg_match('/<span class="his-val">(.+?)<\/span>/s', $action_display_html, $history_val_match);
					if ($history_val_match && !preg_match('/^<span class="colored-box"/', $history_val_match[1]) ){
						//wp_die('history_val <pre>' . print_r($history_val_match, true) . '</pre>');
						$action_display_html = str_replace(
							$history_val_match[1],
							htmlentities($history_val_match[1]),
							$action_display_html
						);
					}
				}
			} else if (!empty($user_action_json)) {
				// Very old plain text format
				$action_display_html = esc_html($user_action_json);
			}

			$rev_is_saved = !empty($rev->saved);
			$save_rev_pos = esc_html__('Permanently save restore point', 'microthemer');
			$save_rev_neg = esc_html__('Unsave restore point', 'microthemer');
			$rev_is_saved_class = $rev_is_saved ? ' revision-is-saved' : '';
			$rev_save_title = $rev_is_saved ? $save_rev_neg : $save_rev_pos;
			$saved_icon = '<span class="save-revision'.$rev_is_saved_class.'" 
                data-pos="'.$save_rev_pos.'" data-neg="'.$save_rev_neg.'" title="'.$rev_save_title.'"
                rel="'.$rev->id.'"></span>';

			$niceDate = date('l jS \of F Y H:i:s', $local_timestamp);
			$timeOutput = $summary ? $niceDate : sprintf(esc_html__('%s ago', 'microthemer'), $time_ago);
			// todo need to have live JS time ago
			//$timeOutput = sprintf(esc_html__('%s ago', 'microthemer'), $time_ago);

			//<td class="rev-num">'.$total_rows.'</td>
			//<td class="rev-size">'.$rev->data_size.'</td>

			// --- FINAL, CORRECTED HTML STRUCTURE ---
			$rev_table .= '
                <tr class="'.$legacy_new_class.$rev_is_saved_class.'">
                    <td class="rev-size">'.$rev->data_size.'</td>
                    <td class="rev-time tvr-help">'.$timeOutput.'</td>
                    <td class="rev-icon '.$main_class.'" title="'.$niceDate.'">'.$rev_icon.'</td>
                    <td class="rev-action '.$main_class.'">'.$action_display_html.'</td>
                    <td class="rev-restore">';

			if ($rev->id == $this->preferences['current_revision']) {
				$restoreActionText = esc_html__('Current', 'microthemer');
				$rev_table .= $summary ? $this->iconFont('check-circle', array('class' => 'current-revision-tick mt-fixed-color', 'title' => $restoreActionText)) : $restoreActionText;
			} else {
				$restoreClass = 'restore-link';
				$restoreRel = 'mt_action=restore_rev&tvr_rev='.$rev->id;
				$restoreText = esc_html__('Restore', 'microthemer');
				$rev_table .= $summary ? $this->iconFont('undo', array('class' => $restoreClass, 'title' => $restoreText, 'rel' => $restoreRel)) : '<span class="'.$restoreClass.' link" rel="'.$restoreRel.'">'.$restoreText.'</span>';
			}

			$rev_table .= '</td><td class="rev-save">'.$saved_icon.'</td></tr>';
			++$i;
		}
		$rev_table .= '</table>';

		/*if ($summary){
					$rev_table.= '<div class="show-dialog view-older-revisions link" rel="display-revisions">'.esc_html__('Older revisions', 'microthemer').'</div>';
				}*/

		return $rev_table;
	}



	// update a single preference
	function update_preference($key, $value){
		$pref_array = array();
		$pref_array[$key] = $value;
		$this->savePreferences($pref_array);
	}

	// Restore a revision
	function restoreRevision($rev_key) {

        // get the revision
		global $wpdb;
		$table_name = $wpdb->prefix . "micro_revisions";
		$rev = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $rev_key) );

		// signal that all selectors should be recompiled (to ensure latest data structure)
		$this->update_preference('manual_recompile_all_css', 1);

        // if revision has preferences, restore those
		if (!empty($rev->preferences)){
			update_option($this->preferencesName, unserialize($rev->preferences));
		}

		// if revision has snippets, restore those
		if (!empty($rev->snippets)){
			// Dynamically calling 'restoreSnippets' as a method
            $this->contentMethod('restoreSnippets', array(&$rev->snippets));
		}

		// restore to options DB field - unless this is purely a revisions history
        if (!empty($rev->settings)){
	        $rev->settings = unserialize($rev->settings);
	        // add css units, mq keys (extra tabs) etc to settings display correctly
	        $filtered_json = $this->filter_incoming_data('restore', $rev->settings);
	        update_option($this->optionsName, $filtered_json);
	        $this->options = get_option($this->optionsName); // this DB interaction doesn't seem necessary...
        }

		return true;
	}

	function css_sass_comment_regex(){
		$commentSingle      = '\/\/';
		$commentMultiLeft   = '\/\*';
		$commentMultiRight  = '\*\/';
		$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
		return '/' . $commentSingle . '[^\n]*\s*|(' . $commentPattern . ')/isu';
	}

	function css_sass_import_regex(){
		return '/@import\s+(?:"|\')([\w\-.\/ ]+)(?:"|\');/';
	}

	function get_css_sass_comments($content){

		preg_match_all(
			$this->css_sass_comment_regex(),
			$content,
			$matches,
			PREG_PATTERN_ORDER
		);

		return $matches ? $matches[0] : false;
	}

	function strip_css_sass_comments($content, $onlyImportComments = false){

		if ($matches = $this->get_css_sass_comments($content)){

			foreach ($matches as $comment){

				if (!$onlyImportComments || preg_match($this->css_sass_import_regex(), $comment) ){
					$content = str_replace($comment, '', $content);
				}

			}
		}

		return $content;
	}

	// generate sass content for replacing @import rules with then compiling sass on the frontend
	function get_sass_import_paths($content, $cur_path){

		$content = $this->strip_css_sass_comments($content, true);

		preg_match_all(
			$this->css_sass_import_regex(),
			$content,
			$matches,
			PREG_PATTERN_ORDER
		);

		if (!$matches){
			return false;
		}

		$formatted = array();

		// sometimes root path has ./ when it should be a blank string
		$cur_path = preg_replace('/^\.\//', '', $cur_path);

		foreach ($matches[0] as $i => $rule){

			$path = $matches[1][$i];
			$resolved_path = $this->normalize_path($cur_path . $path);
			$valid_path = $this->check_sass_file_exists($resolved_path);

			// only get list valid files that do not have .css extension explicitly declared
			if ($valid_path && $this->get_extension($path) !== 'css'){
				$formatted[$i] = array(
					'rule' => $rule,
					'path' => $path,
					'cur_path' => $cur_path,
					'resolved_path' => $valid_path
				);
			}


		}

		return $formatted;
	}

	// convert import file and sub imports into long strings
	function recursively_scan_import_files($config, $cur_path = ''){

		$rule = $config['import']['rule'];
		$file_content = $rule; // default to rule as it will not be possible to replace if file path is wrong
		$resolved_path = $config['import']['resolved_path']; // relative path from root with extension
		$abs_path = $this->micro_root_dir . $resolved_path;

		if (file_exists($abs_path)){

			// get file content
			$file_content = file_get_contents($abs_path);

			// strip commented out @import paths
			$file_content = $this->strip_css_sass_comments($file_content, true);

			// check for sub @imports
			if ($imports = $this->get_sass_import_paths($file_content, $this->get_real_dirname($resolved_path))){

				//$file_content.= '<pre>'.print_r($imports, true).'</pre>';
				//return $file_content;

				foreach ($imports as $i => $arr){

					$sub_content = $this->recursively_scan_import_files(
						array(
							'import' => $arr
						),
						$this->get_real_dirname($arr['resolved_path']) // new current path
					);

					$file_content = str_replace($imports[$i]['rule'], $sub_content, $file_content);

					// debug
					//$file_content.= 'RUULE: '.$rule . 'SUUB: '.$sub_content;
					//$file_content.= 'Prev resolved path: '.$this->get_real_dirname($resolved_path). '<br />';
					//$file_content.= '$imports: <pre>'.print_r($imports, true).'</pre>';

				}
			}
		}

		return $file_content;
	}

	// convert e.g. /path/piece/section/../file.txt to /path/piece/file.txt
	function normalize_path($str){
		$N = 0;
		$A = explode("/",preg_replace("/\/\.\//",'/',$str));  // remove current_location
		$B = array();
		for($i = sizeof($A)-1;$i>=0;--$i){
			if(trim($A[$i]) ===".."){
				$N++;
			}else{
				if($N>0){
					$N--;
				}
				else{
					$B[] = $A[$i];
				}
			}
		}
		return implode("/",array_reverse($B));
	}

	// convert e.g. /path/piece/section/../file.txt to /path/piece/
	function get_real_dirname($path){
		$pathinfo = pathinfo($this->normalize_path($path));
		return !empty($pathinfo['dirname']) ? trailingslashit($pathinfo['dirname']) : '';
	}

	// generate sass content for replacing @import rules with then compiling sass on the frontend
	function get_sass_import_content(){

		$preloaded_sass = array();

		// if there are import paths defined
		if (!empty($this->options['non_section']['hand_coded_css']) &&
		    $imports = $this->get_sass_import_paths($this->options['non_section']['hand_coded_css'], '')
		){

			// recursively import the content so there is one long string of SASS for each @import
			foreach ($imports as $i => $arr){
				$preloaded_sass[$arr['resolved_path']] = $this->recursively_scan_import_files(
					array(
						'import' => $arr
					)
				);
			}
		}

		return $preloaded_sass;
	}

	function client_scss(){
		return !empty($this->preferences['allow_scss']);
	}

	// check if a file exists in /micro-themes dir (various different paths are valid with SASS @imports)
	function check_sass_file_exists($path){

		$files = $this->file_structure;
		$parts = explode('/', $path);
		$partsFinalIndex = count($parts)-1;

		// check for user specified file extension
		$path_parts = pathinfo($path);
		$definedExt = !empty($path_parts['extension']) ? $path_parts['extension'] : '';
		$noExt =  ltrim($path_parts['filename'], '_');

		// variation dimensions
		$extensions = $definedExt ? array($definedExt) : ['scss', 'sass', 'css'];
		$names = array($noExt, '_'.$noExt);

		// create variations
		$variations = array();
		foreach ($extensions as $e) {
			foreach ($names as $n) {
				$partsClone = $parts;
				$partsClone[$partsFinalIndex] = $n.'.'.$e;
				$variations[] = $partsClone;
			}
		}

		// loop through variations
		foreach ($variations as $variation) {

			if ($this->get_item(
				$files,
				$variation
			)){
				return implode('/', $variation); // bingo
			}
		}

		return false;
	}

	// simple wrapper getting data // todo test it works
	function &get_item(&$data, $trail, $startIndex = 0){

		$item = &$this->get_or_update_item(
			$data, array(
			'action' => 'get',
			'trail' => $trail,
		), $startIndex
		);

		return $item;
	}


	// if global !important preference changes MT needs to do full recompile
	function preference_settings_changed($keys, $orig, $new){

		foreach($keys as $key){
			if (intval($orig[$key]) !== intval($new[$key])){
				return true;
			}
		}

		return false;
	}

	// process preferences form
	function process_preferences_form(){

		$pref_array = $this->deep_unescape($_POST['tvr_preferences'], 0, 1, 1);
		$pref_array['num_saves'] = ++$this->preferences['num_saves'];

		if (empty($this->preferences['auto_publish_mode'])){
			$pref_array['num_unpublished_saves'] = ++$this->preferences['num_unpublished_saves'];
		}

        // refresh the template map if set, then set that option back to off
		if (!empty($pref_array['refresh_template_map'])){
            Common::maybeUpdateTemplateCache($this->micro_root_dir, null, true);
			$pref_array['refresh_template_map'] = 0;
		}

		// ensure that changing the default hover inspect setting takes effect immediately
		//wp_die('the post 2'. '<pre>'.print_r($_POST['tvr_preferences'], true).'</pre>');
		$pref_array['hover_inspect'] = !empty($pref_array['hover_inspect_off_initially']) ? 0 : 1;

		// CSS units need saving in a different way (as my_props is more than just css units)
		$pref_array = $this->update_default_css_units($pref_array);

		// update g_url_with_subsets as manual subset param may have changed
		$pref_array['g_url_with_subsets'] =
			$this->g_url_with_subsets(false, false, $pref_array['gfont_subset']);

		// if they changed !important or SCSS settings do full recompile
		if ($this->preference_settings_changed(['css_important', 'allow_scss'],
			$this->preferences, $_POST['tvr_preferences'])){
			$pref_array['manual_recompile_all_css'] = 1;
		}

		if ($this->savePreferences($pref_array)) {

			$this->log(
				esc_html__('Preferences saved', 'microthemer'),
				'<p>' . esc_html__('Your preferences have been successfully updated.', 'microthemer') . '</p>',
				'notice'
			);

			// the admin bar shortcut needs to be applied here else it will only show on next page load
			if (!empty($this->preferences['admin_bar_shortcut'])) {
				add_action( 'admin_bar_menu', array(&$this, 'custom_toolbar_link'), 999999);
			} else {
				remove_action( 'admin_bar_menu', array(&$this, 'custom_toolbar_link'), 999999 );
			}
		}

		// save last message in database so that it can be displayed on page reload (just once)
		$this->cache_global_msg();

		// Return the revision data for indexedDB
		$this->jsonResponse($this->getRevisionData(
                $this->updateRevisions(
                   $this->options,
                    $this->json_format_ua(
                        'mtif-cog lg-icon',
                        esc_html__('Preferences updated', 'microthemer')
                    )
                )
        ));
	}

	// update the preferences array with the new units when the user saves the preferences
	function update_default_css_units($pref_array){
		// cache the posted css units
		$new_css_units = $pref_array['new_css_units'];
		// then discard as junk
		unset($pref_array['new_css_units']);
		// update the existing my_props array
		$pref_array['my_props'] = $this->update_my_prop_default_units($new_css_units);
		return $pref_array;
	}

	// prevent errors when admin or frontend doesn't use SSL, but the other does
	function ensure_iframe_protocol_matches_admin(){

		$preview_url = $this->preferences['preview_url'];
		$preview_plain = strpos($preview_url,'http:') !== false;
		$admin_ssl = Common::get_protocol() === 'https://';

		$update = false;

		// SSL alteration
		if ($admin_ssl and $preview_plain){
			$preview_url = str_replace('http:', 'https:', $preview_url);
			$update = true;
		}

		// maybe strip Oxygen template URL


		if ($update){
			$this->savePreferences(array('preview_url' => $preview_url));
		}

	}

	// we need to know the preview URL post/page id, which should be saved in the preference "preview_item_id"
	// when the preview URL changes either by using a toolbar option or navigating around in MT
	// but there is also a function WP provides for obtaining from the URL we can use as a fallback
	function get_preview_item_id(){
		return !empty($this->preferences['preview_item_id'])
			? $this->preferences['preview_item_id']
			: url_to_postid($this->preferences['preview_url']);
	}

	// update the iframe preview url
	function maybe_set_preview_url($nonce_key = false){

		// update preview url in DB and get title and post/page ID
		$pref_array = array();
		$url = strip_tags(rawurldecode($_GET['mt_preview_url']));
		$label = isset($_GET['mt_path_label'])
			? strip_tags(rawurldecode($_GET['mt_path_label']))
			: false;
		$item_id = isset($_GET['mt_item_id'])
			? strip_tags(rawurldecode($_GET['mt_item_id']))
			: false;

		$pref_array['preview_url'] = Common::strip_page_builder_and_other_params($url);
		$pref_array['preview_title'] = $label;
		$pref_array['preview_item_id'] = $item_id;

		// path won't be set if this is triggered after user clicked WP Toolbar MT link
		if (!empty($_GET['mt_preview_path'])){

			// get path and strip builder and other params
			$path = strip_tags(rawurldecode($_GET['mt_preview_path']));
			$path = Common::strip_page_builder_and_other_params($path);

			//wp_die('$path: '.$_GET['mt_preview_path']);

			// if the preview URL is 1, we should use the site_url with the path
			// this is used on the live demo
			if (intval($url) === 1){
				$pref_array['preview_url'] = untrailingslashit($this->site_url).$path;
			}

			// remove from array if already exists (as we be prepended at start)
			$existingKey = $this->in_array_column($path, $this->preferences['custom_paths'], 'value');
			if ($existingKey){
				array_splice($this->preferences['custom_paths'], $existingKey,1);
			}

			// sometimes the page title might have a different URL e.g. /?p=1, /, /home-page/
			// so we should remove any with the same name,
			// as the user probably just wants to most recent URL e.g. /home-page/ for the title
			for ($i = count($this->preferences['custom_paths'])-1; $i  >= 0; $i --) {

				if (!isset($this->preferences['custom_paths'][$i]['label'])
				    || $this->preferences['custom_paths'][$i]['label'] === $label){
					unset($this->preferences['custom_paths'][$i]);
				}

			}

			// insert url at start of custom_paths array
			array_unshift($this->preferences['custom_paths'], array(
				'value' => $path,
				'label' => $label,
				'item_id' => $item_id
			));

			// ensure only x items, and that paths are unique
			$i = 0;
			$paths_done = array();
			foreach ($this->preferences['custom_paths'] as $key => $pathOrObj){

				$custom_path = is_array($pathOrObj) ? $pathOrObj['value'] : $pathOrObj;

				// add custom path if unique
				if (empty($paths_done[$custom_path])){
					$pref_array['custom_paths'][] = $pathOrObj;
					$paths_done[$custom_path] = 1;
					++$i;
				}

				if ($i >= 8) {
					break;
				}
			}


			//wp_die('$this->preferences[custom_paths]: <pre>'. print_r($pref_array, 1) . '</pre>');
		}

		$this->savePreferences($pref_array);

	}

	// check an array based on nested property with option to search base array for value
	function in_array_column($item, $array, $column = false, $checkFlat = false){

		if ( $foundWithColumn = array_search( $item, array_column($array, $column) ) ){
			return $foundWithColumn;
		}

		if ($checkFlat){
			return array_search($item, $array);
		}

		return false;
	}

	// check if we're on the demo site
	function is_demo_site(){
		return preg_match(
			'/^https?:\/\/(livedemo|beta)\.themeover\.(com|local)/',
			$this->site_url
		);
	}

	// Microthemer UI page
	function microthemer_ui_page() {

		// only run code if it's the ui page
		if ( isset($_GET['page']) and $_GET['page'] == $this->microthemeruipage ) {

			if (!current_user_can('manage_options')){
				wp_die('Access denied');
			}

			$prefix = '';

			// initial microthemer setup
			if (isset($_POST['mt_initial_setup_submit'])) {

				check_admin_referer( 'mt_initial_setup_form' );

                $error = false;
                $imported = false;
				$pref_array = array();
                $prefix = 'amender_';

                // if they provided an import file, load that
                if (!empty($_FILES['preferences_file']['name'])){

	                $filePath = $_FILES['preferences_file']['tmp_name'];
	                $fileSize = filesize($filePath);
	                $fileInfo = finfo_open(FILEINFO_MIME_TYPE);
	                $fileType = finfo_file($fileInfo, $filePath);
                    $ext = $this->get_extension($_FILES['preferences_file']['name']);
                    $max = ' (max '.(floor($this->maxUploadPrefSize / 1000000)).'MB)';

                    // validate
                    if (!is_uploaded_file($filePath)){
	                    $error = 'Incorrect file'; // only shows if user up to no good
                    } elseif ($fileSize > $this->maxUploadPrefSize){
	                    $error = 'File size too big' . $max;
	                } elseif ($fileType !== 'application/json' || $ext !== 'json'){
		                $error = 'Incorrect file type. You must upload a .json file';
	                } elseif ($_FILES['preferences_file']['error']){
	                    $phpFileUploadErrors = array(
		                    1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
		                    2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form' . $max,
		                    3 => 'The uploaded file was only partially uploaded',
		                    4 => 'No file was uploaded',
		                    6 => 'Missing a temporary folder',
		                    7 => 'Failed to write file to disk.',
		                    8 => 'A PHP extension stopped the file upload.',
	                    );

                        // allow for PHP to add more error codes without failing
                        if (isset($phpFileUploadErrors[ $_FILES['preferences_file']['error'] ])){
	                        $error = $phpFileUploadErrors[ $_FILES['preferences_file']['error'] ];
                        } else {
	                        $error = 'Unknown error';
                        }
	                }

                    // validation passed
                    else {

                        if (!$json = file_get_contents($filePath)){
	                        $error = 'The file had no content';
                        } elseif (!$pref_array = $this->json('decode', $json)){
	                        $error = 'The JSON file could not be read';
                        } else {
                            // maybe remove optional preferences
	                        $optional = array('my_props', 'm_queries', 'enq_js', 'active_scripts_deps');
	                        foreach ($optional as $key){
		                        $check_key = $key === 'active_scripts_deps' ? 'enq_js' : $key; // one checkbox for both
		                        if (empty($_POST['tvr_optional_preferences'][$check_key])){
			                        unset($pref_array[$key]);
		                        }
	                        }
	                        //wp_die('The final prefs: <pre>' . print_r($pref_array, 1) . '</pre>');
	                        $this->savePreferences($pref_array);

	                        $imported = true;
                        }
                    }

                    // display the type of error to the user
                    if ($error){
	                    $this->log(
		                    esc_html__('File upload error', 'microthemer'),
		                    '<p>' . $error . '</p>',
		                    'error'
	                    );
                    }
                }

                // else save the handful of preferences
                else {

                    foreach ($_POST['tvr_preferences'] as $key => $value){
                        switch($key){
                            case 'buyer_email':
	                        case $prefix . 'buyer_email':
	                            $pref_array[$key] = strip_tags($value);
                                break;
                            default:
	                            $pref_array[$key] = intval($value);
                                break;
                        }
                    }

                    $this->savePreferences($pref_array);

                    //wp_die('filtered prefs <pre>' . print_r($_POST, 1) . '</pre>');
                }

                // update the error reporting preferences (might be done in combo with file upload
                // so keep separate from save preferences code above
                $pref_array['reporting'] = $this->preferences['reporting'];
				$pref_array['reporting']['permission'] = array();
                if (isset($_POST['reporting_permission']) && is_array($_POST['reporting_permission'])){
	                foreach ($_POST['reporting_permission'] as $key => $value){
		                $pref_array['reporting']['permission'][$key] = intval($value);
	                }
                }
				$this->savePreferences($pref_array);

				// try to unlock if they supply a new Micerothemer license key
                if (!empty($_POST['tvr_preferences']['buyer_email'])){
	                $response = $this->get_validation_response($_POST['tvr_preferences']['buyer_email']);
                    if (!$response){
                        $error = 'Unlock fail';
                    }
                }

				// try to unlock if they supply a new Amender license key
				if (!empty($_POST['tvr_preferences'][$prefix . 'buyer_email'])){
					$response = $this->get_validation_response(
                        $_POST['tvr_preferences'][$prefix . 'buyer_email'],
                        'unlock',
						$prefix
                    );
					if (!$response){
						$error = 'Unlock fail';
					}
				}

				// add to revision table
                $save_data = '';
				$this->updateRevisions(
					$save_data,
					$this->json_format_ua(
						'mtif-cog',
						($imported
                            ? esc_html__('Preferences imported', 'microthemer')
                            : esc_html__('Preferences updated', 'microthemer')
                        )
					),
					true,
					$this->preferences
				);

                // if we have an error, show the setup screen again
                if ($error){
                    $this->savePreferences(array(
                            'show_setup_screen' => 1
                    ));
                    $this->setupError = $error;
                }

			}

			// validate email todo make this an ajax request, with user feedback
			/*if (isset($_POST['tvr_ui_validate_submit'])) {

				check_ajax_referer( 'tvr_validate_form', 'validate_nonce' );

				// tvr_validate_form
				$this->get_validation_response($_POST['tvr_preferences']['buyer_email']);
			}*/

            // make it possible to set an invalid license key
			$this->invalidLic();

			// if user navigates from front to MT via toolbar, set previous front page in preview
			if (isset($_GET['mt_preview_url'])) {

				if (!$this->is_demo_site() && !wp_verify_nonce($_REQUEST['_wpnonce'], 'mt-preview-nonce')) {
					die( 'Security check failed' );
				}

				$this->maybe_set_preview_url();
			}

			// if draft mode is on, but user accessing MT GUI isn't in draft_mode_uids array,
			// add them so they see latest draft changes
			if ($this->preferences['draft_mode'] and
			    !in_array($this->current_user_id, $this->preferences['draft_mode_uids'])){
				$pref_array['draft_mode_uids'] = $this->preferences['draft_mode_uids'];
				$pref_array['draft_mode_uids'][$this->current_user_id] = $this->current_user_id;
				$this->savePreferences($pref_array);
			}

			// ensure Preview URL matches HTTPS in admin
			$this->ensure_iframe_protocol_matches_admin();

			// maybe check valid subscription for MT and Amender
			$this->maybe_check_subscription();
			$this->maybe_check_subscription($prefix);

			// Display user interface
			include $this->thisplugindir . 'includes/tvr-microthemer-ui.php';

		}
	}

	// Documentation page
	function microthemer_docs_page(){

		// only run code on docs page
		if ($_GET['page'] == $this->docspage) {

			if (!current_user_can('manage_options')){
				wp_die('Access denied');
			}

			include $this->thisplugindir . 'includes/internal-docs.php';
		}
	}

	// fonts page
	function microthemer_fonts_page(){

		// only run code on docs page
		if ($_GET['page'] == $this->fontspage) {

			if (!current_user_can('manage_options')){
				wp_die('Access denied');
			}

			include $this->thisplugindir . 'includes/fonts.php';
		}
	}

	// Documentation menu
	function docs_menu($propertyOptions, $cur_prop_group, $cur_property){
		?>
		<div id="docs-menu">
			<ul class="docs-menu">
				<li class="doc-item css-ref-side">
					<?php $this->show_css_index($propertyOptions, $cur_prop_group, $cur_property); ?>
				</li>
			</ul>
		</div>
		<?php
	}

	// function for showing all CSS properties
	function show_css_index($propertyOptions, $cur_prop_group, $cur_property) {
		// output all help snippets
		$i = 1;
		foreach ($propertyOptions as $property_group_name => $prop_array) {
			$ul_class = $arrow_cls = '';
			if ($i&1) { $ul_class.= 'odd'; }
			if ($property_group_name == $cur_prop_group) { $ul_class.= ' show-content'; $arrow_cls = 'on'; }
			//if ($property_group_name == 'code') continue;
			?>
			<ul id="<?php echo $property_group_name; ?>"
			    class="css-index <?php echo $ul_class; ?> accordion-menu">

				<li class="css-group-heading accordion-heading">

					<?php
					echo $this->iconFont(str_replace('_', '-', $property_group_name), array(
						'class' => 'no-click pg-icon',
					));
					echo $this->iconFont('chevron-right', array(
						'class' => 'menu-arrow accordion-menu-arrow '.$arrow_cls,
						//'title' => 'Open/close group'
					));
					?>

					<span class="text-for-group"><?php echo $this->property_option_groups[$property_group_name]; ?></span>

					<?php

					?>
				</li>

				<?php
				foreach ($prop_array as $property_id => $array) {
					$li_class = '';
					$cssf = str_replace('_', '-', $property_id);
					$icon_name = !empty($array['icon-name']) ? $array['icon-name'] : $cssf;
					if ($property_id == $cur_property) { $li_class.= 'current'; }
					//if (!empty($array['field-class'])) { $li_class.= ' '.$array['field-class']; }
					?>
				<li class="property-item accordion-item <?php echo $li_class; ?>">
					<a href="<?php echo 'admin.php?page=' . $this->docspage; ?>&prop=<?php echo $property_id; ?>&prop_group=<?php echo $property_group_name; ?>">

						<span class="option-text"><?php echo $array['label']; ?></span>
						<?php
						echo $this->iconFont($icon_name, array(
							'class' => 'no-click'
						));
						?>
					</a>

					</li><?php
				}
				++$i;
				?>
			</ul>
			<?php
		}
	}

	// Preferences page
	function microthemer_preferences_page() {

		// only run code on preferences page
		if( $_GET['page'] == $this->preferencespage ) {

			if (!current_user_can('manage_options')){
				wp_die('Access denied');
			}

			// this is a separate include because it needs to have separate page for changing gzip
			$page_context = $this->preferencespage;

			$ui_class = '';
			require_once($this->thisplugindir . 'includes/common-inline-assets.php');

			echo '
                    <div id="tvr" class="wrap tvr-wrap">
                        <span id="ajaxUrl" rel="' . $this->wp_ajax_url.'"></span>
                        <span id="returnUrl" rel="admin.php?page=' . $this->preferencespage.'"></span>
                        <div id="pref-standalone">
                            <div id="full-logs">
                                '.$this->display_log().'
                            </div>';
			include $this->thisplugindir . 'includes/tvr-microthemer-preferences.php';
			echo '
                        </div>';

			//$this->hidden_ajax_loaders();

			echo '
                    </div>';

		}
	}

	// Detached preview page
	function microthemer_detached_preview_page() {

		// only run code on preferences page
		if( $_GET['page'] == $this->detachedpreviewpage ) {

			if (!current_user_can('manage_options')){
				wp_die('Access denied');
			}

			// this is a separate include because it needs to have separate page for changing gzip
			$page_context = $this->detachedpreviewpage;

			// set the css filters here so that favourites get updated regardless of display order of main filters
			// we just want the favourites (hover/page-id) to display in the context menu
			$css_filters =  $this->display_css_filters();

			$ui_class = '';
			require_once($this->thisplugindir . 'includes/common-inline-assets.php');

			echo '
                    <div id="tvr" class="wrap tvr-wrap '.$ui_class.'">
                        <span id="ajaxUrl" rel="'.$this->wp_ajax_url.'"></span>
                        <span id="returnUrl" rel="admin.php?page=' . $this->preferencespage.'"></span>
                        <div id="preview-standalone">';

			include $this->thisplugindir . 'includes/tvr-microthemer-preview-wrap.php';

			// Detach certain UI elements from the main window
			// and move them to to the detached when active
			echo '
                            <div id="mt-context-menu" 
                            class="detached-alt-controls detached-mt-context-menu" 
                            data-popupName="contextMenu">'.

			     // selector modifier options (must come before targeting options for fav filters)
			     $this->context_menu_content(array(
				     'base_key' => 'suggestions',
				     'title' => esc_html__('Targeting options', 'microthemer'),
				     'sections' => array(
					     $this->targeting_suggestions('menu')
				     )
			     )).

			     '</div>
                        
                 <div id="advanced-wizard" class="detached-alt-controls detached-advanced-wizard"></div>
                            
                 <div id="style-components"></div>';

                 echo '
                </div>
            </div>';

		}
	}

	/* add run if admin page condition...? */

	/***
	Generic Functions
	 ***/




	// show need help videos
	function need_help_notice() {
		if ($this->preferences['need_help'] == '1') {
			?>
			<p class='need-help'><b><?php esc_html_e('Need Help?', 'microthemer'); ?></b>
				<?php echo wp_kses(
					sprintf(
						__('Browse Our <span %s>Video Guides</span> and <span %s>Tutorials</span> or <span %s>Search Our Forum</span>', 'microthemer'),
						'class="help-trigger" rel="' . $this->thispluginurl.'includes/help-videos.php',
						'class="help-trigger" rel="' . $this->thispluginurl.'includes/tutorials.php',
						'class="help-trigger" rel="' . $this->thispluginurl.'includes/search-forum.php'
					),
					array( 'span' => array() )
				); ?></p>
			<?php
		}
	}

	/* Simple function to check for the browser
			For checking chrome faster notice and FF bug if $.browser is deprecated soon
			// may not be 100% reliable, initially only using to correct sub-pixel alignment of frontend context menu
			// in Chrome and Edge - haven't tested Safari yet
			http://php.net/manual/en/function.get-browser.php */
	function check_browser(){
		$u_agent = $_SERVER['HTTP_USER_AGENT'];
		$ub = $u_agent; //'unknown-browser';

		//return $ub;
		if(preg_match('/(MSIE|Trident)/i',$u_agent) && !preg_match('/Opera/i',$u_agent)){
			$ub = "MSIE";
		}
		elseif(preg_match('/Opera/i',$u_agent) || preg_match('/OPR/',$u_agent)){
			$ub = "Opera";
		}
		elseif(preg_match('/Edg/i',$u_agent)){
			$ub = "Edge";
		}
		elseif(preg_match('/Firefox/i',$u_agent)){
			$ub = "Firefox";
		}
		elseif(preg_match('/Chrome/i',$u_agent)){
			$ub = "Chrome";
		}
		elseif(preg_match('/Safari/i',$u_agent)){
			$ub = "Safari";
		}
		elseif(preg_match('/Netscape/i',$u_agent)){
			$ub = "Netscape";
		}
		return $ub;
	}

	// ie notice
	/*function ie_notice() {
				// display ie message unless disabled
				//global $is_IE;
				if ($this->preferences['ie_notice'] == '1' and $this->check_browser() != 'Chrome') {
					$this->log(
						esc_html__('Chrome Is Faster', 'microthemer'),
						'<p>' .
						sprintf(
							esc_html__('We\'ve noticed that Microthemer runs considerably faster in Chrome than other browsers. Actions like switching tabs, property groups, and accessing preferences are instant in Chrome but can incur a half second delay on other browsers. Speed improvements will be a major focus in our next phase of development. But for now, you can avoid these issues simply by using Microthemer with %s. Internet Explorer 9 and below isn\'t supported at all.', 'microthemer'),
							'<a target="_blank" href="http://www.google.com/intl/' . esc_attr_x('en-US', 'Chrome URL slug: https://www.google.com/intl/en-US/chrome/browser/welcome.html', 'microthemer') . '/chrome/browser/welcome.html">Google Chrome</a>'
						)
						. '</p><p>' .
						wp_kses(__('<b>Note</b>: Web browsers do not conflict with each other, you can install as many as you want on your computer at any one time. But if you love your current browser you can turn this message off on the preferences page.', 'microthemer'), array( 'b' => array() ))
						. '</p>',
						'warning'
					);
				}
			}*/



	/*// show server info
			function server_info() {
				global $wpdb;
				// get MySQL version
				$sql_version = $wpdb->get_var("SELECT VERSION() AS version");
				// evaluate PHP safe mode
				if(ini_get('safe_mode')) {
					$safe_mode = 'On';
				}
				else {
					$safe_mode = 'Off';
				}
				?>
				&nbsp;Operating System:<br />&nbsp;<b><?php echo PHP_OS; ?> (<?php echo (PHP_INT_SIZE * 8) ?> Bit)</b><br />

				&nbsp;MySQL Version:<br />&nbsp;<b><?php echo $sql_version; ?></b><br />
				&nbsp;PHP Version:<br />&nbsp;<b><?php echo PHP_VERSION; ?></b><br />
				&nbsp;PHP Safe Mode:<br />&nbsp;<b><?php echo $safe_mode; ?></b><br />
			<?php
			}
			*/

	// get all-devs and the MQS into a single simple array
	function combined_devices(){
		$comb_devs['all-devices'] = array(
			'label' => esc_html__('All Devices', 'microthemer'),
			'query' => esc_html__('CSS that applies to all devices', 'microthemer'),
			'min' => 0,
			'max' => 0
		);
		foreach ($this->preferences['m_queries'] as $key => $m_query) {
			$comb_devs[$key] = $m_query;
		}
		return $comb_devs;
	}





	// some versions of php don't like empty(trim($input)) so this is workaround
	function trimmedEmpty($input){

		if (empty($input)){
			return true;
		}

		$input = trim($input);

		if (empty($input)){
			return true;
		}

		return false;
	}


	// get micro-theme dir file structure
	/*function dir_loop_old($dir_name) {

				if (empty($this->file_structure)) {
					$this->file_structure = array();
				}

				// check for micro-themes folder, create if doesn't already exist
				if ( !is_dir($dir_name) ) {
					if ( !wp_mkdir_p($dir_name) ) {
						$this->log(
							esc_html__('/micro-themes folder error', 'microthemer'),
							'<p>' .
							sprintf(
								esc_html__('WordPress was not able to create the %s directory.', 'microthemer'),
								$this->root_rel($dir_name)
							) . $this->permissionshelp . '</p>'
						);
						return false;
					}
				}

				// loop over the directory
				if ($handle = opendir($dir_name)) {

					$count = 0;

					while (false !== ($file = readdir($handle))) {
						if ($file != '.' and $file != '..' and $file != '_debug') {
							$file = htmlentities($file); // just in case
							if ($this->is_acceptable($file) or !preg_match('/\./',$file)) {

								// if it's a directory
								if (!preg_match('/\./',$file) ) {
									$this->file_structure[$file] = array();
									$next_dir = $dir_name . $file . '/';
									// loop through the contents of the micro theme
									$this->dir_loop($next_dir);
								}

								// it's a normal file
								else {
									$just_dir = str_replace($this->micro_root_dir, '', $dir_name);
									$just_dir = str_replace('/', '', $just_dir);
									if ($this->is_screenshot($file)){
										$this->file_structure[$just_dir]['screenshot'] = $file;
									} else {
										$this->file_structure[$just_dir][$file] = $file;
									}

									++$count;
								}
							}
						}
					}
					closedir($handle);
				}

				if (is_array($this->file_structure)) {
					ksort($this->file_structure);
				}

				return $this->file_structure;
			}*/


	// display abs file path relative to the root dir (for notifications)
	function root_rel($path, $markup = true, $url = false, $actual_path = false) {

		// normalise \/ slashes
		$abspath_fs = untrailingslashit(str_replace('\\', '/', ABSPATH));
		$path = str_replace('\\', '/', $path);

		if ($markup == true) {
			$rel_path = '<b><i>/' . str_replace($abspath_fs, '', $path) . '</i></b>';
		}
		else {
			$rel_path = str_replace($abspath_fs, '', $path);
		}

		// root relative url (works on mixed ssl sites and if WP is in a subfolder of the doc root - getenv())
		if ($url){

			// WP is sometimes installed in a sub-dir, but pages are served as if from the root.
			// $this->home_url = root, $this->site_url = path to sub-dir
			// We normally want to strip $this->home_url, unless using root rel with ABSPATH (which incs subdir)
			// See https://premium.wpmudev.org/blog/install-wordpress-subdirectory/
			$path_to_strip = $actual_path ? $this->site_url : $this->home_url;

			// we're making an url FILE path root relative. The url WILL contain any subdir, so strip site_url stub
			if ($actual_path){

				// get the path from the www root to the website root. Often this will be the same.
				// But on localhost it might be e.g. /personal/themeover.com/wp-versions/really-fresh/
				// this happens when using sub-dirs without special case above.
				$script_name = getenv("SCRIPT_NAME");

				// $script_name could be be either admin-ajax.php (24) or admin.php (19), which will affect the offset
				$str_offset = strpos($script_name, 'admin-ajax.php') !== false ? -24 : -19;

				// we always strip whole site_url because $script_name will include any sub-dir, we don't want it twice
				$rel_path = substr($script_name, 0, $str_offset) . str_replace($this->site_url, '', $path);
			}

			// we're making an URL path root relative. The URL will NOT contain any subdir, so strip home_url stub
			else {
				$rel_path = str_replace($this->home_url, '', $path);
			}

			/*/*$script_name = getenv("SCRIPT_NAME");
					$rel_path = substr($script_name, 0, -(strlen($script_name))) . str_replace($this->site_url, '', $path);
					if (true){
						$this->show_me.= '(New) $script_name: '. $script_name.'<br />';
						$this->show_me.= '$path: '. $path.'<br />';
						$this->show_me.= 'str_replace($this->site_url, $path): '
							. str_replace($this->site_url, '', $path).'<br />';
						$this->show_me.= 'substr($script_name, 0, $str_offset) : '
							. substr($script_name, 0, $str_offset) .'<br />';
						$this->show_me.= '$this->site_url: '. $this->site_url.'<br />';
						$this->show_me.= '$this->home_url: '. $this->home_url.'<br />';
						$this->show_me.= '$rel_path: '. $rel_path.'<br />';
						$this->log(
							'active-styles debug',
							'<p>' . $this->show_me . '</p>'
						);
					}
					*/


		}

		return $rel_path;
	}

	// extract valid HTML attributes from a config array
	function array_to_html_atts($config){

		$atts_string = '';

		foreach ($config as $key => $value) {

			// to make adding atts and non-atts easier when calling function
			// compare atts against a white list (rather than atts =>)
			$attPattern = '/^(id|class|style|title|href|target|data-.+?|rel|preserveAspectRatio)$/';
			if (!is_null($value) && preg_match($attPattern, $key)){
				$atts_string.= $key . '="'.$value.'" ';
			}
		}

		return $atts_string;
	}

	// output an inline svg file, svg symbol, or a font icon
	function icon($name, $config = array()){

		// default icon type is "f" (font) unless svg or sym is set
		$type = !empty($config['type']) ? $config['type'] : 'f'; // for font
		$dir = isset($config['dir']) ? $config['dir'] : 'svg-min/';

		// base class for icon for settings global and type based defaults,
		// as well as the individual icon class
		//$base_class = 'mt-icon mt-'.$type.'-icon mt-icon-'.$name.' mt-'.$type.'-'.$name;
		$base_class = 'mti'.$type.' mti'.$type.'-'.$name;

		// merge base class with any custom classes
		$config['class'] = !empty($config['class'])
			? $base_class.' '.$config['class']
			: $base_class;

		// generate string of attributes
		$atts_string = $this->array_to_html_atts($config);

		// generate the HTML for the icon, svg, or symbol
		$icon = '';
		switch ($type) {

			// output an icon font or just the classes / atts
			case 'f':
				$tag = !empty($config['tag']) ? $config['tag'] : 'span';
				if (!empty($config['onlyClass'])){
					$icon = $config['class'];
				} else if (!empty($config['onlyAtts'])){
					$icon = $atts_string;
				} else {
					$innerHTML = !empty($config['innerHTML']) ? $config['innerHTML'] : '';
					$icon = '<'.$tag.' '.$atts_string.'>'.$innerHTML.'</'.$tag.'>';
				}
				break;

			// if symbol or svg
			case 'svg':
			case 'sym':

				// attempt to get svg file (silently fail if file not found)
				$icon = @file_get_contents(
					$this->thisplugindir . 'images/'.$dir.$name.'.svg'
				);

				if ($type === 'sym'){

					// if we successfully loaded the SVG file, copy its viewBox attribute
					// that will allow the symbol to scale proportionally like a regular svg
					// but keeps the HTML markup small for better dynamic HTML performance
					if ($icon){
						if (preg_match('/viewBox="[^"]+"/', $icon, $matches)){
							$atts_string.= ' '.$matches[0];
						}
					}

					$icon = '
                            <svg '.$atts_string.'>
                              <use href="#'.$name.'"></use>
                            </svg>';

				}

				// use full SVG file from file-system, adding in custom HTML attributes
				elseif ($type === 'svg'){
					$icon = preg_replace(
						'/<svg /',
						'<svg '.$atts_string,
						$icon,
						1
					);
				}

				break;
		}

		// maybe add some text alongside the icon
		if (!empty($config['adjacentText'])){
			$text_atts_string = $this->array_to_html_atts($config['adjacentText']);
			$icon.= '<span '.$text_atts_string.'>'.$config['adjacentText']['text'].'</span>';
		}

		// maybe wrap icon with a div
		if (isset($config['wrap'])){
			$wrap_atts_string = $this->array_to_html_atts($config['wrap']);
			//$wrapClass = !empty($config['wrapClass']) ? ' '.$config['wrapClass'] : '';
			$icon = '
	                <div '.$wrap_atts_string.'>'
			        . $icon .
			        '</div>';
		}

		return $icon;
	}

	function symbol($name, $config = array()){

		$config['type'] = 'sym';

		return $this->icon($name, $config);
	}

	function svg($name, $config = array()){

		$config['type'] = 'svg';

		return $this->icon($name, $config);
	}

	function iconFont($name, $config = array()){

		$config['type'] = 'f';

		return $this->icon($name, $config);
	}

	// get extension
	function get_extension($file) {
		$tmp = explode('?', $file);
		$file = $tmp[0];
		$ext = strtolower(substr($file, strrpos($file, '.') + 1));
		return $ext;
	}

	// use wp_remote_fopen with some validation checks
	function get_safe_url($uri, $config = array(), $msg_type = 'warning') {

		$r = array();

		// bail if not an URL
		if (!preg_match('/^(https?:)?\/\//i', $uri)){
			$this->log(
				esc_html__('Invalid URL', 'microthemer'),
				'<p>'.esc_html($uri).'</p>',
				'error'
			);
			return false;
		}

		// bail if not correct extension
		if (!empty($config['allowed_ext']) && !in_array($this->get_extension($uri), $config['allowed_ext'])) {
			$this->log(
				esc_html__('Disallowed file extension', 'microthemer') . ':' . $this->get_extension($uri),
				'<p>Please enter an URL with one of the following extensions: '.
				implode(', ', $config['allowed_ext']). '</p>',
				'error'
			);
			return false;
		}

		// check if file exists
		if (!$this->url_exists($uri)){
			return false;
		}

		// it seems ok so get contents of file into string
		if (!$r['content'] = wp_remote_fopen($uri)){
			$this->log(
				esc_html__('File is empty', 'microthemer'),
				'<p>'.esc_html($uri).'</p>',
				$msg_type
			);
			return false;
		}

		// do we need to save as file in tmp dir?
		if (!empty($config['tmp_file'])){
			$r['tmp_file'] = $this->thistmpdir . basename($uri);
			$this->write_file($r['tmp_file'], $r['content']);
		}

		return $r;
	}

	// check if an URL exists (WP can return 404 custom page giving illusion file exists)
	function url_exists($url) {
		$response = wp_remote_get( esc_url_raw($url) );
		// my half-done ssl on localhost fails here, so warn others
		if ( is_wp_error( $response ) ) {
			$str = '';
			foreach ($response->errors as $key => $err_arr){
				$str.= '<p>'.$key.': '.implode(', ', $err_arr).'</p>';
			}
			$this->log(
				esc_html__('Could not get file', 'microthemer'),
				'<p>'.esc_html($url). '</p>'
				.$str
			);
			return false;
		}
		if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
			$this->log(
				esc_html__('File does not exist', 'microthemer'),
				'<p>'.esc_html($url).'</p>'
			);
			return false;

		}
		return true;
	}

	// is english
	function is_en() {
		if ($this->locale == 'en_GB' or $this->locale == 'en_US') return true;
		return false;
	}

	// get first item in an associative array
	function get_first_item($array) {
		$item = false;
		foreach ($array as $key => $value){
			$item = $array[$key];
			break;
		}
		return $item;
	}

	// convert one array format to autocomplete with categories format
	function to_autocomplete_arr(
		$orig_array,
		$new_array = array(),
		$config = array()
	){
		foreach ($orig_array as $category => $array){
			foreach ($array as $i => $value){

				// array may be an array of arrays with the value as the key
				if (is_array($value)){
					$data = array_merge(array(
						'label' => $i,
						'category' => $category
					), $value);
				}

				// simple numeric array with single values
				else {
					$data = array(
						'label' => $value,
						'value' => $value,
						'category' => $category
					);
				}

				$new_array[] = $data;
			}
		}
		return $new_array;
	}

	function autocomplete_to_param_keys($autocomplete_array){

		$new_array = array();

		foreach ($autocomplete_array as $i => $array){
			$new_array[$this->to_param($array['label'])] = $array['label'];
		}

		return $new_array;
	}

	// WordPress normalises magic_quotes, if magic_quotes are enabled.
	// Even though deprecated: http://wordpress.stackexchange.com/questions/21693/wordpress-and-magic-quotes
	// Useful WP functions: stripslashes_deep() and add_magic_quotes() (both recursive)
	// $do is for easy dev experimenting.
	function stripslashes($val, $do = false){
		return $do ? stripslashes_deep($val): $val;
	}

	function addslashes($val, $do){
		return $do ? add_magic_quotes($val): $val;
	}

	function normalise_line_breaks($value, $trailing = "\n\n", $leading = ''){
		return $leading.trim($value, "\n\r").$trailing;
	}

	function normalise_tabs($string, $cur_tab_indent, $isSass = false){

		if (!$isSass){
			return $string;
		}

		$string = preg_replace("/(?<!^)(\n)/", "\n{$cur_tab_indent}\\2", $string);

		return str_replace("\t}", $cur_tab_indent."}", $string);

		/*// strip all leading white space
				$string = preg_replace("/^[ \t]+/m", "", $string);

				// replace line breaks inside with tab (not starting line break or final before })
				$string = preg_replace("/(?<!^)(\n)([^}])/", "\n{$cur_tab_indent}\t\\2", $string);

				// add back start tab and tab before }
				return str_replace("}", $cur_tab_indent."}", $string);*/

	}

	// WP magic_quotes hack doesn't escape \ properly, so this is my workaround
	function unescape_cus_slashes($val){
		return str_replace('&#92;', '\\', $val);
	}

	// Another workaround I came up with along time ago without fully understanding wp magic_quotes, but works too.
	function unescape_cus_quotes($val, $forAttr = false){

		$single = $forAttr ? '&#039;' : "'";
		$double = $forAttr ? '&quot;' : '"';

		$val = str_replace('cus-#039;', $single, $val);
		$val = str_replace('cus-quot;', $double, $val);

		return $val;
	}

	// Unescape slashes and cus quotes recursively
	function deep_unescape($array, $cus_quotes = false, $slashes = false, $cus_slashes = false){
		foreach ( (array) $array as $k => $v ) {
			if (is_array($v)) {
				$array[$k] = $this->deep_unescape($v, $cus_quotes, $slashes, $cus_slashes);
			} else {
				if ($cus_quotes and $k !== 'user_action'){ // user action had issue with quotes inside title attribute not being distinguishable from regualr quotes.
					$array[$k] = $this->unescape_cus_quotes($v);
				}
				if ($slashes){
					$array[$k] = stripslashes($array[$k]);
				}
				if ($cus_slashes){
					$array[$k] = $this->unescape_cus_slashes($array[$k]);
				}
			}
		}
		return $array;
	}

	// make server folder readable
	function readable_name($name) {
		$readable_name = str_replace('_', ' ', $name);
		$readable_name = ucwords(str_replace('-', ' ', $readable_name));
		return $readable_name;
	}

	// convert text to param (same as JS function)
	// todo this doesn't convert non-alpha to unicode like the convert_to_param JS function
	// doesn't seem to do anything to user data right now, so focussing on other things
	function to_param($str) {
		$str = str_replace(' ', '_', $str);
		$str = strtolower(preg_replace("/[^A-Za-z0-9_]/", '', $str));
		return $str;
	}

	// populate the default folders global array with translated strings
	function set_default_folders() {

		//$homePage = $this->homePageData();

        $folders = array(
			'general' => __('General', 'microthemer'), // Auto-create General 2, 3 etc when +25 selectors
			'header' => __('Header', 'microthemer'),
			'main_menu' => __('Main Menu', 'microthemer'),
			'content' => __('Content', 'microthemer'),
			'sidebar' => __('Sidebar', 'microthemer'),
			'footer' => __('Footer', 'microthemer'),
			//'home' => $homePage['title'],

		);

		$initial_index = 0;
		foreach ($folders as $en_slug => $label){

            $translated_slug = $this->to_param($label);

			$this->default_folders[$translated_slug]['this'] = array(
				'label' => $label,
				'index' => $initial_index
			);

			$initial_index+= 100;

            // We now have automatic page-specific folders by default,
            // so include a page-specific folder for the home page, which MT defaults to
            // this will allow new conditional folders for each new page - also need a placeholder selector
            /*if ($en_slug === 'home'){

                // add logic for home page
                $this->default_folders[$translated_slug]['this']['logic'] = array(
                    'expr' => $homePage['logic']
                );

                // add placeholder selector
                $exampleSelLabel = __('Heading 1', 'microthemer');
	            $exampleSelSlug = $this->to_param($exampleSelLabel);
	            $this->default_folders[$translated_slug][$exampleSelSlug] = array(
                        'label' => $exampleSelLabel.'|h1',
                );
            }*/

		}

		// add non_section stuff that would get set by JS otherwise, causing a save request
		// which bumps up num_saves and produces the wrong initial notification and publish button state
		$this->default_folders['non_section'] = array(
			'meta' => array(),
			'active_events' => '',
		);
	}

	// check if the file is an image
	function is_image($file) {
		$ext = $this->get_extension($file);
		if ($ext == 'jpg' or
		    $ext == 'jpeg' or
		    $ext == 'png' or
		    $ext == 'gif'
		) {
			return true;
		}
		else {
			return false;
		}
	}

	// check if it's a screenshot image
	function is_screenshot($file) {
		if(strpos($file, 'screenshot.', 0) !== false) {
			return true;
		}
		else {
			return false;
		}
	}

	// check a multidimentional array for a value
	function in_2dim_array($str, $array, $target_key){
		foreach ($array as $key => $arr2) {
			if ($arr2[$target_key] == $str) {
				return $key;
			}
		}
		return false;
	}

	//check if the file is acceptable
	function is_acceptable($file, $ext = false) {
		$ext = $ext ? $ext : $this->get_extension($file);
		if ($ext == 'jpg' or
		    $ext == 'jpeg' or
		    $ext == 'png' or
		    $ext == 'gif' or
		    $ext == 'txt' or
		    $ext == 'json' or
		    $ext == 'sass' or
		    $ext == 'scss' or
		    $ext == 'css' or
		    $ext == 'psd' or
		    $ext == 'ai'
		) {
			return true;
		}
		else {
			return false;
		}
	}


	// get list of acceptable file types
	function get_acceptable() {
		$acceptable = array (
			'jpg',
			'jpeg',
			'png',
			'gif',
			'txt',
			'json',
			'sass',
			'scss',
			'css',
			'psd',
			'ai');
		return $acceptable;
	}

	// rename dir if dir with same name exists in same location
	function rename_if_required($dir_path, $name) {
		if ( is_dir($dir_path . $name ) ) {
			$suffix = 1;
			do {
				$alt_name  = substr ($name, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
				$dir_check = is_dir($dir_path . $alt_name);
				$suffix++;
			} while ( $dir_check );
			return $alt_name;
		}
		return false;
	}

	// rename the to-be-merged section
	function get_alt_section_name($section_name, $orig_settings, $new_settings) {
		$suffix = 2;
		do {
			$alt_name = substr ($section_name, 0, 200 - ( strlen( $suffix ) + 1 ) ) . "_$suffix";
			$context = 'alt-check';
			$conflict = $this->is_name_conflict($alt_name, $orig_settings, $new_settings, $context);
			$suffix++;
		} while ( $conflict );
		// also need to have index value by itself so return array.
		$alt = array(
			'name' => $alt_name,
			'index' => $suffix-1
		);
		return $alt;
		//return $alt_name;
	}

	// check if the section name exists in the orig_settings or the new_settings (possible after name modification)
	function is_name_conflict($alt_name, $orig_settings, $new_settings, $context='') {
		if ( ( isset($orig_settings[$alt_name]) // conflicts with orig settings or
		       or ($context == 'alt-check' and isset($new_settings[$alt_name]) )) // conflicts with new settings (and is an alt name)
		     and $alt_name != 'non_section' // and is a section
		) {
			return true; // conflict
		}
		else {
			return false; // no name conflict
		}
	}

	/***
	Microthemer UI Functions
	 ***/

	// ui dialog html (start)
	function start_dialog($id, $heading, $class = '', $tabs = array() ) {
		$content_class = '';
		// set dialog specific classes
		if ($id != 'manage-design-packs' and $id != 'program-docs' and $id != 'display-css-code'){
			$content_class.= 'scrollable-area';
		}
		if ($id == 'display-css-code' or $id == 'import-from-pack'){
			$content_class.= ' tvr-editor-area';
		}

		if ( !empty( $tabs ) ) {
			$class.= ' has-mt-tabs';
		}

		$icons = array( // todo
			'mt-initial-setup' => 'box-open',
			'unlock-microthemer' => 'unlock-alt',
			'display-preferences' => 'cog',
			'edit-media-queries' => 'devices',
			'mt-enqueue-js' => 'js',
			'inspect-stylesheet' => 'view-code',
			'import-from-pack' => 'import',
			'export-to-pack' => 'export',
			'display-css-code' => 'view-code',
			'display-revisions' => 'undo',
			'manage-design-packs' => 'manage-packs',
			'google-fonts' => 'google-font',
			'program-docs' => 'docs',
			'view-pack-file' => 'view-code'
		);

		$html = '<div id="'.$id.'-dialog" class="tvr-dialog '.$class.'">
				<div class="dialog-main">
					<div class="dialog-inner">
						<div class="dialog-header">
						    '.$this->iconFont($icons[$id], array(
				'class' => 'dialog-icon'
			)).'
							<span class="text">'.$heading.'</span>
							<span class="dialog-status"><span class="dialog-status-inner"></span></span>
							 '.$this->iconFont('times-circle-regular', array(
				'class' => 'close-dialog'
			)).'
						</div>';

		// If there are any tabs, the content is preceded by a tab menu
		if ( !empty( $tabs ) ) {

			$dialog_tab_param = str_replace('-', '_', $id);

			$active_tab = (!empty($this->preferences['generated_css_focus']) && $dialog_tab_param === 'display_css_code')
				? $this->preferences['generated_css_focus']
				: '0';

			$html.='
					<div class="dialog-tabs query-tabs">
						<input class="dialog-focus" type="hidden"
						name="tvr_mcth[non_section]['.$dialog_tab_param.']"
						value="'.$active_tab.'" />';

			// maybe add functionality to remember pref tab at a later date.
			for ($i = 0; $i < count($tabs); $i++) {

                $tab_name = $tabs[$i];

                if (!$tab_name){
                    continue;
                }

				$tab_class = strtolower(str_replace(' ', '-', $tab_name));
				/*$mode = $tab_class;

				if ($mode === 'previous-scss-compile'){
					$mode = 'scss';
				}

				$mode = str_replace('-(min)', '', $mode);

				data-mode="'.$mode.'"
				*/

				$html .= '
							<span class="' . ($i == $active_tab ? 'active' : '' )
				         . ' mt-tab dialog-tab dialog-tab-'.$i.' dialog-tab-'.$tab_class.'" rel="'.$i.'">
								' . $tab_name . '
							</span>';
			}
			$html .= '
					</div>';
		}
		$html .= '<div class="dialog-content '.$content_class.'">';
		return $html;
	}

	function dialog_button($button_text, $type, $class, $title = ''){
		$tAttr = $title ? 'title="'.$title.'"' : '';
		if ($type == 'span'){
			$button = '<span class="tvr-button dialog-button '.$class.'" '.$tAttr.'>'.$button_text.'</span>';
		} else {
			$button = '<input name="tvr_'.strtolower(str_replace('-', '_', $class)).'_submit"
					type="submit" value="'. esc_attr($button_text) .'"
					class="tvr-button dialog-button" '.$tAttr.' />';
		}

		return $button;
	}

	// ui dialog html (end)
	function end_dialog($button_text, $type = 'span', $class = '', $title = '') {
		$button = $this->dialog_button($button_text, $type, $class, $title);
		$html = '

							</div>
							<div class="dialog-footer">
							'.$this->settings_menu('dialog-footer'). $button. '
							</div>
						</div>
					</div>
				</div>';
		return $html;
	}

	// output clear icon for section, selector, tab, or pg
	function clear_icon($level, $extra = false){

		$title = esc_attr__('Clear', 'microthemer') .  ' ' . $this->level_map[$level];

		$data_att = $extra ? 'data-'.$extra['key'].'="'.$extra['value'].'"' : '';

		return '<span class="'.$this->iconFont('eraser', array('onlyClass' => 1)).' clear-icon" title="'.$title.'" data-input-level="'.$level.'" '.$data_att.'></span>';
	}



	function extra_actions_icon($id = false){

		return $this->ui_toggle(
			'show_extra_actions',
			!$id ? 'conditional' : esc_attr__('Show more actions', 'microthemer'),
			!$id ? 'conditional' : esc_attr__('Show less actions', 'microthemer'),
			!$id ? false : $this->preferences['show_extra_actions'],
			'extra-actions-toggle',
			$id,
			array(
				'dataAtts' => array(
					//'no-save' => 1,
					'dyn-tt-root' => $id ? false : 'show_extra_actions'
				)
			)
		);
	}

	// hover inspect button
	function hover_inspect_button($id = false, $text = false){

		$tip = esc_attr__("(Ctrl+Alt+T)", 'microthemer');

		return $this->ui_toggle(
			'hover_inspect',
			!$id ? 'conditional' : esc_attr__('Target page', 'microthemer')." ".$tip,
			!$id ? 'conditional' : esc_attr__('View page', 'microthemer')." ".$tip,
			!$id ? false : $this->preferences['hover_inspect'],
			'hover-inspect-toggle',
			$id,
			array(
				//'text' => $text ? $text : esc_html__('Target', 'microthemer'),
				//'text' => 'conditional', // this proved tricky to maintain
				// - see css .hover-inspect-toggle position:fixed hack.
				'dataAtts' => array(
					'no-save' => 1,
					'dyn-tt-root' => $id ? false : 'hover-inspect-toggle',
				),
				'innerHTML' => '
                            <span class="mt-ui-toggle mt-icon-switch mt-icon-switch-on edit-mode-icon"></span>
                            <span class="mt-ui-toggle mt-icon-switch mt-icon-switch-off view-mode-icon"></span>
                        '
			)
		);
	}

	// alias for switching to code view ($id will always be false come to think of it)
	function code_view_icon($id = false){

		return $this->ui_toggle(
			'show_code_editor',
			!$id ? 'conditional' : esc_attr__('Code view', 'microthemer'),
			!$id ? 'conditional' : esc_attr__('GUI view', 'microthemer'),
			!$id ? false : $this->preferences['show_code_editor'],
			'toggle-full-code-editor',
			$id,
			array(
				'text' => esc_html__('Code', 'microthemer'),
				'dataAtts' => array(
					'dyn-tt-root' => $id ? false : 'toggle-full-code-editor',


					// would need to dynamically update the aliases text if using this
					//'text-pos' => esc_html__('Code', 'microthemer'),
					//'text-neg' => esc_html__('GUI', 'microthemer')
				)
			)
		);
	}

	/*function manual_resize_icon(){
				return $this->ui_toggle(
					'code_manual_resize',
					'conditional',
					'conditional',
					false,
					'code-manual-resize',
					false,
					// instruct tooltip to get content dynamically
					array('dataAtts' => array(
						'dyn-tt-root' => 'code_manual_resize',
						//'editor-type'=> $editorType
					))
				);
			}*/


    function snippetHeader(){
	    return '
        <div class="mt-snippet-header">
            <div class="sync-snippet-options snippet-header-item" data-sync-state="synced">
                <span class="snippet-sync-notify">Editing synced snippet:</span>
                <span class="link detach-snippet" title="Detach as duplicate snippet (unsynced)" data-mtc="mod.MThtml.detachSyncedSnippet">detach</span>
                <span class="snippet-detached">Detached!</span>
                <span class="snippet-usage-count" data-usage-count="1" title="Usage count"  data-mtc="mod.MThtml.searchSnippetUsage"></span>
            </div>
        </div>';
    }

    function snippetFooter(){

        return '
        <div class="mt-snippet-footer">
        
             <span class="snippet-name-label" title="Name the snippet (opitonal)">
                Name: 
             </span>
             <span class="tvr-input-wrap snippet-name-input-wrap">
			    <input type="text" class="snippet-name-input snippet-dep-adjust" placeholder="" />
			 </span>
           
			 <span class="snippet-actions-label" title="Snippet actions">Actions: </span>
			 <span class="snippet-action-row">
			    '.$this->iconFont('leaf-solid', array(
                    'class' => "snippet-action",
                    'data-action' => 'beautify',
                    'data-mtc' => 'mod.MThtml.snippetAction',
                    'title' => esc_attr__('Beautify snippet', 'microthemer'),
                ))
               .$this->iconFont('file-circle-plus-solid-full', array(
                    'class' => "snippet-action",
                    'data-action' => 'new',
                    'data-mtc' => 'mod.MThtml.snippetAction',
                    'title' => esc_attr__('Create new snippet (without overwriting current)', 'microthemer'),
                ))
               .'
                  
			    <span class="tvr-input-wrap tvr-field-input-wrap search-snippets-wrap">
                    <input type="text" rel="load_snippet" data-appto="#style-components" class="combobox has-arrows mt-load-snippet" placeholder="Search" />
                    <span class="combo-arrow tvr-field-arrow"></span>
                    <span class="mt-clear-field"></span>
                </span>
             </span>
			
			 '.$this->snippet_dependency_table() .'
 
        </div>' .


        $this->ui_toggle(
            'show_snippet_adv',
	        esc_attr__('Expand snippet options', 'microthemer'),
	        esc_attr__('Collapse snippet options', 'microthemer'),
            0,
            'toggle-snippet-adv '.$this->iconFont('double-chevron-up', array('onlyClass' => 1)),
            'toggle-snippet-adv'
        );

    }

    /*function htmlContentActionsMenu(){

        $options = array(
	        'refresh' => array('label' => 'Refresh', 'tooltip' => 'Refresh the site to see your JS changes'),
            //'edit' => array('label' => 'Edit Existing', 'tooltip' => 'Edit existing content'),
            //'starter' => array('label' => 'Starter', 'tooltip' => 'Add starter code'),
        );

	    $optionsHTML = '';
	    foreach ($options as $key => $array){
		    $optionsHTML.= '<span class="tvr-button tvr-gray html-content-option html-content-'.$key.'" 
		    data-option="'.$key.'" title="'.$array['tooltip'].'">'.$array['label'].'</span>';
	    }

	    return '<div class="html-content-actions">'.$optionsHTML.'</div>';

    }*/

	// output feather icon for section, selector, tab, or pg
	function feather_icon($level){
		return '<span class="feather-icon '.$level.'-feather" data-input-level="'.$level.'"></span>';
	}

	// output icon for toggling full interface feature e.g. dock right/bottom
	function ui_toggle($aspect, $pos, $neg, $on, $class, $id = false, $config = array()){

		if ($on){
			$title = $neg;
			$class.= ' on';
		} else {
			$title = $pos;
		}

		$id = $id ? 'id="'.$id.'"' : '';

		// determine tagname
		$tag = !empty($config['tag']) ? $config['tag'] : 'span';

		// css_filter needs to pass
		$pref_sub_key = !empty($config['pref_sub_key']) ? 'data-pref_sub_key="'.$config['pref_sub_key'].'"' : '';

		// e.g. css modifier has a checkbox
		$inner_icon = !empty($config['inner_icon']) ? $config['inner_icon'] : '';

		// output arbitrary data atts
		$dataAtts = '';
		if (!empty($config['dataAtts'])){
			foreach ($config['dataAtts'] as $dat => $dat_val){

				if ($dat_val !== ''){
					$dataAtts.= 'data-'.$dat.'="'.$dat_val.'" ';
				}
			}
		}

		// Note: uit-par may need to be var
		$html = '
				<'.$tag.' '.$id.' class="mt-ui-toggle uit-par '.$class.'" title="'.$title.'"
					  data-pos="'.$pos.'"
					  data-neg="'.$neg.'"
					  '.$dataAtts.'
					  '.$pref_sub_key.'
					  data-aspect="'.$aspect.'">';

		// add text if not an icon
		if (!empty($config['text'])){

			$text = $config['text'];

			// show/hide advanced wizard options uses conditional text, as would most text toggles
			if ($text === 'conditional'){
				$text = $on ? $config['dataAtts']['text-neg'] : $config['dataAtts']['text-pos'];
			}

			// check if an input needs to be added
			if (!empty($config['css_filter']['editable'])){

				$ed = $config['css_filter']['editable'];
				$rel = !empty($ed['combo']) ? 'rel="'.$ed['combo'].'"' : '';
				$combo = '<span class="tvr-input-wrap">'.
				         '<input type="text" class="combobox cssfilter-combo" name="'.$ed['str'].'" '.$rel.'
						value="'.esc_attr(trim($ed['str'], "()")).'" />
						</span>';

				// if it's an input value - for e.g. custom prefix just append
				if (!empty($config['css_filter']['editable']['justInputValue'])){
					$text.= $combo;
				}

				// the replace str has brackets to ensure we get the right (n) in e.g. nth-child(n)
				else {
					$text = '<span class="pre-edfil mt-ui-toggle">' .
					        str_replace($ed['str'], '(</span>'.$combo.'<span class="post-edfil mt-ui-toggle">)</span>', $text);
				}

			}



			$html.= $inner_icon . $text;
		}

		// some custom HTML in between
		if (!empty($config['innerHTML'])){
			$html.= $config['innerHTML'];
		}

		$html.= '</'.$tag.'>';
		return $html;
	}

	// feather, chain, important, pie, disable icons
	function icon_control(
		$justInput,
		$con,
		$on,
		$level,
		$section_name = '',
		$css_selector = '',
		$key = '',
		$group = '',
		$subgroup = '',
		$prop = '',
		$mq_stem = 'tvr_mcth',
		$tabGroup = ''){

		// common atts
		$icon_id = '';
		$input = '';
		$tracker_class = $con.'-tracker';
		$icon_class = $con.'-toggle input-icon-toggle';
		$icon_inside = '';
		$data_atts_arr = array();
		$pos_title = $neg_title = '';


		// set MQ stub for tab and pg inputs
		$imp_key = '';
		if ($level == 'tab-input' or $level == 'subgroup' or $level == 'property'){
			if ($mq_stem == 'tvr_mcth' and $key != 'all-devices'){
				$mq_stem.= '[non_section][m_query]['.$key.']';
				$imp_key = '[m_query]['.$key.']';
			}
		}

		// icon specific
		if ($con == 'disabled'){
			$icon_class.= ' '.$this->iconFont('disable', array('onlyClass' => 1)).' '.$level.'-disabled';
			$pos_title = esc_attr__('Disable', 'microthemer') .  ' ' . $this->level_map[$level];
			$neg_title = esc_attr__('Enable', 'microthemer') .  ' ' . $this->level_map[$level];
			if ($level === 'pgtab'){
				$data_atts_arr['tab-group'] = $tabGroup;
			}
		} elseif ($con == 'chained') {
			$icon_class.= ' '.$this->iconFont('chain', array('onlyClass' => 1)).' '.$subgroup.'-chained';
			$pos_title = esc_attr__('Link fields', 'microthemer');
			$neg_title = esc_attr__('Unlink fields', 'microthemer');
		} elseif ($con == 'important') {
			$pos_title = esc_attr__('Add !important', 'microthemer');
			$neg_title = esc_attr__('Remove !important', 'microthemer');
			$icon_inside = '';
			$icon_class.= ' ' . $this->iconFont('warning-square', array('onlyClass' => 1));
		}

		elseif ($con == 'flexitem' || $con == 'griditem') {
			$icon_class.= ' dynamic-fields-toggle';
			$pos_title = esc_attr__('Show item fields', 'microthemer');
			$neg_title = esc_attr__('Show container fields', 'microthemer');
			// default icon text is 'container' overridden below if toggle is on
			//$icon_inside = esc_html__('Container', 'microthemer');
			// if flex item/container toggle
			$data_atts_arr['text-pos'] = esc_attr__('Item', 'microthemer');
			$data_atts_arr['text-neg'] = esc_attr__('Container', 'microthemer');

		} elseif ($con == 'gradient') {
			$icon_class.= ' dynamic-fields-toggle';
			$pos_title = esc_attr__('Show gradient fields', 'microthemer');
			$neg_title = esc_attr__('Show background-image fields', 'microthemer');
		}

		// generate input if item is on
		$title = $pos_title; // do what toggle is there for
		if (!empty($on)){
			$title = $neg_title; // undo what toggle is there for
			switch ($level) {
				case "section":
					$name = $mq_stem . '['.$section_name.'][this]';
					break;
				case "selector":
					$name = $mq_stem . '['.$section_name.']['.$css_selector.']';
					break;
				case "tab-input":
					$tracker_class.= '-'.$key;
					$name = $mq_stem . '['.$section_name.']['.$css_selector.'][tab]';
					break;
				case "group":
					$name = $mq_stem . '['.$section_name.']['.$css_selector.'][pg_'.$con.']';
					break;
				case "pgtab":
					$name = $mq_stem . '['.$section_name.']['.$css_selector.'][pgtab_'.$con.']['.$tabGroup.']';
					break;
				case "subgroup":
					$name = $mq_stem . '['.$section_name.']['.$css_selector.'][pg_'.$con.']['.$subgroup.']';
					break;
				case "property":
					$name = $mq_stem . '['.$section_name.']['.$css_selector.'][styles]['.$group.']['.$prop.']';
					break;
				case "script":
					$name = 'tvr_preferences[enq_js]['.$section_name.']';
					break;
				default:
					$name = '';
			}
			$name.= '['.$con.']';

			// important is only for props, and has different structure
			if ($con == 'important'){
				$name = 'tvr_mcth[non_section][important]'.$imp_key.'['.$section_name.']['.$css_selector.']['.$group.']['.$prop.']';
			}

			$input = '<input class="input-toggle-tracker '.$tracker_class.'" type="hidden" name="'.$name.'" value="1" />';
			$icon_class.= ' active';
		}

		// output arbitrary data atts
		$dataAtts = '';
		//$test = 'yes' . implode($data_atts_arr);
		if (!empty($data_atts_arr)){
			foreach ($data_atts_arr as $dat => $dat_val){
				$dataAtts.= 'data-'.$dat.'="'.$dat_val.'" ';
			}
			//$test = 'person';
			;				}

		// generate icon
		$icon = '<span '.$icon_id.' class="'.$icon_class.'" title="'.$title.'" data-pos="'.$pos_title.'"
				data-neg="'.$neg_title.'"  data-input-type="'.$con.'" '.$dataAtts.' 
							data-input-level="'.$level.'">'.$icon_inside.'</span>';

		// with feather on tabs, icon and input are output separately
		if ($con == 'disabled'){
			if ($level == 'tab'){
				$input = '';
			} elseif ($level == 'tab-input'){
				$icon = '';
			}
		}

		// with important, a placeholder is used for css3 options that only need one 'i'
		if (!empty($this->propertyoptions[$group][$prop]['hide imp'])) {
			$icon = '<span class="imp-placeholder">i</span>';
		}

		// return control
		if ($justInput){
			return $input;
		}

		return $input . $icon;
	}

	// display the main menu
	function settings_menu($subset = false){

		//$html = '<ul class="mt-options-tabs">';
		$html = '';
		$dialog_footer_html = '<div class="dialog-footer-menu">';

		// menu groups
		foreach ($this->menu as $group_key => $arr){

			// e.g. the exit menu is added to the bottom row with the breadcrumbs
			if (isset($arr['custom_insert'])){
				continue;
			}

			$panel_set = $this->menu_panel_set(false, $group_key, $arr, $subset, $html, $dialog_footer_html);
			$html = $panel_set['main'];
			$dialog_footer_html = $panel_set['footer'];
		}

		//$html.= '</ul>';
		$dialog_footer_html.= '</div>';

		switch ($subset){
			case false:
				return $html;
			case 'dialog-footer':
				return $dialog_footer_html;
		}

		//return $html;
	}

	function menu_panel_set($is_nested, $group_key, $arr, $subset, $html, $dialog_footer_html){

		//$tabs_html = '<div class="mt-options-tabs">';
		$group_li = '';
		$areas_html = '';
		$is_expanded = in_array($group_key, $this->preferences['layout']['right']['expanded_settings'] );
		$field_class = '';
		//$this->preferences['program_settings_tab'] === $group_key;

		// do we have an icon for the set?
		$group_icon = '';
		/*if (!empty($arr['icon_class'])){
					$group_icon.= '<span class="mt-menu-group-icon '.$arr['icon_class'].'"></span>';
				}*/

		// should a link wrap the icon and text?
		$link_start = $link_end = $tab_class = '';
		if (!empty($arr['item_link'])){
			$link_id = !empty($arr['link_id']) ? 'id="'.$arr['link_id'].'"' : '';
			$link_target = !empty($arr['link_target']) ? 'target="'.$arr['link_target'].'"' : '';
			$link_start = '<a class="item-link" '.$link_id.' '.$link_target.' href="'.$arr['item_link'].'">';
			$link_end = '</a>';
			$tab_class = ' direct-action';
		}

		if ($is_expanded){
			$tab_class.= ' expanded';
		}

		if ($is_nested){
			$tab_class.= ' nested-panel-heading';
			$field_class.= ' nested-panel';
		}

		//  $group_icon .
		$group_li.= '
                <div class="mt-expandable-heading mt-'.$group_key . '-heading'. $tab_class.'" rel="'.$group_key.'">
                   '. $link_start . $arr['name'] . $link_end . ' 
                </div>';

		if (!empty($arr['sub'])){
			$panel_area = $this->menu_panel_sub(
				$group_key, $arr, $subset, $areas_html, $field_class, $dialog_footer_html
			);
			$areas_html = $panel_area['areas_html'];
			$dialog_footer_html = $panel_area['footer'];
		}

		// full menu set
		$html.= $group_li . $areas_html;

		return array(
			'main' => $html,
			'footer' => $dialog_footer_html
		);

	}

	function menu_panel_sub($group_key, $arr, $subset, $areas_html = '', $field_class = '', $dialog_footer_html = ''){

		if ($subset){
			$field_class.= ' panel-subset-'.$subset;
		}

		$areas_html.= '<ul class="mt-expandable-panel mt-'.$group_key . '-panel ' . $field_class.'">';

		// menu items
		foreach ($arr['sub'] as $item_key => $arr2){

			// dialog footer only needs a subset of options
			if ($subset === 'dialog-footer' and empty($arr2['dialog'])) continue;

            // no need for AI option if they don't want AI
            if ($item_key === 'dock_ai_right' && !empty($this->preferences['disable_ai'])){
                continue;
            }

			// support nested panels (which we're not currently indenting)
			if (!empty($arr2['nested'])){
				$panel_set = $this->menu_panel_set(
					true, $item_key, $arr2['nested'], $subset, $areas_html, $dialog_footer_html
				);
				$areas_html = $panel_set['main'];
				//$dialog_footer_html = $panel_set['footer'];
				continue;
			}

			// format the data attributes
			$data_attr = '';
			if ( !empty($arr2['data_attr']) ){
				foreach($arr2['data_attr'] as $da_key => $da_value){
					$data_attr.= 'data-'.$da_key.'="'.$da_value.'"';
				}
			}

			$common_class = !empty($arr2['class']) ? $arr2['class'] : '';
			$show_dialog_class = (!empty($arr2['dialog'])) ? ' show-dialog' : '';
			$class = 'item-' . $common_class;
			$class.= (isset($arr2['toggle'])) ? ' mt-toggle' : '';
			$class.= (isset($arr2['item_link'])) ? ' item-link' : '';
			//$class.= (!empty($arr2['new_set'])) ? ' new-set' : '';
			$class.= $show_dialog_class;

			// format rel, class, id, data
			$rel = !empty($arr2['dialog']) ? 'rel="'.$common_class.'"' : '';
			$id = !empty($arr2['id']) ? 'id="'.$arr2['id'].'"' : '';
			$link_id = !empty($arr2['link_id']) ? 'id="'.$arr2['link_id'].'"' : '';
			$link_target = !empty($arr2['link_target']) ? 'target="'.$arr2['link_target'].'"' : '';


			// format icon atts
			$item_icon = !empty($arr2['icon_name'])
				? $this->iconFont($arr2['icon_name'], array(
					'class' => 'mt-menu-icon '.$common_class.$show_dialog_class,
					'id' => !empty($arr2['icon_id']) ? $arr2['icon_id'] : null,
					'rel' => !empty($arr2['dialog']) ? $common_class : null,
					'title' => !empty($arr2['icon_title']) ? $arr2['icon_title'] : null,
				))
				: '';

			$text_class = !empty($arr2['text_class']) ? $arr2['text_class'] : '';
			$text_attr = !empty($arr2['short_name']) ?
				'data-sl="'.$arr2['short_name'].'" data-ll="'.$arr2['name'].'"' : '';

			//$show_dialog = $class.= (!empty($arr2['dialog'])) ? ' show-dialog' : '';
			//$icon_title = !empty($arr2['icon_title']) ? 'title="'.$arr2['icon_title'].'"' : '';
			$sup_checkboxes = !empty($arr2['checkboxes']) ? $arr2['checkboxes'] : false;

			// output heading or nothing if we just want a line
			if (isset($arr2['new_set'])){
				$areas_html.= '<li class="new-set-heading">'.$arr2['new_set'].'</li>';
			}

			// item
			$areas_html.= '<li '.$id.' '.$data_attr.' '.$rel.' class="mt-item '.$item_key.' '.$class.'">';

			// custom HTML
			if (!empty($arr2['custom'])){
				$areas_html.= $arr2['custom'];
			}

			// regular content
			else {
				// should a link wrap the icon and text?
				if (!empty($arr2['item_link'])){
					$areas_html.= '<a '.$link_id.' '.$link_target.' href="'.$arr2['item_link'].'">';
				}

				//$item_icon =  '<span '.$icon_id.' '.$rel.' class="mt-menu-icon mtif-'.$icon_class.' '.$show_dialog.'" '.$icon_title.'></span>';

				// add title to icons
				$item_icon = str_replace(
					'<span', '<span title="'.$arr2['name'].'"', $item_icon
				);

				// make docs option default to font-size
				if ($common_class === 'program-docs'){
					$item_icon = str_replace(
						'<span', '<span data-prop-group="font" data-prop="font_family"', $item_icon
					);
				}

				// append icon to dialog footer
				$dialog_footer_html.= $item_icon;


				// icon
				$areas_html.= $item_icon;

				if (!empty($arr2['text_data_attr'])){
					$text_attr = $this->format_data_attr_array($arr2['text_data_attr'], $text_attr);
				}

				// text label
				$colon = ''; // isset($arr2['toggle']) & ($item_key!= 'highlighting') ? ':' : '';
				$areas_html.= '<span class="mt-menu-text '.$common_class.$show_dialog_class.' '.$text_class.'"
							title="'.$arr2['title'].'" '.$text_attr.'>'
				              .$arr2['name'].$colon.'</span>';

				// do we need toggle?
				if (isset($arr2['toggle'])){
					$areas_html.= $this->toggle($item_key, $arr2);
				}

				// column options
				if (isset($arr2['column_options'])){
					$areas_html.= $this->column_options($arr2['column_options']);
				}

				// do we display keyboard shortcut
				if (isset($arr2['keyboard_shortcut'])){
					$areas_html.= '<span class="keyboard-sh">'.$arr2['keyboard_shortcut'].'</span>';
				}

				// do we need input?
				if (isset($arr2['input'])){
					$input_id = !empty($arr2['input_id']) ? 'id="'.$arr2['input_id'].'"' : '';
					$input_name = !empty($arr2['input_name']) ? $arr2['input_name'] : '';
					$input_placeholder = !empty($arr2['input_placeholder']) ? $arr2['input_placeholder'] : '';

                    // data-appto="#style-components"

					$areas_html.= '
								<div class="combobox-wrap tvr-input-wrap">
		
									<input type="text" name="'.$input_name.'" 
									placeholder="'.$input_placeholder.'" 
									    data-appto="#combo-markup-do-not-clear"
									'.$input_id.' class="combobox has-arrows"
									rel="'.$arr2['combo_data'].'"
									value="'.$arr2['input'].'" />
									<span class="mt-clear-field"></span>
									<span class="combo-arrow"></span>
									<span class="tvr-button '.$arr2['button']['class'].'">
								    '.$arr2['button']['text'].'
								    </span>
								    
								</div>
								';

					$areas_html.= $this->maybe_output_supplementary_checkboxes($sup_checkboxes);
				}

				// custom display value
				if (isset($arr2['display_value'])){
					$areas_html.= $arr2['display_value'];
				}

				if (!empty($arr2['item_link'])){
					$areas_html.= '</a>';
				}
			}

			$areas_html.= '</li>';
		}

		$areas_html.= '</ul>';

		return array(
			'areas_html' => $areas_html,
			'footer' => $dialog_footer_html
		);
	}

	function format_data_attr_array($array, $data_attr = ''){

		if (!empty($array) && is_array($array)){
			foreach($array as $da_key => $da_value){
				$data_attr.= ' data-'.$da_key.'="'.$da_value.'"';
			}
		}

		return $data_attr;
	}

	function suggested_screen_layouts(){

        $allowAI = empty($this->preferences['disable_ai']);

		$sm = array(
			//'expand_device_tabs' => 1,
			'left_sidebar_columns' => 1,
			'right_sidebar_columns' => 1,
		);
		$md = array(
			'full_height_left_sidebar' => 1, // this is now the default for CSS icons
			'dock_folders_left' => 1,
			'dock_styles_left' => 1,
			'dock_editor_left' => 1,
			'left_sidebar_columns' => 1,
            'right_sidebar_columns' => 1,
		);
		$lg = array_merge($md, array(
			'dock_ai_right' => $allowAI ? 1 : 0,
			'dock_settings_right' => $allowAI ? 0 : 1,
		));
		$xl = array_merge($lg, array(
			'left_sidebar_columns' => 3,
			'dock_styles_left' => 0,
			'full_height_right_sidebar' => 1,
			'wizard_expanded' => 1
		));
		$xl2 = array_merge($xl, array(
			'left_sidebar_columns' => 2,
			'right_sidebar_columns' => 2,
			'dock_styles_left' => 1,
			'dock_settings_right' => 1,
		));

		return array(
			'S' => array(
				'size' => 1366,
				'title' => esc_attr__('Options top', 'microthemer'),
				'on' => $sm
			),
			'M' => array(
				'size' => 1920,
				'title' => esc_attr__('Options left', 'microthemer'),
				'on' => $md
			),
			'L' => array(
				'size' => 2160,
				'title' => esc_attr__('Right panel docked', 'microthemer'),
				'on' => $lg
			),
			'XL' => array(
				'size' => 2560,
				'title' => esc_attr__('Multi-column', 'microthemer'),
				'on' => $xl
			),
			'XL2' => array(
				'size' => 2560,
				'title' => esc_attr__('Multi-column', 'microthemer'),
				'on' => $xl2
			),
		);
	}

	function screen_layout_options(){

		$html = '<div class="layout-presets-heading">'.esc_html__('Screen size layout presets', 'microthemer').'</div>'.
		        '<div id="screen-layout-options" class="screen-layout-options fake-radio-parent">';

		foreach ($this->suggested_screen_layouts as $key => $array){

			$on_class = '';
			$checked = '';

			if ($key === $this->preferences['suggested_layout']){
				$on_class = ' on';
				$checked = 'checked="checked"';
			}

            // Let's do away with the original XL layout with the options on the top, and just have XL2 (but display as just XL)
            $displayKey = str_replace('2', '', $key);

			$html.= '
                    <div>
                        <input type="radio" autocomplete="off" class="radio"
                        name="suggested_layout" value="'.$key.'" '.$checked.' />
                        '.$this->iconFont('radio-btn-unchecked', array(
					'class' => 'fake-radio switch-layout-preset '.$on_class,
					'data-preset' => $key,
					'title' => $array['title']
				)).'
                        <span class="radio-label">'.$displayKey.'</span>
                    </div>';
		}

		$html.= '</div>';

		return $html;
	}

	// keyboard shortcuts
	function keyboard_shortcuts_list(){

		$shortcuts = array(
			array(
				'action' => 'Toggle page builder', // (Elementor, Beaver Builder, or Oxygen)
				'win' => 'Ctrl+Alt+B',
			),
			array(
				'action' => 'Toggle full code editor',
				'win' => 'Ctrl+Alt+C',
			),
			array(
				'action' => 'Detach site preview', // in separate window
				'win' => 'Ctrl+Alt+D',
			),
			/*array(
						'action' => 'Search folders', // support this if time
						'win' => 'Ctrl+Alt+F',
					),*/
			array(
				'win' => 'Ctrl+Alt+G',
				'action' => 'View the generated code'
			),
			array(
				'win' => 'Ctrl+Alt+H',
				'action' => 'Toggle selector highlighting'
			),
			array(
				'action' => 'Jump to UI field for editor line', // Jump between code editor and UI property field
				'win' => 'Ctrl+Alt+J',
			),
			/*array(
						'action' => 'Toggle CSS property text labels',
						'win' => 'Ctrl+Alt+L',
					),*/
			array(
				'action' => 'Toggle page navigator menu',
				'win' => 'Ctrl+Alt+N',
			),
			array(
				'action' => 'Beautify code editor',
				'win' => 'Ctrl+Alt+O',
			),
			array(
				'action' => 'Do full Sass compile', // If Sass is enabled, recompiles all MT selectors
				'win' => 'Ctrl+Alt+P',
			),
			array(
				'action' => 'Return focus to editor',
				'win' => 'Ctrl+Alt+R',
			),
			array(
				'win' => 'Ctrl+S',
				'action' => 'Save settings (for JS editor)' // Save settings. This is only needed when typing code in the JavaScript code editor. GUI settings and CSS code edits auto-save.
			),
			array(
				'action' => 'Toggle targeting mode',
				'win' => 'Ctrl+Alt+T',
			),
			array(
				'action' => 'Navigate to previous selector',
				'win' => 'Ctrl+Alt+,',

			),
			array(
				'action' => 'Navigate to next selector',
				'win' => 'Ctrl+Alt+.',

			),

		);

		// keyboard shortcuts alt / shift
		/*
				 * - select element when hovering ALT
				 * -  x 10 increment for property adjust
				 * - ALT + Shift (for left docked view)
				 * */

		$html = '<div class="keyboard-shortcuts-list">';

		// context dependent Ctrl key
		$html.= '
                
                <span class="shortcut-b shortcut-last shortcut-span3">Ctrl</span>
                <ul class="shortcut-span3">
                    <li>'.esc_html__('Press when clicking links to load a new page, even if Targeting mode is enabled', 'microthemer').'</li>
                </ul>';

		// context dependent Alt key
		$html.= '
                
                <span class="shortcut-b shortcut-last shortcut-span3">Alt</span>
                <ul class="shortcut-span3">
                    <li>'.esc_html__('Select an element (instead of clicking it)', 'microthemer').'</li>
                    <li>'.esc_html__('Enable mousewheel adjust', 'microthemer').'</li>
                </ul>';

		// context dependent Shift key
		$html.= '
          
                <span class="shortcut-b shortcut-last shortcut-span3">Shift</span>
                <ul class="shortcut-span3">
                    <li>'.esc_html__('Select multiple elements when clicking', 'microthemer').'</li>
                    <li>'.esc_html__('Increment style value by 10 when using:', 'microthemer').'
                        <ul>
                             <li>'.esc_html__('Increment buttons', 'microthemer').'</li>
                             <li>'.esc_html__('Mousewheel', 'microthemer').'</li>
                             <li>'.esc_html__('Up & down on keyboard', 'microthemer').'</li>
                        </ul>
                    </li>
                </ul>
                
                '; // <span class="new-shortcut-section"></span>


		foreach ($shortcuts as $i => $array){

			$shortcut = $array['win'];
			$shortcut_pieces = explode('+', $shortcut);
			$buttons = '';
			$count = count($shortcut_pieces);

			foreach ($shortcut_pieces as $j => $buttonText){
				$nth = $j+1;
				$last_class = ($nth === $count) ? ' shortcut-last' : '';
				$span_two_class = ($nth === 1 && $count === 2) ? ' shortcut-span2' : '';
				$buttons.= '<span class="shortcut-b shortcut-b'.($nth).$last_class.$span_two_class.'">'.$buttonText.'</span>';
			}

			$html.= '<span class="shortcut-heading">'.$array['action'].'</span>'
			        .$buttons;
		}





		$html.= '</div>';

		return $html;

	}

	function maybe_output_supplementary_checkboxes($checkboxes){

		if (!$checkboxes){
			return '';
		}

		$html = '';

		if (!empty($checkboxes)){
			foreach ($checkboxes as $item){
				$html.= '<div class="menu-supplementary-checkbox tvr-clearfix">
                            <input type="checkbox" name="'.$item['name'].'"> 
                            '.$this->iconFont('tick-box-unchecked', array(
						'class' => 'fake-checkbox'
					)).'
                            <span class="ef-label">'.$item['label'].'</span>
                        </div>';
			}
		}

		return $html;

	}

	// output left/right column options select menu
	function column_options($side = 'left'){

		$totalItems = count($this->preferences['layout'][$side]['items']);
		$optionHTML = '';

        /*if ($side === 'right' && !empty($this->preferences['disable_ai'])){
	        $totalItems = $totalItems - 1;
        }*/

		for ($x = 1; $x <= $totalItems; $x++) {
			$selected = intval($this->preferences['layout'][$side]['num_columns']) === $x
				? ' selected="selected"'
				: '';
			$optionHTML.= '<option value="'.$x.'"'.$selected.'>'.$x.'</option>';
		}

		return '<select id="'.$side.'-column-options" class="settings-select-menu mt-select" data-side="'.$side.'">'.$optionHTML.'</select>';
	}

	function toggle($item_key, $arr){
		$on = $arr['toggle'] ? 'on' : '';
		$id = !empty($arr['toggle_id']) ? 'id="'.$arr['toggle_id'].'"' : '';
		// set dynamic title (adding this feature slowly)
		$pos_neg = $title = '';
		if( !empty($arr['data-pos']) ){
			$pos_neg = 'data-pos="'.$arr['data-pos'].'" data-neg="'.$arr['data-neg'].'" ';
			$title = !$on ? $arr['data-pos'] : $arr['data-neg'];
			$title = 'title="'.$title.'"';
		}


		$fhtml = !empty($arr['data-fhtml']) ? ' data-fhtml="'.$arr['data-fhtml'].'" ' : '';
		$noSave = !empty($arr['data-no-save']) ? ' data-no-save="'.$arr['data-no-save'].'" ' : '';


		$html = '';
		//$html.= print_r($on, true);
		$html.= '
				<div '.$id.' class="mtonoffswitch mt-ui-toggle uit-par '.$on.'"
				data-aspect="'.$item_key.'" '.$pos_neg.' '.$title.$fhtml.$noSave.'>
					<input type="checkbox" name="mtonoffswitch" class="mtonoffswitch-checkbox"
					id="mymtonoffswitch-'.$item_key.'">
					<label class="mtonoffswitch-label mt-ui-toggle" for="mymtonoffswitch-'.$item_key.'">
						<span class="mtonoffswitch-inner mt-ui-toggle"></span>
						<span class="mtonoffswitch-switch mt-ui-toggle"></span>
					</label>
				</div>';
		return $html;
	}

	// Resolve property/value input fields
	function resolve_input_fields(
		$section_name,
		$css_selector,
		$array,
		$property_group_array,
		$property_group_name,
		$property,
		$value,
		$con = 'reg',
		$key = 1,
		$sel_code = 'selector_code') {
		$html = '';

		// get value object, array, and string value
		$valueArray = false;
		$valueObject = $value;
		$value = is_array($valueObject) && !empty($valueObject['value'])
			? $valueObject['value']
			: ''; // should this be

		if (is_array($value)){
			$valueArray = $value;
			$value = !empty($valueArray[0]) ? $valueArray[0] : '';
		}

		// don't display legacy properties or the image display field
		if (!$this->is_legacy_prop($property_group_name, $property) and !strpos($property, 'img_display') ){
			include $this->thisplugindir . 'includes/resolve-input-fields.inc.php';
		}
		return $html;
	}

	// search posts by title or slug so we get precise search results
	function search_by_title_or_slug( $search, $wp_query ) {

		if ( ! empty( $search ) && ! empty( $wp_query->query_vars['search_terms'] ) ) {

			global $wpdb;

			$q = $wp_query->query_vars;
			$n = ! empty( $q['exact'] ) ? '' : '%';
			$search = array();

			foreach ( ( array ) $q['search_terms'] as $term ) {

				$sql_term = $n . $wpdb->esc_like( $term ) . $n;

				$search[] = $wpdb->prepare(
					" ($wpdb->posts.post_title LIKE %s or 
                            $wpdb->posts.post_name LIKE %s) 
                           
                            ",
					// or $wpdb->posts.post_type LIKE %s
					// AND post_status != 'auto-draft' - this caused issues on some sites
					// the above line made search less precise for content
					// e.g. "P" would bring in all pages instead of a page called "Product tour"
					//$sql_term,
					$sql_term,
					$sql_term
				);
			}

			if ( ! is_user_logged_in() ){
				$search[] = "$wpdb->posts.post_password = ''";
			}

			$search = ' AND ' . implode( ' AND ', $search );
		}

		//echo 'mysearch: ' . $search;

		return $search;
	}

	function get_custom_post_types(){

		$args = array(
			'public'   => true,
			'_builtin' => false,
		);

		$output = 'objects'; // names or objects, note names is the default
		$operator = 'and'; // 'and' or 'or'

		return get_post_types( $args, $output, $operator );
	}

	function format_posts_of_type(
		$post_type, $category, $permalink_structure, $common_config, &$urls, $isBlockTheme = null
	){

		$isCustomPosts = ($post_type !== 'page' && $post_type !== 'post');
		$customPostsPathPrefix = $isCustomPosts
			? '/'.$post_type
			: '';

		$items = get_posts(
			array_merge($common_config, array('post_type'=> $post_type))
		);

		foreach($items as $item){

			// I noticed a strange bug whereby "kit" should have got an elementor template post in 'My Templates'
			// category, but also got the search result in all other post categories inc regular page/post
			if ($item->post_type !== $post_type){
				//continue;
			}

			$label = $item->post_title;

			if ($item->post_status === 'draft'){
				$label.= ' — Draft';
			}


			$path = $customPostsPathPrefix.'/'.$item->post_name.'/';
			//$url = rtrim($root_home_url, '/') . $path;

			// if non-standard permalink structure, we have to use the DB method of getting the URL
			if ($permalink_structure !== '/%postname%/' or
			    $item->post_type === 'ct_template' or
			    $item->post_status === 'draft'){

				// format URL as draft preview
				if ($item->post_status === 'draft'){
					$url = $item->guid.'&preview=true'; // maybe build this using id and pos_type
				}

				// it seems to be a quirk of Oxygen that the template admin screen must be loaded first
				elseif ($item->post_type === 'ct_template'){
					$url = $this->wp_blog_admin_url . 'post.php?post=' . $item->ID.'&action=edit';
					//$url = get_permalink($item).'&ct_builder=true';
					// enable this after fixing forever loading issue (maybe due to post locking...)
				}

				// non-standard permalink structure
				else {
					$url = get_permalink($item);
				}

				//$url = get_permalink($item);

				$path = $this->root_rel($url, false, true);
			}

			// exception for Oxygen - template pages produce PHP error if loaded on frontend
			// without ct_builder parameter
			/*if ($item->post_type === 'ct_template'){
						$path = Common::append_url_param($path, 'ct_builder', 'true');
					}*/

            $item_id = !empty($item->ID) ? $item->ID : false;
            $item_slug = !empty($item->post_name) ? $item->post_name : $item_id;

			$urls[$post_type][$label] = array(
				'label' => $label,
				'value' => $path,
				'category' => $category,
				'item_id' => $item_id,
                'logic' => $post_type === 'page'
                    ? 'is_page("'.$item_slug.'")'
                    : ($post_type === 'bricks_template'
                        ? '\Microthemer\has_template("bricks", '.$item_id.', "'.$item->post_title.'")'
                        : 'is_single("'.$item_slug.'")'),
				//'all' => $item, // debug
				//'config' => array_merge($common_config, array('post_type'=> $post_type))
			);

			// todo maybe include Edit Post - xyz, Edit Page - xyz, Site Editor - xyz if it's a block theme

		}

		// sort alphabetically within category - actually no, have recently modified near top
		// ksort($urls[$post_type]);

	}

	// get example category, author, archive, 404
	function get_resource_example(){
		// todo
	}

	/*function get_placeholder_urls(){
				$light = $this->thispluginurl . "includes/place-holder.php";
			    $dark = $light . "?mt_dark_mode=1";
			    return array(
			            'light' => $light,
			            'dark' => $dark,
			            'current' => !empty($this->preferences['mt_dark_mode']) ? $dark : $light
			    );
			}*/

	function get_site_pages($searchTerm = null){

		// get URL vars
		/*$blog_details = function_exists('get_blog_details')
					? get_blog_details( $this->multisite_blog_id )
					: false;*/
		/*$root_home_url = $blog_details
                    ? rtrim($blog_details->path, '/')
                    : $this->home_url;*/

		// get_the_title( get_option('page_on_front') )
        $isBlockTheme = wp_is_block_theme();
		$siteEditorBaseUrl = $isBlockTheme
			? $this->root_rel(get_admin_url(null, 'site-editor.php'), false, true)
			: '';
		$permalink_structure = get_option('permalink_structure');
		$users_can_register = get_option('users_can_register');
		$common_config = array(
			'post_status' => array('publish', 'draft'), // we want user to be able to access drafts
			'numberposts' => 8,
			'suppress_filters' => false,
			'orderby' => 'modified',
			'order' => 'DESC',
			's' => $searchTerm
		);

		// Get data
		$urls = array();
		$formatted_urls = array();
		$urlTypes = array(

			'global' => esc_html__('Global', 'microthemer'),
			'home' => esc_html__('Home page', 'microthemer'),
			'page' => esc_html__('Recently edited pages', 'microthemer'),
			'post' => esc_html__('Recently edited posts', 'microthemer'),
			'types' => esc_html__('Content types', 'microthemer'),
			'has' => esc_html__('Has attribute', 'microthemer'),
			'wordpress' => esc_html__('WordPress', 'microthemer'),
			'custom_posts' => esc_html__('Custom posts', 'microthemer'),
			'create' => esc_html__('Create new', 'microthemer'),
		);
		$custom_post_types = $this->get_custom_post_types();


		foreach ($urlTypes as $key => $category){

			// regular post or page
			if ($key === 'page' || $key === 'post'){
				$post_type = $key;
				$this->format_posts_of_type(
					$post_type, $category, $permalink_structure, $common_config, $urls, $isBlockTheme
				);
			}

			// custom posts
			elseif ($key === 'custom_posts'){

				//$urls[$key] = $custom_post_types;
				foreach ($custom_post_types as $index => $custom_post_type){
					$category = $custom_post_type->label;
					$post_type = $custom_post_type->name;
					$this->format_posts_of_type(
						$post_type, $category, $permalink_structure, $common_config, $urls
					);
				}
			}

			// general / wordpress
			elseif ($key === 'global' || $key === 'home' || $key === 'types' || $key === 'has' || $key === 'create' || $key === 'wordpress'){

				$custom_links = array();

				if ($key === 'global'){

					$custom_links = array(
						array(
							'label' => esc_html__('Is frontend', 'microthemer'),
							'logic' => '\Microthemer\is_public()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is admin area', 'microthemer'),
							'logic' => 'is_admin()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is frontend or admin', 'microthemer'),
							'logic' => '\Microthemer\is_public_or_admin()',
							'logic_only' => 1
						)
					);
				}

				// Home
				if ($key === 'home'){

					/*$homeTitle = ('posts' === get_option( 'show_on_front' ))
						? esc_html__('Blog home', 'microthemer')
						: get_the_title( get_option('page_on_front') );*/

                    $homePage = Helper::homePageData();

					$custom_links[] = array(
						'label' => $homePage['title'], // esc_html__('Home page', 'microthemer'),
						'value' => '/',
                        'logic' => $homePage['logic']
					);
				}

				// Content types
				if ($key === 'types'){

					$custom_links = array(
						array(
							'label' => esc_html__('Is page', 'microthemer'),
							'logic' => 'is_page()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is single post', 'microthemer'),
							'logic' => 'is_single()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is blog home', 'microthemer'),
							'logic' => 'is_home()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is archive', 'microthemer'),
							'logic' => 'is_archive()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is search', 'microthemer'),
							'logic' => 'is_search()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is 404 page', 'microthemer'),
							'logic' => 'is_404()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is author archive', 'microthemer'),
							'logic' => 'is_author()',
							'logic_only' => 1
						),
                        array(
							'label' => esc_html__('Is category archive', 'microthemer'),
							'logic' => 'is_category()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Is date archive', 'microthemer'),
							'logic' => 'is_date()',
							'logic_only' => 1
						),
                        array(
							'label' => esc_html__('Is tag archive', 'microthemer'),
							'logic' => 'is_tag()',
							'logic_only' => 1
						),

                    );

					foreach ($custom_post_types as $custom_post_type){
						$customPostLabel = 'Is ' .  $custom_post_type->label;
						$custom_links[] = array(
							'label' => $customPostLabel,
							'logic' => 'get_post_type() === "'.$custom_post_type->name.'"',
							'logic_only' => 1
						);

					}

				}

				// Has attribute
				if ($key === 'has'){

					$custom_links = array(
						array(
							'label' => esc_html__('Has action', 'microthemer'),
							'logic' => 'has_action()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has block', 'microthemer'),
							'logic' => 'has_block()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has category', 'microthemer'),
							'logic' => 'has_category()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has filter', 'microthemer'),
							'logic' => 'has_filter()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has meta', 'microthemer'),
							'logic' => 'has_meta()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has post format', 'microthemer'),
							'logic' => 'has_post_format()',
							'logic_only' => 1
						),
						array(
							'label' => esc_html__('Has tag', 'microthemer'),
							'logic' => 'has_tag()',
							'logic_only' => 1
						),
						/*array(
							'label' => esc_html__('Has term', 'microthemer'),
							'logic' => 'has_term()',
							'logic_only' => 1
						),*/
					);

					foreach ($custom_post_types as $custom_post_type){
						$customPostLabel = 'Is ' .  $custom_post_type->label;
						$custom_links[] = array(
							'label' => $customPostLabel,
							'logic' => 'get_post_type() === "'.$custom_post_type->name.'"',
							'logic_only' => 1
						);

					}

				}

				// WordPress auth pages
				if ($key === 'wordpress'){

                    $allLoginLabel = esc_html__('Login and Forgot Password', 'microthemer');

					$custom_links[] = array(
						'label' => esc_html__('Login page', 'microthemer'),
						'value' => $this->root_rel(wp_login_url(), false, true),
                        'logic' => 'is_login() and !isset($_GET["action"])',
                        'exclude_logic' => '!is_login() or isset($_GET["action"])',
					);

					$custom_links[] = array(
						'label' => esc_html__('Lost password page', 'microthemer'),
						'value' => $this->root_rel(wp_lostpassword_url(), false, true),
						'logic' => 'is_login() and $_GET["action"] === "lostpassword"',
						'exclude_logic' => '$_GET["action"] !== "lostpassword"',
					);

					// if registration is supported
					if ($users_can_register){

                        $custom_links[] = array(
							'label' => esc_html__('Registration page', 'microthemer'),
							'value' => $this->root_rel(wp_registration_url(), false, true),
							'logic' => 'is_login() and $_GET["action"] === "register"',
                            'exclude_logic' => '$_GET["action"] !== "register"',
						);

						$allLoginLabel.= esc_html__(' and Registration', 'microthemer');
					}

                    // todo maybe add search / archive / author / category etc
					/*$custom_links[] = array(
						'label' => esc_html__('Search page', 'microthemer'),
						'value' => '/?s=test',
					);*/

					if ($isBlockTheme){
						$custom_links[] = array(
							'label' => esc_html__('Site Editor', 'microthemer'),
							'value' => $siteEditorBaseUrl,
						);
						$custom_links[] = array(
							'label' => esc_html__('Site Editor - Navigation', 'microthemer'),
							'value' => $siteEditorBaseUrl . "?path=%2Fnavigation",
						);
						/*$custom_links[] = array(
							'label' => esc_html__('Site Editor - Styles', 'microthemer'),
							'value' => $siteEditorBaseUrl . "?path=%2Fwp_global_styles",
						);*/
						$custom_links[] = array(
							'label' => esc_html__('Site Editor - Pages', 'microthemer'),
							'value' => $siteEditorBaseUrl . "?path=%2Fpage",
						);
						$custom_links[] = array(
							'label' => esc_html__('Site Editor - Templates', 'microthemer'),
							'value' => $siteEditorBaseUrl . "?path=%2Fwp_template",
						);
						$custom_links[] = array(
							'label' => esc_html__('Site Editor - Patterns', 'microthemer'),
							'value' => $siteEditorBaseUrl . "?path=%2Fpatterns",
						);

						// markw@perfectbee.com
					}

                    // When assigning conditional CSS it may be useful to target all WP login pages at once
					$custom_links[] = array(
						'label' => $allLoginLabel,
						'logic' => 'is_login()',
						'logic_only' => 1
					);

                    // WordPress admin pages
					$custom_links[] = array(
						'label' => 'Is admin area',
						'logic' => 'is_admin()',
						'logic_only' => 1
					);

					// Custom MT function - user is a subscriber
					$custom_links[] = array(
						'label' => 'Is a subscriber',
						'logic' => '\Microthemer\user_has_role("subscriber")',
						'logic_only' => 1
					);

					// Custom MT function - user is a subscriber
					$custom_links[] = array(
						'label' => 'Is an administrator',
						'logic' => '\Microthemer\user_has_role("administrator")',
						'logic_only' => 1
					);

					// Custom MT function - is gutenberg editor screen
					$custom_links[] = array(
						'label' => 'Is Gutenberg block editor',
						'logic' => '\Microthemer\query_admin_screen("is_block_editor", true)',
						'logic_only' => 1
					);


				}

				// General types of page - finish later
				elseif ($key === 'create'){

                    $newPostUrl = $this->root_rel(admin_url('post-new.php'), false, true);

                    $custom_links[] = array(
	                    'label' => esc_html__('Post', 'microthemer'),
	                    'value' => $newPostUrl . '?post_type=page',
                    );

					$custom_links[] = array(
						'label' => esc_html__('Page', 'microthemer'),
						'value' => $newPostUrl,
					);

                }

				// add the category and merge with urls array
				foreach ($custom_links as $j => $custom_links_array){
					$urls[$key][] = array_merge($custom_links_array, array('category' => $category));
				}

			}

		}

		// add category urls to flat array
		foreach ($urls as $key => $array){
			if (!empty($urls[$key])){
				$formatted_urls = array_merge($formatted_urls, array_values($urls[$key]));
			}

		}

		//wp_die('<pre>'.print_r($urls, true).'</pre>');

		//return $urls;

		return $formatted_urls;

	}

	// Global system for creating dynamic menus (data, structure, config)
	// Note: passing array/objs into PHP/JS functions over lots of params should become standard practice
	function dyn_menu($d, $s, $c) {

		$base_key = !empty($s['base_key']) ? 'data-base-key="'.$s['base_key'].'"' : '';
		$html = '<div id="dyn-wrap-'.$s['slug'].'" class="dyn-wrap"
				data-slug="'.$s['slug'].'" '.$base_key.'>';

		// add controls if required
		if (!empty($c['controls'])){
			$input_class = !empty($s['add_button']) ? 'combobox' : '';
			$input_placeholder = !empty($s['input_placeholder']) ? 'placeholder="'.$s['input_placeholder'].'"' : '';
			$combo_arrow = '';
			if (!empty($s['combo_add_arrow'])) {
				$combo_arrow = '<span class="combo-arrow"></span>';
				$input_class.= ' has-arrows';
			}
			$html.= '
					<div class="tvr-new-item">
						<span class="tvr-input-wrap">
							<input type="text" class="new-item-input '.$input_class.'" '.$input_placeholder.' 
							name="new_item[name]" rel="'.$s['slug'].'">
							'.$combo_arrow.'
						</span>
						<span class="new-item-add tvr-button" title="'.$s['add_button'].'">'.$s['add_button'].'</span>
					</div>';
		}


		// loop through data (maybe try to make this a recursive function)
		$html.= '
				<ul id="'.$s['slug'].'-dyn-menu" class="tvr-dyn-menu">';

		foreach ($d as $k => $arr){

			$html.= $this->dyn_item($s, $k, $arr);
		}

		$html.= '
				</ul>
				</div>'; // dyn-wrap

		return $html;
	}

	function dyn_item($s, $k, $arr){

		$fields = $s['items']['fields'];

		// resolve display name, class etc
		$display_name = !empty($arr['display_name']) ? $arr['display_name'] : $arr['label'];
		$name_class = !empty($fields['label']['name_class']) ? ' '.$fields['label']['name_class'] : '';

		$icons = array(
			'mqs' => 'devices',
			'enq_js' => 'js',
			'edit' => 'edit',
			'delete' => 'bin'
		);

		$sortableIcon = $this->iconFont($icons[$s['slug']], array(
			'class' => 'sortable-icon',
			'title' => $s['items']['icon']['title']
		));

		$html = '';
		// li item
		$html.= '
				<li id="'.$s['slug'].'-'.$k.'" class="dyn-item '.$s['level'].'-tag '.$s['slug'].' '.$s['slug'].'-'.$s['level'].'">';

		// row with sortable icon, name
		$dis_class = !empty($this->preferences['enq_js'][$k]['disabled']) ? 'item-disabled' : '';
		$html.= '
				<div class="'.$s['level'].'-row item-row '.$dis_class.'">
					'.$sortableIcon.'
					<span class="name-text '.$s['level'].'-name'.$name_class.'">'.esc_html($display_name).'</span>';

		$html.= '
				<span class="manage-'.$s['level'].'-icons folders-menu-actions">';

		// do action icons
		foreach ($s['items']['actions'] as $action => $a_arr){

			// output icon control e.g. disabled
			if (!empty($a_arr['icon_control'])){
				$html.= $this->icon_control(
					false,
					$action,
					!empty($this->preferences['enq_js'][$k]['disabled']),
					$s['level'],
					$k,
					'',
					'all-devices', // just to skip mq stuff
					'',
					'',
					$s['name_stub']
				);
			} else {
				// regular icon
				$a_class = !empty($a_arr['class']) ? $a_arr['class'] : '';

				$html.= $this->iconFont($icons[$action], array(
					'class' => $a_class. ' '.$action.'-'.$s['level'],
					'title' => $a_arr['title']
				));

			}

		}

		// end action icons and row
		$html.= '
					</span>
				</div>';

		// edit fields (for enq_js just hidden input so no need to have edit icon)
		$html.= '
				<div class="edit-item-form float-form hidden">';

		// editing or hidden input fields
		foreach ($s['items']['fields'] as $input_name => $f_arr){
			$input_type = $f_arr['type'];
			$val = !empty($arr[$input_name]) ? $arr[$input_name] : '';
			$input_class = !empty($f_arr['input_class']) ? ' '.$f_arr['input_class'] : '';
			$input_rel = !empty($f_arr['input_rel']) ? ' rel="'.$f_arr['input_rel'].'"' : '';
			$input = '<input type="'.$input_type.'" class="'.$s['level'].'-'.$input_name.$input_class.'"
								'.$input_rel.' name="'.$s['name_stub'].'['.$k.']['.$input_name.']" value="'.esc_html($val).'">';

			if (!empty($f_arr['input_arrows'])){
				$input.= $f_arr['input_arrows'];
			}

			// just input if hidden
			if ($input_type == 'hidden'){
				$html.= $input;
			} else {
				// form fields
				$f_class = !empty($f_arr['field_class']) ? ' '.$f_arr['field_class'] : '';
				//$f_class = $input_type == 'checkbox' ? 'mq-checkbox-wrap' : '';
				$html.= '
						<p class="'.$f_class.'">
							<label title="'.$f_arr['label_title'].'">'.$f_arr['label'].':</label>';
				// regular text input, checkbox
				if ($input_type != 'textarea'){
					$html.= $input;
					if ($input_type == 'checkbox'){
						$html.=
							$this->iconFont('tick-box-unchecked', array(
								'class' => 'fake-checkbox'
							))
							.'<span class="ef-label">'.$f_arr['label2'].'</span>';
					}
				} else {
					// text area
					$html.= '<textarea class="'.$s['level'].'-'.$input_name.'"
								name="'.$s['name_stub'].'['.$k.']['.$input_name.']">'.esc_html($val).'</textarea>';
				}
				$html.= '
						</p>';
			}
		}

		// maybe add recursive functionality for sub items here if needed/possible

		$html.= '
				</li>';

		return $html;
	}

	// menu section html
	function menu_section_html($section_name, $array) {

		$section_name = esc_attr($section_name); //=esc

		// get folder display name
		$display_section_name = $this->get_folder_name_inc_legacy($section_name, $array);

		$selector_count_state = $this->selector_count_state($array);

		// generate html code for sections in this loop to save having to do a 2nd loop later
		//$this->initial_options_html[$this->total_sections] = $this->section_html($section_name, $array);

		$sec_class = '';

		// user disabled
		$disabled = false;
		if (!empty($array['this']['disabled'])){
			$disabled = true;
			$sec_class.= ' item-disabled';
		}

        // folder is conditional
        if (!empty($array['this']['logic']['expr'])
            && !$this->hasGlobalCondition($array['this']['logic']['expr'])
        ){
			$sec_class.= ' item-conditional';
		}

		// should feather be displayed?
		if ($selector_count_state > 0 ) {
			// need deep search of values in selectors
			if ($this->section_has_values($section_name, $array, true)){
				$sec_class.= ' hasValues';
			}
		}
		$folder_title = esc_attr__("Reorder folder", 'microthemer');

		ob_start();
		?>
		<li id="<?php echo 'strk-'.$section_name; ?>" class="section-tag strk strk-sec <?php echo $sec_class; ?>">

            <?php
            // hidden input fields for storing folder values
            $hiddenInputs = array(
	            '[label]' => array(
                        'class' => 'label',
                        'value' => $display_section_name
                ),
                '[logic][exclude]' => array(
	                'class' => 'logic-exclude section-logic-input',
	                'value' => isset($array['this']['logic']['exclude']) ? $array['this']['logic']['exclude'] : ''
                ),
	            /*'[logic][label]' => array(
		            'class' => 'logic-label section-logic-input',
		            'value' => isset($array['this']['logic']['label']) ? $array['this']['logic']['label'] : ''
	            ),*/
	            '[logic][expr]' => array(
		            'class' => 'logic-expr section-logic-input',
		            'value' => isset($array['this']['logic']['expr']) ? $array['this']['logic']['expr'] : ''
	            ),
	            '[logic][css_external]' => array(
		            'class' => 'logic-css_external section-logic-input',
		            'value' => isset($array['this']['logic']['css_external']) ? $array['this']['logic']['css_external'] : ''
	            ),
                '[logic][css_async]' => array(
	                'class' => 'logic-css_async section-logic-input',
	                'value' => isset($array['this']['logic']['css_async']) ? $array['this']['logic']['css_async'] : ''
                ),

            );

            foreach ($hiddenInputs as $nameExtension => $inputData){

                echo '<input type="hidden" class="section-data-'.$inputData['class'].'"
			       name="tvr_mcth['.$section_name.'][this]'.$nameExtension.'"
			       value="'.esc_attr($inputData['value']).'" />';
            }
            ?>

			<div class="sec-row item-row">

                        <span class="item-icon folder-item-icon">
                            <?php
                            echo $this->iconFont('folder-filled', array(
	                            'class' => 'folder-icon sortable-icon',
	                            'title' => $folder_title,
	                            'data-title' => $folder_title
                            ));
                            ?>
                        </span>

				<span class="section-name item-name">
						    <span class="name-text selector-count-state"
						          rel="<?php echo $selector_count_state; ?>"><?php echo $display_section_name; ?></span><?php
					if ($selector_count_state > 0) {
						echo '<span class="folder-count-wrap count-wrap"> (<span class="folder-state-count state-count">'.$selector_count_state.'</span>)</span>';
					}
					// update global $total_selectors count
					$this->total_selectors = $this->total_selectors + $selector_count_state;
					?>
						</span>

				<?php
				echo $this->item_manage_icons('section', $section_name, array(
					'disabled' => $disabled,
					'context' => 'folders-menu',
				));
				?>

			</div>

			<ul class="selector-sub"></ul>
		</li>
		<?php

		return ob_get_clean();
	}




    function hasGlobalCondition($expr){
	    return !$expr
		    ? false
		    : preg_match(
                    '/^(!?)\\s*(is_admin|\\\\Microthemer\\\\is_public(?:_or_admin)?)\\(\\s*\\)$/',
                    $expr
            );
    }

	// MT supports multiple types of item added to folders now (e.g. selectors, code snippets, etc)
	function add_folder_item_tabs(){

		$html = '';

		foreach ($this->folder_item_types as $key => $array){
			$active = $key === 'selector' ? ' active' : '';
			$html.= '
				    <span class="mt-tab fit-tab fit-tab-'.$key.$active.'" rel="'.$key.'">
                        '.$array['label'].'
                    </span>';
		}

		return '<div class="query-tabs">'.$html.'</div>';
	}

	// folder and folder item icons
	function item_manage_icons($level, $slug, $config){

		$context = $config['context'];
		$sub_context = !empty($config['sub_context']) ? $config['sub_context'] : '';

		$labels = array(
			'section' => array(
				'disable' => esc_html__("Disable folder", 'microthemer'),
				'clear' => esc_html__("Clear folder styles", 'microthemer'),
				'delete' => esc_html__("Delete folder", 'microthemer'),
				'duplicate' => esc_html__("Duplicate folder", 'microthemer'),
				'more' => esc_html__("Folder options", 'microthemer'),

			),
			'selector' => array(
				'disable' => esc_html__("Disable selector", 'microthemer'),
				'clear' => esc_html__("Clear selector styles", 'microthemer'),
				'delete' => esc_html__("Delete selector", 'microthemer'),
				'duplicate' => esc_html__("Duplicate selector", 'microthemer'),
				//'variation' => esc_html__("Hover state and more", 'microthemer'),

				'retarget' => esc_html__("Re-target current selector", 'microthemer'),
				'more' => esc_html__("Edit selector", 'microthemer'),
				//'extra' => esc_html__("More options", 'microthemer'),
			)
		);

		$icons = array(
			'disable' => $this->icon_control(false, 'disabled', !empty($config['disabled']), $level, $slug),
			'clear' => $this->clear_icon($level),
			'delete' => $this->iconFont('bin', array(
				'class' => 'delete-'.$level.' mt-icon-divider',
				'title' => $labels[$level]['delete']
			)),
			'duplicate' => $this->iconFont('copy', array(
				'class' => 'copy-'.$level,
				'title' => $labels[$level]['duplicate'],
			)),
			'more' => $this->iconFont('dots-vertical', array(
				'class' => 'toggle-folder-more-options',
				'title' => $labels[$level]['more'],
				'data-level' => $level,
				'data-forpopup' => 'contextMenu'
			)),

		);

		if ($level === 'selector'){

			$icons = array_merge($icons, array(
				// selector only

				'spotlight' => $this->ui_toggle(
					'mt_highlight',
					'conditional',
					'conditional',
					0, // on state is styled by HTML class mt_highlight
					'current-item-spotlight ' . $this->iconFont('spotlight', array(
						'onlyClass' => 1
					)),
					false,
					// instruct tooltip to get content dynamically
					array('dataAtts' =>
						      array(
							      'dyn-tt-root' => 'toggle-highlighting',
							      'highlight-context' => 'current-selector'
						      )
					)
				),
				'retarget' => $this->iconFont('retarget', array(
					'class' => 'retarget-'.$level,
					'title' => $labels[$level]['retarget'],
				))
			));
		}

		$html = '';

		if (isset($config['html_before'])){
			$html.= $config['html_before'];
		}

		foreach ($icons as $key => $icon){

			if (
				($key === 'more' && ($context !== 'folders-menu' && $context !== 'current-item')) ||

				// no duplicate in current item as rarely needed
				//($key === 'duplicate' && $context === 'current-item') ||

				// current item only

				// current main
				($key === 'spotlight' && ($context !== 'current-item' || $sub_context !== 'main')) ||
				($key === 'retarget' && ($context !== 'current-item' || $sub_context !== 'main')) ||
				($key === 'extra' && ($context !== 'current-item' || $sub_context !== 'main')) ||

				// current more
				($key === 'disable' && ($context === 'current-item' && $sub_context !== 'extra')) ||
				($key === 'clear' && ($context === 'current-item' && $sub_context !== 'extra')) ||
				($key === 'delete' && ($context === 'current-item' && $sub_context !== 'extra')) ||
				($key === 'duplicate' && ($context === 'current-item' && $sub_context !== 'extra'))
				//($key === 'variation' && ($context !== 'current-item' || $sub_context !== 'extra'))
			){
				continue;
			}

			$html.= $icon;
		}

		if (isset($config['html_after'])){
			$html.= $config['html_after'];
		}

		return  '
			    <div class="manage-'.$level.'-icons mt-icon-line '.$context.'-actions" 
			        data-context="'.$context.'" data-level="'.$level.'">
			        '.$html.'
			    </div>';

	}

	function add_edit_section_form($context){

		if ($context === 'add'){
			$title = esc_html__("Add folder", 'microthemer');
			$button_text = esc_html__("Add", 'microthemer');
			$icons = '';
		}

		else {
			$title = esc_html__("Edit folder", 'microthemer');
			$button_text = esc_html__("Update", 'microthemer');
			$icons = $this->item_manage_icons('section', 'selector_section', array(
				'context' => 'folder-popup',
			));
		}

		// popup sections
		$sections = array(

			// edit folder name form
			$this->context_menu_form($context.'-section', array(
				'wrap' => 1,
				'wrapClass' => 'mt-folder-form '.$context.'-section-form',
				'fields' => array(
					'label' => array(
						'label' => esc_html__("Folder name", 'microthemer'),
						'type' => 'input',
					),
					'filler' => array(
						'custom' => '<span></span>' // filler
					),
					'icon-line' => array(
						'custom' => $icons,
					),

				),
				'button' => array(
					'text' => $button_text,
				)
			))
		);

		// add extra fields for adding a selector if editing a folder
		if ($context === 'edit'){

			$sections = array_merge($sections, array(

                //  conditional logic
				$this->context_menu_heading(
					esc_html__("Load folder on specific pages", 'microthemer')
				),
				$this->conditional_folder_options(),

                // add selector
				$this->context_menu_heading(
					esc_html__("Add selector to folder", 'microthemer')
				),
				$this->add_folder_item_forms()
			));
		}

		return $this->context_menu_content(
			array(
				'base_key' => $context.'-folder-options',
				'title' => $title,
				'sections' => $sections
			)
		);

	}

    function switchAutoFolder(){

        return '
        <div class="move-current-folder-item">

            <input type="checkbox" name="auto_folders[move_current]" value="1" />

            '.$this->iconFont('tick-box-unchecked', array(
                'class' => 'fake-checkbox toggle-move-current-item',
            )).'

            <span>'.esc_html__('Move current selector to folder', 'microthemer').'</span>

        </div>

        <div class="folder-options-list scrollable-area"></div>';

    }



	function add_edit_selector_form($context){

		if ($context === 'add'){
			$button_text = esc_html__("Add", 'microthemer');
			$icons = '';
		}

		else {
			$button_text = esc_html__("Update", 'microthemer');
			$icons = $this->item_manage_icons('selector', 'selector_css', array(
				'context' => 'folder-popup',
			));
		}

		$form = $this->context_menu_form($context.'-selector', array(
			'wrap' => 1,
			'wrapClass' => $context.'-selector-form modify-selector-form mt-folder-form 
				    '.$context.'-folder-item-form show',
			'fields' => array(
				'label' => array(
					'label' => esc_html__("Selector name", 'microthemer'),
					'type' => 'input',
					'inputClass' => 'selector-name-input linkable-fields'
				),

				'linked-fields-toggle' => array(
					'custom' => $this->ui_toggle(
						'selname_code_synced',
						'conditional', // only wizard toggle has title/on class (easier than maintaining dynamically)
						'conditional',
						false,
						'code-chained-icon selname-code-sync popup-options-sync ' . $this->iconFont('chain', array(
							'onlyClass' => 1
						)),
						false,
						// instruct tooltip to get content dynamically
						array('dataAtts' => array(
							'dyn-tt-root' => 'selname_code_synced'
						))
					)       ),
				'code' => array(
					'label' => esc_html__("Selector code", 'microthemer'),
					'type' => 'input',
					'inputClass' => 'selector-css-textarea linkable-fields'
				),
				'filler' => array(
					'custom' => '<span></span>' // filler
				),
				'icons' => array(
					'custom' => $icons
				),
			),
			'button' => array(
				'text' => $button_text,
				'class' => $context.'-selector',
			)
		));

		// selector variation if edit form
		if ($context === 'edit'){
			$form.=
				$this->context_menu_heading(
					esc_html__("Create selector variation", 'microthemer')
				)
				.
				'<div id="mt-selector-state-options"></div>';
		}

		return $form;
	}

	function add_folder_item_forms(){

		return $this->add_edit_selector_form('add');

	}

	// menu single selector html
	function menu_selector_html($section_name, $css_selector, $array, $sel_loop_count) {

		ob_start();

		$sel_class = '';

		$style_count_state = $this->selector_has_values($section_name, $css_selector, $array, true);

		// trial disabled (all sels will be editable even in free trial in future)
		/*if (!$this->preferences['buyer_validated'] and $this->sel_count > 15 ) {
			$sel_class.= 'mt-trial-disabled'; // visually signals disabled and, prevents editing
		}*/

		// user disabled
		$disabled = false;
		if (!empty($array['disabled'])){
			$disabled = true;
			$sel_class.= ' item-disabled';
		}

		// should feather be displayed?
		if ($style_count_state > 0) {
			$sel_class.= ' hasValues';
		}

		// can't recall why I went down this route of storing label and code in piped single value.
		if (is_array($array) and !empty($array['label'])){
			$labelCss = explode('|', $array['label']);
			// convert my custom quote escaping in recognised html encoded single/double quotes
			$selector_title = esc_attr(str_replace('cus-', '&', $labelCss[1]));
		} else {
			$labelCss = array('', '');
			$array['label'] = '';
			$selector_title = '';
		}

        //
        $xpath = !empty($array['xpath']) ? $array['xpath'] : '';

		?>
		<li id="<?php echo 'strk-'.$section_name.'-'.$css_selector; ?>" class="selector-tag strk strk-sel <?php echo $sel_class; ?>">

			<input type='hidden' class='selector-label' name='tvr_mcth[<?php echo $section_name; ?>][<?php echo $css_selector; ?>][label]' value='<?php echo $array['label']; ?>' />
            <input type='hidden' class='selector-xpath' name='tvr_mcth[<?php echo $section_name; ?>][<?php echo $css_selector; ?>][xpath]' value='<?php echo esc_attr($xpath); ?>' />
            <input type='hidden' class='selector-multi-key-html' name='tvr_mcth[<?php echo $section_name; ?>][<?php echo $css_selector; ?>][multi_key][html]' value='' />


			<div class="sel-row item-row">

                        <span class="item-icon">

                            <?php
                            echo $this->iconFont('spotlight', array(
	                            'class' => 'highlight-preview',
	                            'title' => esc_attr__("Highlight selector", 'microthemer')
                            ));

                            echo $this->iconFont('target', array(
	                            'class' => 'selector-sortable-icon sortable-icon',
	                            'title' => esc_attr__("Reorder selector", 'microthemer')
                            ));
                            ?>
                        </span>

				<span class="selector-name item-name change-selector" title="<?php echo $selector_title; ?>">
						    <span class="name-text style-count-state change-selector"
						          rel="<?php echo $style_count_state; ?>"><?php echo esc_html($labelCss[0]); ?>
                            </span>
						</span>

				<span class="disabled-responsive-tabs"></span>

				<?php

				echo $this->item_manage_icons('selector', $css_selector, array(
					'disabled' => $disabled,
					'context' => 'folders-menu',
				));
				?>

			</div>
		</li>
		<?php

		return ob_get_clean();

	}

    function add_logic_options(){
        return '
        <div id="addLogic" class="popdown-content add-logic-popdown" title="">

            <div class="include-or-exclude mt-binary-buttons" data-run="toggle_include_exclude">
                <span class="binary-button-option" rel="0">Include</span>
                <input type="checkbox" name="logic[exclude]" value="1" class="mt_exclude" />
                <span class="binary-button-option" rel="1">Exclude</span>
            </div>
            
            <div class="tvr-input-wrap mt-search-logic-wrap">
                <input id="logic-search" placeholder="Search conditions" type="text" rel="conditional_logic" class="mt-search-logic combobox has-arrows" name="logic[search]" autocomplete="off">
                <span class="mt-clear-field"></span>
                <span class="combo-arrow"></span>
            </div>
            
            <input id="logic-to-add" type="hidden" name="logic_to_add" value="" />
            
            <div class="tvr-button add-logic-button add-first-logic" data-type="first">Add</div>
            <div class="tvr-button add-logic-button append-logic-button add-with-or" data-type="or" title="Add with \'or\'">or</div>
            <div class="tvr-button add-logic-button append-logic-button add-with-and" data-type="and" title="Add with \'and\'">and</div>
               
        </div>
        ';
    }

    function conditional_folder_options(){

        $logic = new Logic();
        $allowedPHPSyntax = $logic->getAllowedPHPSyntax();
        $functionsList = '';
	    $superglobalsList = '';
        $docPost = 'https://themeover.com/load-wordpress-css-on-specific-pages/';
        $MTdivider = false;
        $PHPdivider = false;

        foreach ($allowedPHPSyntax['functions'] as $functionName){

            $isMTFunction = strpos($functionName, '\Microthemer') === 0;
	        $isPHPFunction = $functionName === 'isset';
	        $functionDisplayName = $functionName;
            $url = 'https://developer.wordpress.org/reference/functions/'.$functionName.'/';
            $extra_class = '';

            // Custom Microthemer function
	        if ($isMTFunction){
		        $functionID = str_replace('\Microthemer\\', '', $functionName);
		        $functionDisplayName = $functionName; // '[MT] ' . $functionID;
		        $url = $docPost.'#'.$functionID;
		        $extra_class.= ' is-mt-function';
                if (!$MTdivider){
	                $functionsList.= '<div class="list-divider"></div>';
	                $MTdivider = true;
                }
	        }

	        // native PHP function
	        if ($isPHPFunction){
		        //$functionDisplayName = '[PHP] ' . $functionName;
		        $url = 'https://www.php.net/manual/en/function.'.$functionName.'.php';
		        $extra_class.= ' is-native-php';
		        if (!$PHPdivider){
			        $functionsList.= '<div class="list-divider"></div>';
			        $PHPdivider = true;
		        }
	        }

	        $functionsList.= '<a href="'.$url.'" target="_blank" class="supported-item'.$extra_class.'">'.$functionDisplayName.'()</a>';

	        /*$functionsList.= '<a href="'.$url.'" target="_blank" class="supported-item">
	            <span class="display-function">'.$functionDisplayName.'()</span>
	            <span class="mt-copy-function"></span>
	        </a>';*/
        }

	    foreach ($allowedPHPSyntax['superglobals'] as $globalName){
		    //$superglobalsList.= '<li class="supported-item">'.$globalName.'</li>';
            $slug = strtolower(str_replace('$_', '', $globalName));
            $url = 'https://www.php.net/manual/en/reserved.variables.'.$slug.'.php';
		    $functionsList.= '
            <a href="'.$url.'" target="_blank" class="supported-item is-native-php">
                '.$globalName.'
            </a>';
	    }

        // show which characters are supported
	    $functionsList.= '
        <li class="supported-item is-allowed-characters">
            ( ) , > < = ! \' " || && or and true false
        </li>';

        /*
         *   <div class="heading">'.count($allowedPHPSyntax['superglobals']).' superglobal </div>
                        '.$superglobalsList.'

         */

        $html = '
        <div class="mt-cm-form mt-folder-form conditional-logic-form">
           
            <div class="conditional-label">
            
                <span class="toggle-popdown-content toggle-php-syntax" data-forpopup="phpSyntax">
                    Condition 
                     <ul id="phpSyntax" class="popdown-content supported-functions-list">
                       <div class="heading">
                           Supported WordPress and '.$this->appName.' PHP code 
                           <a href="'.$docPost.'" target="_blank">Watch video</a>
                       </div>
                        '.$functionsList.'
                        
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" id="condition-clipboard-icon" class="condition-clipboard-icon" title="Copy text"><path d="M280 64h40c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128C0 92.7 28.7 64 64 64h40 9.6C121 27.5 153.3 0 192 0s71 27.5 78.4 64H280zM64 112c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16H320c8.8 0 16-7.2 16-16V128c0-8.8-7.2-16-16-16H304v24c0 13.3-10.7 24-24 24H192 104c-13.3 0-24-10.7-24-24V112H64zm128-8a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg>

                     </ul>
                </span>
 
                '.$this->iconFont('add', array(
                    'class' => 'toggle-add-logic toggle-popdown-content',
                    'title' => esc_attr__('Add a condition', 'microthemer'),
                    'innerHTML' => $this->add_logic_options(),
                    'data-forpopup' => "addLogic"
                )).'
            </div>
            
            <textarea id="mt-logic-expressions" name="logic[expr]" spellcheck="false"></textarea>
            
            <!--<label>Label (optional)</label>
            
            <input id="logic-label" type="text" name="logic[label]" autocomplete="off" />-->
            
            <label class="conditional-folder-only">Load CSS</label>
            
            <div class="mt-asset-loading-options conditional-folder-only">
                <div class="inline-or-stylesheet mt-binary-buttons">
                    <span class="load-inline binary-button-option" rel="0">Inline tag</span>
                    <input type="checkbox" name="logic[css_external]" value="1" />
                    <span class="load-stylesheet binary-button-option" rel="1">
                        <span>External file</span>
                        
                        <span class="async-stylesheet">
                            <input type="checkbox" name="logic[css_async]" value="1" />    
                            '.$this->iconFont('tick-box-unchecked', array(
                                'class' => 'fake-checkbox mt-async-toggle',
                            )).'
                            
                            <span class="mt-async-label">Async</span>
                        </span>
                    </span>
                </div>
            </div>
            
            <div id="logic-testing-wrap">
                
                <div id="logic-testing">
                
                    <span class="mtif mtif-times-circle-regular close-logic-testing"></span>
                    
                    <div class="mt-table-heading">Test page</div>
                    <div class="mt-table-heading">Load</div>
                    <div class="mt-table-heading">Analysis</div>
                    
                    <div class="mt-table-result" id="logic-test-page">
                        <div class="combobox-wrap tvr-input-wrap">
        
                            <input type="text" name="set_test_url" placeholder="Set test page" data-appto="#style-components" id="testPreviewPath" class="combobox has-arrows" rel="custom_paths" value="" autocomplete="off">
                            <span class="mt-clear-field"></span>
                            <span class="combo-arrow"></span>
                            <span class="tvr-button view-test-url">View</span>

                        </div>
                    </div>
                    <div class="mt-table-result mt-test-result" id="logic-load-result"></div>
                    <div class="mt-table-result" id="logic-statement-analysis">
   
                    </div>
                </div>
            </div>
            
            <div class="conditions-menu">
                
                <span class="tvr-button tvr-gray clear-folder-logic" title="'.esc_attr__('Clear folder logic', 'microthemer').'">Clear logic</span>
                
                <span class="tvr-button test-folder-logic" title="'.esc_attr__('Save and test folder logic', 'microthemer').'">Save & Test</span>

            </div>
   
        </div>';

        return $html;
    }

	function context_menu_form($base_key, $form){

		$html = '';
		$fields = '';

		// output the fields
		foreach ($form['fields'] as $key => $item){

			// allow for inserting custom HTML
			if (isset($item['custom'])){
				$fields.= $item['custom'];
				continue;
			}

			$class = $base_key.'-'.$key;
			$name = str_replace('-', '_', $class);
			$input_class = !empty($item['inputClass']) ? ' '.$item['inputClass'] : '';
			$input_rel = !empty($item['rel']) ? 'rel="'.$item['rel'].'"' : '';
			$label_class = !empty($item['labelClass']) ? ' class="'.$item['labelClass'].'" ' : '';

			$fields.= '<label'.$label_class.'>'.$item['label'].'</label>';

			if ($item['type'] === 'input'){

                $inputField = '<input type="text" '.$input_rel.' class="'.$class.'-input'.$input_class.'" name="'.$name.'" />';

                if (!empty($item['combo'])){
	                $fields.= '<div class="tvr-input-wrap">' .$inputField
                                  . '<span class="mt-clear-field"></span>'
                                  . '<span class="combo-arrow"></span>'
                              .'</div>';
                } else {
	                $fields.= $inputField;
                }
			}

            elseif ($item['type'] === 'toggle'){

            }

		}

		// add the button
        if (!empty($form['button'])){
	        $button_class = !empty($form['button']['class']) ? ' '.$form['button']['class'] : '';
	        $fields.= '<span class="tvr-button '.$base_key.'-button '.$button_class.'">'.$form['button']['text'].'</span>';
        }

		// wrap if required
		if (!empty($form['wrap'])){
			$wrap_class = !empty($form['wrapClass']) ? ' '.$form['wrapClass'] : '';
			$html.= '<div class="mt-cm-form '.$base_key.'-form'.$wrap_class.'">'.$fields.'</div>';
		} else {
			$html.= $fields;
		}

		return $html;
	}

	function context_menu_actions($base_key, $config){

		$icon_html = '';
		$html = '';

		// loop actions
		foreach ($config['actions'] as $key => $item){

			// allow for inserting custom HTML
			if (!empty($item['custom'])){
				$icon_html.= $item['custom'];
				continue;
			}

			$icon_html.= $this->iconFont($key, $item);
		}

		// wrap if required
		if (!empty($config['wrap'])){
			$wrap_class = !empty($config['wrapClass']) ? ' '.$config['wrapClass'] : '';
			$html.= '<div class="mt-cm-actions '.$base_key.'-actions'.$wrap_class.'">'.$icon_html.'</div>';
		} else {
			$html.= $icon_html;
		}

		return $html;
	}

	// Output context menu content in a structured way
	function context_menu_content($config){

		$base_key = $config['base_key'];
		$title = !empty($config['title']) ? $config['title'] : esc_html__('Options', 'microthemer');
		$class = !empty($config['class']) ? ' '.$config['class'] : '';
		$id = !empty($config['id']) ? $config['id'] : 'cm-'.$base_key;
		$html = '';

		// loop through the sections
		if (!empty($config['sections']) && is_array($config['sections'])){
			foreach ($config['sections'] as $i => $section){
				$html.= $section;
			}
		}

		return '
			    <div id="'.$id.'" class="cm-'.$base_key.$class.'" data-title="'.$title.'">
			        '. $html .'
                </div>';

	}

	function context_menu_heading($heading = '', $config = array()){

		$title_class = !empty($config['title_class'])
			? ' '.$config['title_class']
			: '';

		return '
			    <div class="mt-panel-header">
                    <div class="mt-panel-title'.$title_class.'">'.$heading.'</div>
                    '.( !empty($config['close']) ?  $this->iconFont('times-circle-regular', array('class' => 'close-context-menu')) : '').'                                   
                </div>';
	}

	function layout_element_height($side, $abort = false){

		if ($abort){
			return '';
		}

		return ' style="height: ' . $this->preferences['layout'][$side]['size'] . 'px"';
	}

	function panel_resizers($config){
		$d = $config['dimension'];
		$grid_axis =  $d === 'width' ? 'column' : 'height';
		$context = $config['context'];
		$html = '';
		for ($x = 1; $x <= $config['total']; $x++) {
			$side = $x <= $config['side_division'] ? $config['side_1'] : $config['side_2'];
			$html.= '<div class="mt-panel-resizer mt-'.$d.'-resizer-'.$x.' mt-'.$d.'-resizer '
			        .$side.'-panel-resizer mt-'.$context.'-resizer-'.$x.'" 
                 data-template-'.$grid_axis.'="'.$x.'" data-side="'.$side.'" data-context="'.$context.'"></div>';
		}
		return $html;
	}

	// section html
	function section_html($section_name, $array) {
		$html = '';
		$html.= '
				<li id="opts-'.$section_name.'" class="section-wrap section-tag">
					'.$this->all_selectors_html($section_name, $array).'
				</li>';
		return $html;
	}

	function all_selectors_html($section_name, $array, $force_load = 0) {
		$html = '';
		$html.= '<ul class="selector-sub">';

		$html.= '</ul>';
		return $html;
	}

	// selector html
	function single_selector_html($section_name, $css_selector, $array, $force_load = 0) {
		++$this->sel_option_count;
		$html = '';
		$css_selector = esc_attr($css_selector); //=esc

		$html.= '<li id="opts-'.$section_name.'-'.$css_selector.'"
				class="selector-tag selector-wrap">';

		// only load style options if we need to force load
		if ($force_load == 1) {
			$html.= $this->all_option_groups_html($section_name, $css_selector, $array);
		}
		$html.= '</li>';
		return $html;
	}

	// determine the number of selectors in the array
	function selector_count_state($array) {

		// with empty folder, might not be an array
		if (!is_array($array)){
			return 0;
		}

		$selector_count_state = count($array);
		$selector_count_recursive = count($array, COUNT_RECURSIVE);

		// if the 2 values are the same, the $selector_count_state variable refers to an empty value
		if ($selector_count_state == $selector_count_recursive) {
			$selector_count_state = 0;
		}
		// [this] will be counted as extra selector. Fix.
		if ($selector_count_state > 0 and array_key_exists('this', $array)){
			--$selector_count_state;
		}
		return $selector_count_state;
	}

	function css_group_icons(){

		// display pg icons
		$html = '
				<ul class="styling-option-icons scrollable-area">
				    <li class="mt-panel-column-heading" data-short-text="'.esc_attr__('CSS', 'microthemer').'" data-long-text="'.esc_attr__('CSS Properties', 'microthemer').'"></li>';

		// display the pg icons
		$i = -1;
		$done = array();
		foreach ($this->propertyoptions as $property_group_name => $junk) {

			$i++;
			$class = '';

			// check if we are starting a new property group category
			$first_item = $this->get_first_item( $this->propertyoptions[$property_group_name] );
			$new_pg_cat = (!empty($first_item['new_pg_cat']) and empty($done[$property_group_name]))
				? $first_item['new_pg_cat']
				: false;
			$close_pg_cat_li = $i === 0 ? '' : '</div></li>';

			// if new cat, close previous and start new
			if ($new_pg_cat){
				$html.= $close_pg_cat_li . '
                        <li class="new-pg-cat new-pg-cat-'.$property_group_name.'">
                            <div class="pg-cat-sub">
                                <div class="new-pg-cat-label">'.$new_pg_cat.'</div>';
				$done[$property_group_name] = true;
			}

			// icon
			$icon_name = str_replace('_', '-', $property_group_name);
			$icon_type = 'f';
			$icon_dir = 'svg-min/';

			if ($property_group_name === 'gradient'){
				$icon_type = 'svg';
			}

			$label = $this->property_option_groups[$property_group_name];
            $tooltip = $label;
            /*if ($property_group_name === 'html'){
	            $tooltip = $label . " (beta)";
            }*/

			$html.= $this->icon($icon_name, array(
				'type' => $icon_type,
				'dir' => $icon_dir,
				'class' => 'pg-icon pg-icon-'.$property_group_name,
				'rel' => $property_group_name,
				'title' => $tooltip,
				'adjacentText' => array(
					'text' => $label,
					'class' => 'mti-text pg-icon-text-label'
				),
				'wrap' => array(
					'class' => 'mti-wrap pg-icon-wrap pg-icon-wrap-' . $property_group_name,
					'rel' => $property_group_name,
					//'title' => $label,
				),
				// 'wrapClass' => $property_group_name. '-wrap',

			));

            // Shortcut for enabling Microthemer addon
            if ( $property_group_name === 'html' && !$this->supportGUICSS() ){
	            $html.= $this->icon($icon_name, array(
		            'type' => $icon_type,
		            'dir' => $icon_dir,
		            'class' => 'trigger-microthemer-addon pg-icon pg-icon-MT-logo',
		            'rel' => 'microthemer-addon',
		            'title' => 'Enable Microthemer addon',
		            'adjacentText' => array(
			            'text' => 'Microthemer addon',
			            'class' => 'mti-text pg-icon-text-label'
		            ),
		            'wrap' => array(
			            'class' => 'mti-wrap pg-icon-wrap pg-icon-wrap-microthemer-addon',
			            'rel' => 'microthemer-addon',
		            ),
		            // 'wrapClass' => $property_group_name. '-wrap',

	            ));
            }

		}

		// close new-pg-cat item and list
		$html.='
                    </div></li>
				</ul>';

		return $html;
	}


	// display property group icons and options
	function all_option_groups_html($section_name, $css_selector, $array){

		// get the last viewed property group
		$pg_focus = ( !empty($array['pg_focus']) ) ? $array['pg_focus'] : '';

		// display actual fields
		$html ='
				<ul class="styling-options">';

		// do all-device and MQ fields
		foreach ($this->propertyoptions as $property_group_name => $junk) {
			$html.= $this->single_option_group_html(
				$section_name,
				$css_selector,
				$array,
				$property_group_name,
				$pg_focus);
		}

		$html.=

			// whole property group
			$this->mt_hor_scroll_buttons('style', 'li') .

			// single template/auto column
			$this->mt_hor_scroll_buttons('gridcolumns', 'li') .

			// single template/auto row
			$this->mt_hor_scroll_buttons('gridrows', 'li') .

			'</ul>';

		return $html;
	}

	function mt_hor_scroll_buttons($type, $el = 'li'){
		return '
                    <'.$el.' class="scroll-lr-buttons scroll-lr-buttons-'.$type.'">
                        <span class="mt-scroll-row mt-scroll-left mt-scroll-'.$type.'" data-type="'.$type.'"></span>
                        <span class="mt-scroll-row mt-scroll-right mt-scroll-'.$type.'" data-type="'.$type.'"></span>
                    </'.$el.'>';
	}

	// if a pg group has loaded but no values were added we don't want to load it into the dom
	function pg_has_values($array){
		if (empty($array) or !is_array($array)){
			return false;
		}
		$no_values = true;
		foreach ($array as $key => $value){
			// must allow zero values!
			if ( !empty($value) or $value === 0 or $value === '0'){
				$no_values = false;
				break;
			}
		}
		return $no_values ? false : true;
	}

	// are legacy values present for pg group?
	function has_legacy_values($styles, $property_group_name){
		$legacy_values = false;
		if (!empty($this->legacy_groups[$property_group_name]) and is_array($this->legacy_groups[$property_group_name])){
			foreach ($this->legacy_groups[$property_group_name] as $leg_group => $array){
				// check if the pg has values and they are specifically ones have have moved to this pg
				if ( !empty($styles[$leg_group]) and
				     $this->pg_has_values($styles[$leg_group]) and
				     $this->props_moved_to_this_pg($styles[$leg_group], $array)){
					$legacy_values = $styles[$leg_group];
					break;
				}
			}
		}
		return $legacy_values;
	}

	function pg_has_values_inc_legacy($array, $property_group_name){
		$styles_found = false;
		if (!empty($array['styles'][$property_group_name]) and $this->pg_has_values($array['styles'][$property_group_name])) {
			$styles_found['cur_leg'] = 'current';
		} elseif (!empty($array['styles']) and $this->has_legacy_values($array['styles'], $property_group_name)){
			$styles_found['cur_leg'] = 'legacy';
		}
		return $styles_found;
	}

	// look for any values in property group, including legacy values - and optionally, media query values
	function pg_has_values_inc_legacy_inc_mq($section_name, $css_selector, $array, $property_group_name){

		// first just look for values in all devices (most likely)
		if ($styles_found = $this->pg_has_values_inc_legacy($array, $property_group_name)) {
			$styles_found['dev_mq'] = 'all-devices';
			return $styles_found;
		} else {
			// look for media query tabs with values too
			// - use preferences mqs for loop because any mq keys in options not in there will not be output
			// also active_queries doesn't currently get updated after deleting an MQ tab via popup
			if (is_array($this->preferences['m_queries'])) {
				foreach ($this->preferences['m_queries'] as $mq_key => $junk) {
					// now check $options
					if (!empty($this->options['non_section']['m_query'][$mq_key][$section_name][$css_selector])){
						$array = $this->options['non_section']['m_query'][$mq_key][$section_name][$css_selector];
						if ($styles_found = $this->pg_has_values_inc_legacy($array, $property_group_name)) {
							$styles_found['dev_mq'] = 'mq';
							break;
						}
					}
				}
			}

			return $styles_found;
		}
	}

	// does the selector contain any styles?
	function selector_has_values($section_name, $css_selector, $array, $deep){

		return !empty($array['compiled_css']) || !empty($array['styles']['html']);
	}

	// does the folder contain any styles?
	function section_has_values($section_name, $array, $deep){
		$style_count_state = 0;
		if ( is_array($array) ) {
			foreach ($array as $css_selector => $sel_array) {
				if ($this->selector_has_values($section_name, $css_selector, $sel_array, $deep)){
					++$style_count_state;
				}
			}
		}
		return $style_count_state;
	}

	// does the $ui_data array have values?
	function ui_data_has_values($ui_data, $deep){
		$style_count_state = 0;
		if (!empty($ui_data) and is_array($ui_data)){
			foreach ($ui_data as $section_name => $array){
				if ($this->section_has_values($section_name, $array, $deep)){
					++$style_count_state;
				}
			}
		}
		return $style_count_state;
	}

	// ensure that specific legacy props have moved to this pg
	function props_moved_to_this_pg($leg_group_styles, $array){
		// loop through legacy props to see if style values exist
		if (is_array($array)){
			foreach ($array as $legacy_prop => $legacy_prop_legend_key){
				if (!empty($leg_group_styles[$legacy_prop])){
					return true;
				}
			}
		}
		return false;
	}

	// determine if options property is legacy or not
	function is_legacy_prop($property_group_name, $property){
		$legacy = false;
		foreach ($this->legacy_groups as $new_group => $array){
			foreach ($array as $leg_group => $arr2){
				foreach ($arr2 as $leg_prop => $legacy_prop_legend_key) {
					if ($property_group_name == $leg_group and $property == $leg_prop) {
						$legacy = $new_group; // return new group for legacy property
						break;
					}
				}
			}
		}
		return $legacy;
	}

	// function to get legacy value (inc !important) if it exists
	function populate_from_legacy_if_exists($styles, $sel_imp_array, $prop){
		$target_leg_prop = false;
		$legacy_adjusted['value'] = false;
		$legacy_adjusted['imp'] = '';
		foreach ($this->legacy_groups as $pg => $leg_group_array){
			foreach ($leg_group_array as $leg_group => $leg_prop_array){
				// look for prop in value: 1 = same as key
				foreach ($leg_prop_array as $leg_prop => $legend_key){
					// prop may have legacy values
					if ( ($prop == $leg_prop and $legend_key) == 1 or $prop == $legend_key){
						$target_leg_prop = $leg_prop;

					} elseif (is_array($legend_key)){
						// loop through array
						if (in_array($prop, $legend_key)){
							$target_leg_prop = $leg_prop;
						}
					}
					// if the property had a previous location, check for a value
					if ($target_leg_prop){
						if (!empty($styles[$leg_group][$target_leg_prop])){
							$legacy_adjusted['value'] = $styles[$leg_group][$target_leg_prop];
							if (!empty($sel_imp_array[$leg_group][$target_leg_prop])){
								$legacy_adjusted['imp'] = $sel_imp_array[$leg_group][$target_leg_prop];
							}
							break 3; // break out of all loops
						}
					}
				}
			}
		}
		return $legacy_adjusted;
	}

	// new system that doesn't restrict section name format
	function get_folder_name_inc_legacy($section_name, $array){
		// legacy 1
		$display_section_name = ucwords(str_replace('_', ' ', $section_name));
		// legacy 2 (abandoned because I don't like having this stored in non_section)
		if (!empty($this->options['non_section']['display_name'][$section_name])) {
			$display_section_name = $this->options['non_section']['display_name'][$section_name];
		}
		// current
		if (!empty($this->options[$section_name]['this']['label'])) {
			$display_section_name = $this->options[$section_name]['this']['label'];
		}
		return $display_section_name;
	}


	// output all the options for a given property group
	function single_option_group_html(
		$section_name,
		$css_selector,
		$array,
		$property_group_name,
		$pg_focus){

		// check if the property group should be "active" (in focus)
		$pg_show_class = ( $property_group_name == $pg_focus ) ? 'show' : '';

		// main pg wrapper
		$html ='
				<li id="opts-'.$section_name.'-'.$css_selector.'-'.$property_group_name.'"
						 class="group-tag group-tag-'.$property_group_name.' '.$pg_show_class.'">';

		// output all devices and MQ fields
		$html.= $this->single_device_fields(
			$section_name,
			$css_selector,
			$array,
			$property_group_name,
			$pg_show_class);

		$html.= '
				</li>';

		return $html;
	}

	// function for outputting all devices and MQs without repeating code
	function single_device_fields(
		$section_name,
		$css_selector,
		$array,
		$property_group_name,
		$pg_show_class){

		$html = '';

		// output all fields
		foreach ($this->combined_devices() as $key => $m_query){

			$property_group_array = false;
			$con = 'reg';

			// get array val if MQ
			if ($key != 'all-devices'){
				$con = 'mq';
				$array = false;
				if (!empty($this->options['non_section']['m_query'][$key][$section_name][$css_selector])) {
					$array = $this->options['non_section']['m_query'][$key][$section_name][$css_selector];
				}
			}

			// need to check for existing styles (inc legacy)
			if ( $array and $styles_found = $this->pg_has_values_inc_legacy(
					$array,
					$property_group_name) ) {

				// if there are current styles for the all devices tab, retrieve them
				if ($styles_found['cur_leg'] == 'current'){
					$property_group_array = $array['styles'][$property_group_name];
				}

				// if only legacy values exist set empty array so inputs are displayed
				else {
					$property_group_array = array();
				}
			}

			// show fields even if no values if tab is current
			if ( !$property_group_array and $this->preferences['mq_device_focus'] == $key and $pg_show_class){
				$property_group_array = array();
			}

			/*$this->debug_custom.= $section_name.'> '.$css_selector .'> '
						. $m_query['label'] .' ('.$key .') > '. $property_group_name .'> '
						. print_r( $property_group_array, true ). 'Arr: ' . is_array($array) . "\n\n";*/

			// output fields if needed
			if ( is_array( $property_group_array ) ) {

				// visible if tab is active
				$show_class = ( $this->preferences['mq_device_focus'] == $key ) ? 'show' : '';

				// pass current CSS selector
				$sel_code = '';
				if (!empty($array['label'])){ // not sure why this would be - troubleshoot
					$sel_meta = explode('|', $array['label']);
					$sel_code = !empty($sel_meta[1]) ? $sel_meta[1] : '';
				}

				// this is contained in a separate function because the li always needs to exist
				// as a wrapper for the tmpl div
				$html.= $this->single_option_fields(
					$section_name,
					$css_selector,
					$array,
					$property_group_array,
					$property_group_name,
					$show_class,
					false,
					$key,
					$con,
					$sel_code);

			}
		}

		return $html;
	}

    function groupPresets($config){

        $options = '';
        foreach ($config as $label => $items){

	        $options.= '<div class="group">';
            $options.= '<label>'.$label.':</label>';
            foreach($items as $key => $array){
	            $options.= '<span class="group-preset-option" data-preset="'.$key.'">'.$array['label'].'</span>';
            }
	        $options.= '</div>';
        }
	    /*$options.= '<span class="clear-presets">'.$this->iconFont('eraser', array(
			    'title' => 'Clear all field settings',
		    )).'</span>';*/

	    return '<div class="mt-group-presets">'.$options.'</div>';
    }

	function back_to_properties($pg_slug){

        // HTML group multi-modification tabs
		$multi_tabs = '';
		$upgradeNotice = '';

        if ($pg_slug === 'html'){
	        $multi_tabs = $this->multi_tabs($pg_slug);
	        $upgradeNotice = '
	        <div class="amender-notice">
	            <div class="amender-inspect-buttons">
	                <div class="label">Amends: </div>
	                <div class="amender-inspect-wrap tvr-multi-buttons">
                        <div id="normal-amends-tab" class="tvr-multi-button amender-inspect-button active" data-mtc="mod.MThtml.inspectButtonClicked" data-view="normal" title="Apply amendments normally">normal</div>
                        <div id="review-amends-tab" class="tvr-multi-button amender-inspect-button" data-mtc="mod.MThtml.inspectButtonClicked" data-view="data" title="Review page amendments">review</div>
                        <div id="no-amends-tab" class="tvr-multi-button amender-inspect-button" data-mtc="mod.MThtml.inspectButtonClicked" data-view="none" title="View page without amendments">none</div>
	                </div>
	                '.$this->iconFont('sync-alt', array(
                        'class' => 'html-content-refresh',
                        'title' => 'Refresh page',
                        'data-mtc' => 'mod.MThtml.refreshPreview'
                    )).'
	            </div>
	            <div class="amender-upgrade">
	                '.$this->iconFont('unlock-alt', array(
                        'class' => 'unlock-amender',
                    )).'
	                <span>
	                    <a target="_blank" href="https://themeover.com/pricing/?contentView=amender">Purchase a license</a> to publish
	                </span>
	            </div>
	            <div class="microthemer-addon">
                     '.$this->icon(
			        'add',
                        array(
	                        'class' => 'trigger-microthemer-addon',
	                        'adjacentText' => array(
		                        'text' => 'Install Microthemer addon',
		                        'class' => 'mti-text trigger-microthemer-addon'
	                        ),
                        )
                    ).'
	            </div>
	        </div>';
        }

		return '
		 <div class="properties-header-wrap">
             <div class="back-to-properties">
                        '.$this->iconFont('arrow-alt-circle-left', array(
                    'class' => 'back-to-properties-icon',
                    'adjacentText' => array(
                        'text' => $this->property_option_groups[$pg_slug],
                        'class' => 'mti-text back-to-properties-text'
                    )
                )).'
            </div>
            '.
                $this->iconFont('dots-vertical', array(
                'class' => 'mt-group-menu-toggle',
                'title' => 'Options',
                'data-forpopup' => 'contextMenu',
                'data-filter' => '#cm-group-'.$pg_slug,
            )).'
		 </div>
		 '.$multi_tabs
          .$upgradeNotice; // . $presetHTML
	}

	function multi_tabs($pg_slug){
        return '
        <div class="multi-tabs-wrap">
            <div class="multi-tabs multi-tabs-'.$pg_slug.'">
                 '.$this->iconFont('add', array(
                    'class' => 'add-multi-tab',
                    'title' => esc_attr__('Add tab', 'microthemer'),
                )).'
            </div>
        </div>';
	}

	// the options fields part of the property group (which can be added as templates)
	function single_option_fields(
		$section_name,
		$css_selector,
		$array,
		$property_group_array,
		$property_group_name,
		$show_class,
		$template = false,
		$key = 'all-devices',
		$con = 'reg',
		$sel_code = 'selector_code'){

		// is this template HTML?
		$id = ( $template ) ? 'id="option-group-template-'.$property_group_name. '"' : '';

		// add certain classes based on property values
		$conditional_classes = '';

		if (!$template){

			// rotation of flex icons
			$special_flex_direction = $this->array_matches(
				$property_group_array,
				'flex_direction',
				'contains',
				array('column', 'row-reverse')
			);

			if ($special_flex_direction){
				$conditional_classes.= ' flex-direction-'.$special_flex_direction;
			}

			// show container or item fields (flex and grid)
			$contItem = array('flexitem', 'griditem');
			foreach ($contItem as $item_type){
				if ($this->array_matches( $array,'pg_'.$item_type)){
					$conditional_classes.= ' show-'.$item_type;
				}
			}
		}

		// do all-devices fields
		$html = '
        <div '.$id.' class="property-fields property-'.$property_group_name.'
        property-fields-'. $key . ' ' . $conditional_classes. ' ' . $show_class.'">
        <div class="pg-inner">
            ';


		// option to go back to properties
		$html.= $this->back_to_properties($property_group_name);

		// line divider for horizontal tabbed grid fields
		if ($property_group_name === 'grid'){
			$html.= '<div class="grid-divider-or-spacer"></div>';
		}

		// merge to allow for new properties added to property-options.inc.php (array with values must come 2nd)
		$property_group_array = array_merge($this->propertyoptions[$property_group_name], $property_group_array);

		$this->group_spacer_count = 0;

        // With Amender, we want to wrap the fields in div that is separately scrollable
		if ($property_group_name === 'html'){
			$html.= '<div class="tvr-scrollable-fields scrollable-area">';
		}

		// output individual property fields
		foreach ($property_group_array as $property => $value) {

			// filter prop
			$property = esc_attr($property);

			/* if a new CSS property has been added with array_merge(), $value will be something like:
					Array ( [label] => Left [default_unit] => px [icon] => position_left )
					- so just set to nothing if it's an array
					*/
			//$value = ( !is_array($value) ) ? esc_attr($value) : ''; todo I hope this is OK...

			// format input fields
			$html.= $this->resolve_input_fields(
				$section_name,
				$css_selector,
				$array,
				$property_group_array,
				$property_group_name,
				$property,
				$value,
				$con,
				$key,
				$sel_code
			);
		}

		// Close scrollable fields
        if ($property_group_name === 'html'){
			$html.= '</div>';
		}

		$html.= '
		</div></div>';

		return $html;
	}

	// check if an array key/value matches
	function array_matches($array, $key, $logic = 'isset', $value = null) {

		// false if not set
		if ( !isset($array[$key]) ) return false;

		// true if only checking if set
		if ($logic == 'isset') return true;

		// compare values
		$arr_val = $array[$key];
		switch ($logic) {
			case 'contains':
				if (is_array($value)){
					foreach ($value as $v){
						if (strpos($arr_val, $v) !== false){
							return $arr_val;
						}
					}
				} else {
					if ( strpos($arr_val, $value) !== false) {
						return $arr_val;
					}
				}
			case 'is':
				if ( $arr_val == $value ) {
					return $arr_val;
				}
			case 'isnot':
				if ( $arr_val != $value ) {
					return $arr_val;
				}
				break;
		}

		return false;
	}



	// format media query min/max width (height later) and units
	function mq_min_max($pref_array){
		// check the media query min/max values
		foreach($pref_array['m_queries'] as $key => $mq_array) {

            $m_conditions = array('min', 'max');
            $q = $mq_array['query'];

            foreach ($m_conditions as $condition){
				$matches = $this->get_screen_size($q, $condition);
				$pref_array['m_queries'][$key][$condition] = 0;
				if ($matches){
					$pref_array['m_queries'][$key][$condition] = intval($matches[1]);
					$pref_array['m_queries'][$key][$condition.'_unit'] = $matches[2];
				}
			}

            // flag if it's a container query
			if (preg_match('/@container/', $q, $matches)) {
				//echo print_r($matches);
				$pref_array['m_queries'][$key]['container'] = 1;
			}

		}
		return $pref_array['m_queries'];
	}

	// get min/max media query screen size
	function get_screen_size($q, $minmax) {
		$pattern = "/$minmax-width:\s*([0-9.]+)\s*(px|em|rem)/";
		if (preg_match($pattern, $q, $matches)) {
			//echo print_r($matches);
			return $matches;
		} else {
			return 0;
		}
	}

	// compare the original set of media queries with a new config to detect deleted mqs
	function deleted_media_queries($orig_media_queries, $new_media_queries){

		$deleted = false;

		foreach($orig_media_queries as $key => $array){
			if (empty($new_media_queries[$key])){
				$deleted[] = $key;
			}
		}

		return $deleted;
	}

	// compare the original set of media queries with a new config to detect deleted mqs
	function clean_deleted_media_queries($orig_media_queries, $new_media_queries){

		if ($deleted = $this->deleted_media_queries($orig_media_queries, $new_media_queries)){

			$non_section = &$this->options['non_section'];

			foreach($deleted as $i => $key){
				if (!empty($non_section['m_query'][$key])){
					unset($non_section['m_query'][$key]);
				}
				if (!empty($non_section['important']['m_query'][$key])){
					unset($non_section['important']['m_query'][$key]);
				}
			}

			// save
			update_option($this->optionsName, $this->options);

			/*$this->log(
						esc_html__('Deleted media query cleaned', 'microthemer'),
						'<pre>' . print_r($deleted, true) . print_r($this->options['non_section']['m_query'], true). '</pre>',
						'notice'
					);*/

		}

	}

	// The new UI always shows the MQ tabs.
	// This happens even when no selectors are showing, so a different approach is needed
	function global_media_query_tabs(){

		/*'.$this->icon('border-white').'
				                           <span class="font-awesome-icon-test"></span>
				                                                                  '.$this->icon('padding-margin', array('type' => 'sym')).'
                     '.$this->icon('gradient', array('type' => 'svg')).'
                     '.$this->icon('check-circle', array('type' => 'svg')).'*/

		$html = '
                <div class="query-tabs-wrap pending-overflow-check">
                
                    <div class="query-tabs menu-style-tabs">
                    
                    ';

		// display tabs
		foreach ($this->combined_devices() as $key => $m_query){

			// don't show if hidden by the user
			if ( isset($m_query['hide']) ) continue;

			// should the tab be active? - let JS handle this
			//$class = ($this->preferences['mq_device_focus'] == $key) ? 'active' : '';

			$menu_trigger = $this->iconFont('dots-horizontal', array(
				'class' => 'item-context-menu devices-context-menu-trigger',
				'data-forpopup' => "contextMenu"
			));

			$html.= '
                            <span class="mt-tab mq-tab mq-tab-'.$key.'" rel="'.$key.'" title="' . esc_attr($m_query['query']). '">' .
			        $menu_trigger .
			        '<span class="mt-tab-txt mq-tab-txt">' . $m_query['label']. '</span>
                            </span>';
		}


		$html.= '


                </div>
                   
				</div>' . $this->mt_hor_scroll_buttons('responsive', 'div');

		return $html;
	}

	// check for 2 values on border-radius corner
	function check_two_radius($radius, $c2){
		$check = explode(' ', $radius);
		// if there are more than two values
		if (!empty($check[1])){
			$radius = $check[0];
			$c2[] = $check[1];
		} else {
			// if ANY 2nd corners have been found so far, but not on this occasion, default to the existing radius
			if ($c2){
				$c2[] = $radius;
			}
		}
		$corner = array($radius, $c2);
		return $corner;
	}

	// check if e.g. box-sahdow-x has none/inherit/initial
	function is_single_keyword($val) {
		$keywords = array('none', 'initial', 'inherit');
		// $strict is needed to prevent 0 returning true
		// https://stackoverflow.com/questions/16787762/in-array-returns-true-if-needle-is-0
		return in_array($val, $keywords, true);
	}

	function is_time_prop($property){
		return strpos($property, 'duration') !== false || strpos($property, 'delay') !== false;
	}

	function is_non_length_unit($factoryUnit, $prop){
		return $factoryUnit === 's' || $factoryUnit === 'deg' || ($factoryUnit === ''); //&& $prop !== 'line_height' -
		// we removed line-height too as this is just for setting units globally,
		// and we don't want to change line-height from default non-unit when using 'ALL Units' option
	}


	function eq_str($name){
		$eq_signs = 25 - strlen($name);
		$eq_signs = $eq_signs > 0 ? $eq_signs : 1; // change to ensure there is at least one trailing = sign
		$eq_str = '';
		for ($x = $eq_signs; $x >= 0; $x--) {
			$eq_str.= '=';
		}
		return $eq_str;
	}


	function g_url_with_subsets($g_url = false, $found_gf_subsets = false, $gfont_subset = false){

		$g_url = $g_url !== false ? $g_url : $this->preferences['g_url'];

		if (empty($g_url)){
			return '';
		}

		$found_gf_subsets = $found_gf_subsets !== false ? $found_gf_subsets : $this->preferences['found_gf_subsets'];
		$gfont_subset = $gfont_subset !== false ? $gfont_subset : $this->preferences['gfont_subset'];
		$subsets = array();

		// add custom fonts subset url param if defined in preferences
		if (!empty($gfont_subset)) {
			preg_match('/subset=(.+)/', $gfont_subset, $matches);
			if (!empty($matches[1])){
				$subsets[] = $matches[1];
			}
		}

		// combine with subsets found in MT settings
		if (!empty($found_gf_subsets) and is_array($found_gf_subsets) and count($found_gf_subsets)){
			$subsets = array_merge($subsets, $found_gf_subsets);
			$subsets = array_unique($subsets);
		}

		if (count($subsets)){
			$g_url.= '&subset=' . implode(',', $subsets);
		}

		return $g_url;
	}

	// pre-process import or restore data
	function filter_incoming_data($con, $data, $isMerge = false){

		$filtered_json = $data;

		$active_enq_js = !empty($filtered_json['non_section']['active_enq_js'])
			? $filtered_json['non_section']['active_enq_js']
			: false;

		// compare media queries in import/restore to existing
		$mq_analysis = $this->analyse_mqs(
			$filtered_json['non_section']['active_queries'],
			$this->preferences['m_queries']
		);

		// check if enq_js needs to be added
		if ($this->new_enq_js(
			$this->preferences['enq_js'],
			$active_enq_js
		)){
			$pref_array['enq_js'] = array_merge(
				$this->preferences['enq_js'],
				$active_enq_js
			);
			if ($this->savePreferences($pref_array)) {
				$this->log(
					esc_html__('JS libraries added', 'microthemer'),
					'<p>' . esc_html__('The settings you added depend on JavaScript libraries that are different from your current setup. These have been imported to ensure correct functioning.',
						'microthemer') . '</p>',
					'warning'
				);
			}
		}

		// check if the import/restore contains the same media queries but with different keys
		// if so, set the keys the same.
		// new queries also trigger this because new queries get assigned fresh keys
		if ($mq_analysis['replacements_needed']){
			foreach ($mq_analysis['replacements'] as $student_key => $role_model_key){
				$filtered_json = $this->replace_mq_keys($student_key, $role_model_key, $filtered_json);
			}
		}

		// check for new media queries in the import
		if ($mq_analysis['new']) {

			// merge the new queries with the current workspace mqs
			$pref_array['m_queries'] = array_merge(
				$this->preferences['m_queries'],
				$mq_analysis['new']
			);

			// format media query min/max width (height later) and units
			$pref_array['m_queries'] = $this->mq_min_max($pref_array);

			// save the new queries
			if ($this->savePreferences($pref_array)) {
				$this->log(
					esc_html__('Media queries added', 'microthemer'),
					'<p>' . esc_html__('The settings you added contain media queries that are different from the ones in your current setup. In order for all styles to display correctly, these additional media queries have been imported into your workspace.',
						'microthemer') . '</p>
								<p>' . wp_kses(
						sprintf(
							__('Please <span %s>review (and possibly rename) the imported media queries</span>. Note: they are marked with "(imp)", which you can remove from the label name once you\'ve reviewed them.', 'microthemer'),
							'class="link show-dialog" rel="edit-media-queries"' ),
						array( 'span' => array() )
					) . ' </p>',
					'warning'
				);
			}
		}

        // Merge certain preferences stored in the backup
        if (!empty($filtered_json['non_section']['meta']['merge_preferences'])){
            $merge_preferences = $filtered_json['non_section']['meta']['merge_preferences'];
	        $pref_array = array();
            if (is_array($merge_preferences)){
                foreach($merge_preferences as $key => $pref_value){

                    $pref_array[$key] = $pref_value;

                    // No longer needed in the options
	                unset($filtered_json['non_section']['meta']['merge_preferences'][$key]);
                }
	           //wp_die('<pre>merge_preferences: '.print_r($pref_array, true).'</pre>');
	            $this->savePreferences($pref_array);
            }
        }

        // import npm_dependencies if provided npm_dependencies
		if (!empty($filtered_json['non_section']['combine_preferences']['npm_dependencies'])){

			$npm_dependencies = &$filtered_json['non_section']['combine_preferences']['npm_dependencies'];

            if ($isMerge){
                foreach($filtered_json['non_section']['combine_preferences']['npm_dependencies'] as $slug => $item){
	                $this->preferences['npm_dependencies'][$slug] = $item;
                }
	            $npm_dependencies = &$this->preferences['npm_dependencies'];
            }

			$this->savePreferences(array('npm_dependencies' => $npm_dependencies));
		}

        // import snippets if provided
        if (!empty($filtered_json['non_section']['active_snippets'])){
	        $this->contentMethod('restoreSnippets', array(&$filtered_json['non_section']['active_snippets'], false, $isMerge));
        }


		// active_queries are just used for import now. Unset as they have served their purpose
		unset($filtered_json['non_section']['active_queries']);
		unset($filtered_json['non_section']['npm_dependencies']);
		unset($filtered_json['non_section']['active_snippets']);

		// just for debugging
		if ($this->debug_import) {

			// get this before modifying in any way
			//$debug_mqs['incoming_active_queries'] = $data['non_section']['active_queries'];
			$debug_mqs['orig'] = $this->preferences['m_queries'];
			$debug_mqs['new'] = $mq_analysis['new'];
			$debug_mqs['merged'] = $debug_mqs['new'] ? $pref_array['m_queries'] : false;
			$debug_mqs['mq_analysis'] = $mq_analysis;

			$debug_file = $this->debug_dir . 'debug-'.$con.'.txt';
			$write_file = @fopen($debug_file, 'w');
			$data = '';
			$data.= "\n\n### 1. Key Debug Analysis \n\n";
			$data.= print_r($debug_mqs, true);
			$data.= "\n\n### 2. The UNMODIFIED incoming data\n\n";
			$data.= print_r($data, true);
			$data.= "\n\n### 3. The (potentially) MODIFIED incoming data\n\n";
			$data.= print_r($filtered_json, true);
			fwrite($write_file, $data);
			fclose($write_file);
		}

		// return the filtered data and mq analysis
		return $filtered_json;
	}


	// ensure mq keys in pref array and options match
	//- NOTE A SIMPLER SOLUTION WOULD BE TO CONVERT ARRAY INTO STRING AND THEN DO str_replace (may have side effects though)
	function replace_mq_keys($student_key, $role_model_key, $options) {
		$old_new_mq_map[$student_key] = $role_model_key;
		// replace the relevant array keys - unset() doesn't work on $this-> so slightly convoluted solution used
		$cons = array('active_queries', 'm_query');
		$updated_array = array();
		foreach ($cons as $stub => $context) {
			unset($updated_array);
			if (is_array($options['non_section'][$context])) {
				foreach ($options['non_section'][$context] as $cur_key => $array) {
					if ($cur_key == $student_key) {
						$key = $role_model_key;
					} else {
						$key = $cur_key;
					}
					$updated_array[$key] = $array;
				}
				$options['non_section'][$context] = $updated_array; // reassign main array with updated keys array
			}
		}
		// and also the !important media query keys
		$updated_array = array();
		if (!empty($options['non_section']['important']['m_query']) and
		    is_array($options['non_section']['important']['m_query'])) {
			foreach ($options['non_section']['important']['m_query'] as $cur_key => $array) {
				if ($cur_key == $student_key) {
					$key = $role_model_key;
				} else {
					$key = $cur_key;
				}
				$updated_array[$key] = $array;
			}
			$options['non_section']['important']['m_query'] = $updated_array; // reassign main array with updated keys array
		}

		return $options;
	}


	// merge the new settings with the current settings
	function merge($orig_settings, $new_settings) {
		// create debug merge file if set at top of script
		if ($this->debug_merge) {
			$debug_file = $this->debug_dir . 'debug-merge.txt';
			$write_file = @fopen($debug_file, 'w');
			$data = '';
			$data.= "\n\n" . __('### The to existing options (before merge)', 'microthemer') . "\n\n";
			$data.= print_r($orig_settings, true);

			$data .= "\n\n" . esc_html__('### The imported options (before any folder renaming)', 'microthemer') . "\n\n";
			$data .= print_r($new_settings, true);

		}
		if (is_array($new_settings)) {
			// check if search needs to be done on important and m_query arrays
			$mq_arr = $imp_arr = false;
			if (!empty($new_settings['non_section']['m_query']) and is_array($new_settings['non_section']['m_query'])){
				$mq_arr = $new_settings['non_section']['m_query'];
			}
			if (!empty($new_settings['non_section']['important']) and is_array($new_settings['non_section']['important'])){
				$imp_arr = $new_settings['non_section']['important'];
			}

			// loop through new sections to check for section name conflicts
			foreach($new_settings as $section_name => $array) {
				// if a name conflict exists
				if ( $this->is_name_conflict($section_name, $orig_settings, $new_settings, 'first-check') ) {
					// create a non-conflicting new name
					$alt = $this->get_alt_section_name($section_name, $orig_settings, $new_settings);
					$alt_name = $alt['name'];
					$alt_index = $alt['index'];
					// rename the to-be-merged section and the corresponding non_section extras
					$new_settings[$alt_name] = $new_settings[$section_name];
					$new_settings[$alt_name]['this']['label'] = $new_settings[$alt_name]['this']['label'].' '.$alt_index;
					$new_settings['non_section']['view_state'][$alt_name] = $new_settings['non_section']['view_state'][$section_name];
					unset($new_settings[$section_name]);
					unset($new_settings['non_section']['view_state'][$section_name]);
					// also rename all the corresponding [m_query] folder names (ouch)
					if ($mq_arr){
						foreach ($mq_arr as $mq_key => $arr){
							foreach ($arr as $orig_sec => $arr){
								// if the folder name exists in the m_query array, replace
								if ($section_name == $orig_sec){
									$new_settings['non_section']['m_query'][$mq_key][$alt_name] = $new_settings['non_section']['m_query'][$mq_key][$section_name];
									unset($new_settings['non_section']['m_query'][$mq_key][$section_name]);
								}
							}
						}
					}
					// and the [important] folder names (double ouch)
					if ($imp_arr){
						foreach ($imp_arr as $orig_sec => $arr){
							// if it's MQ important values
							if ($orig_sec == 'm_query'){
								foreach ($imp_arr['m_query'] as $mq_key => $arr){
									foreach ($arr as $orig_sec => $arr){
										// if the folder name exists in the m_query array, replace
										if ($section_name == $orig_sec){
											$new_settings['non_section']['important']['m_query'][$mq_key][$alt_name] = $new_settings['non_section']['important']['m_query'][$mq_key][$section_name];
											unset($new_settings['non_section']['important']['m_query'][$mq_key][$section_name]);
										}
									}
								}
							} else {
								// regular important value
								$new_settings['non_section']['important'][$alt_name] = $new_settings['non_section']['important'][$section_name];
								unset($new_settings['non_section']['important'][$section_name]);
							}
						}
					}
				}
			}


			if ($this->debug_merge) {
				$data .= "\n\n" . esc_html__('### The imported options (after folder renaming)', 'microthemer') . "\n\n";
				$data .= print_r($new_settings, true);
			}

			// now that we've checked for and corrected possible name conflicts
			// merge the arrays (recursively to avoid overwriting)
			$merged_settings = $this->array_merge_recursive_distinct($orig_settings, $new_settings);

			// the hand-coded CSS of the imported settings needs to be appended to the original
			foreach ($this->custom_code as $key => $arr){


				$new_code = trim($new_settings['non_section'][$key]);
				if (!empty($new_code)) {
					$merged_settings['non_section'][$key] =
						$orig_settings['non_section'][$key]
						. "\n\n/* " . esc_html_x('Imported CSS', 'CSS comment', 'microthemer') . " */\n"
						. $new_settings['non_section'][$key];
				} else {
					// the imported pack has no custom code so keep the original
					$merged_settings['non_section'][$key] = $orig_settings['non_section'][$key];
				}

			}
		}
		// maybe do some more processing here

		if ($this->debug_merge) {
			$data.= "\n\n" . __('### The Merged options', 'microthemer') . "\n\n";
			$data.= print_r($merged_settings, true);
			fwrite($write_file, $data);
			fclose($write_file);
		}
		return $merged_settings;
	}

	// add js deps in import if not
	function new_enq_js($cur_enq_js, $imp_enq_js){
		if ($imp_enq_js && is_array($imp_enq_js)){
			foreach ($imp_enq_js as $k => $arr){
				if (empty($cur_enq_js[$k])) return true;
			}
		}
		return false;
	}

	// get an array of current mq keys paired with replacements -
	// compare against 'role model' to base current array on
	function analyse_mqs($student_mqs, $role_model_mqs){
		$mq_analysis['new'] = false;
		$mq_analysis['replacements_needed'] = false;
		$i = 0;
		if (!empty($student_mqs) and is_array($student_mqs)) {
			foreach ($student_mqs as $student_key => $student_array){
				$replacement_key = $this->in_2dim_array($student_array['query'], $role_model_mqs, 'query');
				// if new media query
				if ( !$replacement_key ) {
					// ensure key is unique by using unique base from last page load
					// otherwise previously exported keys could overwrite rather add to existing MQs (if the query was changed after exporting)
					$new_key = $this->unq_base.++$i;
					$mq_analysis['new'][$new_key]['label'] = $student_array['label']. esc_html_x(' (imp)', '(imported media query)', 'microthemer');
					$mq_analysis['new'][$new_key]['query'] = $student_array['query'];
					// as we're defining new keys, the ui data keys will need replacing too
					$mq_analysis['replacements_needed'] = true;
					$mq_analysis['replacements'][$student_key] = $new_key;
				}
				// else store replacement key
				else {
					if ($replacement_key != $student_key){
						$mq_analysis['replacements_needed'] = true;
						$mq_analysis['replacements'][$student_key] = $replacement_key;
					}
				}
			}
		}
		return $mq_analysis;
	}


	// encode or decode json todo replace other $json_object actions with this function (and test)
	function json($action, $data, $json_file = ''){

		// convert to array
		if ($action == 'decode'){

			// if we can't decode using native PHP function
			if (!$json_array = json_decode($data, true)) {

				//wp_die('$json_array failed: <pre>' . $data . '</pre>');

				// MT may be trying to decode data encoded by an older custom JSON class, rather than PHP native
				// so attempt to decode using legacy class
                try {
	                if( !class_exists('Moxiecode_JSON') ) {
		                //require_once($this->thisplugindir . 'includes/class-json.php');
		                require_once($this->thisplugindir . 'includes/class-json-modernised.php');
	                }
	                $json_object = new \Moxiecode_JSON();

	                // we still can't decode the data
	                if (!$json_array = $json_object->decode($data)) {
		                $this->log('', '', 'error', 'json-decode', array('json_file', $json_file));
		                return false;
	                }
                } catch(\Exception $e){
	                $this->log('', '', 'error', 'json-decode', array('json_file', $json_file));
	                return false;
                }

			}
			return $json_array;
		}

		// convert to json string
		elseif ($action == 'encode'){
			if (!$json_str = json_encode($data)) {
				$this->log(
					esc_html__('Encode json error', 'microthemer'),
					'<p>' . esc_html__('WordPress failed to convert your settings into json.', 'microthemer') . '</p>'
				);
				return false;
			}
			return $json_str;
		}
	}

	// slightly modified version of wp_remote_fopen where we also get the response code
	function wp_remote_fopen( $uri ) {

		$parsed_url = parse_url( $uri );

		if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
			return false;
		}

		$options            = array();
		$options['timeout'] = 10;
		$response = wp_safe_remote_get( $uri, $options );

		if ( is_wp_error( $response ) ) {
			error_log( 'wp_remote_fopen error: ' . $response->get_error_message() );
			return false;
		}

		return array(
			'body' => wp_remote_retrieve_body( $response ),
			'code' => wp_remote_retrieve_response_code( $response )
		);
	}

}


// The PCL zip class requires a pre-defined callback function external to the class
if (!function_exists('\Microthemer\tvr_microthemer_getOnlyValid')) {

	function tvr_microthemer_getOnlyValid($p_event, &$p_header) {

		// avoid null byte hack (THX to Dominic Szablewski)
		if ( strpos($p_header['filename'], chr(0) ) !== false ){
			$p_header['filename'] = substr ( $p_header['filename'], 0, strpos($p_header['filename'], chr(0) ));
		}

		$info = pathinfo($p_header['filename']);

		// check for extension
		$ext = array('jpeg', 'jpg', 'png', 'gif', 'txt', 'json', 'psd', 'ai');
		$check_ext = strtolower($info['extension']);
		if ( in_array($check_ext, $ext) ) {

			// For MAC skip the ".image" files
			if ($info['basename'][0] == '.' ){
				return 0;
			}

			else {
				return 1;
			}

		}

		// ----- all other files are skipped
		else {
			return 0;
		}
	}
}


