<?php
/**
 * Cms_btnsort.php
 *
 * Library class for button sort feature. This is used by the template modules.
 * The idea is to pass in a result array from the module and this library will
 * reorder the order based on the btnsort table. The model for this library uses
 * prefetch therefore perfromance is not an issue. A single template page only
 * requires one DB query for reading regardless of how many types or modules.
 *
 * This is a generic library and is designed to allow all future new features
 * to integrated with this sorting system with no more than 10-20 lines of code
 * and no additonal DB queries! Another nice OOP class in TODCM that shows
 * encapsulation and reusability rule!
 *
 * @author $Author: dtong $
 * @version $Id: Cms_btnsort.php,v 1.5 2011/05/20 11:09:46 dtong Exp $
 * @copyright Copyright (c) 2011, Tiller Software Co., Ltd.
*/

class Cms_btnsort {
	//Index names to be injected to the incoming array of objects, usually a DB result set
	const INDEX_SORTKEY = '__SKEY__';
	const INDEX_BTNID = '__BSID__';
	//Manual button set (2 buttons, up and down) generation. The buttons are usually generated
	//automatically but the segment based sort can not use the auto gen buttons and needs these.
	const BUTTONS_FIRST = 1;
	const BUTTONS_MIDDLE = 2;
	const BUTTONS_LAST = 3;
	//For manual individual button HTML generation.
	const BUTTON_UP = 1;
	const BUTTON_DOWN = 2;
	const BUTTON_NONE = 3;

	private $CI = NULL;
	//coreid index name
	private $coreid_index = FALSE;
	private $linkid_index = FALSE;
	//The actual template type from the mudule data. This is the index name
	private $realtypes_index = FALSE;
	//The coreid passed from caller
	private $coreid = FALSE;
	//The type passed from caller. This can be different than actual template type but
	//has to be a valid template type, usually set as a meta type.
	private $type = FALSE;
	//This is like ubd, pyp. myp etc.
	private $filehash = NULL;
	private $original_filehash = NULL;
	//Tells init() it is ok to proceed or not
	private $needs_reset = TRUE;
	//Tells other functions init ran or not
	private $needs_init = TRUE;
	//Only print error msg once
	private $skip_error_msg = FALSE;
	//Error condition
	private $errorstat = FALSE;
	//Read only operation
	private $skip_db_update = FALSE;
	//Maintain original index in module data passed in from caller
	private $maintain_original_index = FALSE;
	//Ignore missing empty linkid data from caller module data set. The code will not update any row without linkid.
	//This is needed for outcomes and lts since they do contain rows with linkid for outcome/benchmark title purpose.
	private $ignore_empty_linkid = FALSE;

	//The maximum sortkey value in the DB for the specific type and coreid
	//Needed to generate new sortkey for new entries.
	private $max_sortkey;
	//Data from DB, hashed index. This will contain records to be deleted from the prepare function
	private $sort_records_hash;
	//Data from DB, not hashed.
	private $sort_records;
	//Arrays of records passed in from module
	private $module_data;
	//Module Data with existing sortkey
	private $module_data_key;
	//Module Data without sortkey, need to update
	private $module_data_nokey;

	//For actual button genertion, keeping track of the first and last element
	//Links for sort up and down
	private $head;
	private $tail;
	private $total;
	private $sort_link_up;
	private $sort_link_down;

	function __construct() {
		$this->CI = & get_instance();
		$this->CI->load->model('cms/btnsort_model');
		$this->CI->load->library('cms/cms_template/cms_template');
		$this->CI->load->library('cms/cms_security');
		osa_load_lang('template');
		$this->skip_error_msg = FALSE;
		$this->sort_link_up = base_url() . 'cms/btnsort/up/';
		$this->sort_link_down = base_url() . 'cms/btnsort/down/';
	}

	//This is a major system class so protect it from lazy coders...
	function __set($var, $value) {
		echo __METHOD__ . " - You are not allowed to set undeclared class property '$var', bye bye.";
		exit;
	}

