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

MetaController::recordUsage()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 66
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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