<?php
/**
## Version: 2.0 -- Date: 11-JUN-2017 -- Jorge Jardim -- Original coding. This class is intended to house system methods to be used to simplify Sable functions.
## Version: 2.1 -- Date: 05-SEP-2019 -- Jorge Jardim -- Added version information and revision history.
**/	
class SableSystem
{	
	// constructor method
	function __construct(){}
	// desctructor method
	public function __destruct(){}	
	
	public static function onRootPage()
	{
		$res = (self::currentDir()==="/") && (self::currentPage()==="index.php")?true:false;
		return $res;
	}
	
	public static function currentPage()
	{
		/** check for the use of MAGIC CONSTANTS for this value **/
		$ind = explode("/", $_SERVER['PHP_SELF']);
		return $ind[sizeof($ind) - 1];	
	}
	
	public static function currentDir()
	{
		/** 
			This function returns the URL path e.g. 
			For the actual working path (e.g. /home/jonacspz/public_html/macprop/includes) use the __DIR__ magic constant.
		**/
		$ind = explode("/", $_SERVER['PHP_SELF']);
		$pth = $ind[sizeof($ind) - 2];
		$res = empty($pth)?"/":$pth;
		return $res;
	}
	
	/*
	Function 'logout' removes the session from memory and deletes the relevant detail from
	the database. After closing the session the user gets redirected to the login page
	where they must login again to access data.
	*/	
	public static function logout()
	{	
		global $sableDB;
		//if (self::loggedIn())
		if (isset($_SESSION['user']['user_code']))
		{
			$sableDB->updateQuery('delete from member where user_code = "'.$_SESSION['user']['user_code'].'"');
			unset($_SERVER['argv']);
			unset($_SESSION);
			try 
			{
				session_unset();
				session_destroy();
			}
			//catch exception
			catch(Exception $e) 
			{
			  echo 'Exception performing log out: ' .$e->getMessage();
			}
		}
	}//function logout()
	
	public static function loggedIn()
	{
		if (empty($_SESSION['user']['user_code']) || !isset($_SESSION['user']['user_code']))
		{
			global $sableDB;
			$result = $sableDB->getValue('select count(*) from member where session = "'.session_id().'"');
		}
		else 
		{
			$sess = isset($_SESSION['ID']) ? $_SESSION['ID'] : 0;
			$result = ($sess !== session_id())?0:1;
		}
		return $result;
	}
	
	public static function checkSession()
	{
		/*
		Sessions are only checked when a user has been successfully logged in.
		5-DEC-2020 - J. Jardim:
		Sessions may have exceptions (such as return URL's from external gateways) and so a new
		CONSTANT array is defined in the config file for such exceptions. If the URL is excepted,
		do not check the session.
		*/

		$excepted = 0;
		$exceptions = unserialize(SABLE_SESSION_EXCEPTIONS);

		foreach($exceptions as $ex)
		{
			$str1 = rtrim($_SERVER['REQUEST_URI'], '/');
			$str2 = rtrim($ex['uri'], '/');
	
			if ($str1 == $str2)
			{
				$pth1 = rtrim($_SESSION['caller']['args'][0], '/');
				$pth2 = rtrim($ex['path'], '/');
			
				if (($pth1 == $pth2) )
				{
					$excepted = 1;
					break;
				}
			}
		}
		
		if (!$excepted)
		{
			try
			{
				$result = 0;
				$try = self::loggedIn() && ($_SESSION['caller']['args'][1] ?? 0);
				if (self::loggedIn())
				{
					//check if we're being logged off:
					if ($_SESSION['caller']['args'][1]==='logout'){self::logout();}
					
					$len = sizeof($_SESSION['caller']['args'])-1;
					$sId = $_SESSION['caller']['args'][$len];//session id is always the last argument in the array. 
					$id  = session_id();//sableEncode($_SESSION['ID']);
					
					if ($sId === $id)
					{
						$result = 1;
					}
					else
					{
						self::logout();
						if (SABLE_UNDER_MAINTENANCE === 1)
						{
							self::logout();
							SableGeneralFunctions::divertFromPage('/maintenance', '');
						}
						else
						{
							$msg = 'This session has been corrupted. For security reasons, please log in again.';
							SableGeneralFunctions::divertFromPage('signin', $msg);
						}
					}
				} 
				else 
				{
					$result = 1;
					//SableGeneralFunctions::divertFromPage('login/', 'Please sign in to continue.');
				}
				return $result;
			} catch (Exception $e) {die('Caught exception: '.$e->getMessage());}
		}
	}	
	
