<?php

use LightnCandy\LightnCandy;

class CMS_Model extends Model {

	public $edit_auth = '';
	public $INFO;
	public $TABS;
	private $FIELDS;
	private $model_menuedit = NULL;
	private $preview;
	private $pages = NULL;
	private $pageshistory = NULL;
	private $level;
	private $user_id;
	private $salt;

	function __construct() {
		parent::__construct();
		$this->preview = '';
		$this->level = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_level'] : 0;
		$this->user_id = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_id'] : '';
		$settings = $this->SettingGet('config.module_info', NULL);
		$this->salt = isset($settings['key']) ? $settings['secret'] : '';
	}
	
	public function SetPages($value) {
		$this->pages = $value;
	}
	public function SetPagesHistory($value) {
		$this->pageshistory = $value;
	}
	
	public function SetMenuEditModel($model) {
		$this->model_menuedit = $model;
	}
	
	public function TemplateGet($page) {
		return !$this->_loadPage($page);
	}
	
	private function _loadPage($page, $emailhash = FALSE) {
		$this->INFO = $this->pages
				->query('select p.*, concat(name_first, " ", name_last) as __username from pages as p left join users on pg_owner=user_id')
				->where('pg_uri=%s', $page)
				->first();
		$this->pages->DbErr();
		if ($this->INFO === NULL) return TRUE;		// sql error
		$this->INFO->tablename('pages');
		if (empty($this->INFO->pg_extra)) {
			$this->INFO->__extra = [];
		} else {
			$this->INFO->__extra = unserialize($this->INFO->pg_extra);
		}
		if ($emailhash !== FALSE && $this->INFO->empty()) {
			$this->loadDefaultEmailTemplate($emailhash);
		}
		return FALSE;
	}
	
	public function isEditable() {
		if ($this->level >= 9) {
			// is this a 404 page, or do we have a CMS page?
			$httpstatus = $this->controller->HttpStatus();
			$stat = $httpstatus->status();
			if ($stat == 200) return FALSE;		// page is good. Don't allow cms
			$uri = $this->controller->uri();
			$authentication = $this->GenerateAuth($uri);
			$uri = $uri . '?editpage=' . $authentication;
			$data['extra'] = "<p><a href=\"$uri\">create content for this page</a></p>" . $data['extra'];
			return $uri;
		}
		return FALSE;
	}

	// return a value from 0 to 3 based on uri, editmode and user permissions
	public function IsCmsPage($uri, $edithash) {
		$query = ($p = strpos($uri, '?')) !== FALSE ? substr($uri, $p) : '';
		if ($query != '') $uri = substr($uri, 0, $p);
		$action = 0;		// not a CMS page

		// load from database
		if ($this->_loadPage($uri, FALSE)) return 0;
		
		if ($edithash != '') {
			if($this->validateAuth($uri, $edithash)) {
				$this->INFO->pg_uri = $uri;
				$action = 2;		// edit new or exiting page
			}
		}
		if (!$action) {
			if (!$this->INFO->empty()) $action = 1;
			// check if have permission to access it
		}
		return $action;
	}
/*
	public function AddLink_CanEdit(&$navigation) {
		if (!empty($this->edit_auth)) {
			$navigation['edit'] = ['caption' => 'Edit Page', 'menuclass' => 'nav-edit', 'href' => "?editpage={$this->edit_auth}"];
		}
	}
*/
	public function ViewPage() {
		$page_class = basename(rtrim($this->INFO->pg_uri, '/'));
		if ($page_class == '') $page_class = 'page-index';

		$body = $this->GetBody();
		if (hook_exists('cms.body')) {
			$body = hook_execute('cms.body', $body, $this);
		} else {
			$body = "<div class=\"cms-container\">{$body}</div>";
		}
		$title = trim($this->INFO->pg_title);
		if ($title != '') $this->controller->Title($title);
		$this->controller->Body($body);
	}
	
	public function GetBody() {
		$body = ExpandMacro($this->INFO->pg_text);
		$this->edit_auth = $this->GenerateAuth($this->INFO->pg_uri);	// only if authorized
		
/*		// if level > 9 or owner == logged in user, display edit link (if not displaying a preview)
		if ($this->INFO['pg_owner'] == $this->user_id || $this->level > 9 && $this->preview == '') {
			$this->edit_auth = $this->GenerateAuth($this->INFO['pg_uri']);
		} else {
			unset($this->edit_auth);
		} */
		$body = hook_execute('cms.frame', $body, $this);		// add layout header
		return $body;
	}

