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