	public static function addSession($url)
	{
		$sId = self::loggedIn()?'+'.session_id():''; //only add the session id if we're logged in
		$url = substr($url, -1)=='/'?$url.=$sId:$url.=$sId; //add the session id as the second argument
		return $url;
	}
	
	/*
	Function 'VerifySession' will check the time the user has been logged in (this information is stored in 
	table 'member' as soon as a session is created). If logged in for longer than SABLE_TIMEOUT minutes it will log 
	them out and they will need to log in again.
	*/
	public static function verifySession()
	{
		global $sableDB;
		$query = "select count(*) from member where session = '".session_id()."'";
		$numSessionRecs = $sableDB->getValue($query);
		
		$numUserRecs = 0;
		if (isset($_SESSION['user']['user_email']))
		{
			$query = "select count(*) from member where user_email = '".$_SESSION['user']['user_email']."'";
			$numUserRecs = $sableDB->getValue($query);
		} 
						
		if ((!$numSessionRecs)&&(!$numUserRecs))
		{	
			//absolutely no session exists for this user
			return false; //nothing to verify
		}
		else
		{
			if ((!$numSessionRecs)&&($numUserRecs))
			{
				//user already has another session active
				//clear any exising session for the user - we only allow 1 connection per user:
				$query = "delete from member 
				where (user_email = '".$_SESSION['user']['user_email']."') and (session <> '".session_id()."')";
				$sableDB->updateQuery($query);
				return true;
			}
			else
			{
				if (($numSessionRecs)&&($numUserRecs))//current user session still active
				{
					$access_time=strtotime($sableDB->getValue("select TIMESTAMP(access_time) from member where session = '".session_id()."'"));				
					$time_now = time();
					$time_diff = round(($time_now - $access_time)/60); //no. minutes elapsed
					if ($time_diff > SABLE_TIMEOUT)
					{
						SableGeneralFunctions::revertToLoginPage('Your session has timed out. You will need to log in again.');
					}
					else
					{
						//refresh the user's activity time:
						$query = 'update member set access_time = "'.date('Y-m-d H:i:s').'" where session = "'.session_id().'"';
						$sableDB->updateQuery($query);
						return true;
					}
				} //if (($numSessionRecs>0)&&($numUserRecs>0))
			} //if (($numSessionRecs==0)&&($numUserRecs>0))
		} //if (($numSessionRecs==0)&&($numUserRecs==0))
	} //function VerifySession()
	
	public static function setSessionPrefix() //should become deprecated.
	{
		return SABLE_WORKING_PATH;
	}
	
	public static function setOgImage($image)
	{
		self::convertURLPathToWorkPath($image);
		$pref = strpos($image, SABLE_WORKING_PATH);
		$image = SABLE_URL_ROOT.ltrim($image, '/');
		self::convertURLPathToWorkPath($image);
		$tmp = getimagesize(SABLE_WORKING_PATH.$image);
		return ((floatval($tmp[0]) >= 200) && (floatval($tmp[1]) >= 200)) ? $image : SABLE_OG_LOGO; //must be greater than 200px x 200px
	}	
	
	public static function sableEncode($value)
	{
		/*
			we should use this method to alter the usual sableEncode as follows:
			1.	get the base 64 encoded string: $temp = sableEncode($value);
			1.	replace numerics with characters, e.g. 2 = F, 3, = h (case sensitive), etc.
			2.	swop lower case letters with upper case, and the upper case letters with lower case
			3.	replace double-digits (such as ==) with a specific value, e.g. replace "==" with "Y"
			4.  replace triple-digits with a value also (just in case...)
		*/
		if (SABLE_ENABLE_ENCRYPTION===true) return base64_encode($value); else return $value;
	}
	
	public static function sableDecode($value)
	{
		if (SABLE_ENABLE_ENCRYPTION===true) return base64_decode($value); else return $value;
	}
	
	public static function getCallPageLink($url)
	{
		$sessionParam = self::loggedIn()?'+'.session_id():''; //xxxxxx+
		$url = substr($url, -1)==='/'?$url.='index.php':$url;
		$url .= $sessionParam; //session id must come last to allow for the url and its arguments first.
		return 'index.php?'.self::sableEncode($url);
	}
	
