Skip to content

Commit 4cbec24

Browse files
committed
#11326 Implement backend infrastructure for managing and moderating public comments
1 parent e6cfd21 commit 4cbec24

File tree

11 files changed

+1065
-11
lines changed

11 files changed

+1065
-11
lines changed

api/v1/comments/UserCommentController.php

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/**
4+
* @file api/v1/comments/resources/UserCommentReportResource.php
5+
*
6+
* Copyright (c) 2025 Simon Fraser University
7+
* Copyright (c) 2005 John Willinsky
8+
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
9+
*
10+
* @class UserCommentReportResource
11+
*
12+
* @ingroup api_v1_comments
13+
*
14+
* @brief Class for mapping HTTP output values for user comment reports.
15+
*/
16+
17+
namespace PKP\API\v1\comments\resources;
18+
19+
use Illuminate\Http\Request;
20+
use Illuminate\Http\Resources\Json\JsonResource;
21+
use PKP\user\User;
22+
23+
class UserCommentReportResource extends JsonResource
24+
{
25+
public function toArray(Request $request): array
26+
{
27+
/** @var User $user */
28+
$user = $this->user;
29+
30+
return [
31+
'id' => $this->id,
32+
'userCommentId' => $this->userCommentId,
33+
'userId' => $this->userId,
34+
'userName' => $user->getFullName(),
35+
'userOrcidDisplayValue' => $user->getOrcidDisplayValue(),
36+
'note' => $this->note,
37+
'createdAt' => $this->createdAt,
38+
'updatedAt' => $this->updatedAt,
39+
];
40+
}
41+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/**
4+
* @file api/v1/comments/resources/UserCommentResource.php
5+
*
6+
* Copyright (c) 2025 Simon Fraser University
7+
* Copyright (c) 2005 John Willinsky
8+
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
9+
*
10+
* @class UserCommentResource
11+
*
12+
* @ingroup api_v1_comments
13+
*
14+
* @brief Class for mapping HTTP output values for user comments.
15+
*/
16+
17+
namespace PKP\API\v1\comments\resources;
18+
19+
use Illuminate\Http\Request;
20+
use Illuminate\Http\Resources\Json\JsonResource;
21+
use PKP\user\User;
22+
23+
class UserCommentResource extends JsonResource
24+
{
25+
public function toArray(Request $request): array
26+
{
27+
$user = $this->user; /** @var User $user */
28+
$query = $request->query();
29+
30+
$results = [
31+
'id' => $this->id,
32+
'contextId' => $this->contextId,
33+
'commentText' => $this->commentText,
34+
'createdAt' => $this->createdAt,
35+
'isApproved' => $this->isApproved,
36+
'isReported' => $this->reports->isNotEmpty(),
37+
'publicationId' => $this->publicationId,
38+
'updatedAt' => $this->updatedAt,
39+
'userId' => $this->userId,
40+
'userName' => $user->getFullName(),
41+
'userOrcidDisplayValue' => $user->getOrcidDisplayValue(),
42+
];
43+
44+
if (key_exists('includeReports', $query)) {
45+
$reports = $this->reports;
46+
$results['reports'] = UserCommentReportResource::collection($reports);
47+
}
48+
49+
return $results;
50+
}
51+
}

classes/core/PKPBaseController.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
2121
use Illuminate\Foundation\Bus\DispatchesJobs;
2222
use Illuminate\Foundation\Validation\ValidatesRequests;
23+
use Illuminate\Http\JsonResponse;
2324
use Illuminate\Http\Request;
25+
use Illuminate\Http\Response;
26+
use Illuminate\Pagination\LengthAwarePaginator;
2427
use Illuminate\Routing\Controller;
2528
use Illuminate\Routing\Route;
2629
use PKP\security\authorization\AllowedHostsPolicy;
@@ -110,12 +113,12 @@ public static function getRouteController(?Request $request = null): ?static
110113
if (!$requestedRoute = static::getRequestedRoute($request)) {
111114
return null;
112115
}
113-
116+
114117
$calledRouteController = (new ReflectionFunction($requestedRoute->action['uses']))->getClosureThis();
115118

116-
// When the routes are added to router as a closure/callable from other section like from a
119+
// When the routes are added to router as a closure/callable from other section like from a
117120
// plugin through the hook, the resolved called route class may not be an instance of
118-
// `PKPBaseController` and we need to resolve the current controller instance from
121+
// `PKPBaseController` and we need to resolve the current controller instance from
119122
// `APIHandler::getApiController` method
120123
if ($calledRouteController instanceof self) {
121124
return $calledRouteController;
@@ -582,4 +585,22 @@ protected function _validateStatDates(array $params, string $dateStartParam = 'd
582585

583586
return true;
584587
}
588+
589+
/**
590+
* Formats and returns an HTTP response for a LengthAwarePaginator paginated collection.
591+
*
592+
* @param LengthAwarePaginator $pagination - The paginated collection
593+
*
594+
*/
595+
public function paginatedResponse(LengthAwarePaginator $pagination): JsonResponse
596+
{
597+
return response()->json([
598+
'data' => $pagination->values(),
599+
'total' => $pagination->total(),
600+
'pagination' => [
601+
'lastPage' => $pagination->lastPage(),
602+
'currentPage' => $pagination->currentPage(),
603+
],
604+
], Response::HTTP_OK);
605+
}
585606
}

classes/core/PKPString.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,24 +332,44 @@ public static function getWordCount(string $str): int
332332

333333
/**
334334
* Map the specific HTML tags in title/ sub title for JATS schema compability
335+
*
335336
* @see https://jats.nlm.nih.gov/publishing/0.4/xsd/JATS-journalpublishing0.xsd
336337
*
337338
* @param string $htmlTitle The submission title/sub title as in HTML
338-
* @return string
339339
*/
340340
public static function mapTitleHtmlTagsToXml(string $htmlTitle): string
341341
{
342342
$mappings = [
343-
'<b>' => '<bold>',
344-
'</b>' => '</bold>',
345-
'<i>' => '<italic>',
346-
'</i>' => '</italic>',
347-
'<u>' => '<underline>',
348-
'</u>' => '</underline>',
343+
'<b>' => '<bold>',
344+
'</b>' => '</bold>',
345+
'<i>' => '<italic>',
346+
'</i>' => '</italic>',
347+
'<u>' => '<underline>',
348+
'</u>' => '</underline>',
349349
];
350350

351351
return str_replace(array_keys($mappings), array_values($mappings), $htmlTitle);
352352
}
353+
354+
/**
355+
* Does a strict conversion of a string to a boolean value.
356+
*
357+
* @param string $value The value to convert.
358+
*
359+
* @return bool|null Returns true if the string is "true", false if it is "false", and null otherwise.
360+
*/
361+
public static function strictConvertToBoolean(string $value): ?bool
362+
{
363+
$lower = strtolower($value);
364+
if ($lower === 'true') {
365+
return true;
366+
}
367+
if ($lower === 'false') {
368+
return false;
369+
}
370+
371+
return null;
372+
}
353373
}
354374

