![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/CoreBundle/Helper/ |
<?php namespace Mautic\CoreBundle\Helper; class SearchStringHelper { public const COMMAND_NEGATE = 0; public const COMMAND_POSIT = 1; public const COMMAND_NEUTRAL = 2; /** * @var array */ protected $needsParsing = [ ' ', '(', ')', ]; /** * @var array */ protected $needsClosing = [ 'quote' => '"', 'parenthesis' => '(', ]; /** * @var array */ protected $closingChars = [ 'quote' => '"', 'parenthesis' => ')', ]; public function __construct(array $needsParsing = null, array $needsClosing = null, array $closingChars = null) { if (null !== $needsParsing) { $this->needsParsing = $needsParsing; } if (null !== $needsClosing) { $this->needsClosing = $needsClosing; } if (null !== $closingChars) { $this->closingChars = $closingChars; } } /** * @param string $input * * @return \stdClass */ public static function parseSearchString($input, array $needsParsing = null, array $needsClosing = null, array $closingChars = null) { $input = trim(strip_tags($input)); $self = new self($needsParsing, $needsClosing, $closingChars); return $self->parseString($input); } public function parseString($input) { return $this->splitUpSearchString($input); } public static function mergeCommands(&$filters, array $commands): void { if (!isset($filters->commands)) { $filters->commands = $commands; return; } foreach ($commands as $command => $status) { if (isset($filters->commands[$command])) { if ($status !== $filters->commands[$command]) { $filters->commands[$command] = self::COMMAND_NEUTRAL; } } else { $filters->commands[$command] = $status; } } } protected function addFilterCommand(&$filters, $mergeFilter) { $command = $mergeFilter->command; if ('is' === $command) { // Special case $command = $command.':'.$mergeFilter->string; } if (!empty($command)) { if (!isset($filters->commands[$command])) { $filters->commands[$command] = ($mergeFilter->not) ? self::COMMAND_NEGATE : self::COMMAND_POSIT; } else { if (($mergeFilter->not && self::COMMAND_POSIT === $filters->commands[$command]) || !$mergeFilter->not && self::COMMAND_NEGATE === $filters->commands[$command]) { $filters->commands[$command] = self::COMMAND_NEUTRAL; } } } } /** * @param string $input * @param string $baseName * @param string $overrideCommand * * @return \stdClass */ protected function splitUpSearchString($input, $baseName = 'root', $overrideCommand = '') { $keyCount = 0; $command = $overrideCommand; $filters = new \stdClass(); $filters->commands = []; $filters->{$baseName} = []; $filters->{$baseName}[$keyCount] = new \stdClass(); $filters->{$baseName}[$keyCount]->type = 'and'; $filters->{$baseName}[$keyCount]->command = $command; $filters->{$baseName}[$keyCount]->string = ''; $filters->{$baseName}[$keyCount]->not = 0; $filters->{$baseName}[$keyCount]->strict = 0; $chars = str_split($input); $pos = 0; $string = ''; // Iterate through every character to ensure that the search string is properly parsed from left to right while // considering quotes, parenthesis, and commands while (count($chars)) { $char = $chars[$pos]; $string .= $char; unset($chars[$pos]); ++$pos; if (':' == $char) { // the string is a command $command = trim(substr($string, 0, -1)); // does this have a negative? if (str_starts_with($command, '!')) { $filters->{$baseName}[$keyCount]->not = 1; $command = substr($command, 1); } if (empty($chars)) { // Command hasn't been defined so don't allow empty or could end up searching entire table unset($filters->{$baseName}[$keyCount]); } else { $filters->{$baseName}[$keyCount]->command = $command; $string = ''; } } elseif (' ' == $char) { // arrived at the end of a single word that is not within a quote or parenthesis so add it as standalone if (' ' != $string) { $string = trim($string); $type = ('or' == strtolower($string) || 'and' == strtolower($string)) ? $string : ''; $this->setFilter($filters, $baseName, $keyCount, $string, $command, $overrideCommand, true, $type, !empty($chars)); } continue; } elseif (in_array($char, $this->needsClosing)) { // arrived at a character that has a closing partner and thus needs to be parsed as a group // find the closing match $key = array_search($char, $this->needsClosing); $openingCount = 1; $closingCount = 1; // reiterate through the rest of the chars to find its closing match foreach ($chars as $k => $c) { $string .= $c; unset($chars[$k]); ++$pos; if ($c === $this->closingChars[$key] && $openingCount === $closingCount) { // found the matching character (accounts for nesting) // does group have a negative? if (str_starts_with($string, '!')) { $filters->{$baseName}[$keyCount]->not = 1; $string = substr($string, 1); } // remove wrapping grouping chars if (str_starts_with($string, $char) && substr($string, -1) === $c) { $string = substr($string, 1, -1); } // handle characters that support nesting $neededParsing = false; if ('"' !== $c) { // check to see if the nested string needs to be parsed as well foreach ($this->needsParsing as $parseMe) { if (str_contains($string, $parseMe)) { $parsed = $this->splitUpSearchString($string, 'parsed', $command); $filters->{$baseName}[$keyCount]->children = $parsed->parsed; $neededParsing = true; break; } } } $this->setFilter($filters, $baseName, $keyCount, $string, $command, $overrideCommand, !$neededParsing); break; } elseif ($c === $char) { // this is another opening char so keep track of it to properly handle nested strings ++$openingCount; } elseif ($c === $this->closingChars[$key]) { // this is a closing char within a nest but not the one to close the group ++$closingCount; } } } elseif (empty($chars)) { $filters->{$baseName}[$keyCount]->command = $command; $this->setFilter($filters, $baseName, $keyCount, $string, $command, $overrideCommand, true, null, false); }// else keep concocting chars } return $filters; } private function setFilter(&$filters, &$baseName, &$keyCount, &$string, &$command, $overrideCommand, $setFilter = true, $type = null, $setUpNext = true): void { if (!empty($type)) { $filters->{$baseName}[$keyCount]->type = strtolower($type); } elseif ($setFilter) { $string = trim(strtolower($string)); // remove operators and empty values if (in_array($string, ['', 'or', 'and'])) { unset($filters->{$baseName}[$keyCount]); return; } if (!isset($filters->{$baseName}[$keyCount]->strict)) { $filters->{$baseName}[$keyCount]->strict = 0; } if (!isset($filters->{$baseName}[$keyCount]->not)) { $filters->{$baseName}[$keyCount]->not = 0; } $strictPos = strpos($string, '+'); $notPos = strpos($string, '!'); if (0 === $strictPos || 1 === $strictPos || 0 === $notPos || 1 === $notPos) { if (false !== $strictPos && false !== $notPos) { // +! or !+ $filters->{$baseName}[$keyCount]->strict = 1; $filters->{$baseName}[$keyCount]->not = 1; $string = substr($string, 2); } elseif (0 === $strictPos && false === $notPos) { // + $filters->{$baseName}[$keyCount]->strict = 1; $filters->{$baseName}[$keyCount]->not = 0; $string = substr($string, 1); } elseif (false === $strictPos && 0 === $notPos) { // ! $filters->{$baseName}[$keyCount]->strict = 0; $filters->{$baseName}[$keyCount]->not = 1; $string = substr($string, 1); } } $filters->{$baseName}[$keyCount]->string = $string; $this->addFilterCommand($filters, $filters->{$baseName}[$keyCount]); // setup the next filter if ($setUpNext) { ++$keyCount; $filters->{$baseName}[$keyCount] = new \stdClass(); $filters->{$baseName}[$keyCount]->type = 'and'; $filters->{$baseName}[$keyCount]->command = $overrideCommand; $filters->{$baseName}[$keyCount]->string = ''; $filters->{$baseName}[$keyCount]->not = 0; $filters->{$baseName}[$keyCount]->strict = 0; } } $string = ''; $command = $overrideCommand; } }