diff --git a/api/v1/dataCitations/PKPDataCitationController.php b/api/v1/dataCitations/PKPDataCitationController.php new file mode 100644 index 00000000000..686fd5b1f0e --- /dev/null +++ b/api/v1/dataCitations/PKPDataCitationController.php @@ -0,0 +1,224 @@ +whereNumber('publicationId') + ->group(function () { + Route::get('', $this->getMany(...)) + ->name('dataCitation.getMany'); + + Route::get('{dataCitationId}', $this->get(...)) + ->name('dataCitation.getDataCitation') + ->whereNumber('dataCitationId'); + + Route::post('', $this->add(...)) + ->name('dataCitation.add'); + + Route::put('{dataCitationId}', $this->edit(...)) + ->name('dataCitation.edit') + ->whereNumber('dataCitationId'); + + Route::delete('{dataCitationId}', $this->delete(...)) + ->name('dataCitation.delete') + ->whereNumber('dataCitationId'); + }); + } + + /** + * @copydoc \PKP\core\PKPBaseController::authorize() + */ + public function authorize(PKPRequest $request, array &$args, array $roleAssignments): bool + { + $this->addPolicy(new UserRolesRequiredPolicy($request), true); + + $rolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES); + + $this->addPolicy(new ContextRequiredPolicy($request)); + + foreach ($roleAssignments as $role => $operations) { + $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); + } + + $this->addPolicy($rolePolicy); + + return parent::authorize($request, $args, $roleAssignments); + } + + /** + * Get a single data dataCitation. + */ + public function get(Request $illuminateRequest): JsonResponse + { + $announcement = DataCitation::find((int) $illuminateRequest->route('dataCitationId')); + + if (!$dataCitation) { + return response()->json([ + 'error' => __('api.dataCitations.404.dataCitationNotFound') + ], Response::HTTP_OK); + } + + return response()->json(Repo::dataCitation()->getSchemaMap()->map($dataCitation), Response::HTTP_OK); + } + + /** + * Get a collection of data citations. + * + * @hook API::dataCitations::params [[$collector, $illuminateRequest]] + */ + public function getMany(Request $illuminateRequest): JsonResponse + { + $dataCitations = DataCitation::limit(self::DEFAULT_COUNT)->offset(0); + + if ($illuminateRequest->route('publicationId')) { + $dataCitations->withPublicationId($illuminateRequest->route('publicationId')); + } + + Hook::run('API::dataCitations::params', [$dataCitations, $illuminateRequest]); + + return response()->json([ + 'itemsMax' => $dataCitations->count(), + 'items' => Repo::dataCitation()->getSchemaMap()->summarizeMany($dataCitations->get())->values(), + ], Response::HTTP_OK); + + + } + + /** + * Add a data citation. + */ + public function add(Request $illuminateRequest): JsonResponse + { + $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_DATA_CITATION, $illuminateRequest->input()); + $params['publicationId'] = (int) $illuminateRequest->route('publicationId'); + + $errors = Repo::dataCitation()->validate(null, $params); + if (!empty($errors)) { + return response()->json($errors, Response::HTTP_BAD_REQUEST); + } + + $dataCitation = DataCitation::create($params); + + return response()->json(Repo::dataCitation()->getSchemaMap()->map($dataCitation), Response::HTTP_OK); + } + + /** + * Edit a data citation. + */ + public function edit(Request $illuminateRequest): JsonResponse + { + $dataCitation = DataCitation::find((int)$illuminateRequest->route('dataCitationId')); + + if (!$dataCitation) { + return response()->json([ + 'error' => __('api.dataCitations.404.dataCitationNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_DATA_CITATION, $illuminateRequest->input()); + $params['id'] = $dataCitation->id; + + $errors = Repo::dataCitation()->validate($dataCitation, $params); + if (!empty($errors)) { + return response()->json($errors, Response::HTTP_BAD_REQUEST); + } + + $dataCitation->update($params); + + $dataCitation = DataCitation::find($dataCitation->id); + + return response()->json( + Repo::dataCitation()->getSchemaMap()->map($dataCitation), Response::HTTP_OK + ); + } + + /** + * Delete a data citation. + */ + public function delete(Request $illuminateRequest): JsonResponse + { + $dataCitation = DataCitation::find((int) $illuminateRequest->route('dataCitationId')); + + if (!$dataCitation) { + return response()->json([ + 'error' => __('api.dataCitations.404.dataCitationNotFound') + ], Response::HTTP_OK); + } + + $dataCitation->delete(); + + return response()->json( + Repo::dataCitation()->getSchemaMap()->map($dataCitation), Response::HTTP_OK + ); + } + +} diff --git a/api/v1/submissions/PKPSubmissionController.php b/api/v1/submissions/PKPSubmissionController.php index 5ca5a66e742..400eb481b3a 100644 --- a/api/v1/submissions/PKPSubmissionController.php +++ b/api/v1/submissions/PKPSubmissionController.php @@ -43,6 +43,7 @@ use PKP\citation\Citation; use PKP\citation\enum\CitationProcessingStatus; use PKP\components\forms\FormComponent; +use PKP\components\forms\publication\PKPDataAvailabilityAndCitationsForm; use PKP\components\forms\publication\PKPMetadataForm; use PKP\components\forms\publication\PKPPublicationIdentifiersForm; use PKP\components\forms\publication\PKPPublicationLicenseForm; @@ -120,6 +121,7 @@ class PKPSubmissionController extends PKPBaseController 'editContributor', 'saveContributorsOrder', 'addDecision', + 'getPublicationDataAvailabilityAndCitationsForm', 'getPublicationMetadataForm', 'getPublicationIdentifierForm', 'getPublicationLicenseForm', @@ -325,6 +327,7 @@ public function getGroupRoutes(): void Route::prefix('{submissionId}/publications/{publicationId}/_components')->group(function () { Route::get('metadata', $this->getPublicationMetadataForm(...))->name('submission.publication._components.metadata'); + Route::get('dataAvailabilityAndCitation', $this->getPublicationDataAvailabilityAndCitationsForm(...))->name('submission.publication._components.dataCitation'); Route::get('titleAbstract', $this->getPublicationTitleAbstractForm(...))->name('submission.publication._components.titleAbstract'); Route::get('changeLanguageMetadata', $this->getChangeLanguageMetadata(...))->name('submission.publication._components.changeLanguageMetadata'); })->whereNumber(['submissionId', 'publicationId']); @@ -424,6 +427,7 @@ public function authorize(PKPRequest $request, array &$args, array $roleAssignme if (in_array( $actionName, [ + 'getPublicationDataAvailabilityAndCitationsForm', 'getPublicationMetadataForm', 'getPublicationIdentifierForm', 'getPublicationLicenseForm', @@ -2040,6 +2044,33 @@ protected function getPublicationMetadataForm(Request $illuminateRequest): JsonR return response()->json($this->getLocalizedForm($metadataForm, $submissionLocale, $locales), Response::HTTP_OK); } + /** + * Get Publication Data Citation Form component + */ + protected function getPublicationDataAvailabilityAndCitationsForm(Request $illuminateRequest): JsonResponse + { + $data = $this->getSubmissionAndPublicationData($illuminateRequest); + + if (isset($data['error'])) { + return response()->json([ 'error' => $data['error'],], $data['status']); + } + + $context = $data['context']; /** @var Context $context*/ + $submission = $data['submission']; /** @var Submission $submission */ + $publication = $data['publication']; /** @var Publication $publication*/ + + $publicationApiUrl = $data['publicationApiUrl']; /** @var String $publicationApiUrl*/ + + $submissionLocale = $submission->getData('locale'); + $locales = $this->getPublicationFormLocales($context, $submission); + $dataAvailabilitySetting = (bool) $context->getData('dataAvailability'); + + $dataAvailabilityAndCitationsForm = new PKPDataAvailabilityAndCitationsForm($publicationApiUrl, $locales, $publication, $dataAvailabilitySetting); + + return response()->json($this->getLocalizedForm($dataAvailabilityAndCitationsForm, $submissionLocale, $locales), Response::HTTP_OK); + + } + /** * Get Publication License Form component */ diff --git a/classes/components/forms/context/PKPMetadataSettingsForm.php b/classes/components/forms/context/PKPMetadataSettingsForm.php index 5589e0f347d..3439f326bc1 100644 --- a/classes/components/forms/context/PKPMetadataSettingsForm.php +++ b/classes/components/forms/context/PKPMetadataSettingsForm.php @@ -215,6 +215,19 @@ public function __construct($action, $context) ], 'value' => $context->getData('dataAvailability') ? $context->getData('dataAvailability') : Context::METADATA_DISABLE, ])) + ->addField(new FieldMetadataSetting('dataCitations', [ + 'label' => __('manager.setup.metadata.dataCitations'), + 'description' => __('manager.setup.metadata.dataCitations.description'), + 'options' => [ + ['value' => Context::METADATA_ENABLE, 'label' => __('manager.setup.metadata.dataCitations.enable')] + ], + 'submissionOptions' => [ + ['value' => Context::METADATA_ENABLE, 'label' => __('manager.setup.metadata.dataCitations.noRequest')], + ['value' => Context::METADATA_REQUEST, 'label' => __('manager.setup.metadata.dataCitations.request')], + ['value' => Context::METADATA_REQUIRE, 'label' => __('manager.setup.metadata.dataCitations.require')], + ], + 'value' => $context->getData('dataCitations') ? $context->getData('dataCitations') : Context::METADATA_DISABLE, + ])) ->addField(new FieldOptions('submitWithCategories', [ 'label' => __('category.category'), 'description' => __('manager.submitWithCategories.description'), diff --git a/classes/components/forms/dataCitation/DataCitationEditForm.php b/classes/components/forms/dataCitation/DataCitationEditForm.php new file mode 100644 index 00000000000..e76e2d6ab71 --- /dev/null +++ b/classes/components/forms/dataCitation/DataCitationEditForm.php @@ -0,0 +1,99 @@ +action = $action; + + $types = ['DOI', 'Accession', 'PURL', 'ARK', 'URI', 'ARXIV', 'ECLI', 'Handle', 'ISSN', 'ISBN', 'PMID', 'PMCID', 'UUID']; + $identifierTypes = array_map(fn($type) => ['value' => $type, 'label' => $type], $types); + + $types = ['supporting', 'generated', 'analyzed', 'non-analyzed']; + $relationshipTypes = array_map( + fn($type) => [ + 'value' => $type, + 'label' => __('submission.dataCitations.label.relationshipType.' . $type), + ], + $types + ); + + $this->addField(new FieldText('title', [ + 'label' => __('submission.dataCitations.label.title'), + 'description' => '', + 'value' => null, + 'isRequired' => true + ])); + + $this->addField(new FieldSelect('identifierType', [ + 'label' => __('submission.dataCitations.label.identifierType'), + 'options' => $identifierTypes + ])); + + $this->addField(new FieldText('identifier', [ + 'label' => __('submission.dataCitations.label.identifier'), + 'description' => '', + 'value' => null + ])); + + $this->addField(new FieldSelect('relationshipType', [ + 'label' => __('submission.dataCitations.label.relationshipType'), + 'options' => $relationshipTypes, + 'isRequired' => true + ])); + + $this->addField(new FieldText('repository', [ + 'label' => __('submission.dataCitations.label.repository'), + 'description' => '', + 'value' => null + ])); + + $this->addField(new FieldText('year', [ + 'label' => __('submission.dataCitations.label.year'), + 'description' => '', + 'value' => null + ])); + + $this->addField(new FieldAuthors('authors', [ + 'label' => __('submission.dataCitations.label.creators'), + 'description' => '', + 'value' => null, + ])); + + $this->addField(new FieldText('url', [ + 'label' => __('submission.dataCitations.label.url'), + 'description' => '', + 'value' => null + ])); + + } +} \ No newline at end of file diff --git a/classes/components/forms/publication/PKPDataAvailabilityAndCitationsForm.php b/classes/components/forms/publication/PKPDataAvailabilityAndCitationsForm.php new file mode 100644 index 00000000000..75eb0ab1c15 --- /dev/null +++ b/classes/components/forms/publication/PKPDataAvailabilityAndCitationsForm.php @@ -0,0 +1,49 @@ +action = $action; + $this->locales = $locales; + + if ($dataAvailabilitySetting) { + $this->addField(new FieldRichTextarea('dataAvailability', [ + 'label' => __('submission.dataAvailability'), + 'tooltip' => __('manager.setup.metadata.dataAvailability.description'), + 'isMultilingual' => true, + 'value' => $publication->getData('dataAvailability'), + 'isRequired' => $isRequired + ])); + } + + } +} diff --git a/classes/components/forms/publication/PKPMetadataForm.php b/classes/components/forms/publication/PKPMetadataForm.php index c295a85ae0c..1074be1f5c7 100644 --- a/classes/components/forms/publication/PKPMetadataForm.php +++ b/classes/components/forms/publication/PKPMetadataForm.php @@ -129,15 +129,6 @@ public function __construct(string $action, array $locales, Publication $publica ])); } - if ($this->enabled('dataAvailability')) { - $this->addField(new FieldRichTextarea('dataAvailability', [ - 'label' => __('submission.dataAvailability'), - 'tooltip' => __('manager.setup.metadata.dataAvailability.description'), - 'isMultilingual' => true, - 'value' => $publication->getData('dataAvailability'), - ])); - } - if ($this->enabled('pub-id::publisher-id')) { $this->addField(new FieldText('pub-id::publisher-id', [ 'label' => __('submission.publisherId'), diff --git a/classes/context/Context.php b/classes/context/Context.php index 5f9d2928820..1ae96efff7d 100644 --- a/classes/context/Context.php +++ b/classes/context/Context.php @@ -596,6 +596,7 @@ public function getRequiredMetadata(): array return collect([ 'agencies', 'citations', + 'dataCitations', 'coverage', 'dataAvailability', 'disciplines', diff --git a/classes/core/PKPApplication.php b/classes/core/PKPApplication.php index 64f310a5fdf..dbbfa932738 100644 --- a/classes/core/PKPApplication.php +++ b/classes/core/PKPApplication.php @@ -89,6 +89,7 @@ abstract class PKPApplication implements PKPApplicationInfoProvider public const ASSOC_TYPE_ACCESSIBLE_FILE_STAGES = 0x010000d; public const ASSOC_TYPE_NONE = 0x010000e; public const ASSOC_TYPE_DECISION_TYPE = 0x010000f; + public const ASSOC_TYPE_DATA_CITATION = 0x0100010; // Constant used in UsageStats for submission files that are not full texts public const ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER = 0x0000213; @@ -664,6 +665,7 @@ public static function getMetadataFields(): array 'keywords', 'agencies', 'citations', + 'dataCitations', 'dataAvailability', ]; } diff --git a/classes/dataCitation/DataCitation.php b/classes/dataCitation/DataCitation.php new file mode 100644 index 00000000000..37afc20dc5b --- /dev/null +++ b/classes/dataCitation/DataCitation.php @@ -0,0 +1,97 @@ +settingsTable; + } + + /** + * @return bool + * + * @hook DataCitation::add [[$this]] + */ + public function save(array $options = []) + { + + $isNew = !$this->exists; + $saved = parent::save($options); + + if (!$saved) { + return false; + } + + // Reload the model to ensure all relationships and settings are loaded + $this->refresh(); + + if ($isNew) { + // This is a new record + Hook::call('DataCitation::add', [$this]); + } else { + // This is an update + Hook::call('DataCitation::edit', [$this]); + } + + return $saved; + } + + /** + * Filter by publication ID + */ + protected function scopeWithPublicationId(EloquentBuilder $builder, int $publicationId): EloquentBuilder + { + return $builder->where('publication_id', $publicationId); + } + + +} diff --git a/classes/dataCitation/Repository.php b/classes/dataCitation/Repository.php new file mode 100644 index 00000000000..6df3c43809e --- /dev/null +++ b/classes/dataCitation/Repository.php @@ -0,0 +1,91 @@ + $schemaService */ + protected PKPSchemaService $schemaService; + + public function __construct(Request $request, PKPSchemaService $schemaService) + { + $this->request = $request; + $this->schemaService = $schemaService; + } + + /** + * Validate properties for a data citation + * + * Perform validation checks on data used to add or edit a data citation. + * + * @param DataCitation|null $dataCitation Data citation being edited. Pass `null` if creating a new citation + * @param array $props A key/value array with the new data to validate + * + * @return array A key/value array with validation errors. Empty if no errors + * + * @hook DataCitation::validate [[&$errors, $dataCitation, $props]] + */ + public function validate(?DataCitation $dataCitation, array $props): array + { + $schema = DataCitation::getSchemaName(); + + $validator = ValidatorFactory::make( + $props, + $this->schemaService->getValidationRules($schema, []) + ); + + // Check required fields + ValidatorFactory::required( + $validator, + $dataCitation, + $this->schemaService->getRequiredProps($schema), + $this->schemaService->getMultilingualProps($schema), + [], + '' + ); + + $errors = []; + + if ($validator->fails()) { + $errors = $this->schemaService->formatValidationErrors($validator->errors()); + } + + Hook::call('DataCitation::validate', [&$errors, $dataCitation, $props]); + + return $errors; + } + + /** + * Get an instance of the map class for mapping + * announcements to their schema + */ + public function getSchemaMap(): maps\Schema + { + return app('maps')->withExtensions($this->schemaMap); + } + +} diff --git a/classes/dataCitation/maps/Schema.php b/classes/dataCitation/maps/Schema.php new file mode 100644 index 00000000000..378b8b2d7be --- /dev/null +++ b/classes/dataCitation/maps/Schema.php @@ -0,0 +1,113 @@ +mapByProperties($this->getProps(), $item); + } + + /** + * Summarize a Data Citation + * + * Includes properties with the apiSummary flag in the Data Citation schema. + */ + public function summarize(DataCitation $item): array + { + return $this->mapByProperties($this->getSummaryProps(), $item); + } + + /** + * Map a collection of Data Citations + * + * @see self::map + */ + public function mapMany(Enumerable $collection): Enumerable + { + $this->collection = $collection; + return $collection->map(function ($item) { + return $this->map($item); + }); + } + + /** + * Summarize a collection of Data Citations + * + * @see self::summarize + */ + public function summarizeMany(Enumerable $collection): Enumerable + { + $this->collection = $collection; + return $collection->map(function ($item) { + return $this->summarize($item); + }); + } + + /** + * Map schema properties of a Data Citation to an assoc array + */ + protected function mapByProperties(array $props, DataCitation $item): array + { + $authorModel = $this->getDataCitationAuthorDataModel(); + $output = []; + foreach ($props as $prop) { + switch ($prop) { + case 'authors': + $authors = []; + foreach (is_array($item->getAttribute($prop)) ? $item->getAttribute($prop) : [] as $author) { + $authors[] = array_merge($authorModel, $author); + } + $output[$prop] = $authors; + break; + default: + $output[$prop] = $item->getAttribute($prop); + break; + } + } + ksort($output); + return $this->withExtensions($output, $item); + } + + /** + * Get author data model as defined in schemas/dataCitation.json. + */ + public function getDataCitationAuthorDataModel(): array + { + $schemaService = new PKPSchemaService(); + $schema = $schemaService->get($this->schema); + $authorModel = []; + foreach (array_keys((array)$schema->properties->authors->items->properties) as $property) { + $authorModel[$property] = ''; + } + return $authorModel; + } + +} diff --git a/classes/facades/Repo.php b/classes/facades/Repo.php index e19c89213e8..87978df01dd 100644 --- a/classes/facades/Repo.php +++ b/classes/facades/Repo.php @@ -33,6 +33,7 @@ use PKP\category\Repository as CategoryRepository; use PKP\citation\Repository as CitationRepository; use PKP\controlledVocab\Repository as ControlledVocabRepository; +use PKP\dataCitation\Repository as DataCitationRepository; use PKP\decision\Repository as DecisionRepository; use PKP\editorialTask\Repository as EditorialTaskRepository; use PKP\emailTemplate\Repository as EmailTemplateRepository; @@ -91,6 +92,11 @@ public static function creditRole(): CreditRoleRepository return app(CreditRoleRepository::class); } + public static function dataCitation(): DataCitationRepository + { + return app(DataCitationRepository::class); + } + public static function decision(): DecisionRepository { return app()->make(DecisionRepository::class); diff --git a/classes/migration/install/MetadataMigration.php b/classes/migration/install/MetadataMigration.php index 773ba2f983c..eba23b0f533 100644 --- a/classes/migration/install/MetadataMigration.php +++ b/classes/migration/install/MetadataMigration.php @@ -56,6 +56,34 @@ public function up(): void $table->unique(['citation_id', 'locale', 'setting_name'], 'citation_settings_unique'); }); + // Data Citations + Schema::create('data_citations', function (Blueprint $table) { + $table->comment('A data citation pointing to a related data set.'); + $table->bigInteger('data_citation_id')->autoIncrement(); + + $table->bigInteger('publication_id'); + $table->foreign('publication_id', 'data_citations_publication')->references('publication_id')->on('publications')->onDelete('cascade'); + $table->index(['publication_id'], 'data_citations_publication'); + + $table->bigInteger('seq')->default(0); + + }); + + // Data Citation settings + Schema::create('data_citation_settings', function (Blueprint $table) { + $table->comment('Additional data about data citations, including localized content.'); + $table->bigIncrements('data_citation_setting_id'); + $table->bigInteger('data_citation_id'); + $table->foreign('data_citation_id', 'data_citation_settings_data_citation_id')->references('data_citation_id')->on('data_citations')->onDelete('cascade'); + $table->index(['data_citation_id'], 'data_citation_settings_data_citation_id'); + + $table->string('locale', 28)->default(''); + $table->string('setting_name', 255); + $table->mediumText('setting_value')->nullable(); + + $table->unique(['data_citation_id', 'locale', 'setting_name'], 'data_citation_settings_unique'); + }); + // Filter groups Schema::create('filter_groups', function (Blueprint $table) { $table->comment('Filter groups are used to organized filters into named sets, which can be retrieved by the application for invocation.'); @@ -120,5 +148,7 @@ public function down(): void Schema::drop('filter_groups'); Schema::drop('citation_settings'); Schema::drop('citations'); + Schema::drop('data_citation_settings'); + Schema::drop('data_citations'); } } diff --git a/classes/publication/DAO.php b/classes/publication/DAO.php index 4503bf29b93..1a5acb0a7c7 100644 --- a/classes/publication/DAO.php +++ b/classes/publication/DAO.php @@ -24,6 +24,7 @@ use PKP\controlledVocab\ControlledVocab; use PKP\core\EntityDAO; use PKP\core\traits\EntityWithParent; +use PKP\dataCitation\DataCitation; use PKP\services\PKPSchemaService; /** @@ -175,6 +176,7 @@ function __toString() { $this->setAuthors($publication); $this->setCategories($publication); $this->setControlledVocab($publication); + $this->setDataCitations($publication); return $publication; } @@ -237,6 +239,7 @@ public function deleteById(int $publicationId): int $this->deleteAuthors($publicationId); $this->deleteCategories($publicationId); $this->deleteControlledVocab($publicationId); + $this->deleteDataCitations($publicationId); Repo::citation()->deleteByPublicationId($publicationId); return $affectedRows; @@ -465,6 +468,23 @@ protected function deleteCategories(int $publicationId): void PublicationCategory::where('publication_id', $publicationId)->delete(); } + /** + * Set a publication's Data Citations + */ + protected function setDataCitations(Publication $publication) + { + $dataCitations = DataCitation::where('publication_id', $publication->getId())->get()->values()->all(); + $publication->setData('dataCitations', $dataCitations); + } + + /** + * Delete a publication's Data Citations + */ + protected function deleteDataCitations(int $publicationId) + { + DataCitation::where('publication_id', $publicationId)->delete(); + } + /** * Set the DOI object * diff --git a/classes/publication/Repository.php b/classes/publication/Repository.php index 0199f2a8009..d2b7e27ed71 100644 --- a/classes/publication/Repository.php +++ b/classes/publication/Repository.php @@ -29,6 +29,7 @@ use PKP\core\Core; use PKP\core\PKPApplication; use PKP\core\PKPString; +use PKP\dataCitation\DataCitation; use PKP\db\DAORegistry; use PKP\doi\Doi; use PKP\facades\Locale; @@ -398,6 +399,20 @@ public function version(Publication $publication, ?VersionStage $versionStage = } } + Repo::citation()->importCitations( + $newPublication->getId(), + $newPublication->getData('citationsRaw') + ); + + // Clone data citations if any + $dataCitations = DataCitation::where('publication_id', $publication->getId())->get(); + foreach ($dataCitations as $dataCitation) { + $data = $dataCitation->toArray(); + unset($data['dataCitationId']); + $data['publicationId'] = $newPublication->getId(); + $newDataCitation = DataCitation::create($data); + } + $genreDao = DAORegistry::getDAO('GenreDAO'); /** @var \PKP\submission\GenreDAO $genreDao */ $genres = $genreDao->getEnabledByContextId($context->getId()); diff --git a/classes/publication/maps/Schema.php b/classes/publication/maps/Schema.php index 6b5b62ba5b9..315b04297bc 100644 --- a/classes/publication/maps/Schema.php +++ b/classes/publication/maps/Schema.php @@ -20,6 +20,7 @@ use APP\submission\Submission; use Illuminate\Support\Enumerable; use PKP\context\Context; +use PKP\dataCitation\DataCitation; use PKP\services\PKPSchemaService; use PKP\submission\Genre; @@ -141,6 +142,13 @@ protected function mapByProperties(array $props, Publication $publication, bool case 'citationsRaw': $output[$prop] = Repo::citation()->getRawCitationsByPublicationId($publication->getId())->implode(PHP_EOL); break; + case 'dataCitations': + $data = []; + foreach ($publication->getData('dataCitations') as $dataCitation) { + $data[] = Repo::dataCitation()->getSchemaMap()->map($dataCitation); + } + $output[$prop] = $data; + break; case 'doiObject': if ($publication->getData('doiObject')) { $retVal = Repo::doi()->getSchemaMap()->summarize($publication->getData('doiObject')); diff --git a/classes/security/authorization/internal/DataCitationRequiredPolicy.php b/classes/security/authorization/internal/DataCitationRequiredPolicy.php new file mode 100644 index 00000000000..c57c633a2ce --- /dev/null +++ b/classes/security/authorization/internal/DataCitationRequiredPolicy.php @@ -0,0 +1,82 @@ +getDataObjectId(); + if (!$dataCitationId) { + return AuthorizationPolicy::AUTHORIZATION_DENY; + } + + // Need a valid submission in request. + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + if (!$submission instanceof Submission) { + return AuthorizationPolicy::AUTHORIZATION_DENY; + } + + // Need a valid publication in request + $publication = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_PUBLICATION); + if (!$publication instanceof Publication) { + return AuthorizationPolicy::AUTHORIZATION_DENY; + } + + // Make sure the dataCitation belongs to the submission. + $dataCitation = DataCitation::where('data_citation_id', $dataCitationId) + ->where('publication_id', $publication->getId()) + ->first(); + if (!$dataCitation instanceof DataCitation) { + return AuthorizationPolicy::AUTHORIZATION_DENY; + } + + // Save the dataCitation to the authorization context. + $this->addAuthorizedContextObject(Application::ASSOC_TYPE_DATA_CITATION, $dataCitation); + return AuthorizationPolicy::AUTHORIZATION_PERMIT; + } +} + +if (!PKP_STRICT_MODE) { + class_alias('\PKP\security\authorization\internal\DataCitationRequiredPolicy', '\DataCitationRequiredPolicy'); +} diff --git a/classes/services/PKPSchemaService.php b/classes/services/PKPSchemaService.php index 21f1f60e94f..fcb5a0a7474 100644 --- a/classes/services/PKPSchemaService.php +++ b/classes/services/PKPSchemaService.php @@ -36,6 +36,7 @@ class PKPSchemaService public const SCHEMA_CITATION = 'citation'; public const SCHEMA_CONTEXT = 'context'; public const SCHEMA_CONTRIBUTOR_ROLE = 'contributorRole'; + public const SCHEMA_DATA_CITATION = 'dataCitation'; public const SCHEMA_DOI = 'doi'; public const SCHEMA_DECISION = 'decision'; public const SCHEMA_EMAIL_TEMPLATE = 'emailTemplate'; @@ -377,6 +378,9 @@ public function coerce($value, $type, $schema) foreach ($value as $i => $v) { $newArray[$i] = $this->coerce($v, $schema->items->type, $schema->items); } + } elseif (is_string($value) && json_decode($value) !== null && json_last_error() === JSON_ERROR_NONE) { + // If the value is already a JSON string, do not attempt to serialize it again + return $value; } else { $newArray[] = serialize($value); } @@ -394,6 +398,7 @@ public function coerce($value, $type, $schema) } $newObject[$propName] = $this->coerce($value[$propName], $propSchema->type, $propSchema); } + return $newObject; } throw new Exception('Requested variable coercion for a type that was not recognized: ' . $type); diff --git a/classes/submission/Repository.php b/classes/submission/Repository.php index e1e7dda56c4..886fda26aff 100644 --- a/classes/submission/Repository.php +++ b/classes/submission/Repository.php @@ -432,6 +432,7 @@ public function validateSubmit(Submission $submission, Context $context): array if ($metadata === 'citations') { $metadata = 'citationsRaw'; } + // The `supportingAgencies` metadata is called `agencies` on the context if ($metadata === 'agencies') { $metadata = 'supportingAgencies'; diff --git a/locale/en/common.po b/locale/en/common.po index 585c370d6d1..659e67333c9 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -1839,6 +1839,9 @@ msgstr "Title" msgid "search.typeMethodApproach" msgstr "Type (method/approach)" +msgid "grid.action.addDataCitation" +msgstr "Add a new Data Citation" + msgid "grid.action.downloadFile" msgstr "Download this file" diff --git a/locale/en/manager.po b/locale/en/manager.po index a6c20a4f276..116b91402c4 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -2209,6 +2209,25 @@ msgstr "" "Require the author to suggest coverage metadata before accepting their " "submission." +msgid "manager.setup.metadata.dataCitations" +msgstr "Data Citations" + +msgid "manager.setup.metadata.dataCitations.description" +msgstr "A data citation typically identifies a dataset that supports the findings of the work. Data citations ensure reproducibility, transparency, and proper attribution of research data." + +msgid "manager.setup.metadata.dataCitations.enable" +msgstr "Enable data citation metadata" + +msgid "manager.setup.metadata.dataCitations.noRequest" +msgstr "Do not request data citation metadata from the author during submission." + +msgid "manager.setup.metadata.dataCitations.request" +msgstr "Ask the author for data citation metadata during submission." + +msgid "manager.setup.metadata.dataCitations.require" +msgstr "" +"Require the author to add data citation metadata before accepting their submission." + msgid "manager.setup.metadata.keywords.description" msgstr "" "Keywords are typically one- to three-word phrases that are used to indicate " diff --git a/locale/en/submission.po b/locale/en/submission.po index 83245271739..0ba3fedd37e 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -692,6 +692,63 @@ msgstr "The following references have been extracted and will be linked to the s msgid "submission.parsedAndSaveCitations" msgstr "Extract and Save References" +msgid "submission.dataCitations" +msgstr "Data Citations" + +msgid "submission.dataCitations.description" +msgstr "This table allows users to add formal data citations, ensuring datasets are properly credited and appear alongside other references in the publication." + +msgid "submission.dataCitations.title" +msgstr "Title" + +msgid "submission.dataCitations.addModal.title" +msgstr "Add Data Citation" + +msgid "submission.dataCitations.editModal.title" +msgstr "Edit Data Citation" + +msgid "submission.dataCitations.label.title" +msgstr "Title" + +msgid "submission.dataCitations.label.identifierType" +msgstr "Identifier type" + +msgid "submission.dataCitations.label.identifier" +msgstr "Identifier" + +msgid "submission.dataCitations.label.relationshipType" +msgstr "Relationship type" + +msgid "submission.dataCitations.label.relationshipType.supporting" +msgstr "Supporting data without specifying whether they were generated or analyzed (supporting)." + +msgid "submission.dataCitations.label.relationshipType.generated" +msgstr "Supporting data that were generated for the study (generated)." + +msgid "submission.dataCitations.label.relationshipType.analyzed" +msgstr "Supporting data that were analyzed but not generated for the study (analyzed)." + +msgid "submission.dataCitations.label.relationshipType.non-analyzed" +msgstr "Referenced data that were neither generated nor analyzed for the study (non-analyzed)." + +msgid "submission.dataCitations.label.repository" +msgstr "Repository" + +msgid "submission.dataCitations.label.year" +msgstr "Year" + +msgid "submission.dataCitations.label.creators" +msgstr "Creators" + +msgid "submission.dataCitations.label.url" +msgstr "URL" + +msgid "submission.dataCitations.emptyCitations" +msgstr "No data citations have been added." + +msgid "submission.dataCitations.required" +msgstr "Data citations are required." + msgid "submission.comments.addComment" msgstr "Add Comment" @@ -749,6 +806,9 @@ msgstr "Discussion Files" msgid "submission.coverage" msgstr "Coverage Information" +msgid "submission.dataAvailabilityAndCitation.data" +msgstr "Data" + msgid "submission.details" msgstr "Submission Details" diff --git a/pages/authorDashboard/PKPAuthorDashboardHandler.php b/pages/authorDashboard/PKPAuthorDashboardHandler.php index 4c4cf89af4d..509f05166b1 100644 --- a/pages/authorDashboard/PKPAuthorDashboardHandler.php +++ b/pages/authorDashboard/PKPAuthorDashboardHandler.php @@ -25,6 +25,7 @@ use APP\submission\Submission; use APP\template\TemplateManager; use Illuminate\Support\Enumerable; +use PKP\components\forms\publication\PKPDataAvailabilityAndCitationsForm; use PKP\components\forms\publication\PKPMetadataForm; use PKP\components\forms\publication\TitleAbstractForm; use PKP\components\listPanels\ContributorsListPanel; @@ -240,9 +241,11 @@ public function setupTemplate($request) ); $titleAbstractForm = $this->getTitleAbstractForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext); + $dataAvailabilityAndCitationsForm = new PKPDataAvailabilityAndCitationsForm($latestPublicationApiUrl, $locales, $latestPublication); $templateMgr->setConstants([ 'FORM_TITLE_ABSTRACT' => $titleAbstractForm::FORM_TITLE_ABSTRACT, + 'FORM_DATA_AVAILABILITY_AND_CITATIONS' => $dataAvailabilityAndCitationsForm ::FORM_DATA_AVAILABILITY_AND_CITATIONS, ]); // Get the submission props without the full publication details. We'll @@ -301,12 +304,14 @@ public function setupTemplate($request) 'components' => [ $titleAbstractForm::FORM_TITLE_ABSTRACT => $this->getLocalizedForm($titleAbstractForm, $submissionLocale, $locales), $citationsForm::FORM_CITATIONS => $this->getLocalizedForm($citationsForm, $submissionLocale, $locales), + $dataAvailabilityAndCitationsForm::FORM_DATA_AVAILABILITY_AND_CITATIONS => $this->getLocalizedForm($dataAvailabilityAndCitationsForm, $submissionLocale, $locales), $contributorsListPanel->id => $contributorsListPanel->getConfig(), ], 'currentPublication' => $currentPublicationProps, 'publicationFormIds' => [ $titleAbstractForm::FORM_TITLE_ABSTRACT, $citationsForm::FORM_CITATIONS, + $dataAvailabilityAndCitationsForm::FORM_DATA_AVAILABILITY_AND_CITATIONS, ], 'representationsGridUrl' => $canAccessProductionStage ? $this->_getRepresentationsGridUrl($request, $submission) : '', 'submission' => $submissionProps, @@ -316,6 +321,8 @@ public function setupTemplate($request) 'submissionLibraryLabel' => __('grid.libraryFiles.submission.title'), 'submissionLibraryUrl' => $submissionLibraryUrl, 'supportsReferences' => !!$submissionContext->getData('citations'), + 'supportsDataAvailability' => !!$submissionContext->getData('dataAvailability'), + 'supportsDataCitations' => !!$submissionContext->getData('dataCitations'), 'statusLabel' => __('semicolon', ['label' => __('common.status')]), 'uploadFileModalLabel' => __('editor.submissionReview.uploadFile'), 'uploadFileUrl' => $uploadFileUrl, diff --git a/pages/dashboard/PKPDashboardHandler.php b/pages/dashboard/PKPDashboardHandler.php index f46ac6bd779..ef3e9740d13 100644 --- a/pages/dashboard/PKPDashboardHandler.php +++ b/pages/dashboard/PKPDashboardHandler.php @@ -27,6 +27,7 @@ use PKP\components\forms\citation\CitationStructuredEditForm; use PKP\components\forms\decision\LogReviewerResponseForm; use PKP\components\forms\publication\ContributorForm; +use PKP\components\forms\dataCitation\DataCitationEditForm; use PKP\controllers\grid\users\reviewer\PKPReviewerGridHandler; use PKP\core\JSONMessage; use PKP\core\PKPApplication; @@ -174,6 +175,8 @@ public function index($args, $request) $logResponseForm = new LogReviewerResponseForm($context->getSupportedFormLocales(), $context); $citationStructuredEditForm = new CitationStructuredEditForm('emit'); $citationRawEditForm = new CitationRawEditForm('emit'); + $dataCitationEditForm = new DataCitationEditForm('emit'); + $templateMgr->setState([ 'pageInitConfig' => [ 'selectRevisionDecisionForm' => $selectRevisionDecisionForm->getConfig(), @@ -186,6 +189,8 @@ public function index($args, $request) 'contextCitationsMetadataLookup' => $context->getData('citationsMetadataLookup') ?: 0, 'publicationSettings' => [ 'supportsCitations' => !!$context->getData('citations'), + 'supportsDataCitations' => !!$context->getData('dataCitations'), + 'supportsDataAvailability' => !!$context->getData('dataAvailability'), 'identifiersEnabled' => $identifiersEnabled, 'isReviewerSuggestionEnabled' => (bool)$context->getData('reviewerSuggestionEnabled'), ], @@ -194,7 +199,8 @@ public function index($args, $request) 'logResponseForm' => $logResponseForm->getConfig(), 'versionStageOptions' => $versionStageOptions, 'citationStructuredEditForm' => $citationStructuredEditForm->getConfig(), - 'citationRawEditForm' => $citationRawEditForm->getConfig() + 'citationRawEditForm' => $citationRawEditForm->getConfig(), + 'dataCitationEditForm' => $dataCitationEditForm->getConfig() ], ] ]); diff --git a/pages/submission/PKPSubmissionHandler.php b/pages/submission/PKPSubmissionHandler.php index 04145d9b5ae..7dfcdbd7cdf 100644 --- a/pages/submission/PKPSubmissionHandler.php +++ b/pages/submission/PKPSubmissionHandler.php @@ -29,6 +29,8 @@ use Illuminate\Support\LazyCollection; use PKP\components\forms\FormComponent; use PKP\components\forms\publication\PKPCitationsForm; +use PKP\components\forms\dataCitation\DataCitationEditForm; +use PKP\components\forms\publication\PKPDataAvailabilityAndCitationsForm; use PKP\components\forms\publication\TitleAbstractForm; use PKP\components\forms\submission\CommentsForTheEditors; use PKP\components\forms\submission\ConfirmSubmission; @@ -52,6 +54,7 @@ abstract class PKPSubmissionHandler extends Handler { public const SECTION_TYPE_CONFIRM = 'confirm'; public const SECTION_TYPE_CONTRIBUTORS = 'contributors'; + public const SECTION_TYPE_DATA_CITATIONS = 'dataCitations'; public const SECTION_TYPE_REVIEWER_SUGGESTIONS = 'reviewerSuggestions'; public const SECTION_TYPE_FILES = 'files'; public const SECTION_TYPE_FORM = 'form'; @@ -232,6 +235,14 @@ protected function showWizard(array $args, Request $request, Submission $submiss $components[$reviewerSuggestionsListPanel->id] = $reviewerSuggestionsListPanel->getConfig(); } + $dataCitationsSetting = $context->getData('dataCitations'); + if (in_array($dataCitationsSetting, [Context::METADATA_REQUEST, Context::METADATA_REQUIRE])) { + $dataCitationEditForm = new DataCitationEditForm('emit'); + $components['dataCitation'] = [ + 'dataCitationEditForm' => $dataCitationEditForm->getConfig(), + ]; + } + $userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES); $templateMgr = TemplateManager::getManager($request); @@ -760,6 +771,40 @@ protected function getDetailsStep( ]; } + + $dataAvailabilitySetting = $request->getContext()->getData('dataAvailability'); + if (in_array($dataAvailabilitySetting, [Context::METADATA_REQUEST, Context::METADATA_REQUIRE])) { + + $dataAvailabilityAndCitationsForm = new PKPDataAvailabilityAndCitationsForm( + $publicationApiUrl, + $locales, + $publication, + in_array($dataAvailabilitySetting, [Context::METADATA_REQUEST, Context::METADATA_REQUIRE]), + $dataAvailabilitySetting === Context::METADATA_REQUIRE + ); + + $this->removeButtonFromForm($dataAvailabilityAndCitationsForm); + + $sections[] = [ + 'id' => $dataAvailabilityAndCitationsForm->id, + 'name' => 'Data Availability', + 'type' => self::SECTION_TYPE_FORM, + 'description' => '', + 'form' => $dataAvailabilityAndCitationsForm->getConfig(), + ]; + + } + + $dataCitationsSetting = $request->getContext()->getData('dataCitations'); + if (in_array($dataCitationsSetting, [Context::METADATA_REQUEST, Context::METADATA_REQUIRE])) { + $sections[] = [ + 'id' => 'dataCitations', + 'name' => __('submission.dataCitations'), + 'type' => self::SECTION_TYPE_DATA_CITATIONS, + 'description' => __('submission.dataCitations.description'), + ]; + } + return [ 'id' => 'details', 'name' => __('common.details'), @@ -767,7 +812,10 @@ protected function getDetailsStep( 'sections' => $sections, 'reviewTemplate' => '/submission/review-details.tpl', ]; + + } + /** * Get the state for the For the Editors step diff --git a/plugins/importexport/native/filter/NativeXmlPKPPublicationFilter.php b/plugins/importexport/native/filter/NativeXmlPKPPublicationFilter.php index 5bdbd839935..2cd5c5ca9be 100644 --- a/plugins/importexport/native/filter/NativeXmlPKPPublicationFilter.php +++ b/plugins/importexport/native/filter/NativeXmlPKPPublicationFilter.php @@ -192,6 +192,7 @@ public function handleChildElement($n, $publication) case 'authors': $this->parseAuthors($n, $publication); break; +// DATACITATIONS TODO case 'citations': $this->parseCitations($n, $publication); break; @@ -290,6 +291,8 @@ public function parseAuthor($n, $publication) return $this->importWithXMLNode($n, 'native-xml=>author'); } +// DATACITATIONS TODO + /** * Parse a publication citation and add it to the publication. * diff --git a/plugins/importexport/native/filter/PKPPublicationNativeXmlFilter.php b/plugins/importexport/native/filter/PKPPublicationNativeXmlFilter.php index 2fea5cce359..bb8ba8943a0 100644 --- a/plugins/importexport/native/filter/PKPPublicationNativeXmlFilter.php +++ b/plugins/importexport/native/filter/PKPPublicationNativeXmlFilter.php @@ -134,6 +134,8 @@ public function createEntityNode($doc, $entity) $this->addRepresentations($doc, $entityNode, $entity); +// DATACITATIONS TODO + $citationsListNode = $this->createCitationsNode($doc, $deployment, $entity); if ($citationsListNode->hasChildNodes() || $citationsListNode->hasAttributes()) { $entityNode->appendChild($citationsListNode); @@ -342,6 +344,8 @@ public function getFiles($representation) assert(false); // To be overridden by subclasses } +// DATACITATIONS TODO + /** * Create and return a Citations node. * diff --git a/plugins/importexport/native/pkp-native.xsd b/plugins/importexport/native/pkp-native.xsd index 5baa28a9e02..e76d5282674 100644 --- a/plugins/importexport/native/pkp-native.xsd +++ b/plugins/importexport/native/pkp-native.xsd @@ -290,6 +290,7 @@ + @@ -417,4 +418,12 @@ + + + + + + + + diff --git a/schemas/context.json b/schemas/context.json index c5f026d462b..4ac02f834f8 100644 --- a/schemas/context.json +++ b/schemas/context.json @@ -189,6 +189,14 @@ "nullable" ] }, + "dataCitations": { + "type": "string", + "description": "Enable data citations metadata. `0` is disabled. `enable` will make it available in the workflow. `request` will allow an author to enter a value during submission. `require` will require that the author enter a value during submission.", + "validation": [ + "nullable", + "in:0,enable,request,require" + ] + }, "dateFormatLong": { "type": "string", "multilingual": true, diff --git a/schemas/dataCitation.json b/schemas/dataCitation.json new file mode 100644 index 00000000000..9ac5eb1b9cc --- /dev/null +++ b/schemas/dataCitation.json @@ -0,0 +1,113 @@ +{ + "title": "DataCitation", + "description": "A Data Citation in a Publication.", + "properties": { + "id": { + "type": "integer", + "origin": "primary", + "apiSummary": true + }, + "publicationId": { + "type": "integer", + "origin": "primary", + "apiSummary": true + }, + "seq": { + "type": "integer", + "origin": "primary", + "description": "The sequence number of data citation.", + "default": 0, + "validation": [ + "nullable" + ] + }, + "title": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The title of the dataset.", + "apiSummary": true + }, + "identifierType": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The identifier type for the dataset.", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "identifier": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The identifier for the dataset.", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "relationshipType": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The type of relationship between the data and the publication.", + "apiSummary": true + }, + "repository": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The repository where the dataset is stored or the publisher of the dataset.", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "year": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The year the dataset was published.", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "authors": { + "type": "array", + "origin": "setting", + "description": "A list of the creators for this dataset.", + "multilingual": false, + "apiSummary": true, + "validation": [ + "nullable" + ], + "items": { + "type": "object", + "properties": { + "givenName": { + "type": "string" + }, + "familyName": { + "type": "string" + }, + "orcid": { + "type": "string" + } + } + } + }, + "url": { + "type": "string", + "origin": "setting", + "multilingual": false, + "description": "The URL of the dataset.", + "apiSummary": true, + "validation": [ + "nullable" + ] + } + } +} diff --git a/schemas/publication.json b/schemas/publication.json index cd09f1e1bec..8d9131c92b1 100644 --- a/schemas/publication.json +++ b/schemas/publication.json @@ -143,6 +143,15 @@ "nullable" ] }, + "dataCitations": { + "type": "array", + "description": "Optional metadata that contains an array of citations for data cited in this submission.", + "apiSummary": true, + "readOnly": true, + "items": { + "$ref": "#/definitions/DataCitation" + } + }, "datePublished": { "type": "string", "apiSummary": true, diff --git a/templates/submission/review-details.tpl b/templates/submission/review-details.tpl index 3ed05a5351b..54496cdd20e 100644 --- a/templates/submission/review-details.tpl +++ b/templates/submission/review-details.tpl @@ -39,6 +39,42 @@ {if in_array($currentContext->getData('plainLanguageSummary'), [$currentContext::METADATA_REQUEST, $currentContext::METADATA_REQUIRE])} {include file="/submission/review-publication-field.tpl" prop="plainLanguageSummary" inLocale=$localeKey name="{translate key="submission.plainLanguageSummary"}" type="html"} {/if} + + {if in_array($currentContext->getData('dataCitations'), [$currentContext::METADATA_REQUEST, $currentContext::METADATA_REQUIRE])} + {if $localeKey === $submission->getData('locale')} + +
+ + {assign var=isRequired value=($currentContext->getData('dataCitations') === $currentContext::METADATA_REQUIRE)|json_encode} + + + + {translate key="submission.dataCitations.required"} + + +

+ {translate key="submission.dataCitations"} +

+ +
+ + +
+ {{ dataCitation.title }} +
+
+
+ {/if} + {/if} + + {if in_array($currentContext->getData('citations'), [$currentContext::METADATA_REQUEST, $currentContext::METADATA_REQUIRE])} {if $localeKey === $submission->getData('locale')}
diff --git a/templates/submission/wizard.tpl b/templates/submission/wizard.tpl index b120cddd7d7..84a72d0b563 100644 --- a/templates/submission/wizard.tpl +++ b/templates/submission/wizard.tpl @@ -96,6 +96,12 @@ :publication="publication" @updated:reviewer-suggestions="setReviewerSuggestion" > +