<?php

/**
 *
 *	@module			quickform
 *	@version		see info.php of this module
 *	@authors		LEPTON project, Ruud Eisinga
 *	@copyright		2012-2022  LEPTON project (initial by Ruud Eisinga)
 *	@license		GNU General Public License
 *	@license terms	see info.php of this module
 *	@platform		see info.php of this module
 *
 *
 */


class quickform extends LEPTON_abstract 
{

	// custom class
	public $oQCFE;

	// set default language code
	public $default_language = "en";

	// private cache for the evaluated page language code of current page
	private $page_language;

	/**
	 *	The reference to *Singleton* instance of this class
	 *
	 *	@var	object
	 *	@access	private
	 *
	 */
	public static $instance;

    public function initialize()
    {
        // method is required by abstract parent
    }

	public function __construct($section_id = 0)
	{
		// overwrite the default constructor
	}

	/** =========================================================================
	 *	Get a captcha
	 */
	public function captcha($section_id = 0)
	{
		if(file_exists( LEPTON_PATH."/modules/quickform/recaptcha.php" ))
		{
			require_once LEPTON_PATH."/modules/quickform/recaptcha.php";
			return quickform_recaptcha::build_captcha();
		}

		require_once(LEPTON_PATH."/modules/captcha_control/captcha/captcha.php");
		ob_start();
			call_captcha("all","",$section_id);
			$captcha = ob_get_clean();
		return $captcha;
	}

	/** =========================================================================
	 *
	 * get quickform settings
	 *
	 * @param	int			A valid section id
	 * @return	Nothing
	 *
	 */
	public function get_settings( $section_id )
	{
		global $database;
		$result = array();
		$database->execute_query(
			"SELECT * FROM `".TABLE_PREFIX."mod_quickform` WHERE `section_id` = ".$section_id,
			true,
			$result,
			false
		);

		// add hardcoded values
		if ( array_key_exists("max_email_split", $result ) === false )
		{
			$result["max_email_split"] = 3;
		}

		return $result;
	}

	/** =========================================================================
	 *
	 * get page language
	 *
	 * @param	int			A valid page id
	 * @return	string		the page language
	 *
	 */
	public function get_page_language( $page_id )
	{
		if ( empty( $this->page_language ))
		{
			global $database;
			$this->page_language = $database->get_one("SELECT `language` FROM `".TABLE_PREFIX."pages` WHERE `page_id`=".(int)$page_id);
			$this->page_language = strtolower( $this->page_language );

            if (empty($this->page_language))
            {
                $this->page_language = $this->default_language;
            }
        }
        return $this->page_language;
    }

	/** =========================================================================
	 *
	 * get template including proper language as folder
	 *
	 * @param	int			A valid page id
	 * @param	string		the base path
	 * @param	string		a specific language
	 * @param	string		the template name
	 * @return	boolean		true if template has been found, false otherwise
	 *
	 */
	public function get_template( $page_id, &$template_path, &$template_lang, $template_name )
	{
		// set languages to be checked
		if ( $template_lang === "" )
		{
			$page_language = $this->get_page_language( $page_id );
			$languages = array( $page_language, $this->default_language );
		} else {
			$languages = array( $template_lang );
		}

		// validate
		if (strpos( $template_path, DIRECTORY_SEPARATOR . "templates") === false )
		{
			$template_path .= DIRECTORY_SEPARATOR . "templates";
		}
		$template_path .= DIRECTORY_SEPARATOR;
		$template_path = str_replace(array( "\\", "/", DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR ), DIRECTORY_SEPARATOR, $template_path);

		// check for template path
		$result = false;
		foreach( $languages as $template_lang )
		{
			if ( file_exists( $template_path . $template_lang . DIRECTORY_SEPARATOR . $template_name ))
			{
				$result = true;
				break;
			}
		}

		return $result;
	}

