Passed
Push — master ( 9a481f...a0a42c )
by MusikAnimal
05:51
created

AutomatedEditsController   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 85.71%

Importance

Changes 0
Metric Value
dl 0
loc 277
ccs 6
cts 7
cp 0.8571
rs 10
c 0
b 0
f 0
wmc 22

5 Methods

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