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