Passed
Pull Request — master (#141)
by MusikAnimal
04:34
created

MetaController::recordUsage()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 62
Code Lines 39

Duplication

Lines 22
Ratio 35.48 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 39
nc 8
nop 4
dl 22
loc 62
ccs 0
cts 0
cp 0
crap 56
rs 7.3333
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file contains only the MetaController 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 DateTime;
12
use Symfony\Component\HttpFoundation\Response;
13
use Doctrine\DBAL\Connection;
14
15
/**
16
 * This controller serves everything for the Meta tool.
17
 */
18
class MetaController extends XtoolsController
19
{
20
    /**
21
     * Display the form.
22
     * @Route("/meta", name="meta")
23
     * @Route("/meta", name="Meta")
24
     * @Route("/meta/", name="MetaSlash")
25
     * @Route("/meta/index.php", name="MetaIndexPhp")
26
     * @param Request $request
27
     * @return Response
28
     */
29 1
    public function indexAction(Request $request)
30
    {
31 1
        $params = $this->parseQueryParams($request);
32
33 1
        if (isset($params['start']) && isset($params['end'])) {
34
            return $this->redirectToRoute('MetaResult', $params);
35
        }
36
37 1
        return $this->render('meta/index.html.twig', [
38 1
            'xtPage' => 'meta',
39
            'xtPageTitle' => 'tool-meta',
40
            'xtSubtitle' => 'tool-meta-desc',
41
        ]);
42
    }
43
44
    /**
45
     * Display the results.
46
     * @Route("/meta/{start}/{end}/{legacy}", name="MetaResult")
47
     * @param string $start   Start date
48
     * @param string $end     End date
49
     * @param string $legacy  Non-blank value indicates to show stats for legacy XTools
50
     * @return Response
51
     * @codeCoverageIgnore
52
     */
53
    public function resultAction($start, $end, $legacy = false)
54
    {
55
        $db = $legacy ? 'toolsdb' : 'default';
56
        $table = $legacy ? 's51187__metadata.xtools_timeline' : 'usage_timeline';
57
        $client = $this->container
58
            ->get('doctrine')
59
            ->getManager($db)
60
            ->getConnection();
61
62
        $toolUsage = $this->getToolUsageStats($client, $table, $start, $end);
63
        $apiUsage = $this->getApiUsageStats($client, $start, $end);
64
65
        return $this->render('meta/result.html.twig', [
66
            'xtPage' => 'meta',
67
            'start' => $start,
68
            'end' => $end,
69
            'toolUsage' => $toolUsage,
70
            'apiUsage' => $apiUsage,
71
        ]);
72
    }
73
74
    /**
75
     * Get usage statistics of the core tools.
76
     * @param  Connection $client
77
     * @param  string     $table Table to query.
78
     * @param  string     $start Start date.
79
     * @param  string     $end End date.
80
     * @return array
81
     */
82 View Code Duplication
    private function getToolUsageStats(Connection $client, $table, $start, $end)
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...
83
    {
84
        $query = $client->prepare("SELECT * FROM $table
85
                                   WHERE date >= :start AND date <= :end");
86
        $query->bindParam('start', $start);
87
        $query->bindParam('end', $end);
88
        $query->execute();
89
90
        $data = $query->fetchAll();
91
92
        // Create array of totals, along with formatted timeline data as needed by Chart.js
93
        $totals = [];
94
        $dateLabels = [];
95
        $timeline = [];
96
        $startObj = new DateTime($start);
97
        $endObj = new DateTime($end);
98
        $numDays = (int) $endObj->diff($startObj)->format("%a");
99
        $grandSum = 0;
100
101
        // Generate array of date labels
102
        for ($dateObj = new DateTime($start); $dateObj <= $endObj; $dateObj->modify('+1 day')) {
103
            $dateLabels[] = $dateObj->format('Y-m-d');
104
        }
105
106
        foreach ($data as $entry) {
107
            if (!isset($totals[$entry['tool']])) {
108
                $totals[$entry['tool']] = (int) $entry['count'];
109
110
                // Create arrays for each tool, filled with zeros for each date in the timeline
111
                $timeline[$entry['tool']] = array_fill(0, $numDays, 0);
112
            } else {
113
                $totals[$entry['tool']] += (int) $entry['count'];
114
            }
115
116
            $date = new DateTime($entry['date']);
117
            $dateIndex = (int) $date->diff($startObj)->format("%a");
118
            $timeline[$entry['tool']][$dateIndex] = (int) $entry['count'];
119
120
            $grandSum += $entry['count'];
121
        }
122
        arsort($totals);
123
124
        return [
125
            'totals' => $totals,
126
            'grandSum' => $grandSum,
127
            'dateLabels' => $dateLabels,
128
            'timeline' => $timeline,
129
        ];
130
    }
131
132
    /**
133
     * Get usage statistics of the API.
134
     * @param  Connection $client
135
     * @param  string     $start Start date.
136
     * @param  string     $end End date.
137
     * @return array
138
     */
139 View Code Duplication
    private function getApiUsageStats(Connection $client, $start, $end)
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...
140
    {
141
        $query = $client->prepare("SELECT * FROM usage_api_timeline
142
                                   WHERE date >= :start AND date <= :end");
143
        $query->bindParam('start', $start);
144
        $query->bindParam('end', $end);
145
        $query->execute();
146
147
        $data = $query->fetchAll();
148
149
        // Create array of totals, along with formatted timeline data as needed by Chart.js
150
        $totals = [];
151
        $dateLabels = [];
152
        $timeline = [];
153
        $startObj = new DateTime($start);
154
        $endObj = new DateTime($end);
155
        $numDays = (int) $endObj->diff($startObj)->format("%a");
156
        $grandSum = 0;
157
158
        // Generate array of date labels
159
        for ($dateObj = new DateTime($start); $dateObj <= $endObj; $dateObj->modify('+1 day')) {
160
            $dateLabels[] = $dateObj->format('Y-m-d');
161
        }
162
163
        foreach ($data as $entry) {
164
            if (!isset($totals[$entry['endpoint']])) {
165
                $totals[$entry['endpoint']] = (int) $entry['count'];
166
167
                // Create arrays for each endpoint, filled with zeros for each date in the timeline
168
                $timeline[$entry['endpoint']] = array_fill(0, $numDays, 0);
169
            } else {
170
                $totals[$entry['endpoint']] += (int) $entry['count'];
171
            }
172
173
            $date = new DateTime($entry['date']);
174
            $dateIndex = (int) $date->diff($startObj)->format("%a");
175
            $timeline[$entry['endpoint']][$dateIndex] = (int) $entry['count'];
176
177
            $grandSum += $entry['count'];
178
        }
179
        arsort($totals);
180
181
        return [
182
            'totals' => $totals,
183
            'grandSum' => $grandSum,
184
            'dateLabels' => $dateLabels,
185
            'timeline' => $timeline,
186
        ];
187
    }
188
189
    /**
190
     * Record usage of a particular XTools tool. This is called automatically
191
     *   in base.html.twig via JavaScript so that it is done asynchronously
192
     * @Route("/meta/usage/{tool}/{project}/{token}")
193
     * @param  Request $request
194
     * @param  string $tool    Internal name of tool
195
     * @param  string $project Project domain such as en.wikipedia.org
196
     * @param  string $token   Unique token for this request, so we don't have people
197
     *                         meddling with these statistics
198
     * @return Response
199
     * @codeCoverageIgnore
200
     */
201
    public function recordUsage(Request $request, $tool, $project, $token)
202
    {
203
        // Validate method and token.
204
        if ($request->getMethod() !== 'PUT' || !$this->isCsrfTokenValid('intention', $token)) {
205
            throw $this->createAccessDeniedException('This endpoint is for internal use only.');
206
        }
207
208
        // Ready the response object.
209
        $response = new Response();
210
        $response->headers->set('Content-Type', 'application/json');
211
212
        // Don't update counts for tools that aren't enabled
213
        if (!$this->container->getParameter("enable.$tool")) {
214
            $response->setStatusCode(Response::HTTP_FORBIDDEN);
215
            $response->setContent(json_encode([
216
                'error' => 'This tool is disabled'
217
            ]));
218
            return $response;
219
        }
220
221
        $conn = $this->container->get('doctrine')->getManager('default')->getConnection();
222
        $date =  date('Y-m-d');
223
224
        // Increment count in timeline
225
        $existsSql = "SELECT 1 FROM usage_timeline
226
                      WHERE date = '$date'
227
                      AND tool = '$tool'";
228
229 View Code Duplication
        if (count($conn->query($existsSql)->fetchAll()) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
230
            $createSql = "INSERT INTO usage_timeline
231
                          VALUES(NULL, '$date', '$tool', 1)";
232
            $conn->query($createSql);
233
        } else {
234
            $updateSql = "UPDATE usage_timeline
235
                          SET count = count + 1
236
                          WHERE tool = '$tool'
237
                          AND date = '$date'";
238
            $conn->query($updateSql);
239
        }
240
241
        // Update per-project usage, if applicable
242
        if (!$this->container->getParameter('app.single_wiki')) {
243
            $existsSql = "SELECT 1 FROM usage_projects
244
                          WHERE tool = '$tool'
245
                          AND project = '$project'";
246
247 View Code Duplication
            if (count($conn->query($existsSql)->fetchAll()) === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
248
                $createSql = "INSERT INTO usage_projects
249
                              VALUES(NULL, '$tool', '$project', 1)";
250
                $conn->query($createSql);
251
            } else {
252
                $updateSql = "UPDATE usage_projects
253
                              SET count = count + 1
254
                              WHERE tool = '$tool'
255
                              AND project = '$project'";
256
                $conn->query($updateSql);
257
            }
258
        }
259
260
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
261
        $response->setContent(json_encode([]));
262
        return $response;
263
    }
264
}
265