Your IP : 216.73.217.154


Current Path : /home/ereika83/www/wp-content/plugins/microthemer/src/
Upload File :
Current File : /home/ereika83/www/wp-content/plugins/microthemer/src/SettingsTrait.php

<?php

namespace Microthemer;

trait SettingsTrait {

	function actionSaveInterface(){

		// remove slashes and custom escaping so that DB data is clean
		$this->serialised_post =
			$this->deep_unescape($_POST, 1, 1, 1);

		if (!empty($this->serialised_post['serialise'])){
			$this->serialised_post['tvr_mcth'] = $this->json('decode', $this->serialised_post['tvr_mcth']);
			//json_decode($this->serialised_post['tvr_mcth'], true);
			/*echo 'show_me from tvr_mcth: <pre> ';
					print_r($_POST);
					echo '</pre>';*/
		}

		// bail if no save data was successfully decoded
		if (empty($this->serialised_post['tvr_mcth'])) {
			return false;
		}

		// strange Kinsta error prompted this but might have been a fleeting issue
		$partial = !empty($this->serialised_post['partial_data'])
			? $this->serialised_post['partial_data']
			: false;
		$last_save_time = !empty($this->serialised_post['last_save_time'])
			? $this->serialised_post['last_save_time']
			: false;
		$new_select_option = '';


		/*$debug = true;
		if ($debug){
			echo 'show_me from ajax save (before): <pre>';
			echo print_r($this->serialised_post, 1);
			echo '</pre>';
		}*/

		// save settings in DB
		if (!$this->saveUiOptions2(
			$this->serialised_post['tvr_mcth'],
			$partial,
			$last_save_time
		)) {

			// save error
			$this->log(
				esc_html__('Settings failed to save', 'microthemer'),
				'<p>' . esc_html__('Saving your settings to the database failed.', 'microthemer') . '</p>'
			);
		}

		// save successful
		else {

			$saveOk = empty($this->preferences['auto_publish_mode'])
				? esc_html__('Draft saved', 'microthemer')
				: esc_html__('Settings saved and published', 'microthemer');

			$this->log(
				$saveOk,
				'<p>' . esc_html__('The UI interface settings were successfully saved.', 'microthemer') . '</p>',
				'notice'
			);



			// check if settings need to be exported to a design pack
			if (!empty($this->serialised_post['export_to_pack']) && $this->serialised_post['export_to_pack'] == 1) {

				$theme = htmlentities($this->serialised_post['export_pack_name']);
				$context = 'existing';
				$do_option_insert = false;

				if ($this->serialised_post['new_pack'] == 1){
					$context = 'new';
					$do_option_insert = true;
				}

				// function return sanitised theme name
				$theme = $this->update_json_file($theme, $context);

				// save new sanitised theme in span for updating select menu via jQuery
				if ($do_option_insert) {
					$meta_file = $this->micro_root_dir.$theme.'/meta.txt';
					if (file_exists($meta_file)){
						$meta_info = $this->read_meta_file($meta_file);
						$new_select_option = $meta_info['Name'];
					} else {
						$new_select_option = $this->readable_name($theme);
					}

				}
				//$user_action.= sprintf( esc_html__(' & Export to %s', 'microthemer'), '<i>'. $this->readable_name($theme). '</i>');
			}

			// else its a standard save of custom settings
			else {
				$theme = 'customised';
				//$user_action.= esc_html__(' (regular)', 'microthemer');
			}

			// update active-styles.css
			$this->update_assets($theme);

			// update the revisions DB field
			$user_action = !empty($this->serialised_post['user_action'])
				? json_encode($this->serialised_post['user_action'])
				: null;

			if (!$this->updateRevisions($this->options, $user_action)) {
				$this->log('','','error', 'revisions');
			}
		}



		//echo 'carrots!';
		//wp_die();

		// return the globalmessage and then kill the program - this action is always requested via ajax
		// also fullUIData as an interim way to keep JS ui data up to date (post V5 will have new system with less http)
		$html = '<div id="microthemer-notice">' . $this->display_log() . '</div>';

		/*<span id="outdated-tab-issue">'.$this->outdatedTabIssue.'</span>
							<span id="returned-save-time">'.$this->options['non_section']['last_save_time'].'</span>*/

		// we're returning a JSON obejct here, the HTML is added as a property of the object
		$response = array(
			//'prefs' => $this->preferences,
			'html'=> $html,
			'outdatedTab'=> $this->outdatedTabIssue,
			'outdatedTabDebug'=> $this->outdatedTabDebug,
			'returnedSaveTime'=> $this->options['non_section']['last_save_time'],
			'returnedRevisions' => $this->getRevisions(true),
			'exportName' => $new_select_option,
			'num_saves' => $this->preferences['num_saves'],
			'asset_loading' => $this->preferences['asset_loading'],
			'asset_loading_change' => $this->asset_loading_change,
			'adjusted_logic' => !empty($this->serialised_post['adjusted_logic']),
			'recent_logic' => $this->preferences['recent_logic'],
			'num_unpublished_saves' => $this->preferences['num_unpublished_saves']
			//'uiData'=> $this->options
			//'uiData'=> array()
		);

		echo json_encode($response); //$html;
	}

