diff --git a/.travis.yml b/.travis.yml index 81d43f6378..6f2bbbe390 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,4 +68,5 @@ script: #sqlite db generation should occur once Phraseanet is up to date : - bin/developer phraseanet:regenerate-sqlite - ./node_modules/.bin/grunt test +#ls -d tests/Alchemy/Tests/Phrasea/*/ | parallel --gnu 'bin/phpunit {};' - bin/phpunit diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1.php b/lib/Alchemy/Phrasea/Controller/Api/V1.php index fb522e6998..6908ee6566 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1.php @@ -78,21 +78,21 @@ class V1 implements ControllerProviderInterface ->before([$this, 'ensureAdmin']); $controllers->get('/monitor/task/{task}/', 'controller.api.v1:get_task') - ->convert('task', [$app['converter.task'], 'convert']) + ->convert('task', $app['converter.task-callback']) ->before([$this, 'ensureAdmin']) ->assert('task', '\d+'); $controllers->post('/monitor/task/{task}/', 'controller.api.v1:set_task_property') - ->convert('task', [$app['converter.task'], 'convert']) + ->convert('task', $app['converter.task-callback']) ->before([$this, 'ensureAdmin']) ->assert('task', '\d+'); $controllers->post('/monitor/task/{task}/start/', 'controller.api.v1:start_task') - ->convert('task', [$app['converter.task'], 'convert']) + ->convert('task', $app['converter.task-callback']) ->before([$this, 'ensureAdmin']); $controllers->post('/monitor/task/{task}/stop/', 'controller.api.v1:stop_task') - ->convert('task', [$app['converter.task'], 'convert']) + ->convert('task', $app['converter.task-callback']) ->before([$this, 'ensureAdmin']); $controllers->get('/monitor/phraseanet/', 'controller.api.v1:get_phraseanet_monitor') diff --git a/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php index 213d97242c..100d608696 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Model\Converter\ApiApplicationConverter; use Alchemy\Phrasea\Model\Converter\BasketConverter; use Alchemy\Phrasea\Model\Converter\TaskConverter; use Alchemy\Phrasea\Model\Converter\TokenConverter; +use Alchemy\Phrasea\Model\Entities\Task; use Silex\Application; use Silex\ServiceProviderInterface; @@ -23,11 +24,15 @@ class ConvertersServiceProvider implements ServiceProviderInterface public function register(Application $app) { $app['converter.task'] = $app->share(function ($app) { - return new TaskConverter($app['EM']); + return new TaskConverter($app['repo.tasks']); + }); + + $app['converter.task-callback'] = $app->protect(function($id) use ($app) { + return $app['converter.task']->convert($id); }); $app['converter.basket'] = $app->share(function ($app) { - return new BasketConverter($app['EM']); + return new BasketConverter($app['repo.baskets']); }); $app['converter.token'] = $app->share(function ($app) { diff --git a/lib/Alchemy/Phrasea/Model/Converter/BasketConverter.php b/lib/Alchemy/Phrasea/Model/Converter/BasketConverter.php index da37ed27c2..d9ef7abfdb 100644 --- a/lib/Alchemy/Phrasea/Model/Converter/BasketConverter.php +++ b/lib/Alchemy/Phrasea/Model/Converter/BasketConverter.php @@ -12,16 +12,17 @@ namespace Alchemy\Phrasea\Model\Converter; use Alchemy\Phrasea\Model\Entities\Basket; +use Alchemy\Phrasea\Model\Repositories\BasketRepository; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class BasketConverter implements ConverterInterface { - private $om; + private $repository; - public function __construct(ObjectManager $om) + public function __construct(BasketRepository $repository) { - $this->om = $om; + $this->repository = $repository; } /** @@ -31,7 +32,7 @@ class BasketConverter implements ConverterInterface */ public function convert($id) { - if (null === $basket = $this->om->find('Phraseanet:Basket', (int) $id)) { + if (null === $basket = $this->repository->find((int) $id)) { throw new NotFoundHttpException(sprintf('Basket %s not found.', $id)); } diff --git a/lib/Alchemy/Phrasea/Model/Converter/TaskConverter.php b/lib/Alchemy/Phrasea/Model/Converter/TaskConverter.php index 87078cef55..e5174994a0 100644 --- a/lib/Alchemy/Phrasea/Model/Converter/TaskConverter.php +++ b/lib/Alchemy/Phrasea/Model/Converter/TaskConverter.php @@ -12,16 +12,17 @@ namespace Alchemy\Phrasea\Model\Converter; use Alchemy\Phrasea\Model\Entities\Task; +use Alchemy\Phrasea\Model\Repositories\TaskRepository; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class TaskConverter implements ConverterInterface { - private $om; + private $repository; - public function __construct(ObjectManager $om) + public function __construct(TaskRepository $repository) { - $this->om = $om; + $this->repository = $repository; } /** @@ -31,7 +32,7 @@ class TaskConverter implements ConverterInterface */ public function convert($id) { - if (null === $task = $this->om->find('Phraseanet:Task', (int) $id)) { + if (null === $task = $this->repository->find((int) $id)) { throw new NotFoundHttpException(sprintf('Task %s not found.', $id)); } diff --git a/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php new file mode 100644 index 0000000000..07ad79c3be --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/EventProcessorFactory.php @@ -0,0 +1,28 @@ +app = $app; + } + + public function get(WebhookEvent $event) + { + switch ($event->getType()) { + case WebhookEvent::FEED_ENTRY_TYPE: + return new FeedEntryProcessor($event, $this->app); + break; + default: + throw new \RuntimeException(sprintf('No processor found for %s', $event->getType())); + } + } +} diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/AbstractProcessor.php b/lib/Alchemy/Phrasea/Webhook/Processor/AbstractProcessor.php new file mode 100644 index 0000000000..05ab3de790 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/AbstractProcessor.php @@ -0,0 +1,18 @@ +event = $event; + $this->app = $app; + } +} diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php new file mode 100644 index 0000000000..35ea6c9b14 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/FeedEntryProcessor.php @@ -0,0 +1,72 @@ +event->getData(); + + if (!isset($data->{"entry_id"})) { + return null; + } + + $entry = $this->app['EM']->getRepository('Phraseanet::Entry')->find($data->{"entry_id"}); + + if (null === $entry) { + return null; + } + + $data = $this->event->getData(); + + $feed = $entry->getFeed(); + + $query = new \User_Query($this->app); + + $query->include_phantoms(true) + ->include_invite(false) + ->include_templates(false) + ->email_not_null(true); + + if ($feed->getCollection($this->app)) { + $query->on_base_ids(array($feed->getCollection($this->app)->get_base_id())); + } + + $start = 0; + $perLoop = 100; + $users = array(); + + do { + $results = $query->limit($start, $perLoop)->execute()->get_results(); + foreach ($results as $user) { + $users[] = array( + 'email' => $user->getEmail(), + 'firstname' => $user->getFirstname() ?: null, + 'lastname' => $user->getLastname() ?: null, + ); + } + $start += $perLoop; + } while (count($results) > 0); + + return array( + 'event' => $this->event->getName(), + 'users_were_notified' => isset($data->{'notify_email'}) ?: !!$data->{"notify_email"}, + 'feed' => array( + 'id' => $feed->getId(), + 'title' => $feed->getTitle(), + 'description' => $feed->getSubtitle(), + ), + 'entry' => array( + 'id' => $entry->getId(), + 'author' => array( + 'name' => $entry->getAuthorName(), + 'email' => $entry->getAuthorEmail() + ), + 'title' => $entry->getTitle(), + 'description' => $entry->getSubtitle(), + ), + 'users' => $users + ); + } +} \ No newline at end of file diff --git a/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorInterface.php b/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorInterface.php new file mode 100644 index 0000000000..4d354e04c1 --- /dev/null +++ b/lib/Alchemy/Phrasea/Webhook/Processor/ProcessorInterface.php @@ -0,0 +1,8 @@ +user->getId(); foreach ($sbas_ids as $sbas_id) { - if (!$this->has_access_to_sbas($sbas_id)) - $stmt_ins->execute([':sbas_id' => $sbas_id, ':usr_id' => $usr_id]); + if (!$this->has_access_to_sbas($sbas_id)) { + try { + $stmt_ins->execute([':sbas_id' => $sbas_id, ':usr_id' => $usr_id]); + } catch (DBALException $e) { + + } + } } $stmt_ins->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); @@ -1589,7 +1594,7 @@ class ACL implements cache_cacheableInterface */ public function get_order_master_collections() { - $sql = 'SELECT base_id FROM basusr WHERE order_master="1" AND usr_id= :usr_id '; + $sql = 'SELECT base_id FROM basusr WHERE order_master="1" AND usr_id= :usr_id'; $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index a888ed044f..a4549bd2b2 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -31,14 +31,15 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase parent::tearDown(); } + public function getApplicationPath() + { + return '/lib/Alchemy/Phrasea/Application/Api.php'; + } + public function setUp() { parent::setUp(); - self::$DI['app'] = self::$DI->share(function ($DI) { - return $this->loadApp('lib/Alchemy/Phrasea/Application/Api.php'); - }); - if (null === $this->adminAccessToken) { $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens(self::$DI['oauth2-app-acc-user']); if (count($tokens) === 0) { diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Prod/PropertyTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Prod/PropertyTest.php index 5de360ac91..d20d4e46c4 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Prod/PropertyTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Prod/PropertyTest.php @@ -61,6 +61,32 @@ class PropertyTest extends \PhraseanetAuthenticatedWebTestCase $story = \record_adapter::createStory(self::$DI['app'], self::$DI['collection']); $story->appendChild($record2); + + $acl = $this->getMockBuilder('ACL') + ->disableOriginalConstructor() + ->getMock(); + $acl->expects($this->any()) + ->method('has_access_to_record') + ->with($this->isInstanceOf('\record_adapter')) + ->will($this->returnValue(true)); + $acl->expects($this->any()) + ->method('has_right_on_base') + ->with($this->isType(\PHPUnit_Framework_Constraint_IsType::TYPE_INT), $this->equalTo('chgstatus')) + ->will($this->returnValue(true)); + $acl->expects($this->any()) + ->method('has_right_on_sbas') + ->with($this->isType(\PHPUnit_Framework_Constraint_IsType::TYPE_INT), $this->equalTo('chgstatus')) + ->will($this->returnValue(true)); + + $aclProvider = $this->getMockBuilder('Alchemy\Phrasea\Authentication\ACLProvider') + ->disableOriginalConstructor() + ->getMock(); + $aclProvider->expects($this->any()) + ->method('get') + ->will($this->returnValue($acl)); + + self::$DI['app']['acl'] = $aclProvider; + self::$DI['client']->request('POST', '/prod/records/property/status/', [ 'apply_to_children' => [$story->get_sbas_id() => true], 'status' => [ diff --git a/tests/Alchemy/Tests/Phrasea/Model/Converter/BasketConverterTest.php b/tests/Alchemy/Tests/Phrasea/Model/Converter/BasketConverterTest.php index 2a1c6e5c40..501ddb6a9e 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Converter/BasketConverterTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Converter/BasketConverterTest.php @@ -10,7 +10,7 @@ class BasketConverterTest extends \PhraseanetTestCase { $basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); - $converter = new BasketConverter(self::$DI['app']['EM']); + $converter = new BasketConverter(self::$DI['app']['EM']->getRepository('Phraseanet:Basket')); $this->assertSame($basket, $converter->convert($basket->getId())); } @@ -20,7 +20,7 @@ class BasketConverterTest extends \PhraseanetTestCase */ public function testConvertFailure() { - $converter = new BasketConverter(self::$DI['app']['EM']); + $converter = new BasketConverter(self::$DI['app']['EM']->getRepository('Phraseanet:Basket')); $converter->convert('prout'); } } diff --git a/tests/Alchemy/Tests/Phrasea/Model/Converter/TaskConverterTest.php b/tests/Alchemy/Tests/Phrasea/Model/Converter/TaskConverterTest.php index e8f168b4ba..72a40affa0 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Converter/TaskConverterTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Converter/TaskConverterTest.php @@ -10,7 +10,7 @@ class TaskConverterTest extends \PhraseanetTestCase { $task = self::$DI['app']['EM']->find('Phraseanet:Task', 1); - $converter = new TaskConverter(self::$DI['app']['EM']); + $converter = new TaskConverter(self::$DI['app']['EM']->getRepository('Phraseanet:Task')); $this->assertSame($task, $converter->convert(1)); } @@ -20,7 +20,7 @@ class TaskConverterTest extends \PhraseanetTestCase */ public function testConvertFailure() { - $converter = new TaskConverter(self::$DI['app']['EM']); + $converter = new TaskConverter(self::$DI['app']['EM']->getRepository('Phraseanet:Task')); $converter->convert('prout'); } } diff --git a/tests/classes/PhraseanetTestCase.php b/tests/classes/PhraseanetTestCase.php index 4c7bc40e51..2447f36c8e 100644 --- a/tests/classes/PhraseanetTestCase.php +++ b/tests/classes/PhraseanetTestCase.php @@ -46,7 +46,7 @@ abstract class PhraseanetTestCase extends WebTestCase private static $fixtureIds = []; - private function initializeSqliteDB($path = '/tmp/db.sqlite') + protected function initializeSqliteDB($path = '/tmp/db.sqlite') { $path = $path . getmypid(); @@ -56,6 +56,11 @@ abstract class PhraseanetTestCase extends WebTestCase copy(__DIR__ . '/../db-ref.sqlite', $path); } + public function getApplicationPath() + { + return '/lib/Alchemy/Phrasea/Application/Root.php'; + } + public function createApplication() { @@ -87,7 +92,7 @@ abstract class PhraseanetTestCase extends WebTestCase \PHPUnit_Framework_Error_Notice::$enabled = true; self::$DI['app'] = self::$DI->share(function ($DI) { - return $this->loadApp('/lib/Alchemy/Phrasea/Application/Root.php'); + return $this->loadApp($this->getApplicationPath()); }); self::$DI['cli'] = self::$DI->share(function ($DI) {