	/** =========================================================================
	 *
	 * get data fields out of a template, which could be submitted (field name start with "qf_")
	 *
	 * @param	string		the full path qualified template
	 * @return	array		an array with data fields out of the template
	 *
	 */
	public function get_template_fields( $template_file, $honeypotfield = "" )
	{
		// get the template file content
		// $template_string = file_get_contents( $template_file );
		global $section_id;

		$template_values = array(
            "WEBSITE_TITLE"     => WEBSITE_TITLE,
            "LEPTON_URL"		=> LEPTON_URL,
            "MOD_QUICKFORM"     => $this->language,
            "HONEYPOT_FIELD"	=> $honeypotfield,
            "CAPTCHA"			=> $this->captcha( $section_id )//,
            // "MESSAGE"           => ""   // Aldus: 2021-01-03 - to avoid warnings for arrayToString conversations within xDebug
		);
		$oTWIG = lib_twig_box::getInstance();
		$template_string = $oTWIG->render(
			 "@quickform/" . $template_file
			,$template_values
		);

		// find all field rows inside the template
		$fields = array();
		$matches = array();
		preg_match_all(
		    '/<input [^>]*>|<select [^>]*>|<textarea [^>]*>/iU',
		    $template_string,
		    $matches
		);

		// find all fields per field row, identified by name
		foreach( $matches[0] as $match )
		{
			if ( preg_match('/name="([^"]*)"/i', $match, $field ))
			{
				$items = array();

				$items["name"] = str_replace("[]","",$field[1]);
				$items["submit"] = false;
				if ( strpos( $items["name"], "qf_" ) !== false )	{ $items["submit"] = true; }
				$items["required"] = false;
				if ( strpos( $items["name"], "_r_" ) !== false )	{ $items["required"] = true; }

				// set id
				$id = strtolower( $items["name"] );
				$id = str_replace( "qf_r_", "", $id );
				$id = str_replace( "qf_", "", $id );
				$items["id"] = trim( strtoupper( $id ));

				// initialize the field value, will be set in view.php later on in process
				$items["value"] = "";

				// get type
				$items["type"] = "";
				if ( strtolower(Substr( $match, 0, StrLen("<input"))) == "<input" )
				{
					if ( preg_match('/type="([^"]*)"/i', $match, $type ))
					{
						$items["type"] = strtoupper( str_replace("[]","",$type[1]) );
					}
				}
				elseif ( strtolower(Substr( $match, 0, StrLen("<select"))) == "<select" )
					{ $items["type"] = "SELECT"; }
				elseif ( strtolower(Substr( $match, 0, StrLen("<textarea"))) == "<textarea" )
					{ $items["type"] = "TEXTAREA"; }

				// check if a data-label attribute exists in form field definition
				$items["data-label"] = $items["id"];
				// used only in mail
				if ( preg_match('/data-label="([^"]*)"/i', $match, $label ))
				{
					$items["data-label"] = str_replace("[]","",$label[1]);
				}

				// check if a data-value attribute exists in form field definition
				// used only in mail
				if ( preg_match('/data-value="([^"]*)"/i', $match, $label ))
				{
					$items["data-value"] = str_replace("[]","",$label[1]);
				}

				// update array
				$fields[ $items["name"]] = $items;
			}
		}

		return $fields;
	}

	/** =========================================================================
	 *
	 * get next message id following currently moved or deleted message id for anchor target
	 *
	 * @param	int			A valid section id
	 * @param	int			A valid message id or string "all"
	 * @param	char		A group code where messages are inside
	 * @return	Nothing
	 *
	 */
	public function get_anchor_target( $section_id, $msgid, $grpcur )
	{
		// validate
		if ( empty($msgid))		{ return "groups"; }
		if ( $msgid == "all" )	{ return $msgid; }
		$msgid = intval( $msgid );
		if ( ! $msgid > 0 )		{ return "groups"; }

		// execute
		global $database;
		$result = $database->get_one(
			  "SELECT MIN(`message_id`)"
			. " FROM `".TABLE_PREFIX."mod_quickform_data`"
			. " WHERE `section_id` = '".$section_id."'"
		    . "   AND `msg_group`  = '".$grpcur."'"
		    . "   AND `message_id` > '".$msgid."'"
			. " ORDER BY `message_id` desc"
			. ";"
			);

		if ( empty($result))	{ $result = "groups"; }
		return $result;
	}

