<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package clearFusionCMS
 * @copyright Copyright (c) 2012 - 2017 clearFusionCMS. All rights reserved.
 * @link http://clearfusioncms.com
 */

/**

/**
* cfInstallController Class
*
* Controller to manage installation of clearFusionCMS.
*
* @package clearFusionCMS
*/
class cfInstallController extends flController {

	/**
	 * The template engine.
	 *
	 * @var flTemplateEngine
	 */
	protected $tpl;

	/**
	 * Result of system test.
	 *
	 * @var bool
	 */
	protected $result = true;

	/**
	 * Assign an alert message to a template variable.
	 *
	 * @param string $type The message type Error, Success or Info
	 * @param string $message The message text.
	 * @param bool $fade true if Success alerts should fade out else false.
	 * @return string The HTML for the alert.
	 */
	protected function alertMessage($type, $message, $fade = true) {
		if($type != 'Success') $fade = false;
		switch($type) {
			case 'Error': $title = __('Error', 'cfBackend'); break;
			case 'Info': $title = __('Information', 'cfBackend'); break;
			case 'Success': $title = __('Success', 'cfBackend'); break;
			case 'Warning':
			default:
				$title = __('Warning', 'cfBackend'); break;
		}
		return $this->tpl->parseChunk('managerAlert' . $type, array('alertfade' => $fade ? ' alertfade' : '', 'title' => $title, 'message' => $message));
	}

	/**
	 * Encode data.
	 *
	 * @param array $data Data to encode.
	 * @return string The encoded string.
	 */
	protected function enc($data) {
		return urlencode(base64_encode(str_rot13(serialize($data))));
	}

