Skip to content

Commit 1df2f9f

Browse files
authored
Merge branch 'pkp:main' into structured-citations
2 parents 5dc8799 + b8d985f commit 1df2f9f

File tree

16 files changed

+594
-71
lines changed

16 files changed

+594
-71
lines changed

api/v1/stats/sushi/PKPStatsSushiController.php

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
use Illuminate\Support\Facades\Route;
2727
use PKP\core\PKPBaseController;
2828
use PKP\core\PKPRequest;
29+
use PKP\core\PKPRoutingProvider;
2930
use PKP\security\authorization\ContextRequiredPolicy;
3031
use PKP\security\authorization\PolicySet;
3132
use PKP\security\authorization\RoleBasedHandlerOperationPolicy;
3233
use PKP\security\authorization\UserRolesRequiredPolicy;
3334
use PKP\security\Role;
3435
use PKP\sushi\CounterR5Report;
3536
use PKP\sushi\SushiException;
37+
use PKP\validation\ValidatorFactory;
38+
use Symfony\Component\HttpFoundation\StreamedResponse;
3639

3740
class PKPStatsSushiController extends PKPBaseController
3841
{
@@ -243,7 +246,7 @@ protected function getReportList(): array
243246
* COUNTER 'Platform Usage' [PR_P1].
244247
* A customizable report summarizing activity across the Platform (journal, press, or server).
245248
*/
246-
public function getReportsPR(Request $illuminateRequest): JsonResponse
249+
public function getReportsPR(Request $illuminateRequest): JsonResponse|StreamedResponse
247250
{
248251
return $this->getReportResponse(new PR(), $illuminateRequest);
249252
}
@@ -252,24 +255,126 @@ public function getReportsPR(Request $illuminateRequest): JsonResponse
252255
* COUNTER 'Platform Master Report' [PR].
253256
* This is a Standard View of the Platform Master Report that presents usage for the overall Platform broken down by Metric_Type
254257
*/
255-
public function getReportsPR1(Request $illuminateRequest): JsonResponse
258+
public function getReportsPR1(Request $illuminateRequest): JsonResponse|StreamedResponse
256259
{
257260
return $this->getReportResponse(new PR_P1(), $illuminateRequest);
258261
}
259262

263+
/** Validate user input for TSV reports */
264+
protected function _validateUserInput(CounterR5Report $report, array $params): array
265+
{
266+
$request = $this->getRequest();
267+
$context = $request->getContext();
268+
$earliestDate = CounterR5Report::getEarliestDate();
269+
$lastDate = CounterR5Report::getLastDate();
270+
$submissionIds = Repo::submission()->getCollector()->filterByContextIds([$context->getId()])->getIds()->implode(',');
271+
272+
$rules = [
273+
'begin_date' => [
274+
'regex:/^\d{4}-\d{2}(-\d{2})?$/',
275+
'after_or_equal:' . $earliestDate,
276+
'before_or_equal:end_date',
277+
],
278+
'end_date' => [
279+
'regex:/^\d{4}-\d{2}(-\d{2})?$/',
280+
'before_or_equal:' . $lastDate,
281+
'after_or_equal:begin_date',
282+
],
283+
'item_id' => [
284+
// TO-ASK: shell this rather be just validation for positive integer?
285+
'in:' . $submissionIds,
286+
],
287+
'yop' => [
288+
'regex:/^\d{4}((\||-)\d{4})*$/',
289+
],
290+
];
291+
$reportId = $report->getID();
292+
if (in_array($reportId, ['PR', 'TR', 'IR'])) {
293+
$rules['metric_type'] = ['required'];
294+
}
295+
296+
$errors = [];
297+
$validator = ValidatorFactory::make(
298+
$params,
299+
$rules,
300+
[
301+
'begin_date.regex' => __(
302+
'manager.statistics.counterR5Report.settings.wrongDateFormat'
303+
),
304+
'end_date.regex' => __(
305+
'manager.statistics.counterR5Report.settings.wrongDateFormat'
306+
),
307+
'begin_date.after_or_equal' => __(
308+
'stats.dateRange.invalidStartDateMin'
309+
),
310+
'end_date.before_or_equal' => __(
311+
'stats.dateRange.invalidEndDateMax'
312+
),
313+
'begin_date.before_or_equal' => __(
314+
'stats.dateRange.invalidDateRange'
315+
),
316+
'end_date.after_or_equal' => __(
317+
'stats.dateRange.invalidDateRange'
318+
),
319+
'item_id.*' => __(
320+
'manager.statistics.counterR5Report.settings.wrongItemId'
321+
),
322+
'yop.regex' => __(
323+
'manager.statistics.counterR5Report.settings.wrongYOPFormat'
324+
),
325+
]
326+
);
327+
328+
if ($validator->fails()) {
329+
$errors = $validator->errors()->getMessages();
330+
}
331+
332+
return $errors;
333+
}
334+
260335
/**
261336
* Get the requested report
262337
*/
263-
protected function getReportResponse(CounterR5Report $report, Request $illuminateRequest): JsonResponse
338+
protected function getReportResponse(CounterR5Report $report, Request $illuminateRequest): JsonResponse|StreamedResponse
264339
{
265340
$params = $illuminateRequest->query();
341+
//$responseTSV = str_contains($illuminateRequest->getHeaderLine('Accept'), PKPRoutingProvider::RESPONSE_TSV['mime']) ? true : false;
342+
$responseTSV = $illuminateRequest->accepts(PKPRoutingProvider::RESPONSE_TSV['mime']);
343+
344+
if ($responseTSV) {
345+
$errors = $this->_validateUserInput($report, $params);
346+
if (!empty($errors)) {
347+
return response()->json($errors, 400);
348+
}
349+
}
266350

267351
try {
268352
$report->processReportParams($this->getRequest(), $params);
269353
} catch (SushiException $e) {
270354
return response()->json($e->getResponseData(), $e->getHttpStatusCode());
271355
}
272356

357+
if ($responseTSV) {
358+
$reportHeader = $report->getTSVReportHeader();
359+
$reportColumnNames = $report->getTSVColumnNames();
360+
$reportItems = $report->getTSVReportItems();
361+
// consider 3030 error (no usage available)
362+
$key = array_search('3030', array_column($report->warnings, 'Code'));
363+
if ($key !== false) {
364+
$error = $report->warnings[$key]['Code'] . ':' . $report->warnings[$key]['Message'] . '(' . $report->warnings[$key]['Data'] . ')';
365+
foreach ($reportHeader as &$headerRow) {
366+
if (in_array('Exceptions', $headerRow)) {
367+
$headerRow[1] =
368+
$headerRow[1] == '' ?
369+
$error :
370+
$headerRow[1] . ';' . $error;
371+
}
372+
}
373+
}
374+
$report = array_merge($reportHeader, [['']], $reportColumnNames, $reportItems);
375+
return response()->withFile($report, [], count($reportItems));
376+
}
377+
273378
$reportHeader = $report->getReportHeader();
274379
$reportItems = $report->getReportItems();
275380

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
/**
3+
* @file classes/components/form/counter/PKPCounterReportForm.php
4+
*
5+
* Copyright (c) 2024 Simon Fraser University
6+
* Copyright (c) 2024 John Willinsky
7+
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
8+
*
9+
* @class PKPCounterReportForm
10+
*
11+
* @ingroup classes_controllers_form
12+
*
13+
* @brief A form for setting a COUNTER R5 report
14+
*/
15+
16+
namespace PKP\components\forms\counter;
17+
18+
use PKP\components\forms\FormComponent;
19+
20+
define('FORM_COUNTER', 'counter');
21+
22+
abstract class PKPCounterReportForm extends FormComponent
23+
{
24+
/** @copydoc FormComponent::$id */
25+
public $id = FORM_COUNTER;
26+
27+
/** @copydoc FormComponent::$method */
28+
public $method = 'GET';
29+
30+
/** Form fields for each COUNTER R5 report */
31+
public $reportFields = [];
32+
33+
/** Set reportFields, that will contain form fields for each COUNTER R5 report */
34+
abstract public function setReportFields(): void;
35+
36+
/**
37+
* Constructor
38+
*
39+
* @param string $action URL to submit the form to
40+
* @param array $locales Supported locales
41+
*/
42+
public function __construct(string $action, array $locales)
43+
{
44+
$this->action = $action;
45+
$this->locales = $locales;
46+
47+
$this->addPage(['id' => 'default', 'submitButton' => ['label' => __('common.download')]]);
48+
$this->addGroup(['id' => 'default', 'pageId' => 'default']);
49+
50+
$this->setReportFields();
51+
}
52+
53+
public function getConfig()
54+
{
55+
$config = parent::getConfig();
56+
$config['reportFields'] = array_map(function ($reportFields) {
57+
return array_map(function ($reportField) {
58+
$field = $this->getFieldConfig($reportField);
59+
$field['groupId'] = 'default';
60+
return $field;
61+
}, $reportFields);
62+
}, $this->reportFields);
63+
64+
return $config;
65+
}
66+
}

classes/components/forms/dashboard/SubmissionFilters.php renamed to classes/components/forms/dashboard/PKPSubmissionFilters.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
namespace PKP\components\forms\dashboard;
1717

18-
use APP\components\forms\FieldSelectIssues;
1918
use APP\core\Application;
2019
use APP\facades\Repo;
2120
use Illuminate\Support\LazyCollection;
@@ -27,7 +26,7 @@
2726
use PKP\context\Context;
2827
use PKP\security\Role;
2928

30-
class SubmissionFilters extends FormComponent
29+
class PKPSubmissionFilters extends FormComponent
3130
{
3231
/**
3332
* The maximum number of options in a field
@@ -49,7 +48,6 @@ public function __construct(
4948
->addGroup(['id' => 'default', 'pageId' => 'default'])
5049
->addSectionFields()
5150
->addAssignedTo()
52-
->addIssues()
5351
->addCategories()
5452
->addDaysSinceLastActivity()
5553
;
@@ -119,17 +117,6 @@ protected function addAssignedTo(): self
119117
]));
120118
}
121119

122-
protected function addIssues(): self
123-
{
124-
$request = Application::get()->getRequest();
125-
126-
return $this->addField(new FieldSelectIssues('issueIds', [
127-
'groupId' => 'default',
128-
'label' => __('issue.issues'),
129-
'value' => [],
130-
'apiUrl' => $request->getDispatcher()->url($request, Application::ROUTE_API, $request->getContext()->getPath(), 'issues'),
131-
]));
132-
}
133120

134121
protected function addCategories(): self
135122
{

classes/components/forms/publication/ContributorForm.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public function __construct(string $action, array $locales, ?Submission $submiss
119119
->addField(new FieldText('affiliation', [
120120
'label' => __('user.affiliation'),
121121
'isMultilingual' => true,
122+
'size' => 'large',
122123
]));
123124

124125
if ($authorUserGroupsOptions->count() > 1) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* @file classes/components/listPanels/PKPCounterReportsListPanel.php
4+
*
5+
* Copyright (c) 2024 Simon Fraser University
6+
* Copyright (c) 2024 John Willinsky
7+
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
8+
*
9+
* @class PKPCounterReportsListPanel
10+
*
11+
* @ingroup classes_components_list
12+
*
13+
* @brief A ListPanel component for viewing, editing and downloading COUNTER R5 reports
14+
*/
15+
16+
namespace PKP\components\listPanels;
17+
18+
use APP\components\forms\counter\CounterReportForm;
19+
use PKP\sushi\CounterR5Report;
20+
21+
class PKPCounterReportsListPanel extends ListPanel
22+
{
23+
/** URL to the API endpoint where items can be retrieved */
24+
public string $apiUrl = '';
25+
26+
/** Form for setting up and downloading a report*/
27+
public ?CounterReportForm $form = null;
28+
29+
/** Query parameters to pass if this list executes GET requests */
30+
public array $getParams = [];
31+
32+
/**
33+
* @copydoc ListPanel::getConfig()
34+
*/
35+
public function getConfig(): array
36+
{
37+
$config = parent::getConfig();
38+
39+
$earliestDate = CounterR5Report::getEarliestDate();
40+
$lastDate = CounterR5Report::getLastDate();
41+
42+
$config = array_merge(
43+
$config,
44+
[
45+
'apiUrl' => $this->apiUrl,
46+
'form' => $this->form->getConfig(),
47+
'usagePossible' => $lastDate > $earliestDate,
48+
]
49+
);
50+
return $config;
51+
}
52+
}

classes/services/queryBuilders/PKPStatsSushiQueryBuilder.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ public function getSum(array $groupBy = []): Builder
7676
$q->leftJoin('publications as p', function ($q) {
7777
$q->on('p.submission_id', '=', 'm.submission_id')
7878
->whereIn('p.publication_id', function ($q) {
79-
$q->selectRaw('MIN(p2.publication_id)')->from('publications as p2')->where('p2.status', Submission::STATUS_PUBLISHED);
79+
$q->selectRaw('MIN(p2.publication_id)')
80+
->from('publications as p2')
81+
->where('p2.status', Submission::STATUS_PUBLISHED)
82+
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
8083
});
8184
});
8285
}
@@ -123,7 +126,10 @@ protected function _getObject(): Builder
123126
$q->leftJoin('publications as p', function ($q) {
124127
$q->on('p.submission_id', '=', 'm.submission_id')
125128
->whereIn('p.publication_id', function ($q) {
126-
$q->selectRaw('MIN(p2.publication_id)')->from('publications as p2')->where('p2.status', Submission::STATUS_PUBLISHED);
129+
$q->selectRaw('MIN(p2.publication_id)')
130+
->from('publications as p2')
131+
->where('p2.status', Submission::STATUS_PUBLISHED)
132+
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
127133
});
128134
});
129135
foreach ($this->yearsOfPublication as $yop) {

0 commit comments

Comments
 (0)