Skip to content

Commit f155593

Browse files
authored
Merge pull request #11765 from jyhein/f857
Contributor Roles and Type
2 parents d0f2a46 + 52d3a0f commit f155593

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1667
-374
lines changed

api/v1/_dois/PKPBackendDoiController.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,13 @@ public function editPublication(Request $illuminateRequest): JsonResponse
131131
$submission = Repo::submission()->get($publication->getData('submissionId'));
132132

133133
$contextId = $submission->getData('contextId');
134-
$userGroups = UserGroup::withContextIds($contextId)->get();
135134

136135

137136
$genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */
138137
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toAssociativeArray();
139138

140139
return response()->json(
141-
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
140+
Repo::publication()->getSchemaMap($submission, $genres)->map($publication),
142141
Response::HTTP_OK
143142
);
144143
}
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
<?php
2+
3+
/**
4+
* @file api/v1/contributorRoles/ContributorRoleController.php
5+
*
6+
* Copyright (c) 2025 Simon Fraser University
7+
* Copyright (c) 2025 John Willinsky
8+
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
9+
*
10+
* @class ContributorRoleController
11+
*
12+
* @brief Handle API requests for contributor role operations.
13+
*
14+
*/
15+
16+
namespace PKP\API\v1\contributorRoles;
17+
18+
use APP\facades\Repo;
19+
use Illuminate\Http\JsonResponse;
20+
use Illuminate\Http\Request;
21+
use Illuminate\Http\Response;
22+
use Illuminate\Support\Facades\Route;
23+
use PKP\core\PKPBaseController;
24+
use PKP\core\PKPRequest;
25+
use PKP\author\contributorRole\ContributorRole;
26+
use PKP\author\contributorRole\ContributorRoleIdentifier;
27+
use PKP\author\creditContributorRole\CreditContributorRole;
28+
use PKP\security\Role;
29+
use PKP\security\authorization\CanAccessSettingsPolicy;
30+
use PKP\security\authorization\ContextAccessPolicy;
31+
use PKP\services\PKPSchemaService;
32+
33+
class ContributorRoleController extends PKPBaseController
34+
{
35+
/**
36+
* @copydoc \PKP\core\PKPBaseController::getHandlerPath()
37+
*/
38+
public function getHandlerPath(): string
39+
{
40+
return 'contributorRoles';
41+
}
42+
43+
/**
44+
* @copydoc \PKP\core\PKPBaseController::getRouteGroupMiddleware()
45+
*/
46+
public function getRouteGroupMiddleware(): array
47+
{
48+
return [
49+
'has.user',
50+
'has.context',
51+
self::roleAuthorizer([
52+
Role::ROLE_ID_SITE_ADMIN,
53+
Role::ROLE_ID_MANAGER,
54+
]),
55+
];
56+
}
57+
58+
public function authorize(PKPRequest $request, array &$args, array $roleAssignments): bool
59+
{
60+
$this->addPolicy(new ContextAccessPolicy($request, $roleAssignments));
61+
$this->addPolicy(new CanAccessSettingsPolicy());
62+
63+
return parent::authorize($request, $args, $roleAssignments);
64+
}
65+
66+
/**
67+
* @copydoc \PKP\core\PKPBaseController::getGroupRoutes()
68+
*/
69+
public function getGroupRoutes(): void
70+
{
71+
Route::get('{roleId}', $this->get(...))
72+
->name('contributorRole.getContributorRole')
73+
->whereNumber('roleId');
74+
75+
Route::middleware([
76+
self::roleAuthorizer([
77+
Role::ROLE_ID_SITE_ADMIN,
78+
Role::ROLE_ID_MANAGER,
79+
]),
80+
])->group(function () {
81+
Route::get('', $this->getMany(...))
82+
->name('contributorRole.getContributorRoles');
83+
Route::get('identifiers', $this->getIdentifiers(...))
84+
->name('contributorRole.getIdentifiers');
85+
Route::post('', $this->add(...))
86+
->name('contributorRole.addContributorRole');
87+
Route::put('{roleId}', $this->edit(...))
88+
->name('contributorRole.editContributorRole')
89+
->whereNumber('roleId');
90+
Route::delete('{roleId}', $this->delete(...))
91+
->name('contributorRole.deleteContributorRole')
92+
->whereNumber('roleId');
93+
});
94+
}
95+
96+
/**
97+
* Create a new role.
98+
*/
99+
public function add(Request $illuminateRequest): JsonResponse
100+
{
101+
return $this->saveRole($illuminateRequest, null);
102+
}
103+
104+
/**
105+
* Edit an existing role by ID.
106+
*/
107+
public function edit(Request $illuminateRequest): JsonResponse
108+
{
109+
$role = ContributorRole::find((int) $illuminateRequest->route('roleId'));
110+
111+
if (!$role) {
112+
return response()->json([
113+
'error' => __('api.contributorRole.404.roleNotFound'),
114+
], Response::HTTP_NOT_FOUND);
115+
}
116+
117+
$contextId = $this->getRequest()->getContext()->getId();
118+
if ($contextId !== $role->contextId) {
119+
return response()->json([
120+
'error' => __('api.contributorRole.400.contextsNotMatched'),
121+
], Response::HTTP_FORBIDDEN);
122+
}
123+
124+
return $this->saveRole($illuminateRequest, $role);
125+
}
126+
127+
/**
128+
* Delete a role by ID.
129+
*/
130+
public function delete(Request $illuminateRequest): JsonResponse
131+
{
132+
$role = ContributorRole::find((int) $illuminateRequest->route('roleId'));
133+
134+
if (!$role) {
135+
return response()->json([
136+
'error' => __('api.contributorRole.404.roleNotFound'),
137+
], Response::HTTP_NOT_FOUND);
138+
}
139+
140+
$contextId = $this->getRequest()->getContext()->getId();
141+
142+
if ($contextId !== $role->contextId) {
143+
return response()->json([
144+
'error' => __('api.contributorRole.400.contextsNotMatched'),
145+
], Response::HTTP_FORBIDDEN);
146+
}
147+
148+
// Block the removal of a role when in use
149+
if (CreditContributorRole::query()->withContributorRoleId($role->id)->count()) {
150+
return response()->json([
151+
'error' => __('manager.contributorRoles.error.delete.inUse'),
152+
], Response::HTTP_NOT_ACCEPTABLE);
153+
}
154+
155+
$props = Repo::contributorRole()->getSchemaMap()->map($role);
156+
157+
// E.g. last AUTHOR role cannot be deleted
158+
try {
159+
$role->delete();
160+
} catch (\Exception $e) {
161+
return response()->json([
162+
'error' => [__('api.contributorRole.400.errorDeletingAuthorRole')],
163+
], Response::HTTP_NOT_ACCEPTABLE);
164+
}
165+
166+
return response()->json($props, Response::HTTP_OK);
167+
}
168+
169+
/**
170+
* Get a single active role
171+
*/
172+
public function get(Request $illuminateRequest): JsonResponse
173+
{
174+
$role = ContributorRole::find((int) $illuminateRequest->route('roleId'));
175+
176+
if (!$role) {
177+
return response()->json([
178+
'error' => __('api.contributorRole.404.roleNotFound'),
179+
], Response::HTTP_NOT_FOUND);
180+
}
181+
182+
$contextId = $this->getRequest()->getContext()->getId();
183+
184+
if ($contextId !== $role->contextId) {
185+
return response()->json([
186+
'error' => __('api.contributorRole.400.contextsNotMatched'),
187+
], Response::HTTP_FORBIDDEN);
188+
}
189+
190+
return response()->json(Repo::contributorRole()->getSchemaMap()->map($role), Response::HTTP_OK);
191+
}
192+
193+
/**
194+
* Get the list of active roles.
195+
*/
196+
public function getMany(Request $illuminateRequest): JsonResponse
197+
{
198+
$roles = ContributorRole::query()->withContextId($this->getRequest()->getContext()->getId());
199+
return response()->json(Repo::contributorRole()->getSchemaMap()->summarizeMany($roles->get())->values(), Response::HTTP_OK);
200+
}
201+
202+
/**
203+
* Get the list of all identifiers
204+
*/
205+
public function getIdentifiers(Request $illuminateRequest): JsonResponse
206+
{
207+
return response()->json(ContributorRoleIdentifier::getRoles(), Response::HTTP_OK);
208+
}
209+
210+
/**
211+
* Create or update a role.
212+
*
213+
* Used internally to handle both new role creation and editing existing ones.
214+
*/
215+
private function saveRole(Request $illuminateRequest, ?ContributorRole $role): JsonResponse
216+
{
217+
$context = $this->getRequest()->getContext();
218+
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_CONTRIBUTOR_ROLE, $illuminateRequest->input());
219+
220+
$readOnlyErrors = $this->getWriteDisabledErrors(PKPSchemaService::SCHEMA_CONTRIBUTOR_ROLE, $params);
221+
if ($readOnlyErrors) {
222+
return response()->json($readOnlyErrors, Response::HTTP_BAD_REQUEST);
223+
}
224+
225+
$params['id'] = $role?->id;
226+
$params['contextId'] = $context->getId();
227+
228+
$errors = Repo::contributorRole()->validate($role, $params, $context);
229+
if ($errors) {
230+
return response()->json($errors, Response::HTTP_BAD_REQUEST);
231+
}
232+
233+
// Disallow edit of identifer when editing
234+
if ($params['id']) {
235+
unset($params['contributorRoleIdentifier']);
236+
}
237+
238+
try {
239+
$newRole = ContributorRole::add($params);
240+
} catch (\Exception $e) {
241+
return response()->json([
242+
'error' => [__('api.contributorRole.400.errorSavingRole')],
243+
], Response::HTTP_BAD_REQUEST);
244+
}
245+
246+
return response()->json(Repo::contributorRole()->getSchemaMap()->map($newRole), Response::HTTP_OK);
247+
}
248+
249+
/**
250+
* This method returns errors for any params that match
251+
* properties in the schema with writeDisabledInApi set to true.
252+
*
253+
* This is used for properties that can not be edited through
254+
* the API, but which otherwise can be edited by the entity's
255+
* repository.
256+
*/
257+
protected function getWriteDisabledErrors(string $schemaName, array $params): array
258+
{
259+
$schema = app()->get('schema')->get($schemaName);
260+
261+
$writeDisabledProps = [];
262+
foreach ($schema->properties as $propName => $propSchema) {
263+
if (!empty($propSchema->writeDisabledInApi)) {
264+
$writeDisabledProps[] = $propName;
265+
}
266+
}
267+
268+
$errors = [];
269+
270+
$notAllowedProps = array_intersect(
271+
$writeDisabledProps,
272+
array_keys($params)
273+
);
274+
275+
if (!empty($notAllowedProps)) {
276+
foreach ($notAllowedProps as $propName) {
277+
$errors[$propName] = [__('api.400.propReadOnly', ['prop' => $propName])];
278+
}
279+
}
280+
281+
return $errors;
282+
}
283+
}

0 commit comments

Comments
 (0)