Passed
Push — master ( 44236a...cf19b6 )
by MusikAnimal
04:38
created

AutomatedEditsController   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
dl 0
loc 300
rs 10
c 0
b 0
f 0
ccs 7
cts 8
cp 0.875
wmc 23

6 Methods

Rating   Name   Duplication   Size   Complexity  
B resultAction() 0 64 6
A getToolShortname() 0 3 1
A indexAction() 0 22 3
A automatedToolsApiAction() 0 6 1
B automatedEditCountApiAction() 0 40 3
C nonAutomatedEditsApiAction() 0 77 9
1
<?php
2
/**
3
 * This file contains only the AutomatedEditsController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Xtools\AutoEdits;
15
use Xtools\AutoEditsRepository;
16
use Xtools\ProjectRepository;
17
use Xtools\User;
18
use Xtools\UserRepository;
19
use Xtools\Edit;
20
21
/**
22
 * This controller serves the AutomatedEdits tool.
23
 */
24
class AutomatedEditsController extends XtoolsController
25
{
26
27
    /**
28
     * Get the tool's shortname.
29
     * @return string
30
     * @codeCoverageIgnore
31
     */
32
    public function getToolShortname()
33
    {
34
        return 'autoedits';
35
    }
36
37
    /**
38
     * Display the search form.
39
     * @Route("/autoedits", name="autoedits")
40
     * @Route("/autoedits/", name="autoeditsSlash")
41
     * @Route("/automatededits", name="autoeditsLong")
42
     * @Route("/automatededits/", name="autoeditsLongSlash")
43
     * @Route("/autoedits/index.php", name="autoeditsIndexPhp")
44
     * @Route("/automatededits/index.php", name="autoeditsLongIndexPhp")
45
     * @Route("/autoedits/{project}", name="autoeditsProject")
46
     * @param Request $request The HTTP request.
47
     * @return Response
48
     */
49 1
    public function indexAction(Request $request)
50
    {
51 1
        $params = $this->parseQueryParams($request);
52
53
        // Redirect if at minimum project and username are provided.
54 1
        if (isset($params['project']) && isset($params['username'])) {
55
            return $this->redirectToRoute('autoeditsResult', $params);
56
        }
57
58
        // Convert the given project (or default project) into a Project instance.
59 1
        $params['project'] = $this->getProjectFromQuery($params);
60
61 1
        return $this->render('autoEdits/index.html.twig', array_merge([
62 1
            'xtPageTitle' => 'tool-autoedits',
63
            'xtSubtitle' => 'tool-autoedits-desc',
64
            'xtPage' => 'autoedits',
65
66
            // Defaults that will get overriden if in $params.
67
            'namespace' => 0,
68
            'start' => '',
69
            'end' => '',
70 1
        ], $params));
71
    }
72
73
    /**
74
     * Display the results.
75
     * @Route(
76
     *     "/autoedits/{project}/{username}/{namespace}/{start}/{end}", name="autoeditsResult",
77
     *     requirements={
78
     *         "start" = "|\d{4}-\d{2}-\d{2}",
79
     *         "end" = "|\d{4}-\d{2}-\d{2}",
80
     *         "namespace" = "|all|\d+"
81
     *     }
82
     * )
83
     * @param Request $request The HTTP request.
84
     * @param int|string $namespace
85
     * @param null|string $start
86
     * @param null|string $end
87
     * @return RedirectResponse|Response
88
     * @codeCoverageIgnore
89
     */
90
    public function resultAction(Request $request, $namespace = 0, $start = null, $end = null)
91
    {
92
        // Will redirect back to index if the user has too high of an edit count.
93
        $ret = $this->validateProjectAndUser($request, 'autoedits');
94
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
introduced by
The condition $ret instanceof Symfony\...dation\RedirectResponse can never be false since $ret is always a sub-type of Symfony\Component\HttpFoundation\RedirectResponse.
Loading history...
95
            return $ret;
96
        } else {
97
            list($project, $user) = $ret;
98
        }
99
100
        // 'false' means the dates are optional and returned as 'false' if empty.
101
        list($start, $end) = $this->getUTCFromDateParams($start, $end, false);
102
103
        // We'll want to conditionally show some things in the view if there is a start date.
104
        $hasStartDate = $start > 0;
105
106
        // Format dates as needed by User model, if the date is present.
107
        if ($start !== false) {
108
            $start = date('Y-m-d', $start);
109
        }
110
        if ($end !== false) {
111
            $end = date('Y-m-d', $end);
112
        }
113
114
        // Normalize default namespace.
115
        if ($namespace == '') {
116
            $namespace = 0;
117
        }
118
119
        $autoEdits = new AutoEdits($project, $user, $namespace, $start, $end);
120
        $autoEditsRepo = new AutoEditsRepository();
121
        $autoEditsRepo->setContainer($this->container);
122
        $autoEdits->setRepository($autoEditsRepo);
123
124
        $editCount = $user->countEdits($project, $namespace, $start, $end);
125
126
        // Get individual counts of how many times each tool was used.
127
        // This also includes a wikilink to the tool.
128
        $toolCounts = $autoEdits->getAutomatedCounts();
129
        $toolsTotal = array_reduce($toolCounts, function ($a, $b) {
130
            return $a + $b['count'];
131
        });
132
133
        // Query to get combined (semi)automated using for all edits
134
        //   as some automated edits overlap.
135
        $autoCount = $autoEdits->countAutomatedEdits();
136
137
        $ret = [
138
            'xtPage' => 'autoedits',
139
            'user' => $user,
140
            'project' => $project,
141
            'toolCounts' => $toolCounts,
142
            'toolsTotal' => $toolsTotal,
143
            'autoCount' => $autoCount,
144
            'editCount' => $editCount,
145
            'autoPct' => $editCount ? ($autoCount / $editCount) * 100 : 0,
146
            'hasStartDate' => $hasStartDate,
147
            'start' => $start,
148
            'end' => $end,
149
            'namespace' => $namespace,
150
        ];
151
152
        // Render the view with all variables set.
153
        return $this->render('autoEdits/result.html.twig', $ret);
154
    }
