Passed
Push — master ( 48da0f...5f527e )
by MusikAnimal
04:29
created

AutomatedEditsController   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 281
Duplicated Lines 8.19 %

Test Coverage

Coverage 87.5%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 23
loc 281
rs 10
c 1
b 1
f 0
ccs 7
cts 8
cp 0.875
wmc 22

5 Methods

Rating   Name   Duplication   Size   Complexity  
B automatedEditCountApiAction() 0 38 3
C nonAutomatedEditsApiAction() 0 75 9
B resultAction() 0 64 6
A getToolShortname() 0 3 1
A indexAction() 22 22 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 View Code Duplication
    public function indexAction(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
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);
0 ignored issues
show
Bug introduced by
$start of type string is incompatible with the type integer expected by parameter $timestamp of date(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
            $start = date('Y-m-d', /** @scrutinizer ignore-type */ $start);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type false; however, parameter $start of Xtools\AutoEdits::__construct() 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

119
        $autoEdits = new AutoEdits($project, $user, $namespace, /** @scrutinizer ignore-type */ $start, $end);
Loading history...
Bug introduced by
It seems like $end can also be of type false; however, parameter $end of Xtools\AutoEdits::__construct() 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

119
        $autoEdits = new AutoEdits($project, $user, $namespace, $start, /** @scrutinizer ignore-type */ $end);
Loading history...
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
     * Count the number of automated edits the given user has made.
160
     * @Route(
161
     *   "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}",
162
     *   requirements={"start" = "|\d{4}-\d{2}-\d{2}", "end" = "|\d{4}-\d{2}-\d{2}"}
163
     * )
164
     * @param Request $request The HTTP request.
165
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
166
     * @param string $start In the format YYYY-MM-DD
167
     * @param string $end In the format YYYY-MM-DD
168
     * @param string $tools Non-blank to show which tools were used and how many times.
169
     * @return Response
170
     * @codeCoverageIgnore
171
     */
172
    public function automatedEditCountApiAction(
173
        Request $request,
174
        $namespace = 'all',
175
        $start = '',
176
        $end = '',
177
        $tools = ''
178
    ) {
179
        list($project, $user) = $this->validateProjectAndUser($request);
180
181
        $res = [
182
            'project' => $project->getDomain(),
183
            'username' => $user->getUsername(),
184
            'total_editcount' => $user->countEdits($project, $namespace, $start, $end),
185
        ];
186
187
        $autoEdits = new AutoEdits($project, $user, $namespace, $start, $end);
188
        $autoEditsRepo = new AutoEditsRepository();
189
        $autoEditsRepo->setContainer($this->container);
190
        $autoEdits->setRepository($autoEditsRepo);
191
192
        $response = new JsonResponse();
193
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
194
195
        if ($tools != '') {
196
            $tools = $autoEdits->getAutomatedCounts();
197
            $res['automated_editcount'] = 0;
198
            foreach ($tools as $tool) {
199
                $res['automated_editcount'] += $tool['count'];
200
            }
201
            $res['automated_tools'] = $tools;
202
        } else {
203
            $res['automated_editcount'] = $autoEdits->countAutomatedEdits();
204
        }
205
206
        $res['nonautomated_editcount'] = $res['total_editcount'] - $res['automated_editcount'];
207
208
        $response->setData($res);
209
        return $response;
210
    }
211
212
    /**
213
     * Get non-automated edits for the given user.
214
     * @Route(
215
     *   "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}",
216
     *   requirements={
217
     *       "start" = "|\d{4}-\d{2}-\d{2}",
218
     *       "end" = "|\d{4}-\d{2}-\d{2}",
219
     *       "offset" = "\d*"
220
     *   }
221
     * )
222
     * @param Request $request The HTTP request.
223
     * @param int|string $namespace ID of the namespace, or 'all' for all namespaces
224
     * @param string $start In the format YYYY-MM-DD
225
     * @param string $end In the format YYYY-MM-DD
226
     * @param int $offset For pagination, offset results by N edits
227
     * @return Response
228
     * @codeCoverageIgnore
229
     */
230
    public function nonAutomatedEditsApiAction(
231
        Request $request,
232
        $namespace = 0,
233
        $start = '',
234
        $end = '',
235
        $offset = 0
236
    ) {
237
        // Second parameter causes it return a Redirect to the index if the user has too many edits.
238
        // We only want to do this when looking at the user's overall edits, not just to a specific article.
239
        list($project, $user) = $this->validateProjectAndUser($request);
240
241
        // Reject if they've made too many edits.
242
        if ($user->hasTooManyEdits($project)) {
243
            if ($request->query->get('format') !== 'html') {
244
                return new JsonResponse(
245
                    [
246
                        'error' => 'Unable to show any data. User has made over ' .
247
                            $user->maxEdits() . ' edits.',
248
                    ],
249
                    Response::HTTP_FORBIDDEN
250
                );
251
            }
252
253
            $edits = [];
254
        } else {
255
            $autoEdits = new AutoEdits($project, $user, $namespace, $start, $end);
256
            $autoEditsRepo = new AutoEditsRepository();
257
            $autoEditsRepo->setContainer($this->container);
258
            $autoEdits->setRepository($autoEditsRepo);
259
260
            $edits = $autoEdits->getNonautomatedEdits($offset);
261
        }
262
263
        if ($request->query->get('format') === 'html') {
264
            if ($edits) {
265
                $edits = array_map(function ($attrs) use ($project, $user) {
266
                    $page = $project->getRepository()
267
                        ->getPage($project, $attrs['full_page_title']);
268
                    $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...
269
                    $attrs['id'] = $attrs['rev_id'];
270
                    $attrs['username'] = $user->getUsername();
271
                    return new Edit($page, $attrs);
272
                }, $edits);
273
            }
274
275
            $response = $this->render('autoEdits/nonautomated_edits.html.twig', [
276
                'edits' => $edits,
277
                'project' => $project,
278
                'maxEdits' => $user->maxEdits(),
279
            ]);
280
            $response->headers->set('Content-Type', 'text/html');
281
            return $response;
282
        }
283
284
        $ret = [
285
            'project' => $project->getDomain(),
286
            'username' => $user->getUsername(),
287
        ];
288
        if ($namespace != '' && $namespace !== 'all') {
289
            $ret['namespace'] = $namespace;
290
        }
291
        if ($start != '') {
292
            $ret['start'] = $start;
293
        }
294
        if ($end != '') {
295
            $ret['end'] = $end;
296
        }
297
        $ret['offset'] = $offset;
298
        $ret['nonautomated_edits'] = $edits;
299
300
        $response = new JsonResponse();
301
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
302
303
        $response->setData($ret);
304
        return $response;
305
    }
306
}
307