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