Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Report
94.44% covered (success)
94.44%
34 / 36
75.00% covered (warning)
75.00%
3 / 4
16.04
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
 getReportingData
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
8
 convertEncoding
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
5.27
 sanitize
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * The reporting class for simple report generation.
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    Gustavo Solt <gustavo.solt@mayflower.de>
13 * @copyright 2011-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     2011-02-04
17 */
18
19declare(strict_types=1);
20
21namespace phpMyFAQ\Administration;
22
23use phpMyFAQ\Configuration;
24use phpMyFAQ\Date;
25
26/**
27 * Class Report
28 *
29 * @package phpMyFAQ
30 */
31readonly class Report
32{
33    private ReportRepository $reportRepository;
34
35    /**
36     * Constructor.
37     */
38    public function __construct(Configuration $configuration)
39    {
40        $this->reportRepository = new ReportRepository($configuration);
41    }
42
43    /**
44     * Generates a huge array for the report.
45     *
46     * @return array<int, array<string, mixed>>
47     */
48    public function getReportingData(): array
49    {
50        $report = [];
51        $rows = $this->reportRepository->fetchAllReportData();
52
53        $lastId = 0;
54        foreach ($rows as $row) {
55            $rowId = (int) $row->id;
56            if ($rowId === $lastId) {
57                ++$report[$rowId]['faq_translations'];
58            }
59
60            $report[$rowId] = [
61                'faq_id' => (int) $row->id,
62                'faq_language' => (string) $row->lang,
63                'category_id' => $row->category_id === null ? null : (int) $row->category_id,
64                'category_parent' => $row->parent_id === null ? null : (int) $row->parent_id,
65                'category_name' => $row->category_name === null ? null : (string) $row->category_name,
66                'faq_translations' => 0,
67                'faq_sticky' => (int) $row->sticky,
68                'faq_question' => (string) $row->question,
69                'faq_org_author' => (string) $row->original_author,
70                'faq_updated' => Date::createIsoDate((string) $row->updated),
71                'faq_visits' => $row->visits === null ? null : (int) $row->visits,
72                'faq_last_author' => $row->last_author === null ? null : (string) $row->last_author,
73            ];
74
75            $lastId = $rowId;
76        }
77
78        return $report;
79    }
80
81    /**
82     * Convert string to the correct encoding and removes possible
83     * bad strings to avoid formula injection attacks.
84     *
85     * @param  string $outputString String to encode.
86     * @return string Encoded string.
87     */
88    public function convertEncoding(string $outputString = ''): string
89    {
90        $outputString = html_entity_decode($outputString, ENT_QUOTES, encoding: 'utf-8');
91        $outputString = str_replace(search: ',', replace: ' ', subject: $outputString);
92
93        if (extension_loaded(extension: 'mbstring')) {
94            $detected = mb_detect_encoding($outputString);
95
96            if ($detected !== false && $detected !== 'ASCII') {
97                $converted = mb_convert_encoding($outputString, to_encoding: 'UTF-16', from_encoding: $detected);
98                $outputString = is_string($converted) ? $converted : $outputString;
99            }
100        }
101
102        $toBeRemoved = ['=', '+', '-', 'HYPERLINK'];
103        return str_replace(search: $toBeRemoved, replace: '', subject: $outputString);
104    }
105
106    /**
107     * Sanitizes input to avoid CSV injection.
108     */
109    public static function sanitize(int|string $value): string|int
110    {
111        if (preg_match(pattern: '/[=\+\-\@\|]/', subject: (string) $value)) {
112            return '"' . str_replace(search: '"', replace: '""', subject: (string) $value) . '"';
113        }
114
115        return $value;
116    }
117}