Passed
Push — master ( 4ccd3c...a7eee1 )
by MusikAnimal
05:26
created

XtoolsController::setCookies()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
ccs 0
cts 6
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains the abstract XtoolsController, which all other controllers will extend.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use AppBundle\Exception\XtoolsHttpException;
9
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10
use Symfony\Component\DependencyInjection\ContainerInterface;
11
use Symfony\Component\HttpFoundation\Cookie;
12
use Symfony\Component\HttpFoundation\JsonResponse;
13
use Symfony\Component\HttpFoundation\Response;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Xtools\ProjectRepository;
17
use Xtools\UserRepository;
18
use Xtools\Project;
19
use Xtools\Page;
20
use Xtools\PageRepository;
21
use Xtools\User;
22
23
/**
24
 * XtoolsController supplies a variety of methods around parsing and validating parameters, and initializing
25
 * Project/User instances. These are used in other controllers in the AppBundle\Controller namespace.
26
 * @abstract
27
 */
28
abstract class XtoolsController extends Controller
29
{
30
    /** @var Request The request object. */
31
    protected $request;
32
33
    /** @var string Name of the action within the child controller that is being executed. */
34
    protected $controllerAction;
35
36
    /** @var array Hash of params parsed from the Request. */
37
    protected $params;
38
39
    /** @var bool Whether this is a request to an API action. */
40
    protected $isApi;
41
42
    /** @var Project Relevant Project parsed from the Request. */
43
    protected $project;
44
45
    /** @var User Relevant User parsed from the Request. */
46
    protected $user;
47
48
    /** @var Page Relevant Page parsed from the Request. */
49
    protected $page;
50
51
    /** @var int|false Start date parsed from the Request. */
52
    protected $start = false;
53
54
    /** @var int|false End date parsed from the Request. */
55
    protected $end = false;
56
57
    /** @var int|string Namespace parsed from the Request, ID as int or 'all' for all namespaces. */
58
    protected $namespace;
59
60
    /** @var int Pagination offset parsed from the Request. */
61
    protected $offset = 0;
62
63
    /** @var int Number of results to return. */
64
    protected $limit;
65
66
    /** @var bool Is the current request a subrequest? */
67
    protected $isSubRequest;
68
69
    /**
70
     * Stores user preferences such default project.
71
     * This may get altered from the Request and updated in the Response.
72
     * @var array
73
     */
74
    protected $cookies = [
75
        'XtoolsProject' => null,
76
    ];
77
78
    /**
79
     * This activates the 'too high edit count' functionality. This property represents the
80
     * action that should be redirected to if the user has too high of an edit count.
81
     * @var string
82
     */
83
    protected $tooHighEditCountAction;
84
85
    /**
86
     * @var array Actions that are exempt from edit count limitations.
87
     */
88
    protected $tooHighEditCountActionBlacklist = [];
89
90
    /**
91
     * Require the tool's index route (initial form) be defined here. This should also
92
     * be the name of the associated model, if present.
93
     * @return string
94
     */
95
    abstract protected function getIndexRoute();
96
97
    /**
98
     * XtoolsController constructor.
99
     * @param RequestStack $requestStack
100
     * @param ContainerInterface $container
101
     */
102 18
    public function __construct(RequestStack $requestStack, ContainerInterface $container)
103
    {
104 18
        $this->request = $requestStack->getCurrentRequest();
105 18
        $this->container = $container;
106 18
        $this->params = $this->parseQueryParams();
107
108
        // Parse out the name of the controller and action.
109 18
        $pattern = "#::([a-zA-Z]*)Action#";
110 18
        $matches = [];
111 18
        preg_match($pattern, $this->request->get('_controller'), $matches);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
        preg_match($pattern, $this->request->/** @scrutinizer ignore-call */ get('_controller'), $matches);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
112
        // The blank string here only happens in the unit tests, where the request may not be made to an action.
113 18
        $this->controllerAction = isset($matches[1]) ? $matches[1] : '';
114
115
        // Whether the action is an API action.
116 18
        $this->isApi = substr($this->controllerAction, -3) === 'Api';
117
118
        // Whether we're making a subrequest (the view makes a request to another action).
119 18
        $this->isSubRequest = $this->request->get('htmlonly')
120 18
            || null !== $this->get('request_stack')->getParentRequest();
121
122
        // Load user options from cookies.
123 18
        $this->loadCookies();
124
125
        // Set the class-level properties based on params.
126 18
        if (false !== strpos(strtolower($this->controllerAction), 'index')) {
127
            // Index pages should only set the project, and no other class properties.
128 12
            $this->setProject($this->getProjectFromQuery());
129
        } else {
130 6
            $this->setProperties(); // Includes the project.
131
        }
132 18
    }
133
134
    /***********
135
     * COOKIES *
136
     ***********/
137
138
    /**
139
     * Load user preferences from the associated cookies.
140
     */
141 18
    private function loadCookies()
142
    {
143
        // Not done for subrequests.
144 18
        if ($this->isSubRequest) {
145
            return;
146
        }
147
148 18
        foreach (array_keys($this->cookies) as $name) {
149 18
            $this->cookies[$name] = $this->request->cookies->get($name);
150
        }
151 18
    }
152
153
    /**
154
     * Set cookies on the given Response.
155
     * @param Response $response
156
     */
157
    private function setCookies(Response &$response)
158
    {
159
        // Not done for subrequests.
160
        if ($this->isSubRequest) {
161
            return;
162
        }
163
164
        foreach ($this->cookies as $name => $value) {
165
            $response->headers->setCookie(
166
                new Cookie($name, $value)
167
            );
168
        }
169
    }
170
171
    /**
172
     * Sets the project, with the domain in $this->cookies['XtoolsProject'] that will
173
     * later get set on the Response headers in self::getFormattedResponse().
174
     * @param Project $project
175
     */
176 14
    private function setProject(Project $project)
177
    {
178 14
        $this->project = $project;
179 14
        $this->cookies['XtoolsProject'] = $project->getDomain();
180 14
    }
181
182
    /****************************
183
     * SETTING CLASS PROPERTIES *
184
     ****************************/
185
186
    /**
187
     * Normalize all common parameters used by the controllers and set class properties.
188
     */
189 6
    private function setProperties()
190
    {
191
        // No normalization needed for these params.
192 6
        foreach (['namespace', 'offset', 'limit'] as $param) {
193 6
            if (isset($this->params[$param])) {
194 6
                $this->{$param} = $this->params[$param];
195
            }
196
        }
197
198 6
        if (isset($this->params['project'])) {
199 2
            $this->setProject($this->validateProject($this->params['project']));
200 4
        } elseif (null !== $this->cookies['XtoolsProject']) {
201
            // Set from cookie.
202
            $this->setProject(
203
                $this->validateProject($this->cookies['XtoolsProject'])
204
            );
205
        }
206
207 6
        if (isset($this->params['username'])) {
208
            $this->user = $this->validateUser($this->params['username']);
209
        }
210 6
        if (isset($this->params['page'])) {
211
            $this->page = $this->getPageFromNsAndTitle($this->namespace, $this->params['page']);
0 ignored issues
show
Bug introduced by
It seems like $this->namespace can also be of type string; however, parameter $ns of AppBundle\Controller\Xto...getPageFromNsAndTitle() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
            $this->page = $this->getPageFromNsAndTitle(/** @scrutinizer ignore-type */ $this->namespace, $this->params['page']);
Loading history...
212
        }
213
214 6
        $this->setDates();
215 6
    }
216
217
    /**
218
     * Set class properties for dates, if such params were passed in.
219
     */
220 6
    private function setDates()
221
    {
222 6
        $start = isset($this->params['start']) ? $this->params['start'] : false;
223 6
        $end = isset($this->params['end']) ? $this->params['end'] : false;
224 6
        if ($start || $end) {
225
            list($this->start, $this->end) = $this->getUTCFromDateParams($start, $end);
226
        }
227 6
    }
228
229
    /**
230
     * Construct a fully qualified page title given the namespace and title.
231
     * @param int $ns Namespace ID.
232
     * @param string $title Page title.
233
     * @param bool $rawTitle Return only the title (and not a Page).
234
     * @return Page|string
235
     */
236
    protected function getPageFromNsAndTitle($ns, $title, $rawTitle = false)
237
    {
238
        if ((int)$ns === 0) {
239
            return $rawTitle ? $title : $this->validatePage($title);
240
        }
241
242
        // Prepend namespace and strip out duplicates.
243
        $nsName = $this->project->getNamespaces()[$ns];
244
        $title = $nsName.':'.ltrim($title, $nsName.':');
245
        return $rawTitle ? $title : $this->validatePage($title);
246
    }
247
248
    /**
249
     * Get a Project instance from the project string, using defaults if the given project string is invalid.
250
     * @return Project
251
     */
252 12
    public function getProjectFromQuery()
253
    {
254
        // Set default project so we can populate the namespace selector on index pages.
255
        // Defaults to project stored in cookie, otherwise project specified in parameters.yml.
256 12
        if (isset($this->params['project'])) {
257 2
            $project = $this->params['project'];
258 10
        } elseif (null !== $this->cookies['XtoolsProject']) {
259
            $project = $this->cookies['XtoolsProject'];
260
        } else {
261 10
            $project = $this->container->getParameter('default_project');
262
        }
263
264 12
        $projectData = ProjectRepository::getProject($project, $this->container);
265
266
        // Revert back to defaults if we've established the given project was invalid.
267 12
        if (!$projectData->exists()) {
268
            $projectData = ProjectRepository::getProject(
269
                $this->container->getParameter('default_project'),
270
                $this->container
271
            );
272
        }
273
274 12
        return $projectData;
275
    }
276
277
    /*************************
278
     * GETTERS / VALIDATIONS *
279
     *************************/
280
281
    /**
282
     * Validate the given project, returning a Project if it is valid or false otherwise.
283
     * @param string $projectQuery Project domain or database name.
284
     * @return Project
285
     * @throws XtoolsHttpException
286
     */
287 2
    public function validateProject($projectQuery)
288
    {
289
        /** @var Project $project */
290 2
        $project = ProjectRepository::getProject($projectQuery, $this->container);
291
292 2
        if ($project->exists()) {
293 2
            return $project;
294
        }
295
296
        $this->throwXtoolsException(
297
            $this->getIndexRoute(),
298
            'Invalid project',
299
            ['invalid-project', $this->params['project']],
300
            'project'
301
        );
302
    }
303
304
    /**
305
     * Validate the given user, returning a User or Redirect if they don't exist.
306
     * @param string $username
307
     * @return User
308
     * @throws XtoolsHttpException
309
     */
310
    public function validateUser($username)
311
    {
312
        $user = UserRepository::getUser($username, $this->container);
313
314
        // Allow querying for any IP, currently with no edit count limitation...
315
        // Once T188677 is resolved IPs will be affected by the EXPLAIN results.
316
        if ($user->isAnon()) {
317
            return $user;
318
        }
319
320
        $originalParams = $this->params;
321
322
        // Don't continue if the user doesn't exist.
323
        if (!$user->existsOnProject($this->project)) {
324
            $this->throwXtoolsException($this->getIndexRoute(), 'User not found', 'user-not-found', 'username');
325
        }
326
327
        // Reject users with a crazy high edit count.
328
        if (isset($this->tooHighEditCountAction) &&
329
            !in_array($this->controllerAction, $this->tooHighEditCountActionBlacklist) &&
330
            $user->hasTooManyEdits($this->project)
331
        ) {
332
            /** TODO: Somehow get this to use self::throwXtoolsException */
333
334
            // FIXME: i18n!!
335
            $this->addFlash('danger', ['too-many-edits', number_format($user->maxEdits())]);
0 ignored issues
show
Bug introduced by
array('too-many-edits', ...mat($user->maxEdits())) of type array<integer,string> is incompatible with the type string expected by parameter $message of Symfony\Bundle\Framework...\Controller::addFlash(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
            $this->addFlash('danger', /** @scrutinizer ignore-type */ ['too-many-edits', number_format($user->maxEdits())]);
Loading history...
336
337
            // If redirecting to a different controller, show an informative message accordingly.
338
            if ($this->tooHighEditCountAction !== $this->getIndexRoute()) {
339
                // FIXME: This is currently only done for Edit Counter, redirecting to Simple Edit Counter,
340
                // so this bit is hardcoded. We need to instead give the i18n key of the route.
341
                $this->addFlash('info', ['too-many-edits-redir', 'Simple Counter']);
342
            } else {
343
                // Redirecting back to index, so remove username (otherwise we'd get a redirect loop).
344
                unset($this->params['username']);
345
            }
346
347
            throw new XtoolsHttpException(
348
                'User has made too many edits! Maximum '.$user->maxEdits(),
349
                $this->generateUrl($this->tooHighEditCountAction, $this->params),
350
                $originalParams,
351
                $this->isApi
352
            );
353
        }
354
355
        return $user;
356
    }
357
358
    /**
359
     * Get a Page instance from the given page title, and validate that it exists.
360
     * @param string $pageTitle
361
     * @return Page
362
     * @throws XtoolsHttpException
363
     */
364
    public function validatePage($pageTitle)
365
    {
366
        $page = new Page($this->project, $pageTitle);
367
        $pageRepo = new PageRepository();
368
        $pageRepo->setContainer($this->container);
369
        $page->setRepository($pageRepo);
370
371
        if ($page->exists()) { // Page is valid.
372
            return $page;
373
        }
374
375
        $this->throwXtoolsException(
376
            $this->getIndexRoute(),
377
            'Page not found',
378
            isset($this->params['page']) ? ['no-result', $this->params['page']] : null,
379
            'page'
380
        );
381
    }
382
383
    /**
384
     * Throw an XtoolsHttpException, which the given error message and redirects to specified action.
385
     * @param string $redirectAction Name of action to redirect to.
386
     * @param string $message Shown in API responses (TODO: this should be i18n'd too?)
387
     * @param array|string|null $flashParams
388
     * @param string $invalidParam This will be removed from $this->params. Omit if you don't want this to happen.
389
     * @throws XtoolsHttpException
390
     */
391
    public function throwXtoolsException($redirectAction, $message, $flashParams = null, $invalidParam = null)
392
    {
393
        if (null !== $flashParams) {
394
            $this->addFlash('danger', $flashParams);
0 ignored issues
show
Bug introduced by
It seems like $flashParams can also be of type array; however, parameter $message of Symfony\Bundle\Framework...\Controller::addFlash() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

394
            $this->addFlash('danger', /** @scrutinizer ignore-type */ $flashParams);
Loading history...
395
        }
396
        $originalParams = $this->params;
397
398
        // Remove invalid parameter if it was given.
399
        if (is_string($invalidParam)) {
400
            unset($this->params[$invalidParam]);
401
        }
402
403
        // We sometimes are redirecting to the index page, so also remove project (otherwise we'd get a redirect loop).
404
        /**
405
         * FIXME: Index pages should have a 'nosubmit' parameter to prevent submission.
406
         * Then we don't even need to remove $invalidParam.
407
         * Better, we should show the error on the results page, with no results.
408
         */
409
        unset($this->params['project']);
410
411
        // Throw exception which will redirect to $redirectAction.
412
        throw new XtoolsHttpException(
413
            $message,
414
            $this->generateUrl($redirectAction, $this->params),
415
            $originalParams,
416
            $this->isApi
417
        );
418
    }
419
420
    /**
421
     * Get the first error message stored in the session's FlashBag.
422
     * @return string
423
     */
424
    public function getFlashMessage()
425
    {
426
        $key = $this->get('session')->getFlashBag()->get('danger')[0];
427
        $param = null;
428
429
        if (is_array($key)) {
430
            list($key, $param) = $key;
431
        }
432
433
        return $this->render('message.twig', [
434
            'key' => $key,
435
            'params' => [$param]
436
        ])->getContent();
437
    }
438
439
    /******************
440
     * PARSING PARAMS *
441
     ******************/
442
443
    /**
444
     * Get all standardized parameters from the Request, either via URL query string or routing.
445
     * @return string[]
446
     */
447 18
    public function getParams()
448
    {
449
        $paramsToCheck = [
450 18
            'project',
451
            'username',
452
            'namespace',
453
            'page',
454
            'categories',
455
            'redirects',
456
            'deleted',
457
            'start',
458
            'end',
459
            'offset',
460
            'limit',
461
            'format',
462
463
            // Legacy parameters.
464
            'user',
465
            'name',
466
            'article',
467
            'wiki',
468
            'wikifam',
469
            'lang',
470
            'wikilang',
471
            'begin',
472
        ];
473
474
        /** @var string[] Each parameter that was detected along with its value. */
475 18
        $params = [];
476
477 18
        foreach ($paramsToCheck as $param) {
478
            // Pull in either from URL query string or route.
479 18
            $value = $this->request->query->get($param) ?: $this->request->get($param);
480
481
            // Only store if value is given ('namespace' or 'username' could be '0').
482 18
            if ($value !== null && $value !== '') {
483 18
                $params[$param] = rawurldecode($value);
484
            }
485
        }
486
487 18
        return $params;
488
    }
489
490
    /**
491
     * Parse out common parameters from the request. These include the
492
     * 'project', 'username', 'namespace' and 'page', along with their legacy
493
     * counterparts (e.g. 'lang' and 'wiki').
494
     * @return string[] Normalized parameters (no legacy params).
495
     */
496 18
    public function parseQueryParams()
497
    {
498
        /** @var string[] Each parameter and value that was detected. */
499 18
        $params = $this->getParams();
500
501
        // Covert any legacy parameters, if present.
502 18
        $params = $this->convertLegacyParams($params);
503
504
        // Remove blank values.
505 18
        return array_filter($params, function ($param) {
506
            // 'namespace' or 'username' could be '0'.
507 4
            return $param !== null && $param !== '';
508 18
        });
509
    }
510
511
    /**
512
     * Get UTC timestamps from given start and end string parameters. This also makes $start on month before
513
     * $end if not present, and makes $end the current time if not present.
514
     * @param int|string $start Unix timestamp or string accepted by strtotime.
515
     * @param int|string $end Unix timestamp or string accepted by strtotime.
516
     * @param bool $useDefaults Whether to use defaults if the values are blank. The start date is set to one month
517
     *   before the end date, and the end date is set to the present.
518
     * @return mixed[] Start and end date as UTC timestamps or 'false' if empty.
519
     */
520 1
    public function getUTCFromDateParams($start, $end, $useDefaults = false)
521
    {
522 1
        $start = is_int($start) ? $start : strtotime($start);
523 1
        $end = is_int($end) ? $end : strtotime($end);
524
525
        // Use current time if end is not present (and is required), or if it exceeds the current time.
526 1
        if (($useDefaults && $end === false) || $end > time()) {
0 ignored issues
show
introduced by
The condition $end === false is always false.
Loading history...
527
            $end = time();
528
        }
529
530
        // Default to one month before end time if start is not present, as is not optional.
531 1
        if ($useDefaults && $start === false) {
0 ignored issues
show
introduced by
The condition $start === false is always false.
Loading history...
532 1
            $start = strtotime('-1 month', $end);
533
        }
534
535
        // Reverse if start date is after end date.
536 1
        if ($start > $end && $start !== false && $end !== false) {
537 1
            $newEnd = $start;
538 1
            $start = $end;
539 1
            $end = $newEnd;
540
        }
541
542 1
        return [$start, $end];
543
    }
544
545
    /**
546
     * Given the params hash, normalize any legacy parameters to thier modern equivalent.
547
     * @param string[] $params
548
     * @return string[]
549
     */
550 18
    private function convertLegacyParams($params)
551
    {
552
        $paramMap = [
553 18
            'user' => 'username',
554
            'name' => 'username',
555
            'article' => 'page',
556
            'begin' => 'start',
557
558
            // Copy super legacy project params to legacy so we can concatenate below.
559
            'wikifam' => 'wiki',
560
            'wikilang' => 'lang',
561
        ];
562
563
        // Copy legacy parameters to modern equivalent.
564 18
        foreach ($paramMap as $legacy => $modern) {
565 18
            if (isset($params[$legacy])) {
566
                $params[$modern] = $params[$legacy];
567 18
                unset($params[$legacy]);
568
            }
569
        }
570
571
        // Separate parameters for language and wiki.
572 18
        if (isset($params['wiki']) && isset($params['lang'])) {
573
            // 'wikifam' will be like '.wikipedia.org', vs just 'wikipedia',
574
            // so we must remove leading periods and trailing .org's.
575
            $params['project'] = rtrim(ltrim($params['wiki'], '.'), '.org').'.org';
576
577
            /** @var string[] Projects for which there is no specific language association. */
578
            $languagelessProjects = $this->container->getParameter('languageless_wikis');
579
580
            // Prepend language if applicable.
581
            if (isset($params['lang']) && !in_array($params['wiki'], $languagelessProjects)) {
582
                $params['project'] = $params['lang'].'.'.$params['project'];
583
            }
584
585
            unset($params['wiki']);
586
            unset($params['lang']);
587
        }
588
589 18
        return $params;
590
    }
591
592
    /************************
593
     * FORMATTING RESPONSES *
594
     ************************/
595
596
    /**
597
     * Get the rendered template for the requested format. This method also updates the cookies.
598
     * @param string $templatePath Path to template without format,
599
     *   such as '/editCounter/latest_global'.
600
     * @param array $ret Data that should be passed to the view.
601
     * @return Response
602
     * @codeCoverageIgnore
603
     */
604
    public function getFormattedResponse($templatePath, array $ret)
605
    {
606
        $format = $this->request->query->get('format', 'html');
607
        if ($format == '') {
608
            // The default above doesn't work when the 'format' parameter is blank.
609
            $format = 'html';
610
        }
611
612
        // Merge in common default parameters, giving $ret (from the caller) the priority.
613
        $ret = array_merge([
614
            'project' => $this->project,
615
            'user' => $this->user,
616
            'page' => $this->page,
617
        ], $ret);
618
619
        $formatMap = [
620
            'wikitext' => 'text/plain',
621
            'csv' => 'text/csv',
622
            'tsv' => 'text/tab-separated-values',
623
            'json' => 'application/json',
624
        ];
625
626
        $response = new Response();
627
628
        // Set cookies. Note this must be done before rendering the view, as the view may invoke subrequests.
629
        $this->setCookies($response);
630
631
        // If requested format does not exist, assume HTML.
632
        if (false === $this->get('twig')->getLoader()->exists("$templatePath.$format.twig")) {
633
            $format = 'html';
634
        }
635
636
        $response = $this->render("$templatePath.$format.twig", $ret, $response);
637
638
        $contentType = isset($formatMap[$format]) ? $formatMap[$format] : 'text/html';
639
        $response->headers->set('Content-Type', $contentType);
640
641
        return $response;
642
    }
643
644
    /**
645
     * Return a JsonResponse object pre-supplied with the requested params.
646
     * @param $data
647
     * @return JsonResponse
648
     */
649 2
    public function getFormattedApiResponse($data)
650
    {
651 2
        $response = new JsonResponse();
652 2
        $response->setEncodingOptions(JSON_NUMERIC_CHECK);
653 2
        $response->setStatusCode(Response::HTTP_OK);
654
655 2
        $elapsedTime = round(
656 2
            microtime(true) - $this->request->server->get('REQUEST_TIME_FLOAT'),
657 2
            3
658
        );
659
660 2
        $response->setData(array_merge($this->params, [
661
            // In some controllers, $this->params['project'] may be overridden with a Project object.
662 2
            'project' => $this->project->getDomain(),
663 2
        ], $data, ['elapsed_time' => $elapsedTime]));
664
665 2
        return $response;
666
    }
667
668
    /*********
669
     * OTHER *
670
     *********/
671
672
    /**
673
     * Record usage of an API endpoint.
674
     * @param string $endpoint
675
     * @codeCoverageIgnore
676
     */
677
    public function recordApiUsage($endpoint)
678
    {
679
        /** @var \Doctrine\DBAL\Connection $conn */
680
        $conn = $this->container->get('doctrine')
681
            ->getManager('default')
682
            ->getConnection();
683
        $date =  date('Y-m-d');
684
685
        // Increment count in timeline
686
        $existsSql = "SELECT 1 FROM usage_api_timeline
687
                      WHERE date = '$date'
688
                      AND endpoint = '$endpoint'";
689
690
        if (count($conn->query($existsSql)->fetchAll()) === 0) {
691
            $createSql = "INSERT INTO usage_api_timeline
692
                          VALUES(NULL, '$date', '$endpoint', 1)";
693
            $conn->query($createSql);
694
        } else {
695
            $updateSql = "UPDATE usage_api_timeline
696
                          SET count = count + 1
697
                          WHERE endpoint = '$endpoint'
698
                          AND date = '$date'";
699
            $conn->query($updateSql);
700
        }
701
    }
702
}
703