Parser.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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. private static function connectParts(array &$parts, array &$criteria_arr, string $last_op, mixed $eb, bool $force = false): void
  30. {
  31. foreach ([' ' => 'orX', '|' => 'orX', '&' => 'andX'] as $op => $func) {
  32. if ($last_op === $op || $force) {
  33. $criteria_arr[] = $eb->{$func}(...$parts);
  34. break;
  35. }
  36. }
  37. }
  38. /**
  39. * Parse $input string into a Doctrine query Criteria
  40. *
  41. * Currently doesn't support nesting with parenthesis and
  42. * recognises either spaces (currently `or`, should be fuzzy match), `OR` or `|` (`or`) and `AND` or `&` (`and`)
  43. *
  44. * TODO: Better fuzzy match, implement exact match with quotes and nesting with parens
  45. *
  46. * @return Criteria[]
  47. */
  48. public static function parse(string $input, ?string $locale = null, ?Actor $actor = null, int $level = 0): array
  49. {
  50. if ($level === 0) {
  51. $input = trim(preg_replace(['/\s+/', '/\s+AND\s+/', '/\s+OR\s+/'], [' ', '&', '|'], $input), ' |&');
  52. }
  53. $left = 0;
  54. $right = 0;
  55. $lenght = mb_strlen($input);
  56. $eb = Criteria::expr();
  57. $note_criteria_arr = [];
  58. $actor_criteria_arr = [];
  59. $note_parts = [];
  60. $actor_parts = [];
  61. $last_op = null;
  62. for ($index = 0; $index < $lenght; ++$index) {
  63. $end = false;
  64. $match = false;
  65. foreach (['&', '|', ' '] as $delimiter) {
  66. if ($input[$index] === $delimiter || $end = ($index === $lenght - 1)) {
  67. $term = mb_substr($input, $left, $end ? null : $right - $left);
  68. $note_res = null;
  69. $actor_res = null;
  70. Event::handle('CollectionQueryCreateExpression', [$eb, $term, $locale, $actor, &$note_res, &$actor_res]);
  71. if (\is_null($note_res) && \is_null($actor_res)) { // @phpstan-ignore-line
  72. throw new ServerException("No one claimed responsibility for a match term: {$term}");
  73. }
  74. if (!empty($note_res)) { // @phpstan-ignore-line
  75. if (\is_array($note_res)) {
  76. $note_res = $eb->orX(...$note_res);
  77. }
  78. $note_parts[] = $note_res;
  79. }
  80. if (!empty($actor_res)) {
  81. if (\is_array($actor_res)) {
  82. $actor_res = $eb->orX(...$actor_res);
  83. }
  84. $actor_parts[] = $actor_res;
  85. }
  86. $right = $left = $index + 1;
  87. if (!\is_null($last_op) && $last_op !== $delimiter) {
  88. self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: false);
  89. } else {
  90. $last_op = $delimiter;
  91. }
  92. $match = true;
  93. break;
  94. }
  95. }
  96. // TODO
  97. if (!$match) { // @phpstan-ignore-line
  98. ++$right;
  99. }
  100. }
  101. $note_criteria = null;
  102. $actor_criteria = null;
  103. if (!empty($note_parts)) { // @phpstan-ignore-line
  104. self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: true);
  105. $note_criteria = new Criteria($eb->orX(...$note_criteria_arr));
  106. }
  107. if (!empty($actor_parts)) { // @phpstan-ignore-line
  108. self::connectParts($actor_parts, $actor_criteria_arr, $last_op, $eb, force: true);
  109. $actor_criteria = new Criteria($eb->orX(...$actor_criteria_arr));
  110. }
  111. return [$note_criteria, $actor_criteria];
  112. }
  113. }