	public static function printCallPageLink($url){echo(self::getCallPageLink($url));}
	
	public static function getAnchorLink($url, $anchor)
	{
		$ln = self::getCallPageLink($url);
		if (substr($ln, -1)==='/') $ln = copy($ln, 0, strlen($ln)-1);
		return $ln.$anchor;
	}
	
	public static function printAnchorLink($url, $anchor){echo(self::getAnchorLink($url, $anchor));}
	
	public static function ajaxResponse($redirect, $location, $message, $msgType, $displayField, $isValueField, $focusField)
	{
		/*
			We always get an object now as a response. This object will tell us how to handle the message:
			"redirect" = true if the user should be redirected, else false
			"location" = if "redirect" == true, then this is the page to redirect to
			"message" = the message to be displayed
			"msgType" = how the message should be displayed -: 
						0 = alert only
						1 = show in field ID only
						2 = alert & show in field (i.e. 0, and 1 above combined)					
						3 = append to field ID
						4 = alert & append to field ID
						5 = error message
						6 = Same as 5 but for the new error message (JS) format
						7 = success message					
						8 = nicEdit TextArea Field
			"displayField" = Depending on "msgType", display the message in this field (id)
			"isValueField" = should be true if the field uses "value" for content else will use "innerHTML" for content.
			"focusField" = the field ID that should receive focus.		
		*/
		$data['redirect']     = $redirect;
		
		//we need to check if there is an anchor link in the url:
		$hashtag = strpos($location, '#');
		if($hashtag>0)
		{
			$data['location'] = self::getCallPageLink(substr($location, 0, $hashtag));
			$data['location'] .= substr($location, $hashtag);
		}
		else
		{
			$data['location'] = self::getCallPageLink($location);
		}
		
		$data['message']      = $message;
		$data['msgType']      = $msgType;
		$data['displayField'] = $displayField;
		$data['isValueField'] = $isValueField;
		$data['focusField']   = $focusField;
		exit(json_encode($data));
	}
	
	public static function ajaxSEOFriendlyResponse($redirect, $location, $message, $msgType, $displayField, $isValueField, $focusField)
	{
		/*
			We always get an object now as a response. This object will tell us how to handle the message:
			"redirect" = true if the user should be redirected, else false
			"location" = if "redirect" == true, then this is the page to redirect to
			"message" = the message to be displayed
			"msgType" = how the message should be displayed -: 
						0 = alert only
						1 = show in field ID only
						2 = alert & show in field (i.e. 0, and 1 above combined)					
						3 = append to field ID
						4 = alert & append to field ID
						5 = error message
						6 = Same as 5 but for the new error message (JS) format
						7 = success message
						8 = nicEdit TextArea Field						
			"displayField" = Depending on "msgType", display the message in this field (id)
			"isValueField" = should be true if the field uses "value" for content else will use "innerHTML" for content.
			"focusField" = the field ID that should receive focus.		
		*/
		$data['redirect']     = $redirect;
		$data['location']     = $location;
		$data['message']      = $message;
		$data['msgType']      = $msgType;
		$data['displayField'] = $displayField;
		$data['isValueField'] = $isValueField;
		$data['focusField']   = $focusField;
		exit(json_encode($data));
	}		
	
	public static function ajaxDisplayMessage($message){self::ajaxResponse(false, '', $message, 0, '', '', '');}
	
	public static function ajaxValidationError($message, $field){self::ajaxResponse(false, '', $message, 5, $field, false, $field);}
	
	public static function ajaxFillField($message, $field){self::ajaxResponse(false, '', $message, 1, $field, false, '');}
	public static function ajaxFillTag($message, $field){self::ajaxResponse(false, '', $message, 1, $field, true, $field);}
	
	public static function ajaxPageRedirect($url, $message='')
	{
		$msgType = empty($message)?'':0;
		self::ajaxResponse(true, $url, $message, $msgType, '', '', '');
	}
	
	public static function ajaxSEOFriendlyPageRedirect($url, $message='')
	{
		$msgType = empty($message)?'':0;
		self::ajaxSEOFriendlyResponse(true, $url, $message, $msgType, '', '', '');
	}	
	
	public static function callPage($url)
	{
		header('Location: '.self::getCallPageLink($url));
		exit();
	}		
	
