Passed
Push — flex ( 91a41f )
by MusikAnimal
07:11
created

ArticleInfoController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 10
dl 0
loc 15
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\ArticleInfo;
10
use App\Model\Authorship;
11
use App\Model\Page;
12
use App\Model\Project;
13
use App\Repository\ArticleInfoRepository;
14
use GuzzleHttp\Exception\ServerException;
15
use Symfony\Component\HttpFoundation\JsonResponse;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\Routing\Annotation\Route;
18
19
/**
20
 * This controller serves the search form and results for the ArticleInfo tool
21
 */
22
class ArticleInfoController extends XtoolsController
23
{
24
    protected ArticleInfo $articleInfo;
25
26
    /**
27
     * @inheritDoc
28
     * @codeCoverageIgnore
29
     */
30
    public function getIndexRoute(): string
31
    {
32
        return 'ArticleInfo';
33
    }
34
35
    /**
36
     * The search form.
37
     * @Route("/articleinfo", name="ArticleInfo")
38
     * @Route("/articleinfo/index.php", name="articleInfoIndexPhp")
39
     * @Route("/articleinfo/{project}", name="ArticleInfoProject")
40
     * @return Response
41
     */
42
    public function indexAction(): Response
43
    {
44
        if (isset($this->params['project']) && isset($this->params['page'])) {
45
            return $this->redirectToRoute('ArticleInfoResult', $this->params);
46
        }
47
48
        return $this->render('articleInfo/index.html.twig', array_merge([
49
            'xtPage' => 'ArticleInfo',
50
            'xtPageTitle' => 'tool-articleinfo',
51
            'xtSubtitle' => 'tool-articleinfo-desc',
52
53
            // Defaults that will get overridden if in $params.
54
            'start' => '',
55
            'end' => '',
56
            'page' => '',
57
        ], $this->params, ['project' => $this->project]));
58
    }
59
60
    /**
61
     * Setup the ArticleInfo instance and its Repository.
62
     * @param ArticleInfoRepository $articleInfoRepo
63
     * @param AutomatedEditsHelper $autoEditsHelper
64
     */
65
    private function setupArticleInfo(
66
        ArticleInfoRepository $articleInfoRepo,
67
        AutomatedEditsHelper $autoEditsHelper
68
    ): void {
69
        if (isset($this->articleInfo)) {
70
            return;
71
        }
72
73
        $this->articleInfo = new ArticleInfo(
74
            $articleInfoRepo,
75
            $this->i18n,
76
            $autoEditsHelper,
77
            $this->page,
0 ignored issues
show
Bug introduced by
It seems like $this->page can also be of type null; however, parameter $page of App\Model\ArticleInfo::__construct() 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 ignore-type  annotation

77
            /** @scrutinizer ignore-type */ $this->page,
Loading history...
78
            $this->start,
79
            $this->end
80
        );
81
    }
82
83
    /**
84
     * Generate ArticleInfo gadget script for use on-wiki. This automatically points the
85
     * script to this installation's API.
86
     *
87
     * @Route("/articleinfo-gadget.js", name="ArticleInfoGadget")
88
     * @link https://www.mediawiki.org/wiki/XTools/ArticleInfo_gadget
89
     *
90
     * @return Response
91
     * @codeCoverageIgnore
92
     */
93
    public function gadgetAction(): Response
94
    {
95
        $rendered = $this->renderView('articleInfo/articleinfo.js.twig');
96
        $response = new Response($rendered);
97
        $response->headers->set('Content-Type', 'text/javascript');
98
        return $response;
99
    }
100
101
    /**
102
     * Display the results in given date range.
103
     * @Route(
104
     *    "/articleinfo/{project}/{page}/{start}/{end}", name="ArticleInfoResult",
105
     *     requirements={
106
     *         "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$",
107
     *         "start"="|\d{4}-\d{2}-\d{2}",
108
     *         "end"="|\d{4}-\d{2}-\d{2}",
109
     *     },
110
     *     defaults={
111
     *         "start"=false,
112
     *         "end"=false,
113
     *     }
114
     * )
115
     * @param ArticleInfoRepository $articleInfoRepo
116
     * @param AutomatedEditsHelper $autoEditsHelper
117
     * @return Response
118
     * @codeCoverageIgnore
119
     */
120
    public function resultAction(
121
        ArticleInfoRepository $articleInfoRepo,
122
        AutomatedEditsHelper $autoEditsHelper
123
    ): Response {
124
        if (!$this->isDateRangeValid($this->page, $this->start, $this->end)) {
0 ignored issues
show
Bug introduced by
It seems like $this->page can also be of type null; however, parameter $page of App\Controller\ArticleIn...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 ignore-type  annotation

124
        if (!$this->isDateRangeValid(/** @scrutinizer ignore-type */ $this->page, $this->start, $this->end)) {
Loading history...
125
            $this->addFlashMessage('notice', 'date-range-outside-revisions');
126
127
            return $this->redirectToRoute('ArticleInfo', [
128
                'project' => $this->request->get('project'),
129
            ]);
130
        }
131
132
        $this->setupArticleInfo($articleInfoRepo, $autoEditsHelper);
133
        $this->articleInfo->prepareData();
134
135
        $maxRevisions = $this->getParameter('app.max_page_revisions');
136
137
        // Show message if we hit the max revisions.
138
        if ($this->articleInfo->tooManyRevisions()) {
139
            $this->addFlashMessage('notice', 'too-many-revisions', [
140
                $this->i18n->numberFormat($maxRevisions),
141
                $maxRevisions,
142
            ]);
143
        }
144
145
        // For when there is very old data (2001 era) which may cause miscalculations.
146
        if ($this->articleInfo->getFirstEdit()->getYear() < 2003) {
147
            $this->addFlashMessage('warning', 'old-page-notice');
148
        }
149
150
        // When all username info has been hidden (see T303724).
151
        if (0 === $this->articleInfo->getNumEditors()) {
152
            $this->addFlashMessage('warning', 'error-usernames-missing');
153
        }
154
155
        $ret = [
156
            'xtPage' => 'ArticleInfo',
157
            'xtTitle' => $this->page->getTitle(),
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

157
            'xtTitle' => $this->page->/** @scrutinizer ignore-call */ getTitle(),

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.

Loading history...
158
            'project' => $this->project,
159
            'editorlimit' => (int)$this->request->query->get('editorlimit', 20),
160
            'botlimit' => $this->request->query->get('botlimit', 10),
161
            'pageviewsOffset' => 60,
162
            'ai' => $this->articleInfo,
163
            'showAuthorship' => Authorship::isSupportedPage($this->page) && $this->articleInfo->getNumEditors() > 0,
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

163
            'showAuthorship' => Authorship::isSupportedPage(/** @scrutinizer ignore-type */ $this->page) && $this->articleInfo->getNumEditors() > 0,
Loading history...
164
        ];
165
166
        // Output the relevant format template.
167
        return $this->getFormattedResponse('articleInfo/result', $ret);
168
    }
169
170
    /**
171
     * Check if there were any revisions of given page in given date range.
172
     * @param Page $page
173
     * @param false|int $start
174
     * @param false|int $end
175
     * @return bool
176
     */
177
    private function isDateRangeValid(Page $page, $start, $end): bool
178
    {
179
        return $page->getNumRevisions(null, $start, $end) > 0;
180
    }
181
182
    /************************ API endpoints ************************/
183
184
    /**
185
     * Get basic info on a given article.
186
     * @Route(
187
     *     "/api/articleinfo/{project}/{page}",
188
     *     name="ArticleInfoApiAction",
189
     *     requirements={"page"=".+"}
190
     * )
191
     * @Route("/api/page/articleinfo/{project}/{page}", requirements={"page"=".+"})
192
     * @param ArticleInfoRepository $articleInfoRepo
193
     * @param AutomatedEditsHelper $autoEditsHelper
194
     * @return Response|JsonResponse
195
     * See ArticleInfoControllerTest::testArticleInfoApi()
196
     * @codeCoverageIgnore
197
     */
198
    public function articleInfoApiAction(
199
        ArticleInfoRepository $articleInfoRepo,
200
        AutomatedEditsHelper $autoEditsHelper
201
    ): Response {
202
        $this->recordApiUsage('page/articleinfo');
203
204
        $this->setupArticleInfo($articleInfoRepo, $autoEditsHelper);
205
        $data = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
206
207
        try {
208
            $data = $this->articleInfo->getArticleInfoApiData($this->project, $this->page);
0 ignored issues
show
Bug introduced by
It seems like $this->page can also be of type null; however, parameter $page of App\Model\ArticleInfoApi::getArticleInfoApiData() 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 ignore-type  annotation

208
            $data = $this->articleInfo->getArticleInfoApiData($this->project, /** @scrutinizer ignore-type */ $this->page);
Loading history...
209
        } catch (ServerException $e) {
210
            // The Wikimedia action API can fail for any number of reasons. To our users
211
            // any ServerException means the data could not be fetched, so we capture it here
212
            // to avoid the flood of automated emails when the API goes down, etc.
213
            $data['error'] = $this->i18n->msg('api-error', [$this->project->getDomain()]);
214
        }
215
216
        if ('html' === $this->request->query->get('format')) {
217
            return $this->getApiHtmlResponse($this->project, $this->page, $data);
0 ignored issues
show
Bug introduced by
It seems like $this->page can also be of type null; however, parameter $page of App\Controller\ArticleIn...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 ignore-type  annotation

217
            return $this->getApiHtmlResponse($this->project, /** @scrutinizer ignore-type */ $this->page, $data);
Loading history...
218
        }
219
220
        return $this->getFormattedApiResponse($data);
221
    }
222
223
    /**
224
     * Get the Response for the HTML output of the ArticleInfo API action.
225
     * @param Project $project
226
     * @param Page $page
227
     * @param string[] $data The pre-fetched data.
228
     * @return Response
229
     * @codeCoverageIgnore
230
     */
231
    private function getApiHtmlResponse(Project $project, Page $page, array $data): Response
232
    {
233
        $response = $this->render('articleInfo/api.html.twig', [
234
            'project' => $project,
235
            'page' => $page,
236
            'data' => $data,
237
        ]);
238
239
        // All /api routes by default respond with a JSON content type.
240
        $response->headers->set('Content-Type', 'text/html');
241
242
        // This endpoint is hit constantly and user could be browsing the same page over
243
        // and over (popular noticeboard, for instance), so offload brief caching to browser.
244
        $response->setClientTtl(350);
245
246
        return $response;
247
    }
248
249
    /**
250
     * Get prose statistics for the given article.
251
     * @Route(
252
     *     "/api/page/prose/{project}/{page}",
253
     *     name="PageApiProse",
254
     *     requirements={"page"=".+"}
255
     * )
256
     * @param ArticleInfoRepository $articleInfoRepo
257
     * @param AutomatedEditsHelper $autoEditsHelper
258
     * @return JsonResponse
259
     * @codeCoverageIgnore
260
     */
261
    public function proseStatsApiAction(
262
        ArticleInfoRepository $articleInfoRepo,
263
        AutomatedEditsHelper $autoEditsHelper
264
    ): JsonResponse {
265
        $this->recordApiUsage('page/prose');
266
        $this->setupArticleInfo($articleInfoRepo, $autoEditsHelper);
267
        return $this->getFormattedApiResponse($this->articleInfo->getProseStats());
268
    }
269
270
    /**
271
     * Get the page assessments of one or more pages, along with various related metadata.
272
     * @Route(
273
     *     "/api/page/assessments/{project}/{pages}",
274
     *     name="PageApiAssessments",
275
     *     requirements={"pages"=".+"}
276
     * )
277
     * @param string $pages May be multiple pages separated by pipes, e.g. Foo|Bar|Baz
278
     * @return JsonResponse
279
     * @codeCoverageIgnore
280
     */
281
    public function assessmentsApiAction(string $pages): JsonResponse
282
    {
283
        $this->recordApiUsage('page/assessments');
284
285
        $pages = explode('|', $pages);
286
        $out = [];
287
288
        foreach ($pages as $pageTitle) {
289
            try {
290
                $page = $this->validatePage($pageTitle);
291
                $assessments = $page->getProject()
292
                    ->getPageAssessments()
293
                    ->getAssessments($page);
294
295
                $out[$page->getTitle()] = $this->request->get('classonly')
296
                    ? $assessments['assessment']
297
                    : $assessments;
298
            } catch (XtoolsHttpException $e) {
299
                $out[$pageTitle] = false;
300
            }
301
        }
302
303
        return $this->getFormattedApiResponse($out);
304
    }
305
306
    /**
307
     * Get number of in and outgoing links and redirects to the given page.
308
     * @Route(
309
     *     "/api/page/links/{project}/{page}",
310
     *     name="PageApiLinks",
311
     *     requirements={"page"=".+"}
312
     * )
313
     * @return JsonResponse
314
     * @codeCoverageIgnore
315
     */
316
    public function linksApiAction(): JsonResponse
317
    {
318
        $this->recordApiUsage('page/links');
319
        return $this->getFormattedApiResponse($this->page->countLinksAndRedirects());
320
    }
321
322
    /**
323
     * Get the top editors to a page.
324
     * @Route(
325
     *     "/api/page/top_editors/{project}/{page}/{start}/{end}/{limit}", name="PageApiTopEditors",
326
     *     requirements={
327
     *         "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?)?$",
328
     *         "start"="|\d{4}-\d{2}-\d{2}",
329
     *         "end"="|\d{4}-\d{2}-\d{2}",
330
     *         "limit"="|\d+"
331
     *     },
332
     *     defaults={
333
     *         "start"=false,
334
     *         "end"=false,
335
     *         "limit"=20,
336
     *     }
337
     * )
338
     * @param ArticleInfoRepository $articleInfoRepo
339
     * @param AutomatedEditsHelper $autoEditsHelper
340
     * @return JsonResponse
341
     * @codeCoverageIgnore
342
     */
343
    public function topEditorsApiAction(
344
        ArticleInfoRepository $articleInfoRepo,
345
        AutomatedEditsHelper $autoEditsHelper
346
    ): JsonResponse {
347
        $this->recordApiUsage('page/top_editors');
348
349
        $this->setupArticleInfo($articleInfoRepo, $autoEditsHelper);
350
        $topEditors = $this->articleInfo->getTopEditorsByEditCount(
351
            (int)$this->limit,
352
            '' != $this->request->query->get('nobots')
353
        );
354
355
        return $this->getFormattedApiResponse([
356
            'top_editors' => $topEditors,
357
        ]);
358
    }
359
}
360