Passed
Push — master ( 35c320...7deb12 )
by MusikAnimal
04:42
created

CategoryEditsController::getToolShortname()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the CategoryEditsController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use AppBundle\Helper\AutomatedEditsHelper;
9
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
10
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
11
use Symfony\Component\HttpFoundation\JsonResponse;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\HttpFoundation\Response;
15
use Xtools\CategoryEdits;
16
use Xtools\CategoryEditsRepository;
17
use Xtools\Edit;
18
use Xtools\Project;
19
use Xtools\ProjectRepository;
20
use Xtools\User;
21
use Xtools\UserRepository;
22
23
/**
24
 * This controller serves the Category Edits tool.
25
 */
26
class CategoryEditsController extends XtoolsController
27
{
28
    /** @var CategoryEdits The CategoryEdits instance. */
29
    protected $categoryEdits;
30
31
    /** @var Project The project. */
32
    protected $project;
33
34
    /** @var User The user. */
35
    protected $user;
36
37
    /** @var string[] The categories, with or without namespace. */
38
    protected $categories;
39
40
    /** @var string The start date. */
41
    protected $start = '';
42
43
    /** @var string The end date. */
44
    protected $end = '';
45
46
    /** @var string The OFFSET of contributions list. */
47
    protected $offset;
48
49
    /** @var int|string The namespace ID or 'all' for all namespaces. */
50
    protected $namespace;
51
52
    /** @var bool Whether or not this is a subrequest. */
53
    protected $isSubRequest;
54
55
    /** @var array Data that is passed to the view. */
56
    private $output;
57
58
    /**
59
     * Get the name of the tool's index route.
60
     * This is also the name of the associated model.
61
     * @return string
62
     * @codeCoverageIgnore
63
     */
64
    public function getIndexRoute()
65
    {
66
        return 'CategoryEdits';
67
    }
68
69
    /**
70
     * Display the search form.
71
     * @Route("/categoryedits", name="CategoryEdits")
72
     * @Route("/categoryedits/", name="CategoryEditsSlash")
73
     * @Route("/catedits", name="CategoryEditsShort")
74
     * @Route("/catedits/", name="CategoryEditsShortSlash")
75
     * @param Request $request The HTTP request.
76
     * @return Response
77
     * @codeCoverageIgnore
78
     */
79
    public function indexAction(Request $request)
80
    {
81
        $params = $this->parseQueryParams($request);
82
83
        // Redirect if at minimum project, username and categories are provided.
84
        if (isset($params['project']) && isset($params['username']) && isset($params['categories'])) {
85
            return $this->redirectToRoute('CategoryEditsResult', $params);
86
        }
87
88
        // Convert the given project (or default project) into a Project instance.
89
        $params['project'] = $this->getProjectFromQuery($params);
90
91
        return $this->render('categoryEdits/index.html.twig', array_merge([
92
            'xtPageTitle' => 'tool-categoryedits',
93
            'xtSubtitle' => 'tool-categoryedits-desc',
94
            'xtPage' => 'categoryedits',
95
96
            // Defaults that will get overridden if in $params.
97
            'namespace' => 0,
98
            'start' => '',
99
            'end' => '',
100
        ], $params));
101
    }
102
103
    /**
104
     * Set defaults, and instantiate the CategoryEdits model. This is called at
105
     * the top of every view action.
106
     * @param Request $request The HTTP request.
107
     * @codeCoverageIgnore
108
     */
109
    private function setupCategoryEdits(Request $request)
110
    {
111
        // Will redirect back to index if the user has too high of an edit count.
112
        $ret = $this->validateProjectAndUser($request, 'categoryedits');
113
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
$ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
114
            return $ret;
115
        } else {
116
            list($this->project, $this->user) = $ret;
117
        }
118
119
        // Normalize all parameters and set class properties.
120
        // A redirect is returned if we want the normalized values in the URL.
121
        if ($this->normalizeAndSetParams($request) instanceof RedirectResponse) {
122
            return $ret;
123
        }
124
125
        $this->categoryEdits = new CategoryEdits(
126
            $this->project,
127
            $this->user,
128
            $this->categories,
129
            $this->start,
130
            $this->end,
131
            isset($this->offset) ? $this->offset : 0
132
        );
133
        $categoryEditsRepo = new CategoryEditsRepository();
134
        $categoryEditsRepo->setContainer($this->container);
135
        $this->categoryEdits->setRepository($categoryEditsRepo);
136
137
        $this->isSubRequest = $request->get('htmlonly')
138
            || $this->get('request_stack')->getParentRequest() !== null;
139
140
        $this->output = [
141
            'xtPage' => 'categoryedits',
142
            'xtTitle' => $this->user->getUsername(),
143
            'project' => $this->project,
144
            'user' => $this->user,
145
            'ce' => $this->categoryEdits,
146
            'is_sub_request' => $this->isSubRequest,
147
        ];
148
    }
