Fix search reindexing after content deletion (#7187)

* Fix search reindexing after content deletion

* Refactor `ContentSearchService`

* Update ZendLucenceDriver.php

---------

Co-authored-by: Lucas Bartholemy <luke-@users.noreply.github.com>
This commit is contained in:
Yuriy Bakhtin 2024-08-28 17:47:22 +03:00 committed by GitHub
parent 9aa79bc386
commit 60957f5361
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 31 additions and 40 deletions

View File

@ -23,6 +23,7 @@ HumHub Changelog
- Fix #7180: Fix active form on user edit form for correct working of widget inputs - Fix #7180: Fix active form on user edit form for correct working of widget inputs
- Fix #7181: Fix duplicated label of checkbox profile field - Fix #7181: Fix duplicated label of checkbox profile field
- Fix #7182: Fix initialization of several select2 inputs on the same page - Fix #7182: Fix initialization of several select2 inputs on the same page
- Fix #7187: Fix search reindexing after content deletion
- Fix #7152: Fix search starting with special characters - Fix #7152: Fix search starting with special characters
1.16.1 (July 1, 2024) 1.16.1 (July 1, 2024)

View File

@ -11,7 +11,6 @@ namespace humhub\modules\content;
use humhub\commands\CronController; use humhub\commands\CronController;
use humhub\commands\IntegrityController; use humhub\commands\IntegrityController;
use humhub\components\Event; use humhub\components\Event;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\models\Content; use humhub\modules\content\models\Content;
use humhub\modules\content\services\ContentSearchService; use humhub\modules\content\services\ContentSearchService;
use humhub\modules\user\events\UserEvent; use humhub\modules\user\events\UserEvent;
@ -108,16 +107,16 @@ class Events extends BaseObject
} }
/** /**
* After a components\ContentActiveRecord was deleted * After a Content was deleted
* *
* @param \yii\base\Event $event * @param \yii\base\Event $event
*/ */
public static function onContentActiveRecordDelete($event) public static function onContentAfterDelete($event)
{ {
/** @var ContentActiveRecord $record */ /* @var Content $content */
$record = $event->sender; $content = $event->sender;
(new ContentSearchService($record->content))->delete(); (new ContentSearchService($content))->delete();
} }
/** /**

View File

@ -27,7 +27,7 @@ class SearchController extends Controller
public function actionOptimize() public function actionOptimize()
{ {
print "Optimizing search index: "; print "Optimizing search index: ";
$driver = $this->getSearchDriver(); $driver = ContentSearchService::getDriver();
$driver->optimize(); $driver->optimize();
print "OK!\n\n"; print "OK!\n\n";
} }
@ -37,7 +37,7 @@ class SearchController extends Controller
*/ */
public function actionRebuild() public function actionRebuild()
{ {
$driver = $this->getSearchDriver(); $driver = ContentSearchService::getDriver();
$driver->purge(); $driver->purge();
foreach (Content::find()->each() as $content) { foreach (Content::find()->each() as $content) {
(new ContentSearchService($content))->update(false); (new ContentSearchService($content))->update(false);
@ -67,7 +67,7 @@ class SearchController extends Controller
*/ */
public function actionFind($keyword) public function actionFind($keyword)
{ {
$driver = $this->getSearchDriver(); $driver = ContentSearchService::getDriver();
$user = User::findOne(['id' => 1]); $user = User::findOne(['id' => 1]);
@ -82,12 +82,4 @@ class SearchController extends Controller
print "\t - " . $content->object_model . ' ' . $content->object_id . "\n"; print "\t - " . $content->object_model . ' ' . $content->object_id . "\n";
} }
} }
private function getSearchDriver(): AbstractDriver
{
/** @var Module $module */
$module = Yii::$app->getModule('content');
return $module->getSearchDriver();
}
} }

View File

@ -3,11 +3,11 @@
use humhub\commands\CronController; use humhub\commands\CronController;
use humhub\modules\content\Events; use humhub\modules\content\Events;
use humhub\commands\IntegrityController; use humhub\commands\IntegrityController;
use humhub\modules\content\models\Content;
use humhub\modules\content\Module; use humhub\modules\content\Module;
use humhub\modules\content\widgets\WallEntryAddons; use humhub\modules\content\widgets\WallEntryAddons;
use humhub\modules\user\models\User; use humhub\modules\user\models\User;
use humhub\modules\space\models\Space; use humhub\modules\space\models\Space;
use humhub\modules\content\components\ContentActiveRecord;
return [ return [
'id' => 'content', 'id' => 'content',
@ -19,7 +19,7 @@ return [
['class' => User::class, 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::class, 'onUserDelete']], ['class' => User::class, 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::class, 'onUserDelete']],
['class' => User::class, 'event' => User::EVENT_BEFORE_SOFT_DELETE, 'callback' => [Events::class, 'onUserSoftDelete']], ['class' => User::class, 'event' => User::EVENT_BEFORE_SOFT_DELETE, 'callback' => [Events::class, 'onUserSoftDelete']],
['class' => Space::class, 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::class, 'onSpaceDelete']], ['class' => Space::class, 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::class, 'onSpaceDelete']],
['class' => ContentActiveRecord::class, 'event' => ContentActiveRecord::EVENT_AFTER_DELETE, 'callback' => [Events::class, 'onContentActiveRecordDelete']], ['class' => Content::class, 'event' => Content::EVENT_AFTER_DELETE, 'callback' => [Events::class, 'onContentAfterDelete']],
['class' => CronController::class, 'event' => CronController::EVENT_ON_DAILY_RUN, 'callback' => [Events::class, 'onCronDailyRun']], ['class' => CronController::class, 'event' => CronController::EVENT_ON_DAILY_RUN, 'callback' => [Events::class, 'onCronDailyRun']],
['class' => CronController::class, 'event' => CronController::EVENT_BEFORE_ACTION, 'callback' => [Events::class, 'onCronBeforeAction']], ['class' => CronController::class, 'event' => CronController::EVENT_BEFORE_ACTION, 'callback' => [Events::class, 'onCronBeforeAction']],
], ],