	function publishSettings(){

		$root = $this->micro_root_dir;
		$conditionalRoot = $root . 'mt/conditional/';
		$draftDir  = $conditionalRoot . 'draft/';
		$activeDir = $conditionalRoot . 'active/';
		$minifyCSS = !empty($this->preferences['minify_css']);
		$minifyJS = !empty($this->preferences['minify_js']);

		// get a list of the active files before copying over
		$origActiveFiles = $this->getDirectoryFileList($activeDir);

		// copy the global files
		$globals = array('styles.css', 'styles.scss', 'scripts.js');

		foreach ($globals as $file){

			$draft_file = $root . 'draft-' . $file;
			$published_file = $root . 'active-' . $file;
			$ext = $this->get_extension($file);

			// copy the draft file if it exists
			if (file_exists($draft_file)){

				$doMinify = $ext === 'css' && $minifyCSS || $ext === 'js' && $minifyJS;

				// Minify if user preference
				if ($doMinify){

					if (!$this->minify($draft_file, $ext, $published_file)){

						$this->log(
							esc_html__('File not minified', 'microthemer'),
							'<p>' . sprintf(
								esc_html__('Asset failed to minify and publish: %s', 'microthemer'),
								$this->root_rel($published_file)
							) . '</p>',
							'error'
						);
					}
				}

				// Simply copy the file
				else {

					if (!copy($draft_file, $published_file)){

						$this->log(
							esc_html__('File not published', 'microthemer'),
							'<p>' . sprintf(
								esc_html__('Asset failed to publish: %s', 'microthemer'),
								$this->root_rel($published_file)
							) . '</p>',
							'error'
						);
					}
				}


			}

			// if the draft file doesn't exist, but the published one does, clean the published file
			elseif (file_exists($published_file)){
				//echo 'we should del pub: ' . $published_file;
				unlink($published_file);
			}
		}

		// copy the conditional draft folder to the conditional active folder
		$this->copyFolder($draftDir, $activeDir, $minifyCSS, $minifyJS);

		// delete any lingering conditional files that are no longer needed in the published directory
		foreach ($origActiveFiles as $activeFile){
			if (!file_exists($draftDir . $activeFile)){
				unlink($activeDir . $activeFile);
			}
		}

		// update the published preferences
		$this->savePreferences(array(
			'num_unpublished_saves' => 0,
			'asset_loading_published' => $this->preferences['asset_loading'],
			'global_stylesheet_required_published' => $this->preferences['global_stylesheet_required'],
			'load_js_published' => $this->preferences['load_js'],
		));

		return json_encode(array(
			'num_unpublished_saves' => 0,
			//'html' => '<div id="microthemer-notice">' . $this->display_log() . '</div>' // todo show in UI
		));

	}



	// Resest the options.
	function resetUiOptions(){
		delete_option($this->optionsName);
		$this->getOptions(); // reset the defaults
		$pref_array = array();
		$pref_array['active_theme'] = 'customised';
		$pref_array['theme_in_focus'] = '';
		$pref_array['num_saves'] = 0;
		$pref_array['g_fonts_used'] = false;
		$pref_array['g_url'] = '';
		$pref_array['g_url_with_subsets'] = '';
		$pref_array['hover_inspect'] = 1;
		$this->savePreferences($pref_array);
		return true;
	}

	// Save the UI styles to the database - from full or partial save package
	function saveUiOptions2($savePackage, $partial = false, $last_save_time = false){

		// check last save time
		if (!$this->check_last_save_time($last_save_time)){
			return false;
		}

		// plain save if no save package
		if (!$partial){
			$this->options = $savePackage;
		}

		// loop through update items making adjustments to $this->options
		else {
			$this->apply_save_package($savePackage, $this->options);
		}

		// tag version the settings were saved at so e.g. css units can be imported correctly for legacy data
		$this->options['non_section']['mt_version'] = $this->version;

		// update DB
		update_option($this->optionsName, $this->options);

		return true;

	}

	// check the last save time
	function check_last_save_time($last_save_time){

		// if we have no last_save_time to compare, set it for future reference
		if (!isset($this->options['non_section']['last_save_time'])){
			$this->options['non_section']['last_save_time'] = time();
		}

		// else we do have a time in the DB and a passed save time to compare
		else if ($last_save_time){

			// do safety check to make sure newer settings haven't been applied in another tab
			// allow passed last save time to be 15 seconds out due to quirk of resave I haven't fully understood
			if ( intval($last_save_time + 10) < intval($this->options['non_section']['last_save_time']) ){

				$this->log(
					esc_html__('Multiple tabs/users issue', 'microthemer'),
					'<p>' . esc_html__('MT settings were updated more recently by another user or browser tab. Saving from this outdated tab could cause data loss. Please reload the page instead of saving from this tab (to get the latest changes).', 'microthemer') . '</p>'
				);

				$this->outdatedTabDebug = 'Last save time: '.intval($last_save_time). ", \n" .
				                          'Stored save time: '.intval($this->options['non_section']['last_save_time'])  . ", \n" .
				                          'Difference: ' . (intval($last_save_time) - intval($this->options['non_section']['last_save_time']));

				$this->outdatedTabIssue = 1;

				return false;
			}

			else {

				$this->outdatedTabDebug = 'Last save time: '.$last_save_time. ", \n" .
				                          'Stored save time: '.$this->options['non_section']['last_save_time'] . ", \n" .
				                          'Difference: ' . (intval($last_save_time) - intval($this->options['non_section']['last_save_time']));

				// update last save time
				$this->options['non_section']['last_save_time'] = time();



			}



		}

		return true;
	}

