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