	/*
	 * This is required in every module before using this library
	 * This behaves like a singleton so not resetting in a module will result data corruption!
	 *
	 * $filehash - This is actually the file_part like ubd pyp etc. Non-template modules can pass in a number like LT and OS.
	 * $coreid - This is usually unit id, goal id etc. This can be a array of ids. If array then the skip_db_update flag will be set to true.
	 * $type - The template type, reused for sorting type. The modules have to decide what type to use
	 * 		  if there are more than 2 types like the resource multi sort feature
	 * $coreid_index - The array index name for the coreid, usually like 'unitid', 'courseid' etc.
	 * $linkid_index - The array index name for the linking resource like 'resourceid', 'targetid' or just 'id' etc.
	 */
	function reset($filehash, $coreid, $type, $coreid_index, $linkid_index='id', $realtypes_index='type') {
		if ( (!osa_is_int($coreid) && !osa_is_int_array($coreid)) || !osa_is_int_one($type) || empty($coreid_index) ) {
			osa_errorlog(__METHOD__ . " - Incorrect input params.", array($coreid, $type, $coreid_index));
			$this->show_error();
			return FALSE;
		}
		$this->CI->btnsort_model->set_pre_fetch(TRUE);
		$this->CI->btnsort_model->ignore_empty_linkid(FALSE);
		$this->CI->btnsort_model->set_segmentindex(FALSE);
		$this->CI->btnsort_model->set_segmentid(FALSE);
		$this->CI->btnsort_model->set_realtype_override(FALSE);
		//Get the template file_part hash by type.
		$this->coreid = $coreid;
		$this->coreid_index = $coreid_index;
		$this->linkid_index = $linkid_index;
		$this->type = $type;
		$this->sort_records_hash = array();
		$this->sort_records = array();
		$this->module_data = array();
		$this->module_data_key = array();
		$this->module_data_nokey = array();
		$this->max_sortkey = 0;
		$this->total = 0;
		$this->errorstat = FALSE;
		$this->maintain_original_index = FALSE;
		$this->skip_db_update = FALSE;
		$this->ignore_empty_linkid = FALSE;
		////////////////////////////////////////////////////////////////////////////
		//Anything involves logic go below here
		/*
		$this->filehash = $this->CI->cms_template->type_filename_hash($type);
		if ( !$this->filehash ) {
			osa_errorlog(__METHOD__ . " - Filehash is missing for type {$type}");
			$this->show_error();
			return FALSE;
		}
		*/
		$this->original_filehash = $filehash;
		$tmp_hash = $this->CI->cms_template->filehash_filepart($filehash);
		if ( $tmp_hash ) {
			//We have a valid template filepart and a filehash was obtained
			$this->filehash = $tmp_hash;
		}
		elseif ( osa_is_int_one($filehash) ) {
			//This is internal filehash assignment for non-template features
			$this->filehash = $filehash;
		}
		else {
			//Couldn't get a filehash so print error
			osa_log(__METHOD__ , "Failed to obtain filehash.", $filehash);
			$this->show_error();
			return FALSE;
		}

		if ( !is_array($realtypes_index) ) {
			$realtypes_index = array($realtypes_index);
		}
		$this->realtypes_index = $realtypes_index;

		//We can't do update with multiple coreids and it is always a read_only operation.
		if ( is_array($coreid) ) {
			$this->read_only();
			$this->pre_fetch_off();
		}

		//Always the last section
		$this->needs_reset = FALSE;
		$this->needs_init = TRUE;
		//Do not put code here
		return TRUE;
	}

	//For Outcomes and LT. The module data doesn't contain realtype and realtype
	//actually belongs to another part of the data but needs to use this data
	//This will set the read-only flag. For write operation the realtype has to exist in the data set so the caller
	//has to add the realtype if it is not in the DB.
	function override_realtype($type) {
		if ( !osa_is_int_one($type) ) {
			osa_errorlog(__METHOD__ . " - Invalid type {$type}.");
			$this->show_error();
			return FALSE;
		}
		$this->read_only();
		$this->CI->btnsort_model->set_realtype_override($type);
	}

	//Read only operation and no btnsort table update
	function read_only() {
		$this->skip_db_update = TRUE;
		//Since it is readonly no need to worry about empty linkid for DB data integrity
		$this->ignore_empty_linkid();
	}

	//Some modules don't need prefetch or can't use prefecth
	//Use this to turn off prefetch
	function pre_fetch_off() {
		if ( $this->errorstat ) {
			return;
		}
		$this->CI->btnsort_model->set_pre_fetch(FALSE);
	}

