xref: /webtrees/app/Module/StoriesModule.php (revision 6f4ec3cadc983f0a7294108c634bef48846b4311)
18c2e8227SGreg Roach<?php
23976b470SGreg Roach
38c2e8227SGreg Roach/**
48c2e8227SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 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
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
168c2e8227SGreg Roach */
17fcfa147eSGreg Roach
18e7f56f2aSGreg Roachdeclare(strict_types=1);
19e7f56f2aSGreg Roach
2076692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module;
2176692c8bSGreg Roach
220e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth;
23*6f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB;
24e218f363SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
250e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
260e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
270e62c4b8SGreg Roachuse Fisharebest\Webtrees\Menu;
289f0bdfcdSGreg Roachuse Fisharebest\Webtrees\Registry;
2950d6f48cSGreg Roachuse Fisharebest\Webtrees\Services\HtmlService;
30e218f363SGreg Roachuse Fisharebest\Webtrees\Services\TreeService;
310e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree;
32b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator;
336ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
346ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
3583d28054SGreg Roach
3610e06497SGreg Roachuse function in_array;
37e218f363SGreg Roachuse function redirect;
38e218f363SGreg Roachuse function route;
398c2e8227SGreg Roach
408c2e8227SGreg Roach/**
418c2e8227SGreg Roach * Class StoriesModule
428c2e8227SGreg Roach */
4337eb8894SGreg Roachclass StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface
44c1010edaSGreg Roach{
4549a243cbSGreg Roach    use ModuleTabTrait;
4649a243cbSGreg Roach    use ModuleConfigTrait;
4749a243cbSGreg Roach    use ModuleMenuTrait;
4849a243cbSGreg Roach
4943f2f523SGreg Roach    private HtmlService $html_service;
5050d6f48cSGreg Roach
5143f2f523SGreg Roach    private TreeService $tree_service;
52e218f363SGreg Roach
5350d6f48cSGreg Roach    /**
5450d6f48cSGreg Roach     * @param HtmlService $html_service
55e218f363SGreg Roach     * @param TreeService $tree_service
5650d6f48cSGreg Roach     */
57e218f363SGreg Roach    public function __construct(HtmlService $html_service, TreeService $tree_service)
5850d6f48cSGreg Roach    {
5950d6f48cSGreg Roach        $this->html_service = $html_service;
60e218f363SGreg Roach        $this->tree_service = $tree_service;
6150d6f48cSGreg Roach    }
6250d6f48cSGreg Roach
6349a243cbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
6433c746f1SGreg Roach    protected int $access_level = Auth::PRIV_HIDE;
6549a243cbSGreg Roach
66961ec755SGreg Roach    /**
67961ec755SGreg Roach     * A sentence describing what this module does.
68961ec755SGreg Roach     *
69961ec755SGreg Roach     * @return string
70961ec755SGreg Roach     */
7149a243cbSGreg Roach    public function description(): string
72c1010edaSGreg Roach    {
73bbb76c12SGreg Roach        /* I18N: Description of the “Stories” module */
74bbb76c12SGreg Roach        return I18N::translate('Add narrative stories to individuals in the family tree.');
758c2e8227SGreg Roach    }
768c2e8227SGreg Roach
77aee13b6dSGreg Roach    /**
7849a243cbSGreg Roach     * The default position for this menu.  It can be changed in the control panel.
79aee13b6dSGreg Roach     *
8049a243cbSGreg Roach     * @return int
81aee13b6dSGreg Roach     */
8249a243cbSGreg Roach    public function defaultMenuOrder(): int
83c1010edaSGreg Roach    {
84353b36abSGreg Roach        return 7;
858c2e8227SGreg Roach    }
868c2e8227SGreg Roach
8749a243cbSGreg Roach    /**
8849a243cbSGreg Roach     * The default position for this tab.  It can be changed in the control panel.
8949a243cbSGreg Roach     *
9049a243cbSGreg Roach     * @return int
9149a243cbSGreg Roach     */
92cbf4b7faSGreg Roach    public function defaultTabOrder(): int
93cbf4b7faSGreg Roach    {
94fb7a0427SGreg Roach        return 9;
958c2e8227SGreg Roach    }
968c2e8227SGreg Roach
973caaa4d2SGreg Roach    /**
983caaa4d2SGreg Roach     * Generate the HTML content of this tab.
993caaa4d2SGreg Roach     *
1003caaa4d2SGreg Roach     * @param Individual $individual
1013caaa4d2SGreg Roach     *
1023caaa4d2SGreg Roach     * @return string
1033caaa4d2SGreg Roach     */
1049b34404bSGreg Roach    public function getTabContent(Individual $individual): string
105c1010edaSGreg Roach    {
10672ac996dSGreg Roach        return view('modules/stories/tab', [
10772ac996dSGreg Roach            'is_admin'   => Auth::isAdmin(),
108225e381fSGreg Roach            'individual' => $individual,
109225e381fSGreg Roach            'stories'    => $this->getStoriesForIndividual($individual),
1100eb8cd1eSGreg Roach            'tree'       => $individual->tree(),
111225e381fSGreg Roach        ]);
1128c2e8227SGreg Roach    }
1138c2e8227SGreg Roach
114225e381fSGreg Roach    /**
115225e381fSGreg Roach     * @param Individual $individual
116225e381fSGreg Roach     *
117f70bcff5SGreg Roach     * @return array<object>
118225e381fSGreg Roach     */
119c1010edaSGreg Roach    private function getStoriesForIndividual(Individual $individual): array
120c1010edaSGreg Roach    {
1214b92b602SGreg Roach        $block_ids = DB::table('block')
12226684e68SGreg Roach            ->where('module_name', '=', $this->name())
1234b92b602SGreg Roach            ->where('xref', '=', $individual->xref())
1244b92b602SGreg Roach            ->where('gedcom_id', '=', $individual->tree()->id())
1254b92b602SGreg Roach            ->pluck('block_id');
126225e381fSGreg Roach
127225e381fSGreg Roach        $stories = [];
128225e381fSGreg Roach        foreach ($block_ids as $block_id) {
1297d988ec3SGreg Roach            $block_id = (int) $block_id;
1307d988ec3SGreg Roach
131225e381fSGreg Roach            // Only show this block for certain languages
13250d6f48cSGreg Roach            $languages = $this->getBlockSetting($block_id, 'languages');
13365cf5706SGreg Roach            if ($languages === '' || in_array(I18N::languageTag(), explode(',', $languages), true)) {
134225e381fSGreg Roach                $stories[] = (object) [
135225e381fSGreg Roach                    'block_id'   => $block_id,
136225e381fSGreg Roach                    'title'      => $this->getBlockSetting($block_id, 'title'),
13772ac996dSGreg Roach                    'story_body' => $this->getBlockSetting($block_id, 'story_body'),
138225e381fSGreg Roach                ];
139225e381fSGreg Roach            }
140225e381fSGreg Roach        }
141225e381fSGreg Roach
142225e381fSGreg Roach        return $stories;
1438c2e8227SGreg Roach    }
1448c2e8227SGreg Roach
1453caaa4d2SGreg Roach    /**
1463caaa4d2SGreg Roach     * Is this tab empty? If so, we don't always need to display it.
1473caaa4d2SGreg Roach     *
1483caaa4d2SGreg Roach     * @param Individual $individual
1493caaa4d2SGreg Roach     *
1503caaa4d2SGreg Roach     * @return bool
1513caaa4d2SGreg Roach     */
1526ccdf4f0SGreg Roach    public function hasTabContent(Individual $individual): bool
1536ccdf4f0SGreg Roach    {
15454c1ab5eSGreg Roach        return Auth::isManager($individual->tree()) || $this->getStoriesForIndividual($individual) !== [];
1556ccdf4f0SGreg Roach    }
1566ccdf4f0SGreg Roach
1573caaa4d2SGreg Roach    /**
1583caaa4d2SGreg Roach     * A greyed out tab has no actual content, but may perhaps have
1593caaa4d2SGreg Roach     * options to create content.
1603caaa4d2SGreg Roach     *
1613caaa4d2SGreg Roach     * @param Individual $individual
1623caaa4d2SGreg Roach     *
1633caaa4d2SGreg Roach     * @return bool
1643caaa4d2SGreg Roach     */
1656ccdf4f0SGreg Roach    public function isGrayedOut(Individual $individual): bool
1666ccdf4f0SGreg Roach    {
167602c43e8SCarmen        return $this->getStoriesForIndividual($individual) === [];
1686ccdf4f0SGreg Roach    }
1696ccdf4f0SGreg Roach
1703caaa4d2SGreg Roach    /**
1713caaa4d2SGreg Roach     * Can this tab load asynchronously?
1723caaa4d2SGreg Roach     *
1733caaa4d2SGreg Roach     * @return bool
1743caaa4d2SGreg Roach     */
1756ccdf4f0SGreg Roach    public function canLoadAjax(): bool
1766ccdf4f0SGreg Roach    {
1776ccdf4f0SGreg Roach        return false;
1786ccdf4f0SGreg Roach    }
1796ccdf4f0SGreg Roach
1808c2e8227SGreg Roach    /**
1810ee13198SGreg Roach     * A menu, to be added to the main application menu.
1820ee13198SGreg Roach     *
183aee13b6dSGreg Roach     * @param Tree $tree
184aee13b6dSGreg Roach     *
1850ee13198SGreg Roach     * @return Menu|null
1860ee13198SGreg Roach     */
18746295629SGreg Roach    public function getMenu(Tree $tree): ?Menu
188c1010edaSGreg Roach    {
189aa6311c7SGreg Roach        return new Menu($this->title(), route('module', [
19026684e68SGreg Roach            'module' => $this->name(),
191c1010edaSGreg Roach            'action' => 'ShowList',
1929022ab66SGreg Roach            'tree'    => $tree->name(),
193c1010edaSGreg Roach        ]), 'menu-story');
1948c2e8227SGreg Roach    }
19572ac996dSGreg Roach
19672ac996dSGreg Roach    /**
1976ccdf4f0SGreg Roach     * How should this module be identified in the control panel, etc.?
1986ccdf4f0SGreg Roach     *
1996ccdf4f0SGreg Roach     * @return string
2006ccdf4f0SGreg Roach     */
2016ccdf4f0SGreg Roach    public function title(): string
2026ccdf4f0SGreg Roach    {
2036ccdf4f0SGreg Roach        /* I18N: Name of a module */
2046ccdf4f0SGreg Roach        return I18N::translate('Stories');
2056ccdf4f0SGreg Roach    }
2066ccdf4f0SGreg Roach
2076ccdf4f0SGreg Roach    /**
20857ab2231SGreg Roach     * @param ServerRequestInterface $request
20972ac996dSGreg Roach     *
2106ccdf4f0SGreg Roach     * @return ResponseInterface
21172ac996dSGreg Roach     */
21257ab2231SGreg Roach    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
213c1010edaSGreg Roach    {
21472ac996dSGreg Roach        $this->layout = 'layouts/administration';
21572ac996dSGreg Roach
216e218f363SGreg Roach        // This module can't run without a tree
217b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->treeOptional();
218e218f363SGreg Roach
219e218f363SGreg Roach        if (!$tree instanceof Tree) {
220e218f363SGreg Roach            $tree = $this->tree_service->all()->first();
221e218f363SGreg Roach            if ($tree instanceof Tree) {
222e218f363SGreg Roach                return redirect(route('module', ['module' => $this->name(), 'action' => 'Admin', 'tree' => $tree->name()]));
223e218f363SGreg Roach            }
224e218f363SGreg Roach
225e218f363SGreg Roach            return redirect(route(ControlPanel::class));
226e218f363SGreg Roach        }
22757ab2231SGreg Roach
2284b92b602SGreg Roach        $stories = DB::table('block')
22926684e68SGreg Roach            ->where('module_name', '=', $this->name())
2304b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
2314b92b602SGreg Roach            ->orderBy('xref')
2324b92b602SGreg Roach            ->get();
23372ac996dSGreg Roach
23472ac996dSGreg Roach        foreach ($stories as $story) {
2355db543e1SGreg Roach            $block_id = (int) $story->block_id;
23664d90241SGreg Roach            $xref     = (string) $story->xref;
2375db543e1SGreg Roach
2386b9cb339SGreg Roach            $story->individual = Registry::individualFactory()->make($xref, $tree);
2395db543e1SGreg Roach            $story->title      = $this->getBlockSetting($block_id, 'title');
2405db543e1SGreg Roach            $story->languages  = $this->getBlockSetting($block_id, 'languages');
24172ac996dSGreg Roach        }
24272ac996dSGreg Roach
2431e653452SGreg Roach        $tree_names = $this->tree_service->all()->map(static function (Tree $tree): string {
2441e653452SGreg Roach            return $tree->title();
2451e653452SGreg Roach        });
2461e653452SGreg Roach
24772ac996dSGreg Roach        return $this->viewResponse('modules/stories/config', [
24871378461SGreg Roach            'module'     => $this->name(),
24972ac996dSGreg Roach            'stories'    => $stories,
25049a243cbSGreg Roach            'title'      => $this->title() . ' — ' . $tree->title(),
25172ac996dSGreg Roach            'tree'       => $tree,
2521e653452SGreg Roach            'tree_names' => $tree_names,
25372ac996dSGreg Roach        ]);
25472ac996dSGreg Roach    }
25572ac996dSGreg Roach
25672ac996dSGreg Roach    /**
2576ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
25872ac996dSGreg Roach     *
2596ccdf4f0SGreg Roach     * @return ResponseInterface
26072ac996dSGreg Roach     */
261e218f363SGreg Roach    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
262e218f363SGreg Roach    {
263e218f363SGreg Roach        return redirect(route('module', [
264e218f363SGreg Roach            'module' => $this->name(),
265e218f363SGreg Roach            'action' => 'Admin',
266748dbe15SGreg Roach            'tree'   => Validator::parsedBody($request)->string('tree'),
267e218f363SGreg Roach        ]));
268e218f363SGreg Roach    }
269e218f363SGreg Roach
270e218f363SGreg Roach    /**
271e218f363SGreg Roach     * @param ServerRequestInterface $request
272e218f363SGreg Roach     *
273e218f363SGreg Roach     * @return ResponseInterface
274e218f363SGreg Roach     */
27557ab2231SGreg Roach    public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface
276c1010edaSGreg Roach    {
27772ac996dSGreg Roach        $this->layout = 'layouts/administration';
27872ac996dSGreg Roach
279b55cbc6bSGreg Roach        $tree     = Validator::attributes($request)->tree();
2809f0bdfcdSGreg Roach        $block_id = Validator::queryParams($request)->integer('block_id', 0);
2819f0bdfcdSGreg Roach        $url      = Validator::queryParams($request)->string('url', '');
282c532e5bbSGreg Roach
28372ac996dSGreg Roach        if ($block_id === 0) {
28472ac996dSGreg Roach            // Creating a new story
28572ac996dSGreg Roach            $story_title = '';
28672ac996dSGreg Roach            $story_body  = '';
28772ac996dSGreg Roach            $languages   = [];
288c03e33f5SGreg Roach            $xref        = Validator::queryParams($request)->isXref()->string('xref', '');
289cc13d6d8SGreg Roach            $title       = I18N::translate('Add a story') . ' — ' . e($tree->title());
29072ac996dSGreg Roach        } else {
29172ac996dSGreg Roach            // Editing an existing story
2924b92b602SGreg Roach            $xref = (string) DB::table('block')
2934b92b602SGreg Roach                ->where('block_id', '=', $block_id)
2944b92b602SGreg Roach                ->value('xref');
29572ac996dSGreg Roach
29650d6f48cSGreg Roach            $story_title = $this->getBlockSetting($block_id, 'title');
29750d6f48cSGreg Roach            $story_body  = $this->getBlockSetting($block_id, 'story_body');
29872ac996dSGreg Roach            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));
299cc13d6d8SGreg Roach            $title       = I18N::translate('Edit the story') . ' — ' . e($tree->title());
30072ac996dSGreg Roach        }
30172ac996dSGreg Roach
3026b9cb339SGreg Roach        $individual = Registry::individualFactory()->make($xref, $tree);
303453a3d0bSGreg Roach
30472ac996dSGreg Roach        return $this->viewResponse('modules/stories/edit', [
30572ac996dSGreg Roach            'block_id'    => $block_id,
30672ac996dSGreg Roach            'languages'   => $languages,
30772ac996dSGreg Roach            'story_body'  => $story_body,
30872ac996dSGreg Roach            'story_title' => $story_title,
30972ac996dSGreg Roach            'title'       => $title,
31072ac996dSGreg Roach            'tree'        => $tree,
311c532e5bbSGreg Roach            'url'         => $url,
31272ac996dSGreg Roach            'individual'  => $individual,
31372ac996dSGreg Roach        ]);
31472ac996dSGreg Roach    }
31572ac996dSGreg Roach
31672ac996dSGreg Roach    /**
3176ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
31872ac996dSGreg Roach     *
3196ccdf4f0SGreg Roach     * @return ResponseInterface
32072ac996dSGreg Roach     */
32157ab2231SGreg Roach    public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface
322c1010edaSGreg Roach    {
323b55cbc6bSGreg Roach        $tree        = Validator::attributes($request)->tree();
3249f0bdfcdSGreg Roach        $block_id    = Validator::queryParams($request)->integer('block_id', 0);
3259f0bdfcdSGreg Roach        $xref        = Validator::parsedBody($request)->string('xref');
3269f0bdfcdSGreg Roach        $story_body  = Validator::parsedBody($request)->string('story_body');
3279f0bdfcdSGreg Roach        $story_title = Validator::parsedBody($request)->string('story_title');
3289f0bdfcdSGreg Roach        $languages   = Validator::parsedBody($request)->array('languages');
3299f0bdfcdSGreg Roach        $default_url = route('module', ['module' => $this->name(), 'action' => 'Admin', 'tree' => $tree->name()]);
3309f0bdfcdSGreg Roach        $url         = Validator::parsedBody($request)->isLocalUrl()->string('url', $default_url);
33150d6f48cSGreg Roach        $story_body  = $this->html_service->sanitize($story_body);
33250d6f48cSGreg Roach
33372ac996dSGreg Roach        if ($block_id !== 0) {
3344b92b602SGreg Roach            DB::table('block')
3354b92b602SGreg Roach                ->where('block_id', '=', $block_id)
3364b92b602SGreg Roach                ->update([
3374b92b602SGreg Roach                    'gedcom_id' => $tree->id(),
33872ac996dSGreg Roach                    'xref'      => $xref,
33972ac996dSGreg Roach                ]);
34072ac996dSGreg Roach        } else {
3414b92b602SGreg Roach            DB::table('block')->insert([
3424b92b602SGreg Roach                'gedcom_id'   => $tree->id(),
34372ac996dSGreg Roach                'xref'        => $xref,
34426684e68SGreg Roach                'module_name' => $this->name(),
3454b92b602SGreg Roach                'block_order' => 0,
34672ac996dSGreg Roach            ]);
34772ac996dSGreg Roach
3484b92b602SGreg Roach            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
34972ac996dSGreg Roach        }
35072ac996dSGreg Roach
35172ac996dSGreg Roach        $this->setBlockSetting($block_id, 'story_body', $story_body);
35272ac996dSGreg Roach        $this->setBlockSetting($block_id, 'title', $story_title);
35372ac996dSGreg Roach        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
35472ac996dSGreg Roach
3556ccdf4f0SGreg Roach        return redirect($url);
35672ac996dSGreg Roach    }
35772ac996dSGreg Roach
35872ac996dSGreg Roach    /**
3596ccdf4f0SGreg Roach     * @param ServerRequestInterface $request
36072ac996dSGreg Roach     *
3616ccdf4f0SGreg Roach     * @return ResponseInterface
36272ac996dSGreg Roach     */
36357ab2231SGreg Roach    public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface
364c1010edaSGreg Roach    {
365b55cbc6bSGreg Roach        $tree     = Validator::attributes($request)->tree();
366748dbe15SGreg Roach        $block_id = Validator::queryParams($request)->integer('block_id');
36772ac996dSGreg Roach
3684b92b602SGreg Roach        DB::table('block_setting')
3694b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3704b92b602SGreg Roach            ->delete();
37172ac996dSGreg Roach
3724b92b602SGreg Roach        DB::table('block')
3734b92b602SGreg Roach            ->where('block_id', '=', $block_id)
3744b92b602SGreg Roach            ->delete();
37572ac996dSGreg Roach
376c1010edaSGreg Roach        $url = route('module', [
37726684e68SGreg Roach            'module' => $this->name(),
378c1010edaSGreg Roach            'action' => 'Admin',
3799022ab66SGreg Roach            'tree'    => $tree->name(),
380c1010edaSGreg Roach        ]);
38172ac996dSGreg Roach
3826ccdf4f0SGreg Roach        return redirect($url);
38372ac996dSGreg Roach    }
38472ac996dSGreg Roach
38572ac996dSGreg Roach    /**
38657ab2231SGreg Roach     * @param ServerRequestInterface $request
38772ac996dSGreg Roach     *
3886ccdf4f0SGreg Roach     * @return ResponseInterface
38972ac996dSGreg Roach     */
39057ab2231SGreg Roach    public function getShowListAction(ServerRequestInterface $request): ResponseInterface
391c1010edaSGreg Roach    {
392b55cbc6bSGreg Roach        $tree = Validator::attributes($request)->tree();
39357ab2231SGreg Roach
3944b92b602SGreg Roach        $stories = DB::table('block')
39526684e68SGreg Roach            ->where('module_name', '=', $this->name())
3964b92b602SGreg Roach            ->where('gedcom_id', '=', $tree->id())
3974b92b602SGreg Roach            ->get()
398f70bcff5SGreg Roach            ->map(function (object $story) use ($tree): object {
3995db543e1SGreg Roach                $block_id = (int) $story->block_id;
40064d90241SGreg Roach                $xref     = (string) $story->xref;
4015db543e1SGreg Roach
4026b9cb339SGreg Roach                $story->individual = Registry::individualFactory()->make($xref, $tree);
4035db543e1SGreg Roach                $story->title      = $this->getBlockSetting($block_id, 'title');
4045db543e1SGreg Roach                $story->languages  = $this->getBlockSetting($block_id, 'languages');
40572ac996dSGreg Roach
4064b92b602SGreg Roach                return $story;
407f70bcff5SGreg Roach            })->filter(static function (object $story): bool {
408fceda430SGreg Roach                // Filter non-existent and private individuals.
4094b92b602SGreg Roach                return $story->individual instanceof Individual && $story->individual->canShow();
410f70bcff5SGreg Roach            })->filter(static function (object $story): bool {
41172ac996dSGreg Roach                // Filter foreign languages.
41265cf5706SGreg Roach                return $story->languages === '' || in_array(I18N::languageTag(), explode(',', $story->languages), true);
41372ac996dSGreg Roach            });
41472ac996dSGreg Roach
41572ac996dSGreg Roach        return $this->viewResponse('modules/stories/list', [
41672ac996dSGreg Roach            'stories' => $stories,
41749a243cbSGreg Roach            'title'   => $this->title(),
418745968f0SGreg Roach            'tree'    => $tree,
41972ac996dSGreg Roach        ]);
42072ac996dSGreg Roach    }
4218c2e8227SGreg Roach}
422