<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Install extends MY_Controller
{
	private $fatal_errors = array();
	private $pages = array();
	private $data = array("show_menu" => true);

	public function __construct()
	{
		parent::__construct();

		if(INSTALLED)
		{
			$this->load->model("site");
			$this->current_user 	= get_user();
			$this->data["user"] 	= $this->current_user;
			$this->data["sites"] 	= $this->site->get();
		}
	}

	private function find_replace($path_to_file, $patterns, $replacements)
	{
		if(file_exists($path_to_file))
		{
			$file_contents = file_get_contents($path_to_file);
			$file_contents = preg_replace($patterns, $replacements, $file_contents);

			file_put_contents($path_to_file, $file_contents);
		}
	}

	private function get_module_id($name)
	{
		$query = $this->db->query("SELECT `module_id` FROM `modules` WHERE `file_name`=?", array($name));
		return $query->row()->module_id;
	}

	private function sql($filename, $replacements=array())
	{
		$sql = file_get_contents(ABSOLUTE_PATH . "/sql/" . $filename . ".sql");

		foreach($replacements as $key=>$value)
		{
			$sql = str_replace('{' . $key . '}', addslashes($value), $sql);
		}

		mysqli_multi_query($this->db->conn_id, $sql);
		while ($this->db->conn_id->more_results()) { $this->db->conn_id->next_result(); } // flush multi_queries

		return true;
	}

	// private function sql($filename, $replacements=array())
	// {
	// 	static $query_count = 0;

	// 	$sql = file_get_contents(ABSOLUTE_PATH . "/sql/" . $filename . ".sql") . "\n";

	// 	foreach($replacements as $key=>$value)
	// 	{
	// 		$sql = str_replace('{' . $key . '}', addslashes($value), $sql);
	// 	}

	// 	$safe_errors = array(1050, // Table already exists
	// 						 1060, // Column already exists
	// 						 1091, // Can't drop a key because it doesn't exist
	// 						 1061, // Duplicate key name (key likely already exists)
	// 						 1005, // Duplicate key name (key likely already exists)
	// 						 1054, // Unknown column (likely already dropped previously)
	// 						);
	// 	$safe_errors_occurred = 0;

	// 	preg_match_all('/((.|\n)+?);\n/m', $sql, $queries);

	// 	$this->db->trans_start();

	// 	foreach($queries[0] as $query)
	// 	{
	// 		$q = $this->db->query($query);
	// 		$query_count++;

	// 		if($q === false)
	// 		{
	// 			$e = $this->db->error();

	// 			$msg = "MySQL error " . $e["code"] . " in query #" . $query_count . ": " . $e["message"] . " (" . $query . ")";
	// 			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": " . $msg); }

	// 			if(!in_array($e["code"], $safe_errors))
	// 			{
	// 				$this->errors[] = $msg;
	// 			}
	// 			else
	// 			{
	// 				$safe_errors_occurred++;
	// 			}
	// 		}
	// 		else if($q !== true)
	// 		{
	// 			try
	// 			{
	// 				$q->free_result();
	// 			}
	// 			catch(Exception $e)
	// 			{
	// 				// Ignore
	// 			}
	// 		}
	// 	}

	// 	$this->db->trans_complete();

	// 	if($safe_errors_occurred > 0)
	// 	{
	// 		$this->errors[] = $safe_errors_occurred . " other MySQL errors occurred which can probably be ignored.";
	// 	}

	// 	return true;
	// }

	public function index()
	{
		ini_set('log_errors', 1);
		define("LOGGING", true);

		$this->data["show_menu"] = false;
		$this->bare_init();

		if(INSTALLED)
		{
			show_error("Nerivon CMS is already installed.", 403, "Access Denied");
		}

		$files = array(APPLICATION_PATH . "/config/constants.php",
		               APPLICATION_PATH . "/config/database.php",
		               APPLICATION_PATH . "/config/config.php");

		foreach($files as $f)
		{
			// Check if file is writable.
			if(!is_writable($f))
			{
				// Try 755 permission.
				@chmod($f, 0755);

				// Check again if file is writable.
				if(!is_writable($f))
				{
					// Try 775 permission.
					@chmod($f, 0775);

					// Check again if file is writable.
					if(!is_writable($f))
					{
						// Try 777 permission.
						@chmod($f, 0777);

						// Check one last time if file is writable.
						if(!is_writable($f))
						{
							// Nothing we can do. User must fix the permission manually.
							if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": `" . $f . "` must be writable."); }
							$this->fatal_errors[] = $f . " must be writable.";
						}
					}
				}
			}
		}

		$directory = ABSOLUTE_PATH . "/templates";
		$files = scandir($directory);
		$templates = array();
		$ignore_templates = array(".", "..", "default", "default_custom", "latex", "marketing", "index.html");

		foreach($files as $file)
		{
			if(!in_array($file, $ignore_templates))
			{
				$templates[] = $file;
			}
		}

		$themes_temp = scandir(ABSOLUTE_PATH . "/css/themes/colours");
		$themes_temp = array_splice($themes_temp, 2);			// Remove . and ..
		$themes 		= array();

		foreach($themes_temp as $theme)
		{
			$themes[] = str_replace(".less", "", $theme);
		}

		$fonts_temp = scandir(ABSOLUTE_PATH . "/css/themes/fonts");
		$fonts_temp = array_splice($fonts_temp, 2);			// Remove . and ..
		$fonts 		= array();

		foreach($fonts_temp as $theme)
		{
			$fonts[] = str_replace(".less", "", $theme);
		}

		$this->data["templates"] 		= $templates;
		$this->data["themes"] 		= $themes;
		$this->data["fonts"] 			= $fonts;
		$this->data["messages"] 		= $this->messages;
		$this->data["errors"] 		= $this->errors;
		$this->data["fatal_errors"] 	= $this->fatal_errors;
		$this->load->view("common/header", $this->data);
		$this->load->view("install/install", $this->data);
		$this->load->view("common/footer", $this->data);
	}

	public function go()
	{
		ini_set('log_errors', 1);
		define("LOGGING", true);

		if(INSTALLED)
		{
			show_error("Nerivon CMS is already installed.", 403, "Access Denied");
		}

		$this->data["show_menu"] = false;
		$success 	= true;

		//manually connect to the database because the connection values aren't re-read from the config file until next page load
		$config = array(
			'dsn'	=> '',
			'hostname' => $this->input->post("db_server"),
			'username' => $this->input->post("db_username"),
			'password' => $this->input->post("db_password"),
			'database' => $this->input->post("db_name"),
			'dbdriver' => 'mysqli',
			'dbprefix' => '',
			'pconnect' => FALSE,
			'db_debug' => FALSE,
			'cache_on' => FALSE,
			'cachedir' => 'application/cache',
			'char_set' => 'utf8',
			'dbcollat' => 'utf8_general_ci',
			'swap_pre' => '',
			'encrypt' => FALSE,
			'compress' => TRUE,
			'stricton' => FALSE,
			'failover' => array(),
			'save_queries' => FALSE
		);

		$connection = @$this->load->database($config, true);
		$this->load->library("encryption");

		if(!$connection->conn_id)
		{
			$this->errors[] = "Could not connect to the database using the information provided.";
			$this->index();
			return;
		}

		$this->db = $connection;
		$this->db->trans_off();

		// Install tables
		if(!$this->sql("install-tables"))
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of tables."); }
		}

		$directory = ABSOLUTE_PATH . "/templates";
		$files = scandir($directory);
		$templates = array();
		$ignore_templates = array(".", "..", "default_custom", "latex", "marketing", "index.html");

		foreach($files as $file)
		{
			if(!in_array($file, $ignore_templates))
			{
				//insert into db
				$this->db->query("INSERT INTO templates SET `name`='" . ucwords($file) . "', `directory`='$file'");

				if($file == $this->input->post("template_id"))
				{
					$template_id = $this->db->insert_id();
				}
			}
		}

		//generate random admin username and password
		$username = "admin" . mt_rand(1, 9);
		$this->load->helper("password");
		$alphabet 	= "abcdefghjkmnpqrstuwxyzABCDEFGHJKMNPQRSTUWXYZ23456789!@#$%^&*";
		$password 	= "";
		$msg 		= "";

		for ($i = 0; $i < 10; $i++)
		{
        	mt_srand();
			$n = mt_rand(0, strlen($alphabet)-1);
			$password .= $alphabet[$n];
		}

		// Set up ACL core data.
		if(!$this->sql("install-acl-core", array()))
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of ACL core data."); }
		}

		// Install data
		if(!$this->sql("install-data", array("name" 		=> $this->input->post("name"),
		           						 "title" 		=> $this->input->post("name"),
		           						 "template_id" 	=> $template_id,
		           						 "theme" 		=> $this->input->post("theme"),
		           						 "font" 		=> $this->input->post("font"),
		           						 "username" 	=> $username,
		           						 "password" 	=> password_hash($password, PASSWORD_BCRYPT),
		           						 "fname" 		=> $this->input->post("fname"),
		           						 "lname" 		=> $this->input->post("lname"),
		           						 "company" 		=> $this->input->post("company"),
		           						 "email" 		=> $this->input->post("email"),
		           						 "domain" 		=> $this->input->server("SERVER_NAME"),
		           						 "phone_numbers"=> $this->input->post("phone_numbers"),
		           						 "hours" 		=> $this->input->post("hours"),
		           						 "address" 		=> $this->input->post("address"),
		           						 "facebook" 	=> $this->input->post("facebook"),
		           						 "twitter" 		=> $this->input->post("twitter"),
		           						 "gplus" 		=> $this->input->post("gplus"),
		           						 "flickr" 		=> $this->input->post("flickr"),
		           						 "pinterest" 	=> $this->input->post("pinterest"),
		           						 "youtube" 		=> $this->input->post("youtube"),
		           						 "linkedin" 	=> $this->input->post("linkedin"),
		           						 "instagram" 	=> $this->input->post("instagram"),
		           						 "etsy" 		=> $this->input->post("etsy"),
		           						 "tumblr" 		=> $this->input->post("tumblr"))))
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of default data."); }
		}

		$this->messages[] = "An admin user was created with the following username and password:<br><p class='pre big error'>$username / $password</p>";
		$this->messages[] = "<span class='error'>Save your username and password somewhere safe!<br>You will not be able to view the password after you leave this page!</span>";

		$this->items[] = "One \"site\" configuration";
		$this->items[] = "One \"domain name\" configuration";
		$this->items[] = "Two navigation menus (a \"main\" menu and a \"secondary\" menu)";
		$this->items[] = "One \"home\" page with a link on both menus";

		// Create each of the requested common pages.
		if($this->input->post("create_login") && $this->input->post("create_registration"))
		{
			if(!$this->sql("install-login-registration"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of login/registration page."); }
			}
			$this->items[] = "User login / registration page with links on the secondary menu";
		}
		else
		{
			if($this->input->post("create_login"))
			{
				if(!$this->sql("install-login"))
				{
					if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of login page."); }
				}
				$this->items[] = "User login page with a link on the secondary menu";
			}

			if($this->input->post("create_registration"))
			{
				if(!$this->sql("install-registration"))
				{
					if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of registration page."); }
				}
				$this->items[] = "User registration page with a link on the secondary menu";
			}
		}

		if($this->input->post("create_search"))
		{
			if(!$this->sql("install-search", array("company" => $this->input->post("company"))))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of search page."); }
			}
			$this->items[] = "Search results page with a link on the secondary menu";
		}

		if($this->input->post("create_documents"))
		{
			if(!$this->sql("install-documents", array()))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of documents page."); }
			}
			$this->items[] = "Documents page with a link on the main menu";
		}

		if($this->input->post("create_gallery"))
		{
			if(!$this->sql("install-gallery"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of gallery page."); }
			}
			$this->items[] = "Photo gallery page with a link on the main menu";
		}

		if($this->input->post("create_portfolio"))
		{
			if(!$this->sql("install-portfolio"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of portfolio page."); }
			}
			$this->items[] = "Portfolio page with a link on the main menu";
		}

		if($this->input->post("create_testimonials"))
		{
			if(!$this->sql("install-testimonials"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of testimonials page."); }
			}
			$this->items[] = "Testimonials page with a link on the main menu";
		}

		if($this->input->post("create_directory"))
		{
			if(!$this->sql("install-directory"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of directory page."); }
			}
			$this->items[] = "Business directory page with a link on the main menu";
		}

		if($this->input->post("create_shop"))
		{
			if(!$this->sql("install-cart"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of shopping cart."); }
			}
			if(!$this->sql("install-checkout"))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of checkout."); }
			}
			$this->items[] = "Shopping cart and checkout pages with links on the main menu";
		}

		if($this->input->post("create_contact"))
		{
			if(!$this->sql("install-contact", array("name" 	=> $this->input->post("name"),
		           						 "company" 		=> $this->input->post("company"),
		           						 "email" 		=> $this->input->post("email"),
		           						 "phone_numbers"=> $this->input->post("phone_numbers"),
		           						 "hours" 		=> $this->input->post("hours"),
		           						 "address" 		=> $this->input->post("address"),
		           						 "map_address" 	=> preg_replace('/\s\s+/', ", ", $this->input->post("address")),
		           						 "facebook" 	=> $this->input->post("facebook"),
		           						 "twitter" 		=> $this->input->post("twitter"),
		           						 "gplus" 		=> $this->input->post("gplus"),
		           						 "flickr" 		=> $this->input->post("flickr"),
		           						 "pinterest" 	=> $this->input->post("pinterest"),
		           						 "youtube" 		=> $this->input->post("youtube"),
		           						 "linkedin" 	=> $this->input->post("linkedin"),
		           						 "instagram" 	=> $this->input->post("instagram"),
		           						 "etsy" 		=> $this->input->post("etsy"),
		           						 "tumblr" 		=> $this->input->post("tumblr"))))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of contact page."); }
			}
			$this->items[] = "Contact page with a link on the main menu";
		}

		if($this->input->post("additional_pages"))
		{
			for($i=1; $i<=$this->input->post("additional_pages"); $i++)
			{
				if(!$this->sql("install-page", array("title" => "Page " . (int)$i,
				           						 "url" => "page-" . (int)$i)))
				{
					if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of additional pages."); }
				}
			}

			if($this->input->post("additional_pages") == 1)
			{
				$this->items[] = $this->input->post("additional_pages") . " extra blank page with a link on the main menu";
			}
			else
			{
				$this->items[] = $this->input->post("additional_pages") . " extra blank pages with links on the main menu";
			}
		}

		// Install shop data
		if(!$this->sql("install-shop-data"))
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of shop data."); }
		}

		// Load the template
		$query 		= $this->db->query("SELECT * FROM `templates` WHERE `template_id`=?", array($template_id));
		$template 	= $query->row();

		// If the selected template comes with an install script, run it.
		if(file_exists(ABSOLUTE_PATH . "/sql/install-template-" . $template->directory . ".sql"))
		{
			if(!$this->sql("install-template-" . $template->directory,
			           		array(	"name" 			=> $this->input->post("name"),
									"title" 		=> $this->input->post("name"),
									"template_id" 	=> $template_id,
									"theme" 		=> $this->input->post("theme"),
									"font" 			=> $this->input->post("font"),
									"username" 		=> $username,
									"fname" 		=> $this->input->post("fname"),
									"lname" 		=> $this->input->post("lname"),
									"company" 		=> $this->input->post("company"),
									"email" 		=> $this->input->post("email"),
									"domain" 		=> $this->input->server("SERVER_NAME"),
									"phone_numbers" => $this->input->post("phone_numbers"),
									"hours" 		=> $this->input->post("hours"),
									"address" 		=> $this->input->post("address"),
									"facebook" 		=> $this->input->post("facebook"),
									"twitter" 		=> $this->input->post("twitter"),
									"gplus" 		=> $this->input->post("gplus"),
									"flickr" 		=> $this->input->post("flickr"),
									"pinterest" 	=> $this->input->post("pinterest"),
									"youtube" 		=> $this->input->post("youtube"),
									"linkedin" 		=> $this->input->post("linkedin"),
		           					"instagram" 	=> $this->input->post("instagram"),
		           					"etsy" 			=> $this->input->post("etsy"),
		           					"tumblr" 		=> $this->input->post("tumblr"))))
			{
				if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of template data."); }
			}
			$this->items[] = "\"" . $template->name . "\" template was installed and configured";
		}

		// Install any positioned module on every page.  This is just easier to do here than in the SQL script.
		// This query with a join and no comparison will give us every possible combination of pages and module instances, which is exactly what we want.
		$query 	= $this->db->query("SELECT `page_id`, `module_instance_id`
		                           FROM `pages`
		                           INNER JOIN `module_instances`
		                           WHERE `position` IS NOT NULL");
		if($query && $query->num_rows())
		{
			foreach($query->result() as $row)
			{
				$this->db->query("INSERT INTO `modules_pages` SET `module_instance_id`=" . $row->module_instance_id . ", `page_id`=" . $row->page_id);
			}
		}

		$query = $this->db->query("SELECT GROUP_CONCAT(DISTINCT `tag` ORDER BY `tag` SEPARATOR '</li><li>') AS `installed_modules` FROM `module_instances`");
		if($query && $query->num_rows() > 0)
		{
			$installed_modules = $query->row()->installed_modules;
			$this->items[] = "The following modules were created for you: <ul><li>" . $installed_modules . "</li></ul>";
		}

		// If the user uploaded a logo, add it to the logo module that we already created.
		$config = array();
		$config['upload_path'] 		= ABSOLUTE_PATH . '/images/';
		$config['allowed_types'] 	= 'gif|jpg|jpeg|png';
		$config['max_size']			= '5242880';	//5MB
		$config['encrypt_name']		= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] 		= true;

		$this->load->library('upload', $config);

		if($this->upload->do_upload("logo_image"))
		{
			$upload_data 		= $this->upload->data();
			$path 				= ABSOLUTE_PATH . "/images/" . addslashes($upload_data["file_name"]);
			$query 				= $this->db->query("SELECT `module_instance_id` FROM `modules` AS m INNER JOIN `module_instances` AS mi USING(module_id) WHERE `file_name`='logo'");
			$module_instance_id = $query->row()->module_instance_id;
			$thumb 				= preg_replace('/\.(jpe?g|png|gif|webp)$/', '-300.$1', $path);
			createThumbnail($path, $thumb, 300, 500, true, false, Mainframe::site()->webp ? IMAGETYPE_WEBP : null);
			$thumb 				= str_replace(ABSOLUTE_PATH, "", $thumb);

			$this->db->query("UPDATE `module_settings` SET `value`='300' WHERE `module_instance_id`='$module_instance_id' AND `key`='width'");
			$this->db->query("UPDATE `module_settings` SET `value`='$thumb' WHERE `module_instance_id`='$module_instance_id' AND `key`='path'");

			// Create a multi-size favicon from the logo.
			$this->load->library("Icoconverter");
			$destination 	= ABSOLUTE_PATH . '/templates/' . $template->directory . '/images/favicon.ico';
			$dimensions 	= [[16, 16], [24, 24], [32, 32], [64, 64], [128, 128]];
			$converter 		= new Icoconverter($path, $dimensions);
			$converter->saveIco($destination);
			// Also copy this favicon to the main images directory for use by the admin area.
			copy($destination, ABSOLUTE_PATH . "/images/favicon.ico");

			$this->items[] = "Your logo was added and a favicon was created from it";
		}

		if($this->input->post("colour1"))
		{
			$this->db->query("UPDATE `sites` SET `colour1`='" . $this->input->post("colour1") . "' WHERE `site_id`=1");
		}

		if($this->input->post("colour2"))
		{
			$this->db->query("UPDATE `sites` SET `colour2`='" . $this->input->post("colour2") . "' WHERE `site_id`=1");
		}

		if($this->input->post("colour3"))
		{
			$this->db->query("UPDATE `sites` SET `colour3`='" . $this->input->post("colour3") . "' WHERE `site_id`=1");
		}

		if(count($this->items) > 0 && count($this->errors) == 0)
		{
			$this->messages[] = "<div>The following items were automatically created for you:</div>
								<ul class='install-list fa-ul'><li><em class='fas fa-li fa-angle-right'></em> " .
								implode("</li><li><em class='fas fa-li fa-angle-right'></em> ", $this->items) .
								"</li></ul>";
		}

		// Set up additional ACL data.
		if(!$this->sql("install-acl-data", array()))
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Errors during installation of additional ACL data."); }
		}

		// Begin testing to see if the installation appears to have worked or not.
		// Pages are 1 for home and 1 for a sample testimonial, plus optional pages.
		$pages 		= 1 + 1 +
					   ($this->input->post("create_login") 			? 1 : 0) +
                       ($this->input->post("create_documents") 		? 1 : 0) +
                       ($this->input->post("create_registration") && !$this->input->post("create_login") 	? 1 : 0) +
                       ($this->input->post("create_gallery") 		? 1 : 0) +
                       ($this->input->post("create_contact") 		? 1 : 0) +
                       ($this->input->post("create_portfolio") 		? 1 : 0) +
                       ($this->input->post("create_testimonials") 	? 1 : 0) +
                       ($this->input->post("create_directory") 		? 1 : 0) +
                       ($this->input->post("create_shop") 			? 1 : 0) +
                       ($this->input->post("create_search") 		? 1 : 0) +
                       $this->input->post("additional_pages");

        $menu_links = $pages + 1;	// Currently there is only 1 link that is duplicated on both menus.

        // The testimonials snippet doesn't have a menu link.
        $menu_links--;

		$query = $this->db->query("SELECT site_id FROM sites");
		if($query && $query->num_rows() == 1)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "No sites installed.";
			$success = false;
		}

		$query = $this->db->query("SELECT domain_id FROM domains");
		if($query && $query->num_rows() == 1)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "No domain names installed.";
			$success = false;
		}

		$query = $this->db->query("SELECT menu_id FROM menus");
		if($query && $query->num_rows() == 2)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of menus installed. Expected 2 but got " . $query->num_rows() . ".";
			$success = false;
		}

		$query = $this->db->query("SELECT template_id FROM templates");
		if($query && $query->num_rows() >= 1)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "No templates installed.";
			$success = false;
		}

		$query = $this->db->query("SELECT user_id FROM users");
		if($query && $query->num_rows() == 1)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "No users installed.";
			$success = false;
		}

		$query = $this->db->query("SELECT page_id FROM pages");
		if($query && $query->num_rows() >= $pages)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of pages installed. Expected $pages but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT menu_item_id FROM menu_items");
		if($query && $query->num_rows() >= $menu_links)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of menu items installed. Expected $menu_links but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT currency_id FROM shop_currency");
		if($query && $query->num_rows() == 2)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of shop currencies installed. Expected 2 but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT order_status_id FROM shop_order_status");
		if($query && $query->num_rows() == 4)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of shop order statuses installed. Expected 4 but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT payment_gateway_id FROM shop_payment_gateways");
		if($query && $query->num_rows() == 3)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of shop payment gateways installed. Expected 3 but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT shipping_gateway_id FROM shop_shipping_gateways");
		if($query && $query->num_rows() == 3)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of shop shipping gateways installed. Expected 3 but got " . $query->num_rows() . ".";
		}

		$query = $this->db->query("SELECT tax_id FROM shop_taxes");
		if($query && $query->num_rows() == 17)
		{
			$success = $success && true;
		}
		else
		{
			$this->errors[] = "Incorrect number of shop taxes installed. Expected 17 but got " . $query->num_rows() . ".";
		}

		@chmod(ABSOLUTE_PATH . "/images",							0755);
		@chmod(ABSOLUTE_PATH . "/documents",						0755);
		@chmod(ABSOLUTE_PATH . "/temp",								0755);
		@chmod(ABSOLUTE_PATH . "/videos",							0755);
		@chmod(ABSOLUTE_PATH . "/.htaccess",						0444);
		@chmod(TEMPLATE_PATH . "/$template->directory",				0755);
		@chmod(TEMPLATE_PATH . "/$template->directory/index.php",	0644);

		// Remove some placeholder index.html files from empty directories.
		@unlink(ABSOLUTE_PATH . "/images/gallery/index.html");
		@unlink(ABSOLUTE_PATH . "/images/slideshow/index.html");

		$patterns 			= array();
		$patterns[0] 		= '/define\("COMPANY_NAME",.*".*"\);/';
		$patterns[1] 		= '/define\("COMPANY_EMAIL",.*".*"\);/';
		$replacements 		= array();
		$replacements[0] 	= 'define("COMPANY_NAME",		"' . $this->input->post("company") . '");';
		$replacements[1] 	= 'define("COMPANY_EMAIL",		"' . $this->input->post("email") . '");';

		// Update INSTALLED constant, only after we're sure it installed properly.
		if($success)
		{
			$patterns[2] 		= '/define\("INSTALLED",.*false\);/';
			$replacements[2] 	= 'define("INSTALLED",			true);';

			$this->data["show_menu"] = true;
		}
		else
		{
			if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": `INSTALLED` constant was not updated because one or more steps failed."); }
		}

		$this->find_replace(APPLICATION_PATH . "/config/constants.php", $patterns, $replacements);

		$patterns 			= array();
		$patterns[0] 		= "/active_group = '.*'/";
		$patterns[1] 		= "/'hostname' => '.*?'/";
		$patterns[2] 		= "/'username' => '.*?'/";
		$patterns[3] 		= "/'password' => '.*?'/";
		$patterns[4] 		= "/'database' => '.*?'/";
		$replacements 		= array();
		$replacements[0] 	= "active_group = 'default'";
		$replacements[1] 	= "'hostname' => '" . $this->input->post("db_server") . "'";
		$replacements[2] 	= "'username' => '" . $this->input->post("db_username") . "'";
		$replacements[3] 	= "'password' => '" . $this->input->post("db_password") . "'";
		$replacements[4] 	= "'database' => '" . $this->input->post("db_name") . "'";
		$this->find_replace(APPLICATION_PATH . "/config/database.php", $patterns, $replacements);

		$encryption_key 	= bin2hex($this->encryption->create_key(16));
		$patterns 			= array();
		$patterns[0] 		= '/11111111111111111111111111111111/';
		$replacements 		= array();
		$replacements[0] 	= $encryption_key;
		$this->find_replace(APPLICATION_PATH . "/config/config.php", $patterns, $replacements);

		// Reinitialize the encryption library with our key because it won't be in memory until next page load.
		$this->encryption->initialize(array('key' => hex2bin($encryption_key)));

		// Proceed only after we're sure it installed properly.
		if($success)
		{
			// Login the new user
			setcookie("user_id", $this->encryption->encrypt(1), time() + $this->config->item("sess_expiration"), $this->config->item("cookie_path"), $this->config->item("cookie_domain"), $this->config->item("cookie_secure"), $this->config->item("cookie_httponly"));

			// This prevents the landing page from showing up on the first admin screen.
			define("INSTALL_COMPLETE", true);

			// Auto load models that would normally be loaded in `application/config/autoload.php`
			$this->load->model(array("user", "metadata_page", "acl", "acl_role", "acl_action"));
			// Initialize Mainframe & other stuff that was previously skipped.
			$this->initialize();

			$this->data["msg"] 	= $msg;
			// Manually load the user so that ACL will be up to date.
			$this->current_user = new User();
			$this->current_user->load(1);
			$this->data["user"] = $this->current_user;

			$this->load->view("common/header", $this->data);
			$this->load->view("common/message", array("messages" => $this->messages, "errors" => $this->errors));
			$this->load->view("admin/menu", $this->data);
			$this->load->view("common/footer", $this->data);
		}
		else
		{
			$this->errors[] = "One or more problems have occurred while installing the database.";
			$this->errors[] = "If you believe everything installed correctly, just set the INSTALLED constant to true.";
			$this->index();
		}
	}

	public function pages()
	{
		$this->admin_login_required();

		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->load->model("category");
		$this->data["categories"] = $this->category->get();

		$this->load->view("common/header", $this->data);
		$this->load->view("common/message", array("messages" => $this->messages, "errors" => $this->errors));
		$this->load->view("install/pages", $this->data);
		$this->load->view("common/footer", $this->data);
	}

	public function search()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-search", array("company" => COMPANY_NAME));
		$this->messages[] = "A search results page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function documents()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-documents", array());
		$this->messages[] = "A documents page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function contact()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-contact", array("company" => COMPANY_NAME, "email" => COMPANY_EMAIL, "phone_numbers" => "", "hours" => "", "address" => "", "map_address" => "", "facebook" => "", "twitter" => "", "gplus" => "", "flickr" => "", "pinterest" => "", "youtube" => "", "linkedin" => "", "instagram" => "", "etsy" => "", "tumblr" => ""));
		$this->messages[] = "A contact page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function gallery()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-gallery");
		$this->messages[] = "A gallery page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function directory()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-directory");
		$this->messages[] = "A business directory was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function shop()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-cart");
		$this->install_modules();
		$this->sql("install-checkout");
		$this->messages[] = "A shopping cart and checkout were created.";
		$this->install_modules();
		$this->pages();
	}

	public function login()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-login");
		$this->messages[] = "A user login page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function registration()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-registration");
		$this->messages[] = "A user registration page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function login_registration()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-login-registration");
		$this->messages[] = "A user login / registration page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function portfolio()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-portfolio");
		$this->messages[] = "A portfolio page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	public function testimonials()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->sql("install-testimonials");
		$this->messages[] = "A testimonials page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}

	private function get_new_page_link()
	{
		$query = $this->db->query("SELECT MAX(`page_id`) AS `page_id` FROM `pages`");
		$page_id = $query->row()->page_id;

		return '<a href="/admin/page/' . $page_id . '">Edit the new page.</a>';
	}

	private function install_modules()
	{
		$this->load->model(array("site", "moduleinstance", "modulepagelink"));

		$query = $this->db->query("SELECT MAX(`page_id`) AS `page_id` FROM `pages`");
		$page_id = $query->row()->page_id;

		$query = $this->db->query("SELECT `site_id` FROM `pages` WHERE `page_id`=?", array($page_id));
		$site_id = $query->row()->site_id;
		$site = new Site();
		$site->load($site_id);

		$modules = $this->moduleinstance->LoadBySiteID($site_id, "module_instance_id", Mainframe::user());

		foreach($modules as $module)
		{
			$linkit = false;

			// All pages
			if((int)$module->always_on === 1)
			{
				$linkit = true;
			}
			// All pages except main
			else if((int)$module->always_on === 2 && $site->default_page_id != $page_id)
			{
				$linkit = true;
			}
			// Only main page
			else if((int)$module->always_on === 3 && $site->default_page_id == $page_id)
			{
				$linkit = true;
			}

			if($linkit)
			{
				$mp = new ModulePageLink();
				$mp->module_instance_id = $module->module_instance_id;
				$mp->page_id = $page_id;
				$mp->save();
			}
		}
	}

	public function content()
	{
		if(!$this->current_user->has(ACTION_PAGES))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("category", "menuitem", "acl"));

		$category = new Category();
		$url = $this->input->post("title");
		$url = strtolower(preg_replace('/[^A-Za-z0-9\-_]/', "-", preg_replace('/-{2,10}/', "-", preg_replace('/(^-|-$)/', "", $url))));

		if($this->input->post("category") != "")
		{
			$category->site_id = Mainframe::active_site_id();
			$category->name = $this->input->post("category");
			$category->save();
			$category_id = $category->category_id;
			$url = strtolower(preg_replace('/[^A-Za-z0-9\-_]/', "-", preg_replace('/-{2,10}/', "-", preg_replace('/(^-|-$)/', "", $category->name)))) . "/" . $url;
		}
		else if($this->input->post("category_id"))
		{
			$category->load($this->input->post("category_id"));
			$category_id = $category->category_id;
			$url = strtolower(preg_replace('/[^A-Za-z0-9\-_]/', "-", preg_replace('/-{2,10}/', "-", preg_replace('/(^-|-$)/', "", $category->name)))) . "/" . $url;
		}
		else
		{
			$category = null;
			$category_id = null;
		}

		$this->sql("install-content-list", array("title" 		=> $this->input->post("title"),
		                                         "url" 			=> $url,
		                                         "category_id" 	=> ($category_id ? $category_id : "NULL") // string intended
		                                         ));
		if($this->input->post("link"))
		{
			$query = $this->db->query("SELECT MAX(`page_id`) AS `page_id` FROM `pages`");
			$page_id = $query->row()->page_id;

			$i 				= new Menuitem();
			$i->menu_id 	= 1;
			$i->parent_id 	= null;
			$i->title 		= $this->input->post("link_text");
			$i->title_tag 	= null;
			$i->icon 		= null;
			$i->page_id 	= $page_id;
			$i->url 		= '';
			$i->target 		= '';
			$i->sort 		= 100;
			$i->published 	= 1;
			$i->save();

			$query = $this->db->query("SELECT * FROM `acl_role`");

			foreach($query->result() as $role)
			{
				$acl = new Acl();
				$acl->acl_role_id = $role->acl_role_id;
				$acl->menu_item_id = $i->menu_item_id;
				$acl->read = 1;
				$acl->write = ($role->acl_role_id == 3); // Admins only
				$acl->save();
			}
		}

		$this->messages[] = "A content list page was created. " . $this->get_new_page_link();
		$this->install_modules();
		$this->pages();
	}
}
