Merge branch 4.0 into master

This commit is contained in:
Thibaud Fabre
2016-05-25 17:21:08 +02:00
71 changed files with 4409 additions and 2685 deletions

View File

@@ -70,7 +70,7 @@
"ircmaxell/random-lib": "~1.0",
"jms/serializer": "~0.10",
"jms/translation-bundle": "dev-rebase-2015-10-20",
"justinrainbow/json-schema": "~1.3",
"justinrainbow/json-schema": "2.0.3 as 1.6.1",
"league/flysystem": "^1.0",
"league/flysystem-aws-s3-v2": "^1.0",
"league/fractal": "dev-bug/null-resource-serialization#891856f as 0.13.0",

82
composer.lock generated
View File

@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "685ed8f8578c211ee14ebc00703ab281",
"content-hash": "acd719252dc8e17e752f2e87f571a7b5",
"hash": "e3c9335da1fb653e0e2af3c12ad85d1e",
"content-hash": "2af2f721294eb3e2aed29d7a686f6567",
"packages": [
{
"name": "alchemy-fr/tcpdf-clone",
@@ -15,6 +15,12 @@
"url": "https://github.com/alchemy-fr/tcpdf-clone.git",
"reference": "2ba0248a7187f1626df6c128750650416267f0e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/alchemy-fr/tcpdf-clone/zipball/2ba0248a7187f1626df6c128750650416267f0e7",
"reference": "2ba0248a7187f1626df6c128750650416267f0e7",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
@@ -61,6 +67,10 @@
"qrcode",
"tcpdf"
],
"support": {
"source": "https://github.com/alchemy-fr/tcpdf-clone/tree/6.0.039",
"issues": "https://github.com/alchemy-fr/tcpdf-clone/issues"
},
"time": "2013-10-13 16:11:17"
},
{
@@ -537,6 +547,12 @@
"url": "https://github.com/alchemy-fr/symfony-cors.git",
"reference": "dbf7fcff1ce9fc1265db12955476ff169eab7375"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/alchemy-fr/symfony-cors/zipball/dbf7fcff1ce9fc1265db12955476ff169eab7375",
"reference": "dbf7fcff1ce9fc1265db12955476ff169eab7375",
"shasum": ""
},
"require": {
"symfony/http-kernel": "^2.3.0|^3.0.0"
},
@@ -557,11 +573,7 @@
"Alchemy\\CorsBundle\\": "src/Bundle/"
}
},
"autoload-dev": {
"psr-4": {
"Alchemy\\Cors\\Tests\\": "tests/unit/Component/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -1790,12 +1802,12 @@
"source": {
"type": "git",
"url": "https://github.com/igorw/evenement.git",
"reference": "v1.0.0"
"reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/igorw/evenement/zipball/v1.0.0",
"reference": "v1.0.0",
"url": "https://api.github.com/repos/igorw/evenement/zipball/fa966683e7df3e5dd5929d984a44abfbd6bafe8d",
"reference": "fa966683e7df3e5dd5929d984a44abfbd6bafe8d",
"shasum": ""
},
"require": {
@@ -1822,7 +1834,7 @@
"keywords": [
"event-dispatcher"
],
"time": "2012-05-30 08:01:08"
"time": "2012-05-30 15:01:08"
},
{
"name": "facebook/php-sdk",
@@ -2811,12 +2823,12 @@
"source": {
"type": "git",
"url": "https://github.com/hoaproject/Stream.git",
"reference": "011ab91d942f1d7096deade4c8a10fe57d51c5b3"
"reference": "3bc446bc00849bf51166adc415d77aa375d48d8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hoaproject/Stream/zipball/011ab91d942f1d7096deade4c8a10fe57d51c5b3",
"reference": "011ab91d942f1d7096deade4c8a10fe57d51c5b3",
"url": "https://api.github.com/repos/hoaproject/Stream/zipball/3bc446bc00849bf51166adc415d77aa375d48d8c",
"reference": "3bc446bc00849bf51166adc415d77aa375d48d8c",
"shasum": ""
},
"require": {
@@ -2861,7 +2873,7 @@
"stream",
"wrapper"
],
"time": "2015-10-22 06:30:43"
"time": "2015-10-26 12:21:43"
},
{
"name": "hoa/ustring",
@@ -3453,25 +3465,25 @@
},
{
"name": "justinrainbow/json-schema",
"version": "1.6.1",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "cc84765fb7317f6b07bd8ac78364747f95b86341"
"reference": "c21534c635f03428e92254333fab4ae35b2cdfd9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341",
"reference": "cc84765fb7317f6b07bd8ac78364747f95b86341",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/c21534c635f03428e92254333fab4ae35b2cdfd9",
"reference": "c21534c635f03428e92254333fab4ae35b2cdfd9",
"shasum": ""
},
"require": {
"php": ">=5.3.29"
"php": ">=5.3.3"
},
"require-dev": {
"json-schema/json-schema-test-suite": "1.1.0",
"json-schema/json-schema-test-suite": "1.1.2",
"phpdocumentor/phpdocumentor": "~2",
"phpunit/phpunit": "~3.7"
"phpunit/phpunit": "^4.8.22"
},
"bin": [
"bin/validate-json"
@@ -3479,7 +3491,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
"dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -3489,7 +3501,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
"MIT"
],
"authors": [
{
@@ -3515,7 +3527,7 @@
"json",
"schema"
],
"time": "2016-01-25 15:43:01"
"time": "2016-05-10 20:38:51"
},
{
"name": "league/flysystem",
@@ -3890,7 +3902,7 @@
],
"authors": [
{
"name": "Stephen Clay",
"name": "Steve Clay",
"email": "steve@mrclay.org",
"homepage": "http://www.mrclay.org/",
"role": "Developer"
@@ -4076,21 +4088,21 @@
"source": {
"type": "git",
"url": "https://github.com/romainneutron/Imagine-Silex-Service-Provider.git",
"reference": "0.1.2"
"reference": "a8a7862ae90419f2b23746cd8436c2310e4eb084"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/romainneutron/Imagine-Silex-Service-Provider/zipball/0.1.2",
"reference": "0.1.2",
"url": "https://api.github.com/repos/romainneutron/Imagine-Silex-Service-Provider/zipball/a8a7862ae90419f2b23746cd8436c2310e4eb084",
"reference": "a8a7862ae90419f2b23746cd8436c2310e4eb084",
"shasum": ""
},
"require": {
"imagine/imagine": "*",
"php": ">=5.3.3",
"silex/silex": ">=1.0,<2.0"
"silex/silex": "~1.0"
},
"require-dev": {
"symfony/browser-kit": ">=2.0,<3.0"
"symfony/browser-kit": "~2.0"
},
"type": "library",
"autoload": {
@@ -5563,7 +5575,7 @@
},
{
"name": "Phraseanet Team",
"email": "support@alchemy.fr",
"email": "info@alchemy.fr",
"homepage": "http://www.phraseanet.com/"
}
],
@@ -7716,6 +7728,12 @@
}
],
"aliases": [
{
"alias": "1.6.1",
"alias_normalized": "1.6.1.0",
"version": "2.0.3.0",
"package": "justinrainbow/json-schema"
},
{
"alias": "0.13.0",
"alias_normalized": "0.13.0.0",

View File

@@ -6,6 +6,7 @@ main:
maintenance: false
languages: []
key: ''
api_require_ssl: true
database:
host: 127.0.0.1
port: 3306

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of alchemy/pipeline-component.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Account;
class RestrictedStatusExtractor
{
/**
* @var \ACL
*/
private $acl;
/**
* @var \appbox
*/
private $applicationBox;
/**
* @param \ACL $acl
* @param \appbox $applicationBox
*/
public function __construct(\ACL $acl, \appbox $applicationBox)
{
$this->acl = $acl;
$this->applicationBox = $applicationBox;
}
public function getRestrictedStatuses($baseId)
{
$restrictions = [];
$andMask = $this->acl->get_mask_and($baseId);
$xorMask = $this->acl->get_mask_xor($baseId);
$structure = $this->getStatusStructure($baseId);
for ($position = 0; $position < 32; $position++) {
$andBit = (1 << $position) & $andMask;
$xorBit = (1 << $position) & $xorMask;
$status = $structure->getStatus($position);
if (! $andBit && $status['labels_on_i18n'] == null && $status['labels_off_i18n'] == null) {
// Ignore unrestricted statuses with null label arrays (label is not configured, hence not used)
continue;
}
$restrictions[] = [
'position' => $position,
'labels' => [
'on' => $status['labels_on_i18n'],
'off' => $status['labels_off_i18n']
],
'restricted' => (bool) $andBit,
'restriction_flag' => (bool) $xorBit
];
}
return $restrictions;
}
/**
* @param $baseId
* @return \Alchemy\Phrasea\Status\StatusStructure
*/
private function getStatusStructure($baseId)
{
$databoxId = $this->applicationBox->get_collection($baseId)->get_sbas_id();
$databox = $this->applicationBox->get_databox($databoxId);
return $databox->getStatusStructure();
}
}

View File

@@ -23,7 +23,6 @@ use Alchemy\Phrasea\Core\Event\Subscriber\BridgeSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\ExportSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\FeedEntrySubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\LazaretSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\OrderSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaInstallSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\RegistrationSubscriber;
use Alchemy\Phrasea\Core\Event\Subscriber\ValidationSubscriber;
@@ -56,6 +55,7 @@ use Alchemy\Phrasea\Core\Provider\JMSSerializerServiceProvider;
use Alchemy\Phrasea\Core\Provider\LocaleServiceProvider;
use Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider;
use Alchemy\Phrasea\Core\Provider\NotificationDelivererServiceProvider;
use Alchemy\Phrasea\Core\Provider\OrderServiceProvider;
use Alchemy\Phrasea\Core\Provider\PhraseaEventServiceProvider;
use Alchemy\Phrasea\Core\Provider\PhraseanetServiceProvider;
use Alchemy\Phrasea\Core\Provider\PhraseaVersionServiceProvider;
@@ -237,8 +237,10 @@ class Application extends SilexApplication
$this->register(new PhraseaEventServiceProvider());
$this->register(new LocaleServiceProvider());
$this->setupEventDispatcher();
$this->register(new OrderServiceProvider());
$this->register(new WebhookServiceProvider());
$this['phraseanet.exception_handler'] = $this->share(function ($app) {
@@ -705,7 +707,6 @@ class Application extends SilexApplication
$dispatcher->addSubscriber(new RegistrationSubscriber($app));
$dispatcher->addSubscriber(new BridgeSubscriber($app));
$dispatcher->addSubscriber(new ExportSubscriber($app));
$dispatcher->addSubscriber(new OrderSubscriber($app));
$dispatcher->addSubscriber(new BasketSubscriber($app));
$dispatcher->addSubscriber(new LazaretSubscriber($app));
$dispatcher->addSubscriber(new ValidationSubscriber($app));

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Command\TaskManagerCommand;
use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Model\Entities\Task;
use Alchemy\Phrasea\TaskManager\Event\FinishedJobRemoverSubscriber;
use Alchemy\Phrasea\TaskManager\Job\AbstractJob;
use Alchemy\Phrasea\TaskManager\Job\JobData;
use Alchemy\TaskManager\Event\JobSubscriber\DurationLimitSubscriber;
use Alchemy\TaskManager\Event\JobSubscriber\LockFileSubscriber;
@@ -72,6 +73,12 @@ class TaskRun extends TaskManagerCommand
return $task;
}
/**
* @param InputInterface $input
* @param Task $task
* @param Logger $logger
* @return AbstractJob
*/
protected function createJob(InputInterface $input, Task $task, Logger $logger)
{
$job = $this->container['task-manager.job-factory']->create($task->getJobId());

View File

@@ -16,6 +16,7 @@ use Alchemy\Phrasea\Authentication\Exception\AccountLockedException;
use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException;
use Alchemy\Phrasea\Authentication\Phrasea\PasswordAuthenticationInterface;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\Core\Event\PostAuthenticate;
use Alchemy\Phrasea\Core\Event\PreAuthenticate;
use Alchemy\Phrasea\Core\PhraseaEvents;
@@ -174,8 +175,11 @@ class OAuth2Controller extends Controller
*/
public function tokenAction(Request $request)
{
if ( ! $request->isSecure()) {
throw new HttpException(400, 'This route requires the use of the https scheme', null, ['content-type' => 'application/json']);
/** @var PropertyAccess $config */
$config = $this->app['conf'];
if ( ! $request->isSecure() && $config->get(['main', 'api_require_ssl'], true) == true) {
throw new HttpException(400, 'This route requires the use of the https scheme: ' . $config->get(['main', 'api_require_ssl']), null, ['content-type' => 'application/json']);
}
$this->oAuth2Adapter->grantAccessToken($request);

View File

@@ -14,6 +14,7 @@ use Alchemy\Phrasea\Account\AccountService;
use Alchemy\Phrasea\Account\CollectionRequestMapper;
use Alchemy\Phrasea\Account\Command\UpdateAccountCommand;
use Alchemy\Phrasea\Account\Command\UpdatePasswordCommand;
use Alchemy\Phrasea\Account\RestrictedStatusExtractor;
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
@@ -84,6 +85,10 @@ use Alchemy\Phrasea\Status\StatusStructure;
use Alchemy\Phrasea\TaskManager\LiveInformation;
use Alchemy\Phrasea\Utilities\NullableDateTime;
use Doctrine\ORM\EntityManager;
<<<<<<< HEAD
=======
use JMS\TranslationBundle\Annotation\Ignore;
>>>>>>> 4.0
use League\Fractal\Resource\Item;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\File\UploadedFile;
@@ -747,6 +752,9 @@ class V1Controller extends Controller
$grants = [];
$statusMapper = new RestrictedStatusExtractor($acl, $this->getApplicationBox());
foreach ($bases as $base) {
$baseGrants = [];
@@ -763,6 +771,33 @@ class V1Controller extends Controller
'base_id' => $base->get_base_id(),
'collection_id' => $base->get_coll_id(),
'rights' => $baseGrants,
'statuses' => $statusMapper->getRestrictedStatuses($base->get_base_id())
];
}
return $grants;
}
private function listUserDataboxes(User $user)
{
$acl = $this->getAclForUser($user);
$rightsByDatabox = $acl->get_sbas_rights();
$grants = [];
foreach ($rightsByDatabox as $databoxId => $databoxRights) {
$rights = [];
foreach ($databoxRights as $name => $allowedFlag) {
if (! $allowedFlag) {
continue;
}
$rights[] = $name;
}
$grants[] = [
'databox_id' => $databoxId,
'rights' => $rights
];
}
@@ -1180,6 +1215,7 @@ class V1Controller extends Controller
->getRecordRepository()
->findChildren($storyIds, $user);
$children[$databoxId] = array_combine($storyIds, $selections);
<<<<<<< HEAD
}
/** @var StoryView[] $storyViews */
@@ -1223,6 +1259,51 @@ class V1Controller extends Controller
$allChildren[$index] = $childrenView->getRecord();
}
=======
}
/** @var StoryView[] $storyViews */
$storyViews = [];
/** @var RecordView[] $childrenViews */
$childrenViews = [];
foreach ($stories as $index => $story) {
$storyView = new StoryView($story);
$selection = $children[$story->getDataboxId()][$story->getRecordId()];
$childrenView = $this->buildRecordViews($selection);
foreach ($childrenView as $view) {
$childrenViews[spl_object_hash($view)] = $view;
}
$storyView->setChildren($childrenView);
$storyViews[$index] = $storyView;
}
if (in_array('results.stories.thumbnail', $includes, true)) {
$subdefViews = $this->buildSubdefsViews($stories, ['thumbnail'], $urlTTL);
foreach ($storyViews as $index => $storyView) {
$storyView->setSubdefs($subdefViews[$index]);
}
}
if (in_array('results.stories.metadatas', $includes, true)) {
$captions = $this->app['service.caption']->findByReferenceCollection($stories);
$canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($stories);
$this->buildCaptionViews($storyViews, $captions, $canSeeBusiness);
}
$allChildren = new RecordCollection();
foreach ($childrenViews as $index => $childrenView) {
$allChildren[$index] = $childrenView->getRecord();
}
>>>>>>> 4.0
$names = in_array('results.stories.records.subdefs', $includes, true) ? null : ['thumbnail'];
$subdefViews = $this->buildSubdefsViews($allChildren, $names, $urlTTL);
$technicalDatasets = $this->app['service.technical_data']->fetchRecordsTechnicalData($allChildren);
@@ -1339,6 +1420,7 @@ class V1Controller extends Controller
/** @var \media_subdef $subdef */
foreach ($allSubdefs as $index => $subdef) {
$subdefView = new SubdefView($subdef);
<<<<<<< HEAD
if (isset($allPermalinks[$index])) {
$subdefView->setPermalinkView(new PermalinkView($allPermalinks[$index]));
@@ -1407,6 +1489,76 @@ class V1Controller extends Controller
];
}
=======
if (isset($allPermalinks[$index])) {
$subdefView->setPermalinkView(new PermalinkView($allPermalinks[$index]));
}
$subdefView->setUrl($urls[$index]);
$subdefView->setUrlTTL($urlTTL);
$subdefViews[spl_object_hash($subdef)] = $subdefView;
}
$reorderedGroups = [];
/** @var \media_subdef[] $subdefGroup */
foreach ($subdefGroups as $index => $subdefGroup) {
$reordered = [];
foreach ($subdefGroup as $subdef) {
$reordered[] = $subdefViews[spl_object_hash($subdef)];
}
$reorderedGroups[$index] = $reordered;
}
return $reorderedGroups;
}
/**
* Returns requested includes
*
* @param Request $request
* @return string[]
*/
private function resolveSearchIncludes(Request $request)
{
if ($request->attributes->get('_extended', false)) {
return [
'results.stories.records.subdefs',
'results.stories.records.metadata',
'results.stories.records.caption',
'results.stories.records.status',
'results.records.subdefs',
'results.records.metadata',
'results.records.caption',
'results.records.status',
];
}
return [];
}
/**
* Returns requested includes
*
* @param Request $request
* @return string[]
*/
private function resolveSearchRecordsIncludes(Request $request)
{
if ($request->attributes->get('_extended', false)) {
return [
'results.subdefs',
'results.metadata',
'results.caption',
'results.status',
];
}
>>>>>>> 4.0
return [];
}
@@ -2559,6 +2711,7 @@ class V1Controller extends Controller
$ret = [
"user" => $this->listUser($this->getAuthenticatedUser()),
"collections" => $this->listUserCollections($this->getAuthenticatedUser()),
"databoxes" => $this->listUserDataboxes($this->getAuthenticatedUser())
];
if (defined('API_SKIP_USER_REGISTRATIONS') && ! constant('API_SKIP_USER_REGISTRATIONS')) {
@@ -2608,12 +2761,16 @@ class V1Controller extends Controller
$ret = [ 'success' => true ];
}
catch (AccountException $exception) {
/** @Ignore */
$ret = [ 'success' => false, 'message' => $this->app->trans($exception->getMessage()) ];
}
return Result::create($request, $ret)->createResponse();
}
/**
* @Ignore
*/
public function updateCurrentUserPasswordAction(Request $request)
{
$service = $this->getAccountService();
@@ -2631,6 +2788,7 @@ class V1Controller extends Controller
$service->updatePassword($command, null);
$ret = ['success' => true];
} catch (AccountException $exception) {
/** @Ignore */
$ret = [ 'success' => false, 'message' => $this->app->trans($exception->getMessage()) ];
}
} else {

View File

@@ -87,7 +87,7 @@ class PermalinkController extends AbstractDelivery
'sbas_id' => $sbas_id,
'record_id' => $record_id,
'subdef' => $subdefName,
'label' => $record->get_title(),
'label' => str_replace('/', '_', $record->get_title()),
'token' => $token,
]
);

