Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
26.75% covered (danger)
26.75%
42 / 157
15.79% covered (danger)
15.79%
3 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
NewsFilterUtils
26.75% covered (danger)
26.75%
42 / 157
15.79% covered (danger)
15.79%
3 / 19
1156.94
0.00% covered (danger)
0.00%
0 / 1
 getDefaultFilter
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 isValidFilter
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
6
 getValidFilter
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 getAllValidFiltersForSitemap
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getUiFormatFilterOptions
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 getUiDateRangeFilterOptions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getUiArchiveFilterOptions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getDateRangeOptions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getSqlFromFilter
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getSqlDateRangeFilter
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getSqlFormatFilter
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 getTitleFromFilter
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getPresentFormatFilterTitle
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 getPastFormatFilterTitle
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 getArchiveFilterTitleSuffix
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 isFilterNotArchived
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getIsNotArchivedCriteria
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getUrl
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 fromEnv
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Olz\News\Utils;
4
5use Doctrine\Common\Collections\Criteria;
6use Doctrine\Common\Collections\Expr\Comparison;
7use Olz\Utils\DateUtils;
8use Olz\Utils\WithUtilsTrait;
9
10/**
11 * @phpstan-type Option array{ident: string, name: string, icon?: string}
12 * @phpstan-type UiOption array{selected: bool, new_filter: FullFilter, name: string, icon?: ?string, ident: string}
13 * @phpstan-type FullFilter array{format: string, datum: string, archiv: string}
14 * @phpstan-type PartialFilter array{format?: string, datum?: string, archiv?: string}
15 */
16class NewsFilterUtils {
17    use WithUtilsTrait;
18
19    public const ALL_FORMAT_OPTIONS = [
20        ['ident' => 'alle', 'name' => "Alle"],
21        ['ident' => 'aktuell', 'name' => "Aktuell", 'icon' => 'entry_type_aktuell_20.svg'],
22        ['ident' => 'kaderblog', 'name' => "Kaderblog", 'icon' => 'entry_type_kaderblog_20.svg'],
23        ['ident' => 'forum', 'name' => "Forum", 'icon' => 'entry_type_forum_20.svg'],
24        ['ident' => 'galerie', 'name' => "Galerien", 'icon' => 'entry_type_gallery_20.svg'],
25        ['ident' => 'video', 'name' => "Videos", 'icon' => 'entry_type_movie_20.svg'],
26    ];
27
28    public const ALL_ARCHIVE_OPTIONS = [
29        ['ident' => 'ohne', 'name' => "ohne Archiv"],
30        ['ident' => 'mit', 'name' => "mit Archiv"],
31    ];
32
33    /** @return FullFilter */
34    public function getDefaultFilter(): array {
35        $current_year = intval($this->dateUtils()->getCurrentDateInFormat('Y'));
36        return [
37            'format' => 'alle',
38            'datum' => strval($current_year),
39            'archiv' => 'ohne',
40        ];
41    }
42
43    /** @param ?PartialFilter $filter */
44    public function isValidFilter(?array $filter): bool {
45        $has_correct_format = (
46            isset($filter['format'])
47            && array_filter(
48                NewsFilterUtils::ALL_FORMAT_OPTIONS,
49                function ($format_option) use ($filter) {
50                    return $format_option['ident'] === $filter['format'];
51                }
52            )
53        );
54        $has_correct_date_range = (
55            isset($filter['datum'])
56            && array_filter(
57                $this->getDateRangeOptions([...$this->getDefaultFilter(), ...$filter]),
58                function ($date_option) use ($filter) {
59                    return $date_option['ident'] === $filter['datum'];
60                }
61            )
62        );
63        $has_correct_archive = (
64            isset($filter['archiv'])
65            && array_filter(
66                NewsFilterUtils::ALL_ARCHIVE_OPTIONS,
67                function ($archive_option) use ($filter) {
68                    return $archive_option['ident'] === $filter['archiv'];
69                }
70            )
71        );
72        return $has_correct_format && $has_correct_date_range && $has_correct_archive;
73    }
74
75    /**
76     * @param ?PartialFilter $filter
77     *
78     * @return FullFilter
79     */
80    public function getValidFilter(?array $filter): array {
81        $default_filter = $this->getDefaultFilter();
82        if (!$filter) {
83            return $default_filter;
84        }
85        $merged_filter = [
86            'format' => $filter['format'] ?? $default_filter['format'],
87            'datum' => $filter['datum'] ?? $default_filter['datum'],
88            'archiv' => $filter['archiv'] ?? $default_filter['archiv'],
89        ];
90        return $this->isValidFilter($merged_filter) ? $merged_filter : $default_filter;
91    }
92
93    /** @return array<FullFilter> */
94    public function getAllValidFiltersForSitemap(): array {
95        $all_valid_filters = [];
96        foreach (NewsFilterUtils::ALL_FORMAT_OPTIONS as $format_option) {
97            $date_range_options = $this->getDateRangeOptions($this->getValidFilter(['archiv' => 'ohne']));
98            foreach ($date_range_options as $date_range_option) {
99                $all_valid_filters[] = [
100                    'format' => $format_option['ident'],
101                    'datum' => $date_range_option['ident'],
102                    'archiv' => 'ohne',
103                ];
104            }
105        }
106        return $all_valid_filters;
107    }
108
109    /**
110     * @param FullFilter $filter
111     *
112     * @return array<UiOption>
113     */
114    public function getUiFormatFilterOptions(array $filter): array {
115        return array_map(function ($format_option) use ($filter) {
116            $new_filter = $filter;
117            $new_filter['format'] = $format_option['ident'];
118            return [
119                'selected' => $format_option['ident'] === $filter['format'],
120                'new_filter' => $new_filter,
121                'name' => $format_option['name'],
122                'icon' => $format_option['icon'] ?? null,
123                'ident' => $format_option['ident'],
124            ];
125        }, NewsFilterUtils::ALL_FORMAT_OPTIONS);
126    }
127
128    /**
129     * @param FullFilter $filter
130     *
131     * @return array<UiOption>
132     */
133    public function getUiDateRangeFilterOptions(array $filter): array {
134        return array_map(function ($date_range_option) use ($filter) {
135            $new_filter = $filter;
136            $new_filter['datum'] = $date_range_option['ident'];
137            return [
138                'selected' => $date_range_option['ident'] === $filter['datum'],
139                'new_filter' => $new_filter,
140                'name' => $date_range_option['name'],
141                'ident' => $date_range_option['ident'],
142            ];
143        }, $this->getDateRangeOptions($filter));
144    }
145
146    /**
147     * @param FullFilter $filter
148     *
149     * @return array<UiOption>
150     */
151    public function getUiArchiveFilterOptions(array $filter): array {
152        return array_map(function ($archive_option) use ($filter) {
153            $new_filter = $filter;
154            $new_filter['archiv'] = $archive_option['ident'];
155            return [
156                'selected' => $archive_option['ident'] === $filter['archiv'],
157                'new_filter' => $new_filter,
158                'name' => $archive_option['name'],
159                'ident' => $archive_option['ident'],
160            ];
161        }, NewsFilterUtils::ALL_ARCHIVE_OPTIONS);
162    }
163
164    /**
165     * @param FullFilter $filter
166     *
167     * @return array<Option>
168     */
169    public function getDateRangeOptions(array $filter): array {
170        $include_archive = $filter['archiv'] === 'mit';
171        $current_year = intval($this->dateUtils()->getCurrentDateInFormat('Y'));
172        $first_year = $include_archive ? 2006 : $current_year - DateUtils::ARCHIVE_YEARS_THRESHOLD;
173        $options = [];
174        for ($year = $current_year; $year >= $first_year; $year--) {
175            $year_ident = strval($year);
176            $options[] = ['ident' => $year_ident, 'name' => $year_ident];
177        }
178        return $options;
179    }
180
181    /** @param FullFilter $filter */
182    public function getSqlFromFilter(array $filter): string {
183        if (!$this->isValidFilter($filter)) {
184            return "'1'='0'";
185        }
186        $date_range_filter = $this->getSqlDateRangeFilter($filter);
187        $format_filter = $this->getSqlFormatFilter($filter);
188        return "({$date_range_filter}) AND ({$format_filter})";
189    }
190
191    /** @param FullFilter $filter */
192    private function getSqlDateRangeFilter(array $filter): string {
193        $today = $this->dateUtils()->getIsoToday();
194        if (intval($filter['datum']) > 2000) {
195            $sane_year = strval(intval($filter['datum']));
196            return "YEAR(n.published_date) = '{$sane_year}'";
197        }
198        // @codeCoverageIgnoreStart
199        // Reason: Should not be reached.
200        return "'1' = '0'"; // invalid => show nothing
201        // @codeCoverageIgnoreEnd
202    }
203
204    /** @param FullFilter $filter */
205    private function getSqlFormatFilter(array $filter): string {
206        if ($filter['format'] === 'alle') {
207            return "'1' = '1'";
208        }
209        if ($filter['format'] === 'aktuell') {
210            return "n.format LIKE '%aktuell%'";
211        }
212        if ($filter['format'] === 'kaderblog') {
213            return "n.format LIKE '%kaderblog%'";
214        }
215        if ($filter['format'] === 'forum') {
216            return "n.format LIKE '%forum%'";
217        }
218        if ($filter['format'] === 'galerie') {
219            return "n.format LIKE '%galerie%'";
220        }
221        if ($filter['format'] === 'video') {
222            return "n.format LIKE '%video%'";
223        }
224        // @codeCoverageIgnoreStart
225        // Reason: Should not be reached.
226        return "'1' = '0'"; // invalid => show nothing
227        // @codeCoverageIgnoreEnd
228    }
229
230    /** @param FullFilter $filter */
231    public function getTitleFromFilter(array $filter): string {
232        if (!$this->isValidFilter($filter)) {
233            return "News";
234        }
235        $archive_title_suffix = $this->getArchiveFilterTitleSuffix($filter);
236        $this_year = $this->dateUtils()->getCurrentDateInFormat('Y');
237        if ($filter['datum'] === $this_year) {
238            $format_title = $this->getPresentFormatFilterTitle($filter);
239            return "{$format_title}{$archive_title_suffix}";
240        }
241        $format_title = $this->getPastFormatFilterTitle($filter);
242        if (intval($filter['datum']) > 2000) {
243            $year = $filter['datum'];
244            return "{$format_title} {$year}{$archive_title_suffix}";
245        }
246        // @codeCoverageIgnoreStart
247        // Reason: Should not be reached.
248        return "Aktuell{$archive_title_suffix}";
249        // @codeCoverageIgnoreEnd
250    }
251
252    /** @param FullFilter $filter */
253    private function getPresentFormatFilterTitle(array $filter): string {
254        if ($filter['format'] === 'aktuell') {
255            return "Aktuell";
256        }
257        if ($filter['format'] === 'kaderblog') {
258            return "Kaderblog";
259        }
260        if ($filter['format'] === 'forum') {
261            return "Forum";
262        }
263        if ($filter['format'] === 'galerie') {
264            return "Galerien";
265        }
266        if ($filter['format'] === 'video') {
267            return "Videos";
268        }
269        return "News";
270    }
271
272    /** @param FullFilter $filter */
273    private function getPastFormatFilterTitle(array $filter): string {
274        if ($filter['format'] === 'aktuell') {
275            return "Aktuelles von";
276        }
277        if ($filter['format'] === 'kaderblog') {
278            return "Kaderblog von";
279        }
280        if ($filter['format'] === 'forum') {
281            return "Forumseinträge von";
282        }
283        if ($filter['format'] === 'galerie') {
284            return "Galerien von";
285        }
286        if ($filter['format'] === 'video') {
287            return "Videos von";
288        }
289        return "News von";
290    }
291
292    /** @param FullFilter $filter */
293    private function getArchiveFilterTitleSuffix(array $filter): string {
294        if ($filter['archiv'] === 'mit') {
295            return " (Archiv)";
296        }
297        return "";
298    }
299
300    /** @param PartialFilter $filter */
301    public function isFilterNotArchived(array $filter): bool {
302        $valid_filter = $this->getValidFilter($filter);
303        return $valid_filter['archiv'] === 'ohne';
304    }
305
306    public function getIsNotArchivedCriteria(): Comparison {
307        $archive_threshold = $this->dateUtils()->getIsoArchiveThreshold();
308        return Criteria::expr()->gte('published_date', new \DateTime($archive_threshold));
309    }
310
311    /** @param PartialFilter $filter */
312    public function getUrl(array $filter = []): string {
313        $code_href = $this->envUtils()->getCodeHref();
314        $enc_json_filter = urlencode(json_encode($filter) ?: '{}');
315        return "{$code_href}news?filter={$enc_json_filter}";
316    }
317
318    public static function fromEnv(): self {
319        return new self();
320    }
321}