	/*
	Function 'sendEmail' generates the required headers and sends and email according to the given parameters. The 'content' parameter is the actual content of the email that should be constructed by the calling code.
	*/
	public static function sendEmail($email_to, $subject, $content, $cc="")
	{
		if (SABLE_DEMO_SYSTEM===true) 
		{
			$recipients = SABLE_DEMO_RECIPIENTS; 
			$internal = ['michael@jonasolutions.co.uk'];
			if (in_array($email_to, $internal)) $recipients = $email_to;
			$sub = SABLE_DEMO_EMAIL_PREFIX.$subject;		
		}
		else 
		{
			$recipients = $email_to;
			$sub = $subject;
		}
		
		//$sub = str_replace('&#233;', 'é', $sub);
        $sub = SableGeneralFunctions::specialCharsToHTMLEncode($sub);
		
		$coyTitle = SableGeneralFunctions::setCoyTitle();
		$emailLogoURL	= isset($_SESSION['site'])?$_SESSION['site']['site_url'].$_SESSION['site']['site_email_logo']:SABLE_EMAIL_LOGO;		
		$senderSite 	= isset($_SESSION['site'])?$_SESSION['site']['site_description']:SABLE_SENDER_NAME;
		$noReplyEmail	= isset($_SESSION['site'])?SABLE_SENDER_PREFIX.'@'.$_SESSION['site']['site_domain']:SABLE_SENDER_EMAIL;	
		
		$headers = "From: ".utf8_decode($senderSite)." <" . $noReplyEmail . ">\r\n";
		$headers .= "Reply-To: ".$noReplyEmail."\r\n";
		$headers .= "CC: ".$cc."\r\n";
		$headers .= "MIME-Version: 1.0\r\n";
		$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";		
		/*
			Each theme should have its own email styling which needs to be stored in the theme style folder as 
		*/
		if (is_file(SABLE_THEME_EMAIL_STYLE))
		{
			$emailContent = file_get_contents(SABLE_THEME_EMAIL_STYLE);
			$emailContent = str_replace('<%EMAIL_COMPANY%>', $coyTitle, $emailContent);
			$emailContent = str_replace('<%EMAIL_LOGO%>', SABLE_EMAIL_LOGO, $emailContent);
			$emailContent = str_replace('<%EMAIL_CONTENT%>', $content, $emailContent);
		}
		else $emailContent = $content;

//		return mail($recipients, $sub, $emailContent, $headers, '-f '.$noReplyEmail);
		return mail($recipients, utf8_decode($sub), $emailContent, $headers);
	
	} /* function sendEmail() */
	
