<?php

/**
 * This file is part of LEPTON Core, released under the GNU GPL
 * Please see LICENSE and COPYING files in your package for details, specially for terms and warranties.
 * 
 * NOTICE:LEPTON CMS Package has several different licenses.
 * Please see the individual license in the header of each single file or info.php of modules and templates.
 *
 * @author          LEPTON Project
 * @copyright       2010-2022 LEPTON Project
 * @link            https://lepton-cms.org
 * @license         http://www.gnu.org/licenses/gpl.html
 * @license_terms   please see LICENSE and COPYING files in your package
 *
 */

/**
 *  Core class for some basic actions to handle, e.g. install or drop tables, 'register' framework-functions etc.
 *
 */ 
class LEPTON_handle
{
    const HTACCESS_PATH = SECURE_PATH.'/.htaccess';
    const HTPASSWD_PATH = SECURE_PATH.'/.htpasswd';
	
    //  [0] class-constants
    const SPECIAL_CHARS_RESTORE = [
        "&lt;"      => "<",
        "&gt;"      => ">",
        "&amp;"     => "&",
        "&quot;"    => "\""
    ];
	
    /**
     *  Display errors (e.g. from LEPTON_database query results)?
     *
     *	@property bool	For the use of displaying error messages.
     *
     */
    static public $display_errors = true;
	
    /**
     *	Method to change the display_errors.
     *  As the property is static we can't set it "outside" direct." 
     *
     *	@param	boolean True to use LEPTON_tools::display.
     *
     */
    static public function setDisplay( $bUseDisplay=true )
    {
        self::$display_errors = (bool) $bUseDisplay;
    }
    
    /**
     * Called if a method is unknown.  
     * 
     * @param type $name
     * @param type $arguments
     */
    public function __call($name, $arguments)
    {
        $msg  = "Unknown class-method: '".$name."'\nParams:\n";
        $msg .= LEPTON_tools::display($arguments, "pre");
        echo LEPTON_tools::display($msg, "pre", "ui message red");
    }
    
    /**
     * Called if a static method is unknown.  
     * 
     * @param type $name
     * @param type $arguments
     */
    public static function __callStatic($name, $arguments)
    {
        $msg  = "Unknown static class-method: '".$name."'\nParams:\n";
        $msg .= LEPTON_tools::display($arguments, "pre");
        echo LEPTON_tools::display($msg, "pre", "ui message red");
    }
    
    /**
     *	install table
     *	@param string for table_name
     *	@param string for table_fields	 
     *
     *	@code{.php}
     *      $table_name = 'mod_test';
     *      $table_fields='
     *          `id` INT(10) UNSIGNED NOT NULL,
     *          `edit_perm` VARCHAR(50) NOT NULL,
     *          `view_perm` VARCHAR(50) NOT NULL,
     *          PRIMARY KEY ( `id` )
     *      ';
     *      LEPTON_handle::install_table($table_name, $table_fields);
     *
     *	@endcode
     *	@return boolean True if successful, otherwise false.
     *
     */	
    static public function install_table($table_name='',$table_fields='')
    {
		if (($table_name == '') || ($table_fields == '')) {
			return false;
		}
		$database = LEPTON_database::getInstance();
		$table = TABLE_PREFIX .$table_name; 
		$database->simple_query("CREATE TABLE `".$table."`  (" .$table_fields. ") " );
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui message red" );
			}
			
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 *	encrypt fields in a table as an upgrade routine
	 *	@param string for table_name
	 *	@param array for aListOfFields
	 *	@param string for field_condition	 
	 *
	 *	@code{.php}
	 *	LEPTON_handle::encrypt_table($table_name,$aListOfFields,$field_condition);
	 *	@endcode
	 *	@return boolean true if successful
	 *	 
	 *	@example
	 *	$table_name = 'mod_test';
	 *	$aListOfFields = array('name1','name2');
	 *	$field_condition = 'test_id'; 
	 *	LEPTON_handle::encrypt_table($table_name,$aListOfFields,$field_condition);
	 *		 
	 */	
	static public function encrypt_table($table_name ='',$aListOfFields =array(),$field_condition ='') {
		if ( $table_name == '' ) {
			return false;
		}
		if(!is_array( $aListOfFields ))
		{
		    LEPTON_tools::display("REQUIRED list of fieldnames must be an array!", "div", "ui red message");
		    return false;
		}

		if ( $field_condition == '' ) {
			return false;
		}
		
		$database = LEPTON_database::getInstance(); 
		self::create_sik_table($table_name);  // keep in mind, that drop_table adds the table_prefix
		
		//get table content
		$table_content = array();
		$database->execute_query(
			"SELECT * FROM ".TABLE_PREFIX.$table_name." ",
			true,
			$table_content,
			true
		);

		foreach($table_content as $to_encrypt) {
		$result = $database->secure_build_and_execute( 'UPDATE', TABLE_PREFIX.$table_name, $to_encrypt, ''.$field_condition.' ='.$to_encrypt[$field_condition], $aListOfFields);
			
			if($result === false)
			{
                echo LEPTON_tools::display($database->get_error(),'pre','ui message');
			}			
		}
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui red message" );
			}

