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\CensusInterface; 19use Fisharebest\Webtrees\Controller\SimpleController; 20use Fisharebest\Webtrees\Family; 21use Fisharebest\Webtrees\Filter; 22use Fisharebest\Webtrees\Functions\Functions; 23use Fisharebest\Webtrees\Functions\FunctionsDb; 24use Fisharebest\Webtrees\GedcomRecord; 25use Fisharebest\Webtrees\GedcomTag; 26use Fisharebest\Webtrees\I18N; 27use Fisharebest\Webtrees\Individual; 28use Fisharebest\Webtrees\Menu; 29use Fisharebest\Webtrees\Note; 30 31/** 32 * Class CensusAssistantModule 33 */ 34class CensusAssistantModule extends AbstractModule { 35 /** {@inheritdoc} */ 36 public function getTitle() { 37 return /* I18N: Name of a module */ I18N::translate('Census assistant'); 38 } 39 40 /** {@inheritdoc} */ 41 public function getDescription() { 42 return /* I18N: Description of the “Census assistant” module */ I18N::translate('An alternative way to enter census transcripts and link them to individuals.'); 43 } 44 45 /** 46 * This is a general purpose hook, allowing modules to respond to routes 47 * of the form module.php?mod=FOO&mod_action=BAR 48 * 49 * @param string $mod_action 50 */ 51 public function modAction($mod_action) { 52 switch ($mod_action) { 53 case 'census_find': 54 self::censusFind(); 55 break; 56 case 'media_find': 57 self::mediaFind(); 58 break; 59 case 'media_query_3a': 60 self::mediaQuery(); 61 break; 62 default: 63 http_response_code(404); 64 } 65 } 66 67 /** 68 * Find an individual. 69 */ 70 private static function censusFind() { 71 global $WT_TREE; 72 73 $controller = new SimpleController; 74 $filter = Filter::get('filter'); 75 $action = Filter::get('action'); 76 $census = Filter::get('census'); 77 $census = new $census; 78 79 $controller 80 ->restrictAccess($census instanceof CensusInterface) 81 ->setPageTitle(I18N::translate('Find an individual')) 82 ->pageHeader(); 83 84 echo '<table class="list_table width90" border="0">'; 85 echo '<tr><td style="padding: 10px;" class="facts_label03 width90">'; 86 echo I18N::translate('Find an individual'); 87 echo '</td>'; 88 echo '</table>'; 89 echo '<br>'; 90 91 if ($action == 'filter') { 92 $filter = trim($filter); 93 $filter_array = explode(' ', preg_replace('/ {2,}/', ' ', $filter)); 94 95 // Output Individual for GEDFact Assistant ====================== 96 echo '<table class="list_table width90">'; 97 $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE)); 98 if ($myindilist) { 99 echo '<tr><td class="list_value_wrap"><ul>'; 100 usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare'); 101 foreach ($myindilist as $indi) { 102 echo '<li>'; 103 echo '<a href="#" onclick="window.opener.appendCensusRow(\'' . Filter::escapeJs(self::censusTableRow($census, $indi, null)) . '\'); window.close();">'; 104 echo '<b>' . $indi->getFullName() . '</b>'; 105 echo '</a>'; 106 echo $indi->formatFirstMajorFact(WT_EVENTS_BIRT, 1); 107 echo $indi->formatFirstMajorFact(WT_EVENTS_DEAT, 1); 108 echo '<hr>'; 109 echo '</li>'; 110 } 111 echo '</ul></td></tr>'; 112 } else { 113 echo '<tr><td class="list_value_wrap">'; 114 echo I18N::translate('No results found.'); 115 echo '</td></tr>'; 116 } 117 echo '<tr><td>'; 118 echo '<button onclick="window.close();">', I18N::translate('close'), '</button>'; 119 echo '</td></tr>'; 120 echo '</table>'; 121 } 122 } 123 124 /** 125 * Find a media object. 126 */ 127 private static function mediaFind() { 128 global $WT_TREE; 129 130 $controller = new SimpleController; 131 $filter = Filter::get('filter'); 132 $multiple = Filter::getBool('multiple'); 133 134 $controller 135 ->setPageTitle(I18N::translate('Find an individual')) 136 ->pageHeader(); 137 138 ?> 139 <script> 140 function pasterow(id, name, gend, yob, age, bpl) { 141 window.opener.opener.insertRowToTable(id, name, '', gend, '', yob, age, 'Y', '', bpl); 142 } 143 144 function pasteid(id, name, thumb) { 145 if (thumb) { 146 window.opener.paste_id(id, name, thumb); 147 <?php if (!$multiple) { echo "window.close();"; } ?> 148 } else { 149 // GEDFact_assistant ======================== 150 if (window.opener.document.getElementById('addlinkQueue')) { 151 window.opener.insertRowToTable(id, name); 152 } 153 window.opener.paste_id(id); 154 if (window.opener.pastename) { 155 window.opener.pastename(name); 156 } 157 <?php if (!$multiple) { echo "window.close();"; } ?> 158 } 159 } 160 function checknames(frm) { 161 if (document.forms[0].subclick) { 162 button = document.forms[0].subclick.value; 163 } else { 164 button = ""; 165 } 166 if (frm.filter.value.length < 2 && button !== "all") { 167 alert("<?php echo I18N::translate('Please enter more than one character.'); ?>"); 168 frm.filter.focus(); 169 return false; 170 } 171 if (button=="all") { 172 frm.filter.value = ""; 173 } 174 return true; 175 } 176 </script> 177 178 <?php 179 echo '<div>'; 180 echo '<table class="list_table width90" border="0">'; 181 echo '<tr><td style="padding: 10px;" class="facts_label03 width90">'; // start column for find text header 182 echo $controller->getPageTitle(); 183 echo '</td>'; 184 echo '</tr>'; 185 echo '</table>'; 186 echo '<br>'; 187 echo '<button onclick="window.close();">', I18N::translate('close'), '</button>'; 188 echo '<br>'; 189 190 $filter = trim($filter); 191 $filter_array = explode(' ', preg_replace('/ {2,}/', ' ', $filter)); 192 echo '<table class="tabs_table width90"><tr>'; 193 $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE)); 194 if ($myindilist) { 195 echo '<td class="list_value_wrap"><ul>'; 196 usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare'); 197 foreach ($myindilist as $indi) { 198 $nam = Filter::escapeHtml($indi->getFullName()); 199 echo "<li><a href=\"#\" onclick=\"pasterow( 200 '" . $indi->getXref() . "' , 201 '" . $nam . "' , 202 '" . $indi->getSex() . "' , 203 '" . $indi->getBirthYear() . "' , 204 '" . (1901 - $indi->getBirthYear()) . "' , 205 '" . $indi->getBirthPlace() . "'); return false;\"> 206 <b>" . $indi->getFullName() . "</b> "; 207 208 $born = GedcomTag::getLabel('BIRT'); 209 echo "</span><br><span class=\"list_item\">", $born, " ", $indi->getBirthYear(), " ", $indi->getBirthPlace(), "</span></a></li>"; 210 echo "<hr>"; 211 } 212 echo '</ul></td></tr><tr><td class="list_label">', I18N::translate('Total individuals: %s', count($myindilist)), '</tr></td>'; 213 } else { 214 echo "<td class=\"list_value_wrap\">"; 215 echo I18N::translate('No results found.'); 216 echo "</td></tr>"; 217 } 218 echo "</table>"; 219 echo '</div>'; 220 } 221 222 /** 223 * Search for a media object. 224 */ 225 private static function mediaQuery() { 226 global $WT_TREE; 227 228 $iid2 = Filter::get('iid', WT_REGEX_XREF); 229 230 $controller = new SimpleController; 231 $controller 232 ->setPageTitle(I18N::translate('Link to an existing media object')) 233 ->pageHeader(); 234 235 $record = GedcomRecord::getInstance($iid2, $WT_TREE); 236 if ($record) { 237 $headjs = ''; 238 if ($record instanceof Family) { 239 if ($record->getHusband()) { 240 $headjs = $record->getHusband()->getXref(); 241 } elseif ($record->getWife()) { 242 $headjs = $record->getWife()->getXref(); 243 } 244 } 245 ?> 246 <script> 247 function insertId() { 248 if (window.opener.document.getElementById('addlinkQueue')) { 249 // alert('Please move this alert window and examine the contents of the pop-up window, then click OK') 250 window.opener.insertRowToTable('<?php echo $record->getXref(); ?>', '<?php echo htmlspecialchars($record->getFullName()); ?>', '<?php echo $headjs; ?>'); 251 window.close(); 252 } 253 } 254 </script> 255 <?php 256 } else { 257 ?> 258 <script> 259 function insertId() { 260 window.opener.alert('<?php echo $iid2; ?> - <?php echo I18N::translate('Not a valid individual, family, or source ID'); ?>'); 261 window.close(); 262 } 263 </script> 264 <?php 265 } 266 ?> 267 <script>window.onLoad = insertId();</script> 268 <?php 269 } 270 271 /** 272 * Convert custom markup into HTML 273 * 274 * @param Note $note 275 * 276 * @return string 277 */ 278 public static function formatCensusNote(Note $note) { 279 global $WT_TREE; 280 281 $headers = array( 282 'AgM' => 'Age at first marriage', 283 'Age' => 'Age at last birthday', 284 'Assets' => 'Assets = Owned,Rented - Value,Rent - Radio - Farm', 285 'BIC' => 'Born in County', 286 'BOE' => 'Born outside England', 287 'BP' => 'Birthplace - (Chapman format)', 288 'Birthplace' => 'Birthplace (Full format)', 289 'Bmth' => 'Month of birth - If born within Census year', 290 'ChB' => 'Children born alive', 291 'ChD' => 'Children who have died', 292 'ChL' => 'Children still living', 293 'DOB' => 'Date of birth', 294 'Edu' => 'Education - At School, Can Read, Can Write', // or "Cannot Read, Cannot Write" ?? 295 'EmD' => 'Employed?', 296 'EmN' => 'Unemployed?', 297 'EmR' => 'Employer?', 298 'Employ' => 'Employment', 299 'Eng?' => 'English spoken?', 300 'EngL' => 'English spoken?, if not, Native Language', 301 'FBP' => 'Father’s Birthplace - (Chapman format)', 302 'Health' => 'Health - 1.Blind, 2.Deaf & Dumb, 3.Idiotic, 4.Insane, 5.Disabled etc', 303 'Home' => 'Home Ownership - Owned/Rented-Free/Mortgaged-Farm/House-Farm Schedule number', 304 'Industry' => 'Industry', 305 'Infirm' => 'Infirmities - 1. Deaf & Dumb, 2. Blind, 3. Lunatic, 4. Imbecile/feeble-minded', 306 'Lang' => 'If Foreign Born - Native Language', 307 'MBP' => 'Mother’s Birthplace - (Chapman format)', 308 'MC' => 'Marital Condition - Married, Single, Unmarried, Widowed or Divorced', 309 'Mmth' => 'Month of marriage - If married during Census Year', 310 'MnsE' => 'Months employed during Census Year', 311 'MnsU' => 'Months unemployed during Census Year', 312 'N/A' => 'If Foreign Born - Naturalized, Alien', 313 'NL' => 'If Foreign Born - Native Language', 314 'Name' => 'Full Name or Married name if married', 315 'Occupation' => 'Occupation', 316 'Par' => 'Parentage - Father if foreign born, Mother if foreign born', 317 'Race' => 'Race or Color - Black, White, Mulatto, Asian, Indian, Chinese etc', 318 'Relation' => 'Relationship to Head of Household', 319 'Sex' => 'Male or Female', 320 'Situ' => 'Situation - Disease, Infirmity, Convict, Pauper etc', 321 'Ten' => 'Tenure - Owned/Rented, (if owned)Free/Morgaged', 322 'Vet' => 'War Veteran?', 323 'WH' => 'Working at Home?', 324 'War' => 'War or Expedition', 325 'WksU' => 'Weeks unemployed during Census Year', 326 'YOI' => 'If Foreign Born - Year of immigration', 327 'YON' => 'If Foreign Born - Year of naturalization', 328 'YUS' => 'If Foreign Born - Years in the USA', 329 'YrsM' => 'Years Married, or Y if married in Census Year', 330 'Schedule' => 'Schedule Number', 331 'SubNum' => 'Schedule sub number', 332 'Role' => 'for institutions only – for example, Officer, Visitor, Servant, Patient, Inmate' 333 ); 334 335 if (preg_match('/(.*)((?:\n.*)*)\n\.start_formatted_area\.\n(.*)((?:\n.*)*)\n.end_formatted_area\.((?:\n.*)*)/', $note->getNote(), $match)) { 336 // This looks like a census-assistant shared note 337 $title = Filter::escapeHtml($match[1]); 338 $preamble = Filter::escapeHtml($match[2]); 339 $header = Filter::escapeHtml($match[3]); 340 $data = Filter::escapeHtml($match[4]); 341 $postamble = Filter::escapeHtml($match[5]); 342 343 $fmt_headers = array(); 344 foreach ($headers as $key => $value) { 345 $fmt_headers[$key] = '<span title="' . Filter::escapeHtml($value) . '">' . $key . '</span>'; 346 } 347 348 // Substitue header labels and format as HTML 349 $thead = '<tr><th>' . strtr(str_replace('|', '</th><th>', $header), $fmt_headers) . '</th></tr>'; 350 $thead = str_replace('.b.', '', $thead); 351 352 // Format data as HTML 353 $tbody = ''; 354 foreach (explode("\n", $data) as $row) { 355 $tbody .= '<tr>'; 356 foreach (explode('|', $row) as $column) { 357 $tbody .= '<td>' . $column . '</td>'; 358 } 359 $tbody .= '</tr>'; 360 } 361 362 return 363 $title . "\n" . // The newline allows the framework to expand the details and turn the first line into a link 364 '<p>' . $preamble . '</p>' . 365 '<table class="table-census-assistant">' . 366 '<thead>' . $thead . '</thead>' . 367 '<tbody>' . $tbody . '</tbody>' . 368 '</table>' . 369 '<p>' . $postamble . '</p>'; 370 } else { 371 // Not a census-assistant shared note - apply default formatting 372 return Filter::formatText($note->getNote(), $WT_TREE); 373 } 374 } 375 376 /** 377 * Generate an HTML row of data for the census header 378 * 379 * Add prefix cell (store XREF and drag/drop) 380 * Add suffix cell (delete button) 381 * 382 * @param CensusInterface $census 383 * 384 * @return string 385 */ 386 public static function censusTableHeader(CensusInterface $census) { 387 $html = ''; 388 foreach ($census->columns() as $column) { 389 $html .= '<th title="' . $column->title() . '">' . $column->abbreviation() . '</th>'; 390 } 391 392 return '<tr><th hidden></th>' . $html . '<th></th></th></tr>'; 393 } 394 395 /** 396 * Generate an HTML row of data for the census 397 * 398 * Add prefix cell (store XREF and drag/drop) 399 * Add suffix cell (delete button) 400 * 401 * @param CensusInterface $census 402 * 403 * @return string 404 */ 405 public static function censusTableEmptyRow(CensusInterface $census) { 406 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>'; 407 } 408 409 /** 410 * Generate an HTML row of data for the census 411 * 412 * Add prefix cell (store XREF and drag/drop) 413 * Add suffix cell (delete button) 414 * 415 * @param CensusInterface $census 416 * @param Individual $individual 417 * @param Individual|null $head 418 * 419 * @return string 420 */ 421 public static function censusTableRow(CensusInterface $census, Individual $individual, Individual $head = null) { 422 $html = ''; 423 foreach ($census->columns() as $column) { 424 $html .= '<td><input type="text" value="' . $column->generate($individual, $head) . '"></td>'; 425 } 426 427 return '<tr><td hidden>' . $individual->getXref() . '</td>' . $html . '<td><a class="icon-remove" href="#" title="' . I18N::translate('Remove') . '"></a></td></tr>'; 428 } 429 430 /** 431 * Create a family on the census navigator. 432 * 433 * @param CensusInterface $census 434 * @param Family $family 435 * @param Individual $head 436 * 437 * @return string 438 */ 439 public static function censusNavigatorFamily(CensusInterface $census, Family $family, Individual $head) { 440 $headImg2 = '<i class="icon-button_head" title="' . I18N::translate('Click to choose individual as head of family.') . '"></i>'; 441 442 foreach ($family->getSpouses() as $spouse) { 443 $menu = new Menu(Functions::getCloseRelationshipName($head, $spouse)); 444 foreach ($spouse->getChildFamilies() as $grandparents) { 445 foreach ($grandparents->getSpouses() as $grandparent) { 446 $submenu = new Menu( 447 Functions::getCloseRelationshipName($head, $grandparent) . ' - ' . $grandparent->getFullName(), 448 '#', 449 '', 450 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $grandparent, $head)) . '");') 451 ); 452 $submenu->addClass('submenuitem', ''); 453 $menu->addSubmenu($submenu); 454 $menu->addClass('', 'submenu'); 455 } 456 } 457 458 ?> 459 <tr> 460 <td class="optionbox"> 461 <?php echo $menu->getMenu(); ?> 462 </td> 463 <td class="facts_value nowrap"> 464 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $spouse, $head)); ?>');"> 465 <?php echo $spouse->getFullName(); ?> 466 </a> 467 </td> 468 <td class="facts_value"> 469 <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); ?>"> 470 <?php echo $headImg2; ?> 471 </a> 472 </td> 473 </tr> 474 <?php 475 } 476 477 foreach ($family->getChildren() as $child) { 478 $menu = new Menu(Functions::getCloseRelationshipName($head, $child)); 479 foreach ($child->getSpouseFamilies() as $spouse_family) { 480 foreach ($spouse_family->getSpouses() as $spouse_family_spouse) { 481 if ($spouse_family_spouse != $child) { 482 $submenu = new Menu( 483 Functions::getCloseRelationshipName($head, $spouse_family_spouse) . ' - ' . $spouse_family_spouse->getFullName(), 484 '#', 485 '', 486 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_spouse, $head)) . '");') 487 ); 488 $submenu->addClass('submenuitem', ''); 489 $menu->addSubmenu($submenu); 490 $menu->addClass('', 'submenu'); 491 } 492 } 493 foreach ($spouse_family->getChildren() as $spouse_family_child) { 494 $submenu = new Menu( 495 Functions::getCloseRelationshipName($head, $spouse_family_child) . ' - ' . $spouse_family_child->getFullName(), 496 '#', 497 '', 498 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_child, $head)) . '");') 499 ); 500 $submenu->addClass('submenuitem', ''); 501 $menu->addSubmenu($submenu); 502 $menu->addClass('', 'submenu'); 503 } 504 } 505 506 ?> 507 <tr> 508 <td class="optionbox"> 509 <?php echo $menu->getMenu(); ?> 510 </td> 511 <td class="facts_value"> 512 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $child, $head)); ?>');"> 513 <?php echo $child->getFullName(); ?> 514 </a> 515 </td> 516 <td class="facts_value"> 517 <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); ?>"> 518 <?php echo $headImg2; ?> 519 </a> 520 </td> 521 </tr> 522 <?php 523 } 524 echo '<tr><td><br></td></tr>'; 525 } 526} 527