xref: /webtrees/app/Module/StoriesModule.php (revision 5229eade2a8cdd1381e19f96f67fc1c8b92ca95d)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team
68c2e8227SGreg Roach * This program is free software: you can redistribute it and/or modify
78c2e8227SGreg Roach * it under the terms of the GNU General Public License as published by
88c2e8227SGreg Roach * the Free Software Foundation, either version 3 of the License, or
98c2e8227SGreg Roach * (at your option) any later version.
108c2e8227SGreg Roach * This program is distributed in the hope that it will be useful,
118c2e8227SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
128c2e8227SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138c2e8227SGreg Roach * GNU General Public License for more details.
148c2e8227SGreg Roach * You should have received a copy of the GNU General Public License
158c2e8227SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
168c2e8227SGreg Roach */
17e7f56f2aSGreg Roachdeclare(strict_types=1);
18e7f56f2aSGreg Roach
1976692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module;
2076692c8bSGreg Roach
210e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth;
220e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
230e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
240e62c4b8SGreg Roachuse Fisharebest\Webtrees\Menu;
2550d6f48cSGreg Roachuse Fisharebest\Webtrees\Services\HtmlService;
260e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree;
274b92b602SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
28*5229eadeSGreg Roachuse InvalidArgumentException;
296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
31225e381fSGreg Roachuse stdClass;
32*5229eadeSGreg Roachuse function assert;
338c2e8227SGreg Roach
348c2e8227SGreg Roach/**
358c2e8227SGreg Roach * Class StoriesModule
368c2e8227SGreg Roach */
3737eb8894SGreg Roachclass StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface
38c1010edaSGreg Roach{
3949a243cbSGreg Roach    use ModuleTabTrait;
4049a243cbSGreg Roach    use ModuleConfigTrait;
4149a243cbSGreg Roach    use ModuleMenuTrait;
4249a243cbSGreg Roach
4350d6f48cSGreg Roach    /** @var HtmlService */
4450d6f48cSGreg Roach    private $html_service;
4550d6f48cSGreg Roach
4650d6f48cSGreg Roach    /**
4750d6f48cSGreg Roach     * HtmlBlockModule bootstrap.
4850d6f48cSGreg Roach     *
4950d6f48cSGreg Roach     * @param HtmlService $html_service
5050d6f48cSGreg Roach     */
5150d6f48cSGreg Roach    public function boot(HtmlService $html_service)
5250d6f48cSGreg Roach    {
5350d6f48cSGreg Roach        $this->html_service = $html_service;
5450d6f48cSGreg Roach    }
5550d6f48cSGreg Roach
5649a243cbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
5749a243cbSGreg Roach    protected $access_level = Auth::PRIV_HIDE;
5849a243cbSGreg Roach
59961ec755SGreg Roach    /**
60961ec755SGreg Roach     * A sentence describing what this module does.
61961ec755SGreg Roach     *
62961ec755SGreg Roach     * @return string
63961ec755SGreg Roach     */
6449a243cbSGreg Roach    public function description(): string
65c1010edaSGreg Roach    {
66bbb76c12SGreg Roach        /* I18N: Description of the “Stories” module */
67bbb76c12SGreg Roach        return I18N::translate('Add narrative stories to individuals in the family tree.');
688c2e8227SGreg Roach    }
698c2e8227SGreg Roach
70aee13b6dSGreg Roach    /**
7149a243cbSGreg Roach     * The default position for this menu.  It can be changed in the control panel.
72aee13b6dSGreg Roach     *
7349a243cbSGreg Roach     * @return int
74aee13b6dSGreg Roach     */
7549a243cbSGreg Roach    public function defaultMenuOrder(): int
76c1010edaSGreg Roach    {
77353b36abSGreg Roach        return 7;
788c2e8227SGreg Roach    }
798c2e8227SGreg Roach
8049a243cbSGreg Roach    /**
8149a243cbSGreg Roach     * The default position for this tab.  It can be changed in the control panel.
8249a243cbSGreg Roach     *
8349a243cbSGreg Roach     * @return int
8449a243cbSGreg Roach     */
85cbf4b7faSGreg Roach    public function defaultTabOrder(): int
86cbf4b7faSGreg Roach    {
87fb7a0427SGreg Roach        return 9;
888c2e8227SGreg Roach    }
898c2e8227SGreg Roach
903caaa4d2SGreg Roach    /**
913caaa4d2SGreg Roach     * Generate the HTML content of this tab.
923caaa4d2SGreg Roach     *
933caaa4d2SGreg Roach     * @param Individual $individual
943caaa4d2SGreg Roach     *
953caaa4d2SGreg Roach     * @return string
963caaa4d2SGreg Roach     */
979b34404bSGreg Roach    public function getTabContent(Individual $individual): string
98c1010edaSGreg Roach    {
9972ac996dSGreg Roach        return view('modules/stories/tab', [
10072ac996dSGreg Roach            'is_admin'   => Auth::isAdmin(),
101225e381fSGreg Roach            'individual' => $individual,
102225e381fSGreg Roach            'stories'    => $this->getStoriesForIndividual($individual),
103225e381fSGreg Roach        ]);
1048c2e8227SGreg Roach    }
1058c2e8227SGreg Roach
106225e381fSGreg Roach    /**
107225e381fSGreg Roach     * @param Individual $individual
108225e381fSGreg Roach     *
109225e381fSGreg Roach     * @return stdClass[]
110225e381fSGreg Roach     */
111c1010edaSGreg Roach    private function getStoriesForIndividual(Individual $individual): array
112c1010edaSGreg Roach    {
1134b92b602SGreg Roach        $block_ids = DB::table('block')
11426684e68SGreg Roach            ->where('module_name', '=', $this->name())
1154b92b602SGreg Roach            ->where('xref', '=', $individual->xref())
1164b92b602SGreg Roach            ->where('gedcom_id', '=', $individual->tree()->id())
1174b92b602SGreg Roach            ->pluck('block_id');
118225e381fSGreg Roach
119225e381fSGreg Roach        $stories = [];
120225e381fSGreg Roach        foreach ($block_ids as $block_id) {
1217d988ec3SGreg Roach            $block_id = (int) $block_id;
1227d988ec3SGreg Roach
123225e381fSGreg Roach            // Only show this block for certain languages
12450d6f48cSGreg Roach            $languages = $this->getBlockSetting($block_id, 'languages');
12522d65e5aSGreg Roach            if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages), true)) {
126225e381fSGreg Roach                $stories[] = (object) [
127225e381fSGreg Roach                    'block_id'   => $block_id,
128225e381fSGreg Roach                    'title'      => $this->getBlockSetting($block_id, 'title'),
12972ac996dSGreg Roach                    'story_body' => $this->getBlockSetting($block_id, 'story_body'),
130225e381fSGreg Roach                ];
131225e381fSGreg Roach            }
132225e381fSGreg Roach        }
133225e381fSGreg Roach
134225e381fSGreg Roach        return $stories;
1358c2e8227SGreg Roach    }
1368c2e8227SGreg Roach
1373caaa4d2SGreg Roach    /**
1383caaa4d2SGreg Roach     * Is this tab empty? If so, we don't always need to display it.
1393caaa4d2SGreg Roach     *
1403caaa4d2SGreg Roach     * @param Individual $individual
1413caaa4d2SGreg Roach     *
1423caaa4d2SGreg Roach     * @return bool
1433caaa4d2SGreg Roach     */
1446ccdf4f0SGreg Roach    public function hasTabContent(Individual $individual): bool
1456ccdf4f0SGreg Roach    {
1466ccdf4f0SGreg Roach        return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual));
1476ccdf4f0SGreg Roach    }
1486ccdf4f0SGreg Roach
1493caaa4d2SGreg Roach    /**
1503caaa4d2SGreg Roach     * A greyed out tab has no actual content, but may perhaps have
1513caaa4d2SGreg Roach     * options to create content.
1523caaa4d2SGreg Roach     *
1533caaa4d2SGreg Roach     * @param Individual $individual
1543caaa4d2SGreg Roach     *
1553caaa4d2SGreg Roach     * @return bool
1563caaa4d2SGreg Roach     */
1576ccdf4f0SGreg Roach    public function isGrayedOut(Individual $individual): bool
1586ccdf4f0SGreg Roach    {
1596ccdf4f0SGreg Roach        return !empty($this->getStoriesForIndividual($individual));
1606ccdf4f0SGreg Roach    }
1616ccdf4f0SGreg Roach
1623caaa4d2SGreg Roach    /**
1633caaa4d2SGreg Roach     * Can this tab load asynchronously?
1643caaa4d2SGreg Roach     *
1653caaa4d2SGreg Roach     * @return bool
1663caaa4d2SGreg Roach     */
1676ccdf4f0SGreg Roach    public function canLoadAjax(): bool
1686ccdf4f0SGreg Roach    {
1696ccdf4f0SGreg Roach        return false;
1706ccdf4f0SGreg Roach    }
1716ccdf4f0SGreg Roach
1728c2e8227SGreg Roach    /**
1730ee13198SGreg Roach     * A menu, to be added to the main application menu.
1740ee13198SGreg Roach     *
175aee13b6dSGreg Roach     * @param Tree $tree
176aee13b6dSGreg Roach     *
1770ee13198SGreg Roach     * @return Menu|null
1780ee13198SGreg Roach     */
17946295629SGreg Roach    public function getMenu(Tree $tree): ?Menu
180c1010edaSGreg Roach    {
18149a243cbSGreg Roach        $menu = new Menu($this->title(), route('module', [
18226684e68SGreg Roach            'module' => $this->name(),
183c1010edaSGreg Roach            'action' => 'ShowList',
1849022ab66SGreg Roach            'tree'    => $tree->name(),
185c1010edaSGreg Roach        ]), 'menu-story');
1868c2e8227SGreg Roach
1878c2e8227SGreg Roach        return $menu;
1888c2e8227SGreg Roach    }
18972ac996dSGreg Roach
19072ac996dSGreg Roach    /**
1916ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
1926ccdf4f0SGreg Roach     *
1936ccdf4f0SGreg Roach     * @return string
1946ccdf4f0SGreg Roach     */
1956ccdf4f0SGreg Roach    public function title(): string
1966ccdf4f0SGreg Roach    {
1976ccdf4f0SGreg Roach        /* I18N: Name of a module */
1986ccdf4f0SGreg Roach        return I18N::translate('Stories');
1996ccdf4f0SGreg Roach    }
2006ccdf4f0SGreg Roach
2016ccdf4f0SGreg Roach    /**
20257ab2231SGreg Roach     * @param ServerRequestInterface $request
20372ac996dSGreg Roach     *
2046ccdf4f0SGreg Roach     * @return ResponseInterface
20572ac996dSGreg Roach     */
20657ab2231SGreg Roach    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
207c1010edaSGreg Roach    {
20872ac996dSGreg Roach        $this->layout = 'layouts/administration';
20972ac996dSGreg Roach
21057ab2231SGreg Roach        $tree = $request->getAttribute('tree');
211*5229eadeSGreg Roach        assert($tree instanceof Tree, new InvalidArgumentException());
21257ab2231SGreg Roach
2134b92b602SGreg Roach        $stories = DB::table('block')
21426684e68SGreg Roach            ->where('module_name', '=', $this->name())
2154b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
2164b92b602SGreg Roach            ->orderBy('xref')
2174b92b602SGreg Roach            ->get();
21872ac996dSGreg Roach
21972ac996dSGreg Roach        foreach ($stories as $story) {
2205db543e1SGreg Roach            $block_id = (int) $story->block_id;
2215db543e1SGreg Roach
22272ac996dSGreg Roach            $story->individual = Individual::getInstance($story->xref, $tree);
2235db543e1SGreg Roach            $story->title      = $this->getBlockSetting($block_id, 'title');
2245db543e1SGreg Roach            $story->languages  = $this->getBlockSetting($block_id, 'languages');
22572ac996dSGreg Roach        }
22672ac996dSGreg Roach
22772ac996dSGreg Roach        return $this->viewResponse('modules/stories/config', [
22871378461SGreg Roach            'module'     => $this->name(),
22972ac996dSGreg Roach            'stories'    => $stories,
23049a243cbSGreg Roach            'title'      => $this->title() . ' — ' . $tree->title(),
23172ac996dSGreg Roach            'tree'       => $tree,
23272ac996dSGreg Roach            'tree_names' => Tree::getNameList(),
23372ac996dSGreg Roach        ]);
23472ac996dSGreg Roach    }
23572ac996dSGreg Roach
23672ac996dSGreg Roach    /**
2376ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
23872ac996dSGreg Roach     *
2396ccdf4f0SGreg Roach     * @return ResponseInterface
24072ac996dSGreg Roach     */
24157ab2231SGreg Roach    public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface
242c1010edaSGreg Roach    {
24372ac996dSGreg Roach        $this->layout = 'layouts/administration';
24472ac996dSGreg Roach
24557ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
246b6b9dcc9SGreg Roach        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
24772ac996dSGreg Roach
24872ac996dSGreg Roach        if ($block_id === 0) {
24972ac996dSGreg Roach            // Creating a new story
250b6b9dcc9SGreg Roach            $individual  = null;
25172ac996dSGreg Roach            $story_title = '';
25272ac996dSGreg Roach            $story_body  = '';
25372ac996dSGreg Roach            $languages   = [];
25472ac996dSGreg Roach
255cc13d6d8SGreg Roach            $title = I18N::translate('Add a story') . ' — ' . e($tree->title());
25672ac996dSGreg Roach        } else {
25772ac996dSGreg Roach            // Editing an existing story
2584b92b602SGreg Roach            $xref = (string) DB::table('block')
2594b92b602SGreg Roach                ->where('block_id', '=', $block_id)
2604b92b602SGreg Roach                ->value('xref');
26172ac996dSGreg Roach
26272ac996dSGreg Roach            $individual  = Individual::getInstance($xref, $tree);
26350d6f48cSGreg Roach            $story_title = $this->getBlockSetting($block_id, 'title');
26450d6f48cSGreg Roach            $story_body  = $this->getBlockSetting($block_id, 'story_body');
26572ac996dSGreg Roach            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
26672ac996dSGreg Roach
267cc13d6d8SGreg Roach            $title = I18N::translate('Edit the story') . ' — ' . e($tree->title());
26872ac996dSGreg Roach        }
26972ac996dSGreg Roach
27072ac996dSGreg Roach        return $this->viewResponse('modules/stories/edit', [
27172ac996dSGreg Roach            'block_id'    => $block_id,
27272ac996dSGreg Roach            'languages'   => $languages,
27372ac996dSGreg Roach            'story_body'  => $story_body,
27472ac996dSGreg Roach            'story_title' => $story_title,
27572ac996dSGreg Roach            'title'       => $title,
27672ac996dSGreg Roach            'tree'        => $tree,
27772ac996dSGreg Roach            'individual'  => $individual,
27872ac996dSGreg Roach        ]);
27972ac996dSGreg Roach    }
28072ac996dSGreg Roach
28172ac996dSGreg Roach    /**
2826ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
28372ac996dSGreg Roach     *
2846ccdf4f0SGreg Roach     * @return ResponseInterface
28572ac996dSGreg Roach     */
28657ab2231SGreg Roach    public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface
287c1010edaSGreg Roach    {
28857ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
289b6b9dcc9SGreg Roach        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
290b6b9dcc9SGreg Roach
291b6b9dcc9SGreg Roach        $params = $request->getParsedBody();
292b6b9dcc9SGreg Roach
293b6b9dcc9SGreg Roach        $xref        = $params['xref'];
294b6b9dcc9SGreg Roach        $story_body  = $params['story_body'];
295b6b9dcc9SGreg Roach        $story_title = $params['story_title'];
296b6b9dcc9SGreg Roach        $languages   = $params['languages'] ?? [];
29772ac996dSGreg Roach
29850d6f48cSGreg Roach        $story_body  = $this->html_service->sanitize($story_body);
29950d6f48cSGreg Roach        $story_title = $this->html_service->sanitize($story_title);
30050d6f48cSGreg Roach
30172ac996dSGreg Roach        if ($block_id !== 0) {
3024b92b602SGreg Roach            DB::table('block')
3034b92b602SGreg Roach                ->where('block_id', '=', $block_id)
3044b92b602SGreg Roach                ->update([
3054b92b602SGreg Roach                    'gedcom_id' => $tree->id(),
30672ac996dSGreg Roach                    'xref'      => $xref,
30772ac996dSGreg Roach                ]);
30872ac996dSGreg Roach        } else {
3094b92b602SGreg Roach            DB::table('block')->insert([
3104b92b602SGreg Roach                'gedcom_id'   => $tree->id(),
31172ac996dSGreg Roach                'xref'        => $xref,
31226684e68SGreg Roach                'module_name' => $this->name(),
3134b92b602SGreg Roach                'block_order' => 0,
31472ac996dSGreg Roach            ]);
31572ac996dSGreg Roach
3164b92b602SGreg Roach            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
31772ac996dSGreg Roach        }
31872ac996dSGreg Roach
31972ac996dSGreg Roach        $this->setBlockSetting($block_id, 'story_body', $story_body);
32072ac996dSGreg Roach        $this->setBlockSetting($block_id, 'title', $story_title);
32172ac996dSGreg Roach        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
32272ac996dSGreg Roach
323c1010edaSGreg Roach        $url = route('module', [
32426684e68SGreg Roach            'module' => $this->name(),
325c1010edaSGreg Roach            'action' => 'Admin',
3269022ab66SGreg Roach            'tree'    => $tree->name(),
327c1010edaSGreg Roach        ]);
32872ac996dSGreg Roach
3296ccdf4f0SGreg Roach        return redirect($url);
33072ac996dSGreg Roach    }
33172ac996dSGreg Roach
33272ac996dSGreg Roach    /**
3336ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
33472ac996dSGreg Roach     *
3356ccdf4f0SGreg Roach     * @return ResponseInterface
33672ac996dSGreg Roach     */
33757ab2231SGreg Roach    public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface
338c1010edaSGreg Roach    {
33957ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
340b6b9dcc9SGreg Roach        $block_id = $request->getQueryParams()['block_id'];
34172ac996dSGreg Roach
3424b92b602SGreg Roach        DB::table('block_setting')
3434b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3444b92b602SGreg Roach            ->delete();
34572ac996dSGreg Roach
3464b92b602SGreg Roach        DB::table('block')
3474b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3484b92b602SGreg Roach            ->delete();
34972ac996dSGreg Roach
350c1010edaSGreg Roach        $url = route('module', [
35126684e68SGreg Roach            'module' => $this->name(),
352c1010edaSGreg Roach            'action' => 'Admin',
3539022ab66SGreg Roach            'tree'    => $tree->name(),
354c1010edaSGreg Roach        ]);
35572ac996dSGreg Roach
3566ccdf4f0SGreg Roach        return redirect($url);
35772ac996dSGreg Roach    }
35872ac996dSGreg Roach
35972ac996dSGreg Roach    /**
36057ab2231SGreg Roach     * @param ServerRequestInterface $request
36172ac996dSGreg Roach     *
3626ccdf4f0SGreg Roach     * @return ResponseInterface
36372ac996dSGreg Roach     */
36457ab2231SGreg Roach    public function getShowListAction(ServerRequestInterface $request): ResponseInterface
365c1010edaSGreg Roach    {
36657ab2231SGreg Roach        $tree = $request->getAttribute('tree');
367*5229eadeSGreg Roach        assert($tree instanceof Tree, new InvalidArgumentException());
36857ab2231SGreg Roach
3694b92b602SGreg Roach        $stories = DB::table('block')
37026684e68SGreg Roach            ->where('module_name', '=', $this->name())
3714b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
3724b92b602SGreg Roach            ->get()
3734b92b602SGreg Roach            ->map(function (stdClass $story) use ($tree): stdClass {
3745db543e1SGreg Roach                $block_id = (int) $story->block_id;
3755db543e1SGreg Roach
37672ac996dSGreg Roach                $story->individual = Individual::getInstance($story->xref, $tree);
3775db543e1SGreg Roach                $story->title      = $this->getBlockSetting($block_id, 'title');
3785db543e1SGreg Roach                $story->languages  = $this->getBlockSetting($block_id, 'languages');
37972ac996dSGreg Roach
3804b92b602SGreg Roach                return $story;
3810b5fd0a6SGreg Roach            })->filter(static function (stdClass $story): bool {
38272ac996dSGreg Roach                // Filter non-existant and private individuals.
3834b92b602SGreg Roach                return $story->individual instanceof Individual && $story->individual->canShow();
3840b5fd0a6SGreg Roach            })->filter(static function (stdClass $story): bool {
38572ac996dSGreg Roach                // Filter foreign languages.
38622d65e5aSGreg Roach                return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true);
38772ac996dSGreg Roach            });
38872ac996dSGreg Roach
38972ac996dSGreg Roach        return $this->viewResponse('modules/stories/list', [
39072ac996dSGreg Roach            'stories' => $stories,
39149a243cbSGreg Roach            'title'   => $this->title(),
39272ac996dSGreg Roach        ]);
39372ac996dSGreg Roach    }
3948c2e8227SGreg Roach}
395