	public static function sendEmailWithAttachment($email_to, $subject, $content, $file, $cc="")
	{
		$eol = PHP_EOL;
		// a random hash will be necessary to send mixed content
		$separator		= md5(time());
		$mime_boundary 	= "==Multipart_Boundary_x".$separator."x";
		$filetype   	= "application/pdf"; // type
		
		if (SABLE_DEMO_SYSTEM===true) 
		{
			$recipients = SABLE_DEMO_RECIPIENTS; 
			$internal = ['michael@jonasolutions.co.uk'];
			if (in_array($email_to, $internal)) $recipients = $email_to;			
			$sub = SABLE_DEMO_EMAIL_PREFIX.$subject;		
		}
		else 
		{
			$recipients = $email_to;
			$sub = $subject;
		}
		
		//$sub = str_replace('&#233;', 'é', $sub);
        $sub = SableGeneralFunctions::specialCharsToHTMLEncode($sub);
		
		$coyTitle = SableGeneralFunctions::setCoyTitle();
		
		$emailLogoURL	= isset($_SESSION['site'])?$_SESSION['site']['site_url'].$_SESSION['site']['site_email_logo']:
													SABLE_EMAIL_LOGO;		
		$senderSite 	= isset($_SESSION['site'])?$_SESSION['site']['site_description']:SABLE_SENDER_NAME;
		$noReplyEmail	= isset($_SESSION['site'])?SABLE_SENDER_PREFIX.'@'.$_SESSION['site']['site_domain']:SABLE_SENDER_EMAIL;	

		$attachment = chunk_split(base64_encode(file_get_contents($file)));
		
		// main header
		$headers = "From: ".utf8_decode($senderSite)." <" . $noReplyEmail . ">\r\n";
		$headers .= "Reply-To: ".$noReplyEmail."\r\n";
		$headers .= "CC: ".$cc."\r\n";
		$headers .= "MIME-Version: 1.0\r\n";
		$headers .= "Content-Type: multipart/mixed; boundary=\"".$mime_boundary."\"";
		
		if (is_file(SABLE_THEME_EMAIL_STYLE))
		{
			$emailContent = file_get_contents(SABLE_THEME_EMAIL_STYLE);
			$emailContent = str_replace('<%EMAIL_COMPANY%>', $coyTitle, $emailContent);
			$emailContent = str_replace('<%EMAIL_LOGO%>', SABLE_EMAIL_LOGO, $emailContent);
			$emailContent = str_replace('<%EMAIL_CONTENT%>', $content, $emailContent);
		}
		else $emailContent = $content;		
		
		// no more headers after this, we start the body! //
		/**
		$body = "--".$mime_boundary.$eol;
		$body .= "Content-Transfer-Encoding: 7bit".$eol.$eol;
		//$body .= "This is a MIME encoded message.".$eol;
		$body .= $emailContent.$eol;
		**/
		
		// message
		$body = '';
		$body .= "--".$mime_boundary.$eol;
		$body .= "Content-Type: text/html; charset=\"iso-8859-1\"".$eol;
		$body .= "Content-Transfer-Encoding: 7bit".$eol.$eol;
		$body .= $emailContent.$eol;	
		
		// attachment
		$attachmentFileName = basename($file);
		$body .= "--".$mime_boundary.$eol;
		$body .= "Content-Type: ".$filetype.";".$eol;
		$body .= " name=\"".$attachmentFileName."\"".$eol; 
		$body .= "Content-Disposition: attachment".$eol;
		$body .= " filename=\"".$attachmentFileName."\"".$eol; 
		$body .= "Content-Transfer-Encoding: base64".$eol.$eol;		
		$body .= $attachment.$eol.$eol;
		$body .= "--".$mime_boundary."--".$eol;
		
		// send message
		return mail($recipients, utf8_decode($sub), $body, $headers, '-f '.SABLE_SENDER_EMAIL);	
	}
	
	public static function getParameterLink($dir, $file="index.php", $params = '', $seo = false)
	{
		$rootLink = '/'; //SABLE_URL_ROOT;
		if ($seo)
		{
			$ta = explode('/', $dir);
			if ($seo)
				return $rootLink.$ta[sizeof($ta) - 1].'+'.$params.'+'.session_id();
			else
				return $rootLink.$ta[sizeof($ta) - 1].'/'.$file.'+'.$params.'+'.session_id();
		}
		else
			return 'index.php?'.$rootLink.$dir.'/'.$file.'+'.$params.'+'.session_id();
	}
	
	public static function printParameterLink($dir, $file="index.php", $params = '')
	{
		echo(self::getParameterLink($dir, $file, $params));
	}
	
	public static function getLinkPath($urlPath, $workPath, $link, $seo = false)
	{
		$rootLink = '/';
		$extPos = strpos($link, '.php');
		$l = null;
		if ((substr($link, -1) !== '/') && ($extPos === false))
		{	
			/*
			if here then the link provided does not end with a '/', nor does it have a .php extension.
			*/						
			if ($seo)	
			{			
				$l = ($l === 'index')?'':$link; //'home' could also be ''...			
				$url = $rootLink.$l;
			}
			else
			{
//				$uLink = $urlPath.$l.'.php';
				$l = ($l === 'index')?'':$link; //'home' could also be ''...
				$uLink = empty($l) ? $urlPath.'index.php' : $l;
				$url = self::getCallPageLink($uLink);
			}
		}
		else
		{
			/*
			if here then the link provided does end with a '/', or it has a .php extension.
			*/		
			if ($seo)	
			{
				$uLink = $urlPath.$link;
				$ta = explode('/', trim($uLink));
				$idx = empty($ta[sizeof($ta) - 1]) ? sizeof($ta) - 2 : sizeof($ta) - 1;
				$url = $rootLink.$ta[$idx]; //self::getCallPageLink(SABLE_URL_ROOT.$ta[$idx];);
			}
			else
			{	
				$uLink = $urlPath.$link;
				$url = self::getCallPageLink($uLink);
			}
		}
		
		if ($seo)
		{
			$result = $url;
		}
		else
		{
			$wLink = $workPath.$link;
			$result = file_exists($wLink)?$url:'/';
		}
		return $result;
	}
	
