<?php

/**
 * This file is part of an ADDON for use with LEPTON Core.
 * This ADDON is released under the GNU GPL.
 * Additional license terms can be seen in the info.php of this module.
 *
 *
 *	@module			captcha
 *	@version		see info.php of this module
 *	@authors		LEPTON Project, W. Studer
 *	@copyright		2020-2022 LEPTON Project
 *	@link			https://lepton-cms.org
 *	@license		http://www.gnu.org/licenses/gpl.html
 *	@license_terms	please see info.php of this module
 *	@platform		see info.php of this module
 *
 *
 */

trait plugin_trait
{
	// captcha settings
	private $captcha_field = array( "KEY" => "captcha"
									,"REQUEST" => array ('type' => 'string_clean', 'default' => "" ));

	public $language = array();
	public $defaults = array(
			"ACTION"			=> "all",
			"SECTION_ID"		=> 0,
			"TEXT_ATTR"			=> array(),
			"IMAGE_ATTR"		=> array(),
			"INPUT_ATTR"		=> array(),
			"COMPARE_CHAR"		=> ""
	);
	public $is_deprecated = false;
	public $is_useable = true;
	public $supported_actions = array();
	public $default_action = "all";
	public $action_params = array();
	public $show_playground = true;
	public $plugin_name = "";

	// plugin setting
	public $settings = array();

	/**
	 *  Return the instance of this class.
	 *
	 */
	static $instance;

	public static function getInstance()
	{
		if (null === static::$instance)
		{
			static::$instance = new static();
		}
		return static::$instance;
	}

	public function __construct()
	{
		// set default values
		$this->define_defaults();
		$this->plugin_name = get_class( $this );
	}

	public function initialize()
	{
	}

	/* ===============================================================================
	 * test a captcha
	 *
	 * scope:	frontend
	 * access:	public
	 * param:	$value, $section_id
	 * output:	the captcha
	 */
	public function test_captcha( $value, $section_id )
	{
		// get value if not passed as parameter
		if ( true === empty( $value ))
		{
			$oREQUEST = LEPTON_request::getInstance();
			$input_fields = array (
				$this->captcha_field["KEY"]	=> $this->captcha_field["REQUEST"]
			);
			$valid_fields = $oREQUEST->testPostValues($input_fields);
			$value = $valid_fields[ $this->captcha_field["KEY"] ];
		}

		// start verify
		if ( isset( $_SESSION[ "captcha" . $section_id ] ))
		{
			if ( $_SESSION[ "captcha" . $section_id ] == $value )
			{ 
				return true; 
			}	// correct value
			else
			{ 
				return false; 
			}	// wrong value
		}
		else		
		{ 
			return true; 
		}	// no captcha has been set
	}

