<?php 
/**
* @version		$Id: framework.class.php 2621 2025-07-10 17:43:29Z IOS $
* @package		Elxis
* @copyright	Copyright (c) 2006-2025 elxis.org (https://www.elxis.org). All rights reserved.
* @license		Elxis Public License ( https://www.elxis.org/elxis-public-license.html )
* @author		Elxis Team ( https://www.elxis.org )
* @description 	Elxis CMS is free software. Read the license for copyright notices and details
*/

defined('_ELXIS_') or die ('Direct access to this location is not allowed');


class elxisFramework {

	private $cfg = null;
	private $version = array();
	private $_session = null; //session db object
	private $_user = null; //user db object
	private $_acl = null; //ACL class instance


	/*********************/
	/* MAGIC CONSTRUCTOR */
	/*********************/
	public function __construct() {
		$this->loadConfig();
		include(ELXIS_PATH.'/includes/version.php');
		$this->version = $elxis_version;
		$this->applySecurityPolicy();
	}
	

	/******************************************/
	/* PRIMITIVE APPLIANCE OF SECURITY POLICY */
	/******************************************/
	private function applySecurityPolicy() {
		if ((int)$this->cfg->get('SECURITY_LEVEL') > 1) {
			$ok = true;
			if ($this->cfg->get('REPO_PATH') == '') {
				$ok = false;
			} else {
				$ok = (stripos($this->cfg->get('REPO_PATH'), ELXIS_PATH) !== false) ? false : true;
			}
			if (!$ok) {
				$msg = 'Current Elxis security policy requires you to place the reposity folder above the www root folder!';
				exitPage::make('security', 'ELXIS-0001', $msg);
			}
		}
	}


