Passed
Push — master ( bb3b01...ed8c75 )
by MusikAnimal
06:02
created

MetaController::getApiUsageStats()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 47
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 30
nc 6
nop 3
dl 0
loc 47
ccs 0
cts 0
cp 0
crap 20
rs 8.6845
c 0
b 0
f 0
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
     * @codeCoverageIgnore
82
     */
83
    private function getToolUsageStats(Connection $client, $table, $start, $end)
84
    {
85
        $query = $client->prepare("SELECT * FROM $table
86
                                   WHERE date >= :start AND date <= :end");
87
        $query->bindParam('start', $start);
88
        $query->bindParam('end', $end);
89
        $query->execute();
90
91
        $data = $query->fetchAll();
92
93
        // Create array of totals, along with formatted timeline data as needed by Chart.js
94
        $totals = [];
95
        $dateLabels = [];
96
        $timeline = [];
97
        $startObj = new DateTime($start);
98
        $endObj = new DateTime($end);
99
        $numDays = (int) $endObj->diff($startObj)->format("%a");
100
        $grandSum = 0;
101
102
        // Generate array of date labels
103
        for ($dateObj = new DateTime($start); $dateObj <= $endObj; $dateObj->modify('+1 day')) {
104
            $dateLabels[] = $dateObj->format('Y-m-d');
105
        }
106
107
        foreach ($data as $entry) {
108
            if (!isset($totals[$entry['tool']])) {
109
                $totals[$entry['tool']] = (int) $entry['count'];
110
111
                // Create arrays for each tool, filled with zeros for each date in the timeline
112
                $timeline[$entry['tool']] = array_fill(0, $numDays, 0);
113
            } else {
114
                $totals[$entry['tool']] += (int) $entry['count'];
115
            }
116
117
            $date = new DateTime($entry['date']);
118
            $dateIndex = (int) $date->diff($startObj)->format("%a");
119
            $timeline[$entry['tool']][$dateIndex] = (int) $entry['count'];
120
121
            $grandSum += $entry['count'];
122
        }
123
        arsort($totals);
124
125
        return [
126
            'totals' => $totals,
127
            'grandSum' => $grandSum,
128
            'dateLabels' => $dateLabels,
129
            'timeline' => $timeline,
130
        ];
131
    }
132
133
    /**
134
     * Get usage statistics of the API.
135
     * @param  Connection $client
136
     * @param  string     $start Start date.
137
     * @param  string     $end End date.
138
     * @return array
139
     * @codeCoverageIgnore
140
     */
141
    private function getApiUsageStats(Connection $client, $start, $end)
142
    {
143
        $query = $client->prepare("SELECT * FROM usage_api_timeline
144
                                   WHERE date >= :start AND date <= :end");
145
        $query->bindParam('start', $start);
146
        $query->bindParam('end', $end);
147
        $query->execute();
148
149
        $data = $query->fetchAll();
150
151
        // Create array of totals, along with formatted timeline data as needed by Chart.js
152
        $totals = [];
153
        $dateLabels = [];
154
        $timeline = [];
155
        $startObj = new DateTime($start);
156
        $endObj = new DateTime($end);
157
        $numDays = (int) $endObj->diff($startObj)->format("%a");
158
        $grandSum = 0;
159
160
        // Generate array of date labels
161
        for ($dateObj = new DateTime($start); $dateObj <= $endObj; $dateObj->modify('+1 day')) {
162
            $dateLabels[] = $dateObj->format('Y-m-d');
163
        }
164
165
        foreach ($data as $entry) {
166
            if (!isset($totals[$entry['endpoint']])) {
167
                $totals[$entry['endpoint']] = (int) $entry['count'];
168
169
                // Create arrays for each endpoint, filled with zeros for each date in the timeline
170
                $timeline[$entry['endpoint']] = array_fill(0, $numDays, 0);
171
            } else {
172
                $totals[$entry['endpoint']] += (int) $entry['count'];
173
            }
174
175
            $date = new DateTime($entry['date']);
176
            $dateIndex = (int) $date->diff($startObj)->format("%a");
177
            $timeline[$entry['endpoint']][$dateIndex] = (int) $entry['count'];
178
179
            $grandSum += $entry['count'];
180
        }
181
        arsort($totals);
182
183
        return [
184
            'totals' => $totals,
185
            'grandSum' => $grandSum,
186
            'dateLabels' => $dateLabels,
187
            'timeline' => $timeline,
188
        ];
189
    }
190
191
    /**
192
     * Record usage of a particular XTools tool. This is called automatically
193
     *   in base.html.twig via JavaScript so that it is done asynchronously
194
     * @Route("/meta/usage/{tool}/{project}/{token}")
195
     * @param  Request $request
196
     * @param  string $tool    Internal name of tool
197
     * @param  string $project Project domain such as en.wikipedia.org
198
     * @param  string $token   Unique token for this request, so we don't have people
199
     *                         meddling with these statistics
200
     * @return Response
201
     * @codeCoverageIgnore
202
     */
203
    public function recordUsage(Request $request, $tool, $project, $token)
204
    {
205
        // Ready the response object.
206
        $response = new Response();
207
        $response->headers->set('Content-Type', 'application/json');
208
209
        // Validate method and token.
210
        if ($request->getMethod() !== 'PUT' || !$this->isCsrfTokenValid('intention', $token)) {
211
            $response->setStatusCode(Response::HTTP_FORBIDDEN);
212
            $response->setContent(json_encode([
213
                'error' => 'This endpoint is for internal use only.'
214
            ]));
215
            return $response;
216
        }
217
218
        // Don't update counts for tools that aren't enabled
219
        if (!$this->container->getParameter("enable.$tool")) {
220
            $response->setStatusCode(Response::HTTP_FORBIDDEN);
221
            $response->setContent(json_encode([
222
                'error' => 'This tool is disabled'
223
            ]));
224
            return $response;
225
        }
226
227
        $conn = $this->container->get('doctrine')->getManager('default')->getConnection();
228
        $date =  date('Y-m-d');
229
230
        // Increment count in timeline
231
        $existsSql = "SELECT 1 FROM usage_timeline
232
                      WHERE date = '$date'
233
                      AND tool = '$tool'";
234
235
        if (count($conn->query($existsSql)->fetchAll()) === 0) {
236
            $createSql = "INSERT INTO usage_timeline
237
                          VALUES(NULL, '$date', '$tool', 1)";
238
            $conn->query($createSql);
239
        } else {
240
            $updateSql = "UPDATE usage_timeline
241
                          SET count = count + 1
242
                          WHERE tool = '$tool'
243
                          AND date = '$date'";
244
            $conn->query($updateSql);
245
        }
246
247
        // Update per-project usage, if applicable
248
        if (!$this->container->getParameter('app.single_wiki')) {
249
            $existsSql = "SELECT 1 FROM usage_projects
250
                          WHERE tool = '$tool'
251
                          AND project = '$project'";
252
253
            if (count($conn->query($existsSql)->fetchAll()) === 0) {
254
                $createSql = "INSERT INTO usage_projects
255
                              VALUES(NULL, '$tool', '$project', 1)";
256
                $conn->query($createSql);
257
            } else {
258
                $updateSql = "UPDATE usage_projects
259
                              SET count = count + 1
260
                              WHERE tool = '$tool'
261
                              AND project = '$project'";
262
                $conn->query($updateSql);
263
            }
264
        }
265
266
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
267
        $response->setContent(json_encode([]));
268
        return $response;
269
    }
270
}
271