xref: /webtrees/app/Module/StoriesModule.php (revision 8fcd0d32e56ee262912bbdb593202cfd1cbc1615)
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\Auth;
21use Fisharebest\Webtrees\Database;
22use Fisharebest\Webtrees\I18N;
23use Fisharebest\Webtrees\Individual;
24use Fisharebest\Webtrees\Menu;
25use Fisharebest\Webtrees\Tree;
26use stdClass;
27use Symfony\Component\HttpFoundation\RedirectResponse;
28use Symfony\Component\HttpFoundation\Request;
29use Symfony\Component\HttpFoundation\Response;
30
31/**
32 * Class StoriesModule
33 */
34class StoriesModule extends AbstractModule implements ModuleTabInterface, ModuleConfigInterface, ModuleMenuInterface
35{
36    /** {@inheritdoc} */
37    public function getTitle(): string
38    {
39        /* I18N: Name of a module */
40        return I18N::translate('Stories');
41    }
42
43    /** {@inheritdoc} */
44    public function getDescription(): string
45    {
46        /* I18N: Description of the “Stories” module */
47        return I18N::translate('Add narrative stories to individuals in the family tree.');
48    }
49
50    /**
51     * The URL to a page where the user can modify the configuration of this module.
52     *
53     * @return string
54     */
55    public function getConfigLink(): string
56    {
57        return route('module', [
58            'module' => $this->getName(),
59            'action' => 'Admin',
60        ]);
61    }
62
63    /** {@inheritdoc} */
64    public function defaultTabOrder(): int
65    {
66        return 55;
67    }
68
69    /** {@inheritdoc} */
70    public function getTabContent(Individual $individual): string
71    {
72        return view('modules/stories/tab', [
73            'is_admin'   => Auth::isAdmin(),
74            'individual' => $individual,
75            'stories'    => $this->getStoriesForIndividual($individual),
76        ]);
77    }
78
79    /** {@inheritdoc} */
80    public function hasTabContent(Individual $individual): bool
81    {
82        return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual));
83    }
84
85    /** {@inheritdoc} */
86    public function isGrayedOut(Individual $individual): bool
87    {
88        return !empty($this->getStoriesForIndividual($individual));
89    }
90
91    /** {@inheritdoc} */
92    public function canLoadAjax(): bool
93    {
94        return false;
95    }
96
97    /**
98     * @param Individual $individual
99     *
100     * @return stdClass[]
101     */
102    private function getStoriesForIndividual(Individual $individual): array
103    {
104        $block_ids =
105            Database::prepare(
106                "SELECT block_id" .
107                " FROM `##block`" .
108                " WHERE module_name = :module_name" .
109                " AND xref          = :xref" .
110                " AND gedcom_id     = :tree_id"
111            )->execute([
112                'module_name' => $this->getName(),
113                'xref'        => $individual->xref(),
114                'tree_id'     => $individual->tree()->id(),
115            ])->fetchOneColumn();
116
117        $stories = [];
118        foreach ($block_ids as $block_id) {
119            $block_id = (int) $block_id;
120
121            // Only show this block for certain languages
122            $languages = $this->getBlockSetting($block_id, 'languages', '');
123            if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) {
124                $stories[] = (object) [
125                    'block_id'   => $block_id,
126                    'title'      => $this->getBlockSetting($block_id, 'title'),
127                    'story_body' => $this->getBlockSetting($block_id, 'story_body'),
128                ];
129            }
130        }
131
132        return $stories;
133    }
134
135    /**
136     * The user can re-order menus. Until they do, they are shown in this order.
137     *
138     * @return int
139     */
140    public function defaultMenuOrder(): int
141    {
142        return 30;
143    }
144
145    /**
146     * What is the default access level for this module?
147     *
148     * Some modules are aimed at admins or managers, and are not generally shown to users.
149     *
150     * @return int
151     */
152    public function defaultAccessLevel(): int
153    {
154        return Auth::PRIV_HIDE;
155    }
156
157    /**
158     * A menu, to be added to the main application menu.
159     *
160     * @param Tree $tree
161     *
162     * @return Menu|null
163     */
164    public function getMenu(Tree $tree)
165    {
166        $menu = new Menu($this->getTitle(), route('module', [
167            'module' => $this->getName(),
168            'action' => 'ShowList',
169            'ged'    => $tree->name(),
170        ]), 'menu-story');
171
172        return $menu;
173    }
174
175    /**
176     * @param Tree $tree
177     *
178     * @return Response
179     */
180    public function getAdminAction(Tree $tree): Response
181    {
182        $this->layout = 'layouts/administration';
183
184        $stories = Database::prepare(
185            "SELECT block_id, xref, gedcom_id" .
186            " FROM `##block` b" .
187            " WHERE module_name = :module_name" .
188            " AND gedcom_id = :tree_id" .
189            " ORDER BY gedcom_id, xref"
190        )->execute([
191            'tree_id'     => $tree->id(),
192            'module_name' => $this->getName(),
193        ])->fetchAll();
194
195        foreach ($stories as $story) {
196            $block_id = (int) $story->block_id;
197
198            $story->individual = Individual::getInstance($story->xref, $tree);
199            $story->title      = $this->getBlockSetting($block_id, 'title');
200            $story->languages  = $this->getBlockSetting($block_id, 'languages');
201        }
202
203        return $this->viewResponse('modules/stories/config', [
204            'stories'    => $stories,
205            'title'      => $this->getTitle() . ' — ' . $tree->title(),
206            'tree'       => $tree,
207            'tree_names' => Tree::getNameList(),
208        ]);
209    }
210
211    /**
212     * @param Request $request
213     * @param Tree    $tree
214     *
215     * @return Response
216     */
217    public function getAdminEditAction(Request $request, Tree $tree): Response
218    {
219        $this->layout = 'layouts/administration';
220
221        $block_id = (int) $request->get('block_id');
222
223        if ($block_id === 0) {
224            // Creating a new story
225            $individual  = Individual::getInstance($request->get('xref', ''), $tree);
226            $story_title = '';
227            $story_body  = '';
228            $languages   = [];
229
230            $title = I18N::translate('Add a story') . ' — ' . e($tree->title());
231        } else {
232            // Editing an existing story
233            $xref = (string) Database::prepare(
234                "SELECT xref FROM `##block` WHERE block_id = :block_id"
235            )->execute([
236                'block_id' => $block_id,
237            ])->fetchOne();
238
239            $individual  = Individual::getInstance($xref, $tree);
240            $story_title = $this->getBlockSetting($block_id, 'title', '');
241            $story_body  = $this->getBlockSetting($block_id, 'story_body', '');
242            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
243
244            $title = I18N::translate('Edit the story') . ' — ' . e($tree->title());
245        }
246
247        return $this->viewResponse('modules/stories/edit', [
248            'block_id'    => $block_id,
249            'languages'   => $languages,
250            'story_body'  => $story_body,
251            'story_title' => $story_title,
252            'title'       => $title,
253            'tree'        => $tree,
254            'individual'  => $individual,
255        ]);
256    }
257
258    /**
259     * @param Request $request
260     * @param Tree    $tree
261     *
262     * @return RedirectResponse
263     */
264    public function postAdminEditAction(Request $request, Tree $tree): RedirectResponse
265    {
266        $block_id    = (int) $request->get('block_id');
267        $xref        = $request->get('xref', '');
268        $story_body  = $request->get('story_body', '');
269        $story_title = $request->get('story_title', '');
270        $languages   = $request->get('languages', []);
271
272        if ($block_id !== 0) {
273            Database::prepare(
274                "UPDATE `##block` SET gedcom_id = :tree_id, xref = :xref WHERE block_id = :block_id"
275            )->execute([
276                'tree_id'  => $tree->id(),
277                'xref'     => $xref,
278                'block_id' => $block_id,
279            ]);
280        } else {
281            Database::prepare(
282                "INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (:tree_id, :xref, 'stories', 0)"
283            )->execute([
284                'tree_id' => $tree->id(),
285                'xref'    => $xref,
286            ]);
287
288            $block_id = Database::lastInsertId();
289        }
290
291        $this->setBlockSetting($block_id, 'story_body', $story_body);
292        $this->setBlockSetting($block_id, 'title', $story_title);
293        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
294
295        $url = route('module', [
296            'module' => 'stories',
297            'action' => 'Admin',
298            'ged'    => $tree->name(),
299        ]);
300
301        return new RedirectResponse($url);
302    }
303
304    /**
305     * @param Request $request
306     * @param Tree    $tree
307     *
308     * @return Response
309     */
310    public function postAdminDeleteAction(Request $request, Tree $tree): Response
311    {
312        $block_id = (int) $request->get('block_id');
313
314        Database::prepare(
315            "DELETE FROM `##block_setting` WHERE block_id = :block_id"
316        )->execute([
317            'block_id' => $block_id,
318        ]);
319
320        Database::prepare(
321            "DELETE FROM `##block` WHERE block_id = :block_id"
322        )->execute([
323            'block_id' => $block_id,
324        ]);
325
326        $url = route('module', [
327            'module' => 'stories',
328            'action' => 'Admin',
329            'ged'    => $tree->name(),
330        ]);
331
332        return new RedirectResponse($url);
333    }
334
335    /**
336     * @param Tree $tree
337     *
338     * @return Response
339     */
340    public function getShowListAction(Tree $tree): Response
341    {
342        $stories = Database::prepare(
343            "SELECT block_id, xref" .
344            " FROM `##block` b" .
345            " WHERE module_name = :module_name" .
346            " AND gedcom_id = :tree_id" .
347            " ORDER BY xref"
348        )->execute([
349            'module_name' => $this->getName(),
350            'tree_id'     => $tree->id(),
351        ])->fetchAll();
352
353        foreach ($stories as $story) {
354            $block_id = (int) $story->block_id;
355
356            $story->individual = Individual::getInstance($story->xref, $tree);
357            $story->title      = $this->getBlockSetting($block_id, 'title');
358            $story->languages  = $this->getBlockSetting($block_id, 'languages');
359        }
360
361        // Filter non-existant and private individuals.
362        $stories = array_filter($stories, function (stdClass $story): bool {
363            return $story->individual !== null && $story->individual->canShow();
364        });
365
366        // Filter foreign languages.
367        $stories = array_filter($stories, function (stdClass $story): bool {
368            return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages));
369        });
370
371        return $this->viewResponse('modules/stories/list', [
372            'stories' => $stories,
373            'title'   => $this->getTitle(),
374        ]);
375    }
376}
377