	// update the ui options using & reference to behave like JS object
	function apply_save_package($savePackage, &$data){

		$before_after = array('### Save Package Before and After ###');

		foreach($savePackage as $update){

			if ($update['action'] === 'debug'){
				if ($this->debug_save_package) {
					$before_after[] = $update['data'];
				}
				continue;
			} elseif ($update['action'] === 'no_new_data'){
				continue;
			}

			$before = false;
			if ($this->debug_save_package) {
				$before                 = $this->get_or_update_item($data, array_merge($update, array('action' => 'get')));
				$update[ 'callerFunc' ] = !empty($update[ 'callerFunc' ]) ? $update[ 'callerFunc' ] : '';
			}

			$data_item = &$this->get_or_update_item($data, $update, 0);

			if ($this->debug_save_package) {
				$before_after[] = array(
					'before '.$update['callerFunc'].' (' .$update['action'].')' => $before,
					'after '.$update['callerFunc'].' (' .$update['action'].')' => $data_item,
					'update_package '.$update['callerFunc'].' (' .$update['action'].')' => $update
				);
			}
		}

		if ($this->debug_save_package) {
			$before_after[] = array(
				'Full options:' => $this->options
			);
			$write_file = @fopen($this->debug_dir . 'save-package.txt', 'w');
			fwrite($write_file, print_r($before_after, true));
			fclose($write_file);
		}

	}


	// (optionally) update a multidimensional array item using array trail e.g. ['non_section', 'meta'].
	// Returns a reference to the target item. Note '&' must proceed function call for ref rather than copy.
	function &get_or_update_item(&$data, $config, $startIndex = 0){

		$item = &$data;
		$trail = !empty($config['trail']) ? $config['trail'] : array();
		$trail_length = count($trail);

		// to get round PHP error: Only variable references should be returned by reference
		$false = false;

		for ($x = $startIndex; $x < $trail_length; $x++) {
			$key = $trail[$x];

			// if item doesn't exist
			if (!isset($data[$key])){

				// bail if we're trying to get an item that doesn't exist
				if ($config['action'] === 'get'){

					/* $this->log(
								 esc_html__('Trail lead to undefined item: '.$key, 'microthemer'),
								 '<pre>parent: '  . print_r($data, true) . '</pre>'
								 //'notice'
							 );*/

					return $false;
				}

				// create trail is we're trying to perform an action on a non_existant item
				else {

					$data[$key] = array();

					/*$this->log(
								esc_html__('Previously undefined item added: '.$key, 'microthemer'),
								'<pre>parent: '  . print_r($data, true) . '</pre>'
								//'notice'
							);*/
				}

			}

			$item = &$data[$key];
			$next_index = $x+1;

			//$this->show_me.= '<pre>loop key: '.$key. ' $x: '.$x. ' $trail_length: '.$trail_length. ' $item: '.$item.'</pre>';

			if ($next_index < $trail_length){
				return $this->get_or_update_item($item, $config, $next_index);
			}
		}

		// optionally update item
		switch($config['action']){
			case 'get':
				return $item;
			case 'replace':

				/*$this->log(
							esc_html__('The replace item: ', 'microthemer'),
							'<pre>parent: '  . print_r($item, true) . '</pre>'
						//'notice'
						);*/

				$item = $config['data'];
				break;
			case 'delete':
				unset($item[$config['key']]);
				break;
			case 'rename':
				$this->order_item_properties($item, $config['order'], $config['key'], $config['new_key']);
				break;
			case 'reorder':
				$this->order_item_properties($item, $config['order']);
				break;
			case 'append':
				$item[$config['key']] = $config['data'];
				break;
			case 'array_merge':
				$item = array_merge($item, $config['data']);
				break;
			case 'array_merge_recursive_distinct':

				// tip for myself, this causes 500 error otherwise
				if (!is_array($config['data'])){
					$this->log(
						esc_html__('Merge data is not an array: ', 'microthemer'),
						'<pre>Update package: '  . print_r($config, true) . '</pre>'
					);
					return $false;
				}

				$item = $this->array_merge_recursive_distinct($item, $config['data']);
				break;
			/* unlikely to ned this
					 * array_merge_recursive is a bit weird http://php.net/manual/en/function.array-merge-recursive.php
                     * see explaination of diff with array_merge_recursive_distinct on above PHP page
					 * case 'array_merge_recursive':
						$item = array_merge_recursive($item, $config['data']);
						break;*/
		}

		// return the updated item
		return $item;
	}

	function order_item_properties(&$item, $order, $old_key = false, $new_key = false){
		$new_item = array();
		foreach ($order as $i => $key){

			// don't add undefined keys
			if (isset($item[$key])){
				$new_item[(($key == $old_key) ? $new_key : $key)] = $item[$key];
			} else {
				/* for debugging
						 * $this->log(
							esc_html__('Order key was undefined: '.$key, 'microthemer'),
							'<pre>parent: '  . print_r($item, true) . '</pre>'
						);*/
			}
		}
		$item = $new_item;
	}

	// circumvent max_input_vars by passing one serialised input that can be unpacked with this function
	function my_parse_str($string, &$result) {
		if($string==='') return false;
		$result = array();
		// find the pairs "name=value"
		$pairs = explode('&', $string);
		foreach ($pairs as $pair) {
			// use the original parse_str() on each element
			parse_str($pair, $params);
			$k=key($params);
			if(!isset($result[$k])) {
				$result+=$params;
			}
			else {
				if (is_array($result[$k])){
					//echo '<pre>key:'. $k . "\n";
					//echo 'params:';
					//print_r($params);
					//$result[$k]+=$params[$k];
					$result[$k] = $this->array_merge_recursive_distinct($result[$k], $params[$k]);
					// 'result:';
					//print_r($result);
					//echo '</pre>';
				}
			} //
			//else $result[$k]+=$params[$k];
		}
		return true;
	}

