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..a187ad4d47 --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3MetadatasController.php @@ -0,0 +1,382 @@ +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'); + } + + $debug = [ + 'metadatas_ops' => null, + 'sb_ops' => null, + ]; + try { + // do metadatas ops + if (is_array($b->metadatas)) { + $debug['metadatas_ops'] = $this->do_metadatas($struct, $record, $b->metadatas); + } + // do sb ops + if (is_array($b->status)) { + $debug['sb_ops'] = $this->do_status($struct, $record, $b->status); + } + } + catch (Exception $e) { + return $this->app['controller.api.v1']->getBadRequestAction( + $request, + $e->getMessage() + ); + } + + // @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(); + } + + + ////////////////////////////////// + /// TODO : keep multi-values uniques ! + /// it should be done in record_adapter + ////////////////////////////////// + + + /** + * @param databox_field[] $struct + * @param record_adapter $record + * @param $metadatas + * @return array + * @throws Exception + */ + private function do_metadatas($struct, record_adapter $record, $metadatas) + { + $structByKey = []; + $nameToStrucId = []; + $allStructFields = []; + foreach ($struct as $f) { + $nameToStrucId[$f->get_name()] = $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 = []; + 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 + $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]; + } + $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]; + } + } + } + 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); + + 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) { + if(($v = trim((string)$v)) !== '') { + $values[] = $v; + } + } + } + else { + if(($v = trim((string)($_m->value))) !== '') { + $values[] = $v; + } + } + + if(!($action = (string)($_m->action))) { + $action = 'set'; + } + + switch ($_m->action) { + case 'set': + $ops = $this->metadata_set($struct_fields, $caption_fields, $meta_id, $values); + break; + case 'add': + $ops = $this->metadata_add($struct_fields, $values); + break; + case 'delete': + $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->metadata_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); + } + + $record->set_metadatas($metadatas_ops, true); + + return $metadatas_ops; + } + + /** + * @param $struct + * @param $record + * @param $statuses + * @return array + * @throws Exception + */ + private function do_status($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)); + + 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; + } + + /** + * @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 metadata_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) { + 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' => '' + ]; + } + } + } + // 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 ($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())); + } + if( ($value = $values[0]) ) { + $ops[] = [ + 'meta_struct_id' => $sf->get_id(), + 'meta_id' => $meta_id, // probably null, + 'value' => $value + ]; + } + } + } + + 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 metadata_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())); + } + foreach ($values as $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 metadata_replace($caption_fields, $meta_id, $match_method, $values, $replace_with) + { + $ops = []; + + $replace_with = trim((string)$replace_with); + + 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' && $rw != '') { + $rw = preg_replace($value, $rw, $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() + { + return $this->app['controller.api.v3.resulthelpers']; + } +} 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..0438749d98 --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3ResultHelpers.php @@ -0,0 +1,277 @@ +urlgenerator = $urlgenerator; + $this->conf = $conf; + $this->authenticator = $authenticator; + } + + /** + * 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; + } + + 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/V3Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php similarity index 56% rename from lib/Alchemy/Phrasea/Controller/Api/V3Controller.php rename to lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php index 4dcab291fe..c040bde5a5 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V3Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3SearchController.php @@ -1,8 +1,11 @@ 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')); - } - } + use JsonBodyAware; + use DispatcherAware; /** * Search for results @@ -101,7 +89,7 @@ class V3Controller extends Controller $includeResolver = new IncludeResolver($transformerResolver); - $fractal = new \League\Fractal\Manager(); + $fractal = new FractalManager(); $fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher'])); $fractal->parseIncludes($this->resolveSearchIncludes($request)); @@ -125,314 +113,7 @@ class V3Controller extends Controller 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 @@ -660,7 +341,7 @@ class V3Controller extends Controller } /** - * @param RecordCollection|\record_adapter[] $references + * @param RecordCollection|record_adapter[] $references * @return RecordView[] */ private function buildRecordViews($references) @@ -693,7 +374,7 @@ class V3Controller extends Controller foreach ($subdefGroups as $index => $subdefGroup) { if (!isset($subdefGroup['thumbnail'])) { - $fakeSubdef = new \media_subdef($this->app, $references[$index], 'thumbnail', true, []); + $fakeSubdef = new media_subdef($this->app, $references[$index], 'thumbnail', true, []); $fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef; $subdefGroups[$index]['thumbnail'] = $fakeSubdef; @@ -701,9 +382,9 @@ class V3Controller extends Controller } $allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups); - $allPermalinks = \media_Permalink_Adapter::getMany( + $allPermalinks = media_Permalink_Adapter::getMany( $this->app, - array_filter($allSubdefs, function (\media_subdef $subdef) use ($fakeSubdefs) { + array_filter($allSubdefs, function (media_subdef $subdef) use ($fakeSubdefs) { return !isset($fakeSubdefs[spl_object_hash($subdef)]); }) ); @@ -712,7 +393,7 @@ class V3Controller extends Controller $subdefViews = []; - /** @var \media_subdef $subdef */ + /** @var media_subdef $subdef */ foreach ($allSubdefs as $index => $subdef) { $subdefView = new SubdefView($subdef); @@ -728,7 +409,7 @@ class V3Controller extends Controller $reorderedGroups = []; - /** @var \media_subdef[] $subdefGroup */ + /** @var media_subdef[] $subdefGroup */ foreach ($subdefGroups as $index => $subdefGroup) { $reordered = []; @@ -789,7 +470,7 @@ class V3Controller extends Controller /** * @param RecordView[] $recordViews - * @param \caption_record[] $captions + * @param caption_record[] $captions * @param bool[] $canSeeBusiness */ private function buildCaptionViews($recordViews, $captions, $canSeeBusiness) 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..2033c03fed --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Api/V3/V3StoriesController.php @@ -0,0 +1,146 @@ +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'); + } + } + + /** + * 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->getResultHelpers()->listEmbeddableMedia($request, $story, $story->get_thumbnail(), $this->getAclForUser()), + '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->getResultHelpers()->listRecord($request, $record, $this->getAclForUser()); + } + + return $data; + } + + + /** + * @return V3ResultHelpers + */ + private function getResultHelpers() + { + 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 5925c74921..64373dedff 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V3.php @@ -3,21 +3,41 @@ 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\V3ResultHelpers; +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'; public function register(Application $app) { - $app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) { - return (new V3Controller($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)); + }); + $app['controller.api.v3.stories'] = $app->share(function (PhraseaApplication $app) { + return (new V3StoriesController($app)); }); } @@ -36,12 +56,32 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte $controllers->before(new OAuthListener()); - $controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3:getStoryAction') + /** + * @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+'); - $controllers->match('/search/', 'controller.api.v3:searchAction'); + /** + * @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(), 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;