1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 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\I18N; 22use Fisharebest\Webtrees\Individual; 23use Fisharebest\Webtrees\Menu; 24use Fisharebest\Webtrees\Tree; 25use Illuminate\Database\Capsule\Manager as DB; 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 ModuleInterface, ModuleConfigInterface, ModuleMenuInterface, ModuleTabInterface 35{ 36 use ModuleTabTrait; 37 use ModuleConfigTrait; 38 use ModuleMenuTrait; 39 40 /** @var int The default access level for this module. It can be changed in the control panel. */ 41 protected $access_level = Auth::PRIV_HIDE; 42 43 /** {@inheritdoc} */ 44 public function title(): string 45 { 46 /* I18N: Name of a module */ 47 return I18N::translate('Stories'); 48 } 49 50 /** {@inheritdoc} */ 51 public function description(): string 52 { 53 /* I18N: Description of the “Stories” module */ 54 return I18N::translate('Add narrative stories to individuals in the family tree.'); 55 } 56 57 /** 58 * The default position for this menu. It can be changed in the control panel. 59 * 60 * @return int 61 */ 62 public function defaultMenuOrder(): int 63 { 64 return 70; 65 } 66 67 /** 68 * The default position for this tab. It can be changed in the control panel. 69 * 70 * @return int 71 */ 72 public function defaultTabOrder(): int 73 { 74 return 70; 75 } 76 77 /** {@inheritdoc} */ 78 public function getTabContent(Individual $individual): string 79 { 80 return view('modules/stories/tab', [ 81 'is_admin' => Auth::isAdmin(), 82 'individual' => $individual, 83 'stories' => $this->getStoriesForIndividual($individual), 84 ]); 85 } 86 87 /** {@inheritdoc} */ 88 public function hasTabContent(Individual $individual): bool 89 { 90 return Auth::isManager($individual->tree()) || !empty($this->getStoriesForIndividual($individual)); 91 } 92 93 /** {@inheritdoc} */ 94 public function isGrayedOut(Individual $individual): bool 95 { 96 return !empty($this->getStoriesForIndividual($individual)); 97 } 98 99 /** {@inheritdoc} */ 100 public function canLoadAjax(): bool 101 { 102 return false; 103 } 104 105 /** 106 * @param Individual $individual 107 * 108 * @return stdClass[] 109 */ 110 private function getStoriesForIndividual(Individual $individual): array 111 { 112 $block_ids = DB::table('block') 113 ->where('module_name', '=', $this->getName()) 114 ->where('xref', '=', $individual->xref()) 115 ->where('gedcom_id', '=', $individual->tree()->id()) 116 ->pluck('block_id'); 117 118 $stories = []; 119 foreach ($block_ids as $block_id) { 120 $block_id = (int) $block_id; 121 122 // Only show this block for certain languages 123 $languages = $this->getBlockSetting($block_id, 'languages', ''); 124 if ($languages === '' || in_array(WT_LOCALE, explode(',', $languages))) { 125 $stories[] = (object) [ 126 'block_id' => $block_id, 127 'title' => $this->getBlockSetting($block_id, 'title'), 128 'story_body' => $this->getBlockSetting($block_id, 'story_body'), 129 ]; 130 } 131 } 132 133 return $stories; 134 } 135 136 /** 137 * A menu, to be added to the main application menu. 138 * 139 * @param Tree $tree 140 * 141 * @return Menu|null 142 */ 143 public function getMenu(Tree $tree): ?Menu 144 { 145 $menu = new Menu($this->title(), route('module', [ 146 'module' => $this->getName(), 147 'action' => 'ShowList', 148 'ged' => $tree->name(), 149 ]), 'menu-story'); 150 151 return $menu; 152 } 153 154 /** 155 * @param Tree $tree 156 * 157 * @return Response 158 */ 159 public function getAdminAction(Tree $tree): Response 160 { 161 $this->layout = 'layouts/administration'; 162 163 $stories = DB::table('block') 164 ->where('module_name', '=', $this->getName()) 165 ->where('gedcom_id', '=', $tree->id()) 166 ->orderBy('xref') 167 ->get(); 168 169 foreach ($stories as $story) { 170 $block_id = (int) $story->block_id; 171 172 $story->individual = Individual::getInstance($story->xref, $tree); 173 $story->title = $this->getBlockSetting($block_id, 'title'); 174 $story->languages = $this->getBlockSetting($block_id, 'languages'); 175 } 176 177 return $this->viewResponse('modules/stories/config', [ 178 'stories' => $stories, 179 'title' => $this->title() . ' — ' . $tree->title(), 180 'tree' => $tree, 181 'tree_names' => Tree::getNameList(), 182 ]); 183 } 184 185 /** 186 * @param Request $request 187 * @param Tree $tree 188 * 189 * @return Response 190 */ 191 public function getAdminEditAction(Request $request, Tree $tree): Response 192 { 193 $this->layout = 'layouts/administration'; 194 195 $block_id = (int) $request->get('block_id'); 196 197 if ($block_id === 0) { 198 // Creating a new story 199 $individual = Individual::getInstance($request->get('xref', ''), $tree); 200 $story_title = ''; 201 $story_body = ''; 202 $languages = []; 203 204 $title = I18N::translate('Add a story') . ' — ' . e($tree->title()); 205 } else { 206 // Editing an existing story 207 $xref = (string) DB::table('block') 208 ->where('block_id', '=', $block_id) 209 ->value('xref'); 210 211 $individual = Individual::getInstance($xref, $tree); 212 $story_title = $this->getBlockSetting($block_id, 'title', ''); 213 $story_body = $this->getBlockSetting($block_id, 'story_body', ''); 214 $languages = explode(',', $this->getBlockSetting($block_id, 'languages')); 215 216 $title = I18N::translate('Edit the story') . ' — ' . e($tree->title()); 217 } 218 219 return $this->viewResponse('modules/stories/edit', [ 220 'block_id' => $block_id, 221 'languages' => $languages, 222 'story_body' => $story_body, 223 'story_title' => $story_title, 224 'title' => $title, 225 'tree' => $tree, 226 'individual' => $individual, 227 ]); 228 } 229 230 /** 231 * @param Request $request 232 * @param Tree $tree 233 * 234 * @return RedirectResponse 235 */ 236 public function postAdminEditAction(Request $request, Tree $tree): RedirectResponse 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 DB::table('block') 246 ->where('block_id', '=', $block_id) 247 ->update([ 248 'gedcom_id' => $tree->id(), 249 'xref' => $xref, 250 ]); 251 } else { 252 DB::table('block')->insert([ 253 'gedcom_id' => $tree->id(), 254 'xref' => $xref, 255 'module_name' => $this->getName(), 256 'block_order' => 0, 257 ]); 258 259 $block_id = (int) DB::connection()->getPdo()->lastInsertId(); 260 } 261 262 $this->setBlockSetting($block_id, 'story_body', $story_body); 263 $this->setBlockSetting($block_id, 'title', $story_title); 264 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 265 266 $url = route('module', [ 267 'module' => $this->getName(), 268 'action' => 'Admin', 269 'ged' => $tree->name(), 270 ]); 271 272 return new RedirectResponse($url); 273 } 274 275 /** 276 * @param Request $request 277 * @param Tree $tree 278 * 279 * @return Response 280 */ 281 public function postAdminDeleteAction(Request $request, Tree $tree): Response 282 { 283 $block_id = (int) $request->get('block_id'); 284 285 DB::table('block_setting') 286 ->where('block_id', '=', $block_id) 287 ->delete(); 288 289 DB::table('block') 290 ->where('block_id', '=', $block_id) 291 ->delete(); 292 293 $url = route('module', [ 294 'module' => $this->getName(), 295 'action' => 'Admin', 296 'ged' => $tree->name(), 297 ]); 298 299 return new RedirectResponse($url); 300 } 301 302 /** 303 * @param Tree $tree 304 * 305 * @return Response 306 */ 307 public function getShowListAction(Tree $tree): Response 308 { 309 $stories = DB::table('block') 310 ->where('module_name', '=', $this->getName()) 311 ->where('gedcom_id', '=', $tree->id()) 312 ->get() 313 ->map(function (stdClass $story) use ($tree): stdClass { 314 $block_id = (int) $story->block_id; 315 316 $story->individual = Individual::getInstance($story->xref, $tree); 317 $story->title = $this->getBlockSetting($block_id, 'title'); 318 $story->languages = $this->getBlockSetting($block_id, 'languages'); 319 320 return $story; 321 })->filter(function (stdClass $story): bool { 322 // Filter non-existant and private individuals. 323 return $story->individual instanceof Individual && $story->individual->canShow(); 324 })->filter(function (stdClass $story): bool { 325 // Filter foreign languages. 326 return $story->languages === '' || in_array(WT_LOCALE, explode(',', $story->languages)); 327 }); 328 329 return $this->viewResponse('modules/stories/list', [ 330 'stories' => $stories, 331 'title' => $this->title(), 332 ]); 333 } 334} 335