	/** =========================================================================
	 *
	 * move message(s) from one message collection into another
	 *
	 * @param	int			A valid section id
	 * @param	variant		A valid message id or string "all"
	 * @param	char		A group code where messages are inside
	 * @param	char		A group code as target
	 * @param	int			Number of max rows (optional, required when move all)
	 * @return	Nothing
	 *
	 */
	public function move_msg( $section_id, $msgid, $grpcur, $grpnew, $rowcur )
	{
		// validate
		if ( empty($msgid))		{ return -1; }
		if ( empty($grpcur))	{ return -2; }
		if ( empty($grpnew))	{ return -3; }
		if ( $msgid == "all" )
		{
			if (( empty($rowcur) ) || !( $rowcur > 0 )) { return -4; }
		}
		else
		{
			$msgid = intval( $msgid );
			if ( ! $msgid > 0 ) { return -5; }
		}

		// execute
		global $database;
		$result = array();
		$sql = "UPDATE"
			. " `".TABLE_PREFIX."mod_quickform_data`"
			. " SET `msg_group` = '".$grpnew."'"
			. " WHERE `section_id` = '".$section_id."'";

		if ( $msgid == "all" )
		{
			$sql .= "   AND `msg_group` = '".$grpcur."'"
				  . " ORDER BY `message_id` desc"
				  . " LIMIT ".$rowcur;
		}
		else
		{
			$sql .= "   AND `message_id` = '".$msgid."'"
				  . "   AND `msg_group`  = '".$grpcur."'";
		}
		$sql .= ";";
		$result = $database->simple_query( $sql );
		return $result;
	}

	/** =========================================================================
	 *
	 * delete message(s)
	 *
	 * @param	int			A valid section id
	 * @param	variant		A valid message id or string "all"
	 * @param	char		A group code where messages are inside
	 * @param	int			Number of max rows (optional, required when delete all)
	 * @return	Nothing
	 *
	 */
	public function delete_msg( $section_id, $msgid, $grpcur, $rowcur )
	{
		// validate
		if ( empty($msgid))		{ return -1; }
		if ( empty($grpcur))	{ return -2; }
		if ( $msgid == "all" )
		{
			if (( empty($rowcur) ) || !( $rowcur > 0 )) { return -3; }
		}
		else
		{
			$msgid = intval( $msgid );
			if ( ! $msgid > 0 ) { return -4; }
		}

		// execute
		global $database;
		$result = array();
		$sql = "DELETE FROM"
			. " `".TABLE_PREFIX."mod_quickform_data`"
			. " WHERE `section_id` = '".$section_id."'";

		if ( $msgid == "all" )
		{
			$sql .= "   AND `msg_group` = '".$grpcur."'"
				  . " ORDER BY `message_id` desc"
				  . " LIMIT ".$rowcur;
		}
		else
		{
			$sql .= "   AND `message_id` = '".$msgid."'"
				  . "   AND `msg_group`  = '".$grpcur."'";
		}
		$sql .= ";";
		$result = $database->simple_query( $sql );
		return;
	}

	/** =========================================================================
	 *
	 * save number of items to be shown
	 *
	 * @param	int			A valid section id
	 * @param	int			New Number of max rows
	 * @return	Nothing
	 *
	 */
	public function set_rows( $section_id, $rowcur )
	{
		global $database;
		$result = array();
		$sql = "UPDATE "
			. " `".TABLE_PREFIX."mod_quickform`"
			. " SET `usenbritems` = ".$rowcur
			. " WHERE `section_id` = '".$section_id."'"
			.  ";";
		$result = $database->simple_query( $sql );
		return;
	}

	/** =========================================================================
	 *
	 * set new view to be used
	 *
	 * @param	int			A valid section id
	 * @param	char		New view name
	 * @return	Nothing
	 *
	 */
	public function change_view( $section_id, $useview )
	{
		global $database;
		$result = array();
		$sql = "UPDATE "
			. " `".TABLE_PREFIX."mod_quickform`"
			. " SET `useview` = '".$useview."'"
			. " WHERE `section_id` = '".$section_id."'"
			.  ";";
		$result = $database->simple_query( $sql );
		return;
	}

