diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index d8ff6ec9f3..9c41c8465d 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -179,6 +179,7 @@ class Application extends SilexApplication 'fr' => 'Français', 'nl' => 'Dutch', ]; + private static $flashTypes = ['warning', 'info', 'success', 'error']; private $environment; @@ -204,11 +205,6 @@ class Application extends SilexApplication $this['charset'] = 'UTF-8'; mb_internal_encoding($this['charset']); - !defined('JETON_MAKE_SUBDEF') ? define('JETON_MAKE_SUBDEF', 0x01) : ''; - !defined('JETON_WRITE_META_DOC') ? define('JETON_WRITE_META_DOC', 0x02) : ''; - !defined('JETON_WRITE_META_SUBDEF') ? define('JETON_WRITE_META_SUBDEF', 0x04) : ''; - !defined('JETON_WRITE_META') ? define('JETON_WRITE_META', 0x06) : ''; - $this['debug'] = $this->share(function (Application $app) { return Application::ENV_PROD !== $app->getEnvironment(); }); @@ -241,6 +237,7 @@ class Application extends SilexApplication $this->register(new FeedServiceProvider()); $this->register(new FtpServiceProvider()); $this->register(new GeonamesServiceProvider()); + $this['geonames.server-uri'] = $this->share(function (Application $app) { return $app['conf']->get(['registry', 'webservices', 'geonames-server'], 'http://geonames.alchemyasp.com/'); }); @@ -487,6 +484,11 @@ class Application extends SilexApplication $dispatcher->addSubscriber($app['phraseanet.maintenance-subscriber']); $dispatcher->addSubscriber($app['phraseanet.cookie-disabler-subscriber']); $dispatcher->addSubscriber($app['phraseanet.session-manager-subscriber']); + $dispatcher->addSubscriber($app['phraseanet.record-subscriber']); + $dispatcher->addSubscriber($app['phraseanet.story-subscriber']); + $dispatcher->addSubscriber($app['phraseanet.elasticsearch-subscriber']); + $dispatcher->addSubscriber($app['phraseanet.databox-subscriber']); + $dispatcher->addSubscriber($app['phraseanet.collection-subscriber']); $dispatcher->addSubscriber(new PhraseaInstallSubscriber($app)); $dispatcher->addSubscriber(new FeedEntrySubscriber($app)); $dispatcher->addSubscriber(new RegistrationSubscriber($app)); diff --git a/lib/Alchemy/Phrasea/Border/Manager.php b/lib/Alchemy/Phrasea/Border/Manager.php index aa99917690..8a5b18c1bb 100644 --- a/lib/Alchemy/Phrasea/Border/Manager.php +++ b/lib/Alchemy/Phrasea/Border/Manager.php @@ -13,6 +13,8 @@ namespace Alchemy\Phrasea\Border; use Alchemy\Phrasea\Border\Checker\CheckerInterface; use Alchemy\Phrasea\Border\Attribute\AttributeInterface; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Metadata\Tag\TfArchivedate; use Alchemy\Phrasea\Metadata\Tag\TfQuarantine; use Alchemy\Phrasea\Metadata\Tag\TfBasename; @@ -288,6 +290,8 @@ class Manager case AttributeInterface::NAME_STATUS: $element->set_binary_status(decbin(bindec($element->get_status()) | bindec($attribute->getValue()))); + $this->app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($element)); + break; case AttributeInterface::NAME_STORY: @@ -304,7 +308,6 @@ class Manager $this->app['phraseanet.metadata-setter']->replaceMetadata($newMetadata, $element); $element->rebuild_subdefs(); - $element->reindex(); return $element; } diff --git a/lib/Alchemy/Phrasea/Command/RecordAdd.php b/lib/Alchemy/Phrasea/Command/RecordAdd.php index baaa01dabb..80e9103c18 100644 --- a/lib/Alchemy/Phrasea/Command/RecordAdd.php +++ b/lib/Alchemy/Phrasea/Command/RecordAdd.php @@ -13,6 +13,8 @@ namespace Alchemy\Phrasea\Command; use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\Border\Manager; +use Alchemy\Phrasea\Core\Event\RecordEvent\CreateRecordEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\LazaretFile; use Alchemy\Phrasea\Model\Entities\LazaretSession; use Symfony\Component\Console\Input\InputArgument; @@ -115,7 +117,7 @@ class RecordAdd extends Command "Record id %d on collection `%s` (databox `%s`) has been created", $elementCreated->get_record_id(), $elementCreated->get_collection()->get_label($this->container['locale']), $elementCreated->get_databox()->get_label($this->container['locale']) ) ); - $this->container['phraseanet.SE']->addRecord($elementCreated); + $this->container['dispatcher']->dispatch(PhraseaEvents::RECORD_CREATE, new CreateRecordEvent($elementCreated)); } elseif ($elementCreated instanceof LazaretFile) { $output->writeln( sprintf("Quarantine item id %d has been created", $elementCreated->getId()) diff --git a/lib/Alchemy/Phrasea/Controller/Admin/Collection.php b/lib/Alchemy/Phrasea/Controller/Admin/Collection.php index 1e937d8c7e..cbf662d64b 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/Collection.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/Collection.php @@ -11,6 +11,8 @@ namespace Alchemy\Phrasea\Controller\Admin; +use Alchemy\Phrasea\Core\Event\CollectionEvent\ChangeNameEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\RuntimeException; use Silex\Application; use Silex\ControllerProviderInterface; @@ -718,6 +720,8 @@ class Collection implements ControllerProviderInterface try { $collection->set_name($name); $success = true; + + $app['dispatcher']->dispatch(PhraseaEvents::COLLECTION_CHANGE_NAME, new ChangeNameEvent($collection)); } catch (\Exception $e) { } diff --git a/lib/Alchemy/Phrasea/Controller/Admin/Fields.php b/lib/Alchemy/Phrasea/Controller/Admin/Fields.php index 015dd54d8d..339e019522 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/Fields.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/Fields.php @@ -11,6 +11,9 @@ namespace Alchemy\Phrasea\Controller\Admin; +use Alchemy\Phrasea\Core\Event\DataboxEvent\UpdateStructureFieldEvent; +use Alchemy\Phrasea\Core\Event\DataboxEvent\DeleteStructureFieldEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Metadata\TagProvider; use Alchemy\Phrasea\Vocabulary\Controller as VocabularyController; use JMS\TranslationBundle\Annotation\Ignore; @@ -114,6 +117,8 @@ class Fields implements ControllerProviderInterface $this->updateFieldWithData($app, $field, $jsonField); $field->save(); $fields[] = $field->toArray(); + + $app['dispatcher']->dispatch(PhraseaEvents::DATABOX_UPDATE_FIELD, new UpdateStructureFieldEvent($field, $jsonField)); } catch (\Exception $e) { $connection->rollback(); $app->abort(500, $app->trans('Field %name% could not be saved, please try again or contact an admin.', ['%name%' => $jsonField['name']])); @@ -278,13 +283,18 @@ class Fields implements ControllerProviderInterface $this->updateFieldWithData($app, $field, $data); $field->save(); + $app['dispatcher']->dispatch(PhraseaEvents::DATABOX_UPDATE_FIELD, new UpdateStructureFieldEvent($databox, $field, $jsonField)); + return $app->json($field->toArray()); } public function deleteField(Application $app, $sbas_id, $id) { $databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id); - \databox_field::get_instance($app, $databox, $id)->delete(); + $field = \databox_field::get_instance($app, $databox, $id); + $field->delete(); + + $app['dispatcher']->dispatch(PhraseaEvents::DATABOX_DELETE_FIELD, new DeleteStructureFieldEvent($databox, $field)); return new Response('', 204); } @@ -321,6 +331,7 @@ class Fields implements ControllerProviderInterface private function updateFieldWithData(Application $app, \databox_field $field, array $data) { + $field ->set_name($data['name']) ->set_thumbtitle($data['thumbtitle']) diff --git a/lib/Alchemy/Phrasea/Controller/Admin/Root.php b/lib/Alchemy/Phrasea/Controller/Admin/Root.php index 8379fca5cd..ee486062b2 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/Root.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/Root.php @@ -11,6 +11,9 @@ namespace Alchemy\Phrasea\Controller\Admin; +use Alchemy\Phrasea\Core\Event\DataboxEvent\DeleteStatusEvent; +use Alchemy\Phrasea\Core\Event\DataboxEvent\UpdateStatusEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\SessionNotFound; use Alchemy\Phrasea\Helper\DatabaseHelper; use Alchemy\Phrasea\Helper\PathHelper; @@ -322,12 +325,17 @@ class Root implements ControllerProviderInterface $error = false; + $status = \databox_status::getStatus($app, $databox_id)[$bit]; + $databox = $app['phraseanet.appbox']->get_databox((int) $databox_id); + try { \databox_status::deleteStatus($app, $app['phraseanet.appbox']->get_databox($databox_id), $bit); } catch (\Exception $e) { $error = true; } + $app['dispatcher']->dispatch(PhraseaEvents::DATABOX_DELETE_STATUS, new DeleteStatusEvent($databox, $status)); + return $app->json(['success' => !$error]); }) ->bind('admin_statusbit_delete') @@ -349,6 +357,8 @@ class Root implements ControllerProviderInterface 'labels_off' => $request->request->get('labels_off', []), ]; + $databox = $app['phraseanet.appbox']->get_databox((int) $databox_id); + \databox_status::updateStatus($app, $databox_id, $bit, $properties); if (null !== $request->request->get('delete_icon_off')) { @@ -443,6 +453,10 @@ class Root implements ControllerProviderInterface } } + $status = \databox_status::getStatus($app, $databox_id)[$bit]; + + $app['dispatcher']->dispatch(PhraseaEvents::DATABOX_UPDATE_STATUS, new UpdateStatusEvent($databox, $status)); + return $app->redirectPath('database_display_statusbit', ['databox_id' => $databox_id, 'success' => 1]); })->assert('databox_id', '\d+') ->assert('bit', '\d+') diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1.php b/lib/Alchemy/Phrasea/Controller/Api/V1.php index b95fca3071..ab892e58a3 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1.php @@ -11,6 +11,9 @@ namespace Alchemy\Phrasea\Controller\Api; +use Alchemy\Phrasea\Core\Event\ChangeStatusEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\CreateRecordEvent; use Silex\ControllerProviderInterface; use Alchemy\Phrasea\Cache\Cache as CacheInterface; use Alchemy\Phrasea\Core\PhraseaEvents; @@ -558,7 +561,8 @@ class V1 implements ControllerProviderInterface if ($output instanceof \record_adapter) { $ret['entity'] = '0'; $ret['url'] = '/records/' . $output->get_sbas_id() . '/' . $output->get_record_id() . '/'; - $app['phraseanet.SE']->addRecord($output); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CREATE, new CreateRecordEvent($output)); } if ($output instanceof LazaretFile) { $ret['entity'] = '1'; @@ -829,6 +833,7 @@ class V1 implements ControllerProviderInterface }); $record->set_metadatas($metadatas); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); return Result::create($request, ["record_metadatas" => $this->list_record_caption($record->get_caption())])->createResponse(); } @@ -861,7 +866,8 @@ class V1 implements ControllerProviderInterface } $record->set_binary_status(strrev($datas)); - $app['phraseanet.SE']->updateRecord($record); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($record)); $ret = ["status" => $this->list_record_status($databox, $record->get_status())]; diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Edit.php b/lib/Alchemy/Phrasea/Controller/Prod/Edit.php index 5f31d96430..2db3b34742 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Edit.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Edit.php @@ -11,6 +11,9 @@ namespace Alchemy\Phrasea\Controller\Prod; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Vocabulary\Controller as VocabularyController; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Metadata\Tag\TfEditdate; @@ -347,6 +350,8 @@ class Edit implements ControllerProviderInterface if (isset($rec['metadatas']) && is_array($rec['metadatas'])) { $record->set_metadatas($rec['metadatas']); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); } /** @@ -372,6 +377,8 @@ class Edit implements ControllerProviderInterface ]; $record->set_metadatas($metas, true); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); } $newstat = $record->get_status(); @@ -389,6 +396,8 @@ class Edit implements ControllerProviderInterface } $record->set_binary_status($newstat); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($record)); } $record diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Lazaret.php b/lib/Alchemy/Phrasea/Controller/Prod/Lazaret.php index 44f63ff287..dd547b21a5 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Lazaret.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Lazaret.php @@ -11,6 +11,10 @@ namespace Alchemy\Phrasea\Controller\Prod; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\CreateRecordEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\LazaretFile; use Alchemy\Phrasea\Border; use Alchemy\Phrasea\Border\Attribute\AttributeInterface; @@ -195,7 +199,7 @@ class Lazaret implements ControllerProviderInterface $lazaretFile->getSession(), $borderFile, $callBack, Border\Manager::FORCE_RECORD ); - $app['phraseanet.SE']->addRecord($record); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CREATE, new CreateRecordEvent($record)); if ($keepAttributes) { //add attribute @@ -229,6 +233,8 @@ class Lazaret implements ControllerProviderInterface break; case AttributeInterface::NAME_STATUS: $record->set_binary_status($attribute->getValue()); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($record)); break; case AttributeInterface::NAME_METAFIELD: $metaFields->set($attribute->getField()->get_name(), $attribute->getValue()); @@ -241,6 +247,8 @@ class Lazaret implements ControllerProviderInterface $fields = $metaFields->toMetadataArray($record->get_databox()->get_meta_structure()); $record->set_metadatas($fields); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); } //Delete lazaret file diff --git a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollection.php b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollection.php index 69eea98c43..6051036c2d 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollection.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollection.php @@ -12,6 +12,8 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeCollectionEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Silex\Application; use Silex\ControllerProviderInterface; use Symfony\Component\HttpFoundation\Request; @@ -93,10 +95,15 @@ class MoveCollection implements ControllerProviderInterface foreach ($records as $record) { $record->move_to_collection($collection, $app['phraseanet.appbox']); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_COLLECTION, new ChangeCollectionEvent($record)); + if ($request->request->get("chg_coll_son") == "1") { foreach ($record->get_children() as $child) { if ($app['acl']->get($app['authentication']->getUser())->has_right_on_base($child->get_base_id(), 'candeleterecord')) { $child->move_to_collection($collection, $app['phraseanet.appbox']); + + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_COLLECTION, new ChangeCollectionEvent($child)); } } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Property.php b/lib/Alchemy/Phrasea/Controller/Prod/Property.php index fde68a2d82..56e458050c 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Property.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Property.php @@ -12,6 +12,8 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Silex\Application; use Silex\ControllerProviderInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -175,6 +177,7 @@ class Property implements ControllerProviderInterface foreach ($record->get_children() as $child) { if (null !== $updatedStatus = $this->updateRecordStatus($child, $postStatus)) { $updated[$record->get_serialize_key()] = $updatedStatus; + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($record)); } } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Records.php b/lib/Alchemy/Phrasea/Controller/Prod/Records.php index 40b02a4ecb..9cdbb1fd17 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Records.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Records.php @@ -12,6 +12,8 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\RecordEvent\DeleteStoryEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; use Silex\Application; use Silex\ControllerProviderInterface; @@ -174,6 +176,12 @@ class Records implements ControllerProviderInterface $deleted[] = $record->get_serialize_key(); $record->delete(); + + if ($record->isStory()) { + $app['dispatcher']->dispatch(PhraseaEvents::STORY_DELETE, new DeleteStoryEvent($record)); + } else { + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_DELETE, new DeleteRecordEvent($record)); + } } catch (\Exception $e) { } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Story.php b/lib/Alchemy/Phrasea/Controller/Prod/Story.php index 242d51ab9c..48b5f91deb 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Story.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Story.php @@ -13,6 +13,8 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Controller\Exception as ControllerException; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\StoryWZ; use Silex\Application; use Silex\ControllerProviderInterface; @@ -74,6 +76,8 @@ class Story implements ControllerProviderInterface $Story->set_metadatas($metadatas)->rebuild_subdefs(); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($Story)); + $StoryWZ = new StoryWZ(); $StoryWZ->setUser($app['authentication']->getUser()); $StoryWZ->setRecord($Story); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Tools.php b/lib/Alchemy/Phrasea/Controller/Prod/Tools.php index 3252a9f382..5faaf321b0 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Tools.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Tools.php @@ -12,6 +12,8 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\ChangeOriginalNameEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\RuntimeException; use DataURI; use PHPExiftool\Exception\ExceptionInterface as PHPExiftoolException; @@ -149,7 +151,8 @@ class Tools implements ControllerProviderInterface if ((int) $request->request->get('ccfilename') === 1) { $record->set_original_name($fileName); - $app['phraseanet.SE']->updateRecord($record); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_ORIGINAL_NAME, new ChangeOriginalNameEvent($record)); } unlink($tempoFile); rmdir($tempoDir); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/Upload.php b/lib/Alchemy/Phrasea/Controller/Prod/Upload.php index 532810004b..e112c68bb8 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/Upload.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/Upload.php @@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Controller\Prod; use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\Border\Attribute\Status; use Alchemy\Phrasea\Core\Event\LazaretEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\CreateRecordEvent; use Alchemy\Phrasea\Core\PhraseaEvents; use DataURI\Parser; use DataURI\Exception\Exception as DataUriException; @@ -207,7 +208,8 @@ class Upload implements ControllerProviderInterface $id = $elementCreated->get_serialize_key(); $element = 'record'; $message = $app->trans('The record was successfully created'); - $app['phraseanet.SE']->addRecord($elementCreated); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CREATE, new CreateRecordEvent($elementCreated)); // try to create thumbnail from data URI if ('' !== $b64Image = $request->request->get('b64_image', '')) { diff --git a/lib/Alchemy/Phrasea/Controller/Thesaurus/Xmlhttp.php b/lib/Alchemy/Phrasea/Controller/Thesaurus/Xmlhttp.php index 34a3d7c809..a40bdd064f 100644 --- a/lib/Alchemy/Phrasea/Controller/Thesaurus/Xmlhttp.php +++ b/lib/Alchemy/Phrasea/Controller/Thesaurus/Xmlhttp.php @@ -11,6 +11,8 @@ namespace Alchemy\Phrasea\Controller\Thesaurus; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\User; use Silex\Application; use Silex\ControllerProviderInterface; @@ -1494,6 +1496,9 @@ class Xmlhttp implements ControllerProviderInterface if (count($metadatasd) > 0) { if (!$request->get('debug')) { $record->set_metadatas($metadatasd, true); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); + $ret['nRecsUpdated']++; } } diff --git a/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/ChangeNameEvent.php b/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/ChangeNameEvent.php new file mode 100644 index 0000000000..9885571905 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/ChangeNameEvent.php @@ -0,0 +1,17 @@ +collection = $collection; + } + + /** + * @return \collection + */ + public function getCollection() + { + return $this->collection; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/CollectionIndexEvent.php b/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/CollectionIndexEvent.php new file mode 100644 index 0000000000..b3f98ce46e --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/CollectionEvent/CollectionIndexEvent.php @@ -0,0 +1,17 @@ +databox = $databox; + } + + /** + * @return \databox + */ + public function getDatabox() + { + return $this->databox; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DataboxIndexEvent.php b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DataboxIndexEvent.php new file mode 100644 index 0000000000..d8aba82669 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DataboxIndexEvent.php @@ -0,0 +1,17 @@ +status = $status; + + parent::__construct($databox); + } + + public function getStatus() + { + return $this->status; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DeleteStructureFieldEvent.php b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DeleteStructureFieldEvent.php new file mode 100644 index 0000000000..57f2b24372 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/DeleteStructureFieldEvent.php @@ -0,0 +1,32 @@ +field = $field; + + parent::__construct($databox); + } + + /** + * @return \databox_field + */ + public function getField() + { + return $this->field; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStatusEvent.php b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStatusEvent.php new file mode 100644 index 0000000000..04d5a011e9 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStatusEvent.php @@ -0,0 +1,29 @@ +status = $status; + + parent::__construct($databox); + } + + public function getStatus() + { + return $this->status; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStructureFieldEvent.php b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStructureFieldEvent.php new file mode 100644 index 0000000000..a04b65134b --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/DataboxEvent/UpdateStructureFieldEvent.php @@ -0,0 +1,42 @@ +field = $field; + $this->data = $data; + + parent::__construct($databox); + } + + /** + * @return \databox_field + */ + public function getField() + { + return $this->field; + } + + /** + * @return array + */ + public function getData() + { + return $this->data; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/RecordEvent/BuildSubdefEvent.php b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/BuildSubdefEvent.php new file mode 100644 index 0000000000..f11d448de9 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/BuildSubdefEvent.php @@ -0,0 +1,31 @@ +subDefName = $subDefName; + parent::__construct($record); + } + + public function getSubDefName() + { + return $this->subDefName; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/RecordEvent/ChangeCollectionEvent.php b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/ChangeCollectionEvent.php new file mode 100644 index 0000000000..748c9780d0 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/ChangeCollectionEvent.php @@ -0,0 +1,19 @@ +record = $record; + } + + /** + * @return \record_adapter + */ + public function getRecord() + { + return $this->record; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/RecordEvent/RecordIndexEvent.php b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/RecordIndexEvent.php new file mode 100644 index 0000000000..6f1a7e4f0b --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/RecordEvent/RecordIndexEvent.php @@ -0,0 +1,17 @@ + 'onChangeName', + ]; + } + + public function onChangeName(ChangeNameEvent $event) + { + $collection = $event->getCollection(); + + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_COLLECTION, new CollectionIndexEvent($collection)); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/DataboxSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/DataboxSubscriber.php new file mode 100644 index 0000000000..61bdce6e2b --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/DataboxSubscriber.php @@ -0,0 +1,68 @@ + 'onUpdateField', + PhraseaEvents::DATABOX_DELETE_FIELD => 'onDeleteField', + PhraseaEvents::DATABOX_UPDATE_STATUS => 'onUpdateStatus', + PhraseaEvents::DATABOX_DELETE_STATUS => 'onDeleteStatus' + ]; + } + + public function onUpdateField(UpdateStructureFieldEvent $event) + { + $databox = $event->getDatabox(); + $field = $event->getField(); + $data = $event->getData(); + + if ((bool) $field->get_thumbtitle() !== (bool) $data['thumbtitle']) { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_DATABOX, new DataboxIndexEvent($databox)); + } + } + + public function onDeleteField(UpdateStructureFieldEvent $event) + { + $databox = $event->getDatabox(); + + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_DATABOX, new DataboxIndexEvent($databox)); + } + + public function onUpdateStatus(UpdateStatusEvent $event) + { + $databox = $event->getDatabox(); + + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_DATABOX, new DataboxIndexEvent($databox)); + } + + public function onDeleteStatus(DeleteStatusEvent $event) + { + $databox = $event->getDatabox(); + + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_DATABOX, new DataboxIndexEvent($databox)); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/ElasticsearchSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ElasticsearchSubscriber.php new file mode 100644 index 0000000000..5eb8231460 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ElasticsearchSubscriber.php @@ -0,0 +1,194 @@ +recordIndexer = $recordIndexer; + $this->termIndexer = $termIndexer; + $this->client = $client; + $this->appbox = $appbox; + $this->indexName = $indexName; + + self::$recordsToAdd = new \SplObjectStorage(); + self::$recordsToRemove = new \SplObjectStorage(); + self::$recordsToUpdate = new \SplObjectStorage(); + } + + public static function getSubscribedEvents() + { + return [ + PhraseaEvents::INDEX_DATABOX => ['onIndexDatabox', -12], + PhraseaEvents::INDEX_COLLECTION => ['onIndexCollection', -24], + PhraseaEvents::INDEX_NEW_RECORD => ['onIndexNewRecord', -48], + PhraseaEvents::INDEX_UPDATE_RECORD => ['onIndexUpdateRecord', -48], + PhraseaEvents::INDEX_REMOVE_RECORD => ['onIndexRemoveRecord', -48], + KernelEvents::FINISH_REQUEST => 'doFlushIndex', + ]; + } + + public function onIndexDatabox(DataboxIndexEvent $event) + { + $databox = $event->getDatabox(); + $connection = $databox->get_connection(); + + // set 'to_index' fag where 'to_index' flag is not already set + // for given databox + // @todo sql query this should not be done right here + $sql = <<executeUpdate($sql, [ + ':token' => PhraseaTokens::TOKEN_INDEX + ], [ + \PDO::PARAM_INT + ]); + } + + public function onIndexCollection(CollectionIndexEvent $event) + { + $collection = $event->getCollection(); + $connection = $collection->get_connection(); + + // set 'to_index' fag where 'to_index' flag is not already set + // for given collection + // @todo sql query this should not be done right here + $sql = <<executeUpdate($sql, [ + ':coll_id' => $collection->get_coll_id(), + ':token' => PhraseaTokens::TOKEN_INDEX + ], [ + \PDO::PARAM_INT, + \PDO::PARAM_INT + ]); + } + + public function onIndexUpdateRecord(RecordIndexEvent $event) + { + $record = $event->getRecord(); + if (self::$recordsToUpdate->contains($record)) { + return; + } + self::$recordsToUpdate->attach($record); + } + + public function onIndexNewRecord(RecordIndexEvent $event) + { + $record = $event->getRecord(); + if (self::$recordsToAdd->contains($record)) { + return; + } + self::$recordsToAdd->attach($record); + } + + public function onIndexRemoveRecord(RecordIndexEvent $event) + { + $record = $event->getRecord(); + if (self::$recordsToRemove->contains($record)) { + return; + } + self::$recordsToRemove->attach($record); + } + + public function doFlushIndex() + { + if (self::$recordsToUpdate->count() > 0) { + $this->doUpdateIndex(); + } + + if (self::$recordsToAdd->count() > 0) { + $this->doInsertIndex(); + } + + if (self::$recordsToRemove->count() > 0) { + $this->doDeleteIndex(); + } + } + + private function doUpdateIndex() + { + $this->doRecordAction(self::$recordsToUpdate, 'update'); + } + + private function doInsertIndex() + { + $this->doRecordAction(self::$recordsToAdd, 'index'); + } + + private function doDeleteIndex() + { + $this->doRecordAction(self::$recordsToRemove, 'delete'); + } + + private function doRecordAction(\SplObjectStorage $poolOfRecords, $action) + { + // filter by databox + $toIndex = []; + foreach ($poolOfRecords as $record) { + $toIndex[$record->get_sbas_id()][] = $record; + } + + $bulk = new BulkOperation($this->client); + $bulk->setDefaultIndex($this->indexName); + $bulk->setAutoFlushLimit(200); + + $recordHelper = new RecordHelper($this->appbox); + + foreach($toIndex as $databoxId => $records) { + $databox = $this->appbox->get_databox($databoxId); + $fetcher = new RecordPoolFetcher($databox, $recordHelper, $records); + + call_user_func_array([$this->recordIndexer, $action], [$bulk, $fetcher]); + } + + // should we refresh ? + $this->client->indices()->refresh(['index' => $this->indexName]); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordSubscriber.php new file mode 100644 index 0000000000..56fa008a03 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordSubscriber.php @@ -0,0 +1,83 @@ + 'onChangeCollection', + PhraseaEvents::RECORD_CREATE => 'onCreate', + PhraseaEvents::RECORD_DELETE => 'onDelete', + PhraseaEvents::RECORD_CHANGE_METADATA => 'onChangeMetadata', + PhraseaEvents::RECORD_CHANGE_STATUS => 'onChangeStatus', + PhraseaEvents::RECORD_BUILD_SUB_DEFINITION => 'onBuildSubDef', + PhraseaEvents::RECORD_CHANGE_ORIGINAL_NAME => 'onChangeOriginalName', + ]; + } + + public function onChangeCollection(ChangeCollectionEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_UPDATE_RECORD, new RecordIndexEvent($event->getRecord())); + } + + public function onCreate(CreateRecordEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_NEW_RECORD, new RecordIndexEvent($event->getRecord())); + } + + public function onDelete(DeleteRecordEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_REMOVE_RECORD, new RecordIndexEvent($event->getRecord())); + } + + public function onChangeMetadata(ChangeMetadataEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_UPDATE_RECORD, new RecordIndexEvent($event->getRecord())); + } + + public function onChangeStatus(ChangeStatusEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_UPDATE_RECORD, new RecordIndexEvent($event->getRecord())); + } + + public function onBuildSubDef(BuildSubDefEvent $event) + { + if (in_array($event->getSubDefName(), ['thumbnail', 'thumbnailgif', 'preview'])) { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_UPDATE_RECORD, new RecordIndexEvent($event->getRecord())); + } + } + + public function onChangeOriginalName(ChangeOriginalNameEvent $event) + { + $dispatcher = $event->getDispatcher(); + $dispatcher->dispatch(PhraseaEvents::INDEX_UPDATE_RECORD, new RecordIndexEvent($event->getRecord())); + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/StorySubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/StorySubscriber.php new file mode 100644 index 0000000000..e9b4bb16d3 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/StorySubscriber.php @@ -0,0 +1,46 @@ +indexer = $indexer; + } + + public static function getSubscribedEvents() + { + return [ + PhraseaEvents::STORY_CREATE => 'onCreate', + PhraseaEvents::STORY_DELETE => 'onDelete', + ]; + } + + public function onCreate(CreateStoryEvent $event) + { + $this->indexer->indexSingleRecord($event->getRecord()); + } + + public function onDelete(DeleteStoryEvent $event) + { + $this->indexer->deleteSingleRecord($event->getRecord()); + } +} diff --git a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php index e2c19c821f..5aed176147 100644 --- a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php +++ b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php @@ -29,7 +29,6 @@ final class PhraseaEvents const ORDER_CREATE = 'order.create'; const ORDER_DELIVER = 'order.deliver'; const ORDER_DENY = 'order.deny'; - const COLLECTION_CREATE = 'collection.create'; const FEED_ENTRY_CREATE = 'feed-entry.create'; const REGISTRATION_CREATE = 'registration.create'; const REGISTRATION_AUTOREGISTER = 'registration.autoregister'; @@ -42,4 +41,31 @@ final class PhraseaEvents const BRIDGE_UPLOAD_FAILURE = 'bridge.upload-failure'; const EXPORT_MAIL_FAILURE = 'export.mail-failure'; const EXPORT_CREATE = 'export.create'; + + const RECORD_CREATE = 'record.create'; + const RECORD_DELETE = 'record.delete'; + const STORY_CREATE = 'story.create'; + const STORY_DELETE = 'story.delete'; + const RECORD_CHANGE_COLLECTION = 'record.collection'; + const RECORD_CHANGE_METADATA = 'record.metadata'; + const RECORD_CHANGE_ORIGINAL_NAME = 'record.original_name'; + const RECORD_CHANGE_STATUS = 'record.status'; + const RECORD_BUILD_SUB_DEFINITION = 'record.sub_definition'; + const RECORD_SUBSTITUTE = 'record.substitute'; + + const DATABOX_UPDATE_FIELD = 'databox.update.field'; + const DATABOX_DELETE_FIELD = 'databox.delete.field'; + + const DATABOX_UPDATE_STATUS = 'databox.create.status'; + const DATABOX_DELETE_STATUS = 'databox.delete.status'; + + const COLLECTION_CHANGE_NAME = 'collection.name'; + const COLLECTION_CREATE = 'collection.create'; + + const INDEX_NEW_RECORD = 'index.new.record'; + const INDEX_UPDATE_RECORD = 'index.update.record'; + const INDEX_REMOVE_RECORD = 'index.remove.record'; + const INDEX_COLLECTION = 'index.collection'; + const INDEX_DATABOX = 'index.databox'; + const INDEX_THESAURUS = 'index.thesaurus'; } diff --git a/lib/Alchemy/Phrasea/Core/PhraseaTokens.php b/lib/Alchemy/Phrasea/Core/PhraseaTokens.php new file mode 100644 index 0000000000..e0155bec6f --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/PhraseaTokens.php @@ -0,0 +1,22 @@ +share(function (Application $app) { return new ContentNegotiationSubscriber($app); }); + $app['phraseanet.record-subscriber'] = $app->share(function (Application $app) { + return new RecordSubscriber($app['elasticsearch.indexer.record_indexer']); + }); + $app['phraseanet.story-subscriber'] = $app->share(function (Application $app) { + return new StorySubscriber($app['elasticsearch.indexer.record_indexer']); + }); + $app['phraseanet.elasticsearch-subscriber'] = $app->share(function (Application $app) { + return new ElasticSearchSubscriber( + $app['elasticsearch.indexer.record_indexer'], + $app['elasticsearch.indexer.term_indexer'], + $app['elasticsearch.client'], + $app['phraseanet.appbox'], + $app['elasticsearch.options']['index'] + ); + }); + $app['phraseanet.databox-subscriber'] = $app->share(function (Application $app) { + return new DataboxSubscriber(); + }); + $app['phraseanet.collection-subscriber'] = $app->share(function (Application $app) { + return new CollectionSubscriber(); + }); } public function boot(Application $app) diff --git a/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php index e718cc0854..f333e4074f 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/PhraseanetServiceProvider.php @@ -69,7 +69,7 @@ class PhraseanetServiceProvider implements ServiceProviderInterface }); $app['phraseanet.metadata-setter'] = $app->share(function (SilexApplication $app) { - return new PhraseanetMetadataSetter(); + return new PhraseanetMetadataSetter($app['dispatcher']); }); $app['phraseanet.user-query'] = function (SilexApplication $app) { diff --git a/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php index 2aa5907454..6210c72e5e 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/SubdefServiceProvider.php @@ -25,7 +25,7 @@ class SubdefServiceProvider implements ServiceProviderInterface return new SubdefGenerator($app, $app['media-alchemyst'], $app['filesystem'], $app['mediavorus'], isset($app['task-manager.logger']) ? $app['task-manager.logger'] : $app['monolog']); }); $app['subdef.substituer'] = $app->share(function (SilexApplication $app) { - return new SubdefSubstituer($app, $app['filesystem'], $app['media-alchemyst'], $app['mediavorus']); + return new SubdefSubstituer($app, $app['filesystem'], $app['media-alchemyst'], $app['mediavorus'], $app['dispatcher']); }); } diff --git a/lib/Alchemy/Phrasea/Core/Provider/TasksServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/TasksServiceProvider.php index 3d8f610783..6cbaccba01 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/TasksServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/TasksServiceProvider.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\TaskManager\Job\FtpJob; use Alchemy\Phrasea\TaskManager\Job\ArchiveJob; use Alchemy\Phrasea\TaskManager\Job\BridgeJob; use Alchemy\Phrasea\TaskManager\Job\FtpPullJob; +use Alchemy\Phrasea\TaskManager\Job\IndexerJob; use Alchemy\Phrasea\TaskManager\Job\PhraseanetIndexerJob; use Alchemy\Phrasea\TaskManager\Job\RecordMoverJob; use Alchemy\Phrasea\TaskManager\Job\SubdefsJob; @@ -78,6 +79,7 @@ class TasksServiceProvider implements ServiceProviderInterface new SubdefsJob($app['dispatcher'], $logger, $app['translator']), new WriteMetadataJob($app['dispatcher'], $logger, $app['translator']), new WebhookJob($app['dispatcher'], $logger, $app['translator']), + new IndexerJob($app['dispatcher'], $logger, $app['translator']), ]; }); } diff --git a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php index b77269543a..c30f0c6fdd 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php +++ b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php @@ -12,7 +12,8 @@ namespace Alchemy\Phrasea\Media; use Alchemy\Phrasea\Application; -use Alchemy\Phrasea\SearchEngine\Elastic\ElasticSearchEngine; +use Alchemy\Phrasea\Core\Event\RecordEvent\BuildSubDefEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Specification\SpecificationInterface; use MediaVorus\MediaVorus; @@ -74,9 +75,7 @@ class SubdefGenerator $record->clearSubdefCache($subdefname); - if ($this->app['phraseanet.SE'] instanceof ElasticSearchEngine && in_array($subdefname, ['thumbnail', 'thumbnailgif', 'preview'])) { - $this->app['phraseanet.SE']->updateRecord($record); - } + $this->app['dispatcher']->dispatch(PhraseaEvents::RECORD_BUILD_SUB_DEFINITION, new BuildSubDefEvent($record, $subDefName)); } return $this; diff --git a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php index f5ac1a134c..aaa542fc91 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php +++ b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php @@ -12,10 +12,12 @@ namespace Alchemy\Phrasea\Media; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Core\PhraseaEvents; use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Exception\ExceptionInterface as MediaAlchemystException; use MediaVorus\Media\MediaInterface; use MediaVorus\MediaVorus; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Filesystem\Filesystem; class SubdefSubstituer @@ -24,12 +26,13 @@ class SubdefSubstituer private $fs; private $mediavorus; - public function __construct(Application $app, Filesystem $fs, Alchemyst $alchemyst, MediaVorus $mediavorus) + public function __construct(Application $app, Filesystem $fs, Alchemyst $alchemyst, MediaVorus $mediavorus, EventDispatcherInterface $dispatcher) { $this->alchemyst = $alchemyst; $this->app = $app; $this->fs = $fs; $this->mediavorus = $mediavorus; + $this->dispatcher = $dispatcher; } public function substitute(\record_adapter $record, $name, MediaInterface $media) @@ -92,5 +95,7 @@ class SubdefSubstituer if ($name == 'document') { $record->rebuild_subdefs(); } + + $this->dispatcher->dispatch(PhraseaEvents::RECORD_SUBSTITUTE, new SubstituteRecordEvent($record)); } } diff --git a/lib/Alchemy/Phrasea/Metadata/PhraseanetMetadataSetter.php b/lib/Alchemy/Phrasea/Metadata/PhraseanetMetadataSetter.php index 153d87bcf5..4f6c11fa90 100644 --- a/lib/Alchemy/Phrasea/Metadata/PhraseanetMetadataSetter.php +++ b/lib/Alchemy/Phrasea/Metadata/PhraseanetMetadataSetter.php @@ -12,9 +12,17 @@ namespace Alchemy\Phrasea\Metadata; use PHPExiftool\Driver\Metadata\Metadata; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class PhraseanetMetadataSetter { + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->disptatcher = $dispatcher; + } + public function replaceMetadata($metadataCollection, \record_adapter $record) { $metadatas = []; @@ -93,6 +101,8 @@ class PhraseanetMetadataSetter if (count($metas) > 0) { $record->set_metadatas($metas, true); + + $this->disptatcher->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); } } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/BulkOperation.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/BulkOperation.php index 1e8eb5ed21..9d6b69638c 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/BulkOperation.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/BulkOperation.php @@ -49,11 +49,7 @@ class BulkOperation public function index(array $params) { - $header = array(); - $header['_id'] = igorw\get_in($params, ['id']); - $header['_index'] = igorw\get_in($params, ['index']); - $header['_type'] = igorw\get_in($params, ['type']); - $this->stack[] = ['index' => $header]; + $this->stack[] = ['index' => $this->getBulkHeader($params)]; $this->stack[] = igorw\get_in($params, ['body']); if ($this->flushLimit === count($this->stack) / 2) { @@ -61,6 +57,25 @@ class BulkOperation } } + public function update(array $params) + { + $this->stack[] = ['update' => $this->getBulkHeader($params)]; + $this->stack[] = ['doc' => igorw\get_in($params, ['doc'])]; + + if ($this->flushLimit === count($this->stack) / 2) { + $this->flush(); + } + } + + public function delete(array $params) + { + $this->stack[] = ['delete' => $this->getBulkHeader($params)]; + + if ($this->flushLimit === count($this->stack) / 2) { + $this->flush(); + } + } + public function flush() { // Do not try to flush an empty stack @@ -76,7 +91,11 @@ class BulkOperation } } $params['body'] = $this->stack; - printf("ES Bulk query with %d items\n", count($this->stack) / 2); + + if (php_sapi_name() === 'cli') { + printf("ES Bulk query with %d items\n", count($this->stack) / 2); + } + $response = $this->client->bulk($params); $this->stack = array(); @@ -89,4 +108,15 @@ class BulkOperation throw new Exception('Errors occurred during bulk indexing request, index may be in an inconsistent state'); } } + + private function getBulkHeader(array $params) + { + $header = []; + $header['_id'] = igorw\get_in($params, ['id']); + $header['_index'] = igorw\get_in($params, ['index']); + $header['_type'] = igorw\get_in($params, ['type']); + + return $header; + } + } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index 590d125a71..77ba9ce774 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -188,7 +188,7 @@ class ElasticSearchEngine implements SearchEngineInterface */ public function addRecord(\record_adapter $record) { - $this->app['elasticsearch.indexer.record_indexer']->indexSingleRecord($record, $this->indexName); + $this->app['elasticsearch.indexer.record_indexer']->indexSingleRecord($record); return $this; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordFetcher.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/AbstractRecordFetcher.php similarity index 61% rename from lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordFetcher.php rename to lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/AbstractRecordFetcher.php index f2e9aeace6..b85ef84f78 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordFetcher.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/AbstractRecordFetcher.php @@ -9,79 +9,75 @@ * file that was distributed with this source code. */ -namespace Alchemy\Phrasea\SearchEngine\Elastic; +namespace Alchemy\Phrasea\SearchEngine\Elastic\Fetcher; +use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper; use Alchemy\Phrasea\SearchEngine\SearchEngineInterface; use Doctrine\DBAL\Connection; use databox; -use PDO; -class RecordFetcher +abstract class AbstractRecordFetcher { - private $connection; - private $statementRecords; + protected $statementRecords; + protected $connection; + + protected $offset = 0; + protected $batchSize = 1; + private $helper; + private $databox; - private $offset = 0; - private $batchSize = 1; - - private $databoxId; + private $postFetch; public function __construct(databox $databox, RecordHelper $helper) { $this->connection = $databox->get_connection(); - $this->databoxId = $databox->get_sbas_id(); - $this->helper = $helper; + $this->databox = $databox; + $this->helper = $helper; } public function fetch() { $statementRecords = $this->statementRecords(); - // Fetch records rows - $statementRecords->execute(); - printf("Query %d/%d -> %d rows\n", $this->offset, $this->batchSize, $statementRecords->rowCount()); + if (php_sapi_name() === 'cli' && ($this->offset !== 0 || $statementRecords->rowCount() <= 0)) { + printf("Query %d/%d -> %d rows on database %s\n", $this->offset, $this->batchSize, $statementRecords->rowCount(), $this->databox->get_dbname()); + } + $records = []; while ($record = $statementRecords->fetch()) { $records[$record['record_id']] = $record; - printf("Record found (#%d)\n", $record['record_id']); $this->offset++; } if (count($records) < 1) { - printf("End of records\n"); + if (php_sapi_name() === 'cli') { + printf("End of records\n"); + } - return false; // End + return false; } $this->addTitleToRecord($records); $this->addMetadataToRecords($records); - $this->addSubdefsToRecord($records); + $this->addSubDefinitionsToRecord($records); // Hydrate records foreach ($records as $key => $record) { $records[$key] = $this->hydrate($record); } + if (is_callable($this->postFetch)) { + call_user_func($this->postFetch, $records); + } + return $records; } - public function fetchOne(\record_adapter $record_adapter) + public function setPostFetch(\Closure $callable) { - $stmt = $this->statementRecord($record_adapter->get_record_id()); - $stmt->execute(); - - $records = $stmt->fetchAll(); - $this->addTitleToRecord($records); - $this->addMetadataToRecords($records); - $this->addSubdefsToRecord($records); - - foreach ($records as $key => $record) { - $records[$key] = $this->hydrate($record); - } - - return array_pop($records); + $this->postFetch = $callable; } public function setBatchSize($size) @@ -98,9 +94,9 @@ class RecordFetcher $record['record_id'] = (int) $record['record_id']; $record['collection_id'] = (int) $record['collection_id']; // Some identifiers - $record['id'] = $this->helper->getUniqueRecordId($this->databoxId, $record['record_id']); - $record['base_id'] = $this->helper->getUniqueCollectionId($this->databoxId, $record['collection_id']); - $record['databox_id'] = $this->databoxId; + $record['id'] = $this->helper->getUniqueRecordId($this->databox->get_sbas_id(), $record['record_id']); + $record['base_id'] = $this->helper->getUniqueCollectionId($this->databox->get_sbas_id(), $record['collection_id']); + $record['databox_id'] = $this->databox->get_sbas_id(); if ((int) $record['parent_record_id'] === 1) { $record['record_type'] = SearchEngineInterface::GEM_TYPE_STORY; @@ -117,63 +113,6 @@ class RecordFetcher return $record; } - private function statementRecords() - { - if (!$this->statementRecords) { - $sql = <<connection->prepare($sql); - $statement->bindParam(':offset', $this->offset, PDO::PARAM_INT); - $statement->bindParam(':limit', $this->batchSize, PDO::PARAM_INT); - $this->statementRecords = $statement; - } - - return $this->statementRecords; - } - - private function statementRecord($id) - { - $sql = <<connection->prepare($sql); - $statement->bindValue(':id', $id, PDO::PARAM_INT); - - return $statement; - } - private function execStatementMetadata($ids) { $sql = <<execStatementSubdefs(array_keys($records)); + $statementSubDef = $this->execStatementSubDefinitions(array_keys($records)); - while ($subdefs = $statementSubdef->fetch()) { - $records[$subdefs['record_id']]['subdefs'][$subdefs['name']] = array( - 'path' => $subdefs['path'], - 'width' => $subdefs['width'], - 'height' => $subdefs['height'], + while ($subDefinitions = $statementSubDef->fetch()) { + $records[$subDefinitions['record_id']]['subdefs'][$subDefinitions['name']] = array( + 'path' => $subDefinitions['path'], + 'width' => $subDefinitions['width'], + 'height' => $subDefinitions['height'], ); } } - private function execStatementSubdefs($ids) + private function execStatementSubDefinitions($ids) { $sql = <<connection->executeQuery($sql, array($ids), array(Connection::PARAM_INT_ARRAY)); } + + /** Provides PDO Statement that fetches records */ + abstract protected function statementRecords(); } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordFetcher.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordFetcher.php new file mode 100644 index 0000000000..9293ab73c9 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordFetcher.php @@ -0,0 +1,47 @@ +connection->executeQuery($sql, [ + $this->offset, + $this->batchSize, + ], [ + PDO::PARAM_INT, + PDO::PARAM_INT, + ]); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordPoolFetcher.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordPoolFetcher.php new file mode 100644 index 0000000000..01d442b428 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/RecordPoolFetcher.php @@ -0,0 +1,72 @@ +pool = $records; + + parent::__construct($databox, $helper); + } + + protected function statementRecords() + { + $sql = <<get_record_id(); + }, $this->pool); + + return $this->connection->executeQuery($sql, [ + $records, + $this->offset, + $this->batchSize + ], [ + Connection::PARAM_INT_ARRAY, + PDO::PARAM_INT, + PDO::PARAM_INT, + ]); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/ScheduledIndexationRecordFetcher.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/ScheduledIndexationRecordFetcher.php new file mode 100644 index 0000000000..6a5a4e0d98 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/ScheduledIndexationRecordFetcher.php @@ -0,0 +1,56 @@ + 0 + AND (jeton & ?) = 0 + ORDER BY r.record_id ASC + LIMIT ?, ? +SQL; + + $params = [ + PhraseaTokens::TOKEN_INDEX, + PhraseaTokens::TOKEN_INDEXING, + $this->offset, + $this->batchSize, + ]; + + return $this->connection->executeQuery($sql, $params, [ + PDO::PARAM_INT, + PDO::PARAM_INT, + PDO::PARAM_INT, + PDO::PARAM_INT, + ]); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/SingleRecordFetcher.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/SingleRecordFetcher.php new file mode 100644 index 0000000000..a6145b087d --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Fetcher/SingleRecordFetcher.php @@ -0,0 +1,63 @@ +record = $record; + + parent::__construct($databox, $helper); + } + + public function fetch() + { + $records = parent::fetch(); + + return array_pop($records); + } + + protected function statementRecords() + { + $sql = <<connection->executeQuery($sql, [':record_id' => $this->record->get_record_id()], [PDO::PARAM_INT,]); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer.php index 785827700c..e4058a143b 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer.php @@ -122,7 +122,7 @@ class Indexer } $event = $stopwatch->stop('populate'); - printf("Indexation finished in %s min (Mem. %s)", ($event->getDuration()/1000/60), $event->getMemory()); + printf("Indexation finished in %s min (Mem. %s Mo)", ($event->getDuration()/1000/60), bcdiv($event->getMemory(), 1048576, 2)); } private function disableShardRefreshing() diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php index a2d8d05d61..d4d706741e 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php @@ -15,8 +15,11 @@ use Alchemy\Phrasea\SearchEngine\Elastic\BulkOperation; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticSearchEngine; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\MergeException; +use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\AbstractRecordFetcher; +use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\RecordPoolFetcher; +use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\SingleRecordFetcher; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; -use Alchemy\Phrasea\SearchEngine\Elastic\RecordFetcher; +use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\RecordFetcher; use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper; use Alchemy\Phrasea\SearchEngine\Elastic\StringUtils; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus; @@ -64,6 +67,7 @@ class RecordIndexer $params = array(); $params['id'] = $record['id']; $params['type'] = self::TYPE_NAME; + $params['index'] = $this->elasticSearchEngine->getIndexName(); $params['body'] = $this->transform($record); $bulk->index($params); } @@ -71,18 +75,54 @@ class RecordIndexer } } - public function indexSingleRecord(\record_adapter $record_adapter, $indexName) + public function index(BulkOperation $bulk, AbstractRecordFetcher $fetcher) { - $fetcher = new RecordFetcher($record_adapter->get_databox(), $this->helper); - $record = $fetcher->fetchOne($record_adapter); + while ($records = $fetcher->fetch()) { + foreach ($records as $record) { + $params = array(); + // header + $params['id'] = $record['id']; + $params['type'] = self::TYPE_NAME; + $params['index'] = $this->elasticSearchEngine->getIndexName(); + // body + $params['body'] = $this->transform($record); + $bulk->index($params); + } + } + $bulk->flush(); + } - $params = array(); - $params['id'] = $record['id']; - $params['type'] = self::TYPE_NAME; - $params['index'] = $indexName; - $params['body'] = $this->transform($record); + public function update(BulkOperation $bulk, AbstractRecordFetcher $fetcher) + { + while ($records = $fetcher->fetch()) { + foreach ($records as $record) { + $params = array(); + // header + $params['id'] = $record['id']; + $params['type'] = self::TYPE_NAME; + $params['index'] = $this->elasticSearchEngine->getIndexName(); + // doc + $params['doc'] = $this->transform($record); + $bulk->update($params); + } + } + $bulk->flush(); + } - return $this->elasticSearchEngine->getClient()->index($params); + public function delete(BulkOperation $bulk, AbstractRecordFetcher $fetcher) + { + while ($records = $fetcher->fetch()) { + foreach ($records as $record) { + $params = array(); + // header + $params['id'] = $record['id']; + $params['type'] = self::TYPE_NAME; + $params['index'] = $this->elasticSearchEngine->getIndexName(); + + $bulk->delete($params); + } + } + $bulk->flush(); } private function findLinkedConcepts($structure, array $record) @@ -258,6 +298,7 @@ class RecordIndexer private function transform($record) { $dateFields = $this->elasticSearchEngine->getAvailableDateFields(); + $structure = $this->getFieldsStructure(); $databox = $this->appbox->get_databox($record['databox_id']); diff --git a/lib/Alchemy/Phrasea/TaskManager/Editor/IndexerEditor.php b/lib/Alchemy/Phrasea/TaskManager/Editor/IndexerEditor.php new file mode 100644 index 0000000000..d2d9ca013a --- /dev/null +++ b/lib/Alchemy/Phrasea/TaskManager/Editor/IndexerEditor.php @@ -0,0 +1,52 @@ + + +XML; + } + + /** + * {@inheritdoc} + */ + protected function getFormProperties() + { + return []; + } +} diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php index 1fcd73bd94..d6c17fab18 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php @@ -12,6 +12,8 @@ namespace Alchemy\Phrasea\TaskManager\Job; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Core\Event\RecordEvent\CreateStoryEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\TaskManager\Editor\ArchiveEditor; @@ -1016,7 +1018,7 @@ class ArchiveJob extends AbstractJob $story->set_binary_status(\databox_status::operation_or($app, $stat0, $stat1)); $story->rebuild_subdefs(); - $app['phraseanet.SE']->addStory($story); + $app['dispatcher']->dispatch(PhraseaEvents::STORY_CREATE, new CreateStoryEvent($story)); unset($media); diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/IndexerJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/IndexerJob.php new file mode 100644 index 0000000000..306cbffe16 --- /dev/null +++ b/lib/Alchemy/Phrasea/TaskManager/Job/IndexerJob.php @@ -0,0 +1,110 @@ +translator->trans('Indexation task'); + } + + /** + * {@inheritdoc} + */ + public function getJobId() + { + return 'Indexer'; + } + + /** + * {@inheritdoc} + */ + public function getDescription() + { + return $this->translator->trans("Indexing Batch (collections/databox)"); + } + + /** + * {@inheritdoc} + */ + public function getEditor() + { + return new IndexerEditor($this->translator); + } + + /** + * {@inheritdoc} + */ + protected function doJob(JobData $data) + { + $app = $data->getApplication(); + + $recordHelper = new RecordHelper($app['phraseanet.appbox']); + + // set bulk + $bulk = new BulkOperation($app['elasticsearch.client']); + $bulk->setDefaultIndex($app['elasticsearch.options']['index']); + $bulk->setAutoFlushLimit(1000); + + foreach ($app['phraseanet.appbox']->get_databoxes() as $databox) { + if (!$this->isStarted()) { + break; + } + + $connection = $databox->get_connection(); + + // fetch records with 'to_index' flag set and 'indexing' flag not set + $fetcher = new ScheduledIndexationRecordFetcher($databox, $recordHelper); + $fetcher->setBatchSize(200); + + // set 'indexing' flag, unset 'to_index' flag once + // records have been fetched + $fetcher->setPostFetch(function($records) use ($connection) { + $sql = <<executeQuery($sql, [PhraseaTokens::TOKEN_INDEXING, PhraseaTokens::TOKEN_INDEX, $records], [\PDO::PARAM_INT, \PDO::PARAM_INT, Connection::PARAM_INT_ARRAY]); + }); + + // update es index + $app['elasticsearch.indexer.record_indexer']->update($bulk, $fetcher); + + // unset 'indexing' flag + $sql = <<executeQuery($sql, [PhraseaTokens::TOKEN_INDEXING], [\PDO::PARAM_INT]); + } + } +} diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php index 8e8e0cd449..ad504907cf 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/RecordMoverJob.php @@ -12,7 +12,12 @@ namespace Alchemy\Phrasea\TaskManager\Job; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeCollectionEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent; +use Alchemy\Phrasea\Core\Event\RecordEvent\DeleteRecordEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\TaskManager\Editor\RecordMoverEditor; +use JMS\Serializer\EventDispatcher\EventDispatcher; class RecordMoverJob extends AbstractJob { @@ -78,7 +83,9 @@ class RecordMoverJob extends AbstractJob if (array_key_exists('coll', $row)) { $coll = \collection::get_from_coll_id($app, $databox, $row['coll']); $rec->move_to_collection($coll, $app['phraseanet.appbox']); - $app['phraseanet.SE']->updateRecord($rec); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_COLLECTION, new ChangeCollectionEvent($rec)); + if ($logsql) { $this->log('debug', sprintf("on sbas %s move rid %s to coll %s \n", $row['sbas_id'], $row['record_id'], $coll->get_coll_id())); } @@ -93,7 +100,9 @@ class RecordMoverJob extends AbstractJob } } $rec->set_binary_status(implode('', $status)); - $app['phraseanet.SE']->updateRecord($rec); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_STATUS, new ChangeStatusEvent($rec)); + if ($logsql) { $this->log('debug', sprintf("on sbas %s set rid %s status to %s \n", $row['sbas_id'], $row['record_id'], $status)); } @@ -104,14 +113,16 @@ class RecordMoverJob extends AbstractJob if ($row['deletechildren'] && $rec->is_grouping()) { foreach ($rec->get_children() as $child) { $child->delete(); - $app['phraseanet.SE']->removeRecord($child); + + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_DELETE, new DeleteRecordEvent($child)); + if ($logsql) { $this->log('debug', sprintf("on sbas %s delete (grp child) rid %s \n", $row['sbas_id'], $child->get_record_id())); } } } $rec->delete(); - $app['phraseanet.SE']->removeRecord($rec); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_DELETE, new DeleteRecordEvent($rec)); if ($logsql) { $this->log('debug', sprintf("on sbas %s delete rid %s \n", $row['sbas_id'], $rec->get_record_id())); } diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php index 3a7f0e0626..6c5bb4d599 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/SubdefsJob.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\TaskManager\Job; +use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\TaskManager\Editor\SubdefsEditor; use MediaAlchemyst\Transmuter\Image2Image; @@ -67,7 +68,7 @@ class SubdefsJob extends AbstractJob $sql = 'SELECT coll_id, record_id FROM record - WHERE jeton & ' . JETON_MAKE_SUBDEF . ' > 0 + WHERE jeton & ' . PhraseaTokens::TOKEN_MAKE_SUBDEF . ' > 0 ORDER BY record_id DESC LIMIT 0, 30'; $stmt = $conn->prepare($sql); $stmt->execute(); @@ -88,7 +89,7 @@ class SubdefsJob extends AbstractJob } $sql = 'UPDATE record - SET jeton=(jeton & ~' . JETON_MAKE_SUBDEF . '), moddate=NOW() + SET jeton=(jeton & ~' . PhraseaTokens::TOKEN_MAKE_SUBDEF . '), moddate=NOW() WHERE record_id=:record_id'; $stmt = $conn->prepare($sql); @@ -98,7 +99,7 @@ class SubdefsJob extends AbstractJob // rewrite metadata $sql = 'UPDATE record SET status=(status & ~0x03), - jeton=(jeton | ' . JETON_WRITE_META_SUBDEF . ') + jeton=(jeton | ' . PhraseaTokens::TOKEN_MAKE_SUBDEF . ') WHERE record_id=:record_id'; $stmt = $conn->prepare($sql); $stmt->execute([':record_id' => $row['record_id']]); diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php index bcd90b38f5..ca98a06f7b 100644 --- a/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php +++ b/lib/Alchemy/Phrasea/TaskManager/Job/WriteMetadataJob.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\TaskManager\Job; +use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\TaskManager\Editor\WriteMetadataEditor; use PHPExiftool\Driver\Metadata; use PHPExiftool\Driver\Value; @@ -82,7 +83,7 @@ class WriteMetadataJob extends AbstractJob } } - $sql = 'SELECT record_id, coll_id, jeton FROM record WHERE (jeton & ' . JETON_WRITE_META . ' > 0)'; + $sql = 'SELECT record_id, coll_id, jeton FROM record WHERE (jeton & ' . PhraseaTokens::TOKEN_WRITE_META . ' > 0)'; $stmt = $conn->prepare($sql); $stmt->execute(); @@ -98,8 +99,8 @@ class WriteMetadataJob extends AbstractJob $subdefs = []; foreach ($record->get_subdefs() as $name => $subdef) { - $write_document = (($token & JETON_WRITE_META_DOC) && $name == 'document'); - $write_subdef = (($token & JETON_WRITE_META_SUBDEF) && isset($metaSubdefs[$name . '_' . $type])); + $write_document = (($token & PhraseaTokens::TOKEN_WRITE_META_DOC) && $name == 'document'); + $write_subdef = (($token & PhraseaTokens::TOKEN_WRITE_META_SUBDEF) && isset($metaSubdefs[$name . '_' . $type])); if (($write_document || $write_subdef) && $subdef->is_physically_present()) { $subdefs[$name] = $subdef->get_pathfile(); @@ -196,7 +197,7 @@ class WriteMetadataJob extends AbstractJob } } - $sql = 'UPDATE record SET jeton=jeton & ~' . JETON_WRITE_META . ' WHERE record_id = :record_id'; + $sql = 'UPDATE record SET jeton=jeton & ~' . PhraseaTokens::TOKEN_WRITE_META . ' WHERE record_id = :record_id'; $stmt = $conn->prepare($sql); $stmt->execute([':record_id' => $record_id]); $stmt->closeCursor(); diff --git a/lib/classes/caption/field.php b/lib/classes/caption/field.php index 12fcd542f9..7b65f40d2d 100644 --- a/lib/classes/caption/field.php +++ b/lib/classes/caption/field.php @@ -10,6 +10,9 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class caption_field implements cache_cacheableInterface { @@ -282,7 +285,7 @@ class caption_field implements cache_cacheableInterface return $values; } - public static function rename_all_metadatas(databox_field $databox_field) + public static function rename_all_metadata(EventDispatcherInterface $dispatcher, databox_field $databox_field) { $sql = 'SELECT count(id) as count_id FROM metadatas WHERE meta_struct_id = :meta_struct_id'; @@ -318,10 +321,8 @@ class caption_field implements cache_cacheableInterface $record = $databox_field->get_databox()->get_record($row['record_id']); $record->set_metadatas([]); - /** - * TODO NEUTRON add App - */ - $app['phraseanet.SE']->updateRecord($record); + $dispatcher->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); + unset($record); } catch (\Exception $e) { @@ -374,7 +375,8 @@ class caption_field implements cache_cacheableInterface $caption_field->delete(); $record->set_metadatas([]); - $app['phraseanet.SE']->updateRecord($record); + $app['dispatcher']->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); + unset($caption_field); unset($record); } catch (\Exception $e) { diff --git a/lib/classes/databox.php b/lib/classes/databox.php index 8f8c0ed585..c7a96ab5bb 100644 --- a/lib/classes/databox.php +++ b/lib/classes/databox.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Driver\Connection; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Translation\TranslatorInterface; +use Alchemy\Phrasea\Core\PhraseaTokens; class databox extends base { @@ -439,7 +440,7 @@ class databox extends base $ret['thesaurus_indexed'] += $row['n']; } - $sql = "SELECT type, COUNT(record_id) AS n FROM record WHERE jeton & ".JETON_MAKE_SUBDEF." GROUP BY type"; + $sql = "SELECT type, COUNT(record_id) AS n FROM record WHERE jeton & ".PhraseaTokens::TOKEN_MAKE_SUBDEF." GROUP BY type"; $stmt = $this->get_connection()->prepare($sql); $stmt->execute(); $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); diff --git a/lib/classes/databox/field.php b/lib/classes/databox/field.php index 193ff36a4f..eab6ea0d6b 100644 --- a/lib/classes/databox/field.php +++ b/lib/classes/databox/field.php @@ -422,7 +422,7 @@ class databox_field implements cache_cacheableInterface $stmt->closeCursor(); if ($this->renamed) { - caption_field::rename_all_metadatas($this); + caption_field::rename_all_metadata($this->app['dispatcher'], $this); $this->renamed = false; } diff --git a/lib/classes/databox/status.php b/lib/classes/databox/status.php index 7d736e9e38..8bc19dee48 100644 --- a/lib/classes/databox/status.php +++ b/lib/classes/databox/status.php @@ -128,6 +128,7 @@ class databox_status if ( ! isset(self::$_status[$sbas_id])) self::$_status[$sbas_id] = new databox_status($app, $sbas_id); + return self::$_status[$sbas_id]->status; } @@ -265,7 +266,7 @@ class databox_status public static function updateStatus(Application $app, $sbas_id, $bit, $properties) { - self::getStatus($app, $sbas_id); + self::getStatus($app, $sbas_id); $databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id); diff --git a/lib/classes/module/console/fieldsMerge.php b/lib/classes/module/console/fieldsMerge.php index 61a78ddc5f..a535ff15fa 100644 --- a/lib/classes/module/console/fieldsMerge.php +++ b/lib/classes/module/console/fieldsMerge.php @@ -14,6 +14,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent; class module_console_fieldsMerge extends Command { @@ -196,7 +198,7 @@ class module_console_fieldsMerge extends Command ]], true); } - $this->getService('phraseanet.SE')->updateRecord($record); + $this->getService('dispatcher')->dispatch(PhraseaEvents::RECORD_CHANGE_METADATA, new ChangeMetadataEvent($record)); unset($record); } diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index c1f91d6caa..ba14d59264 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -23,6 +23,7 @@ use MediaVorus\MediaVorus; use Rhumsaa\Uuid\Uuid; use Alchemy\Phrasea\Model\RecordInterface; use Symfony\Component\HttpFoundation\File\File as SymfoFile; +use Alchemy\Phrasea\Core\PhraseaTokens; class record_adapter implements RecordInterface, cache_cacheableInterface { @@ -486,8 +487,6 @@ class record_adapter implements RecordInterface, cache_cacheableInterface $this->base_id = $collection->get_base_id(); - $this->app['phraseanet.SE']->updateRecord($this); - $this->app['phraseanet.logger']($this->get_databox()) ->log($this, Session_Logger::EVENT_MOVE, $collection->get_coll_id(), ''); @@ -972,8 +971,6 @@ class record_adapter implements RecordInterface, cache_cacheableInterface ); $stmt->closeCursor(); - $this->reindex(); - return $this; } @@ -1067,26 +1064,11 @@ class record_adapter implements RecordInterface, cache_cacheableInterface $xml->loadXML($this->app['serializer.caption']->serialize($this->get_caption(), CaptionSerializer::SERIALIZE_XML, true)); $this->set_xml($xml); - $this->reindex(); - unset($xml); return $this; } - /** - * Reindex the record - * - * @return record_adapter - */ - public function reindex() - { - $this->app['phraseanet.SE']->updateRecord($this); - $this->delete_data_from_cache(self::CACHE_STATUS); - - return $this; - } - /** * * @return record_adapter @@ -1095,7 +1077,7 @@ class record_adapter implements RecordInterface, cache_cacheableInterface { $databox = $this->app['phraseanet.appbox']->get_databox($this->get_sbas_id()); $connbas = $databox->get_connection(); - $sql = 'UPDATE record SET jeton=(jeton | ' . JETON_MAKE_SUBDEF . ') WHERE record_id = :record_id'; + $sql = 'UPDATE record SET jeton=(jeton | ' . PhraseaTokens::TOKEN_MAKE_SUBDEF . ') WHERE record_id = :record_id'; $stmt = $connbas->prepare($sql); $stmt->execute([':record_id' => $this->get_record_id()]); $stmt->closeCursor(); @@ -1112,7 +1094,7 @@ class record_adapter implements RecordInterface, cache_cacheableInterface $databox = $this->app['phraseanet.appbox']->get_databox($this->get_sbas_id()); $connbas = $databox->get_connection(); $sql = 'UPDATE record - SET jeton = jeton | (' . (JETON_WRITE_META_DOC | JETON_WRITE_META_SUBDEF) . ') + SET jeton = jeton | (' . (PhraseaTokens::TOKEN_WRITE_META_DOC | PhraseaTokens::TOKEN_WRITE_META_SUBDEF) . ') WHERE record_id= :record_id'; $stmt = $connbas->prepare($sql); $stmt->execute([':record_id' => $this->record_id]); @@ -1928,6 +1910,8 @@ class record_adapter implements RecordInterface, cache_cacheableInterface public function setStatus($status) { $this->set_binary_status($status); + + $this->delete_data_from_cache(self::CACHE_STATUS); } /** {@inheritdoc} */ diff --git a/templates/web/admin/task-manager/task-editor/indexer.html.twig b/templates/web/admin/task-manager/task-editor/indexer.html.twig new file mode 100644 index 0000000000..13e24cb9f4 --- /dev/null +++ b/templates/web/admin/task-manager/task-editor/indexer.html.twig @@ -0,0 +1 @@ +{% extends 'admin/task-manager/task-editor/task.html.twig' %}