View File

@@ -162,7 +162,7 @@ class DoDownloadController extends Controller
$this->app,
$token,
$list,
sprintf($this->app['tmp.download.path'].'/%s.zip', $token->getValue()) // Dest file
sprintf('%s/%s.zip', $this->app['tmp.download.path'], $token->getValue()) // Dest file
);
} else {
$list['complete'] = true;

View File

@@ -141,7 +141,7 @@ class PushController extends Controller
if (!$this->getConf()->get(['registry', 'actions', 'enable-push-authentication'])
|| !$request->get('force_authentication')
) {
$arguments['LOG'] = $this->getTokenManipulator()->createBasketAccessToken($Basket, $user_receiver);
$arguments['LOG'] = $this->getTokenManipulator()->createBasketAccessToken($Basket, $user_receiver)->getValue();
}
$url = $this->app->url('lightbox_compare', $arguments);
@@ -343,7 +343,7 @@ class PushController extends Controller
if (!$this->getConf()->get(['registry', 'actions', 'enable-push-authentication'])
|| !$request->get('force_authentication')
) {
$arguments['LOG'] = $this->getTokenManipulator()->createBasketAccessToken($basket, $participantUser);
$arguments['LOG'] = $this->getTokenManipulator()->createBasketAccessToken($basket, $participantUser)->getValue();
}
$url = $this->app->url('lightbox_validation', $arguments);

View File

@@ -22,6 +22,7 @@ use Alchemy\Phrasea\Model\Repositories\ApiOauthTokenRepository;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -239,6 +240,10 @@ class DeveloperController extends Controller
? $this->getApiOAuthTokenRepository()->findDeveloperToken($account)
: null;
if (! $account) {
throw new AccessDeniedHttpException();
}
return $this->render('developers/application.html.twig', [
"application" => $application,
"user" => $user,

View File

@@ -55,6 +55,8 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface
return (new ApiOrderController($app))
->setDispatcher($app['dispatcher'])
->setEntityManagerLocator(new LazyLocator($app, 'orm.em'))
->setDelivererLocator(new LazyLocator($app, 'phraseanet.file-serve'))
->setFileSystemLocator(new LazyLocator($app, 'filesystem'))
->setJsonBodyHelper($app['json.body_helper']);
}
);
@@ -115,6 +117,10 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface
->assert('orderId', '\d+')
->bind('api_v2_orders_deny');
$controllers->get('/orders/{orderId}/archive', 'controller.api.v2.orders:getArchiveAction')
->assert('orderId', '\d+')
->bind('api_v2_orders_archive');
return $controllers;
}