	// better recursive array merge function listed on the function's PHP page
	function array_merge_recursive_distinct ( array &$array1, array &$array2 ){
		$merged = $array1;
		foreach ( $array2 as $key => &$value )
		{
			if ( is_array ( $value ) && isset ( $merged [$key] ) && is_array ( $merged [$key] ) )
			{
				$merged [$key] = $this->array_merge_recursive_distinct ( $merged [$key], $value );
			}
			else
			{
				$merged [$key] = $value;
			}
		}

		return $merged;
	}

	// run data structure updates
	function maybe_do_data_conversions_for_update(){

		// a few minor data format changes were made for the speed version. This runs once.
		if (empty($this->preferences['speed_conversion_done'])){

			// create backup
			//$this->pre_upgrade_backup(); - this happens on every update

			$non_section = &$this->options['non_section'];
			$keys = array(
				'adv_wizard_focus', 'css_focus', 'device_focus', // just pref now
				'last_save_time' // move to meta
			);

			// remove keys that were hack for non-queued settings save
			foreach ($keys as $key){
				$item = &$this->get_or_update_item($non_section, array('trail' => array($key), 'action' => 'get'));
				//$this->show_me.= '<pre>key '.$key. ' $item: '.$item.'</pre>';
				if ($item){

					// move certain key values to meta
					if ($key === 'last_save_time'){
						$this->get_or_update_item($non_section, array(
							'action' => 'append',
							'trail' => array('meta'),
							'key' => $key,
							'data' => $item
						));
					}

					unset($non_section[$key]);
				}
			}

			// we don't need to track view state outside of regular sel
			if (!empty($non_section['view_state'])){
				unset($non_section['view_state']);
			}

			// we only use active_queries for import/revision restore now
			if (!empty($non_section['active_queries'])){
				unset($non_section['active_queries']);
			}

			// remove recent sug for background_image and list_style_image which will be basename - invalid
			$image_props = array('list_style_image', 'background_image', 'url');
			$types = array('recent', 'copiedSrc');
			foreach ($image_props as $image_prop){
				foreach ($types as $type){
					$this->get_or_update_item(
						$this->preferences['my_props']['sug_values'],
						array(
							'trail' => array($image_prop, $type),
							'action' => 'replace',
							'data' => array()
						)
					);
				}

			}

			// update preferences
			$this->savePreferences(
				array(
					'speed_conversion_done' => true,
					'my_props' =>  $this->preferences['my_props']
				)
			);

			// update DB
			update_option($this->optionsName, $this->options);

			//$this->show_me.= '<pre>modified non_section what '.$this->options['css_focus'].'</pre>';

		}

		// transition to more solid system for connecting MT tabs with page builder device views
		if (empty($this->preferences['builder_site_preview_width_conversion_done'])

		    // run this again if Gutenberg site device preview sync has not been setup
		    || $this->preferences['builder_site_preview_width_conversion_done'] !== 2
		)
		{

			$m_queries = $this->preferences['m_queries'];

			$map = array(
				//"bbxl" => "builder.FLBuilder.xl",
				"bb0" => "builder.FLBuilder.large",
				"bb1" => "builder.FLBuilder.medium",
				"bb2" => "builder.FLBuilder.small",
				"elem2" => "builder.elementor.tablet",
				"elem3" => "builder.elementor.mobile",
				"oxy_page_width" => "builder.oxygen.page-width",
				"oxy_tablet" => "builder.oxygen.tablet",
				"oxy_phone_landscape" => "builder.oxygen.phone-landscape",
				"oxy_phone_portrait" => "builder.oxygen.phone-portrait",
				"gutenberg2" => "builder.gutenberg.tablet",
				"gutenberg3" => "builder.gutenberg.mobile",
			);

			// remove keys that were hack for non-queued settings save
			foreach ($m_queries as $mq_key => $m_query){

				foreach ($map as $key_suffix => $site_preview_width){

					$keySuffixMatch = preg_match('/'.$key_suffix.'$/', $mq_key);
					$defaultMobOrTablet = !isset($m_queries[$mq_key]['site_preview_width']) &&
					                      isset($m_query['max']) &&
					                      ($key_suffix === 'gutenberg2' && $m_query['max'] == '767') ||
					                      ($key_suffix === 'gutenberg3' && $m_query['max'] == '479');

					if ( $keySuffixMatch || $defaultMobOrTablet ){
						$m_queries[$mq_key]['site_preview_width'] = $site_preview_width;
					}
				}
			}

			/*wp_die('Old: <pre>$media_queries_list: '.print_r($this->preferences['m_queries'], true). '</pre>'
					. 'New: <pre>$media_queries_list: '.print_r($m_queries, true). '</pre>');*/

			// update preferences
			$this->savePreferences(
				array(
					'builder_site_preview_width_conversion_done' => true,
					'm_queries' => $m_queries
				)
			);

		}

		// there were some errors with recently viewed pages being badly formatted
		// including an Oxygen issue that could cause data loss, so reset custom_paths if not done already
		// custom paths also needed to be reset for the logic feature, so that logic values are included too
		if (empty($this->preferences['custom_paths_reset']) || $this->preferences['custom_paths_reset'] < 3){
			$this->savePreferences(
				array(
					'custom_paths_reset' => 3,
					'custom_paths' =>  array('/')
				)
			);
		}

		// we previously had dock_styles_left which did both styles and editor
		if (empty($this->preferences['dock_left_conversion_done'])){
			$this->savePreferences(
				array(
					'dock_editor_left' => 1,
					'dock_styles_left' => 1,
					'dock_left_conversion_done' =>  1
				)
			);
		}


		// min_widths for resizable panels are stored alongside user custom sizes
		// but this may need to be refined at various intervals
		$layout_adjust_version = 3;// increase this number when
		if (
			empty($this->preferences['layout_adjust_version'])
			|| $this->preferences['layout_adjust_version'] !== $layout_adjust_version
		){

			// set new value - note array_merge had strange affect, so be careful with that
			// simpler to just set new values
			$this->preferences['layout']['inspection_columns']['min_column_sizes'] = array(300, 310);

			// update minimum size for left columns
			$min_left = array(200, 200, 180);
			$this->preferences['layout']['left']['min_column_sizes'] = $min_left;
			foreach ($min_left as $index => $min_size){
				if ($this->preferences['layout']['left']['column_sizes'][$index] < $min_size){
					$this->preferences['layout']['left']['column_sizes'][$index] = $min_size;
				}
			}


			$this->savePreferences(
				array(
					'layout' => $this->preferences['layout'],
					'layout_adjust_version' =>  $layout_adjust_version
				)
			);
		}

		// ensure draft mode is always on, and wizard dock right setting is off
		if (empty($this->preferences['always_draft_conversion_done'])){
			$this->savePreferences(
				array(
					'draft_mode' => 1,
					'dock_wizard_right' => 0, // this is never docked right in 7.0 release (may be supported again)
					'always_draft_conversion_done' =>  1
				)
			);
		}

	}


