Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 253
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
OlzTerminDetailParams
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
OlzTerminDetail
0.00% covered (danger)
0.00%
0 / 253
0.00% covered (danger)
0.00%
0 / 5
2970
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 / 35
0.00% covered (danger)
0.00%
0 / 1
6
 getHtmlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 209
0.00% covered (danger)
0.00%
0 / 1
2256
 getTerminById
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getTimeText
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Olz\Termine\Components\OlzTerminDetail;
4
5use Doctrine\Common\Collections\Criteria;
6use Olz\Components\Common\OlzLocationMap\OlzLocationMap;
7use Olz\Components\Common\OlzRootComponent;
8use Olz\Components\Page\OlzFooter\OlzFooter;
9use Olz\Components\Page\OlzHeader\OlzHeader;
10use Olz\Components\Schema\OlzEventData\OlzEventData;
11use Olz\Entity\Termine\Termin;
12use Olz\Entity\Termine\TerminLabel;
13use Olz\Termine\Components\OlzDateCalendar\OlzDateCalendar;
14use Olz\Users\Components\OlzUserInfoModal\OlzUserInfoModal;
15use Olz\Utils\HttpParams;
16
17/** @extends HttpParams<array{von?: ?string}> */
18class OlzTerminDetailParams extends HttpParams {
19}
20
21/** @extends OlzRootComponent<array<string, mixed>> */
22class OlzTerminDetail extends OlzRootComponent {
23    public function hasAccess(): bool {
24        return true;
25    }
26
27    public function searchSqlWhenHasAccess(array $terms): string|array|null {
28        $code_href = $this->envUtils()->getCodeHref();
29        $today_iso = $this->dateUtils()->getIsoToday();
30        $where = implode(' AND ', array_map(function ($term) {
31            $date_sql = '';
32            $term_date_range = $this->dateUtils()->parseDateTimeRange($term);
33            if ($term_date_range !== null) {
34                $query_start = $term_date_range['start']->format('Y-m-d');
35                $query_end = $term_date_range['end']->format('Y-m-d');
36                $date_sql = <<<ZZZZZZZZZZ
37                    OR (
38                        t.start_date < '{$query_end}'
39                        AND IFNULL(t.end_date, t.start_date) >= '{$query_start}'
40                    )
41                    ZZZZZZZZZZ;
42            }
43            return <<<ZZZZZZZZZZ
44                (
45                    t.title LIKE '%{$term}%'
46                    OR t.text LIKE '%{$term}%'
47                    OR tl.name LIKE '%{$term}%'
48                    {$date_sql}
49                )
50                ZZZZZZZZZZ;
51        }, $terms));
52        return [
53            'with' => [
54                <<<ZZZZZZZZZZ
55                    base_termine AS (
56                        SELECT
57                            CONCAT('{$code_href}termine/', t.id) AS link,
58                            '{$code_href}assets/icns/termine_type_all_20.svg' AS icon,
59                            t.start_date AS date,
60                            CONCAT('Termin: ', t.title) AS title,
61                            CONCAT(IFNULL(tl.name, ''), ' ', IFNULL(t.text, ''), ' Typ: ', (
62                                SELECT GROUP_CONCAT(l.name ORDER BY l.position ASC SEPARATOR ', ')
63                                FROM
64                                    termin_label_map tl
65                                    JOIN termin_labels l ON (l.id = tl.label_id)
66                                WHERE tl.termin_id = t.id
67                                GROUP BY t.id
68                            )) AS text,
69                            DATEDIFF(t.start_date, '{$today_iso}') AS diffdays
70                        FROM termine t LEFT JOIN termin_locations tl ON (t.location_id = tl.id)
71                        WHERE
72                            t.on_off = '1'
73                            AND {$this->termineUtils()->getIsNotArchivedSql('t')}
74                            AND {$where}
75                    )
76                    ZZZZZZZZZZ,
77            ],
78            'query' => <<<'ZZZZZZZZZZ'
79                SELECT
80                    link, icon, date, title, text,
81                    CASE
82                        WHEN diffdays < -30 THEN 0.7
83                        WHEN diffdays <= 0 THEN 1.0 + diffdays * 0.3 / 30.0
84                        WHEN diffdays < 100 THEN 1.0
85                        WHEN diffdays < 400 THEN 1.0 - (diffdays - 100) * 0.3 / 300.0
86                        ELSE 0.7
87                    END AS time_relevance
88                FROM base_termine
89                ZZZZZZZZZZ,
90        ];
91    }
92
93    public function getHtmlWhenHasAccess(mixed $args): string {
94        $this->httpUtils()->validateGetParams(OlzTerminDetailParams::class);
95
96        $code_href = $this->envUtils()->getCodeHref();
97        $code_path = $this->envUtils()->getCodePath();
98        $data_path = $this->envUtils()->getDataPath();
99        $date_utils = $this->dateUtils();
100        $today = $date_utils->getIsoToday();
101        $entityManager = $this->dbUtils()->getEntityManager();
102        $user = $this->authUtils()->getCurrentUser();
103        $id = $args['id'] ?? null;
104
105        $termin_repo = $entityManager->getRepository(Termin::class);
106        $is_not_archived = $this->termineUtils()->getIsNotArchivedCriteria();
107        $criteria = Criteria::create()
108            ->where(Criteria::expr()->andX(
109                $is_not_archived,
110                Criteria::expr()->eq('id', $id),
111                Criteria::expr()->eq('on_off', 1),
112            ))
113            ->setFirstResult(0)
114            ->setMaxResults(1)
115        ;
116        $termine = $termin_repo->matching($criteria);
117        $num_termine = $termine->count();
118        $is_archived = $num_termine !== 1;
119
120        if ($is_archived && !$this->authUtils()->hasPermission('any')) {
121            $this->httpUtils()->dieWithHttpError(404);
122            throw new \Exception('should already have failed');
123        }
124
125        $termin = $this->getTerminById($id);
126
127        if (!$termin) {
128            $this->httpUtils()->dieWithHttpError(404);
129            throw new \Exception('should already have failed');
130        }
131
132        $title = $termin->getTitle() ?? '';
133        $termin_year = $termin->getStartDate()->format('Y');
134        $this_year = $this->dateUtils()->getCurrentDateInFormat('Y');
135        $maybe_date = ($termin_year !== $this_year) ? " {$termin_year}" : '';
136        $title = "{$title}{$maybe_date}";
137        $text = $termin->getText() ?? '';
138        $out = OlzHeader::render([
139            'back_link' => "{$code_href}termine",
140            'title' => "{$title} - Termine",
141            'description' => "{$text}",
142            'norobots' => $is_archived,
143            'canonical_url' => "{$code_href}termine/{$id}",
144        ]);
145
146        $out .= <<<'ZZZZZZZZZZ'
147            <div class='content-right optional'>
148                <div style='padding:4px 3px 10px 3px;'>
149                </div>
150            </div>
151            <div class='content-middle'>
152            ZZZZZZZZZZ;
153
154        $start_date = $termin->getStartDate();
155        $end_date = $termin->getEndDate() ?? null;
156        $start_time = $termin->getStartTime() ?? null;
157        $end_time = $termin->getEndTime() ?? null;
158        $organizer = $termin->getOrganizerUser();
159        $labels = [...$termin->getLabels()];
160        $xkoord = $termin->getCoordinateX() ?? 0;
161        $ykoord = $termin->getCoordinateY() ?? 0;
162        $solv_uid = $termin->getSolvId();
163        $termin_location = $termin->getLocation();
164        $has_olz_location = ($xkoord > 0 && $ykoord > 0);
165        $has_termin_location = (
166            $termin_location
167            && $termin_location->getLatitude() > 0
168            && $termin_location->getLongitude() > 0
169        );
170        $lat = null;
171        $lng = null;
172        $location_name = null;
173        if ($has_termin_location) {
174            $lat = $termin_location->getLatitude();
175            $lng = $termin_location->getLongitude();
176            $location_name = $termin_location->getName();
177        }
178        if ($has_olz_location) {
179            $lat = $this->mapUtils()->CHtoWGSlat($xkoord, $ykoord);
180            $lng = $this->mapUtils()->CHtoWGSlng($xkoord, $ykoord);
181            $location_name = null;
182        }
183        $has_location = $has_olz_location || $has_termin_location;
184        $image_ids = $termin->getImageIds();
185
186        $out .= OlzEventData::render([
187            'name' => $title,
188            'start_date' => $date_utils->olzDate('jjjj-mm-tt', $start_date),
189            'end_date' => $end_date ? $date_utils->olzDate('jjjj-mm-tt', $end_date) : null,
190            'location' => $has_location ? [
191                'lat' => $lat,
192                'lng' => $lng,
193                'name' => $location_name,
194            ] : null,
195        ]);
196
197        $out .= "<div class='olz-termin-detail'>";
198
199        $out .= "<div class='preview'>";
200        // Bild anzeigen
201        if (count($image_ids) > 0) {
202            $out .= $this->imageUtils()->olzImage(
203                'termine',
204                $id,
205                $image_ids[0],
206                840
207            );
208        // Karte zeigen
209        } elseif ($has_location) {
210            $out .= OlzLocationMap::render([
211                'latitude' => $lat,
212                'longitude' => $lng,
213                'zoom' => 13,
214            ]);
215        }
216        // Date Calendar Icon
217        $out .= "<div class='date-calendar-container'>";
218        $out .= "<div class='date-calendars'>";
219        $out .= "<div class='date-calendar'>";
220        $out .= OlzDateCalendar::render(['date' => $start_date]);
221        $out .= $this->getTimeText($start_time) ?? '';
222        $out .= ($end_time && (!$end_date || $end_date === $start_date))
223            ? ' &ndash; '.$this->getTimeText($end_time)
224            : '';
225        $out .= "</div>";
226        $out .= "<div class='date-calendar'>";
227        $out .= ($end_date && $end_date !== $start_date)
228            ? OlzDateCalendar::render(['date' => $end_date])
229            : '';
230        $out .= ($end_time && $end_date && $end_date !== $start_date)
231            ? $this->getTimeText($end_time)
232            : '';
233        $out .= "</div>";
234        $out .= "</div>";
235        $out .= "</div>";
236
237        $out .= "</div>";
238
239        // Editing Tools
240        $is_owner = $user && intval($termin->getOwnerUser()?->getId() ?? 0) === intval($user->getId());
241        $has_termine_permissions = $this->authUtils()->hasPermission('termine');
242        $can_edit = $is_owner || $has_termine_permissions;
243        if ($can_edit) {
244            $json_id = json_encode($id);
245            $out .= <<<ZZZZZZZZZZ
246                <div>
247                    <button
248                        id='edit-termin-button'
249                        class='btn btn-primary'
250                        onclick='return olz.editTermin({$json_id})'
251                    >
252                        <img src='{$code_href}assets/icns/edit_white_16.svg' class='noborder' />
253                        Bearbeiten
254                    </button>
255                </div>
256                ZZZZZZZZZZ;
257        }
258
259        // Date & Title
260        $pretty_date = $this->dateUtils()->formatDateTimeRange(
261            $start_date->format('Y-m-d'),
262            $start_time?->format('H:i:s'),
263            $end_date?->format('Y-m-d'),
264            $end_time?->format('H:i:s'),
265            $format = 'long',
266        );
267        $maybe_solv_link = '';
268        if ($solv_uid) {
269            // SOLV-Übersicht-Link zeigen
270            $maybe_solv_link .= "<a href='https://www.o-l.ch/cgi-bin/fixtures?&mode=show&unique_id={$solv_uid}' target='_blank' class='linkol' style='margin-left: 20px; font-weight: normal;'>O-L.ch</a>\n";
271        }
272        $label_imgs = implode('', array_map(function (TerminLabel $label) use ($code_path, $code_href) {
273            $ident = $label->getIdent();
274            // TODO: Remove fallback mechanism?
275            $fallback_path = "{$code_path}assets/icns/termine_type_{$ident}_20.svg";
276            $fallback_href = is_file($fallback_path)
277                ? "{$code_href}assets/icns/termine_type_{$ident}_20.svg" : null;
278            $icon_href = $label->getIcon() ? $label->getFileHref($label->getIcon()) : $fallback_href;
279            return $icon_href ? "<img src='{$icon_href}' alt='' class='type-icon'>" : '';
280        }, $labels));
281        $out .= "<h5>{$pretty_date}{$maybe_solv_link}</h5>";
282        $out .= "<h1>{$title} {$label_imgs}</h1>";
283        if ($organizer) {
284            $pretty_organizer = OlzUserInfoModal::render(['user' => $organizer]);
285            $out .= "<div>Organisator: {$pretty_organizer}</div><br>";
286        }
287
288        // Text
289        // TODO: Temporary fix for broken Markdown
290        $text = str_replace("\n", "\n\n", $text);
291        $text = str_replace("\n\n\n\n", "\n\n", $text);
292        $text_html = $this->htmlUtils()->renderMarkdown($text, [
293            'html_input' => 'allow', // TODO: Do NOT allow!
294        ]);
295        $text_html = $termin->replaceImagePaths($text_html);
296        $text_html = $termin->replaceFilePaths($text_html);
297        if ($termin->getDeadline() && $termin->getDeadline() != "0000-00-00") {
298            $text_html .= ($text_html == "" ? "" : "<br />")."Meldeschluss: ".$date_utils->olzDate("t. MM ", $termin->getDeadline());
299        }
300        $out .= "<div>".$text_html."</div>";
301
302        // Link
303        $link = '';
304        if ($solv_uid && $start_date <= $today && !preg_match('/(Rangliste|Resultat)/', $link)) {
305            // SOLV Ranglisten-Link zeigen
306            $link .= "<div><a href='http://www.o-l.ch/cgi-bin/results?unique_id={$solv_uid}&club=zimmerberg' target='_blank' class='linkol'>Rangliste</a></div>\n";
307        }
308        $result_filename = "{$termin_year}-termine-{$id}.xml";
309        if (is_file("{$data_path}results/{$result_filename}")) {
310            // OLZ Ranglisten-Link zeigen
311            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Ranglisten</a></div>\n";
312        } elseif ($can_edit) {
313            // OLZ Rangliste-hochladen-Link zeigen
314            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Rangliste hochladen</a></div>\n";
315        }
316        if ($link == "") {
317            $link = "&nbsp;";
318        } else {
319            $link = str_replace("&", "&amp;", str_replace("&amp;", "&", $link));
320        }
321        $link = str_replace("www.solv.ch", "www.o-l.ch", $link);
322        $out .= "<div class='links'>".$link."</div>";
323
324        // Karte zeigen
325        if ($has_location) {
326            if ($location_name !== null) {
327                $location_maybe_link = $location_name;
328                if ($has_termin_location && $this->authUtils()->hasPermission('termine')) {
329                    $location_maybe_link = "<a href='{$code_href}termine/orte/{$termin_location->getId()}' class='linkmap'>{$location_name}</a>";
330                }
331                $out .= "<h3>Ort: {$location_maybe_link}</h3>";
332            } else {
333                $out .= "<h3>Ort</h3>";
334            }
335            $out .= OlzLocationMap::render([
336                'name' => $location_name ?? '',
337                'latitude' => $lat,
338                'longitude' => $lng,
339                'zoom' => 13,
340            ]);
341            if ($has_termin_location) {
342                $details = $termin_location->getDetails() ?? '';
343                $details_html = $this->htmlUtils()->renderMarkdown($details);
344                $details_html = $termin_location->replaceImagePaths($details_html);
345                $details_html = $termin_location->replaceFilePaths($details_html);
346                $out .= $details_html;
347
348                $location_image_ids = $termin_location->getImageIds();
349                if (count($location_image_ids) > 0) {
350                    $out .= "<div class='lightgallery gallery-container'>";
351                    foreach ($location_image_ids as $image_id) {
352                        $out .= "<div class='gallery-image'>";
353                        $out .= $this->imageUtils()->olzImage(
354                            'termin_locations',
355                            $termin_location->getId(),
356                            $image_id,
357                            110,
358                            'gallery[myset]'
359                        );
360                        $out .= "</div>";
361                    }
362                    $out .= "</div>";
363                }
364            }
365        }
366
367        $out .= "</div>"; // olz-termin-detail
368        $out .= "</div>"; // content-middle
369
370        $out .= OlzFooter::render();
371
372        return $out;
373    }
374
375    protected function getTerminById(int $id): ?Termin {
376        $termin_repo = $this->entityManager()->getRepository(Termin::class);
377        return $termin_repo->findOneBy([
378            'id' => $id,
379            'on_off' => 1,
380        ]);
381    }
382
383    protected function getTimeText(?\DateTime $time): ?string {
384        if (!$time || $time->format('H:i:s') === '00:00:00') {
385            return null;
386        }
387        return $time->format('H:i');
388    }
389}