<?php

class HTMLDisplayHandler
{
	/**
	 * jQuery versions
	 */
	public const JQUERY_V2 = '2.2.4';
	public const JQUERY_V2_MIGRATE = '1.4.1';
	public const JQUERY_V3 = '3.6.3';
	public const JQUERY_V3_MIGRATE = '3.4.0';

	/**
	 * Default viewport setting
	 */
	public const DEFAULT_VIEWPORT = 'width=device-width, initial-scale=1.0, user-scalable=yes';

	/**
	 * Reserved scripts
	 */
	public static $reservedCSS = '@\bcommon/css/(?:xe|rhymix|mobile)\.(?:min\.)?(?:s?css|less)$@';
	public static $reservedJS = '@\bcommon/js/(?:jquery(?:-[123][0-9.x-]+)?|xe?|common|js_app|xml_handler|xml_js_filter)\.(?:min\.)?js$@';

	/**
	 * List of scripts to block loading
	 */
	public static $blockedScripts = array(
		'@(?:^|/)j[Qq]uery(?:-[0-9]+(?:\.[0-9x]+)*|-latest)?(?:\.min)?\.js$@',
	);

	/**
	 * Replacement table for XE compatibility
	 */
	public static $replacements = array(
		'@\bcommon/xeicon/@' => 'common/css/xeicon/',
		'@\beditor/skins/xpresseditor/js/xe_textarea\.(?:min\.)?js@' => 'editor/skins/ckeditor/js/xe_textarea.js',
		'@/lang$@' => '/lang/lang.xml',
	);

	/**
	 * Image type information for SEO
	 */
	protected $_image_type = 'none';

	/**
	 * Produce HTML compliant content given a module object.\n
	 * @param ModuleObject $oModule the module object
	 * @return string compiled template string
	 */
	public function toDoc(&$oModule)
	{
		// SECISSUE https://github.com/xpressengine/xe-core/issues/1583
		$oSecurity = new Security();
		$oSecurity->encodeHTML('is_keyword', 'search_keyword', 'search_target', 'order_target', 'order_type');

		$template_path = $oModule->getTemplatePath();
		$template_file = $oModule->getTemplateFile();

		if(!is_dir($template_path))
		{
			if($oModule->module_info->module == $oModule->module)
			{
				$skin = $oModule->origin_module_info->skin;
			}
			else
			{
				$skin = $oModule->module_config->skin;
			}

			if(Context::get('module') != 'admin' && strpos(Context::get('act'), 'Admin') === false)
			{
				if($skin && is_string($skin))
				{
					$theme_skin = explode('|@|', $skin);
					$template_path = $oModule->getTemplatePath();
					if(count($theme_skin) == 2)
					{
						$theme_path = sprintf('./themes/%s', $theme_skin[0]);
						// FIXME $theme_path $theme_path $theme_path ??
						if(substr($theme_path, 0, strlen($theme_path)) != $theme_path)
						{
							$template_path = sprintf('%s/modules/%s/', $theme_path, $theme_skin[1]);
						}
					}
				}
				else
				{
					$template_path = $oModule->getTemplatePath();
				}
			}
			else
			{
				$template_path = $oModule->getTemplatePath();
			}
		}

		$oTemplate = new Rhymix\Framework\Template($template_path, $template_file);
		$output = $oTemplate->compile();

		// add .x div for adminitration pages
		if(Context::getResponseMethod() == 'HTML')
		{
			$x_exclude_actions = array(
				'dispPageAdminContentModify' => true,
				'dispPageAdminMobileContentModify' => true,
				'dispPageAdminMobileContent' => true,
			);
			$current_act = strval(Context::get('act'));
			if(Context::get('module') != 'admin' && strpos($current_act, 'Admin') !== false && !isset($x_exclude_actions[$current_act]))
			{
				$output = '<div class="x">' . $output . '</div>';
			}

			// Wrap content in layout
			$use_layout = Context::get('layout') !== 'none';
			if (!$use_layout && isset($_REQUEST['layout']) && !self::isPartialPageRendering())
			{
				$use_layout = true;
			}
			if ($use_layout)
			{
				$start = microtime(true);

				Context::set('content', $output, false);

				$layout_path = $oModule->getLayoutPath();
				$layout_file = $oModule->getLayoutFile();

				$edited_layout_file = $oModule->getEditedLayoutFile();

				// get the layout information currently requested
				$oLayoutModel = getModel('layout');
				$layout_info = Context::get('layout_info');
				$layout_srl = $layout_info->layout_srl ?? 0;

				// compile if connected to the layout
				if($layout_srl > 0)
				{
					// handle separately if the layout is faceoff
					if($layout_info && isset($layout_info->type) && $layout_info->type == 'faceoff')
					{
						$oLayoutModel->doActivateFaceOff($layout_info);
						Context::set('layout_info', $layout_info);
					}

					// search if the changes CSS exists in the admin layout edit window
					$edited_layout_css = $oLayoutModel->getUserLayoutCss($layout_srl);

					if(FileHandler::exists($edited_layout_css))
					{
						Context::loadFile(array($edited_layout_css, 'all', '', 100));
					}
				}
				if (!$layout_path)
				{
					$layout_path = './common/tpl';
				}
				if (!$layout_file)
				{
					if ($layout_path === './common/tpl')
					{
						$layout_file = 'default_layout';
					}
					else
					{
						$layout_file = 'layout';
					}
				}

				$oTemplate = new Rhymix\Framework\Template;
				$output = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file);

				// Add layout header script.
				if ($layout_srl > 0)
				{
					$part_config = ModuleModel::getModulePartConfig('layout', $layout_srl);
					if ($part_config && isset($part_config->header_script))
					{
						Context::addHtmlHeader($part_config->header_script, true);
					}
				}

				// if popup_layout, remove admin bar.
				$realLayoutPath = FileHandler::getRealPath($layout_path);
				if(substr_compare($realLayoutPath, '/', -1) !== 0)
				{
					$realLayoutPath .= '/';
				}

				$pathInfo = pathinfo($layout_file);
				$onlyLayoutFile = $pathInfo['filename'];

				Rhymix\Framework\Debug::addTime('layout', microtime(true) - $start);
			}
		}

