Passed
Push — master ( 9c80d9...628b52 )
by MusikAnimal
05:31
created

CategoryEditsController::indexAction()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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