	// update active-styles.css
	function update_assets($activated_from, $context = '') {

		// as an interim, MT will continue to load dependencies in the global stylesheet
		// e.g. key frames, GFonts, event JS - this will take some time but can be supported
		$globalStylesheetRequired = 0;

		// cache previous asset loading for change analysis
		$prev_asset_loading = $this->preferences['asset_loading'];

		// get path to active-styles.css
		//$act_styles = $this->micro_root_dir.'active-styles.css';

		// check for micro-themes folder and create if it doesn't exist
		$this->setup_micro_themes_dir();

		// bail if stylesheet isn't writable
		/*if (file_exists($act_styles) && !is_writable($act_styles) ) {
			$this->log(
				esc_html__('Write stylesheet error', 'microthemer'),
				'<p>' . esc_html__('WordPress does not have "write" permission for: ', 'microthemer')
				. '<span title="'.$act_styles.'">'. $this->root_rel($act_styles) . '</span>
						. '.$this->permissionshelp.'</p>'
			);
			return false;
		}*/

		// setup vars
		$asset = array(
			'global' => array(
				'data' => '',
				'scss_data' => '',
				'js_data' => ''
			),
			'conditional' => array(),

			// when the settings are saved, conditional folders are checked and only those with
			// styles are given stylesheets. This make this Server side script a good candidate as the
			// central source of truth building the asset_loading logic value (rather than with JS)
			'preference' => array(

				// log when an asset (global or conditional) has been added or removed
				// so that browser tab syncing can add/remove these assets on other tabs
				'global_css' => 0,
				'global_g_fonts' => 0,
				'conditional' => array(),

				// ordered folder logic
				'logic' => array()
			),
		);

		$title = '/*  MICROTHEMER STYLES  */' . "\n\n";

		// check if hand coded have been set - output before other css
		$scss_custom_code = '';
		$custom_code = '';
		if ( !empty($this->options['non_section']['hand_coded_css']) &&
		     !empty(trim($this->options['non_section']['hand_coded_css'])) ){

			$globalStylesheetRequired = 1;

			// format comment
			$name = esc_attr_x('Full Code Editor CSS', 'CSS comment', 'microthemer');
			$eq_str = $this->eq_str($name);
			$custom_code_comment = "/*= $name $eq_str */\n\n";

			// if the scss compiles in the browser
			if ($this->client_scss()){

				// log raw SCSS for writing to active-styles.scss
				$scss_custom_code.= $custom_code_comment . $this->options['non_section']['hand_coded_css'] ."\n";

				// include already compiled CSS
				if (!empty($this->options['non_section']['hand_coded_css_compiled'])){
					$custom_code.= $custom_code_comment . $this->options['non_section']['hand_coded_css_compiled'] ."\n";
				}
			}

			// server-side scss or no scss support
			else {
				$custom_code.= $custom_code_comment . $this->options['non_section']['hand_coded_css'] ."\n";
			}

		}

		// convert ui data to regular css output
		$this->convert_ui_data($this->options, $asset, 'regular');

		// convert ui data to media query css output
		if (!empty($this->options['non_section']['m_query']) and is_array($this->options['non_section']['m_query'])) {
			foreach ($this->preferences['m_queries'] as $key => $m_query) {
				// process media query if it has been in use at all
				if (!empty($this->options['non_section']['m_query'][$key]) and
				    is_array($this->options['non_section']['m_query'][$key])){
					$this->convert_ui_data($this->options['non_section']['m_query'][$key], $asset, 'mq', $key);
				}
			}
		}

		//$this->log('The total config', json_encode($asset['preference']['conditional']));

		// flag that some CSS will be output to the global stylesheet
		if ($asset['global']['data']){
			$globalStylesheetRequired = 1;
		}

		//wp_die('Styles: <pre>' . print_r($asset, 1) . '</pre>');

		// any animations have been found after iterating GUI options, include if necessary
		$anim_keyframes = '';

		if ( !empty($this->options['non_section']['meta']['animations']['names']) and
		     count($this->options['non_section']['meta']['animations']['names']) ){

			$globalStylesheetRequired = 1;

			// flag section with CSS comment
			$name = esc_attr_x('Animations', 'CSS comment', 'microthemer');
			$eq_str = $this->eq_str($name);
			$anim_keyframes.= "/*= $name $eq_str */\n\n";

			// get array of animation code
			$animations = array();
			include $this->thisplugindir . 'includes/animation/animation-code.inc.php';

			foreach ($this->options['non_section']['meta']['animations']['names'] as $animation_name => $one){

				// if we recognise the animation name, include the keyframe code
				if (!empty($animations[$animation_name])){
					$anim_keyframes.= $animations[$animation_name]['code'];
				}

			}
		}

		// join title, animations, custom code and GUI output in correct order
		$asset['global']['data'] = $title . $anim_keyframes . $custom_code . $asset['global']['data'];
		$asset['global']['scss_data'] = $title . $anim_keyframes . $scss_custom_code . $asset['global']['scss_data'];

		/** UPDATE PREFERENCES */

		// flag if global JS file should load
		$js_data = !empty($this->options['non_section']['js'])
			? trim($this->options['non_section']['js'])
			: '';
		$load_js = !empty($js_data);

		// save the google font values
		$g_fonts = $this->get_item(
			$this->options,
			array('non_section', 'meta', 'g_fonts')
		);

		// copy loading of global CSS/JS to asset_loading preference for ease of lookup
		$asset['preference']['global_g_fonts'] = !empty($g_fonts['g_fonts_used']) ? 1: 0;
		$asset['preference']['global_css'] = $globalStylesheetRequired ? 1: 0;
		$asset['preference']['global_js'] = $load_js ? 1: 0;
		$asset['global']['js_data'] = $js_data;
		$this->asset_loading_change = $this->checkAssetLoadingChange($asset['preference'], $prev_asset_loading);

		// core preference values
		$pref_array = array(
			'global_stylesheet_required' => $globalStylesheetRequired,
			'asset_loading' => $asset['preference'],
			'load_js' => $load_js,
			'active_events' => !empty($this->options['non_section']['active_events'])
				? $this->options['non_section']['active_events']
				: array(),
			'num_saves' => (++$this->preferences['num_saves'])
		);

		// google fonts
		$gf_keys = array('g_fonts_used', 'g_url', 'g_url_with_subsets', 'found_gf_subsets');
		foreach($gf_keys as $index => $key){
			$pref_array[$key] = !empty($g_fonts[$key]) ? $g_fonts[$key] : '';
		}

		// track number of unpublished saves
		if (empty($this->preferences['auto_publish_mode'])){
			$pref_array['num_unpublished_saves'] = ++$this->preferences['num_unpublished_saves'];
		}

		// update the recent logic array
		if (!empty($this->serialised_post['adjusted_logic']['update_recent_logic'])){

			// update recent logic data
			if (!empty($this->serialised_post['adjusted_logic']['expr'])){
				$pref_array['recent_logic'] = $this->updateRecentLogic(
					$this->serialised_post['adjusted_logic'],
					$this->preferences['recent_logic']
				);
			}
		}
		
		// set theme in focus (legacy)
		if ($activated_from != 'customised' and $context != __('Merge', 'microthemer')) {
			$pref_array['theme_in_focus'] = $activated_from;
			$pref_array['active_theme'] = $activated_from;
		}

		if ($context == __('Merge', 'microthemer') or $activated_from == 'customised') {
			$pref_array['active_theme'] = 'customised'; // a merge means a new custom configuration
		}

		if ($this->savePreferences($pref_array) and $activated_from != 'customised') {
			$this->log(
				esc_html__('Design pack activated', 'microthemer'),
				'<p>' . esc_html__('The design pack was successfully activated.', 'microthemer') . '</p>',
				'dev-notice'
			);
		}

		$this->updateAssetFiles($asset, $globalStylesheetRequired);

	}

