Completed
Push — develop ( 9087a8...c9b4ef )
by Greg
16:31 queued 05:44
created

IndividualController::getTabs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2018 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
 */
16
declare(strict_types=1);
17
18
namespace Fisharebest\Webtrees\Http\Controllers;
19
20
use Fisharebest\Webtrees\Auth;
21
use Fisharebest\Webtrees\Date;
22
use Fisharebest\Webtrees\Fact;
23
use Fisharebest\Webtrees\FontAwesome;
24
use Fisharebest\Webtrees\Functions\FunctionsDate;
25
use Fisharebest\Webtrees\Functions\FunctionsPrint;
26
use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
27
use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
28
use Fisharebest\Webtrees\GedcomTag;
29
use Fisharebest\Webtrees\I18N;
30
use Fisharebest\Webtrees\Individual;
31
use Fisharebest\Webtrees\Media;
32
use Fisharebest\Webtrees\Module;
33
use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
34
use Fisharebest\Webtrees\Module\ModuleTabInterface;
35
use Fisharebest\Webtrees\Tree;
36
use Fisharebest\Webtrees\User;
37
use Symfony\Component\HttpFoundation\Request;
38
use Symfony\Component\HttpFoundation\Response;
39
40
/**
41
 * Controller for the individual page.
42
 */
