1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2018 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\Database; 19use Fisharebest\Webtrees\Filter; 20use Fisharebest\Webtrees\I18N; 21use Fisharebest\Webtrees\Menu; 22use Fisharebest\Webtrees\Tree; 23use stdClass; 24use Symfony\Component\HttpFoundation\RedirectResponse; 25use Symfony\Component\HttpFoundation\Request; 26use Symfony\Component\HttpFoundation\Response; 27 28/** 29 * Class FrequentlyAskedQuestionsModule 30 */ 31class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMenuInterface, ModuleConfigInterface 32{ 33 /** {@inheritdoc} */ 34 public function getTitle() 35 { 36 return /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */ 37 I18N::translate('FAQ'); 38 } 39 40 /** {@inheritdoc} */ 41 public function getDescription() 42 { 43 return /* I18N: Description of the “FAQ” module */ 44 I18N::translate('A list of frequently asked questions and answers.'); 45 } 46 47 /** 48 * The URL to a page where the user can modify the configuration of this module. 49 * 50 * @return string 51 */ 52 public function getConfigLink() 53 { 54 return route('module', [ 55 'module' => $this->getName(), 56 'action' => 'Admin', 57 ]); 58 } 59 60 /** 61 * The user can re-order menus. Until they do, they are shown in this order. 62 * 63 * @return int 64 */ 65 public function defaultMenuOrder() 66 { 67 return 40; 68 } 69 70 /** 71 * A menu, to be added to the main application menu. 72 * 73 * @param Tree $tree 74 * 75 * @return Menu|null 76 */ 77 public function getMenu(Tree $tree) 78 { 79 $faqs = Database::prepare( 80 "SELECT block_id FROM `##block`" . 81 " JOIN `##block_setting` USING (block_id)" . 82 " WHERE module_name = :module_name AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" . 83 " AND setting_name='languages' AND (setting_value LIKE CONCAT('%', :locale, '%') OR setting_value='')" 84 )->execute([ 85 'module_name' => $this->getName(), 86 'tree_id_1' => $tree->getTreeId(), 87 'tree_id_2' => $tree->getTreeId(), 88 'locale' => WT_LOCALE, 89 ])->fetchAll(); 90 91 if ($faqs) { 92 return new Menu($this->getTitle(), route('module', [ 93 'module' => 'faq', 94 'action' => 'Show', 95 'ged' => $tree->getName(), 96 ]), 'menu-help'); 97 } else { 98 return null; 99 } 100 } 101 102 /** 103 * @param Request $request 104 * 105 * @return Response 106 */ 107 public function getAdminAction(Request $request): Response 108 { 109 /** @var Tree $tree */ 110 $tree = $request->attributes->get('tree'); 111 112 $this->layout = 'layouts/administration'; 113 114 $faqs = Database::prepare( 115 "SELECT block_id, block_order, gedcom_id, bs1.setting_value AS header, bs2.setting_value AS faqbody" . 116 " FROM `##block` b" . 117 " JOIN `##block_setting` bs1 USING (block_id)" . 118 " JOIN `##block_setting` bs2 USING (block_id)" . 119 " WHERE module_name = :module_name" . 120 " AND bs1.setting_name = 'header'" . 121 " AND bs2.setting_name = 'faqbody'" . 122 " AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" . 123 " ORDER BY block_order" 124 )->execute([ 125 'module_name' => $this->getName(), 126 'tree_id_1' => $tree->getTreeId(), 127 'tree_id_2' => $tree->getTreeId(), 128 ])->fetchAll(); 129 130 $min_block_order = Database::prepare( 131 "SELECT MIN(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)" 132 )->execute([ 133 'tree_id' => $tree->getTreeId(), 134 ])->fetchOne(); 135 136 $max_block_order = Database::prepare( 137 "SELECT MAX(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)" 138 )->execute([ 139 'tree_id' => $tree->getTreeId(), 140 ])->fetchOne(); 141 142 $title = I18N::translate('Frequently asked questions') . ' — ' . $tree->getTitle(); 143 144 return $this->viewResponse('modules/faq/config', [ 145 'faqs' => $faqs, 146 'max_block_order' => $max_block_order, 147 'min_block_order' => $min_block_order, 148 'title' => $title, 149 'tree' => $tree, 150 'tree_names' => Tree::getNameList(), 151 ]); 152 } 153 154 /** 155 * @param Request $request 156 * 157 * @return RedirectResponse 158 */ 159 public function postAdminDeleteAction(Request $request): RedirectResponse 160 { 161 /** @var Tree $tree */ 162 $tree = $request->attributes->get('tree'); 163 164 $block_id = (int)$request->get('block_id'); 165 166 Database::prepare( 167 "DELETE FROM `##block_setting` WHERE block_id = :block_id" 168 )->execute(['block_id' => $block_id]); 169 170 Database::prepare( 171 "DELETE FROM `##block` WHERE block_id = :block_id" 172 )->execute(['block_id' => $block_id]); 173 174 175 $url = route('module', [ 176 'module' => 'faq', 177 'action' => 'Admin', 178 'ged' => $tree->getName(), 179 ]); 180 181 return new RedirectResponse($url); 182 } 183 184 /** 185 * @param Request $request 186 * 187 * @return RedirectResponse 188 */ 189 public function postAdminMoveDownAction(Request $request): RedirectResponse 190 { 191 /** @var Tree $tree */ 192 $tree = $request->attributes->get('tree'); 193 194 $block_id = (int)$request->get('block_id'); 195 196 $block_order = Database::prepare( 197 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 198 )->execute([ 199 'block_id' => $block_id, 200 ])->fetchOne(); 201 202 $swap_block = Database::prepare( 203 "SELECT block_order, block_id" . 204 " FROM `##block`" . 205 " WHERE block_order=(" . 206 " SELECT MIN(block_order) FROM `##block` WHERE block_order > :block_order AND module_name = :module_name_1" . 207 " ) AND module_name = :module_name_2" . 208 " LIMIT 1" 209 )->execute([ 210 'block_order' => $block_order, 211 'module_name_1' => $this->getName(), 212 'module_name_2' => $this->getName(), 213 ])->fetchOneRow(); 214 215 if ($swap_block !== null) { 216 Database::prepare( 217 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 218 )->execute([ 219 'block_order' => $swap_block->block_order, 220 'block_id' => $block_id, 221 ]); 222 Database::prepare( 223 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 224 )->execute([ 225 'block_order' => $block_order, 226 'block_id' => $swap_block->block_id, 227 ]); 228 } 229 230 $url = route('module', [ 231 'module' => 'faq', 232 'action' => 'Admin', 233 'ged' => $tree->getName(), 234 ]); 235 236 return new RedirectResponse($url); 237 } 238 239 /** 240 * @param Request $request 241 * 242 * @return RedirectResponse 243 */ 244 public function postAdminMoveUpAction(Request $request): RedirectResponse 245 { 246 /** @var Tree $tree */ 247 $tree = $request->attributes->get('tree'); 248 249 $block_id = (int)$request->get('block_id'); 250 251 $block_order = Database::prepare( 252 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 253 )->execute([ 254 'block_id' => $block_id, 255 ])->fetchOne(); 256 257 $swap_block = Database::prepare( 258 "SELECT block_order, block_id" . 259 " FROM `##block`" . 260 " WHERE block_order = (" . 261 " SELECT MAX(block_order) FROM `##block` WHERE block_order < :block_order AND module_name = :module_name_1" . 262 " ) AND module_name = :module_name_2" . 263 " LIMIT 1" 264 )->execute([ 265 'block_order' => $block_order, 266 'module_name_1' => $this->getName(), 267 'module_name_2' => $this->getName(), 268 ])->fetchOneRow(); 269 270 if ($swap_block !== null) { 271 Database::prepare( 272 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 273 )->execute([ 274 'block_order' => $swap_block->block_order, 275 'block_id' => $block_id, 276 ]); 277 Database::prepare( 278 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 279 )->execute([ 280 'block_order' => $block_order, 281 'block_id' => $swap_block->block_id, 282 ]); 283 } 284 285 $url = route('module', [ 286 'module' => 'faq', 287 'action' => 'Admin', 288 'ged' => $tree->getName(), 289 ]); 290 291 return new RedirectResponse($url); 292 } 293 294 /** 295 * @param Request $request 296 * 297 * @return Response 298 */ 299 public function getAdminEditAction(Request $request): Response 300 { 301 /** @var Tree $tree */ 302 $tree = $request->attributes->get('tree'); 303 304 $this->layout = 'layouts/administration'; 305 306 $block_id = (int)$request->get('block_id'); 307 308 if ($block_id === 0) { 309 // Creating a new faq 310 $header = ''; 311 $faqbody = ''; 312 $block_order = Database::prepare( 313 "SELECT IFNULL(MAX(block_order)+1, 0) FROM `##block` WHERE module_name = :module_name" 314 )->execute([ 315 'module_name' => $this->getName(), 316 ])->fetchOne(); 317 $languages = []; 318 319 $title = I18N::translate('Add an FAQ'); 320 } else { 321 // Editing an existing faq 322 $header = $this->getBlockSetting($block_id, 'header'); 323 $faqbody = $this->getBlockSetting($block_id, 'faqbody'); 324 $block_order = Database::prepare( 325 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 326 )->execute(['block_id' => $block_id])->fetchOne(); 327 $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 328 329 $title = I18N::translate('Edit the FAQ'); 330 } 331 332 // @TODO enable CKEDITOR 333 334 return $this->viewResponse('modules/faq/edit', [ 335 'block_id' => $block_id, 336 'block_order' => $block_order, 337 'header' => $header, 338 'faqbody' => $faqbody, 339 'languages' => $languages, 340 'title' => $title, 341 'tree' => $tree, 342 'tree_names' => Tree::getNameList(), 343 ]); 344 } 345 346 /** 347 * @param Request $request 348 * 349 * @return RedirectResponse 350 */ 351 public function postAdminEditAction(Request $request): RedirectResponse 352 { 353 /** @var Tree $tree */ 354 $tree = $request->attributes->get('tree'); 355 356 $block_id = (int)$request->get('block_id'); 357 $faqbody = $request->get('faqbody', ''); 358 $header = $request->get('header', ''); 359 $languages = $request->get('languages', []); 360 361 if ($block_id !== 0) { 362 Database::prepare( 363 "UPDATE `##block` SET gedcom_id = NULLIF(:tree_id, '0'), block_order = :block_order WHERE block_id = :block_id" 364 )->execute([ 365 'tree_id' => Filter::postInteger('gedcom_id'), 366 'block_order' => Filter::postInteger('block_order'), 367 'block_id' => $block_id, 368 ]); 369 } else { 370 Database::prepare( 371 "INSERT INTO `##block` (gedcom_id, module_name, block_order) VALUES (NULLIF(:tree_id, '0'), :module_name, :block_order)" 372 )->execute([ 373 'tree_id' => Filter::postInteger('gedcom_id'), 374 'module_name' => $this->getName(), 375 'block_order' => Filter::postInteger('block_order'), 376 ]); 377 378 $block_id = Database::getInstance()->lastInsertId(); 379 } 380 381 $this->setBlockSetting($block_id, 'faqbody', $faqbody); 382 $this->setBlockSetting($block_id, 'header', $header); 383 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 384 385 $url = route('module', [ 386 'module' => 'faq', 387 'action' => 'Admin', 388 'ged' => $tree->getName(), 389 ]); 390 391 return new RedirectResponse($url); 392 } 393 394 /** 395 * @param Request $request 396 * 397 * @return Response 398 */ 399 public function getShowAction(Request $request): Response 400 { 401 /** @var Tree $tree */ 402 $tree = $request->attributes->get('tree'); 403 404 $faqs = Database::prepare( 405 "SELECT block_id, bs1.setting_value AS header, bs2.setting_value AS body, bs3.setting_value AS languages" . 406 " FROM `##block` b" . 407 " JOIN `##block_setting` bs1 USING (block_id)" . 408 " JOIN `##block_setting` bs2 USING (block_id)" . 409 " JOIN `##block_setting` bs3 USING (block_id)" . 410 " WHERE module_name = :module_name" . 411 " AND bs1.setting_name = 'header'" . 412 " AND bs2.setting_name = 'faqbody'" . 413 " AND bs3.setting_name = 'languages'" . 414 " AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" . 415 " ORDER BY block_order" 416 )->execute([ 417 'module_name' => $this->getName(), 418 'tree_id_1' => $tree->getTreeId(), 419 'tree_id_2' => $tree->getTreeId(), 420 ])->fetchAll(); 421 422 // Filter foreign languages. 423 $faqs = array_filter($faqs, function (stdClass $faq) { 424 return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages)); 425 }); 426 427 return $this->viewResponse('modules/faq/show', [ 428 'faqs' => $faqs, 429 'title' => I18N::translate('Frequently asked questions'), 430 'tree' => $tree, 431 ]); 432 } 433} 434