xref: /webtrees/app/Module/UpcomingAnniversariesModule.php (revision 3976b4703df669696105ed6b024b96d433c8fbdb)
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 */
17declare(strict_types=1);
18
19namespace Fisharebest\Webtrees\Module;
20
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   $context
115     * @param string[] $config
116     *
117     * @return string
118     */
119    public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): 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($config, 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('lists/anniversaries-list', [
159                'facts' => $facts,
160            ]);
161        } else {
162            $content = view('lists/anniversaries-table', [
163                'facts' => $facts,
164            ]);
165        }
166
167        if ($context !== self::CONTEXT_EMBED) {
168            return view('modules/block-template', [
169                'block'      => Str::kebab($this->name()),
170                'id'         => $block_id,
171                'config_url' => $this->configUrl($tree, $context, $block_id),
172                'title'      => $this->title(),
173                'content'    => $content,
174            ]);
175        }
176
177        return $content;
178    }
179
180    /**
181     * Should this block load asynchronously using AJAX?
182     *
183     * Simple blocks are faster in-line, more complex ones can be loaded later.
184     *
185     * @return bool
186     */
187    public function loadAjax(): bool
188    {
189        return true;
190    }
191
192    /**
193     * Can this block be shown on the user’s home page?
194     *
195     * @return bool
196     */
197    public function isUserBlock(): bool
198    {
199        return true;
200    }
201
202    /**
203     * Can this block be shown on the tree’s home page?
204     *
205     * @return bool
206     */
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        $params = $request->getParsedBody();
223
224        $this->setBlockSetting($block_id, 'days', $params['days']);
225        $this->setBlockSetting($block_id, 'filter', $params['filter']);
226        $this->setBlockSetting($block_id, 'infoStyle', $params['infoStyle']);
227        $this->setBlockSetting($block_id, 'sortStyle', $params['sortStyle']);
228        $this->setBlockSetting($block_id, 'events', implode(',', $params['events'] ?? []));
229    }
230
231    /**
232     * An HTML form to edit block settings
233     *
234     * @param Tree $tree
235     * @param int  $block_id
236     *
237     * @return string
238     */
239    public function editBlockConfiguration(Tree $tree, int $block_id): string
240    {
241        $default_events = implode(',', self::DEFAULT_EVENTS);
242
243        $days      = (int) $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS);
244        $filter    = $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER);
245        $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
246        $sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT);
247        $events    = $this->getBlockSetting($block_id, 'events', $default_events);
248
249        $event_array = explode(',', $events);
250
251        $all_events = [];
252        foreach (self::ALL_EVENTS as $event) {
253            $all_events[$event] = GedcomTag::getLabel($event);
254        }
255
256        $info_styles = [
257            /* I18N: An option in a list-box */
258            'list'  => I18N::translate('list'),
259            /* I18N: An option in a list-box */
260            'table' => I18N::translate('table'),
261        ];
262
263        $sort_styles = [
264            /* I18N: An option in a list-box */
265            'alpha' => I18N::translate('sort by name'),
266            /* I18N: An option in a list-box */
267            'anniv' => I18N::translate('sort by date'),
268        ];
269
270        return view('modules/upcoming_events/config', [
271            'all_events'  => $all_events,
272            'days'        => $days,
273            'event_array' => $event_array,
274            'filter'      => $filter,
275            'infoStyle'   => $infoStyle,
276            'info_styles' => $info_styles,
277            'max_days'    => self::MAX_DAYS,
278            'sortStyle'   => $sortStyle,
279            'sort_styles' => $sort_styles,
280        ]);
281    }
282}
283