diff --git a/.travis.yml b/.travis.yml index 5c53458..9dce331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,15 @@ env: matrix: include: - - php: 5.6 + - php: 7.1 env: DB=MYSQL INSTALLER_VERSION=4.2.x-dev PHPCS_TEST=1 PHPUNIT_TEST=1 - - php: 7.0 + - php: 7.1 env: DB=PGSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_TEST=1 - - php: 7.1 - env: DB=MYSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_COVERAGE_TEST=1 - - php: 7.1 - env: DB=MYSQL INSTALLER_VERSION=4.4.x-dev PHPUNIT_TEST=1 SUBSITES=1 - php: 7.2 + env: DB=MYSQL INSTALLER_VERSION=4.3.x-dev PHPUNIT_COVERAGE_TEST=1 + - php: 7.3 + env: DB=MYSQL INSTALLER_VERSION=4.4.x-dev PHPUNIT_TEST=1 SUBSITES=1 + - php: 7.3 env: DB=MYSQL INSTALLER_VERSION=4.x-dev PHPUNIT_TEST=1 before_script: diff --git a/composer.json b/composer.json index 364ae0c..90efa8f 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ } ], "require": { + "php": ">=7.1", "silverstripe/framework": "^4.0", "monolog/monolog": "~1.15", "ptcinc/solr-php-client": "^1.0", diff --git a/docs/en/03_configuration.md b/docs/en/03_configuration.md index b11d65e..f95970d 100644 --- a/docs/en/03_configuration.md +++ b/docs/en/03_configuration.md @@ -147,7 +147,7 @@ to render properly in the search results: * `Link()` needs to return the URL to follow from the search results to actually view the object. * `Name` (as a DB field) will be used as the result title. * `Abstract` (as a DB field) will show under the search result title. -* `getShowInSearch()` is required to get the record to show in search, since all results are filtered by `ShowInSearch`. +* `ShowInSearch` (as a DB field) or `getShowInSearch()` is recommended to allow the optional exclusion of DataObjects from being added to the search index. If omitted, then all DataObjects of this type will be added to the search index. So with that, you can add your class to your index: @@ -172,6 +172,28 @@ you've just created, this will add `SearchableDataObject` and the text fields it on the site using `MySolrSearchIndex->search()`, the `SearchableDataObject` results will show alongside normal `Page` results. +### ShowInSearch and getShowInSearch() filtering + +The fulltextsearch module checks the value of `ShowInSearch` on each object it operates against, and if this evaluates +to `false`, the object is excluded from the index / results. You can implement a `getShowInSearch` method on your +DataObject to control the way this is computed. This check happens in two places: + +a) When attempting to add the object to the search index (or update it) +b) Before returning results from the search index. Note: this only applies to Solr 4 implementations. + +The second check is an additional layer to ensure that a result is excluded if the evaluated response changes between +index and query time. For example, a getShowInSearch() implementation that filters out objects after a certain date +might return `true` when the object is added to the index, but `false` when a user later performs a search. + +This filtering is applied to all Page (SiteTree) and File records since they have a ShowInSearch database column. +This will also be applied to any DataObjects that have a ShowInSearch database column or a getShowInSearch() function. + +This is a compulsory check and there is no opt-out available. + +Note: If you implement a custom getShowInSearch() method on a Page, the database column 'ShowInSearch' will not be used +and the 'Show In Search?' settings in the CMS admin found under Page > Settings will no longer work. Either incorporate +the ShowInSearch column in your getShowInSearch() logic, or remove the field from the CMS to minimise confusion. + ## Solr dev tasks There are two dev/tasks that are central to the operation of the module - `Solr_Configure` and `Solr_Reindex`. You can access these through the web, or via CLI. Running via the web will return "quiet" output by default, but you can increase verbosity by adding `?verbose=1` to the `dev/tasks` URL; CLI will return verbose output by default. diff --git a/src/Search/Indexes/SearchIndex.php b/src/Search/Indexes/SearchIndex.php index ea8facd..58e7827 100644 --- a/src/Search/Indexes/SearchIndex.php +++ b/src/Search/Indexes/SearchIndex.php @@ -619,19 +619,14 @@ abstract class SearchIndex extends ViewableData $tableName = DataObject::getSchema()->tableName($step['class']); if ($step['through'] == 'has_one') { - $sql = new SQLSelect('"ID"', '"' . $tableName . '"', '"' . $step['foreignkey'] . '" IN (' . implode(',', $ids) . ')'); - singleton($step['class'])->extend('augmentSQL', $sql); - - $ids = $sql->execute()->column(); + $ids = DataObject::get($step['class']) + ->filter($step['foreignkey'], $ids) + ->column('ID'); } elseif ($step['through'] == 'has_many') { - // Use TableName for queries - $otherTableName = DataObject::getSchema()->tableName($step['otherclass']); - - $sql = new SQLSelect('"' . $tableName . '"."ID"', '"' . $tableName . '"', '"' . $otherTableName . '"."ID" IN (' . implode(',', $ids) . ')'); - $sql->addInnerJoin($otherTableName, '"' . $tableName . '"."ID" = "' . $otherTableName . '"."' . $step['foreignkey'] . '"'); - singleton($step['class'])->extend('augmentSQL', $sql); - - $ids = $sql->execute()->column(); + // foreignkey identifies a has_one column on the model linked via the has_many relation + $ids = DataObject::get($step['otherclass']) + ->filter('ID', $ids) + ->column($step['foreignkey']); } if (empty($ids)) { diff --git a/src/Search/Processors/SearchUpdateProcessor.php b/src/Search/Processors/SearchUpdateProcessor.php index 6c27f1d..de64dfc 100644 --- a/src/Search/Processors/SearchUpdateProcessor.php +++ b/src/Search/Processors/SearchUpdateProcessor.php @@ -2,6 +2,7 @@ namespace SilverStripe\FullTextSearch\Search\Processors; +use SilverStripe\FullTextSearch\Search\Services\IndexableService; use SilverStripe\ORM\DataObject; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; use SilverStripe\FullTextSearch\Search\FullTextSearch; @@ -31,7 +32,7 @@ abstract class SearchUpdateProcessor * @var array */ protected $dirty; - + public function __construct() { $this->dirty = array(); @@ -59,7 +60,7 @@ abstract class SearchUpdateProcessor $this->dirty[$base] = $forclass; } - + /** * Generates the list of indexes to process for the dirty items * @@ -71,6 +72,7 @@ abstract class SearchUpdateProcessor $dirtyIndexes = array(); $dirty = $this->getSource(); $indexes = FullTextSearch::get_indexes(); + $indexableService = IndexableService::singleton(); foreach ($dirty as $base => $statefulids) { if (!$statefulids) { continue; @@ -87,7 +89,12 @@ abstract class SearchUpdateProcessor foreach ($objs as $obj) { foreach ($ids[$obj->ID] as $index) { if (!$indexes[$index]->variantStateExcluded($state)) { - $indexes[$index]->add($obj); + // Remove any existing records from index if ShowInSearch is changed to false + if (!$indexableService->isIndexable($obj)) { + $indexes[$index]->delete($base, $obj->ID, $state); + } else { + $indexes[$index]->add($obj); + } $dirtyIndexes[$index] = $indexes[$index]; } } @@ -105,11 +112,11 @@ abstract class SearchUpdateProcessor } } } - + SearchVariant::activate_state($originalState); return $dirtyIndexes; } - + /** * Commits the specified index to the Solr service * @@ -120,7 +127,7 @@ abstract class SearchUpdateProcessor { return $index->commit() !== false; } - + /** * Gets the record data source to process * diff --git a/src/Search/Services/IndexableService.php b/src/Search/Services/IndexableService.php new file mode 100644 index 0000000..8f56129 --- /dev/null +++ b/src/Search/Services/IndexableService.php @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/Solr/Forms/SearchForm.php b/src/Solr/Forms/SearchForm.php index 140d592..8cd54f4 100644 --- a/src/Solr/Forms/SearchForm.php +++ b/src/Solr/Forms/SearchForm.php @@ -9,6 +9,7 @@ use SilverStripe\Forms\FormAction; use SilverStripe\Forms\TextField; use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; +use SilverStripe\FullTextSearch\Search\Services\IndexableService; use SilverStripe\FullTextSearch\Solr\SolrIndex; use SilverStripe\ORM\DataObject; use SilverStripe\View\ArrayData; @@ -82,11 +83,13 @@ class SearchForm extends Form $index = $indexClass::singleton(); $results = $index->search($query, -1, -1, $params); + $indexableService = IndexableService::singleton(); + // filter by permission if ($results) { foreach ($results->Matches as $match) { /** @var DataObject $match */ - if (!$match->canView()) { + if (!$indexableService->isIndexable($match)) { $results->Matches->remove($match); } } diff --git a/src/Solr/Reindex/Handlers/SolrReindexBase.php b/src/Solr/Reindex/Handlers/SolrReindexBase.php index 1f53829..b022734 100644 --- a/src/Solr/Reindex/Handlers/SolrReindexBase.php +++ b/src/Solr/Reindex/Handlers/SolrReindexBase.php @@ -4,6 +4,7 @@ namespace SilverStripe\FullTextSearch\Solr\Reindex\Handlers; use Psr\Log\LoggerInterface; use SilverStripe\Core\Environment; +use SilverStripe\FullTextSearch\Search\Services\IndexableService; use SilverStripe\FullTextSearch\Solr\Solr; use SilverStripe\FullTextSearch\Solr\SolrIndex; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; @@ -220,6 +221,7 @@ abstract class SolrReindexBase implements SolrReindexHandler { // Generate filtered list of local records $baseClass = DataObject::getSchema()->baseDataClass($class); + /** @var DataList $items */ $items = DataList::create($class) ->where(sprintf( '"%s"."ID" %% \'%d\' = \'%d\'', @@ -236,6 +238,20 @@ abstract class SolrReindexBase implements SolrReindexHandler $items = $items->filter('ClassName', $class); } + $indexableService = IndexableService::singleton(); + + // ShowInSearch filter + // we cannot use $items->remove($item), as that deletes the record from the database + $idsToRemove = []; + foreach ($items as $item) { + if (!$indexableService->isIndexable($item)) { + $idsToRemove[] = $item->ID; + } + } + if (!empty($idsToRemove)) { + sort($idsToRemove); + $items = $items->exclude(['ID' => $idsToRemove]); + } return $items; } diff --git a/src/Solr/SolrIndex.php b/src/Solr/SolrIndex.php index 44d59c4..89d6987 100644 --- a/src/Solr/SolrIndex.php +++ b/src/Solr/SolrIndex.php @@ -9,6 +9,7 @@ use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range; use SilverStripe\FullTextSearch\Search\SearchIntrospection; +use SilverStripe\FullTextSearch\Search\Services\IndexableService; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant_Caller; use SilverStripe\FullTextSearch\Solr\Services\SolrService; @@ -777,11 +778,18 @@ abstract class SolrIndex extends SearchIndex \Apache_Solr_Service::METHOD_POST ); + $indexableService = IndexableService::singleton(); + $results = new ArrayList(); if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) { foreach ($res->response->docs as $doc) { $result = DataObject::get_by_id($doc->ClassName, $doc->ID); if ($result) { + // Filter out any results previously added to the solr index where ShowInSearch == false + if (!$indexableService->isIndexable($result)) { + continue; + } + $results->push($result); // Add highlighting (optional) diff --git a/tests/IndexableServiceTest.php b/tests/IndexableServiceTest.php new file mode 100644 index 0000000..b7a39ee --- /dev/null +++ b/tests/IndexableServiceTest.php @@ -0,0 +1,55 @@ +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)); + } +} diff --git a/tests/SolrIndexTest.php b/tests/SolrIndexTest.php index 00d4121..e8f4efe 100644 --- a/tests/SolrIndexTest.php +++ b/tests/SolrIndexTest.php @@ -2,15 +2,24 @@ namespace SilverStripe\FullTextSearch\Tests; +use Apache_Solr_Document; +use Page; +use SebastianBergmann\Version; +use SilverStripe\Assets\File; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Environment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Kernel; use SilverStripe\Dev\SapphireTest; +use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\Queries\SearchQuery; +use SilverStripe\FullTextSearch\Search\Services\IndexableService; +use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Variants\SearchVariantSubsites; use SilverStripe\FullTextSearch\Solr\SolrIndex; use SilverStripe\FullTextSearch\Solr\Services\Solr3Service; +use SilverStripe\FullTextSearch\Solr\Services\Solr4Service; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_Container; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasOne; use SilverStripe\FullTextSearch\Tests\SearchUpdaterTest\SearchUpdaterTest_HasMany; @@ -21,10 +30,25 @@ use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_AmbiguousRelat use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_BoostedIndex; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex; use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_FakeIndex2; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_ShowInSearchIndex; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo; +use SilverStripe\ORM\DataObject; use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\Versioned\Versioned; class SolrIndexTest extends SapphireTest { + + protected $usesDatabase = true; + + protected static $extra_dataobjects = [ + SolrIndexTest_MyPage::class, + SolrIndexTest_MyDataObjectOne::class, + SolrIndexTest_MyDataObjectTwo::class, + ]; + public function testFieldDataHasOne() { $index = new SolrIndexTest_FakeIndex(); @@ -376,6 +400,125 @@ class SolrIndexTest extends SapphireTest ); } + /** + * Test that ShowInSearch and getShowInSearch() 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 testShowInSearch() + { + $defaultMode = Versioned::get_reading_mode(); + Versioned::set_reading_mode('Stage.' . Versioned::DRAFT); + + $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 true'; + $pageA->ShowInSearch = true; + $pageA->write(); + + // will get filtered out + $page = new Page(); + $page->Title = 'Test Page false'; + $page->ShowInSearch = false; + $page->write(); + + // will get added + $fileA = new File(); + $fileA->Title = 'Test File true'; + $fileA->ShowInSearch = true; + $fileA->write(); + + // will get filtered out + $file = new File(); + $file->Title = 'Test File false'; + $file->ShowInSearch = false; + $file->write(); + + // will get added + $objOneA = new SolrIndexTest_MyDataObjectOne(); + $objOneA->Title = 'Test MyDataObjectOne true'; + $objOneA->ShowInSearch = true; + $objOneA->write(); + + // will get filtered out + $objOne = new SolrIndexTest_MyDataObjectOne(); + $objOne->Title = 'Test MyDataObjectOne false'; + $objOne->ShowInSearch = false; + $objOne->write(); + + // will get added + // this class has a getShowInSearch() == true, which will override $mypage->ShowInSearch = false + $objTwoA = new SolrIndexTest_MyDataObjectTwo(); + $objTwoA->Title = 'Test MyDataObjectTwo false'; + $objTwoA->ShowInSearch = false; + $objTwoA->write(); + + // will get added + // this class has a getShowInSearch() == true, which will override $mypage->ShowInSearch = false + $myPageA = new SolrIndexTest_MyPage(); + $myPageA->Title = 'Test MyPage false'; + $myPageA->ShowInSearch = false; + $myPageA->write(); + + $callback = function (Apache_Solr_Document $doc) use ($pageA, $myPageA, $fileA, $objOneA, $objTwoA): bool { + $validKeys = [ + Page::class . $pageA->ID, + SolrIndexTest_MyPage::class . $myPageA->ID, + File::class . $fileA->ID, + SolrIndexTest_MyDataObjectOne::class . $objOneA->ID, + SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID + ]; + return in_array($this->createSolrDocKey($doc), $validKeys); + }; + + $serviceMock + ->expects($this->exactly(5)) + ->method('addDocument') + ->withConsecutive( + [$this->callback($callback)], + [$this->callback($callback)], + [$this->callback($callback)], + [$this->callback($callback)], + [$this->callback($callback)] + ); + + // This is what actually triggers all the solr stuff + SearchUpdater::flush_dirty_indexes(); + + // 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; + })] + ); + + IndexableService::singleton()->clearCache(); + SearchUpdater::flush_dirty_indexes(); + + Versioned::set_reading_mode($defaultMode); + } + + protected function createSolrDocKey(Apache_Solr_Document $doc) + { + return $doc->getField('ClassName')['value'] . $doc->getField('ID')['value']; + } + protected function getFakeRawSolrResponse() { return new \Apache_Solr_Response( diff --git a/tests/SolrIndexTest/SolrIndexTest_MyDataObjectOne.php b/tests/SolrIndexTest/SolrIndexTest_MyDataObjectOne.php new file mode 100644 index 0000000..f484784 --- /dev/null +++ b/tests/SolrIndexTest/SolrIndexTest_MyDataObjectOne.php @@ -0,0 +1,16 @@ + 'Varchar(255)', + 'ShowInSearch' => 'Boolean' + ]; + + private static $table_name = 'SolrIndexTestMyDataObjectOne'; +} diff --git a/tests/SolrIndexTest/SolrIndexTest_MyDataObjectTwo.php b/tests/SolrIndexTest/SolrIndexTest_MyDataObjectTwo.php new file mode 100644 index 0000000..086f0f5 --- /dev/null +++ b/tests/SolrIndexTest/SolrIndexTest_MyDataObjectTwo.php @@ -0,0 +1,16 @@ +addClass(SolrIndexTest_MyDataObjectOne::class); + $this->addClass(SiteTree::class); + $this->addClass(File::class); + $this->addFilterField('ShowInSearch'); + } +} diff --git a/tests/SolrReindexTest.php b/tests/SolrReindexTest.php index c64e0f5..413b34d 100644 --- a/tests/SolrReindexTest.php +++ b/tests/SolrReindexTest.php @@ -2,27 +2,43 @@ namespace SilverStripe\FullTextSearch\Tests; +use Apache_Solr_Document; +use Page; +use SilverStripe\Assets\File; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; use SilverStripe\FullTextSearch\Search\FullTextSearch; +use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; +use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned; use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexHandler; +use SilverStripe\FullTextSearch\Solr\Reindex\Handlers\SolrReindexImmediateHandler; use SilverStripe\FullTextSearch\Solr\Services\Solr4Service; use SilverStripe\FullTextSearch\Solr\Services\SolrService; use SilverStripe\FullTextSearch\Solr\Tasks\Solr_Reindex; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectOne; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyDataObjectTwo; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_MyPage; +use SilverStripe\FullTextSearch\Tests\SolrIndexTest\SolrIndexTest_ShowInSearchIndex; use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Index; use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Item; use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_RecordingLogger; use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_TestHandler; use SilverStripe\FullTextSearch\Tests\SolrReindexTest\SolrReindexTest_Variant; +use SilverStripe\Versioned\Versioned; class SolrReindexTest extends SapphireTest { + protected $usesDatabase = true; protected static $extra_dataobjects = array( - SolrReindexTest_Item::class + SolrReindexTest_Item::class, + SolrIndexTest_MyPage::class, + SolrIndexTest_MyDataObjectOne::class, + SolrIndexTest_MyDataObjectTwo::class, ); /** @@ -153,7 +169,6 @@ class SolrReindexTest extends SapphireTest $this->assertEquals(240, SolrReindexTest_Item::get()->count()); } - /** * Given the invocation of a new re-index with a given set of data, ensure that the necessary * list of groups are created and segmented for each state @@ -284,4 +299,107 @@ class SolrReindexTest extends SapphireTest // Check ids $this->assertEquals(120, count($ids)); } + + /** + * Test that ShowInSearch filtering is working correctly + */ + public function testShowInSearch() + { + $defaultMode = Versioned::get_reading_mode(); + Versioned::set_reading_mode('Stage.' . Versioned::DRAFT); + + // will get added + $pageA = new Page(); + $pageA->Title = 'Test Page true'; + $pageA->ShowInSearch = true; + $pageA->write(); + + // will get filtered out + $page = new Page(); + $page->Title = 'Test Page false'; + $page->ShowInSearch = false; + $page->write(); + + // will get added + $fileA = new File(); + $fileA->Title = 'Test File true'; + $fileA->ShowInSearch = true; + $fileA->write(); + + // will get filtered out + $file = new File(); + $file->Title = 'Test File false'; + $file->ShowInSearch = false; + $file->write(); + + // will get added + $objOneA = new SolrIndexTest_MyDataObjectOne(); + $objOneA->Title = 'Test MyDataObjectOne true'; + $objOneA->ShowInSearch = true; + $objOneA->write(); + + // will get filtered out + $objOne = new SolrIndexTest_MyDataObjectOne(); + $objOne->Title = 'Test MyDataObjectOne false'; + $objOne->ShowInSearch = false; + $objOne->write(); + + // will get added + // this class has a getShowInSearch() == true, which will override $mypage->ShowInSearch = false + $objTwoA = new SolrIndexTest_MyDataObjectTwo(); + $objTwoA->Title = 'Test MyDataObjectTwo false'; + $objTwoA->ShowInSearch = false; + $objTwoA->write(); + + // will get added + // this class has a getShowInSearch() == true, which will override $mypage->ShowInSearch = false + $myPageA = new SolrIndexTest_MyPage(); + $myPageA->Title = 'Test MyPage false'; + $myPageA->ShowInSearch = false; + $myPageA->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, $myPageA, $fileA, $objOneA, $objTwoA): bool { + $validKeys = [ + Page::class . $pageA->ID, + SolrIndexTest_MyPage::class . $myPageA->ID, + File::class . $fileA->ID, + SolrIndexTest_MyDataObjectOne::class . $objOneA->ID, + SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID + ]; + return in_array($this->createSolrDocKey($doc), $validKeys); + }; + + $serviceMock + ->expects($this->exactly(5)) + ->method('addDocument') + ->withConsecutive( + [$this->callback($callback)], + [$this->callback($callback)], + [$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); + + Versioned::set_reading_mode($defaultMode); + } + + protected function createSolrDocKey(Apache_Solr_Document $doc) + { + return $doc->getField('ClassName')['value'] . $doc->getField('ID')['value']; + } }