. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Database; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Tree; use stdClass; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** * Class StoriesModule */ class StoriesModule extends AbstractModule implements ModuleTabInterface, ModuleConfigInterface, ModuleMenuInterface { /** {@inheritdoc} */ public function getTitle(): string { /* I18N: Name of a module */ return I18N::translate('Stories'); } /** {@inheritdoc} */ public function getDescription(): string { /* I18N: Description of the “Stories” module */ return I18N::translate('Add narrative stories to individuals in the family tree.'); } /** * The URL to a page where the user can modify the configuration of this module. * * @return string */ public function getConfigLink(): string { return route('module', [ 'module' => $this->getName(), 'action' => 'Admin', ]); } /** {@inheritdoc} */ public function defaultTabOrder(): int { return 55; } /** {@inheritdoc} */ public function getTabContent(Individual $individual): string { return view('modules/stories/tab', [ 'is_admin' => Auth::isAdmin(), 'individual' => $individual, 'stories' => $this->getStoriesForIndividual($individual), ]); } /** {@inheritdoc} */ public function hasTabContent(Individual $individual): bool { return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual)); } /** {@inheritdoc} */ public function isGrayedOut(Individual $individual): bool { return !empty($this->getStoriesForIndividual($individual)); } /** {@inheritdoc} */ public function canLoadAjax(): bool { return false; } /** * @param Individual $individual * * @return stdClass[] */ private function getStoriesForIndividual(Individual $individual): array { $block_ids = Database::prepare( "SELECT block_id" . " FROM `##block`" . " WHERE module_name = :module_name" . " AND xref = :xref" . " AND gedcom_id = :tree_id" )->execute([ 'module_name' => $this->getName(), 'xref' => $individual->xref(), 'tree_id' => $individual->tree()->id(), ])->fetchOneColumn(); $stories = []; foreach ($block_ids as $block_id) { $block_id = (int) $block_id; // Only show this block for certain languages $languages = $this->getBlockSetting($block_id, 'languages', ''); if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) { $stories[] = (object) [ 'block_id' => $block_id, 'title' => $this->getBlockSetting($block_id, 'title'), 'story_body' => $this->getBlockSetting($block_id, 'story_body'), ]; } } return $stories; } /** * The user can re-order menus. Until they do, they are shown in this order. * * @return int */ public function defaultMenuOrder(): int { return 30; } /** * What is the default access level for this module? * * Some modules are aimed at admins or managers, and are not generally shown to users. * * @return int */ public function defaultAccessLevel(): int { return Auth::PRIV_HIDE; } /** * A menu, to be added to the main application menu. * * @param Tree $tree * * @return Menu|null */ public function getMenu(Tree $tree) { $menu = new Menu($this->getTitle(), route('module', [ 'module' => $this->getName(), 'action' => 'ShowList', 'ged' => $tree->name(), ]), 'menu-story'); return $menu; } /** * @param Tree $tree * * @return Response */ public function getAdminAction(Tree $tree): Response { $this->layout = 'layouts/administration'; $stories = Database::prepare( "SELECT block_id, xref, gedcom_id" . " FROM `##block` b" . " WHERE module_name = :module_name" . " AND gedcom_id = :tree_id" . " ORDER BY gedcom_id, xref" )->execute([ 'tree_id' => $tree->id(), 'module_name' => $this->getName(), ])->fetchAll(); foreach ($stories as $story) { $block_id = (int) $story->block_id; $story->individual = Individual::getInstance($story->xref, $tree); $story->title = $this->getBlockSetting($block_id, 'title'); $story->languages = $this->getBlockSetting($block_id, 'languages'); } return $this->viewResponse('modules/stories/config', [ 'stories' => $stories, 'title' => $this->getTitle() . ' — ' . $tree->title(), 'tree' => $tree, 'tree_names' => Tree::getNameList(), ]); } /** * @param Request $request * @param Tree $tree * * @return Response */ public function getAdminEditAction(Request $request, Tree $tree): Response { $this->layout = 'layouts/administration'; $block_id = (int) $request->get('block_id'); if ($block_id === 0) { // Creating a new story $individual = Individual::getInstance($request->get('xref', ''), $tree); $story_title = ''; $story_body = ''; $languages = []; $title = I18N::translate('Add a story') . ' — ' . e($tree->title()); } else { // Editing an existing story $xref = (string) Database::prepare( "SELECT xref FROM `##block` WHERE block_id = :block_id" )->execute([ 'block_id' => $block_id, ])->fetchOne(); $individual = Individual::getInstance($xref, $tree); $story_title = $this->getBlockSetting($block_id, 'title', ''); $story_body = $this->getBlockSetting($block_id, 'story_body', ''); $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); $title = I18N::translate('Edit the story') . ' — ' . e($tree->title()); } return $this->viewResponse('modules/stories/edit', [ 'block_id' => $block_id, 'languages' => $languages, 'story_body' => $story_body, 'story_title' => $story_title, 'title' => $title, 'tree' => $tree, 'individual' => $individual, ]); } /** * @param Request $request * @param Tree $tree * * @return RedirectResponse */ public function postAdminEditAction(Request $request, Tree $tree): RedirectResponse { $block_id = (int) $request->get('block_id'); $xref = $request->get('xref', ''); $story_body = $request->get('story_body', ''); $story_title = $request->get('story_title', ''); $languages = $request->get('languages', []); if ($block_id !== 0) { Database::prepare( "UPDATE `##block` SET gedcom_id = :tree_id, xref = :xref WHERE block_id = :block_id" )->execute([ 'tree_id' => $tree->id(), 'xref' => $xref, 'block_id' => $block_id, ]); } else { Database::prepare( "INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (:tree_id, :xref, 'stories', 0)" )->execute([ 'tree_id' => $tree->id(), 'xref' => $xref, ]); $block_id = Database::lastInsertId(); } $this->setBlockSetting($block_id, 'story_body', $story_body); $this->setBlockSetting($block_id, 'title', $story_title); $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); $url = route('module', [ 'module' => 'stories', 'action' => 'Admin', 'ged' => $tree->name(), ]); return new RedirectResponse($url); } /** * @param Request $request * @param Tree $tree * * @return Response */ public function postAdminDeleteAction(Request $request, Tree $tree): Response { $block_id = (int) $request->get('block_id'); Database::prepare( "DELETE FROM `##block_setting` WHERE block_id = :block_id" )->execute([ 'block_id' => $block_id, ]); Database::prepare( "DELETE FROM `##block` WHERE block_id = :block_id" )->execute([ 'block_id' => $block_id, ]); $url = route('module', [ 'module' => 'stories', 'action' => 'Admin', 'ged' => $tree->name(), ]); return new RedirectResponse($url); } /** * @param Tree $tree * * @return Response */ public function getShowListAction(Tree $tree): Response { $stories = Database::prepare( "SELECT block_id, xref" . " FROM `##block` b" . " WHERE module_name = :module_name" . " AND gedcom_id = :tree_id" . " ORDER BY xref" )->execute([ 'module_name' => $this->getName(), 'tree_id' => $tree->id(), ])->fetchAll(); foreach ($stories as $story) { $block_id = (int) $story->block_id; $story->individual = Individual::getInstance($story->xref, $tree); $story->title = $this->getBlockSetting($block_id, 'title'); $story->languages = $this->getBlockSetting($block_id, 'languages'); } // Filter non-existant and private individuals. $stories = array_filter($stories, function (stdClass $story): bool { return $story->individual !== null && $story->individual->canShow(); }); // Filter foreign languages. $stories = array_filter($stories, function (stdClass $story): bool { return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages)); }); return $this->viewResponse('modules/stories/list', [ 'stories' => $stories, 'title' => $this->getTitle(), ]); } }