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