	//Some mudules can have empty linkid so need to turn off checks
	function ignore_empty_linkid() {
		$this->CI->btnsort_model->ignore_empty_linkid(TRUE);
		$this->ignore_empty_linkid = TRUE;
	}

	//Sets the optional segment id index name. This is needed for modules like outcomes and LT
	//since they have multiple segments grouped by outosmes/benchmarks on one page from one single query
	//The code will look for this index in the incoming module data and write it's referenced segmentid value to the DB
	//function set_segmentindex($index_name, $index_name2=FALSE) {
	function set_segmentindex($index_name) {
		if ( empty($index_name) ) {
			osa_errorlog(__METHOD__ . ' - Empty segment index name.');
			$this->show_error();
			return FALSE;
		}
		//$this->CI->btnsort_model->set_segmentindex($index_name, $index_name2);
		$this->CI->btnsort_model->set_segmentindex($index_name);
		return TRUE;
	}

	//Only get the sort data for segment specified.
	//This will turn off prefectch
	function get_by_segment($segmentid) {
		if ( !osa_is_int_one($segmentid) ) {
			osa_errorlog(__METHOD__ . ' - Invalid segmentid.', $segmentid);
			$this->show_error();
			return FALSE;
		}
		$this->CI->btnsort_model->set_segmentid($segmentid);
		//Since we are getting a partial set of data so can't update.
		$this->read_only();
		//And can't have prefectch
		$this->pre_fetch_off();
	}

	//Maintains the incoming module data index. The default is false after each reset.
	function maintain_index() {
		$this->maintain_original_index = TRUE;
	}

	//Initializes/updates the sort entries. This has to be called everytime template data is displayed
	//$data is the db result set to be displayed from within the calling template module
	//$realtypes is the actual data template type and can be different than sort type, can be a string or array.
	//This is just for keeping track of things and not really use in the system yet.
	function init(&$data) {
		if ( $this->errorstat || !is_array($data) || count($data) <= 0 ) {
			//We don't want to sort 1 single record
			osa_errorlog(__METHOD__ . ' - Failed input data (array) check or prior error stat.');
			return FALSE;
		}
		if ( !$this->check_reset() ) {
			//This function can only be run once after each reset()
			return FALSE;
		}
		if ( count($data) <= 1 ) {
			if ( count($data) == 1 ) {
				$sortkey = self::INDEX_SORTKEY;
				//natsort will fail without this.
				$tmp = reset($data);
				$tmp->$sortkey = 1;
			}
			return TRUE;
		}

		$this->module_data = & $data;
		$this->needs_reset = TRUE;

		//Get all the related btnsort records
		$this->sort_records = & $this->CI->btnsort_model->get($this->type, $this->coreid, $this->filehash, $this->original_filehash);
		if ( !$this->sort_records ) {
			$this->sort_records = array();
		}
		//Hashes the btnsort records so we can easily compare it with the module_data array
		$this->hash();
		//Prepare all the data arrays so we know what to add and delete to the btnsort table
		//This will also inject existing btnsort record id and sortkey into the module_data array
		//Newly added module data records will be injected later after adding new btnsort records
		if ( !$this->prepare_sortdata() ) {
			$this->show_error();
			return FALSE;
		}

		if ( !$this->skip_db_update ) {
			//Delete btnsort
			if ( count($this->sort_records_hash) > 0 ) {
				//Need to delete records. The $this->sort_records_hash array was updated in prepare_sortdata()
				if ( !$this->CI->btnsort_model->delete($this->sort_records_hash) ) {
					osa_errorlog(__METHOD__ . 'Failed to delete btnsort records.', $this->sort_records_hash);
					$this->show_error();
					return FALSE;
				}
			}
			//Update/add btnsort
			if ( count($this->module_data_nokey) > 0 &&
				  count($this->module_data_nokey)+count($this->module_data_key) > 1 ) {
				//Add new entries to btnsort. The $this->module_data_nokey array was updated in prepare_sortdata()
				//This will also inject the newly inserted record ids and sortkeys into the module data array
				if ( !$this->CI->btnsort_model->update($this->module_data_nokey, $this->filehash,
					   $this->type, $this->coreid_index, $this->linkid_index, self::INDEX_SORTKEY, self::INDEX_BTNID, $this->realtypes_index) ) {
					osa_errorlog(__METHOD__ . ' - Failed to update/insert into btnsort.',
						array($this->module_data_nokey), $this->filehash, $this->type, $this->coreid_index, $this->linkid_index);
					$this->show_error();
					return FALSE;
				}
			}
			//Set security so can't even hack the sorting system
			$this->CI->cms_security->init(CMS_ACTION_EDIT, CMS_BTNSORT_SECURITY_TEMPLATE_NAME, $this->coreid, $this->type);
			//TODO: This adds full security for the whole page, should make it to support atomic level check later
			$this->CI->cms_security->has_full_security(CMS_ACTION_EDIT, TRUE);
			$this->CI->cms_security->save();
			//The caller may use the same security object so set it back
			$this->CI->cms_security->switch2original();
		}
/*
 * Everywhere that uses segments needs to sort again in the calling environment so this
 * is totally unnecessary.
		//Assign the array indexes to sort.
		if ( ($segmentindex = $this->CI->btnsort_model->get_segmentindex()) ) {
			//We sort by the segment index the first place to make sure we didn't break the segment order
			//This will ensure the old sort data does not break since they will not have the segmentidvalue in the DB.
			$natsort_indexes = array($segmentindex, self::INDEX_SORTKEY);
		}
		else {
			$natsort_indexes = array(self::INDEX_SORTKEY);
		}
*/
		$natsort_indexes = array(self::INDEX_SORTKEY);
		//Sort the array based on the injected sortkey in the array.
		osa_array_natsort($this->module_data, $natsort_indexes, $this->maintain_original_index, FALSE, FALSE, 'osa_numeric_str_compare');
		//Need the following so we know how to auto print the up and down arrows for the first and last row
		//If the caller adds or delete rows in the return result set then this will break.
		//The caller also has to call the get_button_array(0 function for every single row for the following to work.
		$this->total = count($this->module_data);
		$this->tail = end($this->module_data);
		$this->head = reset($this->module_data);
		$this->needs_init = FALSE;

		return TRUE;
	}

