Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 147 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
| OlzAnniversaryParams | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | n/a |
0 / 0 |
|||
| OlzAnniversary | |
0.00% |
0 / 147 |
|
0.00% |
0 / 6 |
420 | |
0.00% |
0 / 1 |
| hasAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| searchSqlWhenHasAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getHtmlWhenHasAccess | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
| getRunsHtml | |
0.00% |
0 / 106 |
|
0.00% |
0 / 1 |
132 | |||
| getElevationStravaHtml | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
30 | |||
| getZielsprintHtml | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Olz\Anniversary\Components\OlzAnniversary; |
| 4 | |
| 5 | use Doctrine\Common\Collections\Criteria; |
| 6 | use Olz\Anniversary\Components\OlzAnniversaryRocket\OlzAnniversaryRocket; |
| 7 | use Olz\Components\Common\OlzEditableText\OlzEditableText; |
| 8 | use Olz\Components\Common\OlzRootComponent; |
| 9 | use Olz\Components\OlzZielsprint\OlzZielsprint; |
| 10 | use Olz\Components\Page\OlzFooter\OlzFooter; |
| 11 | use Olz\Components\Page\OlzHeader\OlzHeader; |
| 12 | use Olz\Entity\Anniversary\RunRecord; |
| 13 | use Olz\Entity\StravaLink; |
| 14 | use Olz\Repository\Snippets\PredefinedSnippet; |
| 15 | use Olz\Utils\HttpParams; |
| 16 | |
| 17 | /** @extends HttpParams<array{}> */ |
| 18 | class OlzAnniversaryParams extends HttpParams { |
| 19 | } |
| 20 | |
| 21 | /** @extends OlzRootComponent<array<string, mixed>> */ |
| 22 | class 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: "Lauf"})' |
| 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 | } |