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