	//Gets the sort button array and the caller will pass them into unit_buttons object for rendering
	//Don't like to have external button data structure in this file but couldn't think of a better place to put it...
	function get_button_array($record, $type, $refresh_link=FALSE, $action=CMS_ACTION_EDIT) {
		if ( $this->skip_db_update || $this->total < 2 || $this->errorstat ) {
			return FALSE;
		}
		if ( $this->needs_init ) {
			osa_errorlog(__METHOD__ . ' - Object needs initialization.');
			$this->show_error();
			return FALSE;
		}
		$btnindex = self::INDEX_BTNID;
		$coreindex = $this->coreid_index;
		$linkindex = $this->linkid_index;
		if ( !is_object($record) || !osa_is_int_one($type) || !isset($record->$btnindex) ||
			  !isset($record->$coreindex) || !isset($record->$linkindex) ) {
			osa_errorlog(__METHOD__ . ' - Invalid input params.', array($type, $coreindex, $linkindex, $record));
			$this->show_error();
			return FALSE;
		}

		$head_id = FALSE;
		$tail_id = FALSE;
		if ( isset($this->head->$btnindex) ) {
			$head_id = $this->head->$btnindex;
		}
		if ( isset($this->tail->$btnindex) ) {
			$tail_id = $this->tail->$btnindex;
		}
		$ret = array();
		$ret[] = $this->get_sort_array($this->sort_link_up .
					"{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}",
					'moveup.gif', lang('gen_sort_button_up'), $type, $action, $refresh_link);
		$ret[] = $this->get_sort_array($this->sort_link_down .
					"{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}",
					'movedown.gif', lang('gen_sort_button_down'), $type, $action, $refresh_link);
		switch ($record->$btnindex) {
			case $head_id:
				$ret[0] = array('_type' => 'nothing', '_action' => $action, '_empty_button' => TRUE);
				break;
			case $tail_id:
				$ret[1] = array('_type' => 'nothing', '_action' => $action, '_empty_button' => TRUE);
				break;
		}
		return $ret;
	}

