Newer
Older
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 1/12/16
* Time: 4:36 PM
*/
namespace qfq;
use qfq;
require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/database/Database.php');

Carsten Rose
committed
require_once(__DIR__ . '/helper/Support.php');
/**
* @var Store
*/
/**
* @var Database
*/
private $startDelimiter = '';
private $startDelimiterLength = 0;
private $endDelimiter = '';
private $endDelimiterLength = 0;

Carsten Rose
committed
private $sqlKeywords = array('SELECT ', 'INSERT ', 'DELETE ', 'UPDATE ', 'SHOW ', 'REPLACE ');
private $escapeTypeDefault = '';
// private $debugStack = array();
* @param Database $db
* @param string $startDelimiter
* @param string $endDelimiter
public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
$this->store = $store;
$this->db = $db;
$this->startDelimiter = $startDelimiter;
$this->startDelimiterLength = strlen($startDelimiter);
$this->endDelimiter = $endDelimiter;
$this->endDelimiterLength = strlen($endDelimiter);
$this->escapeTypeDefault = $this->store->getVar(F_ESCAPE_TYPE_DEFAULT, STORE_SYSTEM);
if (empty($this->escapeTypeDefault) || $this->escapeTypeDefault == TOKEN_ESCAPE_CONFIG) {
$this->escapeTypeDefault = $this->store->getVar(SYSTEM_ESCAPE_TYPE_DEFAULT, STORE_SYSTEM);
}
* Evaluate a whole array or an array of arrays.

Carsten Rose
committed
* @param array $skip Optional Array with keynames, which will not be evaluated.
* @param array $debugStack

Carsten Rose
committed
* @return array

Carsten Rose
committed
* @throws UserFormException