	function updateRecentLogic($adjusted_logic, $prev_recent_logic){

		$existingLabelUpdated = false;
		$adjusted_expr = trim($adjusted_logic['expr']);
		$adjusted_label = !empty($adjusted_logic['label'])
			? trim($adjusted_logic['label'])
			: $adjusted_expr;
		$recent_logic = array();
		$max = 8; // let there be lots

		//$this->log('recent logic issue', 'updateRecentLogic: <pre>'.print_r($adjusted_logic, 1).'</pre>');

		foreach ($prev_recent_logic as $i => $array){

			//echo 'compare: ' . trim($array['logic']) . ' with: ' . $adjusted_expr . "\n";

			// update existing logic label if expressions match
			if (trim($array['logic']) === $adjusted_expr && !empty($adjusted_logic['label'])){
				$array['label'] = $adjusted_label;
				$array['value'] = $adjusted_label;
				$existingLabelUpdated = true;
			}

			// ensure we don't exceed max and that labels are unique
			if ($i < $max && ($existingLabelUpdated || trim($array['label']) !== $adjusted_label)){
				$recent_logic[] = $array;
			}
		}

		// prepend the new logic if an existing item wasn't updated
		if (!$existingLabelUpdated){
			array_unshift($recent_logic, array(
				'logic' => $adjusted_expr,
				'label' => $adjusted_label,
				'value' => $adjusted_label
			));
		}
		
		return $recent_logic;
	}