	/** =========================================================================
	 *
	 * Get the submissions of this section
	 *
	 * @param	int			A valid section id
	 * @param	char		A group code where messages are inside
	 * @param	int			Optional number of max rows
	 * @return	array		A linear array within assoc. subarray
	 *
	 */
	public function get_history ( $section_id, $grpcur, $rowcur, $viewcur )
	{

		$database = LEPTON_database::getInstance();
		$result = array();
		$database->execute_query(
			  "SELECT *"
			. " FROM `".TABLE_PREFIX."mod_quickform_data`"
			. " WHERE `section_id` = '".$section_id."'"
		    . "   AND `msg_group`  = '".$grpcur."'"
			. " ORDER BY `message_id` desc"
			. " LIMIT 0,".$rowcur.";",
			true,
			$result,
			true
		);

		// get the single elements out of the mail. Do not do in CLASSIC view as it is not used therein
		if ( $viewcur !== "CLASSIC" )
		{
			foreach( $result as &$item )	// loop mails
			{
				// initialize the prepared field content array
				$fields = array();

				/*** create a new dom object ***/
				$dom = new domDocument;

				/*** discard white space ***/
				$dom->preserveWhiteSpace = false;
				$dom->formatOutput = false;

				/*** load the html mail content into the object ***/
				$dom->loadHTML( $item[ "data" ] );
				$xpath = new DOMXPath($dom);

				if (strpos( $item[ "data" ], "<table id='emailcontent'"))
				{
					$domcols = $xpath->query('//table[@id="emailcontent"]/tr/td');
				}
				else	// old emails
				{
					/*** load the html mail content into the object ***/
					$domcols = $xpath->query('.//table[last()]/tr/td');
				}

				/*** loop over the table columns ***/
				$i = 1;
				foreach ($domcols as $domcol)
				{
					// first col is header, second the value
					if (( $i == 1 ) && ( substr_count($domcol->nodeValue, ':') == 1 ))
					{
						$key = $domcol->nodeValue;
						$i = 2;
					}
					elseif ( $i == 2 )
					{
                        // Aldus 2020-12-09: Umlauts in the Message
						$fields[ $key ] = utf8_decode($domcol->nodeValue);
						$i = 1;
					}
				}
				unset($dom);

				// ignore because it contains only the current section id
				if ( array_key_exists( "QUICKFORM" , $fields )){ unset( $fields["QUICKFORM"] ); }

				// handle exception in case no data can be evaluated
				if ( count( $fields) == 0 )
				{
					$fields['MAIL SUBMIT'] = date( DATE_FORMAT.'  '.TIME_FORMAT, $item['submitted_when'] );
				}

				// update array
				$item[ "msg_data" ] = $fields;
			}
		}

		return $result;
	}

	/** =========================================================================
	 *
	 * Get the available groups
	 *
	 * @param	int		A valid section id
	 * @return	array	A linear array within assoc. subarray
	 *
	 */
	public function get_groups( $section_id )
	{

		$database = LEPTON_database::getInstance();
		$result = array();
		$database->execute_query(
			  "SELECT DISTINCT `msg_group`, COUNT(*) AS `nbr_items`"
			. " FROM `".TABLE_PREFIX."mod_quickform_data`"
			. " WHERE `section_id` = '".$section_id."'"
			. " GROUP BY `msg_group`",
			true,
			$result,
			true
		);

		return $result;
	}

	/** =========================================================================
	 *
	 * Create a set of messages for testing purposes
	 *
	 * @param	int			A valid section id
	 * @param	char		A group code where messages are inside
	 * @param	int			number of max rows
	 * @return	nothing
	 *
	 */
	public function clone_messages ( $section_id, $grpcur, $rowcur )
	{
		// get one row as base
		$database = LEPTON_database::getInstance();
		$result = array();
		$database->execute_query(
			  "SELECT *"
			. " FROM `".TABLE_PREFIX."mod_quickform_data`"
			. " WHERE `section_id` = '".$section_id."'"
		    . "   AND `msg_group`  = '".$grpcur."'"
			. " ORDER BY `message_id` desc"
			. " LIMIT 0,1;",
			true,
			$result,
			true
		);

		// remove auto increment field
		foreach( $result as $row )
		{
			$row[ "message_id" ] = NULL;

			// create number of clones
			for ($x = 1; $x <= $rowcur; $x++)
			{
				 $result = $database->build_and_execute(
						"insert",
						TABLE_PREFIX."mod_quickform_data",
						$row
					);
			}
		}

		return $result;
	}