149
150
    /**
151
     * Go through the categories, start, end, and offset parameters
152
     * and normalize values, and set them on class properties.
153
     * @param  Request $request
154
     * @return null|RedirectResponse Redirect if categoires were normalized.
155
     * @codeCoverageIgnore
156
     */
157
    private function normalizeAndSetParams(Request $request)
158
    {
159
        $categories = $request->get('categories');
160
161
        // Defaults
162
        $start = '';
163
        $end = '';
164
165
        // Some categories contain slashes, so we'll first make the differentiation
166
        // and extract out the start, end and offset params, even if they are empty.
167
        if (1 === preg_match(
168
            '/(.+?)\/(|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?$/',
169
            $categories,
170
            $matches
171
        )) {
172
            $categories = $matches[1];
173
            $start = $matches[2];
174
            $end = isset($matches[3]) ? $matches[3] : null;
175
            $this->offset = isset($matches[4]) ? $matches[4] : 0;
176
        }
177
178
        // Split cateogries by pipe.
179
        $categories = explode('|', $categories);
180
181
        // Loop through the given categories, stripping out the namespace.
182
        // If a namespace was removed, it is flagged it as normalize
183
        // We look for the wiki's category namespace name, and the MediaWiki default
184
        // 'Category:', which sometimes is used cross-wiki (because it still works).
185
        $normalized = false;
186
        $nsName = $this->project->getNamespaces()[14].':';
187
        $this->categories = array_map(function ($category) use ($nsName, &$normalized) {
188
            if (strpos($category, $nsName) === 0 || strpos($category, 'Category:') === 0) {
189
                $normalized = true;
190
            }
191
            return ltrim(ltrim($category, $nsName.':'), 'Category:');
192
        }, $categories);
193
194
        // Redirect if normalized, since we don't want the Category: prefix in the URL.
195
        if ($normalized) {
196
            return $this->redirectToRoute($request->get('_route'), array_merge(
197
                $request->attributes->get('_route_params'),
198
                ['categories' => implode('|', $this->categories)]
199
            ));
200
        }
201
202
        // 'false' means the dates are optional and returned as 'false' if empty.
203
        list($this->start, $this->end) = $this->getUTCFromDateParams($start, $end, false);
204
205
        // Format dates as needed by User model, if the date is present.
206
        if ($this->start !== false) {
207
            $this->start = date('Y-m-d', $this->start);
208
        }
209
        if ($this->end !== false) {
210
            $this->end = date('Y-m-d', $this->end);
211
        }
212
    }
213
214
    /**
215
     * Display the results.
216
     * @Route(
217
     *     "/categoryedits/{project}/{username}/{categories}/{start}/{end}",
218
     *     name="CategoryEditsResult",
219
     *     requirements={
220
     *         "categories" = ".+",
221
     *         "start" = "|\d{4}-\d{2}-\d{2}",
222
     *         "end" = "|\d{4}-\d{2}-\d{2}"
223
     *     },
224
     *     defaults={"start" = "", "end" = ""}
225
     * )
226
     * @param Request $request The HTTP request.
227
     * @return RedirectResponse|Response
228
     * @codeCoverageIgnore
229
     */