	/**********************/
	/* INITIALIZE SESSION */
	/**********************/
	public function initSession($name=null) {
		$options = array();
		if ($name !== null) {
			$options['encname'] = $name;
		} else {
			$options['name'] = 'elxissessid';
		}

		$db = eFactory::getDB(); //make sure db connection is open
		elxisLoader::loadFile('includes/libraries/elxis/database/tables/session.db.php');
		elxisLoader::loadFile('includes/libraries/elxis/database/tables/users.db.php');
		elxisLoader::loadFile('includes/libraries/elxis/acl.class.php');

		$eSession = eFactory::getSession($options);
		if ($eSession->getState() !== 'active') {
			if ($eSession->getState() == 'error') { //validation error, create new session
				$eSession->restart();
				if ($eSession->getState() !== 'active') {
					exitPage::make('fatal', 'ELXIS-0002', 'Could not initialize session!');
				}
			} else {
				exitPage::make('fatal', 'ELXIS-0003', 'Could not initialize session!');
			}
		}

		$this->_session = new sessionDbTable();
		if (rand(1, 10) === 5) {
			$this->_session->purge($eSession->getExpire());
		}
		$sessid = $eSession->getId();

		if ($this->_session->load($sessid)) {
			$this->_session->refresh();
		} else {
			$this->_session->session_id = $sessid;
			$this->_session->user_agent = $eSession->getUA();
			$this->_session->ip_address = $eSession->getIP();
			$this->_session->clicks = 1;
			if (!$this->_session->insert()) {
				$msg = 'Could not save session! '.$this->_session->getErrorMsg();
				exitPage::make('fatal', 'ELXIS-0003', $msg);
			}
		}

		$tz = (string)$eSession->get('timezone');
		if ($tz != '') {
			if (eFactory::getDate()->setTimezone($tz) === false) { $tz = ''; }
		}

		$cookie_login = ($this->getConfig('TWOFA') != '') ? false : true;
		$this->_user = new usersDbTable();
		$this->_user->uid = $this->_session->uid;
		$this->_user->gid = (int)$this->_session->gid;
		$this->_user->timezone = $tz;
		if ((int)$this->_session->uid > 0) {
			$cookie_login = false;
			if ($this->_user->load($this->_session->uid)) {
				if ($this->_user->block > 0) { //just in case the user blocked during its session
					$this->_user = new usersDbTable();
					$this->_user->uid = 0;
					$this->_user->gid = 7;
					$this->_user->timezone = $tz;
					$this->_session->uid = 0;
					$this->_session->gid = 7;
				} else {
					$this->_user->lastvisitdate = eFactory::getDate()->getDate();
					if ($tz != '') {
						$this->_user->timezone = $tz;
					} else if (trim($this->_user->timezone) != '') {
						eFactory::getDate()->setTimezone($this->_user->timezone);
					}
					$this->_user->addUserClick();
					$this->_user->update();
				}
			} else {
				$this->_session->uid = 0;
				$this->_session->gid = 7;
				$this->_user->uid = 0;
				$this->_user->gid = 7;
			}
		} else if ((int)$this->_session->gid <> 7) {
			$cookie_login = false;
			$this->_user->groupname = 'External user';
			$this->_user->firstname = (string)$eSession->get('firstname');
			$this->_user->lastname = (string)$eSession->get('lastname');
			$this->_user->uname = (string)$eSession->get('uname');
			$this->_user->email = (string)$eSession->get('email');
			$this->_user->avatar = (string)$eSession->get('avatar');
		}

		if (($cookie_login == false) && ($this->getConfig('SECURITY_LEVEL') > 0)) { //regenerate session for already logged in users
			$old_session_id = $this->_session->session_id;
			if ($eSession->regenerate()) {
				$this->_session->session_id = $eSession->getId();
				$this->_session->forceNew();
				if ($this->getConfig('SESSION_HANDLER') == 'database') {
					$ok = $this->_session->update();
				} else {
					$ok = $this->_session->insert();
				}
				if ($ok) {
					$this->_session->removeOld($old_session_id);
				}
			}
		}

		$this->_acl = new elxisACL();
		$this->_acl->load($this->_user->uid, $this->_user->gid);

		if ($cookie_login) {
			$cookie_name = md5('remember');
			if (isset($_COOKIE[$cookie_name])) {
				$cookie_data_enc = trim($this->sanitizeInput($cookie_name, INPUT_COOKIE));
				if ($cookie_data_enc != '') {
					$key = (isset($_SERVER['HTTP_USER_AGENT'])) ? md5($_SERVER['HTTP_USER_AGENT'].$this->getConfig('ENCRYPT_KEY')) : '';
					$cookie_data_ser = $this->obj('crypt')->decrypt($cookie_data_enc, $key);
					if ($cookie_data_ser !== false) {
						/** if user has switched user agent then the decryption key is wrong and the resulted $cookie_data_ser 
						is also wrong. To avoid php notice on serialize we check if the $cookie_data_ser is a valid serialized 
						string by checking if the "uname" string exists in it else we delete the cookie. */
						if (intval(stripos($cookie_data_ser, 'uname')) > 0) {
							$cookie_data = @unserialize($cookie_data_ser);
						} else {
							$cookie_data = array();
							@setcookie($cookie_name, '', time() - 3000, $eSession->getCookiePath(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
						}
						if (is_array($cookie_data) && isset($cookie_data['uname']) && isset($cookie_data['pword']) && isset($cookie_data['auth_method']) && ($cookie_data['auth_method'] == 'elxis')) {
							//Elxis 5.0: added "internal" to fix endless redirect in case of wrong saved password
							$options = array('auth_method' => $cookie_data['auth_method'], 'uname' => $cookie_data['uname'], 'pword' => $cookie_data['pword'], 'internal' => true);
							$this->login($options);
						}
					}
				}
			}
		}
	}


	//-----------------------------------------------//


	/****************************/
	/* LOAD ELXIS CONFIGURATION */
	/****************************/
	private function loadConfig() {
		elxisLoader::loadFile('configuration.php');
		$this->cfg = new elxisConfig();
	}


	/********************************/
	/* GET A CONFIGURATION VARIABLE */
	/********************************/
	public function getConfig($var='') {
		return $this->cfg->get($var);
	}


	/**************************************************/
	/* ALIAS OF getConfig FOR BACKWARDS COMPATIBILITY */
	/**************************************************/
	public function getCfg($var='') {
		return $this->getConfig($var);
	}


	/***************************************************************/
	/* SET A NEW CONFIGURATION VARIABLE (OVERWRITE IS NOT ALLOWED) */
	/***************************************************************/
	public function setConfig($var, $value) {
		return $this->cfg->set($var, $value);
	}


	/*************************/
	/* SEND EMAIL MESSAGE(S) */
	/*************************/
	public function sendmail($subject, $body, $alt_body='', $attachment=null, $type='plain', $to=null, $cc=null, $bcc=null, $from=null, $priority=3, $debug=false, $replyto=null) {
		require_once(ELXIS_PATH.'/includes/libraries/swift/swift_required.php');
		if (($type == '') || ($type != 'html')) {
			$type = 'text/plain';
		} else {
			$type = 'text/html';
		}
		if ($alt_body != '') { $type = 'text/html'; }

		$message = Swift_Message::newInstance();
		$message->setCharset('UTF-8');
		$message->setPriority($priority);
		$message->setSubject($subject);
		$message->setBody($body, $type);
		if ($alt_body != '') {
			$message->addPart($alt_body, 'text/plain');
		}
		
		if (is_array($attachment) && (count($attachment) > 0)) {
			foreach ($attachment as $file) {
				if (file_exists($file)) {
					$message->attach(Swift_Attachment::fromPath($file));
				}
			}
		} elseif (is_string($attachment) && ($attachment != '')) {
			if (file_exists($attachment)) {
				$message->attach(Swift_Attachment::fromPath($attachment));
			}
		}

		$send_batch = false;
		if (is_array($to) && (count($to) > 0)) {
			if (count($to) > 1) {
				$send_batch = true;
				if (is_array($cc) && (count($cc) > 0)) {//send CC as TO in batch send
					foreach ($cc as $recipient) { $to[] = $recipient; }
				} elseif (is_string($cc) && ($cc != '')) {
					$to[] = $cc;
				}
				$cc = '';
				if (is_array($bcc) && (count($bcc) > 0)) {//send BCC as TO in batch send
					foreach ($bcc as $recipient) { $to[] = $recipient; }
				} elseif (is_string($bcc) && ($bcc != '')) {
					$to[] = $bcc;
				}
				$bcc = '';
			}
		}

		if (!$send_batch) {
			if (is_array($to) && (count($to) > 0)) {
				foreach ($to as $recipient) {
					$parts = preg_split('/\,/', $recipient, 2, PREG_SPLIT_NO_EMPTY);
					if (isset($parts[1])) {
						$message->addTo($parts[0], $parts[1]);
					} else {
						$message->addTo($parts[0]);
					}
				}
			} elseif (is_string($to) && ($to != '')) {
				$parts = preg_split('/\,/', $to, 2, PREG_SPLIT_NO_EMPTY);
				if (isset($parts[1])) {
					$message->addTo($parts[0], $parts[1]);
				} else {
					$message->addTo($parts[0]);
				}
			} else {
				$message->addTo($this->getConfig('MAIL_EMAIL'), $this->getConfig('MAIL_NAME'));
			}
		}

		if (is_array($cc) && (count($cc) > 0)) {
			foreach ($cc as $recipient) {
				$parts = preg_split('/\,/', $recipient, 2, PREG_SPLIT_NO_EMPTY);
				if (isset($parts[1])) {
					$message->addCc($parts[0], $parts[1]);
				} else {
					$message->addCc($parts[0]);
				}
			}
		} elseif (is_string($cc) && ($cc != '')) {
			$parts = preg_split('/\,/', $cc, 2, PREG_SPLIT_NO_EMPTY);
			if (isset($parts[1])) {
				$message->addCc($parts[0], $parts[1]);
			} else {
				$message->addCc($parts[0]);
			}
		}

		if (is_array($bcc) && (count($bcc) > 0)) {
			foreach ($bcc as $recipient) {
				$parts = preg_split('/\,/', $recipient, 2, PREG_SPLIT_NO_EMPTY);
				if (isset($parts[1])) {
					$message->addBcc($parts[0], $parts[1]);
				} else {
					$message->addBcc($parts[0]);
				}
			}
		} elseif (is_string($bcc) && ($bcc != '')) {
			$parts = preg_split('/\,/', $bcc, 2, PREG_SPLIT_NO_EMPTY);
			if (isset($parts[1])) {
				$message->addBcc($parts[0], $parts[1]);
			} else {
				$message->addBcc($parts[0]);
			}
		}

		if (is_string($from) && ($from != '')) {
			$parts = preg_split('/\,/', $from, 2, PREG_SPLIT_NO_EMPTY);
			if (isset($parts[1])) {
				$message->setFrom(array($parts[0] => $parts[1]));
			} else {
				$message->setFrom($parts[0]);
			}
		} else {
			$message->setFrom(array($this->getConfig('MAIL_FROM_EMAIL') => $this->getConfig('MAIL_FROM_NAME')));
		}

		if (is_string($replyto) && ($replyto != '')) {
			$parts = preg_split('/\,/', $replyto, 2, PREG_SPLIT_NO_EMPTY);
			if (isset($parts[1])) {
				$message->setReplyTo(array($parts[0] => $parts[1]));
			} else {
				$message->setReplyTo($parts[0]);
			}
		}

		$headers = $message->getHeaders();
		$headers->addTextHeader('X-Mailer', 'Elxis');

		if ($debug) {
			return $message->toString();
		}

		switch ($this->getConfig('MAIL_METHOD')) {
			case 'smtp':
				$transport = Swift_SmtpTransport::newInstance(
					$this->getConfig('MAIL_SMTP_HOST'), 
					$this->getConfig('MAIL_SMTP_PORT'),
					$this->getConfig('MAIL_SMTP_SECURE')
				);
				if ($this->getConfig('MAIL_SMTP_AUTH') == 1) {
					if ($this->getConfig('MAIL_AUTH_METHOD') != '') {
						$transport->setAuthMode($this->getConfig('MAIL_AUTH_METHOD'));
					}
					$transport->setUsername($this->getConfig('MAIL_SMTP_USER'));
					$transport->setPassword($this->getConfig('MAIL_SMTP_PASS'));
				}
			break;
			case 'sendmail':
				//change to: newInstance('/usr/sbin/sendmail -bs'); or any other path if you use different settings
				$transport = Swift_SendmailTransport::newInstance();
			break;
			case 'mail': default:
				$transport = Swift_MailTransport::newInstance();
			break;
		}

		$mailer = Swift_Mailer::newInstance($transport);
		$result = 0;
		if ($send_batch) {
			$failedRecipients = array();
			foreach ($to as $recipient) {
				$parts = preg_split('/\,/', $recipient, 2, PREG_SPLIT_NO_EMPTY);
				if (isset($parts[1])) {
					$message->setTo(array($parts[0] => $parts[1]));
				} else {
					$message->setTo($parts[0]);
				}
				try {
					$result += $mailer->send($message, $failedRecipients);
				} catch (\Swift_TransportException $Ste) {
					$msg = 'SWIFT MAILER ERROR'."\n".$Ste->getMessage();
					elxisError::logError($msg);
				}
			}
		} else {
			try {
				$result = $mailer->send($message);
			} catch (\Swift_TransportException $Ste) {
				$msg = 'SWIFT MAILER ERROR'."\n".$Ste->getMessage();
				elxisError::logError($msg);
			}
		}

		return $result;
	}


	/***********************************************/
	/* COMPOSE URL FROM ELXIS URI FORMATTED STRING */
	/***********************************************/
	public function makeURL($elxis_uri='', $file='', $forcessl=false, $xhtml=true) {
		$eURI = eFactory::getURI();
		return $eURI->makeURL($elxis_uri, $file, $forcessl, $xhtml);
	}


	/*****************************************************/
	/* COMPOSE ADMIN URL FROM ELXIS URI FORMATTED STRING */
	/*****************************************************/
	public function makeAURL($elxis_uri='', $file='', $forcessl=false, $xhtml=true) {
		$eURI = eFactory::getURI();
		return $eURI->makeAURL($elxis_uri, $file, true, true);
	}


	/*********************/
	/* GET ELXIS VERSION */
	/*********************/
	public function getVersion() {
		return $this->version['RELEASE'].'.'.$this->version['LEVEL'];
	}


	/**************************/
	/* GET ELXIS LONG VERSION */
	/**************************/
	public function longVersion() {
		return $this->version['PRODUCT'].' '.$this->version['RELEASE'].'.'.$this->version['LEVEL'].' '
		.$this->version['STATUS'].' [ '.$this->version['CODENAME'].' ] '.$this->version['RELDATE'];
	}


	/*********************************************/
	/* GET CUSTOM INFORMATION FROM ELXIS VERSION */
	/*********************************************/
	public function fromVersion($idx='') {
		if (($idx != '') && isset($this->version[$idx])) { return $this->version[$idx]; }
		return '';
	}


	/***********************/
	/* GET ELXIS COPYRIGHT */
	/***********************/
	public function copyright() {
		return $this->version['COPYRIGHT'];
	}


	/******************************/
	/* GET ELXIS LINKED COPYRIGHT */
	/******************************/
	public function copyrightLink() {
		return $this->version['COPYRIGHTURL'];
	}


	/************************/
	/* GET ELXIS POWERED BY */
	/************************/
	public function poweredBy() {
		return $this->version['POWEREDBY'];
	}


	/**************************************/
	/* GET SITE'S BASE URL SECURE VERSION */
	/**************************************/
	public function secureBase($force=false) {
		$eURI = eFactory::getURI();
		return $eURI->secureBase($force);
	}


	/****************************/
	/* GET URL'S SECURE VERSION */
	/****************************/
	public function secureURL($url='', $force=false) {
		$eURI = eFactory::getURI();
		return $eURI->secureURL($url, $force);
	}


	/*************************************************/
	/* AN ELEGANT WAY TO INIT/GET AN OBJECT INSTANCE */
	/*************************************************/
	public function obj($name, $type='helper', $reload=false) {
		$name = ucfirst($name);
		$utype = ($type != '') ? ucfirst($type) : '';
		$className = 'elxis'.$name.$utype;
		$instance = 'e'.$name.$utype;
		if (eRegistry::isLoaded($instance)) {
			if ($reload === true) {
				eRegistry::remove($instance);
				$obj = new $className();
				eRegistry::set($obj, $instance);
				return $obj;
			} else {
				return eRegistry::get($instance);
			}
		} else {
			switch($type) {
				case 'helper':
					$file = 'includes/libraries/elxis/helpers/'.strtolower($name).'.helper.php';
				break;
				default:
					$file = 'includes/libraries/elxis/'.strtolower($name).'.class.php';
				break;
			}
			if (!elxisLoader::loadFile($file)) { return null; }
			if (!class_exists($className, false)) {
				exitPage::make('fatal', 'ELXIS-0004', 'Class '.$className.' not found!');
			}
			$obj = new $className();
			eRegistry::set($obj, $instance);
			return $obj;
		}
	}


	/********************/
	/* REDIRECT BROWSER */
	/********************/
	public function redirect($url, $msg='', $error=false) {
		if (!filter_var($url, FILTER_VALIDATE_URL)) {
			$url = defined('ELXIS_ADMIN') ? $this->makeAURL() : $this->makeURL();
		}

		if (trim($msg) != '') {
			$type = ($error) ? 'elxerror' : 'elxmsg';
			$url .= (strpos($url, '?')) ? '&'.$type.'='.urlencode($msg) : '?'.$type.'='.urlencode($msg);
		}
		$url = str_replace('&amp;', '&', $url);

		if (headers_sent()) {
			echo '<script>document.location.href="'.$url.'";</script>'."\n";
		} else {
			if (ob_get_length() > 0) { ob_end_clean(); }
			@header('content-type:text/html; charset=utf-8');
			@header('Location: '.$url);
		}
		exit;
	}


	/************************/
	/* GET USER DB INSTANCE */
	/************************/
	public function user() {
		return $this->_user;
	}


	/***************************/
	/* GET SESSION DB INSTANCE */
	/***************************/
	public function session() {
		return $this->_session;
	}


	/**************************/
	/* GET ACL CLASS INSTANCE */
	/**************************/
	public function acl() {
		return $this->_acl;
	}


	/**************************************************************/
	/* LOGIN USER, ON SUCCESS UPDATE SESSION, USER OBJECT AND ACL */
	/**************************************************************/
	public function login($options=array()) {
		$eSession = eFactory::getSession();

		$auth_method = isset($options['auth_method']) ? $options['auth_method'] : '';
		$internal = isset($options['internal']) ? (bool)$options['internal'] : false;
		$iresponse = array('success' => false, 'errormsg' => '');

		if (defined('ELXIS_ADMIN')) {
			$redir_url = $this->makeAURL('', '', true);
			$redir_url2 = $this->makeAURL('', '', true);
		} else {
			$redir_url = $this->makeURL('user:login/', '', true);
			if ($auth_method != 'elxis') {
				$redir_url2 = $this->makeURL('user:login/'.$auth_method.'.html', 'inner.php', true);
			} else {
				$redir_url2 = $this->makeURL('user:login/'.$auth_method.'.html', '', true);
			}
		}

		if ($this->getConfig('TWOFA') != '') {
			if (!isset($options['otp'])) {
				$msg = eFactory::getLang()->get('VCODE_NOPROV');
				if ($internal === true) {
					$iresponse['errormsg'] = $msg;
					return $iresponse;
				} else {
					$this->redirect($redir_url, $msg, true);
				}
			}
			if (trim($options['otp']) == '') {
				$msg = eFactory::getLang()->get('VCODE_WRONG');
				if ($internal === true) {
					$iresponse['errormsg'] = $msg;
					return $iresponse;
				} else {
					$this->redirect($redir_url, $msg, true);
				}
			}
			if ($auth_method != 'elxis') {
				if ($internal === true) {
					$iresponse['errormsg'] = '2 factor authentication login is supported only by Elxis authentication method!';
					return $iresponse;
				} else {
					$this->redirect($redir_url, '2 factor authentication login is supported only by Elxis authentication method!', true);
				}
			}
		}

		elxisLoader::loadInit('libraries:elxis:auth.class', 'eAuth', 'elxisAuth');
		$eAuth = eRegistry::get('eAuth');
		$eAuth->setAuth($auth_method);
		if ($eAuth->getError() != '') {
			if ($internal === true) {
				$iresponse['errormsg'] = $eAuth->getError();
				return $iresponse;
			} else {
				$this->redirect($redir_url, $eAuth->getError(), true);
			}
		}

		$eAuth->authenticate($options);
		$response = $eAuth->getResponse();
		if ($response->loginsuccess !== true) {
			if ($internal  === true) {
				$iresponse['errormsg'] = $response->errormsg;
				return $iresponse;
			} else {
				$this->redirect($redir_url2, $response->errormsg, true);
			}
		}

		$old_session_id = $this->_session->session_id;
		$session_regenerated = false;
		if ($eSession->regenerate()) {
			$this->_session->session_id = $eSession->getId();
			$this->_session->forceNew();
			$session_regenerated = true;
		}

		if ($auth_method == 'elxis') {
			$uid = (int)$response->uid;
			if (!$this->_user->load($uid)) { //just in case
				if ($internal  === true) {
					$iresponse['errormsg'] = 'User not found!';
					return $iresponse;
				} else {
					$redir_url = $this->makeURL('user:login/elxis.html', '', true);
					$this->redirect($redir_url, 'User not found!', true);
				}
			}
			$this->_user->lastvisitdate = eFactory::getDate()->getDate();
			$this->_user->times_online++;
			$this->_user->addUserClick();
			$this->_user->update();
		} else {
			$this->_user->firstname = (string)$response->firstname;
			$this->_user->lastname = (string)$response->lastname;
			$this->_user->uname = (string)$response->uname;
			$this->_user->email = (string)$response->email;
			$this->_user->gid = (int)$response->gid;
			$this->_user->avatar = (string)$response->avatar;

			$eSession->set('firstname', $this->_user->firstname);
			$eSession->set('lastname', $this->_user->lastname);
			$eSession->set('uname', $this->_user->uname);
			$eSession->set('email', $this->_user->email);
			$eSession->set('avatar', $this->_user->avatar);
		}

		$this->_session->uid = (int)$response->uid;
		$this->_session->gid = (int)$response->gid;
		$this->_session->login_method = $auth_method;

		if ($this->getConfig('SESSION_HANDLER') == 'database') {
			if (phpversion() < 7) {//PHP 5.x writes the session when script terminates. So use insert (store) to save it now
				$ok = $this->_session->store();
			} else {
				$ok = $this->_session->update();
			}
		} else {
			$ok = $this->_session->insert();
		}

		if ($ok && $session_regenerated) {
			$this->_session->removeOld($old_session_id);
		}

		$this->_acl->load($this->_user->uid, $this->_user->gid);

		if (isset($options['remember']) && ((int)$options['remember'] === 1) && ($auth_method == 'elxis')) {
			$key = (isset($_SERVER['HTTP_USER_AGENT'])) ? md5($_SERVER['HTTP_USER_AGENT'].$this->getConfig('ENCRYPT_KEY')) : ''; //remember in this browser only!
			$cookie_name = md5('remember');
			$cookie_data = $this->obj('crypt')->encrypt(serialize(array('uname' => $options['uname'], 'pword' => $options['pword'], 'auth_method' => $response->method)), $key);
			$expire = time() + 365*24*3600;

			if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
				$cookoptions = array (
					'expires' => $expire,
					'path' => $eSession->getCookiePath(),
					'domain' => $eSession->getCookieDomain(),
					'secure' => $eSession->getCookieSecure(),
					'httponly' => $eSession->getCookieHTTP(),
					'samesite' => $eSession->getCookieSameSite()
				);
				setcookie($cookie_name, $cookie_data, $cookoptions); 
			} else {
				//setcookie($cookie_name, $cookie_data, $expire, $eSession->getCookiePath(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
				setcookie($cookie_name, $cookie_data, $expire, $eSession->getCookiePath().'; samesite='.$eSession->getCookieSameSite(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
			}
		}

		if ($internal  === true) {
			$iresponse['success'] = true;
			return $iresponse;
		} else {
			return true;
		}
	}


	/**************************************************************/
	/* LOGIN USER, ON SUCCESS UPDATE SESSION, USER OBJECT AND ACL */
	/**************************************************************/
	public function makeOTP($options=array()) {
		$eLang = eFactory::getLang();

		$iresponse = array('success' => false, 'errormsg' => '', 'method' => '');

		$auth_method = isset($options['auth_method']) ? $options['auth_method'] : '';

		if ($this->getConfig('TWOFA') == '') {
			$iresponse['errormsg'] = '2 factor authentication is not enabled!';
			return $iresponse;
		}

		if ($auth_method != 'elxis') {
			$iresponse['errormsg'] = '2 factor authentication login is supported only by Elxis authentication method!';
			return $iresponse;
		}

		elxisLoader::loadInit('libraries:elxis:auth.class', 'eAuth', 'elxisAuth');
		$eAuth = eRegistry::get('eAuth');
		$eAuth->setAuth($auth_method);
		if ($eAuth->getError() != '') {
			$iresponse['errormsg'] = $eAuth->getError();
			return $iresponse;
		}

		$otpResponse = $eAuth->makeOTP($options);
		if ($otpResponse['success'] !== true) {
			$iresponse['errormsg'] = $otpResponse['errormsg'];
			return $iresponse;
		}

		if ($this->getConfig('TWOFA') == 'sms') {//Send via SMS
			if ($this->getConfig('SMS_PARAMS') != '') {
				$to = trim($otpResponse['mobile']);
				$message = $eLang->get('VERIFY_CODE').': '.$otpResponse['otp'];
				$sms = $this->obj('sms');
				$sms->setElxisConfigOptions();
				$ok = $sms->sendMessage($to, $message);
				if ($ok) {
					$iresponse['success'] = true;
					$iresponse['method'] = 'sms';
					return $iresponse;
				}
				unset($sms);
			}
		}

		//Just in case
		if ($otpResponse['email'] == '') {
			$iresponse['errormsg'] = $eLang->get('CNOTSEND_VCODE_EMAIL');
			return $iresponse;
		}

		//Send via E-mail (also send email if sms is selected but failed above)
		$iresponse['method'] = 'email';
		$subject = $eLang->get('VERIFY_CODE');

		$txt = sprintf($eLang->get('EML_DEAR'), $otpResponse['firstname'].' '.$otpResponse['lastname'])."\r\n";
		$txt .= $eLang->get('EML_USEVCODE_SIGN')."\r\n\r\n";
		$txt .= $otpResponse['otp']."\r\n\r\n";
		$txt .= $eLang->get('EML_THANKS')."\r\n";
		$txt .= $this->getConfig('SITENAME')."\r\n";
		$txt .= $this->getConfig('URL');

		$to = $otpResponse['email'].','.$otpResponse['firstname'].' '.$otpResponse['lastname'];
		$ok = $this->sendmail($subject, $txt, '', null, 'plain', $to);

		if (!$ok) {
			$iresponse['errormsg'] = $eLang->get('CNOTSEND_VCODE_EMAIL');
		} else {
			$iresponse['success'] = true;
		}
		return $iresponse;
	}


	/***************************************************************/
	/* LOGOUT USER, ON SUCCESS UPDATE SESSION, USER OBJECT AND ACL */
	/***************************************************************/
	public function logout() {
		$eSession = eFactory::getSession();

		$old_session_id = $this->_session->session_id;

		$auth_method = $this->_session->login_method;
		$eSession->restart();

		$this->_session->forceNew();

		$this->_session->session_id = $eSession->getId();
		$this->_session->uid = 0;
		$this->_session->gid = 7;
		$this->_session->login_method = null;

		if ($this->getConfig('SESSION_HANDLER') != 'database') { //already stored by the session handler
			$ok = $this->_session->insert();
		} else {
			$ok = $this->_session->update();
		}

		if ($ok) {
			$this->_session->removeOld($old_session_id);
		}

		$tz = (string)$eSession->get('timezone');
		if ($tz != '') {
			if (eFactory::getDate()->setTimezone($tz) === false) { $tz = ''; }
		}

		$this->_user = new usersDbTable();
		$this->_user->uid = 0;
		$this->_user->gid = 7;
		$this->_user->timezone = $tz;

		$this->_acl->load($this->_user->uid, $this->_user->gid);

		$cookie_name = md5('remember');
		if (isset($_COOKIE[$cookie_name])) {
			$expire = time() - 24*3600;
			setcookie($cookie_name, '', $expire, $eSession->getCookiePath(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
		}

		$eSession->set('backauth', 0);

		elxisLoader::loadInit('libraries:elxis:auth.class', 'eAuth', 'elxisAuth');
		$eAuth = eRegistry::get('eAuth');
		$eAuth->setAuth($auth_method);
		if ($eAuth->getError() != '') {
			$eAuth->logout();
		}

		return true;
	}


	/******************************/
	/* GENERATE A RANDOM PASSWORD */
	/******************************/
	public function makePassword($length=10) {
		list($usec, $sec) = explode(' ', microtime());
		srand((float) $sec + ((float) $usec * 100000));
		$validchars = "0123456789abcdfghjkmnpqrstvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!-@";
		$password = '';
		$counter = 0;
		while ($counter < $length) {
			$char = substr($validchars, rand(0, strlen($validchars)-1), 1);
			if (!strstr($password, $char)) {
				$password .= $char;
				$counter++;
			}
		}
		return $password;
	}


	/*****************************/
	/* GET IMAGE FROM ICONS PACK */
	/*****************************/
	public function icon($name, $size=16, $pack='', $type='png') {
		if ($pack == '') { $pack = 'nautilus'; }//config option "icons_pack" deprecated as of 5.0. Set to "nautilus" for compatibility with old extensions
		if ($type != 'gif') { $type = 'png'; }
		$dir = $size.'x'.$size;
		if (file_exists(ELXIS_PATH.'/includes/icons/'.$pack.'/'.$dir.'/'.$name.'.'.$type)) {
			return $this->secureBase().'/includes/icons/'.$pack.'/'.$dir.'/'.$name.'.'.$type;
		}
		if (($pack != 'nautilus') && file_exists(ELXIS_PATH.'/includes/icons/nautilus/'.$dir.'/'.$name.'.'.$type)) {
			return $this->secureBase().'/includes/icons/nautilus/'.$dir.'/'.$name.'.'.$type;
		}
		return $this->secureBase().'/includes/icons/nautilus/'.$dir.'/not_found.png';
	}


	/****************************************************/
	/* CONVERT ACCESS LEVEL VALUE TO TEXT USER GROUP(S) */
	/****************************************************/
	public function alevelToGroup($alevel, $groups=array(), $html=true, $detailedgroups=false) {
		$alevel = (int)$alevel;
		if ($alevel == 0) {
			return ($html) ? '<span class="elx5_green">'.eFactory::getLang()->get('GUEST').'</span>' : eFactory::getLang()->get('GUEST');
		} elseif ($alevel == 1000) {
			return ($html) ? '<span class="elx5_blue">'.eFactory::getLang()->get('EXTERNALUSER').'</span>' : eFactory::getLang()->get('EXTERNALUSER');
		} elseif ($alevel == 2000) {
			return ($html) ? '<span class="elx5_orange">'.eFactory::getLang()->get('USER').'</span>' : eFactory::getLang()->get('USER');
		} elseif ($alevel == 100000) {
			return ($html) ? '<span class="elx5_red">'.eFactory::getLang()->get('ADMINISTRATOR').'</span>' : eFactory::getLang()->get('ADMINISTRATOR');
		}
		
		$level = floor($alevel/1000);
		if (!$groups) {
			$db = eFactory::getDB();
			$sql = "SELECT * FROM ".$db->quoteId('#__groups');
			$stmt = $db->prepare($sql);
			$stmt->execute();
			$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
		}

		$levels = array();
		if ($groups) {
			foreach ($groups as $group) {
				$glevel = (int)$group['level'];
				$lowlevel = $glevel * 1000;
				$exactlevel = $lowlevel + $group['gid'];
				if ($alevel == $exactlevel) {//found and it is unique
					$levels = array();
					$levels[] = $group['groupname'];
					break;
				} elseif ($alevel == $lowlevel) {
					$levels[] = $group['groupname'];
				}
			}
		}

		$n = count($levels);
		if ($n > 1) {
			$txt = ($detailedgroups) ? implode(', ', $levels) : eFactory::getLang()->get('LEVEL').' '.$level;
			return ($html) ? '<span class="elx5_red">'.$txt.'</span>' : $txt;
		} else if ($n == 1) {
			return ($html) ? '<span class="elx5_red">'.$levels[0].'</span>' : $levels[0];
		} else {
			return ($html) ? '<span class="elx5_red">'.eFactory::getLang()->get('LEVEL').' '.$level.'</span>' : eFactory::getLang()->get('LEVEL').' '.$level;
		}
	}


	/**************************/
	/* SET OR DELETE A COOKIE */
	/**************************/
	public function setCookie($name, $value, $life=15552000) {
		$eSession = eFactory::getSession();
		$expire = time() + intval($life);

		if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
			$cookoptions = array (
				'expires' => $expire,
				'path' => $eSession->getCookiePath(),
				'domain' => $eSession->getCookieDomain(),
				'secure' => $eSession->getCookieSecure(),
				'httponly' => $eSession->getCookieHTTP(),
				'samesite' => $eSession->getCookieSameSite()
			);
			@setcookie($name, $value, $cookoptions); 
		} else {
			//@setcookie($name, $value, $expire, $eSession->getCookiePath(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
			@setcookie($name, $value, $expire, $eSession->getCookiePath().'; samesite='.$eSession->getCookieSameSite(), $eSession->getCookieDomain(), $eSession->getCookieSecure(), $eSession->getCookieHTTP());
		}
	}


	/**********************************************************/
	/* GET COOKIE'S VALUE - OPTIONALLY CREATE IT IF NOT EXIST */
	/**********************************************************/
	public function getCookie($name, $defvalue='', $create=true, $life=15552000) {
		if (isset($_COOKIE[$name])) { return $_COOKIE[$name]; }
		if ($create == false) { return $defvalue; }
		$this->setcookie($name, $defvalue, $life);
		return $defvalue;
	}


	/***************************************************/
	/* UPDATE COOKIE'S VALUE IF NEW VALUE IS DIFFERENT */
	/***************************************************/
	public function updateCookie($name, $value, $life=15552000) {
		if (isset($_COOKIE[$name])) {
			if ($_COOKIE[$name] == $value) { return true; } 
		}
		$this->setcookie($name, $value, $life);
		return true;
	}


	/***********************************************************************************/
	/* SANITIZE INPUT - EQUIVALENT TO "FILTER_SANITIZE_STRING" (deprecated in PHP 8.1) */
	/***********************************************************************************/
	public function sanitizeInput($name, $type=INPUT_POST, $low=true, $high=true) {
		switch($type) {
			case INPUT_GET:
				if (!isset($_GET[$name])) { return ''; }
			break;
			case INPUT_POST:
				if (!isset($_POST[$name])) { return ''; }
			break;
			case INPUT_COOKIE:
				if (!isset($_COOKIE[$name])) { return ''; }
			break;
			case INPUT_SERVER:
				if (!isset($_SERVER[$name])) { return ''; }
			break;
			case INPUT_ENV:
				if (!isset($_ENV[$name])) { return ''; }
			break;
			default:
				return '';
			break;
		}

		if ($low && $high) {
			$v = filter_input($type, $name, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
		} else if ($low) {
			$v = filter_input($type, $name, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
		} else if ($high) {
			$v = filter_input($type, $name, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_HIGH);
		} else {
			$v = filter_input($type, $name, FILTER_UNSAFE_RAW);
		}
		if ($v == '') { return ''; }

		$str = preg_replace('/\x00|<[^>]*>?/', '', $v);
		return str_replace(["'", '"'], ['&#39;', '&#34;'], $str);
	}


	/*********************************************************************************/
	/* SANITIZE VAR - EQUIVALENT TO "FILTER_SANITIZE_STRING" (deprecated in PHP 8.1) */
	/*********************************************************************************/
	public function sanitizeVar($var, $low=true, $high=true) {
		if ($low && $high) {
			$v = filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
		} else if ($low) {
			$v = filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
		} else if ($high) {
			$v = filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_HIGH);
		} else {
			$v = filter_var($var, FILTER_UNSAFE_RAW);
		}
		if ($v == '') { return ''; }

		$str = preg_replace('/\x00|<[^>]*>?/', '', $v);
		return str_replace(["'", '"'], ['&#39;', '&#34;'], $str);
	}


	/***************************************************/
	/* CHECK IF PASSWORD MEETS CURRENT PASSWORD POLICY */
	/***************************************************/
	public function checkPasswordPolicy($pword, $uname='') {
		$response = array('valid' => false, 'errormsg' => '');

		$pword_san = trim(preg_replace('/[^a-z0-9\_\-\.\!\@\#\$\&\(\)\{\}\[\]\?\<\>+]/i', '', $pword));
		if ($pword !== $pword_san) {
			$eLang = eFactory::getLang();
			$response['errormsg'] = sprintf($eLang->get('FIELDNOACCCHAR'), $eLang->get('PASSWORD'));
			return $response;
		}

		$policy = (int)$this->getConfig('PWPOLICY');
		if ($policy < 5) {//normal
			$matchcharlimit = 1;
			$minlength = 8;
		} else {//high
			$matchcharlimit = 2;
			$minlength = 12;
		}

		preg_match_all("~([a-z+])~", $pword, $matches);
		if (count($matches[0]) < $matchcharlimit) {
			$eLang = eFactory::getLang();
			$policytxt = sprintf($eLang->get('PASS_LOWLCHARS'), $matchcharlimit);
			$response['errormsg'] = sprintf($eLang->get('PASS_MUSTCONTAIN'), $policytxt);
			return $response;
		}
		preg_match_all("~([A-Z+])~", $pword, $matches);
		if (count($matches[0]) < $matchcharlimit) {
			$eLang = eFactory::getLang();
			$policytxt = sprintf($eLang->get('PASS_UPPLCHARS'), $matchcharlimit);
			$response['errormsg'] = sprintf($eLang->get('PASS_MUSTCONTAIN'), $policytxt);
			return $response;
		}
		preg_match_all("~([0-9+])~", $pword, $matches);
		if (count($matches[0]) < $matchcharlimit) {
			$eLang = eFactory::getLang();
			$policytxt = sprintf($eLang->get('PASS_DIGITS'), $matchcharlimit);
			$response['errormsg'] = sprintf($eLang->get('PASS_MUSTCONTAIN'), $policytxt);
			return $response;
		}
		preg_match_all("~([\_\-\.\!\@\#\$\&\(\)\{\}\[\]\?\<\>+])~", $pword, $matches);
		if (count($matches[0]) < $matchcharlimit) {
			$eLang = eFactory::getLang();
			$policytxt = sprintf($eLang->get('PASS_SYMBOLS'), $matchcharlimit);
			$response['errormsg'] = sprintf($eLang->get('PASS_MUSTCONTAIN'), $policytxt);
			return $response;
		}
		if (strlen($pword) < $minlength) {
			$eLang = eFactory::getLang();
			$response['errormsg'] = sprintf($eLang->get('PASS_MUSTLENGTH'), $minlength);
			return $response;
		}
		if ($uname != '') {
			if (strpos($pword, $uname) !== false) {
				$response['errormsg'] = 'Password cannot contain username!';
				return $response;
			}
		}

		$response['valid'] = true;
		return $response;
	}


	/***************************************************/
	/* CHECK IF PASSWORD MEETS CURRENT PASSWORD POLICY */
	/***************************************************/
	public function changeCurrentUserPassword($curpword, $pword) {
		$response = array('success' => 0, 'message' => '', 'months' => 0);

		if ($this->_user->uid < 1) {
			$response['message'] = 'To change your password you must first login!';
			return $response;
		}

		$db = eFactory::getDB();
		$uid = $this->_user->uid;
		$block = 0;

		$sql = "SELECT ".$db->quoteId('uname').", ".$db->quoteId('pword')." FROM ".$db->quoteId('#__users')
		."\n WHERE ".$db->quoteId('uid')." = :xuid AND ".$db->quoteId('block')." = :xbl";
		$stmt = $db->prepareLimit($sql, 0, 1);
		$stmt->bindParam(':xuid', $uid, PDO::PARAM_INT);
		$stmt->bindParam(':xbl', $block, PDO::PARAM_INT);
		$stmt->execute();
		$urow = $stmt->fetch(PDO::FETCH_ASSOC);
		if (!$urow) {
			$response['message'] = 'Strange error, user not found!';
			return $response;
		}
		$curencpword = $this->obj('crypt')->getEncryptedPassword($curpword);
		if ($curencpword != $urow['pword']) {
			$response['message'] = eFactory::getLang()->get('CURPASS_WRONG');
			return $response;
		}

		$pwcheck = $this->checkPasswordPolicy($pword, $this->_user->uname);
		if ($pwcheck['valid'] == false) {
			$response['message'] = $pwcheck['errormsg'];
			return $response;
		}

		$encpword = $this->obj('crypt')->getEncryptedPassword($pword);
		$pwlastchange = time();
		$failedlogin = '';
		$blockuntil = 0;

		$sql = "UPDATE ".$db->quoteId('#__users')
		."\n SET ".$db->quoteId('pword')." = :xpw, ".$db->quoteId('pwlastchange')." = :xplc, ".$db->quoteId('failedlogin')." = :xfl, ".$db->quoteId('blockuntil')." = :xbu"
		."\n WHERE ".$db->quoteId('uid')." = :xuid";
		$stmt = $db->prepare($sql);
		$stmt->bindParam(':xpw', $encpword, PDO::PARAM_STR);
		$stmt->bindParam(':xplc', $pwlastchange, PDO::PARAM_INT);
		$stmt->bindParam(':xfl', $failedlogin, PDO::PARAM_STR);
		$stmt->bindParam(':xbu', $blockuntil, PDO::PARAM_INT);
		$stmt->bindParam(':xuid', $uid, PDO::PARAM_INT);
		$stmt->execute();

		$response['success'] = 1;
		$policy = (int)$this->getConfig('PWPOLICY');
		switch ($policy) {
			case 2: case 6: $response['months'] = 3; break;
			case 3: case 7: $response['months'] = 6; break;
			case 4: case 8: $response['months'] = 12; break;
			case 1: case 5: default: break;
		}

		eFactory::getSession()->set('mustchangepw');

		return $response;
	}

}

?>