1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Module; 19 20use Fisharebest\Webtrees\Auth; 21use Fisharebest\Webtrees\Carbon; 22use Fisharebest\Webtrees\Gedcom; 23use Fisharebest\Webtrees\GedcomTag; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Services\CalendarService; 26use Fisharebest\Webtrees\Tree; 27use Illuminate\Support\Str; 28use Psr\Http\Message\ServerRequestInterface; 29 30/** 31 * Class UpcomingAnniversariesModule 32 */ 33class UpcomingAnniversariesModule extends AbstractModule implements ModuleBlockInterface 34{ 35 use ModuleBlockTrait; 36 37 // Default values for new blocks. 38 private const DEFAULT_DAYS = '7'; 39 private const DEFAULT_FILTER = '1'; 40 private const DEFAULT_SORT = 'alpha'; 41 private const DEFAULT_STYLE = 'table'; 42 43 // Can show this number of days into the future. 44 private const MIN_DAYS = 1; 45 private const MAX_DAYS = 30; 46 47 // All standard GEDCOM 5.5.1 events except CENS, RESI and EVEN 48 private const ALL_EVENTS = [ 49 'ADOP', 50 'ANUL', 51 'BAPM', 52 'BARM', 53 'BASM', 54 'BIRT', 55 'BLES', 56 'BURI', 57 'CHR', 58 'CHRA', 59 'CONF', 60 'CREM', 61 'DEAT', 62 'DIV', 63 'DIVF', 64 'EMIG', 65 'ENGA', 66 'FCOM', 67 'GRAD', 68 'IMMI', 69 'MARB', 70 'MARC', 71 'MARL', 72 'MARR', 73 'MARS', 74 'NATU', 75 'ORDN', 76 'PROB', 77 'RETI', 78 'WILL', 79 ]; 80 81 private const DEFAULT_EVENTS = [ 82 'BIRT', 83 'MARR', 84 'DEAT', 85 ]; 86 87 /** 88 * How should this module be identified in the control panel, etc.? 89 * 90 * @return string 91 */ 92 public function title(): string 93 { 94 /* I18N: Name of a module */ 95 return I18N::translate('Upcoming events'); 96 } 97 98 /** 99 * A sentence describing what this module does. 100 * 101 * @return string 102 */ 103 public function description(): string 104 { 105 /* I18N: Description of the “Upcoming events” module */ 106 return I18N::translate('A list of the anniversaries that will occur in the near future.'); 107 } 108 109 /** 110 * Generate the HTML content of this block. 111 * 112 * @param Tree $tree 113 * @param int $block_id 114 * @param string $ctype 115 * @param string[] $cfg 116 * 117 * @return string 118 */ 119 public function getBlock(Tree $tree, int $block_id, string $ctype = '', array $cfg = []): string 120 { 121 $calendar_service = new CalendarService(); 122 123 $default_events = implode(',', self::DEFAULT_EVENTS); 124 125 $days = (int) $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS); 126 $filter = (bool) $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER); 127 $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE); 128 $sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT); 129 $events = $this->getBlockSetting($block_id, 'events', $default_events); 130 131 extract($cfg, EXTR_OVERWRITE); 132 133 $event_array = explode(',', $events); 134 135 // If we are only showing living individuals, then we don't need to search for DEAT events. 136 if ($filter) { 137 $event_array = array_diff($event_array, Gedcom::DEATH_EVENTS); 138 } 139 140 $events_filter = implode('|', $event_array); 141 142 $startjd = Carbon::now()->julianDay() + 1; 143 $endjd = Carbon::now()->julianDay() + $days; 144 145 $facts = $calendar_service->getEventsList($startjd, $endjd, $events_filter, $filter, $sortStyle, $tree); 146 147 if (empty($facts)) { 148 if ($endjd == $startjd) { 149 $content = view('modules/upcoming_events/empty', [ 150 'message' => I18N::translate('No events exist for tomorrow.'), 151 ]); 152 } else { 153 /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ $content = view('modules/upcoming_events/empty', [ 154 'message' => I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1)), 155 ]); 156 } 157 } elseif ($infoStyle === 'list') { 158 $content = view('modules/upcoming_events/list', [ 159 'facts' => $facts, 160 ]); 161 } else { 162 $content = view('modules/upcoming_events/table', [ 163 'facts' => $facts, 164 ]); 165 } 166 167 if ($ctype !== '') { 168 if ($ctype === 'gedcom' && Auth::isManager($tree)) { 169 $config_url = route('tree-page-block-edit', [ 170 'block_id' => $block_id, 171 'ged' => $tree->name(), 172 ]); 173 } elseif ($ctype === 'user' && Auth::check()) { 174 $config_url = route('user-page-block-edit', [ 175 'block_id' => $block_id, 176 'ged' => $tree->name(), 177 ]); 178 } else { 179 $config_url = ''; 180 } 181 182 return view('modules/block-template', [ 183 'block' => Str::kebab($this->name()), 184 'id' => $block_id, 185 'config_url' => $config_url, 186 'title' => $this->title(), 187 'content' => $content, 188 ]); 189 } 190 191 return $content; 192 } 193 194 /** {@inheritdoc} */ 195 public function loadAjax(): bool 196 { 197 return true; 198 } 199 200 /** {@inheritdoc} */ 201 public function isUserBlock(): bool 202 { 203 return true; 204 } 205 206 /** {@inheritdoc} */ 207 public function isTreeBlock(): bool 208 { 209 return true; 210 } 211 212 /** 213 * Update the configuration for a block. 214 * 215 * @param ServerRequestInterface $request 216 * @param int $block_id 217 * 218 * @return void 219 */ 220 public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void 221 { 222 $this->setBlockSetting($block_id, 'days', $request->get('days', self::DEFAULT_DAYS)); 223 $this->setBlockSetting($block_id, 'filter', $request->get('filter', '')); 224 $this->setBlockSetting($block_id, 'infoStyle', $request->get('infoStyle', self::DEFAULT_STYLE)); 225 $this->setBlockSetting($block_id, 'sortStyle', $request->get('sortStyle', self::DEFAULT_SORT)); 226 $this->setBlockSetting($block_id, 'events', implode(',', (array) $request->get('events'))); 227 } 228 229 /** 230 * An HTML form to edit block settings 231 * 232 * @param Tree $tree 233 * @param int $block_id 234 * 235 * @return void 236 */ 237 public function editBlockConfiguration(Tree $tree, int $block_id): void 238 { 239 $default_events = implode(',', self::DEFAULT_EVENTS); 240 241 $days = (int) $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS); 242 $filter = $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER); 243 $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE); 244 $sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT); 245 $events = $this->getBlockSetting($block_id, 'events', $default_events); 246 247 $event_array = explode(',', $events); 248 249 $all_events = []; 250 foreach (self::ALL_EVENTS as $event) { 251 $all_events[$event] = GedcomTag::getLabel($event); 252 } 253 254 $info_styles = [ 255 /* I18N: An option in a list-box */ 256 'list' => I18N::translate('list'), 257 /* I18N: An option in a list-box */ 258 'table' => I18N::translate('table'), 259 ]; 260 261 $sort_styles = [ 262 /* I18N: An option in a list-box */ 263 'alpha' => I18N::translate('sort by name'), 264 /* I18N: An option in a list-box */ 265 'anniv' => I18N::translate('sort by date'), 266 ]; 267 268 echo view('modules/upcoming_events/config', [ 269 'all_events' => $all_events, 270 'days' => $days, 271 'event_array' => $event_array, 272 'filter' => $filter, 273 'infoStyle' => $infoStyle, 274 'info_styles' => $info_styles, 275 'max_days' => self::MAX_DAYS, 276 'sortStyle' => $sortStyle, 277 'sort_styles' => $sort_styles, 278 ]); 279 } 280} 281