Add elasticsearch reindex events

This commit is contained in:
Nicolas Le Goff
2015-01-02 18:47:21 +01:00
committed by Mathieu Darse
parent ad868dc70f
commit a01bf29c5b
73 changed files with 1708 additions and 182 deletions

View File

@@ -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));

View File

@@ -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;
}

View File

@@ -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 <info>%d</info> 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 <info>%d</info> has been created", $elementCreated->getId())

View File

@@ -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) {
}

View File

@@ -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'])

View File

@@ -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+')

View File

@@ -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())];

View File

@@ -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

View File

@@ -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

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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) {
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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', '')) {

View File

@@ -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']++;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\CollectionEvent;
class ChangeNameEvent extends CollectionEvent
{
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\CollectionEvent;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class CollectionEvent extends SfEvent
{
private $collection;
public function __construct(\collection $collection)
{
$this->collection = $collection;
}
/**
* @return \collection
*/
public function getCollection()
{
return $this->collection;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\CollectionEvent;
class CollectionIndexEvent extends CollectionEvent
{
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class DataboxEvent extends SfEvent
{
private $databox;
public function __construct(\databox $databox)
{
$this->databox = $databox;
}
/**
* @return \databox
*/
public function getDatabox()
{
return $this->databox;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
class DataboxIndexEvent extends DataboxEvent
{
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
class DeleteStatusEvent extends DataboxEvent
{
private $status;
public function __construct(\databox $databox, array $status)
{
$this->status = $status;
parent::__construct($databox);
}
public function getStatus()
{
return $this->status;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
class DeleteStructureFieldEvent extends DataboxEvent
{
private $field;
public function __construct(\databox $databox, \databox_field $field)
{
$this->field = $field;
parent::__construct($databox);
}
/**
* @return \databox_field
*/
public function getField()
{
return $this->field;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
class UpdateStatusEvent extends DataboxEvent
{
private $status;
public function __construct(\databox $databox, array $status)
{
$this->status = $status;
parent::__construct($databox);
}
public function getStatus()
{
return $this->status;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\DataboxEvent;
class UpdateStructureFieldEvent extends DataboxEvent
{
private $field;
private $data;
public function __construct(\databox $databox, \databox_field $field, $data)
{
$this->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;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class BuildSubDefEvent extends RecordEvent
{
private $subDefName;
public function __construct(\record_adapter $record, $subDefName)
{
$this->subDefName = $subDefName;
parent::__construct($record);
}
public function getSubDefName()
{
return $this->subDefName;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class ChangeCollectionEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class ChangeMetadataEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class ChangeOriginalNameEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class ChangeStatusEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class CreateRecordEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class CreateStoryEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class DeleteRecordEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class DeleteStoryEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class RecordEvent extends SfEvent
{
public function __construct(\record_adapter $record)
{
$this->record = $record;
}
/**
* @return \record_adapter
*/
public function getRecord()
{
return $this->record;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
class RecordIndexEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\RecordEvent;
use Alchemy\Phrasea\Model\Entities\Basket;
use Symfony\Component\EventDispatcher\Event as SfEvent;
class SubstituteRecordEvent extends RecordEvent
{
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\CollectionEvent\ChangeNameEvent;
use Alchemy\Phrasea\Core\Event\CollectionEvent\CollectionIndexEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\IndexEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CollectionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
PhraseaEvents::COLLECTION_CHANGE_NAME => 'onChangeName',
];
}
public function onChangeName(ChangeNameEvent $event)
{
$collection = $event->getCollection();
$dispatcher = $event->getDispatcher();
$dispatcher->dispatch(PhraseaEvents::INDEX_COLLECTION, new CollectionIndexEvent($collection));
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\DataboxEvent\DataboxIndexEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\DeleteStatusEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\UpdateStatusEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\UpdateStructureFieldEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DataboxSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
PhraseaEvents::DATABOX_UPDATE_FIELD => '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));
}
}

View File

@@ -0,0 +1,194 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\CollectionEvent\CollectionIndexEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\DataboxIndexEvent;
use Alchemy\Phrasea\Core\Event\DataboxEvent\IndexEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\RecordIndexEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Core\PhraseaTokens;
use Alchemy\Phrasea\SearchEngine\Elastic\BulkOperation;
use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\RecordFetcher;
use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\RecordPoolFetcher;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use Elasticsearch\Client;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
class ElasticSearchSubscriber implements EventSubscriberInterface
{
/** Pool of records to index or remove from ElasticSearch */
private static $recordsToAdd;
private static $recordsToRemove;
private static $recordsToUpdate;
private $recordIndexer;
private $termIndexer;
private $client;
private $appbox;
private $indexName;
public function __construct(RecordIndexer $recordIndexer, TermIndexer $termIndexer, Client $client, \appbox $appbox, $indexName)
{
$this->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 = <<<SQL
UPDATE record
SET jeton = (jeton | :token)
WHERE (jeton & :token) = 0
SQL;
$connection->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 = <<<SQL
UPDATE record
SET jeton = (jeton | :token)
WHERE coll_id = :coll_id
AND (jeton & :token) = 0
SQL;
$connection->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]);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\RecordEvent\BuildSubDefEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeCollectionEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeMetadataEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeOriginalNameEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\ChangeStatusEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\CreateRecordEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\DeleteRecordEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\RecordIndexEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RecordSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
PhraseaEvents::RECORD_CHANGE_COLLECTION => '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()));
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\RecordEvent\CreateStoryEvent;
use Alchemy\Phrasea\Core\Event\RecordEvent\DeleteStoryEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class StorySubscriber implements EventSubscriberInterface
{
private $indexer;
public function __construct(RecordIndexer $indexer)
{
$this->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());
}
}

View File

@@ -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';
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core;
final class PhraseaTokens
{
const TOKEN_MAKE_SUBDEF = 0x01;
const TOKEN_WRITE_META_DOC = 0x02;
const TOKEN_WRITE_META_SUBDEF = 0x04;
const TOKEN_WRITE_META = 0x06;
const TOKEN_INDEX = 0x08;
const TOKEN_INDEXING = 0x10;
}

View File

@@ -11,11 +11,16 @@
namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Core\Event\Subscriber\CollectionSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\ContentNegotiationSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\CookiesDisablerSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\DataboxSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\ElasticSearchSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\MaintenanceSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\RecordSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\StorySubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\SessionManagerSubscriber;
use Silex\Application;
use Silex\ServiceProviderInterface;
@@ -42,6 +47,27 @@ class PhraseaEventServiceProvider implements ServiceProviderInterface
$app['phraseanet.content-negotiation-subscriber'] = $app->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)

View File

@@ -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) {

View File

@@ -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']);
});
}

View File

@@ -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']),
];
});
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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 = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
ORDER BY r.record_id ASC
LIMIT :offset, :limit
SQL;
$statement = $this->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 = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
WHERE r.record_id = :id
SQL;
$statement = $this->connection->prepare($sql);
$statement->bindValue(':id', $id, PDO::PARAM_INT);
return $statement;
}
private function execStatementMetadata($ids)
{
$sql = <<<SQL
@@ -255,20 +194,20 @@ SQL;
}
}
private function addSubdefsToRecord(&$records)
private function addSubDefinitionsToRecord(&$records)
{
$statementSubdef = $this->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 = <<<SQL
SELECT
@@ -285,4 +224,7 @@ SQL;
return $this->connection->executeQuery($sql, array($ids), array(Connection::PARAM_INT_ARRAY));
}
/** Provides PDO Statement that fetches records */
abstract protected function statementRecords();
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Fetcher;
use PDO;
class RecordFetcher extends AbstractRecordFetcher
{
protected function statementRecords()
{
$sql = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
ORDER BY r.record_id ASC
LIMIT ?, ?
SQL;
return $this->connection->executeQuery($sql, [
$this->offset,
$this->batchSize,
], [
PDO::PARAM_INT,
PDO::PARAM_INT,
]);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Fetcher;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use Doctrine\DBAL\Connection;
use databox;
use PDO;
class RecordPoolFetcher extends AbstractRecordFetcher
{
/**
* @var \record_adapter[]
*/
private $pool;
public function __construct(databox $databox, RecordHelper $helper, array $records)
{
if (count($records) === 0) {
throw new \InvalidArgumentException('Pool of records must at least contain one record');
}
$this->pool = $records;
parent::__construct($databox, $helper);
}
protected function statementRecords()
{
$sql = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
WHERE r.record_id IN (?)
ORDER BY r.record_id ASC
LIMIT ?, ?
SQL;
$records = array_map(function($record) {
return $record->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,
]);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Fetcher;
use Alchemy\Phrasea\Core\PhraseaTokens;
use PDO;
class ScheduledIndexationRecordFetcher extends AbstractRecordFetcher
{
protected function statementRecords()
{
$sql = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
WHERE (jeton & ?) > 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,
]);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic\Fetcher;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use databox;
use record_adapter;
use PDO;
class SingleRecordFetcher extends AbstractRecordFetcher
{
/**
* @var \record_adapter
*/
private $record;
public function __construct(databox $databox, RecordHelper $helper, record_adapter $record)
{
$this->record = $record;
parent::__construct($databox, $helper);
}
public function fetch()
{
$records = parent::fetch();
return array_pop($records);
}
protected function statementRecords()
{
$sql = <<<SQL
SELECT r.record_id
, r.coll_id as collection_id
, c.asciiname as collection_name
, r.uuid
, r.status as flags_bitmask
, r.sha256 -- TODO rename in "hash"
, r.originalname as original_name
, r.mime
, r.type
, r.parent_record_id
, r.credate as created_on
, r.moddate as updated_on
FROM record r
INNER JOIN coll c ON (c.coll_id = r.coll_id)
WHERE r.record_id = :record_id
ORDER BY r.record_id ASC
SQL;
return $this->connection->executeQuery($sql, [':record_id' => $this->record->get_record_id()], [PDO::PARAM_INT,]);
}
}

View File

@@ -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()

View File

@@ -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']);

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\TaskManager\Editor;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
class IndexerEditor extends AbstractEditor
{
/**
* {@inheritdoc}
*/
public function getTemplatePath()
{
return 'admin/task-manager/task-editor/indexer.html.twig';
}
/**
* {@inheritdoc}
*/
public function getDefaultPeriod()
{
return 10;
}
/**
* {@inheritdoc}
*/
public function getDefaultSettings(PropertyAccess $config = null)
{
return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<tasksettings></tasksettings>
XML;
}
/**
* {@inheritdoc}
*/
protected function getFormProperties()
{
return [];
}
}

View File

@@ -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);

View File

@@ -0,0 +1,110 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\TaskManager\Job;
use Alchemy\Phrasea\Core\PhraseaTokens;
use Alchemy\Phrasea\SearchEngine\Elastic\BulkOperation;
use Alchemy\Phrasea\SearchEngine\Elastic\Fetcher\ScheduledIndexationRecordFetcher;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use Alchemy\Phrasea\TaskManager\Editor\IndexerEditor;
use Alchemy\Phrasea\TaskManager\Editor\SubdefsEditor;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\SQLAnywhere11Platform;
use Doctrine\DBAL\SQLParserUtils;
use MediaAlchemyst\Transmuter\Image2Image;
class IndexerJob extends AbstractJob
{
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->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 = <<<SQL
UPDATE record
SET jeton = ((jeton | ?) & (jeton & ~ ?))
WHERE record_id IN (?)
SQL;
$records = array_map(function($record) {
return $record['record_id'];
}, $records);
$connection->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 = <<<SQL
UPDATE record
SET jeton = (jeton & ~ ?)
SQL;
$connection->executeQuery($sql, [PhraseaTokens::TOKEN_INDEXING], [\PDO::PARAM_INT]);
}
}
}

View File

@@ -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()));
}

View File

@@ -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']]);

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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} */

View File

@@ -0,0 +1 @@
{% extends 'admin/task-manager/task-editor/task.html.twig' %}