355375
if (!PKP_STRICT_MODE) {

classes/facades/Repo.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@
4444
use PKP\query\Repository as QueryRepository;
4545
use PKP\ror\Repository as RorRepository;
4646
use PKP\stageAssignment\Repository as StageAssignmentRepository;
47+
use PKP\submission\reviewer\recommendation\Repository as ReviewerRecommendationRepository;
4748
use PKP\submissionFile\Repository as SubmissionFileRepository;
4849
use PKP\user\interest\Repository as UserInterestRepository;
50+
use PKP\userComment\Repository as UserCommentRepository;
4951
use PKP\userGroup\Repository as UserGroupRepository;
50-
use PKP\submission\reviewer\recommendation\Repository as ReviewerRecommendationRepository;
5152

5253
class Repo
5354
{
@@ -170,4 +171,8 @@ public static function reviewerRecommendation(): ReviewerRecommendationRepositor
170171
{
171172
return app(ReviewerRecommendationRepository::class);
172173
}
174+
public static function userComment(): UserCommentRepository
175+
{
176+
return app(UserCommentRepository::class);
177+
}
173178
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/**
4+
* @file I11325_UserComments.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 I11325_UserComments
11+
*
12+
* @brief Migration to add table structures for user comments.
13+
*/
14+
15+
namespace PKP\migration\upgrade\v3_6_0;
16+
17+
use Illuminate\Database\Migrations\Migration;
18+
use Illuminate\Database\Schema\Blueprint;
19+
use Illuminate\Support\Facades\Schema;
20+
21+
class I11325_UserComments extends Migration
22+
{
23+
/**
24+
* Run the migration.
25+
*/
26+
public function up(): void
27+
{
28+
Schema::create('user_comments', function (Blueprint $table) {
29+
$table->bigInteger('user_comment_id')->autoIncrement()->comment('Primary key.');
30+
$table->bigInteger('user_id')->comment('ID of the user that made the comment.');
31+
$table->bigInteger('context_id')->comment('ID of the context (e.g., journal) the comment belongs to.');
32+
$table->bigInteger('publication_id')->nullable()->comment('ID of the publication that the comment belongs to.');
33+
$table->text('comment_text')->comment('The comment text.');
34+
$table->boolean('is_approved')->default(false)->comment('Boolean indicating if the comment is approved.');
35+
$table->timestamps();
36+
37+
$table->foreign('user_id')
38+
->references('user_id')
39+
->on('users')
40+
->onDelete('cascade')
41+
->onUpdate('cascade');
42+
});
43+
44+
Schema::create('user_comment_reports', function (Blueprint $table) {
45+
$table->bigInteger('user_comment_report_id')->autoIncrement()->comment('Primary key.');
46+
$table->bigInteger('user_comment_id')->comment('ID of the user comment that the reported.');
47+
$table->bigInteger('user_id')->comment('ID of the user that made the report.');
48+
$table->text('note')->comment('Note or reason for the report.');
49+
$table->timestamps();
50+
51+
// Foreign key constraints
52+
$table->foreign('user_comment_id')
53+
->references('user_comment_id')
54+
->on('user_comments')
55+
->onDelete('cascade')
56+
->onUpdate('cascade');
57+
58+
$table->foreign('user_id')
59+
->references('user_id')
60+
->on('users')
61+
->onDelete('cascade')
62+
->onUpdate('cascade');
63+
});
64+
65+
Schema::create('user_comment_settings', function (Blueprint $table) {
66+
$table->bigInteger('user_comment_setting_id')->autoIncrement()->comment('Primary key.');
67+
$table->bigInteger('user_comment_id')->comment('ID of the user comment that the setting belongs to.');
68+
$table->string('locale', 14)->default('');
69+
$table->string('setting_name', 255);
70+
$table->longText('setting_value')->nullable();
71+
72+
$table->foreign('user_comment_id')
73+
->references('user_comment_id')
74+
->on('user_comments')
75+
->onDelete('cascade')
76+
->onUpdate('cascade');
77+
});
78+
}
79+
80+
/**
81+
* Reverse the migration.
82+
*/
83+
public function down(): void
84+
{
85+
Schema::drop('user_comment_reports');
86+
Schema::drop('user_comment_settings');
87+
Schema::drop('user_comments');
88+
}
89+
}

0 commit comments

Comments
 (0)