1354a9dbaSGreg Roach<?php 2354a9dbaSGreg Roach 3354a9dbaSGreg Roach/** 4354a9dbaSGreg Roach * webtrees: online genealogy 5354a9dbaSGreg Roach * Copyright (C) 2021 webtrees development team 6354a9dbaSGreg Roach * This program is free software: you can redistribute it and/or modify 7354a9dbaSGreg Roach * it under the terms of the GNU General Public License as published by 8354a9dbaSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9354a9dbaSGreg Roach * (at your option) any later version. 10354a9dbaSGreg Roach * This program is distributed in the hope that it will be useful, 11354a9dbaSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12354a9dbaSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13354a9dbaSGreg Roach * GNU General Public License for more details. 14354a9dbaSGreg Roach * You should have received a copy of the GNU General Public License 15354a9dbaSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16354a9dbaSGreg Roach */ 17354a9dbaSGreg Roach 18354a9dbaSGreg Roachdeclare(strict_types=1); 19354a9dbaSGreg Roach 20354a9dbaSGreg Roachnamespace Fisharebest\Webtrees\Module; 21354a9dbaSGreg Roach 22354a9dbaSGreg Roachuse Aura\Router\RouterContainer; 23354a9dbaSGreg Roachuse Fisharebest\Webtrees\Auth; 24354a9dbaSGreg Roachuse Fisharebest\Webtrees\Date\GregorianDate; 25354a9dbaSGreg Roachuse Fisharebest\Webtrees\Fact; 26354a9dbaSGreg Roachuse Fisharebest\Webtrees\Family; 27354a9dbaSGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 2881b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 29354a9dbaSGreg Roachuse Fisharebest\Webtrees\I18N; 30354a9dbaSGreg Roachuse Fisharebest\Webtrees\Individual; 31354a9dbaSGreg Roachuse Fisharebest\Webtrees\Registry; 32354a9dbaSGreg Roachuse Fisharebest\Webtrees\Tree; 33354a9dbaSGreg Roachuse Illuminate\Support\Collection; 34354a9dbaSGreg Roachuse Psr\Http\Message\ResponseInterface; 35354a9dbaSGreg Roachuse Psr\Http\Message\ServerRequestInterface; 36354a9dbaSGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 37354a9dbaSGreg Roachuse Sabre\VObject\Component\VCalendar; 38354a9dbaSGreg Roach 39354a9dbaSGreg Roachuse function app; 40354a9dbaSGreg Roachuse function assert; 41354a9dbaSGreg Roachuse function response; 42354a9dbaSGreg Roachuse function route; 43354a9dbaSGreg Roachuse function strip_tags; 44354a9dbaSGreg Roachuse function view; 45354a9dbaSGreg Roach 46354a9dbaSGreg Roach/** 47354a9dbaSGreg Roach * Class ShareAnniversaryModule 48354a9dbaSGreg Roach */ 49354a9dbaSGreg Roachclass ShareAnniversaryModule extends AbstractModule implements ModuleShareInterface, RequestHandlerInterface 50354a9dbaSGreg Roach{ 51354a9dbaSGreg Roach use ModuleShareTrait; 52354a9dbaSGreg Roach 53354a9dbaSGreg Roach protected const INDIVIDUAL_EVENTS = ['BIRT', 'DEAT']; 54354a9dbaSGreg Roach protected const FAMILY_EVENTS = ['MARR']; 55354a9dbaSGreg Roach 56354a9dbaSGreg Roach protected const ROUTE_URL = '/tree/{tree}/anniversary-ics/{xref}/{fact_id}'; 57354a9dbaSGreg Roach 58354a9dbaSGreg Roach /** 59354a9dbaSGreg Roach * Initialization. 60354a9dbaSGreg Roach * 61354a9dbaSGreg Roach * @return void 62354a9dbaSGreg Roach */ 63354a9dbaSGreg Roach public function boot(): void 64354a9dbaSGreg Roach { 65354a9dbaSGreg Roach $router_container = app(RouterContainer::class); 66354a9dbaSGreg Roach assert($router_container instanceof RouterContainer); 67354a9dbaSGreg Roach 68354a9dbaSGreg Roach $router_container->getMap() 69354a9dbaSGreg Roach ->get(static::class, static::ROUTE_URL, $this); 70354a9dbaSGreg Roach } 71354a9dbaSGreg Roach 72354a9dbaSGreg Roach /** 73354a9dbaSGreg Roach * How should this module be identified in the control panel, etc.? 74354a9dbaSGreg Roach * 75354a9dbaSGreg Roach * @return string 76354a9dbaSGreg Roach */ 77354a9dbaSGreg Roach public function title(): string 78354a9dbaSGreg Roach { 79354a9dbaSGreg Roach return I18N::translate('Share the anniversary of an event'); 80354a9dbaSGreg Roach } 81354a9dbaSGreg Roach 82354a9dbaSGreg Roach /** 83354a9dbaSGreg Roach * A sentence describing what this module does. 84354a9dbaSGreg Roach * 85354a9dbaSGreg Roach * @return string 86354a9dbaSGreg Roach */ 87354a9dbaSGreg Roach public function description(): string 88354a9dbaSGreg Roach { 89354a9dbaSGreg Roach return I18N::translate('Download a .ICS file containing an anniversary'); 90354a9dbaSGreg Roach } 91354a9dbaSGreg Roach 92354a9dbaSGreg Roach /** 93354a9dbaSGreg Roach * HTML to include in the share links page. 94354a9dbaSGreg Roach * 95354a9dbaSGreg Roach * @param GedcomRecord $record 96354a9dbaSGreg Roach * 97354a9dbaSGreg Roach * @return string 98354a9dbaSGreg Roach */ 99354a9dbaSGreg Roach public function share(GedcomRecord $record): string 100354a9dbaSGreg Roach { 101354a9dbaSGreg Roach if ($record instanceof Individual) { 102354a9dbaSGreg Roach $facts = $record->facts(static::INDIVIDUAL_EVENTS, true) 103354a9dbaSGreg Roach ->merge($record->spouseFamilies()->map(fn (Family $family): Collection => $family->facts(static::FAMILY_EVENTS, true))); 104354a9dbaSGreg Roach } elseif ($record instanceof Family) { 105354a9dbaSGreg Roach $facts = $record->facts(static::FAMILY_EVENTS, true); 106354a9dbaSGreg Roach } else { 107354a9dbaSGreg Roach return ''; 108354a9dbaSGreg Roach } 109354a9dbaSGreg Roach 110354a9dbaSGreg Roach // iCalendar only supports exact Gregorian dates. 111354a9dbaSGreg Roach $facts = $facts 112354a9dbaSGreg Roach ->flatten() 113354a9dbaSGreg Roach ->filter(fn (Fact $fact): bool => $fact->date()->isOK()) 114354a9dbaSGreg Roach ->filter(fn (Fact $fact): bool => $fact->date()->qual1 === '') 115354a9dbaSGreg Roach ->filter(fn (Fact $fact): bool => $fact->date()->minimumDate() instanceof GregorianDate) 116138837acSGreg Roach ->filter(fn (Fact $fact): bool => $fact->date()->minimumJulianDay() === $fact->date()->maximumJulianDay()) 117354a9dbaSGreg Roach ->mapWithKeys(fn (Fact $fact): array => [ 118354a9dbaSGreg Roach route(static::class, ['tree' => $record->tree()->name(), 'xref' => $fact->record()->xref(), 'fact_id' => $fact->id()]) => 119*66ecd017SGreg Roach $fact->label() . ' — ' . $fact->date()->display(), 120354a9dbaSGreg Roach ]); 121354a9dbaSGreg Roach 122354a9dbaSGreg Roach if ($facts->isNotEmpty()) { 123354a9dbaSGreg Roach $url = route(static::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()]); 124354a9dbaSGreg Roach 125354a9dbaSGreg Roach return view('modules/share-anniversary/share', [ 126354a9dbaSGreg Roach 'facts' => $facts, 127354a9dbaSGreg Roach 'record' => $record, 128354a9dbaSGreg Roach 'url' => $url, 129354a9dbaSGreg Roach ]); 130354a9dbaSGreg Roach } 131354a9dbaSGreg Roach 132354a9dbaSGreg Roach return ''; 133354a9dbaSGreg Roach } 134354a9dbaSGreg Roach 135354a9dbaSGreg Roach /** 136354a9dbaSGreg Roach * @param ServerRequestInterface $request 137354a9dbaSGreg Roach * 138354a9dbaSGreg Roach * @return ResponseInterface 139354a9dbaSGreg Roach */ 140354a9dbaSGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 141354a9dbaSGreg Roach { 142354a9dbaSGreg Roach $tree = $request->getAttribute('tree'); 143354a9dbaSGreg Roach assert($tree instanceof Tree); 144354a9dbaSGreg Roach 145354a9dbaSGreg Roach $xref = $request->getAttribute('xref'); 146354a9dbaSGreg Roach $fact_id = $request->getAttribute('fact_id'); 147354a9dbaSGreg Roach 148354a9dbaSGreg Roach $record = Registry::gedcomRecordFactory()->make($xref, $tree); 149354a9dbaSGreg Roach $record = Auth::checkRecordAccess($record); 150354a9dbaSGreg Roach 151354a9dbaSGreg Roach $fact = $record->facts() 152354a9dbaSGreg Roach ->filter(fn (Fact $fact): bool => $fact->id() === $fact_id) 153354a9dbaSGreg Roach ->first(); 154354a9dbaSGreg Roach 155354a9dbaSGreg Roach if ($fact instanceof Fact) { 156354a9dbaSGreg Roach $date = $fact->date()->minimumDate()->format('%Y%m%d'); 157354a9dbaSGreg Roach $vcalendar = new VCalendar(); 158354a9dbaSGreg Roach $vevent = $vcalendar->add('VEVENT'); 159354a9dbaSGreg Roach $dtstart = $vevent->add('DTSTART', $date); 160354a9dbaSGreg Roach $dtstart['VALUE'] = 'DATE'; 161354a9dbaSGreg Roach $vevent->add('RRULE', 'FREQ=YEARLY'); 162354a9dbaSGreg Roach $vevent->add('SUMMARY', strip_tags($record->fullName()) . ' — ' . $fact->label()); 163354a9dbaSGreg Roach 164354a9dbaSGreg Roach return response($vcalendar->serialize()) 165354a9dbaSGreg Roach ->withHeader('Content-Type', 'text/calendar') 166354a9dbaSGreg Roach ->withHeader('Content-Disposition', 'attachment; filename="' . $fact->id() . '.ics'); 167354a9dbaSGreg Roach } 168354a9dbaSGreg Roach 169354a9dbaSGreg Roach throw new HttpNotFoundException(); 170354a9dbaSGreg Roach } 171354a9dbaSGreg Roach} 172