43
class IndividualController extends BaseController {
44
	/**
45
	 * Show a individual's page.
46
	 *
47
	 * @param Request $request
48
	 *
49
	 * @return Response
50
	 */
51
	public function show(Request $request): Response {
52
		/** @var Tree $tree */
53
		$tree   = $request->attributes->get('tree');
54
		$xref   = $request->get('xref');
55
		$record = Individual::getInstance($xref, $tree);
56
57
		if ($record === null) {
58
			return $this->individualNotFound();
59
		} elseif (!$record->canShow()) {
60
			return $this->individualNotAllowed();
61
		} else {
62
			$individual_media = [];
63
			// What is (was) the age of the individual
64
			$bdate = $record->getBirthDate();
0 ignored issues
show
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\Media. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\Note. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\Repository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\Family. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\Source. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getBirthDate() does not exist on Fisharebest\Webtrees\GedcomRecord. It seems like you code against a sub-type of Fisharebest\Webtrees\GedcomRecord such as Fisharebest\Webtrees\Individual. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
			/** @scrutinizer ignore-call */ 
65
   $bdate = $record->getBirthDate();
Loading history...
65
			$ddate = $record->getDeathDate();
0 ignored issues
show
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\Note. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\Family. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\Repository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\GedcomRecord. It seems like you code against a sub-type of Fisharebest\Webtrees\GedcomRecord such as Fisharebest\Webtrees\Individual. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();
Loading history...
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\Media. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getDeathDate() does not exist on Fisharebest\Webtrees\Source. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
			/** @scrutinizer ignore-call */ 
66
   $ddate = $record->getDeathDate();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
66
			if ($bdate->isOK() && !$record->isDead()) {
0 ignored issues
show
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\Note. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\GedcomRecord. It seems like you code against a sub-type of Fisharebest\Webtrees\GedcomRecord such as Fisharebest\Webtrees\Individual. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {
Loading history...
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\Media. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\Source. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\Repository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method isDead() does not exist on Fisharebest\Webtrees\Family. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
			if ($bdate->isOK() && !$record->/** @scrutinizer ignore-call */ isDead()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
67
				// If living display age
68
				$age = ' (' . I18N::translate('age') . ' ' . FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate, new Date(strtoupper(date('d M Y'))))) . ')';
69
			} elseif ($bdate->isOK() && $ddate->isOK()) {
70
				// If dead, show age at death
71
				$age = ' (' . I18N::translate('age') . ' ' . FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate, $ddate)) . ')';
72
			} else {
73
				$age = '';
74
			}
75
76
			foreach ($record->getFacts() as $fact) {
77
				$media_object = $fact->getTarget();
78
				if ($media_object instanceof Media) {
79
					$individual_media[] = $media_object->firstImageFile();
80
				}
81
			}
82
			$individual_media = array_filter($individual_media);
83
84
			$name_records = [];
85
			foreach ($record->getFacts('NAME') as $n => $name_fact) {
86
				$name_records[] = $this->formatNameRecord($n, $name_fact);
87
			}
88
89
			$sex_records = [];
90
			foreach ($record->getFacts('SEX') as $n => $sex_fact) {
91
				$sex_records[] = $this->formatSexRecord($sex_fact);
92
			}
93
94
			// If this individual is linked to a user account, show the link
95
			$user_link = '';
96
			if (Auth::isAdmin()) {
97
				$user = User::findByIndividual($record);
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type Fisharebest\Webtrees\Note and Fisharebest\Webtrees\Source and Fisharebest\Webtrees\Repository and Fisharebest\Webtrees\Media and Fisharebest\Webtrees\Family; however, parameter $individual of Fisharebest\Webtrees\User::findByIndividual() does only seem to accept Fisharebest\Webtrees\Individual, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
				$user = User::findByIndividual(/** @scrutinizer ignore-type */ $record);
Loading history...
98
				if ($user) {
0 ignored issues
show
introduced by
The condition $user can never be true.
Loading history...
99
					$user_link = ' —  <a href="admin_users.php?filter=' . e($user->getUserName()) . '">' . e($user->getUserName()) . '</a>';
100
				};
101
			}
102
103
			return $this->viewResponse('individual-page', [
104
				'age'              => $age,
105
				'individual'       => $record,
106
				'individual_media' => $individual_media,
107
				'name_records'     => $name_records,
108
				'sex_records'      => $sex_records,
109
				'sidebars'         => $this->getSidebars($record),
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type Fisharebest\Webtrees\Note and Fisharebest\Webtrees\Source and Fisharebest\Webtrees\Repository and Fisharebest\Webtrees\Media and Fisharebest\Webtrees\Family; however, parameter $individual of Fisharebest\Webtrees\Htt...ntroller::getSidebars() does only seem to accept Fisharebest\Webtrees\Individual, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

109
				'sidebars'         => $this->getSidebars(/** @scrutinizer ignore-type */ $record),
Loading history...
110
				'tabs'             => $this->getTabs($record),
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type Fisharebest\Webtrees\Note and Fisharebest\Webtrees\Source and Fisharebest\Webtrees\Repository and Fisharebest\Webtrees\Media and Fisharebest\Webtrees\Family; however, parameter $individual of Fisharebest\Webtrees\Htt...alController::getTabs() does only seem to accept Fisharebest\Webtrees\Individual, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
				'tabs'             => $this->getTabs(/** @scrutinizer ignore-type */ $record),
Loading history...
111
				'user_link'        => $user_link,
112
			]);
113
		}
114
	}
115
116
	/**
117
	 * @param Request $request
118
	 *
119
	 * @return Response
120
	 */
121
	public function tab(Request $request): Response {
122
		/** @var Tree $tree */
123
		$tree   = $request->attributes->get('tree');
124
		$xref   = $request->get('xref');
125
		$record = Individual::getInstance($xref, $tree);
126
		$tab    = $request->get('module');
127
		$tabs   = Module::getActiveTabs($tree);
128
129
		if ($record === null || !array_key_exists($tab, $tabs)) {
130
			return new Response('', Response::HTTP_NOT_FOUND);
131
		} elseif (!$record->canShow()) {
132
			return new Response('', Response::HTTP_FORBIDDEN);
133
		} else {
134
			$tab = $tabs[$tab];
135
136
			$layout = view('layouts/ajax', [
137
				'content' => $tab->getTabContent($record),
0 ignored issues
show
Bug introduced by
It seems like $record can also be of type Fisharebest\Webtrees\Note and Fisharebest\Webtrees\Source and Fisharebest\Webtrees\Repository and Fisharebest\Webtrees\Media and Fisharebest\Webtrees\Family; however, parameter $individual of Fisharebest\Webtrees\Mod...erface::getTabContent() does only seem to accept Fisharebest\Webtrees\Individual, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

137
				'content' => $tab->getTabContent(/** @scrutinizer ignore-type */ $record),
Loading history...
138
			]);
139
140
			return new Response($layout);
141
		}
142
	}