Carsten Rose
committed
public function parseArray($tokenArray, array $skip = array(), &$debugStack = array()) {
$arr = array();
foreach ($tokenArray as $key => $value) {

Carsten Rose
committed
if (array_search($key, $skip) !== false) {

Carsten Rose
committed
continue;
}
if (is_array($value)) {

Carsten Rose
committed
$arr[] = $this->parseArray($value, $skip);

Carsten Rose
committed
$value = Support::handleEscapeSpaceComment($value);

Carsten Rose
committed

Carsten Rose
committed
$arr[$key] = $this->parse($value, 0, $debugStack);
}
return $arr;
}
/**
* Recursive evaluation of 'line'. Constant string, Variables or SQL Query or all of them. All queries will be
* fired. In case of an 'INSERT' statement, return the last_insert_id().
*
* Token to replace have to be enclosed by '{{' and '}}'
* @param int $recursion
* @return array|mixed|null|string

Carsten Rose
committed
* @throws UserFormException
public function parse($line, $recursion = 0, &$debugStack = array(), &$foundInStore = '') {

Carsten Rose
committed
throw new qfq\UserFormException("Recursion too deep ($recursion). Line: $line", ERROR_RECURSION_TOO_DEEP);
$debugIndent = str_repeat(' ', $recursion);

Carsten Rose
committed
$debugLocal[] = $debugIndent . "PARSE: $result";
$posFirstClose = strpos($result, $this->endDelimiter);
// $this->store->setVar(SYSTEM_SQL_RAW, $line, STORE_SYSTEM);

Carsten Rose
committed
while ($posFirstClose !== false) {
$posMatchOpen = strrpos(substr($result, 0, $posFirstClose), $this->startDelimiter);

Carsten Rose
committed
throw new \qfq\UserFormException("Missing open delimiter: $result", ERROR_MISSING_OPEN_DELIMITER);
$pre = substr($result, 0, $posMatchOpen);
$post = substr($result, $posFirstClose + $this->endDelimiterLength);
$match = substr($result, $posMatchOpen + $this->startDelimiterLength, $posFirstClose - $posMatchOpen - $this->startDelimiterLength);
$evaluated = $this->substitute($match, $foundInStore);

Carsten Rose
committed

Carsten Rose
committed
// newline
$debugLocal[] = '';
$debugLocal[] = $debugIndent . "REPLACE: $match";

Carsten Rose
committed
if ($foundInStore === '') {
// Encode the non replaceable part as preparation not to process again. Recode them at the end.

Carsten Rose
committed
$evaluated = Support::encryptDoubleCurlyBraces($this->startDelimiter . $match . $this->endDelimiter);

Carsten Rose
committed
$debugLocal[] = $debugIndent . "BY: <nothing found - not replaced>";

Carsten Rose
committed
} else {
$flagTokenReplaced = true;

Carsten Rose
committed
// If an array is returned, break everything and return this assoc array.
if (is_array($evaluated)) {
$result = $evaluated;

Carsten Rose
committed
$debugLocal[] = $debugIndent . "BY: array(" . count($result) . ")";

Carsten Rose
committed
break;
}

Carsten Rose
committed
$debugLocal[] = $debugIndent . "BY: $evaluated";

Carsten Rose
committed
// More to substitute in the new evaluated result? Start recursion just with the new result..
if (strpos($evaluated, $this->endDelimiter) !== false) {
$evaluated = $this->parse($evaluated, $recursion + 1, $debugLocal, $foundInStore);
}
}
$result = $pre . $evaluated . $post;

Carsten Rose
committed
$posFirstClose = strpos($result, $this->endDelimiter);
}

Carsten Rose
committed
$result = Support::decryptDoubleCurlyBraces($result);
if ($flagTokenReplaced === true) {

Carsten Rose
committed
$debugLocal[] = $debugIndent . "FINAL: " . is_array($result) ? "array(" . count($result) . ")" : "$result";

Carsten Rose
committed
return $result;

Carsten Rose
committed
* Token might be:
* a) a SQL statement to fire
* b) fetch from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:alnumx', 'form:F:all:s','form:F:all:s:default'

Carsten Rose
committed
*
* The token have to be _without_ Delimiter '{{' , '}}'

Carsten Rose
committed
* If neither a) or b) match, return the token itself.
* @param string $token
* @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found,
* return ''.
*

Carsten Rose
committed
* @return array|null|string
* @throws DbException
* @throws UserFormException
public function substitute($token, &$foundInStore = '') {
$sqlMode = ROW_IMPLODE_ALL;
$token = trim($token);
if ($token[0] === '!') {

Carsten Rose
committed
$token = trim(substr($token, 1));

Carsten Rose
committed
// just to extract the first token: check if this is a SQL Statement
$arr = explode(' ', $token, 2);
// SQL Statement?
if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {

Carsten Rose
committed
$foundInStore = TOKEN_FOUND_IN_STORE_QUERY;
// explode for: <key>:<store priority>:<sanitize class>:<escape>:<default>
$arr = explode(':', $token, 5);
$arr = array_merge($arr, [null, null, null, null, null]); // fake isset()

Carsten Rose
committed
$escapeTypes = (empty($arr[3])) ? $this->escapeTypeDefault : $arr[3];
$value = $this->store->getVar($arr[0], $arr[1], $arr[2], $foundInStore);

Carsten Rose
committed
// escape ticks
if (is_string($value)) {

Carsten Rose
committed
// Process all escape requests in the given order.
for ($ii = 0; $ii < strlen($escapeTypes); $ii++) {
$escape = $escapeTypes[$ii];
if ($escape == TOKEN_ESCAPE_CONFIG) {
$escape = $this->escapeTypeDefault;
}

Carsten Rose
committed
switch ($escape) {
case TOKEN_ESCAPE_SINGLE_TICK:
$value = str_replace("'", "\\'", $value);
break;
case TOKEN_ESCAPE_DOUBLE_TICK:
$value = str_replace('"', '\\"', $value);
break;
case TOKEN_ESCAPE_LDAP_FILTER:

Carsten Rose
committed
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_FILTER);
break;
case TOKEN_ESCAPE_LDAP_DN:

Carsten Rose
committed
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
break;
case TOKEN_ESCAPE_MYSQL:
$value = $this->db->realEscapeString($value);
break;
case TOKEN_ESCAPE_NONE: // do nothing
break;

Carsten Rose
committed
default:
throw new UserFormException("Unknown Escape qualifier: $escape", UNKNOWN_TYPE);

Carsten Rose
committed
break;
}

Carsten Rose
committed
}
}
// Not found and a default is given: take the default.
if ($foundInStore == '' && !empty($arr[4])) {
$foundInStore = TOKEN_FOUND_AS_DEFAULT;
$value = str_replace('\\:', ':', $arr[4]);
}

Carsten Rose
committed
return $value;

Carsten Rose
committed
// public function getDebug() {
// return '<pre>' . implode("\n", $this->debugStack) . '</pre>';
// }