			return false;
		} else {
			return true;
		}
	}	


	/**
	 *	decrypt fields in a table as an upgrade routine
	 *	@param string for table_name
	 *	@param array for aListOfFields
	 *	@param string for field_condition	 
	 *
	 *	@code{.php}
	 *	LEPTON_handle::decrypt_table($table_name,$aListOfFields,$field_condition);
	 *	@endcode
	 *	@return boolean true if successful
	 *	 
	 *	@example
	 *	$table_name = 'mod_test';
	 *	$aListOfFields = array('name1','name2');
	 *	$field_condition = 'test_id'; 
	 *	LEPTON_handle::decrypt_table($table_name,$aListOfFields,$field_condition);
	 *		 
	 */	
	static public function decrypt_table($table_name ='',$aListOfFields =array(),$field_condition ='') {
		if ( $table_name == '' )
		{
			return false;
		}
		if(!is_array( $aListOfFields ))
		{
		    LEPTON_tools::display("REQUIRED list of fieldnames must be an array!", "div", "ui red message");
		    return false;
		}

		if ( $field_condition == '' )
		{
			return false;
		}
		
		$database = LEPTON_database::getInstance(); 
		self::create_sik_table($table_name);  // keep in mind, that drop_table adds the table_prefix
		
		//get table content
		$table_content = array();
		$database->secure_execute_query(
			"SELECT * FROM ".TABLE_PREFIX.$table_name." ",
			true,
			$table_content,
			true,
			$aListOfFields
		);

		foreach($table_content as $to_decrypt) {
		$result = $database->build_and_execute( 'UPDATE', TABLE_PREFIX.$table_name, $to_decrypt, ''.$field_condition.' ='.$to_decrypt[$field_condition]);
			
			if($result === false)
			{
			    echo LEPTON_tools::display($database->get_error(),'pre','ui message');
			}			
		}
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui red message" );
			}

			return false;
		} else {
			return true;
		}
	}	
	
	/**
	 *	insert data into table
	 *	@param string for table_name
	 *	@param string for table_fields	 
	 *
	 *	@code{.php}
	 *	$table_name = 'mod_test';
	 *	$field_values="
	 *		(1, 'module_order', 'wysiwyg', ''),
	 *		(2, 'max_excerpt', '15', ''),
	 *		(3, 'time_limit', '0', '')
	 *	";
	 *	LEPTON_handle::insert_values($table_name, $field_values);
	 *
	 *	@endcode
	 *	@return boolean True if successful, otherwise false.
	 *
	 */	
	static public function insert_values($table_name='',$field_values ='') {
		if (($table_name == '') || ($field_values =='')) {
			return false;
		}
		$database = LEPTON_database::getInstance();
		$table = TABLE_PREFIX .$table_name; 
		$database->simple_query("INSERT INTO `".$table."`  VALUES ".$field_values." ");
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui message red" );
			}
			
			return false;
		} else {
			return true;
		}
	}	
	
	
	/**
	 *	drop table
	 *	@param string for table_name 
	 *
	 *	@code{.php}
	 *	$table_name = 'mod_test';
	 *	LEPTON_handle::drop_table($table_name);
	 *
	 *	@endcode
	 *	@return boolean True if successful.
	 *
	 */	
	static public function drop_table($table_name='') {
		if ($table_name == '') {
			return false;
		}
		$database = LEPTON_database::getInstance();
		$table = TABLE_PREFIX .$table_name; 

		$database->simple_query("DROP TABLE IF EXISTS `".$table."` ");
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui message red" );
			}

			return false;

		} else {

			return true;
		}
	}
	
	/**
	 *	rename table
	 *	@param string for table_name
	 *
	 *	@code{.php}
	 *	LEPTON_handle::rename_table($table_name);
	 *	@endcode
	 *	@return boolean true if successful
	 *
	 */	
	static public function rename_table($table_name ='') {
		if ( $table_name == '' )
		{
			return false;
		}
		$database = LEPTON_database::getInstance();
		$table_source = TABLE_PREFIX .$table_name; 
		$table_target = TABLE_PREFIX .'xsik_'.$table_name; 
		self::drop_table('xsik_'.$table_name);
		$database->simple_query("RENAME TABLE `".$table_source."` TO `".$table_target."` ");
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui message red" );
			}

			return false;
		} else {
			return true;
		}
	}

	/**
	 *	create sik table
	 *	@param string for table_name
	 *
	 *	@code{.php}
	 *	LEPTON_handle::create_sik_table($table_name);
	 *	@endcode
	 *	@return boolean true if successful
	 */	
	static public function create_sik_table($table_name ='') {
		if ( $table_name == '' )
		{
			return false;
		}
		$database = LEPTON_database::getInstance();
		$table_source = TABLE_PREFIX .$table_name; 
		$table_target = TABLE_PREFIX .'xsik_'.$table_name; 
		self::drop_table('xsik_'.$table_name);  // keep in mind, that drop_table adds the table_prefix
		$database->simple_query("CREATE TABLE `".$table_target."` LIKE `".$table_source."`");
		$database->simple_query("INSERT INTO `".$table_target."` SELECT * FROM `".$table_source."`");		
		
		// check for errors
		if ($database->is_error())
		{
			if( true === self::$display_errors )
			{
			    echo LEPTON_tools::display( $database->get_error(), "div", "ui message red" );
			}

			return false;
		} else {
			return true;
		}
	}

    /**
     *    Delete obsolete files
     *
     *    @param array $aFileNames    A linear array with filenames to delete. Since L* IV this method could handle more than one param.
     *
     *    @code{.php}
     *    $file_names = array(
     *      '/modules/lib_r_filemanager/filemanager/js/ZeroClipboard.swf'
     *    );
     *    LEPTON_handle::delete_obsolete_files ($file_names);
     *
     *    @endcode
     *    @return nothing
     *
     */
    static public function delete_obsolete_files( ...$aFileNames ) {
       
        if(is_string($aFileNames))
        { 
            $aFileNames = [ $aFileNames ];
        }
        
        foreach($aFileNames as $del)
        { 
            if(is_array($del))
            {
                foreach($del as $subItem)
                {
                    self::delete_obsolete_files( $subItem );
                }
            }
            else
            {
                $temp_path = ((strpos( $del, LEPTON_PATH) === false) ? LEPTON_PATH : "") . $del;
                if (file_exists($temp_path)) 
                {
                    $result = unlink($temp_path);
                    if (false === $result)
                    {
                        echo "<p>Cannot delete file ".$temp_path.". Please check file permissions and ownership or delete file manually.</p>";
                    }
                }
            }
        }
    }

	/**
	 *	Delete obsolete directories
	 *	@param array linear array with directory_names to delete 
	 *
	 *	@code{.php}
	 *	$directory_names = array(
	 *	'/modules/lib_r_filemanager/thumbs'
	 *	);
	 *	LEPTON_handle::delete_obsolete_directories($directory_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function delete_obsolete_directories($directory_names=array()) {
		self::register('rm_full_dir');
		
		if(is_string($directory_names)) {
			$directory_names = array($directory_names);
		}
		foreach ($directory_names as $del)
		{
			$temp_path = LEPTON_PATH . $del;
			if (file_exists($temp_path)) 
			{
				$result = rm_full_dir($temp_path);
				if (false === $result)
				{
                    echo "Cannot delete directory ".$temp_path.". Please check directory permissions and ownership or deleted directories manually.";
				}
			}
		}			
	} 

	/**
	 *	rename recursive directories
	 *	@param array linear array with directory_names to rename as assoziative array
	 *
	 *	@code{.php}
	 *	$directory_names = array(
	 *		array ('source' =>'old_path1', 'target'=>'new_path1'),
	 *		array ('source' =>'old_path2', 'target'=>'new_path2')
	 *	);
	 *	LEPTON_handle::rename_directories ($directory_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function rename_directories($directory_names=array()) {
		self::register('rename_recursive_dirs');
		foreach ($directory_names as $rename)
		{
			$source_path = LEPTON_PATH . $rename['source'];
			$target_path = LEPTON_PATH . $rename['target'];
			if (file_exists($source_path)) 
			{			

				$result = rename_recursive_dirs($source_path, $target_path);
				if (false === $result)
				{
				    echo "Cannot rename file ".$source_path.". Please check directory permissions and ownership manually.";
				}			
				
			}	
		}
	}

    /**
     *  include files
     *  @param array linear array with filenames to include 
     *
     *  @code{.php}
     *    $file_names = array(
     *    '/framework/summary.frontend_functions.php'
     *    );
     *    LEPTON_handle::include_files ($file_names);
     *
     *  @endcode
     *  @return nothing
     */
    static public function include_files($file_names=array(),$interrupt=true)
    {
        if(is_string($file_names)) {
            $file_names = array($file_names);
        }        

        foreach ($file_names as $req)
        {
            $temp_path = LEPTON_PATH . $req;
            if (file_exists($temp_path)) 
            {
                $result = require_once $temp_path;
                if ( (false === $result) && ($interrupt === true) )
                {
                    die ("<pre class='ui message'>\nCan't include: ".$temp_path."\n</pre>");
                }                    
            }
        }            
    }    

	
	/**
	 *	include files if exist and end process of start file
	 *	@param array linear array with filenames to include 
	 *
	 *	@code{.php}
	 *	$file_names = array(
	 *	    '/backend/access/index.php'
	 *	);
	 *	if( true === LEPTON_handle::require_alternative($file_names) )
	 *  {
	 *      return 0; 
	 *  }
	 *	@endcode
	 *	@return boolean True, if one of the given files are found and included, false of none is found.
	 */	
	static public function require_alternative($file_names=array()) {
		if(is_string($file_names)) {
			$file_names = array($file_names);
		}		
		foreach ($file_names as $req)
		{
			$temp_path = LEPTON_PATH . $req;
			if (file_exists($temp_path)) 
			{
				require_once $temp_path;
				return true;
			}
		}
		return false;
	}	

	/**
	 *	install modules
	 *	@param array linear array with module_names to install 
	 *
	 *	@code{.php}
	 *	$module_names = array(
	 *		'code2'
	 *	);
	 *	LEPTON_handle::install_modules($module_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function install_modules( $module_names = array() ) {
		global $module_name, $module_license, $module_author, $module_directory, $module_version, $module_function, $module_description, $module_platform, $module_guid, $lepton_platform;
		if(is_string($module_names)) {
			$module_names = array($module_names);
		}			
		LEPTON_handle::register('load_module');
		$database = LEPTON_database::getInstance();
		
		foreach($module_names as $temp_addon) {
			$test = $database->get_one("SELECT `addon_id` FROM `".TABLE_PREFIX."addons` WHERE `directory` = '".$temp_addon."' ");
			if($database->is_error() ) {
				die($database->get_error().LEPTON_tools::display($test, 'div','ui info message') );
			}	
				
			if($test === NULL ) 
			{	
				$module_vars = array (
					'module_license', 'module_author'  , 'module_name', 'module_directory',
					'module_version', 'module_function', 'module_description',
					'module_platform', 'module_guid'						
				);
						
				foreach($module_vars as $varname )
				{
					if (isset(  ${$varname} ) )
						{
							unset( ${$varname} );
						}
				}
				
				$temp_path = LEPTON_PATH .'/modules/'.$temp_addon ;
				require $temp_path.'/info.php';
				load_module( $temp_path, true );					
			}											
		}
	}

	
	/**
	 *	upgrade modules
	 *	@param array linear array with module_names to update 
	 *
	 *	@code{.php}
	 *	$module_names = array(
	 *	'code2'
	 *	);
	 *	LEPTON_handle::upgrade_modules($module_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function upgrade_modules($module_names=array())
	{
		if(is_string($module_names))
		{
			$module_names = array($module_names);
		}			
		
		foreach ($module_names as $update)	
		{
			$temp_path = LEPTON_PATH . "/modules/" . $update . "/upgrade.php";
			if (file_exists($temp_path)) 
			{
				// call upgrade-script direct
				require $temp_path;
				
				// update db entries
				load_module( LEPTON_PATH . "/modules/" . $update );
				
				// unset module vars
				foreach(
                    array(
						'module_license', 'module_author'  , 'module_name', 'module_directory',
						'module_version', 'module_function', 'module_description',
						'module_platform', 'module_guid'
					) as $varname
				)
				{
					if (isset(  ${$varname} ) ) 
						{
							unset( ${$varname} );
						}
				}
			}
		}		
	}
	
	/**
	 *	Install droplets.
	 *
	 *	@param string for module name
	 *	@param mixed string/array for zip name
	 *
	 *	@code{.php}
	 *      $module_name = 'droplets';
	 *      $zip_names = array(
	 *          'droplet_LoginBox'
	 *      );	 
	 *      LEPTON_handle::install_droplets($module_name, $zip_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function install_droplets($module_name='',$zip_names=array()) {
		
		droplets::getInstance();
		
		if(is_string($zip_names)) {
			$zip_names = array($zip_names);
		}
		foreach ($zip_names as $to_install)	
		{
			$temp_path = LEPTON_PATH . "/modules/" . $module_name . "/install/".$to_install.".zip";
			if (file_exists($temp_path)) 
			{
				$result = droplet_install($temp_path, LEPTON_PATH . '/temp/unzip/');
				if(count ($result['errors']) > 0)
				{
                    die ('ERROR: file is missing: <b> '.(implode('<br />\n', $result['errors'])).' </b>.');
				}
			}
		}
		self::delete_obsolete_directories("/modules/" . $module_name . "/install");
	}


	/**
	 *	uninstall droplets
	 *	@param string for module name
	 *	@param mixed string/array for zip name
	 *
	 *	@code{.php}
	 *	$droplet_names = array(
	 *	'check-css'
	 *	);	 
	 *	LEPTON_handle::uninstall_droplets($droplet_names);
	 *
	 *	@endcode
	 *	@return nothing
	 */	
	static public function uninstall_droplets($droplet_names=array()) {
		if(is_string($droplet_names)) {
			$droplet_names = array($droplet_names);
		}
		$database = LEPTON_database::getInstance();
		
		foreach ($droplet_names as $to_uninstall)
		{
			$to_delete = array();
			$database->execute_query(
				"SELECT `id` FROM ".TABLE_PREFIX."mod_droplets WHERE `name` = '".$to_uninstall."' ",
				true,
				$to_delete,
				false
            );
			if(isset($to_delete['id'])) 
			{
				$database->simple_query("DELETE FROM `".TABLE_PREFIX."mod_droplets` WHERE `id` = ".$to_delete['id'] );
				$database->simple_query("DELETE FROM `".TABLE_PREFIX."mod_droplets_permissions` WHERE `id` = ".$to_delete['id'] );				
			}
		}
	}
	
    /**
	 *	Static method to "require" a (LEPTON-) internal function file 
	 *
	 *	@param	string	A function name, and/or an array with function-names
	 *
	 *	example given:
	 *	@code{.php}
	 *
	 *		//	one single function
	 *		LEPTON_handle::register( "get_menu_title" );
	 *
	 *		//	a set of functions
	 *		LEPTON_handle::register( "get_menu_title", "page_tree", "js_alert_encode" );
	 *
	 *		//	a set of function names inside an array
	 *		$all_needed= array("get_menu_title", "page_tree", "js_alert_encode" );
	 *		LEPTON_handle::register( $all_needed, "rm_full_dir" );
	 *
	 *	@endcode
	 *
	 */
	static function register() {
		
		if( 0 === func_num_args() )
		{
			return false;
		}
		
		$all_args = func_get_args();
		foreach($all_args as &$param)
		{	
			if( true === is_array($param) )
			{
				foreach($param as $ref) 
				{
					self::register( $ref );	
				}
			} 
			else 
			{
				if(!function_exists($param))
				{
					$lookUpPath = LEPTON_PATH."/framework/functions/function.".$param.".php";
					if(file_exists($lookUpPath))
					{
					    require_once $lookUpPath;
					}
				}
			}
		}
		return true;
	}
	
	/**
	 *  Returns 'false' if the given string contains a non 'valid' char.
	 *  Accepted chars are "a" to "z" (case insensitive) and "0" to "9" with "-", ".", "_". 
	 *   
	 *  @param  string  $sEmail     Any string.
	 *  @return bool                True if all chars matches.
	 *
	 *	@code{.php}
	 *
	 *      $sEmailToCheck = "ad'OR'1'='1'OR'@mail.com";
	 *      $bEmailIsValid = LEPTON_handle::checkEmailChars( $sEmailToCheck );
	 *      // $bEmailIsValid will have boolean "false"
	 *
	 *      $sEmailToCheck = "example.php@toplevel.tld";
	 *      $bEmailIsValid = LEPTON_handle::checkEmailChars( $sEmailToCheck );
	 *      // $bEmailIsValid will have boolean "true"
	 *
	 *	@endcode
	 */
	public static function checkEmailChars($sEmail)
	{ 
		$add_custom = '';
		$ini_file_name = LEPTON_PATH."/config/lepton.ini.php";
			
        if( true === file_exists( $ini_file_name ) ) {
        	$config = parse_ini_string(";".file_get_contents($ini_file_name), true );
			if($config['custom_vars']['additional_email_chars'] != '')
			{
				$add_custom = $config['custom_vars']['additional_email_chars'];
			}
		}	
        return ( false === filter_var( $sEmail, FILTER_VALIDATE_EMAIL ) || ( !preg_match( "#^[a-zA-Z0-9@\-_\.".$add_custom."]+$#", $sEmail ) ) )
            ? false
            : true
            ;
    }
    
    /**
	 *  Returns 'false' if the given string contains a non 'valid' char.
	 *  Accepted chars are "a" to "z" (case insensitive) and "0" to "9" with "-", ".", "_", " " (space). 
	 *   
	 *  @param  string  $sEmail     Any string.
	 *  @return bool                True if all chars matches.
	 *
	 *	@code{.php}
	 *
	 *      $sNameToCheck = "ad'OR'1'='1'OR'@mail.com";
	 *      $bNameIsValid = LEPTON_handle::checkEmailChars( $sNameToCheck );
	 *      // $bNameIsValid will have boolean "false"
	 *
	 *      $sNameToCheck = "Aldus2 - from LEPTON-CMS";
	 *      $bNameIsValid = LEPTON_handle::checkEmailChars( $sNameToCheck );
	 *      // $bNameIsValid will have boolean "true"
	 *
	 *	@endcode
	 */
    public static function checkUsernameChars($sName)
	{ 
		$add_custom = '';
		$ini_file_name = LEPTON_PATH."/config/lepton.ini.php";
			
        if( true === file_exists( $ini_file_name ) ) {
        	$config = parse_ini_string(";".file_get_contents($ini_file_name), true );
			if($config['custom_vars']['additional_usernames_chars'] != '')
			{
				$add_custom = $config['custom_vars']['additional_usernames_chars'];
			}
		}
				
		return (!preg_match("#^[a-zA-Z0-9@ \-,_\.".$add_custom."]+$#", $sName))
            ? false
            : true
            ;
    }

    /**
     *  NOTICE:
     *  Strings (hash) consisting only of integers can be "checked" with intval() 
     *    example: $sValueToCheck = intval($StringToCheck);
     *    result: $sValueToCheck is forced to be an integer
     */

    /**
     *  Returns 'false' if the given string contains a non 'valid' char.
     *  Accepted chars are "a" to "f" (case insensitive) and "0" to "9" with "-"
     *
     *  @param  string  $sHexHash     Any string.
     *  @return bool                True if all chars matches.
     *
     *  @code{.php}
     *
     *      $sHexHash = "ad'OR'1'='1'OR'@mail.com";
     *      $bHashIsValid = LEPTON_handle::checkHexChars( $sHexHash );
     *      // $bHashIsValid will have boolean "false"
     *
     *      $sHexHash = "936DA01F-9ABD-4D9D-80C7-02AF85C822A8";
     *      $bHashIsValid = LEPTON_handle::checkHexChars( $sHexHash );
     *      // $bHashIsValid will have boolean "true"
     *
     *  @endcode
     */
    public static function checkHexChars($sHexHash)
    { 
        return (!preg_match("#^[a-fA-F0-9\-]+$#", $sHexHash))
            ? false
            : true
            ;
    }    
    
    /**
     *  To "restore" some given special chars. E.g. per default "&lt" is resored to "<";
     *
     *  @param string   $sAnyString     Any valid string. Pass|call by reference.
     *  @param array    $aAnyAssocArray Optional an assoc. array with the char-pairs.
     *                                  Default is internal const SPECIAL_CHARS_RESTORE
     */
    public static function restoreSpecialChars( string &$sAnyString, array $aAnyAssocArray = self::SPECIAL_CHARS_RESTORE )
    {
        $aLookUp  = array_keys($aAnyAssocArray);
        $aReplace = array_values($aAnyAssocArray);
        
        $sAnyString = str_replace( $aLookUp, $aReplace, $sAnyString );
    }
    
    
    /**
     *  Create new passwordfiles in standard protected directory
     *
     *  Default directory is '/temp/secure/.htaccess'
     */
    public static function restoreStandardProtection() : bool
    {
        if(file_exists(self::HTACCESS_PATH))
        {
            unlink(self::HTACCESS_PATH);
            unlink(self::HTPASSWD_PATH);
        }
        $admin_username = LEPTON_database::getInstance()->get_one("SELECT username FROM ".TABLE_PREFIX."users WHERE user_id = 1 ");
        $htuser = $admin_username;
        $random_value = random_int(100000,999999);
        $htpassword = password_hash($random_value, PASSWORD_DEFAULT );

$htcontent = "# .htaccess-Datei
AuthType Basic
AuthName 'Protected area - Please insert password!'
AuthUserFile ".self::HTPASSWD_PATH."
require user ".$htuser."
";

$htpwcontent = "# Password file, user:".$htuser.", password: ".$random_value."
".$htuser.":".$htpassword."
";

        file_put_contents(self::HTACCESS_PATH,$htcontent);
        file_put_contents(self::HTPASSWD_PATH,$htpwcontent);

        return true;        
    }

    /**
     *  Create passwordfiles in chosen directory
     *
     *  @param  string  $path       Default directory is '/temp/secure/'.
     *  @param  string  $username   The user name the htacess belongs to
     *  @return bool
     */
    public static function createStandardProtection($path = '/temp/secure/' , $username = '') : bool
    {
        if($username == '')
        {
            echo LEPTON_tools::display('USERNAME is mandantory as second parameter in this method', 'pre', 'ui red message');
            return false;
        }

        if(file_exists(LEPTON_PATH.$path.'/.htaccess'))
        {
            unlink(LEPTON_PATH.$path.'/.htaccess');
            unlink(LEPTON_PATH.$path.'/.htpasswd');
        }

        $htuser = $username;
        $random_value = random_int(100000,999999);
        $htpassword = password_hash($random_value, PASSWORD_DEFAULT );

$htcontent = "# .htaccess-Datei
AuthType Basic
AuthName 'Protected area - Please insert password!'
AuthUserFile ".LEPTON_PATH.$path."/.htpasswd
require user ".$htuser."
";

$htpwcontent = "# Password file, user:".$htuser.", password: ".$random_value."
".$htuser.":".$htpassword."
";

        file_put_contents(LEPTON_PATH.$path.'/.htaccess',$htcontent);
        file_put_contents(LEPTON_PATH.$path.'/.htpasswd',$htpwcontent);
        
        return true;
    }
	
	
    /**
     *  Sort given array
     *	@param  array   $array		Given array
     *  @param  string  $on			key to sort
     *  @param  string  $order   	SORT_ASC OR SORT_DESC
     *  @return array
     *	 
     *  @code{.php}
     *		$array_sorted = LEPTON_handle::array_orderby($array_source,'field_to_sort',SORT_DESC);
     *  @endcode
     */	
    static function array_orderby($array, $on, $order = SORT_ASC)
    {
        $new_array = [];
        $sortable_array = [];

        if (count($array) > 0) 
        {
            foreach ($array as $k => $v)
            {
                if (is_array($v))
                {
                    foreach ($v as $k2 => $v2)
                    {
                        if ($k2 == $on)
                        {
                            $sortable_array[$k] = $v2;
                        }
                    }
                } else {
                    $sortable_array[$k] = $v;
                }
            }

            switch ($order)
            {
                case SORT_ASC:
                    asort($sortable_array);
                    break;
                
                case SORT_DESC:
                    arsort($sortable_array);
                    break;
                
                default:
                    LEPTON_tools::display(__CLASS__." [10023] No order match!", "pre", "ui message red");
                    break;
            }

            foreach ($sortable_array as $k => $v)
            {
                $new_array[$k] = $array[$k];
            }
        }

        return $new_array;
    }

    //  [5.4.1]
    static public function themeExists( string $sThemeName = "")
    {
        $result = LEPTON_database::getInstance()->get_one("SELECT `directory` FROM `".TABLE_PREFIX."addons` WHERE `directory` ='".$sThemeName."'");
        return ($result !== NULL);
    }
  
    /**
     * [5.4.2] Copies module-specific theme-files to the installed themes during  
     * installation and/or upgrade. The "basefiles" have to be inside the module-directory  
     * inside the subdirectory "backendthemes" e.g.  
     *      ~modules/foldergallery/backendthemes/talgos
     *      ~modules/foldergallery/backendthemes/black_hole
     * 
     * @param   string  $sModuleDirectory   Module-Directory or path to a file inside the module.
     * @code
     *  // 1.1 direct by name
     *  LEPTON_handle::moveThemeFiles( "foldergallery" );
     * 
     *  // 1.2 via a var
     *  LEPTON_handle::moveThemeFiles( $modules_directory );
     * 
     *  // 1.3 the directory (path)
     *  LEPTON_handle::moveThemeFiles( __DIR__ );
     * 
     *  // 1.4 via the filepath of the current file
     *  LEPTON_handle::moveThemeFiles( __FILE__ ); // inside upgrade.php
     * 
     * @endcode
     */
    static public function moveThemeFiles( string $sModuleDirectory="")
    { 
        $sSingleModuleDirectory = self::getModuleDirectory($sModuleDirectory);
 
        LEPTON_handle::register("change_mode", "make_dir");
        
        // get all themes
        $aAllThemes = self::getAllThemes();
        foreach($aAllThemes as $sForTheme)
        {
            $sBaseSourcePath = LEPTON_PATH."/modules/".$sSingleModuleDirectory."/backendthemes/".$sForTheme;
            $sTargetPath = LEPTON_PATH."/templates/".$sForTheme."/backend/".$sSingleModuleDirectory;
        
            if( true === file_exists($sBaseSourcePath) )
            {
                self::copyThemeFilesRecursive($sBaseSourcePath, $sTargetPath);
            }
        }
    }
    
    //  [5.4.3] Internal
    static public function copyThemeFilesRecursive( string $dirsource, string $dirdest, int $deep=0 )
    {
        if ( true === is_dir($dirsource) )
        {
            if(($deep === 0) && (false === is_dir($dirdest)))
            {
                make_dir($dirdest);
            }
            $dir= dir($dirsource);
            while ( $file = $dir->read() )
            {
                if( $file[0] === "." )
                {
                    continue;
                }
                if( !is_dir($dirsource."/".$file) )
                {
                    copy ($dirsource."/".$file, $dirdest."/".$file);
                    change_mode($dirdest."/".$file);
                } else {
                    make_dir($dirdest."/".$file);
                    self::copyThemeFilesRecursive($dirsource."/".$file, $dirdest.'/'.$file, $deep+1);
                }
                
            }
            $dir->close();
        }
        if ($deep == 0)
        {
            // Is this a working copy? if not ... we try to remove the unneeded files here
            if(!file_exists(dirname(dirname($dirsource))."/.git"))
            {
                LEPTON_handle::register("rm_full_dir");
                rm_full_dir( $dirsource );
            }
        }
        return true;
    }
    //  [5.4.4] Deletes all "theme" specific files of a given module from all known themes
    static public function removeAllThemeFiles( string $sModuleDirectory = "")
    {
        $sSingleModuleDirectory = self::getModuleDirectory($sModuleDirectory);
        $aAllThemes = self::getAllThemes();
        
        foreach($aAllThemes as $aTempThemeDirectory)
        {
            self::delete_obsolete_directories("/templates/".$aTempThemeDirectory."/backend/".$sSingleModuleDirectory);
        }
    }
    //  [5.4.5] Try to get the module directory
    static public function getModuleDirectory( string $sAnyNameOrPath = "")
    {
        if(empty($sAnyNameOrPath))
        {
            return "";
        }
        
        /**
         *  [5.4.5.2] ist the method called from a file inside the given module-dir?
         *  see: https://stackoverflow.com/questions/2960805/php-determine-where-function-was-called-from
         *  This is to avoid the situation that module A can copy/delete theme-files from module B!
         */
        $backtrace = debug_backtrace();
        $aTempTest = [""];
        if(isset($backtrace[1]['file']))
        {
            $aTempTest = self::getPathElements($backtrace[1]['file']);
            
        }
        
        // [5.4.5.3] try to extract the module directory
        $aTemp = self::getPathElements($sAnyNameOrPath);
        
        return ($aTemp[0] === $aTempTest[0]) ? $aTemp[0] : "";
    }
    //  [5.4.6] Internal
    static public function getAllThemes()
    {
        $aAllThemes = [];
        LEPTON_database::getInstance()->execute_query(
            "SELECT `directory` FROM `".TABLE_PREFIX."addons` WHERE `function` = 'theme'",
            true,
            $aAllThemes,
            true
        );
        $aReturnValues = [];
        foreach($aAllThemes as $aTempTheme)
        {
            $aReturnValues[] = $aTempTheme["directory"];
        }
        return $aReturnValues;
    }
    
    /**
     * [5.4.7] Internal
     * 
     * @param   string  $sAnyNameOrPath
     * @return  array
     * 
     */
    final static public function getPathElements( string $sAnyNameOrPath="") : array
    {
        return explode(
            DIRECTORY_SEPARATOR,
            str_replace(
                LEPTON_PATH.DIRECTORY_SEPARATOR."modules".DIRECTORY_SEPARATOR,
                "",
               $sAnyNameOrPath
        ));   
    }
}