403Webshell
Server IP : 104.21.21.239  /  Your IP : 216.73.216.11
Web Server : Apache/2.4.68 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-69-123.ec2.internal 6.1.176-223.369.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Jul 24 13:34:27 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/tgnewdev/admin/mod/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/tgnewdev/admin/mod/_functions.php
<?php

	require_once('_constants.php');
	require_once('_permissions.php');
	require_once('_password.php');
	include_once __DIR__ . '../../vendor/autoload.php';
	require_once __DIR__ . '../../vendor/simplehtmldom/simple_html_dom.php';

	spl_autoload_register(function ($class) {
		$class = str_replace('\\', '/', $class);
		if (file_exists($class . '.php')) {
			include $class . '.php';
			return;
		}
		if (file_exists('classes/lib/' . $class . '.php')) {
			include'classes/lib/' . $class . '.php';
			return;
		}
		$file = __DIR__ . '/../' . $class . '.php';
		if (file_exists($file)) {
			include $file;
		}
	});

	use UAParser\Parser;
	use Jaybizzle\CrawlerDetect\CrawlerDetect; 
	use Detection\MobileDetect;

	/*
	set_error_handler(function($errno, $errstr, $errfile, $errline) {
		if (
			str_starts_with($errstr, 'Undefined array key')
			|| str_starts_with($errstr, 'Undefined variable')
		) :
			//throw new ErrorException($errstr, 0, E_NOTICE, $errfile, $errline);
			trigger_error($errstr . ' in ' . $errfile . ' on line ' . $errline, E_USER_NOTICE);
			return true;
		else :
			return false; //default error handler
		endif;
	}, E_WARNING);
	*/

	function parse_uri($parm = 'IX', $offset = 0, $default = 'home', $force_slug = false) {
		if (isset($_GET[$parm])) :
			return $_GET[$parm];
		endif;
		global $ar_pagecontrol;
		global $db;
		$result = '';
		$url = strtok($_SERVER['REQUEST_URI'],'?');
		//$ar_uri = explode('/', $_SERVER['REQUEST_URI']);
		$ar_uri = explode('/', $url);
		if ($offset >= 0) :
			$offset = $offset + APP_URI_OFFSET;
		else :
			$offset = count($ar_uri) + $offset;
		endif;
		if ($offset > count($ar_uri) + 1 || $offset < APP_URI_OFFSET) :
			return $default;
		endif;
		$slug = $ar_uri[$offset] ?? '';
		if ($slug === 'admin' && !$force_slug) :
			$slug = $ar_uri[$offset + 1];
		endif;
		$item = null;
		foreach($ar_pagecontrol as $key=>$value) :
			if (isset($value['slug']) && !is_array($value['slug']) && $value['slug'] > ' ' && $slug == $value['slug']) :
				$item = $key;
				break;
			elseif (isset($value['slug']) && is_array($value['slug']) && count($value['slug']) > 0) :	//** for multiple slugs per page **
				foreach ($value['slug'] as $slug_base) :
					//if (strtolower(substr($slug, 0, strlen($slug_base))) == strtolower($slug_base)) :  //** for landing pages **
					if (strtolower($slug) == strtolower($slug_base)) :
						$item = $key;
						break 2;
					endif;
				endforeach;
			endif;
		endforeach;
		if (empty($item)) :
			//******* check articles ********
			$preview = false;
			if (($_SESSION['logged_in'] ?? false) && ($_GET['preview'] ?? '') === 'true') :
				$preview = true;
			endif;
			$sql = "SELECT COUNT(*) AS article_count FROM articles WHERE slug = :slug AND (post_status = 1 OR TRUE = :preview) ORDER BY post_date DESC";
			$rows = db_cached_query($sql, 'obj', [
				':slug' => $slug,
				':preview' => $preview,
			], 10);
			if ($rows[0]->article_count > 0) :
				$item = 'page_article';
			endif;
			//*******************************
		endif;
		if ($force_slug) :
			$result = $slug;
		elseif ($item) :
			$result = $item;
		elseif ($parm != 'IX') :
			$result = $slug;
		endif;
		return $result;
	}

	function load_event(string $slug, $preview = false): bool|object {
		global $db;
		$sql = "SELECT e.*,
			l.name AS location_name,
			l.slug AS location_slug,
			l.address,
			l.city,
			l.state,
			l.zip,
			l.phone,
			l.url AS location_url,
			l.google_map,
			l.phone AS location_phone,
			m.media_credit
			FROM events e
			LEFT JOIN locations l ON (l.id = e.location_id)
			LEFT JOIN media m ON (m.media_type = 'img' AND m.media_path = 'img/content/' AND m.media_filename = e.featured_image)
			WHERE e.slug = :slug AND (e.status = 1 OR TRUE = :preview) ORDER BY e.start_time_local DESC
		";
		$stmt = $db->prepare($sql);
		$stmt->execute([
			':slug' => $slug,
			':preview' => $preview,
		]);
		$row = $stmt->fetch(PDO::FETCH_OBJ);
		if ($row) :
			return $row;
		endif;
		return false;
	}

	function format_event_date_display($start_time, $end_time): string {
		$date = '';
		$start_time = strtotime($start_time);
		$end_time = strtotime($end_time);
		$date .= date('D, M j', $start_time);
		if (date('Y', $start_time) !== date('Y')) :
			$date .= ', ' . date('Y', $start_time);
		endif;
		$date .= ' at ' . date('g:ia', $start_time);
		$date .= ' - ';
		if (date('D, M j', $start_time) !== date('D, M j', $end_time)) :
			$date .= date('D, M j', $end_time);
		endif;
		if (date('Y', $end_time) !== date('Y', $start_time)) :
			$date .= ', ' . date('Y', $end_time);
		endif;
		$date .= date(' g:ia', $end_time);
		return $date;
	}

	function is_admin(): bool {
		if (parse_uri('admin', 0, 'admin', true) === 'admin') :
			return true;
		else :
			return false;
		endif;
	}

	$ar_menu_groups = array();

	function top_menu_item($menu_page, $menu_text, $roles = '', $slug = false) {
		if (role_match(get_roles(), $roles) || empty($roles)) :
			if ($menu_page == $_GET['IX'] ?? '' || (empty($_GET['IX']) && str_ends_with($_SERVER['REQUEST_URI'], $menu_page))) :
				echo '<li class="selected">'.$menu_text.'</li> ';
			else :
				if ($slug) :
					echo '<li><a onclick="appstack_reset();" href="'.$menu_page.'">'.$menu_text.'</a></li> ';
				else :
					echo '<li><a onclick="appstack_reset();" href="index.php?IX='.$menu_page.'">'.$menu_text.'</a></li> ';
				endif;
			endif;
		endif;
	}

	function top_menu_item_group_start($menu_page, $menu_text, $roles, $slug = false) {
		global $ar_menu_groups;
		if (empty($roles) || role_match( get_roles(), $roles)) :
			if ($menu_page == $_GET['IX']) :
				echo '<li class="selected has_submenu"><a href="#">'.$menu_text.'</a><ul> ';
			else :
				if (empty($menu_page) || $menu_page == '#') :
					echo '<li><a href="#">'.$menu_text.'</a><ul> ';
				else :
					/*
					echo '<li><a onclick="appstack_reset();" href="index.php?IX='.$menu_page.'">'.$menu_text.'</a><ul> ';
					*/
					if ($slug) :
						echo '<li class="has_submenu"><a onclick="appstack_reset();" href="'.$menu_page.'">'.$menu_text.'</a><ul> ';
					else :
						echo '<li class="has_submenu"><a onclick="appstack_reset();" href="index.php?IX='.$menu_page.'">'.$menu_text.'</a><ul> ';
					endif;
				endif;
			endif;
			array_push($ar_menu_groups, $menu_text);
		endif;
	}

	function top_menu_item_group_end() {
		global $ar_menu_groups;
		if (count($ar_menu_groups) > 0) :
			echo '</ul></li>';
			array_pop($ar_menu_groups);
		endif;
	}

	function app_has_feature(string $feature): bool {
		if ((defined('APP_FEATURE_FLAGS') && (APP_FEATURE_FLAGS[$feature] ?? false)) || (strtolower($_COOKIE[$feature] ?? '')) === 'true') :
			return true;
		else :
			return false;
		endif;
	}

	$ar_ftype = array();
	$ar_ffields = array();
	$ar_fupdate = array();
	$ar_finsert = array();
	$ar_fretrieve = array();
	
	function is_assoc_array($arr) {
		return array_keys($arr) !== range(0, count($arr) - 1);
	}
	
	function form_field($fname, $ftype = 'text', $fsize = '50', $frows = 0, $frequired = false, $disabled = false, $readonly = false, $fstyle = "", $fclass = "", $fdbname = "", $flabel = "", $ar_group = [], $onchange = "", $onblur = "", $qtip = "", $datatype = '', $err_message = '', $filter_options = null, $echo = true, $maxlength = '', $horizontal_layout = false, $form_id = NULL, $placeholder = '', $nullable = false, $multiselect = null, $tag_required = false )
	{
		global $ar_err;
		global $xform_fid;
		global $curr_tab;
		if (is_array(func_get_arg(0))) :
			$ar_args = func_get_arg(0);
			$fname = (isset($ar_args['fname']) ? $ar_args['fname'] : $fname);
			$ftype = (isset($ar_args['ftype']) ? $ar_args['ftype'] : $ftype);
			$fsize = (isset($ar_args['fsize']) ? $ar_args['fsize'] : $fsize);
			$frows = (isset($ar_args['frows']) ? $ar_args['frows'] : $frows);
			$frequired = (isset($ar_args['frequired']) ? $ar_args['frequired'] : $frequired);
			$disabled = (isset($ar_args['disabled']) ? $ar_args['disabled'] : $disabled);
			$readonly = (isset($ar_args['readonly']) ? $ar_args['readonly'] : $readonly);
			$nullable = (isset($ar_args['nullable']) ? $ar_args['nullable'] : $nullable);
			$fstyle = (isset($ar_args['fstyle']) ? $ar_args['fstyle'] : $fstyle);
			$fclass = (isset($ar_args['fclass']) ? $ar_args['fclass'] : $fclass);
			$fdbname = (isset($ar_args['fdbname']) ? $ar_args['fdbname'] : $fdbname);
			$flabel = (isset($ar_args['flabel']) ? $ar_args['flabel'] : $flabel);
			$ar_group = (isset($ar_args['ar_group']) ? $ar_args['ar_group'] : $ar_group);
			$onchange = (isset($ar_args['onchange']) ? $ar_args['onchange'] : $onchange);
			$onblur = (isset($ar_args['onblur']) ? $ar_args['onblur'] : $onblur);
			$qtip = (isset($ar_args['qtip']) ? $ar_args['qtip'] : $qtip);
			$datatype = (isset($ar_args['datatype']) ? $ar_args['datatype'] : $datatype);
			$err_message = (isset($ar_args['err_message']) ? $ar_args['err_message'] : $err_message);
			$filter_options = (isset($ar_args['filter_options']) ? $ar_args['filter_options'] : $filter_options);
			$echo = (isset($ar_args['echo']) ? $ar_args['echo'] : $echo);
			$maxlength = (isset($ar_args['maxlength']) ? $ar_args['maxlength'] : $maxlength);
			$horizontal_layout = (isset($ar_args['horizontal_layout']) ? $ar_args['horizontal_layout'] : $horizontal_layout);
			$form_id = (isset($ar_args['form_id']) ? $ar_args['form_id'] : $form_id);
			$placeholder = (isset($ar_args['placeholder']) ? $ar_args['placeholder'] : $placeholder);
			$multiselect = (isset($ar_args['multiselect']) ? $ar_args['multiselect'] : $multiselect);
			$tag_required = (isset($ar_args['tag_required']) ? $ar_args['tag_required'] : $tag_required);
		endif;
		$GLOBALS['ar_fields_'.$xform_fid][$fname] = array('ftype'=>$ftype, 'datatype'=>$datatype, 'filter_options'=>$filter_options, 'fsize'=>$fsize, 'frequired'=>$frequired, 'disabled'=>$disabled, 'readonly'=>$readonly, 'fdbname'=>$fdbname, 'tab'=>$curr_tab, 'err_message'=>$err_message);
		if (empty($fdbname)) :
			$fdbname = $fname;
		endif;
		if (!empty($fstyle)) :
			$style = ' style="'.$fstyle.'" ';
		endif;
		if ($frequired) :
			$fclass .= ' required';
		endif;
		if (!empty($ar_err[$fname])) :
			$fclass .= ' inerror';
		endif;
		if ($ftype == 'image' || $ftype == 'file') :
			$fclass .= ' photo_label';
		endif;
		if (!empty($fclass)) :
			$class = ' class="'.trim($fclass).'" ';
		endif;
		$fid = $fname;
		if (!empty($form_id)) :
			$fid = $fid . '_' . $form_id;
			$form_id = ' form="'.trim($form_id).'" ';
		endif;
		if ($disabled) :
			$disabled = ' disabled="disabled" ';
		endif;
		if ($readonly) :
			$readonly = ' readonly="readonly" ';
		endif;
		if (!empty($onchange)) :
			if ($ftype == 'radiogroup') :
				$onchange = 'if ($(this).prop(\'checked\')) {'.$onchange.'}';
			endif;
			$onchange = ' onchange="'.trim($onchange).'" ';
		endif;
		if ($ftype == 'autocomplete') :
			$onblur .= ' lcs_autocomplete_close(\''.$fname.'\'); ';
		endif;
		if (!empty($onblur)) :
			$onblur = ' onblur="'.trim($onblur).'" ';
		endif;
		if (!empty($placeholder)) :
			$placeholder = ' placeholder="'.trim($placeholder).'" ';
		endif;
		if (!empty($multiselect)) :
			$multiselect = ' multiple="multiple" ';
		endif;
		if (!empty($tag_required)) :
			$tag_required = ' required ';
		endif;
		$group_break = '<br />';
		if ($horizontal_layout) :
			$group_break = '&nbsp;&nbsp; ';
		endif;
		$attributes = ($style ?? NULL).($class ?? NULL).($form_id ?? NULL).$disabled.$readonly.$placeholder.$onchange.$onblur.$multiselect.$tag_required;
		$attributes_rc = ($form_id ?? NULL).$disabled.$readonly.$onchange.$onblur.$tag_required;
		$ar_err[$fname] = $ar_err[$fname] ?? NULL;
		$_POST[$fname] = $_POST[$fname] ?? NULL;
		switch ($ftype) :
			case "hidden":
				echo '<input name="'.$fname.'" id="'.$fid.'" type="'.$ftype.'" maxlength="'.$fsize.'" value="'.$_POST[$fname].'" '.$attributes.' />';
				break;
			case "text" :
			case "password" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="'.$ftype.'" maxlength="'.$fsize.'" value="'.$_POST[$fname].'" '.$attributes.' />';
				show_form_error($ar_err[$fname]);
				$ar_ftype[] = $ftype;
				$ar_ffields[] = $fdbname;
				$ar_fupdate[] = $fdbname;
				echo '</div>';
				break;
			case "select":
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				if (!empty($multiselect)) :
					echo '<select name="'.$fname.'[]" id="'.$fid.'" '.$attributes.'>';
				else :
					echo '<select name="'.$fname.'" id="'.$fid.'" '.$attributes.'>';
				endif;
				$selected = '';
				if ($readonly || $disabled) :
					$selected = ' disabled="disabled" ';
				endif;
				echo '<option value="" '.$selected.' >Select...</option>';
				if (is_assoc_array($ar_group)) :
					foreach ($ar_group as $key => $value) :
						$selected = '';
						if ((string)$_POST[$fname] == (string)$key) :
							$selected = ' selected="selected" ';
						elseif ($readonly || $disabled) :
							$selected = ' disabled="disabled" ';
						endif;
						if (is_array($value)) :
							echo '<option data-value="'.$value[0][2].'" value="'.$key.'" '.$selected.' /> '.$value[0][1].'</option>';
						else :
							echo '<option value="'.$key.'" '.$selected.' /> '.$value.'</option>';
						endif;
					endforeach;
				else :
					foreach ($ar_group as $value) :
						$selected = '';
						if ((string)$_POST[$fname] == (string)$value) :
							$selected = ' selected="selected" ';
						elseif ($readonly || $disabled) :
							$selected = ' disabled="disabled" ';
						endif;
						echo '<option value="'.$value.'" '.$selected.' /> '.$value.'</option>';
					endforeach;
				endif;
				echo '</select>';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "autocomplete":
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="text" maxlength="'.$fsize.'" value="'.$_POST[$fname].'" '.$attributes.' onfocus="lcs_autocomplete_open(\''.$fid.'\')" onkeyup="lcs_autocomplete_filter(\''.$fid.'\')" />';
				echo '<div id="'.$fid.'_autocomplete" class="autocomplete"><ul>';
				foreach ($ar_group as $value) :
					echo '<li onclick="lcs_autocomplete_pick(this, \''.$fid.'\');">'.$value.'</li>';
				endforeach;
				echo '</ul></div>';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "radiogroup":
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<fieldset id="'.$fid.'" '.$attributes.'>';
				if (is_assoc_array($ar_group)) :
					foreach ($ar_group as $key => $value) :
						$checked = '';
						if (isset($_POST[$fname]) && $_POST[$fname] == $key) :
							$checked = ' checked="checked" ';
						endif;
						echo '<div class="rc_text"><label><input type="radio" name="'.$fname.'" value="'.$key.'" '.$checked.' '.$attributes_rc.' /> '.$value.'</label></div>'.$group_break;
						//echo '<input type="radio" name="'.$fname.'" value="'.$key.'" '.$checked.' '.$attributes.' /> '.$value.$group_break;
						//echo $key.' '.$value.'<br />';
					endforeach;
				else :
					foreach ($ar_group as $value) :
						$checked = '';
						if ($_POST[$fname] == $value) :
							$checked = ' checked="checked" ';
						endif;
						echo '<div class="rc_text"><label><input type="radio" name="'.$fname.'" value="'.$value.'" '.$checked.' '.$attributes_rc.' /> '.$value.'</label></div>'.$group_break;
						//echo '<input type="radio" name="'.$fname.'" value="'.$value.'" '.$checked.' '.$attributes.' /> '.$value.$group_break;
						//echo $value.'<br />';
					endforeach;
				endif;
				echo '</fieldset>';
				show_form_error($ar_err[$fname], false);
				echo '</div>';
				break;
			case "checkgroup":
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<fieldset id="'.$fid.'" '.$attributes.'>';
				if (is_assoc_array($ar_group)) :
					foreach ($ar_group as $key => $value) :
						$checked = '';
						if (is_array($_POST[$fname]) && in_array($key, $_POST[$fname])) :
							$checked = ' checked="checked" ';
						endif;
						echo '<div class="rc_text"><label><input type="checkbox" name="'.$fname.'[]" value="'.$key.'" '.$checked.' '.$attributes_rc.' /> '.$value.'</label></div>'.$group_break;
						//echo '<input type="checkbox" name="'.$fname.'[]" value="'.$key.'" '.$checked.' '.$attributes.' /> '.$value.$group_break;
						//echo $key.' '.$value.'<br />';
					endforeach;
				else :
					foreach ($ar_group as $value) :
						$checked = '';
						if (is_array($_POST[$fname]) && in_array($value, $_POST[$fname])) :
							$checked = ' checked="checked" ';
						endif;
						echo '<div class="rc_text"><label><input type="checkbox" name="'.$fname.'[]" value="'.$value.'" '.$checked.' '.$attributes_rc.' /> '.$value.'</label></div>'.$group_break;
						//echo '<input type="checkbox" name="'.$fname.'[]" value="'.$value.'" '.$checked.' '.$attributes.' /> '.$value.$group_break;
						//echo $value.'<br />';
					endforeach;
				endif;
				echo '</fieldset>';
				show_form_error($ar_err[$fname], false);
				echo '</div>';
				break;
			case "checkbox":
			case "radio":
				echo '<div class="input_field">';
				$checked = '';
				if ('1' == $_POST[$fname]) :
					$checked = ' checked="checked" ';
				endif;
				echo '<div class="rc_text"><label><input type="'.$ftype.'" name="'.$fname.'" id="'.$fid.'" value="1" '.$checked.' '.$attributes.' /> '.$flabel.'</label></div>';
				//echo '<input type="'.$ftype.'" name="'.$fname.'" id="'.$fname.'" value="1" '.$checked.' '.$attributes.' /> '.$flabel;
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "textarea" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<textarea name="'.$fname.'" id="'.$fid.'" cols="'.$fsize.'" rows="'.$frows.'"  ';
				if ($readonly || $disabled) :
					echo ' disabled="disabled" readonly="readonly" ';
				endif;
				if (!empty($maxlength)) :
					echo ' maxlength="'.$maxlength.'" ';
				endif;
				if (!empty($fstyle)) :
					echo ' style="'.$fstyle.'" ';
				endif;
				if (!empty($fclass)) :
					echo ' class="'.$fclass.'" ';
				endif;
				echo '  >';
				echo $_POST[$fname];
				echo '</textarea>';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "date" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="text" maxlength="'.$fsize.'" value="'.nzdate_display($_POST[$fname]).'" '.$attributes.' />';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "datetime" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="text" maxlength="'.$fsize.'" value="'.nzdate_display_datetime($_POST[$fname]).'" '.$attributes.' />';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "htmlarea" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'">'.$flabel.'</label>';
				echo '<div name="'.$fname.'" id="'.$fid.'" class="form_htmlarea" '.$attributes.'  >';
				echo $_POST[$fname];
				echo '</div>';
				show_form_error($ar_err[$fname]);
				echo '</div>';
				break;
			case "file" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'_label">'.$flabel.'</label>';
				echo '<label for="'.$fid.'" '.$attributes.' >';
				echo 'Click/Touch here to upload a file<br />';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="file" />';
				echo '</label>';
				echo '<input name="'.$fname.'_filename" id="'.$fid.'_filename" type="hidden" maxlength="255" value="'.$_POST[$fname.'_filename'].'"  />';
				show_form_error($ar_err[$fname], false);
				echo '</div>';
				break;
			case "image" :
				echo '<div class="input_field">';
				echo '<label for="'.$fid.'_label">'.$flabel.'</label>';
				echo '<label for="'.$fid.'" '.$attributes.' >';
				echo 'Click/Touch here to upload or snap a photo<br />';
				//echo '<input name="'.$fname.'" id="'.$fid.'" type="file" accept="image/*" capture="camera" />';
				echo '<input name="'.$fname.'" id="'.$fid.'" type="file" accept="image/*" />';
				echo '</label>';
				echo '<input name="'.$fname.'_filename" id="'.$fid.'_filename" type="hidden" maxlength="255" value="'.$_POST[$fname.'_filename'].'"  />';
				show_form_error($ar_err[$fname], false);
				echo '</div>';
				break;
		endswitch;
	}
	
	function form_validate($field, $valtype, $required = false, $tab = 'basic_info', $message = '') {
		global $ar_err;
		global $err_flag;
		global $err_tab;
		if ($valtype == 'image' || $valtype == 'file') :
			if ($required && empty($_FILES[$field]['name'])) :
				if (empty($message)) :
					$message = 'Required!';
				endif;
				$ar_err[$field] = $message;
				$err_flag = true;
				if (empty($err_tab)) :
					$err_tab = $tab;
				endif;
				return;
			endif;
			if ($valtype == 'image' && isset($_FILES[$field]) && !empty($_FILES[$field]['tmp_name'])) :
				$check = getimagesize($_FILES[$field]['tmp_name']);
				if($check === false) :
					$ar_err[$field] = 'Invalid or corrupted image file!';
					$err_flag = true;
					if (empty($err_tab)) :
						$err_tab = $tab;
					endif;
					return;
				endif;
			endif;
		else :
			if ($required && trim($_POST[$field] ?? '') == '') :
				if (empty($message)) :
					$message = 'Required!';
				endif;
				$ar_err[$field] = $message;
				$err_flag = true;
				$err_tab = $tab;
				return;
			endif;
			if (isset($_POST[$field]) && is_string($_POST[$field]) && trim($_POST[$field]) != '') :
				if (empty($message)) :
					$message = 'Invalid!';
				endif;
				switch ($valtype) :
					case 'string' :
						break;
					case 'email' :
						if (filter_var($_POST[$field], FILTER_VALIDATE_EMAIL) === false) :
							$ar_err[$field] = $message;
							$err_flag = true;
							$err_tab = $tab;
						endif;
						break;
					case 'url' :
						if (filter_var($_POST[$field], FILTER_VALIDATE_URL) === false) :
							$ar_err[$field] = $message;
							$err_flag = true;
							$err_tab = $tab;
						endif;
						break;
					case 'int' :
						if (filter_var($_POST[$field], FILTER_VALIDATE_INT) === false) :
							$ar_err[$field] = $message;
							$err_flag = true;
							$err_tab = $tab;
						endif;
						break;
					case 'float' :
						if (filter_var($_POST[$field], FILTER_VALIDATE_FLOAT) === false) :
							$ar_err[$field] = $message;
							$err_flag = true;
							$err_tab = $tab;
						endif;
						break;
					case "captcha" :
						$verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.APP_RECAPTCHA_SECRET_KEY.'&response='.$_POST[$field]);
						$responseData = json_decode($verifyResponse);
						if (!$responseData->success) :
							$ar_err[$field] = $message;
							$err_flag = true;
							$err_tab = $tab;
						endif;
						break;
				endswitch;
			endif;
		endif;
		return;
	}
	
	function form_validate_all($exclusions = []) {
		global $err_flag;
		global $xform_fid;
		if (is_array($_SESSION['ar_fields_'.$xform_fid]) && count($_SESSION['ar_fields_'.$xform_fid]) > 0) :
			foreach($_SESSION['ar_fields_'.$xform_fid] as $key => $value) :
				if (!in_array($key, $exclusions)) :
					$datatype = (!empty($value['datatype']) ? $value['datatype'] : $value['ftype'] );
					form_validate($key, $datatype, $value['frequired'], $value['tab'], '', $value['filter_options']);
				endif;
			endforeach;
		else :
			$err_flag = true;
		endif;
	}

	function form_generate_set(bool $include_read_only = false) {
		global $db;
		global $xform_fid;
		if (is_array($_SESSION['ar_fields_'.$xform_fid]) && count($_SESSION['ar_fields_'.$xform_fid]) > 0) :
			$ar_set = array();
			foreach($_SESSION['ar_fields_'.$xform_fid] as $key => $value) :
				if (!empty($value['fdbname'])) :
					if (($include_read_only && $value['readonly']) || (!$value['readonly'])) :
						$datatype = (!empty($value['datatype']) ? $value['datatype'] : $value['ftype'] );
						switch ($datatype) :
							case 'text' :
							case 'string' :
							case 'password' :
							case 'autocomplete' :
							case 'radiogroup' :
							case 'radio' :
							case 'checkbox' :
							case 'select' :
							case 'colorpick' :
							case 'textarea' :
							case 'url' :
							case 'email' :
								$ar_set[] = $value['fdbname']." = ".$db->quote($_POST[$key] ?? '');
								break;
							case 'checkgroup' :
								$ar_set[] = $value['fdbname']." = ".$db->quote(((!empty($_POST[$key]) && is_array($_POST[$key])) ? implode(',', $_POST[$key]) : ''));
								break;
							case 'date' :
								$ar_set[] = $value['fdbname']." = ".nzdate($_POST[$key]);
								break;
							case 'datetime' :
								$ar_set[] = $value['fdbname']." = ".nzdatetime($_POST[$key]);
								break;
							case 'int' :
								$ar_set[] = $value['fdbname']." = ".nz($_POST[$key], '0');
								break;
							case 'float' :
								$ar_set[] = $value['fdbname']." = ".nzfloat($_POST[$key], '0.00');
								break;
							case 'image' :
								$ar_set[] = $value['fdbname']." = ".$db->quote($_POST[$key.'_filename']);
								break;
						endswitch;
					endif;
				endif;
			endforeach;
			$set = ' '.implode(', ', $ar_set).' ';
			return $set;
		else :
			echo form_fatal_error('Error - Unable to generate database SQL!');
			die();
		endif;
	}

	function form_button_strip($location, $ar_args = [], $show_create_update = true, $extra_class = '') {
		global $ar_pagecontrol;
		global $xform_uid;
		global $xform_fid;
		$defaults = [
			'save'				=> ['show' => true, 'caption' => 'Save', 'js' => "sheet_dirty = false; this.form.submit();"],
			'save_close'		=> ['show' => true, 'caption' => 'Save & Close', 'js' => "sheet_dirty = false; document.getElementById('xsubmit').value = this.value; this.form.submit();"],
			'save_new'			=> ['show' => true, 'caption' => 'Save & New', 'js' => "sheet_dirty = false; document.getElementById('xsubmit').value = this.value; this.form.submit();"],
			'cancel'			=> ['show' => true, 'caption' => 'Cancel', 'js' => "sheet_dirty = false; appstack_pop();"],
		];
		$ar_args = array_merge($defaults, $ar_args);
		echo '<div class="form_strip ' . $extra_class . '">';
		if (strtolower($location) == 'bottom' && $show_create_update) :
			show_create_update();
		endif;
		foreach ($ar_args as $key => $value) :
			if ($value['show']) :
				if ($key == 'cancel' || empty($ar_pagecontrol[$_GET['IX']]['update_roles']) || role_match($ar_pagecontrol[$_GET['IX']]['update_roles'], get_roles())) :
					echo '<input type="button" value="'.$value['caption'].'" onclick="'.$value['js'].'" />';
				endif;
			endif;
		endforeach;
		if (strtolower($location) == 'bottom') :
			echo '<input type="hidden" name="xsubmit" id="xsubmit" />';
			echo '<input type="hidden" name="xform_uid" id="xform_uid" value="'.$xform_uid.'" />';
			if (!empty($xform_fid)) :
				$_SESSION['ar_fields_'.$xform_fid] = $GLOBALS['ar_fields_'.$xform_fid];
			endif;
		endif;
		echo '</div>';
	}
	
	function form_prep($default_ref = '', $default_tab = '', $frm_id = '', $login_required = true) {
		global $action;
		global $active_tab;
		global $xform_uid;
		global $xform_fid;
		global $form_message;
		$form_message = '';
		if ($login_required) :
			if (isset($_SESSION['user_id'])) :
				if (isset($_REQUEST['id']) && is_numeric($_REQUEST['id'])) :
					$action = "Edit";
				else :
					$action = "Add New";
				endif;
			else :
				exit('Not Authorized!');
			endif;
		endif;
		if (empty($_REQUEST['ref'])) :
			$_REQUEST['ref'] = $default_ref;
		endif;
		if (empty($_REQUEST['tab'])) :
			$active_tab = $default_tab;
		else :
			$active_tab = $_REQUEST['tab'];
		endif;
		$xform_uid = generate_random_string();
		$_SESSION[$xform_uid] = true;
		$xform_fid = $_GET['IX'].'_'.$frm_id;
		$GLOBALS['ar_fields_'.$xform_fid] = array();
	}
	
	function form_prep_submit() {
		if ($_SESSION[$_POST['xform_uid']] !== true) :
			echo form_fatal_error('Invalid Operation - perhaps you tried to reload the page, use the browser "Back" button, or your session has expired.');
			exit();
		endif;
		global $active_tab;
		if (isset($_POST['active_tab'])) :
			$active_tab = $_POST['active_tab'];
		else :
			$active_tab = 'basic_info';
		endif;
		$GLOBALS['ar_err'] = array();
	}
	
	function form_uid_reset()
	{
		$_SESSION[$_POST['xform_uid']] = false;
	}
	
	function form_reload_check()
	{
		if ($_SESSION[$_POST['xform_uid']] !== true) :
			echo form_fatal_error('Invalid Operation - you tried to reload the page!');
			exit();
		endif;
	}
	
	function generate_random_string($length = 16) 
	{
		$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$random_string = '';
		for ($i = 0; $i < $length; $i++) :
			$random_string .= $characters[rand(0, strlen($characters) - 1)];
		endfor;
		return $random_string;
	}


	function auto_complete($fname, $value, $text, $fsql, $limit_to_list = null, $w = null, $h = null, $tab_index = null, $change_js = null, $class = null)
	{
		// For DB query, the first field of the SQL statement must be the actual value, second is the display value
		// For list of items, they must be in pairs separated by semicolon and separated by comma within the pair, no spaces. Ex: r,red;b,blue;w,white
		global $db;
		if (is_null($limit_to_list)) :
			$limit_to_list = false;
		endif;
		if (is_null($w)) :
			$w = 300;
		endif;
		if (is_null($h)) :
			$h = 16;
		endif;
		if (is_null($tab_index)) :
			$tab_index = 0;
		endif;
		if (is_null($change_js)) :
			$change_js = '';
		endif;
		if (is_null($class)) :
			$class = '';
		endif;
		//echo 'AC parms: Width: '.$w.' Height: '.$h;
		$hdiv = $h + 4;
		$wdiv = $w + 4;
		if ($limit_to_list == true) :
			$limit_list = 'true';
		else :
			$limit_list = 'false';
		endif;
		$tab = '';
		if ($tab_index > 0) :
			$tab = 'tabindex="'.$tab_index.'"';
		endif;
		echo '<div class="autocomplete" id="ac_wrap_'.$fname.'" style="width:'.$wdiv.'px; height:'.$hdiv.'px;">';
		echo '<input type="hidden" id="'.$fname.'" name="'.$fname.'" value="'.$value.'" />';
		//echo '<input type="text" '.$tab.' class="autocomplete" style="width:'.$w.'px; height:'.$h.'px;" id="'.$fname.'_mask" name="'.$fname.'_mask" value="'.$text.'" onfocus="autocomplete_open(\''.$fname.'\')" onblur="autocomplete_close(\''.$fname.'\','.$limit_list.');'.$change_js.'" onkeyup="autocomplete_filter(\''.$fname.'\')"  />';
		//echo '<input type="text" '.$tab.' class="autocomplete" style="width:'.$w.'px; height:'.$h.'px;" id="'.$fname.'_mask" name="'.$fname.'_mask" value="'.$text.'" onfocus="autocomplete_open(\''.$fname.'\', '.$limit_list.')" onblur="'.$change_js.'" onkeyup="autocomplete_filter(\''.$fname.'\'); ac_filter = 1;" onmouseup="autocomplete_toggle(\''.$fname.'\', '.$limit_list.');"  />';
		echo '<input type="text" '.$tab.' class="autocomplete '.$class.'" style="width:'.$w.'px; height:'.$h.'px;" id="'.$fname.'_mask" name="'.$fname.'_mask" value="'.$text.'" onfocus="autocomplete_open(\''.$fname.'\', '.$limit_list.')" onblur="'.$change_js.'" onkeyup="autocomplete_filter(event, \''.$fname.'\'); ac_filter = 1;"  />';
		echo '<a href="javascript:autocomplete_toggle(\''.$fname.'\', '.$limit_list.');" ><img class="ac_dropdown" src="img/arrow_down.png" /></a>';
		echo '<div class="autocomplete_items" style="width:'.$w.'px;" id="'.$fname.'_items" >';
		if (substr(trim(strtoupper($fsql)),0,6) == 'SELECT') :
			$result = $db->query($fsql);
			echo '<ul class="autocomplete_list" id="'.$fname.'_items_ul" style="list-style-type: none; padding:0; margin:0;">';
			while ($row = $result->fetch(PDO::FETCH_NUM)):
				echo '<li ac_value="'.$row[0].'" onclick="autocomplete_pick(\''.$fname.'\', \''.$row[0].'\', \''.$row[1].'\', '.$limit_list.');" >'.$row[1].'</li>';
			endwhile;
			echo '</ul>';
		else :
		endif;
		echo '</div>';
		echo '</div>';
	}
	
	function form_tab_header($id, $title, $roles = '') {
		if (role_match(get_roles(), $roles) || empty($roles)) :
			echo '<div class="tab_header" id="tab_'.$id.'" onclick="switch_tab(\''.$id.'\');">';
			echo $title;
			echo '</div>';
		endif;
	}
	
	function form_tab_start($tab, $title, $display = true) {
		$GLOBALS['tab_display_control_'.$tab] = $display;
		if ($display === true) :
			$GLOBALS['curr_tab'] = $tab;
			echo '<div class="tab_content" id="tab_content_'.$tab.'">';
			echo '<div class="form_header">';
			echo $title;
			echo '</div>';
		endif;
	}
	
	function form_tab_end()	{
		$display = $GLOBALS['tab_display_control_'.$GLOBALS['curr_tab']];
		if ($display === true) :
			echo '</div>';
		endif;
	}

	function form_column_start($style = '') {
		echo '<div class="input_column" style="' . $style . '">';
	}

	function form_column_end() {
		echo '</div>';
	}

	function form_tab_strip_start($style = '') {
		echo '<div class="tab_strip" style="' . $style . '">';
	}

	function form_tab_strip_end() {
		echo '
			</div>
			<div class="cleardiv">
				&nbsp;
			</div>
		';
	}
	
	function form_message($form_message = '', $extra_class = '') {
		if (!empty($form_message)) :
			echo '<div class="form_message ' . $extra_class . '">'.$form_message.'</div>';
		endif;
	}
	
	function form_fatal_error($form_message = '') {
		if (!empty($form_message)) :
			echo '<div class="form_message" style="color:#ff0000; font-weight:bold;">';
			echo $form_message.'<br /><br />';
			//echo '<input type="button" value="Back" onclick="window.history.back()" />';
			echo '<input type="button" value="Back" onclick="appstack_pop();" />';
			echo '</div>';
		endif;
	}

	function form_tab_revisions(string $table_name, int|null|string $id_value, string $id_field = 'id'): void {
		if (empty($id_value)) :
			return;
		endif;
		global $db;
		form_tab_start('revisions', 'Revision History');
		db_navigator_bar(['table_id' => 'db_grid_revisions', 'form' => '', 'btn_addnew'=>'false', 'print_section' => 'db_grid_revisions', 'call' => 'revisions_ajax', 'sort_col' => '1', 'sort_order' => '1', 'subtab' => 'true', 'parm1' => " AND r.table_name = " . $db->quote($table_name)  . " AND r.id_value = " . nz($id_value, '0') . " ", 'func' => 'grid']);
		form_tab_end();
		?>
		<div id="grid_popup" class="grid_popup" style="padding:18px!important;">
			<div style="position:absolute; right:0; top:0; padding:8px;">
				<a href="javascript:hide_revision_details();">X</a>
			</div>
			<b><br>Revision Details:</b>&nbsp;&nbsp;&nbsp;<br />
			<div id="grid_popup_contents">
			</div>
		</div>
		<?php
	}
	
	function last_id() {
		global $db;
		$last_id = $db->lastInsertId();
		//$_SESSION['appstack'][count($_SESSION['appstack']) - 1]['id'] = $last_id;
		if (PHP_SAPI !== 'cli') :
			echo '<script>';
			echo 'appstack_switch_param("id", "'.$last_id.'");';
			echo '</script>';
		endif;
		return $last_id;
	}

	function next_id($table_name) {
		global $db;
		$result = $db->query("SHOW TABLE STATUS LIKE '".$table_name."'");
		$row = $row = $result->fetch(PDO::FETCH_ASSOC);
		$next_id = $row['Auto_increment'];
		return $next_id;
	}
	
	function db_quote($string)
	{
		global $db;
		$result = $db->quote($string);
		$result = substr($result, 1, strlen($result) - 2);
		return $result;
	}
	
	function get_row_to_post($sql, $assign_unique_col_name = false) {
		global $db;
		global $result;
		global $row;
		$result = $db->query($sql) or die('Database Error!');
		if ($result->rowCount() > 0) :
			$row = $result->fetch(PDO::FETCH_NAMED);
			foreach ($row as $key => $value) :
				if ($assign_unique_col_name && is_array($value)) :
					foreach ($value as $sub_key => $sub_value) :
						$_POST[$key . '_' . $sub_key] = $sub_value;
					endforeach;
				else :
					$_POST[$key] = $value;
				endif;
			endforeach;
			post_set_load_create_update($assign_unique_col_name);
			return true;
		else :
			return false;
		endif;
	}
	
	function var_dump_pre($obj)	{
		echo '<pre>';
		var_dump($obj);
		echo '</pre>';
	}

	function erase_files_in_directory(string $path_to_dir): void {
		if (!str_ends_with($path_to_dir, '/')) :
			$path_to_dir .= '/';
		endif;
		$files = glob($path_to_dir . '*');
		foreach ($files as $file) :
			if (is_file($file)) :
				unlink($file); // delete file
			endif;
		endforeach;
	}

	function reorient_image($image_path) {
		$exif = @exif_read_data($image_path);
		$orientation = $exif['Orientation'] ?? 1;

		$image = imagecreatefromjpeg($image_path);
		if ($image === false) :
			return false;
		endif;

		switch ($orientation) :
			case 3:
				$image = imagerotate($image, 180, 0);
				break;
			case 6:
				$image = imagerotate($image, -90, 0);
				break;
			case 8:
				$image = imagerotate($image, 90, 0);
				break;
		endswitch;

		imagejpeg($image, $image_path, 90);
		imagedestroy($image);
		return true;
	}

	function image_max_size($image_path, $max_dim) {
		$image_info = getimagesize($image_path);
		if ($image_info === false) :
			return false;
		endif;

		$width = $image_info[0];
		$height = $image_info[1];
		$mime_type = $image_info['mime'];

		if ($width <= $max_dim && $height <= $max_dim) :
			return true;
		endif;

		$scale = min($max_dim / $width, $max_dim / $height);
		$new_width = (int) ($width * $scale);
		$new_height = (int) ($height * $scale);

		switch ($mime_type) :
			case 'image/jpeg' :
				$src_image = imagecreatefromjpeg($image_path);
				break;
			case 'image/png' :
				$src_image = imagecreatefrompng($image_path);
				break;
			case 'image/webp' :
				$src_image = imagecreatefromwebp($image_path);
				break;
			case 'image/gif' :
				$src_image = imagecreatefromgif($image_path);
				break;
			case 'image/bmp' :
				$src_image = imagecreatefrombmp($image_path);
				break;
			default :
				return false;
		endswitch;

		if ($src_image === false) :
			return false;
		endif;

		$dst_image = imagecreatetruecolor($new_width, $new_height);

		if ($mime_type === 'image/png' || $mime_type === 'image/gif') :
			imagealphablending($dst_image, false);
			imagesavealpha($dst_image, true);
		endif;

		imagecopyresampled($dst_image, $src_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);

		switch ($mime_type) :
			case 'image/jpeg' :
				imagejpeg($dst_image, $image_path, 90);
				break;
			case 'image/png' :
				imagepng($dst_image, $image_path);
				break;
			case 'image/webp' :
				imagewebp($dst_image, $image_path, 90);
				break;
			case 'image/gif' :
				imagegif($dst_image, $image_path);
				break;
			case 'image/bmp' :
				imagebmp($dst_image, $image_path);
				break;
		endswitch;

		imagedestroy($src_image);
		imagedestroy($dst_image);

		return true;
	}


	function image_url_to_file($image_url, $media_path, $save_file_name = null, $check_for_duplicates = true, $thumbnail_cache = true, $max_size = 8388608): array|bool {

		// Initialize cURL
		$curl_handle = curl_init($image_url);
		curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
		//curl_setopt($curl_handle, CURLOPT_VERBOSE, true);
		curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5);
		curl_setopt($curl_handle, CURLOPT_TIMEOUT, 10);
		$image_data = curl_exec($curl_handle);
		$curl_err = curl_errno($curl_handle);
		if ($curl_err > 0) :
			$curl_info = curl_getinfo($curl_handle);
			error_log('Curl Error' . PHP_EOL . print_r($curl_info, true));
		endif;
		$final_url = clean_string(curl_getinfo($curl_handle, CURLINFO_EFFECTIVE_URL));
		curl_close($curl_handle);

		if ($image_data === false) :
			error_log("Failed to download image $image_url.");
			return false;
		endif;

		if (strlen($image_data) > $max_size) :
			error_log("Image too large $image_url.");
			return false;
		endif;

		$hash = hash("sha512", $image_data . $media_path);
		if ($check_for_duplicates) :
			$file_name = lookup_db_field('media', 'hash', $hash, 'media_filename');
			if (!empty($file_name)) :
				return [
					'filename' => $file_name,
					'hash' => $hash,
					'duplicate' => true,
				];
			endif;
		endif;

		if (empty($save_file_name)) :
			$save_file_name = basename($final_url);
			$save_file_name = strtolower($save_file_name);
			$save_file_name = str_replace(' ','_',$save_file_name);
			$save_file_name = preg_replace('/[^0-9a-z\.\_\-]/i','',$save_file_name);
			$save_file_name = random_append_filename($save_file_name);
		endif;
		$save_path = APP_ROOT_MEDIA_DIR . '/' . $media_path . $save_file_name;

		// Save to temporary file
		$temp_path = tempnam(sys_get_temp_dir(), 'img');
		file_put_contents($temp_path, $image_data);

		// Validate using getimagesize
		$image_info = getimagesize($temp_path);
		if ($image_info === false) :
			unlink($temp_path);
			error_log("File is not a valid image $image_url.");
			return false;
		endif;

		// Optional: Validate MIME type using finfo
		$finfo_handle = finfo_open(FILEINFO_MIME_TYPE);
		$mime_type = finfo_file($finfo_handle, $temp_path);
		finfo_close($finfo_handle);

		$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'];
		if (!in_array($mime_type, $allowed_types)) :
			unlink($temp_path);
			error_log("Invalid image format: $mime_type for image $image_url");
			return false;
		endif;

		// Save validated image
		file_put_contents($save_path, $image_data);
		image_max_size($save_path, 1200);
		if ($mime_type === 'image/jpeg') :
			reorient_image($save_path);
		endif;
		unlink($temp_path);
		if ($thumbnail_cache) :
			image_cache($media_path . $save_file_name, 85, 200, 200);
		endif;
		return [
			'filename' => $save_file_name,
			'hash' => $hash,
			'duplicate' => false,
		];
	}

	function random_append_filename(string $filename): string {
		$pathinfo = pathinfo($filename);
		$unique = str_replace('.', '', uniqid('', true));
		if (isset($pathinfo['extension']) && $pathinfo['extension'] !== '') :
			$filename = $pathinfo['filename'] . '_' . $unique . '.' . $pathinfo['extension'];
		else :
			$filename =  $pathinfo['filename'] . '_' . $unique;
		endif;
		return $filename;
	}

	function generate_slug($text) {
		$text = preg_replace('~[^\pL\d]+~u', '-', $text);		// replace non letter or digits by -
		$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);	// transliterate
		$text = preg_replace('~[^-\w]+~', '', $text);			// remove unwanted characters
		$text = trim($text, '-');								// trim
		$text = preg_replace('~-+~', '-', $text);				// remove duplicate -
		$text = strtolower($text);								// lowercase
		if (empty($text)) :
			return 'n-a';
		endif;
		return $text;
	}	
	
	function generate_unique_slug($text, $table_name, $slug_field) {
		
		global $db;
		$slug = generate_slug($text);
		$original_slug = $slug;
		$sql = "SELECT COUNT(*) FROM " . sql_safe_object($table_name) . " WHERE " . sql_safe_object($slug_field) . " = :slug_value";
		$stmt = $db->prepare($sql);
		$found = true;
		$i = 1;
		while ($found) :
			$stmt->execute([
				':slug_value' => $slug,
			]);
			$num_rows = $stmt->fetchColumn();
			if ($num_rows == 0) :
				$found = false;
				return $slug;
			endif;
			$i++;
			$slug = $original_slug . '-' . $i;
		endwhile;
		return $slug;
	}	

	function clean_string($string) {
		if (empty($string)) :
			return $string;
		endif;
		$string = normalize_quotes(strip_tags(html_entity_decode($string, ENT_QUOTES)));
		return $string;
	}

	function normalize_quotes($text) {
		$search = ['“', '”', '‘', '’', '‚', '‛'];
		$replace = ['"', '"', "'", "'", "'", "'"];
		return str_replace($search, $replace, $text);
	}

	function html_image_parse_and_store($html_source, $category, $description): string {
		global $db;
		if (!empty($html_source)) :
			$html = str_get_html($html_source);
			if ($html) :
				$images = $html->find('img');
				foreach ($images as $img) :
					// Replace src with a new value
					$save_image_file = [];
					$image_url = $img->src;
					if (!empty($image_url)) :
						$image_url = clean_string($image_url);
						$save_image_file = image_url_to_file($image_url, "img/$category/");
						if (!empty($save_image_file['filename']) && !$save_image_file['duplicate']) :
							$image_desc = $img->alt;
							$sql_img = "
								INSERT INTO media SET
									media_cat = :media_cat,
									media_type = :media_type,
									media_path = :media_path,
									media_filename = :media_filename,
									media_desc = :media_desc,
									hash = :hash
							";
							$stmt_img = $db->prepare($sql_img);
							$data_img = [
								':media_cat' => $category,
								':media_type' => 'img',
								':media_path' => "img/$category/",
								':media_filename' => $save_image_file['filename'],
								':media_desc' => !empty($image_desc) ? $image_desc : clean_string($description),
								':hash' => $save_image_file['hash'],
							];
							$stmt_img->execute($data_img);
						endif;
						if (!empty($save_image_file['filename'])) :
							$img->src = APP_BASE_USER_SITE_SECURE . "img/$category/" . $save_image_file['filename'];
						endif;
					endif;
				endforeach;
				$html_source = $html->__toString();
			endif;
		endif;
		return $html_source;
	}

	function empty_extended($value) {
		if (empty($value)) :
			return true;
		endif;
		// Extra rule: treat strings like "0.00000" as empty
		if (is_string($value) && preg_match('/^0+(\.0+)?$/', $value)) :
			return true;
		endif;
		return false;
	}

	function nz($arg, $null_value = '')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			$arg = trim($arg);
			$arg = str_replace(',', '', $arg);
			return intval($arg);
		endif;
	}

	function nzfloat($arg, $null_value = '')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			$arg = trim($arg);
			$arg = str_replace(',', '', $arg);
			return floatval($arg);
		endif;
	}

	function nzdate($arg, $null_value = 'NULL', $quotes = true)
	{
		global $db;
		if (empty($arg)) :
			return $null_value;
		else :
			if ($quotes):
				return $db->quote(date('Y-m-d', strtotime(str_replace('-', '/',trim($arg)))));
			else :
				return date('Y-m-d', strtotime(str_replace('-', '/',trim($arg))));
			endif;
		endif;
	}

	function nzdate_display($arg, $null_value = '')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			return date("m/d/Y", strtotime($arg));
		endif;
	}

	function nzdatetime($arg, $null_value = 'NULL', $quotes = true)
	{
		global $db;
		if (empty($arg)) :
			return $null_value;
		else :
			if ($quotes):
				return $db->quote(date('Y-m-d H:i:s', strtotime(str_replace('-', '/',trim($arg)))));
			else :
				return date('Y-m-d H:i:s', strtotime(str_replace('-', '/',trim($arg))));
			endif;
		endif;
	}

	function nzdate_display_datetime($arg, $show_seconds = false, $null_value = '')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			if ($show_seconds) :
				$format = "m/d/Y h:i:s a";
			else :
				$format = "m/d/Y h:i a";
			endif;
			return date($format, strtotime($arg));
		endif;
	}

	function nztime($arg, $null_value = 'NULL')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			return $db->quote(date('h:i A', strtotime(str_replace('-', '/',trim($arg)))));
		endif;
	}

	function nztime_display($arg, $null_value = '')
	{
		if (empty($arg)) :
			return $null_value;
		else :
			return date("g:i A", strtotime($arg));
		endif;
	}

	function nz_string($arg, $null_value = '')
	{
		if (!isset($arg) || trim($arg) === '') :
			return $null_value;
		else :
			return $arg;
		endif;
	}
	
	function db_err_rollback($e, $die = true) {
		global $db;
		global $sql;
		$db->rollBack();
		error_log('Database error - Index: '.$_GET['IX'].' Error: '.$e->getMessage().PHP_EOL.$e->getTraceAsString() . PHP_EOL . $sql);
		if ($die) :
			die('Database error! Please contact the web site administrator.');
		endif;
	}

	function save_db_revision(string $table_name, string|null $update_sql = null, int|null $id_value = null, string $id_field = 'id'): void {
		global $db;
		if (is_null($id_value)) :
			$id_value = $_REQUEST['id'];
		endif;
		$ar_key_exclusions = [
			'create_date',
			'create_by',
			'create_by_display',
			'update_date',
			'update_by',
			'update_by_display',
		];
		$sql = "SELECT * FROM " . sql_safe_object($table_name) . " WHERE " . sql_safe_object($id_field) . " = :id_value";
		$stmt = $db->prepare($sql);
		$stmt->execute([
			':id_value' => $id_value,
		]);
		$row_old = $stmt->fetch(PDO::FETCH_ASSOC);
		$row_new = [];
		if (!empty($update_sql)) :
			$db->query($update_sql);
			$stmt->execute([
				':id_value' => $id_value,
			]);
			$row_new = $stmt->fetch(PDO::FETCH_ASSOC);
		endif;
		if ($row_old) :
			if ($row_new) :
				$update_flag = false;
				foreach ($row_new as $new_key => $new_valuem) :
					if (array_key_exists($new_key, $row_old)
						&& !in_array($new_key, $ar_key_exclusions)
						&& ((string)(empty_extended($row_old[$new_key]) ? null : $row_old[$new_key])) !== (string)(empty_extended($row_new[$new_key]) ? null : $row_new[$new_key])
					) :
						$update_flag = true;
						break;
					endif;
				endforeach;
			else :
				$update_flag = true;
			endif;
			if ($update_flag) :
				$revision_json = json_encode($row_old);
				$sql = "INSERT INTO revisions SET
					table_name = :table_name,
					user_id = :user_id,
					id_field = :id_field,
					id_value = :id_value,
					revision_json = :revision_json

				";
				$stmt = $db->prepare($sql);
				$stmt->execute([
					':table_name' => $table_name,
					':user_id' => nz(trim($_SESSION['user_id']), NULL),
					':id_field' => $id_field,
					':id_value' => $id_value,
					':revision_json' => $revision_json,
				]);
			endif;
		endif;
	}

	function form_success($extra_parms = null) {
		global $form_message;
		$form_message = "Saved sucessfully!";
		form_uid_reset();
		if ($_POST['xsubmit'] == 'Save & Close') :
			echo "<SCRIPT>";
			echo "appstack_pop()";
			echo "</SCRIPT>";
		endif;
		$url_parms = '';
		if (is_array($extra_parms)) :
			foreach($extra_parms as $key => $value) :
				$url_parms .= '&'.$key.'='.urlencode($value);
			endforeach;
		endif;
		if ($_POST['xsubmit'] == 'Save & New') :
			echo "<SCRIPT>";
			echo "window.location.href = 'index.php?IX=".$_GET['IX'].$url_parms."&ref=".urlencode($_REQUEST['ref'])."'";
			echo "</SCRIPT>";
		endif;
	}

	function show_form_error($err_text, $break = true) {
		if (!empty($err_text)) :
			if ($break) :
				echo '<br />';
			endif;
			echo '<span class="form_error">'.$err_text.'</span>';
		endif;
	}

	function format_http($link)
	{
		if (!empty($link)) :
			//echo 'Point 1';
			if (strtolower(substr($link, 0, 7)) != 'http://' && strtolower(substr($link, 0, 8)) != 'https://') :
				$link = 'http://'.$link;
			endif;
		endif;
		return $link;
	}

	function IsDate($date) {
		return (strtotime($date) !== false);
	}

	function excerpt($text, $length = 55, $more = '...') {
		// Strip HTML tags
		$text = strip_tags($text);
		$text = trim($text);
		// Split into words
		$words = preg_split('/\s+/', $text);

		if (count($words) > $length):
			$excerpt = implode(' ', array_slice($words, 0, $length)) . $more;
		else:
			$excerpt = $text;
		endif;

		return $excerpt;
	}

	function nl_remove($string)
	{
		$search = array("\r\n", "\n");
		$new_string = str_replace($search, ' ', $string);
		$new_string = str_replace('  ', ' ', $new_string);
		return $new_string;
	}

	function write_log($log_type, $user_id, $log_text)
	{
		global $db;
		$sql = "INSERT INTO log set ".
			"user_id = ".nz(trim($user_id), '0').", ".
			"log_type = ".$db->quote(trim($log_type)).", ".
			"log_text = ".$db->quote(trim($log_text)).", ".
			"ip = '".get_client_ip()."' ";
		$db->query($sql) or die('Database error - Log failure! Please contact the web site administrator.');
	}
	
	function get_page_title($ix)
	{
		global $ar_pagecontrol;
		$title = $ar_pagecontrol[$ix]['page_title'];
		return $title;
	}
	
	function get_roles()
	{
		$return_result = '';
		if (($_SESSION['logged_in'] ?? NULL) == 1) :
			$db = $GLOBALS['db'];
			$sql = "SELECT roles FROM sys_users WHERE user_id = ".nz($_SESSION['user_id'], '0')." AND active = 1 ";
			$result = $db->query($sql) or die('Database Error!');
			if ($result->rowCount() > 0) :
				$row = $result->fetch(PDO::FETCH_OBJ);
				$return_result = $row->roles;
			endif;
		endif;
		return $return_result;
	}
	
	function has_role($role)
	{
		$return_result = false;
		if (($_SESSION['logged_in'] ?? NULL) == 1) :
			$ar1 = explode(',',get_roles());
			foreach ($ar1 as $ar1_value) :
				if (strtoupper($role) == strtoupper($ar1_value)) :
					$return_result = true;
				endif;
			endforeach;
		endif;
		return $return_result;
	}
		
	function page_allowed($ix, $type='page') {
		global $ar_pagecontrol;
		/*
		if (str_ends_with('_ajax', $ix) && $type !== 'ajax') :
			return false;
		endif;
		*/
		$return_result = false;
		if (!empty($ar_pagecontrol[$ix])) :
			$bln_show_menu = false;
			if (empty($ar_pagecontrol[$ix]['type'])) :
				$ar_pagecontrol[$ix]['type'] = 'page';
			endif;
			if ($type !== $ar_pagecontrol[$ix]['type']) :
				return false;
			endif;
			if (($_SESSION['logged_in'] ?? NULL) == true && $ar_pagecontrol[$ix]['show_state'] == 1) :
				$bln_show_menu = true;
			endif;
			if (($_SESSION['logged_in'] ?? NULL) != true && $ar_pagecontrol[$ix]['show_state'] == 2) :
				$bln_show_menu = true;
			endif;
			if ($ar_pagecontrol[$ix]['show_state'] == 0) :
				$bln_show_menu = true;
			endif;
			if ($bln_show_menu) :
				if (empty($ar_pagecontrol[$ix]['view_roles']) || role_match($ar_pagecontrol[$ix]['view_roles'], get_roles())) :
					$return_result = true;
				endif;
			endif;			
		endif;
		return $return_result;
	}
		
	function page_secure($ix)
	{
		global $ar_pagecontrol;
		$return_result = false;
		if (!empty($ar_pagecontrol[$ix]))
		{
			if ($ar_pagecontrol[$ix]['secure'] == 1)
			{
				$return_result = true;
			}
		}
		return $return_result;
	}
		
	function curr_page_url() 
	{
		if (!isset($_SERVER['REQUEST_URI'])) {
			$_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'],0 );
			if (isset($_SERVER['QUERY_STRING'])) { 
				$_SERVER['REQUEST_URI'].='?'.$_SERVER['QUERY_STRING']; 
			}
		}		
		$page_url = 'http';
		if (nz_string(($_SERVER["HTTPS"] ?? NULL), 'off') == "on" || nz_string(($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? NULL), 'http') == 'https') {
			$page_url .= "s";
		}
		$page_url .= "://";
		/*
		if ($_SERVER["SERVER_PORT"] != "80") 
		{
			$page_url .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
		} 
		else 
		{
			$page_url .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
		}
		*/
		$page_url .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
		return $page_url;
	}	 
	
	function curr_page_file() 
	{
		$page_url = '';
		$currentFile = $_SERVER["PHP_SELF"];
		$parts = Explode('/', $currentFile);
		$page_url = $parts[count($parts) - 1];
		return $page_url;
	}	 

	function url_fix ($url) {
		if (substr(strtolower($url),0,7) != 'http://' && substr(strtolower($url),0,8) != 'https://') :
			$url = 'http://'.$url;
		else :
			$url = $url;
		endif;
		return $url;
	}

	function image_url_fix($image_link, $media_path = '', $return_full_path = false): string {
		if (substr(strtolower($image_link),0,7) == 'http://' || substr(strtolower($image_link),0,8) == 'https://') :
			return $image_link;
		endif;
		$path = $media_path . $image_link;
		if ($return_full_path) :
			$path = APP_BASE_USER_SITE_SECURE . $path;
		endif;
		return $path;
	}

	function html_to_ical_description(string $html): string {
		// Convert <br> and <p> to newlines
		$html = preg_replace('/<\s*br\s*\/?>/i', "\n", $html);
		//$html = preg_replace('/<\s*p\s*>/i', "\n", $html);

		// 3. Convert <li> to bullet lines
		$html = preg_replace('/<\s*li[^>]*>/i', "\n• ", $html);
		$html = preg_replace('/<\/\s*li\s*>/i', '', $html);
		$block_tags = [
			'p','div','section','article','header','footer','aside',
			'nav','main','figure','figcaption','h1','h2','h3','h4','h5','h6',
			'ul','ol','table','tr','td','th'
		];
		foreach ($block_tags as $tag) :
			$html = preg_replace('/<\s*' . $tag . '[^>]*>/i', "\n", $html);
			$html = preg_replace('/<\/\s*' . $tag . '\s*>/i', "\n", $html);
		endforeach;

		// Strip remaining tags
		$text = strip_tags($html);

		// Decode HTML entities
		$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');

		// Normalize line breaks
		//$text = preg_replace("/\r\n|\r|\n/", "\\n", $text);

		// Escape iCal special chars: \ , ; :
		$text = str_replace(
			['\\', ',', ';'],
			['\\\\', '\,', '\;'],
			$text
		);

    	// Convert real newlines to literal \n
		$text = str_replace("\n", "\\n", $text);

		return trim($text);
	}

	function escape_ical_text(string $text): string {
		return str_replace(['\\', ',', ';'], ['\\\\', '\,', '\;'], $text);
	}

	function encode_email_link_text($html): string {
		$text = '';
		// Step 1: Replace <p> and <br> with line breaks
		$html = preg_replace('/<p[^>]*>/i', "\n", $html);       // opening <p>
		$html = preg_replace('/<\/p>/i', "\n", $html);          // closing </p>
		$html = preg_replace('/<br\s*\/?>/i', "\n", $html);     // <br> or <br />

		// Step 2: Strip all other tags
		$text = strip_tags($html);

		// Step 3: Normalize multiple line breaks
		$text = preg_replace("/\n{2,}/", "\n\n", $text);
		return $text;
	}

	function add_seo_page_header($title = APP_SITE_TITLE, $url = '', $description = APP_SITE_DESCRIPTION, $image = '', $type = 'article') {
		if (empty($url)) :
			$url = curr_page_url();
		endif;
		if (empty($image)) :
			$image = image_url_fix('thoughtgallery_logo_new.svg', 'img/', true);
		endif;
		$og_seo = '';
		$og_header = '';
		$og_twitter = '';
		$og_seo .= ' <meta name="google-site-verification" content="' . htmlspecialchars(APP_GOOGLE_SITE_VERIFICATION) . '">' . "\n";
		$og_header .= ' <meta property="og:locale" content="en_US">' . "\n";
		$og_header .= ' <meta property="og:site_name" content="' . htmlspecialchars(APP_SITE_TITLE, ENT_QUOTES, 'UTF-8') . '">' . "\n";
		$og_twitter .= ' <meta name="twitter:card" content="summary_large_image">' . "\n";
		if (!empty($title)) :
			$og_seo .= ' <title>' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</title>' . "\n";
			$og_header .= ' <meta property="og:title" content="' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '">' . "\n";
			$og_twitter .= ' <meta name="twitter:title" content="' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '">' . "\n";
		endif;
		if (!empty($url)) :
			$og_seo .= ' <link rel="canonical" href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">' . "\n";
			$og_header .= ' <meta property="og:url" content="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">' . "\n";
		endif;
		if (!empty($description)) :
			$og_seo .= ' <meta name="description" content="' . htmlspecialchars(excerpt($description, 50), ENT_QUOTES, 'UTF-8') . '">' . "\n";
			$og_header .= ' <meta property="og:description" content="' . htmlspecialchars(excerpt($description, 50), ENT_QUOTES, 'UTF-8') . '">' . "\n";
			$og_twitter .= ' <meta name="twitter:description" content="' . htmlspecialchars(excerpt($description, 50), ENT_QUOTES, 'UTF-8') . '">' . "\n";
		endif;
		if (!empty($image)) :
			$og_header .= ' <meta property="og:image" content="' . htmlspecialchars($image, ENT_QUOTES, 'UTF-8') . '">' . "\n";
			$og_twitter .= ' <meta name="twitter:image" content="' . htmlspecialchars($image, ENT_QUOTES, 'UTF-8') . '">' . "\n";
		endif;
		if (!empty($type)) :
			$og_header .= ' <meta property="og:type" content="' . htmlspecialchars($type, ENT_QUOTES, 'UTF-8') . '">' . "\n";
		endif;
		$html = ob_get_contents();
		ob_end_clean();
		$html = str_replace('</head>', $og_seo . "\n</head>", $html);
		$html = str_replace('</head>', $og_header . "\n</head>", $html);
		$html = str_replace('</head>', $og_twitter . "\n</head>", $html);
		ob_start();
		echo $html;
	}

	function add_custom_css($css) {
		if (!empty($css)) :
			$html = ob_get_contents();
			ob_end_clean();
			$html = str_replace('</head>', "<style>\n" . $css . "\n</style>\n</head>", $html);
			ob_start();
			echo $html;
		endif;
	}

	function validate_css($css, $strict = false): bool {
		$url = "https://jigsaw.w3.org/css-validator/validator?output=json&text=" . urlencode($css);
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
		curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0");
		$response = curl_exec($ch);
		//error_log('CURL response: ' . $response);
		//error_log('CURL curl_getinfo(): ' . print_r(curl_getinfo($ch), true));
		if ($response === false) :
			$error = curl_errno($ch);
		else :
			$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		endif;
		if ($http_code !== 200) :
			error_log('validate_css() CURL non-200 return curl_getinfo(): ' . print_r(curl_getinfo($ch), true));
			return !$strict;
		endif;
		curl_close($ch);
		$result = json_decode($response, true);
		if (is_array($result) && $result['cssvalidation']['validity'] === true) :
			return true;
		endif;
		return false;
	}

	function normalize_html($html) {
		// Remove enclosing shortcodes: [shortcode ...]content[/shortcode] → keep only "content"
		$html = preg_replace(
			'/\[(\w[\w-]*)(?:\s+[^\]]*)?\](.*?)\[\/\1\]/is',
			'$2',
			$html
		);

		// Remove self-closing shortcodes: [shortcode ...]
		$html = preg_replace(
			'/\[[a-zA-Z_][\w-]*(?:\s+[^\]]*)?\]/',
			'',
			$html
		);

		// Step 1: Convert <br> and <br /> into newlines
		$html = preg_replace('/<br\s*\/?>/i', "\n", $html);

		// Step 2: Convert <p> into newlines
		$html = preg_replace('/<p[^>]*>/i', "\n", $html);
		$html = preg_replace('/<\/p>/i', "\n", $html);

		// Collapse multiple newlines with optional whitespace and &nbsp; in between
		$html = preg_replace('/(\r?\n(?:\s|&nbsp;)*){2,}/i', "\n", $html);

		// Step 4: Wrap each block in <p>
		$paragraphs = array_filter(array_map('trim', explode("\n", $html)));
		$clean = '';
		foreach ($paragraphs as $p) {
			$clean .= "<p>$p</p>\n";
		}

		return $clean;
	}

	function apply_shortcodes($html) {
		require_once('_shortcodes.php');
		foreach (APP_SHORTCODES as $shortcode => $value) :
			if (function_exists('shortcode_' . $shortcode)) :
				$callable = 'shortcode_' . $shortcode;
				$html = str_replace('[' . $shortcode . ']', $callable(), $html);
			endif;
		endforeach;
		return $html;
	}

	function format_html($html_source) {
		libxml_use_internal_errors(true);
		$html_utf = str_replace("\0", '', $html_source);
		$html_utf = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $html_utf);
		$html_utf = iconv('UTF-8', 'UTF-8//IGNORE', $html_utf);
		$html_utf = mb_convert_encoding($html_utf, 'HTML-ENTITIES', 'UTF-8');
		$dom = new DOMDocument('1.0', 'UTF-8');
		if (!empty(trim($html_source)) && $dom->loadHTML('<html>' . $html_utf . '</html>', LIBXML_HTML_NODEFDTD)) :
			// make all links open in new tab unless already specified in the target
			foreach ($dom->getElementsByTagName('a') as $a) :
				if (!$a->hasAttribute('target')) :
					$a->setAttribute('target', '_blank');
				endif;
			endforeach;
			// remove most custom style formatting to adhere to site styles
			$xpath = new DOMXPath($dom);
			$nodes = $xpath->query('//*[@style]');

			$allowed = ['font-weight', 'font-style'];

			foreach ($nodes as $node) :
				if (!($node instanceof DOMElement)) :
					continue;
				endif;
				$style = $node->getAttribute('style');

				// Split into individual declarations
				$parts = explode(';', $style);
				$keep = [];

				foreach ($parts as $part) :
					$part = trim($part);
					if ($part === '') continue;

					// Split property:value
					[$prop, $value] = array_map('trim', explode(':', $part, 2));

					// Keep only allowed properties
					if (in_array(strtolower($prop), $allowed, true)) :
						$keep[] = "$prop: $value";
					endif;
				endforeach;

				// Rebuild or remove style attribute
				if (!empty($keep)) :
					$node->setAttribute('style', implode('; ', $keep));
				else :
					$node->removeAttribute('style');
				endif;
			endforeach;
			//****************************
			$html_source = $dom->saveHTML();
			$html_source = str_ireplace('<html><body>', '', $html_source);
			$html_source = str_ireplace('</body></html>', '', $html_source);
		endif;
		return $html_source;
	}

	function purify_html($html) {
		$config = HTMLPurifier_Config::createDefault();
		$config->set('Attr.AllowedFrameTargets', ['_blank', '_self', '_parent', '_top']);
		$purifier = new HTMLPurifier($config);
		$html = $purifier->purify($html);
		return $html;
	}

	function alternate_page_header($ix) {
		global $ar_pagecontrol;
		$return_result = false;
		if (!empty($ar_pagecontrol[$ix])) :
			if (!empty($ar_pagecontrol[$ix]['header'])) :
				$return_result = $ar_pagecontrol[$ix]['header'];
			endif;
		endif;
		return $return_result;
	}

	function alternate_page_footer($ix) {
		global $ar_pagecontrol;
		$return_result = false;
		if (!empty($ar_pagecontrol[$ix])) :
			if (!empty($ar_pagecontrol[$ix]['footer'])) :
				$return_result = $ar_pagecontrol[$ix]['footer'];
			endif;
		endif;
		return $return_result;
	}

	function stylesheet_overrides($ix): array {
		global $ar_pagecontrol;
		$result = [];
		if (!empty($ar_pagecontrol[$ix])) :
			if (!empty($ar_pagecontrol[$ix]['stylesheets']) && is_array($ar_pagecontrol[$ix]['stylesheets'])) :
				$result = $ar_pagecontrol[$ix]['stylesheets'];
			endif;
		endif;
		return $result;
	}

	function is_date( $str )
	{
		$stamp = strtotime( $str );
		$month = date( 'm', $stamp );
		$day   = date( 'd', $stamp );
		$year  = date( 'Y', $stamp );
		//echo 'is_date  '.$stamp.'  month  '.$month.'  day  '.$day.'  year  '.$year;
		if ($year > 1969)
			return checkdate( $month, $day, $year );
		else
			return false;
	}

	function check_to_bool( $str )
	{
		if ($str == 'on' || $str == 'checked')
			$result = '1';
		else
			$result = '0';
		return $result;
	}

	function bool_to_check( $bool )
	{
		if ($bool == 1)
			$result = 'checked';
		else
			$result = '';
		return $result;
	}

	function bool_to_yesno( $bool )
	{
		if ($bool == 1)
			$result = 'Yes';
		else
			$result = 'No';
		return $result;
	}

	function yesno_to_bool( $str )
	{
		$str = strtoupper($str);
		if ($str == 'ON' || $str == 'CHECKED' || $str == 'YES' || $str == 'Y')
			$result = '1';
		else
			$result = '0';
		return $result;
	}

	function role_match($str1 = null, $str2 = null)
	{
		$result = false;
		if (empty($str1) || empty($str2))
			return $result;
		$ar1 = explode(',',$str1);
		$ar2 = explode(',',$str2);
		foreach ($ar1 as $ar1_value)
		{
			if (is_numeric(array_search($ar1_value,$ar2)))
				$result = true;
		}
		return $result;
	}

	function get_event_field($event_id, $field_name)
	{
		global $db;
		$sql = "SELECT * FROM events WHERE event_id = ".nz(trim($event_id),'0');
		//echo '==='.$sql.'===';
		$result = $db->query($sql);
		if ($result->rowCount() > 0) :
			$row = $result->fetch(PDO::FETCH_ASSOC);
			return $row[$field_name];
		else :
			return 'Not Found!';
			return '';
		endif;
	}

	function image_cache($source, $quality = 85, $max_width = 800, $max_height = 600, $png_to_jpg = false) {
		//************* check if cache already exists and not requesting to rebuild ***********
		$ext = pathinfo(parse_url($source, PHP_URL_PATH), PATHINFO_EXTENSION);
		$file_id = hash('sha256', $source.$quality.$max_width.$max_height).'.'.$ext;
		$target_dir = APP_ROOT_MEDIA_DIR . '/img/cache/';
		$dest = $target_dir.basename($file_id);
		if (file_exists($dest) && ($_REQUEST['lcs_fix_images'] ?? null) !== '1') :
			$fver = '?ver='.filemtime($dest);
			$target_url = 'img/cache/'.$file_id.$fver;
			return $target_url;
		endif;
		//return $source;
		//**************************************************************************************
		if (stripos($source, APP_BASE_USER_SITE) === 0 || stripos($source, APP_BASE_USER_SITE_SECURE) === 0) :
			$ar_url = parse_url($source);
			//var_dump_pre($ar_url);
			$source_path = $_SERVER['DOCUMENT_ROOT'].$ar_url['path'];  // Local full URL
		elseif (stripos($source, 'http') !== 0 && !str_starts_with($source, APP_ROOT_MEDIA_DIR)) :
			$source_path = APP_ROOT_MEDIA_DIR.'/'.$source;  // Local relative URL
		else :
			$source_path = $source;  // Remote full URL
		endif;
		//error_log($source.' === '.$source_path);
		$check = @getimagesize($source_path);
		if($check !== false) :
			$target_dir = APP_ROOT_MEDIA_DIR . '/img/cache/';
			if (!is_dir($target_dir)) :
				mkdir($target_dir, 0777, true);
			endif;
			$image_type = exif_imagetype($source_path);
			if ($image_type) :
				$mime_type = image_type_to_mime_type($image_type);
				$mime_type = end(@explode('/', $mime_type));
				//$ext = strtolower(image_type_to_extension($image_type, false));
				if ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'png' || $ext == 'gif' || $ext == 'webp') :
					$white_background = false;
					if ($png_to_jpg && $ext == 'png') :
						$ext = 'jpg';
						$white_background = true;
					endif;
					list($img_width, $img_height, $_img_type, $img_attr) = $check;
					$file_id = hash('sha256', $source.$quality.$max_width.$max_height).'.'.$ext;
					//$source = $_FILES['filename']['tmp_name'];
					$dest = $target_dir.basename($file_id);
					if (!file_exists($dest) || ($_REQUEST['lcs_fix_images'] ?? null) === '1') :
						$ratio = $img_width / $img_height; // width/height
						$max_ratio = $max_width / $max_height;
						if ( $img_width > $max_width || $img_height > $max_height ) :
							if( $ratio > $max_ratio) :
								$new_width = $max_width;
								$new_height = $max_width / $ratio;
							else :
								$new_height = $max_height;
								$new_width = $max_height * $ratio;
							endif;
						else :
							$new_width = $img_width;
							$new_height = $img_height;
						endif;
						ini_set('memory_limit','256M');
						$image_src_func = 'imagecreatefrom' . $mime_type;
						//echo ' function: '.$image_src_func;
						$src = @call_user_func($image_src_func, $source_path);
						//echo ' src: '.$src;
						//$src = imagecreatefromstring( file_get_contents( $source ) );
						$dst = imagecreatetruecolor( $new_width, $new_height );
						//imagefill($dst,0,0,0x7fff0000); // set transparent background
						if ($png_to_jpg && $white_background) :
							imagefill($dst, 0, 0, imagecolorallocate($dst, 255, 255, 255));  //Set white background in case of PNG transparence
							$mime_type = 'jpeg';
						endif;
						imagealphablending($dst, false);
						imagesavealpha($dst, true);
						imagecopyresampled( $dst, $src, 0, 0, 0, 0, $new_width, $new_height, $img_width, $img_height );
						imagedestroy( $src );
						//echo ' dest: '.$dst.' file: '.$dest;
						switch($mime_type) :
							case "gif":
								$image_result = imagegif($dst, $dest);
								break;
							case "jpeg":
								$image_result = imagejpeg($dst, $dest, $quality);
								break;
							case "webp":
								$image_result = imagewebp($dst, $dest, $quality);
								break;
							case "png":
								$quality = 9 - intval(($quality / 10));
								if ($quality < 0) :
									$quality = 0;
								endif;
								if ($quality > 9) :
									$quality = 9;
								endif;
								$image_result = imagepng($dst, $dest, $quality);
								break;
						endswitch;
						imagedestroy( $dst );
						//echo ' === source: '.$source.' === dest: '.$dest.' === ';
						//if (move_uploaded_file($source, $dest)) :
						if ($image_result) :
							$fver = '?ver='.filemtime($dest);
							$target_url = 'img/cache/'.$file_id.$fver;
							return $target_url;
						else :
							//echo '=== Image create failed ===';
							return $source;
						endif;
					else :
						$fver = '?ver='.filemtime($dest);
						$target_url = 'img/cache/'.$file_id.$fver;
						return $target_url;
					endif;
				else :
					//echo '=== Invalid Extension ===';
					return $source;
				endif;	
			else :
				//echo '=== EXIF Image type failed ===';
				return $source;
			endif;
		else:
			//echo '=== getimagesize failed ===';
			return $source;
		endif;
	}

	function upload_image($post_name, $file_name, $target_dir, $max_dim = 1200, $max_size = 8388608, $media_path = '', $check_for_duplicates = true, $media_form = 'media_form') {
		if (!isset($_FILES[$post_name])) :
			return ['success'=>false, 'status'=>'Upload file not found!'];
		endif;
		if ($_FILES[$post_name]['size'] > $max_size):
			$max_size_mb = $max_size / 1024 / 1024;
			return ['success'=>false, 'status'=>'File too large! Maximum allowed: '.number_format($max_size_mb,1).' MB'];
		endif;
		$check = getimagesize($_FILES[$post_name]['tmp_name']);
		if($check === false) :
			return ['success'=>false, 'status'=>'Not an image!'];
		endif;
		$ext = strtolower(pathinfo($_FILES[$post_name]['name'], PATHINFO_EXTENSION));
		if (!($ext == 'jpg' || $ext == 'jpeg' || $ext == 'png' || $ext == 'gif')) :
			return ['success'=>false, 'status'=>'Invalid file extension!'];
		endif;
		list($img_width, $img_height, $_img_type, $img_attr) = $check;
		$file_id = $file_name.'.'.$ext;
		$source = $_FILES[$post_name]['tmp_name'];
		$dest = $target_dir.basename($file_id);
		$ratio = $img_width/$img_height; // width/height
		if ( $img_width > $max_dim || $img_height > $max_dim ) :
			if( $ratio > 1) :
				$new_width = $max_dim;
				$new_height = $max_dim / $ratio;
			else :
				$new_width = $max_dim * $ratio;
				$new_height = $max_dim;
			endif;
		else :
			$new_width = $img_width;
			$new_height = $img_height;
		endif;
		$image_data = file_get_contents($source);
		$hash = hash('sha512', $image_data . $media_path);
		if ($check_for_duplicates) :
			$duplicate_id = lookup_db_field('media', 'hash', $hash, 'id');
			if (!empty($duplicate_id)) :
				return [
					'success' => false,
					'status' => 'Image already exists - <a href="index.php?IX=' . $media_form . '&id=' . $duplicate_id .'">click here</a>! ',
					'hash' => $hash,
					'duplicate' => true,
					'duplicate_id' => $duplicate_id,
				];
			endif;
		endif;
		$src = imagecreatefromstring($image_data);
		//Auto rotate based on EXIF data
		$exif = exif_read_data($_FILES[$post_name]['tmp_name']);
		//var_dump($exif);
		if(!empty($exif['Orientation'])) :
			switch($exif['Orientation']) :
				case 8:
					$temp_dimension = $new_width;
					$new_width = $new_height;
					$new_height = $temp_dimension;
					$temp_dimension = $img_width;
					$img_width = $img_height;
					$height_orig = $temp_dimension;
					$src = imagerotate($src,90,0);
					break;
				case 3:
					$src = imagerotate($src,180,0);
					break;
				case 6:
					$temp_dimension = $new_width;
					$new_width = $new_height;
					$new_height = $temp_dimension;
					$temp_dimension = $img_width;
					$img_width = $img_height;
					$img_height = $temp_dimension;
					$src = imagerotate($src,-90,0);
					break;
			endswitch;
		endif;							
		//echo 'w '.$fwidth.' h '.$blank_height.' ow '.$width_orig.' oh '.$height_orig;
		//*******************************
		//if (isset($check['bits']) && $check['bits'] == 8) :
			//$dst = imagecreate( $new_width, $new_height );
		//else :
			$dst = imagecreatetruecolor( $new_width, $new_height );
		//endif;
		imagecopyresampled($dst, $src, 0, 0, 0, 0, $new_width, $new_height, $img_width, $img_height);
		imagedestroy($src);
		switch($ext){
			case "gif":
				$image_result = imagegif($dst, $dest);
			break;
			case "jpg":
				$image_result = imagejpeg($dst, $dest);
			break;
			case "jpeg":
				$image_result = imagejpeg($dst, $dest);
			break;
			case "png":
				$image_result = imagepng($dst, $dest);
			break;
		}
		imagedestroy($dst);
		//echo ' === source: '.$source.' === dest: '.$dest.' === ';
		//if (move_uploaded_file($source, $dest)) :
		if (!$image_result) :
			return ['success'=>false, 'status'=>'Error uploading file!'];
		endif;
		return [
			'success'=>true,
			'fullpath'=>$dest,
			'filename'=>$file_id,
			'hash' => $hash,
			'duplicate' => false,
		];
	}

	function uploadImage($subFolderName, $fileName, $maxSize, $maxW, $fullPath, $relPath, $colorR, $colorG, $colorB, $maxH = null){
		$folder = $relPath;
		$maxlimit = $maxSize;
		$allowed_ext = "jpg,jpeg,gif,png,bmp";
		$match = "";
		$filesize = $_FILES[$fileName]['size'];
		if($filesize > 0){	
			$filename = strtolower($_FILES[$fileName]['name']);
			$filename = preg_replace('/\s/', '_', $filename);
		   	if($filesize < 1){ 
				$errorList[] = "File size is empty.";
			}
			if($filesize > $maxlimit){ 
				$errorList[] = "File size is too big.";
			}
			if(count($errorList)<1){
				$file_ext = preg_split("/\./",$filename);
				$allowed_ext = preg_split("/\,/",$allowed_ext);
				foreach($allowed_ext as $ext){
					if($ext==end($file_ext)){
						$match = "1"; // File is allowed
						$NUM = time();
						$front_name = substr($file_ext[0], 0, 15);
						$newfilename = $front_name."_".$NUM.".".end($file_ext);
						$filetype = end($file_ext);
						$directory = $folder.$subFolderName;
						if (!file_exists($directory))
						{
							mkdir($directory);
						}
						$save = $directory.'/'.$newfilename;
						if(!file_exists($save)){
							list($width_orig, $height_orig) = getimagesize($_FILES[$fileName]['tmp_name']);
							if($maxH == null){
								if($width_orig < $maxW){
									$fwidth = $width_orig;
								}else{
									$fwidth = $maxW;
								}
								$ratio_orig = $width_orig/$height_orig;
								$fheight = $fwidth/$ratio_orig;
								
								$blank_height = $fheight;
								$top_offset = 0;
									
							}else{
								if($width_orig <= $maxW && $height_orig <= $maxH){
									$fheight = $height_orig;
									$fwidth = $width_orig;
								}else{
									if($width_orig > $maxW){
										$ratio = ($width_orig / $maxW);
										$fwidth = $maxW;
										$fheight = ($height_orig / $ratio);
										if($fheight > $maxH){
											$ratio = ($fheight / $maxH);
											$fheight = $maxH;
											$fwidth = ($fwidth / $ratio);
										}
									}
									if($height_orig > $maxH){
										$ratio = ($height_orig / $maxH);
										$fheight = $maxH;
										$fwidth = ($width_orig / $ratio);
										if($fwidth > $maxW){
											$ratio = ($fwidth / $maxW);
											$fwidth = $maxW;
											$fheight = ($fheight / $ratio);
										}
									}
								}
								if($fheight == 0 || $fwidth == 0 || $height_orig == 0 || $width_orig == 0){
									die("FATAL ERROR REPORT ERROR CODE [add-pic-line-67-orig] to <a href='http://www.atwebresults.com'>AT WEB RESULTS</a>");
								}
								if($fheight < 45){
									$blank_height = 45;
									$top_offset = round(($blank_height - $fheight)/2);
								}else{
									$blank_height = $fheight;
								}
							}
							switch($filetype){
								case "gif":
									$image = @imagecreatefromgif($_FILES[$fileName]['tmp_name']);
								break;
								case "jpg":
									$image = @imagecreatefromjpeg($_FILES[$fileName]['tmp_name']);
								break;
								case "jpeg":
									$image = @imagecreatefromjpeg($_FILES[$fileName]['tmp_name']);
								break;
								case "png":
									$image = @imagecreatefrompng($_FILES[$fileName]['tmp_name']);
								break;
							}
							//Auto rotate based on EXIF data
							$exif = exif_read_data($_FILES[$fileName]['tmp_name']);
							//var_dump($exif);
							if(!empty($exif['Orientation'])) {
								switch($exif['Orientation']) {
									case 8:
										$temp_dimension = $fwidth;
										$fwidth = $blank_height;
										$blank_height = $temp_dimension;
										$fheight = $temp_dimension;
										$temp_dimension = $width_orig;
										$width_orig = $height_orig;
										$height_orig = $temp_dimension;
										$image = imagerotate($image,90,0);
										break;
									case 3:
										$image = imagerotate($image,180,0);
										break;
									case 6:
										$temp_dimension = $fwidth;
										$fwidth = $blank_height;
										$blank_height = $temp_dimension;
										$fheight = $temp_dimension;
										$temp_dimension = $width_orig;
										$width_orig = $height_orig;
										$height_orig = $temp_dimension;
										$image = imagerotate($image,-90,0);
										break;
								}
							}							
							//echo 'w '.$fwidth.' h '.$blank_height.' ow '.$width_orig.' oh '.$height_orig;
							//*******************************
							$image_p = imagecreatetruecolor($fwidth, $blank_height);
							$white = imagecolorallocate($image_p, $colorR, $colorG, $colorB);
							imagefill($image_p, 0, 0, $white);
							@imagecopyresampled($image_p, $image, 0, $top_offset, 0, 0, $fwidth, $fheight, $width_orig, $height_orig);
							switch($filetype){
								case "gif":
									if(!@imagegif($image_p, $save)){
										$errorList[]= "PERMISSION DENIED [GIF] ".$save;
									}
								break;
								case "jpg":
									if(!@imagejpeg($image_p, $save, 100)){
										$errorList[]= "PERMISSION DENIED [JPG] ".$save;
									}
								break;
								case "jpeg":
									if(!@imagejpeg($image_p, $save, 100)){
										$errorList[]= "PERMISSION DENIED [JPEG] ".$save;
									}
								break;
								case "png":
									if(!@imagepng($image_p, $save, 0)){
										$errorList[]= "PERMISSION DENIED [PNG] ".$save;
									}
								break;

							}
							@imagedestroy($filename);
						}else{
							$errorList[]= "CANNOT MAKE IMAGE IT ALREADY EXISTS";
						}	
					}
				}		
			}
		}else{
			$errorList[]= "NO FILE SELECTED";
		}
		if(!$match){
		   	$errorList[]= "File type isn't allowed: $filename";
		}
		if(sizeof($errorList) == 0){
			//return $fullPath.$subFolderName.'/'.$newfilename;
			return $relPath.$subFolderName.'/'.$newfilename;
		}else{
			$eMessage = array();
			for ($x=0; $x<sizeof($errorList); $x++){
				$eMessage[] = $errorList[$x];
			}
		   	return $eMessage;
		}
	}
	
	function sys_compress($data) {
		return zlib_encode($data, ZLIB_ENCODING_RAW);
	}
	
	function sys_uncompress($data) {
		return zlib_decode($data);
	}

	function sys_encrypt($string, $key) {
		return @openssl_encrypt($string, 'AES-128-CFB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
	}
	
	function sys_decrypt($string, $key) {
		return @openssl_decrypt($string, 'AES-128-CFB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
	}

	function sys_encrypt_string($string, $key = APP_CRYPTO_PWD) {
		$iv_len = openssl_cipher_iv_length(APP_CRYPTO_ALGORITHM);
		$iv = openssl_random_pseudo_bytes($iv_len);
		$encrypted = bin2hex($iv) . openssl_encrypt($string, APP_CRYPTO_ALGORITHM, $key, 0, $iv);
		return $encrypted;
	}

	function sys_decrypt_string($string, $key = APP_CRYPTO_PWD) {
		$iv_len_hex = openssl_cipher_iv_length(APP_CRYPTO_ALGORITHM) * 2;
		$iv = hex2bin(substr($string, 0, $iv_len_hex));
		$encrypted = substr($string, $iv_len_hex);
		$decrypted = openssl_decrypt($encrypted, APP_CRYPTO_ALGORITHM, $key, 0, $iv);
		return $decrypted;
	}

	function xls_sql_encrypt($xls_sql) {
		return urlencode(sys_encrypt(sys_compress($xls_sql), $_SESSION['rand_key']));
	}

	function xls_sql_decrypt($xls_sql) {
		return sys_uncompress(sys_decrypt(urldecode($xls_sql), $_SESSION['rand_key']));
	}
	
	function sql_to_xls($sql, $file_name = 'php://output', $file_format = APP_EXCEL_FORMAT) {
		//***************** generate file using PHPExcel *****************
		global $db;
		//require_once dirname(__FILE__) . '/../vendor_classes/PHPExcel.php';
		
		$objPHPExcel = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
		$objPHPExcel->getProperties()->setCreator(APP_COMPANY)
									 ->setLastModifiedBy(APP_COMPANY)
									 ->setTitle(APP_COMPANY)
									 ->setSubject(APP_TITLE)
									 ->setDescription(APP_TITLE)
									 ->setKeywords(APP_TITLE)
									 ->setCategory(APP_TITLE);
		$sheet = $objPHPExcel->getActiveSheet();
		$sheet0 = $objPHPExcel->setActiveSheetIndex(0);
		//$sql = gzuncompress(sys_decrypt($_GET['sql'], $_SESSION['rand_key']));
		$result = $db->query($sql) or die('Database Error!');
		if ($result->rowCount() > 0) :
			$first_row = true;
			$curr_row = 1;  /* 3 for setups with 2 lines of headers */
			$tot_cols = 1;
			$ar_field_types = [];
			$ar_field_names = [];
			$i = 0;
			//$table_fields = $result->fetchAll(PDO::FETCH_COLUMN);
			while ($i < $result->columnCount()) :
				$meta = $result->getColumnMeta($i);
				if (!$meta) :
					$ar_field_types[$i] = 'string';
					$ar_field_names[$i] = 'N/A';
				else :
					/*
					echo PDO::PARAM_INT;
					echo PDO::PARAM_BOOL;
					var_dump($meta);
					echo '<br />';
					*/
					$ar_field_types[$i] = strtolower($meta['native_type']);
					$ar_field_names[$i] = $meta['name'];
				endif;
				$i++;
			endwhile;
			//error_log(print_r($ar_field_types, true));
			while ($row = $result->fetch(PDO::FETCH_NUM)) :
				if ($first_row) :
					$curr_col = 1;
					//foreach($row as $key => $row_field) :
					foreach($ar_field_names as $key) :
						$sheet0->setCellValue([$curr_col, $curr_row], $key);
						$sheet->getStyle([$curr_col, $curr_row])->applyFromArray([
							'fill' 	=> [
								'fillType'	=> \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
								'startColor'	=> ['argb' => '3E4B62'],
							],
							'borders' => [
								'allBorders' => [
									'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUM, 
									'color' =>['argb' => \PhpOffice\PhpSpreadsheet\Style\Color::COLOR_WHITE]
								]
							],
							'font' => [
								'color' => ['argb' => \PhpOffice\PhpSpreadsheet\Style\Color::COLOR_WHITE],
							],
							'alignment' =>['horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER],
						]);
						$sheet->getColumnDimension('B')->setAutoSize(true);

						$curr_col = $curr_col + 1;
					endforeach;
					$tot_cols = $curr_col;
					$curr_row = $curr_row + 1;
					$first_row = false;
				endif;
				$max_col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($tot_cols - 1);
				$curr_col = 1;
				foreach($row as $row_field) :
					$row_field = utf8_encode($row_field);
					//echo $curr_col.' === '.$ar_field_types[$curr_col].' === <br />';
					//if ($ar_field_types[$curr_col] == 'long' || $ar_field_types[$curr_col] == 'longlong' || $ar_field_types[$curr_col] == 'newdecimal' || $ar_field_types[$curr_col] == 'float' || $ar_field_types[$curr_col] == 'tiny') :
					if (in_array($ar_field_types[$curr_col], ['long', 'longlong', 'newdecimal', 'float', 'tiny']) && is_numeric($row_field)) :
						$sheet0->setCellValueExplicit([$curr_col, $curr_row], html_entity_decode($row_field, ENT_QUOTES), \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
					/*
					elseif ($ar_field_types[$curr_col] == 'float') :
						$sheet0->setCellValueExplicitByColumnAndRow($curr_col, $curr_row, html_entity_decode($row_field, ENT_QUOTES), PHPExcel_Cell_DataType::TYPE_NUMERIC);
					*/
					else :
						$sheet0->setCellValueExplicit([$curr_col, $curr_row], html_entity_decode($row_field, ENT_QUOTES), \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
						if (strtolower(substr($row_field,0,7)) == 'http://' || strtolower(substr($row_field,0,8)) == 'https://') :
							//$url = str_replace('http://', '', $link);
							//$sheet0->getCellByColumnAndRow($curr_col, $curr_row)->getHyperlink()->setUrl('http://www.'.$url);
							$parts = parse_url($row_field);
							parse_str($parts['query'], $query);
							//echo $query['email'];
							$query['mslink'] = 'true';
							$url = $parts['scheme'].'://'.$parts['host'].$parts['path'].'?'.http_build_query($query); 
							$sheet0->getCell([$curr_col, $curr_row])->getHyperlink()->setUrl($url);
							$sheet->getStyle([$curr_col, $curr_row])->applyFromArray(
								array	(	'font' => array('color' => array('argb' => \PhpOffice\PhpSpreadsheet\Style\Color::COLOR_BLUE)),
										)
								);
						endif;
					endif;
					$curr_col = $curr_col + 1;
				endforeach;
				$curr_row = $curr_row + 1;
			endwhile;
			//die();
			$max_row = $curr_row - 1;
			if ($max_row > 3) :
				$i = 0;
				while ($i < $result->columnCount()) :
					if ($ar_field_types[$i] == 'long' || $ar_field_types[$i] == 'longlong' || $ar_field_types[$i] == 'tiny') :
						$sheet->getStyle(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).'2:'.\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).$max_row)->applyFromArray(
							array	('alignment' => array('horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT) )
						);
					elseif ($ar_field_types[$i] == 'newdecimal' || $ar_field_types[$i] == 'float') :
						$sheet->getStyle(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).'2:'.\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).$max_row)->applyFromArray(
							array	('alignment' => array('horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT) )
							);
						$sheet->getStyle(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).'2:'.\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).$max_row)->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
					else :
						$sheet->getStyle(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).'2:'.\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1).$max_row)->applyFromArray(
							array	('alignment' => array('horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT) )
							);
					endif;
					$i++;
				endwhile;

				$sheet->getStyle('A2:'.$max_col.$max_row)->applyFromArray([
					'fill' 	=>	[
						'fillType'	=> \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
						'color'	=> ['argb' => 'ECEDEF'],
					],
					'borders' => [
						'allBorders' => [
							'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_MEDIUM,
							'color' => ['argb' => \PhpOffice\PhpSpreadsheet\Style\Color::COLOR_WHITE],
						]
					]
					]);
			endif;

			foreach (range(0, $tot_cols - 1) as $col) :
				$sheet->getColumnDimensionByColumn($col)->setAutoSize(true);
			endforeach;
		else :
			$sheet0->setCellValue([1, 1], 'No records found.');
		endif;
		// Rename worksheet
		$sheet->setTitle('exportfile');
		
		// Set active sheet index to the first sheet, so Excel opens this as the first sheet
		$sheet0;
		/*
		*/
		if ($file_name == 'php://output') :
			if ($file_format == 'xls') :
				// Redirect output to a client’s web browser (Excel5)
				header('Content-Type: application/vnd.ms-excel');
				header('Content-Disposition: attachment;filename="exportfile.xls"');
			elseif ($file_format == 'xlsx') :
				header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
				header('Content-Disposition: attachment;filename="exportfile.xlsx"');
			endif;
			header('Cache-Control: max-age=0');
			// If you're serving to IE 9, then the following may be needed
			header('Cache-Control: max-age=1');
			
			// If you're serving to IE over SSL, then the following may be needed
			header ('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
			header ('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); // always modified
			header ('Cache-Control: cache, must-revalidate'); // HTTP/1.1
			header ('Pragma: public'); // HTTP/1.0
		endif;
		//PHPExcel_Calculation::getInstance($objPHPExcel)->clearCalculationCache();
		if ($file_format == 'xls') :
			$objWriter = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($objPHPExcel, 'Xls');
			//$objWriter->setOffice2003Compatibility(true);
		elseif ($file_format == 'xlsx') :
			$objWriter = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($objPHPExcel, 'Xlsx');
		endif;
		//$objWriter->save('php://output');
		$objWriter->save($file_name);
		return $result->rowCount();
	}

	function page_404() {
		set_analytics_reference('404', null);
		header("HTTP/1.0 404 Not Found");
		echo '<h1>This page doesn\'t seem to exist.</h1>';
		echo '<h2>It looks like the link pointing here was faulty. Maybe try searching?</h2>';
	}

	function db_navigator_bar($ar_args)
	{
		global $ar_pagecontrol;
		$defaults = array(
							'subtab'			=> 'false',
							'parm1'				=> '',
							'parm2'				=> '',
							'parm3'				=> '',
							'filter_cols'		=> '',
							'btn_addnew'		=> 'true',
							'btn_print' 		=> 'true',
							'btn_excel' 		=> 'true',
							'btn_pdf'	 		=> 'true',
							'record_count' 		=> 'true',
							'extra_buttons'	 	=> [],
							'multi_filter'		=> [],
							'callback'	 		=> 'null',
							'lazy_callback'	 	=> 'null',
						);
		$ar_args = array_merge($defaults, $ar_args);
		//var_dump($ar_args);
		$ar_args['parm1'] = xls_sql_encrypt($ar_args['parm1']);
		$ar_args['parm2'] = xls_sql_encrypt($ar_args['parm2']);
		$ar_args['parm3'] = xls_sql_encrypt($ar_args['parm3']);
		$table_id = $ar_args["table_id"];
		$call = $ar_args["call"];
		$func = $ar_args["func"];
		$ix_form = $ar_args["form"];
		$print_section = $ar_args["print_section"];
		if (empty($_GET['scol_'.$table_id])) :
			$sort_col = $ar_args["sort_col"];
		else :
			$sort_col = nz($_GET['scol_'.$table_id], '0');
		endif;
		if (!isset($_GET['sorder_'.$table_id]) || ($_GET['sorder_'.$table_id] !== '0' && $_GET['sorder_'.$table_id] !== '1')) :
			$sort_order = $ar_args["sort_order"];
		else :
			$sort_order = nz($_GET['sorder_'.$table_id], '0');
		endif;
		if (empty($_GET['fcols_'.$table_id])) :
			$filter_cols = $ar_args["filter_cols"];
		else :
			$filter_cols = trim($_GET['fcols_'.$table_id]);
		endif;
		if (empty($_GET['multi_filter_'.$table_id])) :
			$multi_filter = $ar_args["multi_filter"];
		else :
			$multi_filter = json_decode(trim($_GET['multi_filter_'.$table_id]));
		endif;
		$parm1 = $ar_args["parm1"];
		$parm2 = $ar_args["parm2"];
		$parm3 = $ar_args["parm3"];
		$subtab = $ar_args["subtab"];
		$callback = $ar_args["callback"];
		$lazy_callback = $ar_args["lazy_callback"];
?>
		<div class="db_nav">
			<?php if ($ar_args['btn_addnew'] == 'true' && (empty($ar_pagecontrol[$call]['update_roles']) || role_match($ar_pagecontrol[$call]['update_roles'], get_roles()))) : ?>
				<input type="button" class="mini" value="Add New" onclick="window.location.href = 'index.php?IX=<?php echo $ix_form; ?>';" />
			<?php endif; ?>
			<div class="search_box" id="<?php echo $table_id; ?>_search_box">
				<input type="text" name="<?php echo $table_id; ?>_strSearch" id="<?php echo $table_id; ?>_strSearch" class="input-box-var search" size=30 maxlength="30" value="<?php echo trim($_GET['search_'.$table_id] ?? NULL); ?>" >
				<a href="javascript:clear_search_box('<?php echo $table_id; ?>_strSearch');"></a>
			</div>
			<?php if ($ar_args['btn_excel'] == 'true') : ?>
				<input type="button" class="mini" value="Excel &reg;" onclick="exportExcel(<?php echo $table_id; ?>_xls_sql);" />
			<?php endif; ?>
			<?php if ($ar_args['btn_print'] == 'true') : ?>
				<input type="button" class="mini" value="Print" onclick="printSection('<?php echo $print_section; ?>');" />
			<?php endif; ?>
			<?php if ($ar_args['btn_pdf'] == 'true') : ?>
				<input type="button" class="mini" value="PDF" onclick="pdfSection('<?php echo $print_section; ?>');" />
			<?php endif; ?>
			<?php
				foreach ($ar_args['extra_buttons'] as $btn) :
					echo '<input id="'.$btn['id'].'" type="button" class="mini" style="'.$btn['style'].'" value="'.$btn['title'].'" onclick="'.$btn['action'].'" />';
				endforeach;
			?>
			&emsp;
			<?php if ($ar_args['record_count'] == 'true') : ?>
				Records: <div style="width:50px; text-align:right; display:inline-block;" id="<?php echo $table_id; ?>_total_count"></div>
			<?php endif; ?>
			<div style="width:auto; margin-left:15px; text-align:left; display:inline-block;" id="<?php echo $table_id; ?>_extra_data"></div>
			<div style="width:auto; margin-left:15px; text-align:left; display:inline-block;" id="<?php echo $table_id; ?>_diag"></div>
		</div>
		<div class="db-grid" id="<?php echo $table_id; ?>">
		</div>
		<script>
			print_header = '<div id="header"><h1>Davler Media - <?=APP_TITLE?></h1></div>';
			print_footer = '<div id="footer"><p class="page">Page </p></div>';
			var <?php echo $table_id; ?>_xls_sql;
			var <?php echo $table_id; ?>_timeout;
			var <?php echo $table_id; ?>_subtab = <?php echo $subtab; ?>;
			ar_lcs_db_loaded['<?php echo $table_id; ?>'] = false;
			ar_lcs_db_load['<?php echo $table_id; ?>'] = function()
			{
				lcs_db_load('<?php echo $table_id; ?>', '<?php echo $call; ?>', '<?php echo $func; ?>', '<?php echo $ix_form; ?>', '<?php echo $sort_col; ?>', 
							'<?php echo $sort_order; ?>', '<?php echo trim($_GET['search_'.$table_id] ?? NULL); ?>', "<?php echo $parm1; ?>", "<?php echo $parm2; ?>", "<?php echo $parm3; ?>", '<?php echo $filter_cols; ?>', <?php echo json_encode($multi_filter) ?>,  <?php echo $callback; ?>, <?php echo $lazy_callback; ?> );
				ar_lcs_db_loaded['<?php echo $table_id; ?>'] = true;
				$('#<?php echo $table_id; ?>_strSearch').on( 'keyup', function () {
						clearTimeout(<?php echo $table_id; ?>_timeout);
						<?php echo $table_id; ?>_timeout = setTimeout(function() 
											{
												lcs_db_load('<?php echo $table_id; ?>','<?php echo $call; ?>', '<?php echo $func; ?>', '<?php echo $ix_form; ?>', 
															<?php echo $table_id; ?>_sort_col, <?php echo $table_id; ?>_sort_order, $('#<?php echo $table_id; ?>_strSearch').val(),
															"<?php echo $parm1; ?>", "<?php echo $parm2; ?>", "<?php echo $parm3; ?>", <?php echo $table_id; ?>_filter_cols, <?php echo $table_id; ?>_multi_filter, <?php echo $table_id; ?>_callback, <?php echo $table_id; ?>_lazy_callback);
											}
									,500);
						if (this.value > ' ')
						{
							$('#<?php echo $table_id; ?>_strSearch').removeClass('search');
							$('#<?php echo $table_id; ?>_strSearch').addClass('search_cancel');
							$('#<?php echo $table_id; ?>_search_box a').show();
						}
						else
						{
							$('#<?php echo $table_id; ?>_strSearch').removeClass('search_cancel');
							$('#<?php echo $table_id; ?>_strSearch').addClass('search');
							$('#<?php echo $table_id; ?>_search_box a').hide();
						}
					} );
			}
			$(document).ready(function()	
			{
				if (!<?php echo $table_id; ?>_subtab) 
				{
					ar_lcs_db_load['<?php echo $table_id; ?>']();
				}
				if ($('#<?php echo $table_id; ?>_strSearch').val() > ' ')
				{
					$('#<?php echo $table_id; ?>_strSearch').removeClass('search');
					$('#<?php echo $table_id; ?>_strSearch').addClass('search_cancel');
					$('#<?php echo $table_id; ?>_search_box a').show();
				}
			});
		</script>
<?php	
	}

	function db_navigator_bar_old($ar_args)
	{
		$table_id = $ar_args["table_id"];
		$ix_form = $ar_args["form"];
		$xls_sql = $ar_args["xls_sql"];
		$print_section = $ar_args["print_section"];
?>
		<table align="center" cellpadding="2" cellspacing="5" border="0" >
			<tr>
				<td align="center" >
					<input type="button" class="mini" value="Add New" onclick="window.location.href = 'index.php?IX=<?php echo $ix_form; ?>';" />
					&nbsp;&nbsp;&nbsp;
				</td>
				<td colspan="1" align="right" class="data-label">
					Find:
				</td>
				<td colspan="2">
					<div class="search_box" id="<?php echo $table_id; ?>_search_box">
						<input type="text" name="<?php echo $table_id; ?>_strSearch" id="<?php echo $table_id; ?>_strSearch" class="input-box-var search" size=30 maxlength="30" >
						<a href="javascript:clear_search_box('<?php echo $table_id; ?>_strSearch');"></a>
					</div>
			   </td>
				<td align="center" >
					&nbsp;&nbsp;&nbsp;
					<input type="button" class="mini" value="Excel &reg;" onclick="exportExcel('<?php echo urlencode(sys_encrypt(sys_compress($xls_sql), $_SESSION['rand_key'])); ?>');" />
				</td>
				<td align="center" >
					<input type="button" class="mini" value="Print" onclick="printSection('<?php echo $print_section; ?>');" />
				</td>
				<td align="center" >
					<input type="button" class="mini" value="PDF" onclick="pdfSection('<?php echo $print_section; ?>');" />
				</td>
				<td align="right" >
					&emsp;&emsp;&emsp;
					Records: <div style="width:60px; text-align:right; float:right;" id="<?php echo $table_id; ?>_total_count"></div>
				</td>
			</tr>
		</table>
<?php	
	}
	
	function sql_set_update($add_comma = false)
	{
		$str = "update_by = ".nz(trim($_SESSION['user_id']), 0).", ".
				"update_date = '".date('Y-m-d H:i:s')."', ";
		if ($add_comma) :
			$str .= ', ';
		endif;
		return $str;
	}
	
	function sql_set_create()
	{
		$str = "create_by = ".nz(trim($_SESSION['user_id']), 0).", ".
				"create_date = '".date('Y-m-d H:i:s')."', ";
		return $str;
	}
	
	function post_set_update() {
		$_POST['update_by'] = trim($_SESSION['user_id']);
		$_POST['update_date'] = date('m/d/Y h:i:s a');
	}
	
	function post_set_create_update() {
		$_POST['create_by'] = trim($_SESSION['user_id']);
		$_POST['create_date'] = date('m/d/Y h:i:s a');
		$_POST['update_by'] = trim($_SESSION['user_id']);
		$_POST['update_date'] = date('m/d/Y h:i:s a');
	}
	
	function post_set_load_create_update($assign_unique_col_name = false) {
		global $row;
		$_POST['create_by'] = is_array($row['create_by']) ? $row['create_by'][0] : $row['create_by'];
		$_POST['create_date'] = is_array($row['create_date']) ? nzdate_display_datetime($row['create_date'][0]) : nzdate_display_datetime($row['create_date']);
		$_POST['update_by'] = is_array($row['update_by']) ? $row['update_by'][0] : $row['update_by'];
		$_POST['update_date'] = is_array($row['update_date']) ? nzdate_display_datetime($row['update_date'][0]) : nzdate_display_datetime($row['update_date']);
	}
	
	function get_user($user_id) {
		global $db;
		$return_result = '';
		$sql = "SELECT user_name FROM sys_users WHERE user_id = ".nz($user_id, '0');
		$result = $db->query($sql) or die('Database Error!');
		if ($result->rowCount() > 0) :
			$row = $result->fetch(PDO::FETCH_OBJ);
			$return_result = $row->user_name;
		endif;
		return $return_result;
	}

	function submit_limiter_exceeded(string $entry = '', int $limit = 15): bool {
		global $db;
		$sql = "SELECT COUNT(*) FROM log WHERE log_text = :log_text AND ip = :ip AND DATE(timestamp) = CURDATE()";
		$stmt = $db->prepare($sql);
		$stmt->execute([
			':log_text' => $entry,
			':ip' => get_client_ip(),
		]);
		$num_rows = $stmt->fetchColumn();
		if ($num_rows > $limit) :
			return true;
		else :
			return false;
		endif;
	}
	
	function show_create_update()
	{
?>
		<div class="create_update">
			<table cellpadding="2">
				<tr>
					<td>
						Created:
					</td>
					<td>
						<input name="create_by" id="create_by" type="hidden" readonly="readonly" value="<?php echo $_POST['create_by'] ?? NULL; ?>"  />
						<input name="create_by_display" id="create_by_display" type="text" readonly="readonly" class="create_update" style="width:100px;" value="<?php echo get_user($_POST['create_by'] ?? NULL); ?>"  />
					</td>
					<td>
						<input name="create_date" id="create_date" type="text" readonly="readonly" class="create_update" value="<?php echo $_POST['create_date'] ?? NULL; ?>"  />
					</td>
				</tr>
				<tr>
					<td>
						Updated:
					</td>
					<td>
						<input name="update_by" id="update_by" type="hidden" readonly="readonly" value="<?php echo $_POST['update_by'] ?? NULL; ?>"  />
						<input name="update_by_display" id="update_by_display" type="text" readonly="readonly" class="create_update" style="width:100px;" value="<?php echo get_user($_POST['update_by'] ?? NULL); ?>"  />
					</td>
					<td>
						<input name="update_date" id="update_date" type="text" readonly="readonly" class="create_update" value="<?php echo $_POST['update_date'] ?? NULL; ?>"  />
					</td>
				</tr>
			</table>
		</div>
<?php
	}

	function report_output_format($form_id = NULL) {
		$form_attr = '';
		if (!empty($form_id)) :
			$form_attr = ' form="'.$form_id.'" ';
		endif;
		echo '<div class="input_column">';
		$ar_output_formats = ['html'=>'Browser', 'pdf'=>'PDF', 'doc'=>'MS-Word<sup>&reg;</sup>', 'xls'=>'MS-Excel<sup>&reg;</sup>', 'csv'=>'CSV', 'email'=>'Email to recipient(s) below'];
		$_POST['output_format['.$form_id.']'] = 'html';
		form_field(['fname'=>'output_format['.$form_id.']', 'ftype'=>'radiogroup', 'fsize'=>10, 'frequired'=>false, 'fclass'=>'no_dirty', 'fdbname'=>'', 'ar_group'=>$ar_output_formats, 'flabel'=>'Output Format', 'form_id'=>$form_id]);
		form_field(['fname'=>'email_to['.$form_id.']', 'ftype'=>'text', 'fsize'=>150, 'frequired'=>false, 'fclass'=>'no_dirty', 'fdbname'=>'', 'flabel'=>'Email To (separated by commas)', 'form_id'=>$form_id]);
		$ar_attachment_formats = ['pdf'=>'PDF', 'doc'=>'MS-Word<sup>&reg;</sup>', 'xls'=>'MS-Excel<sup>&reg;</sup>', 'csv'=>'CSV'];
		form_field(['fname'=>'email_attachment['.$form_id.']', 'ftype'=>'select', 'fsize'=>10, 'frequired'=>false, 'fclass'=>'no_dirty', 'fdbname'=>'', 'ar_group'=>$ar_attachment_formats, 'flabel'=>'Email Attachment', 'form_id'=>$form_id]);
		$ar_orientations = ['P'=>'Portrait', 'L'=>'Landscape'];
		$_POST['orientation['.$form_id.']'] = 'P';
		form_field(['fname'=>'orientation['.$form_id.']', 'ftype'=>'radiogroup', 'fsize'=>10, 'frequired'=>false, 'fclass'=>'no_dirty', 'fdbname'=>'', 'ar_group'=>$ar_orientations, 'flabel'=>'Orientation', 'form_id'=>$form_id]);
		echo '<br><br><input type="submit" value="Submit" ' . $form_attr . '>';
		echo '</div>';
	}

	function lookup_db_field(string $table, string $id_field, int|string $id_value, string|array $result_field): string|array {
		global $db;
		if (is_numeric($id_value)) :
			$id_value = nz(trim($id_value),'0');
		else :
			$id_value = $db->quote($id_value);
		endif;
		if (is_array($result_field)) :
			array_map('sql_safe_object', $result_field);
			$field_list = implode(', ', $result_field);
		else :
			$field_list = sql_safe_object($result_field);
		endif;
		$sql = "SELECT " . $field_list . " FROM " . sql_safe_object($table) . " WHERE " . sql_safe_object($id_field) . " = " . $id_value;
		//echo '==='.$sql.'===';
		$result = $db->query($sql);
		if ($result->rowCount() > 0) :
			$row = $result->fetch(PDO::FETCH_ASSOC);
			if (is_array($result_field)) :
				return $row;
			else :
				return $row[$result_field];
			endif;
		else :
			return '';
		endif;
	}

	function sql_safe_object($str) {
		$str = '`' . preg_replace( '/[^a-z0-9_ ]/i', '', $str) . '`';
		return $str;
	}

	function sql_safe_string($str) {
		$str = preg_replace( '/[^a-z0-9_]/i', '', $str);
		return $str;
	}

	function db_cached_query(string $sql, string $arr_or_obj = 'obj', array $sql_parms = [], int $ttl = 300) {
		if (empty($sql)) :
			return false;
		endif;
		$key = hash('sha512', $sql . http_build_query($sql_parms));
		$rows = apcu_fetch($key);
		if ($rows === false) :
			global $db;
			$mode = '';
			switch ($arr_or_obj) :
				case 'arr' :
					$mode = PDO::FETCH_ASSOC;
					break;
				case 'obj' :
					$mode = PDO::FETCH_OBJ;
					break;
				default :
					$mode = PDO::FETCH_OBJ;
			endswitch;
			if ($sql_parms === []) :
				$result = $db->query($sql);
				$rows = $result->fetchAll($mode);
				apcu_store($key, $rows, $ttl);
				return $rows;
			else :
				$stmt = $db->prepare($sql);
				$stmt->execute($sql_parms);
				$rows = $stmt->fetchAll($mode);
				apcu_store($key, $rows, $ttl);
				return $rows;
			endif;
		else :
			return $rows;
		endif;
	}

	function get_client_ip() {
		$ipaddress = '';
		if ($_SERVER['HTTP_CF_CONNECTING_IP'] ?? false) :
			$ipaddress = $_SERVER['HTTP_CF_CONNECTING_IP'];
		elseif ($_SERVER['HTTP_CLIENT_IP'] ?? false) :
			$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
		elseif ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? false) :
			$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
		elseif ($_SERVER['HTTP_X_FORWARDED'] ?? false) :
			$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
		elseif ($_SERVER['HTTP_FORWARDED_FOR'] ?? false) :
			$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
		elseif ($_SERVER['HTTP_FORWARDED'] ?? false) :
			$ipaddress = $_SERVER['HTTP_FORWARDED'];
		elseif ($_SERVER['REMOTE_ADDR'] ?? false) :
			$ipaddress = $_SERVER['REMOTE_ADDR'];
		else :
			$ipaddress = 'UNKNOWN';
	 	endif;
		return $ipaddress;
	}

	function get_ip_geo_data($ip) {
		//$ip_geo_data = @json_decode(file_get_contents("http://www.geoplugin.net/json.gp?ip=".$ip));
		$ip_geo_data = @json_decode(file_get_contents("http://www.ip-api.com/json/".$ip));
		return $ip_geo_data;
	}

	function get_cached_ip_geo_data($ip) {
		global $db;
		$stmt = $db->prepare("SELECT * FROM ip_data WHERE ip = :ip");
		$stmt->execute([
			':ip' => $ip,
		]);
		if ($row = $stmt->fetch(PDO::FETCH_OBJ)) :
			return $row;
		else :
			$ip_data = get_ip_geo_data($ip);
			if (($ip_data->status ?? '') == 'success') :
				$stmt = $db->prepare("INSERT INTO ip_data SET
					ip = :ip,
					country = :country,
					country_code = :country_code,
					region = :region,
					region_name = :region_name,
					city = :city,
					zip = :zip,
					latitude = :latitude,
					longitude = :longitude,
					timezone = :timezone,
					isp = :isp,
					org = :org,
					create_date = NOW(),
					update_date = NOW()
				");
				$data = [
					':ip' => clean_string($ip),
					':country' => clean_string($ip_data->country ?? ''),
					':country_code' => clean_string($ip_data->countryCode ?? ''),
					':region' => clean_string($ip_data->region ?? ''),
					':region_name' => clean_string($ip_data->regionName ?? ''),
					':city' => clean_string($ip_data->city ?? ''),
					':zip' => clean_string($ip_data->zip ?? ''),
					':latitude' => $ip_data->lat ?? 0,
					':longitude' => $ip_data->lon ?? 0,
					':timezone' => clean_string($ip_data->timezone ?? ''),
					':isp' => clean_string($ip_data->isp ?? ''),
					':org' => clean_string($ip_data->org ?? ''),
				];
				$stmt->execute($data);
				$ar_return = [];
				foreach ($data as $key => $value) :
					$new_key = str_replace(':', '', $key);
					$ar_return[$new_key] = $value;
				endforeach;
				return (object)$ar_return;
			endif;
		endif;
	}

	function analytics_limiter_exceeded(string $ip = '', string $category = '', string $action = '', int $limit = 20): bool {
		global $db;
		$sql = "SELECT COUNT(*) FROM analytics WHERE ip = :ip 
			AND category = :category 
			AND action = :action 
			AND create_date >= DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:00') 
			AND create_date < DATE_FORMAT(NOW() + INTERVAL 1 MINUTE, '%Y-%m-%d %H:%i:00')
		";
		$stmt = $db->prepare($sql);
		$stmt->execute([
			':ip' => $ip,
			':category' => $category,
			':action' => $action,
		]);
		$num_rows = $stmt->fetchColumn();
		if ($num_rows > $limit) :
			return true;
		else :
			return false;
		endif;
	}

	function set_analytics_reference(string|null $reference_type = null, int|null $reference_id = null) {
		$GLOBALS['analytics_reference_type'] = $reference_type;
		$GLOBALS['analytics_reference_id'] = $reference_id;
	}

	function analytics_tracking(string $category, string $action, string $label = '', string|null $reference_type = null, int|null $reference_id = null, int $limit_per_minute = 20, $extra_data = NULL) {
		if ($reference_type === '404') :
			return;
		endif;
		if (empty($label)) :
			$label = curr_page_url();
			$ar_spam = [
				'/wp-content/',
				'/admin/',
			];
			foreach ($ar_spam as $spam) :
				if (stripos($label, $spam) !== false) :
					return;
				endif;
			endforeach;
		endif;
		$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
		$crawlerDetect = new CrawlerDetect();
		if ($crawlerDetect->isCrawler($ua)) :
			return;
		endif;
		
		$ip = get_client_ip();
		if (analytics_limiter_exceeded($ip, $category, $action, $limit_per_minute)) :
			return;
		endif;


		$category = str_replace(' ', '_', strtolower($category));
		$action = str_replace(' ', '_', strtolower($action));

		$parser = Parser::create();
		$result = $parser->parse($ua);
		$browser = $result->ua->family;
		$os = $result->os->family;
		$device = $result->device->family;

		$detect = new MobileDetect;
		if ($detect->isMobile()) :
			$device_type = 'mobile';
		else :
			$device_type = 'desktop';
		endif;
		if ($detect->isTablet()) :
			$device_type = 'tablet';
		endif;
		$geo_data = get_cached_ip_geo_data($ip);

		global $db;
		$sql = "INSERT INTO analytics SET
			ip = :ip,
			user_agent = :user_agent,
			category = :category,
			action = :action,
			label = :label,
			reference_type = :reference_type,
			reference_id = :reference_id,
			browser = :browser,
			os = :os,
			device = :device,
			device_type = :device_type ,
			extra_data = :extra_data
		";
		$stmt = $db->prepare($sql);
		$stmt->execute([
			':ip' => $ip,
			':user_agent' => $ua,
			':category' => $category,
			':action' => $action,
			':label' => $label,
			':reference_type' => $reference_type,
			':reference_id' => $reference_id,
			':browser' => $browser,
			':os' => $os,
			':device' => $device,
			':device_type' => $device_type,
			':extra_data' => $extra_data,
		]);
	}

	function check_deeplink_redirect() {
		if (($_SESSION['logged_in'] ?? false) === false && !empty($_GET['deeplink'])) :
			$_SESSION['deeplink_redirect'] = curr_page_url();
			header("Location: " . APP_BASE_SECURE);
			exit();
		endif;
	}

	function clear_cloudflare_cache(array $files = null) {
		$message = '';
		$zone_id = APP_CF_ZONE_ID;
		$api_key = APP_CF_API_KEY;
		$email = APP_CF_EMAIL;

		try {
			$head = [];
			$head[] = 'Content-Type: application/json';
			$head[] = "X-Auth-Email: $email";
			//$head[] = "X-Auth-Key: $api_key";
			$head[] = "Authorization: Bearer $api_key";
			$head[] = 'cache-control: no-cache';

			$url = "https://api.cloudflare.com/client/v4/zones/$zone_id/purge_cache";

			if (!empty($files) && is_array($files)) :
				$purge = ['files' => $files];
			else :
				$purge = ['purge_everything' => true];
			endif;

			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
			curl_setopt($ch, CURLOPT_HTTPHEADER, $head);
			curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($purge));
			$result = curl_exec($ch);
			$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
			if ($http_code != 200) :
				$message = 'Error clearing CloudFlare cache - HTTP Code: ' . $http_code . ' Result: ' . $result;
				error_log($message);
			else :
				$message = 'CloudFlare Caches successfully cleared.';
			endif;
			curl_close($ch);
		} catch (Exception $e) {
			$message = 'Exception clearing CloudFlare cache - Error: ' . $e->getMessage();
			error_log($message);
		}
		return $message;
	}

	function clear_apc_cache() {
		apcu_clear_cache();
		return 'APCu Cache successfully cleared.';
	}

	function send_mail_queue($job_id, $subject, $to, $cc, $bcc, $body, $attachments = [], $replacements = [], $from = APP_MAIL_FROM, $reply_name = '', $reply_address = '') {
		global $db;
		$sql = "INSERT INTO mail_queue SET";
	}

	function send_mail($subject, $to, $cc, $bcc, $body, $attachments = [], $replacements = [], $from = APP_MAIL_FROM, $reply_name = '', $reply_address = '') {
		$mail = new PHPMailer\PHPMailer\PHPMailer();
		/*
		$mail->SMTPDebug = 3;    // Enable verbose debug output 1-4
		$mail->Debugoutput = function($str, $level) {
			write_log('E', 0, 'SMTP DEBUG - '."[SMTP-$level] $str");
		};
		*/
		$mail->isSMTP();
		$mail->Host = APP_MAIL_SMTP; // get_cfg_var('SMTP')  
		$mail->SMTPAuth = APP_MAIL_SMTP_AUTH; // false
		$mail->Username = APP_MAIL_SMTP_USERNAME; // ''
		$mail->Password = APP_MAIL_SMTP_PASSWORD; // ''
		$mail->SMTPSecure = APP_MAIL_SMTP_SECURE; // 'tls' or 'ssl'
		$mail->Port = APP_MAIL_SMTP_PORT; // get_cfg_var(smtp_port)       587 for secure
		$mail->SMTPOptions = APP_MAIL_SMTP_OPTIONS;
		if (!empty($reply_name) && !empty($reply_address)) :
			//$mail->clearReplyTos();
			//$mail->addReplyTo($reply_address, $reply_name);
			$mail->From = $reply_address;
			$mail->FromName = $reply_name;
		else :
			$mail->setFrom($from);
		endif;
		$ar_to = array_map('trim', explode(',', $to));
		foreach ($ar_to as $value) :
			$mail->addAddress($value); 
		endforeach;
		//$mail->addAddress('[email protected]');
		//$mail->addReplyTo('[email protected]', 'Information');
		//$mail->addCC('[email protected]');
		if (!empty($cc)) :
			$ar_cc = array_map('trim', explode(',', $cc));
			foreach ($ar_cc as $value) :
				$mail->addCC($value); 
			endforeach;
		endif;
		if (!empty($bcc)) :
			$ar_bcc = array_map('trim', explode(',', $bcc));
			foreach ($ar_bcc as $value) :
				$mail->addBCC($value); 
			endforeach;
		endif;
		foreach ($attachments as $file_name) :
			$mail->addAttachment($file_name);
		endforeach;
		foreach ($replacements as $placeholder => $value) :
			$body = str_ireplace('['.$placeholder.']', $value, $body);
		endforeach;
		$body = str_ireplace(PHP_EOL, '<br>', $body);
		//$mail->addAttachment('/tmp/image.jpg', 'new.jpg');
		$mail->isHTML(true);
		$mail->Subject = $subject;
		$mail_body = '<html><head>'.
					'<style>body {font-family:Arial, Helvetica, sans-serif; font-size:14px;} table {font-size:14px;}</style>'.
					'</head><body>'.
					$body.
					'</body></html>';
		$mail->Body = $mail_body;
		$mail_body_plain = str_ireplace('</p>', "\n\r", $mail_body);
		$mail_body_plain = str_ireplace('<br>', "\n\r", $mail_body_plain);
		$mail_body_plain = str_ireplace('<br />', "\n\r", $mail_body_plain);
		$mail->AltBody = strip_tags($mail_body_plain);
		
		if(!$mail->send()) :
			//echo '<p>Message could not be sent.</p>';
			//echo '<p>Mailer Error: '.$mail->ErrorInfo.'</p>';
			write_log('E', 0, 'Error sending mail - '.$_GET['IX'].' - '.$mail->ErrorInfo);
			global $app_mail_error_status;
			$app_mail_error_status = $mail->ErrorInfo;
			return false;
		else :
			//echo '<p>Message has been sent</p>';
			return true;
		endif;
	}

	function spam_count_exceeded($max_tries, $table) {
		global $db;
		$sql = "SELECT COUNT(*) AS ip_count FROM `{$table}` WHERE submitted_ip = " . $db->quote(get_client_ip());
		$result = $db->query($sql);
		$row = $result->fetch(PDO::FETCH_ASSOC);
		$tries = $row['ip_count'];
		if ($tries > $max_tries) :
			return true;
		else :
			return false;
		endif;
	}

	function snake_case($string) {
		$string = strtolower(preg_replace("/[^A-Za-z0-9]/", '_', $string));
		$string = preg_replace('~[_]+~', '_', $string);
		$string = trim($string, '_');
		return $string;
	}

	function process_payment($net_price, $cc_number, $cc_exp_month, $cc_exp_year, $cc_cvv, $invoice_num, $description, $cust_id, $first_name, $last_name, $street1, $street2, $city, $state, $zip, $phone, $email, $promo_code, $event_name = '', $event_start_time = '', $event_location = '', $sku = '', $qty = 0, $instruction = '', $ical = '') {
		$net_amt = strval($net_price);
		if ($net_price > 0.00) :
			if (APP_PAYMENT_PROCESSOR == 'authorize.net') :
				/* ----------------- Authorize.net processing ------------------*/
				require_once 'vendor/anet_php_sdk/AuthorizeNet.php'; // Make sure this path is correct.
				$transaction = new AuthorizeNetAIM(APP_ANET_LOGIN_ID, APP_ANET_TRANSACTION_KEY);
				$transaction->VERIFY_PEER = false;
				//$transaction->test_request = $app_authorize_net_test_request;
				//$transaction->amount = $app_authorize_net_amt_first_event;
				if (APP_ANET_SANDBOX === true) :
					$transaction->setSandbox(true);
					//$transaction->test_request = 'TRUE';
					//echo '<p>SANDBOX MODE</p> ';
				else :
					$transaction->setSandbox(false);
				endif;
				$transaction->amount = $net_amt;
				//$transaction->card_num = '4007000000027';
				$transaction->card_num = $cc_number;
				$transaction->exp_date = $cc_exp_month.'/'.$cc_exp_year;
				$transaction->card_code = $cc_cvv;
				$transaction->invoice_num = $invoice_num;
				$transaction->description = $description;
				$transaction->cust_id = $cust_id;
				$transaction->first_name = $first_name;
				$transaction->last_name = $last_name;
				$transaction->address = $street1.' '.$street2;
				$transaction->city = $city;
				$transaction->state = $state;
				$transaction->zip = $zip;
				$transaction->phone = $phone;
				$transaction->email = $email;
				$transaction->customer_ip = get_client_ip();
				//var_dump($transaction);
				$response = $transaction->authorizeAndCapture();
				if ($response->approved) :
					$result = true;
				else :
					//$ar_err['cc_number'] = 'Credit card transaction failed! <br />'.$response->error_message;
					//$ar_err['cc_number'] = 'Credit card transaction failed!';
					write_log('E', 0, 'Credit card transaction failed (signup_form) - '.$first_name.' '.$last_name.' '.substr($cc_number, 0, 4).str_repeat('*', strlen($cc_number) - 8).substr($cc_number, -4).' - '.$response->response_reason_text);
					$result = false;
					//$err_flag = true;
				endif;
				/* -------------------------------------------------------------*/
			elseif (APP_PAYMENT_PROCESSOR == 'braintree') :
				/* -------------- Braintree payment processing -----------------*/
				require_once('vendor/braintree_php/lib/Braintree.php');
				Braintree_Configuration::environment(APP_BRAIN_ENVIRONMENT);
				Braintree_Configuration::merchantId(APP_BRAIN_MERCHANT_ID);
				Braintree_Configuration::publicKey(APP_BRAIN_PUBLIC_KEY);
				Braintree_Configuration::privateKey(APP_BRAIN_PRIVATE_KEY);
				$payment_method_token = null;
				$collection = Braintree_Customer::search([
					Braintree_CustomerSearch::creditCardNumber()->is($cc_number),
					Braintree_CustomerSearch::firstName()->is($first_name),
					Braintree_CustomerSearch::lastName()->is($last_name),
				]);
				//error_log('count($collection) '.count($collection));
				$cust_count = 0;
				foreach ($collection as $customer) :
					$cust_count ++;
					$payment_method_token = $customer->creditCards[0]->token;
					//error_log(print_r($customer, true));
				endforeach;
				if ($cust_count < 1) :
					$result_braintree = Braintree_Customer::create([
						'firstName' => $first_name,
						'lastName' => $last_name,
						'creditCard' => [
							'number' => $cc_number,
							'expirationMonth' => $cc_exp_month,
							'expirationYear' => $cc_exp_year,
							'cvv' => $cc_cvv,
							'billingAddress' => [
								'streetAddress' => $street1.' '.$street2,
								'locality' => $city,
								'region' => $state,
								'postalCode' => $zip,
							]
						]
					]);
					//error_log(print_r($result_braintree, true));
					if ($result_braintree->success) :
						$payment_method_token = $result_braintree->customer->creditCards[0]->token;
					endif;
				else :
					foreach ($collection as $customer) :
						$payment_method_token = $customer->creditCards[0]->token;
						break;
					endforeach;
				endif;
				$response = Braintree_Transaction::sale([
					'amount' => $net_amt,
					'paymentMethodToken' => $payment_method_token,
					'options' => [
						'submitForSettlement' => True
					]
				]);
				//error_log(print_r($response, true));
				if ($response->success) :
					// See $response->transaction for details
					$response->authorization_code = $response->transaction->processorAuthorizationCode;
					$response->transaction_id = $response->transaction->id;
					$result = true;
				else :
					// Handle errors
					write_log('E', 0, 'Credit card transaction failed (signup_form) - '.$first_name.' '.$last_name.' '.substr($cc_number, 0, 4).str_repeat('*', strlen($cc_number) - 8).substr($cc_number, -4).' - '.$response->message);
					$result = false;
				endif;				
				/* -------------------------------------------------------------*/
			else :
				write_log('E', 0, 'Credit card transaction failed (signup_form) - '.$first_name.' '.$last_name.' '.substr($cc_number, 0, 4).str_repeat('*', strlen($cc_number) - 8).substr($cc_number, -4).' - '.'No payment processor configured!');
				$result = false;
			endif;
		else :
			$response = new stdClass();
			$response->authorization_code = 'N/A';
			$response->transaction_id = 'N/A';
			$result = true;
		endif;
		if ($result === true) :
			//***************** mail using PHPMailer *****************
			$mail_subject = 'Thought Gallery Order Confirmation';
			$mail_to = $email;
			$mail_cc = '';
			$mail_bcc = APP_MAIL_BCC;
			$mail_body = '<html><head>'.
						'<style>body {font-family:Arial, Helvetica, sans-serif; font-size:14px;} table {font-size:14px;}</style>'.
						'</head><body>'.
						'<img src="' . APP_BASE_USER_SITE_SECURE . 'img/thoughtgallery_logo_new_white.png" width="375" style="display:block; width:375px; height:auto; border:0;" /><br><br>'.
						'Dear '.$first_name.' '.$last_name.', <br><br>'. 
						'Thank you for signing up for Thought Gallery\'s ' . $event_name . '.  We look forward to seeing you on ' . $event_start_time .
						'. ' . (empty($instruction) ? 'Give your name when you arrive for admission.' : $instruction) .
						' <br><br>Here are your transaction details: <br><br>'.
						'<table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border: 1px solid #000000;">'.
						(!empty($event_name) ? '<tr><td><b>Event Name:</b> </td><td>'.$event_name.'</td></tr>' : '') .
						(!empty($event_start_time) ? '<tr><td><b>Start Time:</b> </td><td>'.$event_start_time.'</td></tr>' : '') .
						(!empty($event_location) ? '<tr><td><b>Location:</b> </td><td>'.$event_location.'</td></tr>' : '') .
						(!empty($sku) ? '<tr><td><b>Ticket Type:</b> </td><td>'.$sku.'</td></tr>' : '') .
						(!empty($qty) ? '<tr><td><b>Quantity:</b> </td><td>'.$qty.'</td></tr>' : '') .
						//'<tr><td><b>Purchase Description:</b> </td><td>'.$description.'</td></tr>'.
						'<tr><td><b>Attendee Name:</b> </td><td>'.$first_name.' '.$last_name.'</td></tr>'.
						'<tr><td><b>Purchase Date:</b> </td><td>'.date('m/d/Y').'</td></tr>'.
						'<tr><td><b>Amount:</b> </td><td>'.number_format($net_amt, 2).'</td></tr>'.
						'<tr><td><b>Credit Card:</b> </td><td>'.str_pad(substr($cc_number, strlen($cc_number) -4), 12, '*', STR_PAD_LEFT).'</td></tr>'.
						//'<tr><td><b>Authorization Code:</b> </td><td>'.$response->authorization_code.'</td></tr>'.
						//'<tr><td><b>Transaction ID:</b> </td><td>'.$response->transaction_id.'</td></tr>'.
						//'<tr><td><b>Promotion Code:</b> </td><td>'.$promo_code.'</td></tr>'.
						//'<tr><td><b>Payment submitted from IP address:</b> </td><td>'.get_client_ip().'</td></tr>'.
						'</table><br>'.
						$ical . '<br><br>' .
						'We are excited to host you. If you have any questions feel free to reach out. <br><br>'.
						'Best, <br>'.
						'The Thought Gallery Team <br>'.
						'<a href="mailto:'.APP_MAIL_REPLY.'">'.APP_MAIL_REPLY.'</a> <br>'.
						'<a href="'.APP_BASE_USER_SITE_SECURE.'">'.APP_BASE_USER_SITE_SECURE.'</a> <br>'.
						'</body></html>' ;
			$mail_body = str_ireplace('<td>', '<td valign="top" style="vertical-align: top; border: 1px solid #000; padding: 4px;">', $mail_body);

			if (send_mail($mail_subject, $mail_to, $mail_cc, $mail_bcc, $mail_body)) :
				//$html .= '<p>Message has been sent to '.$mail_to.'</p>';
			else :
				//$html .= '<p>Error sending mail to '.$mail_to.' - '.$app_mail_error_status.'</p>';
			endif;
			//**** ************************** *****
		endif;
		return array($result, $response);
	}
	
	function process_refund($net_price, $trans_id, $card_num) {
		$net_amt = strval($net_price);
		if ($net_price > 0.00) :
			if (APP_PAYMENT_PROCESSOR == 'authorize.net') :
				/* ----------------- Authorize.net processing ------------------*/
				require_once 'vendor/anet_php_sdk/AuthorizeNet.php'; // Make sure this path is correct.
				$transaction = new AuthorizeNetAIM(APP_ANET_LOGIN_ID, APP_ANET_TRANSACTION_KEY);
				$transaction->VERIFY_PEER = false;
				//$transaction->test_request = $app_authorize_net_test_request;
				//$transaction->amount = $app_authorize_net_amt_first_event;
				if (APP_ANET_SANDBOX === true) :
					$transaction->setSandbox(true);
					//$transaction->test_request = 'TRUE';
					//echo '<p>SANDBOX MODE</p> ';
				else :
					$transaction->setSandbox(false);
				endif;
				$transaction->amount = $net_amt;
				//$transaction->card_num = '4007000000027';
				//var_dump($transaction);
				$response = $transaction->credit($trans_id, $net_amt, $card_num);
				if ($response->approved) :
					$result = true;
				else :
					//$ar_err['cc_number'] = 'Credit card transaction failed! <br />'.$response->error_message;
					//$ar_err['cc_number'] = 'Credit card transaction failed!';
					write_log('E', 0, 'Credit card refund transaction failed (refund_form) - '.$first_name.' '.$last_name.'  Trans ID: '.$trans_id.'  Amt: '.$net_amt.' - '.PHP_EOL.print_r($response, true));
					$result = false;
					//$err_flag = true;
				endif;
				/* -------------------------------------------------------------*/
			elseif (APP_PAYMENT_PROCESSOR == 'braintree') :
				/* -------------- Braintree payment processing -----------------*/
				require_once('vendor/braintree_php/lib/Braintree.php');
				Braintree_Configuration::environment(APP_BRAIN_ENVIRONMENT);
				Braintree_Configuration::merchantId(APP_BRAIN_MERCHANT_ID);
				Braintree_Configuration::publicKey(APP_BRAIN_PUBLIC_KEY);
				Braintree_Configuration::privateKey(APP_BRAIN_PRIVATE_KEY);
				$response_refund = Braintree_Transaction::refund($trans_id);  //*** First try a refund if transaction settled ***
				//error_log(print_r($response, true));
				if ($response_refund->success) :
					$result = true;
				else :
					$response_void = Braintree_Transaction::void($trans_id);  //*** Next try a void if transaction not settled yet ***
					if ($response_void->success) :
						$result = true;
					else :
						write_log('E', 0, 'Credit card refund transaction failed (refund_form) - '.$first_name.' '.$last_name.'  Trans ID: '.$trans_id.'  Amt: '.$net_amt.' - '.PHP_EOL.print_r($response_refund->errors, true).PHP_EOL.print_r($response_void->errors, true));
						$result = false;
					endif;
				endif;				
				/* -------------------------------------------------------------*/
			else :
				write_log('E', 0, 'Credit card refund transaction failed (signup_form) - '.$first_name.' '.$last_name.'  Trans ID: '.$trans_id.'  Amt: '.$net_amt.' - '.'No payment processor configured!');
				$result = false;
			endif;
		else :
			$result = false;
		endif;
		return [$result, $response];
	}
	
	function cc_images() {
		$html = '';
		if (count(APP_CREDIT_CARDS) > 0) :
			$html .= '<div class="credit_card_set">';
			foreach (APP_CREDIT_CARDS as $value) :
				$html .= '<img class="credit_card_logo" src="img/cc_'.$value.'.png" /> ';
			endforeach;
			$html .= '</div>';
		endif;
		return $html;
	}

Youez - 2016 - github.com/yon3zu
LinuXploit