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