mirror of
https://github.com/silverstripe/silverstripe-fulltextsearch
synced 2024-10-22 14:05:29 +02:00
Merge branch '3.7' into 3
This commit is contained in:
commit
6da1beb717
10
.travis.yml
10
.travis.yml
@ -4,7 +4,7 @@ dist: trusty
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- COMPOSER_ROOT_VERSION="3.0.x-dev"
|
- COMPOSER_ROOT_VERSION="3.x-dev"
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@ -13,11 +13,11 @@ matrix:
|
|||||||
- php: 7.1
|
- php: 7.1
|
||||||
env: DB=PGSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_TEST=1
|
env: DB=PGSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_TEST=1
|
||||||
- php: 7.2
|
- php: 7.2
|
||||||
env: DB=MYSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_COVERAGE_TEST=1
|
env: DB=MYSQL INSTALLER_VERSION=4.4.x-dev PHPUNIT_COVERAGE_TEST=1
|
||||||
- php: 7.3
|
- php: 7.3
|
||||||
env: DB=MYSQL INSTALLER_VERSION=4.4.x-dev PHPUNIT_TEST=1 SUBSITES=1
|
env: DB=MYSQL INSTALLER_VERSION=4.5.x-dev PHPUNIT_TEST=1 SUBSITES=1
|
||||||
- php: 7.3
|
- php: 7.4
|
||||||
env: DB=MYSQL INSTALLER_VERSION=4.x-dev PHPUNIT_TEST=1
|
env: DB=MYSQL INSTALLER_VERSION=4.6.x-dev PHPUNIT_TEST=1
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- phpenv rehash
|
- phpenv rehash
|
||||||
|
76
README.md
76
README.md
@ -8,9 +8,81 @@
|
|||||||
Adds support for fulltext search engines like Sphinx and Solr to SilverStripe CMS.
|
Adds support for fulltext search engines like Sphinx and Solr to SilverStripe CMS.
|
||||||
Compatible with PHP 7.2
|
Compatible with PHP 7.2
|
||||||
|
|
||||||
## Maintainer Contact
|
## Important notes when upgrading to fulltextsearch 3.7.0+
|
||||||
|
|
||||||
* Hamish Friedlander <hamish (at) silverstripe (dot) com>
|
There are some significant changes from previous versions:
|
||||||
|
|
||||||
|
Draft content will no longer be automatically added to the search index. This new behaviour was previously an
|
||||||
|
opt-in behaviour that was enabled by adding the following line to a search index:
|
||||||
|
|
||||||
|
```
|
||||||
|
$this->excludeVariantState([SearchVariantVersioned::class => Versioned::DRAFT]);
|
||||||
|
```
|
||||||
|
|
||||||
|
A new `canView()` check against an anonymous user (i.e. someone not logged in) and a `ShowInSearch` check is now
|
||||||
|
performed by default against all records (DataObjects) before being added to the search index, and also before being
|
||||||
|
shown in search results. This may mean that some records that were previously being indexed and shown in search results
|
||||||
|
will no longer appear due to these additional checks.
|
||||||
|
|
||||||
|
These additional checks have been added with data security in mind, and it's assumed that records failing these
|
||||||
|
checks probably should not be indexed in the first place.
|
||||||
|
|
||||||
|
# Enable indexing of draft content:
|
||||||
|
|
||||||
|
You can index draft content with the following yml configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
SilverStripe\FullTextSearch\Search\Services\SearchableService:
|
||||||
|
variant_state_draft_excluded: false
|
||||||
|
```
|
||||||
|
|
||||||
|
However, when set to false, it will still only index draft content when a DataObject is in a published state, not a
|
||||||
|
draft-only or modified state. This is because it will still fail the new anonymous user `canView()` check in
|
||||||
|
`SearchableService::isSearchable()` and be automatically deleted from the index.
|
||||||
|
|
||||||
|
If you wish to also index draft content when a DataObject is in a draft-only or a modified state, then you'll need
|
||||||
|
to also configure `SearchableService::indexing_canview_exclude_classes`. See below for instructions on how to do this.
|
||||||
|
|
||||||
|
# Disabling the anonymous user canView() pre-index check
|
||||||
|
|
||||||
|
You can apply configuration to remove the new pre-index `canView()` check from your DataObjects if it is not necessary,
|
||||||
|
or if it impedes expected functionality (e.g. for sites where users must authenticate to view any content). This will
|
||||||
|
also disable the check for descendants of the specified DataObjects. Ensure that your implementation of fulltextsearch
|
||||||
|
is correctly performing a `canView()` check at query time before disabling the pre-index check, as this may result in
|
||||||
|
leakage of private data.
|
||||||
|
|
||||||
|
```
|
||||||
|
SilverStripe\FullTextSearch\Search\Services\SearchableService:
|
||||||
|
indexing_canview_exclude_classes:
|
||||||
|
- Some\Org\MyDataObject
|
||||||
|
# This will disable the check for all pagetypes:
|
||||||
|
- SilverStripe\CMS\Model\SiteTree
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the `updateIsSearchable` extension point on `SearchableService` to modify the result of the method
|
||||||
|
after the `ShowInSearch` and `canView()` checks have run.
|
||||||
|
|
||||||
|
It is highly recommend you run a [solr_reindex](https://github.com/silverstripe/silverstripe-fulltextsearch/blob/3/docs/en/03_configuration.md#solr-reindex)
|
||||||
|
on your production site after upgrading from 3.6 or earlier to purge any old data that should no longer be in the search index.
|
||||||
|
|
||||||
|
These additional check can have an impact on the reindex performance due to additional queries for permission checks.
|
||||||
|
If your site also indexes content in files, such as pdf's or docx's, using the [text-extraction](https://github.com/silverstripe/silverstripe-textextraction)
|
||||||
|
module which is fairly time-intensive, then the relative performance impact of the `canView()` checks won't be as noticeable.
|
||||||
|
|
||||||
|
## Details on filtering before adding content to the solr index
|
||||||
|
- `SearchableService::isIndexable()` check in `SolrReindexBase`. Used when indexing all records during Solr reindex.
|
||||||
|
- `SearchableService::isIndexable()` check in `SearchUpdateProcessor`. Used when indexing single records during
|
||||||
|
`DataObject->write()`.
|
||||||
|
|
||||||
|
## Details on filtering when extracting results from the solr index
|
||||||
|
- `SearchableService::isViewable()` check in `SolrIndex`. This will often be used in CWP implementations that use the
|
||||||
|
`CwpSearchEngine` class, as well as most custom implementations that call `MySearchIndex->search()`
|
||||||
|
- `SearchableService::isViewable()` check in `SearchForm`. This will be used in solr implementations where a
|
||||||
|
`/SearchForm` url is used to display search results.
|
||||||
|
- Some implementations will call `SearchableService::isViewable()` twice. If this happens then the first call will be
|
||||||
|
cached in memory so there is virtually no performance penalty calling it a second time.
|
||||||
|
- If your implementation is very custom and does not subclass nor make use of either `SolrIndex` or `SearchForm`, then
|
||||||
|
it's recommended you update your implementation to call `SearchableService::isViewable()`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
90
changelog.md
90
changelog.md
@ -1,90 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## [2.2.0]
|
|
||||||
|
|
||||||
* FIX Indexes with custom index names that don't match the classname were breaking
|
|
||||||
* BUG Fix versioned writes where subtables have no fields key
|
|
||||||
* BUGFIX: Fixed issue where the $id variable would be overridden in sub sequent iterations of the derived fields loop
|
|
||||||
* adding stemming support
|
|
||||||
* BUG fix issues with search variants applying to more than one class
|
|
||||||
* API adding stemming support
|
|
||||||
* FIX: Fix initial dev/build on PDO Database.
|
|
||||||
|
|
||||||
## [2.1.1]
|
|
||||||
|
|
||||||
* Converted to PSR-2
|
|
||||||
* FIX: remove parameters from function calls
|
|
||||||
* Added standard code of conduct
|
|
||||||
* Added standard editor config
|
|
||||||
* Updated license
|
|
||||||
* Added standard gitattributes
|
|
||||||
* MINOR: Don't include Hamcrest globally so it doesn't conflict with PHPUnit
|
|
||||||
|
|
||||||
## [2.1.0]
|
|
||||||
|
|
||||||
* 3.2 Compatibility
|
|
||||||
* Add ss 3.2 and PHP 5.6 to CI
|
|
||||||
* Added standard Scrutinizer config
|
|
||||||
* Added standard Travis config
|
|
||||||
|
|
||||||
## [2.0.0]
|
|
||||||
|
|
||||||
* Fix highlight support when querying by fields (or boosting fields)
|
|
||||||
* Updating travis provisioner
|
|
||||||
* Added docs about controller and template usage
|
|
||||||
* API Enable boosted fields to be specified on the index
|
|
||||||
* BUG Prevent subsites breaking solrindexversionedtest
|
|
||||||
* Enable indexes to upload custom config
|
|
||||||
* API Additional support for custom copy_fields
|
|
||||||
* API QueuedJob support for Solr_Reindex
|
|
||||||
|
|
||||||
## [1.1.0]
|
|
||||||
|
|
||||||
* API Solr_Reindex uses configured SearchUpdater instead of always doing a direct write
|
|
||||||
* Fix class limit on delete query in SolrIndex
|
|
||||||
* Regression in SearchUpdater_ObjectHandler
|
|
||||||
* API Separate searchupdate / commit into separate queued-jobs
|
|
||||||
* API Only allow one scheduled commit job at a time
|
|
||||||
|
|
||||||
## [1.0.6]
|
|
||||||
|
|
||||||
* Make spelling suggestions more useful
|
|
||||||
* BUG Add missing addStoredFields method
|
|
||||||
|
|
||||||
## [1.0.5]
|
|
||||||
|
|
||||||
* BUG Fix Solr 4.0 compatibility issue
|
|
||||||
* BUG Fix test case not elegantly failing on missing phockito
|
|
||||||
* API SearchUpdateQueuedJobProcessor now uses batching
|
|
||||||
* Fix many_many fieldData bug
|
|
||||||
* Adding tests for SearchIndex::fieldData()
|
|
||||||
* Add a no-op query to prevent database timeouts during a long reindex
|
|
||||||
|
|
||||||
## [1.0.4]
|
|
||||||
|
|
||||||
* BUG Patch up the information leak of debug information.
|
|
||||||
* FIX: will work for postgreSQL
|
|
||||||
|
|
||||||
## [1.0.3]
|
|
||||||
|
|
||||||
Users upgrading from 1.0.2 or below will need to run the Solr_Reindex task to refresh
|
|
||||||
each SolrIndex. This is due to a change in record IDs, which are now generated from
|
|
||||||
the base class of each DataObject, rather than the instance class, as well as fixes
|
|
||||||
to integration with the subsites module.
|
|
||||||
|
|
||||||
Developers working locally should be aware that by default, all indexes will be updated
|
|
||||||
in realtime when the environment is in dev mode, rather than attempting to queue these
|
|
||||||
updates with the queued jobs module (if installed).
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
* BUG Fix old indexing storing against the incorrect class key
|
|
||||||
* [Don't rely on MySQL ordering of index->getAdded()](https://github.com/silverstripe-labs/silverstripe-fulltextsearch/commit/4b51393e014fc4c0cc8e192c74eb4594acaca605)
|
|
||||||
|
|
||||||
### API
|
|
||||||
|
|
||||||
* [API Disable queued processing for development environments](https://github.com/silverstripe-labs/silverstripe-fulltextsearch/commit/71fc359b3711cf5b9429d86da0f1e0b20bd43dee)
|
|
@ -112,12 +112,13 @@ Depending on the size of the index and how much content needs to be processed, i
|
|||||||
|
|
||||||
If the [Queued Jobs module](https://github.com/symbiote/silverstripe-queuedjobs/) is installed, updates are queued up instead of executed in the same request. Queued jobs are usually processed every minute. Large index updates will be batched into multiple queued jobs to ensure a job can run to completion within common constraints, such as memory and execution time limits. You can check the status of jobs in an administrative interface under `admin/queuedjobs/`.
|
If the [Queued Jobs module](https://github.com/symbiote/silverstripe-queuedjobs/) is installed, updates are queued up instead of executed in the same request. Queued jobs are usually processed every minute. Large index updates will be batched into multiple queued jobs to ensure a job can run to completion within common constraints, such as memory and execution time limits. You can check the status of jobs in an administrative interface under `admin/queuedjobs/`.
|
||||||
|
|
||||||
### Excluding draft content
|
### Draft content
|
||||||
|
|
||||||
By default, the `SearchUpdater` class indexes all available "variant states", so in the case of the `Versioned` extension, both "draft" and "live".
|
By default, the `SearchUpdater` class attempts to index all available "variant states", except for draft content.
|
||||||
For most cases, you'll want to exclude draft content from your search results.
|
Draft content is excluded by default via calls to SearchableService::variantStateExcluded().
|
||||||
|
|
||||||
You can either prevent the draft content from being indexed in the first place, by adding the following to your `SearchIndex::init()` method:
|
Excluding draft content was a new default added in 3.7.0. Prior to that, draft content was previously indexed by
|
||||||
|
default and could be excluded fron the index by adding the following to the `SearchIndex::init()` method:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Page;
|
use Page;
|
||||||
@ -136,7 +137,10 @@ class MyIndex extends SolrIndex
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can index draft content, but simply exclude it from searches. This can be handy to preview search results on unpublished content, in case a CMS author is logged in. Before constructing your `SearchQuery`, conditionally switch to the "live" stage.
|
If required, you can opt-out of the secure default and index draft content, but simply exclude it from searches.
|
||||||
|
Read the inline documentation within SearchableService.php for more details on how to do this.
|
||||||
|
This can be handy to preview search results on unpublished content, in case a CMS author is logged in.
|
||||||
|
Before constructing your `SearchQuery`, conditionally switch to the "live" stage.
|
||||||
|
|
||||||
### Adding DataObjects
|
### Adding DataObjects
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace SilverStripe\FullTextSearch\Search\Processors;
|
namespace SilverStripe\FullTextSearch\Search\Processors;
|
||||||
|
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
abstract class SearchUpdateProcessor
|
abstract class SearchUpdateProcessor
|
||||||
{
|
{
|
||||||
@ -72,7 +74,8 @@ abstract class SearchUpdateProcessor
|
|||||||
$dirtyIndexes = array();
|
$dirtyIndexes = array();
|
||||||
$dirty = $this->getSource();
|
$dirty = $this->getSource();
|
||||||
$indexes = FullTextSearch::get_indexes();
|
$indexes = FullTextSearch::get_indexes();
|
||||||
$indexableService = IndexableService::singleton();
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
foreach ($dirty as $base => $statefulids) {
|
foreach ($dirty as $base => $statefulids) {
|
||||||
if (!$statefulids) {
|
if (!$statefulids) {
|
||||||
continue;
|
continue;
|
||||||
@ -86,11 +89,15 @@ abstract class SearchUpdateProcessor
|
|||||||
|
|
||||||
// Ensure that indexes for all new / updated objects are included
|
// Ensure that indexes for all new / updated objects are included
|
||||||
$objs = DataObject::get($base)->byIDs(array_keys($ids));
|
$objs = DataObject::get($base)->byIDs(array_keys($ids));
|
||||||
|
|
||||||
|
/** @var DataObject $obj */
|
||||||
foreach ($objs as $obj) {
|
foreach ($objs as $obj) {
|
||||||
foreach ($ids[$obj->ID] as $index) {
|
foreach ($ids[$obj->ID] as $index) {
|
||||||
if (!$indexes[$index]->variantStateExcluded($state)) {
|
if (!$searchableService->variantStateExcluded($state) &&
|
||||||
// Remove any existing records from index if ShowInSearch is changed to false
|
!$indexes[$index]->variantStateExcluded($state)
|
||||||
if (!$indexableService->isIndexable($obj)) {
|
) {
|
||||||
|
// Remove any existing data from index if the object is no longer indexable
|
||||||
|
if (!$searchableService->isIndexable($obj)) {
|
||||||
$indexes[$index]->delete($base, $obj->ID, $state);
|
$indexes[$index]->delete($base, $obj->ID, $state);
|
||||||
} else {
|
} else {
|
||||||
$indexes[$index]->add($obj);
|
$indexes[$index]->add($obj);
|
||||||
@ -104,7 +111,9 @@ abstract class SearchUpdateProcessor
|
|||||||
// Generate list of records that do not exist and should be removed
|
// Generate list of records that do not exist and should be removed
|
||||||
foreach ($ids as $id => $fromindexes) {
|
foreach ($ids as $id => $fromindexes) {
|
||||||
foreach ($fromindexes as $index) {
|
foreach ($fromindexes as $index) {
|
||||||
if (!$indexes[$index]->variantStateExcluded($state)) {
|
if (!$searchableService->variantStateExcluded($state) &&
|
||||||
|
!$indexes[$index]->variantStateExcluded($state)
|
||||||
|
) {
|
||||||
$indexes[$index]->delete($base, $id, $state);
|
$indexes[$index]->delete($base, $id, $state);
|
||||||
$dirtyIndexes[$index] = $indexes[$index];
|
$dirtyIndexes[$index] = $indexes[$index];
|
||||||
}
|
}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace SilverStripe\FullTextSearch\Search\Services;
|
|
||||||
|
|
||||||
use SilverStripe\Core\Extensible;
|
|
||||||
use SilverStripe\Core\Injector\Injectable;
|
|
||||||
use SilverStripe\ORM\DataObject;
|
|
||||||
use SilverStripe\ORM\Tests\MySQLDatabaseTest\Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a DataObject is publically viewable thus able to be added or retrieved from a publically searchable index
|
|
||||||
* Caching results because these checks may be done multiple times as there a few different code paths that search
|
|
||||||
* results might follow in real-world search implementations
|
|
||||||
*/
|
|
||||||
class IndexableService
|
|
||||||
{
|
|
||||||
|
|
||||||
use Injectable;
|
|
||||||
use Extensible;
|
|
||||||
|
|
||||||
protected $cache = [];
|
|
||||||
|
|
||||||
public function clearCache(): void
|
|
||||||
{
|
|
||||||
$this->cache = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isIndexable(DataObject $obj): bool
|
|
||||||
{
|
|
||||||
// check if is a valid DataObject that has been persisted to the database
|
|
||||||
if (is_null($obj) || !$obj->ID) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = $this->getCacheKey($obj);
|
|
||||||
if (isset($this->cache[$key])) {
|
|
||||||
return $this->cache[$key];
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = true;
|
|
||||||
|
|
||||||
// This will also call $obj->getShowInSearch() if it exists
|
|
||||||
if (isset($obj->ShowInSearch) && !$obj->ShowInSearch) {
|
|
||||||
$value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->extend('updateIsIndexable', $obj, $value);
|
|
||||||
$this->cache[$key] = $value;
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getCacheKey(DataObject $obj): string
|
|
||||||
{
|
|
||||||
$key = $obj->ClassName . '_' . $obj->ID;
|
|
||||||
$this->extend('updateCacheKey', $obj, $key);
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
}
|
|
212
src/Search/Services/SearchableService.php
Normal file
212
src/Search/Services/SearchableService.php
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Search\Services;
|
||||||
|
|
||||||
|
use SilverStripe\Core\Config\Configurable;
|
||||||
|
use SilverStripe\Core\Extensible;
|
||||||
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Security\Member;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a DataObject is publicly viewable, thus able to be added to or retrieved from a publicly searchable index.
|
||||||
|
* Results are cached because these checks may be run multiple times, as there a few different code paths that search
|
||||||
|
* results might follow in real-world search implementations.
|
||||||
|
*/
|
||||||
|
class SearchableService
|
||||||
|
{
|
||||||
|
|
||||||
|
use Injectable;
|
||||||
|
use Extensible;
|
||||||
|
use Configurable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip the canView() check at a class level to increase performance of search reindex.
|
||||||
|
* Be careful as this may lead to content showing in search results that should not be there such as non-public,
|
||||||
|
* cms-user-only content. This may potentially happen via edge cases such as skipping checks where subclasses
|
||||||
|
* are involved.
|
||||||
|
*
|
||||||
|
* This has no effect on when search results as canView() must still be run there
|
||||||
|
*
|
||||||
|
* @var array namespaced classes to skip canView() check on search reindex
|
||||||
|
*/
|
||||||
|
private static $indexing_canview_exclude_classes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configurable value to index draft content. Default is true for better security.
|
||||||
|
*
|
||||||
|
* If you need to index draft content, then view README.md for instructions
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $variant_state_draft_excluded = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-persistant memory cache that only lasts the lifetime of the request
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $cache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the internal cache
|
||||||
|
*/
|
||||||
|
public function clearCache(): void
|
||||||
|
{
|
||||||
|
$this->cache = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to exclude a variant state
|
||||||
|
*
|
||||||
|
* @param array $state
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function variantStateExcluded(array $state): bool
|
||||||
|
{
|
||||||
|
if (self::config()->get('variant_state_draft_excluded') && $this->isDraftVariantState($state)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a state array represents a draft variant
|
||||||
|
*
|
||||||
|
* @param array $state
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isDraftVariantState(array $state): bool
|
||||||
|
{
|
||||||
|
$class = SearchVariantVersioned::class;
|
||||||
|
return isset($state[$class]) && $state[$class] == Versioned::DRAFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used during search reindex
|
||||||
|
*
|
||||||
|
* This is considered the primary layer of protection
|
||||||
|
*
|
||||||
|
* @param DataObject $obj
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isIndexable(DataObject $obj): bool
|
||||||
|
{
|
||||||
|
return $this->isSearchable($obj, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when retrieving search results
|
||||||
|
*
|
||||||
|
* This is considered the secondary layer of protection
|
||||||
|
*
|
||||||
|
* It's important to still have this layer in conjuction with the index layer as non-searchable results may be
|
||||||
|
* in the search index because:
|
||||||
|
* a) they were added to the index pre-fulltextsearch 3.7 and a reindex to purge old records was never run, OR
|
||||||
|
* b) the DataObject has a non-deterministic canView() check such as `return $date <= $dateOfIndex;`
|
||||||
|
*
|
||||||
|
* @param DataObject $obj
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isViewable(DataObject $obj): bool
|
||||||
|
{
|
||||||
|
return $this->isSearchable($obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and caches whether the given DataObject can be indexed. This is determined by two factors:
|
||||||
|
* - Whether the ShowInSearch property / getShowInSearch() method evaluates to true
|
||||||
|
* - Whether the canView method evaluates to true against an anonymous user (optional, can be disabled)
|
||||||
|
*
|
||||||
|
* @param DataObject $obj
|
||||||
|
* @param bool $indexing
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isSearchable(DataObject $obj, bool $indexing): bool
|
||||||
|
{
|
||||||
|
// check if is a valid DataObject that has been persisted to the database
|
||||||
|
if (is_null($obj) || !$obj->ID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $this->getCacheKey($obj, $indexing);
|
||||||
|
if (isset($this->cache[$key])) {
|
||||||
|
return $this->cache[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = true;
|
||||||
|
|
||||||
|
// ShowInSearch check
|
||||||
|
// This will also call $obj->getShowInSearch() if it exists
|
||||||
|
if (isset($obj->ShowInSearch) && !$obj->ShowInSearch) {
|
||||||
|
$value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// canView() checker
|
||||||
|
if ($value) {
|
||||||
|
$objClass = $obj->getClassName();
|
||||||
|
if ($indexing) {
|
||||||
|
// Anonymous member canView() for indexing
|
||||||
|
if (!$this->classSkipsCanViewCheck($objClass)) {
|
||||||
|
$value = Member::actAs(null, function () use ($obj) {
|
||||||
|
return $obj->canView();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Current member canView() check for retrieving search results
|
||||||
|
$value = $obj->canView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->extend('updateIsSearchable', $obj, $indexing, $value);
|
||||||
|
$this->cache[$key] = $value;
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DataObject $obj
|
||||||
|
* @param bool $indexing
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getCacheKey(DataObject $obj, bool $indexing): string
|
||||||
|
{
|
||||||
|
$type = $indexing ? 'indexing' : 'viewing';
|
||||||
|
// getUniqueKey() requires silverstripe/framework 4.6
|
||||||
|
$uniqueKey = '';
|
||||||
|
if (method_exists($obj, 'getUniqueKey')) {
|
||||||
|
try {
|
||||||
|
$uniqueKey = $obj->getUniqueKey();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$uniqueKey = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$uniqueKey) {
|
||||||
|
$uniqueKey = sprintf('%s-%s', $obj->ClassName, $obj->ID);
|
||||||
|
}
|
||||||
|
$key = sprintf('%s-%s', $type, $uniqueKey);
|
||||||
|
$this->extend('updateCacheKey', $obj, $indexing, $key);
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $class
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function classSkipsCanViewCheck(string $class): bool
|
||||||
|
{
|
||||||
|
$skipClasses = self::config()->get('indexing_canview_exclude_classes') ?? [];
|
||||||
|
if (empty($skipClasses)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (in_array($class, $skipClasses)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach ($skipClasses as $skipClass) {
|
||||||
|
if (in_array($skipClass, class_parents($class))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ use SilverStripe\Forms\FormAction;
|
|||||||
use SilverStripe\Forms\TextField;
|
use SilverStripe\Forms\TextField;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\View\ArrayData;
|
use SilverStripe\View\ArrayData;
|
||||||
@ -83,13 +83,13 @@ class SearchForm extends Form
|
|||||||
$index = $indexClass::singleton();
|
$index = $indexClass::singleton();
|
||||||
$results = $index->search($query, -1, -1, $params);
|
$results = $index->search($query, -1, -1, $params);
|
||||||
|
|
||||||
$indexableService = IndexableService::singleton();
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
// filter by permission
|
// filter by permission
|
||||||
if ($results) {
|
if ($results) {
|
||||||
foreach ($results->Matches as $match) {
|
foreach ($results->Matches as $match) {
|
||||||
/** @var DataObject $match */
|
/** @var DataObject $match */
|
||||||
if (!$indexableService->isIndexable($match)) {
|
if (!$searchableService->isViewable($match)) {
|
||||||
$results->Matches->remove($match);
|
$results->Matches->remove($match);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,8 @@ namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers;
|
|||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use SilverStripe\Core\Environment;
|
use SilverStripe\Core\Environment;
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
use SilverStripe\FullTextSearch\Solr\Solr;
|
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||||
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
@ -12,6 +13,7 @@ use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
|||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\DataList;
|
use SilverStripe\ORM\DataList;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for re-indexing of solr content
|
* Base class for re-indexing of solr content
|
||||||
@ -41,6 +43,8 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
$taskName,
|
$taskName,
|
||||||
$classes = null
|
$classes = null
|
||||||
) {
|
) {
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
// Filter classes for this index
|
// Filter classes for this index
|
||||||
$indexClasses = $this->getClassesForIndex($indexInstance, $classes);
|
$indexClasses = $this->getClassesForIndex($indexInstance, $classes);
|
||||||
|
|
||||||
@ -53,10 +57,12 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
$includeSubclasses = $options['include_children'];
|
$includeSubclasses = $options['include_children'];
|
||||||
|
|
||||||
foreach (SearchVariant::reindex_states($class, $includeSubclasses) as $state) {
|
foreach (SearchVariant::reindex_states($class, $includeSubclasses) as $state) {
|
||||||
|
if (!$searchableService->variantStateExcluded($state)) {
|
||||||
$this->processVariant($logger, $indexInstance, $state, $class, $includeSubclasses, $batchSize, $taskName);
|
$this->processVariant($logger, $indexInstance, $state, $class, $includeSubclasses, $batchSize, $taskName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get valid classes and options for an index with an optional filter
|
* Get valid classes and options for an index with an optional filter
|
||||||
@ -238,13 +244,12 @@ abstract class SolrReindexBase implements SolrReindexHandler
|
|||||||
$items = $items->filter('ClassName', $class);
|
$items = $items->filter('ClassName', $class);
|
||||||
}
|
}
|
||||||
|
|
||||||
$indexableService = IndexableService::singleton();
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
// ShowInSearch filter
|
// Filter out objects that must not be indexed
|
||||||
// we cannot use $items->remove($item), as that deletes the record from the database
|
|
||||||
$idsToRemove = [];
|
$idsToRemove = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (!$indexableService->isIndexable($item)) {
|
if (!$searchableService->isIndexable($item)) {
|
||||||
$idsToRemove[] = $item->ID;
|
$idsToRemove[] = $item->ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
|
|||||||
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range;
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range;
|
||||||
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant_Caller;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant_Caller;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
||||||
@ -778,15 +778,14 @@ abstract class SolrIndex extends SearchIndex
|
|||||||
\Apache_Solr_Service::METHOD_POST
|
\Apache_Solr_Service::METHOD_POST
|
||||||
);
|
);
|
||||||
|
|
||||||
$indexableService = IndexableService::singleton();
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
$results = new ArrayList();
|
$results = new ArrayList();
|
||||||
if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
|
if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
|
||||||
foreach ($res->response->docs as $doc) {
|
foreach ($res->response->docs as $doc) {
|
||||||
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
// Filter out any results previously added to the solr index where ShowInSearch == false
|
if (!$searchableService->isViewable($result)) {
|
||||||
if (!$indexableService->isIndexable($result)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use SilverStripe\Core\Config\Config;
|
|||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessor_QueuedJobService;
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessor_QueuedJobService;
|
||||||
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Index;
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Index;
|
||||||
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Object;
|
use SilverStripe\FullTextSearch\Tests\BatchedProcessorTest\BatchedProcessorTest_Object;
|
||||||
@ -120,6 +121,9 @@ class BatchedProcessorTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testBatching()
|
public function testBatching()
|
||||||
{
|
{
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', [SiteTree::class]);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
$index = singleton(BatchedProcessorTest_Index::class);
|
$index = singleton(BatchedProcessorTest_Index::class);
|
||||||
$index->reset();
|
$index->reset();
|
||||||
$processor = $this->generateDirtyIds();
|
$processor = $this->generateDirtyIds();
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace SilverStripe\FullTextSearch\Tests;
|
|
||||||
|
|
||||||
use SilverStripe\CMS\Model\SiteTree;
|
|
||||||
use SilverStripe\Dev\SapphireTest;
|
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
|
||||||
|
|
||||||
class IndexableServiceTest extends SapphireTest
|
|
||||||
{
|
|
||||||
protected $usesDatabase = true;
|
|
||||||
|
|
||||||
public function setup()
|
|
||||||
{
|
|
||||||
parent::setup();
|
|
||||||
IndexableService::singleton()->clearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIsIndexable()
|
|
||||||
{
|
|
||||||
$indexableService = IndexableService::singleton();
|
|
||||||
|
|
||||||
$page = SiteTree::create();
|
|
||||||
$page->CanViewType = 'Anyone';
|
|
||||||
$page->ShowInSearch = 1;
|
|
||||||
$page->write();
|
|
||||||
$this->assertTrue($indexableService->isIndexable($page));
|
|
||||||
|
|
||||||
$page = SiteTree::create();
|
|
||||||
$page->CanViewType = 'Anyone';
|
|
||||||
$page->ShowInSearch = 0;
|
|
||||||
$page->write();
|
|
||||||
$this->assertFalse($indexableService->isIndexable($page));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testClearCache()
|
|
||||||
{
|
|
||||||
$indexableService = IndexableService::singleton();
|
|
||||||
|
|
||||||
$page = SiteTree::create();
|
|
||||||
$page->CanViewType = 'Anyone';
|
|
||||||
$page->ShowInSearch = 0;
|
|
||||||
$page->write();
|
|
||||||
$this->assertFalse($indexableService->isIndexable($page));
|
|
||||||
|
|
||||||
// test the results are cached (expect stale result)
|
|
||||||
$page->ShowInSearch = 1;
|
|
||||||
$page->write();
|
|
||||||
$this->assertFalse($indexableService->isIndexable($page));
|
|
||||||
|
|
||||||
// after clearing cache, expect fresh result
|
|
||||||
$indexableService->clearCache();
|
|
||||||
$this->assertTrue($indexableService->isIndexable($page));
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ use SilverStripe\Dev\SapphireTest;
|
|||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
||||||
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne;
|
||||||
@ -49,6 +50,9 @@ class SearchUpdaterTest extends SapphireTest
|
|||||||
|
|
||||||
public function testHasOneHook()
|
public function testHasOneHook()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SearchUpdaterTest_Container::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$hasOne = new SearchUpdaterTest_HasOne();
|
$hasOne = new SearchUpdaterTest_HasOne();
|
||||||
$hasOne->write();
|
$hasOne->write();
|
||||||
|
|
||||||
@ -127,6 +131,9 @@ class SearchUpdaterTest extends SapphireTest
|
|||||||
|
|
||||||
public function testHasManyHook()
|
public function testHasManyHook()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SearchUpdaterTest_Container::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$container1 = new SearchUpdaterTest_Container();
|
$container1 = new SearchUpdaterTest_Container();
|
||||||
$container1->write();
|
$container1->write();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use SilverStripe\CMS\Model\SiteTree;
|
|||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex_Recording;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Index;
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Index;
|
||||||
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
@ -45,6 +46,9 @@ class SearchVariantVersionedTest extends SapphireTest
|
|||||||
public function testPublishing()
|
public function testPublishing()
|
||||||
{
|
{
|
||||||
// Check that write updates Stage
|
// Check that write updates Stage
|
||||||
|
$classesToSkip = [SearchVariantVersionedTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
||||||
$item->write();
|
$item->write();
|
||||||
|
120
tests/SearchableServiceTest.php
Normal file
120
tests/SearchableServiceTest.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\FullTextSearch\Tests;
|
||||||
|
|
||||||
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
|
use SilverStripe\Security\Member;
|
||||||
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
|
class SearchableServiceTest extends SapphireTest
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $usesDatabase = true;
|
||||||
|
|
||||||
|
public function setup()
|
||||||
|
{
|
||||||
|
parent::setup();
|
||||||
|
SearchableService::singleton()->clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsIndexable()
|
||||||
|
{
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', [SiteTree::class]);
|
||||||
|
|
||||||
|
Member::actAs(null, function () {
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'Anyone';
|
||||||
|
$page->ShowInSearch = 1;
|
||||||
|
$page->write();
|
||||||
|
$this->assertTrue($searchableService->isIndexable($page));
|
||||||
|
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'Anyone';
|
||||||
|
$page->ShowInSearch = 0;
|
||||||
|
$page->write();
|
||||||
|
$this->assertFalse($searchableService->isIndexable($page));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsViewable()
|
||||||
|
{
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
|
||||||
|
Member::actAs(null, function () {
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'Anyone';
|
||||||
|
$page->ShowInSearch = 1;
|
||||||
|
$page->write();
|
||||||
|
$this->assertTrue($searchableService->isViewable($page));
|
||||||
|
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'LoggedInUsers';
|
||||||
|
$page->ShowInSearch = 1;
|
||||||
|
$page->write();
|
||||||
|
$this->assertFalse($searchableService->isViewable($page));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearCache()
|
||||||
|
{
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', [SiteTree::class]);
|
||||||
|
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'Anyone';
|
||||||
|
$page->ShowInSearch = 0;
|
||||||
|
$page->write();
|
||||||
|
$this->assertFalse($searchableService->isIndexable($page));
|
||||||
|
|
||||||
|
// test the results are cached (expect stale result)
|
||||||
|
$page->ShowInSearch = 1;
|
||||||
|
$page->write();
|
||||||
|
$this->assertFalse($searchableService->isIndexable($page));
|
||||||
|
|
||||||
|
// after clearing cache, expect fresh result
|
||||||
|
$searchableService->clearCache();
|
||||||
|
$this->assertTrue($searchableService->isIndexable($page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSkipIndexingCanViewCheck()
|
||||||
|
{
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
$page = SiteTree::create();
|
||||||
|
$page->CanViewType = 'LoggedInUsers';
|
||||||
|
$page->ShowInSearch = 1;
|
||||||
|
$page->write();
|
||||||
|
$this->assertFalse($searchableService->isIndexable($page));
|
||||||
|
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', [SiteTree::class]);
|
||||||
|
$searchableService->clearCache();
|
||||||
|
$this->assertTrue($searchableService->isIndexable($page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVariantStateExcluded()
|
||||||
|
{
|
||||||
|
$searchableService = SearchableService::singleton();
|
||||||
|
$variantStateDraft = [SearchVariantVersioned::class => Versioned::DRAFT];
|
||||||
|
$variantStateLive = [SearchVariantVersioned::class => Versioned::LIVE];
|
||||||
|
|
||||||
|
// default variant_state_draft_excluded = true
|
||||||
|
$this->assertTrue($searchableService->variantStateExcluded($variantStateDraft));
|
||||||
|
$this->assertFalse($searchableService->variantStateExcluded($variantStateLive));
|
||||||
|
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
$this->assertFalse($searchableService->variantStateExcluded($variantStateDraft));
|
||||||
|
$this->assertFalse($searchableService->variantStateExcluded($variantStateLive));
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ use SilverStripe\Dev\SapphireTest;
|
|||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateImmediateProcessor;
|
||||||
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
use SilverStripe\FullTextSearch\Search\Processors\SearchUpdateProcessor;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
@ -107,6 +108,10 @@ class SolrIndexSubsitesTest extends SapphireTest
|
|||||||
|
|
||||||
public function testPublishing()
|
public function testPublishing()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SiteTree::class, File::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
// Setup mocks
|
// Setup mocks
|
||||||
$serviceMock = $this->getServiceMock();
|
$serviceMock = $this->getServiceMock();
|
||||||
self::$index->setService($serviceMock);
|
self::$index->setService($serviceMock);
|
||||||
|
@ -4,7 +4,6 @@ namespace SilverStripe\FullTextSearch\Tests;
|
|||||||
|
|
||||||
use Apache_Solr_Document;
|
use Apache_Solr_Document;
|
||||||
use Page;
|
use Page;
|
||||||
use SebastianBergmann\Version;
|
|
||||||
use SilverStripe\Assets\File;
|
use SilverStripe\Assets\File;
|
||||||
use SilverStripe\CMS\Model\SiteTree;
|
use SilverStripe\CMS\Model\SiteTree;
|
||||||
use SilverStripe\Core\Config\Config;
|
use SilverStripe\Core\Config\Config;
|
||||||
@ -14,10 +13,9 @@ use SilverStripe\Core\Kernel;
|
|||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
|
||||||
use SilverStripe\FullTextSearch\Search\Services\IndexableService;
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites;
|
||||||
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
|
use SilverStripe\FullTextSearch\Solr\Services\Solr3Service;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container;
|
||||||
@ -34,7 +32,6 @@ use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_ShowInSearchIn
|
|||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo;
|
||||||
use SilverStripe\ORM\DataObject;
|
|
||||||
use SilverStripe\Subsites\Model\Subsite;
|
use SilverStripe\Subsites\Model\Subsite;
|
||||||
use SilverStripe\Versioned\Versioned;
|
use SilverStripe\Versioned\Versioned;
|
||||||
|
|
||||||
@ -409,8 +406,10 @@ class SolrIndexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testShowInSearch()
|
public function testShowInSearch()
|
||||||
{
|
{
|
||||||
$defaultMode = Versioned::get_reading_mode();
|
// allow anonymous users to assess draft-only content to pass canView() check (will auto-reset for next test)
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
||||||
->setMethods(['addDocument', 'deleteById'])
|
->setMethods(['addDocument', 'deleteById'])
|
||||||
@ -508,10 +507,106 @@ class SolrIndexTest extends SapphireTest
|
|||||||
})]
|
})]
|
||||||
);
|
);
|
||||||
|
|
||||||
IndexableService::singleton()->clearCache();
|
SearchableService::singleton()->clearCache();
|
||||||
|
SearchUpdater::flush_dirty_indexes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that canView() check is used to exclude DataObjects from being added to the index
|
||||||
|
*
|
||||||
|
* Note: this code path that really being tested here is SearchUpdateProcessor->prepareIndexes()
|
||||||
|
* This code path is used for 'inlet' filtering on CMS->save()
|
||||||
|
* The results of this will show-up in SolrIndex->_addAs()
|
||||||
|
*/
|
||||||
|
public function testCanView()
|
||||||
|
{
|
||||||
|
// allow anonymous users to assess draft-only content to pass canView() check (will auto-reset for next test)
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
|
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
||||||
|
->setMethods(['addDocument', 'deleteById'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$index = new SolrIndexTest_ShowInSearchIndex();
|
||||||
|
$index->setService($serviceMock);
|
||||||
|
FullTextSearch::force_index_list($index);
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$pageA = new Page();
|
||||||
|
$pageA->Title = 'Test Page Anyone';
|
||||||
|
$pageA->CanViewType = 'Anyone';
|
||||||
|
$pageA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$page = new Page();
|
||||||
|
$page->Title = 'Test Page LoggedInUsers';
|
||||||
|
$page->CanViewType = 'LoggedInUsers';
|
||||||
|
$page->write();
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$fileA = new File();
|
||||||
|
$fileA->Title = 'Test File Anyone';
|
||||||
|
$fileA->CanViewType = 'Anyone';
|
||||||
|
$fileA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$file = new File();
|
||||||
|
$file->Title = 'Test File LoggedInUsers';
|
||||||
|
$file->CanViewType = 'LoggedInUsers';
|
||||||
|
$file->write();
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$objOneA = new SolrIndexTest_MyDataObjectOne();
|
||||||
|
$objOneA->Title = 'Test MyDataObjectOne true';
|
||||||
|
$objOneA->ShowInSearch = true;
|
||||||
|
$objOneA->CanViewValue = true;
|
||||||
|
$objOneA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$objOne = new SolrIndexTest_MyDataObjectOne();
|
||||||
|
$objOne->Title = 'Test MyDataObjectOne false';
|
||||||
|
$objOne->ShowInSearch = true;
|
||||||
|
$objOne->CanViewValue = false;
|
||||||
|
$objOne->write();
|
||||||
|
|
||||||
|
$callback = function (Apache_Solr_Document $doc) use ($pageA, $fileA, $objOneA): bool {
|
||||||
|
$validKeys = [
|
||||||
|
Page::class . $pageA->ID,
|
||||||
|
File::class . $fileA->ID,
|
||||||
|
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID
|
||||||
|
];
|
||||||
|
return in_array($this->createSolrDocKey($doc), $validKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
$serviceMock
|
||||||
|
->expects($this->exactly(3))
|
||||||
|
->method('addDocument')
|
||||||
|
->withConsecutive(
|
||||||
|
[$this->callback($callback)],
|
||||||
|
[$this->callback($callback)],
|
||||||
|
[$this->callback($callback)]
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is what actually triggers all the solr stuff
|
||||||
SearchUpdater::flush_dirty_indexes();
|
SearchUpdater::flush_dirty_indexes();
|
||||||
|
|
||||||
Versioned::set_reading_mode($defaultMode);
|
// delete a solr doc by setting ShowInSearch to false
|
||||||
|
$pageA->ShowInSearch = false;
|
||||||
|
$pageA->write();
|
||||||
|
|
||||||
|
$serviceMock
|
||||||
|
->expects($this->exactly(1))
|
||||||
|
->method('deleteById')
|
||||||
|
->withConsecutive(
|
||||||
|
[$this->callback(function (string $docID) use ($pageA): bool {
|
||||||
|
return strpos($docID, $pageA->ID . '-' . SiteTree::class) !== false;
|
||||||
|
})]
|
||||||
|
);
|
||||||
|
|
||||||
|
SearchableService::singleton()->clearCache();
|
||||||
|
SearchUpdater::flush_dirty_indexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createSolrDocKey(Apache_Solr_Document $doc)
|
protected function createSolrDocKey(Apache_Solr_Document $doc)
|
||||||
|
@ -9,8 +9,14 @@ class SolrIndexTest_MyDataObjectOne extends DataObject implements TestOnly
|
|||||||
{
|
{
|
||||||
private static $db = [
|
private static $db = [
|
||||||
'Title' => 'Varchar(255)',
|
'Title' => 'Varchar(255)',
|
||||||
'ShowInSearch' => 'Boolean'
|
'ShowInSearch' => 'Boolean',
|
||||||
|
'CanViewValue' => 'Boolean(true)',
|
||||||
];
|
];
|
||||||
|
|
||||||
private static $table_name = 'SolrIndexTestMyDataObjectOne';
|
private static $table_name = 'SolrIndexTestMyDataObjectOne';
|
||||||
|
|
||||||
|
public function canView($member = null)
|
||||||
|
{
|
||||||
|
return $this->CanViewValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use Apache_Solr_Document;
|
|||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\Core\Config\Config;
|
use SilverStripe\Core\Config\Config;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
|
||||||
@ -112,6 +113,10 @@ class SolrIndexVersionedTest extends SapphireTest
|
|||||||
|
|
||||||
public function testPublishing()
|
public function testPublishing()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SearchVariantVersionedTest_Item::class, SolrIndexVersionedTest_Object::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
// Check that write updates Stage
|
// Check that write updates Stage
|
||||||
Versioned::set_stage(Versioned::DRAFT);
|
Versioned::set_stage(Versioned::DRAFT);
|
||||||
|
|
||||||
@ -166,6 +171,10 @@ class SolrIndexVersionedTest extends SapphireTest
|
|||||||
|
|
||||||
public function testDelete()
|
public function testDelete()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SearchVariantVersionedTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
// Delete the live record (not the stage)
|
// Delete the live record (not the stage)
|
||||||
Versioned::set_stage(Versioned::DRAFT);
|
Versioned::set_stage(Versioned::DRAFT);
|
||||||
|
|
||||||
|
@ -6,12 +6,14 @@ use SilverStripe\Core\Config\Config;
|
|||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler;
|
||||||
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexQueuedHandler;
|
use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexQueuedHandler;
|
||||||
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexGroupQueuedJob;
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexGroupQueuedJob;
|
||||||
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexQueuedJob;
|
use SilverStripe\FullTextSearch\Solr\Reindex\Jobs\SolrReindexQueuedJob;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrReindexQueuedTest\SolrReindexQueuedTest_Service;
|
use SilverStripe\FullTextSearch\Tests\SolrReindexQueuedTest\SolrReindexQueuedTest_Service;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index;
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item;
|
use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item;
|
||||||
@ -135,6 +137,9 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testReindexSegmentsGroups()
|
public function testReindexSegmentsGroups()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SolrReindexTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$this->createDummyData(18);
|
$this->createDummyData(18);
|
||||||
|
|
||||||
// Deletes are performed in the main task prior to individual groups being processed
|
// Deletes are performed in the main task prior to individual groups being processed
|
||||||
@ -194,6 +199,9 @@ class SolrReindexQueuedTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testRunGroup()
|
public function testRunGroup()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SolrReindexTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$this->createDummyData(18);
|
$this->createDummyData(18);
|
||||||
|
|
||||||
// Just do what the SolrReindexQueuedJob would do to create each sub
|
// Just do what the SolrReindexQueuedJob would do to create each sub
|
||||||
|
@ -10,6 +10,7 @@ use SilverStripe\Core\Config\Config;
|
|||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
use SilverStripe\FullTextSearch\Search\FullTextSearch;
|
||||||
|
use SilverStripe\FullTextSearch\Search\Services\SearchableService;
|
||||||
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
|
||||||
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
|
||||||
@ -18,6 +19,7 @@ use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexImmediateHandle
|
|||||||
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
use SilverStripe\FullTextSearch\Solr\Services\Solr4Service;
|
||||||
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
use SilverStripe\FullTextSearch\Solr\Services\SolrService;
|
||||||
use SilverStripe\FullTextSearch\Solr\Tasks\Solr_Reindex;
|
use SilverStripe\FullTextSearch\Solr\Tasks\Solr_Reindex;
|
||||||
|
use SilverStripe\FullTextSearch\Tests\SearchVariantVersionedTest\SearchVariantVersionedTest_Item;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo;
|
||||||
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage;
|
use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage;
|
||||||
@ -235,6 +237,9 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testRunGroup()
|
public function testRunGroup()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SolrReindexTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$this->service->method('deleteByQuery')
|
$this->service->method('deleteByQuery')
|
||||||
->with('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")');
|
->with('+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=2 u=2}mod(ID, 6)" +(_testvariant:"1")');
|
||||||
|
|
||||||
@ -266,6 +271,9 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testRunAllGroups()
|
public function testRunAllGroups()
|
||||||
{
|
{
|
||||||
|
$classesToSkip = [SolrReindexTest_Item::class];
|
||||||
|
Config::modify()->set(SearchableService::class, 'indexing_canview_exclude_classes', $classesToSkip);
|
||||||
|
|
||||||
$this->service->method('deleteByQuery')
|
$this->service->method('deleteByQuery')
|
||||||
->withConsecutive(
|
->withConsecutive(
|
||||||
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'],
|
['+(ClassHierarchy:' . SolrReindexTest_Item::class . ') +_query_:"{!frange l=0 u=0}mod(ID, 6)" +(_testvariant:"1")'],
|
||||||
@ -305,8 +313,10 @@ class SolrReindexTest extends SapphireTest
|
|||||||
*/
|
*/
|
||||||
public function testShowInSearch()
|
public function testShowInSearch()
|
||||||
{
|
{
|
||||||
$defaultMode = Versioned::get_reading_mode();
|
// allow anonymous users to assess draft-only content to pass canView() check (will auto-reset for next test)
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
// will get added
|
// will get added
|
||||||
$pageA = new Page();
|
$pageA = new Page();
|
||||||
@ -394,8 +404,89 @@ class SolrReindexTest extends SapphireTest
|
|||||||
$handler->runGroup($logger, $index, $state, SiteTree::class, 1, 0);
|
$handler->runGroup($logger, $index, $state, SiteTree::class, 1, 0);
|
||||||
$handler->runGroup($logger, $index, $state, File::class, 1, 0);
|
$handler->runGroup($logger, $index, $state, File::class, 1, 0);
|
||||||
$handler->runGroup($logger, $index, $state, SolrIndexTest_MyDataObjectOne::class, 1, 0);
|
$handler->runGroup($logger, $index, $state, SolrIndexTest_MyDataObjectOne::class, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
Versioned::set_reading_mode($defaultMode);
|
/**
|
||||||
|
* Test that CanView filtering is working correctly
|
||||||
|
*/
|
||||||
|
public function testCanView()
|
||||||
|
{
|
||||||
|
// allow anonymous users to assess draft-only content to pass canView() check (will auto-reset for next test)
|
||||||
|
Versioned::set_draft_site_secured(false);
|
||||||
|
Versioned::set_reading_mode('Stage.' . Versioned::DRAFT);
|
||||||
|
Config::modify()->set(SearchableService::class, 'variant_state_draft_excluded', false);
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$pageA = new Page();
|
||||||
|
$pageA->Title = 'Test Page Anyone';
|
||||||
|
$pageA->CanViewType = 'Anyone';
|
||||||
|
$pageA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$page = new Page();
|
||||||
|
$page->Title = 'Test Page LoggedInUsers';
|
||||||
|
$page->CanViewType = 'LoggedInUsers';
|
||||||
|
$page->write();
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$fileA = new File();
|
||||||
|
$fileA->Title = 'Test File Anyone';
|
||||||
|
$fileA->CanViewType = 'Anyone';
|
||||||
|
$fileA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$file = new File();
|
||||||
|
$file->Title = 'Test File LoggedInUsers';
|
||||||
|
$file->CanViewType = 'LoggedInUsers';
|
||||||
|
$file->write();
|
||||||
|
|
||||||
|
// will get added
|
||||||
|
$objOneA = new SolrIndexTest_MyDataObjectOne();
|
||||||
|
$objOneA->Title = 'Test MyDataObjectOne true';
|
||||||
|
$objOneA->CanViewValue = true;
|
||||||
|
$objOneA->ShowInSearch = true;
|
||||||
|
$objOneA->write();
|
||||||
|
|
||||||
|
// will get filtered out
|
||||||
|
$objOne = new SolrIndexTest_MyDataObjectOne();
|
||||||
|
$objOne->Title = 'Test MyDataObjectOne false';
|
||||||
|
$objOne->CanViewValue = false;
|
||||||
|
$objOneA->ShowInSearch = true;
|
||||||
|
$objOne->write();
|
||||||
|
|
||||||
|
$serviceMock = $this->getMockBuilder(Solr4Service::class)
|
||||||
|
->setMethods(['addDocument', 'deleteByQuery'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$index = new SolrIndexTest_ShowInSearchIndex();
|
||||||
|
$index->setService($serviceMock);
|
||||||
|
FullTextSearch::force_index_list($index);
|
||||||
|
|
||||||
|
$callback = function (Apache_Solr_Document $doc) use ($pageA, $fileA, $objOneA): bool {
|
||||||
|
$validKeys = [
|
||||||
|
Page::class . $pageA->ID,
|
||||||
|
File::class . $fileA->ID,
|
||||||
|
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID,
|
||||||
|
];
|
||||||
|
$solrDocKey = $this->createSolrDocKey($doc);
|
||||||
|
return in_array($this->createSolrDocKey($doc), $validKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
$serviceMock
|
||||||
|
->expects($this->exactly(3))
|
||||||
|
->method('addDocument')
|
||||||
|
->withConsecutive(
|
||||||
|
[$this->callback($callback)],
|
||||||
|
[$this->callback($callback)],
|
||||||
|
[$this->callback($callback)]
|
||||||
|
);
|
||||||
|
|
||||||
|
$logger = new SolrReindexTest_RecordingLogger();
|
||||||
|
$state = [SearchVariantVersioned::class => Versioned::DRAFT];
|
||||||
|
$handler = Injector::inst()->get(SolrReindexImmediateHandler::class);
|
||||||
|
$handler->runGroup($logger, $index, $state, SiteTree::class, 1, 0);
|
||||||
|
$handler->runGroup($logger, $index, $state, File::class, 1, 0);
|
||||||
|
$handler->runGroup($logger, $index, $state, SolrIndexTest_MyDataObjectOne::class, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createSolrDocKey(Apache_Solr_Document $doc)
|
protected function createSolrDocKey(Apache_Solr_Document $doc)
|
||||||
|
Loading…
Reference in New Issue
Block a user