xref: /webtrees/app/Module/UpcomingAnniversariesModule.php (revision 37325501fd7f995cc4d53053bde6fda79d5d2877)
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 Carbon\Carbon;
21use Fisharebest\Webtrees\Auth;
22use Fisharebest\Webtrees\Gedcom;
23use Fisharebest\Webtrees\GedcomTag;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Services\CalendarService;
26use Fisharebest\Webtrees\Tree;
27use Symfony\Component\HttpFoundation\Request;
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 labelled on tabs, menus, 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   $ctype
114     * @param string[] $cfg
115     *
116     * @return string
117     */
118    public function getBlock(Tree $tree, int $block_id, string $ctype = '', array $cfg = []): 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($cfg, 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 = unixtojd(Carbon::now()->timestamp) + 1;
142        $endjd   = unixtojd(Carbon::now()->timestamp) + $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('modules/upcoming_events/list', [
158                'facts' => $facts,
159            ]);
160        } else {
161            $content = view('modules/upcoming_events/table', [
162                'facts' => $facts,
163            ]);
164        }
165
166        if ($ctype !== '') {
167            if ($ctype === 'gedcom' && Auth::isManager($tree)) {
168                $config_url = route('tree-page-block-edit', [
169                    'block_id' => $block_id,
170                    'ged'      => $tree->name(),
171                ]);
172            } elseif ($ctype === 'user' && Auth::check()) {
173                $config_url = route('user-page-block-edit', [
174                    'block_id' => $block_id,
175                    'ged'      => $tree->name(),
176                ]);
177            } else {
178                $config_url = '';
179            }
180
181            return view('modules/block-template', [
182                'block'      => str_replace('_', '-', $this->name()),
183                'id'         => $block_id,
184                'config_url' => $config_url,
185                'title'      => $this->title(),
186                'content'    => $content,
187            ]);
188        }
189
190        return $content;
191    }
192
193    /** {@inheritdoc} */
194    public function loadAjax(): bool
195    {
196        return true;
197    }
198
199    /** {@inheritdoc} */
200    public function isUserBlock(): bool
201    {
202        return true;
203    }
204
205    /** {@inheritdoc} */
206    public function isTreeBlock(): bool
207    {
208        return true;
209    }
210
211    /**
212     * Update the configuration for a block.
213     *
214     * @param Request $request
215     * @param int     $block_id
216     *
217     * @return void
218     */
219    public function saveBlockConfiguration(Request $request, int $block_id)
220    {
221        $this->setBlockSetting($block_id, 'days', $request->get('days', self::DEFAULT_DAYS));
222        $this->setBlockSetting($block_id, 'filter', $request->get('filter', ''));
223        $this->setBlockSetting($block_id, 'infoStyle', $request->get('infoStyle', self::DEFAULT_STYLE));
224        $this->setBlockSetting($block_id, 'sortStyle', $request->get('sortStyle', self::DEFAULT_SORT));
225        $this->setBlockSetting($block_id, 'events', implode(',', (array) $request->get('events')));
226    }
227
228    /**
229     * An HTML form to edit block settings
230     *
231     * @param Tree $tree
232     * @param int  $block_id
233     *
234     * @return void
235     */
236    public function editBlockConfiguration(Tree $tree, int $block_id)
237    {
238        $default_events = implode(',', self::DEFAULT_EVENTS);
239
240        $days      = (int) $this->getBlockSetting($block_id, 'days', self::DEFAULT_DAYS);
241        $filter    = $this->getBlockSetting($block_id, 'filter', self::DEFAULT_FILTER);
242        $infoStyle = $this->getBlockSetting($block_id, 'infoStyle', self::DEFAULT_STYLE);
243        $sortStyle = $this->getBlockSetting($block_id, 'sortStyle', self::DEFAULT_SORT);
244        $events    = $this->getBlockSetting($block_id, 'events', $default_events);
245
246        $event_array = explode(',', $events);
247
248        $all_events = [];
249        foreach (self::ALL_EVENTS as $event) {
250            $all_events[$event] = GedcomTag::getLabel($event);
251        }
252
253        $info_styles = [
254            /* I18N: An option in a list-box */
255            'list'  => I18N::translate('list'),
256            /* I18N: An option in a list-box */
257            'table' => I18N::translate('table'),
258        ];
259
260        $sort_styles = [
261            /* I18N: An option in a list-box */
262            'alpha' => I18N::translate('sort by name'),
263            /* I18N: An option in a list-box */
264            'anniv' => I18N::translate('sort by date'),
265        ];
266
267        echo view('modules/upcoming_events/config', [
268            'all_events'  => $all_events,
269            'days'        => $days,
270            'event_array' => $event_array,
271            'filter'      => $filter,
272            'infoStyle'   => $infoStyle,
273            'info_styles' => $info_styles,
274            'max_days'    => self::MAX_DAYS,
275            'sortStyle'   => $sortStyle,
276            'sort_styles' => $sort_styles,
277        ]);
278    }
279}
280