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