	//Returns actual button HTML and not template button array.
	//Used by outcomes and LT
	//All params are required except for $button_type self::BUTTON_NONE
	function get_button($button_type, $divid=FALSE, $record=FALSE, $type=FALSE, $refresh_link=FALSE) {
		if ( $this->skip_db_update || $this->total < 2 || $this->errorstat ) {
			return FALSE;
		}
		if ( $this->needs_init ) {
			osa_errorlog(__METHOD__ . ' - Object needs initialization.');
			$this->show_error();
			return FALSE;
		}
		if ( $button_type == self::BUTTON_NONE ) {
			return "<img border=\"0\" src=\"" . osa_imagepath() . "none.gif\" />";
		}
		$btnindex = self::INDEX_BTNID;
		$coreindex = $this->coreid_index;
		$linkindex = $this->linkid_index;
		if ( !is_object($record) || !isset($record->$btnindex) ||
			  empty($divid) || empty($refresh_link) || !osa_is_int_one($type) ||
			  !isset($record->$coreindex) || !isset($record->$linkindex) ) {
			osa_errorlog(__METHOD__ . 'Invalid input params.', array($button_type, $divid, $record, $type, $refresh_link));
			$this->show_error();
			return FALSE;
		}
		$link = '';
		$empty_image = '';
		if ( $button_type == self::BUTTON_UP ) {
			$link = $this->sort_link_up . "{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}";
			$image = osa_imagepath() . 'moveup.gif';
		}
		elseif ($button_type == self::BUTTON_DOWN) {
			$link = $this->sort_link_down . "{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}";
			$image = osa_imagepath() . 'movedown.gif';
		}
		else {
			return '';
		}
		return "<a href=\"" . $link . "\" onclick=\"sortUnitItem('" . $link . "'," . "'" . $refresh_link .
				 "','" . $divid .  "');return false;\"><img border=\"0\" src=\"" . $image . "\" /></a>";
	}

	//Gets the proper button combination based on the caller - first, last and middle
	//This is is necessary for segment based sorting since the final array order happens in the caller.
	//get_button_array() gets the combo by looking in the object array data instead.
	function get_buttons_manual($location, $record, $type, $refresh_link=FALSE, $action=CMS_ACTION_EDIT) {
		if ( $this->skip_db_update || $this->total < 2 || $this->errorstat ) {
			return FALSE;
		}
		if ( $this->needs_init ) {
			osa_errorlog(__METHOD__ . ' - Object needs initialization.');
			$this->show_error();
			return FALSE;
		}
		$btnindex = self::INDEX_BTNID;
		$coreindex = $this->coreid_index;
		$linkindex = $this->linkid_index;
		if ( !is_object($record) || !isset($record->$btnindex) || !isset($record->$coreindex) ||
			  !isset($record->$linkindex) || !osa_is_int_one($type) ) {
			osa_errorlog(__METHOD__ . ' - Invalid input params.', array($location, $record, $type, $refresh_link, $action));
			$this->show_error();
			return FALSE;
		}

		$ret = array();
		$ret[] = $this->get_sort_array($this->sort_link_up . "{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}",
					'moveup.gif', lang('gen_sort_button_up'), $type, $action, $refresh_link);
		$ret[] = $this->get_sort_array($this->sort_link_down . "{$record->$btnindex}/{$record->$coreindex}/{$record->$linkindex}",
					'movedown.gif', lang('gen_sort_button_down'), $type, $action, $refresh_link);
		if ( $location == self::BUTTONS_FIRST ) {
			$ret[0] = array('_type' => 'nothing', '_action' => $action, '_empty_button' => TRUE);
		}
		elseif ( $location == self::BUTTONS_LAST ) {
			$ret[1] = array('_type' => 'nothing', '_action' => $action, '_empty_button' => TRUE);
		}
		return $ret;
	}