View File

@@ -13,33 +13,49 @@ namespace Alchemy\Phrasea\Core\Event;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Order\OrderDelivery;
class OrderDeliveryEvent extends OrderEvent
{
private $admin;
private $quantity;
/**
* @var OrderDelivery
*/
private $orderDelivery;
public function __construct(Order $order, User $admin, $quantity)
/**
* @param OrderDelivery $delivery
*/
public function __construct(OrderDelivery $delivery)
{
parent::__construct($order);
$this->admin = $admin;
$this->quantity = $quantity;
parent::__construct($delivery->getOrder());
$this->orderDelivery = $delivery;
}
/**
* @return OrderDelivery
*/
public function getDelivery()
{
return $this->orderDelivery;
}
/**
* @return User
* @deprecated Use OrderDeliveryEvent::getDelivery() to retrieve admin user.
*/
public function getAdmin()
{
return $this->admin;
return $this->orderDelivery->getAdmin();
}
/**
* @return mixed
* @return int
* @deprecated Use OrderDeliveryEvent::getDelivery() to read quantity.
*/
public function getQuantity()
{
return $this->quantity;
return $this->orderDelivery->getQuantity();
}
}

View File

@@ -11,30 +11,42 @@
namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Event\OrderDeliveryEvent;
use Alchemy\Phrasea\Core\Event\OrderEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\OrderElement;
use Alchemy\Phrasea\Notification\Emitter;
use Alchemy\Phrasea\Notification\Mail\MailInfoNewOrder;
use Alchemy\Phrasea\Notification\Mail\MailInfoOrderCancelled;
use Alchemy\Phrasea\Notification\Mail\MailInfoOrderDelivered;
use Alchemy\Phrasea\Notification\Receiver;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Order\ValidationNotifier\CompositeNotifier;
use Alchemy\Phrasea\Order\ValidationNotifierRegistry;
class OrderSubscriber extends AbstractNotificationSubscriber
{
/**
* @var ValidationNotifierRegistry
*/
private $notifierRegistry;
/**
* @param Application $application
* @param ValidationNotifierRegistry $notifierRegistry
*/
public function __construct(Application $application, ValidationNotifierRegistry $notifierRegistry)
{
parent::__construct($application);
$this->notifierRegistry = $notifierRegistry;
}
public function onCreate(OrderEvent $event)
{
$params = [
'usr_id' => $event->getOrder()->getUser()->getId(),
'order_id' => $event->getOrder()->getId(),
];
$base_ids = 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)
->who_have_right(['order_master'])
->execute()->get_results();
@@ -43,99 +55,80 @@ class OrderSubscriber extends AbstractNotificationSubscriber
return;
}
$datas = json_encode($params);
$notificationData = json_encode([
'usr_id' => $event->getOrder()->getUser()->getId(),
'order_id' => $event->getOrder()->getId(),
]);
$orderInitiator = $event->getOrder()->getUser();
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod());
$notifier->notifyCreation($event->getOrder(), $event->getOrder()->getUser());
$notifier = $this->notifierRegistry->getNotifier(Order::NOTIFY_MAIL);
foreach ($users as $user) {
$mailed = false;
$notified = false;
if ($this->shouldSendNotificationFor($user, 'eventsmanager_notify_order')) {
try {
$receiver = Receiver::fromUser($user);
$notifier->notifyCreation($event->getOrder(), $user);
} catch (\Exception $e) {
continue;
}
$mail = MailInfoNewOrder::create($this->app, $receiver);
$mail->setUser($orderInitiator);
$this->deliver($mail);
$mailed = true;
$notified = true;
}
$this->app['events-manager']->notify($user->getId(), 'eventsmanager_notify_order', $datas, $mailed);
$this->app['events-manager']->notify($user->getId(), 'eventsmanager_notify_order', $notificationData, $notified);
}
}
public function onDeliver(OrderDeliveryEvent $event)
{
$params = [
'from' => $event->getAdmin()->getId(),
$notified = false;
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod());
$notificationData = json_encode([
'from' => $event->getDelivery()->getAdmin()->getId(),
'to' => $event->getOrder()->getUser()->getId(),
'ssel_id' => $event->getOrder()->getBasket()->getId(),
'n' => $event->getQuantity(),
];
$datas = json_encode($params);
$mailed = false;
'n' => $event->getDelivery()->getQuantity()
]);
if ($this->shouldSendNotificationFor($event->getOrder()->getUser(), 'eventsmanager_notify_orderdeliver')) {
$user_from = $event->getAdmin();
$user_to = $event->getOrder()->getUser();
$receiver = Receiver::fromUser($event->getOrder()->getUser());
$emitter = Emitter::fromUser($event->getAdmin());
$basket = $event->getOrder()->getBasket();
$url = $this->app->url('lightbox_compare', [
'basket' => $basket->getId(),
'LOG' => $this->app['manipulator.token']->createBasketAccessToken($basket, $user_to)->getValue(),
]);
$mail = MailInfoOrderDelivered::create($this->app, $receiver, $emitter, null);
$mail->setButtonUrl($url);
$mail->setBasket($basket);
$mail->setDeliverer($user_from);
$this->deliver($mail);
$mailed = true;
$notifier->notifyDelivery($event->getDelivery());
$notified = true;
}
return $this->app['events-manager']->notify($params['to'], 'eventsmanager_notify_orderdeliver', $datas, $mailed);
return $this->app['events-manager']->notify(
$event->getOrder()->getUser()->getId(),
'eventsmanager_notify_orderdeliver',
$notificationData,
$notified
);
}
public function onDeny(OrderDeliveryEvent $event)
{
$params = [
'from' => $event->getAdmin()->getId(),
$notified = false;
$notifier = $this->notifierRegistry->getNotifier($event->getOrder()->getNotificationMethod());
$notificationData = json_encode([
'from' => $event->getDelivery()->getAdmin()->getId(),
'to' => $event->getOrder()->getUser()->getId(),
'n' => $event->getQuantity(),
];
'n' => $event->getDelivery()->getQuantity()
]);
$datas = json_encode($params);
$mailed = false;
if ($this->shouldSendNotificationFor($event->getOrder()->getUser(), 'eventsmanager_notify_ordernotdelivered')) {
$user_from = $event->getAdmin();
$user_to = $event->getOrder()->getUser();
$receiver = Receiver::fromUser($user_to);
$emitter = Emitter::fromUser($user_from);
$mail = MailInfoOrderCancelled::create($this->app, $receiver, $emitter);
$mail->setQuantity($params['n']);
$mail->setDeliverer($user_from);
$this->deliver($mail);
$mailed = true;
$notifier->notifyDenial($event->getDelivery());
$notified = true;
}
return $this->app['events-manager']->notify($params['to'], 'eventsmanager_notify_ordernotdelivered', $datas, $mailed);
return $this->app['events-manager']->notify(
$event->getOrder()->getUser()->getId(),
'eventsmanager_notify_ordernotdelivered',
$notificationData,
$notified
);
}
public static function getSubscribedEvents()

View File