		// Add OpenGraph and Twitter metadata
		if (config('seo.og_enabled') && Context::get('module') !== 'admin')
		{
			$this->_addOpenGraphMetadata();
			if (config('seo.twitter_enabled'))
			{
				$this->_addTwitterMetadata();
			}
		}

		// set icon
		$site_module_info = Context::get('site_module_info');
		$oAdminModel = getAdminModel('admin');
		$favicon_url = $oAdminModel->getFaviconUrl($site_module_info->domain_srl);
		$mobicon_url = $oAdminModel->getMobileIconUrl($site_module_info->domain_srl);
		Context::set('favicon_url', $favicon_url);
		Context::set('mobicon_url', $mobicon_url);

		// Only print the X-UA-Compatible meta tag if somebody is still using IE
		if (preg_match('!Trident/7\.0!', $_SERVER['HTTP_USER_AGENT'] ?? ''))
		{
			Context::addMetaTag('X-UA-Compatible', 'IE=edge', true);
		}

		return $output;
	}

	/**
	 * Check if partial page rendering (dropping the layout) is enabled.
	 *
	 * @return bool
	 */
	public static function isPartialPageRendering()
	{
		$ppr = config('view.partial_page_rendering') ?? 'internal_only';
		if ($ppr === 'disabled')
		{
			return false;
		}
		elseif ($ppr === 'ajax_only' && empty($_SERVER['HTTP_X_REQUESTED_WITH']))
		{
			return false;
		}
		elseif ($ppr === 'internal_only' && (!isset($_SERVER['HTTP_REFERER']) || !Rhymix\Framework\URL::isInternalURL($_SERVER['HTTP_REFERER'])))
		{
			return false;
		}
		elseif ($ppr === 'except_robots' && isCrawler())
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * when display mode is HTML, prepare code before print.
	 * @param string $output compiled template string
	 * @return void
	 */
	public function prepareToPrint(&$output)
	{
		if(Context::getResponseMethod() != 'HTML')
		{
			return;
		}

		$start = microtime(true);

		// move <style>...</style> in body to the header
		$output = preg_replace_callback('!<style(.*?)>(.*?)<\/style>!is', array($this, '_moveStyleToHeader'), $output);

		// move <link> and <meta> in body to the header
		$output = preg_replace_callback('!<(link|meta)\b(.*?)>!is', array($this, '_moveLinkToHeader'), $output);

		// change a meta fine(widget often put the tag like <!--Meta:path--> to the content because of caching)
		$output = preg_replace_callback('/<!--(#)?Meta:([a-z0-9\_\-\/\.\@\:]+)(\?\$\_\_Context\-\>[a-z0-9\_\-\/\.\@\:\>]+)?-->/is', array($this, '_transMeta'), $output);

		// handles a relative path generated by using the rewrite module
		if(Context::isAllowRewrite())
		{
			$pattern = '/(action)=(["\'])(["\'])/s';
			$output = preg_replace($pattern, '$1=$2' . \RX_BASEURL . '$3', $output);

			$pattern = '/(action|poster|src|href)=(["\'])\.\/([^"\']*)(["\'])/s';
			$output = preg_replace($pattern, '$1=$2' . \RX_BASEURL . '$3$4', $output);

			$pattern = '/src=(["\'])((?:files\/(?:attach|cache|faceOff|member_extra_info|thumbnails)|addons|common|(?:m\.)?layouts|modules|widgets|widgetstyle)\/[^"\']+)(["\'])/s';
			$output = preg_replace($pattern, 'src=$1' . \RX_BASEURL . '$2$3', $output);

			$pattern = '/href=(["\'])(\?[^"\']+)/s';
			$output = preg_replace($pattern, 'href=$1' . \RX_BASEURL . '$2', $output);
		}

		// prevent the 2nd request due to url(none) of the background-image
		$output = preg_replace('/url\((["\']?)none(["\']?)\)/is', 'none', $output);

		$INPUT_ERROR = Context::get('INPUT_ERROR');
		if(is_array($INPUT_ERROR) && count($INPUT_ERROR))
		{
			$keys = array_map(function($str) { return preg_quote($str, '@'); }, array_keys($INPUT_ERROR));
			$keys = '(' . implode('|', $keys) . ')';

			$output = preg_replace_callback('@(<input)([^>]*?)\sname="' . $keys . '"([^>]*?)/?>@is', array(&$this, '_preserveValue'), $output);
			$output = preg_replace_callback('@<select[^>]*\sname="' . $keys . '".+</select>@isU', array(&$this, '_preserveSelectValue'), $output);
			$output = preg_replace_callback('@<textarea[^>]*\sname="' . $keys . '".+</textarea>@isU', array(&$this, '_preserveTextAreaValue'), $output);
		}

		Rhymix\Framework\Debug::addTime('trans_content', microtime(true) - $start);

		// Remove unnecessary information
		$output = preg_replace('/member\_\-([0-9]+)/s', 'member_0', $output);

		// convert the final layout
		Context::set('content', $output);
		if(Mobile::isFromMobilePhone())
		{
			$this->_loadMobileJSCSS();
		}
		else
		{
			$this->_loadDesktopJSCSS();
		}
		$oTemplate = new Rhymix\Framework\Template('./common/tpl', 'common_layout');
		$output = $oTemplate->compile();

		// replace the user-defined-language
		$output = Context::replaceUserLang($output);

		// remove template path comment tag
		/*
		if(!Rhymix\Framework\Debug::isEnabledForCurrentUser())
		{
			$output = preg_replace('/\n<!-- Template (?:start|end) : .*? -->\r?\n/', "\n", $output);
		}
		*/
	}

	/**
	 * when display mode is HTML, prepare code before print about <input> tag value.
	 * @param array $match input value.
	 * @return string input value.
	 */
	function _preserveValue($match)
	{
		$INPUT_ERROR = Context::get('INPUT_ERROR');
		if (!is_scalar($INPUT_ERROR[$match[3]]))
		{
			return $match[0];
		}

		$str = $match[1] . $match[2] . ' name="' . $match[3] . '"' . $match[4];

		// get type
		$type = 'text';
		if(preg_match('/\stype="([^"]+)"/i', $str, $m))
		{
			$type = strtolower($m[1]);
		}

		switch($type)
		{
			case 'radio':
			case 'checkbox':
				if(preg_match('@\s(?i:value)="' . preg_quote($INPUT_ERROR[$match[3]], '@') . '"@', $str))
				{
					$str = preg_replace('@\schecked(="[^"]*?")?@', ' checked="checked"', $str);
				}
				break;
			default:
				if (!preg_match('@\svalue="([^"]*?)"@', $str))
				{
					$str = $str . ' value=""';
				}
				$str = preg_replace_callback('@\svalue="([^"]*?)"@', function() use($INPUT_ERROR, $match) {
					return ' value="' . escape($INPUT_ERROR[$match[3]], true) . '"';
				}, $str);
		}

		return $str . ' />';
	}

	/**
	 * when display mode is HTML, prepare code before print about <select> tag value.
	 * @param array $matches select tag.
	 * @return string select tag.
	 */
	function _preserveSelectValue($matches)
	{
		$INPUT_ERROR = Context::get('INPUT_ERROR');
		preg_replace('@\sselected(="[^"]*?")?@', ' ', $matches[0]);
		preg_match('@<select.*?>@is', $matches[0], $mm);

		preg_match_all('@<option[^>]*\svalue="([^"]*)".+</option>@isU', $matches[0], $m);

		$key = array_search($INPUT_ERROR[$matches[1]], $m[1]);
		if($key === FALSE)
		{
			return $matches[0];
		}

		$m[0][$key] = preg_replace('@(\svalue=".*?")@is', '$1 selected="selected"', $m[0][$key]);

		return $mm[0] . implode('', $m[0]) . '</select>';
	}

	/**
	 * when display mode is HTML, prepare code before print about <textarea> tag value.
	 * @param array $matches textarea tag information.
	 * @return string textarea tag
	 */
	function _preserveTextAreaValue($matches)
	{
		$INPUT_ERROR = Context::get('INPUT_ERROR');
		preg_match('@<textarea.*?>@is', $matches[0], $mm);
		return $mm[0] . escape($INPUT_ERROR[$matches[1]], true) . '</textarea>';
	}

	/**
	 * Move <style> in the document body to the <head> section.
	 *
	 * @param array $matches
	 * @return void
	 */
	function _moveStyleToHeader($matches)
	{
		if(isset($matches[1]) && stristr($matches[1], 'scoped'))
		{
			return $matches[0];
		}
		Context::addHtmlHeader($matches[0]);
	}

	/**
	 * Move <link> and <meta> in the document body to the <head> section.
	 *
	 * @param array $matches
	 * @return void
	 */
	function _moveLinkToHeader($matches)
	{
		if ($matches[1] === 'link' && preg_match('/\brel="([^"]+)"/', $matches[2], $rel) && $rel[1] !== 'stylesheet' && preg_match('/\bhref="([^"]+)"/', $matches[2], $href))
		{
			Context::addLink($href[1], $rel[1]);
		}
		else
		{
			Context::addHtmlHeader($matches[0]);
		}
	}

	/**
	 * add given .css or .js file names in widget code to Context
	 * @param array $matches
	 * @return void
	 */
	function _transMeta($matches)
	{
		if($matches[1])
		{
			return '';
		}
		if($matches[3] ?? false)
		{
			$vars = Context::get(str_replace('?$__Context->', '', $matches[3]));
			Context::loadFile(array($matches[2], null, null, null, $vars));
		}
		else
		{
			Context::loadFile($matches[2]);
		}
	}

	/**
	 * Add OpenGraph metadata tags.
	 *
	 * @return void
	 */
	function _addOpenGraphMetadata()
	{
		// Get information about the current request.
		$page_type = 'website';
		$current_module_info = Context::get('current_module_info');
		$site_module_info = Context::get('site_module_info');
		$document_srl = Context::get('document_srl');
		$grant = Context::get('grant');
		$permitted = isset($grant->access) ? $grant->access : false;
		if (isset($grant->view) && !$grant->view)
		{
			$permitted = false;
		}
		if ($document_srl && $permitted)
		{
			if (isset($grant->consultation_read) && !$grant->consultation_read && $current_module_info->consultation === 'Y')
			{
				$permitted = false;
			}
			else
			{
				$oDocument = Context::get('oDocument') ?: DocumentModel::getDocument($document_srl, false, false);
				if (is_object($oDocument) && $oDocument->document_srl == $document_srl)
				{
					$page_type = 'article';
					if (method_exists($oDocument, 'isSecret') && $oDocument->isSecret())
					{
						$permitted = false;
					}
				}
			}
		}

		// Get existing metadata.
		$og_data = array();
		foreach (Context::getOpenGraphData() as $val)
		{
			$og_data[$val['property']] = $val['content'];
		}

		// Add basic metadata.
		Context::addOpenGraphData('og:title', $permitted ? Context::getBrowserTitle() : lang('msg_not_permitted'));
		Context::addOpenGraphData('og:site_name', Context::getSiteTitle());
		if (!isset($og_data['og:description']) || !Context::getMetaTag('description'))
		{
			if ($page_type === 'article' && $permitted && config('seo.og_extract_description'))
			{
				$description = trim(utf8_normalize_spaces($oDocument->getContentText(200)));
			}
			else
			{
				$description = Context::getMetaTag('description');
			}
			Context::addOpenGraphData('og:description', $description);
			Context::addMetaTag('description', $description);
		}

		// Add metadata about this page.
		if (!isset($og_data['og:type']))
		{
			Context::addOpenGraphData('og:type', $page_type);
		}
		if (!isset($og_data['og:url']) || !Context::getCanonicalURL())
		{
			if ($page_type === 'article')
			{
				$canonical_url = getNotEncodedFullUrl('', 'mid', $current_module_info->mid, 'document_srl', $document_srl);
			}
			elseif (($page = Context::get('page')) > 1)
			{
				$canonical_url = getNotEncodedFullUrl('', 'mid', $current_module_info->mid, 'page', $page);
			}
			elseif (isset($current_module_info->module_srl) && $current_module_info->module_srl == ($site_module_info->module_srl ?? 0))
			{
				$canonical_url = getNotEncodedFullUrl('');
			}
			else
			{
				if (Rhymix\Framework\Router::getRewriteLevel() === 2 && Context::getCurrentRequest()->url !== '')
				{
					$canonical_url = Rhymix\Framework\URL::getCurrentDomainURL(\RX_BASEURL . preg_replace('/\?.*$/', '', \RX_REQUEST_URL));
				}
				else
				{
					$canonical_url = getNotEncodedFullUrl('', 'mid', $current_module_info->mid);
				}
			}
			Context::setCanonicalURL($canonical_url);
		}

		// Add metadata about the locale.
		$lang_type = Context::getLangType();
		$locales = (include \RX_BASEDIR . 'common/defaults/locales.php');
		if (isset($locales[$lang_type]))
		{
			Context::addOpenGraphData('og:locale', $locales[$lang_type]['locale']);
		}
		if ($page_type === 'article' && $permitted && $oDocument->getLangCode() !== $lang_type && isset($locales[$oDocument->getLangCode()]))
		{
			Context::addOpenGraphData('og:locale:alternate', $locales[$oDocument->getLangCode()]);
		}

		// Add image.
		if ($document_images = Context::getMetaImages())
		{
			// pass
		}
		elseif ($page_type === 'article' && $permitted && config('seo.og_extract_images'))
		{
			if (($document_images = Rhymix\Framework\Cache::get("seo:document_images:$document_srl")) === null)
			{
				$document_images = array();
				if ($oDocument->hasUploadedFiles())
				{
					$document_files = $oDocument->getUploadedFiles();
					usort($document_files, function($a, $b) {
						return ord($b->cover_image) - ord($a->cover_image);
					});

					foreach ($document_files as $file)
					{
						if ($file->isvalid !== 'Y' || !preg_match('/\.(?:bmp|gif|jpe?g|png|webp|mp4)$/i', $file->uploaded_filename))
						{
							continue;
						}

						if (str_starts_with($file->mime_type, 'video/'))
						{
							if ($file->thumbnail_filename)
							{
								list($width, $height) = @getimagesize($file->thumbnail_filename);
								if ($width >= 100 || $height >= 100)
								{
									$document_images[] = array('filepath' => $file->thumbnail_filename, 'width' => $width, 'height' => $height);
									break;
								}
							}
						}
						else
						{
							list($width, $height) = @getimagesize($file->uploaded_filename);
							if ($width >= 100 || $height >= 100)
							{
								$document_images[] = array('filepath' => $file->uploaded_filename, 'width' => $width, 'height' => $height);
								break;
							}
						}
					}
				}
				Rhymix\Framework\Cache::set("seo:document_images:$document_srl", $document_images);
			}
		}
		else
		{
			$document_images = null;
		}

		if ($document_images)
		{
			$first_image = array_first($document_images);
			$first_image['filepath'] = preg_replace('/^.\\/files\\//', \RX_BASEURL . 'files/', $first_image['filepath']);
			Context::addOpenGraphData('og:image', Rhymix\Framework\URL::getCurrentDomainURL($first_image['filepath']));
			Context::addOpenGraphData('og:image:width', $first_image['width']);
			Context::addOpenGraphData('og:image:height', $first_image['height']);
			$this->_image_type = 'document';
		}
		elseif ($default_image = getAdminModel('admin')->getSiteDefaultImageUrl($site_module_info->domain_srl, $width, $height))
		{
			Context::addOpenGraphData('og:image', Rhymix\Framework\URL::getCurrentDomainURL($default_image));
			if ($width && $height)
			{
				Context::addOpenGraphData('og:image:width', $width);
				Context::addOpenGraphData('og:image:height', $height);
			}
			$this->_image_type = 'site';
		}
		else
		{
			$this->_image_type = 'none';
		}

		// Add tags and hashtags for articles.
		if ($page_type === 'article' && $permitted)
		{
			$tags = $oDocument->getTags();
			foreach ($tags as $tag)
			{
				if ($tag !== '')
				{
					Context::addOpenGraphData('og:article:tag', $tag);
				}
			}

			if (config('seo.og_extract_hashtags'))
			{
				$hashtags = $oDocument->getHashtags();
				foreach ($hashtags as $hashtag)
				{
					if (!in_array($hashtag, $tags))
					{
						Context::addOpenGraphData('og:article:tag', escape($hashtag, false));
					}
				}
			}

			Context::addOpenGraphData('og:article:section', Context::replaceUserLang($current_module_info->browser_title));
		}

		// Add author name for articles.
		if ($page_type === 'article' && $permitted && config('seo.og_use_nick_name'))
		{
			Context::addMetaTag('author', $oDocument->getNickName());
			Context::addOpenGraphData('og:article:author', $oDocument->getNickName());
		}

		// Add datetime for articles.
		if ($page_type === 'article' && $permitted && config('seo.og_use_timestamps'))
		{
			Context::addOpenGraphData('og:article:published_time', $oDocument->getRegdate('c'));
			Context::addOpenGraphData('og:article:modified_time', $oDocument->getUpdate('c'));
		}
	}

	/**
	 * Add Twitter metadata tags.
	 *
	 * @return void
	 */
	function _addTwitterMetadata()
	{
		$card_type = $this->_image_type === 'document' ? 'summary_large_image' : 'summary';
		Context::addMetaTag('twitter:card', $card_type, false, false);

		foreach(Context::getOpenGraphData() as $val)
		{
			if ($val['property'] === 'og:title')
			{
				Context::addMetaTag('twitter:title', $val['content'], false, false);
			}
			if ($val['property'] === 'og:description')
			{
				Context::addMetaTag('twitter:description', $val['content'], false, false);
			}
			if ($val['property'] === 'og:image' && $this->_image_type === 'document')
			{
				Context::addMetaTag('twitter:image', $val['content'], false, false);
			}
		}
	}

	/**
	 * import basic .js files.
	 * @return void
	 */
	public function _loadDesktopJSCSS()
	{
		$this->_loadCommonJSCSS();
	}

	/**
	 * import basic .js files for mobile
	 */
	private function _loadMobileJSCSS()
	{
		$this->_loadCommonJSCSS();
	}

	/**
	 * import common .js and .css files for (both desktop and mobile)
	 */
	private function _loadCommonJSCSS()
	{
		if (config('view.jquery_version') === 3)
		{
			$jquery_version = self::JQUERY_V3;
			$jquery_migrate_version = self::JQUERY_V3_MIGRATE;
		}
		else
		{
			$jquery_version = self::JQUERY_V2;
			$jquery_migrate_version = self::JQUERY_V2_MIGRATE;
		}

		Context::loadFile(array('./common/css/rhymix.scss', '', '', -1600000000), true);

		$original_file_list = array(
			'plugins/jquery.migrate/jquery-migrate-' . $jquery_migrate_version . '.min.js',
			'plugins/cookie/js.cookie.min.js',
			'plugins/blankshield/blankshield.min.js',
			'plugins/uri/URI.min.js',
		);

		if (str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'Trident/'))
		{
			$original_file_list[] = 'polyfills/formdata.min.js';
			$original_file_list[] = 'polyfills/promise.min.js';
		}

		$original_file_list[] = 'x.js';
		$original_file_list[] = 'common.js';
		$original_file_list[] = 'js_app.js';
		$original_file_list[] = 'xml_handler.js';
		$original_file_list[] = 'xml_js_filter.js';

		if(config('view.minify_scripts') === 'none')
		{
			Context::loadFile(array('./common/js/jquery-' . $jquery_version . '.js', 'head', '', -1800000000), true);
			foreach($original_file_list as $filename)
			{
				Context::loadFile(array('./common/js/' . $filename, 'head', '', -1700000000), true);
			}
		}
		else
		{
			Context::loadFile(array('./common/js/jquery-' . $jquery_version . '.min.js', 'head', '', -1800000000), true);
			$concat_target_filename = 'files/cache/assets/minified/rhymix.min.js';
			if(file_exists(\RX_BASEDIR . $concat_target_filename))
			{
				$concat_target_mtime = filemtime(\RX_BASEDIR . $concat_target_filename);
				$original_mtime = 0;
				foreach($original_file_list as $filename)
				{
					$original_mtime = max($original_mtime, filemtime(\RX_BASEDIR . 'common/js/' . $filename));
				}
				if($concat_target_mtime > $original_mtime)
				{
					Context::loadFile(array('./' . $concat_target_filename, 'head', '', -1700000000), true);
					return;
				}
			}
			Rhymix\Framework\Formatter::minifyJS(array_map(function($str) {
				return \RX_BASEDIR . 'common/js/' . $str;
			}, $original_file_list), \RX_BASEDIR . $concat_target_filename);
			Context::loadFile(array('./' . $concat_target_filename, 'head', '', -1700000000), true);
		}
	}
}
