<?php
/**
 * Cms_template.php - Library to support template function like types, stages etc.
 * 						 login.php and Cms_template.php call this library. Only check file mod time in login.php
 *
 * @author $Author: dtong $
 * @version $Id: Cms_template_init.php,v 1.20 2011/05/10 16:11:56 dtong Exp $
 * @copyright Copyright (c) 2010, Tiller Software Co., Ltd.
*/

require_once 'cms_template_data.php';

class Cms_template_init {
	const MTIME = 'mtime'; //stat() modification time index

	private $CI=NULL;
	private $org_name=''; //The org name like ISB, ZMS etc
	private $folders = NULL; //Contains folder names like goal, survey etc.
	private $default_path = ''; //Feault path to the unit templates
	private $data_obj = NULL; //The object that contains all the data
	private $data_cache_file = ''; //The path to the cache file
	private $use_cache_file = TRUE; //Use cache file or not
	private $source_files = NULL; //The sources files that can affect the cache data content
	private $check_mod_time = FALSE; //To check template and soruce file mod time or not

	function __construct($lib_params=FALSE) {
		global $g_template_id_array;

		$this->CI = & get_instance();
		//Check for mod time on templates and sources or not
		if ( $lib_params !== FALSE || $this->CI->config->item('cms_check_template_mod_time') === TRUE )
			$this->check_mod_time = TRUE;
		$this->org_name = $this->CI->config->item('cms_unit_org');
		$this->folders = $g_template_id_array;
		//$this->default_path = APPPATH . 'libraries/unit/config/';
		$this->default_path = CMS_TEMPLATEPATH_BUILDER;
		//$this->data_cache_file = APPPATH . 'libraries/cms/cms_template/cms_template_data.cache';
		$this->data_cache_file = dirname($this->CI->config->item('cms_resource_uploadfolder')) . '/' .
		//$this->data_cache_file = $this->CI->config->item('cms_resource_uploadfolder') . '/' .
			$this->CI->config->item('cms_unit_org') . "_cms_template_data.cache";
		$this->source_files = array(__FILE__, str_replace('\\', '/', APPPATH) . 'libraries/cms/cms_template/cms_template_data.php');
		//So it works in windows since APPPATH uses \
		$this->default_path = str_replace('\\', '/', $this->default_path);
		$this->data_cache_file = str_replace('\\', '/', $this->data_cache_file);
		if ( !$this->read_cache_file() )
			$this->init();
	}

	function get() {
		return $this->data_obj;
	}

	//Initiatilize the data sturcture and save the object into the cache file
	private function init() {
		$this->data_obj = new cms_template_data();
		//Save the unit_org so we know if we switch site and update the cache
		$this->data_obj->cms_unit_org = $this->CI->config->item('cms_unit_org');
		//Do the default unit template folder
		//if ( ($files = $this->get_template_files($this->default_path)))
			//$this->process_folder($files, $this->default_path, CMS_TEMPLATE_UNIT_IDENTIFIER);
		//Now look thru other folders under the default folder
		foreach ($this->folders as $folder) {
			$path = $this->default_path . $folder . '/';
			if ( ($files = $this->get_template_files($path)) ) {
				//$folder is the template identifier and also the template folder name
				$this->process_folder($files, $path, $folder);
				$this->data_obj->file_array = array_merge($this->data_obj->file_array, $files);
			}
		}
		$this->write_cache_file();
	}

	//Loads the data cache file if there is one and it is newer than all the template files
	//returns TRUE if cache is loaded else false
	private function read_cache_file() {
		if ( !$this->use_cache_file )
			return FALSE;
		$file_array = array();
		if ( is_file($this->data_cache_file) ) {
			$cache_file = stat($this->data_cache_file);
			//Check unit template mod time and source file mod time
			if ( $this->template_newer($this->default_path, $cache_file[self::MTIME]) ||
					$this->source_files_newer($cache_file[self::MTIME]) )
				return FALSE;
			//Check goal and survey mod time
			foreach ($this->folders as $folder) {
				$path = $this->default_path . $folder . '/';
				if ( $this->template_newer($path, $cache_file[self::MTIME]) )
					return FALSE;
				if ( ($files = $this->get_template_files($path)) ) {
					$file_array = array_merge($file_array, $files);
				}
			}

			//Get the cached object from file system
			$content = file_get_contents($this->data_cache_file);
			if ( empty($content) ) {
				//Something is wrong so we delete the cache file
				@unlink($this->data_cache_file);
				return FALSE;
			}
			$data_obj = @unserialize($content);
			if ( is_object($data_obj) && ($data_obj instanceof cms_template_data) ) {
				//Not the same site config so ignore cache and force to rewrite the cache
				if ( $data_obj->cms_unit_org != $this->CI->config->item('cms_unit_org') ) {
					return FALSE;
				}

				//Check to see did we add or delete templates
				//Only check if $this->check_mod_time is true so we don't always check on every page
				if ( $this->check_mod_time && (
					  count(array_diff($file_array, $data_obj->file_array)) > 0 ||
					  count(array_diff($data_obj->file_array, $file_array)) > 0 ) ) {
					return FALSE;
				}
				$this->data_obj = $data_obj;
				return TRUE; //No need to process, use the cache
			}
			//At this point there is a cache file but we couldn;t use it so delete it
			//A new cahce file will be created
			@unlink($this->data_cache_file);
			osa_errorlog(__METHOD__ . " - {$this->data_cache_file} does not contain a valid cms_template_data object.");
			return FALSE;
		}
		return FALSE;
	}

