. // }}} namespace Component\Collection\Util; use App\Core\Event; use App\Entity\Actor; use App\Util\Exception\ServerException; use Doctrine\Common\Collections\Criteria; abstract class Parser { /** * Merge $parts into $criteria_arr * * @param mixed[] $parts * @param Criteria[] $criteria_arr */ private static function connectParts(array &$parts, array &$criteria_arr, string $last_op, mixed $eb, bool $force = false): void { foreach ([' ' => 'orX', '|' => 'orX', '&' => 'andX'] as $op => $func) { if ($last_op === $op || $force) { $criteria_arr[] = $eb->{$func}(...$parts); break; } } } /** * Parse $input string into a Doctrine query Criteria * * Currently doesn't support nesting with parenthesis and * recognises either spaces (currently `or`, should be fuzzy match), `OR` or `|` (`or`) and `AND` or `&` (`and`) * * TODO: Better fuzzy match, implement exact match with quotes and nesting with parens * TODO: Proper parser, tokenize better. Mostly a rewrite * * @return array{?Criteria, ?Criteria} [?$note_criteria, ?$actor_criteria] */ public static function parse(string $input, ?string $locale = null, ?Actor $actor = null, int $level = 0): array { if ($level === 0) { $input = trim(preg_replace(['/\s+/', '/\s+AND\s+/', '/\s+OR\s+/'], [' ', '&', '|'], $input), ' |&'); } $left = 0; $right = 0; $lenght = mb_strlen($input); $eb = Criteria::expr(); $note_criteria_arr = []; $actor_criteria_arr = []; $note_parts = []; $actor_parts = []; $last_op = null; for ($index = 0; $index < $lenght; ++$index) { $end = false; $match = false; foreach (['&', '|', ' '] as $delimiter) { if ($input[$index] === $delimiter || $end = ($index === $lenght - 1)) { $term = mb_substr($input, $left, $end ? null : $right - $left); $note_res = null; $actor_res = null; Event::handle('CollectionQueryCreateExpression', [$eb, $term, $locale, $actor, &$note_res, &$actor_res]); if (\is_null($note_res) && \is_null($actor_res)) { // @phpstan-ignore-line //throw new ServerException("No one claimed responsibility for a match term: {$term}"); // It's okay if the term doesn't exist, just perform a regular search } if (!empty($note_res)) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234 if (\is_array($note_res)) { $note_res = $eb->orX(...$note_res); } $note_parts[] = $note_res; } if (!empty($actor_res)) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234 if (\is_array($actor_res)) { $actor_res = $eb->orX(...$actor_res); } $actor_parts[] = $actor_res; } $right = $left = $index + 1; if (!\is_null($last_op) && $last_op !== $delimiter) { self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: false); } else { $last_op = $delimiter; } $match = true; break; } } // TODO if (!$match) { ++$right; } } $note_criteria = null; $actor_criteria = null; if (!empty($note_parts)) { self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: true); $note_criteria = new Criteria($eb->orX(...$note_criteria_arr)); } if (!empty($actor_parts)) { // @phpstan-ignore-line weird, but this whole thing needs a rewrite self::connectParts($actor_parts, $actor_criteria_arr, $last_op, $eb, force: true); $actor_criteria = new Criteria($eb->orX(...$actor_criteria_arr)); } return [$note_criteria, $actor_criteria]; } }