Parser.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <?php
  2. declare(strict_types = 1);
  3. // {{{ License
  4. // This file is part of GNU social - https://www.gnu.org/software/social
  5. //
  6. // GNU social is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // GNU social is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  18. // }}}
  19. namespace Component\Collection\Util;
  20. use App\Core\Event;
  21. use App\Entity\Actor;
  22. use App\Util\Exception\ServerException;
  23. use Doctrine\Common\Collections\Criteria;
  24. abstract class Parser
  25. {
  26. /**
  27. * Merge $parts into $criteria_arr
  28. *
  29. * @param mixed[] $parts
  30. * @param Criteria[] $criteria_arr
  31. */
  32. private static function connectParts(array &$parts, array &$criteria_arr, string $last_op, mixed $eb, bool $force = false): void
  33. {
  34. foreach ([' ' => 'orX', '|' => 'orX', '&' => 'andX'] as $op => $func) {
  35. if ($last_op === $op || $force) {
  36. $criteria_arr[] = $eb->{$func}(...$parts);
  37. break;
  38. }
  39. }
  40. }
  41. /**
  42. * Parse $input string into a Doctrine query Criteria
  43. *
  44. * Currently doesn't support nesting with parenthesis and
  45. * recognises either spaces (currently `or`, should be fuzzy match), `OR` or `|` (`or`) and `AND` or `&` (`and`)
  46. *
  47. * TODO: Better fuzzy match, implement exact match with quotes and nesting with parens
  48. * TODO: Proper parser, tokenize better. Mostly a rewrite
  49. *
  50. * @return array{?Criteria, ?Criteria} [?$note_criteria, ?$actor_criteria]
  51. */
  52. public static function parse(string $input, ?string $locale = null, ?Actor $actor = null, int $level = 0): array
  53. {
  54. if ($level === 0) {
  55. $input = trim(preg_replace(['/\s+/', '/\s+AND\s+/', '/\s+OR\s+/'], [' ', '&', '|'], $input), ' |&');
  56. }
  57. $left = 0;
  58. $right = 0;
  59. $lenght = mb_strlen($input);
  60. $eb = Criteria::expr();
  61. $note_criteria_arr = [];
  62. $actor_criteria_arr = [];
  63. $note_parts = [];
  64. $actor_parts = [];
  65. $last_op = null;
  66. for ($index = 0; $index < $lenght; ++$index) {
  67. $end = false;
  68. $match = false;
  69. foreach (['&', '|', ' '] as $delimiter) {
  70. if ($input[$index] === $delimiter || $end = ($index === $lenght - 1)) {
  71. $term = mb_substr($input, $left, $end ? null : $right - $left);
  72. $note_res = null;
  73. $actor_res = null;
  74. Event::handle('CollectionQueryCreateExpression', [$eb, $term, $locale, $actor, &$note_res, &$actor_res]);
  75. if (\is_null($note_res) && \is_null($actor_res)) { // @phpstan-ignore-line
  76. //throw new ServerException("No one claimed responsibility for a match term: {$term}");
  77. // It's okay if the term doesn't exist, just perform a regular search
  78. }
  79. 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
  80. if (\is_array($note_res)) {
  81. $note_res = $eb->orX(...$note_res);
  82. }
  83. $note_parts[] = $note_res;
  84. }
  85. 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
  86. if (\is_array($actor_res)) {
  87. $actor_res = $eb->orX(...$actor_res);
  88. }
  89. $actor_parts[] = $actor_res;
  90. }
  91. $right = $left = $index + 1;
  92. if (!\is_null($last_op) && $last_op !== $delimiter) {
  93. self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: false);
  94. } else {
  95. $last_op = $delimiter;
  96. }
  97. $match = true;
  98. break;
  99. }
  100. }
  101. // TODO
  102. if (!$match) {
  103. ++$right;
  104. }
  105. }
  106. $note_criteria = null;
  107. $actor_criteria = null;
  108. if (!empty($note_parts)) {
  109. self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: true);
  110. $note_criteria = new Criteria($eb->orX(...$note_criteria_arr));
  111. }
  112. if (!empty($actor_parts)) { // @phpstan-ignore-line weird, but this whole thing needs a rewrite
  113. self::connectParts($actor_parts, $actor_criteria_arr, $last_op, $eb, force: true);
  114. $actor_criteria = new Criteria($eb->orX(...$actor_criteria_arr));
  115. }
  116. return [$note_criteria, $actor_criteria];
  117. }
  118. }