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