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