xref: /webtrees/app/Module/StoriesModule.php (revision 83d28054ad99bacc7b0ad8fd08949f4c8e215244)
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;
285229eadeSGreg Roachuse InvalidArgumentException;
296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
31225e381fSGreg Roachuse stdClass;
32*83d28054SGreg Roach
335229eadeSGreg Roachuse function assert;
348c2e8227SGreg Roach
358c2e8227SGreg Roach/**
368c2e8227SGreg Roach * Class StoriesModule
378c2e8227SGreg Roach */
3837eb8894SGreg Roachclass StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface
39c1010edaSGreg Roach{
4049a243cbSGreg Roach    use ModuleTabTrait;
4149a243cbSGreg Roach    use ModuleConfigTrait;
4249a243cbSGreg Roach    use ModuleMenuTrait;
4349a243cbSGreg Roach
4450d6f48cSGreg Roach    /** @var HtmlService */
4550d6f48cSGreg Roach    private $html_service;
4650d6f48cSGreg Roach
4750d6f48cSGreg Roach    /**
4850d6f48cSGreg Roach     * HtmlBlockModule bootstrap.
4950d6f48cSGreg Roach     *
5050d6f48cSGreg Roach     * @param HtmlService $html_service
5150d6f48cSGreg Roach     */
5250d6f48cSGreg Roach    public function boot(HtmlService $html_service)
5350d6f48cSGreg Roach    {
5450d6f48cSGreg Roach        $this->html_service = $html_service;
5550d6f48cSGreg Roach    }
5650d6f48cSGreg Roach
5749a243cbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
5849a243cbSGreg Roach    protected $access_level = Auth::PRIV_HIDE;
5949a243cbSGreg Roach
60961ec755SGreg Roach    /**
61961ec755SGreg Roach     * A sentence describing what this module does.
62961ec755SGreg Roach     *
63961ec755SGreg Roach     * @return string
64961ec755SGreg Roach     */
6549a243cbSGreg Roach    public function description(): string
66c1010edaSGreg Roach    {
67bbb76c12SGreg Roach        /* I18N: Description of the “Stories” module */
68bbb76c12SGreg Roach        return I18N::translate('Add narrative stories to individuals in the family tree.');
698c2e8227SGreg Roach    }
708c2e8227SGreg Roach
71aee13b6dSGreg Roach    /**
7249a243cbSGreg Roach     * The default position for this menu.  It can be changed in the control panel.
73aee13b6dSGreg Roach     *
7449a243cbSGreg Roach     * @return int
75aee13b6dSGreg Roach     */
7649a243cbSGreg Roach    public function defaultMenuOrder(): int
77c1010edaSGreg Roach    {
78353b36abSGreg Roach        return 7;
798c2e8227SGreg Roach    }
808c2e8227SGreg Roach
8149a243cbSGreg Roach    /**
8249a243cbSGreg Roach     * The default position for this tab.  It can be changed in the control panel.
8349a243cbSGreg Roach     *
8449a243cbSGreg Roach     * @return int
8549a243cbSGreg Roach     */
86cbf4b7faSGreg Roach    public function defaultTabOrder(): int
87cbf4b7faSGreg Roach    {
88fb7a0427SGreg Roach        return 9;
898c2e8227SGreg Roach    }
908c2e8227SGreg Roach
913caaa4d2SGreg Roach    /**
923caaa4d2SGreg Roach     * Generate the HTML content of this tab.
933caaa4d2SGreg Roach     *
943caaa4d2SGreg Roach     * @param Individual $individual
953caaa4d2SGreg Roach     *
963caaa4d2SGreg Roach     * @return string
973caaa4d2SGreg Roach     */
989b34404bSGreg Roach    public function getTabContent(Individual $individual): string
99c1010edaSGreg Roach    {
10072ac996dSGreg Roach        return view('modules/stories/tab', [
10172ac996dSGreg Roach            'is_admin'   => Auth::isAdmin(),
102225e381fSGreg Roach            'individual' => $individual,
103225e381fSGreg Roach            'stories'    => $this->getStoriesForIndividual($individual),
104225e381fSGreg Roach        ]);
1058c2e8227SGreg Roach    }
1068c2e8227SGreg Roach
107225e381fSGreg Roach    /**
108225e381fSGreg Roach     * @param Individual $individual
109225e381fSGreg Roach     *
110225e381fSGreg Roach     * @return stdClass[]
111225e381fSGreg Roach     */
112c1010edaSGreg Roach    private function getStoriesForIndividual(Individual $individual): array
113c1010edaSGreg Roach    {
1144b92b602SGreg Roach        $block_ids = DB::table('block')
11526684e68SGreg Roach            ->where('module_name', '=', $this->name())
1164b92b602SGreg Roach            ->where('xref', '=', $individual->xref())
1174b92b602SGreg Roach            ->where('gedcom_id', '=', $individual->tree()->id())
1184b92b602SGreg Roach            ->pluck('block_id');
119225e381fSGreg Roach
120225e381fSGreg Roach        $stories = [];
121225e381fSGreg Roach        foreach ($block_ids as $block_id) {
1227d988ec3SGreg Roach            $block_id = (int) $block_id;
1237d988ec3SGreg Roach
124225e381fSGreg Roach            // Only show this block for certain languages
12550d6f48cSGreg Roach            $languages = $this->getBlockSetting($block_id, 'languages');
12622d65e5aSGreg Roach            if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages), true)) {
127225e381fSGreg Roach                $stories[] = (object) [
128225e381fSGreg Roach                    'block_id'   => $block_id,
129225e381fSGreg Roach                    'title'      => $this->getBlockSetting($block_id, 'title'),
13072ac996dSGreg Roach                    'story_body' => $this->getBlockSetting($block_id, 'story_body'),
131225e381fSGreg Roach                ];
132225e381fSGreg Roach            }
133225e381fSGreg Roach        }
134225e381fSGreg Roach
135225e381fSGreg Roach        return $stories;
1368c2e8227SGreg Roach    }
1378c2e8227SGreg Roach
1383caaa4d2SGreg Roach    /**
1393caaa4d2SGreg Roach     * Is this tab empty? If so, we don't always need to display it.
1403caaa4d2SGreg Roach     *
1413caaa4d2SGreg Roach     * @param Individual $individual
1423caaa4d2SGreg Roach     *
1433caaa4d2SGreg Roach     * @return bool
1443caaa4d2SGreg Roach     */
1456ccdf4f0SGreg Roach    public function hasTabContent(Individual $individual): bool
1466ccdf4f0SGreg Roach    {
1476ccdf4f0SGreg Roach        return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual));
1486ccdf4f0SGreg Roach    }
1496ccdf4f0SGreg Roach
1503caaa4d2SGreg Roach    /**
1513caaa4d2SGreg Roach     * A greyed out tab has no actual content, but may perhaps have
1523caaa4d2SGreg Roach     * options to create content.
1533caaa4d2SGreg Roach     *
1543caaa4d2SGreg Roach     * @param Individual $individual
1553caaa4d2SGreg Roach     *
1563caaa4d2SGreg Roach     * @return bool
1573caaa4d2SGreg Roach     */
1586ccdf4f0SGreg Roach    public function isGrayedOut(Individual $individual): bool
1596ccdf4f0SGreg Roach    {
1606ccdf4f0SGreg Roach        return !empty($this->getStoriesForIndividual($individual));
1616ccdf4f0SGreg Roach    }
1626ccdf4f0SGreg Roach
1633caaa4d2SGreg Roach    /**
1643caaa4d2SGreg Roach     * Can this tab load asynchronously?
1653caaa4d2SGreg Roach     *
1663caaa4d2SGreg Roach     * @return bool
1673caaa4d2SGreg Roach     */
1686ccdf4f0SGreg Roach    public function canLoadAjax(): bool
1696ccdf4f0SGreg Roach    {
1706ccdf4f0SGreg Roach        return false;
1716ccdf4f0SGreg Roach    }
1726ccdf4f0SGreg Roach
1738c2e8227SGreg Roach    /**
1740ee13198SGreg Roach     * A menu, to be added to the main application menu.
1750ee13198SGreg Roach     *
176aee13b6dSGreg Roach     * @param Tree $tree
177aee13b6dSGreg Roach     *
1780ee13198SGreg Roach     * @return Menu|null
1790ee13198SGreg Roach     */
18046295629SGreg Roach    public function getMenu(Tree $tree): ?Menu
181c1010edaSGreg Roach    {
18249a243cbSGreg Roach        $menu = new Menu($this->title(), route('module', [
18326684e68SGreg Roach            'module' => $this->name(),
184c1010edaSGreg Roach            'action' => 'ShowList',
1859022ab66SGreg Roach            'tree'    => $tree->name(),
186c1010edaSGreg Roach        ]), 'menu-story');
1878c2e8227SGreg Roach
1888c2e8227SGreg Roach        return $menu;
1898c2e8227SGreg Roach    }
19072ac996dSGreg Roach
19172ac996dSGreg Roach    /**
1926ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
1936ccdf4f0SGreg Roach     *
1946ccdf4f0SGreg Roach     * @return string
1956ccdf4f0SGreg Roach     */
1966ccdf4f0SGreg Roach    public function title(): string
1976ccdf4f0SGreg Roach    {
1986ccdf4f0SGreg Roach        /* I18N: Name of a module */
1996ccdf4f0SGreg Roach        return I18N::translate('Stories');
2006ccdf4f0SGreg Roach    }
2016ccdf4f0SGreg Roach
2026ccdf4f0SGreg Roach    /**
20357ab2231SGreg Roach     * @param ServerRequestInterface $request
20472ac996dSGreg Roach     *
2056ccdf4f0SGreg Roach     * @return ResponseInterface
20672ac996dSGreg Roach     */
20757ab2231SGreg Roach    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
208c1010edaSGreg Roach    {
20972ac996dSGreg Roach        $this->layout = 'layouts/administration';
21072ac996dSGreg Roach
21157ab2231SGreg Roach        $tree = $request->getAttribute('tree');
2125229eadeSGreg Roach        assert($tree instanceof Tree, new InvalidArgumentException());
21357ab2231SGreg Roach
2144b92b602SGreg Roach        $stories = DB::table('block')
21526684e68SGreg Roach            ->where('module_name', '=', $this->name())
2164b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
2174b92b602SGreg Roach            ->orderBy('xref')
2184b92b602SGreg Roach            ->get();
21972ac996dSGreg Roach
22072ac996dSGreg Roach        foreach ($stories as $story) {
2215db543e1SGreg Roach            $block_id = (int) $story->block_id;
2225db543e1SGreg Roach
22372ac996dSGreg Roach            $story->individual = Individual::getInstance($story->xref, $tree);
2245db543e1SGreg Roach            $story->title      = $this->getBlockSetting($block_id, 'title');
2255db543e1SGreg Roach            $story->languages  = $this->getBlockSetting($block_id, 'languages');
22672ac996dSGreg Roach        }
22772ac996dSGreg Roach
22872ac996dSGreg Roach        return $this->viewResponse('modules/stories/config', [
22971378461SGreg Roach            'module'     => $this->name(),
23072ac996dSGreg Roach            'stories'    => $stories,
23149a243cbSGreg Roach            'title'      => $this->title() . ' — ' . $tree->title(),
23272ac996dSGreg Roach            'tree'       => $tree,
23372ac996dSGreg Roach            'tree_names' => Tree::getNameList(),
23472ac996dSGreg Roach        ]);
23572ac996dSGreg Roach    }
23672ac996dSGreg Roach
23772ac996dSGreg Roach    /**
2386ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
23972ac996dSGreg Roach     *
2406ccdf4f0SGreg Roach     * @return ResponseInterface
24172ac996dSGreg Roach     */
24257ab2231SGreg Roach    public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface
243c1010edaSGreg Roach    {
24472ac996dSGreg Roach        $this->layout = 'layouts/administration';
24572ac996dSGreg Roach
24657ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
247b6b9dcc9SGreg Roach        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
24872ac996dSGreg Roach
24972ac996dSGreg Roach        if ($block_id === 0) {
25072ac996dSGreg Roach            // Creating a new story
251b6b9dcc9SGreg Roach            $individual  = null;
25272ac996dSGreg Roach            $story_title = '';
25372ac996dSGreg Roach            $story_body  = '';
25472ac996dSGreg Roach            $languages   = [];
25572ac996dSGreg Roach
256cc13d6d8SGreg Roach            $title = I18N::translate('Add a story') . ' — ' . e($tree->title());
25772ac996dSGreg Roach        } else {
25872ac996dSGreg Roach            // Editing an existing story
2594b92b602SGreg Roach            $xref = (string) DB::table('block')
2604b92b602SGreg Roach                ->where('block_id', '=', $block_id)
2614b92b602SGreg Roach                ->value('xref');
26272ac996dSGreg Roach
26372ac996dSGreg Roach            $individual  = Individual::getInstance($xref, $tree);
26450d6f48cSGreg Roach            $story_title = $this->getBlockSetting($block_id, 'title');
26550d6f48cSGreg Roach            $story_body  = $this->getBlockSetting($block_id, 'story_body');
26672ac996dSGreg Roach            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
26772ac996dSGreg Roach
268cc13d6d8SGreg Roach            $title = I18N::translate('Edit the story') . ' — ' . e($tree->title());
26972ac996dSGreg Roach        }
27072ac996dSGreg Roach
27172ac996dSGreg Roach        return $this->viewResponse('modules/stories/edit', [
27272ac996dSGreg Roach            'block_id'    => $block_id,
27372ac996dSGreg Roach            'languages'   => $languages,
27472ac996dSGreg Roach            'story_body'  => $story_body,
27572ac996dSGreg Roach            'story_title' => $story_title,
27672ac996dSGreg Roach            'title'       => $title,
27772ac996dSGreg Roach            'tree'        => $tree,
27872ac996dSGreg Roach            'individual'  => $individual,
27972ac996dSGreg Roach        ]);
28072ac996dSGreg Roach    }
28172ac996dSGreg Roach
28272ac996dSGreg Roach    /**
2836ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
28472ac996dSGreg Roach     *
2856ccdf4f0SGreg Roach     * @return ResponseInterface
28672ac996dSGreg Roach     */
28757ab2231SGreg Roach    public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface
288c1010edaSGreg Roach    {
28957ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
290b6b9dcc9SGreg Roach        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
291b6b9dcc9SGreg Roach
292b6b9dcc9SGreg Roach        $params = $request->getParsedBody();
293b6b9dcc9SGreg Roach
294b6b9dcc9SGreg Roach        $xref        = $params['xref'];
295b6b9dcc9SGreg Roach        $story_body  = $params['story_body'];
296b6b9dcc9SGreg Roach        $story_title = $params['story_title'];
297b6b9dcc9SGreg Roach        $languages   = $params['languages'] ?? [];
29872ac996dSGreg Roach
29950d6f48cSGreg Roach        $story_body  = $this->html_service->sanitize($story_body);
30050d6f48cSGreg Roach        $story_title = $this->html_service->sanitize($story_title);
30150d6f48cSGreg Roach
30272ac996dSGreg Roach        if ($block_id !== 0) {
3034b92b602SGreg Roach            DB::table('block')
3044b92b602SGreg Roach                ->where('block_id', '=', $block_id)
3054b92b602SGreg Roach                ->update([
3064b92b602SGreg Roach                    'gedcom_id' => $tree->id(),
30772ac996dSGreg Roach                    'xref'      => $xref,
30872ac996dSGreg Roach                ]);
30972ac996dSGreg Roach        } else {
3104b92b602SGreg Roach            DB::table('block')->insert([
3114b92b602SGreg Roach                'gedcom_id'   => $tree->id(),
31272ac996dSGreg Roach                'xref'        => $xref,
31326684e68SGreg Roach                'module_name' => $this->name(),
3144b92b602SGreg Roach                'block_order' => 0,
31572ac996dSGreg Roach            ]);
31672ac996dSGreg Roach
3174b92b602SGreg Roach            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
31872ac996dSGreg Roach        }
31972ac996dSGreg Roach
32072ac996dSGreg Roach        $this->setBlockSetting($block_id, 'story_body', $story_body);
32172ac996dSGreg Roach        $this->setBlockSetting($block_id, 'title', $story_title);
32272ac996dSGreg Roach        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
32372ac996dSGreg Roach
324c1010edaSGreg Roach        $url = route('module', [
32526684e68SGreg Roach            'module' => $this->name(),
326c1010edaSGreg Roach            'action' => 'Admin',
3279022ab66SGreg Roach            'tree'    => $tree->name(),
328c1010edaSGreg Roach        ]);
32972ac996dSGreg Roach
3306ccdf4f0SGreg Roach        return redirect($url);
33172ac996dSGreg Roach    }
33272ac996dSGreg Roach
33372ac996dSGreg Roach    /**
3346ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
33572ac996dSGreg Roach     *
3366ccdf4f0SGreg Roach     * @return ResponseInterface
33772ac996dSGreg Roach     */
33857ab2231SGreg Roach    public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface
339c1010edaSGreg Roach    {
34057ab2231SGreg Roach        $tree     = $request->getAttribute('tree');
341b6b9dcc9SGreg Roach        $block_id = $request->getQueryParams()['block_id'];
34272ac996dSGreg Roach
3434b92b602SGreg Roach        DB::table('block_setting')
3444b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3454b92b602SGreg Roach            ->delete();
34672ac996dSGreg Roach
3474b92b602SGreg Roach        DB::table('block')
3484b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3494b92b602SGreg Roach            ->delete();
35072ac996dSGreg Roach
351c1010edaSGreg Roach        $url = route('module', [
35226684e68SGreg Roach            'module' => $this->name(),
353c1010edaSGreg Roach            'action' => 'Admin',
3549022ab66SGreg Roach            'tree'    => $tree->name(),
355c1010edaSGreg Roach        ]);
35672ac996dSGreg Roach
3576ccdf4f0SGreg Roach        return redirect($url);
35872ac996dSGreg Roach    }
35972ac996dSGreg Roach
36072ac996dSGreg Roach    /**
36157ab2231SGreg Roach     * @param ServerRequestInterface $request
36272ac996dSGreg Roach     *
3636ccdf4f0SGreg Roach     * @return ResponseInterface
36472ac996dSGreg Roach     */
36557ab2231SGreg Roach    public function getShowListAction(ServerRequestInterface $request): ResponseInterface
366c1010edaSGreg Roach    {
36757ab2231SGreg Roach        $tree = $request->getAttribute('tree');
3685229eadeSGreg Roach        assert($tree instanceof Tree, new InvalidArgumentException());
36957ab2231SGreg Roach
3704b92b602SGreg Roach        $stories = DB::table('block')
37126684e68SGreg Roach            ->where('module_name', '=', $this->name())
3724b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
3734b92b602SGreg Roach            ->get()
3744b92b602SGreg Roach            ->map(function (stdClass $story) use ($tree): stdClass {
3755db543e1SGreg Roach                $block_id = (int) $story->block_id;
3765db543e1SGreg Roach
37772ac996dSGreg Roach                $story->individual = Individual::getInstance($story->xref, $tree);
3785db543e1SGreg Roach                $story->title      = $this->getBlockSetting($block_id, 'title');
3795db543e1SGreg Roach                $story->languages  = $this->getBlockSetting($block_id, 'languages');
38072ac996dSGreg Roach
3814b92b602SGreg Roach                return $story;
3820b5fd0a6SGreg Roach            })->filter(static function (stdClass $story): bool {
38372ac996dSGreg Roach                // Filter non-existant and private individuals.
3844b92b602SGreg Roach                return $story->individual instanceof Individual && $story->individual->canShow();
3850b5fd0a6SGreg Roach            })->filter(static function (stdClass $story): bool {
38672ac996dSGreg Roach                // Filter foreign languages.
38722d65e5aSGreg Roach                return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true);
38872ac996dSGreg Roach            });
38972ac996dSGreg Roach
39072ac996dSGreg Roach        return $this->viewResponse('modules/stories/list', [
39172ac996dSGreg Roach            'stories' => $stories,
39249a243cbSGreg Roach            'title'   => $this->title(),
39372ac996dSGreg Roach        ]);
39472ac996dSGreg Roach    }
3958c2e8227SGreg Roach}
396