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