1<?php 2namespace Fisharebest\Webtrees\Module; 3 4/** 5 * webtrees: online genealogy 6 * Copyright (C) 2015 webtrees development team 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18use Fisharebest\Webtrees\Auth; 19use Fisharebest\Webtrees\Controller\PageController; 20use Fisharebest\Webtrees\Database; 21use Fisharebest\Webtrees\Filter; 22use Fisharebest\Webtrees\Functions\FunctionsEdit; 23use Fisharebest\Webtrees\Functions\FunctionsPrint; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Menu; 27use Fisharebest\Webtrees\Module; 28use Fisharebest\Webtrees\Tree; 29 30/** 31 * Class StoriesModule 32 */ 33class StoriesModule extends AbstractModule implements ModuleTabInterface, ModuleConfigInterface, ModuleMenuInterface { 34 /** {@inheritdoc} */ 35 public function getTitle() { 36 return /* I18N: Name of a module */ I18N::translate('Stories'); 37 } 38 39 /** {@inheritdoc} */ 40 public function getDescription() { 41 return /* I18N: Description of the “Stories” module */ I18N::translate('Add narrative stories to individuals in the family tree.'); 42 } 43 44 /** {@inheritdoc} */ 45 public function modAction($mod_action) { 46 switch ($mod_action) { 47 case 'admin_edit': 48 $this->edit(); 49 break; 50 case 'admin_delete': 51 $this->delete(); 52 $this->config(); 53 break; 54 case 'admin_config': 55 $this->config(); 56 break; 57 case 'show_list': 58 $this->showList(); 59 break; 60 default: 61 http_response_code(404); 62 } 63 } 64 65 /** {@inheritdoc} */ 66 public function getConfigLink() { 67 return 'module.php?mod=' . $this->getName() . '&mod_action=admin_config'; 68 } 69 70 /** {@inheritdoc} */ 71 public function defaultTabOrder() { 72 return 55; 73 } 74 75 /** {@inheritdoc} */ 76 public function getTabContent() { 77 global $controller, $WT_TREE; 78 79 $block_ids = 80 Database::prepare( 81 "SELECT block_id" . 82 " FROM `##block`" . 83 " WHERE module_name=?" . 84 " AND xref=?" . 85 " AND gedcom_id=?" 86 )->execute(array( 87 $this->getName(), 88 $controller->record->getXref(), 89 $controller->record->getTree()->getTreeId(), 90 ))->fetchOneColumn(); 91 92 $html = ''; 93 foreach ($block_ids as $block_id) { 94 // Only show this block for certain languages 95 $languages = $this->getBlockSetting($block_id, 'languages'); 96 if (!$languages || in_array(WT_LOCALE, explode(',', $languages))) { 97 $html .= '<div class="story_title descriptionbox center rela">' . $this->getBlockSetting($block_id, 'title') . '</div>'; 98 $html .= '<div class="story_body optionbox">' . $this->getBlockSetting($block_id, 'story_body') . '</div>'; 99 if (Auth::isEditor($WT_TREE)) { 100 $html .= '<div class="story_edit"><a href="module.php?mod=' . $this->getName() . '&mod_action=admin_edit&block_id=' . $block_id . '">'; 101 $html .= I18N::translate('Edit story') . '</a></div>'; 102 } 103 } 104 } 105 if (Auth::isManager($WT_TREE) && !$html) { 106 $html .= '<div class="news_title center">' . $this->getTitle() . '</div>'; 107 $html .= '<div><a href="module.php?mod=' . $this->getName() . '&mod_action=admin_edit&xref=' . $controller->record->getXref() . '">'; 108 $html .= I18N::translate('Add a story') . '</a></div><br>'; 109 } 110 111 return $html; 112 } 113 114 /** {@inheritdoc} */ 115 public function hasTabContent() { 116 return $this->getTabContent() != ''; 117 } 118 119 /** {@inheritdoc} */ 120 public function isGrayedOut() { 121 global $controller; 122 123 $count_of_stories = 124 Database::prepare( 125 "SELECT COUNT(block_id)" . 126 " FROM `##block`" . 127 " WHERE module_name=?" . 128 " AND xref=?" . 129 " AND gedcom_id=?" 130 )->execute(array( 131 $this->getName(), 132 $controller->record->getXref(), 133 $controller->record->getTree()->getTreeId(), 134 ))->fetchOne(); 135 136 return $count_of_stories == 0; 137 } 138 139 /** {@inheritdoc} */ 140 public function canLoadAjax() { 141 return false; 142 } 143 144 /** {@inheritdoc} */ 145 public function getPreLoadContent() { 146 return ''; 147 } 148 149 /** 150 * Show and process a form to edit a story. 151 */ 152 private function edit() { 153 global $WT_TREE; 154 155 if (Auth::isEditor($WT_TREE)) { 156 if (Filter::postBool('save') && Filter::checkCsrf()) { 157 $block_id = Filter::postInteger('block_id'); 158 if ($block_id) { 159 Database::prepare( 160 "UPDATE `##block` SET gedcom_id=?, xref=? WHERE block_id=?" 161 )->execute(array(Filter::postInteger('gedcom_id'), Filter::post('xref', WT_REGEX_XREF), $block_id)); 162 } else { 163 Database::prepare( 164 "INSERT INTO `##block` (gedcom_id, xref, module_name, block_order) VALUES (?, ?, ?, ?)" 165 )->execute(array( 166 Filter::postInteger('gedcom_id'), 167 Filter::post('xref', WT_REGEX_XREF), 168 $this->getName(), 169 0, 170 )); 171 $block_id = Database::getInstance()->lastInsertId(); 172 } 173 $this->setBlockSetting($block_id, 'title', Filter::post('title')); 174 $this->setBlockSetting($block_id, 'story_body', Filter::post('story_body')); 175 $languages = Filter::postArray('lang'); 176 $this->setBlockSetting($block_id, 'languages', implode(',', $languages)); 177 $this->config(); 178 } else { 179 $block_id = Filter::getInteger('block_id'); 180 181 $controller = new PageController; 182 if ($block_id) { 183 $controller->setPageTitle(I18N::translate('Edit story')); 184 $title = $this->getBlockSetting($block_id, 'title'); 185 $story_body = $this->getBlockSetting($block_id, 'story_body'); 186 $xref = Database::prepare( 187 "SELECT xref FROM `##block` WHERE block_id=?" 188 )->execute(array($block_id))->fetchOne(); 189 } else { 190 $controller->setPageTitle(I18N::translate('Add a story')); 191 $title = ''; 192 $story_body = ''; 193 $xref = Filter::get('xref', WT_REGEX_XREF); 194 } 195 $controller 196 ->pageHeader() 197 ->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL) 198 ->addInlineJavascript('autocomplete();'); 199 if (Module::getModuleByName('ckeditor')) { 200 CkeditorModule::enableEditor($controller); 201 } 202 203 $individual = Individual::getInstance($xref, $WT_TREE); 204 205 ?> 206 <ol class="breadcrumb small"> 207 <li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li> 208 <li><a href="admin_modules.php"><?php echo I18N::translate('Module administration'); ?></a></li> 209 <li><a href="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_config"><?php echo $this->getTitle(); ?></a></li> 210 <li class="active"><?php echo $controller->getPageTitle(); ?></li> 211 </ol> 212 213 <h1><?php echo $controller->getPageTitle(); ?></h1> 214 215 <form class="form-horizontal" method="post" action="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_edit"> 216 <?php echo Filter::getCsrf(); ?> 217 <input type="hidden" name="save" value="1"> 218 <input type="hidden" name="block_id" value="<?php echo $block_id; ?>"> 219 <input type="hidden" name="gedcom_id" value="<?php echo $WT_TREE->getTreeId(); ?>"> 220 221 <div class="form-group"> 222 <label for="title" class="col-sm-3 control-label"> 223 <?php echo I18N::translate('Story title'); ?> 224 </label> 225 <div class="col-sm-9"> 226 <input type="text" class="form-control" name="title" id="title" value="<?php echo Filter::escapeHtml($title); ?>"> 227 </div> 228 </div> 229 230 <div class="form-group"> 231 <label for="story_body" class="col-sm-3 control-label"> 232 <?php echo I18N::translate('Story'); ?> 233 </label> 234 <div class="col-sm-9"> 235 <textarea name="story_body" id="story_body" class="html-edit form-control" rows="10"><?php echo Filter::escapeHtml($story_body); ?></textarea> 236 </div> 237 </div> 238 239 <div class="form-group"> 240 <label for="xref" class="col-sm-3 control-label"> 241 <?php echo I18N::translate('Individual'); ?> 242 </label> 243 <div class="col-sm-9"> 244 <input data-autocomplete-type="INDI" type="text" name="xref" id="xref" size="4" value="<?php echo $xref; ?>"> 245 <?php echo FunctionsPrint::printFindIndividualLink('xref'); ?> 246 <?php if ($individual): ?> 247 <?php echo $individual->formatList('span'); ?> 248 <?php endif; ?> 249 </div> 250 </div> 251 252 <div class="form-group"> 253 <label for="xref" class="col-sm-3 control-label"> 254 <?php echo I18N::translate('Show this block for which languages?'); ?> 255 </label> 256 <div class="col-sm-9"> 257 <?php echo FunctionsEdit::editLanguageCheckboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?> 258 </div> 259 </div> 260 261 <div class="form-group"> 262 <div class="col-sm-offset-3 col-sm-9"> 263 <button type="submit" class="btn btn-primary"> 264 <i class="fa fa-check"></i> 265 <?php echo I18N::translate('save'); ?> 266 </button> 267 </div> 268 </div> 269 270 </form> 271 <?php 272 } 273 } else { 274 header('Location: ' . WT_BASE_URL); 275 } 276 } 277 278 /** 279 * Respond to a request to delete a story. 280 */ 281 private function delete() { 282 global $WT_TREE; 283 284 if (Auth::isEditor($WT_TREE)) { 285 $block_id = Filter::getInteger('block_id'); 286 287 Database::prepare( 288 "DELETE FROM `##block_setting` WHERE block_id=?" 289 )->execute(array($block_id)); 290 291 Database::prepare( 292 "DELETE FROM `##block` WHERE block_id=?" 293 )->execute(array($block_id)); 294 } else { 295 header('Location: ' . WT_BASE_URL); 296 exit; 297 } 298 } 299 300 /** 301 * The admin view - list, create, edit, delete stories. 302 */ 303 private function config() { 304 global $WT_TREE; 305 306 $controller = new PageController; 307 $controller 308 ->restrictAccess(Auth::isAdmin()) 309 ->setPageTitle($this->getTitle()) 310 ->pageHeader() 311 ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL) 312 ->addExternalJavascript(WT_DATATABLES_BOOTSTRAP_JS_URL) 313 ->addInlineJavascript(' 314 jQuery("#story_table").dataTable({ 315 ' . I18N::datatablesI18N() . ', 316 autoWidth: false, 317 paging: true, 318 pagingType: "full_numbers", 319 lengthChange: true, 320 filter: true, 321 info: true, 322 sorting: [[0,"asc"]], 323 columns: [ 324 /* 0-name */ null, 325 /* 1-NAME */ null, 326 /* 2-NAME */ { sortable:false }, 327 /* 3-NAME */ { sortable:false } 328 ] 329 }); 330 '); 331 332 $stories = Database::prepare( 333 "SELECT block_id, xref" . 334 " FROM `##block` b" . 335 " WHERE module_name=?" . 336 " AND gedcom_id=?" . 337 " ORDER BY xref" 338 )->execute(array($this->getName(), $WT_TREE->getTreeId()))->fetchAll(); 339 340 ?> 341 <ol class="breadcrumb small"> 342 <li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li> 343 <li><a href="admin_modules.php"><?php echo I18N::translate('Module administration'); ?></a></li> 344 <li class="active"><?php echo $controller->getPageTitle(); ?></li> 345 </ol> 346 347 <h1><?php echo $controller->getPageTitle(); ?></h1> 348 349 <form class="form form-inline"> 350 <label for="ged" class="sr-only"> 351 <?php echo I18N::translate('Family tree'); ?> 352 </label> 353 <input type="hidden" name="mod" value="<?php echo $this->getName(); ?>"> 354 <input type="hidden" name="mod_action" value="admin_config"> 355 <?php echo FunctionsEdit::selectEditControl('ged', Tree::getNameList(), null, $WT_TREE->getName(), 'class="form-control"'); ?> 356 <input type="submit" class="btn btn-primary" value="<?php echo I18N::translate('show'); ?>"> 357 </form> 358 359 <p> 360 <a href="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_edit" class="btn btn-default"> 361 <i class="fa fa-plus"></i> 362 <?php echo I18N::translate('Add a story'); ?> 363 </a> 364 </p> 365 366 <table class="table table-bordered table-condensed"> 367 <thead> 368 <tr> 369 <th><?php echo I18N::translate('Story title'); ?></th> 370 <th><?php echo I18N::translate('Individual'); ?></th> 371 <th><?php echo I18N::translate('Edit'); ?></th> 372 <th><?php echo I18N::translate('Delete'); ?></th> 373 </tr> 374 </thead> 375 <tbody> 376 <?php foreach ($stories as $story): ?> 377 <tr> 378 <td> 379 <?php echo Filter::escapeHtml($this->getBlockSetting($story->block_id, 'title')); ?> 380 </td> 381 <td> 382 <?php $individual = Individual::getInstance($story->xref, $WT_TREE); ?> 383 <?php if ($individual): ?> 384 <a href="<?php echo $individual->getHtmlUrl(); ?>#stories"> 385 <?php echo $individual->getFullName(); ?> 386 </a> 387 <?php else: ?> 388 <?php echo $story->xref; ?> 389 <?php endif; ?> 390 </td> 391 <td> 392 <a href="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_edit&block_id=<?php echo $story->block_id; ?>"> 393 <div class="icon-edit"> </div> 394 </a> 395 </td> 396 <td> 397 <a 398 href="module.php?mod=<?php echo $this->getName(); ?>&mod_action=admin_delete&block_id=<?php echo $story->block_id; ?>" 399 onclick="return confirm('<?php echo I18N::translate('Are you sure you want to delete this story?'); ?>');" 400 > 401 <div class="icon-delete"> </div> 402 </a> 403 </td> 404 </tr> 405 <?php endforeach; ?> 406 </tbody> 407 </table> 408 <?php 409 } 410 411 /** 412 * Show the list of stories 413 */ 414 private function showList() { 415 global $controller, $WT_TREE; 416 417 $controller = new PageController; 418 $controller 419 ->setPageTitle($this->getTitle()) 420 ->pageHeader() 421 ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL) 422 ->addInlineJavascript(' 423 jQuery("#story_table").dataTable({ 424 dom: \'<"H"pf<"dt-clear">irl>t<"F"pl>\', 425 ' . I18N::datatablesI18N() . ', 426 autoWidth: false, 427 paging: true, 428 pagingType: "full_numbers", 429 lengthChange: true, 430 filter: true, 431 info: true, 432 jQueryUI: true, 433 sorting: [[0,"asc"]], 434 columns: [ 435 /* 0-name */ null, 436 /* 1-NAME */ null 437 ] 438 }); 439 '); 440 441 $stories = Database::prepare( 442 "SELECT block_id, xref" . 443 " FROM `##block` b" . 444 " WHERE module_name=?" . 445 " AND gedcom_id=?" . 446 " ORDER BY xref" 447 )->execute(array($this->getName(), $WT_TREE->getTreeId()))->fetchAll(); 448 449 echo '<h2 class="center">', I18N::translate('Stories'), '</h2>'; 450 if (count($stories) > 0) { 451 echo '<table id="story_table" class="width100">'; 452 echo '<thead><tr> 453 <th>', I18N::translate('Story title'), '</th> 454 <th>', I18N::translate('Individual'), '</th> 455 </tr></thead> 456 <tbody>'; 457 foreach ($stories as $story) { 458 $indi = Individual::getInstance($story->xref, $WT_TREE); 459 $story_title = $this->getBlockSetting($story->block_id, 'title'); 460 $languages = $this->getBlockSetting($story->block_id, 'languages'); 461 if (!$languages || in_array(WT_LOCALE, explode(',', $languages))) { 462 if ($indi) { 463 if ($indi->canShow()) { 464 echo '<tr><td><a href="' . $indi->getHtmlUrl() . '#stories">' . $story_title . '</a></td><td><a href="' . $indi->getHtmlUrl() . '#stories">' . $indi->getFullName() . '</a></td></tr>'; 465 } 466 } else { 467 echo '<tr><td>', $story_title, '</td><td class="error">', $story->xref, '</td></tr>'; 468 } 469 } 470 } 471 echo '</tbody></table>'; 472 } 473 } 474 475 /** {@inheritdoc} */ 476 public function defaultMenuOrder() { 477 return 30; 478 } 479 480 /** {@inheritdoc} */ 481 public function defaultAccessLevel() { 482 return Auth::PRIV_HIDE; 483 } 484 485 /** {@inheritdoc} */ 486 public function getMenu() { 487 if (Auth::isSearchEngine()) { 488 return null; 489 } 490 491 $menu = new Menu($this->getTitle(), 'module.php?mod=' . $this->getName() . '&mod_action=show_list', 'menu-story'); 492 493 return $menu; 494 } 495} 496