View File

@ -7,7 +7,6 @@
namespace humhub\modules\content\jobs; namespace humhub\modules\content\jobs;
use humhub\modules\content\models\Content;
use humhub\modules\content\services\ContentSearchService; use humhub\modules\content\services\ContentSearchService;
use humhub\modules\content\services\SearchJobService; use humhub\modules\content\services\SearchJobService;
use humhub\modules\queue\interfaces\ExclusiveJobInterface; use humhub\modules\queue\interfaces\ExclusiveJobInterface;
@ -31,10 +30,7 @@ class SearchDeleteDocument extends LongRunningActiveJob implements ExclusiveJobI
public function run() public function run()
{ {
return $this->getService()->run(function () { return $this->getService()->run(function () {
$content = Content::findOne(['id' => $this->contentId]); ContentSearchService::deleteContentById($this->contentId, false);
if ($content) {
(new ContentSearchService($content))->delete(false);
}
}); });
} }

View File

@ -15,7 +15,7 @@ abstract class AbstractDriver extends Component
abstract public function update(Content $content): void; abstract public function update(Content $content): void;
abstract public function delete(Content $content): void; abstract public function delete(int $contentId): void;
/** /**
* Run search process * Run search process

View File

@ -41,8 +41,7 @@ class MysqlDriver extends AbstractDriver
public function update(Content $content): void public function update(Content $content): void
{ {
$this->delete($content->id);
$this->delete($content);
$record = new ContentFulltext(); $record = new ContentFulltext();
$record->content_id = $content->id; $record->content_id = $content->id;
@ -65,9 +64,9 @@ class MysqlDriver extends AbstractDriver
$record->save(); $record->save();
} }
public function delete(Content $content): void public function delete(int $contentId): void
{ {
ContentFulltext::deleteAll(['content_id' => $content->id]); ContentFulltext::deleteAll(['content_id' => $contentId]);
} }
/** /**

View File

@ -14,7 +14,6 @@ use humhub\modules\space\models\Space;
use humhub\modules\user\helpers\AuthHelper; use humhub\modules\user\helpers\AuthHelper;
use humhub\modules\user\models\User; use humhub\modules\user\models\User;
use Yii; use Yii;
use yii\base\Exception;
use yii\data\Pagination; use yii\data\Pagination;
use yii\helpers\FileHelper; use yii\helpers\FileHelper;
use ZendSearch\Lucene\Analysis\Analyzer\Analyzer; use ZendSearch\Lucene\Analysis\Analyzer\Analyzer;
@ -100,9 +99,9 @@ class ZendLucenceDriver extends AbstractDriver
]; ];
} }
public function delete(Content $content): void public function delete(int $contentId): void
{ {
$query = new TermQuery(new Term($content->id, 'content_id')); $query = new TermQuery(new Term($contentId, 'content_id'));
foreach ($this->getIndex()->find($query) as $result) { foreach ($this->getIndex()->find($query) as $result) {
try { try {
$this->getIndex()->delete($result->id); $this->getIndex()->delete($result->id);
@ -145,13 +144,13 @@ class ZendLucenceDriver extends AbstractDriver
} catch (\Exception $ex) { } catch (\Exception $ex) {
throw new \Exception('Could not get content id from Lucence search result'); throw new \Exception('Could not get content id from Lucence search result');
} }
$content = Content::findOne(['id' => $contentId]); $content = Content::findOne(['id' => $contentId]);
if ($content !== null) { if ($content !== null) {
$resultSet->results[] = $content; $resultSet->results[] = $content;
} else { } else {
throw new Exception('Could not load result! Content ID: ' . $contentId); Yii::warning("Deleted non-existing content from search index. Content ID: ". $contentId, 'content');
// ToDo: Delete Result $this->delete($contentId);
Yii::error("Could not load search result content: " . $contentId);
} }
} }

View File

@ -32,7 +32,7 @@ class ContentSearchService
if ($asActiveJob) { if ($asActiveJob) {
Yii::$app->queue->push(new SearchUpdateDocument(['contentId' => $this->content->id])); Yii::$app->queue->push(new SearchUpdateDocument(['contentId' => $this->content->id]));
} else { } else {
$this->getSearchDriver()->update($this->content); self::getDriver()->update($this->content);
} }
} else { } else {
$this->delete($asActiveJob); $this->delete($asActiveJob);
@ -40,11 +40,16 @@ class ContentSearchService
} }
public function delete(bool $asActiveJob = true): void public function delete(bool $asActiveJob = true): void
{
self::deleteContentById($this->content->id, $asActiveJob);
}
public static function deleteContentById(int $id, bool $asActiveJob = true): void
{ {
if ($asActiveJob) { if ($asActiveJob) {
Yii::$app->queue->push(new SearchDeleteDocument(['contentId' => $this->content->id])); Yii::$app->queue->push(new SearchDeleteDocument(['contentId' => $id]));
} else { } else {
$this->getSearchDriver()->delete($this->content); self::getDriver()->delete($id);
} }
} }
@ -89,9 +94,9 @@ class ContentSearchService
return true; return true;
} }
private function getSearchDriver(): AbstractDriver public static function getDriver(): AbstractDriver
{ {
/** @var Module $module */ /* @var Module $module */
$module = Yii::$app->getModule('content'); $module = Yii::$app->getModule('content');
return $module->getSearchDriver(); return $module->getSearchDriver();
} }