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