	private function _activetemplates() {
		if (empty($this->active_templates)) 
			$this->active_templates = $GLOBALS['loader']->EmailTemplates_List();
		return $this->active_templates;
	}
	
	public function loadDefaultEmailTemplate($hash) {
		$active_templates = $this->_activetemplates();
		if (isset($active_templates[$hash])) {
			$view_info = $this->_loadEmailTemplate($hash, $active_templates[$hash]);
			$this->INFO->pg_title = $view_info['subject'];
			$this->INFO->pg_text = $view_info['body'];
			$this->INFO->css = $view_info['css'] ?? NULL;
		}
	}
	
	//to do: remove old previews
	//to do: if email template, change fieldname from Title to Subject, and remove Permissions, & Preview button; maybe replace with Send TestEmail button;
	public function EditPage() {
		$uri = $this->controller->uri();
		$this->isEmail = substr($uri, 0, 13) == '/admin/email/';
		$auth = $this->GenerateAuth($uri);
		if ($this->isEmail && $this->INFO->empty()) {
			$elements = explode('/', $uri);
			$this->loadDefaultEmailTemplate($elements[3]);		// also do this if loading a [missing] template when about to email
		}
		hook_execute('component.add', '', 'htmledit');
		
		$duri = htmlspecialchars($this->INFO->pg_uri);
		$dtitle = htmlspecialchars(empty($this->INFO->pg_title) ? '' : $this->INFO->pg_title);
		$dbody = htmlspecialchars(empty($this->INFO->pg_text) ? '' : $this->INFO->pg_text);
/*
		<div class="control-group cf-uri">
		<label for="uri">URI</label>
		<div class="controls"><input class="required" id="uri" name="uri" size="30" type="text" value="{$duri}"></div>
		<div class="status-message" for="uri"></div></div>

*/
		$caption = $this->isEmail ? 'Subject' : 'Page Title';

		$this->TABS = [];
		$body = <<<BLOCK
		<input name="uri" type="hidden" value="{$duri}">

		<div class="control-group cf-title">
		<label for="title">$caption</label>
		<div class="controls"><input class="required" id="title" name="title" size="30" type="text" value="{$dtitle}"></div>
		<div class="status-message" for="title"></div></div>

		<div class="control-group cf-body">
		<label for="body">Body</label>
		<div class="controls"><textarea name="body" class="htmledit pagebody">$dbody</textarea></div>
		</div>
BLOCK;

		hook_execute('cms.fields', $this);						// add extra input fields
		list($tabs, $content) = $this->_GenerateTabHTML();

		$body = hook_execute('cms.prepare', $body, $this);
		$previewhash = md5(time() . ':' . mt_rand(0, 999999999));

		$document_level = empty($this->INFO->__extra['documentlevel']) ? 0 : $this->INFO->__extra['documentlevel'];
		$groups = hook_execute('group.enum', FALSE);
		$options = '';
		foreach($groups as $grouplevel => $groupname) {
			$sel = $document_level == $grouplevel ? ' selected' : '';
			$options .= "<option value=\"$grouplevel\"$sel>$groupname</option>";
		}
		// button generation is part of the theme - theme should be set asap
		$preview = hook_execute_late('html.button', '', 'Preview', 'far fa-file-alt', 'cc-light');
		$button = hook_execute_late('html.button', '', 'Update', 'far fa-check-circle', 'cc-blue');
		
		$body = <<<BLOCK
<form method="post" class="frm json">
	<div id="cms_panel" class="vertical-panel">
	  <ul>
		<li><a href="#tabs-1">General</a></li>
		$tabs
		<div class="action-group">
			<div class="cms-perms">
				<div>Permissions</div>
				<div><select class="cms-perm-select" name="perms" id="perms">$options</select></div>
			</div>
			<div class="action-item"><a class="submit" for="submit_action" data-value="preview" href="/?previewid={$previewhash}" target="_blank">$preview</a></div>
			<div class="action-item"><a class="submit" for="submit_action" data-value="update" href="#">$button</a></div>
			<div style="margin-top:20px"><a href="/admin/cms-pages">Back to Page List</a></div>
			<!-- [COL1] -->
		</div>
		
	  </ul>
	  <div class="cms-tab" id="tabs-1">
			<input id="submit_action" type="hidden" name="s" value="">
			<input type="hidden" name="previewid" value="$previewhash">
			$body
	  </div>
	  $content
	</div>
	
</form>

BLOCK;

		$body = hook_execute('template.cms.edit.COL1', $body);

		if (class_exists('BreadCrumb_model', FALSE)) {
			$model_crumb = BreadCrumb_model::GetHandle();
			$body = $model_crumb . $body;
		}
		$this->controller->Body($body);
	}
	
