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

use \Michelf\Markdown;

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

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

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

		$this->load->library('ftp');

		$this->config = array();
		$this->config['hostname'] 	= 'janus10.easyonnet.io';
		$this->config['username'] 	= 'files@files.nerivon.com';
		$this->config['password'] 	= 's3{}Bd!TK2=c';
		$this->config['debug'] 		= false;
	}

	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);

			if(!@file_put_contents($path_to_file, $file_contents))
			{
				$this->errors[] = "Unable to update the constants file. Please update `PRODUCT_VERSION` in `/application/config/constants.php` manually and ensure that `INSTALLED` is set to `true`.";
			}
		}
	}

	private function get_newest_version()
	{
		$ch = curl_init("https://files.nerivon.com/patches/NCMS.version");
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
		$version = curl_exec($ch);
		curl_close($ch);

		return trim($version);
	}

	private function get_changelog()
	{
		$ch = curl_init("https://files.nerivon.com/patches/NCMS.changelog");
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
		$log = curl_exec($ch);
		curl_close($ch);

		return trim($log);
	}

	private function download_patches($current_version, $install_version)
	{
		chdir(ABSOLUTE_PATH);

		$this->ftp->connect($this->config);

		$list 		= $this->ftp->list_files('/patches/NCMS/');
		$patches 	= array();
		$sqls 		= array();

		// print_r($list);
		sort($list, SORT_NATURAL);

		foreach($list as $patch)
		{
			$patch = basename($patch);

			if(preg_match('/\.log$/', $patch))
			{
				$patch = str_replace(".log", "", $patch);

				if(strnatcmp($patch, $current_version) > 0 && strnatcmp($patch, $install_version) <= 0)
				{
					$patches[] = $patch;
				}
			}
			else if(preg_match('/\.sql$/', $patch))
			{
				$patch = str_replace(".sql", "", $patch);

				if(strnatcmp($patch, $current_version) > 0 && strnatcmp($patch, $install_version) <= 0)
				{
					$sqls[] = $patch;
				}
			}
		}

		// Download DIFFs
		foreach($patches as $patch)
		{
			if(file_exists(ABSOLUTE_PATH . "/patches/NCMS/" . $patch . ".log"))
			{
				unlink(ABSOLUTE_PATH . "/patches/NCMS/" . $patch . ".log");
			}
			$this->ftp->download('/patches/NCMS/' . $patch . ".log", ABSOLUTE_PATH . '/temp/' . $patch . ".log", 'ascii');
		}

		// Download SQLs
		foreach($sqls as $patch)
		{
			// This is the older naming convention. Clean up the old files.
			if(file_exists(ABSOLUTE_PATH . "/sql/update-" . $patch . ".sql"))
			{
				unlink(ABSOLUTE_PATH . "/sql/update-" . $patch . ".sql");
			}
			// Remove the file if it already exists.
			if(file_exists(ABSOLUTE_PATH . "/sql/" . $patch . ".sql"))
			{
				unlink(ABSOLUTE_PATH . "/sql/" . $patch . ".sql");
			}
			// Download the latest version of the patch.
			$this->ftp->download('/patches/NCMS/' . $patch . ".sql", ABSOLUTE_PATH . '/sql/' . $patch . ".sql", 'ascii');
		}
		$this->ftp->close();

		return $patches;
	}

	public function auto($install_version=NULL)
	{
		ini_set("memory_limit", "256M");
		ini_set('log_errors', 1);
		define("LOGGING", true);

		// In order to maintain backwards compatibility,
		// if the constant isn't defined yet we'll ignore the check.
		if(defined("ACTION_UPDATES") && !$this->current_user->has(ACTION_UPDATES))
		{
			return $this->denied($this->data);
		}

		$this->db->trans_off();
		$newest_version  = $this->get_newest_version();
		$install_version = ($install_version ? $install_version : $newest_version);
		$patches         = $this->download_patches(PRODUCT_VERSION, $install_version);
		$skip_files      = array("application/config/constants.php", "application/config/database.php", "build.py");
		$version_code    = (explode(".", $install_version)[0] == "9" ? "9" : "");

		@mkdir(ABSOLUTE_PATH . "/temp/update", 0755);

		// Download latest version of NCMS from https://files.nerivon.com/NCMS.zip
		$this->ftp->connect($this->config);
		$this->ftp->download("/NCMS" . $version_code . ".zip", ABSOLUTE_PATH . "/temp/update/NCMS.zip", 'binary');
		$this->ftp->close();

		// Extract the ZIP file
		$phar = new PharData(ABSOLUTE_PATH . "/temp/update/NCMS.zip", null, null, Phar::ZIP);
		// Not sure why this works, but we get compressed data otherwise.
		$phar2 = $phar->convertToExecutable(Phar::TAR, Phar::NONE);
		// Decompress the file.
		$phar2->extractTo(ABSOLUTE_PATH . "/temp/update/", null, true);

		$modified = 0;
		$deleted = 0;
		$failed_updates = array();
		$failed_deletes = array();

		foreach($patches as $patch)
		{
			$patch_data = trim(file_get_contents(ABSOLUTE_PATH . "/temp/" . $patch . ".log"));
			$actions 	= explode("\n", $patch_data);
			@unlink(ABSOLUTE_PATH . "/temp/" . $patch . ".log");

			foreach($actions as $action)
			{
				$letter = substr($action, 0, 1);
				$path 	= substr($action, 2);

				if(in_array($path, $skip_files))
				{
					continue;
				}

				switch($letter)
				{
					// File added or modified: copy it from the temp directory.
					case "A":
					case "M":
					{
						// Ensure that the directory exists
						@mkdir(ABSOLUTE_PATH . "/" . dirname($path), 0755, true);

						// Copy the file from our ZIP file
						if(@copy(ABSOLUTE_PATH . "/temp/update/" . $path, ABSOLUTE_PATH . "/" . $path))
						{
							$modified++;
						}
						else
						{
							if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Failed to update file `" . $path . "`."); }
							$failed_updates[] = $path;
						}

						break;
					}
					// File deleted: delete it.
					case "D":
					{
						if(@unlink(ABSOLUTE_PATH . "/" . $path))
						{
							$deleted++;
						}
						else
						{
							if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Failed to delete file `" . $path . "`."); }
							$failed_deletes[] = $path;
						}

						break;
					}
				}
			}

			// If there is a corresponding function for this version to execute before the SQL patch, execute it.
			$vars = get_class_methods($this);
			$func = "update" . str_replace(".", "", $patch) . "pre";

			foreach($vars as $var)
			{
				if($var == $func)
				{
					if(!$this->$func())
					{
						if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Update function `" . $func . "` failed."); }
					}
				}
			}

			// If there is a corresponding SQL update for this version, run it now that we know the DIFF was reasonably successful.
			if(file_exists(ABSOLUTE_PATH . "/sql/" . $patch . ".sql"))
			{
				if(!$this->sql($patch))
				{
					if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": SQL patch `" . $patch . "` failed."); }
				}
			}

			// If there is a corresponding function for this version to execute after the SQL, execute it.
			$func = "update" . str_replace(".", "", $patch) . "post";

			foreach($vars as $var)
			{
				if($var == $func)
				{
					if(!$this->$func())
					{
						if(defined("LOGGING")) { error_log(PRODUCT_NAME . ": Update function `" . $func . "` failed."); }
					}
				}
			}
		}

		if(count($failed_updates) > 0)
		{
			$failed_updates = array_unique($failed_updates);
			$this->errors[] = "The following files failed to be updated. Please update them manually:<ul><li>" . implode("</li><li>", $failed_updates) . "</li></ul>";
		}
		else
		{
			$this->messages[] = "Version $install_version was installed successfully.";
		}

		// Remove the entire update temp directory.
		$this->rmtree(ABSOLUTE_PATH . "/temp/update");

		if($modified > 0)
		{
			// If at least one file was modified successfully, we should assume that the update worked for the most part.
			// Update version in constants file. For safety, we'll update INSTALLED too.
			$patterns 			= array();
			$patterns[0] 		= '/define\("PRODUCT_VERSION",.*".*"\);/';
			$patterns[1] 		= '/define\("INSTALLED",.*false\);/';
			$replacements 		= array();
			$replacements[0] 	= 'define("PRODUCT_VERSION",	"' . $install_version . '");';
			$replacements[1] 	= 'define("INSTALLED",			true);';
			$this->find_replace(APPLICATION_PATH . "/config/constants.php", $patterns, $replacements);
		}

		$this->clean_css_cache();
		$this->clean_application_cache();

		$this->messages[] = "CSS cache was cleaned.";
		$this->messages[] = "Application cache was cleaned.";

		$this->index($install_version);
	}

	private function rmtree($dir)
	{
		$files = array_diff(scandir($dir), array('.','..'));
		foreach ($files as $file)
		{
			(is_dir("$dir/$file")) ? $this->rmtree("$dir/$file") : unlink("$dir/$file");
		}
		return rmdir($dir);
	}

	public function index($current_version=PRODUCT_VERSION)
	{
		// In order to maintain backwards compatibility,
		// if the constant isn't defined yet we'll ignore the check.
		if(defined("ACTION_UPDATES") && !$this->current_user->has(ACTION_UPDATES))
		{
			return $this->denied($this->data);
		}

		$latest_version = $this->get_newest_version();
		$changelog 		= $this->get_changelog();
		$new_version 	= null;
		$changes 		= "";
		$manual_action 	= false;

		if(strnatcmp($latest_version, $current_version) > 0)
		{
			require_once(APPLICATION_PATH . "/third_party/Michelf/Markdown.inc.php");
			$new_version 	= $latest_version;
			$pos1 			= strpos($changelog, "## " . $new_version . " ");
			$pos2 			= strpos($changelog, "## " . $current_version . " ");
			$changes 		= substr($changelog, $pos1, $pos2-$pos1);
			$manual_action 	= (strpos($changes, "### Manual Action") !== false);
			$changes 		= Markdown::defaultTransform($changes);
		}

		// Download the latest version of the update controller before starting.
		$this->ftp->connect($this->config);

		if($this->ftp->download('/patches/NCMS/Update.php', APPLICATION_PATH . '/controllers/Update.php', 'ascii'))
		{
			$this->messages[] = "The update controller was updated to the latest version.";
		}
		else
		{
			$this->errors[] = "The update controller could not be updated to the latest version.";
		}

		$this->data["current_version"] 	= $current_version;
		$this->data["new_version"] 		= $new_version;
		$this->data["changes"] 			= $changes;
		$this->data["manual_action"] 	= $manual_action;

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

	private function sql($filename)
	{
		static $query_count = 0;

		$sql = file_get_contents(ABSOLUTE_PATH . "/sql/" . $filename . ".sql") . "\n";
		$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);

		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
				}
			}
		}

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

		return true;
	}

	private function set_innodb()
	{
		// Change all tables to InnoDB.
		// Ensure that all ID fields are int(11) and unsigned.
		$tables = $this->db->query("SHOW TABLES");

		foreach($tables->result() as $table)
		{
			//There's only one field returned, but we don't know its index.
			foreach($table as $t)
			{
				$this->db->query("ALTER TABLE `$t` ENGINE=InnoDB;");

				$describe = $this->db->query("DESCRIBE `$t`;");

				foreach($describe->result() as $schema)
				{
					// If this is an int field (size 10 or 11), but is not "int(11) unsigned" we have work to do.
					if(preg_match('/^int\((10|11)\)/i', $schema->Type) && $schema->Type != "int(11) unsigned")
					{
						$null 		= ($schema->Null == "YES");
						$primary 	= ($schema->Key == "PRI");
						$default 	= $schema->Default;
						$extra 		= $schema->Extra;

						$this->db->query("ALTER TABLE `$t` CHANGE `" . $schema->Field . "` int(11) unsigned " . ($null ? "NULL" : "NOT NULL") . ($default ? " DEFAULT '$default'" : "") . " " . $extra . ";");
					}
				}
			}
		}

		return true;
	}

	private function update300pre()
	{
		return $this->set_innodb();
	}

	private function update310pre()
	{
		return $this->set_innodb();
	}

	private function update311pre()
	{
		return $this->set_innodb();
	}

	private function update320pre()
	{
		return $this->set_innodb();
	}

	private function update321pre()
	{
		return $this->set_innodb();
	}

	private function update400pre()
	{
		return $this->set_innodb();
	}

	private function update410pre()
	{
		return $this->set_innodb();
	}

	private function update411pre()
	{
		return $this->set_innodb();
	}

	private function update420pre()
	{
		return $this->set_innodb();
	}

	private function update450post()
	{
		$query = $this->db->query("SELECT * FROM shop_products");

		foreach($query->result() as $product)
		{
			$this->db->query("INSERT INTO shop_prices SET product_id=?, shopper_group_id='1', price=?",
			                 array($product->product_id, $product->price));
		}

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

		foreach($query->result() as $category)
		{
			$this->db->query("INSERT INTO shop_categories_shopper_groups SET category_id=?, shopper_group_id='1'",
			                 array($category->category_id));
		}

		return true;
	}

	private function update600post()
	{
		$this->load->model(array("page", "content_value"));

		// Convert all page content to content type values.
		$query = $this->db->query("SELECT * FROM `pages`");

		foreach($query->result() as $p)
		{
			$v = new Content_value();
			$v->page_id 	= $p->page_id;
			$v->key 		= "content1";
			$v->value 		= $p->content;
			$v->save();
		}

		// Fix HTML modules to be single-column until changed.
		$query = $this->db->query("SELECT mi.module_instance_id
		                          FROM `module_instances` AS mi
		                          INNER JOIN `modules` AS m
		                          WHERE m.`file_name`='html'");

		foreach($query->result() as $row)
		{
			$this->db->query("INSERT INTO `module_settings` SET `module_instance_id`=?, `key`='number_of_columns', `value`=1",
			                 array($row->module_instance_id));
		}

		// Convert all testimonials to content type values.
		$query 	= $this->db->query("SELECT * FROM `testimonials`");
		$tquery = $this->db->query("SELECT * FROM `categories` WHERE `site_id`=? AND `name`='Testimonials'",
		                           array(1));

		if($tquery->num_rows() == 0)
		{
			$this->db->query("INSERT INTO `categories` SET `site_id`=?, `name`='Testimonials'",
			                 array(1));
		}

		$tquery 		= $this->db->query("SELECT * FROM `categories` WHERE `site_id`=? AND `name`='Testimonials'",
		                             array(1));
		$category_id 	= $tquery->row()->category_id;

		foreach($query->result() as $t)
		{
			$p 					   = new Page();
			$p->site_id 		   = 1;
			$p->category_id 	   = $category_id;
			$p->content_type_id    = ($t->snippet ? 10 : 9);
			$p->created 		   = date("Y-m-d H:i:s");
			$p->title 			   = $t->client_name;
			$p->url 			   = "testmonials/" . $t->url;
			$p->searchable 		   = 1;
			$p->allow_url_segments = 0;
			$p->allow_url_query    = 0;
			$p->published 	       = 1;
			$p->save();

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "content";
			$v->value 			= $t->testimonial;
			$v->save();
		}

		$encryption_key 	= generatePassword(50);
		$patterns 			= array();
		$patterns[0] 		= '/lkjwdfs7d8235rJKH\^823kjhsdfHtfHG78676&\^782@#%@lkasfd/';
		$replacements 		= array();
		$replacements[0] 	= $encryption_key;
		$this->find_replace(APPLICATION_PATH . "/config/config.php", $patterns, $replacements);

		return true;
	}

	private function update632post()
	{
		$this->db->query("UPDATE `sites` SET
		                 `mail_from`=?,
		                 `mail_replyto`=?,
		                 `mail_username`=?",
		                 array(COMPANY_NAME, COMPANY_EMAIL, "noreply@" . server_basename()));

		return true;
	}

	private function update640post()
	{
		$query = $this->db->query("SELECT mi.module_instance_id
		                          FROM `module_instances` AS mi
		                          INNER JOIN `modules` AS m
		                          WHERE m.`file_name`='content_list'");

		foreach($query->result() as $row)
		{
			$this->db->query("INSERT INTO `module_settings` SET `module_instance_id`=?, `key`='num_featured', `value`=0", array($row->module_instance_id));
			$this->db->query("INSERT INTO `module_settings` SET `module_instance_id`=?, `key`='num_columns', `value`=1", array($row->module_instance_id));
		}

		$query = $this->db->query("SELECT mi.module_instance_id
		                          FROM `module_instances` AS mi
		                          INNER JOIN `modules` AS m
		                          WHERE m.`file_name`='slideshow_nivoslider' OR m.`file_name`='slideshow_bxslider'");

		foreach($query->result() as $row)
		{
			$this->db->query("INSERT INTO `module_settings` SET `module_instance_id`=?, `key`='links', `value`=0", array($row->module_instance_id));
		}

		return true;
	}

	private function update710post()
	{
		// Convert all "downloads" to documents.
		$this->load->model(array("document", "redirect"));

		// Convert the main /downloads directory.
		$this->convertDocs(ABSOLUTE_PATH . "/downloads");

		// Convert any subdirectories of the main /downloads directory.
		$files = scandir(ABSOLUTE_PATH . "/downloads");

		foreach($files as $file)
		{
			if(in_array($file, array(".", "..")))
			{
				continue;
			}

			if(is_dir(ABSOLUTE_PATH . "/downloads/" . $file))
			{
			   	$this->convertDocs(ABSOLUTE_PATH . "/downloads/" . $file);
			}
		}

		return true;
	}

	private function convertDocs($path)
	{
		$this->load->model("document_log");
		@unlink($path . "/index.html");
		$files 	= scandir($path);
		$user 	= get_user();

		foreach($files as $file)
		{
			if(in_array($file, array(".", "..", "index.html")))
			{
				continue;
			}

			if(is_file($path . "/" . $file))
			{
				$filename 					= str_replace("/", "_", str_replace(ABSOLUTE_PATH . "/downloads/", "", $path)) . "_" . $file;
				$document 					= new Document();
				$document->site_id 			= 1;
				$document->category_id 		= null;
				$document->title 			= $filename;
				$document->description 		= null;
				$document->filename 		= $filename;

				$document->url 				= preg_replace('/[^a-z0-9\-_]/', '-', strtolower($file));
				$document->url 				= preg_replace('/-{2,10}/', '-', $document->url);
				$document->url 				= preg_replace('/(^-|-$)/', '', $document->url);

				$document->access 			= 1;
				$document->published 		= 1;
				$document->created 			= date("Y-m-d H:i:s");
				$document->created_user_id 	= $user->user_id;

				// Check if this URL is available, add numbers on the end if it isn't.
				$count 	= 0;
				$url 	= $document->url;
				do
				{
					$query = $this->db->query("SELECT `document_id` FROM `document` WHERE `document_id` !=? AND `url`=?", array($document->document_id, $url));

					if($query->num_rows() == 0)
					{
						break;
					}

					$count++;
					$url = $document->url . $count;
				}
				while(true);

				$document->url = $url;
				$document->save();

				$this->db->query("INSERT INTO `acl` SET `acl_role_id`=1, `document_id`=?, `read`=1, `write`=0", array($document->document_id));
				$this->db->query("INSERT INTO `acl` SET `acl_role_id`=2, `document_id`=?, `read`=1, `write`=0", array($document->document_id));
				$this->db->query("INSERT INTO `acl` SET `acl_role_id`=3, `document_id`=?, `read`=1, `write`=1", array($document->document_id));

				$this->document_log->logChanges(new Document(), $document, $user);

				$old_url = str_replace(ABSOLUTE_PATH . "/", "", $path . "/" . $file);
				$new_url = "document/" . $document->url;

				// Move the file from /downloads to /documents
				copy(ABSOLUTE_PATH . "/" . $old_url, DOCUMENT_PATH . "/" . $filename);

				// Update links on pages or in modules.
				$this->db->query("UPDATE `content_values` SET `value`=REPLACE(`value`, " . $this->db->escape($old_url) . ", " . $this->db->escape($new_url) . ")");
				$this->db->query("UPDATE `content_values` SET `value`=REPLACE(`value`, " . $this->db->escape(str_replace(" ", "%20", $old_url)) . ", " . $this->db->escape($new_url) . ")");
				$this->db->query("UPDATE `module_settings` SET `value`=REPLACE(`value`, " . $this->db->escape($old_url) . ", " . $this->db->escape($new_url) . ")");
				$this->db->query("UPDATE `module_settings` SET `value`=REPLACE(`value`, " . $this->db->escape(str_replace(" ", "%20", $old_url)) . ", " . $this->db->escape($new_url) . ")");
				$this->db->query("UPDATE `shop_layout_specific_data_values` SET `data_value`='/" . $this->db->escape_str($new_url) . "' WHERE `data_value`=" . $this->db->escape(basename($old_url)));

				// Create a redirect in case we missed anything.
				$r = new Redirect();
				$r->site_id = 1;
				$r->old_url = $old_url;
				$r->new_url = $new_url;
				$r->save();
			}
		}

		return true;
	}

	private function update830post()
	{
		$this->load->model(array("page", "page_tag", "content_value", "acl", "acl_role"));

		// Convert all portfolio items to content type values.
		$query 	= $this->db->query("SELECT * FROM `portfolio` ORDER BY `year`, `portfolio_id`");
		$tquery = $this->db->query("SELECT * FROM `categories` WHERE `site_id`=? AND `name`='Portfolio'",
		                           array(1));

		if($tquery->num_rows() == 0)
		{
			$this->db->query("INSERT INTO `categories` SET `site_id`=?, `name`='Portfolio'",
			                 array(1));
		}

		$tquery 		= $this->db->query("SELECT * FROM `categories` WHERE `site_id`=? AND `name`='Portfolio'",
		                             array(1));
		$category_id 	= $tquery->row()->category_id;

		$pquery 			= $this->db->query("SELECT * FROM `content_types` WHERE `content_type`='Portfolio Item'");
		$content_type_id 	= $pquery->row()->content_type_id;

		foreach($query->result() as $pf)
		{
			set_time_limit(30);
			$start 				   = date("Y-m-d H:i:s");
			$p 					   = new Page();
			$p->site_id 		   = $pf->site_id;
			$p->category_id 	   = $category_id;
			$p->content_type_id    = $content_type_id;
			$p->created 		   = ($pf->year ? $pf->year : date("Y")) . date("-m-d H:i:s");
			$p->title 			   = $pf->title;
			$p->url 			   = "portfolio/" . $pf->url;
			$p->searchable 		   = 1;
			$p->allow_url_segments = 0;
			$p->allow_url_query    = 0;
			$p->published 		   = $pf->published;
			$p->save();

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "content";
			$v->value 			= $pf->description;
			$v->save();

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "client_name";
			$v->value 			= $pf->client_name;
			$v->save();

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "year";
			$v->value 			= $pf->year;
			$v->save();

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "link";
			$v->value 			= $pf->link;
			$v->save();

			$new_image_full 	= str_replace("/portfolio/", "/content/", $pf->image_full);

			@unlink(ABSOLUTE_PATH . "/" . $pf->image_preview);
			@unlink(ABSOLUTE_PATH . "/" . preg_replace('/\.(jpe?g|gif|png)/i', '@2x.$1', $pf->image_preview));
			@rename(ABSOLUTE_PATH . "/" . $pf->image_full, ABSOLUTE_PATH . "/" . $new_image_full);
			@rename(ABSOLUTE_PATH . "/" . preg_replace('/\.(jpe?g|gif|png)/i', '@2x.$1', $pf->image_full), ABSOLUTE_PATH . "/" . preg_replace('/\.(jpe?g|gif|png)/i', '@2x.$1', $new_image_full));

			$v 					= new Content_value();
			$v->page_id 		= $p->page_id;
			$v->key 			= "image";
			$v->value 			= "/" . $new_image_full;
			$v->save();

			$tags = explode(",", $pf->tags);

			foreach($tags as $tag)
			{
				$tag = trim($tag);

				if($tag == "")
				{
					continue;
				}
				$this->page_tag->add_link($tag, $p->page_id);
			}

			// Save ACL
			$roles = $this->acl_role->get();

			foreach($roles as $role)
			{
				$acl = new ACL();
				$acl->page_id 		= $p->page_id;
				$acl->acl_role_id 	= $role->acl_role_id;

				// Admins get RW, everybody else gets R
				if($role->acl_role_id == 3)
				{
					$acl->write = 1;
				}
				else
				{
					$acl->write = 0;
				}
				$acl->read = 1;
				$acl->save();
			}

			// If 1 second hasn't passed yet, delay slightly so that our next one will have a different created date.
			if($start == date("Y-m-d H:i:s"))
			{
				sleep(1);
			}
		}

		return true;
	}

	/**
	 * Grab each stat type and tally up totals per date.
	 */
	private function update894post()
	{
		$inserts = array();
		set_time_limit(30);

		$q = $this->db->query("SELECT stat_id, stat_date, page_id, url, COUNT(*) AS total
		                      FROM stats
		                      WHERE page_id IS NOT NULL
		                      GROUP BY stat_date, page_id");

		foreach($q->result() as $row)
		{
			set_time_limit(5);
			$this->db->query("INSERT INTO `stats` SET
							`stat_type_id`=1,
							`stat_date`='" . $row->stat_date . "',
							`page_id`=" . $row->page_id . ",
							`url`='" . $row->url . "',
							`count`=" . $row->total);
		}

		set_time_limit(30);
		$q = $this->db->query("SELECT stat_id, stat_date, module_instance_id, url, COUNT(*) AS total
		                      FROM stats
		                      WHERE module_instance_id IS NOT NULL
		                      GROUP BY stat_date, module_instance_id");

		foreach($q->result() as $row)
		{
			set_time_limit(5);
			$this->db->query("INSERT INTO `stats` SET
							`stat_type_id`=2,
							`stat_date`='" . $row->stat_date . "',
							`module_instance_id`=" . $row->module_instance_id . ",
							`url`='" . $row->url . "',
							`count`=" . $row->total);
		}

		set_time_limit(30);
		$q = $this->db->query("SELECT stat_id, stat_date, document_id, url, COUNT(*) AS total
		                      FROM stats
		                      WHERE document_id IS NOT NULL
		                      GROUP BY stat_date, document_id");

		foreach($q->result() as $row)
		{
			set_time_limit(5);
			$this->db->query("INSERT INTO `stats` SET
							`stat_type_id`=3,
							`stat_date`='" . $row->stat_date . "',
							`document_id`=" . $row->document_id . ",
							`url`='" . $row->url . "',
							`count`=" . $row->total);
		}

		set_time_limit(30);
		$q = $this->db->query("SELECT stat_id, stat_date, shop_category_id, url, COUNT(*) AS total
		                      FROM stats
		                      WHERE shop_category_id IS NOT NULL
		                      GROUP BY stat_date, shop_category_id");

		foreach($q->result() as $row)
		{
			set_time_limit(5);
			$this->db->query("INSERT INTO `stats` SET
							`stat_type_id`=4,
							`stat_date`='" . $row->stat_date . "',
							`shop_category_id`=" . $row->shop_category_id . ",
							`url`='" . $row->url . "',
							`count`=" . $row->total);
		}

		set_time_limit(30);
		$q = $this->db->query("SELECT stat_id, stat_date, shop_product_id, url, COUNT(*) AS total
		                      FROM stats
		                      WHERE shop_product_id IS NOT NULL
		                      GROUP BY stat_date, shop_product_id");

		foreach($q->result() as $row)
		{
			set_time_limit(5);
			$this->db->query("INSERT INTO `stats` SET
							`stat_type_id`=5,
							`stat_date`='" . $row->stat_date . "',
							`shop_product_id`=" . $row->shop_product_id . ",
							`url`='" . $row->url . "',
							`count`=" . $row->total);
		}

		set_time_limit(60);
		$this->db->query("DELETE FROM `stats` WHERE `count`=0");

		return true;
	}
}
