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 $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;" valign="top" 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 align=\"center\">"; 181 echo "<table class=\"list_table width90\" border=\"0\">"; 182 echo "<tr><td style=\"padding: 10px;\" valign=\"top\" 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 $headers = array( 283 'AgM' => 'Age at first marriage', 284 'Age' => 'Age at last birthday', 285 'Assets' => 'Assets = Owned,Rented - Value,Rent - Radio - Farm', 286 'BIC' => 'Born in County', 287 'BOE' => 'Born outside England', 288 'BP' => 'Birthplace - (Chapman format)', 289 'Birthplace' => 'Birthplace (Full format)', 290 'Bmth' => 'Month of birth - If born within Census year', 291 'ChB' => 'Children born alive', 292 'ChD' => 'Children who have died', 293 'ChL' => 'Children still living', 294 'DOB' => 'Date of birth', 295 'Edu' => 'Education - At School, Can Read, Can Write', // or "Cannot Read, Cannot Write" ?? 296 'EmD' => 'Employed?', 297 'EmN' => 'Unemployed?', 298 'EmR' => 'Employer?', 299 'Employ' => 'Employment', 300 'Eng?' => 'English spoken?', 301 'EngL' => 'English spoken?, if not, Native Language', 302 'FBP' => 'Father’s Birthplace - (Chapman format)', 303 'Health' => 'Health - 1.Blind, 2.Deaf & Dumb, 3.Idiotic, 4.Insane, 5.Disabled etc', 304 'Home' => 'Home Ownership - Owned/Rented-Free/Mortgaged-Farm/House-Farm Schedule number', 305 'Industry' => 'Industry', 306 'Infirm' => 'Infirmities - 1. Deaf & Dumb, 2. Blind, 3. Lunatic, 4. Imbecile/feeble-minded', 307 'Lang' => 'If Foreign Born - Native Language', 308 'MBP' => 'Mother’s Birthplace - (Chapman format)', 309 'MC' => 'Marital Condition - Married, Single, Unmarried, Widowed or Divorced', 310 'Mmth' => 'Month of marriage - If married during Census Year', 311 'MnsE' => 'Months employed during Census Year', 312 'MnsU' => 'Months unemployed during Census Year', 313 'N/A' => 'If Foreign Born - Naturalized, Alien', 314 'NL' => 'If Foreign Born - Native Language', 315 'Name' => 'Full Name or Married name if married', 316 'Occupation' => 'Occupation', 317 'Par' => 'Parentage - Father if foreign born, Mother if foreign born', 318 'Race' => 'Race or Color - Black, White, Mulatto, Asian, Indian, Chinese etc', 319 'Relation' => 'Relationship to Head of Household', 320 'Sex' => 'Male or Female', 321 'Situ' => 'Situation - Disease, Infirmity, Convict, Pauper etc', 322 'Ten' => 'Tenure - Owned/Rented, (if owned)Free/Morgaged', 323 'Vet' => 'War Veteran?', 324 'WH' => 'Working at Home?', 325 'War' => 'War or Expedition', 326 'WksU' => 'Weeks unemployed during Census Year', 327 'YOI' => 'If Foreign Born - Year of immigration', 328 'YON' => 'If Foreign Born - Year of naturalization', 329 'YUS' => 'If Foreign Born - Years in the USA', 330 'YrsM' => 'Years Married, or Y if married in Census Year', 331 ); 332 333 if (preg_match('/(.*)((?:\n.*)*)\n\.start_formatted_area\.\n(.*)((?:\n.*)*)\n.end_formatted_area\.((?:\n.*)*)/', $note->getNote(), $match)) { 334 // This looks like a census-assistant shared note 335 $title = Filter::escapeHtml($match[1]); 336 $preamble = Filter::escapeHtml($match[2]); 337 $header = Filter::escapeHtml($match[3]); 338 $data = Filter::escapeHtml($match[4]); 339 $postamble = Filter::escapeHtml($match[5]); 340 341 $fmt_headers = array(); 342 foreach ($headers as $key => $value) { 343 $fmt_headers[$key] = '<span title="' . Filter::escapeHtml($value) . '">' . $key . '</span>'; 344 } 345 346 // Substitue header labels and format as HTML 347 $thead = '<tr><th>' . strtr(str_replace('|', '</th><th>', $header), $fmt_headers) . '</th></tr>'; 348 $thead = str_replace('.b.', '', $thead); 349 350 // Format data as HTML 351 $tbody = ''; 352 foreach (explode("\n", $data) as $row) { 353 $tbody .= '<tr>'; 354 foreach (explode('|', $row) as $column) { 355 $tbody .= '<td>' . $column . '</td>'; 356 } 357 $tbody .= '</tr>'; 358 } 359 360 return 361 $title . "\n" . // The newline allows the framework to expand the details and turn the first line into a link 362 '<p>' . $preamble . '</p>' . 363 '<table class="table-census-assistant">' . 364 '<thead>' . $thead . '</thead>' . 365 '<tbody>' . $tbody . '</tbody>' . 366 '</table>' . 367 '<p>' . $postamble . '</p>'; 368 } else { 369 // Not a census-assistant shared note - apply default formatting 370 return Filter::formatText($note->getNote(), $WT_TREE); 371 } 372 } 373 374 /** 375 * Generate an HTML row of data for the census header 376 * 377 * @param CensusInterface $census 378 * 379 * @return string 380 */ 381 public static function censusTableHeader(CensusInterface $census) { 382 $html = '<th></th>'; // hidden column - used to save data 383 foreach ($census->columns() as $column) { 384 $html .= '<th title="' . $column->title() . '">' . $column->abbreviation() . '</th>'; 385 } 386 387 return '<tr>' . $html . '</tr>'; 388 } 389 390 /** 391 * Generate an HTML row of data for the census 392 * 393 * @param CensusInterface $census 394 * 395 * @return string 396 */ 397 public static function censusTableEmptyRow(CensusInterface $census) { 398 return '<tr><td></td>' . str_repeat('<td><input type="text"></td>', count($census->columns())) . '</tr>'; 399 } 400 401 /** 402 * Generate an HTML row of data for the census 403 * 404 * @param CensusInterface $census 405 * @param Individual $individual 406 * @param Individual|null $head 407 * 408 * @return string 409 */ 410 public static function censusTableRow(CensusInterface $census, Individual $individual, Individual $head = null) { 411 $html = '<td>' . $individual->getXref() . '</td>'; 412 foreach ($census->columns() as $column) { 413 $html .= '<td><input type="text" value="' . $column->generate($individual, $head) . '"></td>'; 414 } 415 416 return '<tr>' . $html . '</tr>'; 417 } 418 419 /** 420 * Create a family on the census navigator. 421 * 422 * @param CensusInterface $census 423 * @param Family $family 424 * @param Individual $head 425 * 426 * @return string 427 */ 428 public static function censusNavigatorFamily(CensusInterface $census, Family $family, Individual $head) { 429 $headImg2 = '<i class="icon-button_head" title="' . I18N::translate('Click to choose individual as head of family.') . '"></i>'; 430 431 foreach ($family->getSpouses() as $spouse) { 432 $menu = new Menu(Functions::getCloseRelationshipName($head, $spouse)); 433 foreach ($spouse->getChildFamilies() as $grandparents) { 434 foreach ($grandparents->getSpouses() as $grandparent) { 435 $submenu = new Menu( 436 Functions::getCloseRelationshipName($head, $grandparent) . ' - ' . $grandparent->getFullName(), 437 '#', 438 '', 439 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $grandparent, $head)) . '");') 440 ); 441 $submenu->addClass('submenuitem', ''); 442 $menu->addSubmenu($submenu); 443 $menu->addClass('', 'submenu'); 444 } 445 } 446 447 ?> 448 <tr> 449 <td class="optionbox"> 450 <?php echo $menu->getMenu(); ?> 451 </td> 452 <td class="facts_value nowrap"> 453 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $spouse, $head)); ?>');"> 454 <?php echo $spouse->getFullName(); ?> 455 </a> 456 </td> 457 <td align="left" class="facts_value"> 458 <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); ?>"> 459 <?php echo $headImg2; ?> 460 </a> 461 </td> 462 </tr> 463 <?php 464 } 465 466 foreach ($family->getChildren() as $child) { 467 $menu = new Menu(Functions::getCloseRelationshipName($head, $child)); 468 foreach ($child->getSpouseFamilies() as $spouse_family) { 469 foreach ($spouse_family->getSpouses() as $spouse_family_spouse) { 470 if ($spouse_family_spouse != $child) { 471 $submenu = new Menu( 472 Functions::getCloseRelationshipName($head, $spouse_family_spouse) . ' - ' . $spouse_family_spouse->getFullName(), 473 '#', 474 '', 475 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_spouse, $head)) . '");') 476 ); 477 $submenu->addClass('submenuitem', ''); 478 $menu->addSubmenu($submenu); 479 $menu->addClass('', 'submenu'); 480 } 481 } 482 foreach ($spouse_family->getChildren() as $spouse_family_child) { 483 $submenu = new Menu( 484 Functions::getCloseRelationshipName($head, $spouse_family_child) . ' - ' . $spouse_family_child->getFullName(), 485 '#', 486 '', 487 array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_child, $head)) . '");') 488 ); 489 $submenu->addClass('submenuitem', ''); 490 $menu->addSubmenu($submenu); 491 $menu->addClass('', 'submenu'); 492 } 493 } 494 495 ?> 496 <tr> 497 <td class="optionbox"> 498 <?php echo $menu->getMenu(); ?> 499 </td> 500 <td class="facts_value"> 501 <a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $child, $head)); ?>');"> 502 <?php echo $child->getFullName(); ?> 503 </a> 504 </td> 505 <td class="facts_value"> 506 <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); ?>"> 507 <?php echo $headImg2; ?> 508 </a> 509 </td> 510 </tr> 511 <?php 512 } 513 echo '<tr><td><br></td></tr>'; 514 } 515} 516