143
144
	/**
145
	 * Format a name record
146
	 *
147
	 * @param int  $n
148
	 * @param Fact $fact
149
	 *
150
	 * @return string
151
	 */
152
	private function formatNameRecord($n, Fact $fact) {
153
		$individual = $fact->getParent();
154
155
		// Create a dummy record, so we can extract the formatted NAME value from it.
156
		$dummy = new Individual(
157
			'xref',
158
			"0 @xref@ INDI\n1 DEAT Y\n" . $fact->getGedcom(),
159
			null,
160
			$individual->getTree()
161
		);
162
		$dummy->setPrimaryName(0); // Make sure we use the name from "1 NAME"
163
164
		$container_class = 'card';
165
		$content_class   = 'collapse';
166
		$aria            = 'false';
167
168
		if ($n === 0) {
169
			$content_class = 'collapse show';
170
			$aria          = 'true';
171
		}
172
		if ($fact->isPendingDeletion()) {
173
			$container_class .= ' old';
174
		} elseif ($fact->isPendingAddition()) {
175
			$container_class .= ' new';
176
		}
177
178
		ob_start();
179
		echo '<dl><dt class="label">', I18N::translate('Name'), '</dt>';
180
		echo '<dd class="field">', $dummy->getFullName(), '</dd>';
181
		$ct = preg_match_all('/\n2 (\w+) (.*)/', $fact->getGedcom(), $nmatch, PREG_SET_ORDER);
182
		for ($i = 0; $i < $ct; $i++) {
183
			$tag = $nmatch[$i][1];
184
			if ($tag != 'SOUR' && $tag != 'NOTE' && $tag != 'SPFX') {
185
				echo '<dt class="label">', GedcomTag::getLabel($tag, $individual), '</dt>';
186
				echo '<dd class="field">'; // Before using dir="auto" on this field, note that Gecko treats this as an inline element but WebKit treats it as a block element
187
				if (isset($nmatch[$i][2])) {
188
					$name = e($nmatch[$i][2]);
189
					$name = str_replace('/', '', $name);
190
					$name = preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name);
191
					switch ($tag) {
192
						case 'TYPE':
193
							echo GedcomCodeName::getValue($name, $individual);
194
							break;
195
						case 'SURN':
196
							// The SURN field is not necessarily the surname.
197
							// Where it is not a substring of the real surname, show it after the real surname.
198
							$surname = e($dummy->getAllNames()[0]['surname']);
199
							$surns   = preg_replace('/, */', ' ', $nmatch[$i][2]);
200
							if (strpos($dummy->getAllNames()[0]['surname'], $surns) !== false) {
201
								echo '<span dir="auto">' . $surname . '</span>';
202
							} else {
203
								echo I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $surname . '</span>', '<span dir="auto">' . $name . '</span>');
204
							}
205
							break;
206
						default:
207
							echo '<span dir="auto">' . $name . '</span>';
208
							break;
209
					}
210
				}
211
				echo '</dd>';
212
				echo '</dl>';
213
			}
214
		}
215
		if (preg_match("/\n2 SOUR/", $fact->getGedcom())) {
216
			echo '<div id="indi_sour" class="clearfloat">', FunctionsPrintFacts::printFactSources($fact->getGedcom(), 2), '</div>';
217
		}
218
		if (preg_match("/\n2 NOTE/", $fact->getGedcom())) {
219
			echo '<div id="indi_note" class="clearfloat">', FunctionsPrint::printFactNotes($fact->getGedcom(), 2), '</div>';
220
		}
221
		$content = ob_get_clean();
