Skip to content

Commit 4e0fd59

Browse files
Christoph Lehmanndkd-kaehm
authored andcommitted
[FEATURE] Monitor extbase records
Extbase records are now monitored. * New entities are added to index queue * Updated entities are updated in the index queue * Entities turned inaccessable are removed from index queue * Soft/Hard deleted entities are removed in the index queue Resolves: #126
1 parent 8bb649d commit 4e0fd59

File tree

12 files changed

+333
-24
lines changed

12 files changed

+333
-24
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace ApacheSolrForTypo3\Solr\EventListener\Extbase;
19+
20+
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordDeletedEvent;
21+
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordGarbageCheckEvent;
22+
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordUpdatedEvent;
23+
use ApacheSolrForTypo3\Solr\Trait\SkipMonitoringTrait;
24+
use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
25+
use TYPO3\CMS\Extbase\Event\Persistence\EntityPersistedEvent;
26+
use TYPO3\CMS\Extbase\Event\Persistence\EntityRemovedFromPersistenceEvent;
27+
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory;
28+
29+
/**
30+
* Event listener that handle record changes by \TYPO3\CMS\Extbase\Persistence\Generic\Backend
31+
*/
32+
class PersistenceEventListener
33+
{
34+
use SkipMonitoringTrait;
35+
36+
public function __construct(
37+
protected DataMapFactory $dataMapFactory,
38+
protected EventDispatcher $eventDispatcher
39+
) {}
40+
41+
public function entityPersisted(EntityPersistedEvent $event): void
42+
{
43+
$object = $event->getObject();
44+
$tableName = $this->getTableName($object);
45+
if (!$this->skipMonitoringOfTable($tableName)) {
46+
// Entity might turn inaccessable
47+
$this->eventDispatcher->dispatch(new RecordGarbageCheckEvent($object->getUid(), $tableName));
48+
// Entity added/updated
49+
$this->eventDispatcher->dispatch(new RecordUpdatedEvent($object->getUid(), $tableName));
50+
}
51+
}
52+
53+
public function entityRemoved(EntityRemovedFromPersistenceEvent $event): void
54+
{
55+
$object = $event->getObject();
56+
$tableName = $this->getTableName($object);
57+
if (!$this->skipMonitoringOfTable($tableName)) {
58+
$this->eventDispatcher->dispatch(new RecordDeletedEvent($object->getUid(), $tableName));
59+
}
60+
}
61+
62+
protected function getTableName(object $object): string
63+
{
64+
$dataMap = $this->dataMapFactory->buildDataMap(get_class($object));
65+
return $dataMap->getTableName();
66+
}
67+
}

Classes/IndexQueue/RecordMonitor.php

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordMovedEvent;
2222
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\RecordUpdatedEvent;
2323
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\UpdateHandler\Events\VersionSwappedEvent;
24-
use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration;
24+
use ApacheSolrForTypo3\Solr\Trait\SkipMonitoringTrait;
2525
use ApacheSolrForTypo3\Solr\Util;
2626
use Psr\EventDispatcher\EventDispatcherInterface;
2727
use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -39,6 +39,8 @@
3939
*/
4040
class RecordMonitor
4141
{
42+
use SkipMonitoringTrait;
43+
4244
/**
4345
* @var array
4446
*/
@@ -176,29 +178,6 @@ public function processDatamap_afterDatabaseOperations(
176178
);
177179
}
178180

179-
/**
180-
* Check if the provided table is explicitly configured for monitoring
181-
*
182-
* @param string $table
183-
* @return bool
184-
*/
185-
protected function skipMonitoringOfTable(string $table): bool
186-
{
187-
static $configurationMonitorTables;
188-
189-
if (empty($configurationMonitorTables)) {
190-
$configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
191-
$configurationMonitorTables = $configuration->getIsUseConfigurationMonitorTables();
192-
}
193-
194-
// No explicit configuration => all tables should be monitored
195-
if (empty($configurationMonitorTables)) {
196-
return false;
197-
}
198-
199-
return !in_array($table, $configurationMonitorTables);
200-
}
201-
202181
/**
203182
* Check if at least one page in the record's rootline is configured to exclude sub-entries from indexing
204183
*
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace ApacheSolrForTypo3\Solr\Trait;
19+
20+
use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration;
21+
use TYPO3\CMS\Core\Utility\GeneralUtility;
22+
23+
trait SkipMonitoringTrait
24+
{
25+
/**
26+
* Check if the provided table is explicitly configured for monitoring
27+
*/
28+
protected function skipMonitoringOfTable(string $table): bool
29+
{
30+
static $configurationMonitorTables;
31+
32+
if (empty($configurationMonitorTables)) {
33+
$configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
34+
$configurationMonitorTables = $configuration->getIsUseConfigurationMonitorTables();
35+
}
36+
37+
// No explicit configuration => all tables should be monitored
38+
if (empty($configurationMonitorTables)) {
39+
return false;
40+
}
41+
42+
return !in_array($table, $configurationMonitorTables);
43+
}
44+
}