@@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Helper\JsonBodyHelper;
use JsonSchema\RefResolver;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\Validator;
use Silex\Application;
@@ -31,7 +32,7 @@ class JsonSchemaServiceProvider implements ServiceProviderInterface
});
$app['json-schema.ref_resolver'] = $app->share(function (Application $app) {
return new RefResolver($app['json-schema.retriever']);
return new RefResolver($app['json-schema.retriever'], new UriResolver());
});
$app['json-schema.validator'] = $app->share(function (Application $app) {

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of alchemy/pipeline-component.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Controller\LazyLocator;
use Alchemy\Phrasea\Core\Event\Subscriber\OrderSubscriber;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Order\ValidationNotifier\MailNotifier;
use Alchemy\Phrasea\Order\ValidationNotifier\WebhookNotifier;
use Alchemy\Phrasea\Order\ValidationNotifierRegistry;
use Silex\Application;
use Silex\ServiceProviderInterface;
class OrderServiceProvider implements ServiceProviderInterface
{
/**
* Registers services on the given app.
*
* This method should only be used to configure services and parameters.
* It should not get services.
*/
public function register(Application $app)
{
$app['events.order_subscriber'] = $app->share(function (Application $app) {
$notifierRegistry = new ValidationNotifierRegistry();
$notifierRegistry->registerNotifier(Order::NOTIFY_MAIL, new MailNotifier($app));
$notifierRegistry->registerNotifier(Order::NOTIFY_WEBHOOK, new WebhookNotifier(
new LazyLocator($app, 'manipulator.webhook-event')
));
return new OrderSubscriber($app, $notifierRegistry);
});
}
/**
* Bootstraps the application.
*
* This method is called after all services are registered
* and should be used for "dynamic" configuration (whenever
* a service must be requested).
*/
public function boot(Application $app)
{
$app['dispatcher']->addSubscriber($app['events.order_subscriber']);
}
}

View File

@@ -16,7 +16,7 @@ class Version
/**
* @var string
*/
private $number = '4.0.0-alpha.6';
private $number = '4.0.0-alpha.8';
/**
* @var string

View File

@@ -1,4 +1,5 @@
<?php
/*
* This file is part of Phraseanet
*
@@ -7,6 +8,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Databox;
interface DataboxRepository

View File

@@ -52,11 +52,7 @@ class JsonBodyHelper
*/
public function retrieveSchema($schemaUri)
{
$schema = $this->uriRetriever->retrieve($schemaUri, $this->baseUri);
$this->refResolver->resolve($schema);
return $schema;
return $this->refResolver->resolve($this->baseUri . $schemaUri);
}
/**

View File

@@ -33,7 +33,7 @@ class FeedEntry
private $title;
/**
* @ORM\Column(type="string", length=128)
* @ORM\Column(type="string", length=1024)
*/
private $subtitle;

View File

@@ -20,12 +20,17 @@ use Gedmo\Mapping\Annotation as Gedmo;
*/
class Order
{
const NOTIFY_MAIL = 'mail';
const NOTIFY_WEBHOOK = 'webhook';
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
private $id;
/**
* @ORM\ManyToOne(targetEntity="User")
@@ -67,12 +72,18 @@ class Order
*/
private $basket;
/**
* @ORM\Column(type="string", length=32, name="notification_method")
*/
private $notificationMethod;
/**
* Constructor
*/
public function __construct()
{
$this->elements = new ArrayCollection();
$this->notificationMethod = self::NOTIFY_MAIL;
}
/**
@@ -280,4 +291,28 @@ class Order
{
return $this->basket;
}
/**
* @return string The name of the notification method that will be used to handle this order's status change
* notifications.
*/
public function getNotificationMethod()
{
return $this->notificationMethod;
}
/**
* Sets the name of the notification method to handle this order's status change
* notifications.
* @param string $methodName
* @return void
*/
public function setNotificationMethod($methodName)
{
if (trim($methodName) == '') {
$methodName = self::NOTIFY_MAIL;
}
$this->notificationMethod = $methodName;
}
}

View File

@@ -26,6 +26,11 @@ class WebhookEvent
const RECORD_SUBDEFS_CREATED = 'record.subdefs.created';
const RECORD_SUBDEF_TYPE = 'record.subdef';
const ORDER_TYPE = 'order';
const ORDER_CREATED = 'order.created';
const ORDER_DELIVERED = 'order.delivered';
const ORDER_DENIED = 'order.denied';
/**
* @ORM\Column(type="integer")
* @ORM\Id

View File

@@ -41,11 +41,13 @@ class OrderRepository extends EntityRepository
public function listOrders($baseIds, $offsetStart = 0, $perPage = 20, $sort = "created_on")
{
$qb = $this
->createQueryBuilder('o')
->innerJoin('o.elements', 'e');
->createQueryBuilder('o');
if (!empty($baseIds)) {
$qb->where($qb->expr()->in('e.baseId', $baseIds));
$qb
->innerJoin('o.elements', 'e')
->where($qb->expr()->in('e.baseId', $baseIds))
->groupBy('o.id');
}
if ($sort === 'user') {
@@ -53,7 +55,7 @@ class OrderRepository extends EntityRepository
} elseif ($sort === 'usage') {
$qb->orderBy('o.orderUsage', 'ASC');
} else {
$qb->orderBy('o.createdOn', 'ASC');
$qb->orderBy('o.createdOn', 'DESC');
}
$qb

View File

@@ -16,9 +16,20 @@ use Alchemy\Phrasea\Model\Entities\User;
class Emitter implements EmitterInterface
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $email;
/**
* @param string $name
* @param string $email
*/
public function __construct($name, $email)
{
if (!\Swift_Validate::email($email)) {

View File

@@ -194,7 +194,7 @@ abstract class AbstractMail implements MailInterface
* @param EmitterInterface $emitter
* @param string $message
*
* @return MailInterface
* @return static
*/
public static function create(Application $app, ReceiverInterface $receiver, EmitterInterface $emitter = null, $message = null)
{

View File

@@ -10,24 +10,23 @@
namespace Alchemy\Phrasea\Order\Controller;
use Alchemy\Phrasea\Application\Helper\DelivererAware;
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Core\Event\ExportEvent;
use Alchemy\Phrasea\Core\Event\OrderEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Http\DeliverDataInterface;
use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\OrderElement;
use Alchemy\Phrasea\Model\RecordReferenceInterface;
use Alchemy\Phrasea\Order\OrderElementTransformer;
use Alchemy\Phrasea\Order\OrderElementView;
use Alchemy\Phrasea\Order\OrderFiller;
use Alchemy\Phrasea\Order\OrderTransformer;
use Alchemy\Phrasea\Order\OrderView;
use Alchemy\Phrasea\Order\OrderViewBuilder;
use Alchemy\Phrasea\Record\RecordReference;
use Alchemy\Phrasea\Record\RecordReferenceCollection;
use Assert\Assertion;
use Doctrine\Common\Collections\ArrayCollection;
use League\Fractal\Manager;
use League\Fractal\Pagination\PagerfantaPaginatorAdapter;
@@ -39,9 +38,12 @@ use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ApiOrderController extends BaseOrderController
{
use DelivererAware;
use FilesystemAware;
use JsonBodyAware;
public function createAction(Request $request)
@@ -61,6 +63,7 @@ class ApiOrderController extends BaseOrderController
$order->setUser($this->getAuthenticatedUser());
$order->setDeadline(new \DateTime($data->data->deadline, new \DateTimeZone('UTC')));
$order->setOrderUsage($data->data->usage);
$order->setNotificationMethod(Order::NOTIFY_WEBHOOK);
$filler->fillOrder($order, $recordRequest);
@@ -124,7 +127,7 @@ class ApiOrderController extends BaseOrderController
$fractal = $this->buildFractalManager($request->get('includes', []));
if ($order->getUser()->getId() !== $this->getAuthenticatedUser()->getId()) {
if (! $this->isOrderAccessible($order)) {
throw new AccessDeniedHttpException(sprintf('Cannot access order "%d"', $order->getId()));
}
@@ -134,6 +137,54 @@ class ApiOrderController extends BaseOrderController
return $this->returnResourceResponse($request, $fractal, $resource);
}
/**
* @param Request $request
* @param int $orderId
* @return Response
*/
public function getArchiveAction(Request $request, $orderId)
{
$order = $this->findOr404($orderId);
if (! $this->isOrderAccessible($order)) {
throw new AccessDeniedHttpException(sprintf('Cannot access order "%d"', $order->getId()));
}
$basket = $order->getBasket();
if (null === $basket instanceof Basket) {
throw new NotFoundHttpException(sprintf('No archive could be downloaded for Order "%d"', $order->getId()));
}
$export = new \set_export($this->app, '', $basket->getId());
$exportName = sprintf('%s/%s.zip', $this->app['tmp.download.path'], $export->getExportName());
$user = $this->getAuthenticatedUser();
$subdefs = $this->findDataboxSubdefNames();
$exportData = $export->prepare_export($user, $this->getFilesystem(), $subdefs, true, true);
$exportData['export_name'] = $exportName;
$token = $this->app['manipulator.token']->createDownloadToken($user, serialize($exportData));
$lst = [];
foreach ($exportData['files'] as $file) {
$lst[] = $this->getApplicationBox()->get_collection($file['base_id'])->get_databox()->get_sbas_id() . '_' . $file['record_id'];
}
$this->dispatch(
PhraseaEvents::EXPORT_CREATE,
new ExportEvent($user, $basket->getId(), implode(';', $lst), $subdefs, $exportName)
);
set_time_limit(0);
ignore_user_abort(true);
$file = \set_export::build_zip($this->app, $token, $exportData, $exportName);
return $this->deliverFile($file, $exportName, DeliverDataInterface::DISPOSITION_INLINE, 'application/zip');
}
public function acceptElementsAction(Request $request, $orderId)
{
$elementIds = $this->fetchElementIdsFromRequest($request);
@@ -258,4 +309,42 @@ class ApiOrderController extends BaseOrderController
$this->app['service.media_subdef']
);
}
/**
* @param $order
* @return bool
*/
private function isOrderAccessible(Order $order)
{
if ($order->getUser()->getId() === $this->getAuthenticatedUser()->getId()) {
return true;
}
if ($order->getUser()->isAdmin()) {
return true;
}
return false;
}
/**
* @return string[]
*/
private function findDataboxSubdefNames()
{
$subdefNames = [
'document' => true,
];
foreach ($this->getApplicationBox()->get_databoxes() as $databox) {
foreach ($databox->get_subdef_structure() as $subdefGroup) {
/** @var \databox_subdef $subdef */
foreach ($subdefGroup as $subdef) {
$subdefNames[$subdef->get_name()] = true;
}
}
}
return array_keys($subdefNames);
}
}

View File

@@ -22,6 +22,7 @@ use Alchemy\Phrasea\Model\Entities\OrderElement;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Repositories\OrderElementRepository;
use Alchemy\Phrasea\Model\Repositories\OrderRepository;
use Alchemy\Phrasea\Order\OrderDelivery;
use Alchemy\Phrasea\Order\OrderValidator;
use Alchemy\Phrasea\Order\PartialOrder;
use Alchemy\Phrasea\Record\RecordReference;
@@ -136,7 +137,9 @@ class BaseOrderController extends Controller
$manager->persist($element);
}
$this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, count($basketElements)));
$delivery = new OrderDelivery($order, $acceptor, count($basketElements));
$this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($delivery));
}
$manager->persist($basket);
@@ -164,7 +167,9 @@ class BaseOrderController extends Controller
try {
if (!empty($elements)) {
$this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($order, $acceptor, count($elements)));
$delivery = new OrderDelivery($order, $acceptor, count($elements));
$this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($delivery));
}
$manager = $this->getEntityManager();
@@ -189,7 +194,7 @@ class BaseOrderController extends Controller
$basketReferences = new RecordReferenceCollection();
$basket->getElements()->forAll(function (BasketElement $element) use ($basketReferences) {
$basket->getElements()->forAll(function ($index, BasketElement $element) use ($basketReferences) {
$basketReferences->addRecordReference($element->getSbasId(), $element->getRecordId());
});

View File

@@ -100,8 +100,13 @@ class ProdOrderController extends BaseOrderController
public function displayOrders(Request $request)
{
$page = (int) $request->query->get('page', 1);
$offsetStart = $page - 1;
$perPage = (int) $request->query->get('per-page', 10);
$offsetStart = 0;
if ($page > 0) {
$offsetStart = ($page - 1) * $perPage;
}
$sort = $request->query->get('sort');
$baseIds = array_keys($this->getAclForUser()->get_granted_base(['order_master']));

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
class OrderDelivery
{
/**
* @var User
*/
private $admin;
/**
* @var Order
*/
private $order;
/**
* @var int
*/
private $quantity;
/**
* @param Order $deliveredOrder
* @param User $manager
* @param int $quantity
*/
public function __construct(Order $deliveredOrder, User $manager, $quantity)
{
$this->order = $deliveredOrder;
$this->admin = $manager;
$this->quantity = $quantity;
}
/**
* @return User
*/
public function getAdmin()
{
return $this->admin;
}
/**
* @return Order
*/
public function getOrder()
{
return $this->order;
}
/**
* @return int
*/
public function getQuantity()
{
return $this->quantity;
}
}

View File

@@ -77,6 +77,7 @@ class OrderElementView
/**
* @param \media_subdef[] $subdefs
* @return void
*/
public function setOrderableMediaSubdefs($subdefs)
{

View File

@@ -37,9 +37,13 @@ class OrderTransformer extends TransformerAbstract
'owner_id' => (int)$order->getUser()->getId(),
'created' => $order->getCreatedOn()->format(DATE_ATOM),
'usage' => $order->getOrderUsage(),
'status' => 0 === $order->getTodo() ? 'finished' : 'pending'
'status' => 0 === $order->getTodo() ? 'finished' : 'pending',
];
if ($view->getArchiveUrl()) {
$data['archive_url'] = $view->getArchiveUrl();
}
if ($order->getDeadline()) {
$data['deadline'] = $order->getDeadline()->format(DATE_ATOM);
}

View File

@@ -20,6 +20,11 @@ class OrderView
*/
private $order;
/**
* @var string
*/
private $archiveUrl;
/**
* @var OrderElementView[]
*/
@@ -43,6 +48,14 @@ class OrderView
$this->viewElements = $viewElements instanceof \Traversable ? iterator_to_array($viewElements) : $viewElements;
}
/**
* @param string $archiveUrl
*/
public function setArchiveUrl($archiveUrl)
{
$this->archiveUrl = (string)$archiveUrl;
}
/**
* @return Order
*/
@@ -51,6 +64,14 @@ class OrderView
return $this->order;
}
/**
* @return string
*/
public function getArchiveUrl()
{
return $this->archiveUrl;
}
/**
* @return OrderElementView[]
*/

View File

@@ -84,6 +84,21 @@ class OrderViewBuilder
*/
private function fillViews(array $views, array $includes)
{
array_walk($views, function (OrderView $view) {
// Archive is only available when a Basket is associated with the order (at least one element was accepted)
if (null === $basket = $view->getOrder()->getBasket()) {
return;
}
if ($basket->getElements()->isEmpty()) {
return;
}
$view->setArchiveUrl($this->application->url('api_v2_orders_archive', [
'orderId' => $view->getOrder()->getId(),
]));
});
if (!in_array('elements', $includes, true)) {
return;
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
interface ValidationNotifier
{
/**
* @param Order $order
* @param User $recipient
* @return void
*/
public function notifyCreation(Order $order, User $recipient);
/**
* @param OrderDelivery $delivery
* @return void
*/
public function notifyDelivery(OrderDelivery $delivery);
/**
* @param OrderDelivery $delivery
* @return void
*/
public function notifyDenial(OrderDelivery $delivery);
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Alchemy\Phrasea\Order\ValidationNotifier;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Order\OrderDelivery;
use Alchemy\Phrasea\Order\ValidationNotifier;
use Assert\Assertion;
class CompositeNotifier implements ValidationNotifier
{
/**
* @var ValidationNotifier[]
*/
private $notifiers = [];
public function __construct(array $notifiers)
{
Assertion::allIsInstanceOf($notifiers, ValidationNotifier::class);
$this->notifiers = $notifiers;
}
/**
* @param Order $order
* @param User $recipient
*/
public function notifyCreation(Order $order, User $recipient)
{
foreach ($this->notifiers as $notifier) {
$notifier->notifyCreation($order, $recipient);
}
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDelivery(OrderDelivery $delivery)
{
foreach ($this->notifiers as $notifier) {
$notifier->notifyDelivery($delivery);
}
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDenial(OrderDelivery $delivery)
{
foreach ($this->notifiers as $notifier) {
$notifier->notifyDenial($delivery);
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order\ValidationNotifier;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Notification\Deliverer;
use Alchemy\Phrasea\Notification\Emitter;
use Alchemy\Phrasea\Notification\Mail\MailInfoNewOrder;
use Alchemy\Phrasea\Notification\Mail\MailInfoOrderCancelled;
use Alchemy\Phrasea\Notification\Mail\MailInfoOrderDelivered;
use Alchemy\Phrasea\Notification\Receiver;
use Alchemy\Phrasea\Order\OrderDelivery;
use Alchemy\Phrasea\Order\ValidationNotifier;
class MailNotifier implements ValidationNotifier
{
/**
* @var Application
*/
private $application;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* @return Deliverer
*/
private function getDeliverer()
{
return $this->application['notification.deliverer'];
}
/**
* @param Order $order
* @param User $recipient
*/
public function notifyCreation(Order $order, User $recipient)
{
$mail = MailInfoNewOrder::create($this->application, Receiver::fromUser($recipient));
$mail->setUser($order->getUser());
$this->getDeliverer()->deliver($mail);
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDelivery(OrderDelivery $delivery)
{
$order = $delivery->getOrder();
$recipient = Receiver::fromUser($order->getUser());
$sender = Emitter::fromUser($delivery->getAdmin());
$basket = $order->getBasket();
$token = $this->application['manipulator.token']->createBasketAccessToken($basket, $order->getUser());
$url = $this->application->url('lightbox_compare', [
'basket' => $basket->getId(),
'LOG' => $token->getValue(),
]);
$mail = MailInfoOrderDelivered::create($this->application, $recipient, $sender, null);
$mail->setButtonUrl($url);
$mail->setBasket($basket);
$mail->setDeliverer($delivery->getAdmin());
$this->getDeliverer()->deliver($mail);
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDenial(OrderDelivery $delivery)
{
$sender = Emitter::fromUser($delivery->getAdmin());
$recipient = Receiver::fromUser($delivery->getOrder()->getUser());
$mail = MailInfoOrderCancelled::create($this->application, $recipient, $sender);
$mail->setQuantity($delivery->getQuantity());
$mail->setDeliverer($delivery->getAdmin());
$this->getDeliverer()->deliver($mail);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order\ValidationNotifier;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Alchemy\Phrasea\Model\Manipulator\WebhookEventManipulator;
use Alchemy\Phrasea\Order\OrderDelivery;
use Alchemy\Phrasea\Order\ValidationNotifier;
class WebhookNotifier implements ValidationNotifier
{
/**
* @var callable
*/
private $webhookManipulatorLocator;
/**
* @param callable $webhookEventManipulatorLocator
*/
public function __construct($webhookEventManipulatorLocator)
{
$this->webhookManipulatorLocator = $webhookEventManipulatorLocator;
}
/**
* @return WebhookEventManipulator
*/
private function getManipulator()
{
$factory = $this->webhookManipulatorLocator;
return $factory();
}
/**
* @param Order $order
* @param User $recipient
*/
public function notifyCreation(Order $order, User $recipient)
{
$eventData = [
'order_id' => $order->getId(),
'user_id' => $recipient->getId(),
];
$this->getManipulator()->create(WebhookEvent::ORDER_CREATED, WebhookEvent::ORDER_TYPE, $eventData);
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDelivery(OrderDelivery $delivery)
{
$eventData = [
'order_id' => $delivery->getOrder()->getId(),
'admin_id' => $delivery->getAdmin()->getId(),
'quantity' => $delivery->getQuantity()
];
$this->getManipulator()->create(WebhookEvent::ORDER_DELIVERED, WebhookEvent::ORDER_TYPE, $eventData);
}
/**
* @param OrderDelivery $delivery
*/
public function notifyDenial(OrderDelivery $delivery)
{
$eventData = [
'order_id' => $delivery->getOrder()->getId(),
'admin_id' => $delivery->getAdmin()->getId(),
'quantity' => $delivery->getQuantity()
];
$this->getManipulator()->create(WebhookEvent::ORDER_DENIED, WebhookEvent::ORDER_TYPE, $eventData);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
class ValidationNotifierRegistry
{
/**
* @var ValidationNotifier[]
*/
private $notifiers = [];
/**
* @param string $notificationMethodName
* @param ValidationNotifier $notifier
*/
public function registerNotifier($notificationMethodName, ValidationNotifier $notifier)
{
$this->notifiers[$notificationMethodName] = $notifier;
}
/**
* @param string $notificationMethodName
* @return ValidationNotifier
*/
public function getNotifier($notificationMethodName)
{
if (! isset($this->notifiers[$notificationMethodName])) {
throw new InvalidArgumentException(sprintf('Undefined notifier for method: %s', $notificationMethodName));
}
return $this->notifiers[$notificationMethodName];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Alchemy\Phrasea\Setup\DoctrineMigrations;
use Alchemy\Phrasea\Model\Entities\Order;
use Doctrine\DBAL\Migrations\AbstractMigration as BaseMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160511160640 extends BaseMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql(sprintf("ALTER TABLE Orders ADD COLUMN notification_method VARCHAR(32) NOT NULL DEFAULT '%s'", Order::NOTIFY_MAIL));
$this->addSql("ALTER TABLE Orders ALTER COLUMN notification_method DROP DEFAULT");
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql("ALTER TABLE Orders DROP COLUMN notification_method");
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Alchemy\Phrasea\Setup\DoctrineMigrations;
use Doctrine\DBAL\Migrations\AbstractMigration as BaseMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160520165600 extends BaseMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE FeedEntries CHANGE subtitle subtitle VARCHAR(1024) NOT NULL');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE FeedEntries CHANGE subtitle subtitle VARCHAR(128) NOT NULL COLLATE utf8_unicode_ci');
}
}

View File

@@ -19,6 +19,7 @@ use Alchemy\Phrasea\TaskManager\Editor\DefaultEditor;
use Alchemy\Phrasea\Webhook\EventProcessorFactory;
use Guzzle\Http\Client as GuzzleClient;
use Guzzle\Batch\BatchBuilder;
use Guzzle\Http\Message\Request;
use Silex\Application;
use Guzzle\Common\Event;
use Guzzle\Plugin\Backoff\BackoffPlugin;
@@ -33,6 +34,8 @@ class WebhookJob extends AbstractJob
{
private $httpClient;
private $firstRun = true;
public function __construct(
TranslatorInterface $translator,
EventDispatcherInterface $dispatcher = null,
@@ -88,37 +91,55 @@ class WebhookJob extends AbstractJob
$thirdPartyApplications = $app['repo.api-applications']->findWithDefinedWebhookCallback();
$that = $this;
$this->httpClient->getEventDispatcher()->addListener('request.error', function (Event $event) {
// override guzzle default behavior of throwing exceptions
// when 4xx & 5xx responses are encountered
$event->stopPropagation();
}, -254);
if ($this->firstRun) {
$this->httpClient->getEventDispatcher()->addListener('request.error', function (Event $event) {
// override guzzle default behavior of throwing exceptions
// when 4xx & 5xx responses are encountered
$event->stopPropagation();
}, -254);
$this->httpClient->addSubscriber(new BackoffPlugin(
// set max retries
new TruncatedBackoffStrategy(WebhookEventDelivery::MAX_DELIVERY_TRIES,
// set callback which logs success or failure
new CallbackBackoffStrategy(function ($retries, $request, $response, $e) use ($app, $that) {
$retry = true;
if ($response && (null !== $deliverId = parse_url($request->getUrl(), PHP_URL_FRAGMENT))) {
$delivery = $app['repo.webhook-delivery']->find($deliverId);
// Set callback which logs success or failure
$subscriber = new CallbackBackoffStrategy(function ($retries, Request $request, $response, $e) use ($app, $that) {
$retry = true;
if ($response && (null !== $deliverId = parse_url($request->getUrl(), PHP_URL_FRAGMENT))) {
$delivery = $app['repo.webhook-delivery']->find($deliverId);
if ($response->isSuccessful()) {
$app['manipulator.webhook-delivery']->deliverySuccess($delivery);
$logContext = [ 'host' => $request->getHost() ];
$that->log('info', sprintf('Deliver success event "%d:%s" for app "%s"', $delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(), $delivery->getThirdPartyApplication()->getName()));
$retry = false;
} else {
$app['manipulator.webhook-delivery']->deliveryFailure($delivery);
if ($response->isSuccessful()) {
$app['manipulator.webhook-delivery']->deliverySuccess($delivery);
$that->log('error', sprintf('Deliver failure event "%d:%s" for app "%s"', $delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(), $delivery->getThirdPartyApplication()->getName()));
}
$logType = 'info';
$logEntry = sprintf('Deliver success event "%d:%s" for app "%s"',
$delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(),
$delivery->getThirdPartyApplication()->getName()
);
return $retry;
$retry = false;
} else {
$app['manipulator.webhook-delivery']->deliveryFailure($delivery);
$logType = 'error';
$logEntry = sprintf('Deliver failure event "%d:%s" for app "%s"',
$delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(),
$delivery->getThirdPartyApplication()->getName()
);
}
}, true, new CurlBackoffStrategy())
)
));
$that->log($logType, $logEntry, $logContext);
return $retry;
}
}, true, new CurlBackoffStrategy());
// set max retries
$subscriber = new TruncatedBackoffStrategy(WebhookEventDelivery::MAX_DELIVERY_TRIES, $subscriber);
$subscriber = new BackoffPlugin($subscriber);
$this->httpClient->addSubscriber($subscriber);
$this->firstRun = false;
}
/** @var EventProcessorFactory $eventFactory */
$eventFactory = $app['webhook.processor_factory'];

View File

@@ -7,6 +7,7 @@ use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Webhook\Processor\CallableProcessorFactory;
use Alchemy\Phrasea\Webhook\Processor\FeedEntryProcessorFactory;
use Alchemy\Phrasea\Webhook\Processor\OrderNotificationProcessorFactory;
use Alchemy\Phrasea\Webhook\Processor\ProcessorFactory;
use Alchemy\Phrasea\Webhook\Processor\UserRegistrationProcessorFactory;
@@ -25,6 +26,7 @@ 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));
}
/**

View File

@@ -0,0 +1,79 @@
<?php
namespace Alchemy\Phrasea\Webhook\Processor;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Alchemy\Phrasea\Model\Repositories\OrderRepository;
use Alchemy\Phrasea\Model\Repositories\UserRepository;
class OrderNotificationProcessor implements ProcessorInterface
{
/**
* @var OrderRepository
*/
private $orderRepository;
/**
* @var UserRepository
*/
private $userRepository;
public function __construct(OrderRepository $orderRepository, UserRepository $userRepository)
{
$this->orderRepository = $orderRepository;
$this->userRepository = $userRepository;
}
public function process(WebhookEvent $event)
{
if ($event->getName() == WebhookEvent::ORDER_CREATED) {
return $this->processCreateOrder($event);
}
return $this->processDeliveryOrder($event);
}
protected function processCreateOrder(WebhookEvent $event)
{
$data = $event->getData();
/** @var User $user */
$user = $this->userRepository->find($data['user_id']);
/** @var Order $order */
$order = $this->orderRepository->find($data['order_id']);
return $this->getOrderData($event, $user, $order);
}
protected function processDeliveryOrder(WebhookEvent $event)
{
$data = $event->getData();
/** @var Order $order */
$order = $this->orderRepository->find($data['order_id']);
$user = $order->getUser();
return $this->getOrderData($event, $user, $order);
}
/**
* @param WebhookEvent $event
* @param User $user
* @param Order $order
* @return array
*/
protected function getOrderData(WebhookEvent $event, User $user, Order $order)
{
return [
'event' => $event->getName(),
'user' => [
'id' => $user->getId(),
'email' => $user->getEmail(),
'login' => $user->getLogin()
],
'order' => $order->getId()
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Alchemy\Phrasea\Webhook\Processor;
use Alchemy\Phrasea\Application;
class OrderNotificationProcessorFactory implements ProcessorFactory
{
/**
* @var Application
*/
private $application;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* @return ProcessorInterface
*/
public function createProcessor()
{
return new OrderNotificationProcessor(
$this->application['repo.orders'],
$this->application['repo.users']
);
}
}

View File

@@ -147,6 +147,18 @@ class ACL implements cache_cacheableInterface
return self::$bas_rights;
}
/**
* Returns the list of available rights by databox for the current user
*
* @return array
*/
public function get_sbas_rights()
{
$this->load_rights_sbas();
return $this->_rights_sbas;
}
/**
* Check if a hd grant has been received for a record
*
@@ -580,10 +592,11 @@ class ACL implements cache_cacheableInterface
}
/**
* Check if the user has the right, at least on one collection
* Check if the user has the right, on at least one collection
*
* @param string $right
* @return boolean
* @param string $right
* @return bool
* @throws Exception
*/
public function has_right($right)
{
@@ -598,9 +611,10 @@ class ACL implements cache_cacheableInterface
/**
* Check if the user has the required right on a database
*
* @param <type> $sbas_id
* @param <type> $right
* @return <type>
* @param int $sbas_id
* @param string $right
* @return bool
* @throws Exception
*/
public function has_right_on_sbas($sbas_id, $right)
{
@@ -623,8 +637,8 @@ class ACL implements cache_cacheableInterface
/**
* Retrieve mask AND for user on specified base_id
*
* @param int $base_id
* @return string
* @param int $base_id
* @return int
*/
public function get_mask_and($base_id)
{
@@ -639,8 +653,8 @@ class ACL implements cache_cacheableInterface
/**
* Retrieve mask XOR for user on specified base_id
*
* @param int $base_id
* @return string
* @param int $base_id
* @return int
*/
public function get_mask_xor($base_id)
{

View File

@@ -118,7 +118,7 @@ class media_Permalink_Adapter implements cache_cacheableInterface
'record_id' => $this->media_subdef->get_record_id(),
'subdef' => $this->media_subdef->get_name(),
/** @Ignore */
'label' => $label,
'label' => str_replace('/', '_', $label),
'token' => $this->get_token(),
]));
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
class patch_400alpha7a implements patchInterface
{
/** @var string */
private $release = '4.0.0-alpha.7';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [
'20160511160640'
];
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function apply(base $databox, Application $app)
{
return true;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
class patch_400alpha8a implements patchInterface
{
/** @var string */
private $release = '4.0.0-alpha.8';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [
'20160520165600'
];
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function apply(base $databox, Application $app)
{
return true;
}
}

View File

@@ -68,8 +68,6 @@ class record_exportElement extends record_adapter
parent::__construct($app, $sbas_id, $record_id);
$this->get_actions($remain_hd);
return $this;
}
/**
@@ -270,8 +268,7 @@ class record_exportElement extends record_adapter
}
/**
*
* @return Array
* @return array
*/
public function get_orderable()
{
@@ -279,8 +276,7 @@ class record_exportElement extends record_adapter
}
/**
*
* @return Array
* @return array
*/
public function get_downloadable()
{

View File

@@ -99,19 +99,20 @@ class set_export extends set_abstract
} else {
$this->exportName = "Export_" . date("Y-n-d") . '_' . mt_rand(100, 999);
$tmp_lst = explode(';', $lst);
$n = 1;
foreach ($tmp_lst as $basrec) {
$basrec = explode('_', $basrec);
if (count($basrec) != 2)
continue;
$records = new \Alchemy\Phrasea\Record\RecordReferenceCollection();
foreach (explode(';', $lst) as $basrec) {
try {
$record = new record_adapter($this->app, $basrec[0], $basrec[1]);
} catch (\Exception_Record_AdapterNotFound $e) {
$records[] = \Alchemy\Phrasea\Record\RecordReference::createFromRecordReference($basrec);
} catch (Exception $exception) {
// Ignore invalid record references
continue;
}
}
foreach ($records->toRecords($app->getApplicationBox()) as $record) {
if ($record->isStory()) {
foreach ($record->getChildren() as $child_basrec) {
$base_id = $child_basrec->getBaseId();

View File

@@ -5,6 +5,7 @@ languages:
main:
maintenance: false
key: ''
api_require_ssl: true
database:
host: 'sql-host'
port: 3306

View File

@@ -5,9 +5,16 @@
- name: Create mailcatcher log directory
file: path={{ mailcatcher_log_path }} owner=mailcatcher mode=0755 state=directory
- name: Install Mailcatcher
gem: name={{ mailcatcher_gem }} user_install=no state=latest
notify: restart mailcatcher
# https://github.com/jadb/ansible-role-mailcatcher/blob/b4df99308f0e5222a4ccb7d519504f967b0ea21b/tasks/main.yml
- name: Install mime-types for Ruby1.9
# https://github.com/sj26/mailcatcher/issues/277#issuecomment-209154903
command: gem install mime-types --version "< 3"
- name: Install the mailcatcher (GEM)
# gem module is flaky, this is consistent
command: gem install mailcatcher --conservative
ignore_errors: yes
- name: Install mailcatcher supervisord conf
template: src='program_mailcatcher.conf.j2' dest='/etc/supervisor/conf.d/program_mailcatcher.conf'

View File

@@ -19,10 +19,10 @@ server {
location /api {
root {{ nginx.docroot }}/www;
rewrite ^(.*)$ /api.php/$1 last;
rewrite ^(.*)$ /api_dev.php/$1 last;
}
location ~ ^/(index|index_dev|api)\.php(/|$) {
location ~ ^/(index|index_dev|api|api_dev)\.php(/|$) {
root {{ nginx.docroot }}/www;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-05-19T12:33:00Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<file date="2016-05-24T15:19:59Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>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.</note>
@@ -10,8 +10,8 @@
<source>Please provide the same passwords.</source>
<target state="new">Please provide the same passwords.</target>
<jms:reference-file line="44">Form/Login/PhraseaRecoverPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
<jms:reference-file line="36">Form/Login/PhraseaRenewPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
</trans-unit>
<trans-unit id="90b8c9717bb7ed061dbf20fe1986c8b8593d43d4" resname="The token provided is not valid anymore">
<source>The token provided is not valid anymore</source>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-05-19T12:34:27Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<file date="2016-05-24T15:21:47Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>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.</note>
@@ -10,8 +10,8 @@
<source>Please provide the same passwords.</source>
<target state="translated">Please provide the same passwords.</target>
<jms:reference-file line="44">Form/Login/PhraseaRecoverPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
<jms:reference-file line="36">Form/Login/PhraseaRenewPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
</trans-unit>
<trans-unit id="90b8c9717bb7ed061dbf20fe1986c8b8593d43d4" resname="The token provided is not valid anymore" approved="yes">
<source>The token provided is not valid anymore</source>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-05-19T12:36:01Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<file date="2016-05-24T15:23:43Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>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.</note>
@@ -10,8 +10,8 @@
<source>Please provide the same passwords.</source>
<target state="translated">Veuillez indiquer des mots de passe identiques.</target>
<jms:reference-file line="44">Form/Login/PhraseaRecoverPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
<jms:reference-file line="36">Form/Login/PhraseaRenewPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
</trans-unit>
<trans-unit id="90b8c9717bb7ed061dbf20fe1986c8b8593d43d4" resname="The token provided is not valid anymore" approved="yes">
<source>The token provided is not valid anymore</source>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-05-19T12:37:43Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<file date="2016-05-24T15:25:46Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>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.</note>
@@ -10,8 +10,8 @@
<source>Please provide the same passwords.</source>
<target state="new">Please provide the same passwords.</target>
<jms:reference-file line="44">Form/Login/PhraseaRecoverPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
<jms:reference-file line="36">Form/Login/PhraseaRenewPasswordForm.php</jms:reference-file>
<jms:reference-file line="49">Form/Login/PhraseaRegisterForm.php</jms:reference-file>
</trans-unit>
<trans-unit id="90b8c9717bb7ed061dbf20fe1986c8b8593d43d4" resname="The token provided is not valid anymore">
<source>The token provided is not valid anymore</source>

View File

@@ -5,6 +5,7 @@ languages:
main:
maintenance: false
key: ''
api_require_ssl: true
database:
host: 'sql-host'
port: 3306

View File

@@ -77,19 +77,19 @@
settings = $.extend({}, $.tooltip.defaults, settings);
createHelper(settings);
return this.each(function () {
$.data(this, "tooltip", settings);
// copy tooltip into its own expando and remove the title
this.tooltipText = $(this).attr('title');
this.tooltipSrc = $(this).attr('tooltipsrc');
$.data(this, "tooltip", settings);
// copy tooltip into its own expando and remove the title
this.tooltipText = $(this).attr('title');
this.tooltipSrc = $(this).attr('tooltipsrc');
this.ajaxLoad = ($.trim(this.tooltipText) === '' && this.tooltipSrc !== '');
this.ajaxTimeout;
this.ajaxLoad = ($.trim(this.tooltipText) === '' && this.tooltipSrc !== '');
this.ajaxTimeout;
this.orEl = $(this);
$(this).removeAttr("title");
// also remove alt attribute to prevent default tooltip in IE
this.alt = "";
})
this.orEl = $(this);
$(this).removeAttr("title");
// also remove alt attribute to prevent default tooltip in IE
this.alt = "";
})
.mouseover(save)
.mouseout(hide)
.mouseleave(function () {
@@ -114,10 +114,10 @@
'backgroundImage': 'none',
'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
}).each(function () {
var position = $(this).css('position');
if (position != 'absolute' && position != 'relative')
$(this).css('position', 'relative');
});
var position = $(this).css('position');
if (position != 'absolute' && position != 'relative')
$(this).css('position', 'relative');
});
}
});
} : function () {
@@ -135,7 +135,7 @@
},
hideWhenEmpty: function () {
return this.each(function () {
$(this)[ $(this).html() ? "show" : "hide" ]();
$(this)[$(this).html() ? "show" : "hide"]();
});
},
url: function () {
@@ -149,7 +149,7 @@
return;
// create the helper, h3 for title, div for url
helper.parent = $('<div id="' + settings.id + '"><div class="body"></div></div>')
// add to document
// add to document
.appendTo(document.body)
// hide it at first
.hide();
@@ -197,8 +197,9 @@
event.cancelBubble = true;
if ($.tooltip.blocked || this == $.tooltip.current || (!this.tooltipText && !this.tooltipSrc && !settings(this).bodyHandler))
if ($.tooltip.blocked || this == $.tooltip.current || (!this.tooltipText && !this.tooltipSrc && !settings(this).bodyHandler)) {
return;
}
// save current
$.tooltip.current = this;
@@ -239,7 +240,7 @@
helper.body.show();
var $this = $.tooltip.current;
var tooltipSettings = settings($this) ? settings($this) : {};
var fixedPosition = tooltipSettings.fixable ? tooltipSettings.fixable : false;
var fixedPosition = $.tooltip.blocked;
// fix PNG background for IE
if (tooltipSettings.fixPNG)
helper.parent.fixPNG();
@@ -349,7 +350,7 @@
}
}
if( $selector !== undefined) {
if ($selector !== undefined) {
$selector.css({width: Math.floor(resizeW), height: Math.floor(resizeH)});
}
@@ -365,11 +366,10 @@
// since event target can have different positionning, try to get common closest parent:
var $eventTarget = $origEventTarget.closest('.diapo');
if ($eventTarget.length > 0) {
// tooltip from records answer
recordWidthOffset = 148; // remove size
recordHeightOffset = 195;
recordWidthOffset = $eventTarget.width()-2; // remove width with margin/2
recordHeightOffset = $eventTarget.height()+2; // remove height with margin/2
// change offsets:
topOffset = 14;
leftOffset = 1;
@@ -499,7 +499,6 @@
}
}
var resizeProperties = {
left: left,
top: top
@@ -532,7 +531,7 @@
function show() {
tID = null;
var isBrowsable = false;
if( $.tooltip.current !== null ) {
if ($.tooltip.current !== null) {
isBrowsable = settings($.tooltip.current).isBrowsable;
}
@@ -548,7 +547,7 @@
$(helper.parent[0])
.unbind('mouseenter')
.unbind('mouseleave')
.mouseenter(function(){
.mouseenter(function () {
if (isBrowsable) {
$.tooltip.currentHover = true;
}
@@ -575,6 +574,7 @@
commonModule.showOverlay('_tooltip', 'body', unfix_tooltip, settings(this).fixableIndex);
$('#tooltip .tooltip_closer').show().bind('click', unfix_tooltip);
$.tooltip.blocked = true;
positioning.apply(this, arguments);
}
function visible() {
@@ -613,7 +613,6 @@
// remove position helper classes
helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
if (!settings($.tooltip.current).outside) {
var left = helper.parent[0].offsetLeft;
var top = helper.parent[0].offsetTop;
@@ -667,10 +666,10 @@
// hide helper and restore added classes and the title
function hide(event) {
var isBrowsable = false;
if( $.tooltip.current !== null ) {
if ($.tooltip.current !== null) {
isBrowsable = settings($.tooltip.current).isBrowsable;
}
if( $.tooltip.currentHover && isBrowsable ) {
if ($.tooltip.currentHover && isBrowsable) {
return;
}
@@ -723,7 +722,7 @@ function unfix_tooltip() {
$(document).bind('keydown', function (event) {
if( $.tooltip === undefined ) return;
if ($.tooltip === undefined) return;
if (event.keyCode == 27 && $.tooltip.blocked === true) {
unfix_tooltip();

View File

@@ -30,6 +30,7 @@
<ul class="unstyled">
<li>{{ 'phraseanet:: adresse' | trans }} : {{ collection.get_databox().get_serialized_server_info() }}</li>
<li>{{ 'admin::base:collection: numero de collection distante' | trans }} : {{ collection.get_coll_id() }}</li>
<li>{{ 'admin::base:collection: numero de collection locale' | trans }} : {{ collection.get_base_id() }}</li>
<li>{{ 'admin::base:collection: etat de la collection' | trans }} : {{ collection.is_active() ? "admin::base:collection: activer la collection"| trans : "admin::base:collection: descativer la collection"|trans }}</li>
<li>{{ collection.get_record_amount() }} records <a class="ajax" target="rights" href="{{ path('admin_collection_display_document_details', { 'bas_id' : collection.get_base_id() }) }}">{{ 'phraseanet:: details' | trans }}</a></li>
</ul>

View File

@@ -62,11 +62,11 @@
<div class='well-small'>
<ul class="pager">
{% if previousPage %}
<li class="previous"><a class='self-ajax' href="{{ path('prod_orders', {'page': previousPage}) }}"><i class="icon-arrow-left"></i>{{ 'Previous' | trans }}</a></li>
<li class="previous"><a class="self-ajax btn btn-inverse" href="{{ path('prod_orders', {'page': previousPage, 'per-page': perPage}) }}">{{ 'Previous' | trans }}&nbsp;<i class="icon-arrow-left"></i></a></li>
{% endif %}
{% if nextPage %}
<li class="next"><a class='self-ajax' href="{{ path('prod_orders', {'page': nextPage}) }}"><i class="icon-arrow-right"></i>{{ 'Next' | trans }}</a></li>
<li class="next"><a class="self-ajax btn btn-inverse" href="{{ path('prod_orders', {'page': nextPage, 'per-page': perPage}) }}"><i class="icon-arrow-right"></i>&nbsp;{{ 'Next' | trans }}</a></li>
{% endif %}
</ul>
</div>

View File

@@ -56,8 +56,12 @@
<table class="bottom actions" style="width:100%; table-layout:fixed;">
<tr>
<td style="text-align:left;text-overflow:ellipsis;overflow:hidden;">
{% set collectionLogo = collection_logo(record.baseId) %}
{% if collectionLogo is empty %}
{{ record.collectionName }}
{{ collection_logo(record.baseId) }}
{% else %}
{{ collectionLogo|raw }}
{% endif %}
</td>
{% set l_width = 30 %}

View File

@@ -20,21 +20,18 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
{
$app = $this->getApplication();
$app['notification.deliverer'] = $this->getMockBuilder(Deliverer::class)
->disableOriginalConstructor()
->getMock();
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) {
$app['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function () use (&$triggered) {
$triggered = true;
});
$client = $this->getClient();
$client->request('POST', '/prod/order/', [
$response = $this->request('POST', '/prod/order/', [
'lst' => $this->getRecord1()->getId(),
'deadline' => '+10 minutes'
]);
$this->assertTrue($client->getResponse()->isRedirect(), 'Response should be redirect');
$url = parse_url($client->getResponse()->headers->get('location'));
$this->assertTrue($response->isRedirect(), 'Response should be redirect');
$url = parse_url($response->headers->get('location'));
$var = [];
parse_str($url['query'], $var);
$this->assertTrue(!!$var['success'], 'Response should have a success parameter');
@@ -45,9 +42,6 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
{
$app = $this->getApplication();
$app['notification.deliverer'] = $this->getMockBuilder(Deliverer::class)
->disableOriginalConstructor()
->getMock();
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) {
$triggered = true;
@@ -90,37 +84,53 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
public function testSendOrder()
{
$app = $this->getApplication();
$order = $this->createOneOrder('I need this pictures');
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoOrderDelivered');
$this->mockUserNotificationSettings('eventsmanager_notify_orderdeliver');
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_DELIVER, function (Event $event) use (&$triggered) {
$triggered = true;
});
$parameters = [];
foreach ($order->getElements() as $element) {
$parameters[] = $element->getId();
}
$response = $this->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]);
$this->assertTrue($triggered, 'Order delivered listener not triggered');
$this->assertTrue($response->isRedirect(), 'Could not validate some elements. not a redirect');
$url = parse_url($response->headers->get('location'));
parse_str($url['query']);
$this->assertTrue(strpos($url['query'], 'success=1') === 0, 'Validation of elements is not successful');
}
public function testSendOrderJson()
{
$app = $this->getApplication();
$order = $this->createOneOrder('I need this pictures');
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoOrderDelivered');
$this->mockUserNotificationSettings('eventsmanager_notify_orderdeliver');
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_DELIVER, function (Event $event) use (&$triggered) {
$triggered = true;
});
$parameters = [];
foreach ($order->getElements() as $element) {
$parameters[] = $element->getId();
}
$response = $this->XMLHTTPRequest('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]);
$this->assertTrue($response->isOk());
$this->assertEquals('application/json', $response->headers->get('Content-Type'));
$this->assertTrue($triggered, 'Order delivered listener not triggered');
$content = json_decode($response->getContent());
$this->assertTrue(is_object($content));
$this->assertObjectHasAttribute('success', $content, $response->getContent());
$this->assertTrue( ! ! $content->success, $response->getContent());
@@ -130,10 +140,13 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
public function testDenyOrder()
{
$app = $this->getApplication();
$order = $this->createOneOrder('I need this pictures');
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoOrderCancelled');
$this->mockUserNotificationSettings('eventsmanager_notify_ordernotdelivered');
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_DENY, function (Event $event) use (&$triggered) {
$triggered = true;
});
$parameters = [];
foreach ($order->getElements() as $element) {
@@ -141,7 +154,10 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
}
$client = $this->getClient();
$client->request('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]);
$this->assertTrue($client->getResponse()->isRedirect());
$this->assertTrue($triggered, 'Order denied listener not triggered');
$url = parse_url($client->getResponse()->headers->get('location'));
$var = [];
parse_str($url['query'], $var);
@@ -150,19 +166,27 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
public function testDenyOrderJson()
{
$app = $this->getApplication();
$order = $this->createOneOrder('I need this pictures');
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoOrderCancelled');
$this->mockUserNotificationSettings('eventsmanager_notify_ordernotdelivered');
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_DENY, function (Event $event) use (&$triggered) {
$triggered = true;
});
$parameters = [];
foreach ($order->getElements() as $element) {
$parameters[] = $element->getId();
}
$response = $this->XMLHTTPRequest('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]);
$this->assertTrue($response->isOk());
$this->assertEquals('application/json', $response->headers->get('Content-Type'));
$this->assertTrue($triggered, 'Order denied listener not triggered');
$content = json_decode($response->getContent());
$this->assertTrue(is_object($content));
$this->assertObjectHasAttribute('success', $content, $response->getContent());
$this->assertTrue( ! ! $content->success, $response->getContent());
@@ -172,10 +196,13 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
public function testTodo()
{
$app = $this->getApplication();
$order = $this->createOneOrder('I need this pictures');
$this->mockNotificationDeliverer('Alchemy\Phrasea\Notification\Mail\MailInfoOrderDelivered');
$this->mockUserNotificationSettings('eventsmanager_notify_orderdeliver');
$triggered = false;
$app['dispatcher']->addListener(PhraseaEvents::ORDER_DELIVER, function (Event $event) use (&$triggered) {
$triggered = true;
});
$parameters = [];
foreach ($order->getElements() as $element) {
@@ -183,8 +210,10 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase
}
$this->getClient()->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]);
$app = $this->getApplication();
$this->assertTrue($triggered, 'Order delivered listener not triggered');
$testOrder = $app['orm.em']->getRepository('Phraseanet:Order')->find($order->getId());
$this->assertEquals(0, $testOrder->getTodo());
}

View File

@@ -2231,4 +2231,19 @@ class LoginTest extends \PhraseanetAuthenticatedWebTestCase
{
return self::$DI['user'];
}
private function mockNotificationsDeliverer(array &$expectedMails)
{
$app = $this->getApplication();
$app['notification.deliverer'] = $this->getMockBuilder('Alchemy\Phrasea\Notification\Deliverer')
->disableOriginalConstructor()
->getMock();
$app['notification.deliverer']->expects($this->any())
->method('deliver')
->will($this->returnCallback(function ($email, $receipt) use (&$expectedMails) {
$this->assertTrue(isset($expectedMails[get_class($email)]));
$expectedMails[get_class($email)]++;
}));
}
}

View File

@@ -421,9 +421,7 @@ abstract class PhraseanetTestCase extends WebTestCase
$app['notification.deliverer']->expects($this->any())
->method('deliver')
->will($this->returnCallback(function () {
$this->fail('Notification deliverer must be mocked');
}));
->willReturn(0);
}
public function tearDown()

24
www/api_dev.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
use Symfony\Component\Debug\ErrorHandler;
require_once __DIR__ . '/../lib/autoload.php';
error_reporting(0);
ErrorHandler::register();
$environment = getenv('APP_ENV') ?: Application::ENV_DEV;
$app = require __DIR__ . '/../lib/Alchemy/Phrasea/Application/Api.php';
$app->run();