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() . '&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(); ?>&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(), '&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(); ?>&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 ' '; 470 } else { 471 echo '<a href="module.php?mod=', $this->getName(), '&mod_action=admin_moveup&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 ' '; 476 } else { 477 echo '<a href="module.php?mod=', $this->getName(), '&mod_action=admin_movedown&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(), '&mod_action=admin_edit&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(), '&mod_action=admin_delete&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&mod_action=show', 'menu-help'); 522 } else { 523 return null; 524 } 525 526 } 527} 528