diff --git a/.dockerignore b/.dockerignore index 95d7045edf..12389ce613 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,7 @@ /.env.* /env.* /.git +/.gitignore /.travis.yml /AUTHORS /CONTRIBUTORS diff --git a/Dockerfile b/Dockerfile index 3feefd2d3b..8231d48455 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,11 @@ ######################################################################### FROM php:7.0-fpm-stretch as phraseanet-system -RUN apt-get update \ + +ENV FFMPEG_VERSION=4.2.2 + +RUN echo "deb http://deb.debian.org/debian stretch main non-free" > /etc/apt/sources.list \ + && apt-get update \ && apt-get install -y \ apt-transport-https \ ca-certificates \ @@ -40,6 +44,29 @@ RUN apt-get update \ libreoffice-math \ libreoffice-writer \ libreoffice-pdfimport \ + # FFmpeg + yasm \ + libvorbis-dev \ + texi2html \ + nasm \ + zlib1g-dev \ + libx264-dev \ + libfdk-aac-dev \ + libopus-dev \ + libvpx-dev \ + libmp3lame-dev \ + libogg-dev \ + libopencore-amrnb-dev \ + libopencore-amrwb-dev \ + libdc1394-22-dev \ + libx11-dev \ + libswscale-dev \ + libpostproc-dev \ + libxvidcore-dev \ + libtheora-dev \ + libgsm1-dev \ + libfreetype6-dev \ + # End FFmpeg && update-locale "LANG=fr_FR.UTF-8 UTF-8" \ && dpkg-reconfigure --frontend noninteractive locales \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ @@ -54,6 +81,37 @@ RUN apt-get update \ && docker-php-ext-enable redis amqp zmq imagick \ && pecl clear-cache \ && docker-php-source delete \ + && mkdir /tmp/ffmpeg \ + && curl -s https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 | tar jxf - -C /tmp/ffmpeg \ + && ( \ + cd /tmp/ffmpeg/ffmpeg-${FFMPEG_VERSION} \ + && ./configure \ + --enable-gpl \ + --enable-nonfree \ + --enable-libfdk-aac \ + --enable-libfdk_aac \ + --enable-libgsm \ + --enable-libmp3lame \ + --enable-libtheora \ + --enable-libvorbis \ + --enable-libvpx \ + --enable-libfreetype \ + --enable-libopus \ + --enable-libx264 \ + --enable-libxvid \ + --enable-zlib \ + --enable-postproc \ + --enable-swscale \ + --enable-pthreads \ + --enable-libdc1394 \ + --enable-version3 \ + --enable-libopencore-amrnb \ + --enable-libopencore-amrwb \ + && make \ + && make install \ + && make distclean \ + ) \ + && rm -rf /tmp/ffmpeg \ && apt-get clean \ && rm -rf /var/lib/apt/lists \ && mkdir /entrypoint /var/alchemy \ diff --git a/bin/console b/bin/console index 2dcc02e275..c49fc89052 100755 --- a/bin/console +++ b/bin/console @@ -57,6 +57,7 @@ use Alchemy\Phrasea\Command\User\UserCreateCommand; use Alchemy\Phrasea\Command\User\UserPasswordCommand; use Alchemy\Phrasea\Command\User\UserListCommand; use Alchemy\Phrasea\Command\UpgradeDBDatas; +use Alchemy\Phrasea\Command\ApplyRightsCommand; require_once __DIR__ . '/../lib/autoload.php'; @@ -93,6 +94,7 @@ $cli->command(new \module_console_aboutLicense('about:license')); $cli->command(new CheckConfig('check:config')); $cli->command(new UpgradeDBDatas('system:upgrade-datas')); +$cli->command(new ApplyRightsCommand('system:apply-rights')); $cli->command(new \module_console_systemMailCheck('system:mail-check')); $cli->command(new \module_console_systemBackupDB('system:backup-db')); @@ -160,9 +162,9 @@ $cli->command(new QueryParseCommand()); $cli->command(new QuerySampleCommand()); $cli->command(new FindConceptsCommand()); -$cli->command($cli['alchemy_worker.commands.run_dispatcher_command']); -$cli->command($cli['alchemy_worker.commands.run_worker_command']); -$cli->command($cli['alchemy_worker.commands.show_configuration']); +//$cli->command($cli['alchemy_worker.commands.run_dispatcher_command']); +//$cli->command($cli['alchemy_worker.commands.run_worker_command']); +//$cli->command($cli['alchemy_worker.commands.show_configuration']); $cli->loadPlugins(); diff --git a/cache/.gitkeep b/cache/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/composer.json b/composer.json index 46ff98203e..0fd22effb0 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,7 @@ "league/flysystem": "^1.0", "league/flysystem-aws-s3-v2": "^1.0", "league/fractal": "dev-webgalleries#af1acc0275438571bc8c1d08a05a4b5af92c9f97 as 0.13.0", - "media-alchemyst/media-alchemyst": "^0.5.5", + "media-alchemyst/media-alchemyst": "^0.5.6", "monolog/monolog": "~1.3", "mrclay/minify": "~2.1.6", "neutron/process-manager": "2.0.x-dev@dev", @@ -105,7 +105,7 @@ "neutron/silex-imagine-provider": "~0.1.0", "neutron/temporary-filesystem": "~2.1", "pagerfanta/pagerfanta": "^1.0", - "php-ffmpeg/php-ffmpeg": "~0.5.0", + "php-ffmpeg/php-ffmpeg": "^v0.15", "php-xpdf/php-xpdf": "~0.2.1", "exiftool/exiftool": "^11", "ramsey/uuid": "^3.0", diff --git a/composer.lock b/composer.lock index 41a8b05beb..112a4ecd0f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5a4a0be62b13071a6b06893b7ce08372", + "content-hash": "008ff0b5d3d13b4f0ce5d34348ded83a", "packages": [ { "name": "alchemy-fr/tcpdf-clone", @@ -4331,16 +4331,16 @@ }, { "name": "media-alchemyst/media-alchemyst", - "version": "0.5.5", + "version": "0.5.6", "source": { "type": "git", "url": "https://github.com/alchemy-fr/Media-Alchemyst.git", - "reference": "3bd3204b69882f495adfb617383a077face92ed0" + "reference": "2b9f7697997f7863bbc3d08344c559a1cba519c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/alchemy-fr/Media-Alchemyst/zipball/3bd3204b69882f495adfb617383a077face92ed0", - "reference": "3bd3204b69882f495adfb617383a077face92ed0", + "url": "https://api.github.com/repos/alchemy-fr/Media-Alchemyst/zipball/2b9f7697997f7863bbc3d08344c559a1cba519c2", + "reference": "2b9f7697997f7863bbc3d08344c559a1cba519c2", "shasum": "" }, "require": { @@ -4350,7 +4350,7 @@ "monolog/monolog": "~1.0", "neutron/temporary-filesystem": "^2.1.1", "php": ">=5.3.3", - "php-ffmpeg/php-ffmpeg": ">=0.4.2,<0.6", + "php-ffmpeg/php-ffmpeg": "^v0.15", "php-mp4box/php-mp4box": "~0.3.0", "php-unoconv/php-unoconv": "~0.3.1", "pimple/pimple": "~1.0", @@ -4401,7 +4401,7 @@ "video", "video processing" ], - "time": "2019-12-11T07:20:45+00:00" + "time": "2020-04-01T08:51:55+00:00" }, { "name": "monolog/monolog", @@ -5147,29 +5147,29 @@ }, { "name": "php-ffmpeg/php-ffmpeg", - "version": "0.5.1", + "version": "v0.15", "source": { "type": "git", "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", - "reference": "c8949fe3df89edd7692368cc110a51a27971f28a" + "reference": "984dbd046b6d8c285f9e7419fc7645f197513bfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/c8949fe3df89edd7692368cc110a51a27971f28a", - "reference": "c8949fe3df89edd7692368cc110a51a27971f28a", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/984dbd046b6d8c285f9e7419fc7645f197513bfa", + "reference": "984dbd046b6d8c285f9e7419fc7645f197513bfa", "shasum": "" }, "require": { - "alchemy/binary-driver": "~1.5", - "doctrine/cache": "~1.0", - "evenement/evenement": "~1.0", - "neutron/temporary-filesystem": "~2.1, >=2.1.1", - "php": ">=5.3.3" + "alchemy/binary-driver": "^1.5 || ~2.0.0 || ^5.0", + "doctrine/cache": "^1.0", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "neutron/temporary-filesystem": "^2.1.1", + "php": "^5.3.9 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~3.7", "sami/sami": "~1.0", - "silex/silex": "~1.0" + "silex/silex": "~1.0", + "symfony/phpunit-bridge": "^5.0.4" }, "suggest": { "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" @@ -5177,7 +5177,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.5-dev" + "dev-master": "0.7-dev" } }, "autoload": { @@ -5199,6 +5199,21 @@ "name": "Phraseanet Team", "email": "info@alchemy.fr", "homepage": "http://www.phraseanet.com/" + }, + { + "name": "Patrik Karisch", + "email": "patrik@karisch.guru", + "homepage": "http://www.karisch.guru" + }, + { + "name": "Romain Biard", + "email": "romain.biard@gmail.com", + "homepage": "https://www.strime.io/" + }, + { + "name": "Jens Hausdorf", + "email": "hello@jens-hausdorf.de", + "homepage": "https://jens-hausdorf.de" } ], "description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg", @@ -5212,7 +5227,7 @@ "video", "video processing" ], - "time": "2014-08-26T08:46:56+00:00" + "time": "2020-03-23T09:32:09+00:00" }, { "name": "php-mp4box/php-mp4box", @@ -7772,7 +7787,7 @@ ], "packages-dev": [ { - "name": "mikey179/vfsStream", + "name": "mikey179/vfsstream", "version": "v1.6.4", "source": { "type": "git", diff --git a/docker/phraseanet/entrypoint.sh b/docker/phraseanet/entrypoint.sh index 037fe0aa02..2e0f07519b 100755 --- a/docker/phraseanet/entrypoint.sh +++ b/docker/phraseanet/entrypoint.sh @@ -15,6 +15,7 @@ chown -R app:app \ FILE=config/configuration.yml if [ -f "$FILE" ]; then + bin/setup system:config set registry.general.title $PHRASEANET_PROJECT_NAME echo "$FILE exists, skip setup." else echo "$FILE doesn't exist, entering setup..." diff --git a/lib/Alchemy/Phrasea/Account/AccountService.php b/lib/Alchemy/Phrasea/Account/AccountService.php index 4c0e594e64..567a404a94 100644 --- a/lib/Alchemy/Phrasea/Account/AccountService.php +++ b/lib/Alchemy/Phrasea/Account/AccountService.php @@ -167,11 +167,11 @@ class AccountService * @param string $login * @throws AccountException */ - public function deleteAccount($login = null) + public function deleteAccount($login = null, array $grantedBaseIdList = array()) { $user = $this->getUserOrCurrentUser($login); - $this->userManipulator->delete($user); + $this->userManipulator->delete($user, $grantedBaseIdList); } /** diff --git a/lib/Alchemy/Phrasea/Authentication/RegistrationService.php b/lib/Alchemy/Phrasea/Authentication/RegistrationService.php index 7ffdf8c489..2ffe87f0cd 100644 --- a/lib/Alchemy/Phrasea/Authentication/RegistrationService.php +++ b/lib/Alchemy/Phrasea/Authentication/RegistrationService.php @@ -328,11 +328,9 @@ class RegistrationService $autoReg = $acl->get_granted_base(); - $granted = []; foreach ($autoReg as $baseId => $collection) { $granted[$baseId] = $collection->get_label($this->app['locale']); - } - if(count($granted) > 0) { + $this->app['manipulator.webhook-event']->create( WebhookEvent::USER_REGISTRATION_GRANTED, WebhookEvent::USER_REGISTRATION_TYPE, @@ -340,8 +338,11 @@ class RegistrationService 'user_id' => $user->getId(), 'granted' => $granted, 'rejected' => [] - ] + ], + [$baseId] ); + + unset($granted); } diff --git a/lib/Alchemy/Phrasea/Border/Manager.php b/lib/Alchemy/Phrasea/Border/Manager.php index d8abc9ac99..d017d206e6 100644 --- a/lib/Alchemy/Phrasea/Border/Manager.php +++ b/lib/Alchemy/Phrasea/Border/Manager.php @@ -14,6 +14,8 @@ namespace Alchemy\Phrasea\Border; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Border\Checker\CheckerInterface; use Alchemy\Phrasea\Border\Attribute\AttributeInterface; +use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Metadata\Tag\TfArchivedate; use Alchemy\Phrasea\Metadata\Tag\TfQuarantine; @@ -333,7 +335,7 @@ class Manager $this->app['phraseanet.metadata-setter']->replaceMetadata($newMetadata, $element); if(!$nosubdef) { - $element->rebuild_subdefs(); + $this->app['dispatcher']->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($element, true)); } return $element; diff --git a/lib/Alchemy/Phrasea/Command/ApplyRightsCommand.php b/lib/Alchemy/Phrasea/Command/ApplyRightsCommand.php new file mode 100644 index 0000000000..bcf0b3d0e7 --- /dev/null +++ b/lib/Alchemy/Phrasea/Command/ApplyRightsCommand.php @@ -0,0 +1,89 @@ +setDescription('Apply right on databox, inject appbox:basusr to dboxes:collusr') + ->addOption('user_id', null, InputOption::VALUE_REQUIRED, 'the user ID to apply rights') + ; + + return $this; + } + + protected function doExecute(InputInterface $input, OutputInterface $output) + { + $userId = $input->getOption('user_id'); + $userRepository = $this->container['repo.users']; + + if ($userId) { + if (($user = $userRepository->find($userId)) === null) { + $output->writeln('user not found!'); + + return 0; + } + + $this->injectRightsSbas($user); + } else { + foreach ($userRepository->findAll() as $user) { + $this->injectRightsSbas($user); + } + } + + $output->writeln('Apply right on databox finished!'); + + return 0; + } + + private function injectRightsSbas(User $user) + { + $userAcl = $this->container->getAclForUser($user); + + foreach ($userAcl->get_granted_sbas() as $databox) { + + $userAcl->delete_injected_rights_sbas($databox); + + $sql = "INSERT INTO collusr + (site, usr_id, coll_id, mask_and, mask_xor, ord) + VALUES (:site_id, :usr_id, :coll_id, :mask_and, :mask_xor, :ord)"; + $stmt = $databox->get_connection()->prepare($sql); + $iord = 0; + + // fix collusr if user has right on collection + foreach ($userAcl->get_granted_base([], [$databox->get_sbas_id()]) as $collection) { + try { + $stmt->execute([ + ':site_id' => $this->container['conf']->get(['main', 'key']), + ':usr_id' => $user->getId(), + ':coll_id' => $collection->get_coll_id(), + ':mask_and' => $userAcl->get_mask_and($collection->get_base_id()), + ':mask_xor' => $userAcl->get_mask_xor($collection->get_base_id()), + ':ord' => $iord++ + ]); + } catch (DBALException $e) { + + } + } + + $stmt->closeCursor(); + } + } +} diff --git a/lib/Alchemy/Phrasea/Controller/Admin/SubdefsController.php b/lib/Alchemy/Phrasea/Controller/Admin/SubdefsController.php index 106a60b1d8..97896ac613 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/SubdefsController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/SubdefsController.php @@ -383,7 +383,7 @@ class SubdefsController extends Controller Video::OPTION_GOPSIZE => "25", Video::OPTION_SIZE => "480", Video::OPTION_FRAMERATE => "25", - Video::OPTION_VCODEC => "libtheora", + Video::OPTION_VCODEC => "libx264", Video::OPTION_ACODEC => "libmp3lame", Subdef::OPTION_DEVICE => ["all"] ], @@ -449,7 +449,7 @@ class SubdefsController extends Controller Video::OPTION_GOPSIZE => "25", Video::OPTION_SIZE => "480", Video::OPTION_FRAMERATE => "25", - Video::OPTION_VCODEC => "libtheora", + Video::OPTION_VCODEC => "libx264", Video::OPTION_ACODEC => "libfdk_aac", Subdef::OPTION_DEVICE => ["all"] ], diff --git a/lib/Alchemy/Phrasea/Controller/Admin/UserController.php b/lib/Alchemy/Phrasea/Controller/Admin/UserController.php index 35ff646b8c..a1487603d8 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/UserController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/UserController.php @@ -515,9 +515,9 @@ class UserController extends Controller $denyColl[] = $label; $hookData['rejected'][$bas] = $label; } - } - $this->app['manipulator.webhook-event']->create($hookName, $hookType, $hookData); + $this->app['manipulator.webhook-event']->create($hookName, $hookType, $hookData, [$bas]); + } if ($user->hasMailNotificationsActivated() && (0 !== count($acceptColl) || 0 !== count($denyColl))) { $message = ''; diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php b/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php index df6f2a9f19..f4692c99ac 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Application\Helper\FilesystemAware; use Alchemy\Phrasea\Application\Helper\NotifierAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Core\Event\ExportFailureEvent; +use Alchemy\Phrasea\Core\Event\ExportMailEvent; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; @@ -193,42 +194,26 @@ class ExportController extends Controller $token = $this->getTokenManipulator()->createEmailExportToken(serialize($list)); if (count($destMails) > 0) { - //zip documents - \set_export::build_zip( - $this->app, - $token, - $list, - $this->app['tmp.download.path'].'/'. $token->getValue() . '.zip' - ); + $emitterId = $this->getAuthenticatedUser()->getId(); - $remaingEmails = $destMails; + $tokenValue = $token->getValue(); $url = $this->app->url('prepare_download', ['token' => $token->getValue(), 'anonymous' => false, 'type' => \Session_Logger::EVENT_EXPORTMAIL]); - $user = $this->getAuthenticatedUser(); - $emitter = new Emitter($user->getDisplayName(), $user->getEmail()); + $params = [ + 'url' => $url, + 'textmail' => $request->request->get('textmail'), + 'reading_confirm' => !!$request->request->get('reading_confirm', false), + 'ssttid' => $ssttid = $request->request->get('ssttid', ''), + 'lst' => $lst = $request->request->get('lst', ''), + ]; - foreach ($destMails as $key => $mail) { - try { - $receiver = new Receiver(null, trim($mail)); - } catch (InvalidArgumentException $e) { - continue; - } - - $mail = MailRecordsExport::create($this->app, $receiver, $emitter, $request->request->get('textmail')); - $mail->setButtonUrl($url); - $mail->setExpiration($token->getExpiration()); - - $this->deliver($mail, !!$request->request->get('reading_confirm', false)); - unset($remaingEmails[$key]); - } - - //some mails failed - if (count($remaingEmails) > 0) { - foreach ($remaingEmails as $mail) { - $this->dispatch(PhraseaEvents::EXPORT_MAIL_FAILURE, new ExportFailureEvent($this->getAuthenticatedUser(), $ssttid, $lst, \eventsmanager_notify_downloadmailfail::MAIL_FAIL, $mail)); - } - } + $this->dispatch(PhraseaEvents::EXPORT_MAIL_CREATE, new ExportMailEvent( + $emitterId, + $tokenValue, + $destMails, + $params + )); } return $this->app->json([ diff --git a/lib/Alchemy/Phrasea/Controller/Prod/PushController.php b/lib/Alchemy/Phrasea/Controller/Prod/PushController.php index 6e650aefaa..c9726d5faf 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/PushController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/PushController.php @@ -98,7 +98,7 @@ class PushController extends Controller $Basket->setUser($user_receiver); $Basket->setPusher($this->getAuthenticatedUser()); $Basket->markUnread(); - + $manager->persist($Basket); foreach ($pusher->get_elements() as $element) { @@ -600,6 +600,38 @@ class PushController extends Controller ); } + public function updateExpirationAction(Request $request) + { + $ret = [ + 'success' => false, + 'message' => $this->app->trans('Unable to save the expiration date') + ]; + if (is_null($request->request->get('date'))) { + $ret['message'] = $this->app->trans('The provided date is null!'); + return $this->app->json($ret); + } + $repository = $this->app['repo.baskets']; + $manager = $this->getEntityManager(); + $manager->beginTransaction(); + try { + $basket = $repository->findUserBasket($request->request->get('basket_id'), $this->app->getAuthenticatedUser(), true); + $date = new \DateTime($request->request->get('date') . " 23:59:59"); + $validation = $basket->getValidation(); + if (is_null($validation)) { + return $this->app->json($ret); + } + $validation->setExpires($date); + $manager->persist($validation); + $manager->flush(); + $manager->commit(); + $ret['message'] = $this->app->trans('Expiration date successfully updated!'); + } catch (\Exception $e) { + $ret['message'] = $e->getMessage(); + $manager->rollback(); + } + return $this->app->json($ret); + } + private function formatUser(User $user) { $subtitle = array_filter([$user->getJob(), $user->getCompany()]); @@ -734,4 +766,5 @@ class PushController extends Controller { return $this->app['random.medium']; } + } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php index 8afd22d2b3..d4830859c2 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php @@ -269,7 +269,7 @@ class QueryController extends Controller $infoResult = '
' . $this->app->trans('%number% documents
selectionnes', ['%number%' => '']) - . '
' + . '
' . '
diff --git a/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php b/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php index 0a948d6d1d..8e9a609811 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php @@ -14,6 +14,8 @@ use Alchemy\Phrasea\Application\Helper\EntityManagerAware; use Alchemy\Phrasea\Application\Helper\SearchEngineAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\Record\DeleteEvent; +use Alchemy\Phrasea\Core\Event\Record\RecordEvents; use Alchemy\Phrasea\Core\Event\RecordEdit; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\BasketElement; @@ -234,7 +236,7 @@ class RecordController extends Controller if($trashCollectionsBySbasId[$sbasId] !== null) { if($record->getCollection()->get_coll_id() == $trashCollectionsBySbasId[$sbasId]->get_coll_id()) { // record is already in trash so delete it - $record->delete(); + $this->getEventDispatcher()->dispatch(RecordEvents::DELETE, new DeleteEvent($record)); } else { // move to trash collection $record->move_to_collection($trashCollectionsBySbasId[$sbasId], $this->getApplicationBox()); @@ -247,7 +249,7 @@ class RecordController extends Controller } } else { // no trash collection, delete - $record->delete(); + $this->getEventDispatcher()->dispatch(RecordEvents::DELETE, new DeleteEvent($record)); } } catch (\Exception $e) { } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php index 9a65ed61fa..a90f0a0ed1 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php @@ -14,6 +14,8 @@ use Alchemy\Phrasea\Application\Helper\EntityManagerAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Controller\Exception as ControllerException; +use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Core\Event\RecordEdit; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\StoryWZ; @@ -68,7 +70,9 @@ class StoryController extends Controller break; } - $story->set_metadatas($metadatas)->rebuild_subdefs(); + $recordAdapter = $story->set_metadatas($metadatas); + // tell phraseanet to rebuild subdef + $this->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($recordAdapter)); $storyWZ = new StoryWZ(); $storyWZ->setUser($this->getAuthenticatedUser()); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 872f385455..65f72a8a36 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Application\Helper\SubDefinitionSubstituerAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader; use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter; @@ -156,7 +157,7 @@ class ToolsController extends Controller } if (!$substituted || $force) { - $record->rebuild_subdefs(); + $this->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($record)); } } diff --git a/lib/Alchemy/Phrasea/Controller/Root/AccountController.php b/lib/Alchemy/Phrasea/Controller/Root/AccountController.php index 6bf2d11f17..b56d428cec 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/AccountController.php +++ b/lib/Alchemy/Phrasea/Controller/Root/AccountController.php @@ -518,7 +518,9 @@ class AccountController extends Controller $this->getApiApplicationManipulator()->deleteApiApplications($applications); - // revoke access and delete phraseanet user account + // get list of old granted base_id then revoke access and delete phraseanet user account + + $oldGrantedBaseIds = array_keys($this->app->getAclForUser($user)->get_granted_base()); $list = array_keys($this->app['repo.collections-registry']->getBaseIdMap()); @@ -542,8 +544,9 @@ class AccountController extends Controller $mail = null; } - $this->app['manipulator.user']->delete($user); + $mail = MailSuccessAccountDelete::create($this->app, $receiver); + $this->app['manipulator.user']->delete($user, [$user->getId() => $oldGrantedBaseIds]); if($mail) { $this->deliver($mail); } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Push.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Push.php index 48352e1e39..40b93220c0 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Push.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Push.php @@ -59,6 +59,9 @@ class Push implements ControllerProviderInterface, ServiceProviderInterface $controllers->post('/validate/', 'controller.prod.push:validateAction') ->bind('prod_push_validate'); + $controllers->post('/update-expiration/', 'controller.prod.push:updateExpirationAction') + ->bind('prod_push_do_update_expiration'); + $controllers->get('/user/{usr_id}/', 'controller.prod.push:getUserAction') ->assert('usr_id', '\d+'); diff --git a/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php b/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php index fbce72ccc7..9ebd69c829 100644 --- a/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php +++ b/lib/Alchemy/Phrasea/Core/Configuration/RegistryFormManipulator.php @@ -102,6 +102,8 @@ class RegistryFormManipulator 'keywords' => null, 'description' => null, 'analytics' => null, + 'matomo-analytics-url' => null, + 'matomo-analytics-id' => null, 'allow-indexation' => true, 'home-presentation-mode' => 'GALLERIA', 'default-subdef-url-ttl' => 7200, diff --git a/lib/Alchemy/Phrasea/Core/Event/ExportMailEvent.php b/lib/Alchemy/Phrasea/Core/Event/ExportMailEvent.php new file mode 100644 index 0000000000..3acafb4c70 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/ExportMailEvent.php @@ -0,0 +1,53 @@ +emitterUserId = $emitterUserId; + $this->tokenValue = $tokenValue; + $this->destinationMails = $destMails; + $this->params = $params; + } + + public function getTokenValue() + { + return $this->tokenValue; + } + + public function getDestinationMails() + { + return $this->destinationMails; + } + + public function getEmitterUserId() + { + return $this->emitterUserId; + } + + public function getParams() + { + return $this->params; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Record/DeleteEvent.php b/lib/Alchemy/Phrasea/Core/Event/Record/DeleteEvent.php new file mode 100644 index 0000000000..8d8bfdb379 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Record/DeleteEvent.php @@ -0,0 +1,7 @@ +isNewRecord = $isNewRecord; + } + + /** + * @return bool + */ + public function isNewRecord() + { + return $this->isNewRecord; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/ExportSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ExportSubscriber.php index fca66dab4a..d3c05a64b6 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/ExportSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/ExportSubscriber.php @@ -12,7 +12,15 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber; use Alchemy\Phrasea\Core\Event\ExportFailureEvent; +use Alchemy\Phrasea\Core\Event\ExportMailEvent; use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Exception\InvalidArgumentException; +use Alchemy\Phrasea\Model\Entities\Token; +use Alchemy\Phrasea\Model\Repositories\TokenRepository; +use Alchemy\Phrasea\Model\Repositories\UserRepository; +use Alchemy\Phrasea\Notification\Emitter; +use Alchemy\Phrasea\Notification\Mail\MailRecordsExport; +use Alchemy\Phrasea\Notification\Receiver; class ExportSubscriber extends AbstractNotificationSubscriber { @@ -39,10 +47,65 @@ class ExportSubscriber extends AbstractNotificationSubscriber $this->app['event-manager']->notify($params['usr_id'], 'eventsmanager_notify_downloadmailfail', $datas, $mailed); } + public function onCreateExportMail(ExportMailEvent $event) + { + $destMails = $event->getDestinationMails(); + + $params = $event->getParams(); + + /** @var UserRepository $userRepository */ + $userRepository = $this->app['repo.users']; + + $user = $userRepository->find($event->getEmitterUserId()); + + /** @var TokenRepository $tokenRepository */ + $tokenRepository = $this->app['repo.tokens']; + + /** @var Token $token */ + $token = $tokenRepository->findValidToken($event->getTokenValue()); + + $list = unserialize($token->getData()); + + //zip documents + \set_export::build_zip( + $this->app, + $token, + $list, + $this->app['tmp.download.path'].'/'. $token->getValue() . '.zip' + ); + + $remaingEmails = $destMails; + + $emitter = new Emitter($user->getDisplayName(), $user->getEmail()); + + foreach ($destMails as $key => $mail) { + try { + $receiver = new Receiver(null, trim($mail)); + } catch (InvalidArgumentException $e) { + continue; + } + + $mail = MailRecordsExport::create($this->app, $receiver, $emitter, $params['textmail']); + $mail->setButtonUrl($params['url']); + $mail->setExpiration($token->getExpiration()); + + $this->deliver($mail, $params['reading_confirm']); + unset($remaingEmails[$key]); + } + + //some mails failed + if (count($remaingEmails) > 0) { + foreach ($remaingEmails as $mail) { + $this->app['dispatcher']->dispatch(PhraseaEvents::EXPORT_MAIL_FAILURE, new ExportFailureEvent($user, $params['ssttid'], $params['lst'], \eventsmanager_notify_downloadmailfail::MAIL_FAIL, $mail)); + } + } + } + public static function getSubscribedEvents() { return [ - PhraseaEvents::EXPORT_MAIL_FAILURE => 'onMailExportFailure' + PhraseaEvents::EXPORT_MAIL_FAILURE => 'onMailExportFailure', + PhraseaEvents::EXPORT_MAIL_CREATE => 'onCreateExportMail', ]; } } diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php index 8bc7cb2b52..e229cc3f44 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/FeedEntrySubscriber.php @@ -33,7 +33,8 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber $this->app['manipulator.webhook-event']->create( WebhookEvent::NEW_FEED_ENTRY, WebhookEvent::FEED_ENTRY_TYPE, - array_merge(array('feed_id' => $entry->getFeed()->getId()), $params) + array_merge(array('feed_id' => $entry->getFeed()->getId()), $params), + $entry->getFeed()->getBaseId() ? [$entry->getFeed()->getBaseId()] : [] ); $datas = json_encode($params); diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/OrderSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/OrderSubscriber.php index 142537f418..01f7e7b235 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/OrderSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/OrderSubscriber.php @@ -41,13 +41,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber public function onCreate(OrderEvent $event) { - $base_ids = array_unique(array_map(function (OrderElement $element) { + $baseIds = array_unique(array_map(function (OrderElement $element) { return $element->getBaseId(); }, iterator_to_array($event->getOrder()->getElements()))); $query = $this->app['phraseanet.user-query']; /** @var User[] $users */ - $users = $query->on_base_ids($base_ids) + $users = $query->on_base_ids($baseIds) ->who_have_right([\ACL::ORDER_MASTER]) ->execute()->get_results(); @@ -60,10 +60,12 @@ class OrderSubscriber extends AbstractNotificationSubscriber 'order_id' => $event->getOrder()->getId(), ]); - $notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); + // notify by webhook + $notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK); - $notifier->notifyCreation($event->getOrder(), $event->getOrder()->getUser()); + $notifier->notifyCreation($event->getOrder(), $event->getOrder()->getUser(), $baseIds); + // notify by mail $notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_MAIL); foreach ($users as $user) { @@ -85,7 +87,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber public function onDeliver(OrderDeliveryEvent $event) { + // notify by webhook + $notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK); + $notifier->notifyDelivery($event->getDelivery(), $event->getDelivery()->getPartialOrder()->getBaseIds()); + $notified = false; + + // actually NotificationMethod is always by mail $notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); $notificationData = json_encode([ 'from' => $event->getDelivery()->getAdmin()->getId(), @@ -109,7 +117,13 @@ class OrderSubscriber extends AbstractNotificationSubscriber public function onDeny(OrderDeliveryEvent $event) { + // notify by webhook + $notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_WEBHOOK); + $notifier->notifyDenial($event->getDelivery(), $event->getDelivery()->getPartialOrder()->getBaseIds()); + $notified = false; + + // actually NotificationMethod is always by mail $notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod()); $notificationData = json_encode([ 'from' => $event->getDelivery()->getAdmin()->getId(), diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordEditSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordEditSubscriber.php index d5c0c69f54..8307749eb5 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordEditSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/RecordEditSubscriber.php @@ -11,8 +11,10 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber; use Alchemy\Phrasea\Core\Event\Record\CollectionChangedEvent; +use Alchemy\Phrasea\Core\Event\Record\DeleteEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Core\Event\RecordEdit; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Metadata\Tag\TfEditdate; @@ -26,10 +28,12 @@ class RecordEditSubscriber implements EventSubscriberInterface public static function getSubscribedEvents() { return array( - PhraseaEvents::RECORD_EDIT => 'onEdit', - PhraseaEvents::RECORD_UPLOAD => 'onEdit', - RecordEvents::ROTATE => 'onRecordChange', - RecordEvents::COLLECTION_CHANGED => 'onCollectionChanged', + PhraseaEvents::RECORD_EDIT => 'onEdit', + PhraseaEvents::RECORD_UPLOAD => 'onEdit', + RecordEvents::ROTATE => 'onRecordChange', + RecordEvents::COLLECTION_CHANGED => 'onCollectionChanged', + RecordEvents::SUBDEFINITION_CREATE => 'onSubdefinitionCreate', + RecordEvents::DELETE => 'onDelete', ); } @@ -49,6 +53,18 @@ class RecordEditSubscriber implements EventSubscriberInterface $recordAdapter->clearStampCache(); } + public function onSubdefinitionCreate(SubdefinitionCreateEvent $event) + { + $recordAdapter = $this->convertToRecordAdapter($event->getRecord()); + $recordAdapter->rebuild_subdefs(); + } + + public function onDelete(DeleteEvent $event) + { + $recordAdapter = $this->convertToRecordAdapter($event->getRecord()); + $recordAdapter->delete(); + } + public function onEdit(RecordEdit $event) { static $into = false; diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php new file mode 100644 index 0000000000..ca5d88b2ab --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php @@ -0,0 +1,75 @@ +app = $app; + } + + public function onSubdefCreated(SubDefinitionCreatedEvent $event) + { + $eventData = [ + 'databox_id' => $event->getRecord()->getDataboxId(), + 'record_id' => $event->getRecord()->getRecordId(), + 'subdef' => $event->getSubDefinitionName() + ]; + + $this->app['manipulator.webhook-event']->create( + WebhookEvent::RECORD_SUBDEF_CREATED, + WebhookEvent::RECORD_SUBDEF_TYPE, + $eventData + ); + } + + public function onSubdefCreationFailed(SubDefinitionCreationFailedEvent $event) + { + $eventData = [ + 'databox_id' => $event->getRecord()->getDataboxId(), + 'record_id' => $event->getRecord()->getRecordId(), + 'subdef' => $event->getSubDefinitionName() + ]; + + $this->app['manipulator.webhook-event']->create( + WebhookEvent::RECORD_SUBDEF_FAILED, + WebhookEvent::RECORD_SUBDEF_TYPE, + $eventData + ); + } + + public function onSubdefsCreated(SubDefinitionsCreatedEvent $event) + { + $eventData = [ + 'databox_id' => $event->getRecord()->getDataboxId(), + 'record_id' => $event->getRecord()->getRecordId(), + 'subdef_count' => count($event->getMedia()) + ]; + + $this->app['manipulator.webhook-event']->create( + WebhookEvent::RECORD_SUBDEFS_CREATED, + WebhookEvent::RECORD_SUBDEF_TYPE, + $eventData + ); + } + + public static function getSubscribedEvents() + { + return [ + RecordEvents::SUB_DEFINITION_CREATED => 'onSubdefCreated', + RecordEvents::SUB_DEFINITIONS_CREATED => 'onSubdefsCreated', + RecordEvents::SUB_DEFINITION_CREATION_FAILED => 'onSubdefCreationFailed' + ]; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookUserEventSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookUserEventSubscriber.php index a2f935bf89..74c1c6c23a 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookUserEventSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookUserEventSubscriber.php @@ -45,7 +45,7 @@ class WebhookUserEventSubscriber implements EventSubscriberInterface 'user_id' => $event->getUserId(), 'email' => $event->getEmailAddress(), 'login' => $event->getLogin() - ]); + ], $event->getGrantedBaseIds()); } public static function getSubscribedEvents() diff --git a/lib/Alchemy/Phrasea/Core/Event/User/DeletedEvent.php b/lib/Alchemy/Phrasea/Core/Event/User/DeletedEvent.php index b67ba45b24..1528917245 100644 --- a/lib/Alchemy/Phrasea/Core/Event/User/DeletedEvent.php +++ b/lib/Alchemy/Phrasea/Core/Event/User/DeletedEvent.php @@ -36,4 +36,12 @@ class DeletedEvent extends UserEvent { return $this->args['email']; } + + /** + * @return array + */ + public function getGrantedBaseIds() + { + return $this->args['grantedBaseIds']; + } } diff --git a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php index 97c9c10ba5..90adac69dd 100644 --- a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php +++ b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php @@ -48,7 +48,8 @@ final class PhraseaEvents const BRIDGE_UPLOAD_FAILURE = 'bridge.upload-failure'; const EXPORT_MAIL_FAILURE = 'export.mail-failure'; - const EXPORT_CREATE = 'export.create'; + const EXPORT_CREATE = 'export.create'; + const EXPORT_MAIL_CREATE = 'export.mail-create'; const RECORD_EDIT = 'record.edit'; const RECORD_UPLOAD = 'record.upload'; diff --git a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php index e342bf8380..8968ef5f0a 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/SearchEngineServiceProvider.php @@ -43,6 +43,7 @@ use Silex\ServiceProviderInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\KernelEvents; + class SearchEngineServiceProvider implements ServiceProviderInterface { public function register(Application $app) @@ -145,6 +146,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface $app['elasticsearch.indexer.databox_fetcher_factory'] = $app->share(function ($app) { return new DataboxFetcherFactory( + $app['conf'], $app['elasticsearch.record_helper'], $app['elasticsearch.options'], $app, diff --git a/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php index 4b280c92f0..43a5aeafa4 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/WebhookServiceProvider.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\Core\Provider; +use Alchemy\Phrasea\Core\Event\Subscriber\WebhookSubdefEventSubscriber; use Alchemy\Phrasea\Webhook\EventProcessorFactory; use Alchemy\Phrasea\Webhook\EventProcessorWorker; use Alchemy\Phrasea\Webhook\WebhookInvoker; @@ -10,6 +11,7 @@ use Alchemy\Worker\CallableWorkerFactory; use Alchemy\Worker\TypeBasedWorkerResolver; use Silex\Application; use Silex\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; class WebhookServiceProvider implements ServiceProviderInterface { @@ -58,6 +60,14 @@ class WebhookServiceProvider implements ServiceProviderInterface return $resolver; } ); + + $app['dispatcher'] = $app->share( + $app->extend('dispatcher', function (EventDispatcher $dispatcher, Application $app) { + $dispatcher->addSubscriber(new WebhookSubdefEventSubscriber($app)); + + return $dispatcher; + }) + ); } private function createAlias(Application $app, $alias, $targetServiceKey) @@ -69,6 +79,6 @@ class WebhookServiceProvider implements ServiceProviderInterface public function boot(Application $app) { - // no-op + } } diff --git a/lib/Alchemy/Phrasea/Core/Provider/WorkerConfigurationServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/WorkerConfigurationServiceProvider.php index 1b2e15d4f0..9931d4649f 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/WorkerConfigurationServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/WorkerConfigurationServiceProvider.php @@ -32,11 +32,12 @@ class WorkerConfigurationServiceProvider implements ServiceProviderInterface $app['alchemy_queues.queues'] = $app->share(function (Application $app) { $defaultConfiguration = [ 'worker-queue' => [ - 'registry' => 'alchemy_worker.queue_registry', - 'host' => 'localhost', - 'port' => 5672, - 'user' => 'guest', - 'vhost' => '/' + 'registry' => 'alchemy_worker.queue_registry', + 'host' => 'localhost', + 'port' => 5672, + 'user' => 'guest', + 'password' => 'guest', + 'vhost' => '/' ] ]; @@ -46,19 +47,22 @@ class WorkerConfigurationServiceProvider implements ServiceProviderInterface $queueConfigurations = $configuration->get(['workers', 'queue'], $defaultConfiguration); - $queueConfiguration = reset($queueConfigurations); - $queueKey = key($queueConfigurations); + $config = []; - if (! isset($queueConfiguration['name'])) { - if (! is_string($queueKey)) { - throw new \RuntimeException('Invalid queue configuration: configuration has no key or name.'); + foreach($queueConfigurations as $name => $queueConfiguration) { + $queueKey = $name; + + if (! isset($queueConfiguration['name'])) { + if (! is_string($queueKey)) { + throw new \RuntimeException('Invalid queue configuration: configuration has no key or name.'); + } + + $queueConfiguration['name'] = $queueKey; } - $queueConfiguration['name'] = $queueKey; + $config[$queueConfiguration['name']] = $queueConfiguration ; } - $config = [ $queueConfiguration['name'] => $queueConfiguration ]; - return $config; } catch (RuntimeException $exception) { diff --git a/lib/Alchemy/Phrasea/Form/Configuration/GeneralFormType.php b/lib/Alchemy/Phrasea/Form/Configuration/GeneralFormType.php index 5a1c6a645e..767c91a782 100644 --- a/lib/Alchemy/Phrasea/Form/Configuration/GeneralFormType.php +++ b/lib/Alchemy/Phrasea/Form/Configuration/GeneralFormType.php @@ -38,6 +38,12 @@ class GeneralFormType extends AbstractType $builder->add('analytics', 'text', [ 'label' => 'Google Analytics identifier', ]); + $builder->add('matomo-analytics-url', 'text', [ + 'label' => 'Matomo Analytics url', + ]); + $builder->add('matomo-analytics-id', 'text', [ + 'label' => 'Matomo Analytics identifier', + ]); $builder->add('allow-indexation', 'checkbox', [ 'label' => 'Allow the website to be indexed by search engines like Google', ]); diff --git a/lib/Alchemy/Phrasea/Helper/User/Edit.php b/lib/Alchemy/Phrasea/Helper/User/Edit.php index 2c7513fb3f..970b2ec8e1 100644 --- a/lib/Alchemy/Phrasea/Helper/User/Edit.php +++ b/lib/Alchemy/Phrasea/Helper/User/Edit.php @@ -73,10 +73,12 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper { $list = array_keys($this->app->getAclForUser($this->app->getAuthenticatedUser())->get_granted_base([\ACL::CANADMIN])); + $oldGrantedBaseIds = array_keys($this->app->getAclForUser($user)->get_granted_base()); + $this->app->getAclForUser($user)->revoke_access_from_bases($list); if ($this->app->getAclForUser($user)->is_phantom()) { - $this->app['manipulator.user']->delete($user); + $this->app['manipulator.user']->delete($user, [$user->getId() => $oldGrantedBaseIds]); } return $this; @@ -583,8 +585,8 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper $user = $this->app['repo.users']->find($usr_id); $this->app->getAclForUser($user)->revoke_access_from_bases($delete) - ->give_access_to_base($create) - ->give_access_to_sbas($create_sbas); + ->give_access_to_sbas($create_sbas) // give access to sbas before bas + ->give_access_to_base($create); foreach ($update as $base_id => $rights) { $this->app->getAclForUser($user) diff --git a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php index 88867551a1..9612e3bc19 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefGenerator.php +++ b/lib/Alchemy/Phrasea/Media/SubdefGenerator.php @@ -18,6 +18,7 @@ use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionsCreationEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Databox\Subdef\MediaSubdefRepository; use Alchemy\Phrasea\Filesystem\FilesystemService; use Alchemy\Phrasea\Media\Subdef\Specification\PdfSpecification; use MediaAlchemyst\Alchemyst; @@ -170,13 +171,53 @@ class SubdefGenerator unset($this->tmpFilePath); } - $this->dispatch( - RecordEvents::SUB_DEFINITIONS_CREATED, - new SubDefinitionsCreatedEvent( - $record, - $mediaCreated - ) - ); + // if we created subdef one by one + if (count($wanted_subdefs) == 1) { + $mediaSubdefRepository = $this->getMediaSubdefRepository($record->getDataboxId()); + $mediaSubdefs = $mediaSubdefRepository->findByRecordIdsAndNames([$record->getRecordId()]); + $medias = []; + foreach ($mediaSubdefs as $subdef) { + try { + $medias[$subdef->get_name()] = $this->mediavorus->guess($subdef->getRealPath()); + } catch (MediaVorusFileNotFoundException $e) { + + } + } + + $this->dispatch( + RecordEvents::SUB_DEFINITIONS_CREATED, + new SubDefinitionsCreatedEvent( + $record, + $medias + ) + ); + } else { + $this->dispatch( + RecordEvents::SUB_DEFINITIONS_CREATED, + new SubDefinitionsCreatedEvent( + $record, + $mediaCreated + ) + ); + } + } + + /** + * set a logger to use + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * to get the logger + * @return LoggerInterface + */ + public function getLogger() + { + return $this->logger; } private function generateSubdef(\record_adapter $record, \databox_subdef $subdef_class, $pathdest) @@ -276,4 +317,14 @@ class SubdefGenerator $i = floor(log($bytes, 1024)); return round($bytes / pow(1024, $i), [0,0,2,2,3][$i]).['B','kB','MB','GB'][$i]; } + + /** + * @param $databoxId + * + * @return MediaSubdefRepository|Object + */ + private function getMediaSubdefRepository($databoxId) + { + return $this->app['provider.repo.media_subdef']->getRepositoryForDatabox($databoxId); + } } diff --git a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php index 2259fa572d..8f7aa61e90 100644 --- a/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php +++ b/lib/Alchemy/Phrasea/Media/SubdefSubstituer.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Media; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Core\Event\Record\MediaSubstitutedEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Filesystem\FilesystemService; use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Exception\ExceptionInterface as MediaAlchemystException; @@ -79,7 +80,7 @@ class SubdefSubstituer $record->write_metas(); if ($shouldSubdefsBeRebuilt) { - $record->rebuild_subdefs(); + $this->dispatcher->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($record)); } $this->dispatcher->dispatch(RecordEvents::MEDIA_SUBSTITUTED, new MediaSubstitutedEvent($record)); diff --git a/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php b/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php index 275650f1b3..a1091f531f 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php +++ b/lib/Alchemy/Phrasea/Model/Entities/WebhookEvent.php @@ -68,6 +68,14 @@ class WebhookEvent */ private $created; + /** + * List of collection base_id concerned + * @var array + * + * @ORM\Column(name="collection_base_ids", type="json_array", nullable=true) + */ + private $collectionBaseIds; + /** * @param \DateTime $created * @@ -175,4 +183,24 @@ class WebhookEvent return $this; } + + /** + * @param array $collectionBaseIds + * + * @return $this + */ + public function setCollectionBaseIds(array $collectionBaseIds) + { + $this->collectionBaseIds = $collectionBaseIds; + + return $this; + } + + /** + * @return array + */ + public function getCollectionBaseIds() + { + return $this->collectionBaseIds; + } } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php index 3d0815370e..d36fbf527a 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/UserManipulator.php @@ -126,8 +126,9 @@ class UserManipulator implements ManipulatorInterface * Deletes a user. * * @param User|User[] $users + * @param array $grantedBaseIdList List of the old granted base_id per userId [user_id => [base_id, ...] ] */ - public function delete($users) + public function delete($users, array $grantedBaseIdList = array()) { /** @var User $user */ foreach ($this->makeTraversable($users) as $user) { @@ -146,9 +147,10 @@ class UserManipulator implements ManipulatorInterface new DeletedEvent( null, array( - 'user_id'=>$old_id, - 'login'=>$old_login, - 'email'=>$old_email + 'user_id' => $old_id, + 'login' => $old_login, + 'email' => $old_email, + 'grantedBaseIds' => isset($grantedBaseIdList[$old_id]) ? $grantedBaseIdList[$old_id] : [] ) ) ); diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/WebhookEventManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/WebhookEventManipulator.php index 40e7f812a7..90cbf66b65 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/WebhookEventManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/WebhookEventManipulator.php @@ -12,7 +12,7 @@ namespace Alchemy\Phrasea\Model\Manipulator; use Alchemy\Phrasea\Model\Entities\WebhookEvent; -use Alchemy\Phrasea\Webhook\WebhookPublisher; +use Alchemy\Phrasea\Webhook\WebhookPublisherInterface; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityRepository; @@ -29,18 +29,18 @@ class WebhookEventManipulator implements ManipulatorInterface private $repository; /** - * @var WebhookPublisher + * @var WebhookPublisherInterface */ private $publisher; - public function __construct(ObjectManager $om, EntityRepository $repo, WebhookPublisher $publisher) + public function __construct(ObjectManager $om, EntityRepository $repo, WebhookPublisherInterface $publisher) { $this->om = $om; $this->repository = $repo; $this->publisher = $publisher; } - public function create($eventName, $type, array $data) + public function create($eventName, $type, array $data, array $collectionBaseIds = array()) { $event = new WebhookEvent(); @@ -48,6 +48,10 @@ class WebhookEventManipulator implements ManipulatorInterface $event->setType($type); $event->setData($data); + if (count($collectionBaseIds) > 0) { + $event->setCollectionBaseIds($collectionBaseIds); + } + $this->update($event); $this->publisher->publishWebhookEvent($event); diff --git a/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php index 5b2e90d4cb..cf3c84e981 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php @@ -172,7 +172,7 @@ class BaseOrderController extends Controller $manager->persist($element); } - $delivery = new OrderDelivery($order, $acceptor, count($basketElements)); + $delivery = new OrderDelivery($order, $acceptor, count($basketElements), $partialOrder); $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($delivery)); } @@ -198,11 +198,13 @@ class BaseOrderController extends Controller $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); $order = $this->findOr404($order_id); + $partialOrder = new PartialOrder($order, $elements); + $this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements)); try { if (!empty($elements)) { - $delivery = new OrderDelivery($order, $acceptor, count($elements)); + $delivery = new OrderDelivery($order, $acceptor, count($elements), $partialOrder); $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($delivery)); } diff --git a/lib/Alchemy/Phrasea/Order/OrderDelivery.php b/lib/Alchemy/Phrasea/Order/OrderDelivery.php index 5d8f3313ff..f36a98525f 100644 --- a/lib/Alchemy/Phrasea/Order/OrderDelivery.php +++ b/lib/Alchemy/Phrasea/Order/OrderDelivery.php @@ -31,16 +31,23 @@ class OrderDelivery */ private $quantity; + /** + * @var PartialOrder + */ + private $partialOrder; + /** * @param Order $deliveredOrder * @param User $manager * @param int $quantity + * @param PartialOrder $partialOrder */ - public function __construct(Order $deliveredOrder, User $manager, $quantity) + public function __construct(Order $deliveredOrder, User $manager, $quantity, PartialOrder $partialOrder) { - $this->order = $deliveredOrder; - $this->admin = $manager; - $this->quantity = $quantity; + $this->order = $deliveredOrder; + $this->admin = $manager; + $this->quantity = $quantity; + $this->partialOrder = $partialOrder; } /** @@ -66,4 +73,12 @@ class OrderDelivery { return $this->quantity; } + + /** + * @return PartialOrder + */ + public function getPartialOrder() + { + return $this->partialOrder; + } } diff --git a/lib/Alchemy/Phrasea/Order/ValidationNotifier.php b/lib/Alchemy/Phrasea/Order/ValidationNotifier.php index e79b9ef888..419f180520 100644 --- a/lib/Alchemy/Phrasea/Order/ValidationNotifier.php +++ b/lib/Alchemy/Phrasea/Order/ValidationNotifier.php @@ -19,20 +19,23 @@ interface ValidationNotifier /** * @param Order $order * @param User $recipient + * @param array $baseIds * @return void */ - public function notifyCreation(Order $order, User $recipient); + public function notifyCreation(Order $order, User $recipient, array $baseIds = array()); /** * @param OrderDelivery $delivery + * @param array $baseIds * @return void */ - public function notifyDelivery(OrderDelivery $delivery); + public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array()); /** * @param OrderDelivery $delivery + * @param array $baseIds * @return void */ - public function notifyDenial(OrderDelivery $delivery); + public function notifyDenial(OrderDelivery $delivery, array $baseIds = array()); } diff --git a/lib/Alchemy/Phrasea/Order/ValidationNotifier/CompositeNotifier.php b/lib/Alchemy/Phrasea/Order/ValidationNotifier/CompositeNotifier.php index a3e75cebcb..b38d51a3ca 100644 --- a/lib/Alchemy/Phrasea/Order/ValidationNotifier/CompositeNotifier.php +++ b/lib/Alchemy/Phrasea/Order/ValidationNotifier/CompositeNotifier.php @@ -26,8 +26,9 @@ class CompositeNotifier implements ValidationNotifier /** * @param Order $order * @param User $recipient + * @param array $baseIds */ - public function notifyCreation(Order $order, User $recipient) + public function notifyCreation(Order $order, User $recipient, array $baseIds = array()) { foreach ($this->notifiers as $notifier) { $notifier->notifyCreation($order, $recipient); @@ -36,21 +37,23 @@ class CompositeNotifier implements ValidationNotifier /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDelivery(OrderDelivery $delivery) + public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array()) { foreach ($this->notifiers as $notifier) { - $notifier->notifyDelivery($delivery); + $notifier->notifyDelivery($delivery, $baseIds); } } /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDenial(OrderDelivery $delivery) + public function notifyDenial(OrderDelivery $delivery, array $baseIds = array()) { foreach ($this->notifiers as $notifier) { - $notifier->notifyDenial($delivery); + $notifier->notifyDenial($delivery, $baseIds); } } } diff --git a/lib/Alchemy/Phrasea/Order/ValidationNotifier/MailNotifier.php b/lib/Alchemy/Phrasea/Order/ValidationNotifier/MailNotifier.php index 8a6541793a..9934fafb08 100644 --- a/lib/Alchemy/Phrasea/Order/ValidationNotifier/MailNotifier.php +++ b/lib/Alchemy/Phrasea/Order/ValidationNotifier/MailNotifier.php @@ -46,8 +46,9 @@ class MailNotifier implements ValidationNotifier /** * @param Order $order * @param User $recipient + * @param array $baseIds */ - public function notifyCreation(Order $order, User $recipient) + public function notifyCreation(Order $order, User $recipient, array $baseIds = array()) { $mail = MailInfoNewOrder::create($this->application, Receiver::fromUser($recipient)); @@ -58,8 +59,9 @@ class MailNotifier implements ValidationNotifier /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDelivery(OrderDelivery $delivery) + public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array()) { $order = $delivery->getOrder(); @@ -85,8 +87,9 @@ class MailNotifier implements ValidationNotifier /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDenial(OrderDelivery $delivery) + public function notifyDenial(OrderDelivery $delivery, array $baseIds = array()) { $sender = Emitter::fromUser($delivery->getAdmin()); $recipient = Receiver::fromUser($delivery->getOrder()->getUser()); diff --git a/lib/Alchemy/Phrasea/Order/ValidationNotifier/WebhookNotifier.php b/lib/Alchemy/Phrasea/Order/ValidationNotifier/WebhookNotifier.php index 3634e8e907..4762ff43ec 100644 --- a/lib/Alchemy/Phrasea/Order/ValidationNotifier/WebhookNotifier.php +++ b/lib/Alchemy/Phrasea/Order/ValidationNotifier/WebhookNotifier.php @@ -47,21 +47,23 @@ class WebhookNotifier implements ValidationNotifier /** * @param Order $order * @param User $recipient + * @param array $baseIds */ - public function notifyCreation(Order $order, User $recipient) + public function notifyCreation(Order $order, User $recipient, array $baseIds = array()) { $eventData = [ 'order_id' => $order->getId(), 'user_id' => $recipient->getId(), ]; - $this->getManipulator()->create(WebhookEvent::ORDER_CREATED, WebhookEvent::ORDER_TYPE, $eventData); + $this->getManipulator()->create(WebhookEvent::ORDER_CREATED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds); } /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDelivery(OrderDelivery $delivery) + public function notifyDelivery(OrderDelivery $delivery, array $baseIds = array()) { $eventData = [ 'order_id' => $delivery->getOrder()->getId(), @@ -69,13 +71,14 @@ class WebhookNotifier implements ValidationNotifier 'quantity' => $delivery->getQuantity() ]; - $this->getManipulator()->create(WebhookEvent::ORDER_DELIVERED, WebhookEvent::ORDER_TYPE, $eventData); + $this->getManipulator()->create(WebhookEvent::ORDER_DELIVERED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds); } /** * @param OrderDelivery $delivery + * @param array $baseIds */ - public function notifyDenial(OrderDelivery $delivery) + public function notifyDenial(OrderDelivery $delivery, array $baseIds = array()) { $eventData = [ 'order_id' => $delivery->getOrder()->getId(), @@ -83,6 +86,6 @@ class WebhookNotifier implements ValidationNotifier 'quantity' => $delivery->getQuantity() ]; - $this->getManipulator()->create(WebhookEvent::ORDER_DENIED, WebhookEvent::ORDER_TYPE, $eventData); + $this->getManipulator()->create(WebhookEvent::ORDER_DENIED, WebhookEvent::ORDER_TYPE, $eventData, $baseIds); } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/DataboxFetcherFactory.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/DataboxFetcherFactory.php index b4eb070896..469ef65fc9 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/DataboxFetcherFactory.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/DataboxFetcherFactory.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic; +use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\FetcherDelegateInterface; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Fetcher; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\CoreHydrator; @@ -13,8 +14,14 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator\TitleHydrator; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\CandidateTerms; + class DataboxFetcherFactory { + /** + * @var PropertyAccess phraseanet configuration + */ + private $conf; + /** * @var \ArrayAccess */ @@ -39,14 +46,16 @@ class DataboxFetcherFactory private $options; /** + * @param PropertyAccess $conf * @param RecordHelper $recordHelper * @param ElasticsearchOptions $options * @param \ArrayAccess $container * @param string $structureKey * @param string $thesaurusKey */ - public function __construct(RecordHelper $recordHelper, ElasticsearchOptions $options, \ArrayAccess $container, $structureKey, $thesaurusKey) + public function __construct(PropertyAccess $conf, RecordHelper $recordHelper, ElasticsearchOptions $options, \ArrayAccess $container, $structureKey, $thesaurusKey) { + $this->conf = $conf; $this->recordHelper = $recordHelper; $this->options = $options; $this->container = $container; @@ -70,7 +79,7 @@ class DataboxFetcherFactory [ new CoreHydrator($databox->get_sbas_id(), $databox->get_viewname(), $this->recordHelper), new TitleHydrator($connection, $this->recordHelper), - new MetadataHydrator($connection, $this->getStructure(), $this->recordHelper), + new MetadataHydrator($this->conf, $connection, $this->getStructure(), $this->recordHelper), new FlagHydrator($this->getStructure(), $databox), new ThesaurusHydrator($this->getStructure(), $this->getThesaurus(), $candidateTerms), new SubDefinitionHydrator($connection) diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php index ba0dcf573f..9d4f1aabf7 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/GpsPosition.php @@ -15,6 +15,7 @@ use Assert\Assertion; class GpsPosition { + const FULL_GEO_NOTATION = 'FullNotation'; const LONGITUDE_TAG_NAME = 'Longitude'; const LONGITUDE_REF_TAG_NAME = 'LongitudeRef'; const LONGITUDE_REF_WEST = 'W'; @@ -29,6 +30,16 @@ class GpsPosition private $latitude; private $latitude_ref; + public function __construct() + { + $this->clear(); + } + + public function clear() + { + $this->longitude = $this->longitude_ref = $this->latitude = $this->latitude_ref = null; + } + public function set($tag_name, $value) { switch ($tag_name) { @@ -64,6 +75,53 @@ class GpsPosition $this->latitude_ref = $normalized; break; + case self::FULL_GEO_NOTATION: + $re = '/(-?\d+(?:\.\d+)?°?)\s*(\d+(?:\.\d+)?\')?\s*(\d+(?:\.\d+)?")?\s*(N|S|E|W)?/um'; + $normalized = trim(strtoupper($value)); + $matches = null; + preg_match_all($re, $normalized, $matches, PREG_SET_ORDER, 0); + if(count($matches) === 2) { // we need lat and lon + $lat = $lon = null; + foreach ($matches as $imatch => $match) { + if(count($match) != 5) { + continue; + } + $v = 0.0; + for($part=1, $div=1.0; $part<=3; $part++, $div*=60.0) { + $v += floatval($match[$part]) / $div; + } + switch($match[4]) { // N S E W + case 'N': + $lat = $v; + break; + case 'S': + $lat = -$v; + break; + case 'E': + $lon = $v; + break; + case 'W': + $lon = -$v; + break; + case '': // no ref -> lat lon (first=lat, second=lon) + if($imatch === 0) { + $lat = $v; + } + else { + $lon = $v; + } + break; + default: + throw new \InvalidArgumentException(sprintf('Unsupported reference "%s", should be N|S|E|W.', $match[4])); + } + } + if($lat !== null && $lon != null) { + $this->set(self::LATITUDE_TAG_NAME, $lat); + $this->set(self::LONGITUDE_TAG_NAME, $lon); + } + } + break; + default: throw new \InvalidArgumentException(sprintf('Unsupported tag name "%s".', $tag_name)); } @@ -95,19 +153,11 @@ class GpsPosition public function getCompositeLongitude() { - if ($this->longitude === null) { - return null; - } - return $this->longitude ; } public function getCompositeLatitude() { - if ($this->latitude === null) { - return null; - } - return $this->latitude; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php index 5dc2471cb9..f930fce740 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/Record/Hydrator/MetadataHydrator.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Hydrator; +use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; use Alchemy\Phrasea\SearchEngine\Elastic\FieldMapping; use Alchemy\Phrasea\SearchEngine\Elastic\Mapping; @@ -24,28 +25,48 @@ use InvalidArgumentException; class MetadataHydrator implements HydratorInterface { + private $conf; private $connection; private $structure; private $helper; - private $gps_position_buffer = []; + private $position_fields_mapping; // get from conf - public function __construct(DriverConnection $connection, Structure $structure, RecordHelper $helper) + private $caption_gps_position; + private $exif_gps_position; + + public function __construct(PropertyAccess $conf, DriverConnection $connection, Structure $structure, RecordHelper $helper) { + $this->conf = $conf; $this->connection = $connection; $this->structure = $structure; $this->helper = $helper; + + // get the fieldnames of source of lat / lon geo fields (defined in instance conf) + $this->position_fields_mapping = []; + foreach($conf->get(['geocoding-providers'], []) as $provider) { + if($provider['enabled'] && array_key_exists('position-fields', $provider)) { + foreach ($provider['position-fields'] as $position_field) { + $this->position_fields_mapping[$position_field['name']] = $position_field['type']; + } + } + } + + $this->caption_gps_position = new GpsPosition(); + $this->exif_gps_position = new GpsPosition(); } public function hydrateRecords(array &$records) { - $sql = "(SELECT record_id, ms.name AS `key`, m.value AS value, 'caption' AS type, ms.business AS private\n" + $sql = "SELECT * FROM (" + . "(SELECT record_id, ms.name AS `key`, m.value AS value, 'caption' AS type, ms.business AS private\n" . " FROM metadatas AS m INNER JOIN metadatas_structure AS ms ON (ms.id = m.meta_struct_id)\n" . " WHERE record_id IN (?))\n" . "UNION\n" . "(SELECT record_id, t.name AS `key`, t.value AS value, 'exif' AS type, 0 AS private\n" . " FROM technical_datas AS t\n" - . " WHERE record_id IN (?))\n"; + . " WHERE record_id IN (?))\n" + . ") AS t ORDER BY record_id"; $ids = array_keys($records); $statement = $this->connection->executeQuery( @@ -54,7 +75,17 @@ class MetadataHydrator implements HydratorInterface array(Connection::PARAM_INT_ARRAY, Connection::PARAM_INT_ARRAY) ); + $record_id = -1; while ($metadata = $statement->fetch()) { + + if($metadata['record_id'] !== $record_id) { + // record has changed, don't mix with previous one + $this->caption_gps_position->clear(); + $this->exif_gps_position->clear(); + + $record_id = $metadata['record_id']; + } + // Store metadata value $key = $metadata['key']; $value = trim($metadata['value']); @@ -64,10 +95,10 @@ class MetadataHydrator implements HydratorInterface continue; } - $id = $metadata['record_id']; - if (isset($records[$id])) { - $record =& $records[$id]; - } else { + if (isset($records[$record_id])) { + $record =& $records[$record_id]; + } + else { throw new Exception('Received metadata from unexpected record'); } @@ -89,11 +120,31 @@ class MetadataHydrator implements HydratorInterface $record[$field] = array(); } $record[$field][] = $value; + + if(array_key_exists($key, $this->position_fields_mapping)) { + // this field is mapped as a position part (lat, lon, latlon), push it + switch($this->position_fields_mapping[$key]) { + case 'lat': + $this->handleGpsPosition($this->caption_gps_position, $record, GpsPosition::LATITUDE_TAG_NAME, $value); + break; + case 'lng': + case 'lon': + $this->handleGpsPosition($this->caption_gps_position, $record, GpsPosition::LONGITUDE_TAG_NAME, $value); + break; + case 'latlng': + case 'latlon': + $this->handleGpsPosition($this->caption_gps_position, $record, GpsPosition::FULL_GEO_NOTATION, $value); + break; + } + } + break; case 'exif': - if (GpsPosition::isSupportedTagName($key)) { - $this->handleGpsPosition($records, $id, $key, $value); + // exif gps is a first-chance if caption is not yet set + // anyway if caption is set later, it will override the exif values + if (GpsPosition::isSupportedTagName($key) && !$this->caption_gps_position->isCompleteComposite()) { + $this->handleGpsPosition($this->exif_gps_position, $record, $key, $value); break; } $tag = $this->structure->getMetadataTagByName($key); @@ -109,38 +160,25 @@ class MetadataHydrator implements HydratorInterface break; } } - - $this->clearGpsPositionBuffer(); } - private function handleGpsPosition(&$records, $id, $tag_name, $value) + private function handleGpsPosition(GpsPosition &$position, &$record, $tag_name, $value) { - // Get position object - if (!isset($this->gps_position_buffer[$id])) { - $this->gps_position_buffer[$id] = new GpsPosition(); - } - $position = $this->gps_position_buffer[$id]; // Push this tag into object $position->set($tag_name, $value); + // Try to output complete position if ($position->isCompleteComposite()) { $lon = $position->getCompositeLongitude(); $lat = $position->getCompositeLatitude(); - $records[$id]['metadata_tags']['Longitude'] = $lon; - $records[$id]['metadata_tags']['Latitude'] = $lat; + $record['metadata_tags']['Longitude'] = $lon; + $record['metadata_tags']['Latitude'] = $lat; - $records[$id]["location"] = [ + $record["location"] = [ "lat" => $lat, "lon" => $lon ]; - - unset($this->gps_position_buffer[$id]); } } - - private function clearGpsPositionBuffer() - { - $this->gps_position_buffer = []; - } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordHelper.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordHelper.php index 74f3a297a8..6d19916e62 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordHelper.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/RecordHelper.php @@ -151,6 +151,7 @@ class RecordHelper return (bool) $value; case FieldMapping::TYPE_STRING: + $value = substr($value, 0, 32766); // for lucene limit, before a better solution return str_replace("\0", '', $value); default: diff --git a/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php b/lib/Alchemy/Phrasea/TaskManager/Job/ArchiveJob.php index 532eb8bbb5..de418a4e71 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\Record\RecordEvents; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\Border\Manager as borderManager; @@ -1001,7 +1003,7 @@ class ArchiveJob extends AbstractJob { // quick fix to reconnect if mysql is lost $app->getApplicationBox()->get_connection(); - $databox->get_connection(); + $collection->get_connection(); $status = \databox_status::operation_or($stat0, $stat1); @@ -1032,7 +1034,8 @@ class ArchiveJob extends AbstractJob } $story->setStatus(\databox_status::operation_or($stat0, $stat1)); - $story->rebuild_subdefs(); + + $app['dispatcher']->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($story)); unset($media); diff --git a/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php index 90edefc10c..79109a624c 100644 --- a/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php +++ b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php @@ -12,6 +12,7 @@ use Alchemy\Phrasea\Webhook\Processor\ProcessorFactory; use Alchemy\Phrasea\Webhook\Processor\ProcessorInterface; use Alchemy\Phrasea\Webhook\Processor\UserDeletedProcessorFactory; use Alchemy\Phrasea\Webhook\Processor\UserRegistrationProcessorFactory; +use Alchemy\Phrasea\Webhook\Processor\SubdefEventProcessor; class EventProcessorFactory { @@ -29,7 +30,10 @@ class EventProcessorFactory $this->registerFactory(WebhookEvent::FEED_ENTRY_TYPE, new FeedEntryProcessorFactory($app)); $this->registerFactory(WebhookEvent::USER_REGISTRATION_TYPE, new UserRegistrationProcessorFactory($app)); $this->registerFactory(WebhookEvent::ORDER_TYPE, new OrderNotificationProcessorFactory($app)); - $this->registerFactory(WebhookEvent::USER_DELETED_TYPE, new UserDeletedProcessorFactory($app)); + $this->registerFactory(WebhookEvent::USER_DELETED_TYPE, new UserDeletedProcessorFactory()); + $this->registerCallableFactory(WebhookEvent::RECORD_SUBDEF_TYPE, function () { + return new SubdefEventProcessor(); + }); } /** @@ -43,7 +47,7 @@ class EventProcessorFactory /** * @param string $eventType - * @param callback|callable $callable + * @param callback|callable|\Closure $callable */ public function registerCallableFactory($eventType, $callable) { diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php index 7fd4500c49..c873bbb680 100644 --- a/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php +++ b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php @@ -34,17 +34,16 @@ class FeedEntryProcessor implements ProcessorInterface { $data = $event->getData(); - if (!isset($data->entry_id)) { + if (!isset($data['entry_id'])) { return null; } - $entry = $this->entryRepository->find($data->entry_id); + $entry = $this->entryRepository->find($data['entry_id']); if (null === $entry) { return null; } - $data = $event->getData(); $feed = $entry->getFeed(); $query = $this->userQuery; @@ -54,8 +53,8 @@ class FeedEntryProcessor implements ProcessorInterface ->include_templates(false) ->email_not_null(true); - if ($feed->getCollection($this->app)) { - $query->on_base_ids([$feed->getCollection($this->app)->get_base_id()]); + if ($feed->getCollection($this->application)) { + $query->on_base_ids([$feed->getCollection($this->application)->get_base_id()]); } $start = 0; @@ -76,7 +75,7 @@ class FeedEntryProcessor implements ProcessorInterface return [ 'event' => $event->getName(), - 'users_were_notified' => isset($data->notify_email) ?: (bool) $data->notify_email, + 'users_were_notified' => isset($data['notify_email']) ? (bool) $data['notify_email'] : false, 'feed' => [ 'id' => $feed->getId(), 'title' => $feed->getTitle(), diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/SubdefEventProcessor.php b/lib/Alchemy/Phrasea/Webhook/Processor/SubdefEventProcessor.php new file mode 100644 index 0000000000..4a442ed5db --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/SubdefEventProcessor.php @@ -0,0 +1,17 @@ + $event->getName(), + 'data' => $event->getData() + ]; + } +} diff --git a/lib/Alchemy/Phrasea/Webhook/WebhookPublisher.php b/lib/Alchemy/Phrasea/Webhook/WebhookPublisher.php index 4304a5319f..2beaec292f 100644 --- a/lib/Alchemy/Phrasea/Webhook/WebhookPublisher.php +++ b/lib/Alchemy/Phrasea/Webhook/WebhookPublisher.php @@ -19,7 +19,7 @@ use Alchemy\Queue\MessageQueueRegistry; * Class WebhookPublisher publishes webhook event notifications in message queues * @package Alchemy\Phrasea\Webhook */ -class WebhookPublisher +class WebhookPublisher implements WebhookPublisherInterface { /** * @var MessageQueueRegistry diff --git a/lib/Alchemy/Phrasea/Webhook/WebhookPublisherInterface.php b/lib/Alchemy/Phrasea/Webhook/WebhookPublisherInterface.php new file mode 100644 index 0000000000..f8b4751ef0 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/WebhookPublisherInterface.php @@ -0,0 +1,10 @@ +app['locale'] ?: 'en', + NULL, NULL, NULL, NULL, 'dd MMMM yyyy' + ); + + return $fmt->format($date); + } + /** * * @param DateTime $date @@ -139,17 +149,25 @@ class phraseadate */ private function formatDate(DateTime $date, $locale, $format) { - switch ($locale) { default: + case 'de': case 'fr': switch ($format) { default: case 'DAY_MONTH': - $date_formated = strftime("%e %B", $date->format('U')); + $formatM = new IntlDateFormatter( + $locale, + NULL, NULL, NULL, NULL, 'dd MMMM' + ); + $date_formated = $formatM->format($date); break; case 'DAY_MONTH_YEAR': - $date_formated = strftime("%e %B %Y", $date->format('U')); + $formatY = new IntlDateFormatter( + $locale, + NULL, NULL, NULL, NULL, 'dd MMMM yyyy' + ); + $date_formated = $formatY->format($date); break; } break; @@ -157,26 +175,22 @@ class phraseadate switch ($format) { default: case 'DAY_MONTH': - $date_formated = strftime("%B %e", $date->format('U')); + $formatM = new IntlDateFormatter( + $locale, + NULL, NULL, NULL, NULL, 'MMMM dd' + ); + $date_formated = $formatM->format($date); break; case 'DAY_MONTH_YEAR': - $date_formated = strftime("%B %e %Y", $date->format('U')); - break; - } - break; - case 'de': - switch ($format) { - default: - case 'DAY_MONTH': - $date_formated = strftime("%e. %B", $date->format('U')); - break; - case 'DAY_MONTH_YEAR': - $date_formated = strftime("%e. %B %Y", $date->format('U')); + $formatY = new IntlDateFormatter( + $locale, + NULL, NULL, NULL, NULL, 'MMMM dd yyyy' + ); + $date_formated = $formatY->format($date); break; } break; } - return $date_formated; } diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index 8e181c5d2e..9d94822566 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -19,6 +19,7 @@ use Alchemy\Phrasea\Core\Event\Record\OriginalNameChangedEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; use Alchemy\Phrasea\Core\Event\Record\StatusChangedEvent; +use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\Databox\Subdef\MediaSubdefRepository; use Alchemy\Phrasea\Filesystem\FilesystemService; @@ -284,7 +285,7 @@ class record_adapter implements RecordInterface, cache_cacheableInterface $this->getDataboxConnection()->executeUpdate($sql, ['type' => $type, 'record_id' => $this->getRecordId()]); if ($old_type !== $type) { - $this->rebuild_subdefs(); + $this->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($this)); } $this->type = $type; @@ -341,7 +342,8 @@ class record_adapter implements RecordInterface, cache_cacheableInterface array(':mime' => $mime, ':record_id' => $this->getRecordId()) )) { - $this->rebuild_subdefs(); + $this->dispatch(RecordEvents::SUBDEFINITION_CREATE, new SubdefinitionCreateEvent($this)); + $this->delete_data_from_cache(); } @@ -1735,7 +1737,9 @@ class record_adapter implements RecordInterface, cache_cacheableInterface throw new Exception('This record is not a grouping'); } - $selections = $this->getDatabox()->getRecordRepository()->findChildren([$this->getRecordId()], null, $offset, $max_items); + $user = $this->getAuthenticatedUser(); + + $selections = $this->getDatabox()->getRecordRepository()->findChildren([$this->getRecordId()], $user, $offset, $max_items); return reset($selections); } @@ -1745,7 +1749,9 @@ class record_adapter implements RecordInterface, cache_cacheableInterface */ public function get_grouping_parents() { - $selections = $this->getDatabox()->getRecordRepository()->findParents([$this->getRecordId()]); + $user = $this->getAuthenticatedUser(); + + $selections = $this->getDatabox()->getRecordRepository()->findParents([$this->getRecordId()], $user); return reset($selections); } @@ -1948,4 +1954,15 @@ class record_adapter implements RecordInterface, cache_cacheableInterface { return $this->app['provider.repo.media_subdef']->getRepositoryForDatabox($this->getDataboxId()); } + + /** + * @return User|null + */ + protected function getAuthenticatedUser() + { + /** @var \Alchemy\Phrasea\Authentication\Authenticator $authenticator */ + $authenticator = $this->app['authentication']; + + return $authenticator->getUser(); + } } diff --git a/lib/conf.d/data_templates/DublinCore.xml b/lib/conf.d/data_templates/DublinCore.xml index eae43d5461..db3039df7f 100644 --- a/lib/conf.d/data_templates/DublinCore.xml +++ b/lib/conf.d/data_templates/DublinCore.xml @@ -79,7 +79,7 @@ 748videoyes - libmp3lame + libfdk_aaclibx264screen1000 diff --git a/lib/conf.d/data_templates/en-simple.xml b/lib/conf.d/data_templates/en-simple.xml index 44b0afa69f..81ce397169 100644 --- a/lib/conf.d/data_templates/en-simple.xml +++ b/lib/conf.d/data_templates/en-simple.xml @@ -79,7 +79,7 @@ 748videoyes - libmp3lame + libfdk_aaclibx264screen1000 diff --git a/lib/conf.d/data_templates/fr-simple.xml b/lib/conf.d/data_templates/fr-simple.xml index cc0a2b582c..48d628cedd 100644 --- a/lib/conf.d/data_templates/fr-simple.xml +++ b/lib/conf.d/data_templates/fr-simple.xml @@ -79,7 +79,7 @@ 748videoyes - libmp3lame + libfdk_aaclibx264screen1000 diff --git a/package.json b/package.json index 7ce6d01a66..232c24d86b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "normalize-css": "^2.1.0", "npm": "^6.0.0", "npm-modernizr": "^2.8.3", - "phraseanet-production-client": "0.34.150-d", + "phraseanet-production-client": "0.34.160-d", "requirejs": "^2.3.5", "tinymce": "^4.0.28", "underscore": "^1.8.3", diff --git a/resources/hudson/_GV.php b/resources/hudson/_GV.php index 284f4db6ba..808768544e 100644 --- a/resources/hudson/_GV.php +++ b/resources/hudson/_GV.php @@ -84,5 +84,7 @@ define('GV_homeTitle', 'SuperPhraseanet'); define('GV_metaKeywords', ''); define('GV_metaDescription', ''); define('GV_googleAnalytics', ''); +define('GV_matomoAnalyticsUrl', ''); +define('GV_matomoAnalyticsId', ''); define('GV_allow_search_engine', true); define('GV_display_gcf', true); diff --git a/resources/locales/messages.de.xlf b/resources/locales/messages.de.xlf index 9ca23ed2d7..8972dae59c 100644 --- a/resources/locales/messages.de.xlf +++ b/resources/locales/messages.de.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -192,8 +192,9 @@ %nb_records% records %nb_records% Datensätze + prod/WorkZone/Basket.html.twig prod/Tooltip/Story.html.twig - prod/Tooltip/Basket.html.twig + prod/Tooltip/Basket.html.twig %nb_view% vue @@ -1689,7 +1690,7 @@ Certaines donnees du panier ont change Einige Daten des Sammelkorbs wurden verändert - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig Certaines donnees du reportage ont change @@ -3165,6 +3166,11 @@ Einstellungen von ausführbaren Programme Form/Configuration/MainConfigurationFormType.php + + Expiration date successfully updated! + Expiration date successfully updated! + Controller/Prod/PushController.php + Export Exportieren @@ -6685,6 +6691,11 @@ Die folgende Fehler wurden festgestellt user/import/view.html.twig + + The provided date is null! + The provided date is null! + Controller/Prod/PushController.php + The publication has been stopped Veröffentlichung wurde gestoppt @@ -7046,6 +7057,11 @@ Controller/Root/LoginController.php Controller/Api/OAuth2Controller.php + + Unable to save the expiration date + Unable to save the expiration date + Controller/Prod/PushController.php + Unable to send the documents Es ist nicht möglich Dokumente zu senden @@ -11718,7 +11734,13 @@ prod:workzone:basket:creation-date prod:workzone:basket:creation-date - prod/Tooltip/Basket.html.twig + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + prod:workzone:basket:updated-message + prod:workzone:basket:updated-message + prod/WorkZone/Basket.html.twig prod:workzone:facetstab:search_and_facets_sort_options @@ -11826,7 +11848,7 @@ Aktualisieren prod/WorkZone/Story.html.twig prod/WorkZone/Macros.html.twig - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig prod/results/feeds.html.twig prod/results/feeds.html.twig @@ -13397,128 +13419,134 @@ workzone:datepicker:april workzone:datepicker:april - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:august workzone:datepicker:august - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:closeText workzone:datepicker:closeText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:currentText workzone:datepicker:currentText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:december workzone:datepicker:december - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:february workzone:datepicker:february - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:friday workzone:datepicker:friday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:january workzone:datepicker:january - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:july workzone:datepicker:july - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:june workzone:datepicker:june - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:march workzone:datepicker:march - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:may workzone:datepicker:may - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:monday workzone:datepicker:monday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:nextText workzone:datepicker:nextText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:november workzone:datepicker:november - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:october workzone:datepicker:october - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:prevText workzone:datepicker:prevText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:saturday workzone:datepicker:saturday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:september workzone:datepicker:september - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:sunday workzone:datepicker:sunday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:thursday workzone:datepicker:thursday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:tuesday workzone:datepicker:tuesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:wednesday workzone:datepicker:wednesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - - workzone:feedback:expiration - workzone:feedback:expiration - prod/WorkZone/Basket.html.twig - prod/Tooltip/Basket.html.twig + + workzone:feedback:expiration-closed + workzone:feedback:expiration-closed + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + workzone:feedback:expiration-open + workzone:feedback:expiration-open + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig workzone:feedback:update workzone:feedback:update - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig yes diff --git a/resources/locales/messages.en.xlf b/resources/locales/messages.en.xlf index 301050bb79..4b5369e3e6 100644 --- a/resources/locales/messages.en.xlf +++ b/resources/locales/messages.en.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -192,8 +192,9 @@ %nb_records% records %nb_records% records + prod/WorkZone/Basket.html.twig prod/Tooltip/Story.html.twig - prod/Tooltip/Basket.html.twig + prod/Tooltip/Basket.html.twig %nb_view% vue @@ -1690,7 +1691,7 @@ Certaines donnees du panier ont change This basket has been updated - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig Certaines donnees du reportage ont change @@ -3168,6 +3169,11 @@ Executables setting Form/Configuration/MainConfigurationFormType.php + + Expiration date successfully updated! + Expiration date successfully updated! + Controller/Prod/PushController.php + Export Export @@ -6688,6 +6694,11 @@ The following errors have been detected user/import/view.html.twig + + The provided date is null! + The provided date is null! + Controller/Prod/PushController.php + The publication has been stopped The publication has been stopped. @@ -7049,6 +7060,11 @@ Controller/Root/LoginController.php Controller/Api/OAuth2Controller.php + + Unable to save the expiration date + Unable to save the expiration date + Controller/Prod/PushController.php + Unable to send the documents Unable to send the documents @@ -11721,10 +11737,16 @@ It is possible to place several search areas Delete Selection prod/actions/Push.html.twig - + prod:workzone:basket:creation-date Creation - prod/Tooltip/Basket.html.twig + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + prod:workzone:basket:updated-message + prod:workzone:basket:updated-message + prod/WorkZone/Basket.html.twig prod:workzone:facetstab:search_and_facets_sort_options @@ -11832,7 +11854,7 @@ It is possible to place several search areas Refresh prod/WorkZone/Story.html.twig prod/WorkZone/Macros.html.twig - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig prod/results/feeds.html.twig prod/results/feeds.html.twig @@ -13403,128 +13425,134 @@ It is possible to place several search areas workzone:datepicker:april April - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:august August - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:closeText - workzone:datepicker:closeText - prod/WorkZone/Basket.html.twig + Close + prod/WorkZone/Basket.html.twig - + workzone:datepicker:currentText - workzone:datepicker:currentText - prod/WorkZone/Basket.html.twig + Current + prod/WorkZone/Basket.html.twig workzone:datepicker:december December - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:february February - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:friday Friday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:january January - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:july July - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:june June - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:march March - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:may May - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:monday Monday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:nextText Next - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:november November - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:october October - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:prevText Previous - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:saturday Saturday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:september September - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:sunday Sunday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:thursday Thursday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:tuesday Tuesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:wednesday Wednesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - - workzone:feedback:expiration - Feedback open Until - prod/WorkZone/Basket.html.twig - prod/Tooltip/Basket.html.twig + + workzone:feedback:expiration-closed + workzone:feedback:expiration-closed + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig - + + workzone:feedback:expiration-open + Feedback open until + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + workzone:feedback:update Update Date - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig yes diff --git a/resources/locales/messages.fr.xlf b/resources/locales/messages.fr.xlf index a8166562ca..d830633044 100644 --- a/resources/locales/messages.fr.xlf +++ b/resources/locales/messages.fr.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -192,8 +192,9 @@ %nb_records% records %nb_records% enregistrement(s) + prod/WorkZone/Basket.html.twig prod/Tooltip/Story.html.twig - prod/Tooltip/Basket.html.twig + prod/Tooltip/Basket.html.twig %nb_view% vue @@ -1689,7 +1690,7 @@ Certaines donnees du panier ont change Certaines données du panier ont changé - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig Certaines donnees du reportage ont change @@ -3165,6 +3166,11 @@ Paramètres d'exécutables Form/Configuration/MainConfigurationFormType.php + + Expiration date successfully updated! + Expiration date successfully updated! + Controller/Prod/PushController.php + Export Exporter @@ -6687,6 +6693,11 @@ Pour les utilisateurs authentifiés, la demande de validation est également dis Les erreurs suivantes ont été détectées. user/import/view.html.twig + + The provided date is null! + The provided date is null! + Controller/Prod/PushController.php + The publication has been stopped La publication a été suspendue @@ -7048,6 +7059,11 @@ Pour les utilisateurs authentifiés, la demande de validation est également dis Controller/Root/LoginController.php Controller/Api/OAuth2Controller.php + + Unable to save the expiration date + Unable to save the expiration date + Controller/Prod/PushController.php + Unable to send the documents Impossible d'envoyer les documents @@ -11724,10 +11740,16 @@ Si vous recevez cet e-mail sans l'avoir sollicité, merci de l'ignorer ou de le Supprimer la selection prod/actions/Push.html.twig - + prod:workzone:basket:creation-date Date de création - prod/Tooltip/Basket.html.twig + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + prod:workzone:basket:updated-message + prod:workzone:basket:updated-message + prod/WorkZone/Basket.html.twig prod:workzone:facetstab:search_and_facets_sort_options @@ -11835,7 +11857,7 @@ Si vous recevez cet e-mail sans l'avoir sollicité, merci de l'ignorer ou de le Rafraîchir prod/WorkZone/Story.html.twig prod/WorkZone/Macros.html.twig - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig prod/results/feeds.html.twig prod/results/feeds.html.twig @@ -13406,128 +13428,134 @@ Si vous recevez cet e-mail sans l'avoir sollicité, merci de l'ignorer ou de le workzone:datepicker:april Avril - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:august Aout - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:closeText Clore - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:currentText courant - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:december Décembre - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:february Février - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:friday Vendredi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:january Janvier - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:july Juillet - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:june Juin - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:march Mars - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:may Mai - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:monday Lundi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:nextText Suivant - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:november Novembre - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:october Octobre - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - + workzone:datepicker:prevText Précédent - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:saturday Samedi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:september Septembre - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:sunday Dimanche - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:thursday Jeudi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:tuesday Mardi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:wednesday Mercredi - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - - workzone:feedback:expiration - Validation ouverte jusqu'au - prod/WorkZone/Basket.html.twig - prod/Tooltip/Basket.html.twig + + workzone:feedback:expiration-closed + Validation close depuis + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig - + + workzone:feedback:expiration-open + validation ouverte jusqu'au + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + workzone:feedback:update Valider - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig yes diff --git a/resources/locales/messages.nl.xlf b/resources/locales/messages.nl.xlf index c61524de6a..d5fff61be3 100644 --- a/resources/locales/messages.nl.xlf +++ b/resources/locales/messages.nl.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. @@ -196,8 +196,9 @@ %nb_records% records %nb_records% records + prod/WorkZone/Basket.html.twig prod/Tooltip/Story.html.twig - prod/Tooltip/Basket.html.twig + prod/Tooltip/Basket.html.twig %nb_view% vue @@ -1694,7 +1695,7 @@ Certaines donnees du panier ont change Sommige gegevens in het mandje zijn veranderd - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig Certaines donnees du reportage ont change @@ -3175,6 +3176,11 @@ Executables instellingen Form/Configuration/MainConfigurationFormType.php + + Expiration date successfully updated! + Expiration date successfully updated! + Controller/Prod/PushController.php + Export Exporteer @@ -6695,6 +6701,11 @@ De volgende fouten werden opgemerkt user/import/view.html.twig + + The provided date is null! + The provided date is null! + Controller/Prod/PushController.php + The publication has been stopped Het programma is gestopt @@ -7056,6 +7067,11 @@ Controller/Root/LoginController.php Controller/Api/OAuth2Controller.php + + Unable to save the expiration date + Unable to save the expiration date + Controller/Prod/PushController.php + Unable to send the documents Documenten kunnen niet worden verstuurd @@ -11728,7 +11744,13 @@ prod:workzone:basket:creation-date prod:workzone:basket:creation-date - prod/Tooltip/Basket.html.twig + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + prod:workzone:basket:updated-message + prod:workzone:basket:updated-message + prod/WorkZone/Basket.html.twig prod:workzone:facetstab:search_and_facets_sort_options @@ -11836,7 +11858,7 @@ vernieuwen prod/WorkZone/Story.html.twig prod/WorkZone/Macros.html.twig - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig prod/results/feeds.html.twig prod/results/feeds.html.twig @@ -13407,128 +13429,134 @@ workzone:datepicker:april workzone:datepicker:april - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:august workzone:datepicker:august - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:closeText workzone:datepicker:closeText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:currentText workzone:datepicker:currentText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:december workzone:datepicker:december - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:february workzone:datepicker:february - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:friday workzone:datepicker:friday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:january workzone:datepicker:january - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:july workzone:datepicker:july - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:june workzone:datepicker:june - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:march workzone:datepicker:march - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:may workzone:datepicker:may - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:monday workzone:datepicker:monday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:nextText workzone:datepicker:nextText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:november workzone:datepicker:november - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:october workzone:datepicker:october - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:prevText workzone:datepicker:prevText - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:saturday workzone:datepicker:saturday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:september workzone:datepicker:september - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:sunday workzone:datepicker:sunday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:thursday workzone:datepicker:thursday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:tuesday workzone:datepicker:tuesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig workzone:datepicker:wednesday workzone:datepicker:wednesday - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig - - workzone:feedback:expiration - workzone:feedback:expiration - prod/WorkZone/Basket.html.twig - prod/Tooltip/Basket.html.twig + + workzone:feedback:expiration-closed + workzone:feedback:expiration-closed + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig + + + workzone:feedback:expiration-open + workzone:feedback:expiration-open + prod/WorkZone/Basket.html.twig + prod/Tooltip/Basket.html.twig workzone:feedback:update workzone:feedback:update - prod/WorkZone/Basket.html.twig + prod/WorkZone/Basket.html.twig yes diff --git a/resources/locales/validators.de.xlf b/resources/locales/validators.de.xlf index b7398ca514..4797cbcafd 100644 --- a/resources/locales/validators.de.xlf +++ b/resources/locales/validators.de.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.en.xlf b/resources/locales/validators.en.xlf index f8d0a54e1d..398448e7d5 100644 --- a/resources/locales/validators.en.xlf +++ b/resources/locales/validators.en.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.fr.xlf b/resources/locales/validators.fr.xlf index 6f350ddd76..8d7c716ebd 100644 --- a/resources/locales/validators.fr.xlf +++ b/resources/locales/validators.fr.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/resources/locales/validators.nl.xlf b/resources/locales/validators.nl.xlf index 2218c1ff00..0f8bf37ddc 100644 --- a/resources/locales/validators.nl.xlf +++ b/resources/locales/validators.nl.xlf @@ -1,6 +1,6 @@ - +
The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. diff --git a/templates/mobile/common/index.html.twig b/templates/mobile/common/index.html.twig index 32a08cc094..c5b68592da 100644 --- a/templates/mobile/common/index.html.twig +++ b/templates/mobile/common/index.html.twig @@ -24,5 +24,6 @@ {% block content %}{% endblock %} {% include 'common/analytics.html.twig' %} + {% include 'common/matomo_analytics.html.twig' %} diff --git a/templates/mobile/common/matomo_analytics.html.twig b/templates/mobile/common/matomo_analytics.html.twig new file mode 100644 index 0000000000..5c1e3251ac --- /dev/null +++ b/templates/mobile/common/matomo_analytics.html.twig @@ -0,0 +1,18 @@ +{% if app['conf'].get(['registry', 'general', 'matomo-analytics-url']) != '' %} +{% if app['conf'].get(['registry', 'general', 'matomo-analytics-id']) != '' %} + + + +{% endif %} +{% endif %} diff --git a/templates/web/common/index_bootstrap.html.twig b/templates/web/common/index_bootstrap.html.twig index 9b9f27a228..5c535b1767 100644 --- a/templates/web/common/index_bootstrap.html.twig +++ b/templates/web/common/index_bootstrap.html.twig @@ -42,5 +42,6 @@ {% block extra_content %}{% endblock %} {% block bodyJavascript %}{% endblock %} {% include 'common/analytics.html.twig' %} + {% include 'common/matomo_analytics.html.twig' %} diff --git a/templates/web/common/indexfloat.html.twig b/templates/web/common/indexfloat.html.twig index 8727aae954..25cbf79bc3 100644 --- a/templates/web/common/indexfloat.html.twig +++ b/templates/web/common/indexfloat.html.twig @@ -25,5 +25,6 @@ {% include 'common/analytics.html.twig' %} + {% include 'common/matomo_analytics.html.twig' %} diff --git a/templates/web/common/matomo_analytics.html.twig b/templates/web/common/matomo_analytics.html.twig new file mode 100644 index 0000000000..a44d0308a8 --- /dev/null +++ b/templates/web/common/matomo_analytics.html.twig @@ -0,0 +1,18 @@ +{% if app['conf'].get(['registry', 'general', 'matomo-analytics-url']) != '' %} +{% if app['conf'].get(['registry', 'general', 'matomo-analytics-id']) != '' %} + + + +{% endif %} +{% endif %} diff --git a/templates/web/login/layout/base-layout.html.twig b/templates/web/login/layout/base-layout.html.twig index 8d96cea003..155de6bd6e 100644 --- a/templates/web/login/layout/base-layout.html.twig +++ b/templates/web/login/layout/base-layout.html.twig @@ -100,6 +100,7 @@ {% endblock %} {% block analytics %} {% include 'common/analytics.html.twig' %} + {% include 'common/matomo_analytics.html.twig' %} {% endblock analytics %} {% block scripts %}{% endblock scripts %} diff --git a/templates/web/prod/Tooltip/Basket.html.twig b/templates/web/prod/Tooltip/Basket.html.twig index 9905cbf49c..72e51714aa 100644 --- a/templates/web/prod/Tooltip/Basket.html.twig +++ b/templates/web/prod/Tooltip/Basket.html.twig @@ -11,7 +11,11 @@ {% if basket.getValidation() %} {% set dateExpired = app['date-formatter'].getPrettyString(basket.getValidation().getExpires()) %} - {{ 'workzone:feedback:expiration' | trans }} : {{ dateExpired }} + {% if basket.getValidation().getExpires() < date() %} + {{ 'workzone:feedback:expiration-closed' | trans }} : + {% else %} + {{ 'workzone:feedback:expiration-open' | trans }} : + {% endif %} {{ dateExpired }} {% endif %}
diff --git a/templates/web/prod/WorkZone/Basket.html.twig b/templates/web/prod/WorkZone/Basket.html.twig index fb028812d5..8eaaec2b48 100644 --- a/templates/web/prod/WorkZone/Basket.html.twig +++ b/templates/web/prod/WorkZone/Basket.html.twig @@ -1,4 +1,4 @@ - +
@@ -99,15 +99,22 @@

{% trans with {'%nb_records%' : nb_records} %}%nb_records% records{% endtrans %}
- {% set dateExpired = app['date-formatter'].getPrettyString(basket.getValidation().getExpires()) %} + {% set dateExpired = app['date-formatter'].getTranslatedDate(basket.getValidation().getExpires()) %}
- {{ 'workzone:feedback:expiration' | trans }} : + {% if basket.getValidation().getExpires() < date() %} + {{ 'workzone:feedback:expiration-closed' | trans }} : + {% else %} + {{ 'workzone:feedback:expiration-open' | trans }} : + {% endif %} +
- - - - + + + + + +

{{ 'prod:workzone:basket:updated-message' | trans }}

{% endif %} @@ -135,7 +142,22 @@ diff --git a/templates/web/prod/preview/appears_in.html.twig b/templates/web/prod/preview/appears_in.html.twig index e6087d49ab..988bcde86d 100644 --- a/templates/web/prod/preview/appears_in.html.twig +++ b/templates/web/prod/preview/appears_in.html.twig @@ -23,19 +23,19 @@ data-position="0" data-id="{{basket.getId()}}" data-reload="true" - class="otherBaskToolTip open-preview-action" title="{{basket.getDescription()}}"> + class="otherBaskToolTip to-open-preview-action" title="{{basket.getDescription()}}"> {##} {% if basket.getValidation() %} - + {% elseif basket.getPusher() %} {% if not basket.isRead() %} - + {% else %} - + {% endif %} {% else %} - + {% endif %} {{basket.getName()}} diff --git a/templates/web/prod/preview/short_history.html.twig b/templates/web/prod/preview/short_history.html.twig index 646192e60a..d1c701f2f0 100644 --- a/templates/web/prod/preview/short_history.html.twig +++ b/templates/web/prod/preview/short_history.html.twig @@ -8,7 +8,7 @@
{% set appbox = '' %} -
+
{% if action == 'push' %} {% set n_user = done['final']|length %} {% set appbox = '' ~ site %} @@ -63,11 +63,9 @@ {% endif %} +

{{hour}}

-
{{hour}}
- - {% endfor %} {% endfor %} {% endfor %} diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Prod/ExportTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Prod/ExportTest.php index 886df13e81..e49a967d0c 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Prod/ExportTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Prod/ExportTest.php @@ -169,7 +169,8 @@ class ExportTest extends \PhraseanetAuthenticatedWebTestCase */ public function testExportMail() { - $this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailRecordsExport'); + // deliver method removed in the listener +// $this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailRecordsExport'); $this->getClient()->request('POST', '/prod/export/mail/', [ 'lst' => $this->getRecord1()->getId(), diff --git a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-with-hosts.yml b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-with-hosts.yml index 8e0b322dce..adcc5c8165 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-with-hosts.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-with-hosts.yml @@ -193,6 +193,8 @@ registry: keywords: '' description: '' analytics: '' + matomo-analytics-url: '' + matomo-analytics-id: '' allow-indexation: true home-presentation-mode: COOLIRIS modules: diff --git a/tests/Alchemy/Tests/Phrasea/Functional/UserDeletionTest.php b/tests/Alchemy/Tests/Phrasea/Functional/UserDeletionTest.php index 2db0eefa19..5ac42170c7 100644 --- a/tests/Alchemy/Tests/Phrasea/Functional/UserDeletionTest.php +++ b/tests/Alchemy/Tests/Phrasea/Functional/UserDeletionTest.php @@ -79,7 +79,7 @@ class UserDeletionTest extends \PhraseanetAuthenticatedWebTestCase $apiLog = $apiLogManipulator->create($account, new Request(), new Response()); $apiLogId = $apiLog->getId(); - $this->userManipulator->delete($this->user, true); + $this->userManipulator->delete($this->user); $this->assertTrue($this->user->isDeleted(), 'User was not properly deleted'); $apiLogRepository->clear(); diff --git a/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php b/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php index 7d21844d66..d27c00738e 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php @@ -12,6 +12,6 @@ class WebhookEventRepositoryTest extends \PhraseanetTestCase { $events = self::$DI['app']['orm.em']->getRepository('Phraseanet:WebhookEvent')->findUnprocessedEvents(); // I have no clue as to why this magic number is here, probably best to discard test - $this->assertCount(6, $events); + $this->assertCount(41, $events); } } diff --git a/tests/Alchemy/Tests/Phrasea/Webhook/Processor/FeedEntryProcessorTest.php b/tests/Alchemy/Tests/Phrasea/Webhook/Processor/FeedEntryProcessorTest.php index 60b518e443..39c3ae57d4 100644 --- a/tests/Alchemy/Tests/Phrasea/Webhook/Processor/FeedEntryProcessorTest.php +++ b/tests/Alchemy/Tests/Phrasea/Webhook/Processor/FeedEntryProcessorTest.php @@ -59,6 +59,6 @@ class FeedEntryProcessorTest extends \PhraseanetTestCase self::$DI['app']['repo.feed-entries'], self::$DI['app']['phraseanet.user-query'] ); - $this->assertEquals($processor->process($event), null); + $this->assertInternalType(\PHPUnit_Framework_Constraint_IsType::TYPE_ARRAY, $processor->process($event)); } } diff --git a/yarn.lock b/yarn.lock index 319e85d277..4a5be0c837 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5305,14 +5305,10 @@ jquery-simplecolorpicker@^0.3.1: resolved "https://registry.yarnpkg.com/jquery-simplecolorpicker/-/jquery-simplecolorpicker-0.3.1.tgz#4f6befd380ab05470f585d5482e5180556e460eb" integrity sha1-T2vv04CrBUcPWF1UguUYBVbkYOs= -"jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e": - version "1.4.2" - resolved "git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e" - -"jquery-treeview@https://github.com/alchemy-fr/jquery-treeview.git": +"jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git", "jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e": version "1.4.2" uid "1e9e5a49d2875b878801e904cd08c2d25e85af1e" - resolved "https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e" + resolved "git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e" jquery-ui-datepicker-with-i18n@^1.10.4: version "1.10.4" @@ -7581,10 +7577,10 @@ phraseanet-common@^0.4.5-d: js-cookie "^2.1.0" pym.js "^1.3.1" -phraseanet-production-client@0.34.150-d: - version "0.34.150-d" - resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.150-d.tgz#625ea96c045719b405fe9b707b632b1290aca285" - integrity sha512-JrtPq6dCTCBxX6kGViXZj4Sc26PMFIaifT3PVD1WHLUCNd/U3nnHzpzKUgVC0ibqOj4aVwA8JW/oMilGEe3cmg== +phraseanet-production-client@0.34.160-d: + version "0.34.160-d" + resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.160-d.tgz#3deb3387b54e56aec73b073cae8cc013cc316999" + integrity sha512-00jnCOCDrLowL8TE+h8mH+Lg4P0+EQPSDOXN2Lq6KF577GGu0OWQRh2YmUeXkacHTUT4N0u39IsBkkBYqorVnQ== dependencies: "@mapbox/mapbox-gl-language" "^0.9.2" "@turf/turf" "^5.1.6"