	private function write_cache_file() {
		if ( !$this->use_cache_file )
			return FALSE;
		$content = serialize($this->data_obj);
		$byte_count = @file_put_contents($this->data_cache_file,$content,LOCK_EX);
		if ( $byte_count < 10 ) {
			osa_errorlog(__METHOD__ . " - Failed to write to template cache file {$this->data_cache_file}.");
			return FALSE;
		}
		return TRUE;
	}

	private function source_files_newer($cache_mtime) {
		if ( !$this->check_mod_time )
			return FALSE;
		foreach ($this->source_files as $file) {
				$stat = stat($file);
				if ( $stat[self::MTIME] > $cache_mtime )
					return TRUE;
		}
		return FALSE;
	}

	//Checks all the template files in one folder and check the mtime against the cache file's mtime
	private function template_newer($path, $cache_mtime) {
		if ( !$this->check_mod_time )
			return FALSE;
		if ( ($files = $this->get_template_files($path)) ) {
			foreach ($files as $file) {
				$stat = stat($path . $file);
				if ( $stat[self::MTIME] > $cache_mtime )
					return TRUE;
			}
		}
		return FALSE;
	}

	//Returns all the template paths in an array in the supplied folder/path
	private function get_template_files($path) {
		$origdir = getcwd();
		if (!chdir($path)) {
			return FALSE;
		}
		$ret = glob($this->org_name . '_*');
		$ret1 = glob(CMS_TEMPLATE_SYSTEM_IDENTIFIER . '_*');
		if ( count($ret1) > 0 ) {
			$ret = array_merge($ret, $ret1);
		}
		chdir($origdir);
		if ( count($ret) < 1 )
			return FALSE;
		return $ret;
	}

	//Process all the template files in one folder
	private function process_folder($files, $path, $template_id) {
		$folder = $template_id;
		//unit folder is special and one level up
		//if ( $template_id == CMS_TEMPLATE_UNIT_IDENTIFIER )
			//$folder = '';
		foreach ($files as $file) {
			if ( strstr($file, $this->org_name . '_') == $file )
				$file_part = str_ireplace($this->org_name . '_', '', $file);
			elseif ( strstr($file, CMS_TEMPLATE_SYSTEM_IDENTIFIER . '_') == $file ) {
				$file_part = str_ireplace( CMS_TEMPLATE_SYSTEM_IDENTIFIER . '_', '', $file);
			}
			else {
				@osa_errorlog(__METHOD__ . ' - somehow we don\'t know what to do with this template file.',
								 array($file, $files, $path, $template_id));
			}
			$file_part = str_ireplace('.php', '', $file_part);
			$this->data_obj->file_part_hash[$file_part] = substr(base_convert(md5($file_part),16,10),0,19);
			if ( osa_load_template_obj($file_part, true, $folder, '__temp_template') ) {
				eval("\$template_obj=__temp_template_{$file_part}::get();");
				$this->process_template($template_id, $file_part, $template_obj);
				unset($template_obj);
			}
		}
	}

	//This function assigns the data for a single template
	private function process_template($template_id, $file_part, $template_obj) {
		//Separating all the different data in different functions so it is simple
		//This code does not have to be efficient since it onlys gets run once or when ever one or more templates get updated
		$this->assign_types($file_part, $template_obj, $template_id);
		$this->assign_paging($file_part, $template_obj);
	}

	//Get the paging flag for a template
	//The index is file_part
	private function assign_paging($file_part, $template_obj) {
		if ( isset($template_obj->page) && is_array($template_obj->page) && count($template_obj->page) > 0 ) {
			$this->data_obj->template_paging_array[$file_part] = TRUE;
		}
	}

	private function get_modulename_from_extra($type, $template_obj) {
		if ( !isset($template_obj->extra) || !is_array($template_obj->extra) ||
				!array_key_exists($type, $template_obj->extra) )
			return FALSE;
		return osa_value($template_obj->extra[$type], '_type', FALSE);
	}

