Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 201
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
OlzAnniversaryParams
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
OlzAnniversary
0.00% covered (danger)
0.00%
0 / 201
0.00% covered (danger)
0.00%
0 / 8
756
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 / 21
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 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getRunsHtml
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 1
156
 getElevationStravaHtml
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
90
 getZielsprintHtml
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Olz\Anniversary\Components\OlzAnniversary;
4
5use Doctrine\Common\Collections\Criteria;
6use Olz\Anniversary\Components\OlzAnniversaryRocket\OlzAnniversaryRocket;
7use Olz\Common\Components\OlzEditableText\OlzEditableText;
8use Olz\Common\Components\OlzRootComponent;
9use Olz\Components\OlzZielsprint\OlzZielsprint;
10use Olz\Components\Page\OlzFooter\OlzFooter;
11use Olz\Components\Page\OlzHeader\OlzHeader;
12use Olz\Entity\Anniversary\RunRecord;
13use Olz\Entity\StravaLink;
14use Olz\Repository\Snippets\PredefinedSnippet;
15use Olz\Utils\HttpParams;
16
17/** @extends HttpParams<array{}> */
18class OlzAnniversaryParams extends HttpParams {
19}
20
21/** @extends OlzRootComponent<array<string, mixed>> */
22class OlzAnniversary 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        $snippets_where = $this->searchUtils()->getSnippetsWhereSql([
30            PredefinedSnippet::AnniversaryHoehenmeter,
31            PredefinedSnippet::AnniversaryZielsprint,
32        ], $terms);
33        $static_page_query = $this->searchUtils()->getStaticResultQuery([
34            'link' => "{$code_href}karten",
35            'icon' => "{$code_href}assets/icns/link_map_16.svg",
36            'title' => $this->getPageTitle(),
37            'text' => $this->getPageDescription(),
38        ], $terms);
39        return [
40            'with' => $static_page_query['with'],
41            'query' => <<<ZZZZZZZZZZ
42                    SELECT
43                        '{$code_href}2026' AS link,
44                        '{$code_href}assets/icns/question_mark_20.svg' AS icon,
45                        NULL AS date,
46                        '20 Jahre OL Zimmerberg' AS title,
47                        IFNULL(text, '') AS text,
48                        1.0 AS time_relevance
49                    FROM snippets
50                    WHERE {$snippets_where}
51                UNION ALL
52                    {$static_page_query['query']}
53                ZZZZZZZZZZ,
54        ];
55    }
56
57    public function getPageTitle(): string {
58        return "🎉 20 Jahre OL Zimmerberg 🥳";
59    }
60
61    public function getPageDescription(): string {
62        return "Alle Aktivitäten und Informationen zum Jubiläumsjahr 2026.";
63    }
64
65    public function getHtmlWhenHasAccess(mixed $args): string {
66        $this->httpUtils()->validateGetParams(OlzAnniversaryParams::class);
67
68        $out = OlzHeader::render([
69            'title' => $this->getPageTitle(),
70            'description' => $this->getPageDescription(),
71            'norobots' => true,
72        ]);
73        $out .= <<<ZZZZZZZZZZ
74            <div class='content-full olz-anniversary'>
75                <h1>🎉 20 Jahre OL Zimmerberg 🥳</h1>
76                <br>
77                {$this->getRunsHtml()}
78                {$this->getElevationStravaHtml()}
79                <br>
80                {$this->getZielsprintHtml()}
81            </div>
82            ZZZZZZZZZZ;
83
84        $out .= OlzFooter::render();
85        return $out;
86    }
87
88    protected function getRunsHtml(): string {
89        $code_href = $this->envUtils()->getCodeHref();
90        $user = $this->authUtils()->getCurrentUser();
91        $out = '<h2>🏃 Höhenmeter-Challenge ⛰️</h2>';
92        $out .= OlzEditableText::render(['snippet' => PredefinedSnippet::AnniversaryHoehenmeter]);
93
94        $stats = $this->anniversaryUtils()->getElevationStats();
95        $done_wid = \number_format(max(0, $stats['completion'] * 50), 2);
96        $diff_wid = log10(abs($stats['diffDays']) + 1) * 12.5;
97        $pretty_sum_meters = number_format($stats['sumMeters'], 0, ".", "'");
98        $pretty_done = number_format($stats['completion'] * 100, 1, ".", "'")."%";
99        $diff_verb = $stats['diffMeters'] >= 0 ? 'sind' : 'liegen';
100        $diff_particle = $stats['diffMeters'] >= 0 ? 'voraus' : 'zurück';
101        $pretty_diff_meters = number_format(abs($stats['diffMeters']), 0, ".", "'")."m";
102        $pretty_diff_days = number_format(abs($stats['diffDays']), 1, ".", "'")." Tage";
103        $rocket = OlzAnniversaryRocket::render();
104        $out .= <<<ZZZZZZZZZZ
105            <div class='elevation-stats'>
106                <div class='done-graph'>
107                    <div class='done-range'></div>
108                    <div class='done-bar' style='width: {$done_wid}%;'></div>
109                    <div class='main marker' style='left: 50%;'></div>
110                    <div
111                        class='rocket test-flaky'
112                        style='left: {$done_wid}%;'
113                        ondblclick='olz.handleRocketClick(this, event)'
114                        ontouchstart='olz.handleRocketTap(this)'
115                    >
116                        {$rocket}
117                    </div>
118                </div>
119                <div>
120                    Wir haben zusammen <b>{$pretty_sum_meters} Höhenmeter</b> bewältigt,
121                    und damit unser Ziel zu <b>{$pretty_done}</b> erreicht.
122                </div>
123                <div class='diff-graph'>
124                    <div class='diff-range'></div>
125                    <div class='diff-bar {$stats['diffKind']}' style='width: {$diff_wid}%;'></div>
126                    <div class='marker' style='left: 31.36%;'></div>
127                    <div class='main marker' style='left: 50%;'></div>
128                    <div class='marker' style='left: 68.64%;'></div>
129                    <div class='marker-text' style='left: 31.36%;'>-1 Monat</div>
130                    <div class='marker-text' style='left: 68.64%;'>+1 Monat</div>
131                </div>
132                <div>
133                    Zurzeit {$diff_verb} wir unserem Ziel
134                    <span class='diff-meters {$stats['diffKind']}'>
135                        {$pretty_diff_meters}
136                    </span>
137                    bzw.
138                    <span class='diff-days {$stats['diffKind']}'>
139                        {$pretty_diff_days}
140                    </span>
141                    {$diff_particle}.
142                </div>
143            </div>
144            ZZZZZZZZZZ;
145
146        if (!$user) {
147            $out .= "<p>😕 Du musst <a href='#login-dialog'>eingeloggt</a> sein, um an der Höhenmeter-Challenge teilzunehmen.</p>";
148            return $out;
149        }
150
151        $out .= "<h3>Aktivitäten in den letzten 24 Stunden</h3>";
152        $out .= <<<'ZZZZZZZZZZ'
153            <div class='activities-table activities-24h'>
154                <table>
155                    <tr class='header'>
156                        <td>Datum</td>
157                        <td>Person</td>
158                        <td>Quelle</td>
159                        <td>Distanz</td>
160                        <td>Höhenmeter</td>
161                        <td>Steigung</td>
162                        <td>Art</td>
163                    </tr>
164            ZZZZZZZZZZ;
165        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
166        $iso_now = $this->dateUtils()->getIsoNow();
167        $minus_one_day = \DateInterval::createFromDateString("-24 hours");
168        $one_day_ago = (new \DateTime($iso_now))->add($minus_one_day);
169        $runs = $runs_repo->matching(Criteria::create()
170            ->where(Criteria::expr()->andX(
171                Criteria::expr()->gt('created_at', $one_day_ago),
172                Criteria::expr()->eq('on_off', 1),
173            ))
174            ->orderBy(['created_at' => \SortDirection::Descending])
175            ->setFirstResult(0)
176            ->setMaxResults(1000));
177        foreach ($runs as $run) {
178            $id = $run->getId();
179            $json_id = json_encode($id);
180            $date = $run->getRunAt()->format('d.m.Y H:i');
181            $is_backdated_emoji = $run->getRunAt() < $one_day_ago ? ' 🔙' : '';
182            $is_counting_emoji = $run->getIsCounting() ? '✅' : '🚫';
183            $is_counting_title = $run->getIsCounting() ? 'zählt' : 'zählt nicht';
184            $name = $run->getRunnerName();
185            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
186            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
187            $inclination_percent = $run->getDistanceMeters()
188                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
189                : 'NaN';
190            $sport_type = $run->getSportType() ?? "?";
191            $out .= <<<ZZZZZZZZZZ
192                <tr>
193                    <td>{$date}{$is_backdated_emoji}</td>
194                    <td>{$name}</td>
195                    <td>{$source}</td>
196                    <td class='number'>{$distance_km}km</td>
197                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
198                    <td class='number'>{$inclination_percent}%</td>
199                    <td><span title='{$is_counting_title}'>{$is_counting_emoji} {$sport_type}</span></td>
200                </tr>
201                ZZZZZZZZZZ;
202        }
203        $out .= "</table></div>";
204
205        $out .= <<<ZZZZZZZZZZ
206            <h3>
207                Deine Aktivitäten (ohne Strava)
208                <button
209                    id='create-run-button'
210                    class='btn btn-secondary'
211                    onclick='return olz.initOlzEditRunModal(null, null, {sportType: &quot;Lauf&quot;})'
212                >
213                    <img src='{$code_href}assets/icns/new_white_16.svg' class='noborder' />
214                    Aktivität manuell hinzufügen
215                </button>
216            </h3>
217            ZZZZZZZZZZ;
218        $out .= <<<'ZZZZZZZZZZ'
219            <div class='activities-table activities-manual'>
220                <table>
221                    <tr class='header'>
222                        <td></td>
223                        <td>Datum</td>
224                        <td>Quelle</td>
225                        <td>Distanz</td>
226                        <td>Höhenmeter</td>
227                        <td>Steigung</td>
228                        <td>Art</td>
229                    </tr>
230            ZZZZZZZZZZ;
231        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
232        $runs = $runs_repo->findBy(
233            ['user' => $user, 'on_off' => 1],
234            ['run_at' => 'DESC'],
235        );
236        foreach ($runs as $run) {
237            $id = $run->getId();
238            $json_id = json_encode($id);
239            $date = $run->getRunAt()->format('d.m.Y H:i:s');
240            $edit_button = $run->getSource() === 'manuell' ? <<<ZZZZZZZZZZ
241                    <button
242                        id='edit-run-{$id}-button'
243                        class='btn btn-secondary-outline btn-sm edit-run-list-button'
244                        onclick='return olz.olzAnniversaryEditRun({$json_id})'
245                    >
246                        <img src='{$code_href}assets/icns/edit_16.svg' class='noborder' />
247                    </button>
248                ZZZZZZZZZZ : '';
249            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
250            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
251            $inclination_percent = $run->getDistanceMeters()
252                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
253                : 'NaN';
254            $sport_type = $run->getSportType() ?? '?';
255            $out .= <<<ZZZZZZZZZZ
256                <tr>
257                    <td>{$edit_button}</td>
258                    <td>{$date}</td>
259                    <td>{$source}</td>
260                    <td class='number'>{$distance_km}km</td>
261                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
262                    <td class='number'>{$inclination_percent}%</td>
263                    <td>{$sport_type}</td>
264                </tr>
265                ZZZZZZZZZZ;
266        }
267        $out .= "</table></div>";
268        return $out;
269    }
270
271    protected function getElevationStravaHtml(): string {
272        $out = '';
273        $user = $this->authUtils()->getCurrentUser();
274        if (!$user || !$this->authUtils()->hasPermission('any', $user)) {
275            return $out;
276        }
277        $key = $this->envUtils()->getEncryptionKey('strava-hack-token');
278        $data = [
279            'user_id' => $user->getId(),
280            'token_time' => $this->dateUtils()->getIsoNow(),
281        ];
282        $token = $this->generalUtils()->encrypt($key, $data);
283        $enc_token = json_encode($token) ?: '\"\"';
284        $base_href = "{$this->envUtils()->getBaseHref()}{$this->envUtils()->getCodeHref()}";
285        $enc_base_href = json_encode($base_href) ?: '\"\"';
286        $script_href = "{$base_href}api-cors/strava_script.js";
287        $enc_script_href = json_encode($script_href) ?: '\"\"';
288        $bookmark_lines = [
289            "var OLZ_BASE_HREF={$enc_base_href}",
290            "var OLZ_TOKEN={$enc_token}",
291            "var elem=document.createElement('script')",
292            "elem.src={$enc_script_href}",
293            "elem.addEventListener(\"error\", function () { alert(\"🚫 OLZ-Strava-Skript konnte nicht geladen werden!\"); })",
294            "document.body.appendChild(elem)",
295        ];
296        $bookmark_href = htmlentities("javascript:".implode(';', $bookmark_lines));
297        $out .= <<<ZZZZZZZZZZ
298            <h3>Strava-Anleitung (nach September)</h3>
299            <div><b>Einmalig:</b></div>
300            <ul>
301                <li>Logge dich ein auf <a href='https://www.strava.com/login' target='_blank'>Strava</a> (im Browser, nicht im App).</li>
302                <li>
303                    Speichere diesen Link als Lesezeichen in deinem Browser,<br>
304                    entweder indem du ihn in die Lesezeichen-Leiste ziehst<br>
305                    oder per Rechtsklick -> "Als Lesezeichen speichern..." (oder ähnlich, je nach Browser):<br>
306                    ---&gt; <b>
307                        <a
308                            href='{$bookmark_href}'
309                            onclick='navigator.clipboard.writeText(this.href);alert(&quot;Link kopiert!&quot;);return false;'
310                        >
311                            OLZ 🔁
312                        </a>
313                    </b> &lt;---
314                    <i>(oder klicken um Link zu kopieren)</i>
315                </li>
316            </ul>
317            <div><b>Um eine Strava-Aktivität zu erfassen:</b></div>
318            <ul>
319                <li>
320                    Gehe auf Strava zu der Aktivität, die du erfassen willst,<br>
321                    z.B. via <a href='https://www.strava.com/athlete/training' target='_blank'>Meine Aktivitäten</a>, dann Aktivität auswählen.<br>
322                    <i>Tipp: Du kannst so auch Aktivitäten anderer Personen erfassen (Ehrenkodex: nur OLZ!).</i>
323                </li>
324                <li>
325                    Klicke auf das Lesezeichen, das du zuvor gespeichert hattest.<br>
326                    <i>Tipp: Du solltest eine Rückmeldung erhalten, ob es geklappt hat.</i>
327                </li>
328            </ul>
329            ZZZZZZZZZZ;
330        if (!$this->authUtils()->hasPermission('anniversary', $user)) {
331            return $out;
332        }
333        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
334        $strava_links = $strava_link_repo->findBy(['user' => $user]);
335        $num_strava_links = count($strava_links);
336        $redirect_url = "{$this->envUtils()->getBaseHref()}{$this->envUtils()->getCodeHref()}2026";
337        $strava_url = $this->stravaUtils()->getRegistrationUrl(['read', 'activity:read'], $redirect_url);
338        $out .= "<div class='admin-only'><div class='admin-only-text'>Nur für Organisatoren sichtbar</div>";
339        if ($num_strava_links === 0) {
340            $out .= "<p>😕 Kein Strava-Konto verlinkt. <a href='{$strava_url}' class='linkext'>Jetzt mit Strava verbinden!</a></p>";
341        } else {
342            $out .= "<p>✅ Du bist mit diesen {$num_strava_links} Strava-Konten verbunden:</p><ul>";
343            foreach ($strava_links as $strava_link) {
344                $athlete_id = $strava_link->getStravaUser();
345                $athlete_url = "https://www.strava.com/athletes/{$athlete_id}";
346                $date = $strava_link->getLinkedAt()?->format('d.m.Y H:i:s');
347                $out .= "<li><a href='{$athlete_url}'>{$athlete_id}</a> (erstellt: {$date})</li>";
348            }
349            $out .= "</ul>";
350            $out .= "<p><a href='{$strava_url}'>Mit Strava verbinden</a></p>";
351        }
352
353        $out .= '</div>';
354        return $out;
355    }
356
357    protected function getZielsprintHtml(): string {
358        $out = '<h2>🏁 Zielsprint-Challenge 🏃</h2>';
359        $out .= OlzEditableText::render(['snippet' => PredefinedSnippet::AnniversaryZielsprint]);
360        $out .= OlzZielsprint::render();
361        return $out;
362    }
363}