1b00cb080SGreg Roach<?php 2b00cb080SGreg Roach 3b00cb080SGreg Roach/** 4b00cb080SGreg Roach * webtrees: online genealogy 5b00cb080SGreg Roach * Copyright (C) 2020 webtrees development team 6b00cb080SGreg Roach * This program is free software: you can redistribute it and/or modify 7b00cb080SGreg Roach * it under the terms of the GNU General Public License as published by 8b00cb080SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9b00cb080SGreg Roach * (at your option) any later version. 10b00cb080SGreg Roach * This program is distributed in the hope that it will be useful, 11b00cb080SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12b00cb080SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13b00cb080SGreg Roach * GNU General Public License for more details. 14b00cb080SGreg Roach * You should have received a copy of the GNU General Public License 15b00cb080SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16b00cb080SGreg Roach */ 17b00cb080SGreg Roach 18b00cb080SGreg Roachdeclare(strict_types=1); 19b00cb080SGreg Roach 20b00cb080SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers; 21b00cb080SGreg Roach 22b00cb080SGreg Roachuse Fisharebest\Webtrees\Date; 23b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\FrenchDate; 24b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\GregorianDate; 25b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\HijriDate; 26b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\JalaliDate; 27b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\JewishDate; 28b00cb080SGreg Roachuse Fisharebest\Webtrees\Date\JulianDate; 29b00cb080SGreg Roachuse Fisharebest\Webtrees\Fact; 30b00cb080SGreg Roachuse Fisharebest\Webtrees\Family; 31b00cb080SGreg Roachuse Fisharebest\Webtrees\I18N; 32b00cb080SGreg Roachuse Fisharebest\Webtrees\Individual; 336b9cb339SGreg Roachuse Fisharebest\Webtrees\Registry; 34b00cb080SGreg Roachuse Fisharebest\Webtrees\Services\CalendarService; 35b00cb080SGreg Roachuse Fisharebest\Webtrees\Tree; 36b00cb080SGreg Roachuse Illuminate\Support\Collection; 37b00cb080SGreg Roachuse Psr\Http\Message\ResponseInterface; 38b00cb080SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 39b00cb080SGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 40b00cb080SGreg Roach 41b00cb080SGreg Roachuse function assert; 42b00cb080SGreg Roachuse function count; 43b00cb080SGreg Roachuse function e; 44b00cb080SGreg Roachuse function explode; 45b00cb080SGreg Roachuse function get_class; 46b00cb080SGreg Roachuse function ob_get_clean; 47b00cb080SGreg Roachuse function ob_start; 48b00cb080SGreg Roachuse function range; 49b00cb080SGreg Roachuse function response; 50b00cb080SGreg Roachuse function view; 51b00cb080SGreg Roach 52b00cb080SGreg Roach/** 53b00cb080SGreg Roach * Show anniversaries for events in a given day/month/year. 54b00cb080SGreg Roach */ 55b00cb080SGreg Roachclass CalendarEvents implements RequestHandlerInterface 56b00cb080SGreg Roach{ 57b00cb080SGreg Roach /** @var CalendarService */ 58b00cb080SGreg Roach private $calendar_service; 59b00cb080SGreg Roach 60b00cb080SGreg Roach /** 61b00cb080SGreg Roach * CalendarPage constructor. 62b00cb080SGreg Roach * 63b00cb080SGreg Roach * @param CalendarService $calendar_service 64b00cb080SGreg Roach */ 65b00cb080SGreg Roach public function __construct(CalendarService $calendar_service) 66b00cb080SGreg Roach { 67b00cb080SGreg Roach $this->calendar_service = $calendar_service; 68b00cb080SGreg Roach } 69b00cb080SGreg Roach 70b00cb080SGreg Roach /** 71*fceda430SGreg Roach * Show anniversaries that occurred on a given day/month/year. 72b00cb080SGreg Roach * 73b00cb080SGreg Roach * @param ServerRequestInterface $request 74b00cb080SGreg Roach * 75b00cb080SGreg Roach * @return ResponseInterface 76b00cb080SGreg Roach */ 77b00cb080SGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 78b00cb080SGreg Roach { 79b00cb080SGreg Roach $tree = $request->getAttribute('tree'); 80b00cb080SGreg Roach assert($tree instanceof Tree); 81b00cb080SGreg Roach 82b00cb080SGreg Roach $view = $request->getAttribute('view'); 83b00cb080SGreg Roach $CALENDAR_FORMAT = $tree->getPreference('CALENDAR_FORMAT'); 84b00cb080SGreg Roach 85b00cb080SGreg Roach $cal = $request->getQueryParams()['cal'] ?? ''; 86b00cb080SGreg Roach $day = $request->getQueryParams()['day'] ?? ''; 87b00cb080SGreg Roach $month = $request->getQueryParams()['month'] ?? ''; 88b00cb080SGreg Roach $year = $request->getQueryParams()['year'] ?? ''; 89b00cb080SGreg Roach $filterev = $request->getQueryParams()['filterev'] ?? 'BIRT-MARR-DEAT'; 90b00cb080SGreg Roach $filterof = $request->getQueryParams()['filterof'] ?? 'all'; 91b00cb080SGreg Roach $filtersx = $request->getQueryParams()['filtersx'] ?? ''; 92b00cb080SGreg Roach 93b00cb080SGreg Roach $ged_date = new Date("{$cal} {$day} {$month} {$year}"); 94b00cb080SGreg Roach $cal_date = $ged_date->minimumDate(); 95b00cb080SGreg Roach $today = $cal_date->today(); 96b00cb080SGreg Roach 97b00cb080SGreg Roach $days_in_month = $cal_date->daysInMonth(); 98b00cb080SGreg Roach $days_in_week = $cal_date->daysInWeek(); 99b00cb080SGreg Roach 100b00cb080SGreg Roach // Day and year share the same layout. 101b00cb080SGreg Roach if ($view !== 'month') { 102b00cb080SGreg Roach if ($view === 'day') { 103b00cb080SGreg Roach $anniversary_facts = $this->calendar_service->getAnniversaryEvents($cal_date->minimumJulianDay(), $filterev, $tree, $filterof, $filtersx); 104b00cb080SGreg Roach } else { 105b00cb080SGreg Roach $ged_year = new Date($cal . ' ' . $year); 106b00cb080SGreg Roach $anniversary_facts = $this->calendar_service->getCalendarEvents($ged_year->minimumJulianDay(), $ged_year->maximumJulianDay(), $filterev, $tree, $filterof, $filtersx); 107b00cb080SGreg Roach } 108b00cb080SGreg Roach 109b00cb080SGreg Roach $anniversaries = Collection::make($anniversary_facts) 110b00cb080SGreg Roach ->unique() 111b00cb080SGreg Roach ->sort(static function (Fact $x, Fact $y): int { 112b00cb080SGreg Roach return $x->date()->minimumJulianDay() <=> $y->date()->minimumJulianDay(); 113b00cb080SGreg Roach }); 114b00cb080SGreg Roach 115b00cb080SGreg Roach $family_anniversaries = $anniversaries->filter(static function (Fact $f): bool { 116b00cb080SGreg Roach return $f->record() instanceof Family; 117b00cb080SGreg Roach }); 118b00cb080SGreg Roach 119b00cb080SGreg Roach $individual_anniversaries = $anniversaries->filter(static function (Fact $f): bool { 120b00cb080SGreg Roach return $f->record() instanceof Individual; 121b00cb080SGreg Roach }); 122b00cb080SGreg Roach 123b00cb080SGreg Roach return response(view('calendar-list', [ 124b00cb080SGreg Roach 'family_anniversaries' => $family_anniversaries, 125b00cb080SGreg Roach 'individual_anniversaries' => $individual_anniversaries, 126b00cb080SGreg Roach ])); 127b00cb080SGreg Roach } 128b00cb080SGreg Roach 129b00cb080SGreg Roach $found_facts = []; 130b00cb080SGreg Roach 131b00cb080SGreg Roach $cal_date->day = 0; 132b00cb080SGreg Roach $cal_date->setJdFromYmd(); 133b00cb080SGreg Roach // Make a separate list for each day. Unspecified/invalid days go in day 0. 134b00cb080SGreg Roach for ($d = 0; $d <= $days_in_month; ++$d) { 135b00cb080SGreg Roach $found_facts[$d] = []; 136b00cb080SGreg Roach } 137b00cb080SGreg Roach // Fetch events for each day 138b00cb080SGreg Roach $jds = range($cal_date->minimumJulianDay(), $cal_date->maximumJulianDay()); 139b00cb080SGreg Roach 140b00cb080SGreg Roach foreach ($jds as $jd) { 141b00cb080SGreg Roach foreach ($this->calendar_service->getAnniversaryEvents($jd, $filterev, $tree, $filterof, $filtersx) as $fact) { 142b00cb080SGreg Roach $tmp = $fact->date()->minimumDate(); 143b00cb080SGreg Roach if ($tmp->day >= 1 && $tmp->day <= $tmp->daysInMonth()) { 144b00cb080SGreg Roach // If the day is valid (for its own calendar), display it in the 145b00cb080SGreg Roach // anniversary day (for the display calendar). 146b00cb080SGreg Roach $found_facts[$jd - $cal_date->minimumJulianDay() + 1][] = $fact; 147b00cb080SGreg Roach } else { 148b00cb080SGreg Roach // Otherwise, display it in the "Day not set" box. 149b00cb080SGreg Roach $found_facts[0][] = $fact; 150b00cb080SGreg Roach } 151b00cb080SGreg Roach } 152b00cb080SGreg Roach } 153b00cb080SGreg Roach 154b00cb080SGreg Roach $cal_facts = []; 155b00cb080SGreg Roach 156b00cb080SGreg Roach foreach ($found_facts as $d => $facts) { 157b00cb080SGreg Roach $cal_facts[$d] = []; 158b00cb080SGreg Roach foreach ($facts as $fact) { 159b00cb080SGreg Roach $xref = $fact->record()->xref(); 160b00cb080SGreg Roach $text = $text = $fact->label() . ' — ' . $fact->date()->display(true, null, false); 161b00cb080SGreg Roach if ($fact->anniv > 0) { 162b00cb080SGreg Roach $text .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')'; 163b00cb080SGreg Roach } 164b00cb080SGreg Roach if (empty($cal_facts[$d][$xref])) { 165b00cb080SGreg Roach $cal_facts[$d][$xref] = $text; 166b00cb080SGreg Roach } else { 167b00cb080SGreg Roach $cal_facts[$d][$xref] .= '<br>' . $text; 168b00cb080SGreg Roach } 169b00cb080SGreg Roach } 170b00cb080SGreg Roach } 171b00cb080SGreg Roach // We use JD%7 = 0/Mon…6/Sun. Standard definitions use 0/Sun…6/Sat. 172b00cb080SGreg Roach $week_start = (I18N::locale()->territory()->firstDay() + 6) % 7; 173b00cb080SGreg Roach $weekend_start = (I18N::locale()->territory()->weekendStart() + 6) % 7; 174b00cb080SGreg Roach $weekend_end = (I18N::locale()->territory()->weekendEnd() + 6) % 7; 175b00cb080SGreg Roach // The french calendar has a 10-day week, which starts on primidi 176b00cb080SGreg Roach if ($days_in_week === 10) { 177b00cb080SGreg Roach $week_start = 0; 178b00cb080SGreg Roach $weekend_start = -1; 179b00cb080SGreg Roach $weekend_end = -1; 180b00cb080SGreg Roach } 181b00cb080SGreg Roach 182b00cb080SGreg Roach ob_start(); 183b00cb080SGreg Roach 184789e127fSGreg Roach echo '<table class="w-100 wt-calendar-month"><thead><tr>'; 185b00cb080SGreg Roach for ($week_day = 0; $week_day < $days_in_week; ++$week_day) { 186b00cb080SGreg Roach $day_name = $cal_date->dayNames(($week_day + $week_start) % $days_in_week); 187b00cb080SGreg Roach if ($week_day === $weekend_start || $week_day === $weekend_end) { 188b00cb080SGreg Roach echo '<th class="wt-page-options-label weekend" width="' . (100 / $days_in_week) . '%">', $day_name, '</th>'; 189b00cb080SGreg Roach } else { 190b00cb080SGreg Roach echo '<th class="wt-page-options-label" width="' . (100 / $days_in_week) . '%">', $day_name, '</th>'; 191b00cb080SGreg Roach } 192b00cb080SGreg Roach } 193b00cb080SGreg Roach echo '</tr>'; 194b00cb080SGreg Roach echo '</thead>'; 195b00cb080SGreg Roach echo '<tbody>'; 196b00cb080SGreg Roach // Print days 1 to n of the month, but extend to cover "empty" days before/after the month to make whole weeks. 197b00cb080SGreg Roach // e.g. instead of 1 -> 30 (=30 days), we might have -1 -> 33 (=35 days) 198b00cb080SGreg Roach $start_d = 1 - ($cal_date->minimumJulianDay() - $week_start) % $days_in_week; 199b00cb080SGreg Roach $end_d = $days_in_month + ($days_in_week - ($cal_date->maximumJulianDay() - $week_start + 1) % $days_in_week) % $days_in_week; 200b00cb080SGreg Roach // Make sure that there is an empty box for any leap/missing days 201b00cb080SGreg Roach if ($start_d === 1 && $end_d === $days_in_month && count($found_facts[0]) > 0) { 202b00cb080SGreg Roach $end_d += $days_in_week; 203b00cb080SGreg Roach } 204b00cb080SGreg Roach for ($d = $start_d; $d <= $end_d; ++$d) { 205b00cb080SGreg Roach if (($d + $cal_date->minimumJulianDay() - $week_start) % $days_in_week === 1) { 206b00cb080SGreg Roach echo '<tr>'; 207b00cb080SGreg Roach } 208b00cb080SGreg Roach echo '<td class="wt-page-options-value">'; 209b00cb080SGreg Roach if ($d < 1 || $d > $days_in_month) { 210b00cb080SGreg Roach if (count($cal_facts[0]) > 0) { 211d8278044SGreg Roach echo '<div class="cal_day">', I18N::translate('Day not set'), '</div>'; 212b00cb080SGreg Roach echo '<div class="small" style="height: 180px; overflow: auto;">'; 213b00cb080SGreg Roach echo $this->calendarListText($cal_facts[0], '', '', $tree); 214b00cb080SGreg Roach echo '</div>'; 215b00cb080SGreg Roach $cal_facts[0] = []; 216b00cb080SGreg Roach } 217b00cb080SGreg Roach } else { 218b00cb080SGreg Roach // Format the day number using the calendar 219b00cb080SGreg Roach $tmp = new Date($cal_date->format("%@ {$d} %O %E")); 220b00cb080SGreg Roach $d_fmt = $tmp->minimumDate()->format('%j'); 221d8278044SGreg Roach echo '<div class="d-flex d-flex justify-content-between">'; 222b00cb080SGreg Roach if ($d === $today->day && $cal_date->month === $today->month) { 223b00cb080SGreg Roach echo '<span class="cal_day current_day">', $d_fmt, '</span>'; 224b00cb080SGreg Roach } else { 225b00cb080SGreg Roach echo '<span class="cal_day">', $d_fmt, '</span>'; 226b00cb080SGreg Roach } 227b00cb080SGreg Roach // Show a converted date 228b00cb080SGreg Roach foreach (explode('_and_', $CALENDAR_FORMAT) as $convcal) { 229b00cb080SGreg Roach switch ($convcal) { 230b00cb080SGreg Roach case 'french': 231b00cb080SGreg Roach $alt_date = new FrenchDate($cal_date->minimumJulianDay() + $d - 1); 232b00cb080SGreg Roach break; 233b00cb080SGreg Roach case 'gregorian': 234b00cb080SGreg Roach $alt_date = new GregorianDate($cal_date->minimumJulianDay() + $d - 1); 235b00cb080SGreg Roach break; 236b00cb080SGreg Roach case 'jewish': 237b00cb080SGreg Roach $alt_date = new JewishDate($cal_date->minimumJulianDay() + $d - 1); 238b00cb080SGreg Roach break; 239b00cb080SGreg Roach case 'julian': 240b00cb080SGreg Roach $alt_date = new JulianDate($cal_date->minimumJulianDay() + $d - 1); 241b00cb080SGreg Roach break; 242b00cb080SGreg Roach case 'hijri': 243b00cb080SGreg Roach $alt_date = new HijriDate($cal_date->minimumJulianDay() + $d - 1); 244b00cb080SGreg Roach break; 245b00cb080SGreg Roach case 'jalali': 246b00cb080SGreg Roach $alt_date = new JalaliDate($cal_date->minimumJulianDay() + $d - 1); 247b00cb080SGreg Roach break; 248b00cb080SGreg Roach case 'none': 249b00cb080SGreg Roach default: 250b00cb080SGreg Roach $alt_date = $cal_date; 251b00cb080SGreg Roach break; 252b00cb080SGreg Roach } 253b00cb080SGreg Roach if (get_class($alt_date) !== get_class($cal_date) && $alt_date->inValidRange()) { 254b00cb080SGreg Roach echo '<span class="rtl_cal_day">' . $alt_date->format('%j %M') . '</span>'; 255b00cb080SGreg Roach // Just show the first conversion 256b00cb080SGreg Roach break; 257b00cb080SGreg Roach } 258b00cb080SGreg Roach } 259d8278044SGreg Roach echo '</div>'; 260d8278044SGreg Roach echo '<div class="small" style="height: 180px; overflow: auto;">'; 261b00cb080SGreg Roach echo $this->calendarListText($cal_facts[$d], '', '', $tree); 262b00cb080SGreg Roach echo '</div>'; 263b00cb080SGreg Roach } 264b00cb080SGreg Roach echo '</td>'; 265b00cb080SGreg Roach if (($d + $cal_date->minimumJulianDay() - $week_start) % $days_in_week === 0) { 266b00cb080SGreg Roach echo '</tr>'; 267b00cb080SGreg Roach } 268b00cb080SGreg Roach } 269b00cb080SGreg Roach echo '</tbody>'; 270b00cb080SGreg Roach echo '</table>'; 271b00cb080SGreg Roach 272b00cb080SGreg Roach return response(ob_get_clean()); 273b00cb080SGreg Roach } 274b00cb080SGreg Roach 275b00cb080SGreg Roach /** 276b00cb080SGreg Roach * Format a list of facts for display 277b00cb080SGreg Roach * 278b00cb080SGreg Roach * @param string[] $list 279b00cb080SGreg Roach * @param string $tag1 280b00cb080SGreg Roach * @param string $tag2 281b00cb080SGreg Roach * @param Tree $tree 282b00cb080SGreg Roach * 283b00cb080SGreg Roach * @return string 284b00cb080SGreg Roach */ 285b00cb080SGreg Roach private function calendarListText(array $list, string $tag1, string $tag2, Tree $tree): string 286b00cb080SGreg Roach { 287b00cb080SGreg Roach $html = ''; 288b00cb080SGreg Roach 289b00cb080SGreg Roach foreach ($list as $xref => $facts) { 2906b9cb339SGreg Roach $tmp = Registry::gedcomRecordFactory()->make((string) $xref, $tree); 291b00cb080SGreg Roach $html .= $tag1 . '<a href="' . e($tmp->url()) . '">' . $tmp->fullName() . '</a> '; 292b00cb080SGreg Roach $html .= '<div class="indent">' . $facts . '</div>' . $tag2; 293b00cb080SGreg Roach } 294b00cb080SGreg Roach 295b00cb080SGreg Roach return $html; 296b00cb080SGreg Roach } 297b00cb080SGreg Roach} 298