	private function _GenerateTabHTML() {
		$tabs = $content = '';
		foreach($this->TABS as $index => $tab_item) {
			$name = \Misc::asSlug($tab_item['caption']);
			$tabs .= "<li><a href=\"#{$name}\">{$tab_item['caption']}</a></li>";
			$fields = $this->_GenerateFieldHTML($tab_item['fields']);
			$content .= <<<BLOCK
<div class="cms-tab" id="{$name}">
	<h3>{$tab_item['caption']}</h3>
	$fields
</div>
BLOCK;
		}
		return array($tabs, $content);
	}
	
	public function AddTab($tabname /* field definitions follow */) {
		$field_definitions = func_get_args();
		array_shift($field_definitions);
		
		foreach($this->TABS as &$info) {
			if ($info['caption'] == $tabname) {
				$info['fields'] = array_merge($info['fields'], $field_definitions);
				return;
			}
		}
		
		$this->TABS[] = [	'caption' => $tabname,
							'fields' => $field_definitions ];
	}

	public function AddField_TextArea($fieldname, $caption, $className = FALSE) {
		$node = [	'type' => 'textarea',
					'name' => $fieldname,
					'class' => $className,
					'caption' => $caption ];		// include sizes?
		return $node;
	}

	private function _fieldHtml_TextArea($field_info, $current_value) {
		$name = $field_info['name'];
		$class = $field_info['class'] ?? '';
		$dcurrent = htmlspecialchars($current_value);
		return $this->_label($field_info, "<textarea class=\"cms-textarea $class\" id=\"{$name}\" name=\"{$name}\">{$dcurrent}</textarea>");
	}
	
	public function AddField_Select($fieldname, $caption, $selectlist) {
		$node = [	'type' => 'select',
					'name' => $fieldname,
					'caption' => $caption,
					'list' => $selectlist ];	//to do: should be 'options'
		return $node;
	}

	private function _fieldHtml_Select($field_info, $current_value) {
		$name = $field_info['name'];
		$options = '';
		foreach($field_info['list'] as $key => $value) {
			$sel = $key == $current_value ? ' selected' : '';
			$dvalue = htmlspecialchars($value);
			$options .= "<option value=\"$key\"$sel>$dvalue</option>";
		}
		return $this->_label($field_info, "<select id=\"{$name}\" name=\"{$name}\">{$options}</select>");
	}

	public function AddField_Comment($caption) {
		$node = [	'type' => 'comment',
					'caption' => $caption ];
		return $node;
	}
	
	private function _fieldHtml_comment($field_info) {
		return "<div class=\"cms-comment\">{$field_info['caption']}</div>";
	}

	public function AddField_checkbox($fieldname, $caption) {
		$node = [	'type' => 'checkbox',
					'name' => $fieldname,
					'caption' => $caption ];
		return $node;
	}
	
	// input-frame
	private function _fieldHtml_Checkbox($field_info, $current_value) {
		$name = $field_info['name'];
		$sel = empty($current_value) ? '' : ' checked';
//		return $this->_label($field_info, "<input type=\"checkbox\" id=\"{$name}\" name=\"{$name}\" value=\"1\"$sel>");
		$dcaption = htmlspecialchars($field_info['caption']);
		return <<<BLOCK
		<div class="control-group cf-{$name}">
			<div class="controls input-frame width-50">
				<input type="checkbox" id="{$name}" name="{$name}" value="1"$sel>
				<label for="$name">$dcaption</label>
			</div>
			<div class="status-message" for="{$name}"></div>
		</div>
BLOCK;
	}
	
	
	public function AddField_Hidden($fieldname, $value = NULL) {
		$node = [	'type' => 'hidden',
					'name' => $fieldname,
					'value' => $value ];
		return $node;
	}

	private function _fieldHtml_Hidden($field_info, $current_value) {
		$dvalue = htmlspecialchars($current_value);
		return "<input type=\"hidden\" name=\"{$field_info['name']}\" value=\"{$dvalue}\">";
	}