230
    public function resultAction(Request $request)
231
    {
232
        // Will redirect back to index if the user has too high of an edit count.
233
        $ret = $this->setupCategoryEdits($request);
234
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
$ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
235
            return $ret;
236
        }
237
238
        // Render the view with all variables set.
239
        return $this->render('categoryEdits/result.html.twig', $this->output);
240
    }
241
242
    /**
243
     * Get edits my by a user to pages in given categories.
244
     * @Route(
245
     *   "/categoryedits-contributions/{project}/{username}/{categories}/{start}/{end}/{offset}",
246
     *   name="CategoryContributionsResult",
247
     *   requirements={
248
     *       "categories" = ".+",
249
     *       "start" = "|\d{4}-\d{2}-\d{2}",
250
     *       "end" = "|\d{4}-\d{2}-\d{2}",
251
     *       "offset" = "|\d+"
252
     *   },
253
     *   defaults={"start" = "", "end" = "", "offset" = ""}
254
     * )
255
     * @param Request $request The HTTP request.
256
     * @return Response|RedirectResponse
257
     * @codeCoverageIgnore
258
     */
259
    public function categoryContributionsAction(Request $request)
260
    {
261
        // Will redirect back to index if the user has too high of an edit count.
262
        $ret = $this->setupCategoryEdits($request);
263
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
$ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
264
            return $ret;
265
        }
266
267
        return $this->render('categoryEdits/contributions.html.twig', $this->output);
268
    }
269
270
    /************************ API endpoints ************************/
271
272
    /**
273
     * Count the number of category edits the given user has made.
274
     * @Route(
275
     *   "/api/user/category_editcount/{project}/{username}/{categories}/{start}/{end}",
276
     *   name="UserApiCategoryEditCount",
277
     *   requirements={
278
     *       "categories" = ".+",
279
     *       "start" = "|\d{4}-\d{2}-\d{2}",
280
     *       "end" = "|\d{4}-\d{2}-\d{2}"
281
     *   },
282
     *   defaults={"start" = "", "end" = ""}
283
     * )
284
     * @param Request $request The HTTP request.
285
     * @return Response
286
     * @codeCoverageIgnore
287
     */
288
    public function categoryEditCountApiAction(Request $request)
289
    {
290
        $this->recordApiUsage('user/category_editcount');
291
292
        $ret = $this->setupCategoryEdits($request);
293
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
$ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
294
            // FIXME: Refactor JSON errors/responses, use Intuition as a service.
295
            return new JsonResponse(
296
                [
297
                    'error' => $this->getFlashMessage(),
298
                ],
299
                Response::HTTP_NOT_FOUND
300
            );
301
        }
302
303
        $res = $this->getJsonData();
304
        $res['total_editcount'] = $this->categoryEdits->getEditCount();
305
306
        $response = new JsonResponse();
307
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
308
309
        $res['category_editcount'] = $this->categoryEdits->getCategoryEditCount();
310
311
        $response->setData($res);
312
        return $response;
313
    }
314
315
    /**
316
     * Get data that will be used in API responses.
317
     * @return array
318
     * @codeCoverageIgnore
319
     */
320
    private function getJsonData()
321
    {
322
        $ret = [
323
            'project' => $this->project->getDomain(),
324
            'username' => $this->user->getUsername(),
325
        ];
326
327
        foreach (['categories', 'start', 'end', 'offset'] as $param) {
328
            if (isset($this->{$param}) && $this->{$param} != '') {
329
                $ret[$param] = $this->{$param};
330
            }
331
        }
332
333
        return $ret;
334
    }
335
}
336