1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2016 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\Census\Census; 19use Fisharebest\Webtrees\Census\CensusInterface; 20use Fisharebest\Webtrees\Controller\SimpleController; 21use Fisharebest\Webtrees\Family; 22use Fisharebest\Webtrees\Filter; 23use Fisharebest\Webtrees\Functions\Functions; 24use Fisharebest\Webtrees\Functions\FunctionsDb; 25use Fisharebest\Webtrees\GedcomRecord; 26use Fisharebest\Webtrees\GedcomTag; 27use Fisharebest\Webtrees\I18N; 28use Fisharebest\Webtrees\Individual; 29use Fisharebest\Webtrees\Menu; 30use Fisharebest\Webtrees\Note; 31 32/** 33 * Class CensusAssistantModule 34 */ 35class CensusAssistantModule extends AbstractModule { 36 /** {@inheritdoc} */ 37 public function getTitle() { 38 return /* I18N: Name of a module */ I18N::translate('Census assistant'); 39 } 40 41 /** {@inheritdoc} */ 42 public function getDescription() { 43 return /* I18N: Description of the “Census assistant” module */ I18N::translate('An alternative way to enter census transcripts and link them to individuals.'); 44 } 45 46 /** 47 * This is a general purpose hook, allowing modules to respond to routes 48 * of the form module.php?mod=FOO&mod_action=BAR 49 * 50 * @param string $mod_action 51 */ 52 public function modAction($mod_action) { 53 switch ($mod_action) { 54 case 'census_find': 55 self::censusFind(); 56 break; 57 case 'media_find': 58 self::mediaFind(); 59 break; 60 case 'media_query_3a': 61 self::mediaQuery(); 62 break; 63 default: 64 http_response_code(404); 65 } 66 } 67 68 /** 69 * Find an individual. 70 */ 71 private static function censusFind() { 72 global $WT_TREE; 73 74 $controller = new SimpleController; 75 $filter = Filter::get('filter'); 76 $action = Filter::get('action'); 77 $census = Filter::get('census'); 78 $census = new $census; 79 80 $controller 81 ->restrictAccess($census instanceof CensusInterface) 82 ->setPageTitle(I18N::translate('Find an individual')) 83 ->pageHeader(); 84 85 echo '<table class="list_table width90" border="0">'; 86 echo '<tr><td style="padding: 10px;" class="facts_label03 width90">'; 87 echo I18N::translate('Find an individual'); 88 echo '</td>'; 89 echo '</table>'; 90 echo '<br>'; 91 92 if ($action == 'filter') { 93 $filter = trim($filter); 94 $filter_array = explode(' ', preg_replace('/ {2,}/', ' ', $filter)); 95 96 // Output Individual for GEDFact Assistant ====================== 97 echo '<table class="list_table width90">'; 98 $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE)); 99 if ($myindilist) { 100 echo '<tr><td class="list_value_wrap"><ul>'; 101 usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare'); 102 foreach ($myindilist as $indi) { 103 echo '<li>'; 104 echo '<a href="#" onclick="window.opener.appendCensusRow(\'' . Filter::escapeJs(self::censusTableRow($census, $indi, null)) . '\'); window.close();">'; 105 echo '<b>' . $indi->getFullName() . '</b>'; 106 echo '</a>'; 107 echo $indi->formatFirstMajorFact(WT_EVENTS_BIRT, 1); 108 echo $indi->formatFirstMajorFact(WT_EVENTS_DEAT, 1); 109 echo '<hr>'; 110 echo '</li>'; 111 } 112 echo '</ul></td></tr>'; 113 } else { 114 echo '<tr><td class="list_value_wrap">'; 115 echo I18N::translate('No results found.'); 116 echo '</td></tr>'; 117 } 118 echo '<tr><td>'; 119 echo '<button onclick="window.close();">', I18N::translate('close'), '</button>'; 120 echo '</td></tr>'; 121 echo '</table>'; 122 } 123 } 124 125 /** 126 * Find a media object. 127 */ 128 private static function mediaFind() { 129 global $WT_TREE; 130 131 $controller = new SimpleController; 132 $filter = Filter::get('filter'); 133 $multiple = Filter::getBool('multiple'); 134 135 $controller 136 ->setPageTitle(I18N::translate('Find an individual')) 137 ->pageHeader(); 138 139 ?> 140 <script> 141 function pasterow(id, name, gend, yob, age, bpl) { 142 window.opener.opener.insertRowToTable(id, name, '', gend, '', yob, age, 'Y', '', bpl); 143 } 144 145 function pasteid(id, name, thumb) { 146 if (thumb) { 147 window.opener.paste_id(id, name, thumb); 148 <?php if (!$multiple) { echo "window.close();"; } ?> 149 } else { 150 // GEDFact_assistant ======================== 151 if (window.opener.document.getElementById('addlinkQueue')) { 152 window.opener.insertRowToTable(id, name); 153 } 154 window.opener.paste_id(id); 155 if (window.opener.pastename) { 156 window.opener.pastename(name); 157 } 158 <?php if (!$multiple) { echo "window.close();"; } ?> 159 } 160 } 161 function checknames(frm) { 162 if (document.forms[0].subclick) { 163 button = document.forms[0].subclick.value; 164 } else { 165 button = ""; 166 } 167 if (frm.filter.value.length < 2 && button !== "all") { 168 alert("<?php echo I18N::translate('Please enter more than one character.'); ?>"); 169 frm.filter.focus(); 170 return false; 171 } 172 if (button=="all") { 173 frm.filter.value = ""; 174 } 175 return true; 176 } 177 </script> 178 179 <?php 180 echo '<div>'; 181 echo '<table class="list_table width90" border="0">'; 182 echo '<tr><td style="padding: 10px;" class="facts_label03 width90">'; // start column for find text header 183 echo $controller->getPageTitle(); 184 echo '</td>'; 185 echo '</tr>'; 186 echo '</table>'; 187 echo '<br>'; 188 echo '<button onclick="window.close();">', I18N::translate('close'), '</button>'; 189 echo '<br>'; 190 191 $filter = trim($filter); 192 $filter_array = explode(' ', preg_replace('/ {2,}/', ' ', $filter)); 193 echo '<table class="tabs_table width90"><tr>'; 194 $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE)); 195 if ($myindilist) { 196 echo '<td class="list_value_wrap"><ul>'; 197 usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare'); 198 foreach ($myindilist as $indi) { 199 $nam = Filter::escapeHtml($indi->getFullName()); 200 echo "<li><a href=\"#\" onclick=\"pasterow( 201 '" . $indi->getXref() . "' , 202 '" . $nam . "' , 203 '" . $indi->getSex() . "' , 204 '" . $indi->getBirthYear() . "' , 205 '" . (1901 - $indi->getBirthYear()) . "' , 206 '" . $indi->getBirthPlace() . "'); return false;\"> 207 <b>" . $indi->getFullName() . "</b> "; 208 209 $born = GedcomTag::getLabel('BIRT'); 210 echo "</span><br><span class=\"list_item\">", $born, " ", $indi->getBirthYear(), " ", $indi->getBirthPlace(), "</span></a></li>"; 211 echo "<hr>"; 212 } 213 echo '</ul></td></tr><tr><td class="list_label">', I18N::translate('Total individuals: %s', count($myindilist)), '</tr></td>'; 214 } else { 215 echo "<td class=\"list_value_wrap\">"; 216 echo I18N::translate('No results found.'); 217 echo "</td></tr>"; 218 } 219 echo "</table>"; 220 echo '</div>'; 221 } 222 223 /** 224 * Search for a media object. 225 */ 226 private static function mediaQuery() { 227 global $WT_TREE; 228 229 $iid2 = Filter::get('iid', WT_REGEX_XREF); 230 231 $controller = new SimpleController; 232 $controller 233 ->setPageTitle(I18N::translate('Link to an existing media object')) 234 ->pageHeader(); 235 236 $record = GedcomRecord::getInstance($iid2, $WT_TREE); 237 if ($record) { 238 $headjs = ''; 239 if ($record instanceof Family) { 240 if ($record->getHusband()) { 241 $headjs = $record->getHusband()->getXref(); 242 } elseif ($record->getWife()) { 243 $headjs = $record->getWife()->getXref(); 244 } 245 } 246 ?> 247 <script> 248 function insertId() { 249 if (window.opener.document.getElementById('addlinkQueue')) { 250 // alert('Please move this alert window and examine the contents of the pop-up window, then click OK') 251 window.opener.insertRowToTable('<?php echo $record->getXref(); ?>', '<?php echo htmlspecialchars($record->getFullName()); ?>', '<?php echo $headjs; ?>'); 252 window.close(); 253 } 254 } 255 </script> 256 <?php 257 } else { 258 ?> 259 <script> 260 function insertId() { 261 window.opener.alert('<?php echo $iid2; ?> - <?php echo I18N::translate('Not a valid individual, family, or source ID'); ?>'); 262 window.close(); 263 } 264 </script> 265 <?php 266 } 267 ?> 268 <script>window.onLoad = insertId();</script> 269 <?php 270 } 271 272 /** 273 * Convert custom markup into HTML 274 * 275 * @param Note $note 276 * 277 * @return string 278 */ 279 public static function formatCensusNote(Note $note) { 280 global $WT_TREE; 281 282 283 $headers = array(); 284 foreach (Census::allCensusPlaces() as $allCensusesOfPlace) { 285 foreach ($allCensusesOfPlace->allCensusDates() as $census) { 286 foreach ($census->columns() as $column) { 287 if ($column->abbreviation()) { 288 $headers[$column->abbreviation()] = $column->title(); 289 } 290 } 291 } 292 } 293 294 if (preg_match('/(.*)((?:\n.*)*)\n\.start_formatted_area\.\n(.*)((?:\n.*)*)\n.end_formatted_area\.((?:\n.*)*)/', $note->getNote(), $match)) { 295 // This looks like a census-assistant shared note 296 $title = Filter::escapeHtml($match[1]); 297 $preamble = Filter::escapeHtml($match[2]); 298 $header = Filter::escapeHtml($match[3]); 299 $data = Filter::escapeHtml($match[4]); 300 $postamble = Filter::escapeHtml($match[5]); 301 302 $fmt_headers = array(); 303 foreach ($headers as $key => $value) { 304 $fmt_headers[$key] = '<span title="' . Filter::escapeHtml($value) . '">' . $key . '</span>'; 305 } 306 307 // Substitue header labels and format as HTML 308 $thead = '<tr><th>' . strtr(str_replace('|', '</th><th>', $header), $fmt_headers) . '</th></tr>'; 309 $thead = str_replace('.b.', '', $thead); 310 311 // Format data as HTML 312 $tbody = ''; 313 foreach (explode("\n", $data) as $row) { 314 $tbody .= '<tr>'; 315 foreach (explode('|', $row) as $column) { 316 $tbody .= '<td>' . $column . '</td>'; 317 } 318 $tbody .= '</tr>'; 319 } 320 321 return 322 $title . "\n" . // The newline allows the framework to expand the details and turn the first line into a link 323 '<p>' . $preamble . '</p>' . 324 '<table class="table-census-assistant">' . 325 '<thead>' . $thead . '</thead>' . 326 '<tbody>' . $tbody . '</tbody>' . 327 '</table>' . 328 '<p>' . $postamble . '</p>'; 329 } else { 330 // Not a census-assistant shared note - apply default formatting 331 return Filter::formatText($note->getNote(), $WT_TREE); 332 } 333 } 334 335 /** 336 * Generate an HTML row of data for the census header 337 * 338 * Add prefix cell (store XREF and drag/drop) 339 * Add suffix cell (delete button) 340 * 341 * @param CensusInterface $census 342 * 343 * @return string 344 */ 345 public static function censusTableHeader(CensusInterface $census) { 346 $html = ''; 347 foreach ($census->columns() as $column) { 348 $html .= '<th title="' . $column->title() . '">' . $column->abbreviation() . '</th>'; 349 } 350 351 return '<tr><th hidden></th>' . $html . '<th></th></th></tr>'; 352 } 353 354 /** 355 * Generate an HTML row of data for the census 356 * 357 * Add prefix cell (store XREF and drag/drop) 358 * Add suffix cell (delete button) 359 * 360 * @param CensusInterface $census 361 * 362 * @return string 363 */ 364 public static function censusTableEmptyRow(CensusInterface $census) { 365 return '<tr><td hidden></td>' . str_repeat('<td><input type="text"></td>', count($census->columns())) . '<td><a class="icon-remove" href="#" title="' . I18N::translate('Remove') . '"></a></td></tr>'; 366 } 367 368 /** 369 * Generate an HTML row of data for the census 370 * 371 * Add prefix cell (store XREF and drag/drop) 372 * Add suffix cell (delete button) 373 * 374 * @param CensusInterface $census 375 * @param Individual $individual 376 * @param Individual|null $head 377 * 378 * @return string 379 */ 380 public static function censusTableRow(CensusInterface $census, Individual $individual, Individual $head = null) { 381 $html = ''; 382 foreach ($census->columns() as $column) { 383 $html .= '<td><input type="text" value="' . $column->generate($individual, $head) . '"></td>'; 384 } 385 386 return '<tr><td hidden>' . $individual->getXref() . '</td>' . $html . '<td><a class="icon-remove" href="#" title="' . I18N::translate('Remove') . '"></a></td></tr>'; 387 } 388 389 /** 390 * Create a family on the census navigator. 391 * 392 * @param CensusInterface $census 393 * @param Family $family 394 * @param Individual $head 395 * 396 * @return string 397 */ 398 public static function censusNavigatorFamily(CensusInterface $census, Family $family, Individual $head) { 399 $headImg2 = '<i class="icon-button_head" title="' . I18N::translate('Click to choose individual as head of family.') . '"></i>'; 400 401 foreach ($family->getSpouses() as $spouse) { 402 $menu = new Menu(Functions::getCloseRelationshipName($head, $spouse)); 403 foreach ($spouse->getChildFamilies() as $grandparents) { 404 foreach ($grandparents->getSpouses() as $grandparent) { 405 $submenu = new Menu( 406 Functions::getCloseRelationshipName($head, $grandparent) . ' - ' . $grandparent->getFullName(), 407 '#', 408 '', 409 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $grandparent, $head)) . '");') 410 ); 411 $submenu->addClass('submenuitem', ''); 412 $menu->addSubmenu($submenu); 413 $menu->addClass('', 'submenu'); 414 } 415 } 416 417 ?> 418 <tr> 419 <td class="optionbox"> 420 <?php echo $menu->getMenu(); ?> 421 </td> 422 <td class="facts_value nowrap"> 423 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $spouse, $head)); ?>');"> 424 <?php echo $spouse->getFullName(); ?> 425 </a> 426 </td> 427 <td class="facts_value"> 428 <a href="edit_interface.php?action=addnewnote_assisted&noteid=newnote&xref=<?php echo $spouse->getXref(); ?>&gedcom=<?php echo $spouse->getTree()->getNameUrl(); ?>&census=<?php echo get_class($census); ?>"> 429 <?php echo $headImg2; ?> 430 </a> 431 </td> 432 </tr> 433 <?php 434 } 435 436 foreach ($family->getChildren() as $child) { 437 $menu = new Menu(Functions::getCloseRelationshipName($head, $child)); 438 foreach ($child->getSpouseFamilies() as $spouse_family) { 439 foreach ($spouse_family->getSpouses() as $spouse_family_spouse) { 440 if ($spouse_family_spouse != $child) { 441 $submenu = new Menu( 442 Functions::getCloseRelationshipName($head, $spouse_family_spouse) . ' - ' . $spouse_family_spouse->getFullName(), 443 '#', 444 '', 445 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_spouse, $head)) . '");') 446 ); 447 $submenu->addClass('submenuitem', ''); 448 $menu->addSubmenu($submenu); 449 $menu->addClass('', 'submenu'); 450 } 451 } 452 foreach ($spouse_family->getChildren() as $spouse_family_child) { 453 $submenu = new Menu( 454 Functions::getCloseRelationshipName($head, $spouse_family_child) . ' - ' . $spouse_family_child->getFullName(), 455 '#', 456 '', 457 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_child, $head)) . '");') 458 ); 459 $submenu->addClass('submenuitem', ''); 460 $menu->addSubmenu($submenu); 461 $menu->addClass('', 'submenu'); 462 } 463 } 464 465 ?> 466 <tr> 467 <td class="optionbox"> 468 <?php echo $menu->getMenu(); ?> 469 </td> 470 <td class="facts_value"> 471 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $child, $head)); ?>');"> 472 <?php echo $child->getFullName(); ?> 473 </a> 474 </td> 475 <td class="facts_value"> 476 <a href="edit_interface.php?action=addnewnote_assisted&noteid=newnote&xref=<?php echo $child->getXref(); ?>&gedcom=<?php echo $child->getTree()->getNameUrl(); ?>&census=<?php echo get_class($census); ?>"> 477 <?php echo $headImg2; ?> 478 </a> 479 </td> 480 </tr> 481 <?php 482 } 483 echo '<tr><td><br></td></tr>'; 484 } 485} 486