222
223
		if ($individual->canEdit() && !$fact->isPendingDeletion()) {
224
			$edit_links =
225
				FontAwesome::linkIcon('delete', I18N::translate('Delete this name'), ['class' => 'btn btn-link', 'href' => '#', 'onclick' => 'return delete_fact("' . I18N::translate('Are you sure you want to delete this fact?') . '", "' . $individual->getXref() . '", "' . $fact->getFactId() . '");']) .
226
				FontAwesome::linkIcon('edit', I18N::translate('Edit the name'), ['class' => 'btn btn-link', 'href' => 'edit_interface.php?action=editname&xref=' . $individual->getXref() . '&fact_id=' . $fact->getFactId() . '&ged=' . $individual->getTree()->getNameHtml()]);
227
		} else {
228
			$edit_links = '';
229
		}
230
231
		return '
232
			<div class="' . $container_class . '">
233
        <div class="card-header" role="tab" id="name-header-' . $n . '">
234
		        <a data-toggle="collapse" data-parent="#individual-names" href="#name-content-' . $n . '" aria-expanded="' . $aria . '" aria-controls="name-content-' . $n . '">' . $dummy->getFullName() . '</a>
235
		      ' . $edit_links . '
236
        </div>
237
		    <div id="name-content-' . $n . '" class="' . $content_class . '" role="tabpanel" aria-labelledby="name-header-' . $n . '">
238
		      <div class="card-body">' . $content . '</div>
239
        </div>
240
      </div>';
241
	}
242
243
	/**
244
	 * print information for a sex record
245
	 *
246
	 * @param Fact $fact
247
	 *
248
	 * @return string
249
	 */
250
	private function formatSexRecord(Fact $fact) {
251
		$individual = $fact->getParent();
252
253
		switch ($fact->getValue()) {
254
			case 'M':
255
				$sex = I18N::translate('Male');
256
				break;
257
			case 'F':
258
				$sex = I18N::translate('Female');
259
				break;
260
			default:
261
				$sex = I18N::translateContext('unknown gender', 'Unknown');
262
				break;
263
		}
264
265
		$container_class = 'card';
266
		if ($fact->isPendingDeletion()) {
267
			$container_class .= ' old';
268
		} elseif ($fact->isPendingAddition()) {
269
			$container_class .= ' new';
270
		}
271
272
		if ($individual->canEdit() && !$fact->isPendingDeletion()) {
273
			$edit_links = FontAwesome::linkIcon('edit', I18N::translate('Edit the gender'), ['class' => 'btn btn-link', 'href' => 'edit_interface.php?action=edit&xref=' . $individual->getXref() . '&fact_id=' . $fact->getFactId() . '&ged=' . $individual->getTree()->getNameHtml()]);
274
		} else {
275
			$edit_links = '';
276
		}
277
278
		return '
279
		<div class="' . $container_class . '">
280
			<div class="card-header" role="tab" id="name-header-add">
281
				<div class="card-title mb-0">
282
					<b>' . I18N::translate('Gender') . '</b> ' . $sex . $edit_links . '
283
				</div>
284
			</div>
285
		</div>';
286
	}
287
288
	/**
289
	 * Which tabs should we show on this individual's page.
290
	 * We don't show empty tabs.
291
	 *
292
	 * @param Individual $individual
293
	 *
294
	 * @return ModuleTabInterface[]
295
	 */
296
	public function getSidebars(Individual $individual) {
297
		$sidebars = Module::getActiveSidebars($individual->getTree());
298
299
		return array_filter($sidebars, function (ModuleSidebarInterface $sidebar) use ($individual) {
300
			return $sidebar->hasSidebarContent($individual);
301
		});
302
	}
303
304
	/**
305
	 * Which tabs should we show on this individual's page.
306
	 * We don't show empty tabs.
307
	 *
308
	 * @param Individual $individual
309
	 *
310
	 * @return ModuleTabInterface[]
311
	 */
312
	public function getTabs(Individual $individual) {
313
		$tabs = Module::getActiveTabs($individual->getTree());
314
315
		return array_filter($tabs, function (ModuleTabInterface $tab) use ($individual) {
316
			return $tab->hasTabContent($individual);
317
		});
318
	}
319
}
320