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() . '&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(); ?>&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(), '&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(), '&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 ' '; 459 } else { 460 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>'; 461 } 462 echo '</td><td>'; 463 if ($faq->block_order == $max_block_order) { 464 echo ' '; 465 } else { 466 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>'; 467 } 468 echo '</td><td>'; 469 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>'; 470 echo '</td><td>'; 471 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 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&mod_action=show', 'menu-help'); 510 511 return $menu; 512 } 513} 514