xref: /webtrees/app/Module/StoriesModule.php (revision 22d65e5ad7724941da33d875027b68b86648a321)
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->get('block_id');
203
204        if ($block_id === 0) {
205            // Creating a new story
206            $individual  = Individual::getInstance($request->get('xref', ''), $tree);
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->get('block_id');
246        $xref        = $request->get('xref', '');
247        $story_body  = $request->get('story_body', '');
248        $story_title = $request->get('story_title', '');
249        $languages   = $request->get('languages', []);
250
251        if ($block_id !== 0) {
252            DB::table('block')
253                ->where('block_id', '=', $block_id)
254                ->update([
255                    'gedcom_id' => $tree->id(),
256                    'xref'      => $xref,
257                ]);
258        } else {
259            DB::table('block')->insert([
260                'gedcom_id'   => $tree->id(),
261                'xref'        => $xref,
262                'module_name' => $this->name(),
263                'block_order' => 0,
264            ]);
265
266            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
267        }
268
269        $this->setBlockSetting($block_id, 'story_body', $story_body);
270        $this->setBlockSetting($block_id, 'title', $story_title);
271        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
272
273        $url = route('module', [
274            'module' => $this->name(),
275            'action' => 'Admin',
276            'ged'    => $tree->name(),
277        ]);
278
279        return redirect($url);
280    }
281
282    /**
283     * @param ServerRequestInterface $request
284     * @param Tree                   $tree
285     *
286     * @return ResponseInterface
287     */
288    public function postAdminDeleteAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
289    {
290        $block_id = (int) $request->get('block_id');
291
292        DB::table('block_setting')
293            ->where('block_id', '=', $block_id)
294            ->delete();
295
296        DB::table('block')
297            ->where('block_id', '=', $block_id)
298            ->delete();
299
300        $url = route('module', [
301            'module' => $this->name(),
302            'action' => 'Admin',
303            'ged'    => $tree->name(),
304        ]);
305
306        return redirect($url);
307    }
308
309    /**
310     * @param Tree $tree
311     *
312     * @return ResponseInterface
313     */
314    public function getShowListAction(Tree $tree): ResponseInterface
315    {
316        $stories = DB::table('block')
317            ->where('module_name', '=', $this->name())
318            ->where('gedcom_id', '=', $tree->id())
319            ->get()
320            ->map(function (stdClass $story) use ($tree): stdClass {
321                $block_id = (int) $story->block_id;
322
323                $story->individual = Individual::getInstance($story->xref, $tree);
324                $story->title      = $this->getBlockSetting($block_id, 'title');
325                $story->languages  = $this->getBlockSetting($block_id, 'languages');
326
327                return $story;
328            })->filter(static function (stdClass $story): bool {
329                // Filter non-existant and private individuals.
330                return $story->individual instanceof Individual && $story->individual->canShow();
331            })->filter(static function (stdClass $story): bool {
332                // Filter foreign languages.
333                return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true);
334            });
335
336        return $this->viewResponse('modules/stories/list', [
337            'stories' => $stories,
338            'title'   => $this->title(),
339        ]);
340    }
341}
342