Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 140
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
OlzTermineListParams
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
OlzTermineList
0.00% covered (danger)
0.00%
0 / 140
0.00% covered (danger)
0.00%
0 / 6
650
0.00% covered (danger)
0.00%
0 / 1
 hasAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 searchSqlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getPageTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPageDescription
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHtmlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 1
380
 getMonth
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3// =============================================================================
4// Zeigt geplante und vergangene Termine an.
5// =============================================================================
6
7namespace Olz\Termine\Components\OlzTermineList;
8
9use Olz\Components\Common\OlzEditableText\OlzEditableText;
10use Olz\Components\Common\OlzRootComponent;
11use Olz\Components\Page\OlzFooter\OlzFooter;
12use Olz\Components\Page\OlzHeader\OlzHeader;
13use Olz\Entity\Termine\Termin;
14use Olz\Entity\Termine\TerminLabel;
15use Olz\Repository\Snippets\PredefinedSnippet;
16use Olz\Termine\Components\OlzTermineFilter\OlzTermineFilter;
17use Olz\Termine\Components\OlzTermineListItem\OlzTermineListItem;
18use Olz\Utils\HttpParams;
19
20/** @extends HttpParams<array{filter?: ?string, von?: ?string}> */
21class OlzTermineListParams extends HttpParams {
22}
23
24/** @extends OlzRootComponent<array<string, mixed>> */
25class OlzTermineList extends OlzRootComponent {
26    public function hasAccess(): bool {
27        return true;
28    }
29
30    public function searchSqlWhenHasAccess(array $terms): string|array|null {
31        $code_href = $this->envUtils()->getCodeHref();
32        $where = implode(' AND ', array_map(function ($term) {
33            return <<<ZZZZZZZZZZ
34                (
35                    name LIKE '%{$term}%'
36                    OR details LIKE '%{$term}%'
37                )
38                ZZZZZZZZZZ;
39        }, $terms));
40        return <<<ZZZZZZZZZZ
41            SELECT
42                CONCAT('{$code_href}termine?filter=typ-', ident, '---datum-bevorstehend') AS link,
43                '{$code_href}assets/icns/termine_type_all_20.svg' AS icon,
44                NULL AS date,
45                CONCAT('Termine: ', name) AS title,
46                details AS text,
47                1.0 AS time_relevance
48            FROM termin_labels
49            WHERE
50                on_off = '1'
51                AND {$where}
52            ZZZZZZZZZZ;
53    }
54
55    public function getPageTitle(): string {
56        return "Termine";
57    }
58
59    public function getPageDescription(): string {
60        // TODO: Filter-specific description?
61        return "Orientierungslauf-Wettkämpfe, OL-Wochen, OL-Weekends, Trainings und Vereinsanlässe der OL Zimmerberg.";
62    }
63
64    public function getHtmlWhenHasAccess(mixed $args): string {
65        /** @return array{filter?: ?string} */
66        $params = $this->httpUtils()->validateGetParams(OlzTermineListParams::class);
67        $db = $this->dbUtils()->getDb();
68        $code_href = $this->envUtils()->getCodeHref();
69
70        $termine_utils = $this->termineUtils()->loadTypeOptions();
71        $current_filter = $termine_utils->deserialize($params['filter'] ?? $this->session()->get('termine_filter') ?? '');
72
73        $valid_filter = $termine_utils->getValidFilter($current_filter);
74        $serialized_filter = $termine_utils->serialize($valid_filter);
75
76        if (
77            !$termine_utils->isValidFilter($current_filter)
78            || ($params['filter'] ?? '') !== $serialized_filter
79        ) {
80            $is_backfill = empty($params['filter']);
81            $this->httpUtils()->redirect(
82                "{$code_href}termine?filter={$serialized_filter}",
83                $is_backfill ? 308 : 410,
84            );
85        }
86
87        $this->session()->set('termine_filter', $serialized_filter);
88
89        $termine_list_title = $termine_utils->getTitleFromFilter($valid_filter);
90        $out = OlzHeader::render([
91            'title' => $termine_list_title,
92            'description' => $this->getPageDescription(),
93            'canonical_url' => "{$code_href}termine?filter={$serialized_filter}",
94        ]);
95
96        $admin_menu_out = '';
97        $has_termine_permissions = $this->authUtils()->hasPermission('termine');
98        if ($has_termine_permissions) {
99            $admin_menu_out = <<<ZZZZZZZZZZ
100                <div class='termine-list-admin-menu'>
101                    <span class='entry'>
102                        <a href='{$code_href}termine/orte' class='linkmap'>
103                            Termin-Orte
104                        </a>
105                    </span>
106                    <span class='entry'>
107                        <a href='{$code_href}termine/vorlagen' class='linkint'>
108                            Termin-Vorlagen
109                        </a>
110                    </span>
111                </div>
112                ZZZZZZZZZZ;
113        }
114        $filter_out = OlzTermineFilter::render(['currentFilter' => $valid_filter]);
115        $downloads_links_out = OlzEditableText::render(['snippet' => PredefinedSnippet::TermineDownloadsLinks]);
116        $newsletter_out = OlzEditableText::render(['snippet' => PredefinedSnippet::TermineNewsletter]);
117        $out .= <<<ZZZZZZZZZZ
118            <div class='content-right'>
119                {$admin_menu_out}
120                <h2 class='optional'>Filter</h2>
121                {$filter_out}
122                <div class='optional'>
123                    <h2>Downloads und Links</h2>
124                    {$downloads_links_out}
125                </div>
126                <div class='optional'>
127                    <h2>Newsletter</h2>
128                    {$newsletter_out}
129                </div>
130            </div>
131            <div class='content-middle olz-termine-list-middle'>
132            ZZZZZZZZZZ;
133
134        $has_access = $this->authUtils()->hasPermission('termine');
135        if ($has_access) {
136            $out .= <<<ZZZZZZZZZZ
137                <button
138                    id='create-termin-button'
139                    class='btn btn-secondary create-termin-container'
140                    onclick='return olz.initOlzEditTerminModal()'
141                >
142                    <img src='{$code_href}assets/icns/new_white_16.svg' class='noborder' />
143                    Neuer Termin
144                </button>
145                ZZZZZZZZZZ;
146        }
147
148        $termin_repo = $this->entityManager()->getRepository(Termin::class);
149        $termin_label_repo = $this->entityManager()->getRepository(TerminLabel::class);
150        $termin_label = $termin_label_repo->findOneBy(['ident' => $valid_filter['typ']]);
151        $edit_admin = '';
152        if ($termin_label) {
153            $has_termine_permissions = $this->authUtils()->hasPermission('termine');
154            if ($has_termine_permissions) {
155                $json_id = json_encode($termin_label->getId());
156                $edit_admin = <<<ZZZZZZZZZZ
157                    <button
158                        id='edit-termin-label-button'
159                        class='btn btn-secondary-outline btn-sm'
160                        onclick='return olz.termineListEditTerminLabel({$json_id})'
161                    >
162                        <img src='{$code_href}assets/icns/edit_16.svg' class='noborder' />
163                    </button>
164                    ZZZZZZZZZZ;
165            }
166        }
167        $out .= "<h1>{$termine_list_title}{$edit_admin}</h1>";
168        $details = $termin_label?->getDetails();
169        if ($details) {
170            $details_html = $this->htmlUtils()->renderMarkdown($details);
171            $details_html = $termin_label->replaceImagePaths($details_html);
172            $details_html = $termin_label->replaceFilePaths($details_html);
173            $out .= $details_html;
174        }
175
176        // -------------------------------------------------------------
177        //  VORSCHAU - LISTE
178        $inner_date_filter = $termine_utils->getSqlDateRangeFilter($valid_filter, 't');
179        $inner_sql_where = <<<ZZZZZZZZZZ
180            (t.on_off = '1')
181            AND ({$inner_date_filter})
182            ZZZZZZZZZZ;
183        $type_filter = $termine_utils->getSqlTypeFilter($valid_filter, 'c');
184        $outer_date_filter = $termine_utils->getSqlDateRangeFilter($valid_filter, 'c');
185        $outer_sql_where = "({$type_filter}) AND ({$outer_date_filter})";
186
187        $sql = <<<ZZZZZZZZZZ
188            SELECT * FROM ((
189                SELECT
190                    'termin' as item_type,
191                    t.owner_user_id as owner_user_id,
192                    t.start_date as start_date,
193                    t.start_time as start_time,
194                    t.end_date as end_date,
195                    t.end_time as end_time,
196                    t.organizer_user_id as organizer_user_id,
197                    t.title as title,
198                    t.text as text,
199                    t.id as id,
200                    (
201                        SELECT GROUP_CONCAT(l.ident ORDER BY l.position ASC SEPARATOR ' ')
202                        FROM
203                            termin_label_map tl
204                            JOIN termin_labels l ON (l.id = tl.label_id)
205                        WHERE tl.termin_id = t.id
206                        GROUP BY t.id
207                    ) as typ,
208                    t.on_off as on_off,
209                    t.newsletter as newsletter,
210                    t.latitude as latitude,
211                    t.longitude as longitude,
212                    t.solv_uid as solv_uid,
213                    t.last_modified_by_user_id as last_modified_by_user_id,
214                    t.image_ids as image_ids,
215                    t.location_id as location_id
216                FROM termine t
217                WHERE ({$inner_sql_where})
218            ) UNION ALL (
219                SELECT
220                    'deadline' as item_type,
221                    t.owner_user_id as owner_user_id,
222                    DATE(t.deadline) as start_date,
223                    TIME(t.deadline) as start_time,
224                    NULL as end_date,
225                    NULL as end_time,
226                    NULL as organizer_user_id,
227                    CONCAT('Meldeschluss für ', t.title) as title,
228                    '' as text,
229                    t.id as id,
230                    'meldeschluss' as typ,
231                    t.on_off as on_off,
232                    NULL as newsletter,
233                    NULL as latitude,
234                    NULL as longitude,
235                    t.solv_uid as solv_uid,
236                    t.last_modified_by_user_id as last_modified_by_user_id,
237                    t.image_ids as image_ids,
238                    NULL as location_id
239                FROM termine t
240                WHERE (t.deadline IS NOT NULL) AND ({$inner_sql_where})
241            )) AS c
242            WHERE ({$outer_sql_where})
243            ORDER BY c.start_date ASC
244            ZZZZZZZZZZ;
245
246        $result = $db->query($sql);
247        $today = $this->dateUtils()->getCurrentDateInFormat('Y-m-d');
248        $meldeschluss_label = new TerminLabel();
249        $meldeschluss_label->setIdent('meldeschluss');
250        $meldeschluss_label->setIcon(null);
251        $last_date = null;
252        // @phpstan-ignore-next-line
253        while ($row = $result->fetch_assoc()) {
254            $this_date = $row['start_date'];
255            // @phpstan-ignore-next-line
256            $this_month_start = $this->getMonth($this_date).'-01';
257
258            if ($today < $this_month_start && $today > $last_date) {
259                $out .= "<div class='bar today'>Heute</div>";
260            }
261            // @phpstan-ignore-next-line
262            if ($this->getMonth($this_date) !== $this->getMonth($last_date)) {
263                // @phpstan-ignore-next-line
264                $pretty_month = $this->dateUtils()->olzDate("MM jjjj", $this_date);
265                $out .= "<h3 class='bar green'>{$pretty_month}</h3>";
266            }
267            if ($today <= $this_date && $today > $last_date && $today >= $this_month_start) {
268                $out .= "<div class='bar today'>Heute</div>";
269            }
270            $labels = [$meldeschluss_label];
271            if ($row['item_type'] === 'termin') {
272                $termin = $termin_repo->findOneBy(['id' => $row['id']]);
273                $labels = [...($termin?->getLabels() ?? [])];
274            }
275
276            $out .= OlzTermineListItem::render([
277                'id' => $row['id'],
278                'owner_user_id' => $row['owner_user_id'],
279                'start_date' => $row['start_date'],
280                'start_time' => $row['start_time'],
281                'end_date' => $row['end_date'],
282                'end_time' => $row['end_time'],
283                'organizer_user_id' => $row['organizer_user_id'],
284                'title' => $row['title'],
285                'text' => $row['text'],
286                'solv_uid' => $row['solv_uid'],
287                'labels' => $labels,
288                // @phpstan-ignore-next-line
289                'image_ids' => $row['image_ids'] ? json_decode($row['image_ids'], true) : null,
290                'location_id' => $row['location_id'],
291            ]);
292
293            $last_date = $this_date;
294        }
295        if ($last_date === null) {
296            $out .= "<div class='no-entries'>Keine Einträge. Bitte Filter anpassen.</div>";
297        }
298        $out .= "</div>";
299
300        $out .= OlzFooter::render();
301
302        return $out;
303    }
304
305    protected function getMonth(?string $date): ?string {
306        if ($date === null) {
307            return null;
308        }
309        return substr($date, 0, 7);
310    }
311}