Passed
Push — master ( a78947...820c60 )
by MusikAnimal
04:05
created

MetaController::indexAction()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 1
dl 0
loc 12
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 9.4285
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 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...
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 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...
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
        // Validate method and token.
206
        if ($request->getMethod() !== 'PUT' || !$this->isCsrfTokenValid('intention', $token)) {
207
            throw $this->createAccessDeniedException('This endpoint is for internal use only.');
208
        }
209
210
        // Ready the response object.
211
        $response = new Response();
212
        $response->headers->set('Content-Type', 'application/json');
213
214
        // Don't update counts for tools that aren't enabled
215
        if (!$this->container->getParameter("enable.$tool")) {
216
            $response->setStatusCode(Response::HTTP_FORBIDDEN);
217
            $response->setContent(json_encode([
218
                'error' => 'This tool is disabled'
219
            ]));
220
            return $response;
221
        }
222
223
        $conn = $this->container->get('doctrine')->getManager('default')->getConnection();
224
        $date =  date('Y-m-d');
225
226
        // Increment count in timeline
227
        $existsSql = "SELECT 1 FROM usage_timeline
228
                      WHERE date = '$date'
229
                      AND tool = '$tool'";
230
231 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...
232
            $createSql = "INSERT INTO usage_timeline
233
                          VALUES(NULL, '$date', '$tool', 1)";
234
            $conn->query($createSql);
235
        } else {
236
            $updateSql = "UPDATE usage_timeline
237
                          SET count = count + 1
238
                          WHERE tool = '$tool'
239
                          AND date = '$date'";
240
            $conn->query($updateSql);
241
        }
242
243
        // Update per-project usage, if applicable
244
        if (!$this->container->getParameter('app.single_wiki')) {
245
            $existsSql = "SELECT 1 FROM usage_projects
246
                          WHERE tool = '$tool'
247
                          AND project = '$project'";
248
249 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...
250
                $createSql = "INSERT INTO usage_projects
251
                              VALUES(NULL, '$tool', '$project', 1)";
252
                $conn->query($createSql);
253
            } else {
254
                $updateSql = "UPDATE usage_projects
255
                              SET count = count + 1
256
                              WHERE tool = '$tool'
257
                              AND project = '$project'";
258
                $conn->query($updateSql);
259
            }
260
        }
261
262
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
263
        $response->setContent(json_encode([]));
264
        return $response;
265
    }
266
}
267