	/* ===============================================================================
	 * build a captcha
	 *
	 * scope:	frontend
	 * access:	public
	 * param:	array with attributes
	 * output:	captcha
	 */
	public function build_captcha( $params = array() )
	{
		// init, might be overwritten in calculate_captcha
		$_SESSION['captcha_time'] = time();

		// merge input with defaults
		$params = $this->merge_arrays( $params, $this->defaults );

		// is action valid
		if ( false === in_array( $params[ "ACTION" ], $this->supported_actions ) )
		{
			$params[ "ACTION" ] = $this->default_action; 
		}	// reset to default

		// set section id if missing
		if ( false === array_key_exists( "SECTION_ID", $params   ))
			{ $params[ "SECTION_ID" ] = 0; }
		$_SESSION[ 'captcha_id' ] = $params[ "SECTION_ID" ];

		// build complete parameter set
		$paramset = $this->build_captcha_paramset( $params );
		if ( empty( $paramset ))
		{
			return ""; 
		}

		// add section_id
		if (( false === array_key_exists( "SECTION_ID", $paramset ))
		 && ( true  === array_key_exists( "SECTION_ID", $params   )))
			{ $paramset[ "SECTION_ID" ] = $params[ "SECTION_ID" ]; }
		elseif ( false === array_key_exists( "SECTION_ID", $paramset ))
			{ $paramset[ "SECTION_ID" ] = 0; }

		// evaluate captcha template
		$templatepath = "";
		$template = "";
		if ( defined( "LEPTON_PATH" ))
		{
			$aLookUpPath = array(
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/#PARENT#/templates/captcha_control/#PLUGIN#/",
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/#PARENT#/templates/captcha_control/",
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/#PARENT#/templates/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/#PARENT#/templates/captcha_control/#PLUGIN#/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/#PARENT#/templates/captcha_control/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/#PARENT#/templates/",
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/captcha_control/templates/captcha_control/#PLUGIN#/",
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/captcha_control/templates/captcha_control/",
				LEPTON_PATH . "/templates/#TEMPLATE#/frontend/captcha_control/templates/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/captcha_control/templates/captcha_control/#PLUGIN#/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/captcha_control/templates/captcha_control/",
				LEPTON_PATH . "/templates/" . DEFAULT_TEMPLATE . "/frontend/captcha_control/templates/",
				LEPTON_PATH . "/modules/#PARENT#/templates/captcha_control/#PLUGIN#/",
				LEPTON_PATH . "/modules/#PARENT#/templates/captcha_control/",
				LEPTON_PATH . "/modules/#PARENT#/templates/",
				LEPTON_PATH . "/modules/captcha_control/plugins/#PLUGIN#/templates/",
				LEPTON_PATH . "/modules/captcha_control/templates/"
			);

			// check if a parent module is set in param list
			// and if frontent page related template is defined
			$translate = array( "#PLUGIN#" => $this->plugin_name );
			if ( array_key_exists( "PARENT", $params ) )
				{ $translate[ "#PARENT" ]	= $params[ "PARENT" ]; }
			if ( true === defined( "TEMPLATE" ) )
				{ $translate[ "#TEMPLATE" ]	= TEMPLATE; }

			foreach( $aLookUpPath as $idx => &$path )
			{
				// if one of the codes is available, replace
				if (( strpos( $path, "#PARENT" )   > 0 )
				 || ( strpos( $path, "#TEMPLATE" ) > 0 )
				 || ( strpos( $path, "#PLUGIN" )   > 0 ))
					{ $path = strtr( $path, $translate ); }

				// if one of the codes is still available after replace, remove path
				if (( strpos( $path, "#PARENT" )   > 0 )
				 || ( strpos( $path, "#TEMPLATE" ) > 0 )
				 || ( strpos( $path, "#PLUGIN" )   > 0 ))
					{ unset( $aLookUpPath[ $idx ] ); }
			}

			// get first available template & related path
			foreach( $aLookUpPath as $sTempPath )
			{
				// If it exist, check if it's a directory
				if ( true === is_dir( $sTempPath ))
				{
					if ( true === file_exists( $sTempPath . "/captcha_" . $params[ "ACTION" ] . ".lte" ) )
					{
						$templatepath = $sTempPath;
						$template = "captcha_" . $params[ "ACTION" ] . ".lte";
						break; 	// stop on first file found
					}
				}
			}
		}

		// run twig engine with the template
		$captcha = "";
		if (( false === empty( $templatepath )) && ( false === empty( $template ) ))
		{
			$oTWIG = lib_twig_box::getInstance();
			$oTWIG->registerPath( $templatepath, "captcha_control");

			// create & render twig template engine
			$captcha = $oTWIG->render(
				"@captcha_control/" . $template,
				$paramset
			);
		}

		// special handling for action = data
		if ( $params[ "ACTION" ] == "data" )
		{
			$paramset[ "CAPTCHA" ] = $captcha;
			return $paramset;
		}

		// return the captcha result
		return $captcha;
	}

