Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 147
0.00% covered (danger)
0.00%
0 / 6
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 / 147
0.00% covered (danger)
0.00%
0 / 6
420
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 / 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 / 106
0.00% covered (danger)
0.00%
0 / 1
132
 getElevationStravaHtml
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
 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\Components\Common\OlzEditableText\OlzEditableText;
8use Olz\Components\Common\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        return null;
29    }
30
31    public static string $title = "🎉 20 Jahre OL Zimmerberg 🥳";
32    public static string $description = "Alle Aktivitäten und Informationen zum Jubiläumsjahr 2026.";
33
34    public function getHtmlWhenHasAccess(mixed $args): string {
35        $this->httpUtils()->validateGetParams(OlzAnniversaryParams::class);
36
37        $out = OlzHeader::render([
38            'title' => self::$title,
39            'description' => self::$description,
40            'norobots' => true,
41        ]);
42        $out .= <<<ZZZZZZZZZZ
43            <div class='content-full olz-anniversary'>
44                <h1>🎉 20 Jahre OL Zimmerberg 🥳</h1>
45                <br>
46                {$this->getRunsHtml()}
47                {$this->getElevationStravaHtml()}
48                <br>
49                {$this->getZielsprintHtml()}
50            </div>
51            ZZZZZZZZZZ;
52
53        $out .= OlzFooter::render();
54        return $out;
55    }
56
57    protected function getRunsHtml(): string {
58        $code_href = $this->envUtils()->getCodeHref();
59        $user = $this->authUtils()->getCurrentUser();
60        $out = '<h2>🏃 Höhenmeter-Challenge ⛰️</h2>';
61        $out .= OlzEditableText::render(['snippet' => PredefinedSnippet::AnniversaryHoehenmeter]);
62
63        $stats = $this->anniversaryUtils()->getElevationStats();
64        $done_wid = \number_format(max(0, $stats['completion'] * 100), 2);
65        $diff_wid = log10(abs($stats['diffDays']) + 1) * 25;
66        $pretty_sum_meters = number_format($stats['sumMeters'], 0, ".", "'");
67        $pretty_done = number_format($stats['completion'] * 100, 1, ".", "'")."%";
68        $diff_verb = $stats['diffMeters'] >= 0 ? 'sind' : 'liegen';
69        $diff_particle = $stats['diffMeters'] >= 0 ? 'voraus' : 'zurück';
70        $pretty_diff_meters = number_format(abs($stats['diffMeters']), 0, ".", "'")."m";
71        $pretty_diff_days = number_format(abs($stats['diffDays']), 1, ".", "'")." Tage";
72        $rocket = OlzAnniversaryRocket::render();
73        $out .= <<<ZZZZZZZZZZ
74            <div class='elevation-stats'>
75                <div class='done-graph'>
76                    <div class='done-range'></div>
77                    <div class='done-bar' style='width: {$done_wid}%;'></div>
78                    <div
79                        class='rocket test-flaky'
80                        style='left: {$done_wid}%;'
81                        ondblclick='olz.handleRocketClick(this, event)'
82                        ontouchstart='olz.handleRocketTap(this)'
83                    >
84                        {$rocket}
85                    </div>
86                </div>
87                <div>
88                    Wir haben zusammen <b>{$pretty_sum_meters} Höhenmeter</b> bewältigt,
89                    und damit unser Ziel zu <b>{$pretty_done}</b> erreicht.
90                </div>
91                <div class='diff-graph'>
92                    <div class='diff-range'></div>
93                    <div class='diff-bar {$stats['diffKind']}' style='width: {$diff_wid}%;'></div>
94                    <div class='marker' style='left: 12.72%;'></div>
95                    <div class='marker' style='left: 27.42%;'></div>
96                    <div class='marker' style='left: 42.47%;'></div>
97                    <div class='main marker' style='left: 50%;'></div>
98                    <div class='marker' style='left: 57.53%;'></div>
99                    <div class='marker' style='left: 72.58%;'></div>
100                    <div class='marker' style='left: 87.28%;'></div>
101                    <div class='marker-text' style='left: 12.72%;'>-1 Monat</div>
102                    <div class='marker-text' style='left: 27.42%;'>-1 Woche</div>
103                    <div class='marker-text' style='left: 42.47%;'>-1 Tag</div>
104                    <div class='marker-text' style='left: 57.53%;'>+1 Tag</div>
105                    <div class='marker-text' style='left: 72.58%;'>+1 Woche</div>
106                    <div class='marker-text' style='left: 87.28%;'>+1 Monat</div>
107                </div>
108                <div>
109                    Zurzeit {$diff_verb} wir unserem Ziel
110                    <span class='diff-meters {$stats['diffKind']}'>
111                        {$pretty_diff_meters}
112                    </span>
113                    bzw.
114                    <span class='diff-days {$stats['diffKind']}'>
115                        {$pretty_diff_days}
116                    </span>
117                    {$diff_particle}.
118                </div>
119            </div>
120            ZZZZZZZZZZ;
121
122        if (!$user) {
123            $out .= "<p>😕 Du musst <a href='#login-dialog'>eingeloggt</a> sein, um an der Höhenmeter-Challenge teilzunehmen.</p>";
124            return $out;
125        }
126
127        $out .= "<h3>Aktivitäten in den letzten 24 Stunden</h3>";
128        $out .= <<<'ZZZZZZZZZZ'
129            <div class='activities-table activities-24h'>
130                <table>
131                    <tr class='header'>
132                        <td>Datum</td>
133                        <td>Person</td>
134                        <td>Quelle</td>
135                        <td>Distanz</td>
136                        <td>Höhenmeter</td>
137                        <td>Steigung</td>
138                        <td>Art</td>
139                    </tr>
140            ZZZZZZZZZZ;
141        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
142        $iso_now = $this->dateUtils()->getIsoNow();
143        $minus_one_day = \DateInterval::createFromDateString("-24 hours");
144        $one_day_ago = (new \DateTime($iso_now))->add($minus_one_day);
145        $runs = $runs_repo->matching(Criteria::create()
146            ->where(Criteria::expr()->andX(
147                Criteria::expr()->gt('run_at', $one_day_ago),
148                Criteria::expr()->eq('on_off', 1),
149            ))
150            ->orderBy(['run_at' => 'DESC'])
151            ->setFirstResult(0)
152            ->setMaxResults(1000));
153        foreach ($runs as $run) {
154            $id = $run->getId();
155            $json_id = json_encode($id);
156            $date = $run->getRunAt()->format('d.m.Y H:i');
157            $is_counting_emoji = $run->getIsCounting() ? '✅' : '🚫';
158            $is_counting_title = $run->getIsCounting() ? 'zählt' : 'zählt nicht';
159            $name = $run->getRunnerName() ?? "?";
160            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
161            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
162            $inclination_percent = $run->getDistanceMeters()
163                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
164                : 'NaN';
165            $sport_type = $run->getSportType() ?? "?";
166            $out .= <<<ZZZZZZZZZZ
167                <tr>
168                    <td>{$date}</td>
169                    <td>{$name}</td>
170                    <td>{$source}</td>
171                    <td class='number'>{$distance_km}km</td>
172                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
173                    <td class='number'>{$inclination_percent}%</td>
174                    <td><span title='{$is_counting_title}'>{$is_counting_emoji} {$sport_type}</span></td>
175                </tr>
176                ZZZZZZZZZZ;
177        }
178        $out .= "</table></div>";
179
180        $out .= <<<ZZZZZZZZZZ
181            <h3>
182                Deine Aktivitäten (ohne Strava)
183                <button
184                    id='create-run-button'
185                    class='btn btn-secondary'
186                    onclick='return olz.initOlzEditRunModal(null, null, {sportType: &quot;Lauf&quot;})'
187                >
188                    <img src='{$code_href}assets/icns/new_white_16.svg' class='noborder' />
189                    Aktivität manuell hinzufügen
190                </button>
191            </h3>
192            ZZZZZZZZZZ;
193        $out .= <<<'ZZZZZZZZZZ'
194            <div class='activities-table activities-manual'>
195                <table>
196                    <tr class='header'>
197                        <td></td>
198                        <td>Datum</td>
199                        <td>Quelle</td>
200                        <td>Distanz</td>
201                        <td>Höhenmeter</td>
202                        <td>Steigung</td>
203                        <td>Art</td>
204                    </tr>
205            ZZZZZZZZZZ;
206        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
207        $runs = $runs_repo->findBy(
208            ['user' => $user, 'on_off' => 1],
209            ['run_at' => 'DESC'],
210        );
211        foreach ($runs as $run) {
212            $id = $run->getId();
213            $json_id = json_encode($id);
214            $date = $run->getRunAt()->format('d.m.Y H:i:s');
215            $edit_button = $run->getSource() === 'manuell' ? <<<ZZZZZZZZZZ
216                    <button
217                        id='edit-run-{$id}-button'
218                        class='btn btn-secondary-outline btn-sm edit-run-list-button'
219                        onclick='return olz.olzAnniversaryEditRun({$json_id})'
220                    >
221                        <img src='{$code_href}assets/icns/edit_16.svg' class='noborder' />
222                    </button>
223                ZZZZZZZZZZ : '';
224            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
225            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
226            $inclination_percent = $run->getDistanceMeters()
227                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
228                : 'NaN';
229            $sport_type = $run->getSportType() ?? '?';
230            $out .= <<<ZZZZZZZZZZ
231                <tr>
232                    <td>{$edit_button}</td>
233                    <td>{$date}</td>
234                    <td>{$source}</td>
235                    <td class='number'>{$distance_km}km</td>
236                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
237                    <td class='number'>{$inclination_percent}%</td>
238                    <td>{$sport_type}</td>
239                </tr>
240                ZZZZZZZZZZ;
241        }
242        $out .= "</table></div>";
243        return $out;
244    }
245
246    protected function getElevationStravaHtml(): string {
247        $user = $this->authUtils()->getCurrentUser();
248        if (!$user || !$this->authUtils()->hasPermission('anniversary', $user)) {
249            return '';
250        }
251        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
252        $strava_links = $strava_link_repo->findBy(['user' => $user]);
253        $num_strava_links = count($strava_links);
254        $redirect_url = "{$this->envUtils()->getBaseHref()}{$this->envUtils()->getCodeHref()}2026";
255        $strava_url = $this->stravaUtils()->getRegistrationUrl(['read', 'activity:read'], $redirect_url);
256        $out = "<div class='admin-only'><div class='admin-only-text'>Nur für Organisatoren sichtbar</div>";
257        if ($num_strava_links === 0) {
258            $out .= "<p>😕 Kein Strava-Konto verlinkt. <a href='{$strava_url}' class='linkext'>Jetzt mit Strava verbinden!</a></p>";
259        } else {
260            $out .= "<p>✅ Du bist mit diesen {$num_strava_links} Strava-Konten verbunden:</p><ul>";
261            foreach ($strava_links as $strava_link) {
262                $athlete_id = $strava_link->getStravaUser();
263                $athlete_url = "https://www.strava.com/athletes/{$athlete_id}";
264                $date = $strava_link->getLinkedAt()?->format('d.m.Y H:i:s');
265                $out .= "<li><a href='{$athlete_url}'>{$athlete_id}</a> (erstellt: {$date})</li>";
266            }
267            $out .= "</ul>";
268            $out .= "<p><a href='{$strava_url}'>Mit Strava verbinden</a></p>";
269        }
270        $out .= '</div>';
271        return $out;
272    }
273
274    protected function getZielsprintHtml(): string {
275        $out = '<h2>🏁 Zielsprint-Challenge 🏃</h2>';
276        $out .= OlzEditableText::render(['snippet' => PredefinedSnippet::AnniversaryZielsprint]);
277        $out .= OlzZielsprint::render();
278        return $out;
279    }
280}