This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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, |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
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)) { |
||||
0 ignored issues
–
show
It seems like
$this->page can also be of type null ; however, parameter $page of App\Controller\PageInfoC...ler::isDateRangeValid() does only seem to accept App\Model\Page , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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(), |
||||
0 ignored issues
–
show
The method
getTitle() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
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, |
||||
0 ignored issues
–
show
It seems like
$this->page can also be of type null ; however, parameter $page of App\Model\Authorship::isSupportedPage() does only seem to accept App\Model\Page , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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="modified_rev_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 = []; |
||||
0 ignored issues
–
show
|
|||||
279 | |||||
280 | try { |
||||
281 | $data = $this->pageInfo->getPageInfoApiData($this->project, $this->page); |
||||
0 ignored issues
–
show
It seems like
$this->page can also be of type null ; however, parameter $page of App\Model\PageInfoApi::getPageInfoApiData() does only seem to accept App\Model\Page , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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); |
||||
0 ignored issues
–
show
It seems like
$this->page can also be of type null ; however, parameter $page of App\Controller\PageInfoC...r::getApiHtmlResponse() does only seem to accept App\Model\Page , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
291 | } |
||||
292 | |||||
293 | $this->addFlash('warning', 'In XTools 3.21, the last_edit_id property will be removed. ' . |
||||
294 | 'Use the modified_rev_id property instead.'); |
||||
295 | $data['last_edit_id'] = $data['modified_rev_id']; |
||||
296 | $this->addFlash('warning', 'In XTools 3.21, the author and author_editcount properties ' . |
||||
297 | 'will be removed. Instead, use creator and creator_editcount, respectively.'); |
||||
298 | $data['author'] = $data['creator']; |
||||
299 | $data['author_editcount'] = $data['creator_editcount']; |
||||
300 | |||||
301 | return $this->getFormattedApiResponse($data); |
||||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * Get the Response for the HTML output of the PageInfo API action. |
||||
306 | * @param Project $project |
||||
307 | * @param Page $page |
||||
308 | * @param string[] $data The pre-fetched data. |
||||
309 | * @return Response |
||||
310 | * @codeCoverageIgnore |
||||
311 | */ |
||||
312 | private function getApiHtmlResponse(Project $project, Page $page, array $data): Response |
||||
313 | { |
||||
314 | $response = $this->render('pageInfo/api.html.twig', [ |
||||
315 | 'project' => $project, |
||||
316 | 'page' => $page, |
||||
317 | 'data' => $data, |
||||
318 | ]); |
||||
319 | |||||
320 | // All /api routes by default respond with a JSON content type. |
||||
321 | $response->headers->set('Content-Type', 'text/html'); |
||||
322 | |||||
323 | // This endpoint is hit constantly and user could be browsing the same page over |
||||
324 | // and over (popular noticeboard, for instance), so offload brief caching to browser. |
||||
325 | $response->setClientTtl(350); |
||||
326 | |||||
327 | return $response; |
||||
328 | } |
||||
329 | |||||
330 | /** |
||||
331 | * Get prose statistics for the given page. |
||||
332 | * @Route( |
||||
333 | * "/api/page/prose/{project}/{page}", |
||||
334 | * name="PageApiProse", |
||||
335 | * requirements={"page"=".+"}, |
||||
336 | * methods={"GET"} |
||||
337 | * ) |
||||
338 | * @OA\Tag(name="Page API") |
||||
339 | * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/XTools/Page_History#Prose") |
||||
340 | * @OA\Get(description="Get statistics about the [prose](https://en.wiktionary.org/wiki/prose) (characters, |
||||
341 | word count, etc.) and referencing of a page. ([more info](https://w.wiki/6oAF))") |
||||
342 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
343 | * @OA\Parameter(ref="#/components/parameters/Page", @OA\Schema(example="Metallica")) |
||||
344 | * @OA\Response( |
||||
345 | * response=200, |
||||
346 | * description="Prose stats", |
||||
347 | * @OA\JsonContent( |
||||
348 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
349 | * @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
||||
350 | * @OA\Property(property="bytes", type="integer"), |
||||
351 | * @OA\Property(property="characters", type="integer"), |
||||
352 | * @OA\Property(property="words", type="integer"), |
||||
353 | * @OA\Property(property="references", type="integer"), |
||||
354 | * @OA\Property(property="unique_references", type="integer"), |
||||
355 | * @OA\Property(property="sections", type="integer"), |
||||
356 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
357 | * ) |
||||
358 | * ) |
||||
359 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
360 | * @param PageInfoRepository $pageInfoRepo |
||||
361 | * @param AutomatedEditsHelper $autoEditsHelper |
||||
362 | * @return JsonResponse |
||||
363 | * @codeCoverageIgnore |
||||
364 | */ |
||||
365 | public function proseStatsApiAction( |
||||
366 | PageInfoRepository $pageInfoRepo, |
||||
367 | AutomatedEditsHelper $autoEditsHelper |
||||
368 | ): JsonResponse { |
||||
369 | $responseCode = Response::HTTP_OK; |
||||
370 | $this->recordApiUsage('page/prose'); |
||||
371 | $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
||||
372 | $this->addFlash('info', 'The algorithm used by this API has recently changed. ' . |
||||
373 | 'See https://www.mediawiki.org/wiki/XTools/Page_History#Prose for details.'); |
||||
374 | $ret = $this->pageInfo->getProseStats(); |
||||
375 | if (null === $ret) { |
||||
376 | $this->addFlashMessage('error', 'api-error-wikimedia'); |
||||
377 | $responseCode = Response::HTTP_BAD_GATEWAY; |
||||
378 | $ret = []; |
||||
379 | } |
||||
380 | return $this->getFormattedApiResponse($ret, $responseCode); |
||||
381 | } |
||||
382 | |||||
383 | /** |
||||
384 | * Get the page assessments of one or more pages, along with various related metadata. |
||||
385 | * @Route( |
||||
386 | * "/api/page/assessments/{project}/{pages}", |
||||
387 | * name="PageApiAssessments", |
||||
388 | * requirements={"pages"=".+"}, |
||||
389 | * methods={"GET"} |
||||
390 | * ) |
||||
391 | * @OA\Tag(name="Page API") |
||||
392 | * @OA\Get(description="Get [assessment data](https://w.wiki/6oAM) of the given pages, including the overall |
||||
393 | quality classifications, along with a list of the WikiProjects and their classifications and importance levels.") |
||||
394 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
395 | * @OA\Parameter(ref="#/components/parameters/Pages") |
||||
396 | * @OA\Parameter(name="classonly", in="query", @OA\Schema(type="boolean"), |
||||
397 | * description="Return only the overall quality assessment instead of for each applicable WikiProject." |
||||
398 | * ) |
||||
399 | * @OA\Response( |
||||
400 | * response=200, |
||||
401 | * description="Assessmnet data", |
||||
402 | * @OA\JsonContent( |
||||
403 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
404 | * @OA\Property(property="pages", type="object", |
||||
405 | * @OA\Property(property="Page title", type="object", |
||||
406 | * @OA\Property(property="assessment", ref="#/components/schemas/PageAssessment"), |
||||
407 | * @OA\Property(property="wikiprojects", type="object", |
||||
408 | * @OA\Property(property="name of WikiProject", |
||||
409 | * ref="#/components/schemas/PageAssessmentWikiProject" |
||||
410 | * ) |
||||
411 | * ) |
||||
412 | * ) |
||||
413 | * ), |
||||
414 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
415 | * ) |
||||
416 | * ) |
||||
417 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
418 | * @param string $pages May be multiple pages separated by pipes, e.g. Foo|Bar|Baz |
||||
419 | * @return JsonResponse |
||||
420 | * @codeCoverageIgnore |
||||
421 | */ |
||||
422 | public function assessmentsApiAction(string $pages): JsonResponse |
||||
423 | { |
||||
424 | $this->recordApiUsage('page/assessments'); |
||||
425 | |||||
426 | $pages = explode('|', $pages); |
||||
427 | $out = [ |
||||
428 | 'pages' => [], |
||||
429 | ]; |
||||
430 | |||||
431 | foreach ($pages as $pageTitle) { |
||||
432 | try { |
||||
433 | $page = $this->validatePage($pageTitle); |
||||
434 | $assessments = $page->getProject() |
||||
435 | ->getPageAssessments() |
||||
436 | ->getAssessments($page); |
||||
437 | |||||
438 | $out['pages'][$page->getTitle()] = $this->getBoolVal('classonly') |
||||
439 | ? $assessments['assessment'] |
||||
440 | : $assessments; |
||||
441 | } catch (XtoolsHttpException $e) { |
||||
442 | $out['pages'][$pageTitle] = false; |
||||
443 | } |
||||
444 | } |
||||
445 | |||||
446 | return $this->getFormattedApiResponse($out); |
||||
447 | } |
||||
448 | |||||
449 | /** |
||||
450 | * Get number of in and outgoing links, external links, and redirects to the given page. |
||||
451 | * @Route( |
||||
452 | * "/api/page/links/{project}/{page}", |
||||
453 | * name="PageApiLinks", |
||||
454 | * requirements={"page"=".+"}, |
||||
455 | * methods={"GET"} |
||||
456 | * ) |
||||
457 | * @OA\Tag(name="Page API") |
||||
458 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
459 | * @OA\Parameter(ref="#/components/parameters/Page") |
||||
460 | * @OA\Response( |
||||
461 | * response=200, |
||||
462 | * description="Counts of in and outgoing links, external links, and redirects.", |
||||
463 | * @OA\JsonContent( |
||||
464 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
465 | * @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
||||
466 | * @OA\Property(property="links_ext_count", type="integer"), |
||||
467 | * @OA\Property(property="links_out_count", type="integer"), |
||||
468 | * @OA\Property(property="links_in_count", type="integer"), |
||||
469 | * @OA\Property(property="redirects_count", type="integer"), |
||||
470 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
471 | * ) |
||||
472 | * ) |
||||
473 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
474 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||
475 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||
476 | * @return JsonResponse |
||||
477 | * @codeCoverageIgnore |
||||
478 | */ |
||||
479 | public function linksApiAction(): JsonResponse |
||||
480 | { |
||||
481 | $this->recordApiUsage('page/links'); |
||||
482 | return $this->getFormattedApiResponse($this->page->countLinksAndRedirects()); |
||||
483 | } |
||||
484 | |||||
485 | /** |
||||
486 | * Get the top editors (by number of edits) of a page. |
||||
487 | * @Route( |
||||
488 | * "/api/page/top_editors/{project}/{page}/{start}/{end}/{limit}", name="PageApiTopEditors", |
||||
489 | * requirements={ |
||||
490 | * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?)?$", |
||||
491 | * "start"="|\d{4}-\d{2}-\d{2}", |
||||
492 | * "end"="|\d{4}-\d{2}-\d{2}", |
||||
493 | * "limit"="\d+" |
||||
494 | * }, |
||||
495 | * defaults={ |
||||
496 | * "start"=false, |
||||
497 | * "end"=false, |
||||
498 | * "limit"=20, |
||||
499 | * }, |
||||
500 | * methods={"GET"} |
||||
501 | * ) |
||||
502 | * @OA\Tag(name="Page API") |
||||
503 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
504 | * @OA\Parameter(ref="#/components/parameters/Page") |
||||
505 | * @OA\Parameter(ref="#/components/parameters/Start") |
||||
506 | * @OA\Parameter(ref="#/components/parameters/End") |
||||
507 | * @OA\Parameter(ref="#/components/parameters/Limit") |
||||
508 | * @OA\Parameter(name="nobots", in="query", |
||||
509 | * description="Exclude bots from the results.", @OA\Schema(type="boolean") |
||||
510 | * ) |
||||
511 | * @OA\Response( |
||||
512 | * response=200, |
||||
513 | * description="List of the top editors, sorted by how many edits they've made to the page.", |
||||
514 | * @OA\JsonContent( |
||||
515 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
516 | * @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
||||
517 | * @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
||||
518 | * @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
||||
519 | * @OA\Property(property="limit", ref="#/components/parameters/Limit/schema"), |
||||
520 | * @OA\Property(property="top_editors", type="array", @OA\Items(type="object"), example={ |
||||
521 | * { |
||||
522 | * "rank": 1, |
||||
523 | * "username": "Jimbo Wales", |
||||
524 | * "count": 50, |
||||
525 | * "minor": 15, |
||||
526 | * "first_edit": { |
||||
527 | * "id": 12345, |
||||
528 | * "timestamp": "2020-01-01T12:59:59Z" |
||||
529 | * }, |
||||
530 | * "last_edit": { |
||||
531 | * "id": 54321, |
||||
532 | * "timestamp": "2020-01-20T12:59:59Z" |
||||
533 | * } |
||||
534 | * } |
||||
535 | * }), |
||||
536 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
537 | * ) |
||||
538 | * ) |
||||
539 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
540 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||
541 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||
542 | * @param PageInfoRepository $pageInfoRepo |
||||
543 | * @param AutomatedEditsHelper $autoEditsHelper |
||||
544 | * @return JsonResponse |
||||
545 | * @codeCoverageIgnore |
||||
546 | */ |
||||
547 | public function topEditorsApiAction( |
||||
548 | PageInfoRepository $pageInfoRepo, |
||||
549 | AutomatedEditsHelper $autoEditsHelper |
||||
550 | ): JsonResponse { |
||||
551 | $this->recordApiUsage('page/top_editors'); |
||||
552 | |||||
553 | $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
||||
554 | $topEditors = $this->pageInfo->getTopEditorsByEditCount( |
||||
555 | (int)$this->limit, |
||||
556 | $this->getBoolVal('nobots') |
||||
557 | ); |
||||
558 | |||||
559 | return $this->getFormattedApiResponse([ |
||||
560 | 'top_editors' => $topEditors, |
||||
561 | ]); |
||||
562 | } |
||||
563 | |||||
564 | /** |
||||
565 | * Get data about bots that have edited a page. |
||||
566 | * @Route( |
||||
567 | * "/api/page/bot_data/{project}/{page}/{start}/{end}", name="PageApiBotData", |
||||
568 | * requirements={ |
||||
569 | * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
||||
570 | * "start"="|\d{4}-\d{2}-\d{2}", |
||||
571 | * "end"="|\d{4}-\d{2}-\d{2}", |
||||
572 | * }, |
||||
573 | * defaults={ |
||||
574 | * "start"=false, |
||||
575 | * "end"=false, |
||||
576 | * }, |
||||
577 | * methods={"GET"} |
||||
578 | * ) |
||||
579 | * @OA\Tag(name="Page API") |
||||
580 | * @OA\Get(description="List bots that have edited a page, with edit counts and whether the account |
||||
581 | is still in the `bot` user group.") |
||||
582 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
583 | * @OA\Parameter(ref="#/components/parameters/Page") |
||||
584 | * @OA\Parameter(ref="#/components/parameters/Start") |
||||
585 | * @OA\Parameter(ref="#/components/parameters/End") |
||||
586 | * @OA\Response( |
||||
587 | * response=200, |
||||
588 | * description="List of bots", |
||||
589 | * @OA\JsonContent( |
||||
590 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
591 | * @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
||||
592 | * @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
||||
593 | * @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
||||
594 | * @OA\Property(property="bots", type="object", |
||||
595 | * @OA\Property(property="Page title", type="object", |
||||
596 | * @OA\Property(property="count", type="integer", description="Number of edits to the page."), |
||||
597 | * @OA\Property(property="current", type="boolean", |
||||
598 | * description="Whether the account currently has the bot flag" |
||||
599 | * ) |
||||
600 | * ) |
||||
601 | * ), |
||||
602 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
603 | * ) |
||||
604 | * ) |
||||
605 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
606 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||
607 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||
608 | * @param PageInfoRepository $pageInfoRepo |
||||
609 | * @param AutomatedEditsHelper $autoEditsHelper |
||||
610 | * @return JsonResponse |
||||
611 | * @codeCoverageIgnore |
||||
612 | */ |
||||
613 | public function botDataApiAction( |
||||
614 | PageInfoRepository $pageInfoRepo, |
||||
615 | AutomatedEditsHelper $autoEditsHelper |
||||
616 | ): JsonResponse { |
||||
617 | $this->recordApiUsage('page/bot_data'); |
||||
618 | |||||
619 | $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
||||
620 | $bots = $this->pageInfo->getBots(); |
||||
621 | |||||
622 | return $this->getFormattedApiResponse([ |
||||
623 | 'bots' => $bots, |
||||
624 | ]); |
||||
625 | } |
||||
626 | |||||
627 | /** |
||||
628 | * Get counts of (semi-)automated tools that were used to edit the page. |
||||
629 | * @Route( |
||||
630 | * "/api/page/automated_edits/{project}/{page}/{start}/{end}", name="PageApiAutoEdits", |
||||
631 | * requirements={ |
||||
632 | * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", |
||||
633 | * "start"="|\d{4}-\d{2}-\d{2}", |
||||
634 | * "end"="|\d{4}-\d{2}-\d{2}", |
||||
635 | * }, |
||||
636 | * defaults={ |
||||
637 | * "start"=false, |
||||
638 | * "end"=false, |
||||
639 | * }, |
||||
640 | * methods={"GET"} |
||||
641 | * ) |
||||
642 | * @OA\Tag(name="Page API") |
||||
643 | * @OA\Get(description="Get counts of the number of times known (semi-)automated tools were used to edit the page.") |
||||
644 | * @OA\Parameter(ref="#/components/parameters/Project") |
||||
645 | * @OA\Parameter(ref="#/components/parameters/Page") |
||||
646 | * @OA\Parameter(ref="#/components/parameters/Start") |
||||
647 | * @OA\Parameter(ref="#/components/parameters/End") |
||||
648 | * @OA\Response( |
||||
649 | * response=200, |
||||
650 | * description="List of tools", |
||||
651 | * @OA\JsonContent( |
||||
652 | * @OA\Property(property="project", ref="#/components/parameters/Project/schema"), |
||||
653 | * @OA\Property(property="page", ref="#/components/parameters/Page/schema"), |
||||
654 | * @OA\Property(property="start", ref="#/components/parameters/Start/schema"), |
||||
655 | * @OA\Property(property="end", ref="#/components/parameters/End/schema"), |
||||
656 | * @OA\Property(property="automated_tools", ref="#/components/schemas/AutomatedTools"), |
||||
657 | * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") |
||||
658 | * ) |
||||
659 | * ) |
||||
660 | * @OA\Response(response=404, ref="#/components/responses/404") |
||||
661 | * @OA\Response(response=503, ref="#/components/responses/503") |
||||
662 | * @OA\Response(response=504, ref="#/components/responses/504") |
||||
663 | * @param PageInfoRepository $pageInfoRepo |
||||
664 | * @param AutomatedEditsHelper $autoEditsHelper |
||||
665 | * @return JsonResponse |
||||
666 | * @codeCoverageIgnore |
||||
667 | */ |
||||
668 | public function getAutoEdits( |
||||
669 | PageInfoRepository $pageInfoRepo, |
||||
670 | AutomatedEditsHelper $autoEditsHelper |
||||
671 | ): JsonResponse { |
||||
672 | $this->recordApiUsage('page/auto_edits'); |
||||
673 | |||||
674 | $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); |
||||
675 | return $this->getFormattedApiResponse([ |
||||
676 | 'automated_tools' => $this->pageInfo->getAutoEditsCounts(), |
||||
677 | ]); |
||||
678 | } |
||||
679 | } |
||||
680 |