xref: /webtrees/app/Module/CensusAssistantModule.php (revision 3a7bc14ad32e31ba049b6906a4e881a1685c8926)
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\Census;
19use Fisharebest\Webtrees\Census\CensusInterface;
20use Fisharebest\Webtrees\Controller\SimpleController;
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;" 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>';
181		echo '<table class="list_table width90" border="0">';
182		echo '<tr><td style="padding: 10px;" 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>&nbsp;&nbsp;&nbsp;";
208
209				$born = GedcomTag::getLabel('BIRT');
210				echo "</span><br><span class=\"list_item\">", $born, " ", $indi->getBirthYear(), "&nbsp;&nbsp;&nbsp;", $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
283		$headers = array();
284		foreach (Census::allCensusPlaces() as $allCensusesOfPlace) {
285			foreach ($allCensusesOfPlace->allCensusDates() as $census) {
286				foreach ($census->columns() as $column) {
287					if ($column->abbreviation()) {
288						$headers[$column->abbreviation()] = $column->title();
289					}
290				}
291			}
292		}
293
294		if (preg_match('/(.*)((?:\n.*)*)\n\.start_formatted_area\.\n(.*)((?:\n.*)*)\n.end_formatted_area\.((?:\n.*)*)/', $note->getNote(), $match)) {
295			// This looks like a census-assistant shared note
296			$title     = Filter::escapeHtml($match[1]);
297			$preamble  = Filter::escapeHtml($match[2]);
298			$header    = Filter::escapeHtml($match[3]);
299			$data      = Filter::escapeHtml($match[4]);
300			$postamble = Filter::escapeHtml($match[5]);
301
302			$fmt_headers = array();
303			foreach ($headers as $key => $value) {
304				$fmt_headers[$key] = '<span title="' . Filter::escapeHtml($value) . '">' . $key . '</span>';
305			}
306
307			// Substitue header labels and format as HTML
308			$thead = '<tr><th>' . strtr(str_replace('|', '</th><th>', $header), $fmt_headers) . '</th></tr>';
309			$thead = str_replace('.b.', '', $thead);
310
311			// Format data as HTML
312			$tbody = '';
313			foreach (explode("\n", $data) as $row) {
314				$tbody .= '<tr>';
315				foreach (explode('|', $row) as $column) {
316					$tbody .= '<td>' . $column . '</td>';
317				}
318				$tbody .= '</tr>';
319			}
320
321			return
322				$title . "\n" . // The newline allows the framework to expand the details and turn the first line into a link
323				'<p>' . $preamble . '</p>' .
324				'<table class="table-census-assistant">' .
325				'<thead>' . $thead . '</thead>' .
326				'<tbody>' . $tbody . '</tbody>' .
327				'</table>' .
328				'<p>' . $postamble . '</p>';
329		} else {
330			// Not a census-assistant shared note - apply default formatting
331			return Filter::formatText($note->getNote(), $WT_TREE);
332		}
333	}
334
335	/**
336	 * Generate an HTML row of data for the census header
337	 *
338	 * Add prefix cell (store XREF and drag/drop)
339	 * Add suffix cell (delete button)
340	 *
341	 * @param CensusInterface $census
342	 *
343	 * @return string
344	 */
345	public static function censusTableHeader(CensusInterface $census) {
346		$html = '';
347		foreach ($census->columns() as $column) {
348			$html .= '<th title="' . $column->title() . '">' . $column->abbreviation() . '</th>';
349		}
350
351		return '<tr><th hidden></th>' . $html . '<th></th></th></tr>';
352	}
353
354	/**
355	 * Generate an HTML row of data for the census
356	 *
357	 * Add prefix cell (store XREF and drag/drop)
358	 * Add suffix cell (delete button)
359	 *
360	 * @param CensusInterface $census
361	 *
362	 * @return string
363	 */
364	public static function censusTableEmptyRow(CensusInterface $census) {
365		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>';
366	}
367
368	/**
369	 * Generate an HTML row of data for the census
370	 *
371	 * Add prefix cell (store XREF and drag/drop)
372	 * Add suffix cell (delete button)
373	 *
374	 * @param CensusInterface $census
375	 * @param Individual      $individual
376	 * @param Individual|null $head
377	 *
378	 * @return string
379	 */
380	public static function censusTableRow(CensusInterface $census, Individual $individual, Individual $head = null) {
381		$html = '';
382		foreach ($census->columns() as $column) {
383			$html .= '<td><input type="text" value="' . $column->generate($individual, $head) . '"></td>';
384		}
385
386		return '<tr><td hidden>' . $individual->getXref() . '</td>' . $html . '<td><a class="icon-remove" href="#" title="' . I18N::translate('Remove') . '"></a></td></tr>';
387	}
388
389	/**
390	 * Create a family on the census navigator.
391	 *
392	 * @param CensusInterface $census
393	 * @param Family          $family
394	 * @param Individual      $head
395	 *
396	 * @return string
397	 */
398	public static function censusNavigatorFamily(CensusInterface $census, Family $family, Individual $head) {
399		$headImg2  = '<i class="icon-button_head" title="' . I18N::translate('Click to choose individual as head of family.') . '"></i>';
400
401		foreach ($family->getSpouses() as $spouse) {
402			$menu  = new Menu(Functions::getCloseRelationshipName($head, $spouse));
403			foreach ($spouse->getChildFamilies() as $grandparents) {
404				foreach ($grandparents->getSpouses() as $grandparent) {
405					$submenu = new Menu(
406						Functions::getCloseRelationshipName($head, $grandparent) . ' - ' . $grandparent->getFullName(),
407						'#',
408						'',
409						array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $grandparent, $head)) . '");')
410					);
411					$submenu->addClass('submenuitem', '');
412					$menu->addSubmenu($submenu);
413					$menu->addClass('', 'submenu');
414				}
415			}
416
417			?>
418			<tr>
419				<td class="optionbox">
420					<?php echo $menu->getMenu(); ?>
421				</td>
422				<td class="facts_value nowrap">
423					<a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $spouse, $head)); ?>');">
424						<?php echo $spouse->getFullName(); ?>
425					</a>
426				</td>
427				<td class="facts_value">
428					<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); ?>">
429						<?php echo $headImg2; ?>
430					</a>
431				</td>
432			</tr>
433			<?php
434		}
435
436		foreach ($family->getChildren() as $child) {
437			$menu  = new Menu(Functions::getCloseRelationshipName($head, $child));
438			foreach ($child->getSpouseFamilies() as $spouse_family) {
439				foreach ($spouse_family->getSpouses() as $spouse_family_spouse) {
440					if ($spouse_family_spouse != $child) {
441						$submenu = new Menu(
442							Functions::getCloseRelationshipName($head, $spouse_family_spouse) . ' - ' . $spouse_family_spouse->getFullName(),
443							'#',
444							'',
445							array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_spouse, $head)) . '");')
446						);
447						$submenu->addClass('submenuitem', '');
448						$menu->addSubmenu($submenu);
449						$menu->addClass('', 'submenu');
450					}
451				}
452				foreach ($spouse_family->getChildren() as $spouse_family_child) {
453					$submenu = new Menu(
454						Functions::getCloseRelationshipName($head, $spouse_family_child) . ' - ' . $spouse_family_child->getFullName(),
455						'#',
456						'',
457						array('onclick' => 'return appendCensusRow("' . Filter::escapeJs(self::censusTableRow($census, $spouse_family_child, $head)) . '");')
458					);
459					$submenu->addClass('submenuitem', '');
460					$menu->addSubmenu($submenu);
461					$menu->addClass('', 'submenu');
462				}
463			}
464
465			?>
466			<tr>
467				<td class="optionbox">
468					<?php echo $menu->getMenu(); ?>
469				</td>
470				<td class="facts_value">
471					<a href="#" onclick="return appendCensusRow('<?php echo Filter::escapeJs(self::censusTableRow($census, $child, $head)); ?>');">
472						<?php echo $child->getFullName(); ?>
473					</a>
474				</td>
475				<td class="facts_value">
476					<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); ?>">
477						<?php echo $headImg2; ?>
478					</a>
479				</td>
480			</tr>
481			<?php
482		}
483		echo '<tr><td><br></td></tr>';
484	}
485}
486