	//Get the array structure
	private function assign_module_types($module, $file_part, $template_obj, $stagenum, $template_id) {
		//Set the meta types
		if ( osa_value($module, '_type', FALSE) == 'nothing' ) {
			if ( ($metatypes = osa_value($module, '_meta_typeid', FALSE)) !== FALSE ) {
				if ( !is_array($metatype) ) {
					$metatypes = array($metatypes);
				}
				foreach ($metatypes as $meta) {
					$this->data_obj->meta_types[$meta] =
						array('stage'=>$stagenum, 'file_part'=>$file_part, 'template_id'=>$template_id,
								'file_part_hash' => substr(base_convert(md5($file_part),16,10),0,19));
				}
				return;
			}
		}

		$types = osa_value($module, '_typeid', FALSE);
		$hidden_type = FALSE; //Hidden type not needed for most template_data arays
		if ( $types === FALSE ) {
			$types = osa_value($module, '_hidden_typeid', FALSE);
			if ( $types !== FALSE ) {
				//hidden type always array because we want to get the type from the stage->extra array
				$types = array($types);
				$hidden_type = TRUE;
			}
		}
		if ( $types === FALSE )
			return;

		if ( is_array($types) ) {
			//Handles multi-type modules like for sorting
			//Always get the module name '_type' from stage->extra
			foreach($types as $type_str) {
				$module_name = $this->get_modulename_from_extra($type_str, $template_obj);
				if ( $module_name === FALSE )
					continue;
				if ( array_key_exists($file_part, $this->data_obj->template_module_array) &&
					  array_key_exists($module_name, $this->data_obj->template_module_array[$file_part]) &&
					  in_array($type_str, $this->data_obj->template_module_array[$file_part][$module_name])) {
					//Duplicated entry, don;t add it again
					osa_errorlog(__METHOD__ . " - Type $type_str already exist in module $module_name");
					return;
				}
				if ( !$hidden_type ) {
					$this->data_obj->template_module_array[$file_part][$module_name][] = $type_str;
					sort($this->data_obj->template_module_array[$file_part][$module_name]);

					//Being lazy here to write new code to populate template_stagemodule_types
					//Assuming the logic to get rid of duplicates is fine here with template_stagemodule_types
					$this->data_obj->template_stagemodule_types[$file_part][$stagenum][$module_name][] = $type_str;
					sort($this->data_obj->template_stagemodule_types[$file_part][$stagenum][$module_name]);
				}
				//This one includes hidden types
				$this->data_obj->template_stagemodule_types_special[$file_part][$stagenum][$module_name][] = $type_str;
				sort($this->data_obj->template_stagemodule_types_special[$file_part][$stagenum][$module_name]);

				//Always update the array pointer, not efficient but safe
				//whatever in template_stagemodule_types_special[] will get assigned to template_all_levels_hashidden
				$this->data_obj->template_all_levels_hashidden[$template_id][$file_part] = & $this->data_obj->template_stagemodule_types_special[$file_part];
			}
		}
		else {
			//Handles single-type module, get the types from current module and not from stage->extra[]
			$module_name = osa_value($module, '_type', FALSE);
			if ( $types !== FALSE ) {
				if ( array_key_exists($file_part, $this->data_obj->template_module_array) &&
					  array_key_exists($module_name, $this->data_obj->template_module_array[$file_part]) &&
					  in_array($types, $this->data_obj->template_module_array[$file_part][$module_name])) {
					//Duplicated entry, don;t add it again
					osa_errorlog(__METHOD__ . " - Type $types already exist in module $module_name");
					return;
				}
				$this->data_obj->template_module_array[$file_part][$module_name][] = $types;
				sort($this->data_obj->template_module_array[$file_part][$module_name]);

				//Being lazy here to write new code to populate template_stagemodule_types
				//Assuming the logic to get rid of duplicates is fine here with template_stagemodule_types
				$this->data_obj->template_stagemodule_types[$file_part][$stagenum][$module_name][] = $types;
				sort($this->data_obj->template_stagemodule_types[$file_part][$stagenum][$module_name]);

				//This one includes hidden types
				$this->data_obj->template_stagemodule_types_special[$file_part][$stagenum][$module_name][] = $types;
				sort($this->data_obj->template_stagemodule_types_special[$file_part][$stagenum][$module_name]);

				// Use a array by reference so whatever gets updated above gets into this array too
				// Always update the array pointer, not efficient but safe
				$this->data_obj->template_all_levels_hashidden[$template_id][$file_part] = & $this->data_obj->template_stagemodule_types_special[$file_part];
				/*
				if ( !array_key_exists($template_id, $this->data_obj->template_all_levels_hashidden)) {
					$this->data_obj->template_all_levels_hashidden[$template_id] = array();
					$this->data_obj->template_all_levels_hashidden[$template_id][$file_part] = & $this->data_obj->template_stagemodule_types_special[$file_part];
				}
				else {
					if ( array_key_exists($file_part, $this->data_obj->template_all_levels_hashidden[$template_id]))
						$this->data_obj->template_all_levels_hashidden[$template_id][$file_part] = & $this->data_obj->template_stagemodule_types_special[$file_part];
				} */
			}
		}
	}