	/** =========================================================================
	 *
	 * Build page list
	 *
	 * @param	int		A valid parent page id
	 * @param	int		Current page_id
	 * @return	array	A assoc. array
	 *
	 */
	public function build_pagelist( $parent, $this_page )
	{
		global $database, $pages;
		$iterated_parents = array(); // keep count of already iterated parents to prevent duplicates

		$data = array();
		$database->execute_query(
			  "SELECT link, menu_title, page_title, page_id, level "
			. " FROM `".TABLE_PREFIX."pages`"
			. " WHERE `parent` = ".$parent
			. " ORDER BY level, position ASC"
			,true
			,$data
			,true
		);

		if ( count($data) > 0 )
		{
			foreach( $data as $res )
			{
				$pages[$res["page_id"]] = str_repeat("  -  ", $res["level"]) . $res["menu_title"] . " (" . $res["page_title"] . ")";
				if (!in_array($res["page_id"], $iterated_parents))
				{
					$this->build_pagelist($res["page_id"], $this_page);
					$iterated_parents[] = $res["page_id"];
				}
			}
		}
	}

	/** =========================================================================
	 *
	 * Show an info popup
	 *
	 * @access  public
	 * @param   $modvalues  As optional array containing module specialized values
	 * @param   $bPrompt    True for direct output via echo, false for returning the generated source.
	 * @return  mixed       Depending on the $bPrompt param: boolean or string.
	 */
	public function showmodinfo( $modvalues = null, $bPrompt = true )
	{
		// prepare array with module specific values
		$modvalues = array(
			 "IMAGE_URL"	=> str_replace( LEPTON_PATH, LEPTON_URL, __DIR__ ) . "/../images/quickform.png"
			,"BUTTONS"		=> array(
				 "HELP"		=> array( "AVAILABLE"	=> true
										,"URL"		=> "https://doc.lepton-cms.org/docu/english/tutorials/doc-quickform.php"
				)
			)
		);

		// show module info
		$sSource = parent::showmodinfo( $modvalues );
		
		return $sSource;
	}

	/** =========================================================================
	 *
	 * send the mail
	 *
	 * @param	array		template fields and values
	 * @param	string		mail receiver ident
	 * @param	array		quickform settingss
	 * @param	array	&	email parameter
	 * @return	boolean		true if any error occured, false otherwise
	 */
	public function mail_send( $template_fields, $receiver_ident, $settings, &$email_params )
	{
		// prepare the email settings
		$isError = false;
		$email_params = array();

		// prepare mail with receivers, text, etc
		$isError = $this->mail_prepare( $template_fields, $receiver_ident, $settings, $email_params );

		// an error had occured during the preparation of the email parameters
		if ( true === $isError )
		{
			Return $isError;
		}

		// no email is needed
		if ( $email_params[ "mail2receiver" ] === false )
		{
			Return false;	// No error
		}

		// create mail instance
		$qfMail = LEPTON_mailer::getInstance();
		$qfMail->CharSet	= "UTF-8";

		// set Sender
		$qfMail->setFrom( $email_params[ "sender" ][ "from"], $email_params[ "sender" ][ "name"] );

		// set Recipients
		foreach ( $email_params[ "receiver" ] as $email )
		{
			$qfMail->AddAddress( $email[ "from"], $email[ "name"] );
		}
		// $qfMail->addReplyTo(	"info@example.com", "Information");
		// $qfMail->addCC("cc@example.com");
		// $qfMail->addBCC("bcc@example.com");

		// set Content
		$qfMail->isHTML( $email_params[ "isHTML" ] );
		$qfMail->Subject	= $email_params[ "subject" ];
		$qfMail->Body		= $email_params[ "message" ];
		$qfMail->AltBody	= $email_params[ "message_textonly" ];

		// send mail, then check if there are any send mail errors, otherwise say successful
		if ( ! $qfMail->Send())
		{
			$email_params[ "error" ] = array("ERROR_SENDMAIL", $qfMail->ErrorInfo );
			return true;
		}
		$qfMail = "";

		return false;	// No error
	}