	public function AddField_Dimension($fieldname, $caption, $d1, $d2) {
		$node = [	'type' => 'dimension',
					'name' => $fieldname,
					'label' => [$d1, $d2],
					'caption' => $caption];
		return $node;
	}
	
	private function _fieldHtml_Dimension($field_info, $value1, $value2) {
		$name = $field_info['name'];
		$dim1 = $name . '_' . strtolower($field_info['label'][0]);
		$dim2 = $name . '_' . strtolower($field_info['label'][1]);
		$dvalue1 = htmlspecialchars($value1);
		$dvalue2 = htmlspecialchars($value2);
		$dcaption = htmlspecialchars($field_info['caption']);
		return <<<BLOCK
		<div class="control-group cf-{$name}">
			<label for="$name">$dcaption</label>
			<div class="controls">
				{$field_info['label'][0]}: <input class="tiny" id="{$dim1}" name="{$dim1}" size="15" type="text" value="{$dvalue1}"> 
				{$field_info['label'][1]}: <input class="tiny" id="{$dim2}" name="{$dim2}" size="15" type="text" value="{$dvalue2}"> 
			</div>
			<div class="status-message" for="{$name}"></div>
		</div>
BLOCK;
	}
	
	public function AddField_input($fieldname, $caption) {
		$node = [	'type' => 'input',
					'name' => $fieldname,
					'caption' => $caption ];		// include sizes?
		return $node;
	}
	
	private function _fieldHtml_Input($field_info, $current_value) {
		$dvalue = htmlspecialchars($current_value);
		return $this->_label($field_info, "<input type=\"text\" id=\"{$field_info['name']}\" name=\"{$field_info['name']}\" value=\"{$dvalue}\">");
	}

	public function AddField_input_small($fieldname, $caption) {
		$node = [	'type' => 'small',
					'name' => $fieldname,
					'caption' => $caption ];		// include sizes?
		return $node;
	}
	private function _fieldHtml_Small($field_info, $current_value) {
		$dvalue = htmlspecialchars($current_value);
		return $this->_label($field_info, "<input type=\"text\" class=\"tiny\" id=\"{$field_info['name']}\" name=\"{$field_info['name']}\" value=\"{$dvalue}\">");
	}


	public function AddField_Sortable($fieldname, $caption) {
		$node = [	'type' => 'sortable',
					'name' => $fieldname,
					'caption' => $caption ];
		return $node;
	}
	
	private function _fieldHtml_Sortable($field_info, $current_value) {
		$current_value = trim($current_value);
//		$current_value = '/our-solution=Our Solution;/about-us=About Us;/contact-us=Contact Us';
		if ($current_value == '') {
			$items = [];
		} else {
			$items = explode(';', $current_value);
		}
		$output = '';
		foreach($items as $item) {
			list($k, $v) = explode('=', $item, 2);
			$output .= "<li data-value=\"$k=$v\"><span class=\"cms-action\"><i class=\"cms-icon cms-trash\"></i></span>$v</li>";
		}

		$dcurrent_value = htmlspecialchars($current_value);
		$output = <<<BLOCK
<ul class="cms-sortable" for="{$field_info['name']}">{$output}</ul>
<a href="/~/cms/page-list" class="cms-sortable-add" for="{$field_info['name']}">Add new item...</a>
<input type="hidden" id="{$field_info['name']}" name="{$field_info['name']}" value="{$dcurrent_value}">

BLOCK;
		return $this->_label($field_info, $output);
	}

	private function _label($field_info, $input_html) {
		$name = $field_info['name'];
		$dcaption = htmlspecialchars($field_info['caption']);
		return <<<BLOCK
		<div class="control-group cf-{$name}">
			<label for="{$name}">{$dcaption}</label>
			<div class="controls">{$input_html}</div>
			<div class="status-message" for="{$name}"></div>
		</div>
BLOCK;
	}
	
