From b67c37c340c32cb1a0a44909ebfa4ddba3086e2f Mon Sep 17 00:00:00 2001 From: jygaulier Date: Tue, 2 Jun 2020 20:26:30 +0200 Subject: [PATCH 01/11] WIP --- .../Phrasea/Controller/Api/V3Controller.php | 278 ++++++++++++++++++ .../Phrasea/ControllerProvider/Api/V3.php | 10 +- 2 files changed, 287 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php index 4dcab291fe..69ad88995e 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\Controller\Api; +use Alchemy\Phrasea\Application\Helper\JsonBodyAware; use Alchemy\Phrasea\Collection\Reference\CollectionReference; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Databox\DataboxGroupable; @@ -36,8 +37,285 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use databox_field; + + class V3Controller extends Controller { + use JsonBodyAware; + + /** + * Return detailed information about one story + * + * @param Request $request + * @param int $databox_id + * @param int $record_id + * + * @return Response + */ + public function setmetadatasAction(Request $request, $databox_id, $record_id) + { + $struct = $this->findDataboxById($databox_id)->get_meta_structure(); + $record = $this->findDataboxById($databox_id)->get_record($record_id); + + //$record->set_metadatas() + + $structByKey = []; + $nameToStrucId = []; + foreach($struct as $f) { + $nameToStrucId[$f->get_name()] = $f->get_id(); + $structByKey[$f->get_id()] = $f; + $structByKey[$f->get_name()] = &$structByKey[$f->get_id()]; + } + + try { + $b = $this->decodeJsonBody($request); + } + catch(\Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction($request, 'Bad JSON'); + } + + $metadatas_ops = []; + foreach ($b->metadatas as $_m) { + // sanity + if($_m->meta_struct_id && $_m->field_name) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + "define meta_struct_id OR field_name, not both." + ); + } + // select fields that match meta_struct_id or field_name (can be arrays) + $fields_list = null; // to filter caption_fields from record, default all + $struct_fields = []; // struct fields that match meta_struct_id or field_name + if(($field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name) !== null) { // can be null if none defined (=match all) + if (!is_array($field_keys)) { + $field_keys = [$field_keys]; + } + $fields_list = []; + foreach ($field_keys as $k) { + if(array_key_exists($k, $structByKey)) { + $fields_list[] = $structByKey[$k]->get_name(); + $struct_fields[$structByKey[$k]->get_id()] = $structByKey[$k]; + } + } + } + $caption_fields = $record->get_caption()->get_fields($fields_list, true); + + $meta_id = is_null($_m->meta_id) ? null : (int)($_m->meta_id); + + if(!($match_method = (string)($_m->match_method))) { + $match_method = 'ignore_case'; + } + if(!in_array($match_method, ['strict', 'ignore_case', 'regexp'])) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + sprintf("bad match_method (%s).", $match_method) + ); + } + + $values = []; + if(is_array($_m->value)) { + foreach ($_m->value as $v) { + $values[] = is_null($v) ? null : (string)$v; + } + } + else { + $values = is_null($_m->value) ? [] : [(string)($_m->value)]; + } + + if(!($action = (string)($_m->action))) { + $action = 'set'; + } + switch($_m->action) { + case 'set': + $metadatas_ops = array_merge( + $metadatas_ops, + $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) + ); + break; + case 'add': + $metadatas_ops = array_merge( + $metadatas_ops, + $this->setmetadatasAction_add($struct_fields, $values) + ); + break; + case 'delete': + $metadatas_ops = array_merge( + $metadatas_ops, + $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null) + ); + break; + case 'replace': + if(!is_string($_m->replace_with) && !is_null($_m->replace_with)) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + "bad \"replace_with\" for action \"replace\"." + ); + } + $metadatas_ops = array_merge( + $metadatas_ops, + $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with) + ); + break; + default: + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + sprintf("bad action (%s).", $action) + ); + } + } + + return Result::create($request, $metadatas_ops)->createResponse(); + } + + private function match($pattern, $method, $value) + { + switch ($method) { + case 'strict': + return $value === $pattern; + case 'ignore_case': + return strtolower($value) === strtolower($pattern); + case 'regexp': + return preg_match($pattern, $value) == 1; + } + } + + /** + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param int|null $meta_id + * @param string[] $values + * + * @return array ops to execute + */ + private function setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) + { + $ops = []; + + // if one field was multi-valued and no meta_id was set, we must delete all values + foreach ($caption_fields as $cf) { + if ($cf->is_multi() && is_null($meta_id)) { + foreach ($cf->get_values() as $field_value) { + $a[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => null + ]; + } + } + } + // now set values to matching struct_fields + foreach ($struct_fields as $sf) { + if($sf->is_multi()) { + // add the non-null value(s) + foreach ($values as $value) { + if (!is_null($value)) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // can be null + 'value' => $value + ]; + } + } + } + else { + // mono-valued + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // probably null, + 'value' => $values[0] + ]; + } + } + + return $ops; + } + + /** + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param string[] $values + * + * @return array ops to execute + */ + private function setmetadatasAction_add($struct_fields, $values) + { + $ops = []; + + // now set values to matching struct_fields + foreach ($struct_fields as $sf) { + if(!$sf->is_multi()) { + // todo : return error "cant add to mono-valued" + continue; + } + // add the non-null value(s) + foreach ($values as $value) { + if (!is_null($value)) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => null, + 'value' => $value + ]; + } + } + } + + return $ops; + } + + /** + * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param int|null $meta_id + * @param string $match_method "strict" | "ignore_case" | "regexp" + * @param string[] $values + * @param string|null $replace_with + * + * @return array ops to execute + */ + private function setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) + { + $ops = []; + + foreach ($caption_fields as $cf) { + // match all ? + if(is_null($meta_id) && count($values) == 0) { + foreach ($cf->get_values() as $field_value) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $replace_with + ]; + } + } + // match by meta-id ? + if (!is_null($meta_id)) { + foreach ($cf->get_values() as $field_value) { + if ($field_value->getId() === $meta_id) { + $a[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $replace_with + ]; + } + } + } + // match by value(s) ? + foreach ($values as $value) { + foreach ($cf->get_values() as $field_value) { + if ($this->match($value, $match_method, $field_value->getValue())) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $match_method=='regexp' ? preg_replace($value, $replace_with, $field_value->getValue()): $replace_with + ]; + } + } + } + } + + return $ops; + } + + /** * Return detailed information about one story * diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php index 5925c74921..f59696ddbe 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php @@ -17,7 +17,8 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte public function register(Application $app) { $app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) { - return (new V3Controller($app)); + return (new V3Controller($app)) + ->setJsonBodyHelper($app['json.body_helper']); }); } @@ -43,6 +44,13 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte $controllers->match('/search/', 'controller.api.v3:searchAction'); + $controllers->patch('/records/{databox_id}/{record_id}/setmetadatas/', 'controller.api.v3:setmetadatasAction') + ->before('controller.api.v1:ensureCanAccessToRecord') + ->before('controller.api.v1:ensureCanModifyRecord') + ->assert('databox_id', '\d+') + ->assert('record_id', '\d+'); + $controllers->match('/records/{any_id}/{anyother_id}/setmetadatas/', 'controller.api.v1:getBadRequestAction'); + return $controllers; } } From aeb7b4e60b7269e3af06274cd9364e8869c84b47 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Wed, 3 Jun 2020 12:48:13 +0200 Subject: [PATCH 02/11] WIP fix ; add errors catch todo : - status-bits - apply changes to record - json output --- .../Phrasea/Controller/Api/V3Controller.php | 89 ++++++++++--------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php index 69ad88995e..686328bb3c 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php @@ -126,43 +126,36 @@ class V3Controller extends Controller if(!($action = (string)($_m->action))) { $action = 'set'; } - switch($_m->action) { - case 'set': - $metadatas_ops = array_merge( - $metadatas_ops, - $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) - ); - break; - case 'add': - $metadatas_ops = array_merge( - $metadatas_ops, - $this->setmetadatasAction_add($struct_fields, $values) - ); - break; - case 'delete': - $metadatas_ops = array_merge( - $metadatas_ops, - $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null) - ); - break; - case 'replace': - if(!is_string($_m->replace_with) && !is_null($_m->replace_with)) { - return $this->app['controller.api.v1']->getBadRequestAction( - $request, - "bad \"replace_with\" for action \"replace\"." - ); - } - $metadatas_ops = array_merge( - $metadatas_ops, - $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with) - ); - break; - default: - return $this->app['controller.api.v1']->getBadRequestAction( - $request, - sprintf("bad action (%s).", $action) - ); + + try { + switch ($_m->action) { + case 'set': + $ops = $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values); + break; + case 'add': + $ops = $this->setmetadatasAction_add($struct_fields, $values); + break; + case 'delete': + $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null); + break; + case 'replace': + if (!is_string($_m->replace_with) && !is_null($_m->replace_with)) { + throw new \Exception("bad \"replace_with\" for action \"replace\"."); + } + $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with); + break; + default: + throw new \Exception(sprintf("bad action (%s).", $action)); + } } + catch (\Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + $e->getMessage() + ); + } + + $metadatas_ops = array_merge($metadatas_ops, $ops); } return Result::create($request, $metadatas_ops)->createResponse(); @@ -181,12 +174,13 @@ class V3Controller extends Controller } /** - * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name - * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) * @param int|null $meta_id * @param string[] $values * * @return array ops to execute + * @throws \Exception */ private function setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) { @@ -196,7 +190,7 @@ class V3Controller extends Controller foreach ($caption_fields as $cf) { if ($cf->is_multi() && is_null($meta_id)) { foreach ($cf->get_values() as $field_value) { - $a[] = [ + $ops[] = [ 'meta_struct_id' => $cf->get_meta_struct_id(), 'meta_id' => $field_value->getId(), 'value' => null @@ -220,6 +214,9 @@ class V3Controller extends Controller } else { // mono-valued + if(count($values) > 1) { + throw new \Exception(sprintf("setting mono-valued (%s) requires only one value.", $sf->get_name())); + } $ops[] = [ 'meta_struct_id' => $sf->get_id(), 'meta_id' => $meta_id, // probably null, @@ -232,10 +229,11 @@ class V3Controller extends Controller } /** - * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name * @param string[] $values * * @return array ops to execute + * @throws \Exception */ private function setmetadatasAction_add($struct_fields, $values) { @@ -244,8 +242,7 @@ class V3Controller extends Controller // now set values to matching struct_fields foreach ($struct_fields as $sf) { if(!$sf->is_multi()) { - // todo : return error "cant add to mono-valued" - continue; + throw new \Exception(sprintf("can't \"add\" to mono-valued (%s).", $sf->get_name())); } // add the non-null value(s) foreach ($values as $value) { @@ -290,7 +287,7 @@ class V3Controller extends Controller if (!is_null($meta_id)) { foreach ($cf->get_values() as $field_value) { if ($field_value->getId() === $meta_id) { - $a[] = [ + $ops[] = [ 'meta_struct_id' => $cf->get_meta_struct_id(), 'meta_id' => $field_value->getId(), 'value' => $replace_with @@ -301,11 +298,15 @@ class V3Controller extends Controller // match by value(s) ? foreach ($values as $value) { foreach ($cf->get_values() as $field_value) { + $rw = $replace_with; + if($match_method=='regexp' && !is_null($replace_with)) { + $rw = preg_replace($value, $replace_with, $field_value->getValue()); + } if ($this->match($value, $match_method, $field_value->getValue())) { $ops[] = [ 'meta_struct_id' => $cf->get_meta_struct_id(), 'meta_id' => $field_value->getId(), - 'value' => $match_method=='regexp' ? preg_replace($value, $replace_with, $field_value->getValue()): $replace_with + 'value' => $rw ]; } } From 1057cb0493d4bb2783c48771c25aed44a1f3726d Mon Sep 17 00:00:00 2001 From: aynsix Date: Wed, 3 Jun 2020 17:52:21 +0300 Subject: [PATCH 03/11] fix issuee whe trying to rebuild subdef from tool --- .../Subscriber/RecordSubscriber.php | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php index c0df49717d..429aebe46c 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php @@ -54,18 +54,20 @@ class RecordSubscriber implements EventSubscriberInterface if (!$record->isStory()) { $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType()); - foreach ($subdefs as $subdef) { - $payload = [ - 'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE, - 'payload' => [ - 'recordId' => $event->getRecord()->getRecordId(), - 'databoxId' => $event->getRecord()->getDataboxId(), - 'subdefName' => $subdef->get_name(), - 'status' => $event->isNewRecord() ? MessagePublisher::NEW_RECORD_MESSAGE : '' - ] - ]; + if ($subdefs !==null) { + foreach ($subdefs as $subdef) { + $payload = [ + 'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE, + 'payload' => [ + 'recordId' => $event->getRecord()->getRecordId(), + 'databoxId' => $event->getRecord()->getDataboxId(), + 'subdefName' => $subdef->get_name(), + 'status' => $event->isNewRecord() ? MessagePublisher::NEW_RECORD_MESSAGE : '' + ] + ]; - $this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE); + $this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE); + } } } } From 952388aba1dc08624155f939d1570d60dc90d7c6 Mon Sep 17 00:00:00 2001 From: aynsix Date: Wed, 3 Jun 2020 17:55:39 +0300 Subject: [PATCH 04/11] add espace --- .../Phrasea/WorkerManager/Subscriber/RecordSubscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php index 429aebe46c..cf0a3b940c 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/RecordSubscriber.php @@ -54,7 +54,7 @@ class RecordSubscriber implements EventSubscriberInterface if (!$record->isStory()) { $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType()); - if ($subdefs !==null) { + if ($subdefs !== null) { foreach ($subdefs as $subdef) { $payload = [ 'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE, From 9272f3642fb5957849f13e4045a73b3807283ce4 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Wed, 3 Jun 2020 20:32:10 +0200 Subject: [PATCH 05/11] WIP refacto : explode v3 controller, fix warnings --- .../Api/V3/V3MetadatasController.php | 371 ++++++ .../Controller/Api/V3/V3ResultHelpers.php | 52 + .../Controller/Api/V3/V3SearchController.php | 488 ++++++++ .../Controller/Api/V3/V3StoriesController.php | 359 ++++++ .../Phrasea/Controller/Api/V3Controller.php | 1086 ----------------- .../Phrasea/ControllerProvider/Api/V3.php | 31 +- 6 files changed, 1294 insertions(+), 1093 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php create mode 100644 lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php create mode 100644 lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php create mode 100644 lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php delete mode 100644 lib/Alchemy/Phrasea/Controller/Api/V3Controller.php diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php new file mode 100644 index 0000000000..b016dcfcd7 --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php @@ -0,0 +1,371 @@ +findDataboxById($databox_id)->get_meta_structure(); + $record = $this->findDataboxById($databox_id)->get_record($record_id); + + //$record->set_metadatas() + + //setRecordStatusAction + + try { + $b = $this->decodeJsonBody($request); + } + catch (Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction($request, 'Bad JSON'); + } + + $ret = [ + 'metadatas_ops' => null, + 'sb_ops' => null, + ]; + try { + // do metadatas ops + if (is_array($b->metadatas)) { + $ret['metadatas_ops'] = $this->setmetadatasAction_meta($struct, $record, $b->metadatas); + } + // do sb ops + if (is_array($b->status)) { + $ret['sb_ops'] = $this->setmetadatasAction_sb($struct, $record, $b->status); + } + } + catch (Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + $e->getMessage() + ); + } + + return Result::create($request, $ret)->createResponse(); + } + + /** + * @param $struct + * @param $record + * @param $metadatas + * @return array + * @throws Exception + */ + private function setmetadatasAction_meta($struct, $record, $metadatas) + { + $structByKey = []; + $nameToStrucId = []; + foreach ($struct as $f) { + $nameToStrucId[$f->get_name()] = $f->get_id(); + $structByKey[$f->get_id()] = $f; + $structByKey[$f->get_name()] = &$structByKey[$f->get_id()]; + } + + $metadatas_ops = []; + foreach ($metadatas as $_m) { + // sanity + if($_m->meta_struct_id && $_m->field_name) { + throw new Exception("define meta_struct_id OR field_name, not both."); + } + // select fields that match meta_struct_id or field_name (can be arrays) + $fields_list = null; // to filter caption_fields from record, default all + $struct_fields = []; // struct fields that match meta_struct_id or field_name + if(($field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name) !== null) { // can be null if none defined (=match all) + if (!is_array($field_keys)) { + $field_keys = [$field_keys]; + } + $fields_list = []; + foreach ($field_keys as $k) { + if(array_key_exists($k, $structByKey)) { + $fields_list[] = $structByKey[$k]->get_name(); + $struct_fields[$structByKey[$k]->get_id()] = $structByKey[$k]; + } + } + } + $caption_fields = $record->get_caption()->get_fields($fields_list, true); + + $meta_id = is_null($_m->meta_id) ? null : (int)($_m->meta_id); + + if(!($match_method = (string)($_m->match_method))) { + $match_method = 'ignore_case'; + } + if(!in_array($match_method, ['strict', 'ignore_case', 'regexp'])) { + throw new Exception(sprintf("bad match_method (%s).", $match_method)); + } + + $values = []; + if(is_array($_m->value)) { + foreach ($_m->value as $v) { + $values[] = is_null($v) ? null : (string)$v; + } + } + else { + $values = is_null($_m->value) ? [] : [(string)($_m->value)]; + } + + if(!($action = (string)($_m->action))) { + $action = 'set'; + } + + switch ($_m->action) { + case 'set': + $ops = $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values); + break; + case 'add': + $ops = $this->setmetadatasAction_add($struct_fields, $values); + break; + case 'delete': + $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null); + break; + case 'replace': + if (!is_string($_m->replace_with) && !is_null($_m->replace_with)) { + throw new Exception("bad \"replace_with\" for action \"replace\"."); + } + $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with); + break; + default: + throw new Exception(sprintf("bad action (%s).", $action)); + } + + $metadatas_ops = array_merge($metadatas_ops, $ops); + } + + return $metadatas_ops; + } + + /** + * @param $struct + * @param $record + * @param $statuses + * @return array + * @throws Exception + */ + private function setmetadatasAction_sb($struct, $record, $statuses) + { + $datas = strrev($record->getStatus()); + + foreach ($statuses as $status) { + $n = (int)($status->bit); + $value = (int)($status->state); + if ($n > 31 || $n < 4) { + throw new Exception(sprintf("Invalid status bit number (%s).", $n)); + } + if ($value < 0 || $value > 1) { + throw new Exception(sprintf("Invalid status bit state (%s) for bit (%s).", $value, $n)); + } + + $datas = substr($datas, 0, ($n)) . $value . substr($datas, ($n + 1)); + } + + // $record->setStatus(strrev($datas)); + + // @todo Move event dispatch inside record_adapter class (keeps things encapsulated) + $this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record)); + + return ["status" => $this->getResultHelpers()->listRecordStatus($record)]; + } + + private function match($pattern, $method, $value) + { + switch ($method) { + case 'strict': + return $value === $pattern; + case 'ignore_case': + return strtolower($value) === strtolower($pattern); + case 'regexp': + return preg_match($pattern, $value) == 1; + } + return false; + } + + ////////////////////////////////// + /// TODO : keep multi-values uniques ! + /// it should be done in record_adapter + ////////////////////////////////// + + /** + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param int|null $meta_id + * @param string[] $values + * + * @return array ops to execute + * @throws Exception + */ + private function setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) + { + $ops = []; + + // if one field was multi-valued and no meta_id was set, we must delete all values + foreach ($caption_fields as $cf) { + if ($cf->is_multi() && is_null($meta_id)) { + foreach ($cf->get_values() as $field_value) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => null + ]; + } + } + } + // now set values to matching struct_fields + foreach ($struct_fields as $sf) { + if($sf->is_multi()) { + // add the non-null value(s) + foreach ($values as $value) { + if (!is_null($value)) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // can be null + 'value' => $value + ]; + } + } + } + else { + // mono-valued + if(count($values) > 1) { + throw new Exception(sprintf("setting mono-valued (%s) requires only one value.", $sf->get_name())); + } + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // probably null, + 'value' => $values[0] + ]; + } + } + + return $ops; + } + + /** + * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name + * @param string[] $values + * + * @return array ops to execute + * @throws Exception + */ + private function setmetadatasAction_add($struct_fields, $values) + { + $ops = []; + + // now set values to matching struct_fields + foreach ($struct_fields as $sf) { + if(!$sf->is_multi()) { + throw new Exception(sprintf("can't \"add\" to mono-valued (%s).", $sf->get_name())); + } + // add the non-null value(s) + foreach ($values as $value) { + if (!is_null($value)) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => null, + 'value' => $value + ]; + } + } + } + + return $ops; + } + + /** + * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param int|null $meta_id + * @param string $match_method "strict" | "ignore_case" | "regexp" + * @param string[] $values + * @param string|null $replace_with + * + * @return array ops to execute + */ + private function setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) + { + $ops = []; + + foreach ($caption_fields as $cf) { + // match all ? + if(is_null($meta_id) && count($values) == 0) { + foreach ($cf->get_values() as $field_value) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $replace_with + ]; + } + } + // match by meta-id ? + if (!is_null($meta_id)) { + foreach ($cf->get_values() as $field_value) { + if ($field_value->getId() === $meta_id) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $replace_with + ]; + } + } + } + // match by value(s) ? + foreach ($values as $value) { + foreach ($cf->get_values() as $field_value) { + $rw = $replace_with; + if($match_method=='regexp' && !is_null($replace_with)) { + $rw = preg_replace($value, $replace_with, $field_value->getValue()); + } + if ($this->match($value, $match_method, $field_value->getValue())) { + $ops[] = [ + 'meta_struct_id' => $cf->get_meta_struct_id(), + 'meta_id' => $field_value->getId(), + 'value' => $rw + ]; + } + } + } + } + + return $ops; + } + + /** + * @return V3ResultHelpers + */ + private function getResultHelpers() + { + static $rh = null; + + if(is_null($rh)) { + $rh = new V3ResultHelpers( + $this, + $this->getConf(), + $this->app['media_accessor.subdef_url_generator'] + ); + } + return $rh; + } +} diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php new file mode 100644 index 0000000000..38a6da1c1b --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php @@ -0,0 +1,52 @@ +controller = $controller; + $this->urlgenerator = $urlgenerator; + $this->conf = $conf; + } + + + /** + * Retrieve detailed information about one status + * + * @param record_adapter $record + * @return array + */ + public function listRecordStatus(record_adapter $record) + { + $ret = []; + foreach ($record->getStatusStructure() as $bit => $status) { + $ret[] = [ + 'bit' => $bit, + 'state' => databox_status::bitIsSet($record->getStatusBitField(), $bit), + ]; + } + + return $ret; + } + +} diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php new file mode 100644 index 0000000000..c040bde5a5 --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php @@ -0,0 +1,488 @@ +app['acl'], $this->getAuthenticatedUser(), new PermalinkTransformer()); + $technicalDataTransformer = new TechnicalDataTransformer(); + $recordTransformer = new RecordTransformer($subdefTransformer, $technicalDataTransformer); + $storyTransformer = new StoryTransformer($subdefTransformer, $recordTransformer); + $compositeTransformer = new V1SearchCompositeResultTransformer($recordTransformer, $storyTransformer); + $searchTransformer = new V1SearchResultTransformer($compositeTransformer); + + $transformerResolver = new SearchResultTransformerResolver([ + '' => $searchTransformer, + 'results' => $compositeTransformer, + 'results.stories' => $storyTransformer, + 'results.stories.thumbnail' => $subdefTransformer, + 'results.stories.metadatas' => new CallbackTransformer(), + 'results.stories.caption' => new CallbackTransformer(), + 'results.stories.records' => $recordTransformer, + 'results.stories.records.thumbnail' => $subdefTransformer, + 'results.stories.records.technical_informations' => $technicalDataTransformer, + 'results.stories.records.subdefs' => $subdefTransformer, + 'results.stories.records.metadata' => new CallbackTransformer(), + 'results.stories.records.status' => new CallbackTransformer(), + 'results.stories.records.caption' => new CallbackTransformer(), + 'results.records' => $recordTransformer, + 'results.records.thumbnail' => $subdefTransformer, + 'results.records.technical_informations' => $technicalDataTransformer, + 'results.records.subdefs' => $subdefTransformer, + 'results.records.metadata' => new CallbackTransformer(), + 'results.records.status' => new CallbackTransformer(), + 'results.records.caption' => new CallbackTransformer(), + ]); + + $includeResolver = new IncludeResolver($transformerResolver); + + $fractal = new FractalManager(); + $fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher'])); + $fractal->parseIncludes($this->resolveSearchIncludes($request)); + + $result = $this->doSearch($request); + + $story_max_records = null; + // if search on story + if ($request->get('search_type') == 1) { + $story_max_records = (int)$request->get('story_max_records') ?: 10; + } + + $searchView = $this->buildSearchView( + $result, + $includeResolver->resolve($fractal), + $this->resolveSubdefUrlTTL($request), + $story_max_records + ); + + $ret = $fractal->createData(new Item($searchView, $searchTransformer))->toArray(); + + return Result::create($request, $ret)->createResponse(); + } + + /** + * Returns requested includes + * + * @param Request $request + * @return string[] + */ + private function resolveSearchIncludes(Request $request) + { + $includes = [ + 'results.stories.records' + ]; + + if ($request->attributes->get('_extended', false)) { + if ($request->get('search_type') != SearchEngineOptions::RECORD_STORY) { + $includes = array_merge($includes, [ + 'results.stories.records.subdefs', + 'results.stories.records.metadata', + 'results.stories.records.caption', + 'results.stories.records.status' + ]); + } + else { + $includes = [ 'results.stories.caption' ]; + } + + $includes = array_merge($includes, [ + 'results.records.subdefs', + 'results.records.metadata', + 'results.records.caption', + 'results.records.status' + ]); + } + + return $includes; + } + + /** + * @param SearchEngineResult $result + * @param string[] $includes + * @param int $urlTTL + * @param int|null $story_max_records + * @return SearchResultView + */ + private function buildSearchView(SearchEngineResult $result, array $includes, $urlTTL, $story_max_records = null) + { + $references = new RecordReferenceCollection($result->getResults()); + + $records = new RecordCollection(); + $stories = new RecordCollection(); + + foreach ($references->toRecords($this->getApplicationBox()) as $record) { + if ($record->isStory()) { + $stories[$record->getId()] = $record; + } else { + $records[$record->getId()] = $record; + } + } + + $resultView = new SearchResultView($result); + + if ($stories->count() > 0) { + $user = $this->getAuthenticatedUser(); + $children = []; + + foreach ($stories->getDataboxIds() as $databoxId) { + $storyIds = $stories->getDataboxRecordIds($databoxId); + + $selections = $this->findDataboxById($databoxId) + ->getRecordRepository() + ->findChildren($storyIds, $user,1, $story_max_records); + $children[$databoxId] = array_combine($storyIds, $selections); + } + + /** @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) || + in_array('results.stories.caption', $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(); + } + + $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); + + foreach ($childrenViews as $index => $recordView) { + $recordView->setSubdefs($subdefViews[$index]); + $recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index])); + } + + if (array_intersect($includes, ['results.stories.records.metadata', 'results.stories.records.caption'])) { + $captions = $this->app['service.caption']->findByReferenceCollection($allChildren); + $canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($allChildren); + + $this->buildCaptionViews($childrenViews, $captions, $canSeeBusiness); + } + + $resultView->setStories($storyViews); + } + + if ($records->count() > 0) { + $names = in_array('results.records.subdefs', $includes, true) ? null : ['thumbnail']; + $recordViews = $this->buildRecordViews($records); + $subdefViews = $this->buildSubdefsViews($records, $names, $urlTTL); + + $technicalDatasets = $this->app['service.technical_data']->fetchRecordsTechnicalData($records); + + foreach ($recordViews as $index => $recordView) { + $recordView->setSubdefs($subdefViews[$index]); + $recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index])); + } + + if (array_intersect($includes, ['results.records.metadata', 'results.records.caption'])) { + $captions = $this->app['service.caption']->findByReferenceCollection($records); + $canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($records); + + $this->buildCaptionViews($recordViews, $captions, $canSeeBusiness); + } + + $resultView->setRecords($recordViews); + } + + return $resultView; + } + + /** + * @param Request $request + * @return SearchEngineResult + */ + private function doSearch(Request $request) + { + $options = SearchEngineOptions::fromRequest($this->app, $request); + $options->setFirstResult((int)($request->get('offset_start') ?: 0)); + $options->setMaxResults((int)$request->get('per_page') ?: 10); + + $this->getSearchEngine()->resetCache(); + + $search_result = $this->getSearchEngine()->query((string)$request->get('query'), $options); + + $this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getQueryText()); + + // log array of collectionIds (from $options) for each databox + $collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox(); + foreach ($collectionsReferencesByDatabox as $sbid => $references) { + $databox = $this->findDataboxById($sbid); + $collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references); + $this->getSearchEngineLogger()->log($databox, $search_result->getQueryText(), $search_result->getTotal(), $collectionsIds); + } + + $this->getSearchEngine()->clearCache(); + + return $search_result; + } + + /** + * @return SearchEngineInterface + */ + private function getSearchEngine() + { + return $this->app['phraseanet.SE']; + } + + /** + * @return UserManipulator + */ + private function getUserManipulator() + { + return $this->app['manipulator.user']; + } + + /** + * @return SearchEngineLogger + */ + private function getSearchEngineLogger() + { + return $this->app['phraseanet.SE.logger']; + } + + /** + * @param Request $request + * @return int + */ + private function resolveSubdefUrlTTL(Request $request) + { + $urlTTL = $request->query->get('subdef_url_ttl'); + + if (null !== $urlTTL) { + return (int)$urlTTL; + } + + return $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']); + } + + /** + * @param RecordCollection|record_adapter[] $references + * @return RecordView[] + */ + private function buildRecordViews($references) + { + if (!$references instanceof RecordCollection) { + $references = new RecordCollection($references); + } + + $recordViews = []; + + foreach ($references as $index => $record) { + $recordViews[$index] = new RecordView($record); + } + + return $recordViews; + } + + /** + * @param RecordReferenceInterface[]|RecordReferenceCollection|DataboxGroupable $references + * @param array|null $names + * @param int $urlTTL + * @return SubdefView[][] + */ + private function buildSubdefsViews($references, array $names = null, $urlTTL) + { + $subdefGroups = $this->app['service.media_subdef'] + ->findSubdefsByRecordReferenceFromCollection($references, $names); + + $fakeSubdefs = []; + + foreach ($subdefGroups as $index => $subdefGroup) { + if (!isset($subdefGroup['thumbnail'])) { + $fakeSubdef = new media_subdef($this->app, $references[$index], 'thumbnail', true, []); + $fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef; + + $subdefGroups[$index]['thumbnail'] = $fakeSubdef; + } + } + + $allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups); + $allPermalinks = media_Permalink_Adapter::getMany( + $this->app, + array_filter($allSubdefs, function (media_subdef $subdef) use ($fakeSubdefs) { + return !isset($fakeSubdefs[spl_object_hash($subdef)]); + }) + ); + $urls = $this->app['media_accessor.subdef_url_generator'] + ->generateMany($this->getAuthenticatedUser(), $allSubdefs, $urlTTL); + + $subdefViews = []; + + /** @var media_subdef $subdef */ + foreach ($allSubdefs as $index => $subdef) { + $subdefView = new SubdefView($subdef); + + 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; + } + + /** + * @param array $groups + * @return array|mixed + */ + private function mergeGroupsIntoOneList(array $groups) + { + // Strips keys from the internal array + array_walk($groups, function (array &$group) { + $group = array_values($group); + }); + + if ($groups) { + return call_user_func_array('array_merge', $groups); + } + + return []; + } + + /** + * @param RecordReferenceInterface[]|DataboxGroupable $references + * @return array + */ + private function retrieveSeeBusinessPerDatabox($references) + { + if (!$references instanceof DataboxGroupable) { + $references = new RecordReferenceCollection($references); + } + + $acl = $this->getAclForUser(); + + $canSeeBusiness = []; + + foreach ($references->getDataboxIds() as $databoxId) { + $canSeeBusiness[$databoxId] = $acl->can_see_business_fields($this->findDataboxById($databoxId)); + } + + $rights = []; + + foreach ($references as $index => $reference) { + $rights[$index] = $canSeeBusiness[$reference->getDataboxId()]; + } + + return $rights; + } + + /** + * @param RecordView[] $recordViews + * @param caption_record[] $captions + * @param bool[] $canSeeBusiness + */ + private function buildCaptionViews($recordViews, $captions, $canSeeBusiness) + { + foreach ($recordViews as $index => $recordView) { + $caption = $captions[$index]; + + $captionView = new CaptionView($caption); + + $captionView->setFields($caption->get_fields(null, isset($canSeeBusiness[$index]) && (bool)$canSeeBusiness[$index])); + + $recordView->setCaption($captionView); + } + } +} diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php new file mode 100644 index 0000000000..14fccc741d --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php @@ -0,0 +1,359 @@ +findDataboxById($databox_id)->get_record($record_id); + + return Result::create($request, ['story' => $this->listStory($request, $story)])->createResponse(); + } + catch (NotFoundHttpException $e) { + return Result::createError($request, 404, 'Story Not Found')->createResponse(); + } + catch (Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction($request, 'An error occurred'); + } + } + + /** + * @return V3ResultHelpers + */ + private function getResultHelpers() + { + static $rh = null; + + if(is_null($rh)) { + $rh = new V3ResultHelpers( + $this, + $this->getConf(), + $this->app['media_accessor.subdef_url_generator'] + ); + } + return $rh; + } + + /** + * Retrieve detailed information about one story + * + * @param Request $request + * @param record_adapter $story + * @return array + * @throws Exception + */ + private function listStory(Request $request, record_adapter $story) + { + if (!$story->isStory()) { + return Result::createError($request, 404, 'Story not found')->createResponse(); + } + + $per_page = (int)$request->get('per_page')?:10; + $page = (int)$request->get('page')?:1; + $offset = ($per_page * ($page - 1)) + 1; + + $caption = $story->get_caption(); + + $format = function (caption_record $caption, $dcField) { + + $field = $caption->get_dc_field($dcField); + + if (!$field) { + return null; + } + + return $field->get_serialized_values(); + }; + + return [ + '@entity@' => V1Controller::OBJECT_TYPE_STORY, + 'databox_id' => $story->getDataboxId(), + 'story_id' => $story->getRecordId(), + 'updated_on' => $story->getUpdated()->format(DATE_ATOM), + 'created_on' => $story->getCreated()->format(DATE_ATOM), + 'collection_id' => $story->getCollectionId(), + 'base_id' => $story->getBaseId(), + 'thumbnail' => $this->listEmbeddableMedia($request, $story, $story->get_thumbnail()), + 'uuid' => $story->getUuid(), + 'metadatas' => [ + '@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG, + 'dc:contributor' => $format($caption, databox_Field_DCESAbstract::Contributor), + 'dc:coverage' => $format($caption, databox_Field_DCESAbstract::Coverage), + 'dc:creator' => $format($caption, databox_Field_DCESAbstract::Creator), + 'dc:date' => $format($caption, databox_Field_DCESAbstract::Date), + 'dc:description' => $format($caption, databox_Field_DCESAbstract::Description), + 'dc:format' => $format($caption, databox_Field_DCESAbstract::Format), + 'dc:identifier' => $format($caption, databox_Field_DCESAbstract::Identifier), + 'dc:language' => $format($caption, databox_Field_DCESAbstract::Language), + 'dc:publisher' => $format($caption, databox_Field_DCESAbstract::Publisher), + 'dc:relation' => $format($caption, databox_Field_DCESAbstract::Relation), + 'dc:rights' => $format($caption, databox_Field_DCESAbstract::Rights), + 'dc:source' => $format($caption, databox_Field_DCESAbstract::Source), + 'dc:subject' => $format($caption, databox_Field_DCESAbstract::Subject), + 'dc:title' => $format($caption, databox_Field_DCESAbstract::Title), + 'dc:type' => $format($caption, databox_Field_DCESAbstract::Type), + ], + 'records' => $this->listRecords($request, array_values($story->getChildren($offset, $per_page)->get_elements())), + ]; + } + + /** + * @param Request $request + * @param RecordReferenceInterface[]|RecordReferenceCollection $records + * @return array + */ + private function listRecords(Request $request, $records) + { + if (!$records instanceof RecordReferenceCollection) { + $records = new RecordReferenceCollection($records); + } + + $technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($records); + + $data = []; + + foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) { + $record->setTechnicalDataSet($technicalData[$index]); + + $data[$index] = $this->listRecord($request, $record); + } + + return $data; + } + public function listEmbeddableMedia(Request $request, record_adapter $record, media_subdef $media) + { + if (!$media->is_physically_present()) { + return null; + } + + if ($this->getAuthenticator()->isAuthenticated()) { + $acl = $this->getAclForUser(); + if ($media->get_name() !== 'document' + && false === $acl->has_access_to_subdef($record, $media->get_name()) + ) { + return null; + } + if ($media->get_name() === 'document' + && !$acl->has_right_on_base($record->getBaseId(), ACL::CANDWNLDHD) + && !$acl->has_hd_grant($record) + ) { + return null; + } + } + + if ($media->get_permalink() instanceof media_Permalink_Adapter) { + $permalink = $this->listPermalink($media->get_permalink()); + } else { + $permalink = null; + } + + $urlTTL = (int) $request->get( + 'subdef_url_ttl', + $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']) + ); + if ($urlTTL < 0) { + $urlTTL = -1; + } + $issuer = $this->getAuthenticatedUser(); + + return [ + 'name' => $media->get_name(), + 'permalink' => $permalink, + 'height' => $media->get_height(), + 'width' => $media->get_width(), + 'filesize' => $media->get_size(), + 'devices' => $media->getDevices(), + 'player_type' => $media->get_type(), + 'mime_type' => $media->get_mime(), + 'substituted' => $media->is_substituted(), + 'created_on' => $media->get_creation_date()->format(DATE_ATOM), + 'updated_on' => $media->get_modification_date()->format(DATE_ATOM), + 'url' => $this->app['media_accessor.subdef_url_generator']->generate($issuer, $media, $urlTTL), + 'url_ttl' => $urlTTL, + ]; + } + + private function listPermalink(media_Permalink_Adapter $permalink) + { + $downloadUrl = $permalink->get_url(); + $downloadUrl->getQuery()->set('download', '1'); + + return [ + 'created_on' => $permalink->get_created_on()->format(DATE_ATOM), + 'id' => $permalink->get_id(), + 'is_activated' => $permalink->get_is_activated(), + /** @Ignore */ + 'label' => $permalink->get_label(), + 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), + 'page_url' => $permalink->get_page(), + 'download_url' => (string)$downloadUrl, + 'url' => (string)$permalink->get_url(), + ]; + } + + + /** + * Retrieve detailed information about one record + * + * @param Request $request + * @param record_adapter $record + * @return array + */ + public function listRecord(Request $request, record_adapter $record) + { + $technicalInformation = []; + foreach ($record->get_technical_infos()->getValues() as $name => $value) { + $technicalInformation[] = ['name' => $name, 'value' => $value]; + } + + $data = [ + 'databox_id' => $record->getDataboxId(), + 'record_id' => $record->getRecordId(), + 'mime_type' => $record->getMimeType(), + 'title' => $record->get_title(), + 'original_name' => $record->get_original_name(), + 'updated_on' => $record->getUpdated()->format(DATE_ATOM), + 'created_on' => $record->getCreated()->format(DATE_ATOM), + 'collection_id' => $record->getCollectionId(), + 'base_id' => $record->getBaseId(), + 'sha256' => $record->getSha256(), + 'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail()), + 'technical_informations' => $technicalInformation, + 'phrasea_type' => $record->getType(), + 'uuid' => $record->getUuid(), + ]; + + if ($request->attributes->get('_extended', false)) { + $data = array_merge($data, [ + 'subdefs' => $this->listRecordEmbeddableMedias($request, $record), + 'metadata' => $this->listRecordMetadata($record), + 'status' => $this->getResultHelpers()->listRecordStatus($record), + 'caption' => $this->listRecordCaption($record), + ]); + } + + return $data; + } + + /** + * @param Request $request + * @param record_adapter $record + * @return array + */ + private function listRecordEmbeddableMedias(Request $request, record_adapter $record) + { + $subdefs = []; + + foreach ($record->get_embedable_medias([], []) as $name => $media) { + if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media)) { + $subdefs[] = $subdef; + } + } + + return $subdefs; + } + + /** + * List all fields of given record + * + * @param record_adapter $record + * @return array + */ + private function listRecordMetadata(record_adapter $record) + { + $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); + + return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness)); + } + + /** + * @param caption_field[] $fields + * @return array + */ + private function listRecordCaptionFields($fields) + { + $ret = []; + + foreach ($fields as $field) { + $databox_field = $field->get_databox_field(); + + $fieldData = [ + 'meta_structure_id' => $field->get_meta_struct_id(), + 'name' => $field->get_name(), + 'labels' => [ + 'fr' => $databox_field->get_label('fr'), + 'en' => $databox_field->get_label('en'), + 'de' => $databox_field->get_label('de'), + 'nl' => $databox_field->get_label('nl'), + ], + ]; + + foreach ($field->get_values() as $value) { + $data = [ + 'meta_id' => $value->getId(), + 'value' => $value->getValue(), + ]; + + $ret[] = $fieldData + $data; + } + } + + return $ret; + } + + /** + * @param record_adapter $record + * @return array + */ + private function listRecordCaption(record_adapter $record) + { + $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); + + $caption = []; + + foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) { + $caption[] = [ + 'meta_structure_id' => $field->get_meta_struct_id(), + 'name' => $field->get_name(), + 'value' => $field->get_serialized_values(';'), + ]; + } + + return $caption; + } + +} diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php deleted file mode 100644 index 686328bb3c..0000000000 --- a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php +++ /dev/null @@ -1,1086 +0,0 @@ -findDataboxById($databox_id)->get_meta_structure(); - $record = $this->findDataboxById($databox_id)->get_record($record_id); - - //$record->set_metadatas() - - $structByKey = []; - $nameToStrucId = []; - foreach($struct as $f) { - $nameToStrucId[$f->get_name()] = $f->get_id(); - $structByKey[$f->get_id()] = $f; - $structByKey[$f->get_name()] = &$structByKey[$f->get_id()]; - } - - try { - $b = $this->decodeJsonBody($request); - } - catch(\Exception $e) { - return $this->app['controller.api.v1']->getBadRequestAction($request, 'Bad JSON'); - } - - $metadatas_ops = []; - foreach ($b->metadatas as $_m) { - // sanity - if($_m->meta_struct_id && $_m->field_name) { - return $this->app['controller.api.v1']->getBadRequestAction( - $request, - "define meta_struct_id OR field_name, not both." - ); - } - // select fields that match meta_struct_id or field_name (can be arrays) - $fields_list = null; // to filter caption_fields from record, default all - $struct_fields = []; // struct fields that match meta_struct_id or field_name - if(($field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name) !== null) { // can be null if none defined (=match all) - if (!is_array($field_keys)) { - $field_keys = [$field_keys]; - } - $fields_list = []; - foreach ($field_keys as $k) { - if(array_key_exists($k, $structByKey)) { - $fields_list[] = $structByKey[$k]->get_name(); - $struct_fields[$structByKey[$k]->get_id()] = $structByKey[$k]; - } - } - } - $caption_fields = $record->get_caption()->get_fields($fields_list, true); - - $meta_id = is_null($_m->meta_id) ? null : (int)($_m->meta_id); - - if(!($match_method = (string)($_m->match_method))) { - $match_method = 'ignore_case'; - } - if(!in_array($match_method, ['strict', 'ignore_case', 'regexp'])) { - return $this->app['controller.api.v1']->getBadRequestAction( - $request, - sprintf("bad match_method (%s).", $match_method) - ); - } - - $values = []; - if(is_array($_m->value)) { - foreach ($_m->value as $v) { - $values[] = is_null($v) ? null : (string)$v; - } - } - else { - $values = is_null($_m->value) ? [] : [(string)($_m->value)]; - } - - if(!($action = (string)($_m->action))) { - $action = 'set'; - } - - try { - switch ($_m->action) { - case 'set': - $ops = $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values); - break; - case 'add': - $ops = $this->setmetadatasAction_add($struct_fields, $values); - break; - case 'delete': - $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null); - break; - case 'replace': - if (!is_string($_m->replace_with) && !is_null($_m->replace_with)) { - throw new \Exception("bad \"replace_with\" for action \"replace\"."); - } - $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with); - break; - default: - throw new \Exception(sprintf("bad action (%s).", $action)); - } - } - catch (\Exception $e) { - return $this->app['controller.api.v1']->getBadRequestAction( - $request, - $e->getMessage() - ); - } - - $metadatas_ops = array_merge($metadatas_ops, $ops); - } - - return Result::create($request, $metadatas_ops)->createResponse(); - } - - private function match($pattern, $method, $value) - { - switch ($method) { - case 'strict': - return $value === $pattern; - case 'ignore_case': - return strtolower($value) === strtolower($pattern); - case 'regexp': - return preg_match($pattern, $value) == 1; - } - } - - /** - * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name - * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) - * @param int|null $meta_id - * @param string[] $values - * - * @return array ops to execute - * @throws \Exception - */ - private function setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) - { - $ops = []; - - // if one field was multi-valued and no meta_id was set, we must delete all values - foreach ($caption_fields as $cf) { - if ($cf->is_multi() && is_null($meta_id)) { - foreach ($cf->get_values() as $field_value) { - $ops[] = [ - 'meta_struct_id' => $cf->get_meta_struct_id(), - 'meta_id' => $field_value->getId(), - 'value' => null - ]; - } - } - } - // now set values to matching struct_fields - foreach ($struct_fields as $sf) { - if($sf->is_multi()) { - // add the non-null value(s) - foreach ($values as $value) { - if (!is_null($value)) { - $ops[] = [ - 'meta_struct_id' => $sf->get_id(), - 'meta_id' => $meta_id, // can be null - 'value' => $value - ]; - } - } - } - else { - // mono-valued - if(count($values) > 1) { - throw new \Exception(sprintf("setting mono-valued (%s) requires only one value.", $sf->get_name())); - } - $ops[] = [ - 'meta_struct_id' => $sf->get_id(), - 'meta_id' => $meta_id, // probably null, - 'value' => $values[0] - ]; - } - } - - return $ops; - } - - /** - * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name - * @param string[] $values - * - * @return array ops to execute - * @throws \Exception - */ - private function setmetadatasAction_add($struct_fields, $values) - { - $ops = []; - - // now set values to matching struct_fields - foreach ($struct_fields as $sf) { - if(!$sf->is_multi()) { - throw new \Exception(sprintf("can't \"add\" to mono-valued (%s).", $sf->get_name())); - } - // add the non-null value(s) - foreach ($values as $value) { - if (!is_null($value)) { - $ops[] = [ - 'meta_struct_id' => $sf->get_id(), - 'meta_id' => null, - 'value' => $value - ]; - } - } - } - - return $ops; - } - - /** - * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) - * @param int|null $meta_id - * @param string $match_method "strict" | "ignore_case" | "regexp" - * @param string[] $values - * @param string|null $replace_with - * - * @return array ops to execute - */ - private function setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) - { - $ops = []; - - foreach ($caption_fields as $cf) { - // match all ? - if(is_null($meta_id) && count($values) == 0) { - foreach ($cf->get_values() as $field_value) { - $ops[] = [ - 'meta_struct_id' => $cf->get_meta_struct_id(), - 'meta_id' => $field_value->getId(), - 'value' => $replace_with - ]; - } - } - // match by meta-id ? - if (!is_null($meta_id)) { - foreach ($cf->get_values() as $field_value) { - if ($field_value->getId() === $meta_id) { - $ops[] = [ - 'meta_struct_id' => $cf->get_meta_struct_id(), - 'meta_id' => $field_value->getId(), - 'value' => $replace_with - ]; - } - } - } - // match by value(s) ? - foreach ($values as $value) { - foreach ($cf->get_values() as $field_value) { - $rw = $replace_with; - if($match_method=='regexp' && !is_null($replace_with)) { - $rw = preg_replace($value, $replace_with, $field_value->getValue()); - } - if ($this->match($value, $match_method, $field_value->getValue())) { - $ops[] = [ - 'meta_struct_id' => $cf->get_meta_struct_id(), - 'meta_id' => $field_value->getId(), - 'value' => $rw - ]; - } - } - } - } - - return $ops; - } - - - /** - * Return detailed information about one story - * - * @param Request $request - * @param int $databox_id - * @param int $record_id - * - * @return Response - */ - public function getStoryAction(Request $request, $databox_id, $record_id) - { - try { - $story = $this->findDataboxById($databox_id)->get_record($record_id); - - return Result::create($request, ['story' => $this->listStory($request, $story)])->createResponse(); - } catch (NotFoundHttpException $e) { - return Result::createError($request, 404, $this->app->trans('Story Not Found'))->createResponse(); - } catch (\Exception $e) { - return $this->app['controller.api.v1']->getBadRequestAction($request, $this->app->trans('An error occurred')); - } - } - - /** - * Search for results - * - * @param Request $request - * - * @return Response - */ - public function searchAction(Request $request) - { - $subdefTransformer = new SubdefTransformer($this->app['acl'], $this->getAuthenticatedUser(), new PermalinkTransformer()); - $technicalDataTransformer = new TechnicalDataTransformer(); - $recordTransformer = new RecordTransformer($subdefTransformer, $technicalDataTransformer); - $storyTransformer = new StoryTransformer($subdefTransformer, $recordTransformer); - $compositeTransformer = new V1SearchCompositeResultTransformer($recordTransformer, $storyTransformer); - $searchTransformer = new V1SearchResultTransformer($compositeTransformer); - - $transformerResolver = new SearchResultTransformerResolver([ - '' => $searchTransformer, - 'results' => $compositeTransformer, - 'results.stories' => $storyTransformer, - 'results.stories.thumbnail' => $subdefTransformer, - 'results.stories.metadatas' => new CallbackTransformer(), - 'results.stories.caption' => new CallbackTransformer(), - 'results.stories.records' => $recordTransformer, - 'results.stories.records.thumbnail' => $subdefTransformer, - 'results.stories.records.technical_informations' => $technicalDataTransformer, - 'results.stories.records.subdefs' => $subdefTransformer, - 'results.stories.records.metadata' => new CallbackTransformer(), - 'results.stories.records.status' => new CallbackTransformer(), - 'results.stories.records.caption' => new CallbackTransformer(), - 'results.records' => $recordTransformer, - 'results.records.thumbnail' => $subdefTransformer, - 'results.records.technical_informations' => $technicalDataTransformer, - 'results.records.subdefs' => $subdefTransformer, - 'results.records.metadata' => new CallbackTransformer(), - 'results.records.status' => new CallbackTransformer(), - 'results.records.caption' => new CallbackTransformer(), - ]); - - $includeResolver = new IncludeResolver($transformerResolver); - - $fractal = new \League\Fractal\Manager(); - $fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher'])); - $fractal->parseIncludes($this->resolveSearchIncludes($request)); - - $result = $this->doSearch($request); - - $story_max_records = null; - // if search on story - if ($request->get('search_type') == 1) { - $story_max_records = (int)$request->get('story_max_records') ?: 10; - } - - $searchView = $this->buildSearchView( - $result, - $includeResolver->resolve($fractal), - $this->resolveSubdefUrlTTL($request), - $story_max_records - ); - - $ret = $fractal->createData(new Item($searchView, $searchTransformer))->toArray(); - - return Result::create($request, $ret)->createResponse(); - } - - /** - * Retrieve detailed information about one story - * - * @param Request $request - * @param \record_adapter $story - * @return array - * @throws \Exception - */ - private function listStory(Request $request, \record_adapter $story) - { - if (!$story->isStory()) { - return Result::createError($request, 404, 'Story not found')->createResponse(); - } - - $per_page = (int)$request->get('per_page')?:10; - $page = (int)$request->get('page')?:1; - $offset = ($per_page * ($page - 1)) + 1; - - $caption = $story->get_caption(); - - $format = function (\caption_record $caption, $dcField) { - - $field = $caption->get_dc_field($dcField); - - if (!$field) { - return null; - } - - return $field->get_serialized_values(); - }; - - return [ - '@entity@' => V1Controller::OBJECT_TYPE_STORY, - 'databox_id' => $story->getDataboxId(), - 'story_id' => $story->getRecordId(), - 'updated_on' => $story->getUpdated()->format(DATE_ATOM), - 'created_on' => $story->getCreated()->format(DATE_ATOM), - 'collection_id' => $story->getCollectionId(), - 'base_id' => $story->getBaseId(), - 'thumbnail' => $this->listEmbeddableMedia($request, $story, $story->get_thumbnail()), - 'uuid' => $story->getUuid(), - 'metadatas' => [ - '@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG, - 'dc:contributor' => $format($caption, \databox_Field_DCESAbstract::Contributor), - 'dc:coverage' => $format($caption, \databox_Field_DCESAbstract::Coverage), - 'dc:creator' => $format($caption, \databox_Field_DCESAbstract::Creator), - 'dc:date' => $format($caption, \databox_Field_DCESAbstract::Date), - 'dc:description' => $format($caption, \databox_Field_DCESAbstract::Description), - 'dc:format' => $format($caption, \databox_Field_DCESAbstract::Format), - 'dc:identifier' => $format($caption, \databox_Field_DCESAbstract::Identifier), - 'dc:language' => $format($caption, \databox_Field_DCESAbstract::Language), - 'dc:publisher' => $format($caption, \databox_Field_DCESAbstract::Publisher), - 'dc:relation' => $format($caption, \databox_Field_DCESAbstract::Relation), - 'dc:rights' => $format($caption, \databox_Field_DCESAbstract::Rights), - 'dc:source' => $format($caption, \databox_Field_DCESAbstract::Source), - 'dc:subject' => $format($caption, \databox_Field_DCESAbstract::Subject), - 'dc:title' => $format($caption, \databox_Field_DCESAbstract::Title), - 'dc:type' => $format($caption, \databox_Field_DCESAbstract::Type), - ], - 'records' => $this->listRecords($request, array_values($story->getChildren($offset, $per_page)->get_elements())), - ]; - } - - private function listEmbeddableMedia(Request $request, \record_adapter $record, \media_subdef $media) - { - if (!$media->is_physically_present()) { - return null; - } - - if ($this->getAuthenticator()->isAuthenticated()) { - $acl = $this->getAclForUser(); - if ($media->get_name() !== 'document' - && false === $acl->has_access_to_subdef($record, $media->get_name()) - ) { - return null; - } - if ($media->get_name() === 'document' - && !$acl->has_right_on_base($record->getBaseId(), \ACL::CANDWNLDHD) - && !$acl->has_hd_grant($record) - ) { - return null; - } - } - - if ($media->get_permalink() instanceof \media_Permalink_Adapter) { - $permalink = $this->listPermalink($media->get_permalink()); - } else { - $permalink = null; - } - - $urlTTL = (int) $request->get( - 'subdef_url_ttl', - $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']) - ); - if ($urlTTL < 0) { - $urlTTL = -1; - } - $issuer = $this->getAuthenticatedUser(); - - return [ - 'name' => $media->get_name(), - 'permalink' => $permalink, - 'height' => $media->get_height(), - 'width' => $media->get_width(), - 'filesize' => $media->get_size(), - 'devices' => $media->getDevices(), - 'player_type' => $media->get_type(), - 'mime_type' => $media->get_mime(), - 'substituted' => $media->is_substituted(), - 'created_on' => $media->get_creation_date()->format(DATE_ATOM), - 'updated_on' => $media->get_modification_date()->format(DATE_ATOM), - 'url' => $this->app['media_accessor.subdef_url_generator']->generate($issuer, $media, $urlTTL), - 'url_ttl' => $urlTTL, - ]; - } - - private function listPermalink(\media_Permalink_Adapter $permalink) - { - $downloadUrl = $permalink->get_url(); - $downloadUrl->getQuery()->set('download', '1'); - - return [ - 'created_on' => $permalink->get_created_on()->format(DATE_ATOM), - 'id' => $permalink->get_id(), - 'is_activated' => $permalink->get_is_activated(), - /** @Ignore */ - 'label' => $permalink->get_label(), - 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), - 'page_url' => $permalink->get_page(), - 'download_url' => (string)$downloadUrl, - 'url' => (string)$permalink->get_url(), - ]; - } - - /** - * @param Request $request - * @param RecordReferenceInterface[]|RecordReferenceCollection $records - * @return array - */ - private function listRecords(Request $request, $records) - { - if (!$records instanceof RecordReferenceCollection) { - $records = new RecordReferenceCollection($records); - } - - $technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($records); - - $data = []; - - foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) { - $record->setTechnicalDataSet($technicalData[$index]); - - $data[$index] = $this->listRecord($request, $record); - } - - return $data; - } - - /** - * Retrieve detailed information about one record - * - * @param Request $request - * @param \record_adapter $record - * @return array - */ - private function listRecord(Request $request, \record_adapter $record) - { - $technicalInformation = []; - foreach ($record->get_technical_infos()->getValues() as $name => $value) { - $technicalInformation[] = ['name' => $name, 'value' => $value]; - } - - $data = [ - 'databox_id' => $record->getDataboxId(), - 'record_id' => $record->getRecordId(), - 'mime_type' => $record->getMimeType(), - 'title' => $record->get_title(), - 'original_name' => $record->get_original_name(), - 'updated_on' => $record->getUpdated()->format(DATE_ATOM), - 'created_on' => $record->getCreated()->format(DATE_ATOM), - 'collection_id' => $record->getCollectionId(), - 'base_id' => $record->getBaseId(), - 'sha256' => $record->getSha256(), - 'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail()), - 'technical_informations' => $technicalInformation, - 'phrasea_type' => $record->getType(), - 'uuid' => $record->getUuid(), - ]; - - if ($request->attributes->get('_extended', false)) { - $data = array_merge($data, [ - 'subdefs' => $this->listRecordEmbeddableMedias($request, $record), - 'metadata' => $this->listRecordMetadata($record), - 'status' => $this->listRecordStatus($record), - 'caption' => $this->listRecordCaption($record), - ]); - } - - return $data; - } - - /** - * @param Request $request - * @param \record_adapter $record - * @return array - */ - private function listRecordEmbeddableMedias(Request $request, \record_adapter $record) - { - $subdefs = []; - - foreach ($record->get_embedable_medias([], []) as $name => $media) { - if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media)) { - $subdefs[] = $subdef; - } - } - - return $subdefs; - } - - /** - * List all fields of given record - * - * @param \record_adapter $record - * @return array - */ - private function listRecordMetadata(\record_adapter $record) - { - $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); - - return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness)); - } - - /** - * @param \caption_field[] $fields - * @return array - */ - private function listRecordCaptionFields($fields) - { - $ret = []; - - foreach ($fields as $field) { - $databox_field = $field->get_databox_field(); - - $fieldData = [ - 'meta_structure_id' => $field->get_meta_struct_id(), - 'name' => $field->get_name(), - 'labels' => [ - 'fr' => $databox_field->get_label('fr'), - 'en' => $databox_field->get_label('en'), - 'de' => $databox_field->get_label('de'), - 'nl' => $databox_field->get_label('nl'), - ], - ]; - - foreach ($field->get_values() as $value) { - $data = [ - 'meta_id' => $value->getId(), - 'value' => $value->getValue(), - ]; - - $ret[] = $fieldData + $data; - } - } - - return $ret; - } - - /** - * Retrieve detailed information about one status - * - * @param \record_adapter $record - * @return array - */ - private function listRecordStatus(\record_adapter $record) - { - $ret = []; - foreach ($record->getStatusStructure() as $bit => $status) { - $ret[] = [ - 'bit' => $bit, - 'state' => \databox_status::bitIsSet($record->getStatusBitField(), $bit), - ]; - } - - return $ret; - } - - /** - * @param \record_adapter $record - * @return array - */ - private function listRecordCaption(\record_adapter $record) - { - $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); - - $caption = []; - - foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) { - $caption[] = [ - 'meta_structure_id' => $field->get_meta_struct_id(), - 'name' => $field->get_name(), - 'value' => $field->get_serialized_values(';'), - ]; - } - - return $caption; - } - - /** - * Returns requested includes - * - * @param Request $request - * @return string[] - */ - private function resolveSearchIncludes(Request $request) - { - $includes = [ - 'results.stories.records' - ]; - - if ($request->attributes->get('_extended', false)) { - if ($request->get('search_type') != SearchEngineOptions::RECORD_STORY) { - $includes = array_merge($includes, [ - 'results.stories.records.subdefs', - 'results.stories.records.metadata', - 'results.stories.records.caption', - 'results.stories.records.status' - ]); - } - else { - $includes = [ 'results.stories.caption' ]; - } - - $includes = array_merge($includes, [ - 'results.records.subdefs', - 'results.records.metadata', - 'results.records.caption', - 'results.records.status' - ]); - } - - return $includes; - } - - /** - * @param SearchEngineResult $result - * @param string[] $includes - * @param int $urlTTL - * @param int|null $story_max_records - * @return SearchResultView - */ - private function buildSearchView(SearchEngineResult $result, array $includes, $urlTTL, $story_max_records = null) - { - $references = new RecordReferenceCollection($result->getResults()); - - $records = new RecordCollection(); - $stories = new RecordCollection(); - - foreach ($references->toRecords($this->getApplicationBox()) as $record) { - if ($record->isStory()) { - $stories[$record->getId()] = $record; - } else { - $records[$record->getId()] = $record; - } - } - - $resultView = new SearchResultView($result); - - if ($stories->count() > 0) { - $user = $this->getAuthenticatedUser(); - $children = []; - - foreach ($stories->getDataboxIds() as $databoxId) { - $storyIds = $stories->getDataboxRecordIds($databoxId); - - $selections = $this->findDataboxById($databoxId) - ->getRecordRepository() - ->findChildren($storyIds, $user,1, $story_max_records); - $children[$databoxId] = array_combine($storyIds, $selections); - } - - /** @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) || - in_array('results.stories.caption', $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(); - } - - $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); - - foreach ($childrenViews as $index => $recordView) { - $recordView->setSubdefs($subdefViews[$index]); - $recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index])); - } - - if (array_intersect($includes, ['results.stories.records.metadata', 'results.stories.records.caption'])) { - $captions = $this->app['service.caption']->findByReferenceCollection($allChildren); - $canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($allChildren); - - $this->buildCaptionViews($childrenViews, $captions, $canSeeBusiness); - } - - $resultView->setStories($storyViews); - } - - if ($records->count() > 0) { - $names = in_array('results.records.subdefs', $includes, true) ? null : ['thumbnail']; - $recordViews = $this->buildRecordViews($records); - $subdefViews = $this->buildSubdefsViews($records, $names, $urlTTL); - - $technicalDatasets = $this->app['service.technical_data']->fetchRecordsTechnicalData($records); - - foreach ($recordViews as $index => $recordView) { - $recordView->setSubdefs($subdefViews[$index]); - $recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index])); - } - - if (array_intersect($includes, ['results.records.metadata', 'results.records.caption'])) { - $captions = $this->app['service.caption']->findByReferenceCollection($records); - $canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($records); - - $this->buildCaptionViews($recordViews, $captions, $canSeeBusiness); - } - - $resultView->setRecords($recordViews); - } - - return $resultView; - } - - /** - * @param Request $request - * @return SearchEngineResult - */ - private function doSearch(Request $request) - { - $options = SearchEngineOptions::fromRequest($this->app, $request); - $options->setFirstResult((int)($request->get('offset_start') ?: 0)); - $options->setMaxResults((int)$request->get('per_page') ?: 10); - - $this->getSearchEngine()->resetCache(); - - $search_result = $this->getSearchEngine()->query((string)$request->get('query'), $options); - - $this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getQueryText()); - - // log array of collectionIds (from $options) for each databox - $collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox(); - foreach ($collectionsReferencesByDatabox as $sbid => $references) { - $databox = $this->findDataboxById($sbid); - $collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references); - $this->getSearchEngineLogger()->log($databox, $search_result->getQueryText(), $search_result->getTotal(), $collectionsIds); - } - - $this->getSearchEngine()->clearCache(); - - return $search_result; - } - - /** - * @return SearchEngineInterface - */ - private function getSearchEngine() - { - return $this->app['phraseanet.SE']; - } - - /** - * @return UserManipulator - */ - private function getUserManipulator() - { - return $this->app['manipulator.user']; - } - - /** - * @return SearchEngineLogger - */ - private function getSearchEngineLogger() - { - return $this->app['phraseanet.SE.logger']; - } - - /** - * @param Request $request - * @return int - */ - private function resolveSubdefUrlTTL(Request $request) - { - $urlTTL = $request->query->get('subdef_url_ttl'); - - if (null !== $urlTTL) { - return (int)$urlTTL; - } - - return $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']); - } - - /** - * @param RecordCollection|\record_adapter[] $references - * @return RecordView[] - */ - private function buildRecordViews($references) - { - if (!$references instanceof RecordCollection) { - $references = new RecordCollection($references); - } - - $recordViews = []; - - foreach ($references as $index => $record) { - $recordViews[$index] = new RecordView($record); - } - - return $recordViews; - } - - /** - * @param RecordReferenceInterface[]|RecordReferenceCollection|DataboxGroupable $references - * @param array|null $names - * @param int $urlTTL - * @return SubdefView[][] - */ - private function buildSubdefsViews($references, array $names = null, $urlTTL) - { - $subdefGroups = $this->app['service.media_subdef'] - ->findSubdefsByRecordReferenceFromCollection($references, $names); - - $fakeSubdefs = []; - - foreach ($subdefGroups as $index => $subdefGroup) { - if (!isset($subdefGroup['thumbnail'])) { - $fakeSubdef = new \media_subdef($this->app, $references[$index], 'thumbnail', true, []); - $fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef; - - $subdefGroups[$index]['thumbnail'] = $fakeSubdef; - } - } - - $allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups); - $allPermalinks = \media_Permalink_Adapter::getMany( - $this->app, - array_filter($allSubdefs, function (\media_subdef $subdef) use ($fakeSubdefs) { - return !isset($fakeSubdefs[spl_object_hash($subdef)]); - }) - ); - $urls = $this->app['media_accessor.subdef_url_generator'] - ->generateMany($this->getAuthenticatedUser(), $allSubdefs, $urlTTL); - - $subdefViews = []; - - /** @var \media_subdef $subdef */ - foreach ($allSubdefs as $index => $subdef) { - $subdefView = new SubdefView($subdef); - - 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; - } - - /** - * @param array $groups - * @return array|mixed - */ - private function mergeGroupsIntoOneList(array $groups) - { - // Strips keys from the internal array - array_walk($groups, function (array &$group) { - $group = array_values($group); - }); - - if ($groups) { - return call_user_func_array('array_merge', $groups); - } - - return []; - } - - /** - * @param RecordReferenceInterface[]|DataboxGroupable $references - * @return array - */ - private function retrieveSeeBusinessPerDatabox($references) - { - if (!$references instanceof DataboxGroupable) { - $references = new RecordReferenceCollection($references); - } - - $acl = $this->getAclForUser(); - - $canSeeBusiness = []; - - foreach ($references->getDataboxIds() as $databoxId) { - $canSeeBusiness[$databoxId] = $acl->can_see_business_fields($this->findDataboxById($databoxId)); - } - - $rights = []; - - foreach ($references as $index => $reference) { - $rights[$index] = $canSeeBusiness[$reference->getDataboxId()]; - } - - return $rights; - } - - /** - * @param RecordView[] $recordViews - * @param \caption_record[] $captions - * @param bool[] $canSeeBusiness - */ - private function buildCaptionViews($recordViews, $captions, $canSeeBusiness) - { - foreach ($recordViews as $index => $recordView) { - $caption = $captions[$index]; - - $captionView = new CaptionView($caption); - - $captionView->setFields($caption->get_fields(null, isset($canSeeBusiness[$index]) && (bool)$canSeeBusiness[$index])); - - $recordView->setCaption($captionView); - } - } -} diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php index f59696ddbe..44828c0cb7 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php @@ -3,22 +3,39 @@ namespace Alchemy\Phrasea\ControllerProvider\Api; use Alchemy\Phrasea\Application as PhraseaApplication; -use Alchemy\Phrasea\Controller\Api\V3Controller; +use Alchemy\Phrasea\Controller\Api\V3\V3MetadatasController; +use Alchemy\Phrasea\Controller\Api\V3\V3SearchController; +use Alchemy\Phrasea\Controller\Api\V3\V3StoriesController; use Alchemy\Phrasea\Core\Event\Listener\OAuthListener; use Silex\Application; use Silex\ControllerCollection; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; + class V3 extends Api implements ControllerProviderInterface, ServiceProviderInterface { const VERSION = '3.0.0'; + /** + * @param Application $app + * + * @uses V3MetadatasController::setmetadatasAction() + * @uses V3SearchController::searchAction() + * @uses V3StoriesController::getStoryAction() + */ public function register(Application $app) { - $app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) { - return (new V3Controller($app)) - ->setJsonBodyHelper($app['json.body_helper']); + $app['controller.api.v3.metadatas'] = $app->share(function (PhraseaApplication $app) { + return (new V3MetadatasController($app)) + ->setJsonBodyHelper($app['json.body_helper']) + ; + }); + $app['controller.api.v3.search'] = $app->share(function (PhraseaApplication $app) { + return (new V3SearchController($app)); + }); + $app['controller.api.v3.stories'] = $app->share(function (PhraseaApplication $app) { + return (new V3StoriesController($app)); }); } @@ -37,14 +54,14 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte $controllers->before(new OAuthListener()); - $controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3:getStoryAction') + $controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3.stories:getStoryAction') ->before('controller.api.v1:ensureCanAccessToRecord') ->assert('databox_id', '\d+') ->assert('record_id', '\d+'); - $controllers->match('/search/', 'controller.api.v3:searchAction'); + $controllers->match('/search/', 'controller.api.v3.search:searchAction'); - $controllers->patch('/records/{databox_id}/{record_id}/setmetadatas/', 'controller.api.v3:setmetadatasAction') + $controllers->patch('/records/{databox_id}/{record_id}/setmetadatas/', 'controller.api.v3.metadatas:setmetadatasAction') ->before('controller.api.v1:ensureCanAccessToRecord') ->before('controller.api.v1:ensureCanModifyRecord') ->assert('databox_id', '\d+') From 7ea8e95549f448367fbaf7176104a7bdcfef08b9 Mon Sep 17 00:00:00 2001 From: aynsix Date: Thu, 4 Jun 2020 12:02:52 +0300 Subject: [PATCH 06/11] remove unused webhook subdef_count --- .../WebhookSubdefEventSubscriber.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php index 04af58cf3c..dd676f92f3 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/WebhookSubdefEventSubscriber.php @@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreatedEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent; -use Alchemy\Phrasea\Core\Event\Record\SubDefinitionsCreatedEvent; use Alchemy\Phrasea\Model\Entities\WebhookEvent; use Silex\Application; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -51,27 +50,10 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface ); } - public function onSubdefsCreated(SubDefinitionsCreatedEvent $event) - { - $eventData = [ - 'databox_id' => $event->getRecord()->getDataboxId(), - 'record_id' => $event->getRecord()->getRecordId(), - 'subdef_count' => count($event->getMedia()) - ]; - - $this->app['manipulator.webhook-event']->create( - WebhookEvent::RECORD_SUBDEFS_CREATED, - WebhookEvent::RECORD_SUBDEF_TYPE, - $eventData, - [$event->getRecord()->getBaseId()] - ); - } - public static function getSubscribedEvents() { return [ RecordEvents::SUB_DEFINITION_CREATED => 'onSubdefCreated', - RecordEvents::SUB_DEFINITIONS_CREATED => 'onSubdefsCreated', RecordEvents::SUB_DEFINITION_CREATION_FAILED => 'onSubdefCreationFailed' ]; } From 733d97b5d57d385e285e6756b769f8b88aefccf9 Mon Sep 17 00:00:00 2001 From: aynsix Date: Thu, 4 Jun 2020 14:13:11 +0300 Subject: [PATCH 07/11] fix test --- .../Phrasea/Model/Repositories/WebhookEventRepositoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php b/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php index d27c00738e..b1f25ba585 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Repositories/WebhookEventRepositoryTest.php @@ -12,6 +12,6 @@ class WebhookEventRepositoryTest extends \PhraseanetTestCase { $events = self::$DI['app']['orm.em']->getRepository('Phraseanet:WebhookEvent')->findUnprocessedEvents(); // I have no clue as to why this magic number is here, probably best to discard test - $this->assertCount(41, $events); + $this->assertCount(34, $events); } } From cdadb3acc4dda01c1ee23d5562f5254323140cf0 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Thu, 4 Jun 2020 14:08:08 +0200 Subject: [PATCH 08/11] WIP refacto common code --- .../Api/V3/V3MetadatasController.php | 57 +++-- .../Controller/Api/V3/V3ResultHelpers.php | 241 +++++++++++++++++- .../Controller/Api/V3/V3StoriesController.php | 223 +--------------- .../Phrasea/ControllerProvider/Api/V3.php | 31 ++- .../Phrasea/Search/PermalinkTransformer.php | 1 - 5 files changed, 295 insertions(+), 258 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php index b016dcfcd7..ee9df9dd93 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php @@ -9,8 +9,10 @@ use Alchemy\Phrasea\Controller\Api\Result; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Core\Event\RecordEdit; use Alchemy\Phrasea\Core\PhraseaEvents; +use caption_field; use databox_field; use Exception; +use record_adapter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -53,11 +55,11 @@ class V3MetadatasController extends Controller try { // do metadatas ops if (is_array($b->metadatas)) { - $ret['metadatas_ops'] = $this->setmetadatasAction_meta($struct, $record, $b->metadatas); + $ret['metadatas_ops'] = $this->do_metadatas($struct, $record, $b->metadatas); } // do sb ops if (is_array($b->status)) { - $ret['sb_ops'] = $this->setmetadatasAction_sb($struct, $record, $b->status); + $ret['sb_ops'] = $this->do_status($struct, $record, $b->status); } } catch (Exception $e) { @@ -67,17 +69,26 @@ class V3MetadatasController extends Controller ); } + $ret = $this->getResultHelpers()->listRecord($request, $record, $this->getAclForUser()); + return Result::create($request, $ret)->createResponse(); } + + ////////////////////////////////// + /// TODO : keep multi-values uniques ! + /// it should be done in record_adapter + ////////////////////////////////// + + /** - * @param $struct - * @param $record + * @param databox_field[] $struct + * @param record_adapter $record * @param $metadatas * @return array * @throws Exception */ - private function setmetadatasAction_meta($struct, $record, $metadatas) + private function do_metadatas($struct, record_adapter $record, $metadatas) { $structByKey = []; $nameToStrucId = []; @@ -135,19 +146,19 @@ class V3MetadatasController extends Controller switch ($_m->action) { case 'set': - $ops = $this->setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values); + $ops = $this->metadata_set($struct_fields, $caption_fields, $meta_id, $values); break; case 'add': - $ops = $this->setmetadatasAction_add($struct_fields, $values); + $ops = $this->metadata_add($struct_fields, $values); break; case 'delete': - $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, null); + $ops = $this->metadata_replace($caption_fields, $meta_id, $match_method, $values, null); break; case 'replace': if (!is_string($_m->replace_with) && !is_null($_m->replace_with)) { throw new Exception("bad \"replace_with\" for action \"replace\"."); } - $ops = $this->setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with); + $ops = $this->metadata_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with); break; default: throw new Exception(sprintf("bad action (%s).", $action)); @@ -156,6 +167,8 @@ class V3MetadatasController extends Controller $metadatas_ops = array_merge($metadatas_ops, $ops); } + // $record->set_metadatas($metadatas_ops, true); + return $metadatas_ops; } @@ -166,7 +179,7 @@ class V3MetadatasController extends Controller * @return array * @throws Exception */ - private function setmetadatasAction_sb($struct, $record, $statuses) + private function do_status($struct, $record, $statuses) { $datas = strrev($record->getStatus()); @@ -183,7 +196,7 @@ class V3MetadatasController extends Controller $datas = substr($datas, 0, ($n)) . $value . substr($datas, ($n + 1)); } - // $record->setStatus(strrev($datas)); + $record->setStatus(strrev($datas)); // @todo Move event dispatch inside record_adapter class (keeps things encapsulated) $this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record)); @@ -204,21 +217,16 @@ class V3MetadatasController extends Controller return false; } - ////////////////////////////////// - /// TODO : keep multi-values uniques ! - /// it should be done in record_adapter - ////////////////////////////////// - /** * @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name - * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) * @param int|null $meta_id * @param string[] $values * * @return array ops to execute * @throws Exception */ - private function setmetadatasAction_set($struct_fields, $caption_fields, $meta_id, $values) + private function metadata_set($struct_fields, $caption_fields, $meta_id, $values) { $ops = []; @@ -271,7 +279,7 @@ class V3MetadatasController extends Controller * @return array ops to execute * @throws Exception */ - private function setmetadatasAction_add($struct_fields, $values) + private function metadata_add($struct_fields, $values) { $ops = []; @@ -296,7 +304,7 @@ class V3MetadatasController extends Controller } /** - * @param \caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) + * @param caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set) * @param int|null $meta_id * @param string $match_method "strict" | "ignore_case" | "regexp" * @param string[] $values @@ -304,7 +312,7 @@ class V3MetadatasController extends Controller * * @return array ops to execute */ - private function setmetadatasAction_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) + private function metadata_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) { $ops = []; @@ -357,15 +365,18 @@ class V3MetadatasController extends Controller */ private function getResultHelpers() { + return $this->app['controller.api.v3.resulthelpers']; + /* static $rh = null; if(is_null($rh)) { $rh = new V3ResultHelpers( - $this, $this->getConf(), - $this->app['media_accessor.subdef_url_generator'] + $this->app['media_accessor.subdef_url_generator'], + $this->getAuthenticator() ); } return $rh; + */ } } diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php index 38a6da1c1b..0438749d98 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php @@ -3,33 +3,37 @@ namespace Alchemy\Phrasea\Controller\Api\V3; -use Alchemy\Phrasea\Controller\Controller; +use ACL; +use Alchemy\Phrasea\Authentication\Authenticator; use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Media\MediaSubDefinitionUrlGenerator; +use caption_field; use databox_status; +use media_Permalink_Adapter; +use media_subdef; use record_adapter; +use Symfony\Component\HttpFoundation\Request; class V3ResultHelpers { - /** @var controller */ - private $controller; + /** @var PropertyAccess */ + private $conf; /** @var MediaSubDefinitionUrlGenerator */ private $urlgenerator; - /** @var PropertyAccess */ - private $conf; + /** @var Authenticator */ + private $authenticator; - public function __construct($controller, $conf, $urlgenerator) + public function __construct($conf, $urlgenerator, Authenticator $authenticator) { - $this->controller = $controller; $this->urlgenerator = $urlgenerator; $this->conf = $conf; + $this->authenticator = $authenticator; } - /** * Retrieve detailed information about one status * @@ -49,4 +53,225 @@ class V3ResultHelpers return $ret; } + public function listEmbeddableMedia(Request $request, record_adapter $record, media_subdef $media, ACL $acl) + { + if (!$media->is_physically_present()) { + return null; + } + + if ($this->getAuthenticator()->isAuthenticated()) { + if ($media->get_name() !== 'document' + && false === $acl->has_access_to_subdef($record, $media->get_name()) + ) { + return null; + } + if ($media->get_name() === 'document' + && !$acl->has_right_on_base($record->getBaseId(), ACL::CANDWNLDHD) + && !$acl->has_hd_grant($record) + ) { + return null; + } + } + + if ($media->get_permalink() instanceof media_Permalink_Adapter) { + $permalink = $this->listPermalink($media->get_permalink()); + } else { + $permalink = null; + } + + $urlTTL = (int) $request->get( + 'subdef_url_ttl', + $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']) + ); + if ($urlTTL < 0) { + $urlTTL = -1; + } + $issuer = $this->getAuthenticator()->getUser(); + + return [ + 'name' => $media->get_name(), + 'permalink' => $permalink, + 'height' => $media->get_height(), + 'width' => $media->get_width(), + 'filesize' => $media->get_size(), + 'devices' => $media->getDevices(), + 'player_type' => $media->get_type(), + 'mime_type' => $media->get_mime(), + 'substituted' => $media->is_substituted(), + 'created_on' => $media->get_creation_date()->format(DATE_ATOM), + 'updated_on' => $media->get_modification_date()->format(DATE_ATOM), + 'url' => $this->urlgenerator->generate($issuer, $media, $urlTTL), + 'url_ttl' => $urlTTL, + ]; + } + + /** + * @param media_Permalink_Adapter $permalink + * @return array + * + * @todo fix duplicated code + * @noinspection DuplicatedCode + */ + public function listPermalink(media_Permalink_Adapter $permalink) + { + $downloadUrl = $permalink->get_url(); + $downloadUrl->getQuery()->set('download', '1'); + + return [ + 'created_on' => $permalink->get_created_on()->format(DATE_ATOM), + 'id' => $permalink->get_id(), + 'is_activated' => $permalink->get_is_activated(), + 'label' => $permalink->get_label(), + 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), + 'page_url' => $permalink->get_page(), + 'download_url' => (string)$downloadUrl, + 'url' => (string)$permalink->get_url(), + ]; + } + + /** + * Retrieve detailed information about one record + * + * @param Request $request + * @param record_adapter $record + * @param ACL $aclforuser + * @return array + */ + public function listRecord(Request $request, record_adapter $record, ACL $aclforuser) + { + $technicalInformation = []; + foreach ($record->get_technical_infos()->getValues() as $name => $value) { + $technicalInformation[] = ['name' => $name, 'value' => $value]; + } + + $data = [ + 'databox_id' => $record->getDataboxId(), + 'record_id' => $record->getRecordId(), + 'mime_type' => $record->getMimeType(), + 'title' => $record->get_title(), + 'original_name' => $record->get_original_name(), + 'updated_on' => $record->getUpdated()->format(DATE_ATOM), + 'created_on' => $record->getCreated()->format(DATE_ATOM), + 'collection_id' => $record->getCollectionId(), + 'base_id' => $record->getBaseId(), + 'sha256' => $record->getSha256(), + 'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail(), $aclforuser), + 'technical_informations' => $technicalInformation, + 'phrasea_type' => $record->getType(), + 'uuid' => $record->getUuid(), + ]; + + if ($request->attributes->get('_extended', false)) { + $data = array_merge($data, [ + 'subdefs' => $this->listRecordEmbeddableMedias($request, $record, $aclforuser), + 'metadata' => $this->listRecordMetadata($record, $aclforuser), + 'status' => $this->listRecordStatus($record), + 'caption' => $this->listRecordCaption($record, $aclforuser), + ]); + } + + return $data; + } + + /** + * @param Request $request + * @param record_adapter $record + * @return array + */ + private function listRecordEmbeddableMedias(Request $request, record_adapter $record, ACL $acl) + { + $subdefs = []; + + foreach ($record->get_embedable_medias([], []) as $name => $media) { + if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media, $acl)) { + $subdefs[] = $subdef; + } + } + + return $subdefs; + } + + /** + * List all fields of given record + * + * @param record_adapter $record + * @param ACL $acl + * @return array + */ + private function listRecordMetadata(record_adapter $record, ACL $acl) + { + $includeBusiness = $acl->can_see_business_fields($record->getDatabox()); + + return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness)); + } + + /** + * @param caption_field[] $fields + * @return array + */ + private function listRecordCaptionFields($fields) + { + $ret = []; + + foreach ($fields as $field) { + $databox_field = $field->get_databox_field(); + + $fieldData = [ + 'meta_structure_id' => $field->get_meta_struct_id(), + 'name' => $field->get_name(), + 'labels' => [ + 'fr' => $databox_field->get_label('fr'), + 'en' => $databox_field->get_label('en'), + 'de' => $databox_field->get_label('de'), + 'nl' => $databox_field->get_label('nl'), + ], + ]; + + foreach ($field->get_values() as $value) { + $data = [ + 'meta_id' => $value->getId(), + 'value' => $value->getValue(), + ]; + + $ret[] = $fieldData + $data; + } + } + + return $ret; + } + + /** + * @param record_adapter $record + * @param ACL $acl + * @return array + */ + private function listRecordCaption(record_adapter $record, ACL $acl) + { + $includeBusiness = $acl->can_see_business_fields($record->getDatabox()); + + $caption = []; + + foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) { + $caption[] = [ + 'meta_structure_id' => $field->get_meta_struct_id(), + 'name' => $field->get_name(), + 'value' => $field->get_serialized_values(';'), + ]; + } + + return $caption; + } + + + //////////////////////// + private function getAuthenticator() + { + return $this->authenticator; + } + + protected function getConf() + { + return $this->conf; + } + } diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php index 14fccc741d..2033c03fed 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php @@ -2,7 +2,6 @@ namespace Alchemy\Phrasea\Controller\Api\V3; -use ACL; use Alchemy\Phrasea\Application\Helper\DispatcherAware; use Alchemy\Phrasea\Application\Helper\JsonBodyAware; use Alchemy\Phrasea\Controller\Api\Result; @@ -10,12 +9,9 @@ use Alchemy\Phrasea\Controller\Api\V1Controller; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Model\RecordReferenceInterface; use Alchemy\Phrasea\Record\RecordReferenceCollection; -use caption_field; use caption_record; use databox_Field_DCESAbstract; use Exception; -use media_Permalink_Adapter; -use media_subdef; use record_adapter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -51,23 +47,6 @@ class V3StoriesController extends Controller } } - /** - * @return V3ResultHelpers - */ - private function getResultHelpers() - { - static $rh = null; - - if(is_null($rh)) { - $rh = new V3ResultHelpers( - $this, - $this->getConf(), - $this->app['media_accessor.subdef_url_generator'] - ); - } - return $rh; - } - /** * Retrieve detailed information about one story * @@ -107,7 +86,7 @@ class V3StoriesController extends Controller 'created_on' => $story->getCreated()->format(DATE_ATOM), 'collection_id' => $story->getCollectionId(), 'base_id' => $story->getBaseId(), - 'thumbnail' => $this->listEmbeddableMedia($request, $story, $story->get_thumbnail()), + 'thumbnail' => $this->getResultHelpers()->listEmbeddableMedia($request, $story, $story->get_thumbnail(), $this->getAclForUser()), 'uuid' => $story->getUuid(), 'metadatas' => [ '@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG, @@ -149,211 +128,19 @@ class V3StoriesController extends Controller foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) { $record->setTechnicalDataSet($technicalData[$index]); - $data[$index] = $this->listRecord($request, $record); - } - - return $data; - } - public function listEmbeddableMedia(Request $request, record_adapter $record, media_subdef $media) - { - if (!$media->is_physically_present()) { - return null; - } - - if ($this->getAuthenticator()->isAuthenticated()) { - $acl = $this->getAclForUser(); - if ($media->get_name() !== 'document' - && false === $acl->has_access_to_subdef($record, $media->get_name()) - ) { - return null; - } - if ($media->get_name() === 'document' - && !$acl->has_right_on_base($record->getBaseId(), ACL::CANDWNLDHD) - && !$acl->has_hd_grant($record) - ) { - return null; - } - } - - if ($media->get_permalink() instanceof media_Permalink_Adapter) { - $permalink = $this->listPermalink($media->get_permalink()); - } else { - $permalink = null; - } - - $urlTTL = (int) $request->get( - 'subdef_url_ttl', - $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']) - ); - if ($urlTTL < 0) { - $urlTTL = -1; - } - $issuer = $this->getAuthenticatedUser(); - - return [ - 'name' => $media->get_name(), - 'permalink' => $permalink, - 'height' => $media->get_height(), - 'width' => $media->get_width(), - 'filesize' => $media->get_size(), - 'devices' => $media->getDevices(), - 'player_type' => $media->get_type(), - 'mime_type' => $media->get_mime(), - 'substituted' => $media->is_substituted(), - 'created_on' => $media->get_creation_date()->format(DATE_ATOM), - 'updated_on' => $media->get_modification_date()->format(DATE_ATOM), - 'url' => $this->app['media_accessor.subdef_url_generator']->generate($issuer, $media, $urlTTL), - 'url_ttl' => $urlTTL, - ]; - } - - private function listPermalink(media_Permalink_Adapter $permalink) - { - $downloadUrl = $permalink->get_url(); - $downloadUrl->getQuery()->set('download', '1'); - - return [ - 'created_on' => $permalink->get_created_on()->format(DATE_ATOM), - 'id' => $permalink->get_id(), - 'is_activated' => $permalink->get_is_activated(), - /** @Ignore */ - 'label' => $permalink->get_label(), - 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), - 'page_url' => $permalink->get_page(), - 'download_url' => (string)$downloadUrl, - 'url' => (string)$permalink->get_url(), - ]; - } - - - /** - * Retrieve detailed information about one record - * - * @param Request $request - * @param record_adapter $record - * @return array - */ - public function listRecord(Request $request, record_adapter $record) - { - $technicalInformation = []; - foreach ($record->get_technical_infos()->getValues() as $name => $value) { - $technicalInformation[] = ['name' => $name, 'value' => $value]; - } - - $data = [ - 'databox_id' => $record->getDataboxId(), - 'record_id' => $record->getRecordId(), - 'mime_type' => $record->getMimeType(), - 'title' => $record->get_title(), - 'original_name' => $record->get_original_name(), - 'updated_on' => $record->getUpdated()->format(DATE_ATOM), - 'created_on' => $record->getCreated()->format(DATE_ATOM), - 'collection_id' => $record->getCollectionId(), - 'base_id' => $record->getBaseId(), - 'sha256' => $record->getSha256(), - 'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail()), - 'technical_informations' => $technicalInformation, - 'phrasea_type' => $record->getType(), - 'uuid' => $record->getUuid(), - ]; - - if ($request->attributes->get('_extended', false)) { - $data = array_merge($data, [ - 'subdefs' => $this->listRecordEmbeddableMedias($request, $record), - 'metadata' => $this->listRecordMetadata($record), - 'status' => $this->getResultHelpers()->listRecordStatus($record), - 'caption' => $this->listRecordCaption($record), - ]); + $data[$index] = $this->getResultHelpers()->listRecord($request, $record, $this->getAclForUser()); } return $data; } - /** - * @param Request $request - * @param record_adapter $record - * @return array - */ - private function listRecordEmbeddableMedias(Request $request, record_adapter $record) - { - $subdefs = []; - - foreach ($record->get_embedable_medias([], []) as $name => $media) { - if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media)) { - $subdefs[] = $subdef; - } - } - - return $subdefs; - } /** - * List all fields of given record - * - * @param record_adapter $record - * @return array + * @return V3ResultHelpers */ - private function listRecordMetadata(record_adapter $record) + private function getResultHelpers() { - $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); - - return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness)); - } - - /** - * @param caption_field[] $fields - * @return array - */ - private function listRecordCaptionFields($fields) - { - $ret = []; - - foreach ($fields as $field) { - $databox_field = $field->get_databox_field(); - - $fieldData = [ - 'meta_structure_id' => $field->get_meta_struct_id(), - 'name' => $field->get_name(), - 'labels' => [ - 'fr' => $databox_field->get_label('fr'), - 'en' => $databox_field->get_label('en'), - 'de' => $databox_field->get_label('de'), - 'nl' => $databox_field->get_label('nl'), - ], - ]; - - foreach ($field->get_values() as $value) { - $data = [ - 'meta_id' => $value->getId(), - 'value' => $value->getValue(), - ]; - - $ret[] = $fieldData + $data; - } - } - - return $ret; - } - - /** - * @param record_adapter $record - * @return array - */ - private function listRecordCaption(record_adapter $record) - { - $includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox()); - - $caption = []; - - foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) { - $caption[] = [ - 'meta_structure_id' => $field->get_meta_struct_id(), - 'name' => $field->get_name(), - 'value' => $field->get_serialized_values(';'), - ]; - } - - return $caption; + return $this->app['controller.api.v3.resulthelpers']; } } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php index 44828c0cb7..64373dedff 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php @@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\ControllerProvider\Api; use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\Api\V3\V3MetadatasController; +use Alchemy\Phrasea\Controller\Api\V3\V3ResultHelpers; use Alchemy\Phrasea\Controller\Api\V3\V3SearchController; use Alchemy\Phrasea\Controller\Api\V3\V3StoriesController; use Alchemy\Phrasea\Core\Event\Listener\OAuthListener; @@ -17,19 +18,20 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte { const VERSION = '3.0.0'; - /** - * @param Application $app - * - * @uses V3MetadatasController::setmetadatasAction() - * @uses V3SearchController::searchAction() - * @uses V3StoriesController::getStoryAction() - */ public function register(Application $app) { + $app['controller.api.v3.resulthelpers'] = $app->share(function (PhraseaApplication $app) { + return (new V3ResultHelpers( + $app['conf'], + $app['media_accessor.subdef_url_generator'], + $app['authentication'] + )); + }); $app['controller.api.v3.metadatas'] = $app->share(function (PhraseaApplication $app) { return (new V3MetadatasController($app)) ->setJsonBodyHelper($app['json.body_helper']) - ; + ->setDispatcher($app['dispatcher']) + ; }); $app['controller.api.v3.search'] = $app->share(function (PhraseaApplication $app) { return (new V3SearchController($app)); @@ -54,18 +56,31 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte $controllers->before(new OAuthListener()); + /** + * @uses V3StoriesController::getStoryAction() + */ $controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3.stories:getStoryAction') ->before('controller.api.v1:ensureCanAccessToRecord') ->assert('databox_id', '\d+') ->assert('record_id', '\d+'); + /** + * @uses V3SearchController::searchAction() + */ $controllers->match('/search/', 'controller.api.v3.search:searchAction'); + /** + * @uses V3MetadatasController::setmetadatasAction() + */ $controllers->patch('/records/{databox_id}/{record_id}/setmetadatas/', 'controller.api.v3.metadatas:setmetadatasAction') ->before('controller.api.v1:ensureCanAccessToRecord') ->before('controller.api.v1:ensureCanModifyRecord') ->assert('databox_id', '\d+') ->assert('record_id', '\d+'); + + /** + * @uses \Alchemy\Phrasea\Controller\Api\V1Controller::getBadRequestAction() + */ $controllers->match('/records/{any_id}/{anyother_id}/setmetadatas/', 'controller.api.v1:getBadRequestAction'); return $controllers; diff --git a/lib/Alchemy/Phrasea/Search/PermalinkTransformer.php b/lib/Alchemy/Phrasea/Search/PermalinkTransformer.php index 6bb22be37d..c192b769be 100644 --- a/lib/Alchemy/Phrasea/Search/PermalinkTransformer.php +++ b/lib/Alchemy/Phrasea/Search/PermalinkTransformer.php @@ -25,7 +25,6 @@ class PermalinkTransformer extends TransformerAbstract 'created_on' => $permalink->get_created_on()->format(DATE_ATOM), 'id' => $permalink->get_id(), 'is_activated' => $permalink->get_is_activated(), - /** @Ignore */ 'label' => $permalink->get_label(), 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), 'page_url' => $permalink->get_page(), From 0b099878f52b4b09fc2adc01cf942a6f05215488 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Thu, 4 Jun 2020 20:54:21 +0200 Subject: [PATCH 09/11] OK to test --- .../Api/V3/V3MetadatasController.php | 86 +++++++++---------- lib/classes/record/adapter.php | 16 ++-- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php index ee9df9dd93..a187ad4d47 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php @@ -48,18 +48,18 @@ class V3MetadatasController extends Controller return $this->app['controller.api.v1']->getBadRequestAction($request, 'Bad JSON'); } - $ret = [ + $debug = [ 'metadatas_ops' => null, 'sb_ops' => null, ]; try { // do metadatas ops if (is_array($b->metadatas)) { - $ret['metadatas_ops'] = $this->do_metadatas($struct, $record, $b->metadatas); + $debug['metadatas_ops'] = $this->do_metadatas($struct, $record, $b->metadatas); } // do sb ops if (is_array($b->status)) { - $ret['sb_ops'] = $this->do_status($struct, $record, $b->status); + $debug['sb_ops'] = $this->do_status($struct, $record, $b->status); } } catch (Exception $e) { @@ -69,6 +69,9 @@ class V3MetadatasController extends Controller ); } + // @todo Move event dispatch inside record_adapter class (keeps things encapsulated) + $this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record)); + $ret = $this->getResultHelpers()->listRecord($request, $record, $this->getAclForUser()); return Result::create($request, $ret)->createResponse(); @@ -92,10 +95,12 @@ class V3MetadatasController extends Controller { $structByKey = []; $nameToStrucId = []; + $allStructFields = []; foreach ($struct as $f) { $nameToStrucId[$f->get_name()] = $f->get_id(); - $structByKey[$f->get_id()] = $f; - $structByKey[$f->get_name()] = &$structByKey[$f->get_id()]; + $allStructFields[$f->get_id()] = $f; + $structByKey[$f->get_id()] = &$allStructFields[$f->get_id()];; + $structByKey[$f->get_name()] = &$allStructFields[$f->get_id()]; } $metadatas_ops = []; @@ -107,7 +112,8 @@ class V3MetadatasController extends Controller // select fields that match meta_struct_id or field_name (can be arrays) $fields_list = null; // to filter caption_fields from record, default all $struct_fields = []; // struct fields that match meta_struct_id or field_name - if(($field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name) !== null) { // can be null if none defined (=match all) + $field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name; // can be null if none defined (=match all) + if($field_keys !== null) { if (!is_array($field_keys)) { $field_keys = [$field_keys]; } @@ -119,6 +125,10 @@ class V3MetadatasController extends Controller } } } + else { + // no meta_struct_id, no field_name --> match all struct fields ! + $struct_fields = $allStructFields; + } $caption_fields = $record->get_caption()->get_fields($fields_list, true); $meta_id = is_null($_m->meta_id) ? null : (int)($_m->meta_id); @@ -133,11 +143,15 @@ class V3MetadatasController extends Controller $values = []; if(is_array($_m->value)) { foreach ($_m->value as $v) { - $values[] = is_null($v) ? null : (string)$v; + if(($v = trim((string)$v)) !== '') { + $values[] = $v; + } } } else { - $values = is_null($_m->value) ? [] : [(string)($_m->value)]; + if(($v = trim((string)($_m->value))) !== '') { + $values[] = $v; + } } if(!($action = (string)($_m->action))) { @@ -167,7 +181,7 @@ class V3MetadatasController extends Controller $metadatas_ops = array_merge($metadatas_ops, $ops); } - // $record->set_metadatas($metadatas_ops, true); + $record->set_metadatas($metadatas_ops, true); return $metadatas_ops; } @@ -198,9 +212,6 @@ class V3MetadatasController extends Controller $record->setStatus(strrev($datas)); - // @todo Move event dispatch inside record_adapter class (keeps things encapsulated) - $this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record)); - return ["status" => $this->getResultHelpers()->listRecordStatus($record)]; } @@ -232,12 +243,12 @@ class V3MetadatasController extends Controller // if one field was multi-valued and no meta_id was set, we must delete all values foreach ($caption_fields as $cf) { - if ($cf->is_multi() && is_null($meta_id)) { - foreach ($cf->get_values() as $field_value) { + foreach ($cf->get_values() as $field_value) { + if (is_null($meta_id) || $field_value->getId() === (int)$meta_id) { $ops[] = [ 'meta_struct_id' => $cf->get_meta_struct_id(), 'meta_id' => $field_value->getId(), - 'value' => null + 'value' => '' ]; } } @@ -247,7 +258,7 @@ class V3MetadatasController extends Controller if($sf->is_multi()) { // add the non-null value(s) foreach ($values as $value) { - if (!is_null($value)) { + if ($value) { $ops[] = [ 'meta_struct_id' => $sf->get_id(), 'meta_id' => $meta_id, // can be null @@ -261,11 +272,13 @@ class V3MetadatasController extends Controller if(count($values) > 1) { throw new Exception(sprintf("setting mono-valued (%s) requires only one value.", $sf->get_name())); } - $ops[] = [ - 'meta_struct_id' => $sf->get_id(), - 'meta_id' => $meta_id, // probably null, - 'value' => $values[0] - ]; + if( ($value = $values[0]) ) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // probably null, + 'value' => $value + ]; + } } } @@ -288,15 +301,12 @@ class V3MetadatasController extends Controller if(!$sf->is_multi()) { throw new Exception(sprintf("can't \"add\" to mono-valued (%s).", $sf->get_name())); } - // add the non-null value(s) foreach ($values as $value) { - if (!is_null($value)) { - $ops[] = [ - 'meta_struct_id' => $sf->get_id(), - 'meta_id' => null, - 'value' => $value - ]; - } + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => null, + 'value' => $value + ]; } } @@ -316,6 +326,8 @@ class V3MetadatasController extends Controller { $ops = []; + $replace_with = trim((string)$replace_with); + foreach ($caption_fields as $cf) { // match all ? if(is_null($meta_id) && count($values) == 0) { @@ -343,8 +355,8 @@ class V3MetadatasController extends Controller foreach ($values as $value) { foreach ($cf->get_values() as $field_value) { $rw = $replace_with; - if($match_method=='regexp' && !is_null($replace_with)) { - $rw = preg_replace($value, $replace_with, $field_value->getValue()); + if($match_method=='regexp' && $rw != '') { + $rw = preg_replace($value, $rw, $field_value->getValue()); } if ($this->match($value, $match_method, $field_value->getValue())) { $ops[] = [ @@ -366,17 +378,5 @@ class V3MetadatasController extends Controller private function getResultHelpers() { return $this->app['controller.api.v3.resulthelpers']; - /* - static $rh = null; - - if(is_null($rh)) { - $rh = new V3ResultHelpers( - $this->getConf(), - $this->app['media_accessor.subdef_url_generator'], - $this->getAuthenticator() - ); - } - return $rh; - */ } } diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index 9d94822566..e572b13a34 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -1054,10 +1054,13 @@ class record_adapter implements RecordInterface, cache_cacheableInterface } } - if (trim($params['meta_id']) !== '') { - $tmp_val = trim($params['value']); + $tmp_val = trim($params['value']); - $caption_field_value = $caption_field->get_value($params['meta_id']); + if (trim($params['meta_id']) !== '') { + + if(is_null($caption_field_value = $caption_field->get_value($params['meta_id']))) { + return $this; + } if ($tmp_val === '') { $caption_field_value->delete(); @@ -1068,8 +1071,11 @@ class record_adapter implements RecordInterface, cache_cacheableInterface $caption_field_value->setVocab($vocab, $vocab_id); } } - } else { - $caption_field_value = caption_Field_Value::create($this->app, $databox_field, $this, $params['value'], $vocab, $vocab_id); + } + else { + if($tmp_val !== '') { + caption_Field_Value::create($this->app, $databox_field, $this, $params['value'], $vocab, $vocab_id); + } } return $this; From e47312f02e7743215d68d1cabe93ccf235217db8 Mon Sep 17 00:00:00 2001 From: Harrys Ravalomanana Date: Fri, 5 Jun 2020 13:32:53 +0400 Subject: [PATCH 10/11] PHRAS-3125 Fix on basket browser display --- package.json | 2 +- templates/web/prod/WorkZone/Browser/Basket.html.twig | 8 ++++---- templates/web/prod/WorkZone/Browser/Results.html.twig | 4 ++-- yarn.lock | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 062865c82a..76aa058cd1 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "normalize-css": "^2.1.0", "npm": "^6.0.0", "npm-modernizr": "^2.8.3", - "phraseanet-production-client": "0.34.214-d", + "phraseanet-production-client": "0.34.218-d", "requirejs": "^2.3.5", "tinymce": "^4.0.28", "underscore": "^1.8.3", diff --git a/templates/web/prod/WorkZone/Browser/Basket.html.twig b/templates/web/prod/WorkZone/Browser/Basket.html.twig index 2618d70e22..4ac0e0f25e 100644 --- a/templates/web/prod/WorkZone/Browser/Basket.html.twig +++ b/templates/web/prod/WorkZone/Browser/Basket.html.twig @@ -1,6 +1,6 @@
- +
diff --git a/templates/web/prod/WorkZone/Browser/Results.html.twig b/templates/web/prod/WorkZone/Browser/Results.html.twig index 9ac3a5fa06..ee9f1f5d18 100644 --- a/templates/web/prod/WorkZone/Browser/Results.html.twig +++ b/templates/web/prod/WorkZone/Browser/Results.html.twig @@ -40,8 +40,8 @@   {% endif %} - - {{ Basket.getName() }} + + {{ Basket.getName()|length > 80 ? Basket.getName()|slice(0, 77) ~ '...' : Basket.getName() }}
{{ Basket.getElements().count() }} {{ ' records' }}
diff --git a/yarn.lock b/yarn.lock index f641a68101..7050a03240 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7578,10 +7578,10 @@ phraseanet-common@^0.4.5-d: js-cookie "^2.1.0" pym.js "^1.3.1" -phraseanet-production-client@0.34.214-d: - version "0.34.214-d" - resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.214-d.tgz#55675c3925c81626ddd8b9498e02c9d106a51d93" - integrity sha512-RXg7KMgpPtv4rR3c2tCNMr7RgneAZjvBbFyGVx+a42nPkkZMnulweDm1R2o5d/eOcW6uJWPWczSmCCS0GqNnVQ== +phraseanet-production-client@0.34.218-d: + version "0.34.218-d" + resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.218-d.tgz#6a5c1686a3c2bf5e334adb962fdd529c9bd59bae" + integrity sha512-rU5a/7fqhA8CIcWUicBAhRIO8Ga50V6PRiOnPMXnCxZfQWccG/sFbLSbvA2Sa+58eNSRhf7Z2QNu+S90KgoW1g== dependencies: "@mapbox/mapbox-gl-language" "^0.9.2" "@turf/turf" "^5.1.6" From 659cb69c70ca08e645be21fed06cdf7716c2d917 Mon Sep 17 00:00:00 2001 From: Harrys Ravalomanana Date: Fri, 5 Jun 2020 15:19:01 +0400 Subject: [PATCH 11/11] PHRAS-3125 upgrade phraseanet-production-client --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 76aa058cd1..20d2c861a9 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "normalize-css": "^2.1.0", "npm": "^6.0.0", "npm-modernizr": "^2.8.3", - "phraseanet-production-client": "0.34.218-d", + "phraseanet-production-client": "0.34.219-d", "requirejs": "^2.3.5", "tinymce": "^4.0.28", "underscore": "^1.8.3", diff --git a/yarn.lock b/yarn.lock index 7050a03240..2940fd971a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7578,10 +7578,10 @@ phraseanet-common@^0.4.5-d: js-cookie "^2.1.0" pym.js "^1.3.1" -phraseanet-production-client@0.34.218-d: - version "0.34.218-d" - resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.218-d.tgz#6a5c1686a3c2bf5e334adb962fdd529c9bd59bae" - integrity sha512-rU5a/7fqhA8CIcWUicBAhRIO8Ga50V6PRiOnPMXnCxZfQWccG/sFbLSbvA2Sa+58eNSRhf7Z2QNu+S90KgoW1g== +phraseanet-production-client@0.34.219-d: + version "0.34.219-d" + resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.219-d.tgz#9bd118d0588af0ca58b60ef6b8f2514b6a9ffe63" + integrity sha512-QHLsPBRjtE4wSH5jib6gN71U70lAtoloM5E+NfmFKHLBMv0B8xqcxJKVW7EbRX4DZTpsLCu5tuZ7vzd5PpbkOg== dependencies: "@mapbox/mapbox-gl-language" "^0.9.2" "@turf/turf" "^5.1.6"
@@ -23,9 +23,9 @@ {% endif %} - - {{ Basket.getName() }} - + + {{ Basket.getName()|length > 55 ? Basket.getName()|slice(0, 52) ~ '...' : Basket.getName() }} +