	//Gets all the types by filename like ubd, pyp, myp, sbd etc.
	private function assign_types($file_part, $template_obj, $template_id) {
		//$stage_num = $template_obj->stage_num;
		$stage_num = count($template_obj->stage);
		//Loop thru the stages
		for ($i=1; $i<=$stage_num; $i++) {
			$stage_data = $template_obj->stage[$i];
			//Loop thru the modules in one single stage
			foreach ($stage_data as $module) {
				//The logic for module types is a little different therefore call a function to handle this
				$this->assign_module_types($module, $file_part, $template_obj, $i, $template_id);

				if ( is_array($module) && array_key_exists('_typeid', $module) ) {
					//At this point, we know this is a valid module
					$types = osa_value($module, '_typeid', FALSE);
					//$value can be array or string so always make it an array
					if ( !is_array($types) ) {
						$types = array($types);
					}
					foreach ($types as $type_str) {
						/////////////////////////////////////////////////////////////
						//Assign static types
						if ( osa_value($module, '_static', FALSE) ) {
							$this->data_obj->static_types[$type_str] =
								array('file_part'=>$file_part, 'template_id'=>$template_id,
										'file_part_hash' => substr(base_convert(md5($file_part),16,10),0,19));
						}
						/////////////////////////////////////////////////////////////
						//Assign types by filename
						if ( array_key_exists($file_part, $this->data_obj->type_array) && in_array($type_str, $this->data_obj->type_array[$file_part]) ) {
							osa_errorlog(__METHOD__ . " - type $type_str in $file_part is duplicated.");
						}
						else {
							$this->data_obj->type_array[$file_part][] = $type_str;
							//Have to sort everytime adding a new element
							//Not efficient but this code should only get run once on a production server
							sort($this->data_obj->type_array[$file_part]);
						}

						/////////////////////////////////////////////////////////////
						//Assign types by filename + stage
						$index = "stage{$i}_" . $file_part;
						if ( array_key_exists($index, $this->data_obj->type_array) && in_array($type_str, $this->data_obj->type_array[$index]) ) {
							osa_errorlog(__METHOD__ . " - type $type_str in $file_part stage $i is duplicated.");
						}
						else {
							$this->data_obj->type_array[$index][] = $type_str;
							sort($this->data_obj->type_array[$index]);
							///////////////////////////////////////
							//multi-dimension stages and types
							//The if statement above has the same logic for this array too
							$this->data_obj->template_stage_array[$file_part][$i][] = $type_str;
							sort($this->data_obj->template_stage_array[$file_part][$i]);
						}

						//Create filepart hash integer, this was done for button sorting
						//The sortbtn code will still work even when there is a collision
						$file_part_hash = substr(base_convert(md5($file_part),16,10),0,19);
						/////////////////////////////////////////////////////////////
						//Assign type reference, index is type and data is file_part, stage and template identifier
						//It stores multiple data for types that exists in multiple templates.
						//For example, learning target type exists in ubd, myp and pyp so all of them are saved here
						$tmp = array('file_part'=>$file_part, 'stage'=>$i,
										 'template_id'=>$template_id, 'file_part_hash'=>$file_part_hash);
						if ( array_key_exists($type_str, $this->data_obj->type_reference_array) ) {
							if ( $this->is_duplicate_type_reference($this->data_obj->type_reference_array[$type_str], $tmp) )
								osa_errorlog(__METHOD__ . " - Duplicated template type reference.", array($type_str, $tmp));
							else
								$this->data_obj->type_reference_array[$type_str][] = $tmp;
						}
						else {
							$this->data_obj->type_reference_array[$type_str][] = $tmp;
						}
					} //foreach
				} //if _typeid exists
			} //foreach module
		} //for stage
	}

	//Checks duplicate type, this is the one that stores file_part,stage and template identifier and indexed by type
	private function is_duplicate_type_reference($current_arrays, $new_array) {
		foreach ($current_arrays as $current_array) {
			if ( $current_array['file_part'] == $new_array['file_part'] &&
				  $current_array['stage'] == $new_array['stage'] &&
				  $current_array['template_id'] == $new_array['template_id'] ) {
				return TRUE;
			}
		}
		return FALSE;
	}

}