	private function _GenerateFieldHTML($fieldlist) {
		$output = '';
		foreach($fieldlist as $field_info) {
			$current_value = isset($field_info['name']) ? $this->_getCurrentValue($field_info['name']) : '';
			switch($field_info['type']) {
				case 'textarea':
					$output .= $this->_fieldHtml_TextArea($field_info, $current_value);
					break;
				case 'select':
					$output .= $this->_fieldHtml_Select($field_info, $current_value);
					break;
				case 'comment':
					$output .= $this->_fieldHtml_comment($field_info);
					break;
				case 'checkbox':
					$output .= $this->_fieldHtml_CheckBox($field_info, $current_value);
					break;
				case 'hidden':
					$output .= $this->_fieldHtml_Hidden($field_info, $current_value);
					break;
				case 'dimension':
					$dim1 = $field_info['name'] . '_' . strtolower($field_info['label'][0]);
					$dim1 = $this->_getCurrentValue($dim1);
					$dim2 = $field_info['name'] . '_' . strtolower($field_info['label'][1]);
					$dim2 = $this->_getCurrentValue($dim2);
					$output .= $this->_fieldHtml_Dimension($field_info, $dim1, $dim2);
					break;
				case 'input':
					$output .= $this->_fieldHtml_Input($field_info, $current_value);
					break;
				case 'small':
					$output .= $this->_fieldHtml_Small($field_info, $current_value);
					break;
				case 'sortable':
					$output .= $this->_fieldHtml_Sortable($field_info, $current_value);
					break;
				
			}
		}
		return $output;
	}

	private function _getCurrentValue($name) {
		switch($name) {
			case 'title':
				return empty($this->INFO->pg_title) ? '' : $this->INFO->pg_title;
			case 'body':
				return empty($this->INFO->pg_text) ? '' : $this->INFO->pg_text;
		}
		return $this->INFO->__extra[$name] ?? '';
	}
	
	public function Extra() {
		if (func_num_args()) {
			$this->INFO->__extra = func_get_arg(0);
			return $this;
		}
		return $this->INFO->__extra;
	}

	public function pageSave($post) {
		$results = [];
		//$path = '';
		$path = $this->INFO->pg_uri;
		$isPreview = $post['s'] == 'preview';

		// check for errors - all fields required
		foreach(['title'] as $fieldname) {
			$val = trim($post[$fieldname]);
			$post[$fieldname] = $val;
			$key = $fieldname;
			if ($val == '') {
				if (!isset($results['field'][$key])) {
					if (!isset($results['field'])) $results['field'] = array();
					$results['field'][$key] = array('state' => 1, 'message' => '?');	// '?' will be replaced by 'xxx must have a value'
				} 
			}
		}

		if (isset($results['field'])) return $results;
		
		// process uri
		if (substr($path, 0, 1) != '/') $path = '/' . $path;
		
		$old = [];
		foreach($this->INFO as $key => $value) {
			$old[$key] = $value;
		}
		
		$old = $this->INFO;
		if (is_null($this->INFO->__extra)) $this->INFO->__extra = [];
		
//		$this->INFO->pg_uri = $path;
		$this->INFO->pg_title = $post['title'];
		$this->INFO->pg_text = $post['body'];

		// save any extra fields
		hook_execute('cms.save', $post, $this);		// update  $this->INFO and $this->INFO->__extra
		$level =  empty($post['perms']) ? 0 : intval($post['perms']);

		if (empty($this->INFO->pg_owner)) {
			$this->INFO->pg_owner = $this->user_id;
		}
		$this->INFO->pg_modified = gmdate('Y-m-d H:i:s');
		$extra = $this->INFO->__extra;
		$extra['documentlevel'] = $level;		// who has permission to view?
		$this->INFO->pg_extra = serialize($extra);
		
		if ($isPreview) {
			$id = $this->pageshistory !== NULL ? $this->pageshistory->SavePreview($this->INFO) : 0;
			$_SESSION['preview'][$post['previewid']] = $id;
			session_write_close();
			return $results;
		} else {
			if ($this->pageshistory !== NULL) $this->pageshistory->SaveHistory($old);
		}

		$this->INFO
			->TableName('pages')
			->SaveRecord(['pg_uri' => $path]);
		$err = $this->INFO->err();

		$results['result'] = $err['errnum'];
		$results['message'] = $err['errmsg'];
		
		$title_changed = empty($old->pg_title) ? '' : $old->pg_title;
		$title_changed = $this->INFO->pg_title != $title_changed;
/*		if ($title_changed && is_object($this->model_menuedit) && !$DATABASE->errnum) {
			$this->model_menuedit->MenuItem_UpdateTitle($path, $this->INFO['pg_title']);
		} */
		hook_execute('cms.saved', $this);
		unset($this->INFO);	// assume $this->INFO is destroyed at this point anyway (probably by a hook function)

		if (!$results['result']) {
			$results['result'] = -9999;
			$results['message'] = 'Page has been updated.&nbsp; <a href="' . $path . '">View Page</a>';
		}

		if ($old->pg_uri != $path) {		// name has changed - redirect with new auth
			$auth = $this->GenerateAuth($path);
			$results['redirect'] = $path . '?editpage=' . $auth;
		}
		return $results;
	}
	
