diff --git a/Dockerfile b/Dockerfile index c44cc429..b42e431d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,14 @@ -FROM brettt89/silverstripe-web:7.1-platform +FROM brettt89/silverstripe-web:8.1-apache-bullseye LABEL maintainer="Marco Hermo" ENV DEBIAN_FRONTEND=noninteractive # Debian Jessie Archive sources -RUN echo "deb http://deb.debian.org/debian/ jessie main contrib non-free" > /etc/apt/sources.list && \ - echo "deb-src http://deb.debian.org/debian/ jessie main contrib non-free" >> /etc/apt/sources.list && \ - echo "deb http://security.debian.org/ jessie/updates main contrib non-free" >> /etc/apt/sources.list && \ - echo "deb-src http://security.debian.org/ jessie/updates main contrib non-free" >> /etc/apt/sources.list +RUN echo "deb http://deb.debian.org/debian/ bullseye main contrib non-free" > /etc/apt/sources.list && \ + echo "deb-src http://deb.debian.org/debian/ bullseye main contrib non-free" >> /etc/apt/sources.list && \ + echo "deb http://security.debian.org/ jessbullseyeie/updates main contrib non-free" >> /etc/apt/sources.list && \ + echo "deb-src http://security.debian.org/ bullseye/updates main contrib non-free" >> /etc/apt/sources.list RUN apt-get update \ && apt-get install -y --no-install-recommends \ diff --git a/_config/extensions.yml b/_config/extensions.yml index 55a81079..5c2bf9e5 100644 --- a/_config/extensions.yml +++ b/_config/extensions.yml @@ -1,6 +1,3 @@ SilverStripe\ORM\DataObject: extensions: - Firesphere\SolrSearch\Extensions\DataObjectExtension -SilverStripe\Forms\GridField\GridField: - extensions: - - Firesphere\SolrSearch\Extensions\GridFieldExtension diff --git a/_config/typemap.yml b/_config/typemap.yml deleted file mode 100644 index 7e2d7c4c..00000000 --- a/_config/typemap.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -Name: SolrTypemap ---- -Firesphere\SolrSearch\Helpers\Statics: - typemap: - "*": text - PrimaryKey: tint - HTMLVarchar: htmltext - "SilverStripe\\ORM\\FieldType\\DBHTMLVarchar": htmltext - "DBHTMLVarchar": htmltext - Varchar: text - "SilverStripe\\ORM\\FieldType\\DBVarchar": text - "DBVarchar": text - Enum: text - "SilverStripe\\ORM\\FieldType\\DBEnum": text - "DBEnum": text - Text: text - "SilverStripe\\ORM\\FieldType\\DBText": text - "DBText": text - HTMLText: htmltext - "SilverStripe\\ORM\\FieldType\\DBHTMLText": htmltext - "DBHTMLText": htmltext - Boolean: boolean - "SilverStripe\\ORM\\FieldType\\DBBoolean": boolean - "DBBoolean": boolean - Date: tdate - "SilverStripe\\ORM\\FieldType\\DBDate": tdate - "DBDate": tdate - Time: tdate - "SilverStripe\\ORM\\FieldType\\DBTime": tdate - "DBTime": tdate - Datetime: tdate - "SilverStripe\\ORM\\FieldType\\DBDatetime": tdate - "DBDatetime": tdate - ForeignKey: tint - "SilverStripe\\ORM\\FieldType\\DBForeignKey": tint - "DBForeignKey": tint - Int: tint - "SilverStripe\\ORM\\FieldType\\DBInt": tint - "DBInt": tint - BigInt: tint - "SilverStripe\\ORM\\FieldType\\DBBigInt": tint - "DBBigInt": tint - Float: tfloat - "SilverStripe\\ORM\\FieldType\\DBFloat": tfloat - "DBFloat": tfloat - Decimal: tfloat - "SilverStripe\\ORM\\FieldType\\DBDecimal": tfloat - "DBDecimal": tfloat - Double: tdouble - "SilverStripe\\ORM\\FieldType\\DBDouble": tdouble - "DBDouble": tdouble - Money: tfloat - "SilverStripe\\ORM\\FieldType\\DBMoney": tfloat - "DBMoney": tfloat - Currency: tfloat - "SilverStripe\\ORM\\FieldType\\DBCurrency": tfloat - "DBCurrency": tfloat - ClassName: text - "SilverStripe\\ORM\\FieldType\\DBClassName": text - DBClassName: text - "SilverStripe\\ORM\\FieldType\\HTMLFragment": htmltext - HTMLFragment: htmltext diff --git a/composer.json b/composer.json index 8df5ccd5..1490be69 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "guzzlehttp/guzzle": "^6.3|^7", "http-interop/http-factory-guzzle": "^1", "symfony/event-dispatcher": "^5.4", - "php-http/guzzle7-adapter": "^1.0.0" + "php-http/guzzle7-adapter": "^1.0.0", + "firesphere/searchbackend": "dev-main" }, "require-dev": { "phpunit/phpunit": "^5.7", diff --git a/src/Admins/SearchAdmin.php b/src/Admins/SearchAdmin.php deleted file mode 100644 index ac95779a..00000000 --- a/src/Admins/SearchAdmin.php +++ /dev/null @@ -1,60 +0,0 @@ -ID ) ); - $solrLogger = new SolrLogger(); - $solrLogger->saveSolrLog('Index'); $logger->error($error->getMessage()); } diff --git a/src/Extensions/GridFieldExtension.php b/src/Extensions/GridFieldExtension.php deleted file mode 100644 index 9c0efd24..00000000 --- a/src/Extensions/GridFieldExtension.php +++ /dev/null @@ -1,42 +0,0 @@ -getExtraClass(); - } - } -} diff --git a/src/Factories/DocumentFactory.php b/src/Factories/DocumentFactory.php index 7f34320b..5226f850 100644 --- a/src/Factories/DocumentFactory.php +++ b/src/Factories/DocumentFactory.php @@ -10,14 +10,16 @@ namespace Firesphere\SolrSearch\Factories; use Exception; +use Firesphere\SearchBackend\Factories\DocumentCoreFactory; +use Firesphere\SearchBackend\Helpers\DataResolver; +use Firesphere\SearchBackend\Helpers\Statics; +use Firesphere\SearchBackend\Queries\BaseQuery; +use Firesphere\SearchBackend\Traits\LoggerTrait; use Firesphere\SolrSearch\Extensions\DataObjectExtension; -use Firesphere\SolrSearch\Helpers\DataResolver; -use Firesphere\SolrSearch\Helpers\FieldResolver; -use Firesphere\SolrSearch\Helpers\Statics; use Firesphere\SolrSearch\Indexes\BaseIndex; use Firesphere\SolrSearch\Services\SolrCoreService; use Firesphere\SolrSearch\Traits\DocumentFactoryTrait; -use Firesphere\SolrSearch\Traits\LoggerTrait; +use Psr\Container\NotFoundExceptionInterface; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Extensible; @@ -34,12 +36,10 @@ * * @package Firesphere\Solr\Search */ -class DocumentFactory +class DocumentFactory extends DocumentCoreFactory { use Configurable; use Extensible; - use DocumentFactoryTrait; - use LoggerTrait; /** * @var array Numeral types in Solr @@ -49,18 +49,6 @@ class DocumentFactory 'tfloat', 'tdouble', ]; - /** - * @var bool Debug this build - */ - protected $debug = false; - - /** - * DocumentFactory constructor, sets up the field resolver - */ - public function __construct() - { - $this->fieldResolver = Injector::inst()->get(FieldResolver::class); - } /** * Note, it can only take one type of class at a time! @@ -72,7 +60,7 @@ public function __construct() * @return array Documents to be pushed * @throws Exception */ - public function buildItems($fields, $index, $update): array + public function buildItems($fields, $index, $update = null): array { $this->getFieldResolver()->setIndex($index); $boostFields = $index->getBoostedFields(); @@ -100,30 +88,13 @@ public function buildItems($fields, $index, $update): array return $docs; } - /** - * Show the message about what is being indexed - * - * @param BaseIndex $index - */ - protected function indexGroupMessage(BaseIndex $index): void - { - $debugString = sprintf( - 'Indexing %s on %s (%s items)%s', - $this->getClass(), - $index->getIndexName(), - $this->getItems()->count(), - PHP_EOL - ); - $this->getLogger()->info($debugString); - } - /** * Add fields that should always be included * * @param Document $doc Solr Document * @param DataObject|DataObjectExtension $item Item to get the data from */ - protected function addDefaultFields(Document $doc, DataObject $item) + protected function addDefaultFields($doc, $item) { $doc->setKey(SolrCoreService::ID_FIELD, $item->ClassName . '-' . $item->ID); $doc->addField(SolrCoreService::CLASS_ID_FIELD, $item->ID); @@ -184,59 +155,6 @@ protected function addField($doc, $object, $options): void } } - /** - * Determine if the given object is one of the given type - * - * @param string|DataObject $class Class to compare - * @param array|string $base Class or list of base classes - * @return bool - */ - protected function classIs($class, $base): bool - { - $base = is_array($base) ? $base : [$base]; - - foreach ($base as $nextBase) { - if ($this->classEquals($class, $nextBase)) { - return true; - } - } - - return false; - } - - /** - * Check if a base class is an instance of the expected base group - * - * @param string|DataObject $class Class to compare - * @param string $base Base class - * @return bool - */ - protected function classEquals($class, $base): bool - { - return $class === $base || ($class instanceof $base); - } - - /** - * Use the DataResolver to find the value(s) for a field. - * Returns an array of values, and if it's multiple, it becomes a long array - * - * @param DataObject $object Object to resolve - * @param array $options Customised options - * @return array - */ - protected function getValuesForField($object, $options): array - { - try { - $valuesForField = [DataResolver::identify($object, $options['fullfield'])]; - } catch (Exception $error) { - // @codeCoverageIgnoreStart - $valuesForField = []; - // @codeCoverageIgnoreEnd - } - - return $valuesForField; - } - /** * Push field to a document * diff --git a/src/Factories/QueryComponentFactory.php b/src/Factories/QueryComponentFactory.php index ad38897a..b3b71d49 100644 --- a/src/Factories/QueryComponentFactory.php +++ b/src/Factories/QueryComponentFactory.php @@ -10,7 +10,7 @@ namespace Firesphere\SolrSearch\Factories; use Firesphere\SolrSearch\Indexes\BaseIndex; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\SolrQuery; use Firesphere\SolrSearch\Services\SolrCoreService; use Firesphere\SolrSearch\Traits\QueryComponentBoostTrait; use Firesphere\SolrSearch\Traits\QueryComponentFacetTrait; @@ -36,7 +36,7 @@ class QueryComponentFactory * * @var array */ - const DEFAULT_FIELDS = [ + public const DEFAULT_FIELDS = [ SolrCoreService::ID_FIELD, SolrCoreService::CLASS_ID_FIELD, SolrCoreService::CLASSNAME, @@ -57,7 +57,7 @@ class QueryComponentFactory 'Spellcheck', ]; /** - * @var BaseQuery BaseQuery that needs to be executed + * @var SolrQuery SolrQuery that needs to be executed */ protected $query; /** @@ -107,9 +107,9 @@ public function buildQuery(): Query /** * Get the base query * - * @return BaseQuery + * @return SolrQuery */ - public function getQuery(): BaseQuery + public function getQuery(): SolrQuery { return $this->query; } @@ -117,10 +117,10 @@ public function getQuery(): BaseQuery /** * Set the base query * - * @param BaseQuery $query + * @param SolrQuery $query * @return self */ - public function setQuery(BaseQuery $query): self + public function setQuery(SolrQuery $query): self { $this->query = $query; diff --git a/src/Factories/SchemaFactory.php b/src/Factories/SchemaFactory.php index e3ba1e58..c80b616a 100644 --- a/src/Factories/SchemaFactory.php +++ b/src/Factories/SchemaFactory.php @@ -10,8 +10,8 @@ namespace Firesphere\SolrSearch\Factories; use Exception; -use Firesphere\SolrSearch\Helpers\FieldResolver; -use Firesphere\SolrSearch\Helpers\Statics; +use Firesphere\SearchBackend\Helpers\FieldResolver; +use Firesphere\SearchBackend\Helpers\Statics; use Firesphere\SolrSearch\Services\SolrCoreService; use Firesphere\SolrSearch\Traits\GetSetSchemaFactoryTrait; use SilverStripe\Control\Director; @@ -143,29 +143,6 @@ protected function getStoreFields(): array return $storeFields; } - /** - * Get the fields that should be copied - * - * @return ArrayList - */ - public function getCopyFields() - { - $fields = $this->index->getCopyFields(); - - $return = ArrayList::create(); - foreach ($fields as $field => $copyFields) { - $item = [ - 'Field' => $field, - ]; - - $return->push($item); - } - - $this->extend('onBeforeCopyFields', $return); - - return $return; - } - /** * Get the definition of a copy field for determining what to load in to Solr * @@ -192,6 +169,29 @@ public function getCopyFieldDefinitions() return $return; } + /** + * Get the fields that should be copied + * + * @return ArrayList + */ + public function getCopyFields() + { + $fields = $this->index->getCopyFields(); + + $return = ArrayList::create(); + foreach ($fields as $field => $copyFields) { + $item = [ + 'Field' => $field, + ]; + + $return->push($item); + } + + $this->extend('onBeforeCopyFields', $return); + + return $return; + } + /** * Get the definitions of a filter field to load in to Solr. * diff --git a/src/Forms/SearchForm.php b/src/Forms/SearchForm.php index 4200e480..9a0a5d0a 100644 --- a/src/Forms/SearchForm.php +++ b/src/Forms/SearchForm.php @@ -33,11 +33,12 @@ class SearchForm extends Form */ public function __construct( RequestHandler $controller = null, - $name = self::DEFAULT_NAME, - FieldList $fields = null, - FieldList $actions = null, - Validator $validator = null - ) { + $name = self::DEFAULT_NAME, + FieldList $fields = null, + FieldList $actions = null, + Validator $validator = null + ) + { parent::__construct($controller, $name, $fields, $actions, $validator); $this->setFormMethod('GET'); diff --git a/src/Helpers/DataResolver.php b/src/Helpers/DataResolver.php deleted file mode 100644 index 509729aa..00000000 --- a/src/Helpers/DataResolver.php +++ /dev/null @@ -1,106 +0,0 @@ - 'DataObject', - ArrayData::class => 'ArrayData', - SS_List::class => 'List', - DBField::class => 'Field', - ]; - - /** - * DataResolver constructor. - * - * @param DataObject|ArrayList|SS_List|DBField $component - * @param array|string $columns - */ - public function __construct($component, $columns = []) - { - if (!is_array($columns)) { - $columns = str_replace('.', '_', $columns); - $columns = array_filter(explode('_', $columns)); - } - $this->columns = $columns; - $this->component = $component; - $this->columnName = $this->columns ? array_shift($this->columns) : null; - $this->shortName = ClassInfo::shortName($component); - } - - /** - * Identify the given object's columns - * - * @param DataObject|ArrayData|SS_List|DBField $obj - * @param array|string $columns - * - * @return mixed - * @throws LogicException - */ - public static function identify($obj, $columns = []) - { - /** @var {@link self::$objTypes} $type */ - foreach (self::$objTypes as $type => $method) { - if ($obj instanceof $type) { - $method = 'resolve' . $method; - - $self = new self($obj, $columns); - $result = $self->{$method}(); - gc_collect_cycles(); - - return $result; - } - } - - throw new LogicException(sprintf('Class: %s is not supported.', ClassInfo::shortName($obj))); - } - - /** - * An error occured, so log it - * - * @param DataObject|ArrayData|SS_List $component - * @param array $columns - * - * @return void - * @throws LogicException - */ - protected function cannotIdentifyException($component, $columns = []): void - { - throw new LogicException( - sprintf( - 'Cannot identify, "%s" from class "%s"', - implode('.', $columns), - ClassInfo::shortName($component) - ) - ); - } -} diff --git a/src/Helpers/FieldResolver.php b/src/Helpers/FieldResolver.php deleted file mode 100644 index 37e6ba61..00000000 --- a/src/Helpers/FieldResolver.php +++ /dev/null @@ -1,447 +0,0 @@ -getBuildSources(); - - $found = []; - - if (strpos($field, '.') !== false) { - $lookups = explode('.', $field); - $field = array_pop($lookups); - - foreach ($lookups as $lookup) { - $buildSources = $this->getNext($buildSources, $lookup); - } - } - - $found = $this->getFieldOptions($field, $buildSources, $fullfield, $found); - - return $found; - } - - /** - * Get the sources to build in to a Solr field - * - * @return array - */ - protected function getBuildSources(): array - { - $sources = $this->index->getClasses(); - $buildSources = []; - - $schemaHelper = DataObject::getSchema(); - foreach ($sources as $source) { - $buildSources[$source]['base'] = $schemaHelper->baseDataClass($source); - } - - return $buildSources; - } - - /** - * Get the next lookup item from the buildSources - * - * @param array $buildSources - * @param $lookup - * @return array - * @throws Exception - */ - protected function getNext(array $buildSources, $lookup): array - { - $next = []; - - // @todo remove repetition - foreach ($buildSources as $source => $baseOptions) { - $next = $this->resolveRelation($source, $lookup, $next, $baseOptions); - } - - $buildSources = $next; - - return $buildSources; - } - - /** - * Resolve relations if possible - * - * @param string $source - * @param $lookup - * @param array $next - * @param array $options - * @return array - * @throws ReflectionException - * @throws Exception - */ - protected function resolveRelation($source, $lookup, array $next, array &$options): array - { - $source = $this->getSourceName($source); - - foreach (self::getHierarchy($source) as $dataClass) { - [$options, $next] = $this->resolveNext($options, $lookup, $dataClass, $source, $next); - } - - return $next; - } - - /** - * This is used to clean the source name from suffix - * suffixes are needed to support multiple relations with the same name on different page types - * - * @param string $source - * @return string - */ - private function getSourceName($source) - { - $explodedSource = explode('|xkcd|', $source); - - return $explodedSource[0]; - } - - /** - * Get all the classes involved in a DataObject hierarchy - both super and optionally subclasses - * - * @static - * @param string $class - The class to query - * @param bool $dataOnly - True to only return classes that have tables - * @return array - Integer keys, String values as classes sorted by depth (most super first) - * @throws ReflectionException - */ - public static function getHierarchy($class, $dataOnly = false): array - { - // Generate the unique key for this class and it's call type - // It's a short-lived cache key for the duration of the request - $cacheKey = sprintf('%s-sc-%s', $class, $dataOnly ? 'do' : 'al'); - - if (!isset(self::$hierarchy[$cacheKey])) { - $classes = self::getHierarchyClasses($class); - - if ($dataOnly) { - $classes = array_filter($classes, static function ($class) { - return DataObject::getSchema()->classHasTable($class); - }); - } - - self::$hierarchy[$cacheKey] = array_values($classes); - - return array_values($classes); - } - - return self::$hierarchy[$cacheKey]; - } - - /** - * Get the hierarchy for a class - * - * @param $class - * @return array - * @throws ReflectionException - * @todo clean this up to be more compatible with PHP features - */ - protected static function getHierarchyClasses($class): array - { - if (!isset(self::$ancestry[$class])) { - self::$ancestry[$class] = array_values(ClassInfo::ancestry($class)); - } - $ancestry = self::$ancestry[$class]; - - $classes = self::getSubClasses($class, $ancestry); - - $classes = array_unique($classes); - $classes = self::excludeDataObjectIDx($classes); - - return $classes; - } - - /** - * Get the subclasses for the given class - * Should be replaced with PHP native methods - * - * @param $class - * @param array $classes - * @return array - * @throws ReflectionException - */ - private static function getSubClasses($class, array $classes): array - { - $subClasses = ClassInfo::subclassesFor($class); - $classes = array_merge($classes, array_values($subClasses)); - - return $classes; - } - - /** - * Objects to exclude from the index - * - * @param array $classes - * @return array - */ - private static function excludeDataObjectIDx(array $classes): array - { - // Remove all classes below DataObject from the list - $idx = array_search(DataObject::class, $classes, true); - if ($idx !== false) { - array_splice($classes, 0, $idx + 1); - } - - return $classes; - } - - /** - * Relational data - * - * @param $lookup - * @param DataObjectSchema $schema - * @param $className - * @param array $options - * @return string|array|null - * @throws Exception - */ - protected function getRelationData($lookup, DataObjectSchema $schema, $className, array &$options) - { - if ($hasOne = $schema->hasOneComponent($className, $lookup)) { - return $hasOne; - } - $options['multi_valued'] = true; - if ($hasMany = $schema->hasManyComponent($className, $lookup)) { - return $hasMany; - } - if ($key = $schema->manyManyComponent($className, $lookup)) { - return $key['childClass']; - } - - return null; - } - - /** - * Create field options for the given index field - * - * @param $field - * @param array $sources - * @param string $fullfield - * @param array $found - * @return array - * @throws ReflectionException - */ - protected function getFieldOptions($field, array $sources, $fullfield, array $found): array - { - foreach ($sources as $class => $fieldOptions) { - $found = $this->findOrigin($field, $fullfield, $found, $class, $fieldOptions); - } - - return $found; - } - - /** - * Find the origin of a field - * - * @param $field - * @param $fullfield - * @param array $found - * @param $class - * @param $fieldOptions - * @return array - * @throws ReflectionException - */ - protected function findOrigin($field, $fullfield, array $found, $class, $fieldOptions): array - { - $class = $this->getSourceName($class); - $dataclasses = self::getHierarchy($class); - - $fields = DataObject::getSchema()->databaseFields($class); - while ($dataclass = array_shift($dataclasses)) { - $type = $this->getType($fields, $field, $dataclass); - - if ($type) { - // Don't search through child classes of a class we matched on. - $dataclasses = array_diff($dataclasses, array_values(ClassInfo::subclassesFor($dataclass))); - $found = $this->getOriginForType($field, $fullfield, $found, $fieldOptions, $dataclass, $type); - } - } - - return $found; - } - - /** - * Get the type of this field - * - * @param array $fields - * @param string $field - * @param string $dataclass - * @return string - */ - protected function getType($fields, $field, $dataclass): string - { - if (!empty($fields[$field])) { - return $fields[$field]; - } - - /** @var DataObject $singleton */ - $singleton = singleton($dataclass); - - $type = $singleton->castingClass($field); - - if (!$type) { - $type = 'String'; - } - - return $type; - } - - /** - * Extraction to find the origin for a specific type field - * - * @param $field - * @param $fullfield - * @param array $found - * @param $fieldOptions - * @param $dataclass - * @param string $type - * @return array - */ - protected function getOriginForType( - $field, - $fullfield, - array $found, - $fieldOptions, - $dataclass, - string $type - ): array { - // Trim arguments off the type string - if (preg_match('/^(\w+)\(/', $type, $match)) { - $type = $match[1]; - } - - $found = $this->getFoundOriginData($field, $fullfield, $fieldOptions, $dataclass, $type, $found); - - return $found; - } - - /** - * FoundOriginData is a helper to make sure the options are properly set. - * - * @param string $field - * @param string $fullField - * @param array $fieldOptions - * @param string $dataclass - * @param string $type - * @param array $found - * @return array - */ - private function getFoundOriginData( - $field, - $fullField, - $fieldOptions, - $dataclass, - $type, - $found - ): array { - // Get the origin - $origin = $fieldOptions['origin'] ?? $dataclass; - - $found["{$origin}_{$fullField}"] = [ - 'name' => "{$origin}_{$fullField}", - 'field' => $field, - 'fullfield' => $fullField, - 'origin' => $origin, - 'class' => $dataclass, - 'type' => $type, - 'multi_valued' => isset($fieldOptions['multi_valued']) ? true : false, - ]; - - return $found; - } - - /** - * Resolve the next item in line to be indexed - * - * @param array $options - * @param $lookup - * @param $dataClass - * @param string $source - * @param array $next - * @return array[] - * @throws Exception - */ - protected function resolveNext(array $options, $lookup, $dataClass, string $source, array $next): array - { - $schema = DataObject::getSchema(); - $options['multi_valued'] = false; - - $class = $this->getRelationData($lookup, $schema, $dataClass, $options); - - if (is_string($class) && $class) { - if (!isset($options['origin'])) { - $options['origin'] = $source; - } - - // we add suffix here to prevent the relation to be overwritten by other instances - // all sources lookups must clean the source name before reading it via getSourceName() - $next[$class . '|xkcd|' . $dataClass] = $options; - } - - return [$options, $next]; - } -} diff --git a/src/Helpers/SolrLogger.php b/src/Helpers/SolrLogger.php deleted file mode 100644 index 39ef3eec..00000000 --- a/src/Helpers/SolrLogger.php +++ /dev/null @@ -1,204 +0,0 @@ -get('config'); - $hostConfig = array_shift($config['endpoint']); - $guzzleConfig = [ - 'base_uri' => $hostConfig['host'] . ':' . $hostConfig['port'], - ]; - if ($handler) { - $guzzleConfig['handler'] = $handler; - } - - if (isset($hostConfig['username']) && isset($hostConfig['password'])) { - $this->options = [ - 'auth' => [ - $hostConfig['username'], - $hostConfig['password'] - ] - ]; - } - - - $this->client = new Client($guzzleConfig); - } - - /** - * Log the given message and dump it out. - * Also boot the Log to get the latest errors from Solr - * - * @param string $type - * @param string $message - * @throws HTTPException - * @throws ValidationException - */ - public static function logMessage($type, $message): void - { - $solrLogger = new self(); - $solrLogger->saveSolrLog($type); - /** @var SolrLog $lastError */ - $lastError = SolrLog::get()->last(); - - $err = ($lastError === null) ? 'Unknown' : $lastError->getLastErrorLine(); - $errTime = ($lastError === null) ? 'Unknown' : $lastError->Timestamp; - $message .= sprintf('%sLast known Solr error:%s%s: %s', PHP_EOL, PHP_EOL, $errTime, $err); - /** @var LoggerInterface $logger */ - $logger = Injector::inst()->get(LoggerInterface::class); - $logger->alert($message); - if (Director::is_cli() || Controller::curr()->getRequest()->getVar('unittest')) { - Debug::dump($message); - } - } - - /** - * Save the latest Solr errors to the log - * - * @param string $type - * @throws HTTPException - * @throws ValidationException - */ - public function saveSolrLog($type = 'Query'): void - { - $options = array_merge($this->options, [ - 'query' => [ - 'since' => 0, - 'wt' => 'json', - ], - ]); - $response = $this->client->get('solr/admin/info/logging', $options); - - $arrayResponse = json_decode($response->getBody(), true); - - foreach ($arrayResponse['history']['docs'] as $error) { - $filter = [ - 'Timestamp' => $error['time'], - 'Index' => $error['core'] ?? 'x:Unknown', - 'Level' => $error['level'], - ]; - $this->findOrCreateLog($type, $filter, $error); - } - } - - /** - * Attempt to find, otherwise create, a log object - * - * @param $type - * @param array $filter - * @param $error - * @throws ValidationException - */ - private function findOrCreateLog($type, array $filter, $error): void - { - // Not covered in tests. It's only here to make sure the connection isn't closed by a child process - $conn = DB::is_active(); - // @codeCoverageIgnoreStart - if (!$conn) { - $config = DB::getConfig(); - DB::connect($config); - } - // @codeCoverageIgnoreEnd - if (!SolrLog::get()->filter($filter)->exists()) { - $logData = [ - 'Message' => $error['message'], - 'Type' => $type, - ]; - $log = array_merge($filter, $logData); - SolrLog::create($log)->write(); - if (Director::is_cli() || Controller::curr()->getRequest()->getVar('unittest')) { - /** @var LoggerInterface $logger */ - $logger = Injector::inst()->get(LoggerInterface::class); - $logger->error($error['message']); - } - } - } - - /** - * Return the Guzzle Client - * - * @return Client - */ - public function getClient(): Client - { - return $this->client; - } - - /** - * Set the Guzzle client - * - * @param Client $client - * @return SolrLogger - */ - public function setClient(Client $client): self - { - $this->client = $client; - - return $this; - } - - /** - * Get the options for Guzzle - * - * @return array - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * Set custom options for Guzzle - * - * @param array $options - */ - public function setOptions(array $options): void - { - $this->options = $options; - } -} diff --git a/src/Helpers/Statics.php b/src/Helpers/Statics.php deleted file mode 100644 index 61ff5569..00000000 --- a/src/Helpers/Statics.php +++ /dev/null @@ -1,38 +0,0 @@ -get('typemap'); - } -} diff --git a/src/Helpers/Synonyms.php b/src/Helpers/Synonyms.php deleted file mode 100644 index 96afebcd..00000000 --- a/src/Helpers/Synonyms.php +++ /dev/null @@ -1,65 +0,0 @@ - $synonym) { - // Make all synonym strings - // @todo remove duplicates - $synonym = implode(',', (array)$synonym); - $result[] = sprintf('%s,%s', $word, $synonym); - } - - return implode(PHP_EOL, $result) . PHP_EOL; - } - - /** - * Get the available synonyms as an array from config - * Defaulting to adding the UK to US spelling differences - * - * @param bool $defaults adds the UK to US spelling to the list if true - * @return array - */ - public static function getSynonyms($defaults = true) - { - // If we want the defaults, load it in to the array, otherwise return an empty array - $usuk = $defaults ? static::config()->get('usuk')['synonyms'] : []; - $synonyms = static::config()->get('synonyms') ?: []; - - return array_merge($usuk, $synonyms); - } -} diff --git a/src/Indexes/BaseIndex.php b/src/Indexes/BaseIndex.php index 5bcbdb3e..8456ac47 100644 --- a/src/Indexes/BaseIndex.php +++ b/src/Indexes/BaseIndex.php @@ -10,20 +10,18 @@ namespace Firesphere\SolrSearch\Indexes; use Exception; +use Firesphere\ElasticSearch\Traits\IndexTraits\BaseIndexTrait; +use Firesphere\SearchBackend\Indexes\CoreIndex; +use Firesphere\SearchBackend\Queries\BaseQuery; +use Firesphere\SearchBackend\States\SiteState; use Firesphere\SolrSearch\Factories\QueryComponentFactory; use Firesphere\SolrSearch\Factories\SchemaFactory; -use Firesphere\SolrSearch\Helpers\SolrLogger; -use Firesphere\SolrSearch\Helpers\Synonyms; use Firesphere\SolrSearch\Interfaces\ConfigStore; -use Firesphere\SolrSearch\Models\SearchSynonym; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\QueryBuilder; +use Firesphere\SolrSearch\Queries\SolrQuery; use Firesphere\SolrSearch\Results\SearchResult; use Firesphere\SolrSearch\Services\SolrCoreService; -use Firesphere\SolrSearch\States\SiteState; -use Firesphere\SolrSearch\Traits\BaseIndexTrait; use Firesphere\SolrSearch\Traits\GetterSetterTrait; -use Http\Discovery\HttpClientDiscovery; -use Http\Discovery\Psr17FactoryDiscovery; use LogicException; use ReflectionException; use SilverStripe\Control\Director; @@ -36,12 +34,9 @@ use SilverStripe\ORM\DataList; use SilverStripe\ORM\ValidationException; use SilverStripe\View\ArrayData; -use Solarium\Client as SolariumClient; -use Solarium\Core\Client\Adapter\Psr18Adapter; use Solarium\Exception\HttpException; use Solarium\QueryType\Select\Query\Query; use Solarium\QueryType\Select\Result\Result; -use Symfony\Component\EventDispatcher\EventDispatcher; /** * Base for creating a new Solr core. @@ -51,7 +46,7 @@ * * @package Firesphere\Solr\Search */ -abstract class BaseIndex +abstract class BaseIndex extends CoreIndex { use Extensible; use Configurable; @@ -59,22 +54,6 @@ abstract class BaseIndex use GetterSetterTrait; use BaseIndexTrait; - /** - * Field types that can be added - * Used in init to call build methods from configuration yml - * - * @array - */ - private static $fieldTypes = [ - 'FulltextFields', - 'SortFields', - 'FilterFields', - 'BoostedFields', - 'CopyFields', - 'DefaultField', - 'FacetFields', - 'StoredFields', - ]; /** * {@link SchemaFactory} * @@ -196,18 +175,18 @@ protected function initFromConfig($config): void /** * Default returns a SearchResult. It can return an ArrayData if FTS Compat is enabled * - * @param BaseQuery $query + * @param SolrQuery $query * @return SearchResult|ArrayData|mixed * @throws HTTPException * @throws ValidationException * @throws ReflectionException * @throws Exception */ - public function doSearch(BaseQuery $query) + public function doSearch(SolrQuery $query) { SiteState::alterQuery($query); // Build the actual query parameters - $this->clientQuery = $this->buildSolrQuery($query); + $this->clientQuery = QueryBuilder::buildQuery($query, $this); // Set the sorting $this->clientQuery->addSorts($query->getSort()); @@ -217,8 +196,6 @@ public function doSearch(BaseQuery $query) $result = $this->client->select($this->clientQuery); } catch (Exception $error) { // @codeCoverageIgnoreStart - $logger = new SolrLogger(); - $logger->saveSolrLog('Query'); throw $error; // @codeCoverageIgnoreEnd } @@ -232,6 +209,7 @@ public function doSearch(BaseQuery $query) $collation = $result->getSpellcheck(); $retryResults = $this->spellcheckRetry($query, $searchResult); $this->retry = false; + return $retryResults->setCollatedSpellcheck($collation); } @@ -241,47 +219,6 @@ public function doSearch(BaseQuery $query) return $searchResult; } - /** - * From the given BaseQuery, generate a Solarium ClientQuery object - * - * @param BaseQuery $query - * @return Query - */ - public function buildSolrQuery(BaseQuery $query): Query - { - $clientQuery = $this->client->createSelect(); - $factory = $this->buildFactory($query, $clientQuery); - - $clientQuery = $factory->buildQuery(); - $this->queryTerms = $factory->getQueryArray(); - - $queryData = implode(' ', $this->queryTerms); - $clientQuery->setQuery($queryData); - - return $clientQuery; - } - - /** - * Build a factory to use in the SolrQuery building. {@link static::buildSolrQuery()} - * - * @param BaseQuery $query - * @param Query $clientQuery - * @return QueryComponentFactory|mixed - */ - protected function buildFactory(BaseQuery $query, Query $clientQuery) - { - $factory = $this->queryFactory; - - $helper = $clientQuery->getHelper(); - - $factory->setQuery($query); - $factory->setClientQuery($clientQuery); - $factory->setHelper($helper); - $factory->setIndex($this); - - return $factory; - } - /** * Check if the query should be retried with spellchecking * Conditions are: @@ -326,6 +263,16 @@ protected function spellcheckRetry(BaseQuery $query, SearchResult $searchResult) return $this->doSearch($query); } + public function getQueryTerms(): array + { + return $this->queryTerms; + } + + public function setQueryTerms(array $queryTerms): void + { + $this->queryTerms = $queryTerms; + } + /** * Get all fields that are required for indexing in a unique way * @@ -411,16 +358,6 @@ public function getSynonyms($store = null, $defaults = true) return $synonyms; } - /** - * Get the final, generated terms - * - * @return array - */ - public function getQueryTerms(): array - { - return $this->queryTerms; - } - /** * Get the QueryComponentFactory. {@link QueryComponentFactory} * @@ -448,4 +385,25 @@ public function isRetry(): bool { return $this->retry; } + + /** + * Build a factory to use in the SolrQuery building. {@link static::buildSolrQuery()} + * + * @param SolrQuery $query + * @param Query $clientQuery + * @return QueryComponentFactory|mixed + */ + protected function buildFactory(BaseQuery $query, Query $clientQuery) + { + $factory = $this->queryFactory; + + $helper = $clientQuery->getHelper(); + + $factory->setQuery($query); + $factory->setClientQuery($clientQuery); + $factory->setHelper($helper); + $factory->setIndex($this); + + return $factory; + } } diff --git a/src/Interfaces/SiteStateInterface.php b/src/Interfaces/SiteStateInterface.php deleted file mode 100644 index 78cc9cf1..00000000 --- a/src/Interfaces/SiteStateInterface.php +++ /dev/null @@ -1,63 +0,0 @@ - 'Varchar(6)', - 'Class' => 'Varchar(512)', - 'IDs' => 'Varchar(255)', - ]; - /** - * @var array Summary fields in CMS - */ - private static $summary_fields = [ - 'Class', - 'Type', - 'IDs', - ]; - - /** - * Make the CMS fields readable - * - * @return FieldList - */ - public function getCMSFields() - { - $fields = parent::getCMSFields(); - $fields->removeByName(['Class', 'IDs']); - - $class = singleton($this->Class)->plural_name(); - - $IDs = json_decode($this->IDs, true); - - $fields->addFieldsToTab('Root.Main', [ - ReadonlyField::create('Class', 'Class', $class), - ReadonlyField::create('IDs', _t(self::class . '.DIRTYIDS', 'Dirty IDs'), $IDs), - ]); - - return $fields; - } - - /** - * Nope, can't delete these - * - * @param null|Member $member - * @return bool - */ - public function canDelete($member = null) - { - return false; - } - - /** - * Nope, can't edit these - * - * @param null|Member $member - * @return bool - */ - public function canEdit($member = null) - { - return false; - } - - /** - * Nope, can't create these - * - * @param null|Member $member - * @param array $context - * @return bool - */ - public function canCreate($member = null, $context = []) - { - return false; - } -} diff --git a/src/Models/SearchSynonym.php b/src/Models/SearchSynonym.php deleted file mode 100644 index a2f926f5..00000000 --- a/src/Models/SearchSynonym.php +++ /dev/null @@ -1,84 +0,0 @@ - 'Varchar(255)', - 'Synonym' => 'Text' - ]; - - /** - * @var array Summary fields - */ - private static $summary_fields = [ - 'Keyword', - 'Synonym' - ]; - - /** - * Get the required CMS Fields for this synonym - * - * @return FieldList - */ - public function getCMSFields() - { - $fields = parent::getCMSFields(); - - $fields->dataFieldByName('Synonym')->setDescription( - _t( - __CLASS__ . '.SYNONYM', - 'Create synonyms for a given keyword, add as many synonyms comma separated.' - ) - ); - - return $fields; - } - - /** - * Combine this synonym in to a string for the Solr synonyms.txt file - * - * @return string - */ - public function getCombinedSynonym() - { - return sprintf("\n%s,%s", $this->Keyword, $this->Synonym); - } -} diff --git a/src/Models/SolrLog.php b/src/Models/SolrLog.php deleted file mode 100644 index 6c413dab..00000000 --- a/src/Models/SolrLog.php +++ /dev/null @@ -1,195 +0,0 @@ - 'alert alert-danger', - 'WARN' => 'alert alert-warning', - 'INFO' => 'alert alert-info', - ]; - /** - * @var string Database table name - */ - private static $table_name = 'Solr_SolrLog'; - /** - * @var array Database columns - */ - private static $db = [ - 'Timestamp' => 'Datetime', - 'Index' => 'Varchar(255)', - 'Type' => 'Enum("Config,Index,Query")', - 'Level' => 'Varchar(10)', - 'Message' => 'Text', - ]; - /** - * @var array Summary fields - */ - private static $summary_fields = [ - 'Timestamp', - 'Index', - 'Type', - 'Level', - ]; - /** - * @var array Searchable fields - */ - private static $searchable_fields = [ - 'Created', - 'Timestamp', - 'Index', - 'Type', - 'Level', - ]; - /** - * @var array Timestamp is indexed - */ - private static $indexes = [ - 'Timestamp' => true, - ]; - /** - * @var string Default sort - */ - private static $default_sort = 'Timestamp DESC'; - - /** - * Convert the Timestamp to a DBDatetime for compatibility - */ - public function onBeforeWrite() - { - parent::onBeforeWrite(); - - $this->Timestamp = DBDatetime::create()->setValue(strtotime($this->Timestamp)); - } - - /** - * Return the first line of this log item error - * - * @return string - */ - public function getLastErrorLine() - { - $lines = explode(PHP_EOL, $this->Message); - - return $lines[0]; - } - - /** - * Not creatable by users - * - * @param null|Member $member - * @param array $context - * @return bool|mixed - */ - public function canCreate($member = null, $context = []) - { - return false; - } - - /** - * Not editable by users - * - * @param null|Member $member - * @return bool|mixed - */ - public function canEdit($member = null) - { - return false; - } - - /** - * Member has view access? - * - * @param null|Member $member - * @return bool|mixed - */ - public function canView($member = null) - { - return parent::canView($member); - } - - /** - * Only deleteable by admins or when in dev mode to clean up - * - * @param null|Member $member - * @return bool|mixed - */ - public function canDelete($member = null) - { - if ($member) { - return $member->inGroup('administrators') || Director::isDev(); - } - - return parent::canDelete($member) || Director::isDev(); - } - - /** - * Get the extra classes to colour the gridfield rows - * - * @return mixed|string - */ - public function getExtraClass() - { - $classMap = static::$row_color; - - return $classMap[$this->Level] ?? 'alert alert-info'; - } - - - /** - * Return a map of permission codes to add to the dropdown shown in the Security section of the CMS. - * array( - * 'VIEW_SITE' => 'View the site', - * ); - * - * @return array - */ - public function providePermissions() - { - return [ - 'DELETE_LOG' => [ - 'name' => _t(self::class . '.PERMISSION_DELETE_DESCRIPTION', 'Delete Solr logs'), - 'category' => _t('Permissions.LOGS_CATEGORIES', 'Solr logs permissions'), - 'help' => _t( - self::class . '.PERMISSION_DELETE_HELP', - 'Permission required to delete existing Solr logs.' - ), - ], - 'VIEW_LOG' => [ - 'name' => _t(self::class . '.PERMISSION_VIEW_DESCRIPTION', 'View Solr logs'), - 'category' => _t('Permissions.LOGS_CATEGORIES', 'Solr logs permissions'), - 'help' => _t( - self::class . '.PERMISSION_VIEW_HELP', - 'Permission required to view existing Solr logs.' - ), - ], - ]; - } -} diff --git a/src/Queries/QueryBuilder.php b/src/Queries/QueryBuilder.php new file mode 100644 index 00000000..990dfba5 --- /dev/null +++ b/src/Queries/QueryBuilder.php @@ -0,0 +1,29 @@ +client->createSelect(); + $factory = $index->buildFactory($query, $clientQuery); + + $clientQuery = $factory->buildQuery(); + $index->setQueryTerms($factory->getQueryArray()); + + $queryData = implode(' ', $index->getQueryTerms()); + $clientQuery->setQuery($queryData); + + return $clientQuery; + } +} diff --git a/src/Queries/BaseQuery.php b/src/Queries/SolrQuery.php similarity index 98% rename from src/Queries/BaseQuery.php rename to src/Queries/SolrQuery.php index 205774c0..26975ae5 100644 --- a/src/Queries/BaseQuery.php +++ b/src/Queries/SolrQuery.php @@ -9,6 +9,7 @@ namespace Firesphere\SolrSearch\Queries; +use Firesphere\SearchBackend\Queries\BaseQuery; use Firesphere\SolrSearch\Traits\BaseQueryTrait; use Firesphere\SolrSearch\Traits\GetterSetterTrait; use SilverStripe\Core\Injector\Injectable; @@ -20,7 +21,7 @@ * * @package Firesphere\Solr\Search */ -class BaseQuery +class SolrQuery extends BaseQuery { use GetterSetterTrait; use BaseQueryTrait; diff --git a/src/Results/SearchResult.php b/src/Results/SearchResult.php index 854591ea..68d0cf91 100644 --- a/src/Results/SearchResult.php +++ b/src/Results/SearchResult.php @@ -9,11 +9,13 @@ namespace Firesphere\SolrSearch\Results; +use Firesphere\SearchBackend\Interfaces\SearchResultInterface; +use Firesphere\SearchBackend\Queries\BaseQuery; +use Firesphere\SearchBackend\Traits\SearchResultGetTrait; +use Firesphere\SearchBackend\Traits\SearchResultSetTrait; use Firesphere\SolrSearch\Indexes\BaseIndex; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\SolrQuery; use Firesphere\SolrSearch\Services\SolrCoreService; -use Firesphere\SolrSearch\Traits\SearchResultGetTrait; -use Firesphere\SolrSearch\Traits\SearchResultSetTrait; use SilverStripe\Control\Controller; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; @@ -22,7 +24,7 @@ use SilverStripe\ORM\PaginatedList; use SilverStripe\View\ArrayData; use SilverStripe\View\ViewableData; -use Solarium\Component\Result\Facet\Field; +use Solarium\Component\Result\Analytics\Facet; use Solarium\Component\Result\FacetSet; use Solarium\Component\Result\Spellcheck\Collation; use Solarium\Component\Result\Spellcheck\Result as SpellcheckResult; @@ -38,7 +40,7 @@ * * @package Firesphere\Solr\Search */ -class SearchResult extends ViewableData +class SearchResult extends ViewableData implements SearchResultInterface { use SearchResultGetTrait; use SearchResultSetTrait; @@ -62,7 +64,7 @@ class SearchResult extends ViewableData * See Solarium docs for this. * * @param Result $result - * @param BaseQuery $query + * @param SolrQuery $query * @param BaseIndex $index */ public function __construct(Result $result, BaseQuery $query, BaseIndex $index) @@ -94,24 +96,40 @@ protected function setFacets($facets): self } /** - * Build the given list of key-value pairs in to a SilverStripe useable array + * Set the collated spellcheck string * - * @param FacetSet|null $facets - * @return ArrayData + * @param mixed $collatedSpellcheck + * @return $this */ - protected function buildFacets($facets): ArrayData + public function setCollatedSpellcheck($collatedSpellcheck): self { - $facetArray = []; - if ($facets) { - $facetTypes = $this->index->getFacetFields(); - // Loop all available facet fields by type - foreach ($facetTypes as $class => $options) { - $facetArray = $this->createFacet($facets, $options, $class, $facetArray); + /** @var Collation $collated */ + if (!$this->index->isRetry() && $collatedSpellcheck && ($collated = $collatedSpellcheck->getCollations())) { + $this->collatedSpellcheck = $collated[0]->getQuery(); + } + + return $this; + } + + /** + * Set the spellcheck list as an ArrayList + * + * @param SpellcheckResult|null $spellcheck + * @return SearchResult + */ + public function setSpellcheck($spellcheck): self + { + $spellcheckList = []; + + if ($spellcheck && ($suggestions = $spellcheck->getSuggestion(0))) { + foreach ($suggestions->getWords() as $suggestion) { + $spellcheckList[] = ArrayData::create($suggestion); } } - // Return an ArrayList of the results - return ArrayData::create($facetArray); + $this->spellcheck = ArrayList::create($spellcheckList); + + return $this; } /** @@ -123,10 +141,10 @@ protected function buildFacets($facets): ArrayData * @param array $facetArray * @return array */ - protected function createFacet($facets, $options, $class, array $facetArray): array + public function createFacet($facets, $options, $class, array $facetArray): array { // Get the facets by its title - /** @var Field $typeFacets */ + /** @var Facet $typeFacets */ $typeFacets = $facets->getFacet('facet-' . $options['Title']); $values = $typeFacets->getValues(); $results = ArrayList::create(); @@ -159,43 +177,6 @@ protected function getClassFacets($class, array $values, &$results): void $results = $results->sort(['FacetCount' => 'DESC', 'Title' => 'ASC',]); } - /** - * Set the collated spellcheck string - * - * @param mixed $collatedSpellcheck - * @return $this - */ - public function setCollatedSpellcheck($collatedSpellcheck): self - { - /** @var Collation $collated */ - if (!$this->index->isRetry() && $collatedSpellcheck && ($collated = $collatedSpellcheck->getCollations())) { - $this->collatedSpellcheck = $collated[0]->getQuery(); - } - - return $this; - } - - /** - * Set the spellcheck list as an ArrayList - * - * @param SpellcheckResult|null $spellcheck - * @return SearchResult - */ - public function setSpellcheck($spellcheck): self - { - $spellcheckList = []; - - if ($spellcheck && ($suggestions = $spellcheck->getSuggestion(0))) { - foreach ($suggestions->getWords() as $suggestion) { - $spellcheckList[] = ArrayData::create($suggestion); - } - } - - $this->spellcheck = ArrayList::create($spellcheckList); - - return $this; - } - /** * Get the matches as a Paginated List * @@ -305,15 +286,14 @@ protected function createExcerpt(string $idField, $match, DataObject $item): voi /** * Get the highlight for a specific document * - * @param $docID + * @param int $docId * @return string */ - public function getHighlightByID($docID): string + public function getHighlightByID($docId): string { $highlights = []; - if ($this->highlight && $docID) { - $highlights = []; - foreach ($this->highlight->getResult($docID) as $field => $highlight) { + if ($this->highlight && $docId) { + foreach ($this->highlight->getResult($docId) as $field => $highlight) { $highlights[] = implode(' (...) ', $highlight); } } diff --git a/src/Services/SolrCoreService.php b/src/Services/SolrCoreService.php index 9af44d6b..ab2916b3 100644 --- a/src/Services/SolrCoreService.php +++ b/src/Services/SolrCoreService.php @@ -10,8 +10,9 @@ namespace Firesphere\SolrSearch\Services; use Exception; -use Firesphere\SolrSearch\Factories\DocumentFactory; -use Firesphere\SolrSearch\Helpers\FieldResolver; +use Firesphere\ElasticSearch\Factories\DocumentFactory; +use Firesphere\SearchBackend\Helpers\FieldResolver; +use Firesphere\SearchBackend\Services\BaseService; use Firesphere\SolrSearch\Indexes\BaseIndex; use Firesphere\SolrSearch\Traits\CoreAdminTrait; use Firesphere\SolrSearch\Traits\CoreServiceTrait; @@ -22,7 +23,6 @@ use LogicException; use ReflectionClass; use ReflectionException; -use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injector; @@ -44,7 +44,7 @@ * * @package Firesphere\Solr\Search */ -class SolrCoreService +class SolrCoreService extends BaseService { use Injectable; use Configurable; @@ -54,40 +54,31 @@ class SolrCoreService /** * Unique ID in Solr */ - const ID_FIELD = 'id'; + public const ID_FIELD = 'id'; /** * SilverStripe ID of the object */ - const CLASS_ID_FIELD = 'ObjectID'; + public const CLASS_ID_FIELD = 'ObjectID'; /** * Name of the field that can be used for queries */ - const CLASSNAME = 'ClassName'; + public const CLASSNAME = 'ClassName'; /** * Solr update types */ - const DELETE_TYPE_ALL = 'deleteall'; + public const DELETE_TYPE_ALL = 'deleteall'; /** * string */ - const DELETE_TYPE = 'delete'; + public const DELETE_TYPE = 'delete'; /** * string */ - const UPDATE_TYPE = 'update'; + public const UPDATE_TYPE = 'update'; /** * string */ - const CREATE_TYPE = 'create'; - - /** - * @var array Base indexes that exist - */ - protected $baseIndexes = []; - /** - * @var array Valid indexes out of the base indexes - */ - protected $validIndexes = []; + public const CREATE_TYPE = 'create'; /** * @var array Available config versions */ @@ -97,6 +88,14 @@ class SolrCoreService "5.0.0", "4.0.0", ]; + /** + * @var array Base indexes that exist + */ + protected $baseIndexes = []; + /** + * @var array Valid indexes out of the base indexes + */ + protected $validIndexes = []; /** * SolrCoreService constructor. @@ -113,44 +112,7 @@ public function __construct() $adapter = new Psr18Adapter($httpClient, $requestFactory, $streamFactory); $this->client = new Client($adapter, $eventDispatcher, $config); $this->admin = $this->client->createCoreAdmin(); - $this->baseIndexes = ClassInfo::subclassesFor(BaseIndex::class); - $this->filterIndexes(); - } - - /** - * Filter enabled indexes down to valid indexes that can be instantiated - * or are allowed from config - * - * @throws ReflectionException - */ - protected function filterIndexes(): void - { - $enabledIndexes = static::config()->get('indexes'); - $enabledIndexes = is_array($enabledIndexes) ? $enabledIndexes : $this->baseIndexes; - foreach ($this->baseIndexes as $subindex) { - // If the config of indexes is set, and the requested index isn't in it, skip addition - // Or, the index simply doesn't exist, also a valid option - if (!in_array($subindex, $enabledIndexes, true) || - !$this->checkReflection($subindex) - ) { - continue; - } - $this->validIndexes[] = $subindex; - } - } - - /** - * Check if the class is instantiable - * - * @param $subindex - * @return bool - * @throws ReflectionException - */ - protected function checkReflection($subindex): bool - { - $reflectionClass = new ReflectionClass($subindex); - - return $reflectionClass->isInstantiable(); + parent::__construct(BaseIndex::class); } /** @@ -316,8 +278,8 @@ protected function getFactory($items): DocumentFactory * If no valid version is found, throw an error * * @param HandlerStack|null $handler Used for testing the solr version - * @throws LogicException * @return int + * @throws LogicException */ public function getSolrVersion($handler = null): int { @@ -342,6 +304,7 @@ public function getSolrVersion($handler = null): int $compare = version_compare($version, $result['lucene']['solr-spec-version']); if ($compare !== -1) { list($v) = explode('.', $version); + return (int)$v; } } @@ -371,4 +334,40 @@ private function getSolrAuthentication($firstEndpoint): array return $clientOptions; } + + /** + * Filter enabled indexes down to valid indexes that can be instantiated + * or are allowed from config + * + * @throws ReflectionException + */ + protected function filterIndexes(): void + { + $enabledIndexes = static::config()->get('indexes'); + $enabledIndexes = is_array($enabledIndexes) ? $enabledIndexes : $this->baseIndexes; + foreach ($this->baseIndexes as $subindex) { + // If the config of indexes is set, and the requested index isn't in it, skip addition + // Or, the index simply doesn't exist, also a valid option + if (!in_array($subindex, $enabledIndexes, true) || + !$this->checkReflection($subindex) + ) { + continue; + } + $this->validIndexes[] = $subindex; + } + } + + /** + * Check if the class is instantiable + * + * @param $subindex + * @return bool + * @throws ReflectionException + */ + protected function checkReflection($subindex): bool + { + $reflectionClass = new ReflectionClass($subindex); + + return $reflectionClass->isInstantiable(); + } } diff --git a/src/States/SiteState.php b/src/States/SiteState.php deleted file mode 100644 index 96cb183f..00000000 --- a/src/States/SiteState.php +++ /dev/null @@ -1,289 +0,0 @@ - $instance) { - self::$defaultStates[$variant] = $instance->currentState(); - } - - return self::$defaultStates; - } - - /** - * Returns an array of variants. - * - * @static - * @param bool $force Force updating the variants - * @return array - An array of (string)$variantClassName => (Object)$variantInstance pairs - * @throws ReflectionException - */ - public static function variants($force = false): array - { - // Build up and cache a list of all search variants (subclasses of SearchVariant) - if (empty(self::$variants) || $force) { - $classes = ClassInfo::subclassesFor(static::class); - - foreach ($classes as $variantclass) { - self::isApplicable($variantclass); - } - } - - return self::$variants; - } - - /** - * Is this extension applied and instantiable, and should it be applied to - * the current state of the site - * - * @static - * @param $variantClass - * @return bool - * @throws ReflectionException - */ - public static function isApplicable($variantClass): bool - { - $ref = new ReflectionClass($variantClass); - if ($ref->isInstantiable()) { - /** @var SiteState $variant */ - $variant = singleton($variantClass); - if ($variant->appliesToEnvironment() && $variant->isEnabled()) { - self::$variants[$variantClass] = $variant; - - return true; - } - } - - return false; - } - - /** - * Does this state apply to the current object/environment settings - * - * @return bool - */ - public function appliesToEnvironment(): bool - { - return $this->enabled; - } - - /** - * Is this state enabled - * - * @return bool - */ - public function isEnabled(): bool - { - return $this->enabled; - } - - /** - * Set the state to whatever is required. Most commonly true - * - * @param bool $enabled - */ - public function setEnabled(bool $enabled): void - { - $this->enabled = $enabled; - } - - /** - * Activate a site state for indexing - * - * @param $state - * @throws ReflectionException - */ - public static function withState($state): void - { - /** - * @var string $variant - * @var SiteStateInterface $instance - */ - foreach (self::variants() as $variant => $instance) { - if ($state === self::DEFAULT_STATE) { - $instance->setDefaultState(self::$defaultStates[$variant]); - } elseif ($instance->stateIsApplicable($state)) { - $instance->activateState($state); - } - } - } - - /** - * Alter the query for each instance - * - * @param BaseQuery $query - * @throws ReflectionException - */ - public static function alterQuery(&$query): void - { - /** - * @var string $variant - * @var SiteStateInterface $instance - */ - foreach (self::variants() as $variant => $instance) { - $instance->updateQuery($query); - } - } - - /** - * Get the states set as default - * - * @return array - */ - public static function getDefaultStates(): array - { - return self::$defaultStates; - } - - /** - * Set the default states - * - * @param array $defaultStates - */ - public static function setDefaultStates(array $defaultStates): void - { - self::$defaultStates = $defaultStates; - } -} diff --git a/src/Stores/FileConfigStore.php b/src/Stores/FileConfigStore.php index 66e1f8e8..5cc457af 100644 --- a/src/Stores/FileConfigStore.php +++ b/src/Stores/FileConfigStore.php @@ -21,7 +21,6 @@ */ class FileConfigStore implements ConfigStore { - /** * @var array Configuration to use */ @@ -82,6 +81,17 @@ public function getTargetDir($index) return $targetDir; } + /** + * Location of the instance + * + * @param string|null $index + * @return string + */ + public function instanceDir($index) + { + return sprintf('%s/%s', $this->getPath(), $index); + } + /** * Path to the store location * @@ -106,17 +116,6 @@ public function uploadString($index, $filename, $string) file_put_contents(sprintf('%s/%s', $targetDir, $filename), $string); } - /** - * Location of the instance - * - * @param string|null $index - * @return string - */ - public function instanceDir($index) - { - return sprintf('%s/%s', $this->getPath(), $index); - } - /** * Get the config * diff --git a/src/Tasks/ClearDirtyClassesTask.php b/src/Tasks/ClearDirtyClassesTask.php deleted file mode 100644 index a36123fc..00000000 --- a/src/Tasks/ClearDirtyClassesTask.php +++ /dev/null @@ -1,127 +0,0 @@ -getDirtyClasses($dirtyObject); - try { - $service->updateItems($dirtyClasses, $dirtyObject->Type); - $dirtyObject->delete(); - } catch (Exception $exception) { - // @codeCoverageIgnoreStart - $this->getLogger()->error($exception->getMessage()); - continue; - // @codeCoverageIgnoreEnd - } - } - /** @var SolrLogger $solrLogger */ - $solrLogger = new SolrLogger(); - $solrLogger->saveSolrLog('Index'); - } - - /** - * Get the objects that need to be deleted or updated as a list - * - * @param DirtyClass $dirtyObject - * @return ArrayList|DataList - */ - private function getDirtyClasses($dirtyObject) - { - /** @var string $dirtyClass */ - $dirtyClass = $dirtyObject->Class; - $ids = json_decode($dirtyObject->IDs, true); - $dirtyClasses = ArrayList::create(); - if ($dirtyObject->Type === SolrCoreService::UPDATE_TYPE && count($ids)) { - $dirtyClasses = $dirtyClass::get()->byIDs($ids); - } - if ($dirtyObject->Type === SolrCoreService::DELETE_TYPE) { - $this->createDeleteList($ids, $dirtyClass, $dirtyClasses); - } - - return $dirtyClasses; - } - - /** - * Create an ArrayList of the dirty items to be deleted from Solr - * Uses the given class name to generate stub objects - * - * @param array $items - * @param string $dirtyClass - * @param ArrayList $dirtyClasses - */ - private function createDeleteList($items, $dirtyClass, &$dirtyClasses) - { - /** @var ArrayList $deletions */ - foreach ($items as $item) { - $dirtyItem = $dirtyClass::create(['ClassName' => $dirtyClass, 'ID' => $item]); - $dirtyClasses->push($dirtyItem); - } - } -} diff --git a/src/Tasks/ClearErrorsTask.php b/src/Tasks/ClearErrorsTask.php deleted file mode 100644 index dc586768..00000000 --- a/src/Tasks/ClearErrorsTask.php +++ /dev/null @@ -1,52 +0,0 @@ -get(LoggerInterface::class)->warn(_t( - __class__ . ".CLEARLOG", - "Emptying logs for table SolrLog." . PHP_EOL . "WARNING: Any logs that are not inspected will be gone soon." - )); - DB::query('TRUNCATE TABLE `Solr_SolrLog`'); - } -} diff --git a/src/Tasks/SolrConfigureTask.php b/src/Tasks/SolrConfigureTask.php index f58b95b9..d6ecfd63 100644 --- a/src/Tasks/SolrConfigureTask.php +++ b/src/Tasks/SolrConfigureTask.php @@ -10,13 +10,13 @@ namespace Firesphere\SolrSearch\Tasks; use Exception; -use Firesphere\SolrSearch\Helpers\SolrLogger; +use Firesphere\SearchBackend\Traits\LoggerTrait; use Firesphere\SolrSearch\Indexes\BaseIndex; use Firesphere\SolrSearch\Interfaces\ConfigStore; use Firesphere\SolrSearch\Services\SolrCoreService; use Firesphere\SolrSearch\Stores\FileConfigStore; use Firesphere\SolrSearch\Stores\PostConfigStore; -use Firesphere\SolrSearch\Traits\LoggerTrait; +use Psr\Container\NotFoundExceptionInterface; use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; use SilverStripe\Control\HTTPRequest; @@ -99,14 +99,13 @@ public function run($request) $this->extend('onAfterSolrConfigureTask'); // Grab the latest logs - $solrLogger = new SolrLogger(); - $solrLogger->saveSolrLog('Config'); } /** * Update the index on the given store * * @param string $index Core to index + * @throws NotFoundExceptionInterface */ protected function configureIndex($index): void { @@ -197,6 +196,5 @@ private function logException($index, Exception $error): void PHP_EOL, PHP_EOL ); - SolrLogger::logMessage('ERROR', $msg); } } diff --git a/src/Tasks/SolrIndexTask.php b/src/Tasks/SolrIndexTask.php index a232ee8d..4e9917ee 100644 --- a/src/Tasks/SolrIndexTask.php +++ b/src/Tasks/SolrIndexTask.php @@ -10,12 +10,12 @@ namespace Firesphere\SolrSearch\Tasks; use Exception; -use Firesphere\SolrSearch\Factories\DocumentFactory; -use Firesphere\SolrSearch\Helpers\SolrLogger; +use Firesphere\ElasticSearch\Factories\DocumentFactory; +use Firesphere\SearchBackend\Helpers\SearchLogger; +use Firesphere\SearchBackend\States\SiteState; +use Firesphere\SearchBackend\Traits\LoggerTrait; use Firesphere\SolrSearch\Indexes\BaseIndex; use Firesphere\SolrSearch\Services\SolrCoreService; -use Firesphere\SolrSearch\States\SiteState; -use Firesphere\SolrSearch\Traits\LoggerTrait; use Firesphere\SolrSearch\Traits\SolrIndexTrait; use Psr\Log\LoggerInterface; use ReflectionException; @@ -346,7 +346,7 @@ private function runChild(string $class, int $pid, int $start): void try { $this->doReindex($start, $class, $pid); } catch (Exception $error) { - SolrLogger::logMessage('ERROR', $error); +// Logg::logMessage('ERROR', $error); $msg = sprintf( 'Something went wrong while indexing %s on %s, see the logs for details', $start, @@ -442,6 +442,6 @@ private function logException($index, int $group, Exception $exception): void $index, $group ); - SolrLogger::logMessage('ERROR', $msg); + SearchLogger::logMessage('ERROR', $msg); } } diff --git a/src/Traits/CoreTraits/CoreAdminTrait.php b/src/Traits/CoreTraits/CoreAdminTrait.php index 09b92c5d..dd3f8edc 100644 --- a/src/Traits/CoreTraits/CoreAdminTrait.php +++ b/src/Traits/CoreTraits/CoreAdminTrait.php @@ -8,17 +8,15 @@ * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy */ - namespace Firesphere\SolrSearch\Traits; use Exception; -use Firesphere\SolrSearch\Helpers\SolrLogger; use Firesphere\SolrSearch\Interfaces\ConfigStore; use Firesphere\SolrSearch\Services\SolrCoreService; use Solarium\Client; +use Solarium\Exception\HttpException; use Solarium\QueryType\Server\CoreAdmin\Query\Query; use Solarium\QueryType\Server\CoreAdmin\Result\StatusResult; -use Solarium\Exception\HttpException; /** * Trait CoreAdminTrait is the trait that helps with Admin operations. @@ -60,9 +58,6 @@ public function coreCreate($core, $configStore): bool return $response->getWasSuccessful(); // @codeCoverageIgnoreStart } catch (Exception $error) { - $solrLogger = new SolrLogger(); - $solrLogger->saveSolrLog('Config'); - throw new Exception($error); } // @codeCoverageIgnoreEnd diff --git a/src/Traits/CoreTraits/CoreServiceTrait.php b/src/Traits/CoreTraits/CoreServiceTrait.php index 2854659f..3de12dfe 100644 --- a/src/Traits/CoreTraits/CoreServiceTrait.php +++ b/src/Traits/CoreTraits/CoreServiceTrait.php @@ -10,7 +10,7 @@ namespace Firesphere\SolrSearch\Traits; -use Firesphere\SolrSearch\Helpers\FieldResolver; +use Firesphere\SearchBackend\Helpers\FieldResolver; use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; use ReflectionException; diff --git a/src/Traits/DataResolverTraits/DataResolveTrait.php b/src/Traits/DataResolverTraits/DataResolveTrait.php deleted file mode 100644 index 64f06c00..00000000 --- a/src/Traits/DataResolverTraits/DataResolveTrait.php +++ /dev/null @@ -1,206 +0,0 @@ -columnName)) { - return $this->component->toMap(); - } - // Inspect component has attribute - if (empty($this->columns) && $this->component->hasField($this->columnName)) { - return $this->component->{$this->columnName}; - } - $this->cannotIdentifyException($this->component, array_merge([$this->columnName], $this->columns)); - } - - /** - * Each class using this trait should have a way to throw unidentifiable objects or items - * - * @param DataObject|ArrayData|SS_List $component - * @param array $columns - * - * @return void - * @throws LogicException - */ - abstract protected function cannotIdentifyException($component, $columns = []): void; - - /** - * Resolves a DataList values - * - * @return array|mixed - * @throws LogicException - */ - protected function resolveList() - { - if (empty($this->columnName)) { - return $this->component->toNestedArray(); - } - // Inspect $component for element $relation - if ($this->component->hasMethod($this->columnName)) { - $relation = $this->columnName; - - return self::identify($this->component->$relation(), $this->columns); - } - $data = []; - array_unshift($this->columns, $this->columnName); - foreach ($this->component as $component) { - $data[] = self::identify($component, $this->columns); - } - - return $data; - } - - /** - * Resolves a Single field in the database. - * - * @return mixed - * @throws LogicException - */ - protected function resolveField() - { - if ($this->columnName) { - $method = $this->checkHasMethod(); - - $value = $this->component->$method(); - } else { - $value = $this->component->getValue(); - } - - if (!empty($this->columns)) { - $this->cannotIdentifyException($this->component, $this->columns); - } - - return $value; - } - - /** - * Check if a component has the method instead of it being a property - * - * @return null|mixed|string - * @throws LogicException - */ - protected function checkHasMethod() - { - if ($this->component->hasMethod($this->columnName)) { - $method = $this->columnName; - } elseif ($this->component->hasMethod("get{$this->columnName}")) { - $method = "get{$this->columnName}"; - } else { - throw new LogicException( - sprintf('Method, "%s" not found on "%s"', $this->columnName, $this->shortName) - ); - } - - return $method; - } - - /** - * Resolves a DataObject value - * - * @return mixed - * @throws LogicException - */ - protected function resolveDataObject() - { - if (empty($this->columnName)) { - return $this->component->toMap(); - } - // Inspect component for element $relation - if ($this->component->hasMethod($this->columnName)) { - return $this->getMethodValue(); - } - // Inspect component has attribute - if ($this->component->hasField($this->columnName)) { - return $this->getFieldValue(); - } - $this->cannotIdentifyException($this->component, [$this->columnName]); - } - - /** - * Get the value for a method - * - * @return mixed - */ - protected function getMethodValue() - { - $relation = $this->columnName; - // We hit a direct method that returns a non-object - if (!is_object($this->component->$relation())) { - return $this->component->$relation(); - } - - return self::identify($this->component->$relation(), $this->columns); - } - - /** - * Get the value for a field - * - * @return mixed - */ - protected function getFieldValue() - { - $data = $this->component->{$this->columnName}; - $dbObject = $this->component->dbObject($this->columnName); - if ($dbObject) { - $dbObject->setValue($data); - - return self::identify($dbObject, $this->columns); - } - - return $data; - } -} diff --git a/src/Traits/DocumentFactoryTraits/DocumentFactoryTrait.php b/src/Traits/DocumentFactoryTraits/DocumentFactoryTrait.php deleted file mode 100644 index 7b15ef02..00000000 --- a/src/Traits/DocumentFactoryTraits/DocumentFactoryTrait.php +++ /dev/null @@ -1,95 +0,0 @@ -class; - } - - /** - * Set the current class to be indexed - * - * @param string $class - * @return DocumentFactory - */ - public function setClass(string $class): self - { - $this->class = $class; - - return $this; - } - - /** - * Get the FieldResolver class - * - * @return FieldResolver - */ - public function getFieldResolver(): FieldResolver - { - return $this->fieldResolver; - } - - /** - * Get the items being indexed - * - * @return ArrayList|DataList|null - */ - public function getItems() - { - return $this->items; - } - - /** - * Set the items to index - * - * @param ArrayList|DataList|null $items - * @return DocumentFactory - */ - public function setItems($items): self - { - $this->items = $items; - - return $this; - } -} diff --git a/src/Traits/IndexTraits/BaseIndexTrait.php b/src/Traits/IndexTraits/BaseIndexTrait.php deleted file mode 100644 index 6f030e0e..00000000 --- a/src/Traits/IndexTraits/BaseIndexTrait.php +++ /dev/null @@ -1,438 +0,0 @@ - [ - '*', - ], - ]; - /** - * usedAllFields is used to determine if the addAllFields method has been called - * This is to prevent a notice if there is no yml. - * - * @var bool - */ - protected $usedAllFields = false; - - /** - * Return the copy fields - * - * @return array - */ - public function getCopyFields(): array - { - return $this->copyFields; - } - - /** - * Set the copy fields - * - * @param array $copyField - * @return $this - */ - public function setCopyFields($copyField): self - { - $this->copyFields = $copyField; - - return $this; - } - - /** - * Return the default field for this index - * - * @return string - */ - public function getDefaultField(): string - { - return $this->defaultField; - } - - /** - * Set the default field for this index - * - * @param string $defaultField - * @return $this - */ - public function setDefaultField($defaultField): self - { - $this->defaultField = $defaultField; - - return $this; - } - - /** - * Add a field to sort on - * - * @param $sortField - * @return $this - */ - public function addSortField($sortField): self - { - if (!in_array($sortField, $this->getFulltextFields(), true) && - !in_array($sortField, $this->getFilterFields(), true) - ) { - $this->addFulltextField($sortField); - $this->sortFields[] = $sortField; - } - - $this->setSortFields(array_unique($this->getSortFields())); - - return $this; - } - - /** - * Get the fulltext fields - * - * @return array - */ - public function getFulltextFields(): array - { - return array_values( - array_unique( - $this->fulltextFields - ) - ); - } - - /** - * Set the fulltext fields - * - * @param array $fulltextFields - * @return $this - */ - public function setFulltextFields($fulltextFields): self - { - $this->fulltextFields = $fulltextFields; - - return $this; - } - - /** - * Get the filter fields - * - * @return array - */ - public function getFilterFields(): array - { - return $this->filterFields; - } - - /** - * Set the filter fields - * - * @param array $filterFields - * @return $this - */ - public function setFilterFields($filterFields): self - { - $this->filterFields = $filterFields; - - return $this; - } - - /** - * Add a single Fulltext field - * - * @param string $fulltextField - * @param null|string $forceType - * @param array $options - * @return $this - */ - public function addFulltextField($fulltextField, $forceType = null, $options = []): self - { - if ($forceType) { - Deprecation::notice('5.0', 'ForceType should be handled through casting'); - } - - $key = array_search($fulltextField, $this->getFilterFields(), true); - - if (!$key) { - $this->fulltextFields[] = $fulltextField; - } - - if (isset($options['boost'])) { - $this->addBoostedField($fulltextField, [], $options['boost']); - } - - if (isset($options['stored'])) { - $this->storedFields[] = $fulltextField; - } - - return $this; - } - - /** - * Add an abstract for the add Boosted Field to keep things consistent - * - * @param string $field - * @param array|int $options - * @param null|int $boost - * @return mixed - */ - abstract public function addBoostedField($field, $options = [], $boost = null); - - /** - * Get the sortable fields - * - * @return array - */ - public function getSortFields(): array - { - return $this->sortFields; - } - - /** - * Set/override the sortable fields - * - * @param array $sortFields - * @return $this - */ - public function setSortFields($sortFields): self - { - $this->sortFields = $sortFields; - - return $this; - } - - /** - * Add all text-type fields to the given index - * - * @throws ReflectionException - */ - public function addAllFulltextFields() - { - $this->addAllFieldsByType(DBString::class); - } - - /** - * Add all database-backed text fields as fulltext searchable fields. - * - * For every class included in the index, examines those classes and all parent looking for "DBText" database - * fields (Varchar, Text, HTMLText, etc) and adds them all as fulltext searchable fields. - * - * Note, there is no check on boosting etc. That needs to be done manually. - * - * @param string $dbType - * @throws ReflectionException - */ - protected function addAllFieldsByType($dbType = DBString::class): void - { - $this->usedAllFields = true; - $classes = $this->getClasses(); - foreach ($classes as $key => $class) { - $fields = DataObject::getSchema()->databaseFields($class, true); - - $this->addFulltextFieldsForClass($fields, $dbType); - } - } - - /** - * This trait requires classes to be set, so getClasses can be called. - * - * @return array - */ - abstract public function getClasses(): array; - - /** - * Add all fields of a given type to the index - * - * @param array $fields The fields on the DataObject - * @param string $dbType Class type the reflection should extend - * @throws ReflectionException - */ - protected function addFulltextFieldsForClass(array $fields, $dbType = DBString::class): void - { - foreach ($fields as $field => $type) { - $pos = strpos($type, '('); - if ($pos !== false) { - $type = substr($type, 0, $pos); - } - $conf = Config::inst()->get(Injector::class, $type); - $ref = new ReflectionClass($conf['class']); - if ($ref->isSubclassOf($dbType)) { - $this->addFulltextField($field); - } - } - } - - /** - * Add all date-type fields to the given index - * - * @throws ReflectionException - */ - public function addAllDateFields() - { - $this->addAllFieldsByType(DBDate::class); - } - - /** - * Add a facet field - * - * @param $field - * @param array $options - * @return $this - */ - public function addFacetField($field, $options): self - { - $this->facetFields[$field] = $options; - - if (!in_array($options['Field'], $this->getFilterFields(), true)) { - $this->addFilterField($options['Field']); - } - - return $this; - } - - /** - * Add a filterable field - * - * @param $filterField - * @return $this - */ - public function addFilterField($filterField): self - { - $key = array_search($filterField, $this->getFulltextFields(), true); - if ($key === false) { - $this->filterFields[] = $filterField; - } - - return $this; - } - - /** - * Add a copy field - * - * @param string $field Name of the copyfield - * @param array $options Array of all fields that should be copied to this copyfield - * @return $this - */ - public function addCopyField($field, $options): self - { - $this->copyFields[$field] = $options; - - return $this; - } - - /** - * Add a stored/fulltext field - * - * @param string $field - * @param null|string $forceType - * @param array $extraOptions - * @return BaseIndex - */ - public function addStoredField($field, $forceType = null, $extraOptions = []): self - { - $options = array_merge($extraOptions, ['stored' => 'true']); - $this->addFulltextField($field, $forceType, $options); - - return $this; - } - - /** - * Get the client - * - * @return Client - */ - public function getClient() - { - return $this->client; - } - - /** - * Set/override the client - * - * @param Client $client - * @return $this - */ - public function setClient($client): self - { - $this->client = $client; - - return $this; - } - - /** - * Get the stored field list - * - * @return array - */ - public function getStoredFields(): array - { - return $this->storedFields; - } - - /** - * Set/override the stored field list - * - * @param array $storedFields - * @return BaseIndex - */ - public function setStoredFields(array $storedFields): self - { - $this->storedFields = $storedFields; - - return $this; - } -} diff --git a/src/Traits/IntrospectionTraits/GetSetSearchResolverTrait.php b/src/Traits/IntrospectionTraits/GetSetSearchResolverTrait.php deleted file mode 100644 index 5658e4a2..00000000 --- a/src/Traits/IntrospectionTraits/GetSetSearchResolverTrait.php +++ /dev/null @@ -1,52 +0,0 @@ -index; - } - - /** - * Set the current index - * - * @param mixed $index - * @return $this - */ - public function setIndex($index) - { - $this->index = $index; - - return $this; - } -} diff --git a/src/Traits/LoggerTrait.php b/src/Traits/LoggerTrait.php deleted file mode 100644 index 93f3218f..00000000 --- a/src/Traits/LoggerTrait.php +++ /dev/null @@ -1,54 +0,0 @@ -logger) { - $this->logger = Injector::inst()->get(LoggerInterface::class); - } - - return $this->logger; - } - - /** - * Set the logger if needed - * - * @param LoggerInterface $logger - */ - public function setLogger($logger): void - { - $this->logger = $logger; - } -} diff --git a/src/Traits/QueryComponentTraits/QueryComponentBoostTrait.php b/src/Traits/QueryComponentTraits/QueryComponentBoostTrait.php index e044d036..be6e9666 100644 --- a/src/Traits/QueryComponentTraits/QueryComponentBoostTrait.php +++ b/src/Traits/QueryComponentTraits/QueryComponentBoostTrait.php @@ -11,7 +11,7 @@ namespace Firesphere\SolrSearch\Traits; use Firesphere\SolrSearch\Factories\QueryComponentFactory; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\SolrQuery; use Minimalcode\Search\Criteria; /** @@ -22,9 +22,9 @@ trait QueryComponentBoostTrait { /** - * BaseQuery that is going to be executed + * SolrQuery that is going to be executed * - * @var BaseQuery + * @var SolrQuery */ protected $query; /** diff --git a/src/Traits/QueryComponentTraits/QueryComponentFacetTrait.php b/src/Traits/QueryComponentTraits/QueryComponentFacetTrait.php index 146fec9f..3a4ef5fa 100644 --- a/src/Traits/QueryComponentTraits/QueryComponentFacetTrait.php +++ b/src/Traits/QueryComponentTraits/QueryComponentFacetTrait.php @@ -11,7 +11,7 @@ namespace Firesphere\SolrSearch\Traits; use Firesphere\SolrSearch\Indexes\BaseIndex; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\SolrQuery; use Minimalcode\Search\Criteria; use Solarium\Component\Facet\Field; use Solarium\QueryType\Select\Query\Query; @@ -30,7 +30,7 @@ trait QueryComponentFacetTrait */ protected $index; /** - * @var BaseQuery Query to use + * @var SolrQuery Query to use */ protected $query; /** diff --git a/src/Traits/QueryComponentTraits/QueryComponentFilterTrait.php b/src/Traits/QueryComponentTraits/QueryComponentFilterTrait.php index de6d3d5d..88108bc2 100644 --- a/src/Traits/QueryComponentTraits/QueryComponentFilterTrait.php +++ b/src/Traits/QueryComponentTraits/QueryComponentFilterTrait.php @@ -10,7 +10,7 @@ namespace Firesphere\SolrSearch\Traits; -use Firesphere\SolrSearch\Queries\BaseQuery; +use Firesphere\SolrSearch\Queries\SolrQuery; use Minimalcode\Search\Criteria; use SilverStripe\ORM\DataList; use SilverStripe\Security\Group; @@ -27,7 +27,7 @@ trait QueryComponentFilterTrait { /** - * @var BaseQuery Base query that's about to be executed + * @var SolrQuery Base query that's about to be executed */ protected $query; /** diff --git a/src/Traits/ResultTraits/SearchResultGetTrait.php b/src/Traits/ResultTraits/SearchResultGetTrait.php deleted file mode 100644 index d72f1884..00000000 --- a/src/Traits/ResultTraits/SearchResultGetTrait.php +++ /dev/null @@ -1,100 +0,0 @@ -facets; - } - - /** - * Get the collated spellcheck - * - * @return string - */ - public function getCollatedSpellcheck() - { - return $this->collatedSpellcheck; - } - - /** - * Get the highlighting - * - * @return Highlighting|null - */ - public function getHighlight() - { - return $this->highlight; - } - - /** - * Get the spellchecked results - * - * @return ArrayList - */ - public function getSpellcheck(): ArrayList - { - return $this->spellcheck; - } - - /** - * Total items in the result - * - * @return int - */ - public function getTotalItems(): int - { - return $this->totalItems; - } -} diff --git a/src/Traits/ResultTraits/SearchResultSetTrait.php b/src/Traits/ResultTraits/SearchResultSetTrait.php deleted file mode 100644 index 8585b3bb..00000000 --- a/src/Traits/ResultTraits/SearchResultSetTrait.php +++ /dev/null @@ -1,65 +0,0 @@ -highlight = $highlight; - - return $this; - } - - /** - * Set the total amount of results - * - * @param $count - * @return self - */ - public function setTotalItems($count): self - { - $this->totalItems = $count; - - return $this; - } -} diff --git a/tests/mocks/CanViewObject.php b/tests/mocks/CanViewObject.php index b08326ab..c83c0cf2 100644 --- a/tests/mocks/CanViewObject.php +++ b/tests/mocks/CanViewObject.php @@ -1,6 +1,5 @@ build(); - $conn->selectDatabase($dbName); - $dbAdmin = new DatabaseAdmin(); - $dbAdmin->setRequest(HTTPRequestBuilder::createFromEnvironment()); - $dbAdmin->doBuild(true, true, true); - } - - public function setUp() - { - parent::setUp(); - Injector::inst()->get(Page::class)->requireDefaultRecords(); - foreach (self::$extra_dataobjects as $className) { - Config::modify()->merge($className, 'extensions', [DataObjectExtension::class]); - } - } - - public function testUnsupportedObjectException() - { - $this->expectExceptionMessage("Class: stdClass is not supported."); - $this->expectException(\Exception::class); - $obj = new stdClass(); - $obj->Created = '2019-07-04 22:01:00'; - $obj->Title = 'Hello generic class'; - $this->assertEqualsDump($obj->Title, DataResolver::identify($obj, 'Title')); - } - - public function assertEqualsDump() - { - Debug::dump(func_get_args()); - } - - public function testCannotIdentifyExceptionForDataObject() - { - $this->expectExceptionMessage("Cannot identify, \"UnknownColumn\" from class \"TestPage\""); - $this->expectException(\Exception::class); - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - DataResolver::identify($pageOne, 'UnknownColumn'); - } - - public function testCannotIdentifyExceptionForArrayData() - { - $this->expectExceptionMessage("Cannot identify, \"UnknownColumn\" from class \"ArrayData\""); - $this->expectException(\Exception::class); - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - $arrayOne = new ArrayData($pageOne->toMap()); - DataResolver::identify($arrayOne, 'UnknownColumn'); - } - - public function testMethodNotFoundFromDBField() - { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage("Method, \"SuperNice\" not found on \"DBDatetime\""); - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - DataResolver::identify($pageOne, 'Created.SuperNice'); - } - - public function testCannotIdentifyExceptionForDBField() - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage("Cannot identify, \"Timestamp\" from class \"DBDatetime\""); - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - DataResolver::identify($pageOne, 'Created.Nice.Timestamp'); - } - - public function testDataObjectEmptyColumnIsToMap() - { - $objectOne = $this->objFromFixture(TestObject::class, 'objectOne'); - $this->assertEquals($objectOne->toMap(), DataResolver::identify($objectOne)); - $arrayOne = new ArrayData($objectOne->toMap()); - $this->assertEquals($objectOne->toMap(), $arrayOne->toMap()); - $this->assertEquals($arrayOne->toMap(), DataResolver::identify($arrayOne)); - } - - public function testDataListEmptyColumnIsToNestedArray() - { - $objectOne = $this->objFromFixture(TestObject::class, 'objectOne'); - $dataList = $objectOne->TestPages(); - $this->assertEquals($dataList->toNestedArray(), DataResolver::identify($dataList)); - } - - public function testDataObjectGetMethod() - { - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - $this->assertEquals($pageOne->getSalutation(), DataResolver::identify($pageOne, 'Salutation')); - } - - public function testDataObjectAttributes() - { - $mockDate = '2019-07-04 22:01:00'; - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - $pageOne->Created = $mockDate; - $pageOne->write(); - $this->assertEquals($pageOne->Title, DataResolver::identify($pageOne, 'Title')); - $this->assertEquals($mockDate, $pageOne->Created); - $this->assertEquals($mockDate, DataResolver::identify($pageOne, 'Created')); - $this->assertEquals('Jul 4, 2019, 10:01 PM', DataResolver::identify($pageOne, 'Created.Nice')); - $this->assertEquals('2019-07-04T22:01:00+00:00', DataResolver::identify($pageOne, 'Created.Rfc3339')); - $this->assertEquals('1562277660', DataResolver::identify($pageOne, 'Created.Timestamp')); - $this->assertEquals('1562277660', DataResolver::identify($pageOne, 'Created.getTimestamp')); - $this->assertEquals('y-MM-dd HH:mm:ss', DataResolver::identify($pageOne, 'Created.ISOFormat')); - } - - public function testDataArrayAttributes() - { - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - $arrayOne = new ArrayData($pageOne->toMap()); - $this->assertEquals($arrayOne->Title, DataResolver::identify($arrayOne, 'Title')); - } - - public function testDataTraversal() - { - $mockDate = '2019-07-04 22:01:00'; - $objectOne = $this->objFromFixture(TestObject::class, 'objectOne'); - $pageOne = $this->objFromFixture(TestPage::class, 'pageOne'); - $relationOne = $this->objFromFixture(TestRelationObject::class, 'relationOne'); - $relationOne->Created = $mockDate; - $relationOne->write(); - $this->assertEquals($objectOne->toMap(), DataResolver::identify($pageOne, 'TestObject')); - $this->assertEquals($objectOne->Title, DataResolver::identify($pageOne, 'TestObject.Title')); - $this->assertEquals(0, DataResolver::identify($pageOne, 'TestObject.ShowInSearch')); - $this->assertEquals('No', DataResolver::identify($pageOne, 'TestObject.ShowInSearch.Nice')); - $this->assertEquals( - $objectOne->TestRelation()->toNestedArray(), - DataResolver::identify($pageOne, 'TestObject.TestRelation') - ); - $this->assertEquals( - $objectOne->TestRelation()->First()->toMap(), - DataResolver::identify($pageOne, 'TestObject.TestRelation.First') - ); - $this->assertEquals( - $relationOne->Title, - DataResolver::identify($pageOne, 'TestObject.TestRelation.First.Title') - ); - $this->assertEquals($mockDate, DataResolver::identify($pageOne, 'TestObject.TestRelation.First.Created')); - $this->assertEquals($mockDate, $relationOne->Created); - $this->assertEquals( - 'Jul 4, 2019, 10:01 PM', - DataResolver::identify($pageOne, 'TestObject.TestRelation.First.Created.Nice') - ); - $this->assertEquals( - '2019-07-04T22:01:00+00:00', - DataResolver::identify($pageOne, 'TestObject.TestRelation.First.Created.Rfc3339') - ); - $relationTwo = $this->objFromFixture(TestRelationObject::class, 'relationTwo'); - $relationTwo->Created = '2019-07-05 23:05:00'; - $relationTwo->write(); - $this->assertEquals( - ['Jul 4, 2019, 10:01 PM', 'Jul 5, 2019, 11:05 PM'], - DataResolver::identify($pageOne, 'TestObject.TestRelation.Created.Nice') - ); - } - - public function testGetMethodReturnsArray() - { - /** @var TestRelationObject $relationOne */ - $relationOne = $this->objFromFixture(TestRelationObject::class, 'relationOne'); - $this->assertEquals($relationOne->getFarmAnimals(), DataResolver::identify($relationOne, 'FarmAnimals')); - } - - public function testMethodReturnsString() - { - /** @var TestRelationObject $relationOne */ - $relationOne = $this->objFromFixture(TestRelationObject::class, 'relationOne'); - $this->assertEquals('cow', DataResolver::identify($relationOne, 'Cow')); - $this->assertEquals('sheep', DataResolver::identify($relationOne, 'Sheep')); - $this->assertEquals(['cow', 'sheep'], DataResolver::identify($relationOne, 'getFarmAnimals')); - $this->assertEquals('cow', DataResolver::identify($relationOne, 'getCow')); - $this->assertEquals('sheep', DataResolver::identify($relationOne, 'getSheep')); - } - - public function testArrayList() - { - $list = new ArrayList( - [ - new ArrayData(['Title' => 'one']), - new ArrayData(['Title' => 'two']), - new ArrayData(['Title' => 'three']), - ] - ); - $this->assertEquals($list->toNestedArray(), DataResolver::identify($list)); - $this->assertEquals($list->first()->Title, DataResolver::identify($list, 'First.Title')); - $this->assertEquals($list->last()->Title, DataResolver::identify($list, 'Last.Title')); - } -} diff --git a/tests/unit/DirtyClassTest.php b/tests/unit/DirtyClassTest.php index 0b27bed2..2e046dd1 100644 --- a/tests/unit/DirtyClassTest.php +++ b/tests/unit/DirtyClassTest.php @@ -1,6 +1,5 @@ assertTrue(FieldResolver::isSubclassOf(Page::class, [SiteTree::class, Page::class])); - $this->assertFalse(FieldResolver::isSubclassOf(ModelAdmin::class, [SiteTree::class, Page::class])); - } - - public function testHierarchy() - { - // Expected Hierarchy is all pagetypes, including the test ones - $expected = [ - SiteTree::class, - Page::class, - TestPage::class, - ErrorPage::class, - RedirectorPage::class, - VirtualPage::class, - ]; - - $test = FieldResolver::getHierarchy(Page::class, false); - $this->assertEquals($expected, $test); - $test2 = FieldResolver::getHierarchy(Page::class, true); - unset($expected[1]); // Page::class does not have data fields - $this->assertEquals(array_values($expected), $test2); - } - - public function testGetFieldIntrospection() - { -// $this->markTestSkipped('Currently broken, needs investigation as to why'); - $index = new CircleCITestIndex(); - - $factory = new FieldResolver(); - $factory->setIndex($index); - $expected = [ - SiteTree::class . '_Content' => - [ - 'name' => SiteTree::class . '_Content', - 'field' => 'Content', - 'origin' => SiteTree::class, - 'type' => 'HTMLText', - 'multi_valued' => false, - 'fullfield' => 'Content', - 'class' => SiteTree::class, - ], - ]; - - $result = $factory->resolveField('Content'); - - $this->assertEquals($expected, $result); - $index = new TestIndexTwo(); - - $factory->setIndex($index); - - $expected = [ - SiteTree::class . '_TestObject_TestRelation_Title' => - [ - 'name' => SiteTree::class . '_TestObject_TestRelation_Title', - 'field' => 'Title', - 'fullfield' => 'TestObject_TestRelation_Title', - 'origin' => SiteTree::class, - 'class' => TestRelationObject::class, - 'type' => 'Varchar', - 'multi_valued' => true, - ], - ]; - - $this->assertEquals($expected, $factory->resolveField('TestObject.TestRelation.Title')); - - $expected = [ - SiteTree::class . '_RelationObject_Title' => - [ - 'name' => SiteTree::class . '_RelationObject_Title', - 'field' => 'Title', - 'fullfield' => 'RelationObject_Title', - 'origin' => SiteTree::class, - 'class' => TestRelationObject::class, - 'type' => 'Varchar', - 'multi_valued' => true, - ], - ]; - - $this->assertEquals($expected, $factory->resolveField('RelationObject.Title')); - } - - protected function setUp() - { - $this->fieldResolver = new FieldResolver(); - $this->fieldResolver->setIndex(new CircleCITestIndex()); - - return parent::setUp(); - } -} diff --git a/tests/unit/QueryComponentFactoryTest.php b/tests/unit/QueryComponentFactoryTest.php index 79508f00..8cd6112b 100644 --- a/tests/unit/QueryComponentFactoryTest.php +++ b/tests/unit/QueryComponentFactoryTest.php @@ -1,6 +1,5 @@ method('setQuery') ->with($this->equalTo('-TestExcludeField:TestExcludeValue')); - $mockClientQuery = new class extends Query { + $mockClientQuery = new class () extends Query { public $mockFilterQuery; public $mockExcludeQuery; public function createFilterQuery($options = null): FilterQuery @@ -160,7 +159,7 @@ public function testFilterAndExcludeCriteria() ->method('setQuery') ->with($this->equalTo('-TestExcludeField:TestExcludeValue')); - $mockClientQuery = new class extends Query { + $mockClientQuery = new class () extends Query { public $mockFilterQuery; public $mockExcludeQuery; public function createFilterQuery($options = null): FilterQuery diff --git a/tests/unit/SchemaFactoryTest.php b/tests/unit/SchemaFactoryTest.php index 539e07ce..3833de15 100644 --- a/tests/unit/SchemaFactoryTest.php +++ b/tests/unit/SchemaFactoryTest.php @@ -1,6 +1,5 @@