Configuration/Services.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,13 @@ services:
144144
- name: event.listener
145145
identifier: 'solr.index.pageIndexer.FrontendUserAuthenticator'
146146
event: TYPO3\CMS\Frontend\Authentication\ModifyResolvedFrontendGroupsEvent
147+
148+
ApacheSolrForTypo3\Solr\EventListener\Extbase\PersistenceEventListener:
149+
autowire: true
150+
tags:
151+
- name: event.listener
152+
identifier: 'solr.index.ExtbaseEntityPersisted'
153+
method: 'entityPersisted'
154+
- name: event.listener
155+
identifier: 'solr.index.ExtbaseEntityRemoved'
156+
method: 'entityRemoved'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
public: false
6+
7+
ApacheSolrForTypo3\FakeExtension\:
8+
resource: '../Classes/*'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"tx_fakeextension_domain_model_foo",
2+
,"uid","pid","title","tstamp","deleted"
3+
,3,1,"delete me",0,0
4+
"tx_solr_indexqueue_item",
5+
,"uid","root","item_type","item_uid","changed","errors","indexing_configuration"
6+
,3,1,"tx_fakeextension_domain_model_foo",3,0,0,"foo"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"tx_fakeextension_domain_model_foo",
2+
,"uid","pid","title","tstamp","deleted"
3+
,4,1,"hide me",0,0
4+
"tx_solr_indexqueue_item",
5+
,"uid","root","item_type","item_uid","changed","errors"
6+
,4,1,"tx_fakeextension_domain_model_foo",4,0,0
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"tx_fakeextension_domain_model_foo",
2+
,"uid","pid","title","tstamp"
3+
,2,1,"update me",0
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the TYPO3 CMS project.
5+
*
6+
* It is free software; you can redistribute it and/or modify it under
7+
* the terms of the GNU General Public License, either version 2
8+
* of the License, or any later version.
9+
*
10+
* For the full copyright and license information, please read the
11+
* LICENSE.txt file that was distributed with this source code.
12+
*
13+
* The TYPO3 project - inspiring people to share!
14+
*/
15+
16+
namespace ApacheSolrForTypo3\Solr\Tests\Integration\Extbase;
17+
18+
use ApacheSolrForTypo3\FakeExtension\Domain\Model\Foo;
19+
use ApacheSolrForTypo3\FakeExtension\Domain\Repository\FooRepository;
20+
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
21+
use ApacheSolrForTypo3\Solr\Tests\Integration\IntegrationTest;
22+
use TYPO3\CMS\Core\Context\Context;
23+
use TYPO3\CMS\Core\Utility\GeneralUtility;
24+
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
25+
26+
class PersistenceEventListenerTest extends IntegrationTest
27+
{
28+
protected $testExtensionsToLoad = [
29+
'typo3conf/ext/solr',
30+
'../vendor/apache-solr-for-typo3/solr/Tests/Integration/Fixtures/Extensions/fake_extension',
31+
];
32+
33+
protected ?Queue $indexQueue = null;
34+
protected ?FooRepository $repository = null;
35+
protected ?PersistenceManager $persistenceManager = null;
36+
37+
protected function setUp(): void
38+
{
39+
parent::setUp();
40+
$this->writeDefaultSolrTestSiteConfiguration();
41+
$this->addTypoScriptToTemplateRecord(1, '
42+
plugin.tx_solr.index.queue.foo = 1
43+
plugin.tx_solr.index.queue.foo.type = tx_fakeextension_domain_model_foo
44+
');
45+
$this->indexQueue = GeneralUtility::makeInstance(Queue::class);
46+
$this->repository = GeneralUtility::makeInstance(FooRepository::class);
47+
$this->persistenceManager = GeneralUtility::makeInstance(PersistenceManager::class);
48+
}
49+
50+
/**
51+
* @test
52+
*/
53+
public function newEntityIsAddedToIndexQueue()
54+
{
55+
$object = new Foo();
56+
$object->setTitle('Added');
57+
$object->setPid(1);
58+
$repository = GeneralUtility::makeInstance(FooRepository::class);
59+
$repository->add($object);
60+
$this->persistenceManager->persistAll();
61+
self::assertTrue($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 1));
62+
}
63+
64+
/**
65+
* @test
66+
*/
67+
public function newHiddenEntityIsNotAddedToIndexQueue()
68+
{
69+
$object = new Foo();
70+
$object->setTitle('Added');
71+
$object->setHidden(true);
72+
$object->setPid(1);
73+
$repository = GeneralUtility::makeInstance(FooRepository::class);
74+
$repository->add($object);
75+
$this->persistenceManager->persistAll();
76+
self::assertFalse($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 1));
77+
}
78+
79+
/**
80+
* @test
81+
*/
82+
public function updatedEntityIsUpdatedInIndexQueue()
83+
{
84+
$this->importCSVDataSet(__DIR__ . '/Fixtures/update_items.csv');
85+
/** @var Foo $object */
86+
$object = $this->repository->findByUid(2);
87+
$object->setTitle('Updated');
88+
$this->repository->update($object);
89+
$this->persistenceManager->persistAll();
90+
91+
$context = GeneralUtility::makeInstance(Context::class);
92+
$currentTimestamp = $context->getPropertyFromAspect('date', 'timestamp');
93+
94+
self::assertTrue($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 2));
95+
96+
$item = $this->indexQueue->getItem(1);
97+
self::assertSame(2, $item->getRecordUid());
98+
self::assertSame('foo', $item->getIndexingConfigurationName());
99+
self::assertSame($currentTimestamp, $item->getChanged());
100+
}
101+
102+
/**
103+
* @test
104+
*/
105+
public function softDeletedEntityIsRemovedFromIndexQueue()
106+
{
107+
$this->importCSVDataSet(__DIR__ . '/Fixtures/delete_items.csv');
108+
/** @var Foo $object */
109+
$object = $this->repository->findByUid(3);
110+
$this->repository->remove($object);
111+
$this->persistenceManager->persistAll();
112+
113+
self::assertFalse($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 3));
114+
}
115+
116+
/**
117+
* @test
118+
*/
119+
public function deletedEntityIsRemovedFromIndexQueue()
120+
{
121+
unset($GLOBALS['TCA']['tx_fakeextension_domain_model_foo']['ctrl']['delete']);
122+
$this->importCSVDataSet(__DIR__ . '/Fixtures/delete_items.csv');
123+
/** @var Foo $object */
124+
$object = $this->repository->findByUid(3);
125+
$this->repository->remove($object);
126+
$this->persistenceManager->persistAll();
127+
128+
self::assertFalse($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 3));
129+
}
130+
131+
/**
132+
* @test
133+
*/
134+
public function updatedEntityTurnedHiddenIsRemovedFromIndexQueue()
135+
{
136+
$this->importCSVDataSet(__DIR__ . '/Fixtures/hidden_items.csv');
137+
/** @var Foo $object */
138+
$object = $this->repository->findByUid(4);
139+
$object->setHidden(true);
140+
$this->repository->update($object);
141+
$this->persistenceManager->persistAll();
142+
143+
self::assertFalse($this->indexQueue->containsItem('tx_fakeextension_domain_model_foo', 4));
144+
}
145+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace ApacheSolrForTypo3\FakeExtension\Domain\Model;
4+
5+
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
6+
7+
class Foo extends AbstractEntity
8+
{
9+
protected string $title;
10+
protected bool $hidden;
11+
12+
public function getTitle(): string
13+
{
14+
return $this->title;
15+
}
16+
17+
public function setTitle(string $title): void
18+
{
19+
$this->title = $title;
20+
}
21+
22+
public function isHidden(): bool
23+
{
24+
return $this->hidden;
25+
}
26+
27+
public function setHidden(bool $hidden): void
28+
{
29+
$this->hidden = $hidden;
30+
}
31+
}

0 commit comments

Comments
 (0)