<?php

class InstallController extends Controller {
	public $name = 'Install';
	public $uses = [ 'Config' ];
	public $components = [ 'Session' ];

	private $db;

	private $polyglot;

	# ~ beforeFilter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function beforeFilter() {
		Configure::write('debug', 0);
		$this->layout = 'outside';

		# Make sure there is no config.php
		if (INSTALLED) {
			$this->Session->setFlash(__('There is a app/Config/config.php file present. If you want to reinstall CMS, please delete this file.'), 'danger');
			$this->redirect('/login');
		}

		# Check if method exists
		if (!method_exists($this, $this->request->action)) {
			$this->redirect([ C => 'install', A => 'index' ]);
		}
	}

	# ~ Landing page for the CMS installation  - - - - - - - - - - - - - - - - - - #
	public function index() {
	}

	# ~ Provide a checklist for CMS installation - - - - - - - - - - - - - - - - - #
	public function checklist() {

		# Define check list
		$rules = [
			__('Miscelanious') => [
				'Backups' => is_writable(APP . 'Backups') ],

			__('Webroot') => [
				'webroot/assets'        => is_writable(APP . 'webroot/assets'),
				'webroot/' . UPLOAD_DIR => is_writable(APP . 'webroot/' . UPLOAD_DIR),
				'webroot/thumbs'        => is_writable(APP . 'webroot/thumbs'),
				'webroot/generated'     => is_writable(APP . 'webroot/generated') ],

			__('Theme') => [
				'webroot/theme'          => is_writable(APP . 'webroot/theme'),
				'webroot/theme/favicons' => is_writable(APP . 'webroot/theme/favicons') ],

			__('Temporaly directories') => [
				'/tmp'                  => is_writable(APP . 'tmp'),
				'/tmp/logs'             => is_writable(APP . 'tmp/logs'),
				'/tmp/tests'            => is_writable(APP . 'tmp/tests'),
				'/tmp/upload'           => is_writable(APP . 'tmp/upload'),
				'/tmp/sessions'         => is_writable(APP . 'tmp/sessions'),
				'/tmp/cache'            => is_writable(APP . 'tmp/cache'),
				'/tmp/cache/data'       => is_writable(APP . 'tmp/cache/data'),
				'/tmp/cache/live'       => is_writable(APP . 'tmp/cache/live'),
				'/tmp/cache/view'       => is_writable(APP . 'tmp/cache/view'),
				'/tmp/cache/format'     => is_writable(APP . 'tmp/cache/format'),
				'/tmp/cache/models'     => is_writable(APP . 'tmp/cache/models'),
				'/tmp/cache/persistent' => is_writable(APP . 'tmp/cache/persistent') ]
		];

		# Get the result
		$passed = true;
		foreach ($rules as $name => $group) {
			foreach ($group as $result) {
				if (!$result) {
					$passed = false;
					break(2);
				}
			}
		}

		$this->set(compact('rules', 'passed'));
	}

	# ~ Insert database connection parameters  - - - - - - - - - - - - - - - - - - #
	public function setup() {

		# If data has been submited
		$data = $this->request->data;
		if (!empty($data)) {
			$success = true;

			# Validate
			$success = $this->validateGeneral($data) && $success;
			$success = $this->validateLanguage($data) && $success;
			$success = $this->validateDatabase($data) && $success;

			# Next step of success
			if ($success) {

				# Generate the config
				$config = [
					'sitename'  => $data['name'],
					'type'      => $data['type'],
					'polyglot'  => $this->polyglot,
					'languages' => array_unique(array_merge([ $this->polyglot['default'] ], $this->polyglot['additional'])),
					'database'  => [
						'host'     => $data['db_host'],
						'database' => $data['db_database'],
						'user'     => $data['db_user'],
						'pass'     => $data['db_pass']
					]
				];

				# Generate SQL
				ob_start();
				{
					InstallAssist::setLocales($config['languages']);
					require VECTORCMS_ROOT . 'View/Install/install.sql';
				};
				$sql = ob_get_clean();

				# Import data
				if (!mysqli_multi_query($this->db, $sql)) {
					$this->error("Multi query failed: (" . mysqli_errno($this->db) . ") " . mysqli_error($this->db), 'database');
				}

				# Store results
				do {
					if ($res = mysqli_store_result($this->db)) {
						$res->free();
					}
				} while (mysqli_more_results($this->db) && mysqli_next_result($this->db));

				# Commit changes to database
				mysqli_commit($this->db);
				mysqli_close($this->db);

				# Save to session
				$this->Session->write('Installer', $config);

				# Redirect
				$this->redirect([ C => 'install', A => 'congratulations' ]);
			}

			# Close the databaes
			if ($this->db != null) {
				mysqli_close($this->db);
			}
		}
	}

	# ~ Installation successfull - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function congratulations() {

		# Read config from session
		$config = $this->Session->read('Installer');
		if (empty($config)) {
			$this->redirect([ C => 'install', A => 'checklist' ]);
		}

		# Generate salt and seed
		$pma = $this->randomString(20);
		$salt = $this->randomString(40);
		$seed = $this->randomString(30, false);

		# Try to connect to database
		$db = $this->connect($config['database']);
		if (is_string($db)) {
			$this->errorOnSetup($db, 'database');
		}

		# Insert password for the administrator
		$pass = $this->randomString(8);
		$hash = sha1($salt . $pass);
		if (!mysqli_query($db, "UPDATE `administrators` SET `password` = '{$hash}' LIMIT 1")) {
			$this->errorOnSetup('Could not update administrator with a password: ' . mysqli_error($db), 'database');
		}

		# Set the site name
		if (!empty($config['sitename'])) {
			if (!mysqli_query($db, "UPDATE `config` SET value='{$config['sitename']}' WHERE `config`.`group`='Website' AND `config`.`name`='title'")) {
				$this->errorOnSetup('Could not update site name: ' . mysqli_error($db), 'database');
			}

		} else {
			$this->errorOnSetup('Please provide site name', 'general');
		}

		# Update session with variables
		$this->Session->write('Installer.pma', $pma);
		$this->Session->write('Installer.salt', $salt);
		$this->Session->write('Installer.seed', $seed);

		$this->set(compact('pass'));
	}

	# ~ Download config.php	 - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function config($download = false) {

		# Read config from session
		$config = $this->Session->read('Installer');
		if (empty($config['salt']) || empty($config['seed']) || empty($config['database']['host']) || empty($config['database']['user']) || !isset($config['database']['pass']) || empty($config['database']['database'])) {
			$this->Session->setFlash(__('You have already downloaded the file, or your session has expired'), 'danger');
			$this->redirect([ C => 'install', A => 'index' ]);
		}

		$this->set(compact('config', 'download'));
		//~ $this->Session->write('Installer', null);
		//~ $this->Session->delete('Installer');
	}

	# ~ Show the list of supported locales - - - - - - - - - - - - - - - - - - - - #
	public function locales() {
		$this->set('languages', InstallAssist::$available);
	}

	# ~ Validate general CMS settings  - - - - - - - - - - - - - - - - - - - - - - #
	private function validateGeneral($data) {

		# Site name
		if (empty($data['name'])) {
			return $this->error(__('Please set the name of the website'), 'general');

			# CMS type
		} else if (!in_array($data['type'], [ 'web', 'api' ])) {
			return $this->error(__('Please select a proper CMS type'), 'general');
		}

		return true;
	}

	# ~ Validate selected languages	 - - - - - - - - - - - - - - - - - - - - - - - #
	private function validateLanguage($data) {

		if (empty($data['default_locale'])) {
			return $this->error(__('Please select a default locale'), 'language');
		}

		# Default
		$this->polyglot['default'] = $this->validLocale($data['default_locale']);
		if (!$this->polyglot['default']) {
			return $this->error(__('Supplied value `%s` is not a valid locale (<a href="/install/locales" target="_blank">show supported</a>)', $data['default_locale']), 'language');
		}

		# Additional
		$this->polyglot['additional'] = [];
		foreach (explode(',', $data['additional_locales']) as $locale)
			if (!empty($locale)) {
				if (!($loc = $this->validLocale($locale))) {
					return $this->error(__('Supplied value `%s` is not a valid locale', $locale), 'language');
				}
				$this->polyglot['additional'][] = $loc;
			}

		return true;
	}

	# ~ Validate database connection - - - - - - - - - - - - - - - - - - - - - - - #
	private function validateDatabase($data) {

		# Get params
		$mask = [ 'host', 'database', 'user', 'pass' ];
		foreach ($mask as $field) {
			$connection[$field] = $data["db_{$field}"];
		}

		# Try to connect to database
		$db = $this->connect($connection);
		if (!is_string($db)) {
			$this->db = $db;
			return true;

			# Error connection to database
		} else {
			return $this->error($db, 'database');
		}
	}

	# ~ Manual connection to database  - - - - - - - - - - - - - - - - - - - - - - #
	private function connect($data) {
		extract($data);

		# Try to connect to server
		if (!$db = @mysqli_connect($host, $user, $pass)) {
			return 'Could not connect to server';
		}

		# Try to select database
		if (!@mysqli_select_db($db, $database)) {
			return 'Could not select database: ' . mysqli_error($db);
		}

		return $db;
	}

	# ~ Generates a random string  - - - - - - - - - - - - - - - - - - - - - - - - #
	private function randomString($length, $alpha = true, $base = '') {

		# Define available chars
		$range = '0123456789' . ($alpha ? 'abcdefghijklmnopqrstyxvzwqABCDEFGHIJKLMNOPQRSTYXVZWQ' : '');
		$range = preg_split('~~', $range);
		$len = sizeof($range);

		# Generate random string
		while (strlen($base) < $length) {
			$base .= $range[rand(1, $len) - 1];
		}

		return $base;
	}

	# ~ Validate the proper locale - - - - - - - - - - - - - - - - - - - - - - - - #
	private function validLocale($locale) {
		$locale = strtolower(trim($locale));
		$language = InstallAssist::getLanguageName($locale);
		return $language !== InstallAssist::UNKNOWN
			? $locale
			: null;
	}

	# ~ Generate an error flash for the user - - - - - - - - - - - - - - - - - - - #
	private function error($message, $namespace = null) {
		$this->Session->setFlash($message, 'danger', [], $namespace);
		return false;
	}

	# ~ Generate an error and redirect to the setup page - - - - - - - - - - - - - #
	private function errorOnSetup($message, $namespace = null) {
		$this->error($message, $namespace);
		$this->redirect([ C => 'install', A => 'setup' ]);
		exit(0);
	}

}
