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