	/** =========================================================================
	 *
	 * prepare the mail and validate the input
	 *
	 * @param	array		template fields and values
	 * @param	string		mail receiver ident
	 * @param	array		quickform settingss
	 * @param	array	&	email parameter
	 * @return	boolean		true if any error occured, false otherwise
	 */
	public function mail_prepare( $template_fields, $receiver_ident, $settings, &$email_params )
	{
		// define subject
		$email_params[ "subject" ] = $settings["subject"];

		// define sender mail address
		$email_params[ "sender" ] = array();
		if ( ! empty( SERVER_EMAIL ))
		{
			$email_params[ "sender" ] = array( "from" => SERVER_EMAIL, "name" => MAILER_DEFAULT_SENDERNAME );
		}
		if ( count( $email_params[ "sender" ] ) == 0 )
		{
			$email_params[ "error" ] = "ERROR_SENDER_MISSED";
			return true;
		}

		// evaluate receiver mail address(es)
		$email_params[ "receiver" ] = [];
		$email_params[ "mail2receiver" ] = false;
		switch( $receiver_ident )
		{
			case "quickform":
				if ( ! empty( $settings["email"] ))
				{
					$email_params[ "receiver" ] = $this->mail_split_address( $settings["email"], $email_params );
				}
                if (empty($email_params["receiver"]))
				{
					// missing mail receiver together with CC_MAIL setup
					$email_params[ "error" ] = "ERROR_RECEIVER_MISSED";
					return true;
				}
				$email_params[ "mail2receiver" ] = true;
				break;

			case "requestor":
				// requires both the CC_MAIL form field with non empty value
				// and at least one valid email address
				foreach( $template_fields as $key=>$items )
				{
					if ( substr( $items["id"], 0, 5 ) == "EMAIL" )
					{
						$email_params[ "receiver" ] = $this->mail_split_address( $items["value"], $email_params );
					}
					elseif (( $items["id"] == "CC_MAIL" ) && ( $items["value"] != "" ) && ( $items["value"] != "-" ))
					{
						$email_params[ "mail2receiver" ] = true;
					}
				}
                if ((false === $email_params["mail2receiver"]) || (empty($email_params["receiver"])))
                {
                    // no CC_MAIL is requested or no mail addresses are defined: No error
                    $email_params[ "mail2receiver" ] = false;
                    return false;
                }
				break;
		};

		// find mail template
		$email_params[ "template_name" ] = "";
			// get page language (has been set earlier)
			$page_language = $this->get_page_language( 0 );

			// build array with possible file names
			$templates = array();
			switch( $receiver_ident )
			{
				case "requestor":
					$templates[] = "cc_mail";
				case "quickform":
					$templates[] = "email";	// add to array for requestor and for quickform
			};
			$tempate_files = array();
			foreach ( $templates as $template )
			{
				$tempate_files[] = $template . "_" . $page_language . ".lte";
				$tempate_files[] = $template . "_" . $this->default_language . ".lte";
				$tempate_files[] = $template . ".lte";
			}

			// add default mail template as fallback
			$tempate_files[] = "qf_email.lte";

			// search for template
			$template_path = __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR;
			$template_lang = "backend";
			foreach ( $tempate_files as $template_name )
			{
				if ( true === $this->get_template( 0, $template_path, $template_lang, $template_name ) )
				{
					$email_params[ "template_name" ] = $template_name;
					break;		// stop on first template found
				}
			}
		if ( empty( $email_params[ "template_name" ] ))
		{
			$email_params[ "error" ] = "ERROR_MAIL_MISSED";
			return true;
		}

		// build mail field and values list
		$isTimestampKey = '';
		$field_values = array();
		foreach( $template_fields as $key => $items )
		{
			// check for timestamp
			if ( $items[ "id" ] == "TIMESTAMP" )
			{
				$items[ "submit" ] = true;
			}

			// field is a submit field (name start with "qf_")
			if ( $items[ "submit" ] === true )
			{
				if ( array_key_exists( "data-value", $items ))
				{
					$field_values[ $items["data-label"] ] = $items["data-value"];
				} else {
					$field_values[ $items["data-label"] ] = $items["value"];
				}

				// format value
				if ( $items[ "id" ] == "TIMESTAMP" )
				{
					if ( False !== ( $value = @strftime('%c', $field_values[ $items["data-label"] ] ) ))
					{
						$field_values[ $items["data-label"] ] = $value;
					}
				}
			}
		}

		// set initial template values
		$template_values = array(
				 "MOD_QUICKFORM"	=> $this->language
				,"field_values"		=> $field_values
				,"settings"			=> $settings
		);

		// enhance template values by custom class 
		if ( is_object($this->oQCFE) )
		{
			if (( method_exists( $this->oQCFE, 'set_mail_values' ))
			&&  ( is_callable( array($this->oQCFE, 'set_mail_values' )) ))
			{
				$this->oQCFE->set_mail_values( $template_fields, $template_values );
			}
		}

		// get prepared mail text
		$oTWIG = lib_twig_box::getInstance();
		$email_params[ "message" ] = $oTWIG->render(
			 "@quickform/" . $template_lang . DIRECTORY_SEPARATOR . $email_params[ "template_name" ]
			,$template_values
		);

		$email_params[ "message_textonly" ] = "";
		$email_params[ "isHTML" ] = false;

		// prepare alternate text (remove html tags)
		$textbody = strip_tags( $email_params[ "message" ]);
		$textbody = str_replace("\t","",$textbody);
		while (strpos($textbody,"\n\n\n") !== false)
			$textbody = str_replace("\n\n\n","\n\n",$textbody);
		while (strpos($textbody,"\r\n\r\n\r\n") !== false)
			$textbody = str_replace("\r\n\r\n\r\n","\r\n\r\n",$textbody);

		if ( $textbody != $email_params[ "message" ] )
		{
			$email_params[ "message_textonly" ] = $textbody;
			$email_params[ "isHTML" ] = true;
		}

		return false;	// no error
	}

