| Current Path : /proc/thread-self/root/proc/thread-self/root/proc/self/root/tmp/ |
| Current File : //proc/thread-self/root/proc/thread-self/root/proc/self/root/tmp/phpycY9IU |
<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class ConditionValueException extends \Exception
{
public function __construct($condition_name)
{
parent::__construct("008 - Condition Value Error: The Condition '" . $condition_name . "' does not return a value.");
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class InvalidConditionException extends \Exception
{
public function __construct($condition_name)
{
parent::__construct("002 - Invalid Condition: The condition '" . $condition_name . "' does not exist.");
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class SyntaxErrorException extends \Exception
{
public function __construct($message)
{
parent::__construct("001 - Syntax Error: " . $message);
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class UnknownFunctionException extends \Exception
{
public function __construct($func_name)
{
parent::__construct("007 - Unknown Function: " . $func_name);
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class UnknownOperatorException extends \Exception
{
public function __construct($operator)
{
parent::__construct("003 - Unknown Comparison Operator: " . $operator);
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class UnsupportedOperatorException extends \Exception
{
public function __construct($operator, $condition_name, $accepts_multi_values)
{
$message = 'The Comparison Operator "' . $operator . '" can only be used with Condition Operands that return ' . ($accepts_multi_values ? 'multiple values.' : 'single values.');
parent::__construct("005 - Unsupported Comparison Operator: " . $message);
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser\Exceptions;
defined('_JEXEC') or die;
class UnsupportedValueOperandException extends \Exception
{
public function __construct($operator, $accepts_multi_values)
{
$message = 'The Comparison Operator "' . $operator . '" can only be used with ' . ($accepts_multi_values ? 'multiple values.' : 'single values.');
parent::__construct("006 - Unsupported Value Operand: " . $message);
}
}<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser;
defined('_JEXEC') or die;
use NRFramework\Parser\Lexer;
/**
* ConditionLexer
*
* Tokens:
* -------
* and : 'AND'
* or : 'OR'
* quotedval : quotes ~(quotes)* quotes
* literal : ~(whitespace | quotes)+
* ident : ('a'..'z' | 'A'..'Z' | '_' | '\-' | '\.')+
* quotes : '\'' | '\"'
* comma : ','
* l_paren : '('
* r_paren : ')'
*
* negate_op : '!'
* equals : '=' | 'equals'
* contains : '*=' | 'contains'
* contains_any : 'containsAny'
* contains_all : 'containsAll'
* contains_only : 'containsOnly'
* ends_with : '$=' | 'endsWith'
* starts_with : '^=' | 'startsWith'
* lt : '<' | 'lt' | 'lowerThan'
* lte : '<=' | 'lte' | 'lowerThanEqual'
* gt : '>' | 'gt' | 'greaterThan'
* gte : '>=' | 'gte' | 'greaterThanEqual'
* empty : 'empty'
*
* param : '--' . ident
* whitespace : ' ' | '\r' | '\n' | '\t'
*/
class ConditionLexer extends Lexer
{
/**
* ConditionLexer constructor
*
* @param string $input
*/
public function __construct($input)
{
parent::__construct($input);
// single char tokens
$this->tokens->addType('comma');
$this->tokens->addType('quote');
$this->tokens->addType('dquote');
$this->tokens->addType('l_paren');
$this->tokens->addType('r_paren');
// operators
$this->tokens->addType('negate_op');
$this->tokens->addType('equals');
$this->tokens->addType('contains');
$this->tokens->addType('contains_all');
$this->tokens->addType('contains_any');
$this->tokens->addType('contains_only');
$this->tokens->addType('ends_with');
$this->tokens->addType('starts_with');
$this->tokens->addType('lt');
$this->tokens->addType('gt');
$this->tokens->addType('lte');
$this->tokens->addType('gte');
$this->tokens->addType('empty');
// logical operators
$this->tokens->addType('and');
$this->tokens->addType('or');
// values/literals/identifiers/parameters
$this->tokens->addType('quotedvalue');
$this->tokens->addType('literal');
$this->tokens->addType('ident');
$this->tokens->addType('param');
}
/**
* Returns the next token from the input string
*
* @return NRFramework\Parser\Token
* @throws Exception
*/
public function nextToken()
{
while ($this->cur !== Lexer::EOF)
{
if (preg_match('/\s+/', $this->cur))
{
$this->whitespace();
continue;
}
switch ($this->cur)
{
// match tokens from single char predictions
case ',':
return $this->comma();
case "'":
return $this->quotedValue("'");
case '"':
return $this->quotedValue('"');
case '=':
return $this->equals();
case '!':
return $this->negate_op();
case '*':
return $this->contains();
case '$':
return $this->ends_with();
case '^':
return $this->starts_with();
case '<':
return $this->lt_or_lte();
case '>':
return $this->gt_or_gte();
case '(':
return $this->l_paren();
case ')':
return $this->r_paren();
case '-':
$this->mark();
$next_chars = $this->consume(2);
if ($next_chars === '--')
{
$this->reset();
return $this->param();
}
$this->reset();
// match other tokens
default:
if (!$this->isValidChar())
{
throw new Exceptions\SyntaxErrorException('Invalid character: ' . $this->cur);
}
$token = null;
// try to match literal operators
$token = $this->literal_ops();
if($token)
{
return $token;
}
// try to match boolean operators
$token = $this->_and();
if($token)
{
return $token;
}
$token = $this->_or();
if($token)
{
return $token;
}
// if we get here the token is certainly a literal
$pos = $this->index;
$token = $this->literal();
if ($token)
{
// check if the literal also qualifies to be an identifier
if ($this->isValidIdentifier($token->text))
{
$token = $this->tokens->create('ident', $token->text, $pos);
}
return $token;
}
return null;
}
}
return $this->tokens->create('EOF', '<EOF>', -1);
}
/**
* Checks if a string qualifies to be an identifier
*
* @return bool
*/
protected function isValidIdentifier($text)
{
$ident_regex = '/(^[a-zA-Z\_]{1}$)|(^[a-zA-Z\_](?=([\w\-\.]*))([\w\-\.]*))/';
return preg_match($ident_regex, $text);
}
/**
* Check if the current character is valid for
* some matching rules (and, or, literal, ident)
*
* @return boolean
*/
protected function isValidChar()
{
$r = '/[^\s\'\",=\!\(\)\~\*\<\>\$\^]/';
return preg_match($r, $this->cur);
}
/**
* literal : ~(whitespace | quotes)+ //one or more chars except whitespace and quotes
*
* @return Token|void
*/
protected function literal()
{
$pos = $this->index;
$buf = '';
do
{
if (!$this->isValidChar())
{
break;
}
$buf .= $this->cur;
$this->consume();
}
while ($this->cur !== Lexer::EOF);
if (strlen($buf) > 0)
{
return $this->tokens->create('literal', $buf, $pos);
}
}
/**
* and : 'AND'
*
* @return Token|void
*/
protected function _and()
{
$pos = $this->index;
$this->mark();
$buf = '';
$buf .= $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if (preg_match('/and/', strtolower($buf)))
{
return $this->tokens->create('and', trim($buf), $pos);
}
$this->reset();
}
/**
* or : 'OR'
*
* @return Token|void
*/
public function _or()
{
$pos = $this->index;
$this->mark();
$buf = '';
$buf .= $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if (preg_match('/or/', strtolower($buf)))
{
return $this->tokens->create('or', trim($buf), $pos);
}
$this->reset();
}
/**
* quotedval : quotes ~(quotes)* quotes
*
* @return Token|void
* @throws Exception
*/
protected function quotedValue($q)
{
$pos = $this->index;
$otherQuote = $q === '"' ? "'" : '"';
$quote_queue = [];
$buf = '';
$quote_queue[] = $q;
$this->consume();
while (!empty($quote_queue))
{
if ($this->cur === Lexer::EOF)
{
throw new Exceptions\SyntaxErrorException('Missing quote at: ' . $buf);
}
if ($this->cur === end($quote_queue))
{
array_pop($quote_queue);
// if it's not the opening quote
if (!empty($quote_queue))
{
$buf .= $this->cur;
}
}
else if ($this->cur === $otherQuote)
{
array_push($quote_queue, $otherQuote);
$buf .= $otherQuote;
}
else
{
$buf .= $this->cur;
}
$this->consume();
}
return $this->tokens->create('quotedvalue', $buf, $pos);
}
/**
* param : '--' . ident
*
* @return Token|void
*/
protected function param()
{
$pos = $this->index;
$this->mark();
$buf = '';
$buf .= $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '--')
{
$buf = '';
do
{
if (!$this->isValidChar())
{
break;
}
$buf .= $this->cur;
$this->consume();
}
while ($this->cur !== Lexer::EOF);
if (strlen($buf) > 0 && $this->isValidIdentifier($buf))
{
return $this->tokens->create('param', $buf, $pos);
}
}
$this->reset();
}
/**
* equals : '='
*
* @return Token|void
*/
protected function equals()
{
$pos = $this->index;
$this->consume();
return $this->tokens->create('equals', "=", $pos);
}
protected function negate_op()
{
$pos = $this->index;
$this->consume();
return $this->tokens->create('negate_op', "!", $pos);
}
/**
* comma : ','
*
* @return Token
*/
protected function comma()
{
$pos = $this->index;
$this->consume();
return $this->tokens->create('comma', ",", $pos);
}
/**
* l_paren : '('
*/
protected function l_paren()
{
$pos = $this->index;
$this->consume();
return $this->tokens->create('l_paren', '(', $pos);
}
/**
* r_paren : ')'
*/
protected function r_paren()
{
$pos = $this->index;
$this->consume();
return $this->tokens->create('r_paren', ')', $pos);
}
/**
* contains: '*='
*
* @return Token|void
*/
protected function contains()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '*=')
{
return $this->tokens->create('contains', "*=", $pos);
}
$this->reset();
}
/**
* contains_word: '~='
*
* @return Token|void
*/
protected function contains_word()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '~=')
{
return $this->tokens->create('contains_word', "~=", $pos);
}
$this->reset();
}
/**
* ends_with: '$='
*
* @return Token|void
*/
protected function ends_with()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '$=')
{
return $this->tokens->create('ends_with', "$=", $pos);
}
$this->reset();
}
/**
* starts_with: '$='
*
* @return Token|void
*/
protected function starts_with()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '^=')
{
return $this->tokens->create('starts_with', "^=", $pos);
}
$this->reset();
}
/**
* lt_or_lte: '<' | '<='
*
* @return Token|void
*/
protected function lt_or_lte()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '<=')
{
return $this->tokens->create('lte', "<=", $pos);
}
else
{
$this->reset();
$this->consume();
return $this->tokens->create('lt', '<', $pos);
}
$this->reset();
}
/**
* gt_or_gte: '>' | '>='
*
* @return Token|void
*/
protected function gt_or_gte()
{
$pos = $this->index;
$this->mark();
$buf = $this->cur;
$this->consume();
$buf .= $this->cur;
$this->consume();
if ($buf === '>=')
{
return $this->tokens->create('gte', ">=", $pos);
}
else
{
$this->reset();
$this->consume();
return $this->tokens->create('gt', '>', $pos);
}
$this->reset();
}
/**
* Literal Operators predictor
*
* @return Token|null
*/
protected function literal_ops()
{
$pos = $this->index;
$this->mark();
$lit = $this->literal();
if ($lit)
{
switch (strtolower($lit->text))
{
case 'equals':
return $this->tokens->create('equals', $lit->text, $pos);
case 'startswith':
return $this->tokens->create('starts_with', $lit->text, $pos);
case 'endswith':
return $this->tokens->create('ends_with', $lit->text, $pos);
case 'contains':
return $this->tokens->create('contains', $lit->text, $pos);
case 'containsall':
return $this->tokens->create('contains_all', $lit->text, $pos);
case 'containsany':
return $this->tokens->create('contains_any', $lit->text, $pos);
case 'containsonly':
return $this->tokens->create('contains_only', $lit->text, $pos);
case 'lt':
case 'lowerthan':
return $this->tokens->create('lt', $lit->text, $pos);
case 'lte':
case 'lowerthanequal':
return $this->tokens->create('lte', $lit->text, $pos);
case 'gt':
case 'greaterthan':
return $this->tokens->create('gt', $lit->text, $pos);
case 'gte':
case 'greaterthantequal':
return $this->tokens->create('gte', $lit->text, $pos);
case 'empty':
return $this->tokens->create('empty', $lit->text, $pos);
}
}
$this->reset();
}
}
<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser;
defined('_JEXEC') or die;
use NRFramework\Parser\Parser;
use NRFramework\Parser\ConditionLexer;
/**
* ConditionParser
* LL(1) recursive-decent parser
* Uses NRFramework\Parser\ConditionLexer as input source
*
* Grammar:
* --------
* expr : condition (logic_op condition)* (option)*
* condition : {negate_op} alias (parameter)* | (alias|l_func) ({negate_op}? operator (values)? (parameter)*
* alias : {ident}
* values : value ({comma} value)*
* value : {quotedval} | ({literal} | {ident})+
* func : {ident} {l_paren} values {r_paren}
* l_func : func
* r_func : func
* parameter : {param} ({equals} value)?
* option : {ident} ({equals} value)?
* logic_op : {and} | {or}
* operator : {equals} | {starts_with} | {ends_with} | {empty} | {contains} | {contains_any} | {contains_all}| {contains_only} | {lt} | {lte} | {gt} | {gte}
*/
class ConditionParser extends Parser
{
/**
* Constructor
*
* @param ConditionLexer $input
*/
public function __construct(ConditionLexer $input)
{
parent::__construct($input, 2);
}
/**
* value : {quotedval} | ({literal} | {ident})+
*
* @return string
* @throws Exception
*/
public function value()
{
if ($this->lookahead[0]->type === 'quotedvalue')
{
$text = $this->lookahead[0]->text;
$this->match('quotedvalue');
return $text;
}
else if ($this->lookahead[0]->type !== 'ident' && $this->lookahead[0]->type !== 'literal')
{
throw new \Exception("Syntax error in ConditionParser::value(); expecting 'ident' or 'literal'; found {$this->lookahead[0]}");
}
$text = $this->lookahead[0]->text;
$this->consume();
while ($this->lookahead[0]->type === 'ident' || $this->lookahead[0]->type === 'literal')
{
$text .= ' ' . $this->lookahead[0]->text;
$this->consume();
}
return $text;
}
/**
* values : value ({comma} value)*
*
* @return array
*/
public function values()
{
$vals = [];
$vals[] = $this->value();
while ($this->lookahead[0]->type === 'comma')
{
$this->consume();
$vals[] = $this->value();
}
return $vals;
}
/**
* func : {ident} {l_paren} values {r_paren}
*
*/
public function func()
{
$func_name = $this->lookahead[0]->text;
$this->match('ident');
$this->match('l_paren');
if ($this->lookahead[0]->type === 'quotedvalue' ||
$this->lookahead[0]->type === 'ident' ||
$this->lookahead[0]->type === 'literal')
{
$func_args = $this->values();
}
$this->match('r_paren');
return ['func_name' => $func_name, 'func_args' => $func_args ?? []];
}
/**
* parameter : {param} ({equals} value)?
*
* @return string
*/
public function param()
{
$param = $this->lookahead[0]->text;
$value = true;
$this->match('param');
// If this is the 'context' parameter make sure that it appears as the last token
// if ($param === 'context')
// {
// $this->consume(); // consume the 'equals' operator
// $value = $this->value(); // expect a value
// if ($this->lookahead[0]->type !== 'EOF')
// {
// throw new \Exception("Syntax error in ConditionParser::param(); the 'context' parameter can only appear as the last token");
// }
// }
// else
if ($this->isOperator($this->lookahead[0]->type))
{
if ($this->lookahead[0]->type === 'equals')
{
$this->consume(); // consume the 'equals' operator
$value = $this->value(); // expect a value
}
else
{
// only the 'equals' operator is supported for the 'param' rule.
throw new \Exception("Syntax error in ConditionParser::param(); expecting 'equals', found {$this->lookahead[0]}");
}
}
return ['param' => $param, 'value' => $value];
}
/**
* alias : {ident}
*
* @return string
*/
public function alias()
{
$sel = $this->lookahead[0]->text;
$this->match('ident');
return $sel;
}
/**
* condition : {negate_op} alias (parameter)* | alias ({negate_op}? operator values)? (parameter)*
*
* @return object
*/
public function condition()
{
$result = [];
$operator = '';
$params = [];
$negate_op = false;
if ($this->lookahead[0]->type === 'negate_op')
{
$this->match('negate_op');
$operator = 'empty';
$result['alias'] = $this->alias();
}
else
{
if($this->lookahead[0]->type === 'ident' && $this->lookahead[1]->type === 'l_paren')
{
$l_func = $this->func();
$result['l_func_name'] = $l_func['func_name'];
$result['l_func_args'] = $l_func['func_args'];
}
else
{
$result['alias'] = $this->alias();
}
if ($this->lookahead[0]->type === 'negate_op')
{
$this->match('negate_op');
$negate_op = true;
// expect an operator after '!'
if (!$this->isOperator($this->lookahead[0]->type))
{
throw new Exceptions\SyntaxErrorException("Expecting an 'operator' after '!', found {$this->lookahead[0]}");
}
}
if ($this->isOperator($this->lookahead[0]->type))
{
$operator = $this->operator();
if($this->lookahead[0]->type === 'ident' && $this->lookahead[1]->type === 'l_paren')
{
$r_func = $this->func();
$result['r_func_name'] = $r_func['func_name'];
$result['r_func_args'] = $r_func['func_args'];
}
else if (
$this->lookahead[0]->type === 'quotedvalue' ||
$this->lookahead[0]->type === 'ident' ||
$this->lookahead[0]->type === 'literal'
)
{
$values = $this->values();
if (count($values) === 1)
{
$values = $values[0];
}
$result['values'] = $values;
}
}
}
while ($this->lookahead[0]->type === 'param')
{
$params[] = $this->param();
}
if (!$operator) {
$operator = 'empty';
$negate_op = true;
}
//
$_params = [];
foreach($params as $p)
{
$_params[$p['param']] = $p['value'];
}
$result['operator'] = $operator;
$result['negate_op'] = $negate_op;
$result['params'] = $_params;
return $result;
}
/**
* operator : {equals} | {starts_with} | {ends_with} | {empty} | {contains} | {contains_any} | {contains_all}| {contains_only} | {lt} | {lte} | {gt} | {gte}
*
* @return string
* @throws Exception
*/
public function operator()
{
if (!$this->isOperator($this->lookahead[0]->type))
{
throw new Exceptions\SyntaxErrorException("Expecting an 'operator', found " . $this->lookahead[0]);
}
$op = $this->lookahead[0]->type;
$this->consume();
return $op;
}
/**
* expr : condition ({logic_op} condition)* (option)*
*
* @return array The condition expression results
*/
public function expr()
{
$logic_op = 'and';
$res = [
'conditions' => [$this->condition()],
'logic_op' => 'and',
'context' => null,
'global_params' => []
];
if ($this->lookahead[0]->type === 'or')
{
$logic_op = 'or';
}
while ($this->lookahead[0]->type !== 'EOF')
{
$this->match($logic_op);
$res['conditions'][] = $this->condition();
}
$res['logic_op'] = $logic_op;
// check the last parsed condition for global parameters
$globalParams = [
'debug',
'dateformat',
'context',
'nopreparecontent',
'excludebots'
];
$last_params = $res['conditions'][count($res['conditions'])-1]['params'];
foreach(array_keys($last_params) as $param_key)
{
if (in_array(strtolower($param_key), $globalParams))
{
$res['global_params'][strtolower($param_key)] = $last_params[$param_key];
unset($res['conditions'][count($res['conditions'])-1]['params'][$param_key]);
}
}
// foreach ($last_params as $idx => $param)
// {
// if (in_array($param['param'], $globalParams))
// {
// $res['global_params'][$param['param']] = $param['value'];
// unset($res['conditions'][count($res['conditions'])-1]['params'][$idx]);
// }
// }
return $res;
}
/**
* Helper method that checks if the given Token is an operator.
*/
protected function isOperator($token_type)
{
return in_array($token_type, [
'equals',
'starts_with',
'ends_with',
'contains',
'contains_any',
'contains_all',
'contains_only',
'lt',
'lte',
'gt',
'gte',
'empty'
]);
}
}
<?php
/**
* @author Tassos.gr <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2024 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
namespace NRFramework\Parser;
defined('_JEXEC') or die;
use DateTime;
use DateTimeZone;
use Exception;
use Joomla\CMS\Factory;
class ConditionsEvaluator
{
/**
* Payload associative array
*
* @var array
*/
protected $payload;
/**
* Parsed conditions
*
* @var array
*/
protected $conditions;
/**
* Framework Condition aliases
*
* @var array
*/
protected $condition_aliases;
/**
* Debug flag
*
* @var bool
*/
protected $debug;
/**
* @param array $conditions The parsed conditions
* @param array $payload Shortcode parser payload
*/
public function __construct($conditions, $payload = null, $debug = false)
{
$this->conditions = $conditions;
$this->payload = $payload;
$this->debug = $debug;
$this->generateConditionAliasesMap();
}
/**
* @return array
*/
public function evaluate() : array
{
$results = [];
$caseSensitive = false;
foreach($this->conditions as $condition)
{
// case sensitivity param
if (array_key_exists('caseSensitive', $condition['params']))
{
$caseSensitive = strtolower($condition['params']['caseSensitive']) != 'false';
}
$result = [
'operator' => $condition['operator'],
'params' => $condition['params']
];
$l_value = null;
$r_value = null;
if(array_key_exists('r_func_name', $condition))
{
$r_value = $this->applyFunction($condition['r_func_name'], $condition['r_func_args']);
$result['r_func_name'] = $condition['r_func_name'];
$result['r_func_args'] = $condition['r_func_args'];
$result['r_func_val'] = $r_value;
}
else
{
$r_value = $condition['values'] ?? null;
}
if (array_key_exists('alias', $condition) && $this->isPayloadCondition($condition['alias']))
{
$l_value = $this->payload[$condition['alias']];
$result = array_merge($result, $this->evaluatePayloadCondition($l_value, $r_value, $condition['operator'], $caseSensitive));
$result['pass'] = $condition['negate_op'] ? !$result['pass'] : $result['pass'];
$result['actual_value'] = $this->payload[$condition['alias']];
}
else if (array_key_exists('alias', $condition) && $this->isFrameworkCondition($condition['alias']))
{
$result = array_merge($result, $this->evaluateFrameworkCondition($condition, $r_value));
}
else if (array_key_exists('l_func_name', $condition))
{
$l_value = $this->applyFunction($condition['l_func_name'], $condition['l_func_args']);
$result['l_func_name'] = $condition['l_func_name'];
$result['l_func_args'] = $condition['l_func_args'];
$result['l_func_val'] = $l_value;
$result = array_merge($result, $this->evaluatePayloadCondition($l_value, $r_value, $condition['operator'], $caseSensitive));
$result['pass'] = $condition['negate_op'] ? !$result['pass'] : $result['pass'];
}
// not a payload or framework condition with the 'empty' op
else if ($condition['operator'] === 'empty')
{
$result['pass'] = !$condition['negate_op'];
}
//
else
{
// Unknown condition
throw new Exceptions\InvalidConditionException($condition['alias']);
}
$results[] = $result;
}
return $results;
}
/**
*
*/
public function applyFunction($func_name, $args)
{
$arg_values = [];
foreach($args as $arg)
{
if ($this->isPayloadCondition($arg))
{
$arg_values[] = $this->payload[$arg];
}
else if ($this->isFrameworkCondition($arg))
{
$conditions_helper = \NRFramework\Conditions\ConditionsHelper::getInstance();
$framework_condition = $conditions_helper->getCondition($this->condition_aliases[strtolower($arg)]);
// Some framework condition don't implement the 'value()' method.
if (method_exists($framework_condition, 'value'))
{
$arg_values[] = $framework_condition->value();
}
else
{
throw new Exceptions\ConditionValueException($arg);
}
}
else
{
$arg_values[] = $arg;
}
}
switch(strtolower($func_name))
{
case 'count':
return $this->funcCount($arg_values);
case 'today':
return $this->funcToday();
case 'now':
return $this->funcNow();
case 'date':
return $this->funcDate($arg_values);
case 'datediff':
return $this->funcDateDiff($arg_values);
default:
throw new Exceptions\UnknownFunctionException($func_name);
}
}
/**
*
*/
public function funcCount($args)
{
if (count($args) !== 1)
{
throw new Exception("count() accepts 1 argument. " . count($args) . " were given.");
}
if (is_array($args[0]))
{
return count($args[0]);
}
else if (is_string($args[0]))
{
return mb_strlen($args[0]);
}
else
{
throw new Exception("count() accepts only strings and arrays.");
}
}
/**
*
*/
public function funcToday()
{
return (new DateTime('today'))->format('Y-m-d');
}
/**
*
*/
public function funcNow()
{
return new DateTime('now');
}
/**
*
*/
public function funcDate($args)
{
if (count($args) < 1 || count($args) > 3)
{
throw new Exception("date() accepts between 1 and 3 arguments. " . count($args) . " were given.");
}
if ($args[0] instanceof \DateTime || $args[0] instanceof \DateTimeImmutable)
{
return $args[0];
}
$date = $args[0];
$format = null;
if (count($args) > 1)
{
$format = $args[1] === 'null' ? null : $args[1];
}
$timezone = new \DateTimeZone($args[2] ?? Factory::getApplication()->get('offset','UTC'));
if ($format)
{
return \DateTime::createFromFormat('!'.$format, $date, $timezone);
}
return new \DateTime($date, $timezone);
}
/**
*
*/
public function funcDateDiff($args)
{
if (count($args) != 2)
{
throw new Exception("dateDiff() accepts 2 arguments. " . count($args) . " were given.");
}
$date1 = $this->convertToDateTime($args[0]);
$date2 = $this->convertToDateTime($args[1]);
return abs($date1->diff($date2)->days);
}
/**
* @var array $condition
*
* @return array Evaluation result
*/
protected function evaluatePayloadCondition($l_value, $r_value, $operator, $caseSensitive = false) : array
{
if (!$caseSensitive)
{
$l_value = $this->_lowercaseValues($l_value);
$r_value = $this->_lowercaseValues($r_value);
}
$result = [];
switch($operator)
{
case 'equals':
$result = $this->evaluateEquals($l_value, $r_value);
break;
case 'starts_with':
$result = $this->evaluateStartsWith($l_value, $r_value);
break;
case 'ends_with':
$result = $this->evaluateEndsWith($l_value, $r_value);
break;
case 'contains':
$result = $this->evaluateContains($l_value, $r_value);
break;
case 'contains_any':
$result = $this->evaluateContainsAny($l_value, $r_value);
break;
case 'contains_all':
$result = $this->evaluateContainsAll($l_value, $r_value);
break;
case 'contains_only':
$result = $this->evaluateContainsOnly($l_value, $r_value);
break;
case 'lt':
$result = $this->evaluateLessThan($l_value, $r_value);
break;
case 'lte':
$result = $this->evaluateLessThanEquals($l_value, $r_value);
break;
case 'gt':
$result = $this->evaluateGreaterThan($l_value, $r_value);
break;
case 'gte':
$result = $this->evaluateGreaterThanEquals($l_value, $r_value);
break;
case 'empty':
$result = $this->evaluateEmpty($l_value);
break;
default:
throw new Exceptions\UnknownOperatorException($operator);
}
return $result;
}
/**
* @var array $condition
*
* @return array Evaluation result
*/
public function evaluateFrameworkCondition($condition, $r_value)
{
$operator = $condition['operator'];
// Certain framework operators only work on single values.
// Force fail if the parsed condition contains more than one value.
if (in_array($operator, [
'contains',
'lt', 'lte',
'gt', 'gte',
'starts_with',
'ends_with'
]))
{
if (is_array($r_value) && !empty($r_value))
{
throw new Exceptions\UnsupportedValueOperandException($operator, false);
}
}
//
$conditions_helper = \NRFramework\Conditions\ConditionsHelper::getInstance();
$result = ['actual_value' => null];
// Transform 'caseSensitive' parameter to 'ignoreCase'
if (array_key_exists('caseSensitive', $condition['params']))
{
$condition['params']['ignoreCase'] = !$condition['params']['caseSensitive'];
}
// Instantiate the framework condition
$framework_condition = $conditions_helper->getCondition(
$this->condition_aliases[strtolower($condition['alias'])],
$r_value,
$operator,
$condition['params']
);
// Try to grab the actual condition's value if 'debug' is enabled.
if ($this->debug)
{
// Some framework conditions don't implement the 'value()' method.
if (method_exists($framework_condition, 'value'))
{
$result['actual_value'] = $framework_condition->value();
}
}
// Special handling for Date/Time framework conditions
if (in_array(strtolower($condition['alias']), ['date', 'time', 'datetime']))
{
$pass = $this->evaluatePayloadCondition($framework_condition->value(), $r_value, $operator)['pass'];
}
// Check if the condition passes using the 'passOne()' helper method
else
{
$pass = $conditions_helper->passOne(
$this->condition_aliases[strtolower($condition['alias'])],
$r_value,
$operator,
$condition['params']
);
}
$result['pass'] = $condition['negate_op'] ? !$pass : $pass;
return $result;
}
/**
* Generates an array mapping Condition aliases to Condition class names
*/
protected function generateConditionAliasesMap()
{
$conditions_namespace = 'NRFramework\\Conditions\\Conditions\\';
$dir_iterator = new \RecursiveDirectoryIterator(JPATH_PLUGINS . "/system/nrframework/NRFramework/Conditions/Conditions/");
$iterator = new \RecursiveIteratorIterator($dir_iterator, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file)
{
$condition_class = str_replace(JPATH_PLUGINS . "/system/nrframework/NRFramework/Conditions/Conditions/", '', $file);
$condition_class = str_replace('.php', '', $condition_class);
$condition_class = str_replace('/', '\\', $condition_class);
if (class_exists($conditions_namespace . $condition_class))
{
$this->condition_aliases[strtolower($file->getBasename('.php'))] = $condition_class;
if (property_exists($conditions_namespace . $condition_class, 'shortcode_aliases'))
{
foreach(($conditions_namespace . $condition_class)::$shortcode_aliases as $alias)
{
$this->condition_aliases[$alias] = $condition_class;
}
}
}
}
}
/**
*
*/
protected function convertToDateTime($date, $format = null, $tz = null)
{
if ($tz == null)
{
$tz = Factory::getApplication()->getCfg('offset','UTC');
}
if ($date instanceof \DateTime || $date instanceof \DateTimeImmutable)
{
return $date;
}
try
{
if ($format)
{
return DateTime::createFromFormat($format, $date, $tz);
}
return new DateTime($date, new DateTimeZone($tz));
}
catch (\Throwable $t)
{
return null;
}
}
/**
*
*/
protected function isPayloadCondition($alias)
{
return $this->payload && array_key_exists($alias, $this->payload);
}
/**
*
*/
protected function isFrameworkCondition($alias)
{
return array_key_exists(strtolower($alias), $this->condition_aliases);
}
/**
* @return array Evaluation result
*/
protected function evaluateEquals($l_value, $r_value) : array
{
// are we comparing arrays?
if (is_array($l_value))
{
return $this->evaluateContainsAny($l_value, $r_value);
}
if (is_numeric($r_value))
{
return ['pass' => $l_value == $r_value];
}
// check if we are comparing dates
$l_date = $this->convertToDateTime($l_value);
$r_date = $this->convertToDateTime($r_value);
if($l_date && $r_date)
{
if (is_string($r_value) && !preg_match("/\d{1,2}:\d{1,2}(:\d{1,2})?/", $r_value))
{
$l_date->setTime(0,0);
$r_date->setTime(0,0);
}
return [
'l_eval' => $l_date,
'r_eval' => $r_date,
'pass' => $l_date == $r_date
];
}
// generic equality test
return ['pass' => $l_value == $r_value];
}
/**
* @return array Evaluation result
*/
protected function evaluateStartsWith($l_value, $r_value) : array
{
if (!is_string($l_value))
{
throw new Exceptions\UnsupportedOperatorException('startsWith', $l_value, false);
}
if (!is_string($r_value))
{
throw new Exceptions\UnsupportedValueOperandException('startsWith', false);
}
return ['pass' => $this->_starts_with($l_value, $r_value)];
}
/**
* @return array Evaluation result
*/
protected function evaluateEndsWith($l_value, $r_value) : array
{
if (!is_string($l_value))
{
throw new Exceptions\UnsupportedOperatorException('endsWith', $l_value, false);
}
if (!is_string($r_value))
{
throw new Exceptions\UnsupportedValueOperandException('endsWith', false);
}
return ['pass' => $this->_ends_with($l_value, $r_value)];
}
/**
* @return array Evaluation result
*/
protected function evaluateContains($l_value, $r_value) : array
{
if (!is_string($l_value))
{
throw new Exceptions\UnsupportedOperatorException('contains', $l_value, false);
}
if (!is_string($r_value))
{
throw new Exceptions\UnsupportedValueOperandException('contains', false);
}
return ['pass' => strlen($l_value) > 0 && strpos($l_value, $r_value) !== false];
}
/**
* @return array Evaluation result
*/
protected function evaluateContainsAny($l_value, $r_value) : array
{
if (!is_array($l_value))
{
throw new Exceptions\UnsupportedOperatorException('containsAny', $l_value, true);
}
$r_value = (array) $r_value;
return ['pass' => !empty(array_intersect($l_value, $r_value))];
}
/**
* @return array Evaluation result
*/
protected function evaluateContainsAll($l_value, $r_value) : array
{
if (!is_array($l_value))
{
throw new Exceptions\UnsupportedOperatorException('containsAll', $l_value, true);
}
$r_value = (array) $r_value;
return ['pass' => count(array_intersect($l_value, $r_value)) == count($r_value)];
}
/**
* @return array Evaluation result
*/
protected function evaluateContainsOnly($l_value, $r_value) : array
{
if (!is_array($l_value))
{
throw new Exceptions\UnsupportedOperatorException('containsOnly', $l_value, true);
}
$r_value = (array) $r_value;
return ['pass' => count(array_diff($l_value, $r_value)) == 0];
}
/**
* @return array Evaluation result
*/
protected function evaluateLessThan($l_value, $r_value) : array
{
if (is_numeric($r_value))
{
return ['pass' => $l_value < $r_value];
}
// check if we are comparing dates
$l_date = $this->convertToDateTime($l_value);
$r_date = $this->convertToDateTime($r_value);
if($l_date && $r_date)
{
if (is_string($r_value) && !preg_match("/\d{1,2}:\d{1,2}(:\d{1,2})?/", $r_value))
{
$l_date->setTime(0,0);
$r_date->setTime(0,0);
}
return [
'l_eval' => $l_date,
'r_eval' => $r_date,
'pass' => $l_date < $r_date
];
}
throw new Exceptions\SyntaxErrorException("The 'lessThan' operator accepts only numeric values and dates.");
}
/**
* @return array Evaluation result
*/
protected function evaluateLessThanEquals($l_value, $r_value) : array
{
if (is_numeric($r_value))
{
return ['pass' => $l_value <= $r_value];
}
// check if we are comparing dates
$l_date = $this->convertToDateTime($l_value);
$r_date = $this->convertToDateTime($r_value);
if($l_date && $r_date)
{
if (is_string($r_value) && !preg_match("/\d{1,2}:\d{1,2}(:\d{1,2})?/", $r_value))
{
$l_date->setTime(0,0);
$r_date->setTime(0,0);
}
return [
'l_eval' => $l_date,
'r_eval' => $r_date,
'pass' => $l_date <= $r_date
];
}
throw new Exceptions\SyntaxErrorException("The 'lessThanEquals' operator accepts only numeric values and dates.");
}
/**
* @return array Evaluation result
*/
protected function evaluateGreaterThan($l_value, $r_value) : array
{
if (is_numeric($r_value))
{
return ['pass' => $l_value > $r_value];
}
// check if we are comparing dates
$l_date = $this->convertToDateTime($l_value);
$r_date = $this->convertToDateTime($r_value);
if($l_date && $r_date)
{
if (is_string($r_value) && !preg_match("/\d{1,2}:\d{1,2}(:\d{1,2})?/", $r_value))
{
$l_date->setTime(0,0);
$r_date->setTime(0,0);
}
return [
'l_eval' => $l_date,
'r_eval' => $r_date,
'pass' => $l_date > $r_date
];
}
throw new Exceptions\SyntaxErrorException("The 'greaterThan' operator accepts only numeric values and dates.");
}
/**
* @return array Evaluation result
*/
protected function evaluateGreaterThanEquals($l_value, $r_value) : array
{
if (is_numeric($r_value))
{
return ['pass' => $l_value >= $r_value];
}
// check if we are comparing dates
$l_date = $this->convertToDateTime($l_value);
$r_date = $this->convertToDateTime($r_value);
if($l_date && $r_date)
{
if (is_string($r_value) && !preg_match("/\d{1,2}:\d{1,2}(:\d{1,2})?/", $r_value))
{
$l_date->setTime(0,0);
$r_date->setTime(0,0);
}
return [
'l_eval' => $l_date,
'r_eval' => $r_date,
'pass' => $l_date >= $r_date
];
}
throw new Exceptions\SyntaxErrorException("The 'greaterThanEquals' operator accepts only numeric values and dates.");
}
/**
* @return array Evaluation result
*/
protected function evaluateEmpty($payload_value) : array
{
// $payload_value = $this->payload[$payload_key];
if (is_array($payload_value))
{
return ['pass' => empty($payload_value)];
}
else if(is_string($payload_value))
{
$payload_value = trim($payload_value);
return ['pass' => empty($payload_value) || $payload_value == 'false'];
}
else if(is_bool($payload_value))
{
return ['pass' => !$payload_value];
}
return ['pass' => is_null($payload_value)];
}
/**
* @return bool
*/
protected function _starts_with($haystack, $needle)
{
return strlen($needle) > 0 && strncmp($haystack, $needle, strlen($needle)) === 0;
}
/**
* @return bool
*/
protected function _ends_with($haystack, $needle)
{
return strlen($needle) > 0 && substr($haystack, -strlen($needle)) === (string)$needle;
}
/**
* @return bool
*/
protected function _contains($haystack, $needle)
{
return strlen($needle) > 0 && strpos($haystack, $needle) !== false;
}
/**
* @return string|array
*/
protected function _lowercaseValues($value)
{
if (is_array($value))
{
foreach($value as $idx => $val)
{
if (is_string($val))
{
$value[$idx] = strtolower($val);
}
}
}
else if(is_string($value))
{
$value = strtolower($value);
}
return $value;
}
}