155
156
    /************************ API endpoints ************************/
157
158
    /**
159
     * Get a list of the automated tools and their regex/tags/etc.
160
     * @Route("/api/user/automated_tools/{project}")
161
     * @param string $project The project domain or database name.
162
     * @return JsonResponse
163
     * @codeCoverageIgnore
164
     */
165
    public function automatedToolsApiAction($project)
166
    {
167
        $this->recordApiUsage('user/automated_tools');
168
        $project = $this->validateProject($project);
169
        $aeh = $this->container->get('app.automated_edits_helper');
170
        return new JsonResponse($aeh->getTools($project));
171
    }
172
173
    /**
174
     * Count the number of automated edits the given user has made.
175
     * @Route(
176
     *   "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}",
177
     *   requirements={"start" = "|\d{4}-\d{2}-\d{2}", "end" = "|\d{4}-\d{2}-\d{2}"}
178
     * )
179
     * @param Request $request The HTTP request.
180
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
181
     * @param string $start In the format YYYY-MM-DD
182
     * @param string $end In the format YYYY-MM-DD
183
     * @param string $tools Non-blank to show which tools were used and how many times.
184
     * @return Response
185
     * @codeCoverageIgnore
186
     */
187
    public function automatedEditCountApiAction(
188
        Request $request,
189
        $namespace = 'all',
190
        $start = '',
191
        $end = '',
192
        $tools = ''
193
    ) {
194
        $this->recordApiUsage('user/automated_editcount');
195
196
        list($project, $user) = $this->validateProjectAndUser($request);
197
198
        $res = [
199
            'project' => $project->getDomain(),
200
            'username' => $user->getUsername(),
201
            'total_editcount' => $user->countEdits($project, $namespace, $start, $end),
202
        ];
203
204
        $autoEdits = new AutoEdits($project, $user, $namespace, $start, $end);
205
        $autoEditsRepo = new AutoEditsRepository();
206
        $autoEditsRepo->setContainer($this->container);
207
        $autoEdits->setRepository($autoEditsRepo);
208
209
        $response = new JsonResponse();
210
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
211
212
        if ($tools != '') {
213
            $tools = $autoEdits->getAutomatedCounts();
214
            $res['automated_editcount'] = 0;
215
            foreach ($tools as $tool) {
216
                $res['automated_editcount'] += $tool['count'];
217
            }
218
            $res['automated_tools'] = $tools;
219
        } else {
220
            $res['automated_editcount'] = $autoEdits->countAutomatedEdits();
221
        }
222
223
        $res['nonautomated_editcount'] = $res['total_editcount'] - $res['automated_editcount'];
224
225
        $response->setData($res);
226
        return $response;
227
    }
