source: trunk/index.php

Last change on this file was 46, checked in by chronos, 2 years ago
  • Fixed: Correctly import data from leaderboard.
File size: 49.1 KB
Line 
1<?php
2
3include_once('Database.php');
4include_once('Config.php');
5include_once('Global.php');
6include_once('Run.php');
7include_once('Packages/Common/Base.php');
8include_once('Packages/Common/Locale.php');
9include_once('Packages/Common/PrefixMultiplier.php');
10
11session_start();
12
13class Application extends System
14{
15 var $NoFullPage = false;
16
17 function Run()
18 {
19 }
20}
21
22class MyApplication extends Application
23{
24 var $Config;
25 var $LeaderboardURL;
26 var $LocaleManager;
27 var $BaseURL;
28 var $LinkLocaleExceptions;
29 var $Title;
30 var $LapLength;
31 var $MoneyKm;
32 var $MaxRunnerSpeed;
33 var $MinRunnerSpeed;
34
35 function __construct()
36 {
37 //$this->LeaderboardURL = 'https://registrace.teribear.cz/Leaderboard';
38 $this->LeaderboardURL = 'https://leaderboard.teribear.cz/';
39 $this->BaseURL = '';
40 $this->LinkLocaleExceptions = array();
41 $this->Title = '';
42 $this->LapLength = 0; // km
43 $this->MoneyKm = 0; // Kč
44 $this->MaxRunnerSpeed = 20; // km/hour
45 $this->MinRunnerSpeed = 2; // km/hour
46 }
47
48 function GetLatestYear()
49 {
50 $Year = 0;
51 $DbResult = $this->Database->query('SELECT DISTINCT(Year) AS Year FROM `Runner` ORDER BY Year DESC');
52 if ($DbResult->num_rows > 0)
53 {
54 $DbRow = $DbResult->fetch_assoc();
55 $Year = $DbRow['Year'];
56 }
57 return $Year;
58 }
59
60 function YearList($Path, $SelectedYear, $Table = 'Runner', $Where = '1')
61 {
62 $Output = T('Year').': ';
63 $DbResult = $this->Database->query('SELECT DISTINCT(Year) AS Year FROM `'.$Table.'` WHERE '.$Where.' ORDER BY Year ASC');
64 while ($DbRow = $DbResult->fetch_assoc())
65 {
66 $Year = $DbRow['Year'];
67 $Item = '<a href="'.$this->Link($Path.$Year.'/').'">'.$Year.'</a>';
68 if ($SelectedYear == $Year) $Item = '<strong>'.$Item.'</strong>';
69 $Output .= $Item.' ';
70 }
71 return $Output;
72 }
73
74 function ItemsYearList($Path, $SelectedId, $Table = 'Runner', $Where = '1')
75 {
76 $Output = T('Year').': ';
77 $DbResult = $this->Database->query('SELECT T1.Id AS Id, T2.Year AS Year FROM (SELECT DISTINCT(Id) AS Id FROM `'.$Table.'` WHERE '.$Where.' ORDER BY Year ASC) AS T1 '.
78 'LEFT JOIN '.$Table.' AS T2 ON T1.Id=T2.Id');
79 while ($DbRow = $DbResult->fetch_assoc())
80 {
81 $Item = '<a href="'.$this->Link($Path.$DbRow['Id'].'/').'">'.$DbRow['Year'].'</a>';
82 if ($SelectedId == $DbRow['Id']) $Item = '<strong>'.$Item.'</strong>';
83 $Output .= $Item.' ';
84 }
85 return $Output;
86 }
87
88 function ShowMenu()
89 {
90 $Output = '<div>'.
91 '<a href="'.$this->Link('/').'">'.T('Summary').'</a> '.
92 '<a href="'.$this->Link('/runners/').'">'.T('Runners').'</a> '.
93 '<a href="'.$this->Link('/teams/').'">'.T('Teams').'</a> '.
94 '<a href="'.$this->Link('/families/').'">'.T('Families').'</a> '.
95 $this->ShowLocaleSelector().
96 '</div>';
97 return $Output;
98 }
99
100 function ShowLocaleSelector()
101 {
102 //$Output .= ' <form action="?setlocale" method="get">';
103 $Output = ' <select onchange="window.location=this.value">';
104 foreach ($this->LocaleManager->Available as $Locale)
105 {
106 $Remaining = substr($_SERVER["REQUEST_URI"], strlen($this->BaseURL));
107 if (substr($Remaining, 1, strlen($Locale['Code'].'/')) == $this->LocaleManager->LangCode.'/')
108 $Remaining = substr($Remaining, strlen('/'.$Locale['Code']));
109 if ($Locale['Code'] == $this->LocaleManager->CurrentLocale->Texts->Code) $Selected = ' selected="selected"';
110 else $Selected = '';
111 $Remaining = $this->TranslateReverseURL($Remaining, $this->LocaleManager->LangCode);
112
113 $URL = $this->LinkLocale($Remaining, $Locale['Code']);
114 $Output .= '<option title="" value="'.$URL.'"'.$Selected.' onchange="this.form.submit()">'.$Locale['Title'].'</option>';
115 }
116 $Output .= '</select><noscript><span><input type="submit" value="Submit"/></span></noscript>';
117
118 return $Output;
119 }
120
121 function ProcessURL()
122 {
123 if (array_key_exists('REDIRECT_QUERY_STRING', $_SERVER))
124 $PathString = $_SERVER['REDIRECT_QUERY_STRING'];
125 else $PathString = '';
126 if (substr($PathString, -1, 1) == '/') $PathString = substr($PathString, 0, -1);
127 $PathItems = explode('/', $PathString);
128 if (array_key_exists('REQUEST_URI', $_SERVER) and (strpos($_SERVER['REQUEST_URI'], '?') !== false))
129 $_SERVER['QUERY_STRING'] = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], '?') + 1);
130 else $_SERVER['QUERY_STRING'] = '';
131 parse_str($_SERVER['QUERY_STRING'], $_GET);
132 return $PathItems;
133 }
134
135 // Query: text from name
136 // Page: index of page, one page is 30 items
137 // Category: '', all, men, women, kids, families, teams
138 // TeamId: id of team
139 // Count: number of items per page, default 30
140 function QueryRunners($Category, $Page = 0, $TeamId = null, $Query = null, $StartNumber = null, $Count = null)
141 {
142 $URL = $this->LeaderboardURL.'/Home/GetRunners?category='.$Category;
143 if ($Page != 0) $URL .= '&page='.$Page;
144 if ($TeamId != null) $URL .= '&teamId='.$TeamId;
145 if ($Count != null) $URL .= '&count='.$Count;
146 if ($Query != null) $URL .= '&query='.$Query;
147 if ($StartNumber != null) $URL .= '&startNumber='.$StartNumber;
148 $Content = file_get_contents($URL);
149 if (($Content !== false) and !empty($Content))
150 {
151 $JSON = json_decode($Content, true);
152 } else
153 {
154 $JSON = null;
155 echo('Cannot load data from remote server ('.$URL.').<br/>'."\n");
156 }
157 return $JSON;
158 }
159
160 function QueryRunnersAll($Category = 'all', $Count = 0)
161 {
162 $ItemsPerPage = 30;
163 $MaxCount = 250 * 30;
164 if ($Count > 0) $MaxCount = $Count;
165 $Result = array();
166 $I = 0;
167 while (true)
168 {
169 $Page = $I * floor($MaxCount / $ItemsPerPage);
170 $JSON = $this->QueryRunners($Category, $Page, null, null, null, $MaxCount);
171 if ($JSON != null)
172 {
173 foreach ($JSON['items'] as $Item)
174 {
175 // Use RegistrationNumber as ChipNumber.
176 $Item['ChipNumber'] = $Item['RegistrationNumber'] * 1;
177 unset($Item['RegistrationNumber']);
178 if ($Item['Name'] != null)
179 {
180 // Normalize names
181 $Item['Name'] = trim($Item['Name']);
182 while (strpos($Item['Name'], ' ') !== false)
183 {
184 $Item['Name'] = str_replace(' ', ' ', $Item['Name']);
185 }
186 }
187 $Result[] = $Item;
188 }
189 if (($JSON['last'] == 'true') or (count($JSON) == 0)) break;
190 if ($I > 30) break; // Safe limit if last would not work
191 $I++;
192 } else break;
193 }
194 return $Result;
195 }
196
197 function ShowEmpty()
198 {
199 $Output = '';
200
201 $this->Database->query('DELETE FROM RunnerStat');
202 $this->Database->query('DELETE FROM Runner');
203 $this->Database->query('DELETE FROM TeamStat');
204 $this->Database->query('DELETE FROM Team');
205 $this->Database->query('DELETE FROM Import');
206
207 // Find duplicates
208 /*
209 // ALTER TABLE `RunnerStat` ADD `Prev` INT NULL AFTER `Money`;
210 // UPDATE RunnerStat AS A SET Prev = (SELECT Id FROM RunnerStat AS B WHERE A.Runner = B.Runner AND B.Id < A.Id ORDER BY Id DESC LIMIT 1)
211 // SELECT * FROM `RunnerStat` AS A LEFT JOIN RunnerStat AS B ON B.ID=A.Prev WHERE A.Distance=B.Distance
212
213 $Table = 'Runner';
214 $TableStat = $Table.'Stat';
215 //$Queries = array();
216 $Output .= 'DELETE FROM RunnerStat WHERE Id IN (';
217 $DbResult = $this->Database->query('SELECT A.Id FROM `RunnerStat` AS A LEFT JOIN RunnerStat AS B ON B.ID=A.Prev WHERE A.Distance=B.Distance');
218 while ($Item = $DbResult->fetch_assoc())
219 {
220 $Output .= $Item[Id].', ';
221 }
222 $Output .= ');<br/>';
223
224 */
225
226 /*
227 // ALTER TABLE `TeamStat` ADD `Prev` INT NULL AFTER `Money`;
228 // UPDATE TeamStat AS A SET Prev = (SELECT Id FROM TeamStat AS B WHERE A.Team = B.Team AND B.Id < A.Id ORDER BY Id DESC LIMIT 1)
229 // SELECT * FROM `TeamStat` AS A LEFT JOIN TeamStat AS B ON B.ID=A.Prev WHERE A.Distance=B.Distance
230
231 $Table = 'Team';
232 $TableStat = $Table.'Stat';
233 //$Queries = array();
234 $Output .= 'DELETE FROM TeamStat WHERE Id IN (';
235 $DbResult = $this->Database->query('SELECT A.Id FROM `TeamStat` AS A LEFT JOIN TeamStat AS B ON B.ID=A.Prev WHERE A.Distance=B.Distance');
236 while ($Item = $DbResult->fetch_assoc())
237 {
238 $Output .= $Item[Id].', ';
239 }
240 $Output .= ');<br/>';
241 */
242/*
243 $I = 0;
244 $DbResult2 = $this->Database->query('SELECT * FROM Runner WHERE Year=2020');
245 while ($Runner = $DbResult2->fetch_assoc())
246 {
247 $Output .= $Runner['Id'].' '.$Runner['Name'].'<br/>';
248 $Where = '('.$TableStat.'.Runner='.$Runner['Id'].')';
249 $DbResult = $this->Database->query('SELECT *'.
250 ', (SELECT '.$TableStat.'.Distance - B.Distance FROM '.$TableStat.' AS B WHERE (B.Id < '.$TableStat.'.Id) AND (B.'.$Table.' = '.$TableStat.'.'.$Table.') ORDER BY B.Id DESC LIMIT 1) AS Length'.
251 ' FROM '.$TableStat.
252 ' WHERE '.$Where);
253 while ($Item = $DbResult->fetch_assoc())
254 {
255 if (($Item['Length'] != null) and ($Item['Length'] == 0))
256 {
257 $Output .= ' '.$Item['Id'].' '.$Item['Time'].' '.$Item['Distance'].' '.$Item['Length'].'<br/>';
258 $Queries[] = 'DELETE FROM RunnerStat WHERE Id='.$Item[Id];
259 }
260 }
261 flush();
262 $I++;
263 if ($I > 500) break;
264 }
265 echo('START TRANSACTION;<br/>');
266 foreach ($Queries as $Query)
267 {
268 echo($Query.";<br/>");
269 }
270 echo('COMMIT;<br/>');
271*/
272 return $Output;
273 }
274
275 function Sync($Items, $Time)
276 {
277 $Year = date("Y", $Time);
278
279 // Load all runners
280 $DbResult = $this->Database->query('SELECT MAX(Id) AS Id FROM Runner');
281 $DbRow = $DbResult->fetch_assoc();
282 $NextRunnerId = $DbRow['Id'] + 1;
283
284 $Runners = array();
285 $DbResult = $this->Database->query('SELECT Runner.Id, Runner.ChipNumber, Runner.Team, '.
286 '(SELECT RunnerStat.Distance FROM RunnerStat WHERE (RunnerStat.Runner = Runner.Id) ORDER BY RunnerStat.Id DESC LIMIT 1) AS Distance '.
287 'FROM Runner WHERE Year='.$Year);
288 while ($DbRow = $DbResult->fetch_assoc())
289 {
290 $Runners[$DbRow['ChipNumber']] = $DbRow;
291 }
292
293 // Load all teams
294 $DbResult = $this->Database->query('SELECT MAX(Id) AS Id FROM Team');
295 $DbRow = $DbResult->fetch_assoc();
296 $NextTeamId = $DbRow['Id'] + 1;
297
298 $Teams = array();
299 $DbResult = $this->Database->query('SELECT Team.Id, Team.WebId, Team.Name, '.
300 '(SELECT TeamStat.Distance FROM TeamStat WHERE (TeamStat.Team = Team.Id) ORDER BY TeamStat.Id DESC LIMIT 1) AS Distance '.
301 'FROM Team WHERE Year='.$Year);
302 while ($DbRow = $DbResult->fetch_assoc())
303 {
304 $Teams[$DbRow['WebId']] = $DbRow;
305 }
306
307 $Queries = array();
308 foreach ($Items as $Item)
309 {
310 if (($Item['Type'] == 'child') or ($Item['Type'] == 'woman') or ($Item['Type'] == 'man'))
311 {
312 if (!array_key_exists($Item['ChipNumber'], $Runners))
313 {
314 if ($Item['TeamId'] == null)
315 {
316 $TeamId = null;
317 } else
318 {
319 if (!array_key_exists($Item['TeamId'], $Teams))
320 {
321 $TeamId = $NextTeamId;
322 $Queries[] = $this->Database->GetInsert('Team', array(
323 'Id' => $TeamId,
324 'Name' => '',
325 'WebId' => $Item['TeamId'],
326 'Year' => $Year,
327 'IsFamily' => 0,
328 ));
329 $Teams[$Item['TeamId']] = array('Id' => $TeamId, 'Distance' => -1);
330 $NextTeamId++;
331 } else
332 $TeamId = $Teams[$Item['TeamId']]['Id'];
333 }
334
335 $Gender = 0;
336 if ($Item['Type'] == 'man') $Gender = 1;
337 if ($Item['Type'] == 'woman') $Gender = 2;
338 if ($Item['Type'] == 'child') $Gender = 3;
339 $RunnerId = $NextRunnerId;
340 $Queries[] = $this->Database->GetInsert('Runner', array(
341 'Id' => $RunnerId,
342 'Name' => $Item['Name'],
343 'Gender' => $Gender,
344 'Team' => $TeamId,
345 'ChipNumber' => $Item['ChipNumber'],
346 'Year' => $Year,
347 ));
348 $Runners[$Item['ChipNumber']] = array('Id' => $RunnerId, 'Distance' => -1, 'Team' => $TeamId);
349 $NextRunnerId++;
350 } else
351 {
352 $RunnerId = $Runners[$Item['ChipNumber']]['Id'];
353
354 // Update runner team if it was changed
355 $OldRunnerTeamId = $Runners[$Item['ChipNumber']]['Team'];
356 if ($Item['TeamId'] == null)
357 {
358 $NewRunnerTeamId = null;
359 } else
360 {
361 if (!array_key_exists($Item['TeamId'], $Teams))
362 {
363 $NewRunnerTeamId = $NextTeamId;
364 $Queries[] = $this->Database->GetInsert('Team', array(
365 'Id' => $NewRunnerTeamId,
366 'Name' => '',
367 'WebId' => $Item['TeamId'],
368 'Year' => $Year,
369 'IsFamily' => 0,
370 ));
371 $Teams[$Item['TeamId']] = array('Id' => $NewRunnerTeamId, 'Distance' => -1);
372 $NextTeamId++;
373 } else
374 {
375 $NewRunnerTeamId = $Teams[$Item['TeamId']]['Id'];
376 }
377 }
378 if ($OldRunnerTeamId != $NewRunnerTeamId)
379 {
380 $Runners[$Item['ChipNumber']]['Team'] = $NewRunnerTeamId;
381 $Queries[] = $this->Database->GetUpdate('Runner', 'Id='.$RunnerId, array('Team' => $NewRunnerTeamId));
382 }
383 }
384
385 if ($Runners[$Item['ChipNumber']]['Distance'] != $Item['OverallDistance'])
386 {
387 $Queries[] = $this->Database->GetInsert('RunnerStat', array(
388 'Time' => TimeToMysqlDateTime($Time),
389 'Runner' => $RunnerId,
390 'Distance' => $Item['OverallDistance'],
391 'Rank' => $Item['Pos'],
392 'Money' => $Item['Money'],
393 ));
394 }
395 } else
396 if (($Item['Type'] == 'team') or ($Item['Type'] == 'rodina'))
397 {
398 if ($Item['Name'] == null) $Item['Name'] = '';
399 if ($Item['Type'] == 'rodina') $IsFamily = 1;
400 else $IsFamily = 0;
401 if (!array_key_exists($Item['GroupId'], $Teams))
402 {
403 $Queries[] = $this->Database->GetInsert('Team', array(
404 'Id' => $NextTeamId,
405 'Name' => $Item['Name'],
406 'WebId' => $Item['GroupId'],
407 'IsFamily' => $IsFamily,
408 'Year' => $Year,
409 ));
410 $TeamId = $NextTeamId;
411 $Teams[$Item['GroupId']] = array('Id' => $NextTeamId, 'Distance' => -1);
412 $NextTeamId++;
413 } else
414 $TeamId = $Teams[$Item['GroupId']]['Id'];
415
416 // Update missing team names
417 if (!array_key_exists('Name', $Teams[$Item['GroupId']]) or $Teams[$Item['GroupId']]['Name'] == "")
418 {
419 $Queries[] = $this->Database->GetUpdate('Team', 'Id='.$TeamId, array(
420 'Name' => $Item['Name'],
421 'IsFamily' => $IsFamily
422 ));
423 $Teams[$Item['GroupId']]['Name'] = $Item['Name'];
424 $Teams[$Item['GroupId']]['IsFamily'] = $IsFamily;
425 }
426
427 if ($Teams[$Item['GroupId']]['Distance'] != $Item['OverallDistance'])
428 {
429 $Queries[] = $this->Database->GetInsert('TeamStat', array(
430 'Time' => TimeToMysqlDateTime($Time),
431 'Team' => $TeamId,
432 'Distance' => $Item['OverallDistance'],
433 'Rank' => $Item['Pos'],
434 'Money' => $Item['Money'],
435 ));
436 }
437 } else
438 if ($Item['Type'] == '')
439 {
440 // Skip empty type
441 } else
442 {
443 echo(T('Unsupported type').' "'.$Item['Type'].'".<br/>');
444 }
445 }
446 //print_r($Queries);
447 //foreach ($Queries as $Query) $this->Database->query($Query);
448 $this->Database->Transaction($Queries);
449 }
450
451 function ShowSync()
452 {
453 $Time = time();
454 $Items = $this->QueryRunnersAll('all');
455 $Output = T('Loaded items count: '.count($Items).'<br/>');
456 $ItemsWithoutProgress = array();
457 foreach ($Items as $Item)
458 {
459 unset($Item['Progress']);
460 $ItemsWithoutProgress[] = $Item;
461 }
462 $Hash = md5(serialize($ItemsWithoutProgress));
463
464 $DbResult = $this->Database->query('SELECT * FROM Import ORDER BY Time DESC LIMIT 1');
465 if ($DbResult->num_rows > 0)
466 {
467 $Import = $DbResult->fetch_assoc();
468 } else $Import = array('Hash' => '');
469 if ($Import['Hash'] != $Hash)
470 {
471 $this->Sync($Items, $Time);
472 $this->Database->insert('Import', array(
473 'Time' => TimeToMysqlDateTime($Time),
474 'Hash' => $Hash,
475 'ItemCount' => count($Items)
476 ));
477 }
478 $Output .= T('Data synchronization from leaderboard finished.<br/>');
479 return $Output;
480 }
481
482 function ShowTeams()
483 {
484 return $this->ShowTeamsInternal(T('Teams'), T('Team'), 'teams', 'team', 'Team', '(IsFamily=0)');
485 }
486
487 function ShowFamilies()
488 {
489 return $this->ShowTeamsInternal(T('Families'), T('Family'), 'families', 'family', 'Team', '(IsFamily=1)');
490 }
491
492 function ShowTeamsInternal($Title, $TitleItem, $UrlDir, $UrlDirItem, $Table, $Where = '1')
493 {
494 $Output = '';
495 $this->Title = $Title.' - '.$this->Title;
496 $Output .= '<div class="page-title">'.$Title.'</div>';
497 $Year = $this->GetYear();
498
499 $Where .= ' AND (Year='.$Year.') AND (Hidden=0)';
500 if (array_key_exists('query', $_GET) and ($_GET['query'] != ''))
501 {
502 $Where .= ' AND (Name LIKE "%'.addslashes($_GET['query']).'%")';
503 }
504 $Output .= '<div class="section-title">'.$this->YearList('/'.$UrlDir.'/', $Year, $Table).' '.T('Name').': '.$this->ShowSearch().'</div>';
505
506 $DbResult = $this->Database->query('SELECT COUNT(*) FROM `'.$Table.'` WHERE '.$Where);
507 $DbRow = $DbResult->fetch_row();
508 $PageList = GetPageList($DbRow[0]);
509
510 $Output .= '<div id="list_content">';
511 $Output .= $PageList['Output'];
512 $TableColumns = array(
513 array('Name' => 'Name', 'Title' => T('Name')),
514 array('Name' => 'RunnersCount', 'Title' => T('Runners')),
515 array('Name' => 'Distance', 'Title' => T('Distance')),
516 array('Name' => 'Money', 'Title' => T('Money')),
517 array('Name' => 'DistanceRunner', 'Title' => T('Distance per runner')),
518 array('Name' => 'MoneyRunner', 'Title' => T('Money per runner')),
519 array('Name' => 'Rank', 'Title' => T('Rank')),
520 );
521 $Order = GetOrderTableHeader($TableColumns, 'Distance', 1);
522 $Output .= '<table class="WideTable">';
523 $Output .= $Order['Output'];
524 $DbResult = $this->Database->select('Team', '*, '.
525 '(SELECT COUNT(*) FROM Runner WHERE Runner.Team=Team.Id) AS RunnersCount, '.
526 '(SELECT TeamStat.Distance FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY TeamStat.Time DESC LIMIT 1) AS Distance, '.
527 '(SELECT TeamStat.Money FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) AS Money, '.
528 '(SELECT TeamStat.Time FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) AS Time, '.
529 '(SELECT TeamStat.Rank FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) AS Rank, '.
530 'ROUND((SELECT TeamStat.Distance FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY TeamStat.Time DESC LIMIT 1) / (SELECT COUNT(*) FROM Runner WHERE Runner.Team=Team.Id), 1) AS DistanceRunner, '.
531 'ROUND((SELECT TeamStat.Money FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) / (SELECT COUNT(*) FROM Runner WHERE Runner.Team=Team.Id)) AS MoneyRunner',
532 $Where.$Order['SQL'].$PageList['SQLLimit']);
533 while ($Item = $DbResult->fetch_assoc())
534 {
535 if ($Item['Name'] == '') $Item['Name'] = $TitleItem.' '.$Item['WebId'];
536 $Output .= '<tr>'.
537 '<td><a href="'.$this->Link('/'.$UrlDirItem.'/'.$Item['Id'].'/').'">'.$Item['Name'].'</a></td>'.
538 '<td>'.$Item['RunnersCount'].'</td>'.
539 '<td>'.$Item['Distance'].'</td>'.
540 '<td>'.$Item['Money'].'</td>'.
541 '<td>'.$Item['DistanceRunner'].'</td>'.
542 '<td>'.$Item['MoneyRunner'].'</td>'.
543 '<td>'.$Item['Rank'].'</td>'.
544 '</tr>';
545 }
546 $Output .= '</table>';
547 $Output .= $PageList['Output'];
548 $Output .= '</div>';
549
550 return $Output;
551 }
552
553 function RunningState($Time)
554 {
555 $Output = HumanDateTime($Time);
556 if ($Time > time() - 30 * 60)
557 $Output = '<span style="color: green; font-weight: bold; ">'.$Output.'</span>';
558 return $Output;
559 }
560
561 function ShowTeam()
562 {
563 return $this->ShowTeamInternal(T('Team'), 'team');
564 }
565
566 function ShowFamily()
567 {
568 return $this->ShowTeamInternal(T('Family'), 'family');
569 }
570
571 function ShowTeamInternal($Title, $UrlDir)
572 {
573 $Output = '';
574
575 $TeamId = 0;
576 if ((count($this->PathItems) > 0) and ($this->PathItems[count($this->PathItems) - 1] != ''))
577 $TeamId = $this->PathItems[count($this->PathItems) - 1];
578 if (!is_numeric($TeamId)) die(T('Team id needs to be numeric'));
579
580 $DbResult = $this->Database->query('SELECT Team.*, '.
581 '(SELECT COUNT(*) FROM Runner WHERE (Runner.Team=Team.Id) AND (Runner.Hidden=0)) AS RunnerCount, '.
582 '(SELECT TeamStat.Distance FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY TeamStat.Time DESC LIMIT 1) AS Distance, '.
583 '(SELECT TeamStat.Money FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) AS Money, '.
584 '(SELECT TeamStat.Rank FROM TeamStat WHERE TeamStat.Team=Team.Id ORDER BY Time DESC LIMIT 1) AS Rank '.
585 'FROM Team WHERE (Hidden=0) AND (Id='.$TeamId.')');
586 if ($DbResult->num_rows > 0)
587 {
588 $DbRow = $DbResult->fetch_assoc();
589 $this->Title = $Title.' '.$DbRow['Name'].' - '.$this->Title;
590 $Output .= '<div class="page-title">'.$Title.' '.$DbRow['Name'].'</div>';
591 $Output .= '<div class="section-title">'.$this->ItemsYearList('/'.$UrlDir.'/', $TeamId, 'Team', 'Name="'.$this->Database->real_escape_string($DbRow['Name']).'"').'</div>';
592 $this->LoadYearParameters($DbRow['Year']);
593 $Output .= '<div class="section-title">'.
594 T('Runners').': '.$DbRow['RunnerCount'].', '.
595 T('Distance').': '.$DbRow['Distance'].' km, '.
596 T('Money').': '.$DbRow['Money'].' Kč, '.
597 T('Rank').': '.$DbRow['Rank'].'</div>';
598
599 $Where = '(Hidden=0) AND (Team='.$TeamId.')';
600
601 // Show runners
602 $DbResult = $this->Database->query('SELECT COUNT(*) FROM `Runner` WHERE '.$Where);
603 $DbRow = $DbResult->fetch_row();
604 $PageList = GetPageList($DbRow[0]);
605
606 $Gender = array('', T('Man'), T('Woman'), T('Kid'));
607 $Output .= '<div id="list_content">';
608 $Output .= $PageList['Output'];
609 $TableColumns = array(
610 array('Name' => 'Name', 'Title' => T('Name')),
611 array('Name' => 'Gender', 'Title' => T('Category')),
612 array('Name' => 'Distance', 'Title' => T('Distance')),
613 array('Name' => 'Money', 'Title' => T('Money')),
614 array('Name' => 'Rank', 'Title' => T('Rank')),
615 array('Name' => 'Time', 'Title' => T('Last change')),
616 );
617 $Order = GetOrderTableHeader($TableColumns, 'Distance', 1);
618 $Output .= '<table class="WideTable">';
619 $Output .= $Order['Output'];
620 $DbResult = $this->Database->select('Runner', '*, '.
621 '(SELECT RunnerStat.Distance FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Distance'.
622 ', (SELECT RunnerStat.Money FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Money'.
623 ', (SELECT RunnerStat.Time FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Time'.
624 ', (SELECT RunnerStat.Rank FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Rank',
625 $Where.$Order['SQL'].$PageList['SQLLimit']);
626 while ($Item = $DbResult->fetch_assoc())
627 {
628 $Output .= '<tr>'.
629 '<td><a href="'.$this->Link('/runner/'.$Item['Id'].'/').'">'.$Item['Name'].'</a></td>'.
630 '<td>'.$Gender[$Item['Gender']].'</td>'.
631 '<td>'.$Item['Distance'].'</td>'.
632 '<td>'.$Item['Money'].'</td>'.
633 '<td>'.$Item['Rank'].'</td>'.
634 '<td>'.$this->RunningState(MysqlDateTimeToTime($Item['Time'])).'</td>'.
635 '</tr>';
636 }
637 $Output .= '</table>';
638 $Output .= $PageList['Output'];
639 $Output .= '</div><br/>';
640
641 $Output .= $this->ShowDetailed('Team', $TeamId);
642 //$Output .= $this->ShowDetailedChart('Team', $TeamId);
643 $Output .= $this->ShowDaily('Team', $TeamId);
644 //$Output .= $this->ShowDailyChart('Team', $TeamId);
645 } else $Output .= T('Team not found.');
646 return $Output;
647 }
648
649 function ShowDetailed($Table, $Id)
650 {
651 $PrefixMultiplier = new PrefixMultiplier();
652
653 $TableStat = $Table.'Stat';
654 $Output = '<div class="section-title">'.T('Lap progress').'</div>';
655 $Where = $TableStat.'.'.$Table.'='.$Id;
656 $DbResult = $this->Database->query('SELECT COUNT(*) FROM '.$TableStat.' WHERE '.$Where);
657 $DbRow = $DbResult->fetch_row();
658 $PageList = GetPageList($DbRow[0]);
659
660 $Output .= '<div id="list_content">';
661 $Output .= $PageList['Output'];
662 $TableColumns = array(
663 array('Name' => 'Time', 'Title' => T('Time')),
664 array('Name' => 'Distance', 'Title' => T('Distance').' [km]'),
665 array('Name' => 'Money', 'Title' => T('Money').' [Kč]'),
666 array('Name' => 'Rank', 'Title' => T('Rank')),
667 array('Name' => 'Duration', 'Title' => T('Duration')),
668 array('Name' => 'Length', 'Title' => T('Length').' [km]'),
669 array('Name' => 'Speed', 'Title' => T('Speed').' [km/'.T('hour').']'),
670 );
671 $Order = GetOrderTableHeader($TableColumns, 'Time', 1);
672 $Output .= '<table class="WideTable">';
673 $Output .= $Order['Output'];
674 $DbResult = $this->Database->query('SELECT *'.
675 ', (SELECT '.$TableStat.'.Distance - B.Distance FROM '.$TableStat.' AS B WHERE (B.Time < '.$TableStat.'.Time) AND (B.'.$Table.' = '.$TableStat.'.'.$Table.') ORDER BY B.Time DESC LIMIT 1) AS Length'.
676 ', (SELECT TIME_TO_SEC(TIMEDIFF('.$TableStat.'.Time, B.Time)) FROM '.$TableStat.' AS B WHERE (B.Time < '.$TableStat.'.Time) AND (B.'.$Table.' = '.$TableStat.'.'.$Table.') ORDER BY B.Time DESC LIMIT 1) AS Duration'.
677 ', NULL AS Speed'.
678 ' FROM '.$TableStat.
679 ' WHERE '.$Where.$Order['SQL'].$PageList['SQLLimit']);
680 while ($Item = $DbResult->fetch_assoc())
681 {
682 $Output .= '<tr>'.
683 '<td>'.HumanDateTime(MysqlDateTimeToTime($Item['Time'])).'</td>'.
684 '<td>'.$Item['Distance'].'</td>'.
685 '<td>'.$Item['Money'].'</td>'.
686 '<td>'.$Item['Rank'].'</td>';
687 if (($Item['Duration'] != null) and ($Item['Duration'] < $Item['Length'] / $this->MinRunnerSpeed * 3600)) $Output .= '<td>'.$PrefixMultiplier->Add($Item['Duration'], '', 4, 'Time').'</td>';
688 else $Output .= '<td>&nbsp;</td>';
689 $Output .= '<td>'.$Item['Length'].'</td>';
690
691 if (($Item['Duration'] > 0) and ($Item['Duration'] < $Item['Length'] * 3600 / $this->MinRunnerSpeed))
692 {
693 $Speed = $Item['Length'] / $Item['Duration'] * 3600;
694 $Output .= '<td>'.$PrefixMultiplier->Add($Speed, '', 4, 'Decimal').'</td>';
695 } else $Output .= '<td>&nbsp;</td>';
696 $Output .= '</tr>';
697 }
698 $Output .= '</table>';
699 $Output .= $PageList['Output'];
700 $Output .= '</div><br/>';
701 return $Output;
702 }
703
704 function ShowDetailedChart($Table, $Id)
705 {
706 $TableStat = $Table.'Stat';
707 $DbResult = $this->Database->query('SELECT *'.
708 ', (SELECT '.$TableStat.'.Distance - B.Distance FROM '.$TableStat.' AS B WHERE (B.Time < '.$TableStat.'.Time) AND (B.'.$Table.' = '.$TableStat.'.'.$Table.') ORDER BY B.Time DESC LIMIT 1) AS Length'.
709 ', (SELECT TIME_TO_SEC(TIMEDIFF('.$TableStat.'.Time, B.Time)) FROM '.$TableStat.' AS B WHERE (B.Time < '.$TableStat.'.Time) AND (B.'.$Table.' = '.$TableStat.'.'.$Table.') ORDER BY B.Time DESC LIMIT 1) AS Duration'.
710 ', NULL AS Speed'.
711 ' FROM '.$TableStat.
712 ' WHERE '.$TableStat.'.'.$Table.'='.$Id.' ORDER BY Time');
713 $ChartValues = array();
714 while ($Item = $DbResult->fetch_assoc())
715 {
716 $ChartValues[MysqlDateTimeToTime($Item['Time'])] = $Item['Distance'];
717 }
718 $Output = $this->ShowChart($Table.'Detailed', $ChartValues, T('Lap progress'));
719 return $Output;
720 }
721
722 function ShowDaily($Table, $Id)
723 {
724 $PrefixMultiplier = new PrefixMultiplier();
725 $Where = '1';
726
727 $TableStat = $Table.'Stat';
728 $DailyTableMaxId = 'SELECT * FROM (SELECT MAX(Id) AS MaxId FROM '.$TableStat.' AS T1 WHERE T1.'.$Table.'='.$Id.' GROUP BY DATE(Time)) AS T2 LEFT JOIN '.$TableStat.' AS T3 ON T3.Id=T2.MaxId';
729 $DailyTableMinId = 'SELECT * FROM (SELECT MIN(Id) AS MinId FROM '.$TableStat.' AS T1 WHERE T1.'.$Table.'='.$Id.' GROUP BY DATE(Time)) AS T2 LEFT JOIN '.$TableStat.' AS T3 ON T3.Id=T2.MinId';
730 $DbResult = $this->Database->query('SELECT COUNT(*) FROM ('.$DailyTableMaxId.') AS B');
731 $DbRow = $DbResult->fetch_row();
732 $PageList = GetPageList($DbRow[0]);
733
734 $Output = '<div class="section-title">'.T('Daily progress').'</div>';
735 $Output .= '<div id="list_content">';
736 $Output .= $PageList['Output'];
737 $TableColumns = array(
738 array('Name' => 'Time', 'Title' => T('Time')),
739 array('Name' => 'Distance', 'Title' => T('Distance').' [km]'),
740 array('Name' => 'Money', 'Title' => T('Money').' [Kč]'),
741 array('Name' => 'Rank', 'Title' => T('Rank')),
742 array('Name' => 'Duration', 'Title' => T('Duration')),
743 array('Name' => 'Length', 'Title' => T('Length').' [km]'),
744 array('Name' => 'Speed', 'Title' => T('Speed').' [km/'.T('hour').']'),
745 );
746 $Order = GetOrderTableHeader($TableColumns, 'Time', 1);
747 $Output .= '<table class="WideTable">';
748 $Output .= $Order['Output'];
749
750 if ($this->LapLength == 0)
751 {
752 $DbResult = $this->Database->query('SELECT * '.
753 ', COALESCE((SELECT T4.Distance - B.Distance FROM ('.$DailyTableMaxId.') AS B WHERE (DATE(B.Time) < DATE(T4.Time)) AND (B.'.$Table.' = T4.'.$Table.') ORDER BY B.Time DESC LIMIT 1), T4.Distance) AS Length'.
754 ', NULL AS Duration'.
755 ', NULL AS Speed'.
756 ' FROM ('.$DailyTableMaxId.') AS T4'.
757 ' WHERE '.$Where.$Order['SQL'].$PageList['SQLLimit']);
758 } else
759 {
760 $DbResult = $this->Database->query('SELECT * '.
761 ', COALESCE((SELECT T4.Distance - B.Distance FROM ('.$DailyTableMaxId.') AS B WHERE (DATE(B.Time) < DATE(T4.Time)) AND (B.'.$Table.' = T4.'.$Table.') ORDER BY B.Time DESC LIMIT 1), T4.Distance) AS Length'.
762 //', (SELECT COUNT(*) * '.$this->LapLength.' FROM '.$TableStat.' AS B WHERE (DATE(B.Time) = DATE(T4.Time)) AND (B.'.$Table.' = T4.'.$Table.')) AS LapLength'.
763 ', (SELECT TIME_TO_SEC(TIMEDIFF(T4.Time, B.Time)) / (Length - '.$this->LapLength.') * Length FROM ('.$DailyTableMinId.') AS B WHERE (DATE(B.Time) = DATE(T4.Time)) AND (B.'.$Table.' = T4.'.$Table.') ORDER BY B.Time DESC LIMIT 1) AS Duration'.
764 ', NULL AS Speed'.
765 ' FROM ('.$DailyTableMaxId.') AS T4'.
766 ' WHERE '.$Where.$Order['SQL'].$PageList['SQLLimit']);
767 }
768 while ($Item = $DbResult->fetch_assoc())
769 {
770 $Output .= '<tr>'.
771 '<td>'.HumanDate(MysqlDateTimeToTime($Item['Time'])).'</td>'.
772 '<td>'.$Item['Distance'].'</td>'.
773 '<td>'.$Item['Money'].'</td>'.
774 '<td>'.$Item['Rank'].'</td>';
775 if ($Item['Duration'] != null) $Output .= '<td>'.$PrefixMultiplier->Add($Item['Duration'], '', 4, 'Time').'</td>';
776 else $Output .= '<td>&nbsp;</td>';
777 $Output .= '<td>'.$Item['Length'].'</td>';
778 if ($Item['Duration'] > 0) $Output .= '<td>'.$PrefixMultiplier->Add($Item['Length'] / $Item['Duration'] * 3600, '', 4, 'Decimal').'</td>';
779 else $Output .= '<td>&nbsp;</td>';
780 $Output .= '</tr>';
781 }
782 $Output .= '</table>';
783 $Output .= $PageList['Output'];
784 $Output .= '</div>';
785 return $Output;
786 }
787
788 function ShowDailyChart($Table, $Id)
789 {
790 $TableStat = $Table.'Stat';
791 $DailyTableMaxId = 'SELECT * FROM (SELECT MAX(Id) AS MaxId FROM '.$TableStat.' AS T1 WHERE T1.'.$Table.'='.$Id.' GROUP BY DATE(Time)) AS T2 LEFT JOIN '.$TableStat.' AS T3 ON T3.Id=T2.MaxId';
792 $DbResult = $this->Database->query('SELECT * '.
793 ', COALESCE((SELECT T4.Distance - B.Distance FROM ('.$DailyTableMaxId.') AS B WHERE (DATE(B.Time) < DATE(T4.Time)) AND (B.'.$Table.' = T4.'.$Table.') ORDER BY B.Time DESC LIMIT 1), T4.Distance) AS Length'.
794 ' FROM ('.$DailyTableMaxId.') AS T4'.
795 ' ORDER BY Time');
796 $ChartValues = array();
797 while ($Item = $DbResult->fetch_assoc())
798 {
799 $ChartValues[MysqlDateTimeToTime($Item['Time'])] = $Item['Length'];
800 }
801 $Output = $this->ShowChart($Table.'Daily', $ChartValues, T('Daily progress'));
802 return $Output;
803 }
804
805 function ShowSearch()
806 {
807 if (array_key_exists('query', $_GET)) $Query = $_GET['query'];
808 else $Query = '';
809 $Output = '<form action="?" method="get" style="display: inline;">';
810 $Output .= '<input type="text" size="10" name="query" value="'.$Query.'"/> '.
811 '<input type="submit" value="'.T('Search').'"/>';
812 $Output .= '</form>';
813 return $Output;
814 }
815
816 function ShowRunnersAll()
817 {
818 return $this->ShowRunners();
819 }
820
821 function ShowRunnersMen()
822 {
823 return $this->ShowRunners('(Gender=1)');
824 }
825
826 function ShowRunnersWomen()
827 {
828 return $this->ShowRunners('(Gender=2)');
829 }
830
831 function ShowRunnersKids()
832 {
833 return $this->ShowRunners('(Gender=3)');
834 }
835
836 function ShowRunners($Where = '1')
837 {
838 $this->Title = T('Runners').' - '.$this->Title;
839 $Output = '<div class="page-title">'.T('Runners').'</div>';
840 $Output .= '<div class="section-title">'.
841 '<a href="'.$this->Link('/runners/').'">'.T('All').'</a> '.
842 '<a href="'.$this->Link('/men/').'">'.T('Men').'</a> '.
843 '<a href="'.$this->Link('/women/').'">'.T('Women').'</a> '.
844 '<a href="'.$this->Link('/kids/').'">'.T('Kids').'</a>'.
845 '</div>';
846 $Year = $this->GetYear();
847
848 $Output .= '<div class="section-title">'.$this->YearList('/runners/', $Year, 'Runner').' '.T('Name').': '.$this->ShowSearch().'</div>';
849 $Where .= ' AND (Year='.$Year.') AND (Hidden=0)';
850 if (array_key_exists('query', $_GET) and ($_GET['query'] != ''))
851 {
852 $Where .= ' AND (Name LIKE "%'.addslashes($_GET['query']).'%")';
853 }
854
855 $DbResult = $this->Database->query('SELECT COUNT(*) FROM `Runner` WHERE '.$Where);
856 $DbRow = $DbResult->fetch_row();
857 $PageList = GetPageList($DbRow[0]);
858
859 $Gender = array('', T('Man'), T('Woman'), T('Kid'));
860 $Output .= '<div id="list_content">';
861 $Output .= $PageList['Output'];
862 $TableColumns = array(
863 array('Name' => 'Name', 'Title' => T('Name')),
864 array('Name' => 'Gender', 'Title' => T('Category')),
865 array('Name' => 'Distance', 'Title' => T('Distance')),
866 array('Name' => 'Money', 'Title' => T('Money')),
867 array('Name' => 'Rank', 'Title' => T('Rank')),
868 array('Name' => 'Time', 'Title' => T('Last change')),
869 );
870 $Order = GetOrderTableHeader($TableColumns, 'Distance', 1);
871 $Output .= '<table class="WideTable">';
872 $Output .= $Order['Output'];
873 $DbResult = $this->Database->select('Runner', '*, '.
874 '(SELECT RunnerStat.Distance FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Distance'.
875 ', (SELECT RunnerStat.Money FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Money'.
876 ', (SELECT RunnerStat.Time FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Time'.
877 ', (SELECT RunnerStat.Rank FROM RunnerStat WHERE RunnerStat.Runner=Runner.Id ORDER BY Time DESC LIMIT 1) AS Rank',
878 $Where.$Order['SQL'].$PageList['SQLLimit']);
879 while ($Item = $DbResult->fetch_assoc())
880 {
881 $Output .= '<tr>'.
882 '<td><a href="'.$this->Link('/runner/'.$Item['Id'].'/').'">'.$Item['Name'].'</a></td>'.
883 '<td>'.$Gender[$Item['Gender']].'</td>'.
884 '<td>'.$Item['Distance'].'</td>'.
885 '<td>'.$Item['Money'].'</td>'.
886 '<td>'.$Item['Rank'].'</td>'.
887 '<td>'.$this->RunningState(MysqlDateTimeToTime($Item['Time'])).'</td>'.
888 '</tr>';
889 }
890 $Output .= '</table>';
891 $Output .= $PageList['Output'];
892 $Output .= '</div>';
893
894 return $Output;
895 }
896
897 function ShowRunner()
898 {
899 $PrefixMultiplier = new PrefixMultiplier();
900 $Output = '';
901
902 $RunnerId = 0;
903 if ((count($this->PathItems) > 0) and ($this->PathItems[count($this->PathItems) - 1] != ''))
904 $RunnerId = $this->PathItems[count($this->PathItems) - 1];
905 if (!is_numeric($RunnerId)) die(T('Runner id needs to be numeric'));
906
907 $DbResult = $this->Database->query('SELECT Runner.Name, Team.Name AS TeamName, Team.Id AS TeamId, Runner.Year FROM Runner '.
908 'LEFT JOIN Team ON Team.Id=Runner.Team WHERE (Runner.Hidden=0) AND (Runner.Id='.$RunnerId.')');
909 if ($DbResult->num_rows > 0)
910 {
911 $DbRow = $DbResult->fetch_assoc();
912 $this->Title = T('Runner').' '.$DbRow['Name'].' - '.$this->Title;
913 $Output .= '<div class="page-title">'.T('Runner').' '.$DbRow['Name'].'</div>';
914 if ($DbRow['TeamName'] != '')
915 $Output .= '<div class="section-title"><a href="'.$this->Link('/team/'.$DbRow['TeamId']).'">'.$DbRow['TeamName'].'</a></div>';
916 $Output .= '<div class="section-title">'.$this->ItemsYearList('/runner/', $RunnerId, 'Runner', 'Name="'.$this->Database->real_escape_string($DbRow['Name']).'"').'</div>';
917 $this->LoadYearParameters($DbRow['Year']);
918
919 $Output .= $this->ShowDetailed('Runner', $RunnerId);
920 //$Output .= $this->ShowDetailedChart('Runner', $RunnerId);
921 $Output .= $this->ShowDaily('Runner', $RunnerId);
922 //$Output .= $this->ShowDailyChart('Runner', $RunnerId);
923 } else $Output .= T('Runner not found.');
924 return $Output;
925 }
926
927 function GetTotals($Where = '1', $Table = 'Runner')
928 {
929 $DbResult = $this->Database->query('SELECT (SELECT COUNT(*) FROM '.$Table.' WHERE '.$Where.') AS TotalCount, '.
930 '(SELECT SUM(T1.Distance) FROM (SELECT (SELECT Distance FROM '.$Table.'Stat WHERE '.$Table.'Stat.'.$Table.' = '.$Table.'.Id ORDER BY Time DESC LIMIT 1) AS Distance FROM '.$Table.' WHERE '.$Where.') AS T1) AS TotalDistance, '.
931 '(SELECT SUM(T2.Money) FROM (SELECT (SELECT Money FROM '.$Table.'Stat WHERE '.$Table.'Stat.'.$Table.' = '.$Table.'.Id ORDER BY Time DESC LIMIT 1) AS Money FROM '.$Table.' WHERE '.$Where.') AS T2) AS TotalMoney');
932 $DbRow = $DbResult->fetch_assoc();
933 return $DbRow;
934 }
935
936 function GetYear()
937 {
938 $Year = 0;
939 if (count($this->PathItems) > 0)
940 {
941 $Param = $this->PathItems[count($this->PathItems) - 1];
942 if (is_numeric($this->PathItems[count($this->PathItems) - 1]))
943 $Year = $this->PathItems[count($this->PathItems) - 1] * 1;
944 }
945 if ($Year == 0) $Year = $this->GetLatestYear();
946 $this->LoadYearParameters($Year);
947 return $Year;
948 }
949
950 function ShowMain()
951 {
952 $Output = '';
953 $Output .= '<p>'.sprintf(T('This website collects data from official <a href="%s">leaderboard</a> site and tracks and presents progress of runners and teams.'), $this->LeaderboardURL).'</p>';
954 $Output .= '<div class="page-title">'.T('Summary').'</div>';
955 $Year = $this->GetYear();
956
957 $Output .= '<div class="section-title">'.$this->YearList('/', $Year).'</div>';
958
959 $Runners = $this->GetTotals('(Year='.$Year.')');
960 $Men = $this->GetTotals('(Runner.Gender=1) AND (Year='.$Year.')', 'Runner');
961 $Women = $this->GetTotals('(Runner.Gender=2) AND (Year='.$Year.')', 'Runner');
962 $Kids = $this->GetTotals('(Runner.Gender=3) AND (Year='.$Year.')', 'Runner');
963 $Teams = $this->GetTotals('(Team.IsFamily=0) AND (Year='.$Year.')', 'Team');
964 $Families = $this->GetTotals('(Team.IsFamily=1) AND (Year='.$Year.')', 'Team');
965
966 $Output .= '<table class="WideTable">';
967 $Output .= '<tr><th>'.T('Category').'</th><th>'.T('Count').'</th><th>'.T('Distance').' [km]</th><th>'.T('Money').' [Kč]</th></tr>';
968 $Output .= '<tr><td>'.T('Everyone').'</td><td>'.$Runners['TotalCount'].'</td><td>'.$Runners['TotalDistance'].'</td><td>'.$Runners['TotalMoney'].'</td></tr>';
969 $Output .= '<tr><td>'.T('Men').'</td><td>'.$Men['TotalCount'].'</td><td>'.$Men['TotalDistance'].'</td><td>'.$Men['TotalMoney'].'</td></tr>';
970 $Output .= '<tr><td>'.T('Women').'</td><td>'.$Women['TotalCount'].'</td><td>'.$Women['TotalDistance'].'</td><td>'.$Women['TotalMoney'].'</td></tr>';
971 $Output .= '<tr><td>'.T('Kids').'</td><td>'.$Kids['TotalCount'].'</td><td>'.$Kids['TotalDistance'].'</td><td>'.$Kids['TotalMoney'].'</td></tr>';
972 $Output .= '<tr><td>'.T('Teams').'</td><td>'.$Teams['TotalCount'].'</td><td>'.$Teams['TotalDistance'].'</td><td>'.$Teams['TotalMoney'].'</td></tr>';
973 $Output .= '<tr><td>'.T('Families').'</td><td>'.$Families['TotalCount'].'</td><td>'.$Families['TotalDistance'].'</td><td>'.$Families['TotalMoney'].'</td></tr>';
974 $Output .= '</table>';
975
976 return $Output;
977 }
978
979 function ShowPage($Content)
980 {
981 global $Config;
982
983 $Lang = 'en';
984 $Output = '<?xml version="1.0" encoding="'.$this->Config['Encoding'].'"?>'."\n".
985 '<!doctype html>'.
986 '<html>'.
987 '<head>'.
988 '<link rel="stylesheet" href="'.$this->Link('/style/style.css').'" type="text/css" media="all" />'.
989 '<meta http-equiv="content-type" content="application/xhtml+xml; charset='.$this->Config['Encoding'].'" />'.
990 '<meta name="viewport" content="width=device-width, initial-scale=1">'.
991 '<script type="text/javascript" src="'.$this->Link('/Packages/Chart.js/Chart.js').'"></script>';
992 //'<script src="'.$this->Link('/jquery.js').'"></script>';
993 $Output .= '<title>'.$this->Title.'</title>'.
994 '</head><body>';
995 $Output .= $Content;
996 $Output .= '<br/><div class="footer">'.T('Contact').': <a href="mailto:'.$Config['Contact'].'">'.$Config['Contact'].'</a> '.
997 '<a href="https://app.zdechov.net/teribear/">'.T('Source code').'</a></div>';
998 $Output .= '</body></html>';
999 return $Output;
1000 }
1001
1002 function Link($Target)
1003 {
1004 if (substr($Target, 0, strlen($this->BaseURL)) == $this->BaseURL)
1005 $Remaining = substr($Target, strlen($this->BaseURL));
1006 else $Remaining = $Target;
1007 $TargetParts = explode('/', $Remaining);
1008 if ((count($TargetParts) > 0) and ($TargetParts[0] == ''))
1009 array_splice($TargetParts, 0, 1);
1010 if (count($TargetParts) > 0)
1011 {
1012 if (in_array($TargetParts[0], $this->LinkLocaleExceptions))
1013 {
1014 $Result = $this->BaseURL.$Target;
1015 } else $Result = $this->LinkLocale($Target);
1016 } else $Result = $this->LinkLocale($Target);
1017 return $Result;
1018 }
1019
1020 function TranslateURL($URL, $Locale)
1021 {
1022 // Try translate URL directory parts from current locale to target locale
1023 $Remaining = $URL;
1024 $RemainingParts = explode('?', $Remaining);
1025 $Directory = $RemainingParts[0];
1026 if (count($RemainingParts) > 1)
1027 {
1028 $Params = $RemainingParts[1];
1029 } else
1030 {
1031 $Params = '';
1032 }
1033 $TargetLocaleManager = new LocaleManager($this);
1034 $TargetLocaleManager->Dir = $this->LocaleManager->Dir;
1035 $TargetLocaleManager->Available = $this->LocaleManager->Available;
1036 $TargetLocaleManager->LoadLocale($Locale);
1037
1038 $DirectoryParts = explode('/', $Directory);
1039 foreach ($DirectoryParts as $Index => $Item)
1040 {
1041 $NewText = $TargetLocaleManager->CurrentLocale->Texts->Translate($Item, 'URL');
1042 $DirectoryParts[$Index] = $NewText;
1043 }
1044 $Directory = implode('/', $DirectoryParts);
1045 $Remaining = $Directory;
1046 if ($Params != '') $Remaining .= '?'.$Params;
1047
1048 return $Remaining;
1049 }
1050
1051 function TranslateReverseURL($URL, $Locale)
1052 {
1053 // Try translate URL directory parts from current locale to target locale
1054 $Remaining = $URL;
1055 $RemainingParts = explode('?', $Remaining);
1056 $Directory = $RemainingParts[0];
1057 if (count($RemainingParts) > 1)
1058 {
1059 $Params = $RemainingParts[1];
1060 } else {
1061 $Params = '';
1062 }
1063 $TargetLocaleManager = new LocaleManager($this);
1064 $TargetLocaleManager->Dir = $this->LocaleManager->Dir;
1065 $TargetLocaleManager->Available = $this->LocaleManager->Available;
1066 $TargetLocaleManager->LoadLocale($Locale);
1067
1068 $DirectoryParts = explode('/', $Directory);
1069 foreach ($DirectoryParts as $Index => $Item)
1070 {
1071 $NewText = $TargetLocaleManager->CurrentLocale->Texts->TranslateReverse($Item, 'URL');
1072 $DirectoryParts[$Index] = $NewText;
1073 }
1074 $Directory = implode('/', $DirectoryParts);
1075 $Remaining = $Directory;
1076 if ($Params != '') $Remaining .= '?'.$Params;
1077
1078 return $Remaining;
1079 }
1080
1081 function LinkLocale($Target, $Locale = '')
1082 {
1083 if ($Locale == '') $Locale = $this->LocaleManager->LangCode;
1084
1085 $Target = $this->TranslateURL($Target, $Locale);
1086
1087 if ($Locale == $this->LocaleManager->DefaultLangCode)
1088 return $this->BaseURL.$Target;
1089 return $this->BaseURL.'/'.$Locale.$Target;
1090 }
1091
1092 function LoadYearParameters($Year)
1093 {
1094 $DbResult = $this->Database->query('SELECT * FROM `Year` WHERE `Year`.`Year`='.$Year);
1095 if ($DbResult->num_rows > 0)
1096 {
1097 $DbRow = $DbResult->fetch_assoc();
1098 $this->LapLength = $DbRow['LapLength'];
1099 $this->MoneyKm = $DbRow['MoneyKm'];
1100 }
1101 }
1102
1103 function ShowChart($ChartName, $Values, $Title)
1104 {
1105 $Output = '<div style="width:50%; text-align: center; margin-left: auto; margin-right: auto;">'.
1106 ' <canvas id="'.$ChartName.'"></canvas>'.
1107 '</div>'.
1108 '<script type="text/javascript">';
1109 $Output .= "var ctx = document.getElementById('".$ChartName."').getContext('2d');
1110 window.chartColors = {
1111 red: 'rgb(255, 99, 132)',
1112 orange: 'rgb(255, 159, 64)',
1113 yellow: 'rgb(255, 205, 86)',
1114 green: 'rgb(75, 192, 192)',
1115 blue: 'rgb(54, 162, 235)',
1116 purple: 'rgb(153, 102, 255)',
1117 grey: 'rgb(201, 203, 207)'
1118 };
1119 var config = {
1120 type: 'line',
1121 data: {
1122 labels: [";
1123 foreach ($Values as $Key => $Value)
1124 {
1125 $Output .= "'".$Key."', ";
1126 }
1127 $Output .= "],
1128 datasets: [{
1129 label: '',
1130 backgroundColor: window.chartColors.red,
1131 borderColor: window.chartColors.red,
1132 data: [";
1133 foreach ($Values as $Key => $Value)
1134 {
1135 $Output .= "{ x: ".$Key.", ";
1136 $Output .= "y: ".$Value." }, ";
1137 }
1138 $Output .= "
1139 ],
1140 fill: false,
1141 }]
1142 },
1143 options: {
1144 responsive: true,
1145 title: {
1146 display: true,
1147 text: '".$Title."'
1148 },
1149 tooltips: {
1150 mode: 'index',
1151 intersect: false,
1152 },
1153 hover: {
1154 mode: 'nearest',
1155 intersect: true
1156 },
1157 scales: {
1158 xAxes: [{
1159 type: 'time',
1160 display: true,
1161 scaleLabel: {
1162 display: true,
1163 labelString: 'Date'
1164 },
1165 ticks: {
1166 major: {
1167 fontStyle: 'bold',
1168 fontColor: '#FF0000'
1169 }
1170 }
1171 }],
1172 yAxes: [{
1173 display: true,
1174 scaleLabel: {
1175 display: true,
1176 labelString: 'value'
1177 }
1178 }]
1179 }
1180 }
1181 };
1182 var ".$ChartName." = new Chart(ctx, config);
1183 </script>";
1184 return $Output;
1185 }
1186
1187 function Run()
1188 {
1189 global $Config, $GlobalLocaleManager;
1190
1191 $this->Config = $Config;
1192 $this->Database = new Database();
1193 $this->Database->Connect($this->Config['Database']['Host'], $this->Config['Database']['User'],
1194 $this->Config['Database']['Password'], $this->Config['Database']['Database']);
1195 $this->Database->Prefix = $this->Config['Database']['Prefix'];
1196 $this->Database->charset($this->Config['Database']['Charset']);
1197 //$this->Database->ShowSQLError = true;
1198 //$this->Database->ShowSQLQuery = true;
1199
1200 $this->LocaleManager = new LocaleManager($this);
1201 $this->LocaleManager->Dir = dirname(__FILE__).'/Locale';
1202 $this->LocaleManager->Available = array(
1203 'cs' => array('Code' => 'cs', 'Title' => 'Česky'),
1204 'en' => array('Code' => 'en', 'Title' => 'English'),
1205 );
1206 $GlobalLocaleManager = $this->LocaleManager;
1207 $this->LinkLocaleExceptions = array('style', 'Packages');
1208
1209 $this->PathItems = $this->ProcessURL();
1210
1211 // Detect interface locale
1212 if (isset($this->Config['Locale']))
1213 $this->LocaleManager->DefaultLangCode = $this->Config['Locale'];
1214 $this->LocaleManager->LangCode = $this->LocaleManager->DefaultLangCode;
1215 if (count($this->PathItems) > 0)
1216 {
1217 $NewLangCode = $this->PathItems[0];
1218 if (array_key_exists($NewLangCode, $this->LocaleManager->Available))
1219 {
1220 array_shift($this->PathItems);
1221 $this->LocaleManager->LangCode = $NewLangCode;
1222 }
1223 }
1224 if (array_key_exists($this->LocaleManager->LangCode, $this->LocaleManager->Available))
1225 {
1226 $this->LocaleManager->LoadLocale($this->LocaleManager->LangCode);
1227 }
1228 InitPrefixMultipliers();
1229
1230 if (GetRemoteAddress() != '')
1231 {
1232 $this->BaseURL = $_SERVER["CONTEXT_PREFIX"];
1233 if (substr($this->BaseURL, -1, 1) == '/') $this->BaseURL = substr($this->BaseURL, 0, -1);
1234 }
1235
1236 $this->Title = T('Teribear stats');
1237 $Output = '';
1238
1239 $this->Year = 0;
1240 if (count($this->PathItems) > 0)
1241 {
1242 $Item = $this->PathItems[0];
1243 $Item = $this->LocaleManager->CurrentLocale->Texts->TranslateReverse($Item, 'URL');
1244 if ($Item == 'sync') $Output .= $this->ShowSync();
1245 //else if ($Item == 'empty') $Output .= $this->ShowEmpty();
1246 else if ($Item == 'runner') $Output .= $this->ShowRunner();
1247 else if ($Item == 'runners') $Output .= $this->ShowRunnersAll();
1248 else if ($Item == 'men') $Output .= $this->ShowRunnersMen();
1249 else if ($Item == 'women') $Output .= $this->ShowRunnersWomen();
1250 else if ($Item == 'kids') $Output .= $this->ShowRunnersKids();
1251 else if ($Item == 'team') $Output .= $this->ShowTeam();
1252 else if ($Item == 'teams') $Output .= $this->ShowTeams();
1253 else if ($Item == 'family') $Output .= $this->ShowFamily();
1254 else if ($Item == 'families') $Output .= $this->ShowFamilies();
1255 else $Output .= $this->ShowMain();
1256 } else $Output .= $this->ShowMain();
1257 if (!$this->NoFullPage)
1258 {
1259 $Output = $this->ShowMenu().$Output;
1260 echo($this->ShowPage($Output));
1261 } else echo($Output);
1262 }
1263}
1264
1265$Application = new MyApplication();
1266$Application->Run();
Note: See TracBrowser for help on using the repository browser.