Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.00% covered (success)
92.00%
23 / 25
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionController
92.00% covered (success)
92.00%
23 / 25
50.00% covered (danger)
50.00%
1 / 2
6.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 export
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
5.01
1<?php
2
3/**
4 * The Session Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @author    Jan Harms <model_railroader@gmx-topmail.de>
13 * @copyright 2024-2026 phpMyFAQ Team
14 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
15 * @link      https://www.phpmyfaq.de
16 * @since     2024-01-13
17 */
18
19declare(strict_types=1);
20
21namespace phpMyFAQ\Controller\Administration\Api;
22
23use Exception;
24use phpMyFAQ\Administration\Session;
25use phpMyFAQ\Enums\AdminLogType;
26use phpMyFAQ\Enums\PermissionType;
27use phpMyFAQ\Filter;
28use phpMyFAQ\Session\Token;
29use phpMyFAQ\Translation;
30use Symfony\Component\HttpFoundation\BinaryFileResponse;
31use Symfony\Component\HttpFoundation\JsonResponse;
32use Symfony\Component\HttpFoundation\Request;
33use Symfony\Component\HttpFoundation\Response;
34use Symfony\Component\HttpFoundation\ResponseHeaderBag;
35use Symfony\Component\Routing\Attribute\Route;
36
37final class SessionController extends AbstractAdministrationApiController
38{
39    public function __construct(
40        private readonly Session $adminSession,
41    ) {
42        parent::__construct();
43    }
44
45    /**
46     * @throws Exception
47     */
48    #[Route(path: 'session/export', name: 'admin.api.session.export', methods: ['POST'])]
49    public function export(Request $request): BinaryFileResponse|JsonResponse
50    {
51        $this->userHasPermission(PermissionType::STATISTICS_VIEWLOGS);
52
53        $requestData = $this->getJsonObject($request);
54
55        if (!Token::getInstance($this->session)->verifyToken('export-sessions', (string) ($requestData->csrf ?? ''))) {
56            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
57        }
58
59        $firstHour = (string) Filter::filterVar($requestData->firstHour ?? null, FILTER_SANITIZE_SPECIAL_CHARS, '');
60        $lastHour = (string) Filter::filterVar($requestData->lastHour ?? null, FILTER_SANITIZE_SPECIAL_CHARS, '');
61
62        $data = $this->adminSession->getSessionsByDate((int) strtotime($firstHour), (int) strtotime($lastHour));
63        $filePath = tempnam(sys_get_temp_dir(), prefix: 'csv_');
64        if ($filePath === false) {
65            return $this->json(['error' => 'Cannot create the export file.'], Response::HTTP_INTERNAL_SERVER_ERROR);
66        }
67
68        $file = fopen($filePath, mode: 'w');
69        if ($file) {
70            foreach ($data as $row) {
71                fputcsv($file, [$row['ip'], $row['time']], separator: ',', enclosure: '"', eol: PHP_EOL);
72            }
73
74            fclose($file);
75
76            $this->adminLog->log($this->currentUser, AdminLogType::DATA_EXPORT_SESSIONS->value);
77
78            $binaryFileResponse = new BinaryFileResponse($filePath);
79            $binaryFileResponse->setContentDisposition(
80                ResponseHeaderBag::DISPOSITION_INLINE,
81                'sessions_' . $firstHour . '-' . $lastHour . '.csv',
82            );
83            $binaryFileResponse->headers->set('Content-Type', 'text/csv');
84            return $binaryFileResponse;
85        }
86
87        return $this->json(['error' => 'Unable to open file.'], Response::HTTP_BAD_REQUEST);
88    }
89}