	function checkAssetLoadingChange($asset_loading, $prev_asset_loading){

		$change = array();

		// log any change in the Google font loading
		if ($asset_loading['global_g_fonts'] !== $prev_asset_loading['global_g_fonts']){
			$change[] = array(
				'key' => 'global_g_fonts',
				'action' => ($asset_loading['global_g_fonts'] ? 'added' : 'removed'),
			);
		}

		// log any change in the global CSS loading
		if ($asset_loading['global_css'] !== $prev_asset_loading['global_css']){
			$change[] = array(
				'key' => 'global_css',
				'action' => ($asset_loading['global_css'] ? 'added' : 'removed'),
			);
		}

		// log newly added conditional folders
		foreach ($asset_loading['conditional'] as $key => $on){

			if (empty($prev_asset_loading['conditional'][$key])){
				$change[] = array(
					'key' => $key,
					'action' => 'added',
				);
			}

			// they are both on, remove from previous asset loading array so any leftover have been removed
			else {
				unset($prev_asset_loading['conditional'][$key]);
			}
		}

		// log removed conditional folders
		if (count($prev_asset_loading['conditional'])) {
			foreach ( $prev_asset_loading['conditional'] as $key => $on) {
				$change[] = array(
					'key'    => $key,
					'action' => 'removed'
				);
			}
		}

		return count($change) ? $change : false;
	}

	function updateAssetFiles($asset, $globalStylesheetRequired){

		$root = $this->micro_root_dir;
		$conditionalDir = $root . 'mt/conditional/draft/';
		$global_css =  $root . 'draft-styles.css';
		$global_scss =  $root . 'draft-styles.scss';
		$global_js =  $root . 'draft-scripts.js';

		// The Gutenberg FSE editor will not load CSS in the iframe if the file does not contain
		// .wp-block. So ensure all MT CSS has this.
		$fseFix = $this->supportAdminAssets() ?  '.wp-block {} /*  (.wp-block {} ensures MT files load in Gutenberg editor) */' . "\n" : '';

		// we only minified published assets, if that preferences is set (on by default)
		/*$minifyCSS = !empty($this->preferences['minify_css']);
		$minifyJS = !empty($this->preferences['minify_js']);*/
		$minifyCSS = false;
		$minifyJS = false;

		// write any global styles
		if ($globalStylesheetRequired){
			$this->write_file($global_css, $fseFix . $asset['global']['data'], $minifyCSS, 'css');
		} elseif (file_exists($global_css)) {
			unlink($global_css);
		}

		/* debug
		 * $this->log('An update', json_encode([
			'$globalStylesheetRequired' => $globalStylesheetRequired,
			'file exists' => file_exists($global_css),
			'file' => $global_css
		]));*/

		// if Sass is enabled, write to global .scss file which holds all Sass (which has a single scope)
		if ($this->preferences['allow_scss']){
			$this->write_file($global_scss, $asset['global']['scss_data']);
		}

		// Write to global JavaScript file, or delete it
		if ($asset['global']['js_data']){
			$this->write_file($global_js, $asset['global']['js_data'], $minifyJS, 'js');
		} elseif (file_exists($global_js)) {
			unlink($global_js);
		}

		// write any conditional styles
		if (count($asset['conditional'])){

			// list of existing files to compare with current files, and possibly cleaned
			$previousFiles = $this->getDirectoryFileList($conditionalDir);
			$currentFiles = array();

			foreach ($asset['conditional'] as $folderSlug => $condSty){

				if (empty($condSty['data'])){
					continue;
				}

				$name = $folderSlug . '.css';
				$file = $conditionalDir . $name;
				$currentFiles[] = $name;

				$this->write_file($file, $fseFix . $condSty['data'], $minifyCSS, 'css');
			}

			// clean up any files that are no longer conditional / renamed
			$redundantFiles = array_diff($previousFiles, $currentFiles);
			foreach ($redundantFiles as $fileName){
				unlink($conditionalDir . $fileName);
			}
		}

		// publish settings if auto-publish is on
		if (!empty($this->preferences['auto_publish_mode'])){
			$this->publishSettings();
		}

	}

