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 /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */ 37 return I18N::translate('FAQ'); 38 } 39 40 /** {@inheritdoc} */ 41 public function getDescription() 42 { 43 /* I18N: Description of the “FAQ” module */ 44 return 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 Tree $tree 104 * 105 * @return Response 106 */ 107 public function getAdminAction(Tree $tree): Response 108 { 109 $this->layout = 'layouts/administration'; 110 111 $faqs = Database::prepare( 112 "SELECT block_id, block_order, gedcom_id, bs1.setting_value AS header, bs2.setting_value AS faqbody" . 113 " FROM `##block` b" . 114 " JOIN `##block_setting` bs1 USING (block_id)" . 115 " JOIN `##block_setting` bs2 USING (block_id)" . 116 " WHERE module_name = :module_name" . 117 " AND bs1.setting_name = 'header'" . 118 " AND bs2.setting_name = 'faqbody'" . 119 " AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" . 120 " ORDER BY block_order" 121 )->execute([ 122 'module_name' => $this->getName(), 123 'tree_id_1' => $tree->getTreeId(), 124 'tree_id_2' => $tree->getTreeId(), 125 ])->fetchAll(); 126 127 $min_block_order = Database::prepare( 128 "SELECT MIN(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)" 129 )->execute([ 130 'tree_id' => $tree->getTreeId(), 131 ])->fetchOne(); 132 133 $max_block_order = Database::prepare( 134 "SELECT MAX(block_order) FROM `##block` WHERE module_name = 'faq' AND (gedcom_id = :tree_id OR gedcom_id IS NULL)" 135 )->execute([ 136 'tree_id' => $tree->getTreeId(), 137 ])->fetchOne(); 138 139 $title = I18N::translate('Frequently asked questions') . ' — ' . $tree->getTitle(); 140 141 return $this->viewResponse('modules/faq/config', [ 142 'faqs' => $faqs, 143 'max_block_order' => $max_block_order, 144 'min_block_order' => $min_block_order, 145 'title' => $title, 146 'tree' => $tree, 147 'tree_names' => Tree::getNameList(), 148 ]); 149 } 150 151 /** 152 * @param Request $request 153 * @param Tree $tree 154 * 155 * @return RedirectResponse 156 */ 157 public function postAdminDeleteAction(Request $request, Tree $tree): RedirectResponse 158 { 159 $block_id = (int)$request->get('block_id'); 160 161 Database::prepare( 162 "DELETE FROM `##block_setting` WHERE block_id = :block_id" 163 )->execute(['block_id' => $block_id]); 164 165 Database::prepare( 166 "DELETE FROM `##block` WHERE block_id = :block_id" 167 )->execute(['block_id' => $block_id]); 168 169 170 $url = route('module', [ 171 'module' => 'faq', 172 'action' => 'Admin', 173 'ged' => $tree->getName(), 174 ]); 175 176 return new RedirectResponse($url); 177 } 178 179 /** 180 * @param Request $request 181 * @param Tree $tree 182 * 183 * @return RedirectResponse 184 */ 185 public function postAdminMoveDownAction(Request $request, Tree $tree): RedirectResponse 186 { 187 $block_id = (int)$request->get('block_id'); 188 189 $block_order = Database::prepare( 190 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 191 )->execute([ 192 'block_id' => $block_id, 193 ])->fetchOne(); 194 195 $swap_block = Database::prepare( 196 "SELECT block_order, block_id" . 197 " FROM `##block`" . 198 " WHERE block_order=(" . 199 " SELECT MIN(block_order) FROM `##block` WHERE block_order > :block_order AND module_name = :module_name_1" . 200 " ) AND module_name = :module_name_2" . 201 " LIMIT 1" 202 )->execute([ 203 'block_order' => $block_order, 204 'module_name_1' => $this->getName(), 205 'module_name_2' => $this->getName(), 206 ])->fetchOneRow(); 207 208 if ($swap_block !== null) { 209 Database::prepare( 210 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 211 )->execute([ 212 'block_order' => $swap_block->block_order, 213 'block_id' => $block_id, 214 ]); 215 Database::prepare( 216 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 217 )->execute([ 218 'block_order' => $block_order, 219 'block_id' => $swap_block->block_id, 220 ]); 221 } 222 223 $url = route('module', [ 224 'module' => 'faq', 225 'action' => 'Admin', 226 'ged' => $tree->getName(), 227 ]); 228 229 return new RedirectResponse($url); 230 } 231 232 /** 233 * @param Request $request 234 * @param Tree $tree 235 * 236 * @return RedirectResponse 237 */ 238 public function postAdminMoveUpAction(Request $request, Tree $tree): RedirectResponse 239 { 240 $block_id = (int)$request->get('block_id'); 241 242 $block_order = Database::prepare( 243 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 244 )->execute([ 245 'block_id' => $block_id, 246 ])->fetchOne(); 247 248 $swap_block = Database::prepare( 249 "SELECT block_order, block_id" . 250 " FROM `##block`" . 251 " WHERE block_order = (" . 252 " SELECT MAX(block_order) FROM `##block` WHERE block_order < :block_order AND module_name = :module_name_1" . 253 " ) AND module_name = :module_name_2" . 254 " LIMIT 1" 255 )->execute([ 256 'block_order' => $block_order, 257 'module_name_1' => $this->getName(), 258 'module_name_2' => $this->getName(), 259 ])->fetchOneRow(); 260 261 if ($swap_block !== null) { 262 Database::prepare( 263 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 264 )->execute([ 265 'block_order' => $swap_block->block_order, 266 'block_id' => $block_id, 267 ]); 268 Database::prepare( 269 "UPDATE `##block` SET block_order = :block_order WHERE block_id = :block_id" 270 )->execute([ 271 'block_order' => $block_order, 272 'block_id' => $swap_block->block_id, 273 ]); 274 } 275 276 $url = route('module', [ 277 'module' => 'faq', 278 'action' => 'Admin', 279 'ged' => $tree->getName(), 280 ]); 281 282 return new RedirectResponse($url); 283 } 284 285 /** 286 * @param Request $request 287 * @param Tree $tree 288 * 289 * @return Response 290 */ 291 public function getAdminEditAction(Request $request, Tree $tree): Response 292 { 293 $this->layout = 'layouts/administration'; 294 295 $block_id = (int)$request->get('block_id'); 296 297 if ($block_id === 0) { 298 // Creating a new faq 299 $header = ''; 300 $faqbody = ''; 301 $block_order = Database::prepare( 302 "SELECT IFNULL(MAX(block_order)+1, 0) FROM `##block` WHERE module_name = :module_name" 303 )->execute([ 304 'module_name' => $this->getName(), 305 ])->fetchOne(); 306 $languages = []; 307 308 $title = I18N::translate('Add an FAQ'); 309 } else { 310 // Editing an existing faq 311 $header = $this->getBlockSetting($block_id, 'header'); 312 $faqbody = $this->getBlockSetting($block_id, 'faqbody'); 313 $block_order = Database::prepare( 314 "SELECT block_order FROM `##block` WHERE block_id = :block_id" 315 )->execute(['block_id' => $block_id])->fetchOne(); 316 $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 317 318 $title = I18N::translate('Edit the FAQ'); 319 } 320 321 // @TODO enable CKEDITOR 322 323 return $this->viewResponse('modules/faq/edit', [ 324 'block_id' => $block_id, 325 'block_order' => $block_order, 326 'header' => $header, 327 'faqbody' => $faqbody, 328 'languages' => $languages, 329 'title' => $title, 330 'tree' => $tree, 331 'tree_names' => Tree::getNameList(), 332 ]); 333 } 334 335 /** 336 * @param Request $request 337 * @param Tree $tree 338 * 339 * @return RedirectResponse 340 */ 341 public function postAdminEditAction(Request $request, Tree $tree): RedirectResponse 342 { 343 $block_id = (int)$request->get('block_id'); 344 $faqbody = $request->get('faqbody', ''); 345 $header = $request->get('header', ''); 346 $languages = $request->get('languages', []); 347 348 if ($block_id !== 0) { 349 Database::prepare( 350 "UPDATE `##block` SET gedcom_id = NULLIF(:tree_id, '0'), block_order = :block_order WHERE block_id = :block_id" 351 )->execute([ 352 'tree_id' => Filter::postInteger('gedcom_id'), 353 'block_order' => Filter::postInteger('block_order'), 354 'block_id' => $block_id, 355 ]); 356 } else { 357 Database::prepare( 358 "INSERT INTO `##block` (gedcom_id, module_name, block_order) VALUES (NULLIF(:tree_id, '0'), :module_name, :block_order)" 359 )->execute([ 360 'tree_id' => Filter::postInteger('gedcom_id'), 361 'module_name' => $this->getName(), 362 'block_order' => Filter::postInteger('block_order'), 363 ]); 364 365 $block_id = Database::getInstance()->lastInsertId(); 366 } 367 368 $this->setBlockSetting($block_id, 'faqbody', $faqbody); 369 $this->setBlockSetting($block_id, 'header', $header); 370 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 371 372 $url = route('module', [ 373 'module' => 'faq', 374 'action' => 'Admin', 375 'ged' => $tree->getName(), 376 ]); 377 378 return new RedirectResponse($url); 379 } 380 381 /** 382 * @param Request $request 383 * @param Tree $tree 384 * 385 * @return Response 386 */ 387 public function getShowAction(Request $request, Tree $tree): Response 388 { 389 $faqs = Database::prepare( 390 "SELECT block_id, bs1.setting_value AS header, bs2.setting_value AS body, bs3.setting_value AS languages" . 391 " FROM `##block` b" . 392 " JOIN `##block_setting` bs1 USING (block_id)" . 393 " JOIN `##block_setting` bs2 USING (block_id)" . 394 " JOIN `##block_setting` bs3 USING (block_id)" . 395 " WHERE module_name = :module_name" . 396 " AND bs1.setting_name = 'header'" . 397 " AND bs2.setting_name = 'faqbody'" . 398 " AND bs3.setting_name = 'languages'" . 399 " AND IFNULL(gedcom_id, :tree_id_1) = :tree_id_2" . 400 " ORDER BY block_order" 401 )->execute([ 402 'module_name' => $this->getName(), 403 'tree_id_1' => $tree->getTreeId(), 404 'tree_id_2' => $tree->getTreeId(), 405 ])->fetchAll(); 406 407 // Filter foreign languages. 408 $faqs = array_filter($faqs, function (stdClass $faq): bool { 409 return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages)); 410 }); 411 412 return $this->viewResponse('modules/faq/show', [ 413 'faqs' => $faqs, 414 'title' => I18N::translate('Frequently asked questions'), 415 'tree' => $tree, 416 ]); 417 } 418} 419