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

use \Michelf\Markdown;

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

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

		if(INSTALLED)
		{
			$this->load->model("page_tag");
			$this->current_user = get_user();
			$this->data["user"] = $this->current_user;
			$this->data["tags"] = $this->page_tag->get();
		}
		else
		{
			redirect("/install", "location");
			return false;
		}
	}

	protected function init()
	{
		$this->admin_login_required();
		$this->load->model(array("site", "moduleinstance"));

		if(!Mainframe::adminInit())
		{
			redirect("/install", "location");
			return false;
		}

		$site = Mainframe::site();
		$this->sites = $this->site->get();
		$this->data["sites"] = $this->sites;

		$key = "module_js:" . Mainframe::active_site_id() . ":" . $this->current_user->user_id;

		if(($module_js = $this->cache->get($key)) === false)
		{
			$modules = $this->moduleinstance->LoadBySiteID(Mainframe::active_site_id(), "tag", $this->current_user);
			$module_js = "[";

			foreach($modules as $module)
			{
				$module_js .= "[\"$module->tag ($module->name)\", $module->module_instance_id], ";
			}
			$module_js = substr($module_js, 0, strlen($module_js)-2);
			$module_js .= "]";

			if($module_js == "]")
			{
				$module_js = "null";
			}

			$this->cache->save($key, $module_js, 86400);
		}

		Mainframe::setModuleJS($module_js);

		return true;
	}

	public function index()
	{
		$this->init();

		$this->load->model("template");

		$site = Mainframe::site();
		$template = new Template();
		$template->Load($site->template_id);
		Mainframe::setTemplate($template);

		$user = Mainframe::user();

		if(!$template->template_id || !file_exists(TEMPLATE_PATH . "/$template->directory/index.php"))
		{
			FeralRedirect("/admin/templates");
		}

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

	public function media()
	{
		$this->init();

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

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

	/* generic functions
	 * Most list and delete functions are identical except for the class and ID used.
	 * The "generic" functions will be called from the specific functions when possible to save tons of duplicate code.
	*/
	private function generic_list($model, $dataset)
	{
		$this->init();

		$this->load->view("common/header", $this->data);
		$this->load->view("common/message", array("messages" => $this->messages, "errors" => $this->errors));
		$this->load->view("admin/" . $model . "_list", $this->data);
		$this->load->view("common/footer", $this->data);
	}

	private function generic_delete($model, $models, $id="")
	{
		$this->init();

		$this->load->model("$model");

		if(!$id)
		{
			$id = get_id();// $this->input->get($model . "_id");
		}

		if($id)
		{
			//get the class name so we know what object to create
			$class = ucwords($model);
			$obj = new $class();
			$obj->load($id);

			//format the class name pretty for display
			$class = ucwords(str_replace("_", " ", $class));

			if($obj->delete())
			{
				$this->cache->clean();
				$this->messages[] = "$class Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting $class<br /><br />You can't delete a $class that has data linked to it.";
			}
		}

		$this->$models();
	}
	/* end generic functions */

	/* user functions */
	public function users()
	{
		$this->init();

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

		$this->generic_list("user", "users");
	}

	public function user($id=0, $edit_user=null)
	{
		$this->init();

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

		if(!$edit_user)
		{
			$edit_user = new User();
			$edit_user->load($id);
		}

		$roles 			= $this->acl_role->loadByUserID($edit_user->user_id);

		foreach($roles as $role)
		{
			$role->actions = $this->acl_action->loadByRoleID($role->acl_role_id);
		}

		$this->data["id"] 				= $edit_user->user_id;
		$this->data["edit_user"] 		= $edit_user;
		$this->data["roles"]			= $roles;

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

	public function user_save()
	{
		$this->init();
		csrf_verify();
		$this->load->model("acl_user_role");

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

		$id = $this->input->post("user_id");

		$check_user = new User();
		$edit_user = new User();
		$edit_user->load($id);
		$edit_user->readPostVars();

		if($this->input->post("password") && $this->input->post("password") != $this->input->post("password2"))
		{
			$this->errors[] = "Passwords don't match.";
			return $this->user($id, $edit_user);
		}
		else if($check_user->LoadByUsername($this->input->post("username")) && $check_user->user_id != $edit_user->user_id)
		{
			$this->errors[] = "Username already exists!";
			return $this->user($id, $edit_user);
		}
		else if($check_user->LoadByEmail($this->input->post("email")) && $check_user->user_id != $edit_user->user_id)
		{
			$this->errors[] = "E-mail address already exists!";
			return $this->user($id, $edit_user);
		}

		$saved = $edit_user->save();

		$roles = $this->acl_role->get();

		foreach($roles as $role)
		{
			$checked = $this->input->post("role_" . $role->acl_role_id);

			if($checked)
			{
				$this->acl_user_role->add_link($edit_user->user_id, $role->acl_role_id);
			}
			else
			{
				$this->acl_user_role->delete_link($edit_user->user_id, $role->acl_role_id);
			}
		}

		// if(!$id)
		// {
		// 	//new record, continue editing so we can add roles
		// 	$message = "<p>An account has been created for you on " . COMPANY_NAME . ".</p>";
		// 	$message .= "<p><a href='" . LIVE_SITE . "' target='_blank'>" . LIVE_SITE . "</a></p>";
		// 	$message .= "<p>Your credentials are:<br /><br /><b>Username:</b> " . $edit_user->username . "<br /><b>Password:</b> " . $this->input->post("password") . "</p>";

		// 	send_notification($edit_user, "New User Account", $message);
		// }
		// else
		// {
		// 	if($this->input->post("password"))
		// 	{
		// 		$message = "<p>Your account on " . COMPANY_NAME . " has been updated.</p>";
		// 		$message .= "<p><a href='" . LIVE_SITE . "' target='_blank'>" . LIVE_SITE . "</a></p>";
		// 		$message .= "<p>Your new credentials are:<br /><br /><b>Username:</b> " . $edit_user->username . "<br /><b>Password:</b> " . $this->input->post("password") . "</p>";

		// 		send_notification($edit_user, "Account Updated", $message);
		// 	}
		// }

		if($saved)
		{
			$this->cache->clean();
			$this->messages[] = "User Saved";
		}
		else
		{
			$this->errors[] = "Error Saving User";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->user($edit_user->user_id);
		}
		else
		{
			$this->users();
		}
	}

	public function user_delete()
	{
		$this->init();

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

		$this->generic_delete("user", "users");
	}
	/* end user functions */

	/* domain functions */
	public function domains()
	{
		$this->init();

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

		$this->generic_list("domain", "domains");
	}

	public function domain($id=0)
	{
		$this->init();

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

		$this->load->model(array("domain", "site"));

		$domain = new Domain();
		$domain->load($id);

		$sites = $this->site->get();

		$this->data["id"] = $id;
		$this->data["domain"] = $domain;
		$this->data["sites"] = $sites;

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

	public function domain_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("domain");
		$id = $this->input->post("domain_id");

		$domain = new Domain();
		$domain->load($id);
		$domain->readPostVars();

		if($domain->save())
		{
			$this->cache->clean();
			$this->messages[] = "Domain Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Domain";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->domain($domain->domain_id);
		}
		else
		{
			$this->domains();
		}
	}

	public function domain_delete()
	{
		$this->init();

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

		$this->generic_delete("domain", "domains");
	}
	/* end domain functions */

	/* site functions */
	public function sites()
	{
		$this->init();

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

		$this->generic_list("site", "sites");
	}

	public function site($id=0)
	{
		$this->init();

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

		$this->load->model(array("template", "site", "page", "domain", "shop/shop_currency"));
		$this->load->library("encryption");

		$site = new Site();
		$site->load($id);
		$site->mail_password = $this->encryption->decrypt($site->mail_password);

		$domains 		= $this->domain->get();
		$templates 		= $this->template->get();
		$pages 			= $this->page->get();
		$currencies 	= $this->shop_currency->get();
		$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["id"] 			= $id;
		$this->data["site"] 		= $site;
		$this->data["templates"] 	= $templates;
		$this->data["pages"] 		= $pages;
		$this->data["currencies"] 	= $currencies;
		$this->data["themes"] 		= $themes;
		$this->data["fonts"] 		= $fonts;
		$this->data["domains"] 		= $domains;

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

	public function site_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model(array("site", "template", "redirect"));
		$id = $this->input->post("site_id");

		$site = new Site();
		$site->load($id);
		$site->readPostVars();
		$site->mail_password = ($site->mail_password ? $this->encryption->encrypt($site->mail_password) : null);

		$template = new Template();
		$template->load($site->template_id);

		$config = array();
		$config['upload_path'] 		= ABSOLUTE_PATH . '/temp/';
		$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("favicon"))
		{
			$upload_data 	= $this->upload->data();

			// 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($upload_data["full_path"], $dimensions);
			$converter->saveIco($destination);
			unlink($upload_data["full_path"]);
			// Also copy this favicon to the main images directory for use by the admin area.
			copy($destination, ABSOLUTE_PATH . "/images/favicon.ico");
		}

		if($site->save())
		{
			$this->cache->clean();
			$this->clean_css_cache();
			$this->messages[] = "Site Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Site";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->site($site->site_id);
		}
		else
		{
			$this->sites();
		}
	}

	public function activate($id)
	{
		$this->init();

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

		$this->load->model("site");

		$site = new Site();
		$site->load($id);
		$site->show_landing = 0;

		if($id && $site->save())
		{
			$this->clean_css_cache();
			$this->messages[] = "Your site has been activated! If you did this by mistake, <a href='/admin/deactivate/" . $id . "'>click here</a>.";
		}
		else
		{
			$this->errors[] = "Error Activating Site";
		}

		$this->site($id);
	}

	public function deactivate($id)
	{
		$this->init();

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

		$this->load->model("site");

		$site = new Site();
		$site->load($id);
		$site->show_landing = 1;

		if($id && $site->save())
		{
			$this->clean_css_cache();
			$this->messages[] = "Your site has been hidden behind a landing page. If you did this by mistake, <a href='/admin/activate/" . $id . "'>click here</a>.";
		}
		else
		{
			$this->errors[] = "Error Deactivating Site";
		}

		$this->site($id);
	}

	public function site_delete()
	{
		$this->init();

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

		$this->clean_css_cache();
		$this->generic_delete("site", "sites");
	}
	/* end site functions */

	/* category functions */
	public function categories()
	{
		$this->init();

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

		$this->generic_list("category", "categories");
	}

	public function category($id=0)
	{
		$this->init();

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

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

		$category = new Category();
		$category->load($id);

		$sites = $this->site->get();

		$this->data["id"] = $id;
		$this->data["category"] = $category;
		$this->data["sites"] = $sites;

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

	public function category_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("category");
		$id = $this->input->post("category_id");

		$category = new Category();
		$category->load($id);
		$category->readPostVars();

		if($category->save())
		{
			$this->cache->clean();
			$this->messages[] = "Category Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Category";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->category($category->category_id);
		}
		else
		{
			$this->categories();
		}
	}

	public function category_delete()
	{
		$this->init();

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

		$this->generic_delete("category", "categories");
	}
	/* end category functions */

	/* page functions */
	public function pages()
	{
		$this->init();

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

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

		$content_types 	= $this->content_type->get();
		$categories 	= $this->category->loadBySiteID(Mainframe::active_site_id());

		$this->data["content_types"] 	= $content_types;
		$this->data["categories"] 		= $categories;

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

	public function content_types()
	{
		$this->init();

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

		$this->load->model(array("content_type"));

		$content_types = $this->content_type->get();
		$sites = $this->site->get();

		$this->data["content_types"] 	= $content_types;
		$this->data["sites"] 			= $sites;

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

	public function page($id=0, $content_type_id=1)
	{
		$this->init();

		if(!$this->current_user->has(ACTION_PAGES) ||
		   !$this->acl->checkPage($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("category", "site", "page", "moduleinstance", "content_type", "page_tag"));

		$page = new Page();
		$page->load($id);

		if(!$id)
		{
			$type = new Content_type();
			$type->load($content_type_id);

			$page->content_type_id 			= $type->content_type_id;
			$page->content_type 			= $type->content_type;
			$page->content_type_filename 	= $type->filename;
			$page->page_info 				= $type->page_info;
		}

		$sites 			= $this->site->get();
		$categories 	= $this->category->get();
		$content_types 	= $this->content_type->get();
		$tags 			= $this->page_tag->get();

		//$modules = new ModuleInstanceList();
		$modules = $this->moduleinstance->LoadInstalledByPageID($page->page_id, ($page->site_id ? $page->site_id : Mainframe::active_site_id()));

		// If this is a new page, we're going to automatically enable modules
		// that are enabled on all published pages already.
		if(!$page->page_id)
		{
			$defaults = $this->moduleinstance->loadDefaultModules(($page->site_id ? $page->site_id : Mainframe::active_site_id()));

			foreach($defaults as $d)
			{
				foreach($modules as $m)
				{
					if($m->module_instance_id == $d->module_instance_id)
					{
						$m->module_installed = 1;
					}
				}
			}
		}

		$acls = $this->acl->loadByPageID($page->page_id);

		$this->data["id"] 			= $id;
		$this->data["page"] 		= $page;
		$this->data["sites"] 		= $sites;
		$this->data["categories"] 	= $categories;
		$this->data["content_types"]= $content_types;
		$this->data["tags"]			= $tags;
		$this->data["modules"] 		= $modules;
		$this->data["acls"]			= $acls;

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

	public function page_save()
	{
		$this->init();
		csrf_verify();
		$id = $this->input->post("page_id");

		if(!$this->current_user->has(ACTION_PAGES) ||
		   !$this->acl->checkPage($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("page", "moduleinstance", "modulepagelink", "content_value", "page_tag"));

		$page = new Page();
		$page->load($id);
		$old_url = $page->url;
		$page->readPostVars();
		$page->start_publishing = (strtotime($this->input->post("start_publishing")) 	!== false ? date("Y-m-d H:i:s", strtotime($this->input->post("start_publishing")))  : null);
		$page->stop_publishing 	= (strtotime($this->input->post("stop_publishing")) 	!== false ? date("Y-m-d H:i:s", strtotime($this->input->post("stop_publishing"))) 	: null);

		$count = 1;
		$new_url = $page->url;

		// Loop until we have a unique URL for this page/site.
		while(!$this->page->checkURL(Mainframe::active_site_id(), $page->page_id, $new_url))
		{
			$new_url = $page->url . $count;
			$count++;
		}

		// If the URL was modified above, set it to the new URL.
		$page->url = $new_url;

		if($page->created == "" || $page->created == "0000-00-00 00:00:00")
		{
			$page->created = date("Y-m-d H:i:s");
		}
		else
		{
			$page->modified = date("Y-m-d H:i:s");
		}
		$saved = $page->save();
		$this->cache->clean();

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

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

			if(!$acl->acl_id)
			{
				$acl->page_id 		= $page->page_id;
				$acl->acl_role_id 	= $role->acl_role_id;
			}

			if($this->input->post("acl_" . $role->acl_role_id) == "r")
			{
				$acl->read = 1;
				$acl->write = 0;
			}
			else if($this->input->post("acl_" . $role->acl_role_id) == "rw")
			{
				$acl->read = 1;
				$acl->write = 1;
			}
			else
			{
				$acl->read = 0;
				$acl->write = 0;
			}
			$acl->save();
		}

		$modules = $this->moduleinstance->LoadInstalledByPageID($page->page_id, $page->site_id);

		foreach($modules as $module)
		{
			$mp = new ModulePageLink();
			$new = false;

			if(!$mp->LoadByModulePageIDs($module->module_instance_id, $page->page_id))
			{
				/*
				no longer needed
				if($this->input->post("module_$module->module_instance_id") == "1")
				{
					$mp->Add();
				}
				*/

				$new = true;
			}

			if($this->input->post("module_$module->module_instance_id") == "1")
			{
				$mp->module_instance_id = $module->module_instance_id;
				$mp->page_id = $page->page_id;
				$saved = $mp->save() && $saved;
			}
			else if(!$new)
			{
				$saved = $mp->Delete() && $saved;
			}
		}

		// Using the new content types, we just look for any POST vars starting with field_
		// Look for textual fields.
		// Because unchecked checkboxes are NOT sent in POST, we also have to remove any settings that were not sent.
		$content_value_ids = "";
		foreach($this->input->post() as $key => $value)
		{
			if(preg_match('/^field_/', $key))
			{
				$key = str_replace("field_", "", $key);

				// Data URLs will be added to the $_FILES array for processing below.
				if(preg_match('/^data:(.*?);/', $value, $filetype))
				{
					$newfile  = array("simulated" => true);
					$filetype = $filetype[1];

					if($filetype == "image/jpeg" || $filetype == "image/jpg")
					{
						$newfile["file_name"] = $page->page_id . "_" . $key . ".jpg";
						$newfile["file_ext"] = ".jpg";
					}
					else if($filetype == "image/png")
					{
						$newfile["file_name"] = $page->page_id . "_" . $key . ".png";
						$newfile["file_ext"] = ".png";
					}
					else
					{
						// Only JPG and PNG are supported via data URI.
						continue;
					}

					$newfile["raw_name"] = substr($newfile["file_name"], 0, -4);
					$contents            = file_get_contents($value);
					$tmp_name            = ABSOLUTE_PATH . '/images/content/' . $newfile["file_name"];
					file_put_contents($tmp_name, $contents);
					$_FILES["field_" . $key] = $newfile;

					continue;
				}

				$v = new Content_value();
				$v->LoadByPageKey($page->page_id, $key);
				$v->page_id 	= $page->page_id;
				$v->key 		= $key;
				$v->value 		= cleanAngularGarbage($value);

				if($key == "page_id")
				{
					$v->value 	= $page->page_id;
				}

				$saved = $v->save() && $saved;
				$content_value_ids .= $v->content_value_id . ",";
			}
		}

		if($content_value_ids)
		{
			$content_value_ids = substr($content_value_ids, 0, -1);
			$this->content_value->delete_all_others($page->page_id, $content_value_ids);
		}
		else
		{
			$this->content_value->delete_all($page->page_id);
		}

		// Look for file uploads.
		$config['upload_path'] 		= ABSOLUTE_PATH . '/images/content/';
		$config['allowed_types'] 	= 'gif|jpg|jpeg|png|pdf|doc|docx|xls|xlsx';
		$config['encrypt_name']		= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] 		= true;
		$this->load->library('upload', $config);

		foreach($_FILES as $key => $value)
		{
			if(preg_match('/^field_/', $key))
			{
				if(isset($_FILES[$key]["simulated"]) || $this->upload->do_upload("$key"))
				{
					if(isset($_FILES[$key]["simulated"]))
					{
						$upload_data = $_FILES[$key];
					}
					else
					{
						$upload_data = $this->upload->data();

						if($upload_data["file_type"] == "image/jpeg" || $upload_data["file_type"] == "image/jpg")
						{
							$upload_data["file_name"] = $page->page_id . "_" . $key . ".jpg";
						}
						else if($upload_data["file_type"] == "image/png")
						{
							$upload_data["file_name"] = $page->page_id . "_" . $key . ".png";
						}
						$upload_data["raw_name"] = $page->page_id . "_" . $key;

						rename($upload_data["full_path"], ABSOLUTE_PATH . "/images/content/" . $upload_data["file_name"]);
					}
					$key      = str_replace("field_", "", $key);
					$filename = $upload_data["file_name"];

					Mainframe::deleteThumbnails($upload_data["full_path"]);
					Mainframe::deleteThumbnails(ABSOLUTE_PATH . "/images/content/" . $filename);

					// If there are width and height fields with a matching name, we'll resize the image automatically.
					// ie: if the image field was "myimage" we look for "myimage_width" and "myimage_height"
					if($this->input->post("field_" . $key . "_width") && $this->input->post("field_" . $key . "_height"))
					{
						$thumbnail = $upload_data["raw_name"] . "_" . $this->input->post("field_" . $key . "_width") . "x" . $this->input->post("field_" . $key . "_height") . $upload_data["file_ext"];

						// Delete file first, if it exists.
						if(file_exists(ABSOLUTE_PATH . "/images/content/" . $thumbnail))
						{
							unlink(ABSOLUTE_PATH . "/images/content/" . $thumbnail);
						}

						if($this->input->post("field_" . $key . "_crop") != "")
						{
							$crop = $this->input->post("field_" . $key . "_crop");

							if($crop == "0")
							{
								$crop = false;
							}
						}
						else
						{
							$crop = $this->input->post("field_" . $key . "_width") . ":" . $this->input->post("field_" . $key . "_height");
						}

						createThumbnail($upload_data["full_path"], ABSOLUTE_PATH . "/images/content/" . $thumbnail,
						                $this->input->post("field_" . $key . "_width"),
						                $this->input->post("field_" . $key . "_height"), true,
						                $crop, Mainframe::site()->webp ? IMAGETYPE_WEBP : null);
					}

					$v = new Content_value();
					$v->LoadByPageKey($page->page_id, $key);
					$v->page_id 	= $page->page_id;
					$v->key 		= $key;
					$v->value 		= "/images/content/" . $filename;
					$saved 			= $v->save() && $saved;
				}
			}
		}

		if($saved)
		{
			// Tags
			$ids = "";
			$tags = explode(",", $this->input->post("tag_ids"));

			foreach($tags as $t)
			{
				$tag = new Page_tag();

				if(is_numeric($t))
				{
					$tag->load($t);
				}
				else if(trim($t) != "")
				{
					$t = trim($t);
					$tag->loadByTagName($t);
					$tag->tag = $t;
					$tag->save();
				}

				if($tag->tag_id)
				{
					$this->page_tag->add_link($tag->tag, $page->page_id);
					$ids .= $tag->tag_id . ",";
				}
			}

			if($ids)
			{
				$ids = substr($ids, 0, -1);
				$this->page_tag->deleteAllOtherTags($page->page_id, $ids);
			}
			else
			{
				$this->page_tag->deleteAllTags($page->page_id);
			}

			$this->clean_css_cache();
			$this->cache->clean();
			$this->messages[] = "Page Saved";

			// Reload the page so we get reloaded field values
			$page->load($page->page_id);

			foreach($page->fields as $f)
			{
				$width = null;
				$height = null;

				foreach($page->fields as $f2)
				{
					if($f2->key == $f->key . "_width")
					{
						$width = $f2->value;
					}
					else if($f2->key == $f->key . "_height")
					{
						$height = $f2->value;
					}
				}

				if($width && $height)
				{
					// If this is already a resized image, which it should be, get the original file.
					if(preg_match('/_([0-9]*)x([0-9]*)\.(jpe?g|png|gif|webp)/i', $f->value))
					{
						$src = str_replace(LIVE_SITE, "", preg_replace('/_([0-9]*)x([0-9]*)\.(jpe?g|png|gif|webp)/i', '.$3', $f->value));
					}
					else
					{
						$src = $f->value;
					}

					if($src && file_exists(ABSOLUTE_PATH . $src))
					{
						$ext 		= strrpos($src, ".");
						$filename 	= substr($src, 0, $ext) . "_" . $width . "x" . $height . substr($src, $ext);

						// Delete file first, if it exists.
						if(file_exists(ABSOLUTE_PATH . $filename))
						{
							unlink(ABSOLUTE_PATH . $filename);
						}
						Mainframe::deleteThumbnails(ABSOLUTE_PATH . $src);
						Mainframe::deleteThumbnails(ABSOLUTE_PATH . $filename);

						createThumbnail(ABSOLUTE_PATH . $src,
						                ABSOLUTE_PATH . $filename,
							            $width,
							            $height, true,
							            $width . ":" . $height,
							            Mainframe::site()->webp ? IMAGETYPE_WEBP : null);

						$v = new Content_value();
						$v->Load($f->content_value_id);
						$v->value = $filename;
						$v->save();
					}
				}
			}

			if($page->content_type_id == 1) // Blank page
			{
				// Get all images from the content.
				preg_match_all('/<img (.*?)\/?>/i', $page->field("content1"), $images);

				// Remove any which are module placeholders.
				$final_images = array();
				foreach($images[0] as $i)
				{
					if(stripos($i, "module_placeholder") === false)
					{
						$final_images[] = $i;
					}
				}

				// If only one image is present, the user should consider a "content + image" content type.
				if(count($final_images) == 1)
				{
					$this->messages[] = "Your content only has one image. The content type \"Content with image aligned left or right\" may be better suited. <a href='/admin/content_type_1_to_2/" . $page->page_id . "'>Click here to automatically convert it.</a>";
				}
			}

			// If the URL has been updated, we'll automatically add a redirect for the old URL.
			if($old_url && $old_url != $new_url)
			{
				$redirect = new Redirect();
				$redirect->loadByURL($old_url, $page->site_id);
				$redirect->old_url = $old_url;
				$redirect->new_url = null;
				$redirect->site_id = $page->site_id;
				$redirect->page_id = $page->page_id;
				$redirect->save();

				$this->messages[] = "A redirect was automatically created because you changed the page's URL.";
			}
		}
		else
		{
			$this->errors[] = "Error Saving Page";
		}

		// Regenerate RSS feed.
		$this->generate_rss();

		if($this->input->post("submit_action") == "continue")
		{
			$this->page($page->page_id);
		}
		else
		{
			$this->pages();
		}
	}

	public function page_delete()
	{
		$this->init();
		$id = get_id();

		if(!$this->current_user->has(ACTION_PAGES) ||
		   !$this->acl->checkPage($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->clean_css_cache();
		$this->generic_delete("page", "pages");
	}

	public function content_type_1_to_2($page_id)
	{
		$this->init();

		if(!$this->current_user->has(ACTION_PAGES) ||
		   !$this->acl->checkPage($page_id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$p = new Page();
		$p->load($page_id);

		// Get all images from the content.
		preg_match('/<img (.*?)\/?>/i', $p->field("content1"), $images);

		if(count($images) == 0)
		{
			$this->errors[] = "No images were found in this content item. It was not converted.";
			$this->page($p->page_id);
			return;
		}

		// Get the image src
		preg_match('/<img .*?src=[\'"](.*?)[\'"].*?>/', $images[0], $srcs);

		$copy_from 	= str_replace("//", "/", ABSOLUTE_PATH . "/" . str_replace(LIVE_SITE, "", $srcs[1]));
		$copy_to 	= ABSOLUTE_PATH . "/images/content/" . basename($srcs[1]);
		$size 		= getimagesize($copy_from);

		if(!copy($copy_from, $copy_to))
		{
			$this->errors[] = "An error occurred converting this page.";
			$this->page($p->page_id);
			return;
		}

		$p->content_type_id = 2;
		$p->save();
		$this->cache->clean();

		$setting = new Content_value();
		$setting->load($p->fields["content1"]->content_value_id);
		$setting->value = preg_replace('/<img (.*?)\/?>/', "", $setting->value);
		$setting->save();

		$setting = new Content_value();
		$setting->page_id = $p->page_id;
		$setting->key = "image";
		$setting->value = "/images/content/" . basename($srcs[1]);
		$setting->save();

		$setting = new Content_value();
		$setting->page_id = $p->page_id;
		$setting->key = "image_width";
		$setting->value = $size[0];
		$setting->save();

		$setting = new Content_value();
		$setting->page_id = $p->page_id;
		$setting->key = "image_height";
		$setting->value = $size[1];
		$setting->save();

		$setting = new Content_value();
		$setting->page_id = $p->page_id;
		$setting->key = "image-alignment";
		$setting->value = "right";
		$setting->save();

		$this->cache->clean();
		$this->messages[] = "Your page was converted to \"Content with image aligned left or right\"";
		$this->page($p->page_id);
	}

	public function generate_rss()
	{
		$CI =& get_instance();
		$CI->load->model(array("site", "page"));

		// Currently RSS is only supported for the first site, which 99.9% of the time is the only site.
		$site = new Site();
		$site->load(1);
		$pages = $CI->page->LoadForRSSFeed(1);

		$rss = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
		$rss .= '<rss version="2.0">' . "\n";
		$rss .= "\t" . '<channel>' . "\n";
		$rss .= "\t\t" . '<title>' . htmlspecialchars($site->title) . '</title>' . "\n";
		$rss .= "\t\t" . '<description></description>' . "\n";
 		$rss .= "\t\t" . '<link>' . LIVE_SITE . '</link>' . "\n";
 		$rss .= "\t\t" . '<lastBuildDate>' . date("D, d M Y H:i:s O") . '</lastBuildDate>' . "\n";
 		$rss .= "\t\t" . '<pubDate>' . date("D, d M Y H:i:s O") . '</pubDate>' . "\n";
 		$rss .= "\t\t" . '<ttl>1800</ttl>' . "\n";

 		foreach($pages as $p)
 		{
 			$rss .= "\t\t" . '<item>' . "\n";
			$rss .= "\t\t\t" . '<title>' . htmlspecialchars($p->title) . '</title>' . "\n";
			$rss .= "\t\t\t" . '<description>' . htmlspecialchars($p->description) . '</description>' . "\n";
			$rss .= "\t\t\t" . '<link>' . LIVE_SITE . "/" . $p->url . '</link>' . "\n";
			$rss .= "\t\t\t" . '<guid isPermaLink="false">' . $p->page_id . '</guid>' . "\n";
			$rss .= "\t\t\t" . '<pubDate>' . date("D, d M Y H:i:s O", strtotime($p->created)) . '</pubDate>' . "\n";
			$rss .= "\t\t" . '</item>' . "\n";
 		}

 		$rss .= "\t" . '</channel>' . "\n";
		$rss .= '</rss>';

		@file_put_contents(ABSOLUTE_PATH . "/rss.xml", $rss);
	}
	/* end page functions */

	/* module functions */
	public function modules_available()
	{
		$this->init();

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

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

	public function modules()
	{
		$this->init();

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

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

	public function module($module_id, $module_instance_id=0)
	{
		$this->init();

		if(!$this->current_user->has(ACTION_MODULES) ||
		   !$this->acl->checkModule($module_instance_id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("module", "moduleinstance", "site", "page"));

		$module = new ModuleInstance();
		$module->Load($module_instance_id);

		$sites 		= $this->site->get();
		$module_id 	= (isset($module->module_id) ? $module->module_id : $module_id);
		$pages 		= $this->page->LoadBySiteModuleIDs(($module->site_id ? $module->site_id : Mainframe::active_site_id()), $module_instance_id, $this->current_user);

		$module_type = new Module();
		$module_type->Load($module_id);

		// When creating new modules, the file_name is not set.  We need it.
		$module->module_id = $module_id;
		$module->file_name = $module_type->file_name;

		$site = new Site();
		$site->Load(($module->site_id ? $module->site_id : Mainframe::active_site_id()));

		require_once(MODULE_PATH . "/$module_type->file_name/$module_type->file_name" . ".php");
		$m = new $module_type->class_name;
		$m->Load($module_instance_id);

		$modules 	= Mainframe::LoadModulesBySiteID(Mainframe::active_site_id());
		$positions 	= Mainframe::GetModulePositions($site->template_id);
		$acls 		= $this->acl->loadByModuleInstanceID($module_instance_id);

		$this->data["id"] 			= $module_instance_id;
		$this->data["module"] 		= $module;
		$this->data["positions"] 	= $positions;
		$this->data["pages"] 		= $pages;
		$this->data["site"] 		= $site;
		$this->data["sites"] 		= $sites;
		$this->data["m"] 			= $m;
		$this->data["modules"] 		= $modules;
		$this->data["module_id"] 	= $module_id;
		$this->data["acls"]			= $acls;

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

	public function module_save()
	{
		$this->init();
		$id = $this->input->post("module_instance_id");
		csrf_verify();

		if(!$this->current_user->has(ACTION_MODULES) ||
		   !$this->acl->checkModule($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("module", "modulesetting", "moduleinstance", "page", "modulepagelink"));

		$module = new ModuleInstance();
		$module->load($id);
		$module->readPostVars();

		if($module->position == "")
		{
			$module->always_on = 0;
		}

		$saved = $module->save();
		$this->cache->clean();

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

		foreach($roles as $role)
		{
			$acl = new ACL();
			$acl->loadByModuleRoleID($module->module_instance_id, $role->acl_role_id);

			if(!$acl->acl_id)
			{
				$acl->module_instance_id 	= $module->module_instance_id;
				$acl->acl_role_id 			= $role->acl_role_id;
			}

			if($this->input->post("acl_" . $role->acl_role_id) == "r")
			{
				$acl->read = 1;
				$acl->write = 0;
			}
			else if($this->input->post("acl_" . $role->acl_role_id) == "rw")
			{
				$acl->read = 1;
				$acl->write = 1;
			}
			else
			{
				$acl->read = 0;
				$acl->write = 0;
			}
			$acl->save();
		}

		$m1 = new Module();
		$m1->load($module->module_id);

		// Run the module's installer, which will be configured to handle module dependencies if required by this module.
		require_once(MODULE_PATH . "/" . $m1->file_name . "/" . $m1->file_name . ".php");

		$m2 = new $m1->class_name();
		$m2->Load($m1->module_id);
		try
		{
			$m2->install();
		}
		catch(Exception $e){}

		$pages = $this->page->LoadBySiteModuleIDs($this->input->post("site_id"), $module->module_instance_id, $this->current_user);

		foreach($pages as $page)
		{
			$mp = new ModulePageLink();
			$new = false;

			if(!$mp->LoadByModulePageIDs($module->module_instance_id, $page->page_id))
			{
				/*
				no longer needed
				if($this->input->post("page_$page->page_id") == "1")
				{
					$mp->Add();
				}
				*/

				$new = true;
			}

			if($module->always_on > 0 || $this->input->post("page_$page->page_id") == "1")
			{
				// Skip the default page if always_on == "all pages except home"
				if($module->always_on == 2 && Mainframe::site()->default_page_id == $page->page_id)
				{
					// If the link already existed, delete it.
					if(!$new)
					{
						$mp->delete();
					}

					continue;
				}
				// Skip all inside pages if always_on == "only main page"
				else if($module->always_on == 3 && Mainframe::site()->default_page_id != $page->page_id)
				{
					// If the link already existed, delete it.
					if(!$new)
					{
						$mp->delete();
					}

					continue;
				}

				$mp->module_instance_id = $module->module_instance_id;
				$mp->page_id = $page->page_id;
				$saved = $mp->save() && $saved;
			}
			else if(!$new)
			{
				$saved = $mp->Delete() && $saved;
			}
		}

		// Using the new module_settings, we just look for any POST vars starting with param_
		// Because unchecked checkboxes are NOT sent in POST, we also have to remove any settings that were not sent.
		// First we are going to check for param_ which will always be the current module.
		$ids = "";

		foreach($this->input->post() as $key => $value)
		{
			if(preg_match('/^param_/', $key))
			{
				$key = str_replace("param_", "", $key);

				$s = new ModuleSetting();
				$s->LoadByModuleKey($module->module_instance_id, $key);
				$s->module_instance_id 	= $module->module_instance_id;
				$s->key 				= $key;
				$s->value 				= cleanAngularGarbage($value);
				$saved 					= $s->save() && $saved;

				$ids .= $s->module_setting_id . ",";
			}
		}

		// Look for file uploads.
		$config['upload_path'] 		= ABSOLUTE_PATH . '/images/content/';
		$config['allowed_types'] 	= 'gif|jpg|jpeg|png|pdf|doc|docx|xls|xlsx';
		$config['encrypt_name']		= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] 		= true;
		$this->load->library('upload', $config);

		foreach($_FILES as $key => $value)
		{
			if(preg_match('/^param_/', $key))
			{
				if($this->upload->do_upload("$key"))
				{
					$key 			= str_replace("param_", "", $key);
					$upload_data 	= $this->upload->data();
					$filename 		= $upload_data["file_name"];

					Mainframe::deleteThumbnails($upload_data["full_path"]);
					Mainframe::deleteThumbnails(ABSOLUTE_PATH . "/images/content/" . $filename);

					// If there are width and height fields with a matching name, we'll resize the image automatically.
					// ie: if the image field was "myimage" we look for "myimage_width" and "myimage_height"
					if($this->input->post("param_" . $key . "_width") && $this->input->post("param_" . $key . "_height"))
					{
						$filename = $upload_data["raw_name"] . "_" . $this->input->post("param_" . $key . "_width") . "x" . $this->input->post("param_" . $key . "_height") . $upload_data["file_ext"];

						// Delete file first, if it exists.
						if(file_exists(ABSOLUTE_PATH . "/images/content/" . $filename))
						{
							unlink(ABSOLUTE_PATH . "/images/content/" . $filename);
						}

						if($this->input->post("param_" . $key . "_crop") != "")
						{
							$crop = $this->input->post("param_" . $key . "_crop");

							if($crop == "0")
							{
								$crop = false;
							}
						}
						else
						{
							$crop = $this->input->post("param_" . $key . "_width") . ":" . $this->input->post("param_" . $key . "_height");
						}

						createThumbnail($upload_data["full_path"], ABSOLUTE_PATH . "/images/content/" . $filename,
						                $this->input->post("param_" . $key . "_width"),
						                $this->input->post("param_" . $key . "_height"), true,
						                $crop, Mainframe::site()->webp ? IMAGETYPE_WEBP : null);
					}

					$s = new ModuleSetting();
					$s->LoadByModuleKey($module->module_instance_id, $key);
					$s->module_instance_id 	= $module->module_instance_id;
					$s->key 				= $key;
					$s->value 				= "/images/content/" . $filename;
					$saved 					= $s->save() && $saved;
					$ids .= $s->module_setting_id . ",";
				}
			}
		}

		if($ids)
		{
			$ids = substr($ids, 0, -1);
			$this->modulesetting->delete_all_others($module->module_instance_id, $ids);
		}
		else
		{
			$this->modulesetting->delete_all($module->module_instance_id);
		}

		// If there are any children of this module, their fields will be indexed starting from 0, in the same order as they
		// were defined in the module. For example: param_1_width refers to "width" for "child 1" of this module (index 0).
		// The form indexes are 1 based while the children array is zero based. This is for clarity on the settings form.
		if(count($m2->children()))
		{
			for($i=0; $i<count($m2->children()); $i++)
			{
				$module_instance_id 	= $m2->children()[$i];
				$ids 			= "";

				foreach($this->input->post() as $key => $value)
				{
					if(preg_match('/^param_' . ($i+1) . '_/', $key))
					{
						$key = str_replace("param_" . ($i+1) . "_", "", $key);

						$s = new ModuleSetting();
						$s->LoadByModuleKey($module_instance_id, $key);
						$s->module_instance_id 	= $module_instance_id;
						$s->key 				= $key;
						$s->value 				= cleanAngularGarbage($value);
						$saved 					= $s->save() && $saved;

						$ids .= $s->module_setting_id . ",";
					}
				}

				if($ids)
				{
					$ids = substr($ids, 0, -1);
					$this->modulesetting->delete_all_others($module_instance_id, $ids);
				}
				else
				{
					$this->modulesetting->delete_all($module_instance_id);
				}
			}
		}

		if($saved)
		{
			$this->clean_css_cache();
			$this->cache->clean();
			$this->messages[] = "Module Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Module";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->module($module->module_id, $module->module_instance_id);
		}
		else
		{
			$this->modules();
		}
	}

	public function module_delete()
	{
		$this->init();
		$id = get_id();

		if(!$this->current_user->has(ACTION_MODULES) ||
		   !$this->acl->checkModule($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model("moduleinstance");

		if($id)
		{
			$moduleinstance = new ModuleInstance();
			$moduleinstance->load($id);

			if($moduleinstance->delete())
			{
				$this->clean_css_cache();
				$this->messages[] = "Module Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Module.";
			}
		}

		$this->modules();
	}

	public function module_duplicate($id=0)
	{
		$this->init();

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

		if($id <= 0)
		{
			$this->errors[] = "Invalid Module";
			$this->modules_available();
			return;
		}

		$this->load->model("module");

		$module = new Module();
		$module->load($id);

		$new_module = new Module();
		$new_module->module_id 	= "";
		$new_module->name 		= $module->name . " (Custom)";
		$new_module->file_name 	= $module->file_name . "_custom";
		$new_module->class_name = "Custom" . $module->class_name;

		// Keep going until we find a directory name that doesn't exist yet.
		// In most cases, this loop will never run.
		$count = 0;
		while(file_exists(ABSOLUTE_PATH . "/modules/" . $new_module->file_name))
		{
			$count++;
			$new_module->file_name = $module->file_name . "_custom" . $count;
			$new_module->class_name = "Custom" . $module->class_name . $count;
		}
		$new_module->save();

		// Copy main module directory.
		if(file_exists(ABSOLUTE_PATH . "/modules/" . $module->file_name))
		{
			copy_directory(ABSOLUTE_PATH . "/modules/" . $module->file_name, ABSOLUTE_PATH . "/modules/" . $new_module->file_name);
		}

		// Copy module views directory.
		if(file_exists(ABSOLUTE_PATH . "/application/views/modules/" . $module->file_name))
		{
			copy_directory(ABSOLUTE_PATH . "/application/views/modules/" . $module->file_name, ABSOLUTE_PATH . "/application/views/modules/" . $new_module->file_name);
		}

		// Rename the main module PHP file.
		if(file_exists(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $module->file_name . ".php"))
		{
			rename(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $module->file_name . ".php",
			       ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $new_module->file_name . ".php");
		}

		// Rename the main module LESS file.
		if(file_exists(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $module->file_name . ".less"))
		{
			rename(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $module->file_name . ".less",
			       ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $new_module->file_name . ".less");
		}

		// Automatically replace some parts of the new module.
		$module_content = file_get_contents(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $new_module->file_name . ".php");
		$module_content = str_ireplace("class " . $module->class_name, "class " . $new_module->class_name, $module_content);
		$module_content = str_ireplace("function " . $module->class_name, "function " . $new_module->class_name, $module_content);
		$module_content = str_ireplace("modules/" . $module->file_name, "modules/" . $new_module->file_name, $module_content);
		file_put_contents(ABSOLUTE_PATH . "/modules/" . $new_module->file_name . "/" . $new_module->file_name . ".php", $module_content);

		$this->messages[] = "\"" . $module->name . "\" Duplicated As \"" . $new_module->name . "\"";
		$this->messages[] = "Customize the module at " . ABSOLUTE_PATH . "/modules/" . $new_module->file_name;

		$this->modules_available();
	}
	/* end module functions */

	/* menu functions */
	public function menus()
	{
		$this->init();

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

		$this->generic_list("menu", "menus");
	}

	private function buildMenu($menu_id, $parent_id=null)
	{
		$items = $this->menuitem->LoadByMenuID($menu_id, $parent_id, null);

		foreach($items as $item)
		{
			$item->children = $this->buildMenu($menu_id, $item->menu_item_id);
		}

		return $items;
	}

	public function menu($id=0)
	{
		$this->init();

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

		$this->load->model(array("menu", "menuitem", "site", "page"));

		$menu = new Menu();
		$menu->load($id);

		$menuitems 	= $this->buildMenu($id, null);
		$orphans 	= $this->page->getUnlinkedByMenuID(Mainframe::active_site_id(), $menu->menu_id);
		$sites 		= $this->site->get();

		$this->data["id"] 		= $id;
		$this->data["menu"] 	= $menu;
		$this->data["items"] 	= $menuitems;
		$this->data["orphans"] 	= $orphans;
		$this->data["sites"] 	= $sites;

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

	private function _sort_menu_items($sorting, $parent_id)
	{
		$count = 1;

		foreach($sorting as $sort)
		{
			$item = new MenuItem();
			$item->Load($sort->id);
			$item->parent_id = $parent_id;
			$item->sort = $count;
			$item->save();
			$count++;

			if(count($sort->children[0]))
			{
				$this->_sort_menu_items($sort->children[0], $sort->id);
			}
		}
		$this->cache->clean();
	}

	public function menu_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model(array("menu", "menuitem"));
		$id = $this->input->post("menu_id");

		$menu = new Menu();
		$menu->load($id);
		$menu->readPostVars();

		if($menu->save())
		{
			$this->cache->clean();

			if($this->input->post("sorting"))
			{
				$sorting = json_decode($this->input->post("sorting"))[0];
				$this->_sort_menu_items($sorting, null);
			}

			$this->cache->clean();
			$this->messages[] = "Menu Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Menu";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->menu($menu->menu_id);
		}
		else
		{
			$this->menus();
		}
	}

	public function menu_delete()
	{
		$this->init();

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

		$this->generic_delete("menu", "menus");
	}
	/* end menu functions */

	/* menu item functions */
	public function menuitem($menu_id, $id=0)
	{
		$this->init();

		if(!$this->current_user->has(ACTION_MENUS) ||
		   !$this->acl->checkMenuItem($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("menu", "menuitem", "site", "page"));

		$item = new MenuItem();
		$item->load($id);

		$menus = $this->menu->LoadBySiteID(Mainframe::active_site_id());
		$sites = $this->site->get();

		$menu = new Menu();
		$menu->Load($item->menu_id);

		$items = $this->menuitem->LoadByMenuID(($id ? $item->menu_id : $menu_id), -1, null);
		$pages = $this->page->LoadBySiteID(($menu->site_id ? $menu->site_id : Mainframe::active_site_id()), $this->current_user, "category_name, p.`title`");
		$acls  = $this->acl->loadByMenuItemID($item->menu_item_id);

		$this->data["id"] 		= $id;
		$this->data["menu_id"] 	= $menu_id;
		$this->data["item"] 	= $item;
		$this->data["items"] 	= $items;
		$this->data["menu"] 	= $menu;
		$this->data["menus"] 	= $menus;
		$this->data["sites"] 	= $sites;
		$this->data["pages"] 	= $pages;
		$this->data["acls"] 	= $acls;

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

	public function menuitem_save()
	{
		$this->init();
		csrf_verify();

		$id = $this->input->post("menu_item_id");

		if(!$this->current_user->has(ACTION_MENUS) ||
		   !$this->acl->checkMenuItem($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model("menuitem");

		$item = new MenuItem();
		$item->load($id);
		$item->readPostVars();

		// When no page or URL is provided, link to # automatically.
		// If a page is provided, remove the URL if it was set to #.
		if(!$item->page_id && !$item->url)
		{
			$item->url = "#";
		}
		else if($item->page_id && $item->url == "#")
		{
			$item->url = null;
		}

		if($item->save())
		{
			$this->cache->clean();

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

			foreach($roles as $role)
			{
				$acl = new ACL();
				$acl->loadByMenuItemRoleID($item->menu_item_id, $role->acl_role_id);

				if(!$acl->acl_id)
				{
					$acl->menu_item_id 	= $item->menu_item_id;
					$acl->acl_role_id 	= $role->acl_role_id;
				}

				if($this->input->post("acl_" . $role->acl_role_id) == "r")
				{
					$acl->read = 1;
					$acl->write = 0;
				}
				else if($this->input->post("acl_" . $role->acl_role_id) == "rw")
				{
					$acl->read = 1;
					$acl->write = 1;
				}
				else
				{
					$acl->read = 0;
					$acl->write = 0;
				}
				$acl->save();
			}

			$this->cache->clean();
			$this->messages[] = "Menu Item Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Menu Item";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->menuitem($item->menu_id, $item->menu_item_id);
		}
		else
		{
			$this->menu($item->menu_id);
		}
	}

	public function menuitem_bulk_save()
	{
		$this->init();
		csrf_verify();

		$ids 		= explode(",", $this->input->post("page_ids"));
		$menu_id 	= $this->input->post("menu_id");
		$sort 		= 1000;

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

		foreach($ids as $id)
		{
			$p = new Page();
			$p->load($id);

			$item 				= new MenuItem();
			$item->menu_id 		= $menu_id;
			$item->title 		= $p->title;
			$item->page_id 		= $p->page_id;
			$item->sort 		= $sort;
			$item->published 	= 1;
			$sort++;

			if($item->save())
			{
				// Save ACL
				$roles = $this->acl_role->get();

				foreach($roles as $role)
				{
					$acl 				= new Acl();
					$acl->acl_role_id 	= $role->acl_role_id;
					$acl->menu_item_id 	= $item->menu_item_id;

					if($role->acl_role_id == 3)
					{
						$acl->read = 1;
						$acl->write = 1;
					}
					else
					{
						$acl->read = 1;
						$acl->write = 0;
					}
					$acl->save();
				}

				$this->messages[] = "Menu Items Saved";
			}
			else
			{
				$this->errors[] = "Error saving one or more menu items";
			}
		}

		$this->cache->clean();
		$this->messages = array_unique($this->messages);
		$this->errors 	= array_unique($this->errors);

		$this->menu($menu_id);
	}

	public function menuitem_delete()
	{
		$this->init();

		$id = get_id();

		if(!$this->current_user->has(ACTION_MENUS) ||
		   !$this->acl->checkMenuItem($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model("menuitem");

		if($id)
		{
			$item = new MenuItem();
			$item->load($id);
			$menu_id = $item->menu_id;

			if($item->delete())
			{
				$this->messages[] = "Menu Item Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Menu Item.";
			}
		}

		$this->menu($menu_id);
	}
	/* end menu item functions */

	/* booking functions */
	public function appointments()
	{
		$this->init();

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

		$this->generic_list("appointment", "appointments");
	}

	public function appointment_delete()
	{
		$this->init();

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

		$this->load->model("appointment");

		$id = get_id();

		if($id)
		{
			$appt = new Appointment();
			$appt->load($id);

			if($appt->delete())
			{
				$this->messages[] = "Appointment Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Appointment.";
			}
		}

		$this->appointments();
	}
	/* end booking functions */

	/* event functions */
	public function events()
	{
		$this->init();

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

		$this->generic_list("event", "events");
	}

	public function event($id=0)
	{
		$this->init();

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

		$this->load->model(array("event"));

		$event = new Event();
		$event->load($id);

		$events = $this->event->LoadParents(Mainframe::active_site_id());

		$this->data["id"] = $id;
		$this->data["event"] = $event;
		$this->data["events"] = $events;

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

	public function event_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("event");
		$id = $this->input->post("event_id");

		$event = new Event();
		$event->load($id);
		$event->readPostVars();

		// URLs on child events are problematic.
		if($event->parent_id > 0)
		{
			$event->url = null;
		}

		$config['upload_path'] = ABSOLUTE_PATH . '/images/events/';
		$config['allowed_types'] = 'gif|jpg|jpeg|png';
		$config['max_size']	= '5242880';	//5MB
		$config['encrypt_name']	= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] = true;
		//$config['max_width']  = '1024';
		//$config['max_height']  = '768';

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

		if($this->upload->do_upload("banner_upload"))
		{
			$upload_data = array('upload_data' => $this->upload->data());
			$event->banner_image = $upload_data["upload_data"]["file_name"];
		}

		if($event->save())
		{
			$this->cache->clean();
			$this->messages[] = "Event Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Event";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->event($event->event_id);
		}
		else
		{
			$this->events();
		}
	}

	public function event_delete()
	{
		$this->init();

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

		$this->load->model("event");

		$id = get_id();

		if($id)
		{
			$event = new Event();
			$event->load($id);

			if($event->delete())
			{
				$this->messages[] = "Event Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Event.";
			}
		}

		$this->events();
	}
	/* end event functions */

	/* event form functions */
	public function event_forms()
	{
		$this->init();

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

		$this->generic_list("event_form", "event_forms");
	}

	public function event_form($id=0)
	{
		$this->init();

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

		$this->load->model(array("event", "event_form"));

		$form = new Event_Form();
		$form->load($id);

		$events = $this->event->LoadParents(Mainframe::active_site_id());

		$this->data["id"] = $id;
		$this->data["form"] = $form;
		$this->data["events"] = $events;

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

	public function event_form_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("event_form");
		$id = $this->input->post("form_id");

		$form = new Event_Form();
		$form->load($id);
		$form->readPostVars();

		if($form->save())
		{
			$this->cache->clean();
			$this->messages[] = "Event Form Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Event Form";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->event_form($form->form_id);
		}
		else
		{
			$this->event_forms();
		}
	}

	public function event_form_delete()
	{
		$this->init();

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

		$this->load->model("event_form");

		$id = get_id();

		if($id)
		{
			$form = new Event_Form();
			$form->load($id);

			if($form->delete())
			{
				$this->messages[] = "Event Form Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Event Form";
			}
		}

		$this->event_forms();
	}
	/* end event form functions */

	/* metadata functions */
	public function metadata_pages()
	{
		$this->init();

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

		$this->load->model(array("metadata_page", "page"));

		$id = get_id();

		if($this->input->get_post("add") == "1")
		{
			$metadata = new Metadata_Page();
			$metadata->site_id = Mainframe::active_site_id();
			$metadata->page_id = 0;
			$metadata->url = time();
			$metadata->title = "";
			$metadata->description = "";
			$metadata->save();

			//redirect to avoid refreshes adding more and more new entries
			redirect("/admin/metadata_pages");
		}

		$metadata = $this->metadata_page->LoadAllPagesBySiteID(Mainframe::active_site_id());
		$pages = $this->page->get();

		$this->data["metadata"] = $metadata;
		$this->data["pages"] = $pages;
		$this->data["sites"] = $this->sites;

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

	public function metadata_pages_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model(array("metadata_page", "page"));

		$id = $this->input->post("metadata_id");

		$metadata = new Metadata_Page();
		$metadata->load($id);
		$metadata->readPostVars();

		//if we selected a page, use the URL field to update the page's URL
		if($metadata->page_id)
		{
			$page = new Page();
			$page->load($metadata->page_id);
			$page->url = $metadata->url;
			$page->title = $metadata->title;
			$page->description = $metadata->description;
			$page->save();
		}

		//AJAX request
		if($metadata->save())
		{
			$this->cache->clean();
			die((string)$metadata->metadata_id);
		}
		else
		{
			die("FAILED");
		}
	}

	public function get_page_data()
	{
		$this->init();

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

		$this->load->model("page");

		$id = $this->input->post("page_id");

		$page = new Page();
		$page->load($id);

		//AJAX request
		if(isset($page->page_id))
		{
			header("Content-type: application/json");
			echo(json_encode($page));
		}
		else
		{
			die("FAILED");
		}
	}

	public function metadata_pages_delete($id=null)
	{
		$this->init();

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

		$this->load->model("metadata_page");

		if($id)
		{
			$page = new Metadata_Page();
			$page->load($id);

			if($page->delete())
			{
				$this->messages[] = "Meta Data Deleted";
			}
			else
			{
				$this->errors[] = "Error Deleting Meta Data.";
			}

			$this->metadata_pages();
		}
	}

	private function find_dirs(&$dirs, $directory)
	{
		$dirs[] = $directory;

		$new_files = scandir($directory);

		foreach($new_files as $file)
		{
			if($file != "." && $file != "..")
			{
				if(is_dir("$directory/$file"))
				{
					$this->find_dirs($dirs, "$directory/$file");
				}
			}
		}
	}

	private function images_to_array(&$files, $directory)
	{
		$new_files = scandir($directory);

		foreach($new_files as $file)
		{
			if($file != "." && $file != "..")
			{
				if(is_file("$directory/$file") &&
				   preg_match('/(ui-(bg_|icons_)|@2x\.|_thumb_)/', $file) === 0)
				{
					if(preg_match('/\.(jpg|jpeg|gif|png|bmp|tiff)$/i', trim($file)) > 0)
					{
						$files[] = "$directory/$file";
					}
				}
				// else if(is_dir("$directory/$file"))
				// {
				// 	$this->images_to_array($files, "$directory/$file");
				// }
			}
		}
	}
	/* end metadata functions */

	/* role functions */
	public function roles()
	{
		$this->init();

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

		$this->generic_list("role", "roles");
	}

	public function role($acl_role_id=null)
	{
		$this->init();

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

		$role = new Acl_Role();
		$role->load($acl_role_id);

		$actions = $this->acl_action->loadByRoleID($role->acl_role_id);

		$this->data["actions"] 		= $actions;
		$this->data["acl_role_id"] 	= $acl_role_id;
		$this->data["role"] 		= $role;

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

	public function role_save()
	{
		$this->init();
		csrf_verify();

		$acl_role_id = $this->input->post("acl_role_id");

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

		$this->load->model(array("acl", "acl_role_action", "page", "moduleinstance", "document", "menuitem"));

		$role = new Acl_Role();
		$role->load($acl_role_id);
		$role->readPostVars();

		$saved = $role->save();
		$this->cache->clean();

		$actions = $this->acl_action->get();

		foreach($actions as $action)
		{
			$checked = $this->input->post("action_" . $action->acl_action_id);

			if($checked)
			{
				$this->acl_role_action->add_link($role->acl_role_id, $action->acl_action_id);
			}
			else
			{
				$this->acl_role_action->delete_link($role->acl_role_id, $action->acl_action_id);
			}
		}

		if($this->input->post("bulk_page_acl") == "n")
		{
			$pages = $this->page->get();

			foreach($pages as $p)
			{
				$acl = new ACL();
				$acl->loadByPageRoleID($p->page_id, $role->acl_role_id);
				$acl->delete();
			}
		}
		else if($this->input->post("bulk_page_acl") == "r")
		{
			$pages = $this->page->get();

			foreach($pages as $p)
			{
				$acl = new ACL();
				$acl->loadByPageRoleID($p->page_id, $role->acl_role_id);
				$acl->page_id = $p->page_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 0;
				$acl->save();
			}
		}
		else if($this->input->post("bulk_page_acl") == "rw")
		{
			$pages = $this->page->get();

			foreach($pages as $p)
			{
				$acl = new ACL();
				$acl->loadByPageRoleID($p->page_id, $role->acl_role_id);
				$acl->page_id = $p->page_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 1;
				$acl->save();
			}
		}

		if($this->input->post("bulk_document_acl") == "n")
		{
			$documents = $this->document->get();

			foreach($documents as $d)
			{
				$acl = new ACL();
				$acl->loadBydocumentRoleID($d->document_id, $role->acl_role_id);
				$acl->delete();
			}
		}
		else if($this->input->post("bulk_document_acl") == "r")
		{
			$documents = $this->document->get();

			foreach($documents as $d)
			{
				$acl = new ACL();
				$acl->loadBydocumentRoleID($d->document_id, $role->acl_role_id);
				$acl->document_id = $d->document_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 0;
				$acl->save();
			}
		}
		else if($this->input->post("bulk_document_acl") == "rw")
		{
			$documents = $this->document->get();

			foreach($documents as $d)
			{
				$acl = new ACL();
				$acl->loadByDocumentRoleID($d->document_id, $role->acl_role_id);
				$acl->document_id = $d->document_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 1;
				$acl->save();
			}
		}

		if($this->input->post("bulk_module_acl") == "n")
		{
			$modules = $this->moduleinstance->get();

			foreach($modules as $m)
			{
				$acl = new ACL();
				$acl->loadByModuleRoleID($m->module_instance_id, $role->acl_role_id);
				$acl->delete();
			}
		}
		else if($this->input->post("bulk_module_acl") == "r")
		{
			$modules = $this->moduleinstance->get();

			foreach($modules as $m)
			{
				$acl = new ACL();
				$acl->loadByModuleRoleID($m->module_instance_id, $role->acl_role_id);
				$acl->module_instance_id = $m->module_instance_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 0;
				$acl->save();
			}
		}
		else if($this->input->post("bulk_module_acl") == "rw")
		{
			$modules = $this->moduleinstance->get();

			foreach($modules as $m)
			{
				$acl = new ACL();
				$acl->loadByModuleRoleID($m->module_instance_id, $role->acl_role_id);
				$acl->module_instance_id = $m->module_instance_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 1;
				$acl->save();
			}
		}

		if($this->input->post("bulk_menu_acl") == "n")
		{
			$menu_items = $this->menuitem->get();

			foreach($menu_items as $m)
			{
				$acl = new ACL();
				$acl->loadByMenuItemRoleID($m->menu_item_id, $role->acl_role_id);
				$acl->delete();
			}
		}
		else if($this->input->post("bulk_menu_acl") == "r")
		{
			$menu_items = $this->menuitem->get();

			foreach($menu_items as $m)
			{
				$acl = new ACL();
				$acl->loadByMenuItemRoleID($m->menu_item_id, $role->acl_role_id);
				$acl->menu_item_id = $m->menu_item_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 0;
				$acl->save();
			}
		}
		else if($this->input->post("bulk_menu_acl") == "rw")
		{
			$menu_items = $this->menuitem->get();

			foreach($menu_items as $m)
			{
				$acl = new ACL();
				$acl->loadByMenuItemRoleID($m->menu_item_id, $role->acl_role_id);
				$acl->menu_item_id = $m->menu_item_id;
				$acl->acl_role_id = $role->acl_role_id;
				$acl->read = 1;
				$acl->write = 1;
				$acl->save();
			}
		}

		$this->cache->clean();

		if(!$acl_role_id)
		{
			//new record, continue editing so we can add actions
			$this->messages[] = "Role Created";
			$this->role($role->acl_role_id);
		}
		else
		{
			if($saved)
			{
				$this->messages[] = "Role Saved";
			}
			else
			{
				$this->errors[] = "Error Saving Role";
			}

			if($this->input->post("submit_action") == "continue")
			{
				$this->role($role->acl_role_id);
			}
			else
			{
				$this->roles();
			}
		}
	}

	public function role_delete($acl_role_id)
	{
		$this->init();

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

		$this->generic_delete("acl_role", "roles", $acl_role_id);
	}
	/* end role functions */

	/**
	 * Generate a random password and its related hash.
	 * The only purpose of this function is to generate a new password and hash for manually updating the database when a password is lost.
	 */
	public function generate_password()
	{
		$this->load->helper("password");

		$alphabet = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
		$password = "";
		$msg = "";

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

		echo("<pre>Password: $password</pre>");
		echo("<pre>Hash: " . password_hash($password, PASSWORD_BCRYPT) . "</pre>");
	}

	/* directory functions */
	public function update_directory_latlng()
	{
		$this->init();
		$this->load->model("directory_company");
		$company_id = $this->input->get_post("company_id");
		$latitude = $this->input->get_post("latitude");
		$longitude = $this->input->get_post("longitude");

		if($company_id <= 0 || $latitude == "" || $longitude == "")
		{
			die();
		}

		$c = new Directory_Company();
		$c->load($company_id);
		$c->latitude = $latitude;
		$c->longitude = $longitude;
		$c->save();
	}
	/* end directory functions */

	/* directory category functions */
	public function directory_categories()
	{
		$this->init();

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

		$this->generic_list("directory_category", "directory_categories");
	}

	public function directory_category($id=0)
	{
		$this->init();

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

		$this->load->model(array("directory_category", "site"));

		$category = new Directory_Category();
		$category->load($id);

		$sites = $this->site->get();

		$this->data["id"] = $id;
		$this->data["category"] = $category;
		$this->data["sites"] = $sites;

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

	public function directory_category_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("directory_category");

		$id = $this->input->post("category_id");

		$category = new Directory_Category();
		$category->load($id);
		$category->readPostVars();

		$config['upload_path'] = ABSOLUTE_PATH . '/images/directory/categories/';
		$config['allowed_types'] = 'gif|jpg|jpeg|png';
		$config['max_size']	= '5242880';	//5MB
		$config['encrypt_name']	= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] = true;
		//$config['max_width']  = '1024';
		//$config['max_height']  = '768';

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

		if($this->upload->do_upload("photo_upload"))
		{
			$upload_data = array('upload_data' => $this->upload->data());
			$category->photo = $upload_data["upload_data"]["file_name"];
		}

		if($category->save())
		{
			$this->cache->clean();
			$this->messages[] = "Category Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Category";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->directory_category($category->category_id);
		}
		else
		{
			$this->directory_categories();
		}
	}

	public function directory_category_delete()
	{
		$this->init();

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

		$this->generic_delete("directory_category", "directory_categories");
	}
	/* end directory category functions */

	/* directory company functions */
	public function directory_companies()
	{
		$this->init();

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

		$this->generic_list("directory_company", "directory_companies");
	}

	public function directory_company($id=0)
	{
		$this->init();

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

		$this->load->model(array("directory_company", "directory_category", "site"));

		$company = new Directory_Company();
		$company->load($id);

		$categories = $this->directory_category->LoadByCompanyID($company->company_id);
		$sites = $this->site->get();

		$this->data["id"] = $id;
		$this->data["company"] = $company;
		$this->data["categories"] = $categories;
		$this->data["sites"] = $sites;

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

	public function directory_company_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model(array("directory_company", "directory_category", "directory_category_link"));

		$id = $this->input->post("company_id");

		$company = new Directory_Company();
		$company->load($id);
		$company->readPostVars();
		$company->postalcode = preg_replace("/[^A-Za-z0-9]/", "", $company->postalcode);

		if($company->added == null || $company->added == "" || $company->added == "0000-00-00 00:00:00")
		{
			$company->added = date("Y-m-d H:i:s");
		}

		//upload logo
		$config['upload_path'] = ABSOLUTE_PATH . '/images/directory/logos/';
		$config['allowed_types'] = 'gif|jpg|jpeg|png';
		$config['max_size']	= '10485760';	//10MB
		$config['encrypt_name']	= false;
		$config['remove_spaces']	= true;
		$config['overwrite'] = true;
		//$config['max_width']  = '1024';
		//$config['max_height']  = '768';

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

		if($this->upload->do_upload("logo_upload"))
		{
			$upload_data = array('upload_data' => $this->upload->data());
			$company->logo = $upload_data["upload_data"]["file_name"];
		}

		//upload photo
		$config['upload_path'] = ABSOLUTE_PATH . '/images/directory/companies/';

		$this->upload->initialize($config);

		if($this->upload->do_upload("photo_upload"))
		{
			$upload_data = array('upload_data' => $this->upload->data());
			$company->photo = $upload_data["upload_data"]["file_name"];
		}

		if($this->input->post("logo_remove") == 1)
		{
			$company->logo = null;
		}

		if($this->input->post("photo_remove") == 1)
		{
			$company->photo = null;
		}

		$saved = $company->save();
		$this->cache->clean();

		$categories = $this->directory_category->LoadBySiteID(Mainframe::active_site_id());

		foreach($categories as $category)
		{
			$link = new Directory_Category_Link();
			$link->LoadByCompanyCategoryIDs($company->company_id, $category->category_id);

			if($this->input->post("category_" . $category->category_id) == "1" && $this->input->post("category_" . $category->category_id . "_current") != "1")
			{
				if(isset($link->directory_company_category_id) && $link->directory_company_category_id > 0)
				{
					//this link already exists
				}
				else
				{
					//create a link
					$link = new Directory_Category_Link();
					$link->company_id = $company->company_id;
					$link->category_id = $category->category_id;
					$saved = $link->save() && $saved;
				}
			}
			else if($this->input->post("category_" . $category->category_id) != "1" && $this->input->post("category_" . $category->category_id . "_current") == "1")
			{
				//delete link
				if(isset($link->directory_company_category_id))
				{
					$saved = $link->delete() && $saved;
				}
			}
		}

		if($saved)
		{
			$this->cache->clean();
			$this->messages[] = "Company Saved";
		}
		else
		{
			$this->errors[] = "Error Saving Company";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->directory_company($company->company_id);
		}
		else
		{
			$this->directory_companies();
		}
	}

	public function directory_company_delete()
	{
		$this->init();

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

		$this->generic_delete("directory_company", "directory_companies");
	}
	/* end directory company functions */

	public function directory_export()
	{
		$this->init();

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

		$this->load->dbutil();

		$result = $this->db->query("SELECT c.*, cat.category_name AS Category
		                           FROM directory_companies AS c
		                           INNER JOIN directory_companies_categories AS link ON c.company_id=link.company_id
		                           INNER JOIN directory_categories AS cat ON cat.category_id=link.category_id
		                           WHERE c.site_id=?
		                           ORDER BY published DESC, company, category_name",
		                           array(Mainframe::active_site_id()));

		header('Content-Type: text/csv');
		header('Content-Disposition: attachment; filename="Directory Export ' . date("Y-m-d H:i") . '.csv"');
		header('Pragma: no-cache');
		echo $this->dbutil->csv_from_result($result);
	}

	public function contact_responses_export()
	{
		$this->init();

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

		$this->load->dbutil();

		$result = $this->db->query("SELECT *
									FROM `contact_responses`
									WHERE `site_id`=? AND `module_instance_id`=? AND `timestamp` >= ? AND `timestamp` <= ?
									ORDER BY `timestamp`",
									array(
										Mainframe::active_site_id(),
										$this->input->post("module_instance_id"),
										$this->input->post("date_start"),
										$this->input->post("date_end"),
									));

		header('Content-Type: text/csv');
		header('Content-Disposition: attachment; filename="Contact Form Responses ' . date("Y-m-d H:i") . '.csv"');
		header('Pragma: no-cache');
		echo $this->dbutil->csv_from_result($result);
	}

	public function crawler_issues()
	{
		$this->init();

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

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

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

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

		set_time_limit(600);

		// Find any long running queries and kill them.
		$query 				= $this->db->query("show processlist");
		$mysql_killed 		= 0;
		$tables_defragged 	= 0;

		foreach($query->result() as $process)
		{
			if($process->Time > 500)
			{
				$this->db->query("KILL $process->Id");
				$mysql_killed++;
			}
		}

		if($mysql_killed > 0)
		{
			$this->messages[] = $mysql_killed . " MySQL queries killed.";
		}

		// Optimize database.
		$this->load->dbutil();
		$this->dbutil->optimize_database();
		$this->messages[] = "Database Optimized";

		$this->index();
	}

	function upload_image()
	{
		$this->init();

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

		$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'] 		= false;

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

		if($this->upload->do_upload("image"))
		{
			$upload_data = $this->upload->data();
			$filename = "/images/" . $upload_data["file_name"];

			// Return the size of the uploaded image.
			// If it appears to be a retina image, return half the size.
			$size 	= @getimagesize(ABSOLUTE_PATH . $filename);
			$width 	= $size[0];
			$height = $size[1];

			if(preg_match('/@2x\./', $filename))
			{
				$width 	/= 2;
				$height /= 2;
			}

			http_response_code(200);
			$this->spit_json(array("src" => $filename, "width" => $width, "height" => $height));
		}
		else
		{
			$errors = implode('\n', $this->upload->error_msg);
			http_response_code(409);
			die($errors);
		}
	}

	public function clean_cache()
	{
		$this->init();
		$this->clean_css_cache();
		$this->clean_application_cache();

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

	public function changelog()
	{
		$this->init();
		require_once(APPLICATION_PATH . "/third_party/Michelf/Markdown.inc.php");

		$this->data["changelog"] = Markdown::defaultTransform(file_get_contents(ABSOLUTE_PATH . "/CHANGELOG.md"));

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

	public function help($pdf=null)
	{
		$this->init();
		require_once(APPLICATION_PATH . "/third_party/Michelf/Markdown.inc.php");

		$help = Markdown::defaultTransform(file_get_contents(ABSOLUTE_PATH . "/README.md"));
		$help = str_replace(array("{domain}", "{cwd}"), array($_SERVER["SERVER_NAME"], getcwd()), $help);

		if($pdf === null)
		{
			$this->data["help"] = $help;
			$this->load->view("common/header", $this->data);
			$this->load->view("common/message", array("messages" => $this->messages, "errors" => $this->errors));
			$this->load->view("help", $this->data);
			$this->load->view("common/footer", $this->data);
		}
		else
		{
			require_once(APPLICATION_PATH . "/controllers/Latex.php");
			$title = PRODUCT_NAME . " User Manual";
			Latex::latex_pdf($title, $title, $help, true, true, $title, "help.tex");
		}
	}

	public function errorlog()
	{
		$this->init();

		$errorlog = false;

		if(file_exists(ABSOLUTE_PATH . "/error_log"))
		{
			$errorlog = ABSOLUTE_PATH . "/error_log";
		}
		else if(file_exists(APPLICATION_PATH . "/logs/error_log"))
		{
			$errorlog = APPLICATION_PATH . "/logs/error_log";
		}

		if($errorlog)
		{
			$errorlog = nl2br(file_get_contents($errorlog));
		}

		$this->data["errorlog"] = $errorlog;

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

	public function errorlog_delete()
	{
		$this->init();

		$errorlog = false;

		if(file_exists(ABSOLUTE_PATH . "/error_log"))
		{
			$errorlog = ABSOLUTE_PATH . "/error_log";
		}
		else if(file_exists(APPLICATION_PATH . "/logs/error_log"))
		{
			$errorlog = APPLICATION_PATH . "/logs/error_log";
		}

		if($errorlog)
		{
			if(unlink($errorlog))
			{
				$this->messages = "Error Log Deleted";
			}
			else
			{
				$this->errors[] = "Could Not Delete Error Log";
			}
		}
		else
		{
			$this->errors[] = "No Error Log Found";
		}

		$this->errorlog();
	}

	public function seo($sitemap=0)
	{
		$this->init();
		$this->load->model("template");

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

		$ga_code = Mainframe::site()->ga_code;

		if(!$ga_code)
		{
			$t = new Template();
			$t->load(Mainframe::site()->template_id);

			$template_code = file_get_contents(ABSOLUTE_PATH . "/templates/" . $t->directory . "/index.php");
			preg_match('/(G-..........)/', $template_code, $matches);

			if(count($matches) >= 1)
			{
				$ga_code = $matches[0];
			}
		}

		$this->data["ga_code"] = $ga_code;

		if($sitemap == 1)
		{
			$this->messages[] = 'A sitemap was saved to <a href="' . LIVE_SITE . '/sitemap.xml" target="_blank">' . LIVE_SITE . '/sitemap.xml</a>';
		}

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

	/* redirect functions */
	public function redirects()
	{
		$this->init();

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

		$this->generic_list("redirect", "redirects");
	}

	public function redirect($id=0)
	{
		$this->init();

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

		$this->load->model("redirect");

		$redirect = new Redirect();
		$redirect->load($id);

		if($this->input->get("old") != "")
		{
			$redirect->old_url = $this->input->get("old");
		}

		$pages = $this->page->get();
		$site_id = ($redirect->site_id ? $redirect->site_id : Mainframe::active_site_id());

		$this->data["id"] 		= $id;
		$this->data["return"] 	= ($this->input->get("return") ? $this->input->get("return") : "/admin/redirects");
		$this->data["redirect"] = $redirect;
		$this->data["pages"] 	= $pages;
		$this->data["site_id"] 	= $site_id;

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

	public function redirect_save()
	{
		$this->init();
		csrf_verify();

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

		$this->load->model("redirect");

		$id = $this->input->post("redirect_id");

		$redirect = new redirect();
		$redirect->load($id);
		$redirect->readPostVars();

		// Remove leading slash if present.
		if(startsWith($redirect->old_url, "/"))
		{
			$redirect->old_url = substr($redirect->old_url, 1);
		}

		if($redirect->save())
		{
			$this->cache->clean();
			$this->clean_css_cache();
			$this->messages[] = "URL Redirect Saved";
		}
		else
		{
			$this->errors[] = "Error Saving URL Redirect";
		}

		if($this->input->post("submit_action") == "continue")
		{
			$this->redirect($redirect->redirect_id);
		}
		else
		{
			if($this->input->post("return") == "/admin/http404s")
			{
				$this->http404s();
			}
			else
			{
				$this->redirects();
			}
		}
	}

	public function redirect_delete()
	{
		$this->init();

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

		$this->clean_css_cache();
		$this->generic_delete("redirect", "redirects");
	}
	/* end redirect functions */

	public function http404s()
	{
		$this->init();

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

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

	/* document functions */
	public function documents()
	{
		$this->init();

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

		$this->generic_list("document", "documents");
	}

	public function document($id=0)
	{
		$this->init();

		if(!$this->current_user->has(ACTION_DOCUMENTS) ||
		   !$this->acl->checkDocument($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

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

		$document = new document();
		$document->load($id);

		$sites 			= $this->site->get();
		$categories 	= $this->category->get();
		$site_id 		= ($document->site_id ? $document->site_id : Mainframe::active_site_id());
		$files 			= $this->document->getFiles($site_id, $this->current_user);
		$acls 			= $this->acl->loadByDocumentID($document->document_id);

		$this->data["id"] 			= $id;
		$this->data["document"] 	= $document;
		$this->data["files"] 		= $files;
		$this->data["site_id"] 		= $site_id;
		$this->data["sites"] 		= $sites;
		$this->data["categories"]	= $categories;
		$this->data["acls"]			= $acls;

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

	public function document_save($ajax=false)
	{
		$this->init();
		$id = $this->input->post("document_id");

		if(!$this->current_user->has(ACTION_DOCUMENTS) ||
		   !$this->acl->checkDocument($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("document", "redirect", "document_log", "document_tag"));

		$document 		= new document();
		$document->load($id);
		$old_document 	= clone($document);
		$old_url 		= $document->url;
		$old_filename 	= $document->filename;

		if($ajax)
		{
			$document->site_id 		= $this->input->post("site_id");
			$document->category_id 	= ($this->input->post("category_id") ? $this->input->post("category_id") : null);
			$document->title 		= $this->input->post("title");
			$document->description 	= ($this->input->post("description") ? $this->input->post("description") : null);
			$document->filename 	= $this->input->post("filename");
			$document->url 			= $this->input->post("url");
			$document->access 		= $this->input->post("access");
			$document->published 	= $this->input->post("published");
		}
		else
		{
			$document->readPostVars();
		}

		if(!$id)
		{
			$document->created = date("Y-m-d H:i:s");
			$document->created_user_id = $this->current_user->user_id;
		}
		else
		{
			$document->modified = date("Y-m-d H:i:s");
			$document->modified_user_id = $this->current_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;
		$new_url 		= $document->url;

		if($this->input->post("filename") == "0" && !isset($_FILES["upload_file"]))
		{
			if($ajax)
			{
				http_response_code(409);
				die("You must upload a file from your computer or select an existing file.");
			}
			else
			{
				$this->errors[] = "You must upload a file from your computer or select an existing file.";
				$this->document($document->document_id);
				return;
			}
		}

		$config = array();
		$config['allowed_types']	= 'jpg|png|gif|txt|md|csv|tsv|pdf|doc|docx|xls|xlsx|zip|gz|7z|odt|ods|ai|svg|mp3|wav|mov|mp4|mpeg|mpg|log|ppt|pptx|ppsx';
		$config['upload_path'] 		= DOCUMENT_PATH . '/';
		$config['file_ext_tolower'] = true;
		$config['overwrite'] 		= ($this->input->post("overwrite") == 1 ? true : false);
		$this->load->library('upload', $config);

		if(isset($_FILES["upload_file"]) && $_FILES["upload_file"]["name"] != "")
		{
			if($this->upload->do_upload("upload_file"))
			{
				$upload_data 				= $this->upload->data();
				$document->filename 		= $upload_data["file_name"];

				// If there was an old filename, check if any other files reference it.
				if($old_filename)
				{
					$morefiles = $this->document->loadByFilename($old_filename, $this->current_user);

					if(count($morefiles) == 0)
					{
						unlink(DOCUMENT_PATH . "/" . $old_filename);
					}
				}
			}
			else
			{
				if($ajax)
				{
					http_response_code(409);
					die(implode('\n', $this->upload->error_msg));
				}
				else
				{
					if(is_array($this->upload->error_msg))
					{
						$this->errors = array_merge($this->errors, $this->upload->error_msg);
					}
					else
					{
						$this->errors[] = $this->upload->error_msg;
					}

					$this->document();
					return;
				}
			}
		}

		if($document->save())
		{
			$this->cache->clean();

			// Tags
			$ids = "";
			$tags = explode(",", $this->input->post("tag_ids"));

			foreach($tags as $t)
			{
				$tag = new Document_tag();

				if(is_numeric($t))
				{
					$tag->load($t);
				}
				else if(trim($t) != "")
				{
					$t = trim($t);
					$tag->loadByTagName($t);
					$tag->tag = $t;
					$tag->save();
				}

				if($tag->tag_id)
				{
					$this->document_tag->add_link($tag->tag, $document->document_id);
					$ids .= $tag->tag_id . ",";
				}
			}

			if($ids)
			{
				$ids = substr($ids, 0, -1);
				$this->document_tag->deleteAllOtherTags($document->document_id, $ids);
			}
			else
			{
				$this->document_tag->deleteAllTags($document->document_id);
			}

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

			foreach($roles as $role)
			{
				$acl = new ACL();
				$acl->loadByDocumentRoleID($document->document_id, $role->acl_role_id);

				if(!$acl->acl_id)
				{
					$acl->document_id 	= $document->document_id;
					$acl->acl_role_id 	= $role->acl_role_id;
				}

				if($this->input->post("acl_" . $role->acl_role_id) == "r")
				{
					$acl->read = 1;
					$acl->write = 0;
				}
				else if($this->input->post("acl_" . $role->acl_role_id) == "rw")
				{
					$acl->read = 1;
					$acl->write = 1;
				}
				else
				{
					$acl->read = 0;
					$acl->write = 0;
				}
				$acl->save();
			}

			$this->document_log->logChanges($old_document, $document, $this->current_user);

			// If the URL has been updated, we'll automatically add a redirect for the old URL.
			if($old_url && $old_url != $new_url)
			{
				$redirect = new Redirect();
				$redirect->loadByURL($old_url, $document->site_id);
				$redirect->old_url = "document/" . $old_url;
				$redirect->new_url = "document/" . $new_url;
				$redirect->site_id = $document->site_id;
				$redirect->page_id = null;
				$redirect->save();

				$this->messages[] = "A redirect was automatically created because you changed the page's URL.";
			}

			$this->cache->clean();

			if($ajax)
			{
				http_response_code(200);
				$this->spit_json($document);
			}
			else
			{
				$this->messages[] = "Document Saved";
			}
		}
		else
		{
			if($ajax)
			{
				http_response_code(409);
				die("Error Saving Document");
			}
			else
			{
				$this->errors[] = "Error Saving Document";
			}
		}

		if(!$ajax)
		{
			if($this->input->post("submit_action") == "continue")
			{
				$this->document($document->document_id);
			}
			else
			{
				$this->documents();
			}
		}
	}

	public function document_delete($ajax=false)
	{
		$this->init();
		$id = $this->input->get_post("document_id");

		if(!$this->current_user->has(ACTION_DOCUMENTS) ||
		   !$this->acl->checkDocument($id, $this->current_user, "write"))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("document", "document_log"));

		$doc 	= new Document();
		$doc->load($id);
		$old_document = clone($doc);
		$old_filename = $doc->filename;

		if($doc->url != "")
		{
			$check1 = $this->db->query("SELECT COUNT(*) AS total, GROUP_CONCAT(p.`title` SEPARATOR '</li><li>') AS pages
			                           FROM `content_values` AS v
			                           INNER JOIN `pages` AS p USING(page_id)
			                           WHERE v.`value` LIKE '%document/" . $this->db->escape_str($doc->url) . "%'");
			$check2 = $this->db->query("SELECT COUNT(*) AS total, GROUP_CONCAT(i.`tag` SEPARATOR '</li><li>') AS modules
			                           FROM `module_settings` AS s
			                           INNER JOIN `module_instances` AS i USING(module_instance_id)
			                           WHERE s.`value` LIKE '%document/" . $this->db->escape_str($doc->url) . "%'");
			$row1 = $check1->row();
			$row2 = $check2->row();
			$total1 = $row1->total;
			$total2 = $row2->total;
			$total_links = $total1 + $total2;
			$link_details = "";

			if($total_links > 0)
			{
				if($total1 > 0)
				{
					$link_details .= "<br /><br />Pages with links:<ul><li>" . $row1->pages . "</li></ul>";
				}
				if($total2 > 0)
				{
					$link_details .= "<br /><br />Modules with links:<ul><li>" . $row2->modules . "</li></ul>";
				}

				if($ajax)
				{
					http_response_code(409);
					die("You can not delete this document because " . $total_links . " active link" . ($total_links == 1 ? " exists" : "s exist") . " on your website." . $link_details);
				}
				else
				{
					$this->errors[] = "You can not delete this document because " . $total_links . " active link" . ($total_links == 1 ? " exists" : "s exist") . " on your website." . $link_details;
					$this->documents();
					return;
				}
			}
		}

		if($doc->delete())
		{
			$this->document_log->logDeleted($old_document, $this->current_user);

			// Check if any other files reference it.
			$morefiles = $this->document->loadByFilename($old_filename, $this->current_user);

			if(count($morefiles) == 0)
			{
				unlink(DOCUMENT_PATH . "/" . $old_filename);
			}

			if($ajax)
			{
				http_response_code(200);
			}
			else
			{
				$this->messages[] = "Document Deleted";
			}
		}
		else
		{
			if($ajax)
			{
				http_response_code(409);
				die("Error Deleting Document");
			}
			else
			{
				$this->errors[] = "Error Deleting Document";
			}
		}


		if(!$ajax)
		{
			$this->documents();
		}
	}
	/* end document functions */

	public function test_mail_settings()
	{
		$settings 					= array();
		$settings["mail_to"] 		= $this->input->post("mail_to");
		$settings["mail_from"] 		= $this->input->post("mail_from");
		$settings["mail_replyto"] 	= $this->input->post("mail_replyto");
		$settings["mail_host"] 		= $this->input->post("mail_host");
		$settings["mail_port"] 		= $this->input->post("mail_port");
		$settings["mail_tls"] 		= $this->input->post("mail_tls");
		$settings["mail_username"] 	= $this->input->post("mail_username");
		$settings["mail_password"] 	= $this->input->post("mail_password");

		if($this->_test_mail_settings($settings))
		{
			http_response_code(200);
			die();
		}

		// Try a bunch of variations to the settings entered.
		// If using TLS on the wrong port, try the right port.
		if($settings["mail_tls"] == "tls" && $settings["mail_port"] != "587")
		{
			$settings["mail_port"] = "587";

			if($this->_test_mail_settings($settings))
			{
				http_response_code(200);
				die();
			}
		}
		// If using SSL on the wrong port, try the right port.
		if($settings["mail_tls"] == "ssl" && $settings["mail_port"] != "465")
		{
			$settings["mail_port"] = "465";

			if($this->_test_mail_settings($settings))
			{
				http_response_code(200);
				die();
			}
		}

		http_response_code(409);
		die("No working mail settings could be determined.");
	}

	private function _test_mail_settings($settings)
	{
		$mail = new PHPMailer();
		$mail->IsSMTP();
		$mail->IsHTML(true);
	    $mail->SMTPOptions = array
		(
		    'ssl' => array(
		        'verify_peer' => false,
		        'verify_peer_name' => false,
		        'allow_self_signed' => true
		    )
		);
		$mail->SMTPSecure		= $settings["mail_tls"];
	    $mail->SMTPAuth			= ($settings["mail_username"] && $settings["mail_password"]);
	    $mail->Timeout 			= 30;
	    $mail->SMTPKeepAlive 	= true;
		$mail->Host				= $settings["mail_host"];
		$mail->Port				= $settings["mail_port"];
		$mail->Username			= $settings["mail_username"];
		$mail->Password			= $settings["mail_password"];
		$mail->SetFrom($settings["mail_username"], $settings["mail_from"]);
	    $mail->ClearReplyTos();
		$mail->AddReplyTo($settings["mail_replyto"], $settings["mail_from"]);
		$mail->AddAddress($settings["mail_to"]);
		$mail->Subject = COMPANY_NAME . " Mail Settings Test";
		$mail->Body = "<p>The following mail settings worked:</p>";
		$mail->Body .= '<table width="100%" border="0">';
		$mail->Body .= '<tr><td>Server</td><td>' . $settings["mail_host"] . '</td></tr>';
		$mail->Body .= '<tr><td>Port</td><td>' . $settings["mail_port"] . '</td></tr>';
		$mail->Body .= '<tr><td>SSL/TLS</td><td>' . strtoupper($settings["mail_tls"]) . '</td></tr>';
		$mail->Body .= '<tr><td>Username</td><td>' . $settings["mail_username"] . '</td></tr>';
		$mail->Body .= '<tr><td>Password</td><td>[hidden]</td></tr>';
		$mail->Body .= '</table>';

		return $mail->Send();
	}

	public function environment($environment)
	{
		$content = file_get_contents(ABSOLUTE_PATH . "/index.php");
		$content = preg_replace('/define\(\'ENVIRONMENT\', \'.*\'\);/', "define('ENVIRONMENT', '" . $environment . "');", $content);

		file_put_contents(ABSOLUTE_PATH . "/index.php", $content);

		$this->messages[] = "Your website's environment was changed to " . $environment . ".";
		$this->index();
	}

	public function optimize_images()
	{
		$this->init();
		ini_set('gd.jpeg_ignore_warning', true);

		$obliterated = false;

		if(file_exists(ABSOLUTE_PATH . "/images/thumbnails"))
		{
			$this->rrmdir(ABSOLUTE_PATH . "/images/thumbnails");
			mkdir(ABSOLUTE_PATH . "/images/thumbnails");
			$obliterated = true;
		}
		else
		{
			mkdir(ABSOLUTE_PATH . "/images/thumbnails");
		}
		touch(ABSOLUTE_PATH . "/images/thumbnails/index.html");

		$bytes_saved = 0;
		$bytes_saved += $this->_optimize_images(ABSOLUTE_PATH . '/images');
		$bytes_saved += $this->_optimize_images(ABSOLUTE_PATH . '/templates');

		// Pop the total summary to the top of the messages.
		array_unshift($this->messages, "Saved a total of " . number_format($bytes_saved / 1024, 1) . " KB");

		if($obliterated)
		{
			array_unshift($this->messages, "/images/thumbnails was emptied.");
		}

		$this->index();
	}

	private function rrmdir($src)
	{
		$dir = opendir($src);
		while(false !== ( $file = readdir($dir)) )
		{
			if (( $file != '.' ) && ( $file != '..' ))
			{
				$full = $src . '/' . $file;
				if ( is_dir($full) )
				{
					$this->rrmdir($full);
				}
				else
				{
					unlink($full);
				}
			}
		}
		closedir($dir);
		rmdir($src);
	}

	private function _optimize_images($path)
	{
		$bytes_saved = 0;

		// We must use opendir rather than scandir() or glob() in order to handle massive directories.
		if($handle = opendir($path))
		{
		    while(false !== ($image = readdir($handle)))
		    {
		        if($image == "." || $image == "..")
		        {
		        	continue;
		        }

		        set_time_limit(20);

				if(is_dir($path . "/" . $image))
				{
					$bytes_saved += $this->_optimize_images($path . "/" . $image);
				}

				try
				{
					$size = @getimagesize($path . "/" . $image);

					if(preg_match('/jpe?g/i', $size["mime"]))
					{
						$bytes_saved += $this->_optimize_jpg($path . "/" . $image);
					}
					else if(preg_match('/png/i', $size["mime"]))
					{
						$bytes_saved += $this->_optimize_png($path . "/" . $image);
					}
				}
				catch(Exception $e)
				{
					$this->errors[] = str_replace(ABSOLUTE_PATH, "", $path . "/" . $image) . " failed to be optimized.";
					return 0;
				}
		    }

		    closedir($handle);
		}

		return $bytes_saved;
	}

	private function _optimize_png($image)
	{
		try
		{
			$size1 	= filesize($image);
			$i 		= imagecreatefrompng($image);

			// As per Google's suggestion, images over 10KB should be progressive.
			// Not sure if this is applicable to PNG but they suggest this for JPG so why not eh?
			if($size1 > 10240)
			{
				imageinterlace($i);
			}

			imagepng($i, $image, 9, PNG_NO_FILTER);
			$size2 = filesize($image);

			if($size1-$size2 > 0)
			{
				$this->messages[] = str_replace(ABSOLUTE_PATH, "", $image) . " reduced by " . number_format(($size1-$size2) / 1024, 1) . " KB";
				@imagedestroy($i);
				return ($size1-$size2);
			}
			// else if($size2-$size1 > 0)
			// {
			// 	devecho("Gained " . ($size1-$size2) . " bytes");
			// }

			@imagedestroy($i);
		}
		catch(Exception $e)
		{
			$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " failed to be optimized.";
			@imagedestroy($i);
			return 0;
		}

		@imagedestroy($i);
		return 0;
	}

	private function _optimize_jpg($image)
	{
		// Check for JPG file header and footer - also try to fix it
		if( false !== (@$fd = fopen($image, 'r+b' )) )
		{
			if ( fread($fd,2)==chr(255).chr(216) )
			{
				fseek ( $fd, -2, SEEK_END );
				if ( fread($fd,2)==chr(255).chr(217) )
				{
					fclose($fd);
					// header/footer is ok
				}
				else
				{
					if (!fwrite($fd,chr(255).chr(217)) )
					{
						// header/footer could not be fixed
						fclose($fd);
						$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " has an invalid JPG header or footer and could not be fixed.";
						return 0;
					}
					fclose($fd);
				}
			}
			else
			{
				fclose($fd);
				$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " could not be read.";
				return 0;
			}
		}
		else
		{
			$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " could not be opened.";
			return 0;
		}

		try
		{
			$temp 	= $image . ".optimized";
			$size1 	= filesize($image);
			$i 		= imagecreatefromjpeg($image);

			// As per Google's suggestion, images over 10KB should be progressive.
			if($size1 > 10240)
			{
				imageinterlace($i);
			}

			// Starting with quality 100, continually compress the original image.
			// Compressing an already compressed image with a higher compression yields a larger file.
			// When we find a JPG quality that creates a smaller file, we can assume that's the approximate existing compression.
			// If we find an image that is compressed approximately higher than 85%, reduce to 85%.
			for($q=100; $q>=86; $q--)
			{
				try
				{
					@unlink($temp);
					imagejpeg($i, $temp, $q);
					$size2 = filesize($temp);

					if($size1-$size2 > 0)
					{
						@unlink($temp);
						imagejpeg($i, $image, 85);
						$size2 = filesize($image);
						$this->messages[] = str_replace(ABSOLUTE_PATH, "", $image) . " reduced by " . number_format(($size1-$size2) / 1024, 1) . " KB";

						@unlink($temp);
						@imagedestroy($i);

						return ($size1-$size2);
					}
				}
				catch(Exception $e)
				{
					$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " failed to be optimized.";
					break;
				}
			}
			@unlink($temp);
			@imagedestroy($i);
		}
		catch(Exception $e)
		{
			$this->errors[] = str_replace(ABSOLUTE_PATH, "", $image) . " failed to be optimized.";
			@unlink($temp);
			@imagedestroy($i);
			return 0;
		}

		@unlink($temp);
		@imagedestroy($i);
		return 0;
	}

	public function moduleBench()
	{
		$this->output->enable_profiler();
		$this->admin_login_required();
		$query = $this->db->query("SELECT * FROM `modules`");

		foreach($query->result() as $module)
		{
			$start = microtime();

			require_once(ABSOLUTE_PATH . "/modules/" . $module->file_name . "/" . $module->file_name . ".php");
			$m = new $module->class_name();

			$settings = file_get_contents(APPLICATION_PATH . "/views/modules/" . $module->file_name . "/settings.php");
			preg_match_all('/"(param_.*?)"/', $settings, $matches);
			$settings = array_unique($matches[1]);

			foreach($settings as $setting)
			{
				$setting 		= str_replace("param_", "", $setting);
				$value 			= new StdClass();
				$value->key 	= $setting;
				$value->value 	= "x";

				if(preg_match('/directory/', $setting))
				{
					$value->value = "images/gallery";
				}
				else if(preg_match('/width/', $setting))
				{
					$value->value = 400;
				}
				else if(preg_match('/height/', $setting))
				{
					$value->value = 300;
				}
				else if(preg_match('/(_id|per_row)/', $setting))
				{
					$value->value = 1;
				}
				else if(preg_match('/breakpoint/', $setting))
				{
					$value->value = 100;
				}

				$m->set($value->key, $value->value);
			}

			ob_start();
			$m->render();
			$content = ob_get_contents();
			ob_end_clean();

			$end = microtime();

			devecho($module->file_name . " took " . number_format($end-$start, 5));
		}
	}

	public function sitecopy()
	{
		$this->load->model(array(
"Page",
"Content_value",
"Acl",
"Moduleinstance",
"Modulesetting",
"Menu",
"Menuitem",
"Document"));

		$from_site_id = 2;
		$site_ids     = array(4,5,6,7,8,9,11,12,13,14,15);
		$page_ids     = array();
		$module_ids   = array();

		foreach($site_ids as $site_id)
		{
			$q = $this->db->query("SELECT * FROM pages WHERE site_id=?", array($from_site_id));

			foreach($q->result() as $row)
			{
				if($row->page_id == 30) // Development milestones - skip
				{
					continue;
				}

				$p = new Page();
				$p->populate($row);
				$p->page_id = null;
				$p->site_id = $site_id;
				$p->save();

				$page_ids[$row->page_id] = $p->page_id;

				$values = $this->content_value->LoadByPageID($row->page_id);

				foreach($values as $value)
				{
					$v = new Content_value();
					$v->populate($value);
					$v->content_value_id = null;
					$v->page_id = $p->page_id;
					$v->save();
				}

				$acls = $this->acl->loadByPageID($row->page_id);

				foreach($acls as $acl)
				{
					$a = new Acl();
					$a->populate($acl);
					$a->acl_id = null;
					$a->page_id = $p->page_id;
					$a->save();
				}
			}

			$q = $this->db->query("SELECT * FROM module_instances WHERE site_id=?", array($from_site_id));

			foreach($q->result() as $row)
			{
				$mi = new Moduleinstance();
				$mi->populate($row);
				$mi->module_instance_id = null;
				$mi->site_id = $site_id;
				$mi->save();

				$module_ids[$row->module_instance_id] = $mi->module_instance_id;

				$settings = $this->modulesetting->load_by_instance_id($row->module_instance_id);

				foreach($settings as $setting)
				{
					$s = new Modulesetting();
					$s->populate($setting);
					$s->module_setting_id = null;
					$s->module_instance_id = $mi->module_instance_id;
					$s->save();
				}

				$acls = $this->acl->loadByModuleInstanceID($row->module_id);

				foreach($acls as $acl)
				{
					$a = new Acl();
					$a->populate($acl);
					$a->acl_id = null;
					$a->module_instance_id = $mi->module_instance_id;
					$a->save();
				}
			}

			$q = $this->db->query("SELECT * FROM menus WHERE site_id=?", array($from_site_id));

			foreach($q->result() as $row)
			{
				$m = new Menu();
				$m->populate($row);
				$m->menu_id = null;
				$m->site_id = $site_id;
				$m->save();

				$q2 = $this->db->query("SELECT * FROM menu_items WHERE menu_id=?", array($row->menu_id));

				foreach($q2->result() as $row2)
				{
					$mi = new Menuitem();
					$mi->populate($row2);
					$mi->menu_item_id = null;
					$mi->parent_id = null;
					$mi->menu_id = $m->menu_id;

					if($mi->page_id)
					{
						$mi->page_id = $page_ids[$mi->page_id];
					}
					$mi->save();

					$acls = $this->acl->loadByMenuItemID($row2->menu_item_id);

					foreach($acls as $acl)
					{
						$a = new Acl();
						$a->populate($acl);
						$a->acl_id = null;
						$a->menu_item_id = $mi->menu_item_id;
						$a->save();
					}
				}
			}

			// $q = $this->db->query("SELECT * FROM documents WHERE site_id=?", array($from_site_id));

			// foreach($q->result() as $row)
			// {
			// 	$d = new Document();
			// 	$d->populate($row);
			// 	$d->document_id = null;
			// 	$d->site_id = $site_id;
			// 	$d->save();

			// 	$acls = $this->acl->loadByDocumentID($row->document_id);

			// 	foreach($acls as $acl)
			// 	{
			// 		$a = new Acl();
			// 		$a->populate($acl);
			// 		$a->acl_id = null;
			// 		$a->document_id = $d->document_id;
			// 		$a->save();
			// 	}
			// }
		}

		foreach($page_ids as $page_id=>$new_page_id)
		{
			foreach($module_ids as $module_id=>$new_module_id)
			{
				$q = $this->db->query("SELECT * FROM modules_pages WHERE module_instance_id=? AND page_id=?",
				                      array($module_id, $page_id));

				foreach($q->result() as $row)
				{
					$this->db->query("INSERT INTO modules_pages SET module_instance_id=?, page_id=?",
					                 array($new_module_id, $new_page_id));
				}
			}
		}
	}

	public function cleanup()
	{
		$this->init();
		$this->clean_css_cache();
		$this->clean_application_cache();

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

		$dir_iterator = new RecursiveDirectoryIterator(ABSOLUTE_PATH . "/images");
		$iterator     = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
		$files        = array();
		$bytes        = 0;
		$unused       = 0;

		foreach($iterator as $file)
		{
			$file = (string)$file;

			// Ignore jQuery UI theme files.
			if(preg_match('/\/ui-.*/', $file))
			{
				continue;
			}

			// Ignore shop images. They have their own cleanup function.
			if(preg_match('/\/shop\/.*/', $file))
			{
				continue;
			}

			// Ignore thumbnails.
			if(preg_match('/\/thumbnails\/.*/', $file))
			{
				continue;
			}

			// Ignore @2x, we'll clean those up if their main image is unused.
			if(preg_match('/@2x\.(jpe?g|png|gif|webp)$/', $file))
			{
				continue;
			}

			if(is_file($file) && !preg_match('/\.html$/', $file))
		    {
		    	$files[] = str_replace(ABSOLUTE_PATH . "/", "", $file);
		    }
		}

		foreach($files as $file)
		{
			$used = false;

			if(!$used)
			{
				$q = $this->db->query("SELECT * FROM `content_values` WHERE `value` LIKE ?", array("%" . $file . "%"));

				if($q->num_rows())
				{
					$used = true;
				}
			}

			if(!$used)
			{
				$q = $this->db->query("SELECT * FROM `module_settings` WHERE `value` LIKE ?", array("%" . $file . "%"));

				if($q->num_rows())
				{
					$used = true;
				}
			}

			if(!$used)
			{
				$q = $this->db->query("SELECT * FROM `pages` WHERE `content` LIKE ?", array("%" . $file . "%"));

				if($q->num_rows())
				{
					$used = true;
				}
			}

			if(!$used)
			{
				$q = $this->db->query("SELECT * FROM `document` WHERE `description` LIKE ?", array("%" . $file . "%"));

				if($q->num_rows())
				{
					$used = true;
				}
			}

			$dir = $file;
			$failsafe = 0;

			while(strpos($dir, "/") !== false)
			{
				$failsafe++;
				$dir = dirname($dir);

				if(!$used)
				{
					$q = $this->db->query("SELECT * FROM `content_values` WHERE `value` = ? OR `value` = ?", array("/" . $dir, $dir));

					if($q->num_rows())
					{
						$used = true;
					}
				}

				if(!$used)
				{
					$q = $this->db->query("SELECT * FROM `module_settings` WHERE `value` = ? OR `value` = ?", array("/" . $dir, $dir));

					if($q->num_rows())
					{
						$used = true;
					}
				}

				if($failsafe == 100)
				{
					break;
				}
			}

			if(!$used)
			{
				$unused++;
				$bytes += filesize(ABSOLUTE_PATH . "/" . $file);
				unlink(ABSOLUTE_PATH . "/" . $file);
				$retina = preg_replace('/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/', '@2x.$1', $file);

				if(file_exists($retina))
				{
					$unused++;
					$bytes += filesize(ABSOLUTE_PATH . "/" . $retina);
					unlink(ABSOLUTE_PATH . "/" . $retina);
				}

				$thumbs = glob(ABSOLUTE_PATH . "/" . preg_replace(array('/^images\//', '/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/'),
				                                                   array('images/thumbnails/', '_*x*.$1'), $file));
				$thumbs = array_merge($thumbs, glob(ABSOLUTE_PATH . "/" . preg_replace(array('/^images\//', '/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/'),
				                                                   array('images/thumbnails/', '_*x*@2x.$1'), $file)));
				$thumbs = array_merge($thumbs, glob(ABSOLUTE_PATH . "/" . preg_replace(array('/^images\//', '/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/'),
				                                                   array('images/thumbnails/', '_*x*_*_*.$1'), $file)));
				$thumbs = array_merge($thumbs, glob(ABSOLUTE_PATH . "/" . preg_replace(array('/^images\//', '/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/'),
				                                                   array('images/thumbnails/', '_*x*_*_*@2x.$1'), $file)));

				foreach($thumbs as $thumb)
				{
					$unused++;
					$bytes += filesize($thumb);
					unlink($thumb);
				}
			}
		}

		// Look for unused thumbnails.
		$dir_iterator = new RecursiveDirectoryIterator(ABSOLUTE_PATH . "/images/thumbnails");
		$iterator     = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
		$files        = array();

		foreach($iterator as $file)
		{
			$file = (string)$file;

			if(is_file($file) && preg_match('/\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/', $file))
		    {
		    	$files[] = str_replace(ABSOLUTE_PATH . "/", "", $file);
		    }
		}

		foreach($files as $file)
		{
			$original = ABSOLUTE_PATH . "/" . preg_replace(array('/images\/thumbnails\//', '/_[0-9]+x[0-9]+(_[0-9]+_[0-9]+)?(_[a-z]+_[a-z]+)?(@2x)?\.(jpe?g|png|gif|webp|JPE?G|PNG|GIF|WEBP)$/'),
			                         array('images/', '.$3'), $file);

			if(!file_exists($original))
			{
				$unused++;
				$bytes += filesize(ABSOLUTE_PATH . "/" . $file);
				unlink(ABSOLUTE_PATH . "/" . $file);
			}
		}

		$this->messages[] = number_format($unused, 0) . " unused images were deleted, saving " . bytes_format($bytes) . ".";
		$this->index();
	}

	/* order functions */
	public function orders()
	{
		if(!$this->current_user->has(ACTION_SHOP_ORDERS))
		{
			return $this->denied($this->data);
		}

		$this->load->model(array("shop/shop_order", "shop/shop_order_status"));


		$orders = $this->shop_order->get();
		$this->data["orders"] = $orders;

		$statuses = $this->shop_order_status->get();
		$this->data["statuses"] = $statuses;

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

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

		$this->load->model(array("shop/shop_order"));

		$id = get_id();

		$order = new Shop_Order();
		$order->LoadWithDetails($id);

		$this->data["id"] = $id;
		$this->data["order"] = $order;

		$this->load->view("common/header", $this->data);
		$this->load->view("common/message", array("messages" => $this->messages, "errors" => $this->errors));
		$this->load->view("shop/order", $this->data);
		$this->load->view("common/footer", $this->data);
	}
	/* end order functions */

	/* AJAX product features */
	public function order_update_status()
	{
		if(!$this->current_user->has(ACTION_SHOP_ORDERS))
		{
			return $this->denied($this->data);
		}

		$this->load->model("shop/shop_order");
		$order_ids 			= $this->input->get_post("order_ids");
		$status_id 			= $this->input->get_post("status_id");
		$paid 				= ($this->input->get_post("paid") == 1 ? 1 : 0);
		$tracking_number1 	= $this->input->get_post("tracking_number1");
		$tracking_number2 	= $this->input->get_post("tracking_number2");
		$tracking_number3 	= $this->input->get_post("tracking_number3");

		$this->shop_order->update_status($order_ids, $status_id);

		//$ids is in the format SQL wants for LIKE
		//ie: '1','2','3'
		$ids = explode(",", $order_ids);

		//If any tracking numbers are present, fire an event.
		if($tracking_number1 != "" || $tracking_number2 != "" || $tracking_number3 != "" || $paid !== "")
		{
			foreach($ids as $id)
			{
				$clean_id = trim(str_replace("'", "", $id));

				//remove quotes from around the number
				$order = new Shop_Order();
				$order->load($clean_id);

				if($tracking_number1 != "")
				{
					$order->add_tracking_number($tracking_number1);
				}
				if($tracking_number2 != "")
				{
					$order->add_tracking_number($tracking_number2);
				}
				if($tracking_number3 != "")
				{
					$order->add_tracking_number($tracking_number3);
				}
				if($paid !== "")
				{
					$order->paid = ($paid ? 1 : 0);
				}
				$order->save();

				if($tracking_number1 != "" || $tracking_number2 != "" || $tracking_number3 != "")
				{
					tracking_info_added($clean_id);
				}
			}
		}

		die("SUCCESS");
	}
	/* end AJAX product features */

	/**
	 * Download the postal codes SQL file from files.nerivon.com, extract it, and run the queries.
	 * This function takes a LONG TIME to run since there are nearly 1 million queries in the file.
	 */
	public function install_postalcodes()
	{
		if(!$this->current_user->has(ACTION_SHOP_CONFIG))
		{
			return $this->denied($this->data);
		}

		// This is going to require significant memory
    	ini_set("memory_limit", "256M");

    	// Download the postal codes file to a temp directory.
		$ch = curl_init("https://files.nerivon.com/shop_postalcodes.sql");
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
		$postalcodes = curl_exec($ch);
		curl_close($ch);

		@unlink("/tmp/shop_postalcodes.sql");
		file_put_contents("/tmp/shop_postalcodes.sql", $postalcodes);
		unset($postalcodes);

		if(file_exists("/tmp/shop_postalcodes.sql"))
		{
			// Extract the ZIP file.
			// try
			// {
			// 	$phar = new PharData("/tmp/shop_postalcodes.zip", 0, "shop_postalcode.zip", Phar::ZIP);
			// 	$phar->extractTo('/tmp', "shop_postalcodes.sql", true);
	    	// }
	    	// catch(Exception $e)
	    	// {
	    	// 	$this->errors[] = "Could not extract postal codes: $e";
	    	// 	$this->index();
	    	// 	return;
	    	// }
	    }
	    else
    	{
    		$this->errors[] = "Could not download postal codes";
    		$this->index();
    		return;
    	}

    	$this->db->trans_off();

    	$sql = file_get_contents("/tmp/shop_postalcodes.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

    	// Close the file and delete the temp files.
	    @unlink("/tmp/shop_postalcodes.sql");
	    @unlink("/tmp/shop_postalcodes.zip");

		$this->messages[] = "Postal Codes Installed";
		$this->index();
	}

	/* gateway functions */
	public function gateways()
	{
		$this->init();

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

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

	public function payment_gateway($id)
	{
		$this->init();

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

		$this->load->model("shop/shop_payment_gateway");

		$gateway = new Shop_payment_gateway();
		$gateway->load($id);
		$this->data["id"] 		= $id;
		$this->data["gateway"] 	= $gateway;

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

	public function payment_gateway_save()
	{
		$this->init();

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

		$this->load->model("shop/shop_payment_gateway");

		$gateway = new Shop_payment_gateway();
		$gateway->load($this->input->post("id"));
		$gateway->enabled = ($this->input->post("enabled") ? 1 : 0);
		$gateway->sort = $this->input->post("sort");

		$configs = explode("\n", $gateway->configuration);
		$gateway->configuration = "";

		foreach($configs as $config)
		{
			if($config != "")
			{
				$line = explode("=", $config, 2);

				$gateway->configuration .= $line[0] . "=" . $this->input->post("config_" . $line[0]) . "\n";
			}
		}

		if($gateway->save())
		{
			$this->cache->clean();
			$this->messages[] = "Payment Gateway Saved";

			if($this->input->post("submit_action") == "continue")
			{
				$this->payment_gateway($gateway->payment_gateway_id);
			}
			else
			{
				$this->gateways();
			}
		}
		else
		{
			$this->errors[] = "Error Saving Payment Gateway";
			$this->payment_gateway($gateway->payment_gateway_id);
		}
	}

	public function shipping_gateway($id)
	{
		$this->init();

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

		$this->load->model("shop/shop_shipping_gateway");

		$gateway = new Shop_shipping_gateway();
		$gateway->load($id);
		$this->data["id"] 		= $id;
		$this->data["gateway"] 	= $gateway;

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

	public function shipping_gateway_save()
	{
		$this->init();

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

		$this->load->model("shop/shop_shipping_gateway");

		$gateway = new Shop_shipping_gateway();
		$gateway->load($this->input->post("id"));
		$gateway->enabled = ($this->input->post("enabled") ? 1 : 0);
		$gateway->sort = $this->input->post("sort");

		$configs = explode("\n", $gateway->configuration);
		$gateway->configuration = "";

		foreach($configs as $config)
		{
			if($config != "")
			{
				$line = explode("=", $config, 2);

				$gateway->configuration .= $line[0] . "=" . $this->input->post("config_" . $line[0]) . "\n";
			}
		}

		if($gateway->save())
		{
			$this->cache->clean();
			$this->messages[] = "Shipping Gateway Saved";

			if($this->input->post("submit_action") == "continue")
			{
				$this->shipping_gateway($gateway->shipping_gateway_id);
			}
			else
			{
				$this->gateways();
			}
		}
		else
		{
			$this->errors[] = "Error Saving Shipping Gateway";
			$this->shipping_gateway($gateway->shipping_gateway_id);
		}
	}
	/* end of gateway functions */
}
