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; 28*5229eadeSGreg Roachuse InvalidArgumentException; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 31225e381fSGreg Roachuse stdClass; 32*5229eadeSGreg Roachuse function assert; 338c2e8227SGreg Roach 348c2e8227SGreg Roach/** 358c2e8227SGreg Roach * Class StoriesModule 368c2e8227SGreg Roach */ 3737eb8894SGreg Roachclass StoriesModule extends AbstractModule implements ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface 38c1010edaSGreg Roach{ 3949a243cbSGreg Roach use ModuleTabTrait; 4049a243cbSGreg Roach use ModuleConfigTrait; 4149a243cbSGreg Roach use ModuleMenuTrait; 4249a243cbSGreg Roach 4350d6f48cSGreg Roach /** @var HtmlService */ 4450d6f48cSGreg Roach private $html_service; 4550d6f48cSGreg Roach 4650d6f48cSGreg Roach /** 4750d6f48cSGreg Roach * HtmlBlockModule bootstrap. 4850d6f48cSGreg Roach * 4950d6f48cSGreg Roach * @param HtmlService $html_service 5050d6f48cSGreg Roach */ 5150d6f48cSGreg Roach public function boot(HtmlService $html_service) 5250d6f48cSGreg Roach { 5350d6f48cSGreg Roach $this->html_service = $html_service; 5450d6f48cSGreg Roach } 5550d6f48cSGreg Roach 5649a243cbSGreg Roach /** @var int The default access level for this module. It can be changed in the control panel. */ 5749a243cbSGreg Roach protected $access_level = Auth::PRIV_HIDE; 5849a243cbSGreg Roach 59961ec755SGreg Roach /** 60961ec755SGreg Roach * A sentence describing what this module does. 61961ec755SGreg Roach * 62961ec755SGreg Roach * @return string 63961ec755SGreg Roach */ 6449a243cbSGreg Roach public function description(): string 65c1010edaSGreg Roach { 66bbb76c12SGreg Roach /* I18N: Description of the “Stories” module */ 67bbb76c12SGreg Roach return I18N::translate('Add narrative stories to individuals in the family tree.'); 688c2e8227SGreg Roach } 698c2e8227SGreg Roach 70aee13b6dSGreg Roach /** 7149a243cbSGreg Roach * The default position for this menu. It can be changed in the control panel. 72aee13b6dSGreg Roach * 7349a243cbSGreg Roach * @return int 74aee13b6dSGreg Roach */ 7549a243cbSGreg Roach public function defaultMenuOrder(): int 76c1010edaSGreg Roach { 77353b36abSGreg Roach return 7; 788c2e8227SGreg Roach } 798c2e8227SGreg Roach 8049a243cbSGreg Roach /** 8149a243cbSGreg Roach * The default position for this tab. It can be changed in the control panel. 8249a243cbSGreg Roach * 8349a243cbSGreg Roach * @return int 8449a243cbSGreg Roach */ 85cbf4b7faSGreg Roach public function defaultTabOrder(): int 86cbf4b7faSGreg Roach { 87fb7a0427SGreg Roach return 9; 888c2e8227SGreg Roach } 898c2e8227SGreg Roach 903caaa4d2SGreg Roach /** 913caaa4d2SGreg Roach * Generate the HTML content of this tab. 923caaa4d2SGreg Roach * 933caaa4d2SGreg Roach * @param Individual $individual 943caaa4d2SGreg Roach * 953caaa4d2SGreg Roach * @return string 963caaa4d2SGreg Roach */ 979b34404bSGreg Roach public function getTabContent(Individual $individual): string 98c1010edaSGreg Roach { 9972ac996dSGreg Roach return view('modules/stories/tab', [ 10072ac996dSGreg Roach 'is_admin' => Auth::isAdmin(), 101225e381fSGreg Roach 'individual' => $individual, 102225e381fSGreg Roach 'stories' => $this->getStoriesForIndividual($individual), 103225e381fSGreg Roach ]); 1048c2e8227SGreg Roach } 1058c2e8227SGreg Roach 106225e381fSGreg Roach /** 107225e381fSGreg Roach * @param Individual $individual 108225e381fSGreg Roach * 109225e381fSGreg Roach * @return stdClass[] 110225e381fSGreg Roach */ 111c1010edaSGreg Roach private function getStoriesForIndividual(Individual $individual): array 112c1010edaSGreg Roach { 1134b92b602SGreg Roach $block_ids = DB::table('block') 11426684e68SGreg Roach ->where('module_name', '=', $this->name()) 1154b92b602SGreg Roach ->where('xref', '=', $individual->xref()) 1164b92b602SGreg Roach ->where('gedcom_id', '=', $individual->tree()->id()) 1174b92b602SGreg Roach ->pluck('block_id'); 118225e381fSGreg Roach 119225e381fSGreg Roach $stories = []; 120225e381fSGreg Roach foreach ($block_ids as $block_id) { 1217d988ec3SGreg Roach $block_id = (int) $block_id; 1227d988ec3SGreg Roach 123225e381fSGreg Roach // Only show this block for certain languages 12450d6f48cSGreg Roach $languages = $this->getBlockSetting($block_id, 'languages'); 12522d65e5aSGreg Roach if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages), true)) { 126225e381fSGreg Roach $stories[] = (object) [ 127225e381fSGreg Roach 'block_id' => $block_id, 128225e381fSGreg Roach 'title' => $this->getBlockSetting($block_id, 'title'), 12972ac996dSGreg Roach 'story_body' => $this->getBlockSetting($block_id, 'story_body'), 130225e381fSGreg Roach ]; 131225e381fSGreg Roach } 132225e381fSGreg Roach } 133225e381fSGreg Roach 134225e381fSGreg Roach return $stories; 1358c2e8227SGreg Roach } 1368c2e8227SGreg Roach 1373caaa4d2SGreg Roach /** 1383caaa4d2SGreg Roach * Is this tab empty? If so, we don't always need to display it. 1393caaa4d2SGreg Roach * 1403caaa4d2SGreg Roach * @param Individual $individual 1413caaa4d2SGreg Roach * 1423caaa4d2SGreg Roach * @return bool 1433caaa4d2SGreg Roach */ 1446ccdf4f0SGreg Roach public function hasTabContent(Individual $individual): bool 1456ccdf4f0SGreg Roach { 1466ccdf4f0SGreg Roach return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual)); 1476ccdf4f0SGreg Roach } 1486ccdf4f0SGreg Roach 1493caaa4d2SGreg Roach /** 1503caaa4d2SGreg Roach * A greyed out tab has no actual content, but may perhaps have 1513caaa4d2SGreg Roach * options to create content. 1523caaa4d2SGreg Roach * 1533caaa4d2SGreg Roach * @param Individual $individual 1543caaa4d2SGreg Roach * 1553caaa4d2SGreg Roach * @return bool 1563caaa4d2SGreg Roach */ 1576ccdf4f0SGreg Roach public function isGrayedOut(Individual $individual): bool 1586ccdf4f0SGreg Roach { 1596ccdf4f0SGreg Roach return !empty($this->getStoriesForIndividual($individual)); 1606ccdf4f0SGreg Roach } 1616ccdf4f0SGreg Roach 1623caaa4d2SGreg Roach /** 1633caaa4d2SGreg Roach * Can this tab load asynchronously? 1643caaa4d2SGreg Roach * 1653caaa4d2SGreg Roach * @return bool 1663caaa4d2SGreg Roach */ 1676ccdf4f0SGreg Roach public function canLoadAjax(): bool 1686ccdf4f0SGreg Roach { 1696ccdf4f0SGreg Roach return false; 1706ccdf4f0SGreg Roach } 1716ccdf4f0SGreg Roach 1728c2e8227SGreg Roach /** 1730ee13198SGreg Roach * A menu, to be added to the main application menu. 1740ee13198SGreg Roach * 175aee13b6dSGreg Roach * @param Tree $tree 176aee13b6dSGreg Roach * 1770ee13198SGreg Roach * @return Menu|null 1780ee13198SGreg Roach */ 17946295629SGreg Roach public function getMenu(Tree $tree): ?Menu 180c1010edaSGreg Roach { 18149a243cbSGreg Roach $menu = new Menu($this->title(), route('module', [ 18226684e68SGreg Roach 'module' => $this->name(), 183c1010edaSGreg Roach 'action' => 'ShowList', 1849022ab66SGreg Roach 'tree' => $tree->name(), 185c1010edaSGreg Roach ]), 'menu-story'); 1868c2e8227SGreg Roach 1878c2e8227SGreg Roach return $menu; 1888c2e8227SGreg Roach } 18972ac996dSGreg Roach 19072ac996dSGreg Roach /** 1916ccdf4f0SGreg Roach * How should this module be identified in the control panel, etc.? 1926ccdf4f0SGreg Roach * 1936ccdf4f0SGreg Roach * @return string 1946ccdf4f0SGreg Roach */ 1956ccdf4f0SGreg Roach public function title(): string 1966ccdf4f0SGreg Roach { 1976ccdf4f0SGreg Roach /* I18N: Name of a module */ 1986ccdf4f0SGreg Roach return I18N::translate('Stories'); 1996ccdf4f0SGreg Roach } 2006ccdf4f0SGreg Roach 2016ccdf4f0SGreg Roach /** 20257ab2231SGreg Roach * @param ServerRequestInterface $request 20372ac996dSGreg Roach * 2046ccdf4f0SGreg Roach * @return ResponseInterface 20572ac996dSGreg Roach */ 20657ab2231SGreg Roach public function getAdminAction(ServerRequestInterface $request): ResponseInterface 207c1010edaSGreg Roach { 20872ac996dSGreg Roach $this->layout = 'layouts/administration'; 20972ac996dSGreg Roach 21057ab2231SGreg Roach $tree = $request->getAttribute('tree'); 211*5229eadeSGreg Roach assert($tree instanceof Tree, new InvalidArgumentException()); 21257ab2231SGreg Roach 2134b92b602SGreg Roach $stories = DB::table('block') 21426684e68SGreg Roach ->where('module_name', '=', $this->name()) 2154b92b602SGreg Roach ->where('gedcom_id', '=', $tree->id()) 2164b92b602SGreg Roach ->orderBy('xref') 2174b92b602SGreg Roach ->get(); 21872ac996dSGreg Roach 21972ac996dSGreg Roach foreach ($stories as $story) { 2205db543e1SGreg Roach $block_id = (int) $story->block_id; 2215db543e1SGreg Roach 22272ac996dSGreg Roach $story->individual = Individual::getInstance($story->xref, $tree); 2235db543e1SGreg Roach $story->title = $this->getBlockSetting($block_id, 'title'); 2245db543e1SGreg Roach $story->languages = $this->getBlockSetting($block_id, 'languages'); 22572ac996dSGreg Roach } 22672ac996dSGreg Roach 22772ac996dSGreg Roach return $this->viewResponse('modules/stories/config', [ 22871378461SGreg Roach 'module' => $this->name(), 22972ac996dSGreg Roach 'stories' => $stories, 23049a243cbSGreg Roach 'title' => $this->title() . ' — ' . $tree->title(), 23172ac996dSGreg Roach 'tree' => $tree, 23272ac996dSGreg Roach 'tree_names' => Tree::getNameList(), 23372ac996dSGreg Roach ]); 23472ac996dSGreg Roach } 23572ac996dSGreg Roach 23672ac996dSGreg Roach /** 2376ccdf4f0SGreg Roach * @param ServerRequestInterface $request 23872ac996dSGreg Roach * 2396ccdf4f0SGreg Roach * @return ResponseInterface 24072ac996dSGreg Roach */ 24157ab2231SGreg Roach public function getAdminEditAction(ServerRequestInterface $request): ResponseInterface 242c1010edaSGreg Roach { 24372ac996dSGreg Roach $this->layout = 'layouts/administration'; 24472ac996dSGreg Roach 24557ab2231SGreg Roach $tree = $request->getAttribute('tree'); 246b6b9dcc9SGreg Roach $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 24772ac996dSGreg Roach 24872ac996dSGreg Roach if ($block_id === 0) { 24972ac996dSGreg Roach // Creating a new story 250b6b9dcc9SGreg Roach $individual = null; 25172ac996dSGreg Roach $story_title = ''; 25272ac996dSGreg Roach $story_body = ''; 25372ac996dSGreg Roach $languages = []; 25472ac996dSGreg Roach 255cc13d6d8SGreg Roach $title = I18N::translate('Add a story') . ' — ' . e($tree->title()); 25672ac996dSGreg Roach } else { 25772ac996dSGreg Roach // Editing an existing story 2584b92b602SGreg Roach $xref = (string) DB::table('block') 2594b92b602SGreg Roach ->where('block_id', '=', $block_id) 2604b92b602SGreg Roach ->value('xref'); 26172ac996dSGreg Roach 26272ac996dSGreg Roach $individual = Individual::getInstance($xref, $tree); 26350d6f48cSGreg Roach $story_title = $this->getBlockSetting($block_id, 'title'); 26450d6f48cSGreg Roach $story_body = $this->getBlockSetting($block_id, 'story_body'); 26572ac996dSGreg Roach $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 26672ac996dSGreg Roach 267cc13d6d8SGreg Roach $title = I18N::translate('Edit the story') . ' — ' . e($tree->title()); 26872ac996dSGreg Roach } 26972ac996dSGreg Roach 27072ac996dSGreg Roach return $this->viewResponse('modules/stories/edit', [ 27172ac996dSGreg Roach 'block_id' => $block_id, 27272ac996dSGreg Roach 'languages' => $languages, 27372ac996dSGreg Roach 'story_body' => $story_body, 27472ac996dSGreg Roach 'story_title' => $story_title, 27572ac996dSGreg Roach 'title' => $title, 27672ac996dSGreg Roach 'tree' => $tree, 27772ac996dSGreg Roach 'individual' => $individual, 27872ac996dSGreg Roach ]); 27972ac996dSGreg Roach } 28072ac996dSGreg Roach 28172ac996dSGreg Roach /** 2826ccdf4f0SGreg Roach * @param ServerRequestInterface $request 28372ac996dSGreg Roach * 2846ccdf4f0SGreg Roach * @return ResponseInterface 28572ac996dSGreg Roach */ 28657ab2231SGreg Roach public function postAdminEditAction(ServerRequestInterface $request): ResponseInterface 287c1010edaSGreg Roach { 28857ab2231SGreg Roach $tree = $request->getAttribute('tree'); 289b6b9dcc9SGreg Roach $block_id = (int) ($request->getQueryParams()['block_id'] ?? 0); 290b6b9dcc9SGreg Roach 291b6b9dcc9SGreg Roach $params = $request->getParsedBody(); 292b6b9dcc9SGreg Roach 293b6b9dcc9SGreg Roach $xref = $params['xref']; 294b6b9dcc9SGreg Roach $story_body = $params['story_body']; 295b6b9dcc9SGreg Roach $story_title = $params['story_title']; 296b6b9dcc9SGreg Roach $languages = $params['languages'] ?? []; 29772ac996dSGreg Roach 29850d6f48cSGreg Roach $story_body = $this->html_service->sanitize($story_body); 29950d6f48cSGreg Roach $story_title = $this->html_service->sanitize($story_title); 30050d6f48cSGreg Roach 30172ac996dSGreg Roach if ($block_id !== 0) { 3024b92b602SGreg Roach DB::table('block') 3034b92b602SGreg Roach ->where('block_id', '=', $block_id) 3044b92b602SGreg Roach ->update([ 3054b92b602SGreg Roach 'gedcom_id' => $tree->id(), 30672ac996dSGreg Roach 'xref' => $xref, 30772ac996dSGreg Roach ]); 30872ac996dSGreg Roach } else { 3094b92b602SGreg Roach DB::table('block')->insert([ 3104b92b602SGreg Roach 'gedcom_id' => $tree->id(), 31172ac996dSGreg Roach 'xref' => $xref, 31226684e68SGreg Roach 'module_name' => $this->name(), 3134b92b602SGreg Roach 'block_order' => 0, 31472ac996dSGreg Roach ]); 31572ac996dSGreg Roach 3164b92b602SGreg Roach $block_id = (int) DB::connection()->getPdo()->lastInsertId(); 31772ac996dSGreg Roach } 31872ac996dSGreg Roach 31972ac996dSGreg Roach $this->setBlockSetting($block_id, 'story_body', $story_body); 32072ac996dSGreg Roach $this->setBlockSetting($block_id, 'title', $story_title); 32172ac996dSGreg Roach $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 32272ac996dSGreg Roach 323c1010edaSGreg Roach $url = route('module', [ 32426684e68SGreg Roach 'module' => $this->name(), 325c1010edaSGreg Roach 'action' => 'Admin', 3269022ab66SGreg Roach 'tree' => $tree->name(), 327c1010edaSGreg Roach ]); 32872ac996dSGreg Roach 3296ccdf4f0SGreg Roach return redirect($url); 33072ac996dSGreg Roach } 33172ac996dSGreg Roach 33272ac996dSGreg Roach /** 3336ccdf4f0SGreg Roach * @param ServerRequestInterface $request 33472ac996dSGreg Roach * 3356ccdf4f0SGreg Roach * @return ResponseInterface 33672ac996dSGreg Roach */ 33757ab2231SGreg Roach public function postAdminDeleteAction(ServerRequestInterface $request): ResponseInterface 338c1010edaSGreg Roach { 33957ab2231SGreg Roach $tree = $request->getAttribute('tree'); 340b6b9dcc9SGreg Roach $block_id = $request->getQueryParams()['block_id']; 34172ac996dSGreg Roach 3424b92b602SGreg Roach DB::table('block_setting') 3434b92b602SGreg Roach ->where('block_id', '=', $block_id) 3444b92b602SGreg Roach ->delete(); 34572ac996dSGreg Roach 3464b92b602SGreg Roach DB::table('block') 3474b92b602SGreg Roach ->where('block_id', '=', $block_id) 3484b92b602SGreg Roach ->delete(); 34972ac996dSGreg Roach 350c1010edaSGreg Roach $url = route('module', [ 35126684e68SGreg Roach 'module' => $this->name(), 352c1010edaSGreg Roach 'action' => 'Admin', 3539022ab66SGreg Roach 'tree' => $tree->name(), 354c1010edaSGreg Roach ]); 35572ac996dSGreg Roach 3566ccdf4f0SGreg Roach return redirect($url); 35772ac996dSGreg Roach } 35872ac996dSGreg Roach 35972ac996dSGreg Roach /** 36057ab2231SGreg Roach * @param ServerRequestInterface $request 36172ac996dSGreg Roach * 3626ccdf4f0SGreg Roach * @return ResponseInterface 36372ac996dSGreg Roach */ 36457ab2231SGreg Roach public function getShowListAction(ServerRequestInterface $request): ResponseInterface 365c1010edaSGreg Roach { 36657ab2231SGreg Roach $tree = $request->getAttribute('tree'); 367*5229eadeSGreg Roach assert($tree instanceof Tree, new InvalidArgumentException()); 36857ab2231SGreg Roach 3694b92b602SGreg Roach $stories = DB::table('block') 37026684e68SGreg Roach ->where('module_name', '=', $this->name()) 3714b92b602SGreg Roach ->where('gedcom_id', '=', $tree->id()) 3724b92b602SGreg Roach ->get() 3734b92b602SGreg Roach ->map(function (stdClass $story) use ($tree): stdClass { 3745db543e1SGreg Roach $block_id = (int) $story->block_id; 3755db543e1SGreg Roach 37672ac996dSGreg Roach $story->individual = Individual::getInstance($story->xref, $tree); 3775db543e1SGreg Roach $story->title = $this->getBlockSetting($block_id, 'title'); 3785db543e1SGreg Roach $story->languages = $this->getBlockSetting($block_id, 'languages'); 37972ac996dSGreg Roach 3804b92b602SGreg Roach return $story; 3810b5fd0a6SGreg Roach })->filter(static function (stdClass $story): bool { 38272ac996dSGreg Roach // Filter non-existant and private individuals. 3834b92b602SGreg Roach return $story->individual instanceof Individual && $story->individual->canShow(); 3840b5fd0a6SGreg Roach })->filter(static function (stdClass $story): bool { 38572ac996dSGreg Roach // Filter foreign languages. 38622d65e5aSGreg Roach return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages), true); 38772ac996dSGreg Roach }); 38872ac996dSGreg Roach 38972ac996dSGreg Roach return $this->viewResponse('modules/stories/list', [ 39072ac996dSGreg Roach 'stories' => $stories, 39149a243cbSGreg Roach 'title' => $this->title(), 39272ac996dSGreg Roach ]); 39372ac996dSGreg Roach } 3948c2e8227SGreg Roach} 395