示例#1
0
/**
 * Utility function to translate a filesize in bytes into a human-readable version.
 *
 * @param int $filesize Filesize in bytes
 * @param int $round Precision to round to
 *
 * @return string
 */
function format_size($filesize, $round = 2) {
	$suf = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
	$c   = 0;
	while ($filesize >= 1024) {
		$c++;
		$filesize = $filesize / 1024;
	}

	return I18NLoader::FormatNumber($filesize, $round) . ' ' . $suf[$c];
}
/**
 * Display a filesize in a human-readable format.
 *
 * #### Example Usage
 *
 * <pre>
 * {filesize 123} => "123 bytes"
 * {filesize 2048} => "2 kiB"
 * </pre>
 *
 * @param array  $params  Associative (and/or indexed) array of smarty parameters passed in from the template
 * @param Smarty $smarty  Parent Smarty template object
 *
 * @return string
 * @throws SmartyException
 */
function smarty_function_filespeed($params, $smarty){
	
	$size = $params[0];
	
	$suf = array('bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps', 'Ebps', 'Zbps', 'Ybps');
	$c   = 0;
	while ($size >= 1024) {
		$c++;
		$size = $size / 1024;
	}

	return \Core\i18n\I18NLoader::FormatNumber($size, 1) . ' ' . $suf[$c];
}
示例#3
0
		$default = str_replace('"', '\\"', $keyData['results']['FALLBACK']);
		$output .= implode("\n;", explode("\n", $default)) . "\";\n";
		if($keyData['found']){
			$output .= $keyData['key'] . " = \"" . str_replace('"', '\\"', $keyData['match_str']) . "\";\n";
		}
		else{
			$output .= $keyData['key'] . " = \"\";\n";
		}
		$output .= "\n";
	}
	
	// Now generate any dialect-specific keys.
	foreach($baseData['dialects'] as $dialect){
		$output .= "; Dialect-specific overrides for " . $dialect . "\n[" . $dialect . "]\n";
		foreach($matches as $m){
			$keyData = \Core\i18n\I18NLoader::Get($m, $dialect);
			if($keyData['found'] && $keyData['match_key'] == $dialect){
				// This specific dialect has an override.
				$output .= $keyData['key'] . " = \"" . str_replace('"', '\\"', $keyData['match_str']) . "\";\n";
			}
		}
		$output .= "\n";
	}

	if($arguments->getArgumentValue('dry-run')){
		echo $output;
	}
	else{
		// Write this output to the requested ini file!
		$file = \Core\Filestore\Factory::File($dir . '/i18n/' . $lang . '.ini');
		$file->putContents($output);
示例#4
0
	public function view(){
		$v = $this->getView();

		$pages = PageModel::Find(array('admin' => '1'));
		$groups = array();
		$flatlist = array();

		if(isset($_SESSION['user_sudo'])){
			$p = new PageModel('/user/sudo');
			$p->set('title', 'Exit SUDO Mode');
			$groups['SUDO'] = [
				'title' => 'SUDO',
				'href' => '',
				'children' => [
					'Exit SUDO Mode' => $p,
				],
			];
			$flatlist[ 'Exit SUDO Mode' ] = $p;
		}


		if(\Core\user()){
			foreach($pages as $p){
				/** @var PageModel $p */
				if(!\Core\user()->checkAccess($p->get('access'))) continue;

				// Pages can define which sub-menu they get grouped under.
				// The 'Admin' submenu is the default.
				$group = $p->get('admin_group') ? $p->get('admin_group') : 't:STRING_ADMIN';
				// Support i18n here!
				if(strpos($group, 't:') === 0){
					$group = t(substr($group, 2));
				}

				if(!isset($groups[$group])){
					$groups[$group] = [
						'title'    => $group,
						'href'     => '',
						'children' => [],
					];
				}

				if($p->get('baseurl') == '/admin'){
					// Admin gets special treatment.
					$groups[t('STRING_ADMIN')]['href'] = '/admin';
					continue;
				}

				switch($p->get('title')){
					case 'System Configuration':
						$p->set('title', "System Config");
						break;
					case 'Navigation Listings':
						$p->set('title', "Navigation");
						break;
					case 'Content Page Listings':
						$p->set('title', "Content Pages");
						break;
					default:
						$p->set(
							'title',
							trim( str_replace(['Administration', 'Admin'],'', $p->get('title')) )
						);
				}

				$title = $p->get('title');
				// Support i18n here!
				if(strpos($title, 't:') === 0){
					$title = t(substr($title, 2));
				}

				if(isset($groups[$title])){
					// Link the main group to this page instead of an empty link.
					// This removes duplicate links such as the group "User" and page "User".
					$groups[$title]['href'] = $p->get('rewriteurl');
				}
				else{
					// The new grouped pages
					$groups[$group]['children'][ $title ] = $p;
					// And the flattened list to support legacy templates.
					$flatlist[ $title ] = $p;
				}
			}

			// This is a hack to make sure that users can view the /admin link if they can view other admin pages.
			/*if(sizeof($flatlist) && !isset($groups['Admin']['Dashboard'])){
				$p = new PageModel('/admin');
				$p->set('title', 'Dashboard');
				$groups['Admin']['Dashboard'] = $p;
			}*/
		}

		ksort($flatlist);
		ksort($groups);

		foreach($groups as $gname => $dat){
			ksort($groups[$gname]['children']);
		}

		// Build a list of languages that can be set by the user.
		$locales = \Core\i18n\I18NLoader::GetLocalesEnabled();
		$selected = \Core\i18n\I18NLoader::GetUsersLanguage();
		$languages = [];
		if(sizeof($locales) > 1){
			// There is at least 1 language available on the system, YAY!
			foreach($locales as $localeKey => $localeDat){
				if(($pos = strpos($localeKey, '_')) !== false){
					// This locale contains an underscore, that means it has a corresponding country!
					// These are what we want to display to the end user.
					$country = substr($localeKey, $pos+1);

					// Here I am retrieving the language and dialect in the native dialect if at all possible.
					// This is because if you as a user only can read your native language and your browser renders something different,
					// then you want to be able to read what you're switching it to.
					$str1 = new \Core\i18n\I18NString($localeDat['lang']);
					$str1->setLanguage($localeKey);
					$localeTitle = $str1->getTranslation();
					if($localeDat['dialect']){
						$str2 = new \Core\i18n\I18NString($localeDat['dialect']);
						$str2->setLanguage($localeKey);
						$localeTitle .= ' (' . $str2->getTranslation() . ')';
					}

					$languages[] = [
						'key'      => $localeKey,
					    'title'    => $localeTitle,
					    'country'  => $country,
					    'image'    => 'assets/images/iso-country-flags/' . strtolower($country) . '.png',
					    'selected' => $localeKey == $selected,
					];
				}
			}
		}

		$v->templatename = 'widgets/adminmenu/view.tpl';
		$v->assign('pages', $flatlist);
		$v->assign('groups', $groups);
		$v->assign('languages', $languages);

		return $v;
	}
	/**
	 * Set the site permissions to a given Form object.
	 *
	 * Used by the create and update pages.
	 *
	 * @param Form $form
	 * @param UserGroupModel $model
	 */
	private function _setPermissionsToForm(Form $form, UserGroupModel $model){
		// I want to split up the permission set into a set of groups, based on the first key.
		$groups = [];
		foreach(Core::GetPermissions() as $key => $data){
			if($key{0} == '/'){
				$group = substr($key, 1, strpos($key, '/', 1)-1);
			}
			else{
				$group = 'general';
			}

			if(!isset($groups[$group])){
				$groups[$group] = [];
			}

			// NEW i18n support for config options!
			$i18nKey = \Core\i18n\I18NLoader::KeyifyString($key);
			//$opts['description'] = t('MESSAGE_PERM__' . $i18nKey);
			$groups[$group][$key] = t('STRING_PERMISSION_' . $i18nKey);
		}

		// Now, I can add these groups to the form.
		foreach($groups as $gkey => $options){
			// Make the title a little more friendly.
			$gtitle = ucwords($gkey) . ' Permissions to Assign';

			$form->addElement(
				'checkboxes',
				array(
					'group' => 'Permissions',
					'id' => 'permissions-' . $gkey,
					'name' => 'permissions',
					'title' => $gtitle,
					'options' => $options,
					'value' => $model->getPermissions(),
				)
			);
		}
	}
示例#6
0
	/**
	 * Page to view and test the i18n settings and strings of this site.
	 *
	 * Also useful for viewing what strings are currently installed and where they came from!
	 *
	 * @return int
	 */
	public function i18n(){
		$view = $this->getView();
		$request = $this->getPageRequest();

		if(!\Core\user()->checkAccess('g:admin')){
			// This test page is an admin-only utility.
			return View::ERROR_ACCESSDENIED;
		}

		/*$locales = Core\i18n\I18NLoader::GetLocalesAvailable();

		// Languages will be the current languages/locales available on the system.
		$languages = [];

		foreach($locales as $lang => $dat){
			if(strpos($lang, '_') !== false){
				$base = substr($lang, 0, strpos($lang, '_'));

				if(!isset($languages[$base])){
					// Add the base language, (useful here because the editor may want to edit only the base language and not specific dialects).
					$languages[$base] = t($dat['lang']);
				}
			}

			$languages[$lang] = t($dat['lang']) . (($dat['dialect']) ? ' (' . t($dat['dialect']) . ')' : '');
		}*/
		// I need to use GetLocalesAvailable because the higher level functions will only return what's currently enabled,
		// which is the entire point of this page!
		$locales = Core\i18n\I18NLoader::GetLocalesAvailable();
		
		// Make this full list a more flat list suitable for populating directly into the form element.
		$all = [];
		foreach($locales as $key => $dat){
			$all[$key] = t($dat['lang']) . ' (' . t($dat['dialect']) . ')';
		}
		
		$enabled = \ConfigHandler::Get('/core/language/languages_enabled');
		// This is expected to be a pipe-seperated list of languages/locales enabled.
		$enabled = array_map('trim', explode('|', $enabled));

		// Did the user request a specific language?
		$requested = $request->getParameter('lang');
		if($requested){
			$showStrings = false;
			$showForm = true;
		}
		else{
			$showStrings = true;
			$showForm = false;
			$requested = \Core\i18n\I18NLoader::GetUsersLanguage();
		}

		$strings = \Core\i18n\I18NLoader::GetAllStrings($requested);

		$form = new Form();
		$form->set('callsmethod', 'AdminController::_i18nSaveHandler');
		
		$form->addElement(
			'checkboxes', 
			[
				'name' => 'languages[]',
				'title' => t('STRING_CONFIG_CORE_LANGUAGE_LANGUAGES_ENABLED'),
				'description' => t('MESSAGE_CONFIG_CORE_LANGUAGE_LANGUAGES_ENABLED'),
			    'options' => $all,
			    'value' => $enabled,
			]
		);
		
		/*

		$form->addElement('system', ['name' => 'lang', 'value' => $requested]);

		foreach($strings as $dat){
			$type = strpos($dat['key'], 'MESSAGE_') === 0 ? 'textarea' : 'text';

			$form->addElement(
				$type,
				[
					'name' => $dat['key'],
				    'title' => $dat['key'],
				    'value' => $dat['found'] ? $dat['match_str'] : '',
				    'description' => $dat['results']['DEFAULT'] ? $dat['results']['DEFAULT'] : $dat['results']['FALLBACK'],
				]
			);
		}
		*/

		$form->addElement('submit', ['value' => t('STRING_SAVE')]);

		$view->addBreadcrumb('t:STRING_ADMIN', '/admin');
		$view->title = 't:STRING_I18N_LANGUAGES';
		//$view->assign('languages', $languages);
		$view->assign('form', $form);
		$view->assign('strings', $strings);
		$view->assign('show_strings', $showStrings);
		$view->assign('show_form', $showForm);
		$view->assign('requested', $requested);
	}
示例#7
0
	/**
	 * Get the breakdown of recorded events and their time into the profiler operation.
	 * 
	 * @return string
	 */
	public function getEventTimesFormatted(){
		$out = '';
		foreach ($this->getEvents() as $t) {
			$in = round($t['timetotal'], 5) * 1000;
			$dcm = I18NLoader::GetLocaleConv('decimal_point');

			if ($in == 0){
				$time = '0000' . $dcm . '00 ms';
			}
			else{
				$parts = explode($dcm, $in);
				$whole = str_pad($parts[0], 4, 0, STR_PAD_LEFT);
				$dec   = (isset($parts[1])) ? str_pad($parts[1], 2, 0, STR_PAD_RIGHT) : '00';
				$time = $whole . $dcm . $dec . ' ms';
			}

			$mem = '[mem: ' . \Core\Filestore\format_size($t['memory']) . '] ';

			$event = $t['event'];

			$out .= "[$time] $mem- $event" . "\n";
		}

		return $out;
	}
示例#8
0
	/**
	 * Fetch this view as an HTML string.
	 * @return mixed|null|string
	 */
	public function fetch() {

		if($this->_fetchCache !== null){
			// w00t ;)
			return $this->_fetchCache;
		}

		try{
			$body = $this->fetchBody();
			\Core\Utilities\Profiler\Profiler::GetDefaultProfiler()->record(
				'Fetched application content from within View->fetch() for ' . $this->templatename
			);
		}
		catch(Exception $e){
			$this->error = View::ERROR_SERVERERROR;
			\Core\ErrorManagement\exception_handler($e, ($this->mode == View::MODE_PAGE));
			$body = '';
		}


		// If there's no template, I have nothing to even do!
		if ($this->mastertemplate === false) {
			return $body;
		}
		// Else if it's null, it's just not set yet :p
		// @deprecated here!
		elseif ($this->mastertemplate === null) {
			$this->mastertemplate = ConfigHandler::Get('/theme/default_template');
		}

		// Whee!
		//var_dump($this->templatename, Core\Templates\Template::ResolveFile($this->templatename));
		// Content types take priority on controlling the master template.
		if ($this->contenttype == View::CTYPE_JSON) {
			$mastertpl = false;
		}
		else {
			// Master template depends on the render mode.
			switch ($this->mode) {
				case View::MODE_PAGEORAJAX:
					if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
						$mastertpl = false;
						$this->mode = View::MODE_AJAX;
					}
					else{
						$mastertpl = ROOT_PDIR . 'themes/' . ConfigHandler::Get('/theme/selected') . '/skins/' . $this->mastertemplate;
						$this->mode = View::MODE_PAGE;
					}
					break;
				case View::MODE_NOOUTPUT:
				case View::MODE_AJAX:
					$mastertpl = false;
					break;
				case View::MODE_PAGE:
				case View::MODE_EMAILORPRINT:
					$mastertpl = Core\Templates\Template::ResolveFile('skins/' . $this->mastertemplate);
					//$mastertpl = ROOT_PDIR . 'themes/' . ConfigHandler::Get('/theme/selected') . '/skins/' . $this->mastertemplate;
					break;
				case View::MODE_WIDGET:
					$mastertpl = Core\Templates\Template::ResolveFile('widgetcontainers/' . $this->mastertemplate);
					break;
			}
		}

		// If there's *still* no template, I still have nothing to do.
		if (!$mastertpl) return $body;


		$template = \Core\Templates\Template::Factory($mastertpl);
		// Ensure that the template is linked to this View correctly.
		$template->setView($this);
		//$template = new Core\Templates\Template();
		//$template->setBaseURL('/');
		// Page-level views have some special variables.
		if ($this->mode == View::MODE_PAGE) {
			$template->assign('breadcrumbs', $this->getBreadcrumbs());
			$template->assign('controls', $this->controls);
			$template->assign('messages', Core::GetMessages());

			// Tack on the pre and post body variables from the current page.
			//$body = CurrentPage::GetBodyPre() . $body . CurrentPage::GetBodyPost();
		}
		// Widgets need some special variables too.
		//if($this->mode == View::MODE_WIDGET){
		//	//var_dump($this->getVariable('widget')); die();
		//	$template->assign('widget', $this->getVariable('widget'));
		//}


		// This logic is needed for the SEO title, since that's usually completely human unfriendly.
		if(isset($this->meta['title']) && $this->meta['title']){
			$template->assign('seotitle', $this->meta['title']);
		}
		else{
			$template->assign('seotitle', $this->getTitle());
		}
		$template->assign('title', $this->getTitle());
		$template->assign('body', $body);

		// The body needs some custom classes for assisting the designers.
		// These are mainly pulled from the UA.
		$ua = \Core\UserAgent::Construct();

		$this->bodyclasses = array_merge($this->bodyclasses, $ua->getPseudoIdentifier(true));

		// Provide a way for stylesheets to target this page specifically.
		switch ($this->error) {
			case View::ERROR_BADREQUEST:
			case View::ERROR_PAYMENTREQUIRED:
			case View::ERROR_ACCESSDENIED:
			case View::ERROR_NOTFOUND:
			case View::ERROR_METHODNOTALLOWED:
			case View::ERROR_NOTACCEPTABLE:
			case View::ERROR_PROXYAUTHENTICATIONREQUIRED:
			case View::ERROR_REQUESTTIMEOUT:
			case View::ERROR_CONFLICT:
			case View::ERROR_GONE:
			case View::ERROR_LENGTHREQUIRED:
			case View::ERROR_PRECONDITIONFAILED:
			case View::ERROR_ENTITYTOOLARGE:
			case View::ERROR_URITOOLARGE:
			case View::ERROR_UNSUPPORTEDMEDIATYPE:
			case View::ERROR_RANGENOTSATISFIABLE:
			case View::ERROR_EXPECTATIONFAILED:
			case View::ERROR_UNAUTHORIZED:
				$url = 'error-' . $this->error;
				break;
			case 403:
				$url = "error-403 page-user-login";
				break;
			default:
				$url  = strtolower(trim(preg_replace('/[^a-z0-9\-]*/i', '', str_replace('/', '-', $this->baseurl)), '-'));
		}

		while($url != ''){
			$this->bodyclasses[] = 'page-' . $url;
			$url = substr($url, 0, strrpos($url, '-'));
		}


		$bodyclasses = strtolower(implode(' ', $this->bodyclasses));
		$template->assign('body_classes', $bodyclasses);

		try{
			$data = $template->fetch();
		}
		catch(SmartyException $e){
			$this->error = View::ERROR_SERVERERROR;
			error_log('[view error]');
			error_log('Template name: [' . $mastertpl . ']');
			\Core\ErrorManagement\exception_handler($e);
			require(ROOT_PDIR . 'core/templates/halt_pages/fatal_error.inc.html');
			die();
		}
		catch(TemplateException $e){
			$this->error = View::ERROR_SERVERERROR;
			error_log('[view error]');
			error_log('Template name: [' . $mastertpl . ']');
			\Core\ErrorManagement\exception_handler($e);
			require(ROOT_PDIR . 'core/templates/halt_pages/fatal_error.inc.html');
			die();
		}

		if($this->mode == View::MODE_EMAILORPRINT && $this->contenttype == View::CTYPE_HTML){
			// Inform other elements that the page is just about to be rendered.
			HookHandler::DispatchHook('/core/page/rendering', $this);

			// Replace the </head> tag with the head data from the current page
			// and the </body> with the foot data from the current page.
			// This is needed to be done at this stage because some element in
			// the template after rendering may add additional script to the head.
			// Also tack on any attributes for the <html> tag.
			if(preg_match('#</head>#i', $data)){
				// I need to do preg_replace because I only want to replace the FIRST instance of </head>
				$data = preg_replace('#</head>#i', $this->getHeadContent() . "\n" . '</head>', $data, 1);
			}
		}
		elseif ($this->mode == View::MODE_PAGE && $this->contenttype == View::CTYPE_HTML) {
			// Inform other elements that the page is just about to be rendered.
			HookHandler::DispatchHook('/core/page/rendering', $this);

			// Metadata!  w00t

			// Replace the </head> tag with the head data from the current page
			// and the </body> with the foot data from the current page.
			// This is needed to be done at this stage because some element in
			// the template after rendering may add additional script to the head.
			// Also tack on any attributes for the <html> tag.
			if(preg_match('#</head>#i', $data)){
				// I need to do preg_replace because I only want to replace the FIRST instance of </head>
				$data = preg_replace('#</head>#i', $this->getHeadContent() . "\n" . '</head>', $data, 1);
			}
			if(preg_match('#</body>#i', $data)){
				// I need to use strrpos because I only want the LAST instance of </body>
				$match = strrpos($data, '</body>');

				$foot = $this->getFootContent();

				if(defined('ENABLE_XHPROF') && function_exists('xhprof_disable')){
					require_once('xhprof_lib/utils/xhprof_lib.php'); #SKIPCOMPILER
					require_once('xhprof_lib/utils/xhprof_runs.php'); #SKIPCOMPILER
					$xhprof_data = xhprof_disable();
					$namespace = trim(str_replace(['.', '/'], '-', HOST . REL_REQUEST_PATH), '-');
					$xhprof_runs = new XHProfRuns_Default();
					$run_id = $xhprof_runs->save_run($xhprof_data, $namespace);

					define('XHPROF_RUN', $run_id);
					define('XHPROF_SOURCE', $namespace);

					$xhprof_link = sprintf(
						'<a href="' . SERVERNAME . '/xhprof/index.php?run=%s&source=%s" target="_blank">View XHprof Profiler Report</a>' . "\n",
						$run_id,
						$namespace
					);
				}
				else{
					$xhprof_link = '';
				}

				// If the viewmode is regular and DEVELOPMENT_MODE is enabled, show some possibly useful information now that everything's said and done.
				if (DEVELOPMENT_MODE) {
					$legend = '<div class="fieldset-title">%s<i class="icon-chevron-down expandable-hint"></i><i class="icon-chevron-up collapsible-hint"></i></div>' . "\n";

					$debug = '';
					$debug .= '<pre class="xdebug-var-dump screen">';
					$debug .= '<fieldset class="debug-section collapsible" id="debug-section-template-information">';
					$debug .= sprintf($legend, 'Template Information');
					$debug .= "<span>";
					$debug .= 'Base URL: ' . $this->baseurl . "\n";
					$debug .= 'Template Requested: ' . $this->templatename . "\n";
					$debug .= 'Template Actually Used: ' . \Core\Templates\Template::ResolveFile($this->templatename) . "\n";
					$debug .= 'Master Skin: ' . $this->mastertemplate . "\n";
					$debug .= "</span>";
					$debug .= '</fieldset>';

					$debug .= '<fieldset class="debug-section collapsible" id="debug-section-performance-information">';
					$debug .= sprintf($legend, 'Performance Information');
					$debug .= "<span>";
					$debug .= $xhprof_link;
					$debug .= "Database Reads: " . \Core\Utilities\Profiler\DatamodelProfiler::GetDefaultProfiler()->readCount() . "\n";
					$debug .= "Database Writes: " . \Core\Utilities\Profiler\DatamodelProfiler::GetDefaultProfiler()->writeCount() . "\n";
					//$debug .= "Number of queries: " . DB::Singleton()->counter . "\n";
					//$debug .= "Amount of memory used by PHP: " . \Core\Filestore\format_size(memory_get_usage()) . "\n";
					$debug .= "Amount of memory used by PHP: " . \Core\Filestore\format_size(memory_get_peak_usage(true)) . "\n";
					$profiler = Core\Utilities\Profiler\Profiler::GetDefaultProfiler();
					$debug .= "Total processing time: " . $profiler->getTimeFormatted() . "\n";
					$debug .= "</span>";
					$debug .= '</fieldset>';

					$debug .= '<fieldset class="debug-section collapsible" id="debug-section-profiler-information">';
					$debug .= sprintf($legend, 'Core Profiler');
					$debug .= "<span>";
					$debug .= $profiler->getEventTimesFormatted();
					$debug .= "</span>";
					$debug .= '</fieldset>';

					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-components-information">';
					// Tack on what components are currently installed.
					$debug .= sprintf($legend, 'Available Components');
					$debugcomponents = array_merge(Core::GetComponents(), Core::GetDisabledComponents());
					$debug .= "<span>";
					// Give me sorting!
					ksort($debugcomponents);
					foreach ($debugcomponents as $l => $v) {
						if($v->isEnabled() && $v->isReady()){
							$debug .= '[<span style="color:green;">Enabled</span>]';
						}
						elseif($v->isEnabled() && !$v->isReady()){
							$debug .= '[<span style="color:red;">!ERROR!</span>]';
						}
						else{
							$debug .= '[<span style="color:red;">Disabled</span>]';
						}


						$debug .= $v->getName() . ' ' . $v->getVersion() . "<br/>";
					}
					$debug .= "</span>";
					$debug .= '</fieldset>';

					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-hooks-information">';
					// I wanna see what hooks are registered too!
					$debug .= sprintf($legend, 'Registered Hooks');
					foreach(HookHandler::GetAllHooks() as $hook){
						$debug .= "<span>";
						/** @var $hook Hook */
						$debug .= $hook->name;
						if($hook->description) $debug .= ' <em> - ' . $hook->description . '</em>';
						$debug .= "\n" . '<span style="color:#999;">Return expected: ' . $hook->returnType . '</span>';
						$debug .= "\n" . '<span style="color:#999;">Attached by ' . $hook->getBindingCount() . ' binding(s).</span>';
						foreach($hook->getBindings() as $b){
							$debug .= "\n" . ' * ' . $b['call'];
						}
						$debug .= "\n\n";
						$debug .= "</span>";
					}
					$debug .= '</fieldset>';

					// Display the licensed content on this application
					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-licenser-information">';
					$debug .= sprintf($legend, 'Licensed Information');
					$lic = \Core\Licenser::GetRaw();
					$debug .= '<div>';
					foreach($lic as $dat){
						$debug .= $dat['url'] . '::' . $dat['feature'] . ' => ' . $dat['value'] . "\n";
					}
					$debug .= '</div></fieldset>';

					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-includes-information">';
					// I want to see how many files were included.
					$debug .= sprintf($legend, 'Included Files');
					$debug .= '<span>Number: ' . sizeof(get_included_files()) . "</span>";
					$debug .= '<span>'. implode("<br/>", get_included_files()) . "</span>";
					$debug .= '</fieldset>';

					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-query-information">';
					$debug .= sprintf($legend, 'Query Log');
					$profiler = \Core\Utilities\Profiler\DatamodelProfiler::GetDefaultProfiler();
					$debug .= '<div>' . $profiler->getEventTimesFormatted() . '</div>';
					$debug .= '</fieldset>';

					// Display all the i18n strings available on the system.
					$debug .= '<fieldset class="debug-section collapsible collapsed" id="debug-section-i18nstrings-information">';
					$debug .= sprintf($legend, 'I18N Strings Available');
					$strings = \Core\i18n\I18NLoader::GetAllStrings();
					$debug .= '<ul>';
					foreach($strings as &$s){
						$debug .= '<li>' . $s['key'] . '</li>';
					}
					$debug .= '</ul>';
					$debug .= '</fieldset>';
					$debug .= '</pre>';

					// And append!
					$foot .= "\n" . $debug;
				}
				$data = substr_replace($data, $foot . "\n" . '</body>', $match, 7);
			}
			$data = preg_replace('#<html#', '<html ' . $this->getHTMLAttributes(), $data, 1);


			// This logic has been migrated to the {$body_classes} variable.
			/*
			if(preg_match('/<body[^>]*>/', $data, $matches)){
				// body is $matches[0].
				$fullbody = $matches[0];
				if($fullbody == '<body>'){
					$body = '<body class="' . $bodyclass . '">';
				}
				elseif(strpos($fullbody, 'class=') === false){
					// Almost as easy, other elements but no class.
					$body = substr($fullbody, 0, -1) . ' class="' . $bodyclass . '">';
				}
				else{
					// parsing HTML is far easier with XML objects.
					$node = new SimpleXMLElement($fullbody . '</body>');
					$body = '<body';
					foreach($node->attributes() as $k => $v){
						if($k == 'class'){
							$body .= ' ' . $k . '="' . $bodyclass . ' ' . $v . '"';
						}
						else{
							$body .= ' ' . $k . '="' . $v . '"';
						}
					}
					$body .= '>';
				}

				// And replace!
				$data = preg_replace('#<body[^>]*>#', $body, $data, 1);
			}
			*/
		}

		$this->_fetchCache = $data;

		return $data;
	}
	public function getFormAttributes(){
		$opts = [];

		$formOptions = $this->get('form_attributes');
		if($formOptions != ''){
			// Explode them by a semicolon
			$formOptions = array_map('trim', explode(';', $formOptions));
			foreach($formOptions as $o){
				if(($cpos = strpos($o, ':')) !== false){
					$k = substr($o, 0, $cpos);
					$v = substr($o, $cpos+1);

					$opts[$k] = $v;
				}
			}
		}

		if(!isset($opts['type'])){
			// Determine this automatically by the config data type.
			switch ($this->get('type')) {
				case 'string':
				case 'int':
					$opts['type'] = 'text';
					break;
				case 'text':
					$opts['type'] = 'textarea';
					break;
				case 'enum':
					$opts['type'] = 'select';
					break;
				case 'boolean':
					$opts['type'] = 'radio';
					break;
				case 'set':
					$opts['type'] ='checkboxes';
					break;
				default:
					$opts['type'] = 'text';
					break;
			}
		}

		// SELECT
		if(($opts['type'] == 'select' || $opts['type'] == 'checkboxes') && trim($this->get('options'))){
			// This is set from the main option set.
			$opts['options'] =  array_map('trim', explode('|', $this->get('options')));
		}

		// RADIO
		if($opts['type'] == 'radio'){
			$opts['options'] = ['false' => t('STRING_NO'), 'true'  => t('STRING_YES')];
		}

		$key = $this->get('key');

		$gname = substr($key, 1);
		$gname = ucwords(substr($gname, 0, strpos($gname, '/')));

		// NEW i18n support for config options!
		$i18nKey = \Core\i18n\I18NLoader::KeyifyString($key);
		$opts['title'] = t('STRING_CONFIG_' . $i18nKey);
		$opts['description'] = t('MESSAGE_CONFIG_' . $i18nKey);

		// Generate the title dynamically from either the title attribute or the key attribute.
		if(!isset($opts['title'])){
			// If the title is set, use that.  Otherwise pull it from the key name less the group.
			if($this->get('title')){
				$opts['title'] = $this->get('title');
			}
			else{
				$title = substr($key, strlen($gname) + 2);
				// Split the title on '/' and capitalize it to make it more user-friendly.
				$title = str_replace('/', ' ', $title);
				// Same thing for underscores '_', remove them and capitalize the words.
				$title = str_replace('_', ' ', $title);
				$title = ucwords($title);

				$opts['title'] = $title;
			}
		}

		// Description can be set dynamically or pulled from the attributes.
		if(!isset($opts['description'])){
			$desc = $this->get('description');
			if ($this->get('default_value') && $desc){
				$desc .= ' (' . t('MESSAGE_DEFAULT_VALUE_IS_S_', $this->get('default_value')) . ')';
			}
			elseif($this->get('default_value')) {
				$desc = t('MESSAGE_DEFAULT_VALUE_IS_S_', $this->get('default_value'));
			}
			$opts['description'] = $desc;
		}

		if(!isset($opts['group'])){
			$opts['group'] = $gname;
		}

		// The name can't be set by the XML metadata, but it is based on the type of form element, slightly.
		if($opts['type'] == 'checkboxes'){
			// Append "[]" to the name as there are many checkboxes!
			$opts['name'] = 'config[' . $key . '][]';
		}
		else{
			$opts['name'] = 'config[' . $key . ']';
		}

		return $opts;
	}