xref: /webtrees/app/Module/ClippingsCartModule.php (revision c775d8732eade73a612a956ad390833fcb5378b1)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2017 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\Auth;
19use Fisharebest\Webtrees\Controller\PageController;
20use Fisharebest\Webtrees\Family;
21use Fisharebest\Webtrees\Filter;
22use Fisharebest\Webtrees\GedcomRecord;
23use Fisharebest\Webtrees\Html;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Individual;
26use Fisharebest\Webtrees\Menu;
27use Fisharebest\Webtrees\Module\ClippingsCart\ClippingsCartController;
28use Fisharebest\Webtrees\Session;
29
30/**
31 * Class ClippingsCartModule
32 */
33class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface, ModuleSidebarInterface {
34	/** {@inheritdoc} */
35	public function getTitle() {
36		return /* I18N: Name of a module */
37			I18N::translate('Clippings cart');
38	}
39
40	/** {@inheritdoc} */
41	public function getDescription() {
42		return /* I18N: Description of the “Clippings cart” module */
43			I18N::translate('Select records from your family tree and save them as a GEDCOM file.');
44	}
45
46	/**
47	 * What is the default access level for this module?
48	 *
49	 * Some modules are aimed at admins or managers, and are not generally shown to users.
50	 *
51	 * @return int
52	 */
53	public function defaultAccessLevel() {
54		return Auth::PRIV_USER;
55	}
56
57	/**
58	 * This is a general purpose hook, allowing modules to respond to routes
59	 * of the form module.php?mod=FOO&mod_action=BAR
60	 *
61	 * @param string $mod_action
62	 */
63	public function modAction($mod_action) {
64		switch ($mod_action) {
65		case 'ajax':
66			$html = $this->getSidebarAjaxContent();
67			header('Content-Type: text/html; charset=UTF-8');
68			echo $html;
69			break;
70		case 'index':
71			global $controller, $WT_TREE;
72
73			$MAX_PEDIGREE_GENERATIONS = $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS');
74
75			$clip_ctrl = new ClippingsCartController;
76			$cart      = Session::get('cart');
77
78			$controller = new PageController;
79			$controller
80				->setPageTitle($this->getTitle())
81				->pageHeader();
82
83			echo '<script>';
84			echo 'function radAncestors(elementid) {var radFamilies=document.getElementById(elementid);radFamilies.checked=true;}';
85			echo '</script>';
86			echo '<div class="clipping-cart">';
87
88			if (!$cart[$WT_TREE->getTreeId()]) {
89				echo '<h2>', I18N::translate('Family tree clippings cart'), '</h2>';
90			}
91
92			if ($clip_ctrl->action == 'add') {
93				$record = GedcomRecord::getInstance($clip_ctrl->id, $WT_TREE);
94				if ($clip_ctrl->type === 'FAM') { ?>
95					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" action="module.php">
96						<input type="hidden" name="mod" value="clippings">
97						<input type="hidden" name="mod_action" value="index">
98						<input type="hidden" name="id" value="<?= $clip_ctrl->id ?>">
99						<input type="hidden" name="type" value="<?= $clip_ctrl->type ?>">
100						<input type="hidden" name="action" value="add1">
101						<table class="add-to center">
102							<thead>
103								<tr>
104									<td class="topbottombar">
105										<?= I18N::translate('Add to the clippings cart') ?>
106									</td>
107								</tr>
108							</thead>
109							<tbody>
110								<tr>
111									<td class="optionbox">
112										<input type="radio" name="others" value="parents">
113										<?= $record->getFullName() ?>
114									</td>
115								</tr>
116								<tr>
117									<td class="optionbox">
118										<input type="radio" name="others" value="members" checked>
119										<?= /* I18N: %s is a family (husband + wife) */
120										I18N::translate('%s and their children', $record->getFullName()) ?>
121									</td>
122								</tr>
123								<tr>
124									<td class="optionbox">
125										<input type="radio" name="others" value="descendants">
126										<?= /* I18N: %s is a family (husband + wife) */
127										I18N::translate('%s and their descendants', $record->getFullName()) ?>
128									</td>
129								</tr>
130							</tbody>
131							<tfoot>
132								<tr>
133									<td class="topbottombar"><input type="submit" value="<?= I18N::translate('continue') ?>">
134									</td>
135								</tr>
136							</tfoot>
137						</table>
138					</form>
139				</div>
140				<?php } elseif ($clip_ctrl->type === 'INDI') { ?>
141					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" action="module.php">
142						<input type="hidden" name="mod" value="clippings">
143						<input type="hidden" name="mod_action" value="index">
144						<input type="hidden" name="id" value="<?= $clip_ctrl->id ?>">
145						<input type="hidden" name="type" value="<?= $clip_ctrl->type ?>">
146						<input type="hidden" name="action" value="add1">
147						<table class="add-to center">
148							<thead>
149								<tr>
150									<td class="topbottombar">
151										<?= I18N::translate('Add to the clippings cart') ?>
152									</td>
153								</tr>
154							</thead>
155							<tbody>
156								<tr>
157									<td class="optionbox">
158										<label>
159											<input type="radio" name="others" checked value="none">
160											<?= $record->getFullName() ?>
161										</label>
162									</td>
163								</tr>
164								<tr>
165									<td class="optionbox">
166										<label>
167											<input type="radio" name="others" value="parents">
168											<?php
169											if ($record->getSex() === 'F') {
170												echo /* I18N: %s is a woman's name */
171												I18N::translate('%s, her parents and siblings', $record->getFullName());
172											} else {
173												echo /* I18N: %s is a man's name */
174												I18N::translate('%s, his parents and siblings', $record->getFullName());
175											}
176											?>
177										</label>
178									</td>
179								</tr>
180								<tr>
181									<td class="optionbox">
182										<label>
183											<input type="radio" name="others" value="members">
184											<?php
185											if ($record->getSex() === 'F') {
186												echo /* I18N: %s is a woman's name */
187												I18N::translate('%s, her spouses and children', $record->getFullName());
188											} else {
189												echo /* I18N: %s is a man's name */
190												I18N::translate('%s, his spouses and children', $record->getFullName());
191											}
192											?>
193										</label>
194									</td>
195								</tr>
196								<tr>
197									<td class="optionbox">
198										<label>
199											<input type="radio" name="others" value="ancestors" id="ancestors">
200											<?php
201											if ($record->getSex() === 'F') {
202												echo /* I18N: %s is a woman's name */
203												I18N::translate('%s and her ancestors', $record->getFullName());
204											} else {
205												echo /* I18N: %s is a man's name */
206												I18N::translate('%s and his ancestors', $record->getFullName());
207											}
208											?>
209										</label>
210										<br>
211										<?= I18N::translate('Number of generations') ?>
212										<input type="text" size="5" name="level1" value="<?= $MAX_PEDIGREE_GENERATIONS ?>" onfocus="radAncestors('ancestors');">
213									</td>
214								</tr>
215								<tr>
216									<td class="optionbox">
217										<label>
218											<input type="radio" name="others" value="ancestorsfamilies" id="ancestorsfamilies">
219											<?php
220											if ($record->getSex() === 'F') {
221												echo /* I18N: %s is a woman's name */
222												I18N::translate('%s, her ancestors and their families', $record->getFullName());
223											} else {
224												echo /* I18N: %s is a man's name */
225												I18N::translate('%s, his ancestors and their families', $record->getFullName());
226											}
227											?>
228										</label>
229										<br>
230										<?= I18N::translate('Number of generations') ?>
231										<input type="text" size="5" name="level2" value="<?= $MAX_PEDIGREE_GENERATIONS ?>" onfocus="radAncestors('ancestorsfamilies');">
232									</td>
233								</tr>
234								<tr>
235									<td class="optionbox">
236										<label>
237											<input type="radio" name="others" value="descendants" id="descendants">
238											<?php
239											if ($record->getSex() === 'F') {
240												echo /* I18N: %s is a woman's name */
241												I18N::translate('%s, her spouses and descendants', $record->getFullName());
242											} else {
243												echo /* I18N: %s is a man's name */
244												I18N::translate('%s, his spouses and descendants', $record->getFullName());
245											}
246											?>
247										</label>
248										<br>
249										<?= I18N::translate('Number of generations') ?>
250										<input type="text" size="5" name="level3" value="<?= $MAX_PEDIGREE_GENERATIONS ?>" onfocus="radAncestors('descendants');">
251									</td>
252								</tr>
253							</tbody>
254							<tfoot>
255								<tr>
256									<td class="topbottombar">
257										<input type="submit" value="<?= I18N::translate('continue') ?>">
258									</td>
259								</tr>
260							</tfoot>
261						</table>
262					</form>
263				</div>
264				<?php } elseif ($clip_ctrl->type === 'SOUR') { ?>
265					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" action="module.php">
266						<input type="hidden" name="mod" value="clippings">
267						<input type="hidden" name="mod_action" value="index">
268						<input type="hidden" name="id" value="<?= $clip_ctrl->id ?>">
269						<input type="hidden" name="type" value="<?= $clip_ctrl->type ?>">
270						<input type="hidden" name="action" value="add1">
271						<table class="add-to center">
272							<thead>
273								<tr>
274									<td class="topbottombar">
275										<?= I18N::translate('Add to the clippings cart') ?>
276									</td>
277								</tr>
278							</thead>
279							<tbody>
280								<tr>
281									<td class="optionbox">
282										<label>
283											<input type="radio" name="others" checked value="none">
284											<?= $record->getFullName() ?>
285										</label>
286									</td>
287								</tr>
288								<tr>
289									<td class="optionbox">
290										<label>
291											<input type="radio" name="others" value="linked">
292											<?= /* I18N: %s is the name of a source */
293											I18N::translate('%s and the individuals that reference it.', $record->getFullName()) ?>
294										</label>
295									</td>
296								</tr>
297							</tbody>
298							<tfoot>
299								<tr>
300									<td class="topbottombar">
301										<input type="submit" value="<?= I18N::translate('continue') ?>">
302									</td>
303								</tr>
304							</tfoot>
305						</table>
306					</form>
307				</div>
308				<?php }
309			}
310
311			if (!$cart[$WT_TREE->getTreeId()]) {
312				if ($clip_ctrl->action != 'add') {
313					echo '<div class="center">';
314					echo I18N::translate('The clippings cart allows you to take extracts from this family tree and download them as a GEDCOM file.');
315					echo '</div>';
316					?>
317					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" name="addin" action="module.php">
318						<input type="hidden" name="mod" value="clippings">
319						<input type="hidden" name="mod_action" value="index">
320						<table class="add-to center">
321							<thead>
322								<tr>
323									<td colspan="2" class="topbottombar">
324										<?= I18N::translate('Add to the clippings cart') ?>
325									</td>
326								</tr>
327							</thead>
328							<tbody>
329								<tr>
330									<td class="optionbox">
331										<input type="hidden" name="action" value="add">
332										<input type="text" data-autocomplete-type="IFSRO" name="id" id="cart_item_id" size="5">
333									</td>
334									<td class="optionbox">
335										<input type="submit" value="<?= /* I18N: A button label. */ I18N::translate('add') ?>">
336									</td>
337								</tr>
338							</tbody>
339						</table>
340					</form>
341				</div>
342					<?php
343				}
344				echo '<div class="center">';
345				// -- end new lines
346				echo I18N::translate('Your clippings cart is empty.');
347				echo '</div>';
348			} else {
349				// Keep track of the INDI from the parent page, otherwise it will
350				// get lost after ajax updates
351				$pid = Filter::get('pid', WT_REGEX_XREF);
352
353				if ($clip_ctrl->action !== 'download' && $clip_ctrl->action !== 'add') { ?>
354					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" action="module.php">
355						<input type="hidden" name="mod" value="clippings">
356						<input type="hidden" name="mod_action" value="index">
357						<input type="hidden" name="action" value="download">
358						<input type="hidden" name="pid" value="<?= $pid ?>">
359						<table class="add-to center">
360							<tr>
361								<td colspan="2" class="topbottombar">
362									<h2><?= I18N::translate('Download') ?></h2>
363								</td>
364							</tr>
365							<?php if (Auth::isManager($WT_TREE)) { ?>
366								<tr>
367									<td class="descriptionbox width50 wrap">
368										<?= I18N::translate('Apply privacy settings') ?>
369									</td>
370									<td class="optionbox">
371										<input type="radio" name="privatize_export" value="none" checked>
372										<?= I18N::translate('None') ?>
373										<br>
374										<input type="radio" name="privatize_export" value="gedadmin">
375										<?= I18N::translate('Manager') ?>
376										<br>
377										<input type="radio" name="privatize_export" value="user">
378										<?= I18N::translate('Member') ?>
379										<br>
380										<input type="radio" name="privatize_export" value="visitor">
381										<?= I18N::translate('Visitor') ?>
382									</td>
383								</tr>
384							<?php } elseif (Auth::isMember($WT_TREE)) { ?>
385								<tr>
386									<td class="descriptionbox width50 wrap">
387										<?= I18N::translate('Apply privacy settings') ?>
388									</td>
389									<td class="optionbox">
390										<input type="radio" name="privatize_export" value="user" checked> <?= I18N::translate('Member') ?><br>
391										<input type="radio" name="privatize_export" value="visitor"> <?= I18N::translate('Visitor') ?>
392									</td>
393								</tr>
394							<?php } ?>
395
396							<tr>
397								<td class="descriptionbox width50 wrap">
398									<?= I18N::translate('Convert from UTF-8 to ISO-8859-1') ?>
399								</td>
400								<td class="optionbox">
401									<input type="checkbox" name="convert" value="yes">
402								</td>
403							</tr>
404
405							<tr>
406								<td class="topbottombar" colspan="2">
407									<input type="submit" value="<?= /* I18N: A button label. */ I18N::translate('download') ?>">
408								</td>
409							</tr>
410						</table>
411					</form>
412				</div>
413					<br>
414
415					<form class="wt-page-options wt-page-options-clipping-cart hidden-print" name="addin" action="module.php">
416						<input type="hidden" name="mod" value="clippings">
417						<input type="hidden" name="mod_action" value="index">
418						<table class="add-to center">
419							<thead>
420								<tr>
421									<td colspan="2" class="topbottombar" style="text-align:center; ">
422										<?= I18N::translate('Add to the clippings cart') ?>
423									</td>
424								</tr>
425							</thead>
426							<tbody>
427								<tr>
428									<td class="optionbox">
429										<input type="hidden" name="action" value="add">
430										<input type="text" data-autocomplete-type="IFSRO" name="id" id="cart_item_id" size="8">
431									</td>
432									<td class="optionbox">
433										<input type="submit" value="<?= /* I18N: A button label. */ I18N::translate('add') ?>">
434									</td>
435								</tr>
436							</tbody>
437							<tfoot>
438								<tr>
439									<th colspan="2">
440										<a href="module.php?mod=clippings&amp;mod_action=index&amp;action=empty">
441											<?= I18N::translate('Empty the clippings cart') ?>
442										</a>
443									</th>
444								</tr>
445							</tfoot>
446						</table>
447					</form>
448				</div>
449				<?php } ?>
450				<div class="clipping-cart">
451				<h2>
452					<?= I18N::translate('Family tree clippings cart') ?>
453				</h2>
454				<table id="mycart" class="sortable list_table width50">
455					<thead>
456						<tr>
457							<th class="list_label"><?= I18N::translate('Record') ?></th>
458							<th class="list_label"><?= I18N::translate('Remove') ?></th>
459						</tr>
460					</thead>
461					<tbody>
462						<?php
463						foreach (array_keys($cart[$WT_TREE->getTreeId()]) as $xref) {
464							$record = GedcomRecord::getInstance($xref, $WT_TREE);
465							if ($record) {
466								switch ($record::RECORD_TYPE) {
467								case 'INDI':
468									$icon = 'icon-indis';
469									break;
470								case 'FAM':
471									$icon = 'icon-sfamily';
472									break;
473								case 'SOUR':
474									$icon = 'icon-source';
475									break;
476								case 'REPO':
477									$icon = 'icon-repository';
478									break;
479								case 'NOTE':
480									$icon = 'icon-note';
481									break;
482								case 'OBJE':
483									$icon = 'icon-media';
484									break;
485								default:
486									$icon = 'icon-clippings';
487									break;
488								}
489								?>
490								<tr>
491									<td class="list_value">
492										<i class="<?= $icon ?>"></i>
493										<?php
494										echo '<a href="', $record->getHtmlUrl(), '">', $record->getFullName(), '</a>';
495										?>
496									</td>
497									<td class="list_value center vmiddle"><a href="module.php?mod=clippings&amp;mod_action=index&amp;action=remove&amp;id=<?= $xref ?>" class="icon-remove" title="<?= I18N::translate('Remove') ?>"></a></td>
498								</tr>
499								<?php
500							}
501						}
502						?>
503				</table>
504			</div>
505				<?php
506			}
507			break;
508		default:
509			http_response_code(404);
510			break;
511		}
512	}
513
514	/**
515	 * The user can re-order menus. Until they do, they are shown in this order.
516	 *
517	 * @return int
518	 */
519	public function defaultMenuOrder() {
520		return 20;
521	}
522
523	/**
524	 * A menu, to be added to the main application menu.
525	 *
526	 * @return Menu|null
527	 */
528	public function getMenu() {
529		global $controller, $WT_TREE;
530
531		$submenus = [];
532		if (isset($controller->record)) {
533			$submenus[] = new Menu($this->getTitle(), 'module.php?mod=clippings&amp;mod_action=index&amp;ged=' . $WT_TREE->getNameUrl(), 'menu-clippings-cart', ['rel' => 'nofollow']);
534		}
535		if (!empty($controller->record) && $controller->record->canShow()) {
536			$submenus[] = new Menu(I18N::translate('Add to the clippings cart'), 'module.php?mod=clippings&amp;mod_action=index&amp;action=add&amp;id=' . $controller->record->getXref(), 'menu-clippings-add', ['rel' => 'nofollow']);
537		}
538
539		if ($submenus) {
540			return new Menu($this->getTitle(), '#', 'menu-clippings', ['rel' => 'nofollow'], $submenus);
541		} else {
542			return new Menu($this->getTitle(), 'module.php?mod=clippings&amp;mod_action=index&amp;ged=' . $WT_TREE->getNameUrl(), 'menu-clippings', ['rel' => 'nofollow']);
543		}
544	}
545
546	/** {@inheritdoc} */
547	public function defaultSidebarOrder() {
548		return 60;
549	}
550
551	/** {@inheritdoc} */
552	public function hasSidebarContent() {
553		// Creating a controller has the side effect of initialising the cart
554		new ClippingsCartController;
555
556		return true;
557	}
558
559	/**
560	 * Load this sidebar synchronously.
561	 *
562	 * @return string
563	 */
564	public function getSidebarContent() {
565		global $controller;
566
567		$controller->addInlineJavascript('
568				$("#sb_clippings_content").on("click", ".add_cart, .remove_cart", function() {
569					$("#sb_clippings_content").load(this.href);
570					return false;
571				});
572			');
573
574		return '<div id="sb_clippings_content">' . $this->getCartList() . '</div>';
575	}
576
577	/** {@inheritdoc} */
578	public function getSidebarAjaxContent() {
579		global $WT_TREE;
580
581		$cart = Session::get('cart');
582
583		$clip_ctrl         = new ClippingsCartController;
584		$add               = Filter::get('add', WT_REGEX_XREF);
585		$add1              = Filter::get('add1', WT_REGEX_XREF);
586		$remove            = Filter::get('remove', WT_REGEX_XREF);
587		$others            = Filter::get('others');
588		$clip_ctrl->level1 = Filter::getInteger('level1');
589		$clip_ctrl->level2 = Filter::getInteger('level2');
590		$clip_ctrl->level3 = Filter::getInteger('level3');
591		if ($add) {
592			$record = GedcomRecord::getInstance($add, $WT_TREE);
593			if ($record) {
594				$clip_ctrl->id   = $record->getXref();
595				$clip_ctrl->type = $record::RECORD_TYPE;
596				$clip_ctrl->addClipping($record);
597			}
598		} elseif ($add1) {
599			$record = Individual::getInstance($add1, $WT_TREE);
600			if ($record) {
601				$clip_ctrl->id   = $record->getXref();
602				$clip_ctrl->type = $record::RECORD_TYPE;
603				if ($others == 'parents') {
604					foreach ($record->getChildFamilies() as $family) {
605						$clip_ctrl->addClipping($family);
606						$clip_ctrl->addFamilyMembers($family);
607					}
608				} elseif ($others == 'ancestors') {
609					$clip_ctrl->addAncestorsToCart($record, $clip_ctrl->level1);
610				} elseif ($others == 'ancestorsfamilies') {
611					$clip_ctrl->addAncestorsToCartFamilies($record, $clip_ctrl->level2);
612				} elseif ($others == 'members') {
613					foreach ($record->getSpouseFamilies() as $family) {
614						$clip_ctrl->addClipping($family);
615						$clip_ctrl->addFamilyMembers($family);
616					}
617				} elseif ($others == 'descendants') {
618					foreach ($record->getSpouseFamilies() as $family) {
619						$clip_ctrl->addClipping($family);
620						$clip_ctrl->addFamilyDescendancy($family, $clip_ctrl->level3);
621					}
622				}
623			}
624		} elseif ($remove) {
625			unset($cart[$WT_TREE->getTreeId()][$remove]);
626			Session::put('cart', $cart);
627		} elseif (isset($_REQUEST['empty'])) {
628			$cart[$WT_TREE->getTreeId()] = [];
629			Session::put('cart', $cart);
630		} elseif (isset($_REQUEST['download'])) {
631			return $this->downloadForm();
632		}
633
634		return $this->getCartList();
635	}
636
637	/**
638	 * A list for the side bar.
639	 *
640	 * @return string
641	 */
642	public function getCartList() {
643		global $WT_TREE;
644
645		$cart = Session::get('cart', []);
646		if (!array_key_exists($WT_TREE->getTreeId(), $cart)) {
647			$cart[$WT_TREE->getTreeId()] = [];
648		}
649		$pid = Filter::get('pid', WT_REGEX_XREF);
650
651		if (!$cart[$WT_TREE->getTreeId()]) {
652			$out = I18N::translate('Your clippings cart is empty.');
653		} else {
654			$out = '';
655			if (!empty($cart[$WT_TREE->getTreeId()])) {
656				$out .=
657					'<a href="module.php?mod=' . $this->getName() . '&amp;mod_action=ajax&amp;empty=true&amp;pid=' . $pid . '" class="remove_cart">' .
658					I18N::translate('Empty the clippings cart') .
659					'</a>' .
660					'<br>' .
661					'<a href="module.php?mod=' . $this->getName() . '&amp;mod_action=ajax&amp;download=true&amp;pid=' . $pid . '" class="add_cart">' .
662					I18N::translate('Download') .
663					'</a><br><br>';
664			}
665			$out .= '<ul>';
666			foreach (array_keys($cart[$WT_TREE->getTreeId()]) as $xref) {
667				$record = GedcomRecord::getInstance($xref, $WT_TREE);
668				if ($record instanceof Individual || $record instanceof Family) {
669					switch ($record::RECORD_TYPE) {
670					case 'INDI':
671						$icon = 'icon-indis';
672						break;
673					case 'FAM':
674						$icon = 'icon-sfamily';
675						break;
676					}
677					$out .= '<li>';
678					if (!empty($icon)) {
679						$out .= '<i class="' . $icon . '"></i>';
680					}
681					$out .= '<a href="' . $record->getHtmlUrl() . '">';
682					if ($record instanceof Individual) {
683						$out .= $record->getSexImage();
684					}
685					$out .= ' ' . $record->getFullName() . ' ';
686					if ($record instanceof Individual && $record->canShow()) {
687						$out .= ' (' . $record->getLifeSpan() . ')';
688					}
689					$out .= '</a>';
690					$out .= '<a class="icon-remove remove_cart" href="module.php?mod=' . $this->getName() . '&amp;mod_action=ajax&amp;remove=' . $xref . '&amp;pid=' . $pid . '" title="' . I18N::translate('Remove') . '"></a>';
691					$out .= '</li>';
692				}
693			}
694			$out .= '</ul>';
695		}
696
697		$record = Individual::getInstance($pid, $WT_TREE);
698		if ($record && !array_key_exists($record->getXref(), $cart[$WT_TREE->getTreeId()])) {
699			$out .= '<br><a href="module.php?mod=' . $this->getName() . '&amp;mod_action=ajax&amp;action=add1&amp;type=INDI&amp;id=' . $pid . '&amp;pid=' . $pid . '" class="add_cart"><i class="icon-clippings"></i> ' . I18N::translate('Add %s to the clippings cart', $record->getFullName()) . '</a>';
700		}
701
702		return $out;
703	}
704
705	/**
706	 * A form to choose the download options.
707	 *
708	 * @return string
709	 */
710	public function downloadForm() {
711		global $WT_TREE;
712
713		$pid = Filter::get('pid', WT_REGEX_XREF);
714
715		$out = '<script>';
716		$out .= 'function cancelDownload() {
717				var link = "module.php?mod=' . $this->getName() . '&mod_action=ajax&pid=' . $pid . '";
718				$("#sb_clippings_content").load(link);
719			}';
720		$out .= '</script>';
721		$out .= '<form class="wt-page-options wt-page-options-clipping-cart hidden-print" action="module.php">
722		<input type="hidden" name="mod" value="clippings">
723		<input type="hidden" name="mod_action" value="index">
724		<input type="hidden" name="pid" value="' . $pid . '">
725		<input type="hidden" name="action" value="download">
726		<table>
727		<tr><td colspan="2" class="topbottombar"><h2>' . I18N::translate('Download') . '</h2></td></tr>
728		';
729
730		if (Auth::isManager($WT_TREE)) {
731			$out .=
732				'<tr><td class="descriptionbox width50 wrap">' . I18N::translate('Apply privacy settings') . '</td>' .
733				'<td class="optionbox">' .
734				'<input type="radio" name="privatize_export" value="none" checked> ' . I18N::translate('None') . '<br>' .
735				'<input type="radio" name="privatize_export" value="gedadmin"> ' . I18N::translate('Manager') . '<br>' .
736				'<input type="radio" name="privatize_export" value="user"> ' . I18N::translate('Member') . '<br>' .
737				'<input type="radio" name="privatize_export" value="visitor"> ' . I18N::translate('Visitor') .
738				'</td></tr>';
739		} elseif (Auth::isMember($WT_TREE)) {
740			$out .=
741				'<tr><td class="descriptionbox width50 wrap">' . I18N::translate('Apply privacy settings') . '</td>' .
742				'<td class="list_value">' .
743				'<input type="radio" name="privatize_export" value="user" checked> ' . I18N::translate('Member') . '<br>' .
744				'<input type="radio" name="privatize_export" value="visitor"> ' . I18N::translate('Visitor') .
745				'</td></tr>';
746		}
747
748		$out .= '
749		<tr><td class="descriptionbox width50 wrap">' . I18N::translate('Convert from UTF-8 to ISO-8859-1') . '</td>
750		<td class="optionbox"><input type="checkbox" name="convert" value="yes"></td></tr>
751
752		<tr><td class="topbottombar" colspan="2">
753		<input type="button" class="btn btn-secondary" value="' . /* I18N: A button label. */ I18N::translate('cancel') . '" onclick="cancelDownload();">
754		<input type="submit" class="btn btn-primary" value="' . /* I18N: A button label. */ I18N::translate('download') . '">
755		</form>';
756
757		return $out;
758	}
759}
760