xref: /webtrees/app/Module/FrequentlyAskedQuestionsModule.php (revision 8de50a4ea3630afae524c9f14cc199f00ba1538e)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 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 */
16namespace Fisharebest\Webtrees\Module;
17
18use Fisharebest\Webtrees\Auth;
19use Fisharebest\Webtrees\Bootstrap4;
20use Fisharebest\Webtrees\Controller\PageController;
21use Fisharebest\Webtrees\Database;
22use Fisharebest\Webtrees\Filter;
23use Fisharebest\Webtrees\Functions\FunctionsEdit;
24use Fisharebest\Webtrees\Html;
25use Fisharebest\Webtrees\I18N;
26use Fisharebest\Webtrees\Menu;
27use Fisharebest\Webtrees\Module;
28use Fisharebest\Webtrees\Tree;
29use stdClass;
30use Symfony\Component\HttpFoundation\RedirectResponse;
31use Symfony\Component\HttpFoundation\Request;
32use Symfony\Component\HttpFoundation\Response;
33
34/**
35 * Class FrequentlyAskedQuestionsModule
36 */
37class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMenuInterface, ModuleConfigInterface {
38	/** {@inheritdoc} */
39	public function getTitle() {
40		return /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */
41			I18N::translate('FAQ');
42	}
43
44	/** {@inheritdoc} */
45	public function getDescription() {
46		return /* I18N: Description of the “FAQ” module */
47			I18N::translate('A list of frequently asked questions and answers.');
48	}
49
50	/**
51	 * The URL to a page where the user can modify the configuration of this module.
52	 *
53	 * @return string
54	 */
55	public function getConfigLink() {
56		return route('module', ['module' => $this->getName(), 'action' => 'Admin']);
57	}
58
59	/**
60	 * The user can re-order menus. Until they do, they are shown in this order.
61	 *
62	 * @return int
63	 */
64	public function defaultMenuOrder() {
65		return 40;
66	}
67
68	/**
69	 * A menu, to be added to the main application menu.
70	 *
71	 * @param Tree $tree
72	 *
73	 * @return Menu|null
74	 */
75	public function getMenu(Tree $tree) {
76		$faqs = Database::prepare(
77			"SELECT block_id FROM `##block`" .
78			" JOIN `##block_setting` USING (block_id)" .
79			" WHERE module_name = :module_name AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" .
80			" AND setting_name='languages' AND (setting_value LIKE CONCAT('%', :locale, '%') OR setting_value='')"
81		)->execute([
82			'module_name' => $this->getName(),
83			'tree_id_1'   => $tree->getTreeId(),
84			'tree_id_2'   => $tree->getTreeId(),
85			'locale'      => WT_LOCALE,
86		])->fetchAll();
87
88		if ($faqs) {
89			return new Menu($this->getTitle(), e(route('module', ['module' => 'faq', 'action' => 'Show', 'ged' => $tree->getName()])), 'menu-help');
90		} else {
91			return null;
92		}
93	}
94
95	/**
96	 * @param Request $request
97	 *
98	 * @return Response
99	 */
100	public function getAdminAction(Request $request): Response {
101		/** @var Tree $tree */
102		$tree = $request->attributes->get('tree');
103
104		$this->layout = 'layouts/administration';
105
106		$faqs = Database::prepare(
107			"SELECT block_id, block_order, gedcom_id, bs1.setting_value AS header, bs2.setting_value AS faqbody" .
108			" FROM `##block` b" .
109			" JOIN `##block_setting` bs1 USING (block_id)" .
110			" JOIN `##block_setting` bs2 USING (block_id)" .
111			" WHERE module_name = :module_name" .
112			" AND bs1.setting_name = 'header'" .
113			" AND bs2.setting_name = 'faqbody'" .
114			" AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" .
115			" ORDER BY block_order"
116		)->execute([
117			'module_name' => $this->getName(),
118			'tree_id_1'   => $tree->getTreeId(),
119			'tree_id_2'   => $tree->getTreeId(),
120		])->fetchAll();
121
122		$min_block_order = Database::prepare(
123			"SELECT MIN(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)"
124		)->execute([
125			'tree_id' => $tree->getTreeId(),
126		])->fetchOne();
127
128		$max_block_order = Database::prepare(
129			"SELECT MAX(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)"
130		)->execute([
131			'tree_id' => $tree->getTreeId(),
132		])->fetchOne();
133
134		$title = I18N::translate('Frequently asked questions') . ' — ' . $tree->getTitle();
135
136		return $this->viewResponse('modules/faq/config', [
137			'faqs'            => $faqs,
138			'max_block_order' => $max_block_order,
139			'min_block_order' => $min_block_order,
140			'title'           => $title,
141			'tree'            => $tree,
142			'tree_names'      => Tree::getNameList(),
143		]);
144	}
145
146	/**
147	 * @param Request $request
148	 *
149	 * @return RedirectResponse
150	 */
151	public function postAdminDeleteAction(Request $request): RedirectResponse {
152		/** @var Tree $tree */
153		$tree = $request->attributes->get('tree');
154
155		$block_id = (int) $request->get('block_id');
156
157		Database::prepare(
158			"DELETE FROM `##block_setting` WHERE block_id = :block_id"
159		)->execute(['block_id' => $block_id]);
160
161		Database::prepare(
162			"DELETE FROM `##block` WHERE block_id = :block_id"
163		)->execute(['block_id' => $block_id]);
164
165
166		$url = route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->getName()]);
167
168		return new RedirectResponse($url);
169	}
170
171	/**
172	 * @param Request $request
173	 *
174	 * @return RedirectResponse
175	 */
176	public function postAdminMoveDownAction(Request $request): RedirectResponse {
177		/** @var Tree $tree */
178		$tree = $request->attributes->get('tree');
179
180		$block_id = (int) $request->get('block_id');
181
182		$block_order = Database::prepare(
183			"SELECT block_order FROM `##block` WHERE block_id = :block_id"
184		)->execute([
185			'block_id' => $block_id,
186		])->fetchOne();
187
188		$swap_block = Database::prepare(
189			"SELECT block_order, block_id" .
190			" FROM `##block`" .
191			" WHERE block_order=(" .
192			"  SELECT MIN(block_order) FROM `##block` WHERE block_order > :block_order AND module_name = :module_name_1" .
193			" ) AND module_name = :module_name_2" .
194			" LIMIT 1"
195		)->execute([
196			'block_order'   => $block_order,
197			'module_name_1' => $this->getName(),
198			'module_name_2' => $this->getName(),
199		])->fetchOneRow();
200
201		if ($swap_block !== null) {
202			Database::prepare(
203				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
204			)->execute([
205				'block_order' => $swap_block->block_order,
206				'block_id'    => $block_id,
207			]);
208			Database::prepare(
209				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
210			)->execute([
211				'block_order' => $block_order,
212				'block_id'    => $swap_block->block_id,
213			]);
214		}
215
216		$url = route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->getName()]);
217
218		return new RedirectResponse($url);
219	}
220
221	/**
222	 * @param Request $request
223	 *
224	 * @return RedirectResponse
225	 */
226	public function postAdminMoveUpAction(Request $request): RedirectResponse {
227		/** @var Tree $tree */
228		$tree = $request->attributes->get('tree');
229
230		$block_id = (int) $request->get('block_id');
231
232		$block_order = Database::prepare(
233			"SELECT block_order FROM `##block` WHERE block_id = :block_id"
234		)->execute([
235			'block_id' => $block_id,
236			])->fetchOne();
237
238		$swap_block = Database::prepare(
239			"SELECT block_order, block_id" .
240			" FROM `##block`" .
241			" WHERE block_order = (" .
242			"  SELECT MAX(block_order) FROM `##block` WHERE block_order < :block_order AND module_name = :module_name_1" .
243			" ) AND module_name = :module_name_2" .
244			" LIMIT 1"
245		)->execute([
246			'block_order'   => $block_order,
247			'module_name_1' => $this->getName(),
248			'module_name_2' => $this->getName(),
249		])->fetchOneRow();
250
251		if ($swap_block !== null) {
252			Database::prepare(
253				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
254			)->execute([
255				'block_order' => $swap_block->block_order,
256				'block_id'    => $block_id,
257			]);
258			Database::prepare(
259				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
260			)->execute([
261				'block_order' => $block_order,
262				'block_id'    => $swap_block->block_id,
263			]);
264		}
265
266		$url = route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->getName()]);
267
268		return new RedirectResponse($url);
269	}
270
271	/**
272	 * @param Request $request
273	 *
274	 * @return Response
275	 */
276	public function getAdminEditAction(Request $request): Response {
277		/** @var Tree $tree */
278		$tree = $request->attributes->get('tree');
279
280		$this->layout = 'layouts/administration';
281
282		$block_id = (int) $request->get('block_id');
283
284		if ($block_id === 0) {
285			// Creating a new faq
286			$header      = '';
287			$faqbody     = '';
288			$block_order = Database::prepare(
289				"SELECT IFNULL(MAX(block_order)+1, 0) FROM `##block` WHERE module_name = :module_name"
290			)->execute([
291				'module_name' => $this->getName(),
292			])->fetchOne();
293			$languages = [];
294
295			$title = I18N::translate('Add an FAQ');
296		} else {
297			// Editing an existing faq
298			$header      = $this->getBlockSetting($block_id, 'header');
299			$faqbody     = $this->getBlockSetting($block_id, 'faqbody');
300			$block_order = Database::prepare(
301				"SELECT block_order FROM `##block` WHERE block_id = :block_id"
302			)->execute(['block_id' => $block_id])->fetchOne();
303			$languages = explode(',', $this->getBlockSetting($block_id, 'languages'));
304
305			$title = I18N::translate('Edit the FAQ');
306		}
307
308		// @TODO enable CKEDITOR
309
310		return $this->viewResponse('modules/faq/edit', [
311			'block_id'    => $block_id,
312			'block_order' => $block_order,
313			'header'      => $header,
314			'faqbody'     => $faqbody,
315			'languages'   => $languages,
316			'title'       => $title,
317			'tree'        => $tree,
318			'tree_names' => Tree::getNameList(),
319		]);
320	}
321
322	/**
323	 * @param Request $request
324	 *
325	 * @return RedirectResponse
326	 */
327	public function postAdminEditAction(Request $request): RedirectResponse {
328		/** @var Tree $tree */
329		$tree = $request->attributes->get('tree');
330
331		$block_id  = (int) $request->get('block_id');
332		$faqbody   = $request->get('faqbody', '');
333		$header    = $request->get('header', '');
334		$languages = $request->get('languages', []);
335
336		if ($block_id !== 0) {
337			Database::prepare(
338				"UPDATE `##block` SET gedcom_id = NULLIF(:tree_id, '0'), block_order = :block_order WHERE block_id = :block_id"
339			)->execute([
340				'tree_id'     => Filter::postInteger('gedcom_id'),
341				'block_order' => Filter::postInteger('block_order'),
342				'block_id'    => $block_id,
343			]);
344		} else {
345			Database::prepare(
346				"INSERT INTO `##block` (gedcom_id, module_name, block_order) VALUES (NULLIF(:tree_id, '0'), :module_name, :block_order)"
347			)->execute([
348				'tree_id'     => Filter::postInteger('gedcom_id'),
349				'module_name' => $this->getName(),
350				'block_order' => Filter::postInteger('block_order'),
351			]);
352
353			$block_id = Database::getInstance()->lastInsertId();
354		}
355
356		$this->setBlockSetting($block_id, 'faqbody', $faqbody);
357		$this->setBlockSetting($block_id, 'header', $header);
358		$this->setBlockSetting($block_id, 'languages', implode(',', $languages));
359
360		$url = route('module', ['module' => 'faq', 'action' => 'Admin', 'ged' => $tree->getName()]);
361
362		return new RedirectResponse($url);
363	}
364
365	/**
366	 * @param Request $request
367	 *
368	 * @return Response
369	 */
370	public function getShowAction(Request $request): Response {
371		/** @var Tree $tree */
372		$tree = $request->attributes->get('tree');
373
374		$faqs = Database::prepare(
375			"SELECT block_id, bs1.setting_value AS header, bs2.setting_value AS body, bs3.setting_value AS languages" .
376			" FROM `##block` b" .
377			" JOIN `##block_setting` bs1 USING (block_id)" .
378			" JOIN `##block_setting` bs2 USING (block_id)" .
379			" JOIN `##block_setting` bs3 USING (block_id)" .
380			" WHERE module_name = :module_name" .
381			" AND bs1.setting_name = 'header'" .
382			" AND bs2.setting_name = 'faqbody'" .
383			" AND bs3.setting_name = 'languages'" .
384			" AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" .
385			" ORDER BY block_order"
386		)->execute([
387			'module_name' => $this->getName(),
388			'tree_id_1'   => $tree->getTreeId(),
389			'tree_id_2'   => $tree->getTreeId(),
390		])->fetchAll();
391
392		// Filter foreign languages.
393		$faqs = array_filter($faqs, function (stdClass $faq) {
394			return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages));
395		});
396
397		return $this->viewResponse('modules/faq/show', [
398			'faqs' => $faqs,
399			'title' => I18N::translate('Frequently asked questions'),
400			'tree' => $tree,
401		]);
402	}
403}
404