	/* ===============================================================================
	 * build the captcha parameterset
	 *
	 * scope:	frontend
	 * access:	private
	 * param:	array with attributes
	 * output:	array with all captcha related settings
	 */
	private function build_captcha_paramset( $params = array() )
	{
		// build complete parameter set
		$paramset = array();
		switch( $params[ "ACTION" ] )
		{
			case "data":
			case "js":
			case "all":
				$paramset[ "SECTION_ID" ]	= $params[ "SECTION_ID" ];
				if (( $this->settings[ "GENERIC" ][ "reload_type" ] == "IFRAME" )
				 && ( file_exists( LEPTON_PATH . "/modules/captcha_control/plugins/" . $this->plugin_name . "/reload.php" )))
					{ $paramset[ "RELOAD_SRC" ]	= LEPTON_URL . "/modules/captcha_control/plugins/" . $this->plugin_name . "/reload.php"; }
				$paramset[ "IMAGE_ATTR" ]	= $this->concat_attributes( $params, "IMAGE_ATTR" );
				$paramset[ "IMAGE_SRC" ]	= $this->calculate_captcha( $params );
				if ( array_key_exists( "CAPTCHA_WIDTH", $params ))
					{ $paramset[ "CAPTCHA_WIDTH" ]= $params[ "CAPTCHA_WIDTH" ]; }
				if ( array_key_exists( "CAPTCHA_HEIGHT", $params ))
					{ $paramset[ "CAPTCHA_HEIGHT" ]= $params[ "CAPTCHA_HEIGHT" ]; }
				$paramset[ "COMPARE_CHAR" ]	= $params[ "COMPARE_CHAR" ];
				$paramset[ "INPUT_ATTR" ]	= $this->concat_attributes( $params, "INPUT_ATTR" );
				$paramset[ "INPUT_FORM" ]	= "INPUT";
				$paramset[ "TEXT_ATTR" ]	= $this->concat_attributes( $params, "TEXT_ATTR" );
				$paramset[ "TEXT_TEXT" ]	= $this->get_translation( 'ENTER_RESULT' );

				$paramset[ "RELOAD_TYPE" ]	= $this->settings[ "GENERIC" ][ "reload_type" ];
				$paramset[ "CAPTCHA_SPEECH"]= $this->settings[ "GENERIC" ][ "captcha_speech" ];
				if ( $paramset[ "CAPTCHA_SPEECH" ] == 1 )
				{
					$paramset[ "CAPTCHA_SPEECH_RATE" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_rate" ];
					$paramset[ "CAPTCHA_SPEECH_PITCH" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_pitch" ];
				}

				$_SESSION[ 'CAPTCHA_IMAGE_ATTR' ]	= $paramset[ "IMAGE_ATTR" ];	// used in reload.php
				$_SESSION[ 'CAPTCHA_IMAGE_SRC' ]	= $paramset[ "IMAGE_SRC" ];		// used in reload.php
				break;

			case "image_iframe":
				if ( $this->settings[ "GENERIC" ][ "reload_type" ] == "IFRAME" )
				{
					$paramset[ "SECTION_ID" ]	= $params[ "SECTION_ID" ];
					if ( file_exists( LEPTON_PATH . "/modules/captcha_control/plugins/" . $this->plugin_name . "/reload.php" ))
						{ $paramset[ "RELOAD_SRC" ]	= LEPTON_URL . "/modules/captcha_control/plugins/" . $this->plugin_name . "/reload.php"; }
					$paramset[ "IMAGE_ATTR" ]	= $this->concat_attributes( $params, "IMAGE_ATTR" );
					$paramset[ "IMAGE_SRC" ]	= $this->calculate_captcha( $params );
					if ( array_key_exists( "CAPTCHA_WIDTH", $params ))
						{ $paramset[ "CAPTCHA_WIDTH" ]= $params[ "CAPTCHA_WIDTH" ]; }
					if ( array_key_exists( "CAPTCHA_HEIGHT", $params ))
						{ $paramset[ "CAPTCHA_HEIGHT" ]= $params[ "CAPTCHA_HEIGHT" ]; }

					$paramset[ "RELOAD_TYPE" ]	= $this->settings[ "GENERIC" ][ "reload_type" ];
					$paramset[ "CAPTCHA_SPEECH"]= $this->settings[ "GENERIC" ][ "captcha_speech" ];
					if ( $paramset[ "CAPTCHA_SPEECH" ] == 1 )
					{
						$paramset[ "CAPTCHA_SPEECH_RATE" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_rate" ];
						$paramset[ "CAPTCHA_SPEECH_PITCH" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_pitch" ];
					}

					$_SESSION[ 'CAPTCHA_IMAGE_ATTR' ]	= $paramset[ "IMAGE_ATTR" ];	// used in reload.php
					$_SESSION[ 'CAPTCHA_IMAGE_SRC' ]	= $paramset[ "IMAGE_SRC" ];		// used in reload.php
				}
				break;

			case "image":
				$paramset[ "IMAGE_ATTR" ]	= $this->concat_attributes( $params, "IMAGE_ATTR" );
				$paramset[ "IMAGE_SRC" ]	= $this->calculate_captcha( $params );

				$paramset[ "RELOAD_TYPE" ]	= $this->settings[ "GENERIC" ][ "reload_type" ];
				$paramset[ "CAPTCHA_SPEECH"]= $this->settings[ "GENERIC" ][ "captcha_speech" ];
				if ( $paramset[ "CAPTCHA_SPEECH" ] == 1 )
				{
					$paramset[ "CAPTCHA_SPEECH_RATE" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_rate" ];
					$paramset[ "CAPTCHA_SPEECH_PITCH" ]	= $this->settings[ "GENERIC" ][ "captcha_speech_pitch" ];
				}

				$_SESSION[ 'CAPTCHA_IMAGE_ATTR' ]	= $paramset[ "IMAGE_ATTR" ];	// used in reload.php
				$_SESSION[ 'CAPTCHA_IMAGE_SRC' ]	= $paramset[ "IMAGE_SRC" ];		// used in reload.php
				break;

			case "input":
				$paramset[ "INPUT_ATTR" ]	= $this->concat_attributes( $params, "INPUT_ATTR" );
				$paramset[ "INPUT_FORM" ]	= "INPUT";
				break;

			case "text":
				$paramset[ "TEXT_ATTR" ]	= $this->concat_attributes( $params, "TEXT_ATTR" );
				$paramset[ "TEXT_TEXT" ]	= $this->get_translation( 'ENTER_RESULT' );
				break;
		}

		return $paramset;
	}

	/* ===============================================================================
	 * get fields for captcha maintenance in admin module captcha_control
	 *
	 * scope:	backend
	 * access:	public
	 * param:	none
	 * output:	array containing list of fields shown in backend gui
	 */
	public function get_control_fieldset()
	{
		// return array of field definition(s)
		$fieldset = array();

		return $fieldset;
	}

	/* ===============================================================================
	 * get captcha maintenance fields saved in db
	 *
	 * scope:	backend
	 * access:	public
	 * param:	none
	 * output:	array containing db related fields and their input validations
	 */
	public function get_db_fields()
	{
		// return array of field definition(s)
		$fieldset = array();

		return $fieldset;
	}

	/* ===============================================================================
	 * do plugin specific actions before plugin related settings are saved
	 *
	 * scope:	backend
	 * access:	public
	 * param:	array containing plugin related settings
	 * output:	array containing plugin related settings after validation (if any)
	 */
	public function settings_save( $settings )
	{
		return $settings;
	}

	/* ===============================================================================
	 * set settings
	 *
	 * scope:	frontend, backend
	 * access:	public
	 * param:	settings array
	 * output:	none
	 */
	public function set_settings( $settings )
	{
		$plugin = $this->plugin_name;

		// check if a configuration for this plugin exists
		if ( false === array_key_exists( $plugin, $settings[ "all_plugin_config" ] ))
			{ $settings[ "all_plugin_config" ][ $plugin ] = array(); }

		// initial set of settings
		$this->settings = $settings;

		// set plugin specific settings for direct access
		$this->settings[ "PLUGIN" ] = $settings[ "all_plugin_config" ][ $plugin ];

		// get list of plugin specific fields
		$db_fieldset = $this->get_db_fields();
		if ( count( $db_fieldset ) > 0 )
		{
			// initialize all fields if not present in the settings
			foreach( $db_fieldset as $field => $fieldset )
			{
				if ( false === array_key_exists( $field, $this->settings[ "PLUGIN" ] ))
				{
					$this->settings[ "PLUGIN" ][ $field ] = $fieldset[ "default" ];
				}
			}
		}

		// check if a generic configuration exists
		if ( false === array_key_exists( "GENERIC", $settings[ "all_plugin_config" ] ))
			{ $settings[ "all_plugin_config" ][ "GENERIC" ] = array(); }

		// set generic captcha settings for direct access
		$this->settings[ "GENERIC" ] = $settings[ "all_plugin_config" ][ "GENERIC" ];

		// set possible generic fields with their defaults
		$generic_fields = array(
			'reload_type'			=> "IFRAME",
			'captcha_speech'		=> 1,
			'captcha_speech_rate'	=> 0.9,
			'captcha_speech_pitch'	=> 0.6
		);
		// initialize all fields if not present in the settings
		foreach( $generic_fields as $field => $default )
		{
			if ( false === array_key_exists( $field, $this->settings[ "GENERIC" ] ))
			{
				$this->settings[ "GENERIC" ][ $field ] = $default;
			}
		}

		return true;
	}

	/* ===============================================================================
	 * merge with caller translation
	 *
	 * scope:	frontend, backend
	 * access:	public
	 * param:	caller translation array
	 * output:	merged arrays of translations
	 */
	public function merge_languages( $languages )
	{
		$this->getLanguageFile();

		$this->language = $this->merge_arrays( $this->language, $languages, "MIXED" );

		return $this->language;
	}

	/* ===============================================================================
	 * calculate the captcha string
	 *
	 * scope:	frontend
	 * access:	public
	 * param:	array with attributes
	 * output:	the captcha
	 */
	public function calculate_captcha( $params )
	{
		// init
		$t = time();
		$_SESSION[ 'captcha_time' ]	= $t;
		$_SESSION[ 'captcha_id' ]	= $params[ "SECTION_ID" ];

		// calculate the captcha
		$captcha = LEPTON_URL . "/modules/captcha_control/plugins/". $this->plugin_name . "/image.php?" . $t;

		return $captcha;
	}

	/* ===============================================================================
	 * concat all array attributes together which are not empty
	 *
	 * scope:	frontend, backend
	 * access:	public
	 * param:	array with attributes
	 * param:	the attribute key
	 * output:	string with attributes
	 */
	public function concat_attributes( $params, $attribute )
	{
		$return = "";

		if ( true === is_array( $params[ $attribute ] ))
		{
			foreach( $params[ $attribute ] as $key => $value )
			{
				if ( false === empty ( $value ))
				{
					$return .= ' ' . $key . '="' . $value . '"';

					// keep it as parameter used in current action
					if ( false === array_key_exists( $attribute, $this->action_params ))
						{ $this->action_params[ $attribute ] = array(); }
					$this->action_params[ $attribute ][ $key ] = $value;
				}
			}
		}
		else
		{
			$return = $params[ $attribute ];
		}

		return trim( $return );
	}

	/* ===============================================================================
	 * merge 2 arrays and allign all keys to be in same case (upper, lower)
	 *
	 * scope:	frontend, backend
	 * access:	private
	 * param:	primary array, e.g. input params
	 * param:	secondary array, e.g. defaults
	 * param:	case
	 * output:	merged array with defined case
	 */
	private function merge_arrays( $arr1, $arr2, $case = CASE_UPPER )
	{
		if (( $case === CASE_UPPER ) || ( $case === CASE_LOWER ))
		{
			$arr1 = array_change_key_case( $arr1, $case );
			$arr2 = array_change_key_case( $arr2, $case );
		}

		$array = array_replace_recursive( $arr2, $arr1 );

		return $array;
	}	 
	 
	 
	 
	/* ===============================================================================
	 * Try to get a module-spezific language file.
	 *
	 * scope:	frontend, backend
	 * access:	private
	 * param:	none
	 * output:	none
	 */
	private function getLanguageFile()
	{
		if ( defined( "LEPTON_PATH" ))
		{
			$aLookUpFilenames = [
				LANGUAGE."_custom.php",
				LANGUAGE.".php",
				"EN_custom.php",
				"EN.php"
			];
			$lookUpPath = LEPTON_PATH."/modules/captcha_control/plugins/" . $this->plugin_name . "/languages/";

			foreach( $aLookUpFilenames as $sTempFilename )
			{
				if ( true === file_exists( $lookUpPath.$sTempFilename ) )
				{
					require $lookUpPath.$sTempFilename;
					break;
				}
			}

			if ( isset( $MOD_PLUGIN ))
			{
				$this->language = $MOD_PLUGIN;
			}
		}
	}

	/* ===============================================================================
	 * get a language translations for a codes
	 *
	 * scope:	frontend, backend
	 * access:	private
	 * param:	$code
	 * output:	$translation
	 */
	private function get_translation( $code )
	{
		// add translations if exists
		if ( true === array_key_exists( $code, $this->language ))
		{ 
			return $this->language[ $code ];
		}
		elseif (true === array_key_exists( strtoupper( $code ), $this->language ))			 
		{	
			return $this->language[ strtoupper( $code ) ]; 
		}
		else
		{
			$oTempCaptcha = captcha_control::getInstance();
			if(array_key_exists($code,$oTempCaptcha->language ))
			{
				return $oTempCaptcha->language[ strtoupper( $code ) ]; 
			}
		}
		return $code;
	}

}

?>
