Passed
Pull Request — main (#442)
by MusikAnimal
08:00 queued 04:10
created

CategoryEditsController   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 58
dl 0
loc 201
rs 10
c 0
b 0
f 0
wmc 14

8 Methods

Rating   Name   Duplication   Size   Complexity  
A tooHighEditCountRoute() 0 3 1
A resultAction() 0 5 1
A categoryContributionsAction() 0 5 1
A extractCategories() 0 25 4
A setupCategoryEdits() 0 21 1
A categoryEditCountApiAction() 0 12 1
A indexAction() 0 19 4
A getIndexRoute() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Controller;
6
7
use App\Exception\XtoolsHttpException;
8
use App\Model\CategoryEdits;
9
use App\Repository\CategoryEditsRepository;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\Routing\Annotation\Route;
13
14
/**
15
 * This controller serves the Category Edits tool.
16
 */
17
class CategoryEditsController extends XtoolsController
18
{
19
    protected CategoryEdits $categoryEdits;
20
21
    /** @var string[] The categories, with or without namespace. */
22
    protected array $categories;
23
24
    /** @var array Data that is passed to the view. */
25
    private array $output;
26
27
    /**
28
     * Get the name of the tool's index route.
29
     * This is also the name of the associated model.
30
     * @return string
31
     * @codeCoverageIgnore
32
     */
33
    public function getIndexRoute(): string
34
    {
35
        return 'CategoryEdits';
36
    }
37
38
    /**
39
     * Will redirect back to index if the user has too high of an edit count.
40
     * @return string
41
     */
42
    public function tooHighEditCountRoute(): string
43
    {
44
        return $this->getIndexRoute();
45
    }
46
47
    /**
48
     * Display the search form.
49
     * @Route("/categoryedits", name="CategoryEdits")
50
     * @Route("/categoryedits/{project}", name="CategoryEditsProject")
51
     * @return Response
52
     * @codeCoverageIgnore
53
     */
54
    public function indexAction(): Response
55
    {
56
        // Redirect if at minimum project, username and categories are provided.
57
        if (isset($this->params['project']) && isset($this->params['username']) && isset($this->params['categories'])) {
58
            return $this->redirectToRoute('CategoryEditsResult', $this->params);
59
        }
60
61
        return $this->render('categoryEdits/index.html.twig', array_merge([
62
            'xtPageTitle' => 'tool-categoryedits',
63
            'xtSubtitle' => 'tool-categoryedits-desc',
64
            'xtPage' => 'CategoryEdits',
65
66
            // Defaults that will get overridden if in $params.
67
            'namespace' => 0,
68
            'start' => '',
69
            'end' => '',
70
            'username' => '',
71
            'categories' => '',
72
        ], $this->params, ['project' => $this->project]));
73
    }
74
75
    /**
76
     * Set defaults, and instantiate the CategoryEdits model. This is called at the top of every view action.
77
     * @codeCoverageIgnore
78
     */
79
    private function setupCategoryEdits(CategoryEditsRepository $categoryEditsRepo): void
80
    {
81
        $this->extractCategories();
82
83
        $this->categoryEdits = new CategoryEdits(
84
            $categoryEditsRepo,
85
            $this->project,
86
            $this->user,
0 ignored issues
show
Bug introduced by
It seems like $this->user can also be of type null; however, parameter $user of App\Model\CategoryEdits::__construct() does only seem to accept App\Model\User, 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

86
            /** @scrutinizer ignore-type */ $this->user,
Loading history...
87
            $this->categories,
88
            $this->start,
89
            $this->end,
90
            $this->offset
91
        );
92
93
        $this->output = [
94
            'xtPage' => 'CategoryEdits',
95
            'xtTitle' => $this->user->getUsername(),
0 ignored issues
show
Bug introduced by
The method getUsername() 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

95
            'xtTitle' => $this->user->/** @scrutinizer ignore-call */ getUsername(),

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...
96
            'project' => $this->project,
97
            'user' => $this->user,
98
            'ce' => $this->categoryEdits,
99
            'is_sub_request' => $this->isSubRequest,
100
        ];
101
    }
102
103
    /**
104
     * Go through the categories and normalize values, and set them on class properties.
105
     * @codeCoverageIgnore
106
     */
107
    private function extractCategories(): void
108
    {
109
        // Split categories by pipe.
110
        $categories = explode('|', $this->request->get('categories'));
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('categories') can also be of type null; however, parameter $string of explode() does only seem to accept string, 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

110
        $categories = explode('|', /** @scrutinizer ignore-type */ $this->request->get('categories'));
Loading history...
111
112
        // Loop through the given categories, stripping out the namespace.
113
        // If a namespace was removed, it is flagged it as normalize
114
        // We look for the wiki's category namespace name, and the MediaWiki default
115
        // 'Category:', which sometimes is used cross-wiki (because it still works).
116
        $normalized = false;
117
        $nsName = $this->project->getNamespaces()[14].':';
118
        $this->categories = array_map(function ($category) use ($nsName, &$normalized) {
119
            if (0 === strpos($category, $nsName) || 0 === strpos($category, 'Category:')) {
120
                $normalized = true;
121
            }
122
            return preg_replace('/^'.$nsName.'/', '', $category);
123
        }, $categories);
124
125
        // Redirect if normalized, since we don't want the Category: prefix in the URL.
126
        if ($normalized) {
127
            throw new XtoolsHttpException(
128
                '',
129
                $this->generateUrl($this->request->get('_route'), array_merge(
0 ignored issues
show
Bug introduced by
It seems like $this->request->get('_route') can also be of type null; however, parameter $route of Symfony\Bundle\Framework...ntroller::generateUrl() does only seem to accept string, 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

129
                $this->generateUrl(/** @scrutinizer ignore-type */ $this->request->get('_route'), array_merge(
Loading history...
130
                    $this->request->attributes->get('_route_params'),
131
                    ['categories' => implode('|', $this->categories)]
132
                ))
133
            );
134
        }
135
    }
136
137
    /**
138
     * Display the results.
139
     * @Route(
140
     *     "/categoryedits/{project}/{username}/{categories}/{start}/{end}/{offset}",
141
     *     name="CategoryEditsResult",
142
     *     requirements={
143
     *         "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
144
     *         "categories"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$",
145
     *         "start"="|\d{4}-\d{2}-\d{2}",
146
     *         "end"="|\d{4}-\d{2}-\d{2}",
147
     *         "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}",
148
     *     },
149
     *     defaults={"start"=false, "end"=false, "offset"=false}
150
     * )
151
     * @param CategoryEditsRepository $categoryEditsRepo
152
     * @return Response
153
     * @codeCoverageIgnore
154
     */
155
    public function resultAction(CategoryEditsRepository $categoryEditsRepo): Response
156
    {
157
        $this->setupCategoryEdits($categoryEditsRepo);
158
159
        return $this->getFormattedResponse('categoryEdits/result', $this->output);
160
    }
161
162
    /**
163
     * Get edits by a user to pages in given categories.
164
     * @Route(
165
     *   "/categoryedits-contributions/{project}/{username}/{categories}/{start}/{end}/{offset}",
166
     *   name="CategoryContributionsResult",
167
     *   requirements={
168
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
169
     *       "categories"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2}))?",
170
     *       "start"="|\d{4}-\d{2}-\d{2}",
171
     *       "end"="|\d{4}-\d{2}-\d{2}",
172
     *       "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}",
173
     *   },
174
     *   defaults={"start"=false, "end"=false, "offset"=false}
175
     * )
176
     * @param CategoryEditsRepository $categoryEditsRepo
177
     * @return Response
178
     * @codeCoverageIgnore
179
     */
180
    public function categoryContributionsAction(CategoryEditsRepository $categoryEditsRepo): Response
181
    {
182
        $this->setupCategoryEdits($categoryEditsRepo);
183
184
        return $this->render('categoryEdits/contributions.html.twig', $this->output);
185
    }
186
187
    /************************ API endpoints ************************/
188
189
    /**
190
     * Count the number of category edits the given user has made.
191
     * @Route(
192
     *   "/api/user/category_editcount/{project}/{username}/{categories}/{start}/{end}",
193
     *   name="UserApiCategoryEditCount",
194
     *   requirements={
195
     *       "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)",
196
     *       "categories" = "(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$",
197
     *       "start" = "|\d{4}-\d{2}-\d{2}",
198
     *       "end" = "|\d{4}-\d{2}-\d{2}"
199
     *   },
200
     *   defaults={"start" = false, "end" = false}
201
     * )
202
     * @param CategoryEditsRepository $categoryEditsRepo
203
     * @return JsonResponse
204
     * @codeCoverageIgnore
205
     */
206
    public function categoryEditCountApiAction(CategoryEditsRepository $categoryEditsRepo): JsonResponse
207
    {
208
        $this->recordApiUsage('user/category_editcount');
209
210
        $this->setupCategoryEdits($categoryEditsRepo);
211
212
        $ret = [
213
            'total_editcount' => $this->categoryEdits->getEditCount(),
214
            'category_editcount' => $this->categoryEdits->getCategoryEditCount(),
215
        ];
216
217
        return $this->getFormattedApiResponse($ret);
218
    }
219
}
220