Passed
Push — master ( 35c320...7deb12 )
by MusikAnimal
04:42
created

MetaController::getIndexRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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