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