	//Compare module data against DB records and set up the arrays for DB update.
	//The sortkey is set in this function based on the current DB max sort key value
	private function prepare_sortdata() {
		$index_sortkey = self::INDEX_SORTKEY;
		$index_btnid = self::INDEX_BTNID;
		$coreindex = $this->coreid_index;
		$linkindex = $this->linkid_index;
		$hash_data = & $this->sort_records_hash;

		//Check the first element and make sure we have the indexes. Types are always checked in the loop
		$current = reset($this->module_data);
		//if ( !isset($current->$coreindex) || !isset($current->$linkindex) || !osa_is_int($current->$coreindex) ||
		if ( !property_exists($current, $coreindex) || !property_exists($current, $linkindex) ||
			  (!osa_is_int($current->$coreindex) && !$this->ignore_empty_linkid) ||
			  (!osa_is_int($current->$linkindex) && !$this->ignore_empty_linkid) ) {
			osa_errorlog(__METHOD__ . ' - Invalid row data or indexes.', array($coreindex, $linkindex, $current));
			return FALSE;
		}

		//Loop thru the module data and match against the db records
		foreach ($this->module_data as $key => $value) {
			//Skip anything that doesn't have corid or linkid value.
			//The caller has to sort everything again after btnsort else the sorting order will be corrupted.
			//This is due to the segment support for outcomes and LT
			if ( $this->ignore_empty_linkid && (!$value->$linkindex || !$value->$coreindex) ) {
				//Need to add a fake sortkey else natsort will fail. This is usually for strand, standards, benchmarks
				//without a learning target. Elements that get here will never get written to the DB.
				$this->module_data[$key]->$index_sortkey = 1;
				continue;
			}
			//This will find the first matching realtype index matching the module data set
			if ( !($type = $this->CI->btnsort_model->get_first_realtype($this->realtypes_index, $value)) ) {
				return FALSE;
			}
			$hash_key = "{$value->$coreindex}_{$value->$linkindex}_{$type}";
			if ( isset($hash_data[$hash_key]) ) {
				//Record has sortkey already
				$tmp = $hash_data[$hash_key];
				if ( $tmp->sortkey > $this->max_sortkey ) {
					//Save max sortkey so we know what to assign for new btnsort records
					$this->max_sortkey = $tmp->sortkey;
				}
				$this->module_data_key[$key] = $this->module_data[$key];
				$this->module_data_key[$key]->$index_sortkey = $tmp->sortkey;
				$this->module_data_key[$key]->$index_btnid = $tmp->id;
				//Take out the hash key so what is left are ones that were deleted by the user
				//We expect the incoming module data is unique based on coreid and linkid else this will break
				unset($hash_data[$hash_key]);
			}
			else {
				//Record has no sort key - newly added record by user.
				//This is an object so copy by reference
				$this->module_data_nokey[$key] = $this->module_data[$key];
			}
		}
		//At this point, module_data_nokey contains new module data records that need missing sort key
		//module_data_key contains records with key, sort_records_hash contains db records need to be deleted

		//Assign the maxsortkey to records without matching DB record
		//This is the place where the new sortkeys come from
		foreach ($this->module_data_nokey as $key => $value) {
			$this->module_data_nokey[$key]->$index_sortkey = ++$this->max_sortkey;
		}
		return TRUE;
	}

	//Gets a single button array. If $refresh_link is FALSE then the standard unit refresh is used.
	private function get_sort_array($sort_link, $image, $alttex, $type, $action, $refresh_link=FALSE) {
		if ( !$refresh_link ) {
			$refresh_url_part = osa_stage_refresh_urlpart($type);
			$refresh_link = base_url() . "{$refresh_url_part}/_STAGENUM_/" . CMS_ACTION_EDIT . "/_UNITID_";
		}
		return array(
			'_type' => $type, '_image' => $image, '_action' => $action ,
      	'_link' => $sort_link, '_alt_title' => $alttex,
      	'_jscript' => "return sortUnitItem('" . $sort_link . "','" . $refresh_link . "','_STAGEDIV_')",
		);
	}

	//Hash the DB data for faster search
	private function hash() {
		if ( !$this->sort_records || !is_array($this->sort_records) ) {
			return TRUE;
		}
		$this->sort_records_hash = array();
		foreach ($this->sort_records as $key => $record) {
			$index = "{$record->coreid}_{$record->linkid}_{$record->realtype}";
			$this->sort_records_hash[$index] = $this->sort_records[$key];
		}
		return TRUE;
	}

	//Only prints the error once
	private function show_error() {
		if ( $this->skip_error_msg === FALSE ) {
			echo '<div class="buildererror">' . lang('template_btn_needs_reset_msg') . '</div>';
			@ob_flush(); @flush();
			$this->skip_error_msg = TRUE;
		}
		$this->errorstat = TRUE;
	}

	//Check to make sure object was reset before use
	private function check_reset() {
		if ( $this->needs_reset ) {
			osa_errorlog(__METHOD__ . ' - Object didn\'t get reset.');
			$this->show_error();
			return FALSE;
		}
		return TRUE;
	}

}