diff --git a/CodecheckPlugin.php b/CodecheckPlugin.php index c7ba412d..be26c7d4 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); + + $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 = new CodecheckRoleManager( + readMetadata: $readRoles, + editMetadata: $editRoles, + admin: $adminRoles, + ); + + $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..d4cc9df4 --- /dev/null +++ b/api/v1/ApiEndpoint.php @@ -0,0 +1,29 @@ +endpoint = $endpoint; + break; + } + } + } + + public function getHandler(): array + { + return $this->endpoint['handler']; + } + + public function getRoles(): CodecheckRoleArray + { + return $this->endpoint['roles']; + } +} \ No newline at end of file diff --git a/api/v1/CodecheckApiHandler.php b/api/v1/CodecheckApiHandler.php index ee7c145c..447cd257 100644 --- a/api/v1/CodecheckApiHandler.php +++ b/api/v1/CodecheckApiHandler.php @@ -2,10 +2,8 @@ namespace APP\plugins\generic\codecheck\api\v1; -use PKP\security\Role; use APP\plugins\generic\codecheck\api\v1\JsonResponse; 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; @@ -23,14 +21,13 @@ use APP\facades\Repo; use \Github\Client; -use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlInitException; -use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlReadException; -use Illuminate\Support\Facades\DB; +use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager; +use APP\plugins\generic\codecheck\classes\Exceptions\RoleExceptions\RoleNotFoundException; class CodecheckApiHandler { private JsonResponse $response; - private array $roles; + private CodecheckRoleManager $roles; private array $endpoints; private string $route; private CodecheckPlugin $plugin; @@ -41,9 +38,10 @@ class CodecheckApiHandler * Initialize the Codecheck APIHandler class * * @param Request $request API Request + * @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) + public function __construct(CodecheckPlugin $plugin, Request $request, CodecheckRoleManager $roles) { $this->plugin = $plugin; @@ -54,75 +52,81 @@ 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, + 'roles' => $roles->readMetadata(), ], [ 'route' => 'metadata', 'handler' => [$this, 'getMetadata'], - 'roles' => $this->roles, + 'roles' => $roles->readMetadata(), ], [ 'route' => 'download', 'handler' => [$this, 'downloadFile'], - 'roles' => $this->roles, + 'roles' => $roles->readMetadata(), ], [ 'route' => 'yaml', 'handler' => [$this, 'generateYaml'], - 'roles' => $this->roles, + 'roles' => $roles->readMetadata(), ], ], 'POST' => [ [ 'route' => 'identifier', 'handler' => [$this, 'reserveIdentifier'], - 'roles' => $this->roles, + 'roles' => $roles->editMetadata(), ], [ 'route' => 'metadata', 'handler' => [$this, 'saveMetadata'], - 'roles' => $this->roles, + 'roles' => $roles->editMetadata(), ], [ 'route' => 'upload', 'handler' => [$this, 'uploadFile'], - 'roles' => $this->roles, + 'roles' => $roles->editMetadata(), ], [ 'route' => 'repository', 'handler' => [$this, 'loadMetadataFromRepository'], - 'roles' => $this->roles, + 'roles' => $roles->editMetadata(), ], [ 'route' => 'yaml/validate', 'handler' => [$this, 'validateYamlStructure'], - 'roles' => $this->roles, + 'roles' => $roles->readMetadata(), ], ], ]; $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); + } + /** * Authorize the API connection * @@ -144,12 +148,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->getRoles(); + + try { + $pkpRoles = $codecheckRole->getRoles(); - 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 +192,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); + CodecheckLogger::debug('Method: ' . $requestMethod); - 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()); } /** @@ -383,11 +396,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); } /** @@ -401,11 +414,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); } /** @@ -554,11 +567,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/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/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 @@ + { cy.intercept('GET', '**/codecheck/metadata*', { statusCode: 200, body: { + success: true, submissionId: 1, submission: { id: 1, diff --git a/public/build/build.iife.js b/public/build/build.iife.js new file mode 100644 index 00000000..69e4412d --- /dev/null +++ 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+"