Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 215
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 / 215
0.00% covered (danger)
0.00%
0 / 5
3192
0.00% covered (danger)
0.00%
0 / 1
 getSearchTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSearchResults
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getHtml
0.00% covered (danger)
0.00%
0 / 192
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\Utils\HttpParams;
15
16/** @extends HttpParams<array{filter?: ?string, von?: ?string}> */
17class OlzTerminDetailParams extends HttpParams {
18}
19
20/** @extends OlzRootComponent<array<string, mixed>> */
21class OlzTerminDetail extends OlzRootComponent {
22    public function getSearchTitle(): string {
23        return 'Termine';
24    }
25
26    public function getSearchResults(array $terms): array {
27        $results = [];
28        $code_href = $this->envUtils()->getCodeHref();
29        $termin_repo = $this->entityManager()->getRepository(Termin::class);
30        $termine = $termin_repo->search($terms);
31        foreach ($termine as $termin) {
32            $id = $termin->getId();
33            $results[] = $this->searchUtils()->getScoredSearchResult([
34                'link' => "{$code_href}termine/{$id}",
35                'icon' => "{$code_href}assets/icns/termine_type_all_20.svg",
36                'date' => $termin->getStartDate(),
37                'title' => $termin->getTitle() ?: '?',
38                'text' => $termin->getText() ?: null,
39            ], $terms);
40        }
41        return $results;
42    }
43
44    public function getHtml(mixed $args): string {
45        $params = $this->httpUtils()->validateGetParams(OlzTerminDetailParams::class);
46
47        $code_href = $this->envUtils()->getCodeHref();
48        $code_path = $this->envUtils()->getCodePath();
49        $data_path = $this->envUtils()->getDataPath();
50        $date_utils = $this->dateUtils();
51        $today = $date_utils->getIsoToday();
52        $entityManager = $this->dbUtils()->getEntityManager();
53        $user = $this->authUtils()->getCurrentUser();
54        $id = $args['id'] ?? null;
55
56        $termin_repo = $entityManager->getRepository(Termin::class);
57        $is_not_archived = $this->termineUtils()->getIsNotArchivedCriteria();
58        $criteria = Criteria::create()
59            ->where(Criteria::expr()->andX(
60                $is_not_archived,
61                Criteria::expr()->eq('id', $id),
62                Criteria::expr()->eq('on_off', 1),
63            ))
64            ->setFirstResult(0)
65            ->setMaxResults(1)
66        ;
67        $termine = $termin_repo->matching($criteria);
68        $num_termine = $termine->count();
69        $is_archived = $num_termine !== 1;
70
71        if ($is_archived && !$this->authUtils()->hasPermission('any')) {
72            $this->httpUtils()->dieWithHttpError(404);
73            throw new \Exception('should already have failed');
74        }
75
76        $termin = $this->getTerminById($id);
77
78        if (!$termin) {
79            $this->httpUtils()->dieWithHttpError(404);
80            throw new \Exception('should already have failed');
81        }
82
83        $title = $termin->getTitle() ?? '';
84        $termin_year = $termin->getStartDate()->format('Y');
85        $this_year = $this->dateUtils()->getCurrentDateInFormat('Y');
86        $maybe_date = ($termin_year !== $this_year) ? " {$termin_year}" : '';
87        $title = "{$title}{$maybe_date}";
88        $back_filter = json_decode($params['filter'] ?? 'null', true);
89        $termine_utils = $this->termineUtils()->loadTypeOptions();
90        if ($back_filter && !$termine_utils->isValidFilter($back_filter)) {
91            $valid_filter = $termine_utils->getValidFilter($back_filter);
92            $enc_json_filter = urlencode(json_encode($valid_filter) ?: '{}');
93            $this->httpUtils()->redirect("{$code_href}termine/{$id}?filter={$enc_json_filter}", 308);
94        }
95        $enc_back_filter = urlencode(json_encode($back_filter ?: $termine_utils->getDefaultFilter()) ?: '{}');
96        $out = OlzHeader::render([
97            'back_link' => "{$code_href}termine?filter={$enc_back_filter}",
98            'title' => "{$title} - Termine",
99            'description' => "Orientierungslauf-Wettkämpfe, OL-Wochen, OL-Weekends, Trainings und Vereinsanlässe der OL Zimmerberg.",
100            'norobots' => $is_archived,
101            'canonical_url' => "{$code_href}termine/{$id}",
102        ]);
103
104        $out .= <<<'ZZZZZZZZZZ'
105            <div class='content-right optional'>
106                <div style='padding:4px 3px 10px 3px;'>
107                </div>
108            </div>
109            <div class='content-middle'>
110            ZZZZZZZZZZ;
111
112        $start_date = $termin->getStartDate();
113        $end_date = $termin->getEndDate() ?? null;
114        $start_time = $termin->getStartTime() ?? null;
115        $end_time = $termin->getEndTime() ?? null;
116        $text = $termin->getText() ?? '';
117        $labels = [...$termin->getLabels()];
118        $xkoord = $termin->getCoordinateX() ?? 0;
119        $ykoord = $termin->getCoordinateY() ?? 0;
120        $solv_uid = $termin->getSolvId();
121        $termin_location = $termin->getLocation();
122        $has_olz_location = ($xkoord > 0 && $ykoord > 0);
123        $has_termin_location = (
124            $termin_location
125            && $termin_location->getLatitude() > 0
126            && $termin_location->getLongitude() > 0
127        );
128        $lat = null;
129        $lng = null;
130        $location_name = null;
131        if ($has_termin_location) {
132            $lat = $termin_location->getLatitude();
133            $lng = $termin_location->getLongitude();
134            $location_name = $termin_location->getName();
135        }
136        if ($has_olz_location) {
137            $lat = $this->mapUtils()->CHtoWGSlat($xkoord, $ykoord);
138            $lng = $this->mapUtils()->CHtoWGSlng($xkoord, $ykoord);
139            $location_name = null;
140        }
141        $has_location = $has_olz_location || $has_termin_location;
142        $image_ids = $termin->getImageIds();
143
144        $out .= OlzEventData::render([
145            'name' => $title,
146            'start_date' => $date_utils->olzDate('jjjj-mm-tt', $start_date),
147            'end_date' => $end_date ? $date_utils->olzDate('jjjj-mm-tt', $end_date) : null,
148            'location' => $has_location ? [
149                'lat' => $lat,
150                'lng' => $lng,
151                'name' => $location_name,
152            ] : null,
153        ]);
154
155        $out .= "<div class='olz-termin-detail'>";
156
157        $out .= "<div class='preview'>";
158        // Bild anzeigen
159        if (count($image_ids) > 0) {
160            $out .= $this->imageUtils()->olzImage(
161                'termine',
162                $id,
163                $image_ids[0],
164                840
165            );
166        // Karte zeigen
167        } elseif ($has_location) {
168            $out .= OlzLocationMap::render([
169                'latitude' => $lat,
170                'longitude' => $lng,
171                'zoom' => 13,
172            ]);
173        }
174        // Date Calendar Icon
175        $out .= "<div class='date-calendar-container'>";
176        $out .= "<div class='date-calendars'>";
177        $out .= "<div class='date-calendar'>";
178        $out .= OlzDateCalendar::render(['date' => $start_date]);
179        $out .= $this->getTimeText($start_time) ?? '';
180        $out .= ($end_time && (!$end_date || $end_date === $start_date))
181            ? ' &ndash; '.$this->getTimeText($end_time)
182            : '';
183        $out .= "</div>";
184        $out .= "<div class='date-calendar'>";
185        $out .= ($end_date && $end_date !== $start_date)
186            ? OlzDateCalendar::render(['date' => $end_date])
187            : '';
188        $out .= ($end_time && $end_date && $end_date !== $start_date)
189            ? $this->getTimeText($end_time)
190            : '';
191        $out .= "</div>";
192        $out .= "</div>";
193        $out .= "</div>";
194
195        $out .= "</div>";
196
197        // Editing Tools
198        $is_owner = $user && intval($termin->getOwnerUser()?->getId() ?? 0) === intval($user->getId());
199        $has_termine_permissions = $this->authUtils()->hasPermission('termine');
200        $can_edit = $is_owner || $has_termine_permissions;
201        if ($can_edit) {
202            $json_id = json_encode($id);
203            $out .= <<<ZZZZZZZZZZ
204                <div>
205                    <button
206                        id='edit-termin-button'
207                        class='btn btn-primary'
208                        onclick='return olz.editTermin({$json_id})'
209                    >
210                        <img src='{$code_href}assets/icns/edit_white_16.svg' class='noborder' />
211                        Bearbeiten
212                    </button>
213                </div>
214                ZZZZZZZZZZ;
215        }
216
217        // Date & Title
218        $pretty_date = $this->dateUtils()->formatDateTimeRange(
219            $start_date->format('Y-m-d'),
220            $start_time?->format('H:i:s'),
221            $end_date?->format('Y-m-d'),
222            $end_time?->format('H:i:s'),
223            $format = 'long',
224        );
225        $maybe_solv_link = '';
226        if ($solv_uid) {
227            // SOLV-Übersicht-Link zeigen
228            $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";
229        }
230        $label_imgs = implode('', array_map(function (TerminLabel $label) use ($code_path, $code_href) {
231            $ident = $label->getIdent();
232            // TODO: Remove fallback mechanism?
233            $fallback_path = "{$code_path}assets/icns/termine_type_{$ident}_20.svg";
234            $fallback_href = is_file($fallback_path)
235                ? "{$code_href}assets/icns/termine_type_{$ident}_20.svg" : null;
236            $icon_href = $label->getIcon() ? $label->getFileHref($label->getIcon()) : $fallback_href;
237            return $icon_href ? "<img src='{$icon_href}' alt='' class='type-icon'>" : '';
238        }, $labels));
239        $out .= "<h5>{$pretty_date}{$maybe_solv_link}</h5>";
240        $out .= "<h1>{$title} {$label_imgs}</h1>";
241
242        // Text
243        // TODO: Temporary fix for broken Markdown
244        $text = str_replace("\n", "\n\n", $text);
245        $text = str_replace("\n\n\n\n", "\n\n", $text);
246        $text_html = $this->htmlUtils()->renderMarkdown($text, [
247            'html_input' => 'allow', // TODO: Do NOT allow!
248        ]);
249        $text_html = $termin->replaceImagePaths($text_html);
250        $text_html = $termin->replaceFilePaths($text_html);
251        if ($termin->getDeadline() && $termin->getDeadline() != "0000-00-00") {
252            $text_html .= ($text_html == "" ? "" : "<br />")."Meldeschluss: ".$date_utils->olzDate("t. MM ", $termin->getDeadline());
253        }
254        $out .= "<div>".$text_html."</div>";
255
256        // Link
257        $link = '';
258        if ($solv_uid && $start_date <= $today && !preg_match('/(Rangliste|Resultat)/', $link)) {
259            // SOLV Ranglisten-Link zeigen
260            $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";
261        }
262        $result_filename = "{$termin_year}-termine-{$id}.xml";
263        if (is_file("{$data_path}results/{$result_filename}")) {
264            // OLZ Ranglisten-Link zeigen
265            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Ranglisten</a></div>\n";
266        } elseif ($can_edit) {
267            // OLZ Rangliste-hochladen-Link zeigen
268            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Rangliste hochladen</a></div>\n";
269        }
270        if ($link == "") {
271            $link = "&nbsp;";
272        } else {
273            $link = str_replace("&", "&amp;", str_replace("&amp;", "&", $link));
274        }
275        $link = str_replace("www.solv.ch", "www.o-l.ch", $link);
276        $out .= "<div class='links'>".$link."</div>";
277
278        // Karte zeigen
279        if ($has_location) {
280            if ($location_name !== null) {
281                $location_maybe_link = $location_name;
282                if ($has_termin_location) {
283                    $location_maybe_link = "<a href='{$code_href}termine/orte/{$termin_location->getId()}?filter={$enc_back_filter}&id={$id}' class='linkmap'>{$location_name}</a>";
284                }
285                $out .= "<h3>Ort: {$location_maybe_link}</h3>";
286            } else {
287                $out .= "<h3>Ort</h3>";
288            }
289            $out .= OlzLocationMap::render([
290                'name' => $location_name ?? '',
291                'latitude' => $lat,
292                'longitude' => $lng,
293                'zoom' => 13,
294            ]);
295        }
296
297        $out .= "</div>"; // olz-termin-detail
298        $out .= "</div>"; // content-middle
299
300        $out .= OlzFooter::render();
301
302        return $out;
303    }
304
305    protected function getTerminById(int $id): ?Termin {
306        $termin_repo = $this->entityManager()->getRepository(Termin::class);
307        return $termin_repo->findOneBy([
308            'id' => $id,
309            'on_off' => 1,
310        ]);
311    }
312
313    protected function getTimeText(?\DateTime $time): ?string {
314        if (!$time || $time->format('H:i:s') === '00:00:00') {
315            return null;
316        }
317        return $time->format('H:i');
318    }
319}