	// transform MT form settings into stylesheet data
	function convert_ui_data($ui_data, &$asset, $con, $mq_key = '1') {

		$tab = $sec_breaks = $mq_line = "";
		$sassToo = $this->client_scss();

		if ($con == 'mq') {

			// don't output media query if no values inside
			if (!$this->ui_data_has_values($ui_data, false)){
				return false;
			}

			// reset tracker that opening MQ has been added
			$asset['global']['mq_opened'] = 0;

			$mq_label = $this->preferences['m_queries'][$mq_key]['label'];
			$mq_query = $this->preferences['m_queries'][$mq_key]['query'];
			$tab = "\t";
			$sec_breaks = "";
			$mq_line = "\n/*( $mq_label )*/\n$mq_query {\n";
		}

		// loop through the sections
		foreach ($ui_data as $section_name => $array) {

			// check if the folder is empty but don't skip straight away, we need to assess folder logic
			$emptyFolder = !$this->section_has_values($section_name, $array, false);

			//$this->log('Empty folder ('.$section_name.'): '.$emptyFolder, 'More info');

			//echo 'empty folder ('.$section_name.'): '.$emptyFolder . "\n";

			// skip non_section stuff or empty sections
			if ($section_name == 'non_section'){ // || !$this->section_has_values($section_name, $array, false)
				continue;
			}

			// Get folder name
			$display_section_name = $this->get_folder_name_inc_legacy($section_name, $array);

			// we either update the global styles data or conditional styles
			$sectionLoading = !empty($this->options[$section_name]['this']['logic']['expr'])
				? 'conditional'
				: 'global';

			// update conditional folder stylesheet
			if ($sectionLoading === 'conditional'){

				// ensure data store is set
				if (!isset($asset['conditional'][$section_name])){

					// update the asset_loading preference data
					$asset['preference']['conditional'][$section_name] = $emptyFolder ? 'empty' : 1;
					$asset['preference']['logic'][] = array_merge(
						$this->options[$section_name]['this']['logic'],
						array(
							'slug' => $section_name,
							//'isEmpty' => $emptyFolder
						)
					);

					// Prepare asset data keys if the folder is not empty
					if (!$emptyFolder){
						$asset['conditional'][$section_name] = array(
							'data' => '',
							'scss_data' => '',
							'js_data' => '',
							'mq_opened' => 0
						);
					}
				}

				$dataToUpdate = &$asset['conditional'][$section_name];

				// flag that the folder is conditional
				//$display_section_name.= ' (conditional folder)'; // make this a clickable link in the ACE editor
			}

			// update the global stylesheet
			else {
				$dataToUpdate = &$asset['global'];
			}

			// Now that a potentially conditional folder has been flagged, we can continue if it's empty
			if ($emptyFolder){
				continue;
			}

			// start the opening media query bracket if not created yet
			if ($con == 'mq' && empty($dataToUpdate['mq_opened'])) {

				$this->updateStyleData($sassToo, $asset, $dataToUpdate, $mq_line);

				$dataToUpdate['mq_opened'] = 1;
			}

			// flag if the folder is disabled and
			$sectionIsDisabled = !empty($this->options[$section_name]['this']['disabled']);
			if ($sectionIsDisabled){
				$display_section_name.= ' ('.$this->dis_text.')';
			}

			// make sections same width by adding extra = and accounting for char length
			$eq_str = $this->eq_str($display_section_name);
			$section_comment = $sec_breaks."\n$tab/*= $display_section_name $eq_str */\n\n";

			// Add the section folder name comment regardless of whether styles are omitted
			// not for media queries as that's too many unused folder comments
			if ($con !== 'mq') {
				$this->updateStyleData($sassToo, $asset, $dataToUpdate, $section_comment);
			}

			// if section disabled, continue
			if ($sectionIsDisabled) {
				continue;
			}

			// loop the CSS selectors - section_has_values() already tells us array is good
			foreach ( $array as $css_selector => $sub_array ) {

				// skip this or empty selectors
				if ($css_selector == 'this' or
				    !$this->selector_has_values($section_name, $css_selector, $sub_array, false)) {
					continue;
				}

				// sort out the css selector - need to get css label/code from regular ui array
				if ($con == 'mq') {
					$sub_array['label'] = $this->options[$section_name][$css_selector]['label'];
				}
				$label_array = explode('|', $sub_array['label']);
				$css_label = $label_array[0];
				$sel_disabled = false;
				if (!empty($sub_array['tab']['disabled']) ||
				    !empty($this->options[$section_name][$css_selector]['disabled'])) {
					$sel_disabled = true;
					$css_label.= ' ('.$this->dis_text.')';
				}

				// output sel comment
				$selector_comment = "$tab/** $display_section_name >> $css_label **/\n$tab";
				$this->updateStyleData($sassToo, $asset, $dataToUpdate, $selector_comment);

				// move on if sel disabled
				if ($sel_disabled) {
					continue;
				}

				// add selector data
				$this->updateStyleData(
					$sassToo,
					$asset,
					$dataToUpdate,
					$this->normalise_tabs(
						$this->normalise_line_breaks($sub_array['compiled_css']), $tab
					),
					($sassToo
						? $this->normalise_tabs(
							$this->normalise_line_breaks($sub_array['raw_scss']), $tab, true
						)
						: null
					)

				);

			}
		}

		// Close media query bracket
		if ($con == 'mq') {

			$close_mq = "}\n\n";

			// add for global stylesheet if opened
			if (!empty($asset['global']['mq_opened'])){
				$this->updateStyleData($sassToo, $asset, $asset['global'], $close_mq);
			}

			// add for any conditional stylesheets
			foreach ($asset['conditional'] as &$dataToUpdate){

				if (!empty($dataToUpdate['mq_opened'])){

					$this->updateStyleData($sassToo, $asset, $dataToUpdate, $close_mq);

					// reset for next MQ that calls this method
					$dataToUpdate['mq_opened'] = 0;
				}
			}

		}



		//return $asset;
	}

	// update the css and possible sass data strings
	function updateStyleData($sassToo, &$asset, &$dataToUpdate, $string, $string2 = false){

		$dataToUpdate['data'].= $string;

		// for now, we still add all SCSS to a single global file.
		// This kind of makes sense because the Sass is processed in a single global scope,
		// Even if the output CSS is distributed between separate files
		if ($sassToo){
			$asset['global']['scss_data'].= ($string2 ?: $string);
		}

	}


}