	public static function getThemeImgPath($imageName){return SABLE_THEME_IMAGES_PATH.$imageName;}
	public static function printThemeImgPath($imageName){echo(self::getThemeImgPath($imageName));}
	
	public static function getThemeImgWorkPath($imageName){return "/".SABLE_THEME_PATH.SABLE_THEME_NAME."/images/".$imageName;}
	public static function printThemeImgWorkPath($imageName){echo(self::getThemeImgWorkPath($imageName));}
	
	public static function getThemeLinkPath($link, $seo = false){return self::getLinkPath('/'.SABLE_THEME_PATH.SABLE_THEME_NAME.'/', SABLE_THEME_WORKING_PATH, $link, $seo);}
	public static function printThemeLinkPath($link, $seo = false){echo(self::getThemeLinkPath($link, $seo));}
	
	//public static function getSableImgPath($imageName){return SABLE_URL_ROOT.'media/images/'.$imageName;}
	public static function getSableImgPath($imageName){return '/media/images/'.$imageName;}
	public static function printSableImgPath($imageName){echo(self::getSableImgPath($imageName));}
	
	public static function getSableImgWorkPath($imageName){return SABLE_WORKING_PATH.'media/images/'.$imageName;}
	public static function printSableImgWorkPath($imageName){echo(self::getSableImgWorkPath($imageName));}
	
	public static function getSableLinkPath($link, $seo = false){return self::getLinkPath('/', SABLE_WORKING_PATH, $link, $seo);}
	public static function printSableLinkPath($link, $seo = false){echo(self::getSableLinkPath($link, $seo));}	
	
	public static function convertURLPathToWorkPath(&$urlPath)
	{
		if (strpos($urlPath, SABLE_URL_ROOT) !== false)
		{
			$tmp = str_replace(SABLE_URL_ROOT, '/', $urlPath);
			$tmp = str_replace('//', '/', $tmp);
			$urlPath = $tmp;
			//$urlPath = str_replace(SABLE_URL_ROOT, SABLE_WORKING_PATH, $urlPath);
			//$urlPath = str_replace(SABLE_WORKING_PATH, '/', $urlPath);
		}
	}
	
	public static function convertWorkPathToURLPath(&$urlPath)
	{
		if (strpos($urlPath, SABLE_WORKING_PATH) !== false)
		{
			$urlPath = str_replace(SABLE_WORKING_PATH, SABLE_URL_ROOT, $urlPath);
		}
	}
	
	public static function extractTagLines_1($constantArray)
	{
		$tagLines = json_decode($constantArray);
		$html = '';
		if (isset($tagLines)) foreach($tagLines as $tag) $html .= $tag.PHP_EOL;
		echo $html;
	}
	
	public static function extractTagLines($arr)
	{
		//SableDebug::debug($arr);
		$shortEndTags = array('link', 'meta', 'img');
		$dt = unserialize($arr);
		$in		= self::loggedIn();
		$ok		= is_array($dt) && isset($dt) && !empty($dt);
		$html	= '';
		if ($ok) 
		{
			foreach ($dt as $itm) 
			{
				if (is_array($itm))
				{
					$html	= '';
					$use	= 0; //ignore by default
					//if 'cond' is not specified, then by default we accept it is used when either logged in or not.
					$flg = isset($itm['cond']) && !empty($itm['cond']) ? intval($itm['cond']) : 0;
					switch ($flg)
					{
						case 0: 	$use = 1;		break; //use whether logged in or not, whether in debug or not
						case 1: 	$use = !$in;	break; //use when not logged in only, whether in debug or not 
						case 2:		$use = $in;		break; //use when logged in only, whether in debug or not
						
						case 3:		$use = (SABLE_SITE_DEBUGGING===true); 		  break; //use only in debug mode
						case 4:		$use = $in && (SABLE_SITE_DEBUGGING===false); break; //use when logged in only, when not in debug mode
						case 5:		$use = $in && (SABLE_SITE_DEBUGGING===true);  break; //use when logged in only, only in debug mode
						
						case 6:		$use = !$in && (SABLE_SITE_DEBUGGING===false); break; //use when NOT logged in, when NOT in debug mode
						case 7:		$use = !$in && (SABLE_SITE_DEBUGGING===true);  break; //use when NOT logged in only, only in debug mode						
						default: 	$use = 1;		break; //use whether logged in or not
									
					}
					
					$use = $use && is_array($itm['cont']);
					if ($use)
					{
						$ct = in_array($itm['tag'], $shortEndTags) ? '/>' : '></'.$itm['tag'].'>';
						$html .= '<'.$itm['tag'].' ';
						foreach($itm['cont'] as $attr => $val) 
						{
							$html .= $attr.'="'.$val.'" ';
						}
						$html .= $ct.PHP_EOL;
						echo($html);
					}
				}
			}
		}
		return $html;
	}
	
