| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:26:18 +01:00
										 |  |  | declare(strict_types = 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  | // {{{ License
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This file is part of GNU social - https://www.gnu.org/software/social
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is distributed in the hope that it will be useful,
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // }}}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Component\Search\Util; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use App\Core\Event; | 
					
						
							| 
									
										
										
										
											2021-12-24 09:27:24 +00:00
										 |  |  | use App\Entity\Actor; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  | use App\Util\Exception\ServerException; | 
					
						
							|  |  |  | use Doctrine\Common\Collections\Criteria; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | abstract class Parser | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-10-10 09:23:51 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * 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; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * 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`) | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  |      * TODO: Better fuzzy match, implement exact match with quotes and nesting with parens | 
					
						
							| 
									
										
										
										
											2021-10-10 05:44:10 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @return Criteria[] | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-12-24 09:27:24 +00:00
										 |  |  |     public static function parse(string $input, ?string $language = null, ?Actor $actor = null, int $level = 0): array | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |         if ($level === 0) { | 
					
						
							|  |  |  |             $input = trim(preg_replace(['/\s+/', '/\s+AND\s+/', '/\s+OR\s+/'], [' ', '&', '|'], $input), ' |&'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  |         $left               = 0; | 
					
						
							|  |  |  |         $right              = 0; | 
					
						
							| 
									
										
										
										
											2021-10-10 05:44:10 +01:00
										 |  |  |         $lenght             = mb_strlen($input); | 
					
						
							|  |  |  |         $eb                 = Criteria::expr(); | 
					
						
							|  |  |  |         $note_criteria_arr  = []; | 
					
						
							|  |  |  |         $actor_criteria_arr = []; | 
					
						
							|  |  |  |         $note_parts         = []; | 
					
						
							|  |  |  |         $actor_parts        = []; | 
					
						
							|  |  |  |         $last_op            = null; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for ($index = 0; $index < $lenght; ++$index) { | 
					
						
							|  |  |  |             $end   = false; | 
					
						
							|  |  |  |             $match = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             foreach (['&', '|', ' '] as $delimiter) { | 
					
						
							|  |  |  |                 if ($input[$index] === $delimiter || $end = ($index === $lenght - 1)) { | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  |                     $term      = mb_substr($input, $left, $end ? null : $right - $left); | 
					
						
							|  |  |  |                     $note_res  = null; | 
					
						
							|  |  |  |                     $actor_res = null; | 
					
						
							| 
									
										
										
										
											2021-12-24 09:27:24 +00:00
										 |  |  |                     Event::handle('SearchCreateExpression', [$eb, $term, $language, $actor, &$note_res, &$actor_res]); | 
					
						
							| 
									
										
										
										
											2021-12-07 20:25:28 +00:00
										 |  |  |                     if (\is_null($note_res) && \is_null($actor_res)) { | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |                         throw new ServerException("No one claimed responsibility for a match term: {$term}"); | 
					
						
							| 
									
										
										
										
											2021-12-08 21:25:48 +00:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-12-11 22:21:31 +00:00
										 |  |  |                     if (!\is_null($note_res) && !empty($note_res)) { | 
					
						
							| 
									
										
										
										
											2021-12-11 19:33:30 +00:00
										 |  |  |                         if (\is_array($note_res)) { | 
					
						
							|  |  |  |                             $note_res = $eb->orX(...$note_res); | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2021-10-10 09:26:18 +01:00
										 |  |  |                         $note_parts[] = $note_res; | 
					
						
							| 
									
										
										
										
											2021-12-08 21:25:48 +00:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-12-11 22:21:31 +00:00
										 |  |  |                     if (!\is_null($actor_res) && !empty($note_res)) { | 
					
						
							| 
									
										
										
										
											2021-12-11 19:33:30 +00:00
										 |  |  |                         if (\is_array($actor_res)) { | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  |                             $actor_res = $eb->orX(...$actor_res); | 
					
						
							| 
									
										
										
										
											2021-12-11 19:33:30 +00:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2021-10-10 09:26:18 +01:00
										 |  |  |                         $actor_parts[] = $actor_res; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     $right = $left = $index + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:23:51 +01:00
										 |  |  |                     if (!\is_null($last_op) && $last_op !== $delimiter) { | 
					
						
							|  |  |  |                         self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: false); | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |                     } else { | 
					
						
							|  |  |  |                         $last_op = $delimiter; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     $match = true; | 
					
						
							|  |  |  |                     continue 2; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (!$match) { | 
					
						
							|  |  |  |                 ++$right; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-23 13:27:31 +00:00
										 |  |  |         $note_criteria  = null; | 
					
						
							|  |  |  |         $actor_criteria = null; | 
					
						
							| 
									
										
										
										
											2021-12-26 19:08:56 +00:00
										 |  |  |         if (!empty($note_parts)) { // @phpstan-ignore-line
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:23:51 +01:00
										 |  |  |             self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: true); | 
					
						
							| 
									
										
										
										
											2021-10-10 05:44:10 +01:00
										 |  |  |             $note_criteria = new Criteria($eb->orX(...$note_criteria_arr)); | 
					
						
							| 
									
										
										
										
											2021-12-08 21:25:48 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-26 19:08:56 +00:00
										 |  |  |         if (!empty($actor_parts)) { // @phpstan-ignore-line
 | 
					
						
							| 
									
										
										
										
											2021-10-10 09:23:51 +01:00
										 |  |  |             self::connectParts($actor_parts, $actor_criteria_arr, $last_op, $eb, force: true); | 
					
						
							|  |  |  |             $actor_criteria = new Criteria($eb->orX(...$actor_criteria_arr)); | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 05:44:10 +01:00
										 |  |  |         return [$note_criteria, $actor_criteria]; | 
					
						
							| 
									
										
										
										
											2021-09-27 10:39:58 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } |