xref: /webtrees/app/Module/StoriesModule.php (revision bed27cedea507c28d3a20c0fce49bfb8580f832f)
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\I18N;
22use Fisharebest\Webtrees\Individual;
23use Fisharebest\Webtrees\Menu;
24use Fisharebest\Webtrees\Tree;
25use Illuminate\Database\Capsule\Manager as DB;
26use Psr\Http\Message\ResponseInterface;
27use Psr\Http\Message\ServerRequestInterface;
28use stdClass;
29
30/**
31 * Class StoriesModule
32 */
33class StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface
34{
35    use ModuleTabTrait;
36    use ModuleConfigTrait;
37    use ModuleMenuTrait;
38
39    /** @var int The default access level for this module.  It can be changed in the control panel. */
40    protected $access_level = Auth::PRIV_HIDE;
41
42    /**
43     * A sentence describing what this module does.
44     *
45     * @return string
46     */
47    public function description(): string
48    {
49        /* I18N: Description of the “Stories” module */
50        return I18N::translate('Add narrative stories to individuals in the family tree.');
51    }
52
53    /**
54     * The default position for this menu.  It can be changed in the control panel.
55     *
56     * @return int
57     */
58    public function defaultMenuOrder(): int
59    {
60        return 7;
61    }
62
63    /**
64     * The default position for this tab.  It can be changed in the control panel.
65     *
66     * @return int
67     */
68    public function defaultTabOrder(): int
69    {
70        return 9;
71    }
72
73    /** {@inheritdoc} */
74    public function getTabContent(Individual $individual): string
75    {
76        return view('modules/stories/tab', [
77            'is_admin'   => Auth::isAdmin(),
78            'individual' => $individual,
79            'stories'    => $this->getStoriesForIndividual($individual),
80        ]);
81    }
82
83    /**
84     * @param Individual $individual
85     *
86     * @return stdClass[]
87     */
88    private function getStoriesForIndividual(Individual $individual): array
89    {
90        $block_ids = DB::table('block')
91            ->where('module_name', '=', $this->name())
92            ->where('xref', '=', $individual->xref())
93            ->where('gedcom_id', '=', $individual->tree()->id())
94            ->pluck('block_id');
95
96        $stories = [];
97        foreach ($block_ids as $block_id) {
98            $block_id = (int) $block_id;
99
100            // Only show this block for certain languages
101            $languages = $this->getBlockSetting($block_id, 'languages', '');
102            if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages), true)) {
103                $stories[] = (object) [
104                    'block_id'   => $block_id,
105                    'title'      => $this->getBlockSetting($block_id, 'title'),
106                    'story_body' => $this->getBlockSetting($block_id, 'story_body'),
107                ];
108            }
109        }
110
111        return $stories;
112    }
113
114    /** {@inheritdoc} */
115    public function hasTabContent(Individual $individual): bool
116    {
117        return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual));
118    }
119
120    /** {@inheritdoc} */
121    public function isGrayedOut(Individual $individual): bool
122    {
123        return !empty($this->getStoriesForIndividual($individual));
124    }
125
126    /** {@inheritdoc} */
127    public function canLoadAjax(): bool
128    {
129        return false;
130    }
131
132    /**
133     * A menu, to be added to the main application menu.
134     *
135     * @param Tree $tree
136     *
137     * @return Menu|null
138     */
139    public function getMenu(Tree $tree): ?Menu
140    {
141        $menu = new Menu($this->title(), route('module', [
142            'module' => $this->name(),
143            'action' => 'ShowList',
144            'ged'    => $tree->name(),
145        ]), 'menu-story');
146
147        return $menu;
148    }
149
150    /**
151     * How should this module be identified in the control panel, etc.?
152     *
153     * @return string
154     */
155    public function title(): string
156    {
157        /* I18N: Name of a module */
158        return I18N::translate('Stories');
159    }
160
161    /**
162     * @param Tree $tree
163     *
164     * @return ResponseInterface
165     */
166    public function getAdminAction(Tree $tree): ResponseInterface
167    {
168        $this->layout = 'layouts/administration';
169
170        $stories = DB::table('block')
171            ->where('module_name', '=', $this->name())
172            ->where('gedcom_id', '=', $tree->id())
173            ->orderBy('xref')
174            ->get();
175
176        foreach ($stories as $story) {
177            $block_id = (int) $story->block_id;
178
179            $story->individual = Individual::getInstance($story->xref, $tree);
180            $story->title      = $this->getBlockSetting($block_id, 'title');
181            $story->languages  = $this->getBlockSetting($block_id, 'languages');
182        }
183
184        return $this->viewResponse('modules/stories/config', [
185            'stories'    => $stories,
186            'title'      => $this->title() . ' — ' . $tree->title(),
187            'tree'       => $tree,
188            'tree_names' => Tree::getNameList(),
189        ]);
190    }
191
192    /**
193     * @param ServerRequestInterface $request
194     * @param Tree                   $tree
195     *
196     * @return ResponseInterface
197     */
198    public function getAdminEditAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
199    {
200        $this->layout = 'layouts/administration';
201
202        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
203
204        if ($block_id === 0) {
205            // Creating a new story
206            $individual  = null;
207            $story_title = '';
208            $story_body  = '';
209            $languages   = [];
210
211            $title = I18N::translate('Add a story') . ' — ' . e($tree->title());
212        } else {
213            // Editing an existing story
214            $xref = (string) DB::table('block')
215                ->where('block_id', '=', $block_id)
216                ->value('xref');
217
218            $individual  = Individual::getInstance($xref, $tree);
219            $story_title = $this->getBlockSetting($block_id, 'title', '');
220            $story_body  = $this->getBlockSetting($block_id, 'story_body', '');
221            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
222
223            $title = I18N::translate('Edit the story') . ' — ' . e($tree->title());
224        }
225
226        return $this->viewResponse('modules/stories/edit', [
227            'block_id'    => $block_id,
228            'languages'   => $languages,
229            'story_body'  => $story_body,
230            'story_title' => $story_title,
231            'title'       => $title,
232            'tree'        => $tree,
233            'individual'  => $individual,
234        ]);
235    }
236
237    /**
238     * @param ServerRequestInterface $request
239     * @param Tree                   $tree
240     *
241     * @return ResponseInterface
242     */
243    public function postAdminEditAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
244    {
245        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
246
247        $params = $request->getParsedBody();
248
249        $xref        = $params['xref'];
250        $story_body  = $params['story_body'];
251        $story_title = $params['story_title'];
252        $languages   = $params['languages'] ?? [];
253
254        if ($block_id !== 0) {
255            DB::table('block')
256                ->where('block_id', '=', $block_id)
257                ->update([
258                    'gedcom_id' => $tree->id(),
259                    'xref'      => $xref,
260                ]);
261        } else {
262            DB::table('block')->insert([
263                'gedcom_id'   => $tree->id(),
264                'xref'        => $xref,
265                'module_name' => $this->name(),
266                'block_order' => 0,
267            ]);
268
269            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
270        }
271
272        $this->setBlockSetting($block_id, 'story_body', $story_body);
273        $this->setBlockSetting($block_id, 'title', $story_title);
274        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
275
276        $url = route('module', [
277            'module' => $this->name(),
278            'action' => 'Admin',
279            'ged'    => $tree->name(),
280        ]);
281
282        return redirect($url);
283    }
284
285    /**
286     * @param ServerRequestInterface $request
287     * @param Tree                   $tree
288     *
289     * @return ResponseInterface
290     */
291    public function postAdminDeleteAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
292    {
293        $block_id = $request->getQueryParams()['block_id'];
294
295        DB::table('block_setting')
296            ->where('block_id', '=', $block_id)
297            ->delete();
298
299        DB::table('block')
300            ->where('block_id', '=', $block_id)
301            ->delete();
302
303        $url = route('module', [
304            'module' => $this->name(),
305            'action' => 'Admin',
306            'ged'    => $tree->name(),
307        ]);
308
309        return redirect($url);
310    }
311
312    /**
313     * @param Tree $tree
314     *
315     * @return ResponseInterface
316     */
317    public function getShowListAction(Tree $tree): ResponseInterface
318    {
319        $stories = DB::table('block')
320            ->where('module_name', '=', $this->name())
321            ->where('gedcom_id', '=', $tree->id())
322            ->get()
323            ->map(function (stdClass $story) use ($tree): stdClass {
324                $block_id = (int) $story->block_id;
325
326                $story->individual = Individual::getInstance($story->xref, $tree);
327                $story->title      = $this->getBlockSetting($block_id, 'title');
328                $story->languages  = $this->getBlockSetting($block_id, 'languages');
329
330                return $story;
331            })->filter(static function (stdClass $story): bool {
332                // Filter non-existant and private individuals.
333                return $story->individual instanceof Individual && $story->individual->canShow();
334            })->filter(static function (stdClass $story): bool {
335                // Filter foreign languages.
336                return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true);
337            });
338
339        return $this->viewResponse('modules/stories/list', [
340            'stories' => $stories,
341            'title'   => $this->title(),
342        ]);
343    }
344}
345