From 2e3164352f4144b06e120bc1a2e48e7a6e939d3b Mon Sep 17 00:00:00 2001 From: dxL1nus Date: Fri, 16 Jan 2026 17:41:02 +0100 Subject: [PATCH 1/5] Different CodecheckRoles are now possible containing multiple PKP roles #87 --- CodecheckPlugin.php | 33 ++++++- README.md | 15 +++- api/v1/ApiEndpoint.php | 29 +++++++ api/v1/CodecheckApiHandler.php | 87 +++++++++++++------ .../RoleExceptions/RoleNotFoundException.php | 13 +++ classes/Roles/CodecheckRole.php | 22 +++++ classes/Roles/ReadAccessRole.php | 25 ++++++ classes/Roles/StandardAccessRole.php | 25 ++++++ classes/Roles/WriteAccessRole.php | 25 ++++++ 9 files changed, 244 insertions(+), 30 deletions(-) create mode 100644 api/v1/ApiEndpoint.php create mode 100644 classes/Exceptions/RoleExceptions/RoleNotFoundException.php create mode 100644 classes/Roles/CodecheckRole.php create mode 100644 classes/Roles/ReadAccessRole.php create mode 100644 classes/Roles/StandardAccessRole.php create mode 100644 classes/Roles/WriteAccessRole.php diff --git a/CodecheckPlugin.php b/CodecheckPlugin.php index c7ba412d..36615134 100644 --- a/CodecheckPlugin.php +++ b/CodecheckPlugin.php @@ -1,6 +1,7 @@ getRequestPath(), 'api/v1/codecheck')) { CodecheckLogger::debug('Instantiating the CODECHECK APIHandler'); - $apiHandler = new CodecheckApiHandler($this, $request); + $standardAccessRole = new StandardAccessRole([ + Role::ROLE_ID_SITE_ADMIN, + Role::ROLE_ID_MANAGER, + Role::ROLE_ID_SUB_EDITOR, + Role::ROLE_ID_ASSISTANT, + Role::ROLE_ID_REVIEWER, + Role::ROLE_ID_AUTHOR + ]); + + $readAccessRole = new ReadAccessRole([ + Role::ROLE_ID_SITE_ADMIN, + Role::ROLE_ID_MANAGER, + Role::ROLE_ID_SUB_EDITOR, + Role::ROLE_ID_ASSISTANT, + ]); + + $writeAccessRole = new WriteAccessRole([ + Role::ROLE_ID_SITE_ADMIN, + Role::ROLE_ID_MANAGER, + ]); + + $roles = [ + $standardAccessRole, + $readAccessRole, + $writeAccessRole + ]; + + $apiHandler = new CodecheckApiHandler($this, $request, $roles); CodecheckLogger::debug('API request: ' . $request->getRequestPath()); } diff --git a/README.md b/README.md index 38c925e4..ebe99179 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ $this->endpoints = [ [ 'route' => 'your endpoint route', 'handler' => [$this, 'yourFunction'], - 'roles' => $this->roles, + 'role' => CodecheckRole::class, // give the `'Role'` property a class that extends CodecheckRole ], ], ]; @@ -293,6 +293,19 @@ private function yourFunction(): void } ``` +Finally your defined `CodecheckRoleArray` can have the following PKP rules (`PKP\security\Role`): + +| Constant | Role name | +|---------------------------|-------------------------------------| +|`Role::ROLE_ID_SITE_ADMIN` | Site Administrator | +|`Role::ROLE_ID_MANAGER` | Manager Journal/Press/Server Manager| +|`Role::ROLE_ID_SUB_EDITOR` | Sub Editor | +|`Role::ROLE_ID_ASSISTANT` | Editorial assistant / support role | +|`Role::ROLE_ID_REVIEWER` | Reviewer | +|`Role::ROLE_ID_AUTHOR` | Author | +|`Role::ROLE_ID_READER` | Reader | +|`Role::ROLE_ID_SUBSCRIPTION_MANAGER` | Subscription Manager | + ## Running Tests The plugin includes comprehensive test coverage for both backend PHP code and frontend Vue components. diff --git a/api/v1/ApiEndpoint.php b/api/v1/ApiEndpoint.php new file mode 100644 index 00000000..973ace03 --- /dev/null +++ b/api/v1/ApiEndpoint.php @@ -0,0 +1,29 @@ +endpoint = $endpoint; + break; + } + } + } + + public function getHandler(): array + { + return $this->endpoint['handler']; + } + + public function getRole(): string + { + return $this->endpoint['role']; + } +} \ No newline at end of file diff --git a/api/v1/CodecheckApiHandler.php b/api/v1/CodecheckApiHandler.php index ee7c145c..ef2b754c 100644 --- a/api/v1/CodecheckApiHandler.php +++ b/api/v1/CodecheckApiHandler.php @@ -2,10 +2,9 @@ namespace APP\plugins\generic\codecheck\api\v1; -use PKP\security\Role; use APP\plugins\generic\codecheck\api\v1\JsonResponse; +use APP\plugins\generic\codecheck\api\v1\CodecheckRoleManager; use APP\core\Request; - use APP\plugins\generic\codecheck\classes\Exceptions\ApiCreateException; use APP\plugins\generic\codecheck\classes\Exceptions\ApiFetchException; use APP\plugins\generic\codecheck\classes\Exceptions\NoMatchingIssuesFoundException; @@ -25,6 +24,11 @@ use \Github\Client; use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlInitException; use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlReadException; +use APP\plugins\generic\codecheck\classes\Exceptions\RoleExceptions\RoleNotFoundException; +use APP\plugins\generic\codecheck\classes\Roles\CodecheckRole; +use APP\plugins\generic\codecheck\classes\Roles\ReadAccessRole; +use APP\plugins\generic\codecheck\classes\Roles\WriteAccessRole; +use APP\plugins\generic\codecheck\classes\Roles\StandardAccessRole; use Illuminate\Support\Facades\DB; class CodecheckApiHandler @@ -41,9 +45,10 @@ class CodecheckApiHandler * Initialize the Codecheck APIHandler class * * @param Request $request API Request + * @param array $roles The CODECHECK roles for `read`, `write` and `standard` access to the API routes * @return void */ - public function __construct(CodecheckPlugin $plugin, Request $request) + public function __construct(CodecheckPlugin $plugin, Request $request, array $roles) { $this->plugin = $plugin; @@ -54,51 +59,46 @@ public function __construct(CodecheckPlugin $plugin, Request $request) $this->codecheckMetadataHandler = new CodecheckMetadataHandler($request, new Client(), new CurlApiClient()); - $this->roles = [ - Role::ROLE_ID_MANAGER, - Role::ROLE_ID_SUB_EDITOR, - Role::ROLE_ID_ASSISTANT, - Role::ROLE_ID_AUTHOR - ]; + $this->roles = $roles; $this->endpoints = [ 'GET' => [ [ 'route' => 'venue', 'handler' => [$this, 'getVenueData'], - 'roles' => $this->roles, + 'role' => StandardAccessRole::class, ], [ 'route' => 'metadata', 'handler' => [$this, 'getMetadata'], - 'roles' => $this->roles, + 'role' => ReadAccessRole::class, ], [ 'route' => 'download', 'handler' => [$this, 'downloadFile'], - 'roles' => $this->roles, + 'roles' => ReadAccessRole::class, ], [ 'route' => 'yaml', 'handler' => [$this, 'generateYaml'], - 'roles' => $this->roles, + 'role' => ReadAccessRole::class, ], ], 'POST' => [ [ 'route' => 'identifier', 'handler' => [$this, 'reserveIdentifier'], - 'roles' => $this->roles, + 'role' => StandardAccessRole::class, ], [ 'route' => 'metadata', 'handler' => [$this, 'saveMetadata'], - 'roles' => $this->roles, + 'role' => WriteAccessRole::class, ], [ 'route' => 'upload', 'handler' => [$this, 'uploadFile'], - 'roles' => $this->roles, + 'role' => WriteAccessRole::class, ], [ 'route' => 'repository', @@ -115,14 +115,36 @@ public function __construct(CodecheckPlugin $plugin, Request $request) $this->request = $request; - $this->authorize(); - // Get the API Route that was called from the request $this->route = $this->getRouteFromRequest(); + + $this->authorize(); + // Serve the Request $this->serveRequest(); } + private function getEndpoint(): ApiEndpoint + { + // get the request Method like POST or GET + $requestMethod = $this->request->getRequestMethod(); + + error_log("Method: " . $requestMethod); + + return new ApiEndpoint($this->endpoints, $this->route, $requestMethod); + } + + private function getPkpRoles(string $codecheckRole): array + { + foreach ($this->roles as $role) { + if($role::class === $codecheckRole) { + return $role->getRoles(); + } + } + + throw new RoleNotFoundException("The CODECHECK Role was not found in the array of roles."); + } + /** * Authorize the API connection * @@ -144,12 +166,24 @@ public function authorize() // Check if the user that accesses this resource has at least one valid Role and if user exists $user = $this->request->getUser() ?? null; $contextId = $this->request->getContext()->getId(); + $apiEndpoint = $this->getEndpoint(); + $codecheckRole = $apiEndpoint->getRole(); + + try { + $pkpRoles = $this->getPkpRoles($codecheckRole); - if(!($user && $user->hasRole($this->roles, $contextId))) { + if(!($user && $user->hasRole($pkpRoles, $contextId))) { + JsonResponse::staticResponse([ + 'success' => false, + 'error' => "User has no assigned Role or doesn't have the right roles assigned to access this resource" + ], 400); + return; + } + } catch (RoleNotFoundException $roleNotFoundException) { JsonResponse::staticResponse([ 'success' => false, - 'error' => "User has no assigned Role or doesn't have the right roles assigned to access this resource" - ], 400); + 'error' => $roleNotFoundException->getMessage() + ], $roleNotFoundException->getCode()); return; } } @@ -176,16 +210,13 @@ private function getRouteFromRequest(): ?string private function serveRequest(): void { // get the request Method like POST or GET - $method = $this->request->getRequestMethod(); + $requestMethod = $this->request->getRequestMethod(); CodecheckLogger::debug('Method: ' . $method); - foreach ($this->endpoints[$method] as $endpoint) { - if($this->route == $endpoint['route']) { - call_user_func($endpoint['handler']); - return; - } - } + $apiEndpoint = $this->getEndpoint(); + + call_user_func($apiEndpoint->getHandler()); } /** diff --git a/classes/Exceptions/RoleExceptions/RoleNotFoundException.php b/classes/Exceptions/RoleExceptions/RoleNotFoundException.php new file mode 100644 index 00000000..5b025389 --- /dev/null +++ b/classes/Exceptions/RoleExceptions/RoleNotFoundException.php @@ -0,0 +1,13 @@ +roles = UniqueArray::from($roles); + } + + /** + * Get the PKP roles + * + * @return array The array containing all PKP roles of this `CodecheckRole` + */ + abstract public function getRoles(): array; +} \ No newline at end of file diff --git a/classes/Roles/ReadAccessRole.php b/classes/Roles/ReadAccessRole.php new file mode 100644 index 00000000..e994a1fc --- /dev/null +++ b/classes/Roles/ReadAccessRole.php @@ -0,0 +1,25 @@ +roles = UniqueArray::from($roles); + } + + /** + * Get the PKP roles + * + * @return array The array containing all PKP roles of this `CodecheckRole` + */ + public function getRoles(): array + { + return $this->roles->toArray(); + } +} \ No newline at end of file diff --git a/classes/Roles/StandardAccessRole.php b/classes/Roles/StandardAccessRole.php new file mode 100644 index 00000000..7eb4b879 --- /dev/null +++ b/classes/Roles/StandardAccessRole.php @@ -0,0 +1,25 @@ +roles = UniqueArray::from($roles); + } + + /** + * Get the PKP roles + * + * @return array The array containing all PKP roles of this `CodecheckRole` + */ + public function getRoles(): array + { + return $this->roles->toArray(); + } +} \ No newline at end of file diff --git a/classes/Roles/WriteAccessRole.php b/classes/Roles/WriteAccessRole.php new file mode 100644 index 00000000..71e9a3f6 --- /dev/null +++ b/classes/Roles/WriteAccessRole.php @@ -0,0 +1,25 @@ +roles = UniqueArray::from($roles); + } + + /** + * Get the PKP roles + * + * @return array The array containing all PKP roles of this `CodecheckRole` + */ + public function getRoles(): array + { + return $this->roles->toArray(); + } +} \ No newline at end of file From 760cc7c7513314a2727d616c874333c4175cc0da Mon Sep 17 00:00:00 2001 From: dxL1nus Date: Mon, 1 Jun 2026 12:30:20 +0200 Subject: [PATCH 2/5] Refactored this branch to work a lot cleaner now #87 --- CodecheckPlugin.php | 37 ++++---------- api/v1/ApiEndpoint.php | 4 +- api/v1/CodecheckApiHandler.php | 49 +++++++------------ classes/CodecheckRoles/CodecheckRoleArray.php | 43 ++++++++++++++++ .../CodecheckRoles/CodecheckRoleManager.php | 18 +++++++ classes/Roles/CodecheckRole.php | 22 --------- classes/Roles/ReadAccessRole.php | 25 ---------- classes/Roles/StandardAccessRole.php | 25 ---------- classes/Roles/WriteAccessRole.php | 25 ---------- public/build/build.iife.js | 0 .../js/Components/CodecheckMetadataForm.vue | 16 +++--- 11 files changed, 99 insertions(+), 165 deletions(-) create mode 100644 classes/CodecheckRoles/CodecheckRoleArray.php create mode 100644 classes/CodecheckRoles/CodecheckRoleManager.php delete mode 100644 classes/Roles/CodecheckRole.php delete mode 100644 classes/Roles/ReadAccessRole.php delete mode 100644 classes/Roles/StandardAccessRole.php delete mode 100644 classes/Roles/WriteAccessRole.php create mode 100644 public/build/build.iife.js diff --git a/CodecheckPlugin.php b/CodecheckPlugin.php index 36615134..be26c7d4 100644 --- a/CodecheckPlugin.php +++ b/CodecheckPlugin.php @@ -19,9 +19,8 @@ use PKP\core\JSONMessage; use APP\plugins\generic\codecheck\classes\Constants; use APP\plugins\generic\codecheck\controllers\page\CodecheckPageHandler; -use APP\plugins\generic\codecheck\classes\Roles\ReadAccessRole; -use APP\plugins\generic\codecheck\classes\Roles\WriteAccessRole; -use APP\plugins\generic\codecheck\classes\Roles\StandardAccessRole; +use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray; +use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager; class CodecheckPlugin extends GenericPlugin { @@ -95,32 +94,16 @@ public function setupAPIHandler(string $hookName, array $args): void if (str_contains($request->getRequestPath(), 'api/v1/codecheck')) { CodecheckLogger::debug('Instantiating the CODECHECK APIHandler'); - $standardAccessRole = new StandardAccessRole([ - Role::ROLE_ID_SITE_ADMIN, - Role::ROLE_ID_MANAGER, - Role::ROLE_ID_SUB_EDITOR, - Role::ROLE_ID_ASSISTANT, - Role::ROLE_ID_REVIEWER, - Role::ROLE_ID_AUTHOR - ]); - - $readAccessRole = new ReadAccessRole([ - Role::ROLE_ID_SITE_ADMIN, - Role::ROLE_ID_MANAGER, - Role::ROLE_ID_SUB_EDITOR, - Role::ROLE_ID_ASSISTANT, - ]); - $writeAccessRole = new WriteAccessRole([ - Role::ROLE_ID_SITE_ADMIN, - Role::ROLE_ID_MANAGER, - ]); + $adminRoles = new CodecheckRoleArray([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]); + $editRoles = new CodecheckRoleArray([$adminRoles, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_MANAGER]); + $readRoles = new CodecheckRoleArray([$editRoles, Role::ROLE_ID_READER, Role::ROLE_ID_AUTHOR]); - $roles = [ - $standardAccessRole, - $readAccessRole, - $writeAccessRole - ]; + $roles = new CodecheckRoleManager( + readMetadata: $readRoles, + editMetadata: $editRoles, + admin: $adminRoles, + ); $apiHandler = new CodecheckApiHandler($this, $request, $roles); CodecheckLogger::debug('API request: ' . $request->getRequestPath()); diff --git a/api/v1/ApiEndpoint.php b/api/v1/ApiEndpoint.php index 973ace03..c77441f9 100644 --- a/api/v1/ApiEndpoint.php +++ b/api/v1/ApiEndpoint.php @@ -2,7 +2,7 @@ namespace APP\plugins\generic\codecheck\api\v1; -use APP\plugins\generic\codecheck\classes\Roles\CodecheckRole; +use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray; class ApiEndpoint { @@ -22,7 +22,7 @@ public function getHandler(): array return $this->endpoint['handler']; } - public function getRole(): string + public function getRole(): CodecheckRoleArray { return $this->endpoint['role']; } diff --git a/api/v1/CodecheckApiHandler.php b/api/v1/CodecheckApiHandler.php index ef2b754c..40c85d3e 100644 --- a/api/v1/CodecheckApiHandler.php +++ b/api/v1/CodecheckApiHandler.php @@ -3,7 +3,6 @@ namespace APP\plugins\generic\codecheck\api\v1; use APP\plugins\generic\codecheck\api\v1\JsonResponse; -use APP\plugins\generic\codecheck\api\v1\CodecheckRoleManager; use APP\core\Request; use APP\plugins\generic\codecheck\classes\Exceptions\ApiCreateException; use APP\plugins\generic\codecheck\classes\Exceptions\ApiFetchException; @@ -24,17 +23,14 @@ use \Github\Client; use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlInitException; use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlReadException; +use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager; use APP\plugins\generic\codecheck\classes\Exceptions\RoleExceptions\RoleNotFoundException; -use APP\plugins\generic\codecheck\classes\Roles\CodecheckRole; -use APP\plugins\generic\codecheck\classes\Roles\ReadAccessRole; -use APP\plugins\generic\codecheck\classes\Roles\WriteAccessRole; -use APP\plugins\generic\codecheck\classes\Roles\StandardAccessRole; use Illuminate\Support\Facades\DB; class CodecheckApiHandler { private JsonResponse $response; - private array $roles; + private CodecheckRoleManager $roles; private array $endpoints; private string $route; private CodecheckPlugin $plugin; @@ -45,10 +41,10 @@ class CodecheckApiHandler * Initialize the Codecheck APIHandler class * * @param Request $request API Request - * @param array $roles The CODECHECK roles for `read`, `write` and `standard` access to the API routes + * @param CodecheckRoleManager $roles The CODECHECK roles for `read`, `write` and `standard` access to the API routes * @return void */ - public function __construct(CodecheckPlugin $plugin, Request $request, array $roles) + public function __construct(CodecheckPlugin $plugin, Request $request, CodecheckRoleManager $roles) { $this->plugin = $plugin; @@ -66,39 +62,39 @@ public function __construct(CodecheckPlugin $plugin, Request $request, array $ro [ 'route' => 'venue', 'handler' => [$this, 'getVenueData'], - 'role' => StandardAccessRole::class, + 'role' => $roles->readMetadata(), ], [ 'route' => 'metadata', 'handler' => [$this, 'getMetadata'], - 'role' => ReadAccessRole::class, + 'role' => $roles->readMetadata(), ], [ 'route' => 'download', 'handler' => [$this, 'downloadFile'], - 'roles' => ReadAccessRole::class, + 'roles' => $roles->readMetadata(), ], [ 'route' => 'yaml', 'handler' => [$this, 'generateYaml'], - 'role' => ReadAccessRole::class, + 'role' => $roles->editMetadata(), ], ], 'POST' => [ [ 'route' => 'identifier', 'handler' => [$this, 'reserveIdentifier'], - 'role' => StandardAccessRole::class, + 'role' => $roles->editMetadata(), ], [ 'route' => 'metadata', 'handler' => [$this, 'saveMetadata'], - 'role' => WriteAccessRole::class, + 'role' => $roles->editMetadata(), ], [ 'route' => 'upload', 'handler' => [$this, 'uploadFile'], - 'role' => WriteAccessRole::class, + 'role' => $roles->editMetadata(), ], [ 'route' => 'repository', @@ -134,17 +130,6 @@ private function getEndpoint(): ApiEndpoint return new ApiEndpoint($this->endpoints, $this->route, $requestMethod); } - private function getPkpRoles(string $codecheckRole): array - { - foreach ($this->roles as $role) { - if($role::class === $codecheckRole) { - return $role->getRoles(); - } - } - - throw new RoleNotFoundException("The CODECHECK Role was not found in the array of roles."); - } - /** * Authorize the API connection * @@ -170,7 +155,7 @@ public function authorize() $codecheckRole = $apiEndpoint->getRole(); try { - $pkpRoles = $this->getPkpRoles($codecheckRole); + $pkpRoles = $codecheckRole->getRoles(); if(!($user && $user->hasRole($pkpRoles, $contextId))) { JsonResponse::staticResponse([ @@ -212,7 +197,7 @@ private function serveRequest(): void // get the request Method like POST or GET $requestMethod = $this->request->getRequestMethod(); - CodecheckLogger::debug('Method: ' . $method); + CodecheckLogger::debug('Method: ' . $requestMethod); $apiEndpoint = $this->getEndpoint(); @@ -414,11 +399,11 @@ public function getMetadata(): void $result = $this->codecheckMetadataHandler->getMetadata($this->request, $submissionId); if(isset($result['error'])) { - $result = array_merge($result, ['submissionID' => $submissionId]); + $result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]); JsonResponse::staticResponse($result, 404); } - JsonResponse::staticResponse($result, 200); + JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200); } /** @@ -432,11 +417,11 @@ public function saveMetadata(): void $result = $this->codecheckMetadataHandler->saveMetadata($this->request, $submissionId); if(isset($result['error'])) { - $result = array_merge($result, ['submissionID' => $submissionId]); + $result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]); JsonResponse::staticResponse($result, 404); } - JsonResponse::staticResponse($result, 200); + JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200); } /** diff --git a/classes/CodecheckRoles/CodecheckRoleArray.php b/classes/CodecheckRoles/CodecheckRoleArray.php new file mode 100644 index 00000000..e93e6f35 --- /dev/null +++ b/classes/CodecheckRoles/CodecheckRoleArray.php @@ -0,0 +1,43 @@ + so Roles can be defined dependant on each other + * + * @param array $roles The array of the PKPRoles which are allowed to access this Codecheck Resource + */ + public function __construct(array $roles) + { + $this->roles = new UniqueArray(); + $this->addRoles($roles); + } + + private function addRoles(mixed $roles) { + foreach ($roles as $role) { + if($role instanceof CodecheckRoleArray) { + $this->addRoles($role->getRoles()); + } elseif (is_array($role)) { + $this->addRoles($role); + } else { + $this->roles->add($role); + } + } + } + + /** + * Get the PKP roles + * + * @return array The array containing all PKP roles of this `CodecheckRole` + */ + public function getRoles(): array + { + return $this->roles->toArray(); + } +} \ No newline at end of file diff --git a/classes/CodecheckRoles/CodecheckRoleManager.php b/classes/CodecheckRoles/CodecheckRoleManager.php new file mode 100644 index 00000000..9fa8d28c --- /dev/null +++ b/classes/CodecheckRoles/CodecheckRoleManager.php @@ -0,0 +1,18 @@ +readMetadata; } + public function editMetadata(): CodecheckRoleArray { return $this->editMetadata; } + public function admin(): CodecheckRoleArray { return $this->admin; } +} \ No newline at end of file diff --git a/classes/Roles/CodecheckRole.php b/classes/Roles/CodecheckRole.php deleted file mode 100644 index b2fa800b..00000000 --- a/classes/Roles/CodecheckRole.php +++ /dev/null @@ -1,22 +0,0 @@ -roles = UniqueArray::from($roles); - } - - /** - * Get the PKP roles - * - * @return array The array containing all PKP roles of this `CodecheckRole` - */ - abstract public function getRoles(): array; -} \ No newline at end of file diff --git a/classes/Roles/ReadAccessRole.php b/classes/Roles/ReadAccessRole.php deleted file mode 100644 index e994a1fc..00000000 --- a/classes/Roles/ReadAccessRole.php +++ /dev/null @@ -1,25 +0,0 @@ -roles = UniqueArray::from($roles); - } - - /** - * Get the PKP roles - * - * @return array The array containing all PKP roles of this `CodecheckRole` - */ - public function getRoles(): array - { - return $this->roles->toArray(); - } -} \ No newline at end of file diff --git a/classes/Roles/StandardAccessRole.php b/classes/Roles/StandardAccessRole.php deleted file mode 100644 index 7eb4b879..00000000 --- a/classes/Roles/StandardAccessRole.php +++ /dev/null @@ -1,25 +0,0 @@ -roles = UniqueArray::from($roles); - } - - /** - * Get the PKP roles - * - * @return array The array containing all PKP roles of this `CodecheckRole` - */ - public function getRoles(): array - { - return $this->roles->toArray(); - } -} \ No newline at end of file diff --git a/classes/Roles/WriteAccessRole.php b/classes/Roles/WriteAccessRole.php deleted file mode 100644 index 71e9a3f6..00000000 --- a/classes/Roles/WriteAccessRole.php +++ /dev/null @@ -1,25 +0,0 @@ -roles = UniqueArray::from($roles); - } - - /** - * Get the PKP roles - * - * @return array The array containing all PKP roles of this `CodecheckRole` - */ - public function getRoles(): array - { - return $this->roles->toArray(); - } -} \ No newline at end of file diff --git a/public/build/build.iife.js b/public/build/build.iife.js new file mode 100644 index 00000000..e69de29b diff --git a/resources/js/Components/CodecheckMetadataForm.vue b/resources/js/Components/CodecheckMetadataForm.vue index 283eea56..f2329895 100644 --- a/resources/js/Components/CodecheckMetadataForm.vue +++ b/resources/js/Components/CodecheckMetadataForm.vue @@ -498,11 +498,11 @@ export default { } }); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - const data = await response.json(); + + if (!response.ok || !data.success) { + throw new Error(`[HTTP ${response.status}] ${data.error}`); + } this.submissionData = { id: data.submission?.id || submissionId, @@ -810,15 +810,17 @@ export default { body: JSON.stringify(dataToSave) }); - if (!response.ok) { - throw new Error('Failed to save'); + const data = await response.json(); + + if (!response.ok || !data.success) { + throw new Error(`[HTTP ${response.status}] ${data.error}`); } this.hasUnsavedChanges = false; this.showMessage(this.t('plugins.generic.codecheck.savedSuccessfully'), 'success'); } catch (error) { console.error('Save error:', error); - this.showMessage(this.t('plugins.generic.codecheck.saveFailed'), 'error'); + this.showMessage(this.t('plugins.generic.codecheck.saveFailed') + ': ' + error.message, 'error'); } finally { this.saving = false; } From 835977bef325af1eaaeab86bdce9bb822c588565 Mon Sep 17 00:00:00 2001 From: dxL1nus Date: Mon, 1 Jun 2026 12:51:52 +0200 Subject: [PATCH 3/5] Fixed small bugs after rebase #87 --- api/v1/ApiEndpoint.php | 2 +- api/v1/CodecheckApiHandler.php | 10 +++--- public/build/build.iife.js | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/api/v1/ApiEndpoint.php b/api/v1/ApiEndpoint.php index c77441f9..f5e13c0d 100644 --- a/api/v1/ApiEndpoint.php +++ b/api/v1/ApiEndpoint.php @@ -24,6 +24,6 @@ public function getHandler(): array public function getRole(): CodecheckRoleArray { - return $this->endpoint['role']; + return $this->endpoint['roles']; } } \ No newline at end of file diff --git a/api/v1/CodecheckApiHandler.php b/api/v1/CodecheckApiHandler.php index 40c85d3e..fd4fa2df 100644 --- a/api/v1/CodecheckApiHandler.php +++ b/api/v1/CodecheckApiHandler.php @@ -77,7 +77,7 @@ public function __construct(CodecheckPlugin $plugin, Request $request, Codecheck [ 'route' => 'yaml', 'handler' => [$this, 'generateYaml'], - 'role' => $roles->editMetadata(), + 'roles' => $roles->readMetadata(), ], ], 'POST' => [ @@ -99,12 +99,12 @@ public function __construct(CodecheckPlugin $plugin, Request $request, Codecheck [ 'route' => 'repository', 'handler' => [$this, 'loadMetadataFromRepository'], - 'roles' => $this->roles, + 'roles' => $roles->editMetadata(), ], [ 'route' => 'yaml/validate', 'handler' => [$this, 'validateYamlStructure'], - 'roles' => $this->roles, + 'roles' => $roles->readMetadata(), ], ], ]; @@ -570,11 +570,11 @@ public function generateYaml(): void $result = $this->codecheckMetadataHandler->generateYaml($this->request, $submissionId); if(isset($result['error'])) { - $result = array_merge($result, ['submissionID' => $submissionId]); + $result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]); JsonResponse::staticResponse($result, 404); } - JsonResponse::staticResponse($result, 200); + JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200); } /** diff --git a/public/build/build.iife.js b/public/build/build.iife.js index e69de29b..69e4412d 100644 --- a/public/build/build.iife.js +++ b/public/build/build.iife.js @@ -0,0 +1,56 @@ +(function(e){"use strict";const _=(c,i)=>{const n=c.__vccOpts||c;for(const[o,t]of i)n[o]=t;return n},C={class:"codecheck-manifest-files"},D={class:"manifest-files-list"},B=["onUpdate:modelValue","placeholder"],T=["onUpdate:modelValue","placeholder"],M=["onClick"],w=_({__name:"CodecheckManifestFiles",props:{name:{type:String,required:!0},label:{type:String,required:!0},description:{type:String,default:""},value:{type:String,default:""},isRequired:{type:Boolean,default:!1}},setup(c){const{useLocalize:i}=pkp.modules.useLocalize,{t:n}=i(),o=c,t=e.ref([]);e.onMounted(()=>{o.value&&o.value.split(` +`).forEach(l=>{var m,p;if(l.trim()){const r=l.split(" - ");t.value.push({filename:((m=r[0])==null?void 0:m.trim())||"",comment:((p=r[1])==null?void 0:p.trim())||""})}}),t.value.length===0&&a()});function a(){t.value.push({filename:"",comment:""})}function s(l){t.value.splice(l,1),t.value.length===0&&a(),d()}function d(){var r;const l=t.value.filter(h=>h.filename.trim()).map(h=>{const u=h.filename.trim(),g=h.comment.trim();return g?`${u} - ${g}`:u}).join(` +`),m=new CustomEvent("update",{detail:l,bubbles:!0}),p=(r=document.querySelector(`textarea[name="${o.name}"]`))==null?void 0:r.previousElementSibling;p&&p.dispatchEvent(m)}return(l,m)=>(e.openBlock(),e.createElementBlock("div",C,[e.createElementVNode("div",D,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(p,r)=>(e.openBlock(),e.createElementBlock("div",{key:r,class:"manifest-file-row"},[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":h=>p.filename=h,type:"text",placeholder:e.unref(n)("plugins.generic.codecheck.manifestFiles.filenamePlaceholder"),onInput:d,class:"form-control"},null,40,B),[[e.vModelText,p.filename]]),e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":h=>p.comment=h,type:"text",placeholder:e.unref(n)("plugins.generic.codecheck.manifestFiles.commentPlaceholder"),onInput:d,class:"form-control"},null,40,T),[[e.vModelText,p.comment]]),e.createElementVNode("button",{type:"button",onClick:h=>s(r),class:"btn-remove"},"×",8,M)]))),128))]),e.createElementVNode("button",{type:"button",onClick:a,class:"btn-add"},e.toDisplayString(e.unref(n)("plugins.generic.codecheck.manifestFiles.addButton")),1)]))}},[["__scopeId","data-v-b2e2483c"]]),x={class:"codecheck-repository-list"},I={class:"repository-list"},R=["onUpdate:modelValue","placeholder","onBlur"],F=["onClick"],E=_({__name:"CodecheckRepositoryList",props:{name:{type:String,required:!0},label:{type:String,required:!0},description:{type:String,default:""},value:{type:String,default:""}},setup(c){const{useLocalize:i}=pkp.modules.useLocalize,{t:n}=i(),o=c,t=e.ref([]),a=e.ref([]);e.onMounted(()=>{o.value&&o.value.split(` +`).forEach(p=>{p.trim()&&t.value.push(p.trim())}),t.value.length===0&&s()});function s(){t.value.push("https://"),a.value.push("")}function d(p){t.value.splice(p,1),a.value.splice(p,1),m()}function l(p){const r=t.value[p];if(!r.trim()||r==="https://"){a.value[p]="";return}try{new URL(r),!r.startsWith("http://")&&!r.startsWith("https://")?a.value[p]=n("plugins.generic.codecheck.repository.validation.protocol"):a.value[p]=""}catch{a.value[p]=n("plugins.generic.codecheck.repository.validation.invalid")}}function m(){var u;const p=t.value.filter(g=>g.trim()&&g!=="https://").join(` +`),r=new CustomEvent("update",{detail:p,bubbles:!0}),h=(u=document.querySelector(`textarea[name="${o.name}"]`))==null?void 0:u.previousElementSibling;h&&h.dispatchEvent(r)}return(p,r)=>(e.openBlock(),e.createElementBlock("div",x,[e.createElementVNode("div",I,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(h,u)=>(e.openBlock(),e.createElementBlock("div",{key:u,class:"repository-row"},[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":g=>t.value[u]=g,type:"url",placeholder:e.unref(n)("plugins.generic.codecheck.repository.placeholder"),onInput:m,onBlur:g=>l(u),class:e.normalizeClass(["form-control",{"is-invalid":a.value[u]}])},null,42,R),[[e.vModelText,t.value[u]]]),e.createElementVNode("button",{type:"button",onClick:g=>d(u),class:"btn-remove"},"×",8,F)]))),128)),p.error?(e.openBlock(!0),e.createElementBlock(e.Fragment,{key:0},e.renderList(a.value,(h,u)=>(e.openBlock(),e.createElementBlock("div",{key:"error-"+u,class:"pkpFormField__error"},e.toDisplayString(h),1))),128)):e.createCommentVNode("",!0)]),e.createElementVNode("button",{type:"button",onClick:s,class:"btn-add"},e.toDisplayString(e.unref(n)("plugins.generic.codecheck.repository.addButton")),1)]))}},[["__scopeId","data-v-7edbd363"]]),U={class:"codecheck-review-display"},L={key:0,class:"codecheck-info"},v={class:"info-section"},A={key:0,class:"info-section"},O={key:1,class:"info-section"},P={key:2,class:"info-section"},z={key:3,class:"info-section"},H={key:0},q={key:4,class:"info-section"},j={key:0,class:"orcid-badge"},Y={key:5,class:"info-section"},W=["href"],K={key:6,class:"info-section"},J={key:7,class:"info-section"},X={key:8,class:"info-section"},G=["href"],Q={class:"actions"},Z={key:1,class:"codecheck-not-opted"},$=_({__name:"CodecheckReviewDisplay",props:{submission:{type:Object,required:!0}},setup(c){const{useLocalize:i}=pkp.modules.useLocalize,{t:n}=i(),o=c,t=e.computed(()=>{if(o.submission.codecheckMetadata){if(typeof o.submission.codecheckMetadata=="string")try{return JSON.parse(o.submission.codecheckMetadata)}catch(r){return console.error("Failed to parse codecheck metadata:",r),{}}return o.submission.codecheckMetadata}return{}}),a=e.computed(()=>Object.keys(t.value).length>0);function s(){return t.value.certificate&&t.value.checkTime?"complete":a.value?"in-progress":"pending"}const d=e.computed(()=>{switch(s()){case"complete":return"status-complete";case"in-progress":return"status-in-progress";case"pending":default:return"status-pending"}});function l(){switch(s()){case"complete":return n("plugins.generic.codecheck.status.complete");case"in-progress":return n("plugins.generic.codecheck.status.inProgress");case"pending":default:return n("plugins.generic.codecheck.status.pending")}}function m(r){return r?new Date(r).toLocaleString():""}function p(){const r=pkp.registry.getPiniaStore("workflow");r.selectedMenuState={primaryMenuItem:"workflow",stageId:999}}return(r,h)=>{var g;const u=e.resolveComponent("pkp-button");return e.openBlock(),e.createElementBlock("div",U,[e.createElementVNode("h3",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.reviewTitle")),1),(g=c.submission)!=null&&g.codecheckOptIn?(e.openBlock(),e.createElementBlock("div",L,[e.createElementVNode("div",v,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.status")),1),e.createElementVNode("span",{class:e.normalizeClass(["status-badge",d.value])},e.toDisplayString(l()),3)]),a.value&&t.value.configVersion?(e.openBlock(),e.createElementBlock("div",A,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.review.configVersion")),1),e.createElementVNode("p",null,e.toDisplayString(t.value.configVersion),1)])):e.createCommentVNode("",!0),a.value&&t.value.publicationType?(e.openBlock(),e.createElementBlock("div",O,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.review.publicationType")),1),e.createElementVNode("p",null,e.toDisplayString(t.value.publicationType==="doi"?e.unref(n)("plugins.generic.codecheck.review.publicationType.doi"):e.unref(n)("plugins.generic.codecheck.review.publicationType.separate")),1)])):e.createCommentVNode("",!0),a.value&&t.value.certificate?(e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.identifier.label")),1),e.createElementVNode("p",null,e.toDisplayString(t.value.certificate),1)])):e.createCommentVNode("",!0),a.value&&t.value.manifest&&t.value.manifest.length>0?(e.openBlock(),e.createElementBlock("div",z,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.review.manifestFiles")),1),e.createElementVNode("ul",null,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value.manifest,(y,b)=>(e.openBlock(),e.createElementBlock("li",{key:b},[e.createElementVNode("strong",null,e.toDisplayString(y.file),1),y.comment?(e.openBlock(),e.createElementBlock("span",H," - "+e.toDisplayString(y.comment),1)):e.createCommentVNode("",!0)]))),128))])])):e.createCommentVNode("",!0),a.value&&t.value.codecheckers&&t.value.codecheckers.length>0?(e.openBlock(),e.createElementBlock("div",q,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.review.codecheckers")),1),e.createElementVNode("ul",null,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value.codecheckers,(y,b)=>(e.openBlock(),e.createElementBlock("li",{key:b},[e.createTextVNode(e.toDisplayString(y.name)+" ",1),y.orcid?(e.openBlock(),e.createElementBlock("span",j,e.toDisplayString(y.orcid),1)):e.createCommentVNode("",!0)]))),128))])])):e.createCommentVNode("",!0),a.value&&t.value.repository?(e.openBlock(),e.createElementBlock("div",Y,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.repositories.title")),1),e.createElementVNode("a",{href:t.value.repository,target:"_blank"},e.toDisplayString(t.value.repository),9,W)])):e.createCommentVNode("",!0),a.value&&t.value.checkTime?(e.openBlock(),e.createElementBlock("div",K,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.completionTime.label")),1),e.createElementVNode("p",null,e.toDisplayString(m(t.value.checkTime)),1)])):e.createCommentVNode("",!0),a.value&&t.value.summary?(e.openBlock(),e.createElementBlock("div",J,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.certificate.summary")),1),e.createElementVNode("p",null,e.toDisplayString(t.value.summary),1)])):e.createCommentVNode("",!0),a.value&&t.value.reportUrl?(e.openBlock(),e.createElementBlock("div",X,[e.createElementVNode("h4",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.review.reportUrl")),1),e.createElementVNode("a",{href:t.value.reportUrl,target:"_blank"},e.toDisplayString(t.value.reportUrl),9,G)])):e.createCommentVNode("",!0),e.createElementVNode("div",Q,[e.createVNode(u,{onClick:p},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(e.unref(n)("plugins.generic.codecheck.viewFullMetadata")),1)]),_:1})])])):(e.openBlock(),e.createElementBlock("div",Z,[e.createElementVNode("p",null,e.toDisplayString(e.unref(n)("plugins.generic.codecheck.notOptedIn")),1)]))])}}},[["__scopeId","data-v-2ac9f582"]]),{useLocalize:ee}=pkp.modules.useLocalize,te={name:"CodecheckMetadataForm",props:{submission:{type:Object,required:!0},canEdit:{type:Boolean,default:!0},name:{type:String},value:{type:String}},setup(){const{t:c}=ee();return{t:c}},data(){return{loading:!0,saving:!1,dataLoaded:!1,error:null,saveMessage:"",saveMessageType:"",repositories:[],hasUnsavedChanges:!1,submissionData:{id:null,title:"",authors:[],doi:"",codeRepository:"",dataRepository:"",manifestFiles:"",dataAvailabilityStatement:""},certificateIdentifier:{venueType:"default",venueName:"default",venueTypes:[],venueNames:[],customLabelSelected:[],customLabels:[],issueUrl:""},metadata:{version:"latest",publicationType:"doi",manifest:[],repository:"",source:"",codecheckers:[],certificate:"",check_time:"",summary:"",report:"",additionalContent:""}}},computed:{canPreview(){return this.metadata.manifest.length>0&&this.metadata.codecheckers.length>0&&this.metadata.certificate&&!this.hasUnsavedChanges},previewButtonTooltip(){return this.hasUnsavedChanges?this.t("plugins.generic.codecheck.preview.saveFirst"):this.canPreview?this.t("plugins.generic.codecheck.preview.ready"):this.t("plugins.generic.codecheck.preview.missingRequired")},isIdentifierReserved(){return this.metadata.certificate.trim()!==""}},mounted(){this.loadData(),this.getVenueData()},watch:{metadata:{handler(){this.hasUnsavedChanges=!0},deep:!0},repositories:{handler(){this.hasUnsavedChanges=!0},deep:!0}},methods:{async loadData(){var c,i,n,o,t,a,s,d;this.loading=!0,this.error=null,this.dataLoaded=!1;try{if(!this.submission||!this.submission.id)throw new Error("Invalid submission object");const l=this.submission.id;let m=pkp.context.apiBaseUrl;m+="codecheck",m=`${m}/metadata?submissionId=${l}`;const p=await fetch(m,{method:"GET",headers:{"X-Csrf-Token":pkp.currentUser.csrfToken}}),r=await p.json();if(!p.ok||!r.success)throw new Error(`[HTTP ${p.status}] ${r.error}`);this.submissionData={id:((c=r.submission)==null?void 0:c.id)||l,title:((i=r.submission)==null?void 0:i.title)||"",authors:Array.isArray((n=r.submission)==null?void 0:n.authors)?r.submission.authors:[],doi:((o=r.submission)==null?void 0:o.doi)||"",codeRepository:((t=r.submission)==null?void 0:t.codeRepository)||"",dataRepository:((a=r.submission)==null?void 0:a.dataRepository)||"",manifestFiles:((s=r.submission)==null?void 0:s.manifestFiles)||"",dataAvailabilityStatement:((d=r.submission)==null?void 0:d.dataAvailabilityStatement)||""},r.codecheck&&typeof r.codecheck=="object"&&(this.metadata={version:r.codecheck.version||r.codecheck.version||"latest",publicationType:r.codecheck.publicationType||r.codecheck.publication_type||"doi",manifest:Array.isArray(r.codecheck.manifest)?r.codecheck.manifest:typeof r.codecheck.manifest=="string"?JSON.parse(r.codecheck.manifest):[],repository:r.codecheck.repository||"",source:r.codecheck.source||"",codecheckers:Array.isArray(r.codecheck.codecheckers)?r.codecheck.codecheckers:typeof r.codecheck.codecheckers=="string"?JSON.parse(r.codecheck.codecheckers):[],certificate:r.codecheck.certificate||"",check_time:r.codecheck.check_time||r.codecheck.check_time?this.formatDateTimeLocal(r.codecheck.check_time||r.codecheck.check_time):"",summary:r.codecheck.summary||"",report:r.codecheck.report||r.codecheck.report||"",additionalContent:r.codecheck.additionalContent||r.codecheck.additional_content||""},r.codecheck.repository&&(this.repositories=r.codecheck.repository.split(",").map(h=>h.trim()).filter(h=>h))),this.dataLoaded=!0,this.$nextTick(()=>{this.hasUnsavedChanges=!1})}catch(l){console.error("Load error:",l),this.error=this.t("plugins.generic.codecheck.loadError")+": "+l.message}finally{this.loading=!1}},async loadMetadataFromRepository(c){var o,t,a,s,d,l,m,p,r,h,u,g,y,b;let i=this.repositories[c];console.log(i);let n=pkp.context.apiBaseUrl+"codecheck";try{const f=await(await fetch(`${n}/repository`,{method:"POST",headers:{"Content-Type":"application/json","X-Csrf-Token":pkp.currentUser.csrfToken},body:JSON.stringify({repository:i})})).json();f.success?(console.log("Success:",f.repository),this.submissionData={id:this.submissionData.id,title:((o=f.metadata)==null?void 0:o.paper.title)??this.submissionData.title,authors:((t=f.metadata)==null?void 0:t.paper.authors)??this.submissionData.authors,doi:((a=f.metadata)==null?void 0:a.paper.doi)??this.submissionData.doi,codeRepository:this.submissionData.codeRepository,dataRepository:this.submissionData.dataRepository,manifestFiles:((s=f.metadata)==null?void 0:s.manifest)??this.submissionData.manifestFiles,dataAvailabilityStatement:this.submissionData.dataAvailabilityStatement},this.metadata={version:((d=f.metadata)==null?void 0:d.version.replace(/^https:\/\/codecheck\.org\.uk\/spec\/config\/|\/$/g,""))??this.metadata.version,publicationType:((l=f.metadata)==null?void 0:l.publicationType)??this.metadata.publicationType,manifest:((m=f.metadata)==null?void 0:m.manifest)??this.metadata.manifest,repository:this.metadata.repository,source:((p=f.metadata)==null?void 0:p.source)??this.metadata.source,codecheckers:((r=f.metadata)==null?void 0:r.codechecker)??this.metadata.codecheckers,certificate:((h=f.metadata)==null?void 0:h.certificate)??this.metadata.certificate,check_time:this.formatDateTimeLocal((u=f.metadata)==null?void 0:u.check_time)??this.metadata.check_time,summary:((g=f.metadata)==null?void 0:g.summary)??this.metadata.summary,report:((y=f.metadata)==null?void 0:y.report)??this.metadata.report,additionalContent:((b=f.metadata)==null?void 0:b.additionalContent)??this.metadata.additionalContent}):console.error("Error:",f.error)}catch(N){console.error("Failed to fetch metadata from existing Repository:",N)}},triggerFileUpload(){this.$refs.fileInput.click()},handleFileUpload(c){const i=c.target.files;if(!(!i||i.length===0)){for(let n=0;n";i({title:this.t("plugins.generic.codecheck.repositories.infoTitle"),message:n,actions:[{label:this.t("plugins.generic.codecheck.modal.close"),callback:o=>o()}]})},showFallbackRepositoryInfoModal(){prompt(this.t("plugins.generic.codecheck.repositories.infoTextOne")+this.t("plugins.generic.codecheck.repositories.infoTextTwo")+this.t("plugins.generic.codecheck.repositories.infoTextNote")+this.t("plugins.generic.codecheck.repositories.infoTextMoreInformation")+''+this.t("plugins.generic.codecheck.repositories.infoTextLinkToAllowedRepositories")+"")},showCodecheckerModal(){this.canUsePkpModal()?this.showPkpCodecheckerModal():this.showFallbackCodecheckerModal()},showPkpCodecheckerModal(){const{useModal:c}=pkp.modules.useModal,{openDialog:i}=c(),n='';i({title:this.t("plugins.generic.codecheck.codecheckers.addCodechecker"),message:n,actions:[{label:this.t("plugins.generic.codecheck.modal.cancel"),callback:o=>o()},{label:this.t("plugins.generic.codecheck.modal.add"),isPrimary:!0,callback:o=>{const t=document.getElementById("checker-name"),a=document.getElementById("checker-orcid"),s=(t==null?void 0:t.value)||"",d=(a==null?void 0:a.value)||"";s.trim()&&this.metadata.codecheckers.push({name:s.trim(),orcid:d.trim()}),o()}}]})},showFallbackCodecheckerModal(){const c=prompt(this.t("plugins.generic.codecheck.codecheckers.enterName"));if(c&&c.trim()){const i=prompt(this.t("plugins.generic.codecheck.codecheckers.enterOrcid"));this.metadata.codecheckers.push({name:c.trim(),orcid:i?i.trim():""})}},removeCodechecker(c){confirm(this.t("plugins.generic.codecheck.codecheckers.removeConfirm"))&&this.metadata.codecheckers.splice(c,1)},async saveMetadata(){if(this.validateForm()){this.saving=!0,this.saveMessage="";try{const c={version:this.metadata.version,publication_type:this.metadata.publicationType,manifest:this.metadata.manifest,repository:this.repositories.join(", "),source:this.metadata.source,codecheckers:this.metadata.codecheckers,certificate:this.metadata.certificate,check_time:this.metadata.check_time,summary:this.metadata.summary,report:this.metadata.report,additional_content:this.metadata.additionalContent};console.log("Saving CODECHECK data:",c);const i=this.submission.id;let n=pkp.context.apiBaseUrl;n+="codecheck",n=`${n}/metadata?submissionId=${i}`;const o=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-Csrf-Token":pkp.currentUser.csrfToken},body:JSON.stringify(c)}),t=await o.json();if(!o.ok||!t.success)throw new Error(`[HTTP ${o.status}] ${t.error}`);this.hasUnsavedChanges=!1,this.showMessage(this.t("plugins.generic.codecheck.savedSuccessfully"),"success")}catch(c){console.error("Save error:",c),this.showMessage(this.t("plugins.generic.codecheck.saveFailed")+": "+c.message,"error")}finally{this.saving=!1}}},async generateYamlContent(){try{const c=this.submission.id;let i=pkp.context.apiBaseUrl;i+="codecheck",i=`${i}/yaml?submissionId=${c}`;const n=await fetch(i,{method:"GET",headers:{"X-Csrf-Token":pkp.currentUser.csrfToken}});if(!n.ok)throw new Error("Failed to generate YAML");return(await n.json()).yaml}catch(c){throw console.error("Yaml generation error:",c),c}},async previewYaml(){try{const c=await this.generateYamlContent();if(!await this.validateGeneratedYamlFile(c))return;this.canUsePkpModal()?this.showYamlModal(c):this.showYamlFallback(c)}catch(c){console.error("Preview error:",c),this.showMessage(`${this.t("plugins.generic.codecheck.yamlPreviewFailed")} +${c}`,"error")}},showYamlModal(c){const{useModal:i}=pkp.modules.useModal,{openDialog:n}=i(),o="downloadCodecheckYaml_"+Date.now();window[o]=function(){const a=new Blob([c],{type:"text/yaml"}),s=URL.createObjectURL(a),d=document.createElement("a");d.href=s,d.download="codecheck.yml",d.click(),URL.revokeObjectURL(s)};const t='
'+this.escapeHtml(c)+"
";n({title:this.t("plugins.generic.codecheck.yaml.previewTitle"),message:t,actions:[{label:this.t("plugins.generic.codecheck.yaml.download"),isPrimary:!0,callback:a=>{window[o](),delete window[o],a()}},{label:this.t("plugins.generic.codecheck.yaml.close"),callback:a=>{delete window[o],a()}}]})},showYamlFallback(c){const i=window.open("","_blank"),n=this.escapeHtml(c),o=JSON.stringify(c),t='CODECHECK Metadata Preview

📄 CODECHECK Metadata Preview

'+n+"