	/** =========================================================================
	 *
	 * split the email address if needed
	 *
	 * @param	string		email address
	 * @param	array		already available email addresses
	 * @return	array		enhanced array with input email address(es)
	 */
	private function mail_split_address( $mail_address, $email_params )
	{
		$mail_receivers = $email_params[ "receiver" ];
		$addresses = array();
		// email address is a form array
		if ( is_array( $mail_address ) === true )
		{
			foreach ( $mail_address as $key => $value )
			{
				$addresses[] = $value;
			}
		}
		// email address is a single address
		elseif ( strpbrk ( $mail_address , ",; " ) === false )
		{
			$addresses[] = $mail_address;
		}
		// split address by first delimiter char found
		// but ignore empty elements like in "mail_1,,mail_2"
		else
		{
			foreach( array( ",", ";", " ") as $delimiter )
			{
				if ( strpos ( $mail_address , $delimiter ) > 0 )
				{
					$addresses = array_filter( explode ( $delimiter, $mail_address ), "strlen" );
					break;	// foreach delimiter loop
				}
			}
			// only defined number of split elements are allowed in order to protect against spam
			if ( count( $addresses ) > $email_params[ "max_email_split" ] )
			{
				$addresses = array_slice( $addresses, 0, $email_params[ "max_email_split" ]);
			}
		}

		if ( count( $addresses ) > 0 )
		{
			foreach( $addresses as $key => $val )
			{
				// use only valid email addresses
				if ( filter_var( $val, FILTER_VALIDATE_EMAIL ))
				{
					$mail_receivers[ $val ] = array( "from" => $val, "name" => "" );
				}
			}
		}

		return $mail_receivers;
	}

