xref: /webtrees/app/Module/UpcomingAnniversariesModule.php (revision b295534922aa0d15bf8b3821f74784a5ff810d62)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 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 */
16namespace Fisharebest\Webtrees\Module;
17
18use Fisharebest\Webtrees\Auth;
19use Fisharebest\Webtrees\Filter;
20use Fisharebest\Webtrees\Functions\FunctionsDb;
21use Fisharebest\Webtrees\GedcomTag;
22use Fisharebest\Webtrees\I18N;
23use Fisharebest\Webtrees\Tree;
24
25/**
26 * Class UpcomingAnniversariesModule
27 */
28class UpcomingAnniversariesModule extends AbstractModule implements ModuleBlockInterface {
29	// Default values for new blocks.
30	const DEFAULT_DAYS    = 7;
31	const DEFAULT_FILTER  = '1';
32	const DEFAULT_SORT    = 'alpha';
33	const DEFAULT_STYLE   = 'table';
34
35	// Can show this number of days into the future.
36	const MIN_DAYS = 1;
37	const MAX_DAYS = 30;
38
39	// All standard GEDCOM 5.5.1 events except CENS, RESI and EVEN
40	const ALL_EVENTS = [
41		'ADOP',
42		'ANUL',
43		'BAPM',
44		'BARM',
45		'BASM',
46		'BIRT',
47		'BLES',
48		'BURI',
49		'CHR',
50		'CHRA',
51		'CONF',
52		'CREM',
53		'DEAT',
54		'DIV',
55		'DIVF',
56		'EMIG',
57		'ENGA',
58		'FCOM',
59		'GRAD',
60		'IMMI',
61		'MARB',
62		'MARC',
63		'MARL',
64		'MARR',
65		'MARS',
66		'NATU',
67		'ORDN',
68		'PROB',
69		'RETI',
70		'WILL',
71	];
72
73	const DEFAULT_EVENTS = [
74		'BIRT',
75		'MARR',
76		'DEAT',
77	];
78
79	/**
80	 * How should this module be labelled on tabs, menus, etc.?
81	 *
82	 * @return string
83	 */
84	public function getTitle() {
85		return /* I18N: Name of a module */ I18N::translate('Upcoming events');
86	}
87
88	/**
89	 * A sentence describing what this module does.
90	 *
91	 * @return string
92	 */
93	public function getDescription() {
94		return /* I18N: Description of the “Upcoming events” module */ I18N::translate('A list of the anniversaries that will occur in the near future.');
95	}
96
97	/**
98	 * Generate the HTML content of this block.
99	 *
100	 * @param Tree     $tree
101	 * @param int      $block_id
102	 * @param bool     $template
103	 * @param string[] $cfg
104	 *
105	 * @return string
106	 */
107	public function getBlock(Tree $tree, int $block_id, bool $template = true, array $cfg = []): string {
108		global $ctype;
109
110		$default_events = implode(',', self::DEFAULT_EVENTS);
111
112		$days      = $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS);
113		$filter    = (bool) $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER);
114		$infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
115		$sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT);
116		$events    = $this->getBlockSetting($block_id, 'events', $default_events);
117
118		extract($cfg, EXTR_OVERWRITE);
119
120		$event_array = explode(',', $events);
121
122		// If we are only showing living individuals, then we don't need to search for DEAT events.
123		if ($filter) {
124			$death_events = explode('|', WT_EVENTS_DEAT);
125			$event_array = array_diff($event_array, $death_events);
126		}
127
128		$events_filter = implode('|', $event_array);
129
130		$startjd = WT_CLIENT_JD + 1;
131		$endjd   = WT_CLIENT_JD + (int) $days;
132
133		$facts = FunctionsDb::getEventsList($startjd, $endjd, $events_filter, $filter, $sortStyle, $tree);
134
135		if (empty($facts)) {
136			if ($endjd == $startjd) {
137				$content = view('modules/upcoming_events/empty', [
138					'message' => I18N::translate('No events exist for tomorrow.')
139				]);
140			} else {
141				$content = view('modules/upcoming_events/empty', [
142					'message' => /* I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” */ 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))
143				]);
144			}
145		} elseif ($infoStyle === 'list') {
146			$content = view('modules/upcoming_events/list', [
147				'facts'   => $facts,
148			]);
149		} else {
150			$content = view('modules/upcoming_events/table', [
151				'facts'   => $facts,
152			]);
153		}
154
155		if ($template) {
156			if ($ctype === 'gedcom' && Auth::isManager($tree)) {
157				$config_url = route('tree-page-block-edit', ['block_id' => $block_id, 'ged' => $tree->getName()]);
158			} elseif ($ctype === 'user' && Auth::check()) {
159				$config_url = route('user-page-block-edit', ['block_id' => $block_id, 'ged' => $tree->getName()]);
160			} else {
161				$config_url = '';
162			}
163
164			return view('modules/block-template', [
165				'block'      => str_replace('_', '-', $this->getName()),
166				'id'         => $block_id,
167				'config_url' => $config_url,
168				'title'      => $this->getTitle(),
169				'content'    => $content,
170			]);
171		} else {
172			return $content;
173		}
174	}
175
176	/** {@inheritdoc} */
177	public function loadAjax(): bool {
178		return true;
179	}
180
181	/** {@inheritdoc} */
182	public function isUserBlock(): bool {
183		return true;
184	}
185
186	/** {@inheritdoc} */
187	public function isGedcomBlock(): bool {
188		return true;
189	}
190
191	/**
192	 * An HTML form to edit block settings
193	 *
194	 * @param Tree $tree
195	 * @param int  $block_id
196	 *
197	 * @return void
198	 */
199	public function configureBlock(Tree $tree, int $block_id) {
200		if ($_SERVER['REQUEST_METHOD'] === 'POST') {
201			$this->setBlockSetting($block_id, 'days', Filter::postInteger('days', self::MIN_DAYS, self::MAX_DAYS, self::DEFAULT_DAYS));
202			$this->setBlockSetting($block_id, 'filter', Filter::postBool('filter'));
203			$this->setBlockSetting($block_id, 'infoStyle', Filter::post('infoStyle', 'list|table', self::DEFAULT_STYLE));
204			$this->setBlockSetting($block_id, 'sortStyle', Filter::post('sortStyle', 'alpha|anniv', self::DEFAULT_SORT));
205			$this->setBlockSetting($block_id, 'events', implode(',', Filter::postArray('events')));
206
207			return;
208		}
209
210		$default_events = implode(',', self::DEFAULT_EVENTS);
211
212		$days      = $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS);
213		$filter    = $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER);
214		$infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
215		$sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT);
216		$events    = $this->getBlockSetting($block_id, 'events', $default_events);
217
218		$event_array = explode(',', $events);
219
220		$all_events = [];
221		foreach (self::ALL_EVENTS as $event) {
222			$all_events[$event] = GedcomTag::getLabel($event);
223		}
224
225		$info_styles = [
226			'list'  => /* I18N: An option in a list-box */ I18N::translate('list'),
227			'table' => /* I18N: An option in a list-box */ I18N::translate('table'),
228		];
229
230		$sort_styles = [
231			'alpha' => /* I18N: An option in a list-box */ I18N::translate('sort by name'),
232			'anniv' => /* I18N: An option in a list-box */ I18N::translate('sort by date'),
233		];
234
235		echo view('modules/upcoming_events/config', [
236			'all_events'  => $all_events,
237			'days'        => $days,
238			'event_array' => $event_array,
239			'filter'      => $filter,
240			'infoStyle'   => $infoStyle,
241			'info_styles' => $info_styles,
242			'max_days'    => self::MAX_DAYS,
243			'sortStyle'   => $sortStyle,
244			'sort_styles' => $sort_styles,
245		]);
246	}
247}
248