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