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