1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace App\Controller; |
6
|
|
|
|
7
|
|
|
use App\Exception\XtoolsHttpException; |
8
|
|
|
use App\Helper\AutomatedEditsHelper; |
9
|
|
|
use App\Model\Authorship; |
10
|
|
|
use App\Model\Page; |
11
|
|
|
use App\Model\PageInfo; |
12
|
|
|
use App\Model\Project; |
13
|
|
|
use App\Repository\PageInfoRepository; |
14
|
|
|
use GuzzleHttp\Exception\ServerException; |
15
|
|
|
use OpenApi\Annotations as OA; |
16
|
|
|
use Symfony\Component\HttpFoundation\JsonResponse; |
17
|
|
|
use Symfony\Component\HttpFoundation\Response; |
18
|
|
|
use Symfony\Component\Routing\Annotation\Route; |
19
|
|
|
use Twig\Markup; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* This controller serves the search form and results for the PageInfo tool |
23
|
|
|
*/ |
24
|
|
|
class PageInfoController extends XtoolsController |
25
|
|
|
{ |
26
|
|
|
protected PageInfo $pageInfo; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @inheritDoc |
30
|
|
|
* @codeCoverageIgnore |
31
|
|
|
*/ |
32
|
|
|
public function getIndexRoute(): string |
33
|
|
|
{ |
34
|
|
|
return 'PageInfo'; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The search form. |
39
|
|
|
* @Route("/pageinfo", name="PageInfo") |
40
|
|
|
* @Route("/pageinfo/{project}", name="PageInfoProject") |
41
|
|
|
* @Route("/articleinfo", name="PageInfoLegacy") |
42
|
|
|
* @Route("/articleinfo/index.php", name="PageInfoLegacyPhp") |
43
|
|
|
* @return Response |
44
|
|
|
*/ |
45
|
|
|
public function indexAction(): Response |
46
|
|
|
{ |
47
|
|
|
if (isset($this->params['project']) && isset($this->params['page'])) { |
48
|
|
|
return $this->redirectToRoute('PageInfoResult', $this->params); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
return $this->render('pageInfo/index.html.twig', array_merge([ |
52
|
|
|
'xtPage' => 'PageInfo', |
53
|
|
|
'xtPageTitle' => 'tool-pageinfo', |
54
|
|
|
'xtSubtitle' => 'tool-pageinfo-desc', |
55
|
|
|
|
56
|
|
|
// Defaults that will get overridden if in $params. |
57
|
|
|
'start' => '', |
58
|
|
|
'end' => '', |
59
|
|
|
'page' => '', |
60
|
|
|
], $this->params, ['project' => $this->project])); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Setup the PageInfo instance and its Repository. |
65
|
|
|
* @param PageInfoRepository $pageInfoRepo |
66
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
67
|
|
|
* @codeCoverageIgnore |
68
|
|
|
*/ |
69
|
|
|
private function setupPageInfo( |
70
|
|
|
PageInfoRepository $pageInfoRepo, |
71
|
|
|
AutomatedEditsHelper $autoEditsHelper |
72
|
|
|
): void { |
73
|
|
|
if (isset($this->pageInfo)) { |
74
|
|
|
return; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
$this->pageInfo = new PageInfo( |
78
|
|
|
$pageInfoRepo, |
79
|
|
|
$this->i18n, |
80
|
|
|
$autoEditsHelper, |
81
|
|
|
$this->page, |
|
|
|
|
82
|
|
|
$this->start, |
83
|
|
|
$this->end |
84
|
|
|
); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Generate PageInfo gadget script for use on-wiki. This automatically points the |
89
|
|
|
* script to this installation's API. |
90
|
|
|
* |
91
|
|
|
* @Route("/pageinfo-gadget.js", name="PageInfoGadget") |
92
|
|
|
* @link https://www.mediawiki.org/wiki/XTools/PageInfo_gadget |
93
|
|
|
* |
94
|
|
|
* @return Response |
95
|
|
|
* @codeCoverageIgnore |
96
|
|
|
*/ |
97
|
|
|
public function gadgetAction(): Response |
98
|
|
|
{ |
99
|
|
|
$rendered = $this->renderView('pageInfo/pageinfo.js.twig'); |
100
|
|
|
$response = new Response($rendered); |
101
|
|
|
$response->headers->set('Content-Type', 'text/javascript'); |
102
|
|
|
return $response; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Display the results in given date range. |
107
|
|
|
* @Route( |
108
|
|
|
* "/pageinfo/{project}/{page}/{start}/{end}", name="PageInfoResult", |
109
|
|
|
* requirements={ |
110
|
|
|
* "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
111
|
|
|
* "start"="|\d{4}-\d{2}-\d{2}", |
112
|
|
|
* "end"="|\d{4}-\d{2}-\d{2}", |
113
|
|
|
* }, |
114
|
|
|
* defaults={ |
115
|
|
|
* "start"=false, |
116
|
|
|
* "end"=false, |
117
|
|
|
* } |
118
|
|
|
* ) |
119
|
|
|
* @Route( |
120
|
|
|
* "/articleinfo/{project}/{page}/{start}/{end}", name="PageInfoResultLegacy", |
121
|
|
|
* requirements={ |
122
|
|
|
* "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
123
|
|
|
* "start"="|\d{4}-\d{2}-\d{2}", |
124
|
|
|
* "end"="|\d{4}-\d{2}-\d{2}", |
125
|
|
|
* }, |
126
|
|
|
* defaults={ |
127
|
|
|
* "start"=false, |
128
|
|
|
* "end"=false, |
129
|
|
|
* } |
130
|
|
|
* ) |
131
|
|
|
* @param PageInfoRepository $pageInfoRepo |
132
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
133
|
|
|
* @return Response |
134
|
|
|
* @codeCoverageIgnore |
135
|
|
|
*/ |
136
|
|
|
public function resultAction( |
137
|
|
|
PageInfoRepository $pageInfoRepo, |
138
|
|
|
AutomatedEditsHelper $autoEditsHelper |
139
|
|
|
): Response { |
140
|
|
|
if (!$this->isDateRangeValid($this->page, $this->start, $this->end)) { |
|
|
|
|
141
|
|
|
$this->addFlashMessage('notice', 'date-range-outside-revisions'); |
142
|
|
|
|
143
|
|
|
return $this->redirectToRoute('PageInfo', [ |
144
|
|
|
'project' => $this->request->get('project'), |
145
|
|
|
]); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
149
|
|
|
$this->pageInfo->prepareData(); |
150
|
|
|
|
151
|
|
|
$maxRevisions = $this->getParameter('app.max_page_revisions'); |
152
|
|
|
|
153
|
|
|
// Show message if we hit the max revisions. |
154
|
|
|
if ($this->pageInfo->tooManyRevisions()) { |
155
|
|
|
$this->addFlashMessage('notice', 'too-many-revisions', [ |
156
|
|
|
$this->i18n->numberFormat($maxRevisions), |
157
|
|
|
$maxRevisions, |
158
|
|
|
]); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// For when there is very old data (2001 era) which may cause miscalculations. |
162
|
|
|
if ($this->pageInfo->getFirstEdit()->getYear() < 2003) { |
163
|
|
|
$this->addFlashMessage('warning', 'old-page-notice'); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// When all username info has been hidden (see T303724). |
167
|
|
|
if (0 === $this->pageInfo->getNumEditors()) { |
168
|
|
|
$this->addFlashMessage('warning', 'error-usernames-missing'); |
169
|
|
|
} elseif ($this->pageInfo->numDeletedRevisions()) { |
170
|
|
|
$link = new Markup( |
171
|
|
|
$this->renderView('flashes/deleted_data.html.twig', [ |
172
|
|
|
'numRevs' => $this->pageInfo->numDeletedRevisions(), |
173
|
|
|
]), |
174
|
|
|
'UTF-8' |
175
|
|
|
); |
176
|
|
|
$this->addFlashMessage( |
177
|
|
|
'warning', |
178
|
|
|
$link, |
179
|
|
|
[$this->pageInfo->numDeletedRevisions(), $link] |
180
|
|
|
); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$ret = [ |
184
|
|
|
'xtPage' => 'PageInfo', |
185
|
|
|
'xtTitle' => $this->page->getTitle(), |
|
|
|
|
186
|
|
|
'project' => $this->project, |
187
|
|
|
'editorlimit' => (int)$this->request->query->get('editorlimit', 20), |
188
|
|
|
'botlimit' => $this->request->query->get('botlimit', 10), |
189
|
|
|
'pageviewsOffset' => 60, |
190
|
|
|
'ai' => $this->pageInfo, |
191
|
|
|
'showAuthorship' => Authorship::isSupportedPage($this->page) && $this->pageInfo->getNumEditors() > 0, |
|
|
|
|
192
|
|
|
]; |
193
|
|
|
|
194
|
|
|
// Output the relevant format template. |
195
|
|
|
return $this->getFormattedResponse('pageInfo/result', $ret); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Check if there were any revisions of given page in given date range. |
200
|
|
|
* @param Page $page |
201
|
|
|
* @param false|int $start |
202
|
|
|
* @param false|int $end |
203
|
|
|
* @return bool |
204
|
|
|
*/ |
205
|
|
|
private function isDateRangeValid(Page $page, $start, $end): bool |
206
|
|
|
{ |
207
|
|
|
return $page->getNumRevisions(null, $start, $end) > 0; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/************************ API endpoints ************************/ |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Get basic information about a page. |
214
|
|
|
* @Route( |
215
|
|
|
* "/api/page/pageinfo/{project}/{page}", |
216
|
|
|
* name="PageApiPageInfo", |
217
|
|
|
* requirements={"page"=".+"}, |
218
|
|
|
* methods={"GET"} |
219
|
|
|
* ) |
220
|
|
|
* @Route( |
221
|
|
|
* "/api/page/articleinfo/{project}/{page}", |
222
|
|
|
* name="PageApiPageInfoLegacy", |
223
|
|
|
* requirements={"page"=".+"}, |
224
|
|
|
* methods={"GET"} |
225
|
|
|
* ) |
226
|
|
|
* @OA\Get(description="Get basic information about the history of a page. |
227
|
|
|
See also the [pageviews](https://w.wiki/6o9k) and [edit data](https://w.wiki/6o9m) REST APIs.") |
228
|
|
|
* @OA\Tag(name="Page API") |
229
|
|
|
* @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/XTools/API/Page#Page_info") |
230
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
231
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page") |
232
|
|
|
* @OA\Parameter(name="format", in="query", @OA\Schema(default="json", type="string", enum={"json","html"})) |
233
|
|
|
* @OA\Response( |
234
|
|
|
* response=200, |
235
|
|
|
* description="Basic information about the page.", |
236
|
|
|
* @OA\JsonContent( |
237
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
238
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
239
|
|
|
* @OA\Property(property="watchers", type="integer"), |
240
|
|
|
* @OA\Property(property="pageviews", type="integer"), |
241
|
|
|
* @OA\Property(property="pageviews_offset", type="integer"), |
242
|
|
|
* @OA\Property(property="revisions", type="integer"), |
243
|
|
|
* @OA\Property(property="editors", type="integer"), |
244
|
|
|
* @OA\Property(property="minor_edits", type="integer"), |
245
|
|
|
* @OA\Property(property="author", type="string", example="Jimbo Wales"), |
246
|
|
|
* @OA\Property(property="author_editcount", type="integer"), |
247
|
|
|
* @OA\Property(property="created_at", type="date"), |
248
|
|
|
* @OA\Property(property="created_rev_id", type="integer"), |
249
|
|
|
* @OA\Property(property="modified_at", type="date"), |
250
|
|
|
* @OA\Property(property="secs_since_last_edit", type="integer"), |
251
|
|
|
* @OA\Property(property="last_edit_id", type="integer"), |
252
|
|
|
* @OA\Property(property="assessment", type="object", example={ |
253
|
|
|
* "value":"FA", |
254
|
|
|
* "color": "#9CBDFF", |
255
|
|
|
* "category": "Category:FA-Class articles", |
256
|
|
|
* "badge": "https://upload.wikimedia.org/wikipedia/commons/b/bc/Featured_article_star.svg" |
257
|
|
|
* }), |
258
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
259
|
|
|
* ), |
260
|
|
|
* @OA\XmlContent(format="text/html") |
261
|
|
|
* ) |
262
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
263
|
|
|
* @OA\Response(response=503, ref="#/components/responses/503") |
264
|
|
|
* @OA\Response(response=504, ref="#/components/responses/504") |
265
|
|
|
* @param PageInfoRepository $pageInfoRepo |
266
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
267
|
|
|
* @return Response|JsonResponse |
268
|
|
|
* See PageInfoControllerTest::testPageInfoApi() |
269
|
|
|
* @codeCoverageIgnore |
270
|
|
|
*/ |
271
|
|
|
public function pageInfoApiAction( |
272
|
|
|
PageInfoRepository $pageInfoRepo, |
273
|
|
|
AutomatedEditsHelper $autoEditsHelper |
274
|
|
|
): Response { |
275
|
|
|
$this->recordApiUsage('page/pageinfo'); |
276
|
|
|
|
277
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
278
|
|
|
$data = []; |
|
|
|
|
279
|
|
|
|
280
|
|
|
try { |
281
|
|
|
$data = $this->pageInfo->getPageInfoApiData($this->project, $this->page); |
|
|
|
|
282
|
|
|
} catch (ServerException $e) { |
283
|
|
|
// The Wikimedia action API can fail for any number of reasons. To our users |
284
|
|
|
// any ServerException means the data could not be fetched, so we capture it here |
285
|
|
|
// to avoid the flood of automated emails when the API goes down, etc. |
286
|
|
|
$data['error'] = $this->i18n->msg('api-error', [$this->project->getDomain()]); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
if ('html' === $this->request->query->get('format')) { |
290
|
|
|
return $this->getApiHtmlResponse($this->project, $this->page, $data); |
|
|
|
|
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
$this->addApiWarningAboutDates(['created_at', 'modified_at']); |
294
|
|
|
$this->addFlash('warning', 'In XTools 3.20, the author and author_editcount properties will be ' . |
295
|
|
|
'renamed to creator and creator_editcount, respectively.'); |
296
|
|
|
$this->addFlash('warning', 'In XTools 3.20, the last_edit_id property will be renamed to modified_rev_id'); |
297
|
|
|
$this->addFlash('warning', 'In XTools 3.20, the watchers property will return null instead of 0 ' . |
298
|
|
|
'if the number of page watchers is unknown.'); |
299
|
|
|
return $this->getFormattedApiResponse($data); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Get the Response for the HTML output of the PageInfo API action. |
304
|
|
|
* @param Project $project |
305
|
|
|
* @param Page $page |
306
|
|
|
* @param string[] $data The pre-fetched data. |
307
|
|
|
* @return Response |
308
|
|
|
* @codeCoverageIgnore |
309
|
|
|
*/ |
310
|
|
|
private function getApiHtmlResponse(Project $project, Page $page, array $data): Response |
311
|
|
|
{ |
312
|
|
|
$response = $this->render('pageInfo/api.html.twig', [ |
313
|
|
|
'project' => $project, |
314
|
|
|
'page' => $page, |
315
|
|
|
'data' => $data, |
316
|
|
|
]); |
317
|
|
|
|
318
|
|
|
// All /api routes by default respond with a JSON content type. |
319
|
|
|
$response->headers->set('Content-Type', 'text/html'); |
320
|
|
|
|
321
|
|
|
// This endpoint is hit constantly and user could be browsing the same page over |
322
|
|
|
// and over (popular noticeboard, for instance), so offload brief caching to browser. |
323
|
|
|
$response->setClientTtl(350); |
324
|
|
|
|
325
|
|
|
return $response; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Get prose statistics for the given page. |
330
|
|
|
* @Route( |
331
|
|
|
* "/api/page/prose/{project}/{page}", |
332
|
|
|
* name="PageApiProse", |
333
|
|
|
* requirements={"page"=".+"}, |
334
|
|
|
* methods={"GET"} |
335
|
|
|
* ) |
336
|
|
|
* @OA\Tag(name="Page API") |
337
|
|
|
* @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/XTools/Page_History#Prose") |
338
|
|
|
* @OA\Get(description="Get statistics about the [prose](https://en.wiktionary.org/wiki/prose) (characters, |
339
|
|
|
word count, etc.) and referencing of a page. ([more info](https://w.wiki/6oAF))") |
340
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
341
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page", @OA\Schema(example="Metallica")) |
342
|
|
|
* @OA\Response( |
343
|
|
|
* response=200, |
344
|
|
|
* description="Prose stats", |
345
|
|
|
* @OA\JsonContent( |
346
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
347
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
348
|
|
|
* @OA\Property(property="bytes", type="integer"), |
349
|
|
|
* @OA\Property(property="characters", type="integer"), |
350
|
|
|
* @OA\Property(property="words", type="integer"), |
351
|
|
|
* @OA\Property(property="references", type="integer"), |
352
|
|
|
* @OA\Property(property="unique_references", type="integer"), |
353
|
|
|
* @OA\Property(property="sections", type="integer"), |
354
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
355
|
|
|
* ) |
356
|
|
|
* ) |
357
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
358
|
|
|
* @param PageInfoRepository $pageInfoRepo |
359
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
360
|
|
|
* @return JsonResponse |
361
|
|
|
* @codeCoverageIgnore |
362
|
|
|
*/ |
363
|
|
|
public function proseStatsApiAction( |
364
|
|
|
PageInfoRepository $pageInfoRepo, |
365
|
|
|
AutomatedEditsHelper $autoEditsHelper |
366
|
|
|
): JsonResponse { |
367
|
|
|
$responseCode = Response::HTTP_OK; |
368
|
|
|
$this->recordApiUsage('page/prose'); |
369
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
370
|
|
|
$this->addFlash('info', 'The algorithm used by this API has recently changed. ' . |
371
|
|
|
'See https://www.mediawiki.org/wiki/XTools/Page_History#Prose for details.'); |
372
|
|
|
$ret = $this->pageInfo->getProseStats(); |
373
|
|
|
if (null === $ret) { |
374
|
|
|
$this->addFlashMessage('error', 'api-error-wikimedia'); |
375
|
|
|
$responseCode = Response::HTTP_BAD_GATEWAY; |
376
|
|
|
$ret = []; |
377
|
|
|
} |
378
|
|
|
return $this->getFormattedApiResponse($ret, $responseCode); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Get the page assessments of one or more pages, along with various related metadata. |
383
|
|
|
* @Route( |
384
|
|
|
* "/api/page/assessments/{project}/{pages}", |
385
|
|
|
* name="PageApiAssessments", |
386
|
|
|
* requirements={"pages"=".+"}, |
387
|
|
|
* methods={"GET"} |
388
|
|
|
* ) |
389
|
|
|
* @OA\Tag(name="Page API") |
390
|
|
|
* @OA\Get(description="Get [assessment data](https://w.wiki/6oAM) of the given pages, including the overall |
391
|
|
|
quality classifications, along with a list of the WikiProjects and their classifications and importance levels.") |
392
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
393
|
|
|
* @OA\Parameter(ref="#/components/parameters/Pages") |
394
|
|
|
* @OA\Parameter(name="classonly", in="query", @OA\Schema(type="boolean"), |
395
|
|
|
* description="Return only the overall quality assessment instead of for each applicable WikiProject." |
396
|
|
|
* ) |
397
|
|
|
* @OA\Response( |
398
|
|
|
* response=200, |
399
|
|
|
* description="Assessmnet data", |
400
|
|
|
* @OA\JsonContent( |
401
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
402
|
|
|
* @OA\Property(property="pages", type="object", |
403
|
|
|
* @OA\Property(property="Page title", type="object", |
404
|
|
|
* @OA\Property(property="assessment", ref="#/components/schemas/PageAssessment"), |
405
|
|
|
* @OA\Property(property="wikiprojects", type="object", |
406
|
|
|
* @OA\Property(property="name of WikiProject", |
407
|
|
|
* ref="#/components/schemas/PageAssessmentWikiProject" |
408
|
|
|
* ) |
409
|
|
|
* ) |
410
|
|
|
* ) |
411
|
|
|
* ), |
412
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
413
|
|
|
* ) |
414
|
|
|
* ) |
415
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
416
|
|
|
* @param string $pages May be multiple pages separated by pipes, e.g. Foo|Bar|Baz |
417
|
|
|
* @return JsonResponse |
418
|
|
|
* @codeCoverageIgnore |
419
|
|
|
*/ |
420
|
|
|
public function assessmentsApiAction(string $pages): JsonResponse |
421
|
|
|
{ |
422
|
|
|
$this->recordApiUsage('page/assessments'); |
423
|
|
|
|
424
|
|
|
$pages = explode('|', $pages); |
425
|
|
|
$out = [ |
426
|
|
|
'pages' => [], |
427
|
|
|
]; |
428
|
|
|
|
429
|
|
|
foreach ($pages as $pageTitle) { |
430
|
|
|
try { |
431
|
|
|
$page = $this->validatePage($pageTitle); |
432
|
|
|
$assessments = $page->getProject() |
433
|
|
|
->getPageAssessments() |
434
|
|
|
->getAssessments($page); |
435
|
|
|
|
436
|
|
|
$out['pages'][$page->getTitle()] = $this->getBoolVal('classonly') |
437
|
|
|
? $assessments['assessment'] |
438
|
|
|
: $assessments; |
439
|
|
|
} catch (XtoolsHttpException $e) { |
440
|
|
|
$out['pages'][$pageTitle] = false; |
441
|
|
|
} |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
return $this->getFormattedApiResponse($out); |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Get number of in and outgoing links, external links, and redirects to the given page. |
449
|
|
|
* @Route( |
450
|
|
|
* "/api/page/links/{project}/{page}", |
451
|
|
|
* name="PageApiLinks", |
452
|
|
|
* requirements={"page"=".+"}, |
453
|
|
|
* methods={"GET"} |
454
|
|
|
* ) |
455
|
|
|
* @OA\Tag(name="Page API") |
456
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
457
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page") |
458
|
|
|
* @OA\Response( |
459
|
|
|
* response=200, |
460
|
|
|
* description="Counts of in and outgoing links, external links, and redirects.", |
461
|
|
|
* @OA\JsonContent( |
462
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
463
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
464
|
|
|
* @OA\Property(property="links_ext_count", type="integer"), |
465
|
|
|
* @OA\Property(property="links_out_count", type="integer"), |
466
|
|
|
* @OA\Property(property="links_in_count", type="integer"), |
467
|
|
|
* @OA\Property(property="redirects_count", type="integer"), |
468
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
469
|
|
|
* ) |
470
|
|
|
* ) |
471
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
472
|
|
|
* @OA\Response(response=503, ref="#/components/responses/503") |
473
|
|
|
* @OA\Response(response=504, ref="#/components/responses/504") |
474
|
|
|
* @return JsonResponse |
475
|
|
|
* @codeCoverageIgnore |
476
|
|
|
*/ |
477
|
|
|
public function linksApiAction(): JsonResponse |
478
|
|
|
{ |
479
|
|
|
$this->recordApiUsage('page/links'); |
480
|
|
|
return $this->getFormattedApiResponse($this->page->countLinksAndRedirects()); |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* Get the top editors (by number of edits) of a page. |
485
|
|
|
* @Route( |
486
|
|
|
* "/api/page/top_editors/{project}/{page}/{start}/{end}/{limit}", name="PageApiTopEditors", |
487
|
|
|
* requirements={ |
488
|
|
|
* "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?)?$", |
489
|
|
|
* "start"="|\d{4}-\d{2}-\d{2}", |
490
|
|
|
* "end"="|\d{4}-\d{2}-\d{2}", |
491
|
|
|
* "limit"="\d+" |
492
|
|
|
* }, |
493
|
|
|
* defaults={ |
494
|
|
|
* "start"=false, |
495
|
|
|
* "end"=false, |
496
|
|
|
* "limit"=20, |
497
|
|
|
* }, |
498
|
|
|
* methods={"GET"} |
499
|
|
|
* ) |
500
|
|
|
* @OA\Tag(name="Page API") |
501
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
502
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page") |
503
|
|
|
* @OA\Parameter(ref="#/components/parameters/Start") |
504
|
|
|
* @OA\Parameter(ref="#/components/parameters/End") |
505
|
|
|
* @OA\Parameter(ref="#/components/parameters/Limit") |
506
|
|
|
* @OA\Parameter(name="nobots", in="query", |
507
|
|
|
* description="Exclude bots from the results.", @OA\Schema(type="boolean") |
508
|
|
|
* ) |
509
|
|
|
* @OA\Response( |
510
|
|
|
* response=200, |
511
|
|
|
* description="List of the top editors, sorted by how many edits they've made to the page.", |
512
|
|
|
* @OA\JsonContent( |
513
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
514
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
515
|
|
|
* @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
516
|
|
|
* @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
517
|
|
|
* @OA\Property(property="limit", ref="#/components/parameters/Limit/schema"), |
518
|
|
|
* @OA\Property(property="top_editors", type="array", @OA\Items(type="object"), example={ |
519
|
|
|
* { |
520
|
|
|
* "rank": 1, |
521
|
|
|
* "username": "Jimbo Wales", |
522
|
|
|
* "count": 50, |
523
|
|
|
* "minor": 15, |
524
|
|
|
* "first_edit": { |
525
|
|
|
* "id": 12345, |
526
|
|
|
* "timestamp": 20200101125959 |
527
|
|
|
* }, |
528
|
|
|
* "last_edit": { |
529
|
|
|
* "id": 54321, |
530
|
|
|
* "timestamp": 20200120125959 |
531
|
|
|
* } |
532
|
|
|
* } |
533
|
|
|
* }), |
534
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
535
|
|
|
* ) |
536
|
|
|
* ) |
537
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
538
|
|
|
* @OA\Response(response=503, ref="#/components/responses/503") |
539
|
|
|
* @OA\Response(response=504, ref="#/components/responses/504") |
540
|
|
|
* @param PageInfoRepository $pageInfoRepo |
541
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
542
|
|
|
* @return JsonResponse |
543
|
|
|
* @codeCoverageIgnore |
544
|
|
|
*/ |
545
|
|
|
public function topEditorsApiAction( |
546
|
|
|
PageInfoRepository $pageInfoRepo, |
547
|
|
|
AutomatedEditsHelper $autoEditsHelper |
548
|
|
|
): JsonResponse { |
549
|
|
|
$this->recordApiUsage('page/top_editors'); |
550
|
|
|
|
551
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
552
|
|
|
$topEditors = $this->pageInfo->getTopEditorsByEditCount( |
553
|
|
|
(int)$this->limit, |
554
|
|
|
$this->getBoolVal('nobots') |
555
|
|
|
); |
556
|
|
|
|
557
|
|
|
$this->addApiWarningAboutDates(['timestamp']); |
558
|
|
|
return $this->getFormattedApiResponse([ |
559
|
|
|
'top_editors' => $topEditors, |
560
|
|
|
]); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Get data about bots that have edited a page. |
565
|
|
|
* @Route( |
566
|
|
|
* "/api/page/bot_data/{project}/{page}/{start}/{end}", name="PageApiBotData", |
567
|
|
|
* requirements={ |
568
|
|
|
* "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
569
|
|
|
* "start"="|\d{4}-\d{2}-\d{2}", |
570
|
|
|
* "end"="|\d{4}-\d{2}-\d{2}", |
571
|
|
|
* }, |
572
|
|
|
* defaults={ |
573
|
|
|
* "start"=false, |
574
|
|
|
* "end"=false, |
575
|
|
|
* }, |
576
|
|
|
* methods={"GET"} |
577
|
|
|
* ) |
578
|
|
|
* @OA\Tag(name="Page API") |
579
|
|
|
* @OA\Get(description="List bots that have edited a page, with edit counts and whether the account |
580
|
|
|
is still in the `bot` user group.") |
581
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
582
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page") |
583
|
|
|
* @OA\Parameter(ref="#/components/parameters/Start") |
584
|
|
|
* @OA\Parameter(ref="#/components/parameters/End") |
585
|
|
|
* @OA\Response( |
586
|
|
|
* response=200, |
587
|
|
|
* description="List of bots", |
588
|
|
|
* @OA\JsonContent( |
589
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
590
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
591
|
|
|
* @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
592
|
|
|
* @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
593
|
|
|
* @OA\Property(property="bots", type="object", |
594
|
|
|
* @OA\Property(property="Page title", type="object", |
595
|
|
|
* @OA\Property(property="count", type="integer", description="Number of edits to the page."), |
596
|
|
|
* @OA\Property(property="current", type="boolean", |
597
|
|
|
* description="Whether the account currently has the bot flag" |
598
|
|
|
* ) |
599
|
|
|
* ) |
600
|
|
|
* ), |
601
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
602
|
|
|
* ) |
603
|
|
|
* ) |
604
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
605
|
|
|
* @OA\Response(response=503, ref="#/components/responses/503") |
606
|
|
|
* @OA\Response(response=504, ref="#/components/responses/504") |
607
|
|
|
* @param PageInfoRepository $pageInfoRepo |
608
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
609
|
|
|
* @return JsonResponse |
610
|
|
|
* @codeCoverageIgnore |
611
|
|
|
*/ |
612
|
|
|
public function botDataApiAction( |
613
|
|
|
PageInfoRepository $pageInfoRepo, |
614
|
|
|
AutomatedEditsHelper $autoEditsHelper |
615
|
|
|
): JsonResponse { |
616
|
|
|
$this->recordApiUsage('page/bot_data'); |
617
|
|
|
|
618
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
619
|
|
|
$bots = $this->pageInfo->getBots(); |
620
|
|
|
|
621
|
|
|
return $this->getFormattedApiResponse([ |
622
|
|
|
'bots' => $bots, |
623
|
|
|
]); |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* Get counts of (semi-)automated tools that were used to edit the page. |
628
|
|
|
* @Route( |
629
|
|
|
* "/api/page/automated_edits/{project}/{page}/{start}/{end}", name="PageApiAutoEdits", |
630
|
|
|
* requirements={ |
631
|
|
|
* "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
632
|
|
|
* "start"="|\d{4}-\d{2}-\d{2}", |
633
|
|
|
* "end"="|\d{4}-\d{2}-\d{2}", |
634
|
|
|
* }, |
635
|
|
|
* defaults={ |
636
|
|
|
* "start"=false, |
637
|
|
|
* "end"=false, |
638
|
|
|
* }, |
639
|
|
|
* methods={"GET"} |
640
|
|
|
* ) |
641
|
|
|
* @OA\Tag(name="Page API") |
642
|
|
|
* @OA\Get(description="Get counts of the number of times known (semi-)automated tools were used to edit the page.") |
643
|
|
|
* @OA\Parameter(ref="#/components/parameters/Project") |
644
|
|
|
* @OA\Parameter(ref="#/components/parameters/Page") |
645
|
|
|
* @OA\Parameter(ref="#/components/parameters/Start") |
646
|
|
|
* @OA\Parameter(ref="#/components/parameters/End") |
647
|
|
|
* @OA\Response( |
648
|
|
|
* response=200, |
649
|
|
|
* description="List of tools", |
650
|
|
|
* @OA\JsonContent( |
651
|
|
|
* @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
652
|
|
|
* @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
653
|
|
|
* @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
654
|
|
|
* @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
655
|
|
|
* @OA\Property(property="automated_tools", ref="#/components/schemas/AutomatedTools"), |
656
|
|
|
* @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
657
|
|
|
* ) |
658
|
|
|
* ) |
659
|
|
|
* @OA\Response(response=404, ref="#/components/responses/404") |
660
|
|
|
* @OA\Response(response=503, ref="#/components/responses/503") |
661
|
|
|
* @OA\Response(response=504, ref="#/components/responses/504") |
662
|
|
|
* @param PageInfoRepository $pageInfoRepo |
663
|
|
|
* @param AutomatedEditsHelper $autoEditsHelper |
664
|
|
|
* @return JsonResponse |
665
|
|
|
* @codeCoverageIgnore |
666
|
|
|
*/ |
667
|
|
|
public function getAutoEdits( |
668
|
|
|
PageInfoRepository $pageInfoRepo, |
669
|
|
|
AutomatedEditsHelper $autoEditsHelper |
670
|
|
|
): JsonResponse { |
671
|
|
|
$this->recordApiUsage('page/auto_edits'); |
672
|
|
|
|
673
|
|
|
$this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
674
|
|
|
return $this->getFormattedApiResponse([ |
675
|
|
|
'automated_tools' => $this->pageInfo->getAutoEditsCounts(), |
676
|
|
|
]); |
677
|
|
|
} |
678
|
|
|
} |
679
|
|
|
|