store = $store; $this->db = $db; $this->startDelimiter = $startDelimiter; $this->startDelimiterLength = strlen($startDelimiter); $this->endDelimiter = $endDelimiter; $this->endDelimiterLength = strlen($endDelimiter); } /** * Evaluate a whole array or a array of arrays. * * @param $tokenArray * @return mixed */ public function parseArray($tokenArray, &$debugStack = array()) { $arr = array(); foreach ($tokenArray as $key => $value) { if (is_array($value)) { $arr[] = $this->parseArray($value); } else { $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. * * Token to replace have to be enclosed by '{{' and '}}' * * @param $line * @param int $recursion * @return array|mixed|null|string * @throws UserFormException */ public function parse($line, $recursion = 0, &$debugStack = array(), &$foundInStore = '') { $flagTokenReplaced = false; if ($recursion > 4) { throw new qfq\UserFormException("Recursion too deep ($recursion). Line: $line", ERROR_RECURSION_TOO_DEEP); } $result = $line; $debugIndent = str_repeat(' ', $recursion); $debugLocal[] = $debugIndent . "PARSE: $result"; $posFirstClose = strpos($result, $this->endDelimiter); while ($posFirstClose !== false) { $posMatchOpen = strrpos(substr($result, 0, $posFirstClose), $this->startDelimiter); if ($posMatchOpen === false) { 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); // newline $debugLocal[] = ''; $debugLocal[] = $debugIndent . "REPLACE: $match"; if ($foundInStore === '') { // Encode the non replaceable part as preparation not to process again. Recode them at the end. $evaluated = Support::encryptDoubleCurlyBraces($this->startDelimiter . $match . $this->endDelimiter); $debugLocal[] = $debugIndent . "BY: "; } else { $flagTokenReplaced = true; // If an array is returned, break everything and return this assoc array. if (is_array($evaluated)) { $result = $evaluated; $debugLocal[] = $debugIndent . "BY: array(" . count($result) . ")"; break; } $debugLocal[] = $debugIndent . "BY: $evaluated"; // 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; $posFirstClose = strpos($result, $this->endDelimiter); } if ($flagTokenReplaced === true) { $debugLocal[] = $debugIndent . "FINAL: " . is_array($result) ? "array(" . count($result) . ")" : "$result"; $debugStack = $debugLocal; } return Support::decryptDoubleCurlyBraces($result); } /** * Tries to substitute $token. * 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' * * The token have to be _without_ Delimiter '{{' / '}}' * If neither a) or b) match, return the token itself. * * @param $token * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''. * @return array|null|string * @throws CodeException * @throws DbException */ public function substitute($token, &$foundInStore = '') { $sqlMode = ROW_IMPLODE_ALL; $token = trim($token); // just to extract the first token: check if this is a SQL Statement $arr = explode(' ', $token, 2); if ($token[0] === '!') { $token = substr($token, 1); $arr[0] = substr($arr[0], 1); $sqlMode = ROW_REGULAR; } // SQL Statement? if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) { $foundInStore = TOKEN_FOUND_IN_STORE_QUERY; return $this->db->sql($token, $sqlMode); } // explode for: ::: $arr = explode(':', $token, 4); $arr = array_merge($arr, [null, null, null, null]); // fake isset() // search for value in stores $value = $this->store->getVar($arr[0], $arr[1], $arr[2], $foundInStore); // escape ticks if (is_string($value)) { switch ($arr[3]) { case TOKEN_ESCAPE_SINGLE_TICK: $value = str_replace("'", "\\'", $value); break; case TOKEN_ESCAPE_DOUBLE_TICK: $value = str_replace('"', '\\"', $value); break; default: break; } } // OLD: nothing replaced: put ticks around, to sanitize strings for SQL statements. Nothing to substitute is not a wished situation. // return ($value === false) ? "'" . $token . "'" : $value; // NEW: nothing replaced: higher level should decide what to do return $value; } /** * @return string */ public function getDebug() { return '
' . implode("\n", $this->debugStack) . '
'; } }