. // }}} namespace Component\Search\Util; use App\Core\Event; use App\Util\Exception\ServerException; use Doctrine\Common\Collections\Criteria; abstract class Parser { /** * Merge $parts into $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 * * @return Criteria[] */ public static function parse(string $input, ?string $language = 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('SearchCreateExpression', [$eb, $term, $language, &$note_res, &$actor_res]); if (\is_null($note_res) && \is_null($actor_res)) { throw new ServerException("No one claimed responsibility for a match term: {$term}"); } if (!\is_null($note_res) && !empty($note_res)) { if (\is_array($note_res)) { $note_res = $eb->orX(...$note_res); } $note_parts[] = $note_res; } if (!\is_null($actor_res) && !empty($note_res)) { 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; continue 2; } } 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)) { 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]; } }