1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2018 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\Auth; 21use Fisharebest\Webtrees\Database; 22use Fisharebest\Webtrees\I18N; 23use Fisharebest\Webtrees\Individual; 24use Fisharebest\Webtrees\Menu; 25use Fisharebest\Webtrees\Tree; 26use stdClass; 27use Symfony\Component\HttpFoundation\RedirectResponse; 28use Symfony\Component\HttpFoundation\Request; 29use Symfony\Component\HttpFoundation\Response; 30 31/** 32 * Class StoriesModule 33 */ 34class StoriesModule extends AbstractModule implements ModuleTabInterface, ModuleConfigInterface, ModuleMenuInterface 35{ 36 /** {@inheritdoc} */ 37 public function getTitle(): string 38 { 39 /* I18N: Name of a module */ 40 return I18N::translate('Stories'); 41 } 42 43 /** {@inheritdoc} */ 44 public function getDescription(): string 45 { 46 /* I18N: Description of the “Stories” module */ 47 return I18N::translate('Add narrative stories to individuals in the family tree.'); 48 } 49 50 /** 51 * The URL to a page where the user can modify the configuration of this module. 52 * 53 * @return string 54 */ 55 public function getConfigLink(): string 56 { 57 return route('module', [ 58 'module' => $this->getName(), 59 'action' => 'Admin', 60 ]); 61 } 62 63 /** {@inheritdoc} */ 64 public function defaultTabOrder(): int 65 { 66 return 55; 67 } 68 69 /** {@inheritdoc} */ 70 public function getTabContent(Individual $individual): string 71 { 72 return view('modules/stories/tab', [ 73 'is_admin' => Auth::isAdmin(), 74 'individual' => $individual, 75 'stories' => $this->getStoriesForIndividual($individual), 76 ]); 77 } 78 79 /** {@inheritdoc} */ 80 public function hasTabContent(Individual $individual): bool 81 { 82 return Auth::isManager($individual->getTree()) || !empty($this->getStoriesForIndividual($individual)); 83 } 84 85 /** {@inheritdoc} */ 86 public function isGrayedOut(Individual $individual): bool 87 { 88 return !empty($this->getStoriesForIndividual($individual)); 89 } 90 91 /** {@inheritdoc} */ 92 public function canLoadAjax(): bool 93 { 94 return false; 95 } 96 97 /** 98 * @param Individual $individual 99 * 100 * @return stdClass[] 101 */ 102 private function getStoriesForIndividual(Individual $individual): array 103 { 104 $block_ids = 105 Database::prepare( 106 "SELECT block_id" . 107 " FROM `##block`" . 108 " WHERE module_name = :module_name" . 109 " AND xref = :xref" . 110 " AND gedcom_id = :tree_id" 111 )->execute([ 112 'module_name' => $this->getName(), 113 'xref' => $individual->getXref(), 114 'tree_id' => $individual->getTree()->getTreeId(), 115 ])->fetchOneColumn(); 116 117 $stories = []; 118 foreach ($block_ids as $block_id) { 119 // Only show this block for certain languages 120 $languages = $this->getBlockSetting($block_id, 'languages', ''); 121 if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) { 122 $stories[] = (object) [ 123 'block_id' => $block_id, 124 'title' => $this->getBlockSetting($block_id, 'title'), 125 'story_body' => $this->getBlockSetting($block_id, 'story_body'), 126 ]; 127 } 128 } 129 130 return $stories; 131 } 132 133 /** 134 * The user can re-order menus. Until they do, they are shown in this order. 135 * 136 * @return int 137 */ 138 public function defaultMenuOrder(): int 139 { 140 return 30; 141 } 142 143 /** 144 * What is the default access level for this module? 145 * 146 * Some modules are aimed at admins or managers, and are not generally shown to users. 147 * 148 * @return int 149 */ 150 public function defaultAccessLevel(): int 151 { 152 return Auth::PRIV_HIDE; 153 } 154 155 /** 156 * A menu, to be added to the main application menu. 157 * 158 * @param Tree $tree 159 * 160 * @return Menu|null 161 */ 162 public function getMenu(Tree $tree) 163 { 164 $menu = new Menu($this->getTitle(), route('module', [ 165 'module' => $this->getName(), 166 'action' => 'ShowList', 167 'ged' => $tree->getName(), 168 ]), 'menu-story'); 169 170 return $menu; 171 } 172 173 /** 174 * @param Tree $tree 175 * 176 * @return Response 177 */ 178 public function getAdminAction(Tree $tree): Response 179 { 180 $this->layout = 'layouts/administration'; 181 182 $stories = Database::prepare( 183 "SELECT block_id, xref, gedcom_id" . 184 " FROM `##block` b" . 185 " WHERE module_name = :module_name" . 186 " AND gedcom_id = :tree_id" . 187 " ORDER BY gedcom_id, xref" 188 )->execute([ 189 'tree_id' => $tree->getTreeId(), 190 'module_name' => $this->getName(), 191 ])->fetchAll(); 192 193 foreach ($stories as $story) { 194 $story->individual = Individual::getInstance($story->xref, $tree); 195 $story->title = $this->getBlockSetting($story->block_id, 'title'); 196 $story->languages = $this->getBlockSetting($story->block_id, 'languages'); 197 } 198 199 return $this->viewResponse('modules/stories/config', [ 200 'stories' => $stories, 201 'title' => $this->getTitle() . ' — ' . $tree->getTitle(), 202 'tree' => $tree, 203 'tree_names' => Tree::getNameList(), 204 ]); 205 } 206 207 /** 208 * @param Request $request 209 * @param Tree $tree 210 * 211 * @return Response 212 */ 213 public function getAdminEditAction(Request $request, Tree $tree): Response 214 { 215 $this->layout = 'layouts/administration'; 216 217 $block_id = (int) $request->get('block_id'); 218 219 if ($block_id === 0) { 220 // Creating a new story 221 $individual = Individual::getInstance($request->get('xref', ''), $tree); 222 $story_title = ''; 223 $story_body = ''; 224 $languages = []; 225 226 $title = I18N::translate('Add a story') . ' — ' . e($tree->getTitle()); 227 } else { 228 // Editing an existing story 229 $xref = Database::prepare( 230 "SELECT xref FROM `##block` WHERE block_id = :block_id" 231 )->execute([ 232 'block_id' => $block_id, 233 ])->fetchOne(); 234 235 $individual = Individual::getInstance($xref, $tree); 236 $story_title = $this->getBlockSetting($block_id, 'title', ''); 237 $story_body = $this->getBlockSetting($block_id, 'story_body', ''); 238 $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 239 240 $title = I18N::translate('Edit the story') . ' — ' . e($tree->getTitle()); 241 } 242 243 return $this->viewResponse('modules/stories/edit', [ 244 'block_id' => $block_id, 245 'languages' => $languages, 246 'story_body' => $story_body, 247 'story_title' => $story_title, 248 'title' => $title, 249 'tree' => $tree, 250 'individual' => $individual, 251 ]); 252 } 253 254 /** 255 * @param Request $request 256 * @param Tree $tree 257 * 258 * @return RedirectResponse 259 */ 260 public function postAdminEditAction(Request $request, Tree $tree): RedirectResponse 261 { 262 $block_id = (int) $request->get('block_id'); 263 $xref = $request->get('xref', ''); 264 $story_body = $request->get('story_body', ''); 265 $story_title = $request->get('story_title', ''); 266 $languages = $request->get('languages', []); 267 268 if ($block_id !== 0) { 269 Database::prepare( 270 "UPDATE `##block` SET gedcom_id = :tree_id, xref = :xref WHERE block_id = :block_id" 271 )->execute([ 272 'tree_id' => $tree->getTreeId(), 273 'xref' => $xref, 274 'block_id' => $block_id, 275 ]); 276 } else { 277 Database::prepare( 278 "INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (:tree_id, :xref, 'stories', 0)" 279 )->execute([ 280 'tree_id' => $tree->getTreeId(), 281 'xref' => $xref, 282 ]); 283 284 $block_id = Database::getInstance()->lastInsertId(); 285 } 286 287 $this->setBlockSetting($block_id, 'story_body', $story_body); 288 $this->setBlockSetting($block_id, 'title', $story_title); 289 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 290 291 $url = route('module', [ 292 'module' => 'stories', 293 'action' => 'Admin', 294 'ged' => $tree->getName(), 295 ]); 296 297 return new RedirectResponse($url); 298 } 299 300 /** 301 * @param Request $request 302 * @param Tree $tree 303 * 304 * @return Response 305 */ 306 public function postAdminDeleteAction(Request $request, Tree $tree): Response 307 { 308 $block_id = (int) $request->get('block_id'); 309 310 Database::prepare( 311 "DELETE FROM `##block_setting` WHERE block_id = :block_id" 312 )->execute([ 313 'block_id' => $block_id, 314 ]); 315 316 Database::prepare( 317 "DELETE FROM `##block` WHERE block_id = :block_id" 318 )->execute([ 319 'block_id' => $block_id, 320 ]); 321 322 $url = route('module', [ 323 'module' => 'stories', 324 'action' => 'Admin', 325 'ged' => $tree->getName(), 326 ]); 327 328 return new RedirectResponse($url); 329 } 330 331 /** 332 * @param Tree $tree 333 * 334 * @return Response 335 */ 336 public function getShowListAction(Tree $tree): Response 337 { 338 $stories = Database::prepare( 339 "SELECT block_id, xref" . 340 " FROM `##block` b" . 341 " WHERE module_name = :module_name" . 342 " AND gedcom_id = :tree_id" . 343 " ORDER BY xref" 344 )->execute([ 345 'module_name' => $this->getName(), 346 'tree_id' => $tree->getTreeId(), 347 ])->fetchAll(); 348 349 foreach ($stories as $story) { 350 $story->individual = Individual::getInstance($story->xref, $tree); 351 $story->title = $this->getBlockSetting($story->block_id, 'title'); 352 $story->languages = $this->getBlockSetting($story->block_id, 'languages'); 353 } 354 355 // Filter non-existant and private individuals. 356 $stories = array_filter($stories, function (stdClass $story): bool { 357 return $story->individual !== null && $story->individual->canShow(); 358 }); 359 360 // Filter foreign languages. 361 $stories = array_filter($stories, function (stdClass $story): bool { 362 return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages)); 363 }); 364 365 return $this->viewResponse('modules/stories/list', [ 366 'stories' => $stories, 367 'title' => $this->getTitle(), 368 ]); 369 } 370} 371