1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17declare(strict_types=1); 18 19namespace Fisharebest\Webtrees\Module; 20 21use Fisharebest\Webtrees\I18N; 22use Fisharebest\Webtrees\Menu; 23use Fisharebest\Webtrees\Services\HtmlService; 24use Fisharebest\Webtrees\Tree; 25use Illuminate\Database\Capsule\Manager as DB; 26use Illuminate\Database\Query\Builder; 27use Illuminate\Support\Collection; 28use Psr\Http\Message\ResponseInterface; 29use Psr\Http\Message\ServerRequestInterface; 30use stdClass; 31 32/** 33 * Class FrequentlyAskedQuestionsModule 34 */ 35class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface 36{ 37 use ModuleConfigTrait; 38 use ModuleMenuTrait; 39 40 /** @var HtmlService */ 41 private $html_service; 42 43 /** 44 * HtmlBlockModule bootstrap. 45 * 46 * @param HtmlService $html_service 47 */ 48 public function boot(HtmlService $html_service) 49 { 50 $this->html_service = $html_service; 51 } 52 53 /** 54 * How should this module be identified in the control panel, etc.? 55 * 56 * @return string 57 */ 58 public function title(): string 59 { 60 /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */ 61 return I18N::translate('FAQ'); 62 } 63 64 /** 65 * A sentence describing what this module does. 66 * 67 * @return string 68 */ 69 public function description(): string 70 { 71 /* I18N: Description of the “FAQ” module */ 72 return I18N::translate('A list of frequently asked questions and answers.'); 73 } 74 75 /** 76 * The default position for this menu. It can be changed in the control panel. 77 * 78 * @return int 79 */ 80 public function defaultMenuOrder(): int 81 { 82 return 8; 83 } 84 85 /** 86 * A menu, to be added to the main application menu. 87 * 88 * @param Tree $tree 89 * 90 * @return Menu|null 91 */ 92 public function getMenu(Tree $tree): ?Menu 93 { 94 if ($this->faqsExist($tree, WT_LOCALE)) { 95 return new Menu($this->title(), route('module', [ 96 'module' => $this->name(), 97 'action' => 'Show', 98 'ged' => $tree->name(), 99 ]), 'menu-help'); 100 } 101 102 return null; 103 } 104 105 /** 106 * @param ServerRequestInterface $request 107 * 108 * @return ResponseInterface 109 */ 110 public function getAdminAction(ServerRequestInterface $request): ResponseInterface 111 { 112 $this->layout = 'layouts/administration'; 113 114 $tree = $request->getAttribute('tree'); 115 $faqs = $this->faqsForTree($tree); 116 117 $min_block_order = DB::table('block') 118 ->where('module_name', '=', $this->name()) 119 ->where(static function (Builder $query) use ($tree): void { 120 $query 121 ->whereNull('gedcom_id') 122 ->orWhere('gedcom_id', '=', $tree->id()); 123 }) 124 ->min('block_order'); 125 126 $max_block_order = DB::table('block') 127 ->where('module_name', '=', $this->name()) 128 ->where(static function (Builder $query) use ($tree): void { 129 $query 130 ->whereNull('gedcom_id') 131 ->orWhere('gedcom_id', '=', $tree->id()); 132 }) 133 ->max('block_order'); 134 135 $title = I18N::translate('Frequently asked questions') . ' — ' . $tree->title(); 136 137 return $this->viewResponse('modules/faq/config', [ 138 'action' => route('module', ['module' => $this->name(), 'action' => 'Admin']), 139 'faqs' => $faqs, 140 'max_block_order' => $max_block_order, 141 'min_block_order' => $min_block_order, 142 'title' => $title, 143 'tree' => $tree, 144 'tree_names' => Tree::getNameList(), 145 ]); 146 } 147 148 /** 149 * @param ServerRequestInterface $request 150 * 151 * @return ResponseInterface 152 */ 153 public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface 154 { 155 $tree = $request->getAttribute('tree'); 156 $block_id = (int) $request->getQueryParams()['block_id']; 157 158 DB::table('block_setting')->where('block_id', '=', $block_id)->delete(); 159 160 DB::table('block')->where('block_id', '=', $block_id)->delete(); 161 162 $url = route('module', [ 163 'module' => $this->name(), 164 'action' => 'Admin', 165 'ged' => $tree->name(), 166 ]); 167 168 return redirect($url); 169 } 170 171 /** 172 * @param ServerRequestInterface $request 173 * 174 * @return ResponseInterface 175 */ 176 public function postAdminMoveDownAction(ServerRequestInterface $request): ResponseInterface 177 { 178 $tree = $request->getAttribute('tree'); 179 $block_id = (int) $request->getQueryParams()['block_id']; 180 181 $block_order = DB::table('block') 182 ->where('block_id', '=', $block_id) 183 ->value('block_order'); 184 185 $swap_block = DB::table('block') 186 ->where('module_name', '=', $this->name()) 187 ->where('block_order', '=', function (Builder $query) use ($block_order): void { 188 $query 189 ->from('block') 190 ->where('module_name', '=', $this->name()) 191 ->where('block_order', '>', $block_order) 192 ->min('block_order'); 193 }) 194 ->select(['block_order', 'block_id']) 195 ->first(); 196 197 if ($swap_block !== null) { 198 DB::table('block') 199 ->where('block_id', '=', $block_id) 200 ->update([ 201 'block_order' => $swap_block->block_order, 202 ]); 203 204 DB::table('block') 205 ->where('block_id', '=', $swap_block->block_id) 206 ->update([ 207 'block_order' => $block_order, 208 ]); 209 } 210 211 $url = route('module', [ 212 'module' => $this->name(), 213 'action' => 'Admin', 214 'ged' => $tree->name(), 215 ]); 216 217 return redirect($url); 218 } 219 220 /** 221 * @param ServerRequestInterface $request 222 * 223 * @return ResponseInterface 224 */ 225 public function postAdminMoveUpAction(ServerRequestInterface $request): ResponseInterface 226 { 227 $tree = $request->getAttribute('tree'); 228 $block_id = (int) $request->getQueryParams()['block_id']; 229 230 $block_order = DB::table('block') 231 ->where('block_id', '=', $block_id) 232 ->value('block_order'); 233 234 $swap_block = DB::table('block') 235 ->where('module_name', '=', $this->name()) 236 ->where('block_order', '=', function (Builder $query) use ($block_order): void { 237 $query 238 ->from('block') 239 ->where('module_name', '=', $this->name()) 240 ->where('block_order', '<', $block_order) 241 ->max('block_order'); 242 }) 243 ->select(['block_order', 'block_id']) 244 ->first(); 245 246 if ($swap_block !== null) { 247 DB::table('block') 248 ->where('block_id', '=', $block_id) 249 ->update([ 250 'block_order' => $swap_block->block_order, 251 ]); 252 253 DB::table('block') 254 ->where('block_id', '=', $swap_block->block_id) 255 ->update([ 256 'block_order' => $block_order, 257 ]); 258 } 259 260 $url = route('module', [ 261 'module' => $this->name(), 262 'action' => 'Admin', 263 'ged' => $tree->name(), 264 ]); 265 266 return redirect($url); 267 } 268 269 /** 270 * @param ServerRequestInterface $request 271 * 272 * @return ResponseInterface 273 */ 274 public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface 275 { 276 $this->layout = 'layouts/administration'; 277 278 $tree = $request->getAttribute('tree'); 279 $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 280 281 if ($block_id === 0) { 282 // Creating a new faq 283 $header = ''; 284 $faqbody = ''; 285 286 $block_order = 1 + (int) DB::table('block') 287 ->where('module_name', '=', $this->name()) 288 ->max('block_order'); 289 290 $languages = []; 291 292 $title = I18N::translate('Add an FAQ'); 293 } else { 294 // Editing an existing faq 295 $header = $this->getBlockSetting($block_id, 'header'); 296 $faqbody = $this->getBlockSetting($block_id, 'faqbody'); 297 298 $block_order = DB::table('block') 299 ->where('block_id', '=', $block_id) 300 ->value('block_order'); 301 302 $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 303 304 $title = I18N::translate('Edit the FAQ'); 305 } 306 307 $tree_names = ['' => I18N::translate('All')] + Tree::getIdList(); 308 309 return $this->viewResponse('modules/faq/edit', [ 310 'block_id' => $block_id, 311 'block_order' => $block_order, 312 'header' => $header, 313 'faqbody' => $faqbody, 314 'languages' => $languages, 315 'title' => $title, 316 'tree' => $tree, 317 'tree_names' => $tree_names, 318 ]); 319 } 320 321 /** 322 * @param ServerRequestInterface $request 323 * 324 * @return ResponseInterface 325 */ 326 public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface 327 { 328 $tree = $request->getAttribute('tree'); 329 $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 330 331 $params = $request->getParsedBody(); 332 333 $faqbody = $params['faqbody']; 334 $header = $params['header']; 335 $languages = $params['languages'] ?? []; 336 $gedcom_id = (int) $params['gedcom_id'] ?: null; 337 $block_order = (int) $params['block_order']; 338 339 $faqbody = $this->html_service->sanitize($faqbody); 340 $header = $this->html_service->sanitize($header); 341 342 if ($block_id !== 0) { 343 DB::table('block') 344 ->where('block_id', '=', $block_id) 345 ->update([ 346 'gedcom_id' => $gedcom_id, 347 'block_order' => $block_order, 348 ]); 349 } else { 350 DB::table('block')->insert([ 351 'gedcom_id' => $gedcom_id, 352 'module_name' => $this->name(), 353 'block_order' => $block_order, 354 ]); 355 356 $block_id = (int) DB::connection()->getPdo()->lastInsertId(); 357 } 358 359 $this->setBlockSetting($block_id, 'faqbody', $faqbody); 360 $this->setBlockSetting($block_id, 'header', $header); 361 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 362 363 $url = route('module', [ 364 'module' => $this->name(), 365 'action' => 'Admin', 366 'ged' => $tree->name(), 367 ]); 368 369 return redirect($url); 370 } 371 372 /** 373 * @param ServerRequestInterface $request 374 * 375 * @return ResponseInterface 376 */ 377 public function getShowAction(ServerRequestInterface $request): ResponseInterface 378 { 379 $tree = $request->getAttribute('tree'); 380 381 // Filter foreign languages. 382 $faqs = $this->faqsForTree($tree) 383 ->filter(static function (stdClass $faq): bool { 384 return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages), true); 385 }); 386 387 return $this->viewResponse('modules/faq/show', [ 388 'faqs' => $faqs, 389 'title' => I18N::translate('Frequently asked questions'), 390 'tree' => $tree, 391 ]); 392 } 393 394 /** 395 * @param Tree $tree 396 * 397 * @return Collection 398 */ 399 private function faqsForTree(Tree $tree): Collection 400 { 401 return DB::table('block') 402 ->join('block_setting AS bs1', 'bs1.block_id', '=', 'block.block_id') 403 ->join('block_setting AS bs2', 'bs2.block_id', '=', 'block.block_id') 404 ->join('block_setting AS bs3', 'bs3.block_id', '=', 'block.block_id') 405 ->where('module_name', '=', $this->name()) 406 ->where('bs1.setting_name', '=', 'header') 407 ->where('bs2.setting_name', '=', 'faqbody') 408 ->where('bs3.setting_name', '=', 'languages') 409 ->where(static function (Builder $query) use ($tree): void { 410 $query 411 ->whereNull('gedcom_id') 412 ->orWhere('gedcom_id', '=', $tree->id()); 413 }) 414 ->orderBy('block_order') 415 ->select(['block.block_id', 'block_order', 'gedcom_id', 'bs1.setting_value AS header', 'bs2.setting_value AS faqbody', 'bs3.setting_value AS languages']) 416 ->get(); 417 } 418 419 /** 420 * @param Tree $tree 421 * @param string $language 422 * 423 * @return bool 424 */ 425 private function faqsExist(Tree $tree, string $language): bool 426 { 427 return DB::table('block') 428 ->join('block_setting', 'block_setting.block_id', '=', 'block.block_id') 429 ->where('module_name', '=', $this->name()) 430 ->where('setting_name', '=', 'languages') 431 ->where(static function (Builder $query) use ($tree): void { 432 $query 433 ->whereNull('gedcom_id') 434 ->orWhere('gedcom_id', '=', $tree->id()); 435 }) 436 ->select(['setting_value AS languages']) 437 ->get() 438 ->filter(static function (stdClass $faq) use ($language): bool { 439 return $faq->languages === '' || in_array($language, explode(',', $faq->languages), true); 440 }) 441 ->isNotEmpty(); 442 } 443} 444