Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 152
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 / 152
0.00% covered (danger)
0.00%
0 / 6
462
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 / 111
0.00% covered (danger)
0.00%
0 / 1
156
 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(['created_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_backdated_emoji = $run->getRunAt() < $one_day_ago ? ' 🔙' : '';
158            $is_counting_emoji = $run->getIsCounting() ? '✅' : '🚫';
159            $is_counting_title = $run->getIsCounting() ? 'zählt' : 'zählt nicht';
160            $name = $run->getRunnerName() ?? "?";
161            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
162            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
163            $inclination_percent = $run->getDistanceMeters()
164                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
165                : 'NaN';
166            $sport_type = $run->getSportType() ?? "?";
167            $out .= <<<ZZZZZZZZZZ
168                <tr>
169                    <td>{$date}{$is_backdated_emoji}</td>
170                    <td>{$name}</td>
171                    <td>{$source}</td>
172                    <td class='number'>{$distance_km}km</td>
173                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
174                    <td class='number'>{$inclination_percent}%</td>
175                    <td><span title='{$is_counting_title}'>{$is_counting_emoji} {$sport_type}</span></td>
176                </tr>
177                ZZZZZZZZZZ;
178        }
179        $out .= "</table></div>";
180
181        $out .= <<<ZZZZZZZZZZ
182            <h3>
183                Deine Aktivitäten (ohne Strava)
184                <button
185                    id='create-run-button'
186                    class='btn btn-secondary'
187                    onclick='return olz.initOlzEditRunModal(null, null, {sportType: &quot;Lauf&quot;})'
188                >
189                    <img src='{$code_href}assets/icns/new_white_16.svg' class='noborder' />
190                    Aktivität manuell hinzufügen
191                </button>
192            </h3>
193            ZZZZZZZZZZ;
194        $out .= <<<'ZZZZZZZZZZ'
195            <div class='activities-table activities-manual'>
196                <table>
197                    <tr class='header'>
198                        <td></td>
199                        <td>Datum</td>
200                        <td>Quelle</td>
201                        <td>Distanz</td>
202                        <td>Höhenmeter</td>
203                        <td>Steigung</td>
204                        <td>Art</td>
205                    </tr>
206            ZZZZZZZZZZ;
207        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
208        $runs = $runs_repo->findBy(
209            ['user' => $user, 'on_off' => 1],
210            ['run_at' => 'DESC'],
211        );
212        foreach ($runs as $run) {
213            $id = $run->getId();
214            $json_id = json_encode($id);
215            $date = $run->getRunAt()->format('d.m.Y H:i:s');
216            $edit_button = $run->getSource() === 'manuell' ? <<<ZZZZZZZZZZ
217                    <button
218                        id='edit-run-{$id}-button'
219                        class='btn btn-secondary-outline btn-sm edit-run-list-button'
220                        onclick='return olz.olzAnniversaryEditRun({$json_id})'
221                    >
222                        <img src='{$code_href}assets/icns/edit_16.svg' class='noborder' />
223                    </button>
224                ZZZZZZZZZZ : '';
225            $source = $this->anniversaryUtils()->getPrettySource($run->getSource() ?? '?');
226            $distance_km = number_format($run->getDistanceMeters() / 1000, 2);
227            $inclination_percent = $run->getDistanceMeters()
228                ? number_format($run->getElevationMeters() * 100 / $run->getDistanceMeters(), 2)
229                : 'NaN';
230            $sport_type = $run->getSportType() ?? '?';
231            $out .= <<<ZZZZZZZZZZ
232                <tr>
233                    <td>{$edit_button}</td>
234                    <td>{$date}</td>
235                    <td>{$source}</td>
236                    <td class='number'>{$distance_km}km</td>
237                    <td class='number'><b>{$run->getElevationMeters()}m</b></td>
238                    <td class='number'>{$inclination_percent}%</td>
239                    <td>{$sport_type}</td>
240                </tr>
241                ZZZZZZZZZZ;
242        }
243        $out .= "</table></div>";
244        return $out;
245    }
246
247    protected function getElevationStravaHtml(): string {
248        $user = $this->authUtils()->getCurrentUser();
249        if (!$user || !$this->authUtils()->hasPermission('anniversary', $user)) {
250            return '';
251        }
252        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
253        $strava_links = $strava_link_repo->findBy(['user' => $user]);
254        $num_strava_links = count($strava_links);
255        $redirect_url = "{$this->envUtils()->getBaseHref()}{$this->envUtils()->getCodeHref()}2026";
256        $strava_url = $this->stravaUtils()->getRegistrationUrl(['read', 'activity:read'], $redirect_url);
257        $out = "<div class='admin-only'><div class='admin-only-text'>Nur für Organisatoren sichtbar</div>";
258        if ($num_strava_links === 0) {
259            $out .= "<p>😕 Kein Strava-Konto verlinkt. <a href='{$strava_url}' class='linkext'>Jetzt mit Strava verbinden!</a></p>";
260        } else {
261            $out .= "<p>✅ Du bist mit diesen {$num_strava_links} Strava-Konten verbunden:</p><ul>";
262            foreach ($strava_links as $strava_link) {
263                $athlete_id = $strava_link->getStravaUser();
264                $athlete_url = "https://www.strava.com/athletes/{$athlete_id}";
265                $date = $strava_link->getLinkedAt()?->format('d.m.Y H:i:s');
266                $out .= "<li><a href='{$athlete_url}'>{$athlete_id}</a> (erstellt: {$date})</li>";
267            }
268            $out .= "</ul>";
269            $out .= "<p><a href='{$strava_url}'>Mit Strava verbinden</a></p>";
270        }
271        $out .= '</div>';
272        return $out;
273    }
274
275    protected function getZielsprintHtml(): string {
276        $out = '<h2>🏁 Zielsprint-Challenge 🏃</h2>';
277        $out .= OlzEditableText::render(['snippet' => PredefinedSnippet::AnniversaryZielsprint]);
278        $out .= OlzZielsprint::render();
279        return $out;
280    }
281}