	function toFilename($classname) {
		$classname = str_replace('\\', '/', $classname);
		return HOME . '/' .  $classname  . '.php';
	}
	
	private function _loadEmailTemplate($key, $item) {
		list($classname, $entrypoint) = explode('::', $item['entrypoint'] . '::');	// classname is the file in which it appears
		$entrypoint = strtolower($entrypoint);
		$filename = $this->toFilename($classname);
		if (!file_exists($filename)) {
			die("unable to locate file specified by email template '{$item['entrypoint']}'");
		}
		$info = $GLOBALS['loader']->GetFileInformation($filename);
		$view_info = NULL;
		require_once $filename;
		foreach($info as $class_id => $publics) {
			foreach($publics as $public => $unused) {
				if ($entrypoint == strtolower($public)) {
					$view = new $class_id;
					$view_info = $view->$entrypoint();
					break;
				}
			}
			if ($view_info !== NULL) break;
		}
		return $view_info;
	}

	public function getEmailLinks($active_templates) {
		$output = [];
		foreach($active_templates as $key => $item) {
			$view_info = $this->_loadEmailTemplate($key, $item);
			$value = ($view_info === NULL) ? 'email template could not be located' : $view_info['title'];
			$path = '/admin/email/' . $key;
			$auth = $this->GenerateAuth($path);
			$node = ['key' => $key,
					'value' => $value,
					'link' => $path . '?editpage=' . $auth];
			$output[] = $node;
		}
		return $output;
	}
	
	private function GetEmailTemplateHash($mailobject) {
		$template = $mailobject->template();
		if (strpos($template, '::') !== FALSE) {
			list($module, $template) = explode('::', $template);
		} else {
			$module = '*';
		}
		
		$hashes = $this->_activetemplates();
		foreach($hashes as $hash => $email_info) {
			if ($email_info['internal'] == $template) {
				if ($module == '*' || $module == $email_info['module']) return $hash;
			}
		}
		return NULL;
	}
	
	public function ApplyEmailTemplate(&$mailobject) {
		$hash = $this->GetEmailTemplateHash($mailobject);
		$page = '/admin/email/' . $hash;
		// get page from cms - if missing then get default email page
		if (!$this->_loadPage($page, $hash)) {
			$data = $mailobject->data();
			$subject = $mailobject->subject();
			$css = $mailobject->css();
			if (empty($subject)) {
				$mailobject->subject($this->_ProcessHandlebars($data, $this->INFO->pg_title));
			}
			$mailobject->body($this->_ProcessHandlebars($data, $this->INFO->pg_text));
			if (!empty($this->INFO->css) && empty($css)) {
				$mailobject->css($this->INFO->css);
			}
		}
	}

	private function _ProcessHandlebars($data, $templatetext) {
		// md5 is used so we only compile when the source template has changed
		$dir = HOME . '/storage/handlebars';
		$workfile = $dir . '/hb' . md5($this->salt . ':' . $templatetext) . '.php';
		if (!file_exists($dir)) mkdir($dir);
		
		if (!file_exists($workfile)) {		// do we have a compiled copy? 
			$php = LightnCandy::compile($templatetext, [
				'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_BESTPERFORMANCE
			]);
			file_put_contents($workfile, '<?php ' . $php);
		}
		$renderer = include($workfile);		// get a handle to the compiled function
		return $renderer($data);

	}

	//to do: fix this.  $config is not a global
	public function RedirectToPreview($previewid, $hostname) {
		if ($this->pageshistory !== NULL) {
			$data = NULL;
			if (isset($_SESSION['preview'][$previewid])) {
				$id = $_SESSION['preview'][$previewid];
				$data = $this->pageshistory->LoadPreview($id);
			}
			if (is_null($data) || $data->empty()) {
				die("Unable to preview. Please try reloading the page.\n");
			}
			
			$auth = $this->pageshistory->GetPreviewAuth($data);
			header('Location: ' . $hostname . $data->pg_uri . '?preview=' . $data->ph_id . '-' . $auth);
		} else {
			header('Content-type: text/plain');
			die('10833. Page history unavailable');
		}
	}
	
