xref: /webtrees/app/Module/CensusAssistantModule.php (revision 4e153c6af1eb3f56ad3d7f7db56fcaebf3f310bc)
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>&nbsp;&nbsp;&nbsp;";
211
212				$born = GedcomTag::getLabel('BIRT');
213				echo "</span><br><span class=\"list_item\">", $born, " ", $indi->getbirthyear(), "&nbsp;&nbsp;&nbsp;", $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&amp;noteid=newnote&amp;xref=<?php echo $spouse->getXref(); ?>&amp;gedcom=<?php echo $spouse->getTree()->getNameUrl(); ?>&amp;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&amp;noteid=newnote&amp;xref=<?php echo $child->getXref(); ?>&amp;gedcom=<?php echo $child->getTree()->getNameUrl(); ?>&amp;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