    /** =========================================================================
     *
     * honeypot check
     *
     * @param   string  one of the actions show or save
     * @param   string  sha1( $_SERVER['REMOTE_ADDR'] )
     * @param   string  sha1( $_SERVER['HTTP_USER_AGENT'] );
     * @param   array   the quickform settings
     * @param   array   page values
     * @param   number  form load time, used for entry time check
     * @param   string  the name of the currently used honeypot field
     * @param   number  the index of the currently used honeypot field
     * @return  array   return status
     *                       1 = success
     *                       0 = no check executed
     *                      -1 = failure: too many sessions within 1 hour
     *                      -2 = failure: no temp file row available
     *                      -3 = failure: invalid timestamps, form vs. temp table
     *                      -4 = failure: too fast data entry
     *                      -5 = failure: invalid honeypot index
     *                      -6 = failure: invalid honeypot field
     *                      -7 = failure: honeypot field contains a value
     */
    public function spam_check(
        $action,
        $ip_fingerprint,
        $browser_fingerprint,
        $settings,
        $page_values,
        $pageloadtime,
        $honeypotfield,
        $honeypotindex
    ) {
        global $database;

        $return = 1;
        $info = [];

        // delete old rows older than 1 hour
        $time = $pageloadtime - 3600;
        $database->simple_query("
            DELETE from `".TABLE_PREFIX."temp`
            WHERE `temp_time` < '" . $time . "' 
                AND `temp_ip` like 'qf_%'"
        );

        // No further actions if honeypot functionality is switched off
        if ((int)$settings["use_honeypot"] != 1)
        {
            return 0;
        }

		// ... get available temp table row
		$database->execute_query(
			"SELECT * FROM `".TABLE_PREFIX."temp` WHERE `temp_ip` = 'qf_".$ip_fingerprint."'",
			true,
			$info,
			false
		);

		// reaction depending on action ...
		switch( $action )
		{
			// when action = show, ...
            case "show":
            // ... any active row exists should be older than 1 hour
/*
                // Problem: This failure occurs also with a simple refresh ....
                // So let's forget for the moment ;-)
                if (( count($info) > 0 ) && ( 1 == $info['temp_active'] ) && ( $info['temp_time'] > $time ))
                {
                    $return = -1;
                    break;  // end with error
                }
*/
                // [2] remove existing temp table row
                $database->simple_query("
                    DELETE FROM `".TABLE_PREFIX."temp` 
                    WHERE `temp_ip` = 'qf_" . $ip_fingerprint . "'"
                );

                // [3] create new temp table row
                $fields = [
                    'temp_ip'      => "qf_".$ip_fingerprint,
                    'temp_browser' => "qf_".$browser_fingerprint,
                    'temp_time'    => $page_values["TIMESTAMP"],
                    'temp_count'   => 1,
                    'temp_active'  => 1
                ];

                $database->build_and_execute(
                    'insert',
                    TABLE_PREFIX."temp",
                    $fields
                );
                break;  // end successfull

            // when action = save, ... [Refactor!] 
            // >> Developers might be asked to review the architecture and, where possible, refactor code to create smaller and more manageable components
            case "save":
                // [1.1] trigger failure when no row exists
                if (empty($info))
                {
                    $return = -2;
                    break;  // end with error nr. -2
                }

                // [1.2] check saved time and the form time
                if ((int)$info['temp_time'] != (int)$page_values["TIMESTAMP"])
                {
                    $return = -3;
                    break;  // end with error nr. -3
                }

                // [1.3] check time used for data entry, fail if too fast
                if ((int)$settings['spam_checktime'] > 0)
                {
                    if ((int)$pageloadtime - (int)$info['temp_time'] < (int)$settings['spam_checktime'])
                    {
                        $return = -4;
                        break;  // end with error -4
                    }
                }

                // [1.4] No honeypot fields defined, no further checks
                if (!Trim($settings["spam_honeypot"]))
                {
                    break;  // end without change the error
                }

				// ... check time used for data entry, fail if too fast
				if ( (int)$settings['spam_checktime'] > 0 )
				{
					if ( (int)$pageloadtime - (int)$info['temp_time'] < (int)$settings['spam_checktime'] )
					{
						$return = -4;
						break;	// end with error
					}
				}

				// no honeypot fields defined, no further checks
				if ( ! Trim( $settings["spam_honeypot"] ))
				{
					break;	// end without error
				}

                // check honeypot index
                if ((int)$honeypotindex >= 0)
                {
                    $honeypots = explode( ",", $settings["spam_honeypot"]);     // array of honeypot fields
                    $indexfield = trim($honeypots[(int)$honeypotindex] ?? "");  // currently used honeypot field
                    if (empty($indexfield))
                    {
                        $return = -5;
                        break;  // end with error
                    }
                }
                else
                {
                    $return = -51;
                    break;  // end with error
                }

                // check honeypot field
                if ((!Trim( $honeypotfield)) || (strtolower(trim($indexfield)) != strtolower(trim($honeypotfield))))
                {
                    $return = -6;
                    break;  // end with error
                }

				// check honeypot value
				if ( trim( $page_values[ strtoupper( $honeypotfield ) ] ) != "" )
				{
					$return = -7;
					break;	// end with error
				}

				// no spam, remove existing temp table row
				//$database->simple_query("DELETE from `".TABLE_PREFIX."temp` WHERE `temp_ip` = 'qf_".$ip_fingerprint."'");
				// line 1155 commented out because query leads to failures -2, entries are deleted if they are older than 1 hour -> see line 1016

				break;	// end successfull
		}	// end switch

        // increase tempt table counter in case of error
        if (($return < 0) && (1 === count($info)) && ($settings['spam_logging'] == 1))
        {
            $fields = [
                'temp_count' => ($info['temp_count'] + 1),
                'temp_time'  => time()
            ];

            $database->build_and_execute(
                'update',
                TABLE_PREFIX."temp",
                $fields,
                "`temp_id`='qf_".$info['temp_id']."'"
            );
        }

        // return with spam check result
        return $return;
    }
}