	private function GeneratePreview($preview) {
		list($id, $requested_auth) = explode('-', $preview . '-');
		$this->INFO = $this->LoadPreview($id);
		$auth = $this->GetPreviewAuth($this->INFO);
		return ($auth == $requested_auth);
	}

	
	public function GenerateAuth($page) {
		$key = md5($page);
		$auth = hook_execute('nonce.create', FALSE, 'cmspage', $key);
		return $auth;
	}
	
	public function validateAuth($page, $requested_auth) {
		$key = md5($page);
		return hook_execute('nonce.verify', FALSE, 'cmspage', $key, $requested_auth);
	}

	public function GetPages($pages) {
		$output = [];
		if (count($pages)) {
			$rows = $this->pages->Query('select pages.* from pages')
					->WhereIn('pg_uri', $pages)
					->Get();
			foreach($rows as $data_item) {
				$key = $data_item->pg_uri;
				$data_item->__extra = unserialize($data_item->pg_extra);
				unset($data_item->pg_uri, $data_item->pg_extra);
				$output[$key] = $data_item;
			}
		}
		return $output;
	}
	
	public function ErrorPage(&$data, $uri) {
		if ($this->level >= 9) {
			$authentication = $this->GenerateAuth($uri);
			$uri = $uri . '?editpage=' . $authentication;
			$data['extra'] = "<p><a href=\"$uri\">create content for this page</a></p>" . $data['extra'];
			return $uri;
		}
		return FALSE;
	}

	public function getEmailTemplate($template_name) {
	}
	
	public function datatable_pagelist() {
		$groups = hook_execute('group.enum', FALSE);
		
		$data = $this->pages->LoadDynamicPages(TRUE);
		foreach($data as &$row) {
			$mod = strtotime($row['mod'] . 'Z');
			$link = $row['l'];
			$html = htmlspecialchars($link);
			$auth = $this->GenerateAuth($link);
			$row['l'] = [
				'disp'	=>  "<a href=\"$html?editpage=$auth\">$html</a>",
				'val'	=>  $html,
			];
			$row['mod'] = [
				'disp'	=>  date('Y-m-d H:i', $mod),
				'val'	=>  $mod,
			];
			$level = $row['x']['documentlevel'] ?? 0;
			unset($row['x']);
			$row['v'] = $groups[$level] ?? $level;
		}

		$result = [
			'columns' => [
				['data' => 'l', 	'width' => '140px', 'render' => ['_' => 'disp', 'sort' => 'val'] ],
				['data' => 'c',		],	//'width' => '420px'
				['data' => 's', 	'width' => '120px'],
				['data' => 'v', 	'width' => '80px' , 'defaultContent' => ''],
				['data' => 'mod',	'width' => '120px', 'render' => ['_' => 'disp', 'sort' => 'val'] ],
			],
			'order' => [ [ 0, 'asc'] ],
			'pageLength' => 25,
			'autoWidth' => FALSE,
			'bPaginate' => TRUE,
			'bFilter' => TRUE,
			'bInfo' => TRUE,					// Showing x to y of z entries
			'data' => $data,
		];
		return $result;
	}

	// remove pages that already appear in $order
	public function GeneratePageList($order, $page) {
		$data = $this->pages->LoadDynamicPages();
		foreach($data as &$data_item) {
			unset($data_item['s']);
			$data_item['c'] .= " [{$data_item['l']}]";
		}
		return [
					'popup' => [
						'pages' => $data,
						'width' => 430,
						'template' => 'cms_selectpage',
						'title' => 'Select CMS Page(s)',
						'modal' => true,
						'dialogClass' => 'cms-selectpage',
						'actions' => [
							[
								'data-action' 	=> 'add',
								'text'			=> 'Add selected to Page List',
								'class'			=> 'cms-selection',
							],
						]
					]
				];
	}
	
	// show page and expiry
	public function decodeEditPage($hash) {
		$info = hook_execute('nonce.info', FALSE, $hash);
		unset($info['random']);
		if ($info['subject'] = 'cmspage') {
			$data = $this->pages->LoadDynamicPages();
			foreach($data as $row) {
				if (md5($row['l']) == $info['key']) {
					$info['uri'] = $row['l'];
					$info['title'] = $row['c'];
					$info['modified'] = $row['mod'];
					break;
				}
			}
		}
		return var_export($info, 1);
	}

}