$pages * @param Shared|null $Shared */ public function __construct(string $searchValue, bool $isRegex, bool $isCaseSensitive, array $pages, ?Shared $Shared) { $this->searchValue = preg_quote($searchValue, '/'); $this->regexFlags = 'ims'; $this->pages = $pages; $this->Shared = $Shared; if ($isRegex) { $this->searchValue = str_replace('/', '\/', $searchValue); } if ($isCaseSensitive) { $this->regexFlags = 'ms'; } } /** * Check whether a property name represents a valid block property. * * @param string $property * @return bool true if the property name is in the whitelist */ public static function isValidBlockProperty(string $property): bool { return in_array($property, Search::VALID_BLOCK_PROPERTIES); } /** * Perform a search in all data arrays and return an array with `FileResults`. * * @see FileResults * @return array an array of `FileResults` */ public function searchPerFile(): array { $resultsPerFile = array(); if (!trim($this->searchValue)) { return $resultsPerFile; } $sharedData = $this->Shared?->data ?? array(); if ($fieldResultsArray = $this->searchData($sharedData)) { $resultsPerFile[] = new FileResults($fieldResultsArray, null, null); } foreach ($this->pages as $Page) { if ($fieldResultsArray = $this->searchData($Page->data)) { $resultsPerFile[] = new FileResults($fieldResultsArray, $Page->path, $Page->origUrl); } } return $resultsPerFile; } /** * Append an item to a given array only in case it is an results. * * @param array $resultsArray * @param FieldResults|null $results * @return array the results array */ private function appendFieldResults(array $resultsArray, ?FieldResults $results): array { if (is_a($results, FieldResults::class)) { $resultsArray[] = $results; } return $resultsArray; } /** * Merge an array of `FieldResults` into a single results. * * @param string $field * @param array $results * @return FieldResults|null a field results results */ private function mergeFieldResults(string $field, array $results): ?FieldResults { $matches = array(); $contextArray = array(); foreach ($results as $result) { $matches = array_merge($matches, $result->matches); $contextArray[] = $result->context; } if (!empty($matches)) { return new FieldResults( $field, $matches, join(' ... ', $contextArray) ); } return null; } /** * Perform a search in a block field recursively and return a * `FieldResults` results for a given search value. * * @param string $field * @param array|null $blocks * @return FieldResults|null a field results results */ private function searchBlocksRecursively(string $field, ?array $blocks): ?FieldResults { if (is_null($blocks)) { return null; } $results = array(); foreach ($blocks as $block) { if ($block->type == 'section') { $results = $this->appendFieldResults( $results, $this->searchBlocksRecursively($field, $block->data->content->blocks) ); } else { foreach ($block->data as $blockProperty => $value) { if (Search::isValidBlockProperty($blockProperty)) { if (is_array($value) || is_object($value)) { $results = $this->appendFieldResults($results, $this->searchDataRecursively($field, $value)); } if (is_string($value)) { $results = $this->appendFieldResults($results, $this->searchTextField($field, $value)); } } } } } return $this->mergeFieldResults($field, $results); } /** * Perform a search in a single data array and return an * array of `FieldResults`. * * @see FieldResults * @param array $data * @return array an array of `FieldResults` resultss */ private function searchData(array $data): array { $fieldResults = array(); foreach ($data as $field => $value) { if (str_starts_with($field, '+')) { /** @var object{blocks: mixed} $value */ try { $FieldResults = $this->searchBlocksRecursively($field, $value->blocks); } catch (Exception $error) { $FieldResults = false; } } else { /** @var ?string $value */ $FieldResults = $this->searchTextField($field, Value::asString($value)); } if (!empty($FieldResults)) { $fieldResults[] = $FieldResults; } } return $fieldResults; } /** * Search an array of values recursively. * * @param string $field * @param array|object $arrayOrObject * @return FieldResults|null a field results results */ private function searchDataRecursively(string $field, array|object $arrayOrObject): ?FieldResults { $results = array(); foreach ($arrayOrObject as $item) { if (is_array($item) || is_object($item)) { $results = $this->appendFieldResults($results, $this->searchDataRecursively($field, $item)); } if (is_string($item)) { $results = $this->appendFieldResults($results, $this->searchTextField($field, $item)); } } return $this->mergeFieldResults($field, $results); } /** * Perform a search in a single data field and return a * `FieldResults` results for a given search value. * * @param string $field * @param string $value * @return FieldResults|null the field results */ private function searchTextField(string $field, string $value): ?FieldResults { $ignoredKeys = array( Fields::HIDDEN, Fields::PRIVATE, Fields::TEMPLATE, Fields::TEMPLATE_LEGACY, Fields::THEME, Fields::URL, Fields::TITLE ); if (preg_match('/^(:|date|checkbox|tags|color)/', $field) || in_array($field, $ignoredKeys)) { return null; } $fieldMatches = array(); preg_match_all( '/(?P(?:^|\s).{0,50})(?P' . $this->searchValue . ')(?P.{0,50}(?:\s|$))/' . $this->regexFlags, $value, $matches, PREG_SET_ORDER ); if (!empty($matches[0])) { $parts = array(); foreach ($matches as $match) { $before = htmlspecialchars($match['before']); $marked = htmlspecialchars($match['match']); $after = htmlspecialchars($match['after']); $parts[] = preg_replace( '/\s+/', ' ', trim("$before$marked$after") ); } $context = join(' ... ', $parts); foreach ($matches as $match) { $fieldMatches[] = $match['match']; } return new FieldResults( $field, $fieldMatches, $context ); } return null; } } __halt_compiler();----SIGNATURE:----HSeTn7Zz0e38AbOdcC/M4Qlar0UujKbvgdXfO/JdO5JYdMhCW5GYFSvx4S/7ePaTZbnIw6xZIrBUlD1WeuyOEC6+jgnszKnBTeOLuFgaNpnt2mCI/bYW9vkKXvJNRrU9ziTF3hufUWkg3+06m7D6t5APD+lTe/11f6MBtnU71CBxhjW4F/witAy7k5/3Ht62x9wF41Pd1JEGQbj9RgxNtrlWzOXCA946x7tGYk8FOeLE/H2Bl11733pAEU4bRa9buYQ0msnyeZd7RKqlgsOSnLNsGYzBQ2yD9yrG6Aum9YmCtrooVKdCMxbjp8Iltm12Cgx6s6yYnYwW2nbHZ5DY5wuuPqgJHkPkKMU0mOsSGvQhbYGEVcQXzlYou7yVjUQiI889kmoIGh/bTF6gF7cdwxWjpWgB6ix4AzxyEJxAR9rrX65w1cy7xnjwtjKCWrQxU/2zOj9rfah8Mo590JH2bBb0PUU2IVNNUO+MiLqfrFdgCvPHBtteWCc1ono7oEWiERXEfeHiD00z7zmmWG9DCujJIGIcfKZrbcDleSCJHqxa3rsNonLPm7N0BaxtaYaJRSovk+FqiDuAtrsuoK5oXDq6Q0OnmY96ZS7Yss9mii1h2eaQ7Y+mJIu9CxP+RHKrFMLXhTphtbg7XAouz+mbDXxMgr02H/J3h6OX4jPBEuI=----ATTACHMENT:----MzUwOTI0MjE1MDQxMzkyIDg1ODk3MDU4NjQ3NjgwMjggODQ1Mjk4OTY2MDU3OTcwNQ==