228
229
    /**
230
     * Get non-automated edits for the given user.
231
     * @Route(
232
     *   "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
233
     *   requirements={
234
     *       "start" = "|\d{4}-\d{2}-\d{2}",
235
     *       "end" = "|\d{4}-\d{2}-\d{2}",
236
     *       "offset" = "\d*"
237
     *   }
238
     * )
239
     * @param Request $request The HTTP request.
240
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
241
     * @param string $start In the format YYYY-MM-DD
242
     * @param string $end In the format YYYY-MM-DD
243
     * @param int $offset For pagination, offset results by N edits
244
     * @return Response
245
     * @codeCoverageIgnore
246
     */
247
    public function nonAutomatedEditsApiAction(
248
        Request $request,
249
        $namespace = 0,
250
        $start = '',
251
        $end = '',
252
        $offset = 0
253
    ) {
254
        $this->recordApiUsage('user/nonautomated_edits');
255
256
        // Second parameter causes it return a Redirect to the index if the user has too many edits.
257
        // We only want to do this when looking at the user's overall edits, not just to a specific article.
258
        list($project, $user) = $this->validateProjectAndUser($request);
259
260
        // Reject if they've made too many edits.
261
        if ($user->hasTooManyEdits($project)) {
262
            if ($request->query->get('format') !== 'html') {
263
                return new JsonResponse(
264
                    [
265
                        'error' => 'Unable to show any data. User has made over ' .
266
                            $user->maxEdits() . ' edits.',
267
                    ],
268
                    Response::HTTP_FORBIDDEN
269
                );
270
            }
271
272
            $edits = [];
273
        } else {
274
            $autoEdits = new AutoEdits($project, $user, $namespace, $start, $end);
275
            $autoEditsRepo = new AutoEditsRepository();
276
            $autoEditsRepo->setContainer($this->container);
277
            $autoEdits->setRepository($autoEditsRepo);
278
279
            $edits = $autoEdits->getNonautomatedEdits($offset);
280
        }
281
282
        if ($request->query->get('format') === 'html') {
283
            if ($edits) {
284
                $edits = array_map(function ($attrs) use ($project, $user) {
285
                    $page = $project->getRepository()
286
                        ->getPage($project, $attrs['full_page_title']);
287
                    $pageTitles[] = $attrs['full_page_title'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$pageTitles was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pageTitles = array(); before regardless.
Loading history...
288
                    $attrs['id'] = $attrs['rev_id'];
289
                    $attrs['username'] = $user->getUsername();
290
                    return new Edit($page, $attrs);
291
                }, $edits);
292
            }
293
294
            $response = $this->render('autoEdits/nonautomated_edits.html.twig', [
295
                'edits' => $edits,
296
                'project' => $project,
297
                'maxEdits' => $user->maxEdits(),
298
            ]);
299
            $response->headers->set('Content-Type', 'text/html');
300
            return $response;
301
        }
302
303
        $ret = [
304
            'project' => $project->getDomain(),
305
            'username' => $user->getUsername(),
306
        ];
307
        if ($namespace != '' && $namespace !== 'all') {
308
            $ret['namespace'] = $namespace;
309
        }
310
        if ($start != '') {
311
            $ret['start'] = $start;
312
        }
313
        if ($end != '') {
314
            $ret['end'] = $end;
315
        }
316
        $ret['offset'] = $offset;
317
        $ret['nonautomated_edits'] = $edits;
318
319
        $response = new JsonResponse();
320
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
321
322
        $response->setData($ret);
323
        return $response;
324
    }
325
}
326