Test Failed
Push — dependency-injection ( 7565fa )
by MusikAnimal
07:05
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\Helper\I18nHelper;
10
use App\Model\ArticleInfo;
11
use App\Model\Authorship;
12
use App\Model\Page;
13
use App\Model\Project;
14
use App\Repository\ArticleInfoRepository;
15
use App\Repository\PageRepository;
16
use App\Repository\ProjectRepository;
17
use App\Repository\UserRepository;
18
use GuzzleHttp\Client;
19
use GuzzleHttp\Exception\ServerException;
20
use Psr\Cache\CacheItemPoolInterface;
21
use Psr\Container\ContainerInterface;
22
use Symfony\Component\HttpFoundation\JsonResponse;
23
use Symfony\Component\HttpFoundation\RequestStack;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\Routing\Annotation\Route;
26
27
/**
28
 * This controller serves the search form and results for the ArticleInfo tool
29
 */
30
class ArticleInfoController extends XtoolsController
31
{
32
    protected ArticleInfo $articleInfo;
33
    protected ArticleInfoRepository $articleInfoRepo;
34
    protected AutomatedEditsHelper $autoEditsHelper;
35
36
    /**
37
     * Get the name of the tool's index route. This is also the name of the associated model.
38
     * @return string
39
     * @codeCoverageIgnore
40
     */
41
    public function getIndexRoute(): string
42
    {
43
        return 'ArticleInfo';
44
    }
45
46
    public function __construct(
47
        RequestStack $requestStack,
48
        ContainerInterface $container,
49
        CacheItemPoolInterface $cache,
50
        Client $guzzle,
51
        I18nHelper $i18n,
52
        ProjectRepository $projectRepo,
53
        UserRepository $userRepo,
54
        PageRepository $pageRepo,
55
        ArticleInfoRepository $articleInfoRepo,
56
        AutomatedEditsHelper $autoEditsHelper
57
    ) {
58
        $this->articleInfoRepo = $articleInfoRepo;
59
        $this->autoEditsHelper = $autoEditsHelper;
60
        parent::__construct($requestStack, $container, $cache, $guzzle, $i18n, $projectRepo, $userRepo, $pageRepo);
61
    }
62
63
    /**
64
     * The search form.
65
     * @Route("/articleinfo", name="ArticleInfo")
66
     * @Route("/articleinfo/index.php", name="articleInfoIndexPhp")
67
     * @Route("/articleinfo/{project}", name="ArticleInfoProject")
68
     * @return Response
69
     */
70
    public function indexAction(): Response
71
    {
72
        if (isset($this->params['project']) && isset($this->params['page'])) {
73
            return $this->redirectToRoute('ArticleInfoResult', $this->params);
74
        }
75
76
        return $this->render('articleInfo/index.html.twig', array_merge([
77
            'xtPage' => 'ArticleInfo',
78
            'xtPageTitle' => 'tool-articleinfo',
79
            'xtSubtitle' => 'tool-articleinfo-desc',
80
81
            // Defaults that will get overridden if in $params.
82
            'start' => '',
83
            'end' => '',
84
            'page' => '',
85
        ], $this->params, ['project' => $this->project]));
86
    }
87
88
    /**
89
     * Setup the ArticleInfo instance and its Repository.
90
     */
91
    private function setupArticleInfo(): void
92
    {
93
        if (isset($this->articleInfo)) {
94
            return;
95
        }
96
97
        $this->articleInfo = new ArticleInfo(
98
            $this->articleInfoRepo,
99
            $this->i18n,
100
            $this->autoEditsHelper,
101
            $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

101
            /** @scrutinizer ignore-type */ $this->page,
Loading history...
102
            $this->start,
103
            $this->end
104
        );
105
    }
106
107
    /**
108
     * Generate ArticleInfo gadget script for use on-wiki. This automatically points the
109
     * script to this installation's API. Pass ?uglify=1 to uglify the code.
110
     *
111
     * @Route("/articleinfo-gadget.js", name="ArticleInfoGadget")
112
     * @link https://www.mediawiki.org/wiki/XTools/ArticleInfo_gadget
113
     *
114
     * @return Response
115
     * @codeCoverageIgnore
116
     */
117
    public function gadgetAction(): Response
118
    {
119
        $rendered = $this->renderView('articleInfo/articleinfo.js.twig');
120
        $response = new Response($rendered);
121
        $response->headers->set('Content-Type', 'text/javascript');
122
        return $response;
123
    }
124
125
    /**
126
     * Display the results in given date range.
127
     * @Route(
128
     *    "/articleinfo/{project}/{page}/{start}/{end}", name="ArticleInfoResult",
129
     *     requirements={
130
     *         "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$",
131
     *         "start"="|\d{4}-\d{2}-\d{2}",
132
     *         "end"="|\d{4}-\d{2}-\d{2}",
133
     *     },
134
     *     defaults={
135
     *         "start"=false,
136
     *         "end"=false,
137
     *     }
138
     * )
139
     * @return Response
140
     * @codeCoverageIgnore
141
     */
142
    public function resultAction(): Response
143
    {
144
        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

144
        if (!$this->isDateRangeValid(/** @scrutinizer ignore-type */ $this->page, $this->start, $this->end)) {
Loading history...
145
            $this->addFlashMessage('notice', 'date-range-outside-revisions');
146
147
            return $this->redirectToRoute('ArticleInfo', [
148
                'project' => $this->request->get('project'),
149
            ]);
150
        }
151
152
        $this->setupArticleInfo();
153
        $this->articleInfo->prepareData();
154
155
        $maxRevisions = $this->getParameter('app.max_page_revisions');
156
157
        // Show message if we hit the max revisions.
158
        if ($this->articleInfo->tooManyRevisions()) {
159
            $this->addFlashMessage('notice', 'too-many-revisions', [
160
                $i18n->numberFormat($maxRevisions),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i18n seems to be never defined.
Loading history...
161
                $maxRevisions,
162
            ]);
163
        }
164
165
        // For when there is very old data (2001 era) which may cause miscalculations.
166
        if ($this->articleInfo->getFirstEdit()->getYear() < 2003) {
167
            $this->addFlashMessage('warning', 'old-page-notice');
168
        }
169
170
        // When all username info has been hidden (see T303724).
171
        if (0 === $this->articleInfo->getNumEditors()) {
172
            $this->addFlashMessage('warning', 'error-usernames-missing');
173
        }
174
175
        $ret = [
176
            'xtPage' => 'ArticleInfo',
177
            '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

177
            '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...
178
            'project' => $this->project,
179
            'editorlimit' => (int)$this->request->query->get('editorlimit', 20),
180
            'botlimit' => $this->request->query->get('botlimit', 10),
181
            'pageviewsOffset' => 60,
182
            'ai' => $this->articleInfo,
183
            '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

183
            'showAuthorship' => Authorship::isSupportedPage(/** @scrutinizer ignore-type */ $this->page) && $this->articleInfo->getNumEditors() > 0,
Loading history...
184
        ];
185
186
        // Output the relevant format template.
187
        return $this->getFormattedResponse('articleInfo/result', $ret);
188
    }
189
190
    /**
191
     * Check if there were any revisions of given page in given date range.
192
     * @param Page $page
193
     * @param false|int $start
194
     * @param false|int $end
195
     * @return bool
196
     */
197
    private function isDateRangeValid(Page $page, $start, $end): bool
198
    {
199
        return $page->getNumRevisions(null, $start, $end) > 0;
200
    }
201
202
    /************************ API endpoints ************************/
203
204
    /**
205
     * Get basic info on a given article.
206
     * @Route(
207
     *     "/api/articleinfo/{project}/{page}",
208
     *     name="ArticleInfoApiAction",
209
     *     requirements={"page"=".+"}
210
     * )
211
     * @Route("/api/page/articleinfo/{project}/{page}", requirements={"page"=".+"})
212
     * @return Response|JsonResponse
213
     * See ArticleInfoControllerTest::testArticleInfoApi()
214
     * @codeCoverageIgnore
215
     */
216
    public function articleInfoApiAction(): Response
217
    {
218
        $this->recordApiUsage('page/articleinfo');
219
220
        $this->setupArticleInfo();
221
        $data = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
222
223
        try {
224
            $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

224
            $data = $this->articleInfo->getArticleInfoApiData($this->project, /** @scrutinizer ignore-type */ $this->page);
Loading history...
225
        } catch (ServerException $e) {
226
            // The Wikimedia action API can fail for any number of reasons. To our users
227
            // any ServerException means the data could not be fetched, so we capture it here
228
            // to avoid the flood of automated emails when the API goes down, etc.
229
            $data['error'] = $this->i18n->msg('api-error', [$this->project->getDomain()]);
230
        }
231
232
        if ('html' === $this->request->query->get('format')) {
233
            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

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