18c2e8227SGreg Roach<?php 23976b470SGreg Roach 38c2e8227SGreg Roach/** 48c2e8227SGreg Roach * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 68c2e8227SGreg Roach * This program is free software: you can redistribute it and/or modify 78c2e8227SGreg Roach * it under the terms of the GNU General Public License as published by 88c2e8227SGreg Roach * the Free Software Foundation, either version 3 of the License, or 98c2e8227SGreg Roach * (at your option) any later version. 108c2e8227SGreg Roach * This program is distributed in the hope that it will be useful, 118c2e8227SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 128c2e8227SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138c2e8227SGreg Roach * GNU General Public License for more details. 148c2e8227SGreg Roach * You should have received a copy of the GNU General Public License 158c2e8227SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 168c2e8227SGreg Roach */ 17e7f56f2aSGreg Roachdeclare(strict_types=1); 18e7f56f2aSGreg Roach 1976692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module; 2076692c8bSGreg Roach 210e62c4b8SGreg Roachuse Fisharebest\Webtrees\Auth; 220e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N; 230e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual; 240e62c4b8SGreg Roachuse Fisharebest\Webtrees\Menu; 2550d6f48cSGreg Roachuse Fisharebest\Webtrees\Services\HtmlService; 260e62c4b8SGreg Roachuse Fisharebest\Webtrees\Tree; 274b92b602SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 285229eadeSGreg Roachuse InvalidArgumentException; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 31225e381fSGreg Roachuse stdClass; 32*83d28054SGreg Roach 335229eadeSGreg Roachuse function assert; 348c2e8227SGreg Roach 358c2e8227SGreg Roach/** 368c2e8227SGreg Roach * Class StoriesModule 378c2e8227SGreg Roach */ 3837eb8894SGreg Roachclass StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface 39c1010edaSGreg Roach{ 4049a243cbSGreg Roach use ModuleTabTrait; 4149a243cbSGreg Roach use ModuleConfigTrait; 4249a243cbSGreg Roach use ModuleMenuTrait; 4349a243cbSGreg Roach 4450d6f48cSGreg Roach /** @var HtmlService */ 4550d6f48cSGreg Roach private $html_service; 4650d6f48cSGreg Roach 4750d6f48cSGreg Roach /** 4850d6f48cSGreg Roach * HtmlBlockModule bootstrap. 4950d6f48cSGreg Roach * 5050d6f48cSGreg Roach * @param HtmlService $html_service 5150d6f48cSGreg Roach */ 5250d6f48cSGreg Roach public function boot(HtmlService $html_service) 5350d6f48cSGreg Roach { 5450d6f48cSGreg Roach $this->html_service = $html_service; 5550d6f48cSGreg Roach } 5650d6f48cSGreg Roach 5749a243cbSGreg Roach /** @var int The default access level for this module. It can be changed in the control panel. */ 5849a243cbSGreg Roach protected $access_level = Auth::PRIV_HIDE; 5949a243cbSGreg Roach 60961ec755SGreg Roach /** 61961ec755SGreg Roach * A sentence describing what this module does. 62961ec755SGreg Roach * 63961ec755SGreg Roach * @return string 64961ec755SGreg Roach */ 6549a243cbSGreg Roach public function description(): string 66c1010edaSGreg Roach { 67bbb76c12SGreg Roach /* I18N: Description of the “Stories” module */ 68bbb76c12SGreg Roach return I18N::translate('Add narrative stories to individuals in the family tree.'); 698c2e8227SGreg Roach } 708c2e8227SGreg Roach 71aee13b6dSGreg Roach /** 7249a243cbSGreg Roach * The default position for this menu. It can be changed in the control panel. 73aee13b6dSGreg Roach * 7449a243cbSGreg Roach * @return int 75aee13b6dSGreg Roach */ 7649a243cbSGreg Roach public function defaultMenuOrder(): int 77c1010edaSGreg Roach { 78353b36abSGreg Roach return 7; 798c2e8227SGreg Roach } 808c2e8227SGreg Roach 8149a243cbSGreg Roach /** 8249a243cbSGreg Roach * The default position for this tab. It can be changed in the control panel. 8349a243cbSGreg Roach * 8449a243cbSGreg Roach * @return int 8549a243cbSGreg Roach */ 86cbf4b7faSGreg Roach public function defaultTabOrder(): int 87cbf4b7faSGreg Roach { 88fb7a0427SGreg Roach return 9; 898c2e8227SGreg Roach } 908c2e8227SGreg Roach 913caaa4d2SGreg Roach /** 923caaa4d2SGreg Roach * Generate the HTML content of this tab. 933caaa4d2SGreg Roach * 943caaa4d2SGreg Roach * @param Individual $individual 953caaa4d2SGreg Roach * 963caaa4d2SGreg Roach * @return string 973caaa4d2SGreg Roach */ 989b34404bSGreg Roach public function getTabContent(Individual $individual): string 99c1010edaSGreg Roach { 10072ac996dSGreg Roach return view('modules/stories/tab', [ 10172ac996dSGreg Roach 'is_admin' => Auth::isAdmin(), 102225e381fSGreg Roach 'individual' => $individual, 103225e381fSGreg Roach 'stories' => $this->getStoriesForIndividual($individual), 104225e381fSGreg Roach ]); 1058c2e8227SGreg Roach } 1068c2e8227SGreg Roach 107225e381fSGreg Roach /** 108225e381fSGreg Roach * @param Individual $individual 109225e381fSGreg Roach * 110225e381fSGreg Roach * @return stdClass[] 111225e381fSGreg Roach */ 112c1010edaSGreg Roach private function getStoriesForIndividual(Individual $individual): array 113c1010edaSGreg Roach { 1144b92b602SGreg Roach $block_ids = DB::table('block') 11526684e68SGreg Roach ->where('module_name', '=', $this->name()) 1164b92b602SGreg Roach ->where('xref', '=', $individual->xref()) 1174b92b602SGreg Roach ->where('gedcom_id', '=', $individual->tree()->id()) 1184b92b602SGreg Roach ->pluck('block_id'); 119225e381fSGreg Roach 120225e381fSGreg Roach $stories = []; 121225e381fSGreg Roach foreach ($block_ids as $block_id) { 1227d988ec3SGreg Roach $block_id = (int) $block_id; 1237d988ec3SGreg Roach 124225e381fSGreg Roach // Only show this block for certain languages 12550d6f48cSGreg Roach $languages = $this->getBlockSetting($block_id, 'languages'); 12622d65e5aSGreg Roach if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages), true)) { 127225e381fSGreg Roach $stories[] = (object) [ 128225e381fSGreg Roach 'block_id' => $block_id, 129225e381fSGreg Roach 'title' => $this->getBlockSetting($block_id, 'title'), 13072ac996dSGreg Roach 'story_body' => $this->getBlockSetting($block_id, 'story_body'), 131225e381fSGreg Roach ]; 132225e381fSGreg Roach } 133225e381fSGreg Roach } 134225e381fSGreg Roach 135225e381fSGreg Roach return $stories; 1368c2e8227SGreg Roach } 1378c2e8227SGreg Roach 1383caaa4d2SGreg Roach /** 1393caaa4d2SGreg Roach * Is this tab empty? If so, we don't always need to display it. 1403caaa4d2SGreg Roach * 1413caaa4d2SGreg Roach * @param Individual $individual 1423caaa4d2SGreg Roach * 1433caaa4d2SGreg Roach * @return bool 1443caaa4d2SGreg Roach */ 1456ccdf4f0SGreg Roach public function hasTabContent(Individual $individual): bool 1466ccdf4f0SGreg Roach { 1476ccdf4f0SGreg Roach return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual)); 1486ccdf4f0SGreg Roach } 1496ccdf4f0SGreg Roach 1503caaa4d2SGreg Roach /** 1513caaa4d2SGreg Roach * A greyed out tab has no actual content, but may perhaps have 1523caaa4d2SGreg Roach * options to create content. 1533caaa4d2SGreg Roach * 1543caaa4d2SGreg Roach * @param Individual $individual 1553caaa4d2SGreg Roach * 1563caaa4d2SGreg Roach * @return bool 1573caaa4d2SGreg Roach */ 1586ccdf4f0SGreg Roach public function isGrayedOut(Individual $individual): bool 1596ccdf4f0SGreg Roach { 1606ccdf4f0SGreg Roach return !empty($this->getStoriesForIndividual($individual)); 1616ccdf4f0SGreg Roach } 1626ccdf4f0SGreg Roach 1633caaa4d2SGreg Roach /** 1643caaa4d2SGreg Roach * Can this tab load asynchronously? 1653caaa4d2SGreg Roach * 1663caaa4d2SGreg Roach * @return bool 1673caaa4d2SGreg Roach */ 1686ccdf4f0SGreg Roach public function canLoadAjax(): bool 1696ccdf4f0SGreg Roach { 1706ccdf4f0SGreg Roach return false; 1716ccdf4f0SGreg Roach } 1726ccdf4f0SGreg Roach 1738c2e8227SGreg Roach /** 1740ee13198SGreg Roach * A menu, to be added to the main application menu. 1750ee13198SGreg Roach * 176aee13b6dSGreg Roach * @param Tree $tree 177aee13b6dSGreg Roach * 1780ee13198SGreg Roach * @return Menu|null 1790ee13198SGreg Roach */ 18046295629SGreg Roach public function getMenu(Tree $tree): ?Menu 181c1010edaSGreg Roach { 18249a243cbSGreg Roach $menu = new Menu($this->title(), route('module', [ 18326684e68SGreg Roach 'module' => $this->name(), 184c1010edaSGreg Roach 'action' => 'ShowList', 1859022ab66SGreg Roach 'tree' => $tree->name(), 186c1010edaSGreg Roach ]), 'menu-story'); 1878c2e8227SGreg Roach 1888c2e8227SGreg Roach return $menu; 1898c2e8227SGreg Roach } 19072ac996dSGreg Roach 19172ac996dSGreg Roach /** 1926ccdf4f0SGreg Roach * How should this module be identified in the control panel, etc.? 1936ccdf4f0SGreg Roach * 1946ccdf4f0SGreg Roach * @return string 1956ccdf4f0SGreg Roach */ 1966ccdf4f0SGreg Roach public function title(): string 1976ccdf4f0SGreg Roach { 1986ccdf4f0SGreg Roach /* I18N: Name of a module */ 1996ccdf4f0SGreg Roach return I18N::translate('Stories'); 2006ccdf4f0SGreg Roach } 2016ccdf4f0SGreg Roach 2026ccdf4f0SGreg Roach /** 20357ab2231SGreg Roach * @param ServerRequestInterface $request 20472ac996dSGreg Roach * 2056ccdf4f0SGreg Roach * @return ResponseInterface 20672ac996dSGreg Roach */ 20757ab2231SGreg Roach public function getAdminAction(ServerRequestInterface $request): ResponseInterface 208c1010edaSGreg Roach { 20972ac996dSGreg Roach $this->layout = 'layouts/administration'; 21072ac996dSGreg Roach 21157ab2231SGreg Roach $tree = $request->getAttribute('tree'); 2125229eadeSGreg Roach assert($tree instanceof Tree, new InvalidArgumentException()); 21357ab2231SGreg Roach 2144b92b602SGreg Roach $stories = DB::table('block') 21526684e68SGreg Roach ->where('module_name', '=', $this->name()) 2164b92b602SGreg Roach ->where('gedcom_id', '=', $tree->id()) 2174b92b602SGreg Roach ->orderBy('xref') 2184b92b602SGreg Roach ->get(); 21972ac996dSGreg Roach 22072ac996dSGreg Roach foreach ($stories as $story) { 2215db543e1SGreg Roach $block_id = (int) $story->block_id; 2225db543e1SGreg Roach 22372ac996dSGreg Roach $story->individual = Individual::getInstance($story->xref, $tree); 2245db543e1SGreg Roach $story->title = $this->getBlockSetting($block_id, 'title'); 2255db543e1SGreg Roach $story->languages = $this->getBlockSetting($block_id, 'languages'); 22672ac996dSGreg Roach } 22772ac996dSGreg Roach 22872ac996dSGreg Roach return $this->viewResponse('modules/stories/config', [ 22971378461SGreg Roach 'module' => $this->name(), 23072ac996dSGreg Roach 'stories' => $stories, 23149a243cbSGreg Roach 'title' => $this->title() . ' — ' . $tree->title(), 23272ac996dSGreg Roach 'tree' => $tree, 23372ac996dSGreg Roach 'tree_names' => Tree::getNameList(), 23472ac996dSGreg Roach ]); 23572ac996dSGreg Roach } 23672ac996dSGreg Roach 23772ac996dSGreg Roach /** 2386ccdf4f0SGreg Roach * @param ServerRequestInterface $request 23972ac996dSGreg Roach * 2406ccdf4f0SGreg Roach * @return ResponseInterface 24172ac996dSGreg Roach */ 24257ab2231SGreg Roach public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface 243c1010edaSGreg Roach { 24472ac996dSGreg Roach $this->layout = 'layouts/administration'; 24572ac996dSGreg Roach 24657ab2231SGreg Roach $tree = $request->getAttribute('tree'); 247b6b9dcc9SGreg Roach $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 24872ac996dSGreg Roach 24972ac996dSGreg Roach if ($block_id === 0) { 25072ac996dSGreg Roach // Creating a new story 251b6b9dcc9SGreg Roach $individual = null; 25272ac996dSGreg Roach $story_title = ''; 25372ac996dSGreg Roach $story_body = ''; 25472ac996dSGreg Roach $languages = []; 25572ac996dSGreg Roach 256cc13d6d8SGreg Roach $title = I18N::translate('Add a story') . ' — ' . e($tree->title()); 25772ac996dSGreg Roach } else { 25872ac996dSGreg Roach // Editing an existing story 2594b92b602SGreg Roach $xref = (string) DB::table('block') 2604b92b602SGreg Roach ->where('block_id', '=', $block_id) 2614b92b602SGreg Roach ->value('xref'); 26272ac996dSGreg Roach 26372ac996dSGreg Roach $individual = Individual::getInstance($xref, $tree); 26450d6f48cSGreg Roach $story_title = $this->getBlockSetting($block_id, 'title'); 26550d6f48cSGreg Roach $story_body = $this->getBlockSetting($block_id, 'story_body'); 26672ac996dSGreg Roach $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 26772ac996dSGreg Roach 268cc13d6d8SGreg Roach $title = I18N::translate('Edit the story') . ' — ' . e($tree->title()); 26972ac996dSGreg Roach } 27072ac996dSGreg Roach 27172ac996dSGreg Roach return $this->viewResponse('modules/stories/edit', [ 27272ac996dSGreg Roach 'block_id' => $block_id, 27372ac996dSGreg Roach 'languages' => $languages, 27472ac996dSGreg Roach 'story_body' => $story_body, 27572ac996dSGreg Roach 'story_title' => $story_title, 27672ac996dSGreg Roach 'title' => $title, 27772ac996dSGreg Roach 'tree' => $tree, 27872ac996dSGreg Roach 'individual' => $individual, 27972ac996dSGreg Roach ]); 28072ac996dSGreg Roach } 28172ac996dSGreg Roach 28272ac996dSGreg Roach /** 2836ccdf4f0SGreg Roach * @param ServerRequestInterface $request 28472ac996dSGreg Roach * 2856ccdf4f0SGreg Roach * @return ResponseInterface 28672ac996dSGreg Roach */ 28757ab2231SGreg Roach public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface 288c1010edaSGreg Roach { 28957ab2231SGreg Roach $tree = $request->getAttribute('tree'); 290b6b9dcc9SGreg Roach $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 291b6b9dcc9SGreg Roach 292b6b9dcc9SGreg Roach $params = $request->getParsedBody(); 293b6b9dcc9SGreg Roach 294b6b9dcc9SGreg Roach $xref = $params['xref']; 295b6b9dcc9SGreg Roach $story_body = $params['story_body']; 296b6b9dcc9SGreg Roach $story_title = $params['story_title']; 297b6b9dcc9SGreg Roach $languages = $params['languages'] ?? []; 29872ac996dSGreg Roach 29950d6f48cSGreg Roach $story_body = $this->html_service->sanitize($story_body); 30050d6f48cSGreg Roach $story_title = $this->html_service->sanitize($story_title); 30150d6f48cSGreg Roach 30272ac996dSGreg Roach if ($block_id !== 0) { 3034b92b602SGreg Roach DB::table('block') 3044b92b602SGreg Roach ->where('block_id', '=', $block_id) 3054b92b602SGreg Roach ->update([ 3064b92b602SGreg Roach 'gedcom_id' => $tree->id(), 30772ac996dSGreg Roach 'xref' => $xref, 30872ac996dSGreg Roach ]); 30972ac996dSGreg Roach } else { 3104b92b602SGreg Roach DB::table('block')->insert([ 3114b92b602SGreg Roach 'gedcom_id' => $tree->id(), 31272ac996dSGreg Roach 'xref' => $xref, 31326684e68SGreg Roach 'module_name' => $this->name(), 3144b92b602SGreg Roach 'block_order' => 0, 31572ac996dSGreg Roach ]); 31672ac996dSGreg Roach 3174b92b602SGreg Roach $block_id = (int) DB::connection()->getPdo()->lastInsertId(); 31872ac996dSGreg Roach } 31972ac996dSGreg Roach 32072ac996dSGreg Roach $this->setBlockSetting($block_id, 'story_body', $story_body); 32172ac996dSGreg Roach $this->setBlockSetting($block_id, 'title', $story_title); 32272ac996dSGreg Roach $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 32372ac996dSGreg Roach 324c1010edaSGreg Roach $url = route('module', [ 32526684e68SGreg Roach 'module' => $this->name(), 326c1010edaSGreg Roach 'action' => 'Admin', 3279022ab66SGreg Roach 'tree' => $tree->name(), 328c1010edaSGreg Roach ]); 32972ac996dSGreg Roach 3306ccdf4f0SGreg Roach return redirect($url); 33172ac996dSGreg Roach } 33272ac996dSGreg Roach 33372ac996dSGreg Roach /** 3346ccdf4f0SGreg Roach * @param ServerRequestInterface $request 33572ac996dSGreg Roach * 3366ccdf4f0SGreg Roach * @return ResponseInterface 33772ac996dSGreg Roach */ 33857ab2231SGreg Roach public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface 339c1010edaSGreg Roach { 34057ab2231SGreg Roach $tree = $request->getAttribute('tree'); 341b6b9dcc9SGreg Roach $block_id = $request->getQueryParams()['block_id']; 34272ac996dSGreg Roach 3434b92b602SGreg Roach DB::table('block_setting') 3444b92b602SGreg Roach ->where('block_id', '=', $block_id) 3454b92b602SGreg Roach ->delete(); 34672ac996dSGreg Roach 3474b92b602SGreg Roach DB::table('block') 3484b92b602SGreg Roach ->where('block_id', '=', $block_id) 3494b92b602SGreg Roach ->delete(); 35072ac996dSGreg Roach 351c1010edaSGreg Roach $url = route('module', [ 35226684e68SGreg Roach 'module' => $this->name(), 353c1010edaSGreg Roach 'action' => 'Admin', 3549022ab66SGreg Roach 'tree' => $tree->name(), 355c1010edaSGreg Roach ]); 35672ac996dSGreg Roach 3576ccdf4f0SGreg Roach return redirect($url); 35872ac996dSGreg Roach } 35972ac996dSGreg Roach 36072ac996dSGreg Roach /** 36157ab2231SGreg Roach * @param ServerRequestInterface $request 36272ac996dSGreg Roach * 3636ccdf4f0SGreg Roach * @return ResponseInterface 36472ac996dSGreg Roach */ 36557ab2231SGreg Roach public function getShowListAction(ServerRequestInterface $request): ResponseInterface 366c1010edaSGreg Roach { 36757ab2231SGreg Roach $tree = $request->getAttribute('tree'); 3685229eadeSGreg Roach assert($tree instanceof Tree, new InvalidArgumentException()); 36957ab2231SGreg Roach 3704b92b602SGreg Roach $stories = DB::table('block') 37126684e68SGreg Roach ->where('module_name', '=', $this->name()) 3724b92b602SGreg Roach ->where('gedcom_id', '=', $tree->id()) 3734b92b602SGreg Roach ->get() 3744b92b602SGreg Roach ->map(function (stdClass $story) use ($tree): stdClass { 3755db543e1SGreg Roach $block_id = (int) $story->block_id; 3765db543e1SGreg Roach 37772ac996dSGreg Roach $story->individual = Individual::getInstance($story->xref, $tree); 3785db543e1SGreg Roach $story->title = $this->getBlockSetting($block_id, 'title'); 3795db543e1SGreg Roach $story->languages = $this->getBlockSetting($block_id, 'languages'); 38072ac996dSGreg Roach 3814b92b602SGreg Roach return $story; 3820b5fd0a6SGreg Roach })->filter(static function (stdClass $story): bool { 38372ac996dSGreg Roach // Filter non-existant and private individuals. 3844b92b602SGreg Roach return $story->individual instanceof Individual && $story->individual->canShow(); 3850b5fd0a6SGreg Roach })->filter(static function (stdClass $story): bool { 38672ac996dSGreg Roach // Filter foreign languages. 38722d65e5aSGreg Roach return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true); 38872ac996dSGreg Roach }); 38972ac996dSGreg Roach 39072ac996dSGreg Roach return $this->viewResponse('modules/stories/list', [ 39172ac996dSGreg Roach 'stories' => $stories, 39249a243cbSGreg Roach 'title' => $this->title(), 39372ac996dSGreg Roach ]); 39472ac996dSGreg Roach } 3958c2e8227SGreg Roach} 396