xref: /webtrees/app/Module/FrequentlyAskedQuestionsModule.php (revision da3772f85950756f9d294284c449c6923e3bcf84)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2015 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\Controller\PageController;
20use Fisharebest\Webtrees\Database;
21use Fisharebest\Webtrees\Filter;
22use Fisharebest\Webtrees\Functions\FunctionsEdit;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Menu;
25use Fisharebest\Webtrees\Module;
26use Fisharebest\Webtrees\Tree;
27
28/**
29 * Class FrequentlyAskedQuestionsModule
30 */
31class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMenuInterface, ModuleConfigInterface {
32	/** {@inheritdoc} */
33	public function getTitle() {
34		return /* I18N: Name of a module.  Abbreviation for “Frequently Asked Questions” */ I18N::translate('FAQ');
35	}
36
37	/** {@inheritdoc} */
38	public function getDescription() {
39		return /* I18N: Description of the “FAQ” module */ I18N::translate('A list of frequently asked questions and answers.');
40	}
41
42	/**
43	 * This is a general purpose hook, allowing modules to respond to routes
44	 * of the form module.php?mod=FOO&mod_action=BAR
45	 *
46	 * @param string $mod_action
47	 */
48	public function modAction($mod_action) {
49		switch ($mod_action) {
50		case 'admin_config':
51			$this->config();
52			break;
53		case 'admin_delete':
54			$this->delete();
55			$this->config();
56			break;
57		case 'admin_edit':
58			$this->edit();
59			break;
60		case 'admin_movedown':
61			$this->movedown();
62			$this->config();
63			break;
64		case 'admin_moveup':
65			$this->moveup();
66			$this->config();
67			break;
68		case 'show':
69			$this->show();
70			break;
71		default:
72			http_response_code(404);
73		}
74	}
75
76	/** {@inheritdoc} */
77	public function getConfigLink() {
78		return 'module.php?mod=' . $this->getName() . '&amp;mod_action=admin_config';
79	}
80
81	/**
82	 * Action from the configuration page
83	 */
84	private function edit() {
85		global $WT_TREE;
86
87		if (Filter::postBool('save') && Filter::checkCsrf()) {
88			$block_id = Filter::postInteger('block_id');
89			if ($block_id) {
90				Database::prepare(
91					"UPDATE `##block` SET gedcom_id = NULLIF(:tree_id, '0'), block_order = :block_order WHERE block_id = :block_id"
92				)->execute(array(
93					'tree_id'     => Filter::postInteger('gedcom_id'),
94					'block_order' => Filter::postInteger('block_order'),
95					'block_id'    => $block_id,
96				));
97			} else {
98				Database::prepare(
99					"INSERT INTO `##block` (gedcom_id, module_name, block_order) VALUES (NULLIF(:tree_id, '0'), :module_name, :block_order)"
100				)->execute(array(
101					'tree_id'     => Filter::postInteger('gedcom_id'),
102					'module_name' => $this->getName(),
103					'block_order' => Filter::postInteger('block_order'),
104				));
105				$block_id = Database::getInstance()->lastInsertId();
106			}
107			$this->setBlockSetting($block_id, 'header', Filter::post('header'));
108			$this->setBlockSetting($block_id, 'faqbody', Filter::post('faqbody'));
109
110			$languages = Filter::postArray('lang');
111			$this->setBlockSetting($block_id, 'languages', implode(',', $languages));
112			$this->config();
113		} else {
114			$block_id   = Filter::getInteger('block_id');
115			$controller = new PageController;
116			if ($block_id) {
117				$controller->setPageTitle(I18N::translate('Edit FAQ item'));
118				$header      = $this->getBlockSetting($block_id, 'header');
119				$faqbody     = $this->getBlockSetting($block_id, 'faqbody');
120				$block_order = Database::prepare(
121					"SELECT block_order FROM `##block` WHERE block_id = :block_id"
122				)->execute(array('block_id' => $block_id))->fetchOne();
123				$gedcom_id   = Database::prepare(
124					"SELECT gedcom_id FROM `##block` WHERE block_id = :block_id"
125				)->execute(array('block_id' => $block_id))->fetchOne();
126			} else {
127				$controller->setPageTitle(I18N::translate('Add an FAQ item'));
128				$header      = '';
129				$faqbody     = '';
130				$block_order = Database::prepare(
131					"SELECT IFNULL(MAX(block_order)+1, 0) FROM `##block` WHERE module_name = :module_name"
132				)->execute(array('module_name' => $this->getName()))->fetchOne();
133				$gedcom_id   = $WT_TREE->getTreeId();
134			}
135			$controller->pageHeader();
136			if (Module::getModuleByName('ckeditor')) {
137				CkeditorModule::enableEditor($controller);
138			}
139
140			?>
141			<ol class="breadcrumb small">
142				<li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li>
143				<li><a href="admin_modules.php"><?php echo I18N::translate('Module administration'); ?></a></li>
144				<li><a
145						href="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_config"><?php echo I18N::translate('Frequently asked questions'); ?></a>
146				</li>
147				<li class="active"><?php echo $controller->getPageTitle(); ?></li>
148			</ol>
149			<h1><?php echo $controller->getPageTitle(); ?></h1>
150
151			<form name="faq" class="form-horizontal" method="post" action="module.php?mod=<?php echo $this->getName(); ?>&amp;mod_action=admin_edit">
152			<?php echo Filter::getCsrf(); ?>
153			<input type="hidden" name="save" value="1">
154			<input type="hidden" name="block_id" value="<?php echo $block_id; ?>">
155
156			<div class="form-group">
157				<label for="header" class="col-sm-3 control-label">
158					<?php echo I18N::translate('Question'); ?>
159				</label>
160
161				<div class="col-sm-9">
162					<input type="text" class="form-control" name="header" id="header"
163					       value="<?php echo Filter::escapeHtml($header); ?>">
164				</div>
165			</div>
166
167			<div class="form-group">
168				<label for="faqbody" class="col-sm-3 control-label">
169					<?php echo I18N::translate('Answer'); ?>
170				</label>
171
172				<div class="col-sm-9">
173					<textarea name="faqbody" id="faqbody" class="form-control"
174					          rows="10"><?php echo Filter::escapeHtml($faqbody); ?></textarea>
175				</div>
176			</div>
177
178			<div class="form-group">
179				<label for="xref" class="col-sm-3 control-label">
180					<?php echo I18N::translate('Show this block for which languages?'); ?>
181				</label>
182
183				<div class="col-sm-9">
184					<?php echo FunctionsEdit::editLanguageCheckboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?>
185				</div>
186			</div>
187
188			<div class="form-group">
189				<label for="block_order" class="col-sm-3 control-label">
190					<?php echo I18N::translate('FAQ position'); ?>
191				</label>
192
193				<div class="col-sm-9">
194					<input type="text" name="block_order" id="block_order" class="form-control" value="<?php echo $block_order; ?>">
195				</div>
196			</div>
197
198			<div class="form-group">
199				<label for="gedcom_id" class="col-sm-3 control-label">
200					<?php echo I18N::translate('FAQ visibility'); ?>
201				</label>
202
203				<div class="col-sm-9">
204					<?php echo FunctionsEdit::selectEditControl('gedcom_id', Tree::getIdList(), I18N::translate('All'), $gedcom_id, 'class="form-control"'); ?>
205					<p class="small text-muted">
206						<?php echo I18N::translate('A FAQ item can be displayed on just one of the family trees, or on all the family trees.'); ?>
207					</p>
208				</div>
209			</div>
210
211			<div class="form-group">
212				<div class="col-sm-offset-3 col-sm-9">
213					<button type="submit" class="btn btn-primary">
214						<i class="fa fa-check"></i>
215						<?php echo I18N::translate('save'); ?>
216					</button>
217				</div>
218			</div>
219
220		</form>
221		<?php
222		}
223	}
224
225	/**
226	 * Respond to a request to delete a FAQ.
227	 */
228	private function delete() {
229		$block_id = Filter::getInteger('block_id');
230
231		Database::prepare(
232			"DELETE FROM `##block_setting` WHERE block_id = :block_id"
233		)->execute(array('block_id' => $block_id));
234
235		Database::prepare(
236			"DELETE FROM `##block` WHERE block_id = :block_id"
237		)->execute(array('block_id' => $block_id));
238	}
239
240	/**
241	 * Respond to a request to move a FAQ up the list.
242	 */
243	private function moveup() {
244		$block_id = Filter::getInteger('block_id');
245
246		$block_order = Database::prepare(
247			"SELECT block_order FROM `##block` WHERE block_id = :block_id"
248		)->execute(array('block_id' => $block_id))->fetchOne();
249
250		$swap_block = Database::prepare(
251			"SELECT block_order, block_id" .
252			" FROM `##block`" .
253			" WHERE block_order = (" .
254			"  SELECT MAX(block_order) FROM `##block` WHERE block_order < :block_order AND module_name = :module_name_1" .
255			" ) AND module_name = :module_name_2" .
256			" LIMIT 1"
257		)->execute(array(
258			'block_order'   => $block_order,
259			'module_name_1' => $this->getName(),
260			'module_name_2' => $this->getName(),
261		))->fetchOneRow();
262		if ($swap_block) {
263			Database::prepare(
264				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
265			)->execute(array(
266				'block_order' => $swap_block->block_order,
267				'block_id'    => $block_id,
268			));
269			Database::prepare(
270				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
271			)->execute(array(
272				'block_order' => $block_order,
273				'block_id'    => $swap_block->block_id,
274			));
275		}
276	}
277
278	/**
279	 * Respond to a request to move a FAQ down the list.
280	 */
281	private function movedown() {
282		$block_id = Filter::get('block_id');
283
284		$block_order = Database::prepare(
285			"SELECT block_order FROM `##block` WHERE block_id = :block_id"
286		)->execute(array(
287			'block_id' => $block_id,
288		))->fetchOne();
289
290		$swap_block = Database::prepare(
291			"SELECT block_order, block_id" .
292			" FROM `##block`" .
293			" WHERE block_order=(" .
294			"  SELECT MIN(block_order) FROM `##block` WHERE block_order > :block_order AND module_name = :module_name_1" .
295			" ) AND module_name = :module_name_2" .
296			" LIMIT 1"
297		)->execute(array(
298			'block_order'   => $block_order,
299			'module_name_1' => $this->getName(),
300			'module_name_2' => $this->getName(),
301			))->fetchOneRow();
302		if ($swap_block) {
303			Database::prepare(
304				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
305			)->execute(array(
306				'block_order' => $swap_block->block_order,
307				'block_id'    => $block_id,
308			));
309			Database::prepare(
310				"UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id"
311			)->execute(array(
312				'block_order' => $block_order,
313				'block_id'    => $swap_block->block_id,
314			));
315		}
316	}
317
318	/**
319	 * Show a list of FAQs
320	 */
321	private function show() {
322		global $controller, $WT_TREE;
323
324		$controller = new PageController;
325		$controller
326			->setPageTitle(I18N::translate('Frequently asked questions'))
327			->pageHeader();
328
329		$faqs = Database::prepare(
330			"SELECT block_id, bs1.setting_value AS header, bs2.setting_value AS body, bs3.setting_value AS languages" .
331			" FROM `##block` b" .
332			" JOIN `##block_setting` bs1 USING (block_id)" .
333			" JOIN `##block_setting` bs2 USING (block_id)" .
334			" JOIN `##block_setting` bs3 USING (block_id)" .
335			" WHERE module_name = :module_name" .
336			" AND bs1.setting_name = 'header'" .
337			" AND bs2.setting_name = 'faqbody'" .
338			" AND bs3.setting_name = 'languages'" .
339			" AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" .
340			" ORDER BY block_order"
341		)->execute(array(
342			'module_name' => $this->getName(),
343			'tree_id_1'   => $WT_TREE->getTreeId(),
344			'tree_id_2'   => $WT_TREE->getTreeId(),
345		))->fetchAll();
346
347		// Define your colors for the alternating rows
348		echo '<h2 class="center">', I18N::translate('Frequently asked questions'), '</h2>';
349		// Instructions
350		echo '<div class="faq_italic">', I18N::translate('Click on a title to go straight to it, or scroll down to read them all.');
351		if (Auth::isManager($WT_TREE)) {
352			echo '<div class="faq_edit"><a href="module.php?mod=', $this->getName(), '&amp;mod_action=admin_config">', I18N::translate('Click here to add, edit, or delete'), '</a></div>';
353		}
354		echo '</div>';
355		$row_count = 0;
356		echo '<table class="faq">';
357		// List of titles
358		foreach ($faqs as $id => $faq) {
359			if (!$faq->languages || in_array(WT_LOCALE, explode(',', $faq->languages))) {
360				$row_color = ($row_count % 2) ? 'odd' : 'even';
361				// NOTE: Print the header of the current item
362				echo '<tr class="', $row_color, '"><td style="padding: 5px;">';
363				echo '<a href="#faq', $id, '">', $faq->header, '</a>';
364				echo '</td></tr>';
365				$row_count++;
366			}
367		}
368		echo '</table><hr>';
369		// Detailed entries
370		foreach ($faqs as $id => $faq) {
371			if (!$faq->languages || in_array(WT_LOCALE, explode(',', $faq->languages))) {
372				echo '<div class="faq_title" id="faq', $id, '">', $faq->header;
373				echo '<div class="faq_top faq_italic">';
374				echo '<a href="#content">', I18N::translate('back to top'), '</a>';
375				echo '</div>';
376				echo '</div>';
377				echo '<div class="faq_body">', substr($faq->body, 0, 1) == '<' ? $faq->body : nl2br($faq->body, false), '</div>';
378				echo '<hr>';
379			}
380		}
381	}
382
383	/**
384	 * Provide a form to manage the FAQs.
385	 */
386	private function config() {
387		global $WT_TREE;
388
389		$controller = new PageController;
390		$controller
391			->restrictAccess(Auth::isAdmin())
392			->setPageTitle(I18N::translate('Frequently asked questions'))
393			->pageHeader();
394
395		$faqs = Database::prepare(
396			"SELECT block_id, block_order, gedcom_id, bs1.setting_value AS header, bs2.setting_value AS faqbody" .
397			" FROM `##block` b" .
398			" JOIN `##block_setting` bs1 USING (block_id)" .
399			" JOIN `##block_setting` bs2 USING (block_id)" .
400			" WHERE module_name = :module_name" .
401			" AND bs1.setting_name = 'header'" .
402			" AND bs2.setting_name = 'faqbody'" .
403			" AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" .
404			" ORDER BY block_order"
405		)->execute(array(
406			'module_name' => $this->getName(),
407			'tree_id_1'   => $WT_TREE->getTreeId(),
408			'tree_id_2'   => $WT_TREE->getTreeId(),
409			))->fetchAll();
410
411		$min_block_order = Database::prepare(
412			"SELECT MIN(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)"
413		)->execute(array(
414			'tree_id' => $WT_TREE->getTreeId(),
415		))->fetchOne();
416
417		$max_block_order = Database::prepare(
418			"SELECT MAX(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)"
419		)->execute(array(
420			'tree_id' => $WT_TREE->getTreeId(),
421		))->fetchOne();
422
423		?>
424		<ol class="breadcrumb small">
425			<li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li>
426			<li><a href="admin_modules.php"><?php echo I18N::translate('Module administration'); ?></a></li>
427			<li class="active"><?php echo $controller->getPageTitle(); ?></li>
428		</ol>
429		<h2><?php echo $controller->getPageTitle(); ?></h2>
430		<p>
431			<?php echo I18N::translate('FAQs are lists of questions and answers, which allow you to explain the site’s rules, policies, and procedures to your visitors.  Questions are typically concerned with privacy, copyright, user-accounts, unsuitable content, requirement for source-citations, etc.'); ?>
432			<?php echo I18N::translate('You may use HTML to format the answer and to add links to other websites.'); ?>
433		</p>
434		<form class="form form-inline">
435			<label for="ged" class="sr-only">
436				<?php echo I18N::translate('Family tree'); ?>
437			</label>
438			<input type="hidden" name="mod" value="<?php echo  $this->getName(); ?>">
439			<input type="hidden" name="mod_action" value="admin_config">
440			<?php echo FunctionsEdit::selectEditControl('ged', Tree::getNameList(), null, $WT_TREE->getName(), 'class="form-control"'); ?>
441			<input type="submit" class="btn btn-primary" value="<?php echo I18N::translate('show'); ?>">
442		</form>
443
444		<p>
445			<a href="module.php?mod=<?php echo $this->getName(); ?>&amp;mod_action=admin_edit" class="btn btn-default">
446				<i class="fa fa-plus"></i>
447				<?php echo I18N::translate('Add an FAQ item'); ?>
448			</a>
449		</p>
450
451		<?php
452		echo '<table class="table table-bordered">';
453		if (empty($faqs)) {
454			echo '<tr><td class="error center" colspan="5">', I18N::translate('The FAQ list is empty.'), '</td></tr></table>';
455		} else {
456			foreach ($faqs as $faq) {
457				// NOTE: Print the position of the current item
458				echo '<tr class="faq_edit_pos"><td>';
459				echo I18N::translate('#%s', $faq->block_order + 1), ' ';
460				if ($faq->gedcom_id === null) {
461					echo I18N::translate('All');
462				} else {
463					echo $WT_TREE->getTitleHtml();
464				}
465				echo '</td>';
466				// NOTE: Print the edit options of the current item
467				echo '<td>';
468				if ($faq->block_order == $min_block_order) {
469					echo '&nbsp;';
470				} else {
471					echo '<a href="module.php?mod=', $this->getName(), '&amp;mod_action=admin_moveup&amp;block_id=', $faq->block_id, '"><i class="fa fa-arrow-up"></i></i> ', I18N::translate('Move up'), '</a>';
472				}
473				echo '</td><td>';
474				if ($faq->block_order == $max_block_order) {
475					echo '&nbsp;';
476				} else {
477					echo '<a href="module.php?mod=', $this->getName(), '&amp;mod_action=admin_movedown&amp;block_id=', $faq->block_id, '"><i class="fa fa-arrow-down"></i></i> ', I18N::translate('Move down'), '</a>';
478				}
479				echo '</td><td>';
480				echo '<a href="module.php?mod=', $this->getName(), '&amp;mod_action=admin_edit&amp;block_id=', $faq->block_id, '"><i class="fa fa-pencil"></i> ', I18N::translate('Edit'), '</a>';
481				echo '</td><td>';
482				echo '<a href="module.php?mod=', $this->getName(), '&amp;mod_action=admin_delete&amp;block_id=', $faq->block_id, '" onclick="return confirm(\'', I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeHtml($faq->header)), '\');"><i class="fa fa-trash"></i> ', I18N::translate('Delete'), '</a>';
483				echo '</td></tr>';
484				// NOTE: Print the title text of the current item
485				echo '<tr><td colspan="5">';
486				echo '<div class="faq_edit_item">';
487				echo '<div class="faq_edit_title">', $faq->header, '</div>';
488				// NOTE: Print the body text of the current item
489				echo '<div class="faq_edit_content">', substr($faq->faqbody, 0, 1) == '<' ? $faq->faqbody : nl2br($faq->faqbody, false), '</div></div></td></tr>';
490			}
491			echo '</table>';
492		}
493	}
494
495	/**
496	 * The user can re-order menus.  Until they do, they are shown in this order.
497	 *
498	 * @return int
499	 */
500	public function defaultMenuOrder() {
501		return 40;
502	}
503
504	/**
505	 * A menu, to be added to the main application menu.
506	 *
507	 * @return Menu|null
508	 */
509	public function getMenu() {
510		global $WT_TREE;
511
512		$faqs = Database::prepare(
513			"SELECT block_id FROM `##block` WHERE module_name = :module_name AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2"
514		)->execute(array(
515			'module_name' => $this->getName(),
516			'tree_id_1'   => $WT_TREE->getTreeId(),
517			'tree_id_2'   => $WT_TREE->getTreeId(),
518		))->fetchAll();
519
520		if ($faqs) {
521			return new Menu(I18N::translate('FAQ'), 'module.php?mod=faq&amp;mod_action=show', 'menu-help');
522		} else {
523			return null;
524		}
525
526	}
527}
528