Lines 94.44% 34 / 36
Methods 75.00% 3 / 4
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 getReportingData 100.00% 23 / 23 100.00% 1 / 1 8
 convertEncoding 77.77% 7 / 9 0.00% 0 / 1 5.27
 sanitize 100.00% 3 / 3 100.00% 1 / 1 2
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}