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