	/**
	 * The action to install clearFusionCMS
	 */
	function installAction() {
		// If the config file exists then don't run
		if(file_exists(FLAPPPATH . 'config/appConfig.php'))
			flExceptionReporter::showException(new flException('Unable to continue installation as system/app/config/appConfig.php already exists.'));

		// Initialise response, router and request objects
		$this->response = new flResponse();
		$this->request = new flRequest('/');
		$this->router = new flRouter($this->request);
		$this->tpl = new flTemplateEngine();
		$encoded = function_exists('ioncube_file_is_encoded') && ioncube_file_is_encoded();

		$msg = '';
		if($this->request->hasParam('data'))
			$data = unserialize(str_rot13(base64_decode($this->request->getParam('data'))));
		else
			$data = array('key' => '', 'dbconnect' => '', 'basepath' => '');
		$stage = $this->request->getParam('installstage', 1);
		if(!preg_match('/^\d+$/', $stage))
			$stage = 1;

		$form = new flForm($this->request, 'installform', $this->request->getSelf() . "?installstage=$stage&amp;data=" . $this->enc($data), true);

		switch($stage) {
			// Start
			case 1:
				$msg = $this->alertMessage('Warning', 'Before you continue installation please ensure that you have read, understood and agree to the software license, if you do not agree to be bound by the license do not continue installation.<br /><a href="system/app/LicenceAgreement.pdf">Read License</a>');

				$licCode = new flFormElementText($form, 'licence');
				$licCode->setLabel('Your License Key')
						->addValidator(new flFormValidatorRegex('/^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i', 'The license code must be in the format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX.'));
				if(!$encoded) {
					$licCode->setRequired()
						->addValidator(new flFormValidatorRequired());
				}
				else
					$licCode->setDescription($this->alertMessage('Info', 'You will need a license key to use some add-ons, you can obtain a license key for clearFusionCMS Free at <a href="http://clearfusioncms.com">clearFusionCMS.com</a> free of charge.'));

				$submit = new flFormElementSubmit($form, 'agree');
				$submit->setCaption('I Agree To The License, Install')->setCSS('primary');

				if($form->isSubmitted() && $form->isValid()) {
					if($licCode->getValue()) {
						// Phone home
						$info = new flConfigIni(FLMODPATH . 'content/contentInfo.ini', null);
						$secret = 'RLV4T9U6AJ4CW8COK80400G8W8W4O0CH';
						$payload = array(
							'domain' => $this->request->getServerName(),
							'key' => strtoupper($licCode->getValue()),
							'subject' => $encoded ? 'clearFusionCMSFree' : 'clearFusionCMSPro',
							'version' => $info->version
						);
						$payload['hash'] = md5($secret . $payload['domain'] . $payload['key'] . $payload['subject'] . $payload['version']);

						$client = new flHttpClient('http://clearfusioncms.com/licence-remote-endpoint.json');
						$client->setPostData($payload);
						if($client->sendRequest() && $client->getResponseCode() == 200) {
							$res = json_decode($client->getBody());
							if($res->result != 'ok' && getenv('CLEARFUSIONCMS_ENV') !== 'development') {
								$licCode->forceError($res->message);
							}
							else {
								$stage++;
								$data['key'] = strtoupper($licCode->getValue());
								$this->response->redirectTo($this->request->getSelf() . "?installstage=$stage&data=" . $this->enc($data));
							}
						}
						else
							$licCode->forceError('Unable to contact license server, please try again.');
					}
					else {
						$stage++;
						$this->response->redirectTo($this->request->getSelf() . "?installstage=$stage&data=" . $this->enc($data));
					}
				}
				break;

			// Test system requirements
			case 2:

				ob_start();
				echo '<h2>File Access</h2><ul style="list-style: none; margin: 15px 0;">';
				$this->doTestAvail(is_writable(FLSYSPATH . 'cache/'), 'system/cache/ folder is writable.');
				$this->doTestAvail(is_writable(FLROOTPATH . 'assets/private_files/'), 'assets/private_files/ folder is writable.');
				$this->doTestAvail(is_writable(FLROOTPATH . 'assets/private_storage/'), 'assets/private_storage/ folder is writable.');
				$this->doTestAvail(is_writable(FLROOTPATH . 'assets/public_files/'), 'assets/public_files/ folder is writable.');
				$this->doTestAvail(is_writable(FLROOTPATH . 'assets/public_storage/'), 'assets/public_storage/ folder is writable.');

				echo "</ul>";

				echo $this->alertMessage('Info','The above folders need to be writable by this PHP script. Your web host can advise on the correct permission.');

				echo '<h2>Required PHP Settings and Modules</h2><ul style="list-style: none; margin: 15px 0;">';

				$this->doTestVer(phpversion(), '5.3.0', 'PHP Version 5.3.0 or later required.');
				$this->doTestAvail(@preg_match('/\pL/u', 'a') == 1, 'PCRE unicode support available.');
				$this->doTestAvail(function_exists('fsockopen'), 'Sockets enabled.');
				$this->doTestExtension('simplexml');
				$this->doTestExtension('hash');
				$this->doTestExtension('mcrypt');
				$this->doTestExtension('openssl');
				$this->doTestExtension('zlib');
				$this->doTestAvail(function_exists('gmp_add') || function_exists('bcadd'), 'GMP or BCMath available.');
				$this->doTestAvail(function_exists('json_encode') && function_exists('json_decode'), 'JSON functions available.');
				$this->doTestAvail(function_exists('mysqli_connect') || function_exists('mysql_connect'), 'MySQLi or MySQL available.');

				echo "</ul>";

				echo '<h2>Optional PHP Items</h2><ul style="list-style: none; margin: 15px 0;">';

				$this->doTestAvail(function_exists('mb_internal_encoding'), 'Multibyte string functions available.', true);

				echo '</ul>';

				new flFormElementPane($form, 'check', ob_get_clean());

				if(!$this->result) {
					$msg = $this->alertMessage('Error', 'There are one or more problems listed above that will stop this application functioning on this server.<br />
You will need to correct each item marked with <img src="system/app/images/error.png" atl="Error" /> and then run this test again.');

					$submit = new flFormElementSubmit($form, 'submit');
					$submit->setCaption('Test Again')->setCSS('primary');
				}
				else {
					$submit = new flFormElementSubmit($form, 'sumbit');
					$submit->setCaption('Continue with Install')->setCSS('primary');

					if($form->isSubmitted() && $form->isValid()) {
						$stage++;
						$this->response->redirectTo($this->request->getSelf() . "?installstage=$stage&data=" . $this->enc($data));
					}
				}

				break;

			// Collect base path and db information
			case 3:
				// Base path information
				$u = new flUri($this->request->getSelf());
				$basePath = new flFormElementText($form, 'basepath', $u->getPath());
				$basePath->setLabel('Base Path')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->setDescription($this->alertMessage('Info', 'The path to the installation excluding the domain name.<br />
If not using mod_rewrite then if the install is at http://www.example.com/index.php the base path will be <strong>index.php</strong><br />
If using mod_rewrite then the same install will have a base path of <strong>/</strong><br /><br />
For an install at http://www.example.com/subfolder/ then:<br />
If not using mod_rewrite the base path is <strong>/subfolder/index.php</strong><br />
If using mod_rewrite the base path is <strong>/subfolder/</strong>'));

				// Database information
				$dbhost = new flFormElementText($form, 'dbhost', 'localhost');
				$dbhost->setLabel('Database Host')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->setDescription('This is the address of the server running MySQL, your web host should provide this information to you.');

				$database = new flFormElementText($form, 'database');
				$database->setLabel('Database to Use')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->setDescription('This is the name of the database that you have created to use with this installation.');

				$dbuser = new flFormElementText($form, 'dbuser');
				$dbuser->setLabel('Database User')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->setDescription('This is the name of a user that has full access to the above database.');

				$dbpwd = new flFormElementPassword($form, 'dbpwd');
				$dbpwd->setLabel('Database User Password')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->setDescription('This is the password required by the above user to connect to your MySQL install.');

				$submit = new flFormElementSubmit($form, 'agree');
				$submit->setCaption('Test Database Connection and Continue')->setCSS('primary');

				$form->focusElement($basePath);

				$form->groupElements('location', array($basePath))
					->setGroupLabel('location', 'Installation Location')
					->groupElements('db', array($dbhost, $database, $dbuser, $dbpwd))
					->setGroupLabel('db', 'Database Connection')
					->groupElements('install', array($submit), new flFormDecoratorList(new flFormDecoratorDl()));

				if($form->isSubmitted() && $form->isValid()) {
					try {
						// Try connecting to the database
						$scheme = function_exists('mysqli_connect') ? 'mysqli://' : 'mysql://';
						$db = flDb::connect($dbconnect = $scheme . $dbuser->getValue() . ':' . $dbpwd->getValue() . '@' . $dbhost->getValue() . '/' . $database->getValue());

						// Check that the database is not already installed
						if(count($db->fetchTables())) {
							$msg = $this->alertMessage('Error', 'Unable to continue as the provided database already contains data, please provide an empty database and try again.');
						}
						else {
							$data['dbconnect'] = $dbconnect;
							$data['basepath'] = $basePath->getValue();
							$stage++;
							$this->response->redirectTo($this->request->getSelf() . "?installstage=$stage&data=" . $this->enc($data));
						}
					} catch(flException $e) {
						$msg = $this->alertMessage('Error', 'Unable to connect to the database with the supplied details, please check them and try again.');
					}
				}
				break;

			// Collect app configuration Settings
			case 4:
				$username = new flFormElementText($form, 'username');
				$username->setLabel('Username')
					->setDescription('The username you will use to administer your site.')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->addValidator(new flFormValidatorMinLength(3))
					->addValidator(new flFormValidatorMaxLength(100))
					->addValidator(new flFormValidatorRegex('/^[0-9a-zA-Z.-_]+$/', 'The username can only contain numbers, letters and the characters ., - and _.'));

				$email = new flFormElementText($form, 'email');
				$email->setLabel('Email')
					->setDescription('The email address for the administrator user.')
					->setRequired()
					->addValidator(new flFormValidatorRequired());

				$password = new flFormElementPassword($form, 'password');
				$password->setLabel('Password')
					->setRequired()
					->setDescription('The password must be at least 8 characters long.')
					->addValidator(new flFormValidatorRequired())
					->addValidator(new flFormValidatorMinLength(8));

				$usertz = new flFormElementSelect($form, 'usertz', 'Etc/GMT', flDateTzMenu::get());
				$usertz->setLabel('Timezone');

				$sysemail = new flFormElementText($form, 'sysemail');
				$sysemail->setLabel('System Email')
					->setRequired()
					->setDescription('The email address used to send system emails from e.g. password reset emails.')
					->addValidator(new flFormValidatorRequired());

				$systz = new flFormElementSelect($form, 'systz', 'Etc/GMT', flDateTzMenu::get());
				$systz->setLabel('System Timezone');

				$smtpserver = new flFormElementText($form, 'smtpserver');
				$smtpserver->setLabel('SMTP Server')
					->setDescription('The SMTP server or leave blank to use PHP mail to send emails.');

				$smtpport = new flFormElementText($form, 'smtpport', 25);
				$smtpport->setLabel('SMTP Port')
					->setDescription('The SMTP server port to use, default is 25.')
					->addValidator(new flFormValidatorInteger());

				$smtpuser = new flFormElementText($form, 'smtpuser');
				$smtpuser->setLabel('SMTP User')
					->setDescription('Optional user to connect to the SMTP server with.');

				$smtppwd = new flFormElementPassword($form, 'smtppwd');
				$smtppwd->setLabel('SMTP Password')
					->setDescription('Password for user used to connect to the SMTP server with.');

				$sitedomain = new flFormElementText($form, 'domain', $this->request->getServerName());
				$sitedomain->setLabel('Site Domain')
					->setDescription('The domain name that you want the site to appear under e.g. www.example.com')
					->setRequired()
					->addValidator(new flFormValidatorRegex('/^[a-z0-9_\.-]+$/', 'The domain name can only contain letters, numbers, ., - and _ characters.'))
					->addValidator(new flFormValidatorRequired())
					->addValidator(new flFormValidatorMinLength(3))
					->addValidator(new flFormValidatorMaxLength(255));

				$sitename = new flFormElementText($form, 'sitename');
				$sitename->setLabel('Site Name')
					->setDescription('The name of the site, can be changed later.')
					->setRequired()
					->addValidator(new flFormValidatorRequired())
					->addValidator(new flFormValidatorMinLength(1))
					->addValidator(new flFormValidatorMaxLength(64));

				// Guess if running suPHP etc
				$suPHP = false;
				if(function_exists('posix_getuid')) {
					$info = posix_getpwuid(posix_getuid());
					file_put_contents(FLSYSPATH . 'cache/test.txt', 'Stuff!');
					$owner = fileowner(FLSYSPATH . 'cache/test.txt');
					unlink(FLSYSPATH . 'cache/test.txt');

					$suPHP = $owner == $info['uid'];
				}

				$runningOwner = new flFormElementCheckbox($form, 'runningasowner', $suPHP);
				$runningOwner->setLabel('PHP is running as account owner e.g. PHP runs via suPHP')
					->setDescription('Tick this box if PHP runs as the account owner, your hosting company will be able to answer this question or if unsure leave unticked although it may be less secure.');

				$submit = new flFormElementSubmit($form, 'doinstall');
				$submit->setCaption('Install System')->setCSS('primary');

				$form->groupElements('user', array($username, $email, $password, $usertz))
					->setGroupLabel('user', 'Administrator User')
					->groupElements('system', array($sysemail, $systz))
					->setGroupLabel('system', 'System Settings')
					->groupElements('site', array($sitedomain, $sitename))
					->setGroupLabel('site', 'The Website')
					->groupElements('smtp', array($smtpserver, $smtpport, $smtpuser, $smtppwd))
					->setGroupLabel('smtp', 'SMTP Server Settings (optional)')
					->groupElements('phpowner', array($runningOwner))
					->setGroupLabel('phpowner', 'How Does PHP Run');

				$form->groupElements('install', array($submit), new flFormDecoratorList(new flFormDecoratorDl()));

				$form->focusElement($username);

				if($form->isSubmitted() && $form->isValid()) {
					$data['ownerPHP'] = $runningOwner->getValue();

					// Load dist config & set base path and db connection
					$config = array();
					include FLAPPPATH . 'config/appConfig.dist.php';
					$config['basepath'] = $data['basepath'];
					$config['db'] = $data['dbconnect'];

					// Initialise the app object
					clearFusionCMS::getInstance()->init(new flConfig('instConfig', $config));

					$db = clearFusionCMS::getDb();

					// Install fusionLib database tables
					flDbInstaller::install($db);

					// Create the admin user
					$um = new flUserMgr($db);
					$uid = $um->create($username->getValue(), $email->getValue(), $password->getValue(), 'active', $usertz->getValue());

					// Apply the admin role to the user
					$um->setUserRoles($uid, array(1));

					$siteMgr = new cfSiteManager($db);
					$siteMgr->initSite('default_site');

					// Create the system configuration
					$sysCfg = $siteMgr->getSiteCfg('cfSystemEmail', 'default_site');
					$sysCfg->sysEmail = trim($sysemail->getValue());
					$sysCfg->smtpServer = trim($smtpserver->getValue());
					$sysCfg->smtpPort = trim($smtpport->getValue());
					$sysCfg->smtpUser = trim($smtpuser->getValue());
					$sysCfg->smtpPass = $smtppwd->getValue();

					$sysCfg = $siteMgr->getSiteCfg('cfSystemTZ', 'default_site');
					$sysCfg->timezone = $systz->getValue();

					$this->installDatabase($db, $sitedomain->getValue(), $sitename->getValue());

					// Store licence code
					$cfg = new flConfigDb('cfSystemLicence', $db);
					$cfg->code = $data['key'];

					// Install all modules that are on disk
					$mm = new flModuleMgr($db);
					$mm->install('content');
					$modules = $mm->getAvailable();
					foreach($modules as $mname) {
						$info = $mm->getInfo($mname);
						if($info->get('autoinstall', true)) {
							try {
								$mm->install($mname);
							} catch (flException $e) {
							}
						}
					}

					// Next stage
					$stage++;
					$this->response->redirectTo($this->request->getSelf() . "?installstage=$stage&data=" . $this->enc($data));
				}

				break;

			// Write config file
			case 5:
				// Load the default config file
				$cfg = file_get_contents(FLAPPPATH . 'config/appConfig.dist.php');

				// Replace the base path and database connect
				$cfg = str_replace(array(
					"\$config['basepath'] = '/';",
					"\$config['db'] = 'mysqli://user:password@dbserver/database';"
				),
				array(
					"\$config['basepath'] = '$data[basepath]';",
					"\$config['db'] = '$data[dbconnect]';"
				), $cfg);

				if(!$data['ownerPHP'])
					$cfg = str_replace("\$config['umask'] = 0002;", "\$config['umask'] = 0000;", $cfg);

				// Try writing config to config file
				if(is_writable(FLAPPPATH . 'config/') && file_put_contents(FLAPPPATH . 'config/appConfig.php', $cfg) !== false) {
					new flFormElementPane($form, 'config', 'The installation has completed.<br />
You can now login to the <a href="' . (substr($data['basepath'], -1) == '/' ? $data['basepath'] : ($data['basepath'] . '/')) . 'manager/">Site Manager</a>.');
				}
				// Get the user to write the config file
				else {
					$text = new flFormElementTextArea($form, 'config', $cfg);
					$text->setLabel('Configuration Information')
						->setRows(20)
						->setReadonly()
						->setDescription('To complete installation you need to:
<ul>
<li>Copy the above configuration to a text file named appConfig.php</li>
<li>Upload the appConfig.php file to ' . FLAPPPATH . 'config/appConfig.php</li>
</ul><br />
Once done you can login to the <a href="' . (substr($data['basepath'], -1) == '/' ? $data['basepath'] : ($data['basepath'] . '/')) . 'manager/">Site Manager</a>.');
				}

				break;
		}

		// Draw the output
		$this->response->setBody($this->tpl->parseDocument('managerInstallPage', 'backend_' . $this->request->getSelf(),
			array(
				'siteuri' => $this->request->getSelf(true),
				'message' => $msg,
				'page_title' => "Install Stage $stage of 5",
				'content' => isset($form) ? $form->render($this->tpl->getCodeBuffer()) : ''
			)))
			->sendResponse();
	}

	/**
	 * Test a runtime version against a required version.
	 *
	 * @param string $what The runtime version.
	 * @param string $min The minimum version required.
	 * @param string $msg The message to go with the test.
	 */
	protected function doTestVer($what, $min, $msg) {
		$this->doTestAvail(version_compare($what, $min) >= 0, $msg);
	}

	/**
	 * Show result of test if a runtime feature is available.
	 *
	 * @param bool $what true if the feature is available.
	 * @param string $msg The message to go with the test.
	 * @param bool $warn true if only a warning should be generated rather than a fail.
	 */
	protected function doTestAvail($what, $msg, $warn = false) {
		if($what) {
			echo "<li><img src=\"system/app/images/checkbox.png\" alt=\"Ok\" /> $msg</li>\n";
			$this->result = $this->result && true;
		}
		else if($warn) {
			echo "<li><img src=\"system/app/images/warning.png\" alt=\"Warning\" /> $msg</li>\n";
			$this->result = $this->result && true;
		}
		else {
			echo "<li><img src=\"system/app/images/error.png\" alt=\"Error\" /> $msg</li>\n";
			$this->result = false;
		}
	}

	/**
	 * Show/Test the presence of an extension.
	 *
	 * @param string $extension The extension to test, or an array of extensions to test for only 1 in the list is required.
	 * @param bool $warn true if only a warning should be generated rather than a fail.
	 */
	protected function doTestExtension($extension, $warn = false) {
		if(is_array($extension)) {

			$found = false;
			foreach($extension as $e) {
				if(extension_loaded($e)) {
					$this->doTestAvail(true, "Extension '$e' available.", $warn);
					$found = true;
					break;
				}
			}

			if(!$found) {
				$s = join("', '", $extension);
				$this->doTestAvail(false, "None of the extension '$s' are available.", $warn);
			}
		}
		else
			$this->doTestAvail(extension_loaded($extension), "Extension '$extension' available.", $warn);
	}

	/**
	 * Do the install of the system database.
	 *
	 * @param flDb $db The database object.
	 * @param string $sitedomain The domain of the default site.
	 * @param string $sitename The name of the default site.
	 */
	function installDatabase($db, $sitedomain, $sitename) {
		// Create clearFusionCMS database tables
		$db->query("CREATE  TABLE IF NOT EXISTS `cf_dashboardgrps` (
  `grpname` VARCHAR(64) NOT NULL,
  `displayname` VARCHAR(255) NOT NULL,
  `textdomain` VARCHAR(255) NOT NULL,
  `faicon` VARCHAR(32) NOT NULL,
  `mdicon` VARCHAR(64) NOT NULL,
  `disporder` INT NOT NULL,
  PRIMARY KEY (`grpname`))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE  TABLE IF NOT EXISTS `cf_dashboardicons` (
  `grpname` VARCHAR(64) NOT NULL,
  `iconname` VARCHAR(128) NOT NULL,
  `permission` VARCHAR(32) NOT NULL,
  `displayname` VARCHAR(255) NOT NULL,
  `description` VARCHAR(255) NOT NULL,
  `textdomain` VARCHAR(255) NOT NULL,
  `iconfile` VARCHAR(255) NOT NULL,
  `module` VARCHAR(64) NOT NULL,
  `action` VARCHAR(64) NOT NULL,
  PRIMARY KEY (`grpname`, `iconname`),
  INDEX `dashboardgrps` (`grpname` ASC),
  UNIQUE INDEX `name_UNIQUE` (`iconname` ASC))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE TABLE IF NOT EXISTS `cf_contenttype` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `gzip` TINYINT(1) NOT NULL,
  `typename` VARCHAR(255) NOT NULL,
  `mimetype` VARCHAR(255) NOT NULL,
  `fileext` VARCHAR(255) NOT NULL,
  `snippet` VARCHAR(64) NOT NULL,
  `editor` VARCHAR(64) NOT NULL DEFAULT '' ,
  PRIMARY KEY (`id`))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE  TABLE IF NOT EXISTS `cf_sites` (
  `shortname` VARCHAR(32) NOT NULL ,
  `domain` VARCHAR(255) NOT NULL ,
  `displayname` VARCHAR(255) NOT NULL ,
  `locale` VARCHAR(5) NOT NULL ,
  `editperm` VARCHAR(32) NOT NULL ,
  `redirectto` VARCHAR(255) NOT NULL ,
  `twitterusername` VARCHAR(32) NOT NULL DEFAULT '' ,
  `puressl` TINYINT NOT NULL DEFAULT '0' ,
  PRIMARY KEY (`shortname`, `domain`) ,
  UNIQUE INDEX `domain` (`domain` ASC) ,
  INDEX `redirectto` (`redirectto` ASC) )
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE TABLE IF NOT EXISTS `cf_customflds` (
  `placeholder` VARCHAR(32) NOT NULL ,
  `fldtype` VARCHAR(32) NOT NULL ,
  `caption` VARCHAR(64) NOT NULL ,
  `description` VARCHAR(255) NOT NULL ,
  `editperm` VARCHAR(32) NOT NULL ,
  `editor` VARCHAR(64) NOT NULL DEFAULT '' ,
  `snippet` VARCHAR(64) NOT NULL DEFAULT '' ,
  `validation` TEXT NOT NULL ,
  `catid` INT UNSIGNED NOT NULL ,
  `disporder` INT NOT NULL ,
  `ignoregroup` TINYINT NOT NULL ,
  PRIMARY KEY (`placeholder`) )
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE TABLE IF NOT EXISTS `cf_customflds_templates` (
  `template` VARCHAR(64) NOT NULL ,
  `placeholder` VARCHAR(32) NOT NULL ,
  PRIMARY KEY (`template`, `placeholder`) )
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE  TABLE IF NOT EXISTS `cf_crontasks` (
  `event` VARCHAR(64) NOT NULL ,
  `nextrun` DATETIME NOT NULL ,
  `runrate` INT NOT NULL ,
  PRIMARY KEY (`event`) ,
  INDEX `nextrun` (`nextrun` ASC) )
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		$db->query("CREATE  TABLE IF NOT EXISTS `cf_tasksnippets` (
  `taskid` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `taskname` VARCHAR(25) NOT NULL ,
  `description` VARCHAR(255) NOT NULL ,
  `email` VARCHAR(255) NOT NULL ,
  `catid` INT UNSIGNED NOT NULL ,
  `updaterate` INT UNSIGNED NOT NULL ,
  `content` MEDIUMTEXT NOT NULL ,
  PRIMARY KEY (`taskid`) ,
  INDEX `nextrun` (`taskname` ASC) ,
  UNIQUE INDEX `taskname` (`taskname` ASC) )
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");
		$db->query("CREATE  TABLE IF NOT EXISTS `cf_objlocks` (
  `modulename` VARCHAR(64) NOT NULL,
  `contentid` VARCHAR(64) NOT NULL,
  `sitename` VARCHAR(32) NOT NULL,
  `uid` INT UNSIGNED NOT NULL,
  `lockedat` DATETIME NOT NULL,
  `hash` VARCHAR(32) NOT NULL,
  `lockid` BIGINT UNSIGNED NOT NULL,
  PRIMARY KEY (`modulename`, `contentid`, `sitename`),
  INDEX `hash` (`hash` ASC),
  INDEX `uid` (`uid` ASC),
  INDEX `lockedat` (`lockedat` ASC))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
");

		// Create permissions
		$ac = new flAcMgr($db);
		$ac->createPermissionGroup('core', __noop('System'), 'cfBackend'); // 1000
		$ac->createPermission('core.accessmgr', __noop('Can access the manager interface.'), 1010, 'cfBackend');
		$ac->createPermission('core.users', __noop('Can manage users.'), 1020, 'cfBackend');
		$ac->createPermission('core.usersdel', __noop('Can delete users.'), 1023, 'cfBackend');
		$ac->createPermission('core.usersadd', __noop('Can add users.'), 1024, 'cfBackend');
		$ac->createPermission('core.usersroles', __noop('Can assign users roles.'), 1027, 'cfBackend');
		$ac->createPermission('core.roles', __noop('Can manage roles and permissions.'), 1030, 'cfBackend');
		$ac->createPermission('core.settings', __noop('Can access settings.'), 1040, 'cfBackend');
		$ac->createPermission('core.syssettings', __noop('Can edit core system settings.'), 1045, 'cfBackend');
		$ac->createPermission('core.modules', __noop('Can install and uninstall modules.'), 1050, 'cfBackend');
		$ac->createPermission('core.clearcache', __noop('Can manually clear the site data cache.'), 1060, 'cfBackend');
		$ac->createPermission('core.fingerprint', __noop('Can create and test system fingerprints.'), 1070, 'cfBackend');
		$ac->createPermission('core.apikeys', __noop('Can create and manage API keys.'), 1080, 'cfBackend');
		$ac->createPermission('core.audit', __noop('Can view the audit log.'), 1090, 'cfBackend');
		$ac->createPermission('core.auditSettings', __noop('Can view settings data in audit log.'), 1100, 'cfBackend');

		$ac->createPermissionGroup('cfContent', __noop('Content Editing'), 'cfBackend'); // 2000
		$ac->createPermission('cfContent.documents', __noop('Can edit documents.'), 2010, 'cfBackend');
		$ac->createPermission('cfContent.canpublish', __noop('Can publish content.'), 2020, 'cfBackend');
		$ac->createPermission('cfContent.candelete', __noop('Can delete content.'), 2030, 'cfBackend');
		$ac->createPermission('cfContent.private', __noop('Can access and edit private files.'), 2040, 'cfBackend');
		$ac->createPermission('cfContent.elements', __noop('Can manage content elements.'), 2050, 'cfBackend');
		$ac->createPermission('cfContent.elemchunk', __noop('Can manage chunks.'), 2060, 'cfBackend');
		$ac->createPermission('cfContent.elemfields', __noop('Can manage custom fields.'), 2070, 'cfBackend');
		$ac->createPermission('cfContent.elemsnippet', __noop('Can manage snippets.'), 2080, 'cfBackend');
		$ac->createPermission('cfContent.elemtemplate', __noop('Can manage templates.'), 2090, 'cfBackend');
		$ac->createPermission('cfContent.elemtheme', __noop('Can manage themes.'), 2100, 'cfBackend');
		$ac->createPermission('cfContent.settheme', __noop('Can set the active theme.'), 2110, 'cfBackend');
		$ac->createPermission('cfContent.setdefpages', __noop('Can set the default page and error pages.'), 2120, 'cfBackend');
		$ac->createPermission('cfContent.permissions', __noop('Can define content permissions.'), 2130, 'cfBackend');
		$ac->createPermission('cfContent.types', __noop('Can create and manage content types.'), 2140, 'cfBackend');
		$ac->createPermission('cfContent.managesites', __noop('Can manage the available sites within the system.'), 2150, 'cfBackend');
		$ac->createPermission('cfContent.selsites', __noop('Can select site to manage.'), 2155, 'cfBackend');
		$ac->createPermission('cfContent.elemtasks', __noop('Can manage tasks.'), 2160, 'cfBackend');
		$ac->createPermission('cfContent.forceunlock', __noop('Can forcibly unlock items locked by other users.'), 2170, 'cfBackend');
		$ac->createPermission('cfContent.editpubdate', __noop('Can edit document publish dates.'), 2180, 'cfBackend');

		$ac->createPermissionGroup('cfSocialImg', __noop('Social Media Images'), 'cfBackend'); // 3000
		$ac->createPermission('cfSocialImg.canedit', __noop('Can edit images used in social media.'), 3000, 'cfBackend');
		$ac->createPermission('cfSocialImg.candelete', __noop('Can delete images used in social media.'), 3010, 'cfBackend');
		$ac->createPermission('cfSocialImg.canadd', __noop('Can add images used in social media.'), 3020, 'cfBackend');

		// Create dashboard icon groups
		$dmgr = new cfDashboardMgr($db);
		$dmgr->addGroup('cfContent', 1000, __noop('Content'), 'cfBackend');
		$dmgr->addGroup('cfResources', 2000, __noop('Site'), 'cfBackend');
		$dmgr->addGroup('cfSecurity', 3000, __noop('Security'), 'cfBackend');
		$dmgr->addGroup('cfSystem', 4000, __noop('System'), 'cfBackend');

		$dmgr->setGroupFAIcon('cfContent', 'fa-file-o');
		$dmgr->setGroupFAIcon('cfResources', 'fa-code');
		$dmgr->setGroupFAIcon('cfSecurity', 'fa-lock');
		$dmgr->setGroupFAIcon('cfSystem', 'fa-cog');

		$dmgr->setGroupMDIcon('cfContent', 'description');
		$dmgr->setGroupMDIcon('cfResources', 'code');
		$dmgr->setGroupMDIcon('cfSecurity', 'security');
		$dmgr->setGroupMDIcon('cfSystem', 'settings');

		// Create content icons
		$dmgr->addIcon('cfContent', 'cfContent.documents', 'cfContent.documents', __noop('Documents'), __noop('Manage existing and new documents within the site.'), 'system/app/images/content.svg', '', 'documents', 'cfBackend');
		$dmgr->addIcon('cfResources', 'cfContent.elements', 'cfContent.elements', __noop('Elements'), __noop('Manage existing and new page elements.'), 'system/app/images/elements.svg', '', 'elements', 'cfBackend');
		$dmgr->addIcon('cfContent', 'cfContent.filemgr', 'cfContent.documents', __noop('File Manager'), __noop('Manage the publicly available files within the system.'), 'system/app/images/filemgr.svg', '', 'filemgr', 'cfBackend');
		$dmgr->addIcon('cfContent', 'cfContent.privfilemgr', 'cfContent.private', __noop('Private Files'), __noop('Manage the private files within the system.'), 'system/app/images/privfilemgr.svg', '', 'privatefilemgr', 'cfBackend');
		$dmgr->addIcon('cfResources', 'cfContent.settheme', 'cfContent.settheme', __noop('Set Theme'), __noop('Set the theme to use when showing the site.'), 'system/app/images/settheme.svg', '', 'settheme', 'cfBackend');
		$dmgr->addIcon('cfContent', 'cfContent.drafts', 'cfContent.documents', __noop('Drafts'), __noop('View draft content.'), 'system/app/images/content_drafts.svg', '', 'drafts', 'cfBackend');

		// Create security icons
		$dmgr->addIcon('cfSecurity', 'cfSecurity.contentperms', 'cfContent.permissions', __noop('Content Perms'), __noop('Manage the content access and edit permissions.'), 'system/app/images/ctntperms.svg', '', 'contentperms', 'cfBackend');
		$dmgr->addIcon('cfSecurity', 'cfSecurity.roles', 'core.roles', __noop('Roles'), __noop('Manage user roles and their permissions.'), 'system/app/images/roles.svg', '', 'roles', 'cfBackend');
		$dmgr->addIcon('cfSecurity', 'cfSecurity.users', 'core.users', __noop('Users'), __noop('Manage existing users and create new users.'), 'system/app/images/users.svg', '', 'listusers', 'cfBackend');
		$dmgr->addIcon('cfSecurity', 'cfSecurity.fingerprint', 'core.fingerprint', __noop('Fingerprint'), __noop('Create or test a system fingerprint.'), 'system/app/images/fingerprint.svg', '', 'fingerprint', 'cfBackend');
		$dmgr->addIcon('cfSecurity', 'cfSecurity.audit', 'core.audit', __noop('Audit Log'), __noop('View the audit log.'), 'system/app/images/audit.svg', '', 'audit', 'cfBackend');

		// Create system icons
		$dmgr->addIcon('cfResources', 'cfSystem.clearcache', 'core.clearcache', __noop('Clear Cache'), __noop('Clear the site data cache.'), 'system/app/images/clearcache.svg', '', 'clearcache', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.contenttypes', 'cfContent.types', __noop('Content Types'), __noop('Manage the available content types.'), 'system/app/images/ctnttypes.svg', '', 'contenttypes', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.modules', 'core.modules', __noop('Modules'), __noop('View, install and uninstall modules.'), 'system/app/images/modules.svg', '', 'modules', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.set403', 'cfContent.setdefpages', __noop('Set 403 Page'), __noop('Set the page shown on 403 Forbidden page errors.'), 'system/app/images/403page.svg', '', 'set403', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.set404', 'cfContent.setdefpages', __noop('Set 404 Page'), __noop('Set the page shown on 404 Not Found page errors.'), 'system/app/images/link_broken.svg', '', 'set404', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.seterror', 'cfContent.setdefpages', __noop('Set Error Page'), __noop('Set the page shown on errors other than 404 or 403.'), 'system/app/images/seterrorpage.svg', '', 'seterror', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.setdefault', 'cfContent.setdefpages', __noop('Set Home Page'), __noop('Set the home page for the site.'), 'system/app/images/sethome.svg', '', 'sethome', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.settings', 'core.settings', __noop('Settings'), __noop('Edit site settings.'), 'system/app/images/settings.svg', '', 'settings', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.managesites', 'cfContent.managesites', __noop('Manage Sites'), __noop('Manage the available sites in the system.'), 'system/app/images/sitemgr.svg', '', 'sitemgr', 'cfBackend');
		$dmgr->addIcon('cfResources', 'cfSystem.selectsite', 'cfContent.selsites', __noop('Select Site'), __noop('Select the site to work with.'), 'system/app/images/selectsite.svg', '', 'selectsite', 'cfBackend');
		$dmgr->addIcon('cfSystem', 'cfSystem.apikeys', 'core.apikeys', __noop('API Keys'), __noop('Manage API Keys.'), 'system/app/images/apikeys.svg', '', 'apikeys', 'cfBackend');
		$dmgr->addIcon('cfContent', 'cfSystem.socialimg', 'cfSocialImg.canedit', __noop('Social Images'), __noop('Manage Social Media Images.'), 'system/app/images/social_images.svg', '', 'socialimgs', 'cfBackend');

		// Set content types
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => 'HTML No Extension', 'mimetype' => 'text/html', 'fileext' => '', 'snippet' => '', 'editor' => ''));
		$defContentType = $db->insertID();
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.html', 'mimetype' => 'text/html', 'fileext' => 'html', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.php', 'mimetype' => 'text/html', 'fileext' => 'php', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.css', 'mimetype' => 'text/css', 'fileext' => 'css', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.less', 'mimetype' => 'text/css', 'fileext' => 'css', 'snippet' => 'lessc', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.js', 'mimetype' => 'application/javascript', 'fileext' => 'js', 'snippet' => 'packjs', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.xml', 'mimetype' => 'text/xml', 'fileext' => 'xml', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.txt', 'mimetype' => 'text/plain', 'fileext' => 'txt', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.jpg', 'mimetype' => 'image/jpeg', 'fileext' => 'jpg', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.png', 'mimetype' => 'image/png', 'fileext' => 'png', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.gif', 'mimetype' => 'image/gif', 'fileext' => 'gif', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.doc', 'mimetype' => 'application/msword', 'fileext' => 'doc', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.docx', 'mimetype' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'fileext' => 'docx', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.gz', 'mimetype' => 'application/x-gzip', 'fileext' => 'gz', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.mpg', 'mimetype' => 'video/mpeg', 'fileext' => 'mpg', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.mp3', 'mimetype' => 'audio/mpeg3', 'fileext' => 'mp3', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.pdf', 'mimetype' => 'application/pdf', 'fileext' => 'pdf', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.mov', 'mimetype' => 'video/quicktime', 'fileext' => 'mov', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.tar', 'mimetype' => 'application/x-tar', 'fileext' => 'tar', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.tgz', 'mimetype' => 'application/x-compressed', 'fileext' => 'tgz', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.zip', 'mimetype' => 'application/zip', 'fileext' => 'zip', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.ogv', 'mimetype' => 'video/ogg', 'fileext' => 'ogv', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.mp4', 'mimetype' => 'video/mp4', 'fileext' => 'mp4', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '0', 'typename' => '.webm', 'mimetype' => 'video/webm', 'fileext' => 'webm', 'snippet' => '', 'editor' => ''));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.json', 'mimetype' => 'application/json', 'fileext' => 'json', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => '.md', 'mimetype' => 'text/markdown', 'fileext' => 'md', 'snippet' => '', 'editor' => 'codemirror'));
		$db->insert('cf_contenttype', array('gzip' => '1', 'typename' => 'Markdown', 'mimetype' => 'text/html', 'fileext' => '', 'snippet' => 'parseMarkdown'));

		// Create the system management settings
		$siteMgr = new cfSiteManager($db);
		$cfg = $siteMgr->getSiteCfg('cfSystemManager', 'default_site');
		$cfg->template = '';
		$cfg->locale = 'en_US';

		// Create site look configuration
		$cfg = $siteMgr->getSiteCfg('cfSystemSite', 'default_site');
		$cfg->siteDefault = 0;
		$cfg->site404 = 0;
		$cfg->site403 = 0;
		$cfg->siteTheme = '';

		// Content editing settings
		$cfg = $siteMgr->getSiteCfg('cfSystemCtntEdit', 'default_site');
		$cfg->defContentType = $defContentType;
		$cfg->defTemplate = 'default';
		$cfg->editor = file_exists(FLMODPATH . 'contentbuilder') ? 'contentbuilder' : (file_exists(FLMODPATH . 'liveeditor') ? 'liveeditor' : (file_exists(FLMODPATH . 'ckeditor') ? 'ckeditor' : ''));
		$cfg->codeEditor = file_exists(FLMODPATH . 'codemirror') ? 'codemirror' : '';
		$cfg->editorCSS = '';

		// File storage locations
		$cfg = $siteMgr->getSiteCfg('cfSystemFileStore', 'default_site');
		$cfg->publicFiles = 'assets/public_files';
		$cfg->privateFiles = 'assets/private_files';
		$cfg->publicStorage = 'assets/public_storage';
		$cfg->privateStorage = 'assets/private_storage';

		// Create password reset email
		$cfg = new flConfigDb('cfSystemEmails', $db);
		$cfg->pwdResetEmail = "You have requested that your password be reset for the manager interface at [[+domain]].

Please copy and paste the authorization code below into the password reset form:

[[+hash]]

If you did not request your password be reset then please ignore this email.";
		$cfg->pwdResetSubject = "Lost Password Recovery at [[+domain]]";

		// Setup the default site
		$db->insert('cf_sites', array('shortname' => 'default_site', 'domain' => $sitedomain,  'displayname' => $sitename, 'locale' => 'en_US', 'editperm' => '', 'redirectto' => ''));

		// Configure cron system
		$t = new flDate();
		$cfg = new flConfigDb('cfSystemCron', $db);
		$cfg->usePseudoCron = true;

		$var = new flVariables('cfSystemCron', $db);
		$var->lockcode = '';
		$var->lastrun = $t->toString();
		$var->key = flUuid::get();

		// Create cron events
		$cron = new cfCron($db);
		$cron->addTask('system.cron5m', 300);
		$cron->addTask('system.cron30m', 1800);
		$cron->addTask('system.cron1h', 3600);
		$cron->addTask('system.cron3h', 10800);
		$cron->addTask('system.cron6h', 21600);
		$cron->addTask('system.cron12h', 43200);
		$cron->addTask('system.cron1d', 86400);

		// Add event to include pseudo cron AJAX in pages
		clearFusionCMS::getEventManager()->addListener('system.beforePageRender', 'cfPseudoCron', 'addJS');
		clearFusionCMS::getEventManager()->addListener('system.beforeAdminPageRender', 'cfPseudoCron', 'addJS');
	}
}