	public static function setImageSize($maxSize, $imageFile, &$width, &$height)
	{
		$tmp = $imageFile;
		self::convertURLPathToWorkPath($tmp);
		$tmp = SABLE_WORKING_PATH.$imageFile;
		if (file_exists($tmp)) 
		{
			
			$imgsize = getimagesize($tmp);
			$imgVal1 = floatval($imgsize[0]);
			$imgVal2 = floatval($imgsize[1]) ? floatval($imgsize[1]) : 1;
			$ratio = ($imgVal2) ? $imgVal1/$imgVal2 : 1;
		} 
		else $ratio = 1;
		
		$maxSize = is_numeric($maxSize) ? $maxSize : 80; //set a default
		if ($ratio > 1) 
		{
			$width	= $maxSize;
			$height = $maxSize/$ratio;
		}
		else 
		{
			$width	= $maxSize*$ratio;
			$height = $maxSize;
		}
	}
	
	public static function getImgHTML($img, $size, $id='', $responsive=false, $alt="")
	{
		$imgHeight 	= 0;
		$imgWidth 	= 0;
		$imgFile 	= $img;

		self::convertURLPathToWorkPath($imgFile);
		$imgFile = SABLE_WORKING_PATH.$imgFile;

		$imgHTML 	= null;
		$validImage = null;
		if (file_exists($imgFile) || is_file($imgFile))
		{
			self::convertURLPathToWorkPath($imgFile);
			$validImage = SableGeneralFunctions::checkURLExists($img) || file_exists($imgFile);
		}
		if (!$validImage) $img = SABLE_UNKNOWN_IMAGE;
		self::setImageSize($size, $img, $imgWidth, $imgHeight);		
		
		$fieldId	= !empty($id)?'id="'.$id.'"':'';
		$fieldName	= !empty($id)?'name="'.$id.'"':'';
		$class 		= $responsive?' class="img-responsive zoom-img"':'';	
		$imgHTML = '<img '.$fieldId.' '.$fieldName.' src="'.$img.'" style="width:'.$imgWidth.'px; height:auto; max-width:'.$size.'px !important;" '.$class.' alt="'.$alt.'" />';
		
		return $imgHTML;
	}
	
	public static function printImgHTML($img, $size, $id='', $responsive=false, $alt=''){echo(self::getImgHTML($img, $size, $id, $responsive, $alt));}
	
	public static function getClickableImgHTML($img, $size, $id, $onclick)
	{
		$html = self::getImgHTML($img, $size, $id);
		$p = strpos($html,'/>');
		$tmp = substr($html, 0, $p);
		$tmp = $tmp.' class="pointer" onclick="'.$onclick.'" />';
		return $tmp;
	}
	
	public static function getImageStorePath($imagePath)
	{
		$tmpImage = $imagePath;
		SableSystem::convertURLPathToWorkPath($tmpImage);
		return $tmpImage;
	}
	
	public static function generateHash($len, $specialChars=false)
	{
		if ($specialChars)
			$chars = "!@#$^*90{}~-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$^*90{}~-_";
		else
			$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		$seed = strlen($chars);
		$keyLength = $len;
		$result = '';
		for($i=0;$i<$keyLength;$i++) $result .= $chars[rand(0,$seed-1)];
		return $result;
	}
	
	public static function buildArgvArgc()
	{
		if ((SABLE_BUILD_ARGV_ARGC) && isset($_SERVER['QUERY_STRING']))
		{
			unset($_SERVER['argv']);
			$query = $_SERVER['QUERY_STRING'];	
			$argv = explode("&",$query);
			$argv = explode("+",$query);
			$cnt=0;
			foreach($argv as $arg) 
			{
				//if($cnt>0)
				$_SERVER['argv'][] = urldecode($arg);
				$cnt++;
			}
			//SableDebug::debug($_SERVER['argv']);
			$_SERVER['argc'] = $cnt;
		}
	}
	
