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