. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Services\HtmlService; use Fisharebest\Webtrees\Services\TreeService; use Fisharebest\Webtrees\Tree; use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Query\Builder; use Illuminate\Support\Collection; use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use stdClass; use function assert; use function redirect; use function route; /** * Class FrequentlyAskedQuestionsModule */ class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface { use ModuleConfigTrait; use ModuleMenuTrait; /** @var HtmlService */ private $html_service; /** @var TreeService */ private $tree_service; /** * BatchUpdateModule constructor. * * @param HtmlService $html_service * @param TreeService $tree_service */ public function __construct(HtmlService $html_service, TreeService $tree_service) { $this->html_service = $html_service; $this->tree_service = $tree_service; } /** * How should this module be identified in the control panel, etc.? * * @return string */ public function title(): string { /* I18N: Name of a module. Abbreviation for “Frequently Asked Questions” */ return I18N::translate('FAQ'); } /** * A sentence describing what this module does. * * @return string */ public function description(): string { /* I18N: Description of the “FAQ” module */ return I18N::translate('A list of frequently asked questions and answers.'); } /** * The default position for this menu. It can be changed in the control panel. * * @return int */ public function defaultMenuOrder(): int { return 8; } /** * A menu, to be added to the main application menu. * * @param Tree $tree * * @return Menu|null */ public function getMenu(Tree $tree): ?Menu { if ($this->faqsExist($tree, WT_LOCALE)) { return new Menu($this->title(), route('module', [ 'module' => $this->name(), 'action' => 'Show', 'tree' => $tree->name(), ]), 'menu-help'); } return null; } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function getAdminAction(ServerRequestInterface $request): ResponseInterface { $this->layout = 'layouts/administration'; // This module can't run without a tree $tree = $request->getQueryParams()['tree'] ?? ''; $tree = $this->tree_service->findByName($tree) ?? $this->tree_service->all()->first(); if (!$tree instanceof Tree) { return redirect(route(ControlPanel::class)); } $faqs = $this->faqsForTree($tree); $min_block_order = DB::table('block') ->where('module_name', '=', $this->name()) ->where(static function (Builder $query) use ($tree): void { $query ->whereNull('gedcom_id') ->orWhere('gedcom_id', '=', $tree->id()); }) ->min('block_order'); $max_block_order = DB::table('block') ->where('module_name', '=', $this->name()) ->where(static function (Builder $query) use ($tree): void { $query ->whereNull('gedcom_id') ->orWhere('gedcom_id', '=', $tree->id()); }) ->max('block_order'); $title = I18N::translate('Frequently asked questions') . ' — ' . $tree->title(); return $this->viewResponse('modules/faq/config', [ 'action' => route('module', ['module' => $this->name(), 'action' => 'Admin']), 'faqs' => $faqs, 'max_block_order' => $max_block_order, 'min_block_order' => $min_block_order, 'module' => $this->name(), 'title' => $title, 'tree' => $tree, 'tree_names' => Tree::getNameList(), ]); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface { $block_id = (int) $request->getQueryParams()['block_id']; DB::table('block_setting')->where('block_id', '=', $block_id)->delete(); DB::table('block')->where('block_id', '=', $block_id)->delete(); $url = route('module', [ 'module' => $this->name(), 'action' => 'Admin', ]); return redirect($url); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function postAdminMoveDownAction(ServerRequestInterface $request): ResponseInterface { $block_id = (int) $request->getQueryParams()['block_id']; $block_order = DB::table('block') ->where('block_id', '=', $block_id) ->value('block_order'); $swap_block = DB::table('block') ->where('module_name', '=', $this->name()) ->where('block_order', '>', $block_order) ->orderBy('block_order', 'asc') ->first(); if ($block_order !== null && $swap_block !== null) { DB::table('block') ->where('block_id', '=', $block_id) ->update([ 'block_order' => $swap_block->block_order, ]); DB::table('block') ->where('block_id', '=', $swap_block->block_id) ->update([ 'block_order' => $block_order, ]); } return response(); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function postAdminMoveUpAction(ServerRequestInterface $request): ResponseInterface { $block_id = (int) $request->getQueryParams()['block_id']; $block_order = DB::table('block') ->where('block_id', '=', $block_id) ->value('block_order'); $swap_block = DB::table('block') ->where('module_name', '=', $this->name()) ->where('block_order', '<', $block_order) ->orderBy('block_order', 'desc') ->first(); if ($block_order !== null && $swap_block !== null) { DB::table('block') ->where('block_id', '=', $block_id) ->update([ 'block_order' => $swap_block->block_order, ]); DB::table('block') ->where('block_id', '=', $swap_block->block_id) ->update([ 'block_order' => $block_order, ]); } return response(); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface { $this->layout = 'layouts/administration'; $tree = $request->getAttribute('tree'); $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); if ($block_id === 0) { // Creating a new faq $header = ''; $faqbody = ''; $block_order = 1 + (int) DB::table('block') ->where('module_name', '=', $this->name()) ->max('block_order'); $languages = []; $title = I18N::translate('Add an FAQ'); } else { // Editing an existing faq $header = $this->getBlockSetting($block_id, 'header'); $faqbody = $this->getBlockSetting($block_id, 'faqbody'); $block_order = DB::table('block') ->where('block_id', '=', $block_id) ->value('block_order'); $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); $title = I18N::translate('Edit the FAQ'); } $tree_names = ['' => I18N::translate('All')] + Tree::getIdList(); return $this->viewResponse('modules/faq/edit', [ 'block_id' => $block_id, 'block_order' => $block_order, 'header' => $header, 'faqbody' => $faqbody, 'languages' => $languages, 'title' => $title, 'tree' => $tree, 'tree_names' => $tree_names, ]); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface { $tree = $request->getAttribute('tree'); $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); $params = $request->getParsedBody(); $faqbody = $params['faqbody']; $header = $params['header']; $languages = $params['languages'] ?? []; $gedcom_id = (int) $params['gedcom_id'] ?: null; $block_order = (int) $params['block_order']; $faqbody = $this->html_service->sanitize($faqbody); $header = $this->html_service->sanitize($header); if ($block_id !== 0) { DB::table('block') ->where('block_id', '=', $block_id) ->update([ 'gedcom_id' => $gedcom_id, 'block_order' => $block_order, ]); } else { DB::table('block')->insert([ 'gedcom_id' => $gedcom_id, 'module_name' => $this->name(), 'block_order' => $block_order, ]); $block_id = (int) DB::connection()->getPdo()->lastInsertId(); } $this->setBlockSetting($block_id, 'faqbody', $faqbody); $this->setBlockSetting($block_id, 'header', $header); $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); $url = route('module', [ 'module' => $this->name(), 'action' => 'Admin', 'tree' => $tree->name(), ]); return redirect($url); } /** * @param ServerRequestInterface $request * * @return ResponseInterface */ public function getShowAction(ServerRequestInterface $request): ResponseInterface { $tree = $request->getAttribute('tree'); assert($tree instanceof Tree, new InvalidArgumentException()); // Filter foreign languages. $faqs = $this->faqsForTree($tree) ->filter(static function (stdClass $faq): bool { return $faq->languages === '' || in_array(WT_LOCALE, explode(',', $faq->languages), true); }); return $this->viewResponse('modules/faq/show', [ 'faqs' => $faqs, 'title' => I18N::translate('Frequently asked questions'), 'tree' => $tree, ]); } /** * @param Tree $tree * * @return Collection */ private function faqsForTree(Tree $tree): Collection { return DB::table('block') ->join('block_setting AS bs1', 'bs1.block_id', '=', 'block.block_id') ->join('block_setting AS bs2', 'bs2.block_id', '=', 'block.block_id') ->join('block_setting AS bs3', 'bs3.block_id', '=', 'block.block_id') ->where('module_name', '=', $this->name()) ->where('bs1.setting_name', '=', 'header') ->where('bs2.setting_name', '=', 'faqbody') ->where('bs3.setting_name', '=', 'languages') ->where(static function (Builder $query) use ($tree): void { $query ->whereNull('gedcom_id') ->orWhere('gedcom_id', '=', $tree->id()); }) ->orderBy('block_order') ->select(['block.block_id', 'block_order', 'gedcom_id', 'bs1.setting_value AS header', 'bs2.setting_value AS faqbody', 'bs3.setting_value AS languages']) ->get(); } /** * @param Tree $tree * @param string $language * * @return bool */ private function faqsExist(Tree $tree, string $language): bool { return DB::table('block') ->join('block_setting', 'block_setting.block_id', '=', 'block.block_id') ->where('module_name', '=', $this->name()) ->where('setting_name', '=', 'languages') ->where(static function (Builder $query) use ($tree): void { $query ->whereNull('gedcom_id') ->orWhere('gedcom_id', '=', $tree->id()); }) ->select(['setting_value AS languages']) ->get() ->filter(static function (stdClass $faq) use ($language): bool { return $faq->languages === '' || in_array($language, explode(',', $faq->languages), true); }) ->isNotEmpty(); } }