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