xref: /webtrees/app/Module/FrequentlyAskedQuestionsModule.php (revision 1a218474113038005e50986fff24ebcbd58554ff)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2019 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17declare(strict_types=1);
18
19namespace Fisharebest\Webtrees\Module;
20
21use Fisharebest\Webtrees\I18N;
22use Fisharebest\Webtrees\Menu;
23use Fisharebest\Webtrees\Services\HtmlService;
24use Fisharebest\Webtrees\Tree;
25use Illuminate\Database\Capsule\Manager as DB;
26use Illuminate\Database\Query\Builder;
27use Illuminate\Support\Collection;
28use InvalidArgumentException;
29use Psr\Http\Message\ResponseInterface;
30use Psr\Http\Message\ServerRequestInterface;
31use stdClass;
32
33use function assert;
34
35/**
36 * Class FrequentlyAskedQuestionsModule
37 */
38class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface
39{
40    use ModuleConfigTrait;
41    use ModuleMenuTrait;
42
43    /** @var HtmlService */
44    private $html_service;
45
46    /**
47     * HtmlBlockModule bootstrap.
48     *
49     * @param HtmlService $html_service
50     */
51    public function boot(HtmlService $html_service)
52    {
53        $this->html_service = $html_service;
54    }
55
56    /**
57     * How should this module be identified in the control panel, etc.?
58     *
59     * @return string
60     */
61    public function title(): string
62    {
63        /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */
64        return I18N::translate('FAQ');
65    }
66
67    /**
68     * A sentence describing what this module does.
69     *
70     * @return string
71     */
72    public function description(): string
73    {
74        /* I18N: Description of the “FAQ” module */
75        return I18N::translate('A list of frequently asked questions and answers.');
76    }
77
78    /**
79     * The default position for this menu.  It can be changed in the control panel.
80     *
81     * @return int
82     */
83    public function defaultMenuOrder(): int
84    {
85        return 8;
86    }
87
88    /**
89     * A menu, to be added to the main application menu.
90     *
91     * @param Tree $tree
92     *
93     * @return Menu|null
94     */
95    public function getMenu(Tree $tree): ?Menu
96    {
97        if ($this->faqsExist($tree, WT_LOCALE)) {
98            return new Menu($this->title(), route('module', [
99                'module' => $this->name(),
100                'action' => 'Show',
101                'tree'   => $tree->name(),
102            ]), 'menu-help');
103        }
104
105        return null;
106    }
107
108    /**
109     * @param ServerRequestInterface $request
110     *
111     * @return ResponseInterface
112     */
113    public function getAdminAction(ServerRequestInterface $request): ResponseInterface
114    {
115        $this->layout = 'layouts/administration';
116
117        $tree = $request->getAttribute('tree');
118        assert($tree instanceof Tree, new InvalidArgumentException());
119
120        $faqs = $this->faqsForTree($tree);
121
122        $min_block_order = DB::table('block')
123            ->where('module_name', '=', $this->name())
124            ->where(static function (Builder $query) use ($tree): void {
125                $query
126                    ->whereNull('gedcom_id')
127                    ->orWhere('gedcom_id', '=', $tree->id());
128            })
129            ->min('block_order');
130
131        $max_block_order = DB::table('block')
132            ->where('module_name', '=', $this->name())
133            ->where(static function (Builder $query) use ($tree): void {
134                $query
135                    ->whereNull('gedcom_id')
136                    ->orWhere('gedcom_id', '=', $tree->id());
137            })
138            ->max('block_order');
139
140        $title = I18N::translate('Frequently asked questions') . ' — ' . $tree->title();
141
142        return $this->viewResponse('modules/faq/config', [
143            'action'          => route('module', ['module' => $this->name(), 'action' => 'Admin']),
144            'faqs'            => $faqs,
145            'max_block_order' => $max_block_order,
146            'min_block_order' => $min_block_order,
147            'module'          => $this->name(),
148            'title'           => $title,
149            'tree'            => $tree,
150            'tree_names'      => Tree::getNameList(),
151        ]);
152    }
153
154    /**
155     * @param ServerRequestInterface $request
156     *
157     * @return ResponseInterface
158     */
159    public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface
160    {
161        $tree     = $request->getAttribute('tree');
162        $block_id = (int) $request->getQueryParams()['block_id'];
163
164        DB::table('block_setting')->where('block_id', '=', $block_id)->delete();
165
166        DB::table('block')->where('block_id', '=', $block_id)->delete();
167
168        $url = route('module', [
169            'module' => $this->name(),
170            'action' => 'Admin',
171            'tree'   => $tree->name(),
172        ]);
173
174        return redirect($url);
175    }
176
177    /**
178     * @param ServerRequestInterface $request
179     *
180     * @return ResponseInterface
181     */
182    public function postAdminMoveDownAction(ServerRequestInterface $request): ResponseInterface
183    {
184        $tree     = $request->getAttribute('tree');
185        $block_id = (int) $request->getQueryParams()['block_id'];
186
187        $block_order = DB::table('block')
188            ->where('block_id', '=', $block_id)
189            ->value('block_order');
190
191        $swap_block = DB::table('block')
192            ->where('module_name', '=', $this->name())
193            ->where('block_order', '=', function (Builder $query) use ($block_order): void {
194                $query
195                    ->from('block')
196                    ->where('module_name', '=', $this->name())
197                    ->where('block_order', '>', $block_order)
198                    ->min('block_order');
199            })
200            ->select(['block_order', 'block_id'])
201            ->first();
202
203        if ($swap_block !== null) {
204            DB::table('block')
205                ->where('block_id', '=', $block_id)
206                ->update([
207                    'block_order' => $swap_block->block_order,
208                ]);
209
210            DB::table('block')
211                ->where('block_id', '=', $swap_block->block_id)
212                ->update([
213                    'block_order' => $block_order,
214                ]);
215        }
216
217        $url = route('module', [
218            'module' => $this->name(),
219            'action' => 'Admin',
220            'tree'   => $tree->name(),
221        ]);
222
223        return redirect($url);
224    }
225
226    /**
227     * @param ServerRequestInterface $request
228     *
229     * @return ResponseInterface
230     */
231    public function postAdminMoveUpAction(ServerRequestInterface $request): ResponseInterface
232    {
233        $tree     = $request->getAttribute('tree');
234        $block_id = (int) $request->getQueryParams()['block_id'];
235
236        $block_order = DB::table('block')
237            ->where('block_id', '=', $block_id)
238            ->value('block_order');
239
240        $swap_block = DB::table('block')
241            ->where('module_name', '=', $this->name())
242            ->where('block_order', '=', function (Builder $query) use ($block_order): void {
243                $query
244                    ->from('block')
245                    ->where('module_name', '=', $this->name())
246                    ->where('block_order', '<', $block_order)
247                    ->max('block_order');
248            })
249            ->select(['block_order', 'block_id'])
250            ->first();
251
252        if ($swap_block !== null) {
253            DB::table('block')
254                ->where('block_id', '=', $block_id)
255                ->update([
256                    'block_order' => $swap_block->block_order,
257                ]);
258
259            DB::table('block')
260                ->where('block_id', '=', $swap_block->block_id)
261                ->update([
262                    'block_order' => $block_order,
263                ]);
264        }
265
266        $url = route('module', [
267            'module' => $this->name(),
268            'action' => 'Admin',
269            'tree'    => $tree->name(),
270        ]);
271
272        return redirect($url);
273    }
274
275    /**
276     * @param ServerRequestInterface $request
277     *
278     * @return ResponseInterface
279     */
280    public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface
281    {
282        $this->layout = 'layouts/administration';
283
284        $tree     = $request->getAttribute('tree');
285        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
286
287        if ($block_id === 0) {
288            // Creating a new faq
289            $header  = '';
290            $faqbody = '';
291
292            $block_order = 1 + (int) DB::table('block')
293                    ->where('module_name', '=', $this->name())
294                    ->max('block_order');
295
296            $languages = [];
297
298            $title = I18N::translate('Add an FAQ');
299        } else {
300            // Editing an existing faq
301            $header  = $this->getBlockSetting($block_id, 'header');
302            $faqbody = $this->getBlockSetting($block_id, 'faqbody');
303
304            $block_order = DB::table('block')
305                ->where('block_id', '=', $block_id)
306                ->value('block_order');
307
308            $languages = explode(',', $this->getBlockSetting($block_id, 'languages'));
309
310            $title = I18N::translate('Edit the FAQ');
311        }
312
313        $tree_names = ['' => I18N::translate('All')] + Tree::getIdList();
314
315        return $this->viewResponse('modules/faq/edit', [
316            'block_id'    => $block_id,
317            'block_order' => $block_order,
318            'header'      => $header,
319            'faqbody'     => $faqbody,
320            'languages'   => $languages,
321            'title'       => $title,
322            'tree'        => $tree,
323            'tree_names'  => $tree_names,
324        ]);
325    }
326
327    /**
328     * @param ServerRequestInterface $request
329     *
330     * @return ResponseInterface
331     */
332    public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface
333    {
334        $tree     = $request->getAttribute('tree');
335        $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0);
336
337        $params = $request->getParsedBody();
338
339        $faqbody     = $params['faqbody'];
340        $header      = $params['header'];
341        $languages   = $params['languages'] ?? [];
342        $gedcom_id   = (int) $params['gedcom_id'] ?: null;
343        $block_order = (int) $params['block_order'];
344
345        $faqbody = $this->html_service->sanitize($faqbody);
346        $header  = $this->html_service->sanitize($header);
347
348        if ($block_id !== 0) {
349            DB::table('block')
350                ->where('block_id', '=', $block_id)
351                ->update([
352                    'gedcom_id'   => $gedcom_id,
353                    'block_order' => $block_order,
354                ]);
355        } else {
356            DB::table('block')->insert([
357                'gedcom_id'   => $gedcom_id,
358                'module_name' => $this->name(),
359                'block_order' => $block_order,
360            ]);
361
362            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
363        }
364
365        $this->setBlockSetting($block_id, 'faqbody', $faqbody);
366        $this->setBlockSetting($block_id, 'header', $header);
367        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));
368
369        $url = route('module', [
370            'module' => $this->name(),
371            'action' => 'Admin',
372            'tree'   => $tree->name(),
373        ]);
374
375        return redirect($url);
376    }
377
378    /**
379     * @param ServerRequestInterface $request
380     *
381     * @return ResponseInterface
382     */
383    public function getShowAction(ServerRequestInterface $request): ResponseInterface
384    {
385        $tree = $request->getAttribute('tree');
386        assert($tree instanceof Tree, new InvalidArgumentException());
387
388        // Filter foreign languages.
389        $faqs = $this->faqsForTree($tree)
390            ->filter(static function (stdClass $faq): bool {
391                return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages), true);
392            });
393
394        return $this->viewResponse('modules/faq/show', [
395            'faqs'  => $faqs,
396            'title' => I18N::translate('Frequently asked questions'),
397            'tree'  => $tree,
398        ]);
399    }
400
401    /**
402     * @param Tree $tree
403     *
404     * @return Collection
405     */
406    private function faqsForTree(Tree $tree): Collection
407    {
408        return DB::table('block')
409            ->join('block_setting AS bs1', 'bs1.block_id', '=', 'block.block_id')
410            ->join('block_setting AS bs2', 'bs2.block_id', '=', 'block.block_id')
411            ->join('block_setting AS bs3', 'bs3.block_id', '=', 'block.block_id')
412            ->where('module_name', '=', $this->name())
413            ->where('bs1.setting_name', '=', 'header')
414            ->where('bs2.setting_name', '=', 'faqbody')
415            ->where('bs3.setting_name', '=', 'languages')
416            ->where(static function (Builder $query) use ($tree): void {
417                $query
418                    ->whereNull('gedcom_id')
419                    ->orWhere('gedcom_id', '=', $tree->id());
420            })
421            ->orderBy('block_order')
422            ->select(['block.block_id', 'block_order', 'gedcom_id', 'bs1.setting_value AS header', 'bs2.setting_value AS faqbody', 'bs3.setting_value AS languages'])
423            ->get();
424    }
425
426    /**
427     * @param Tree   $tree
428     * @param string $language
429     *
430     * @return bool
431     */
432    private function faqsExist(Tree $tree, string $language): bool
433    {
434        return DB::table('block')
435            ->join('block_setting', 'block_setting.block_id', '=', 'block.block_id')
436            ->where('module_name', '=', $this->name())
437            ->where('setting_name', '=', 'languages')
438            ->where(static function (Builder $query) use ($tree): void {
439                $query
440                    ->whereNull('gedcom_id')
441                    ->orWhere('gedcom_id', '=', $tree->id());
442            })
443            ->select(['setting_value AS languages'])
444            ->get()
445            ->filter(static function (stdClass $faq) use ($language): bool {
446                return $faq->languages === '' || in_array($language, explode(',', $faq->languages), true);
447            })
448            ->isNotEmpty();
449    }
450}
451