	public static function getModalWindow($module, $id)
	{
		$ok = true;
		$content = "<p>Content not found. Please advise support.</p>";
		
		if(is_file($module))
			$file = $module;
		else
		{
			$module = str_replace('//', '/', SABLE_WORKING_PATH.'/'.$module);
			if (is_file($module)) $file = $module; else $ok = false;
		}
		
		$html1 = '<div class="modal fade" id="'.$id.'" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
					<div class="modal-dialog modal-lg" role="document">
						<div class="modal-content">'.PHP_EOL;
		$html2 = PHP_EOL.'
						</div>
					</div>
				</div>';
		
		echo($html1);
		if ($ok) include($file); else echo($content);
		echo($html2);
	}
	
	public static function validateFixNumber(&$number)
	{
		//number must be in format 27821234569 - no 'PLUS' sign, but international format:
		//!! *** FOR SOUTH AFRICA ONLY!!
		$res = false;
		$number = preg_replace("/\s+/", "", $number); //remove any whitespace from the number
		$number = substr($number, -9); //get last 9 characters of our string.

		//now we should be left with a number that is 9 characters long
		if (strlen($number)===9)
		{
			$number = '27'.$number; //apply the format.		
			$res = true; //continue sending.
		} 
		return $res;
	}
	
	public static function sendSMS($message, $recipient, $clientId, $sender='')
	{
		global $sableDB;
		/**
		Numbers must be in the correct format:
		**/
		$sender = empty($sender) ? SABLE_SENDER_NAME : $sender;
		require_once(SABLE_CLASS_PATH."panacea_api.php");
		$api = new PanaceaApi();
		$api->setUsername(PANACEA_USERNAME);
		$api->setPassword(PANACEA_PASSWORD);
		
		if (SABLE_DEMO_SYSTEM)
		{
			//if(in_array("))
			$to = '0823382113';//'0823382113';
			$internal = ['0722128645', '0716797273'];
			if (in_array($recipient, $internal)) 
			{
				$to = $recipient;
				$recipient = $to; //for reporting and logging, we show the correct number
			}
			self::validateFixNumber($recipient);
			//SableDebug::debugfetch($recipient, "sms_tester");
			$result = $api->message_send($to, $message, $sender);
		}
		else
		{
			self::validateFixNumber($recipient);
			//SableDebug::debugfetch("Recipient number: ".$recipient, "sms_tester");
			$result = $api->message_send($recipient, $message, $sender);
			SableDebug::debugfetch($result, 'sms_sending_errors');
			/**
			Array
			(
				[status] => -32
				[message] => Route not found
				[details] => 512: Number blocked
			)
				**/
		}

		$ret = 1;
		if ($api->ok($result)) 
		{
			//SableDebug::debugfetch("Message sent! ID was {$result['details']}\n", "sms_tester");	
			$ret = 1;
		} 
		else 
		{
			/* There was an error */
			$chk = $api->getError();
			SableDebug::debugfetch($chk, 'sms_sending_errors');
			//SableDebug::debugfetch($api->getError(), "sms_tester");
			$ret = 0;
		}
		$reply		= $ret ? "Message sent." : $api->getError();
		$smsReply	= $ret ? $result['message'] : $result['details'];
		$smsKey		= $ret ? $result['details'] : $result['message'];
		$ins = 'insert into sms_message 
				(sms_number, client_id, sms_message, sms_sent, sms_reply, sms_key) 
				values 
				("'.$recipient.'", '.$clientId.', "'.$message.'", "'.intval($ret).'", "'.$smsReply.'", "'.$smsKey.'")';
		
		$sableDB->updateQuery($ins);
		
/**
insert into sms_message 
				(sms_number, sms_message, sms_sent, sms_reply, sms_key) 
				values 
				("27825686129", "Just a reminder that your paw-babies are booked for a Champion grooming session 2021-09-07 @ 12:30", , "Error communicating with API", "")
**/
		
		return $ret;
	}	
	
	public static function setCookie($cookieName, $cookieValue)
	{
		return setcookie($cookieName, $cookieValue, time()+60*60*24*365, '/');
	}
	
	public static function getCookie($cookieName)
	{
		return isset($_COOKIE[$cookieName]) ? $_COOKIE[$cookieName] : '';
	}
	
	public static function deleteCookie($cookieName)
	{
		unset($_COOKIE[$cookieName]);
		return setcookie($cookieName, '', time()-3600, '/');
	}	
	
} /* Class Album */