Completed
Push — develop ( 989811...4ba2ac )
by Greg
10:44
created

IndividualController::getEditMenu()   C

Complexity

Conditions 12
Paths 27

Size

Total Lines 49
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 49
rs 5.1474
cc 12
eloc 26
nc 27
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 */
16
namespace Fisharebest\Webtrees\Controller;
17
18
use Fisharebest\Webtrees\Auth;
19
use Fisharebest\Webtrees\Database;
20
use Fisharebest\Webtrees\Fact;
21
use Fisharebest\Webtrees\Family;
22
use Fisharebest\Webtrees\Filter;
23
use Fisharebest\Webtrees\Functions\FunctionsPrint;
24
use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
25
use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
26
use Fisharebest\Webtrees\GedcomTag;
27
use Fisharebest\Webtrees\I18N;
28
use Fisharebest\Webtrees\Individual;
29
use Fisharebest\Webtrees\Menu;
30
use Fisharebest\Webtrees\Module;
31
use Fisharebest\Webtrees\User;
32
33
/**
34
 * Controller for the individual page
35
 */
36
class IndividualController extends GedcomRecordController {
37
	/** @var int Count of names */
38
	public $name_count  = 0;
39
40
	/** @var int Count of names. */
41
	public $total_names = 0;
42
43
	/** ModuleTabInterface[] List of tabs to show */
44
	public $tabs;
45
46
	/**
47
	 * Startup activity
48
	 */
49
	public function __construct($record) {
50
		parent::__construct($record);
51
52
		// If we can display the details, add them to the page header
53
		if ($this->record && $this->record->canShow()) {
54
			$this->setPageTitle($this->record->getFullName() . ' ' . $this->record->getLifeSpan());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\GedcomRecord as the method getLifeSpan() does only exist in the following sub-classes of Fisharebest\Webtrees\GedcomRecord: Fisharebest\Webtrees\Individual. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
55
			$this->tabs = Module::getActiveTabs($this->record->getTree());
56
		}
57
	}
58
59
	/**
60
	 * Get significant information from this page, to allow other pages such as
61
	 * charts and reports to initialise with the same records
62
	 *
63
	 * @return Individual
64
	 */
65
	public function getSignificantIndividual() {
66
		if ($this->record) {
67
			return $this->record;
68
		}
69
70
		return parent::getSignificantIndividual();
71
	}
72
73
	/**
74
	 * Get significant information from this page, to allow other pages such as
75
	 * charts and reports to initialise with the same records
76
	 *
77
	 * @return Family
78
	 */
79
	public function getSignificantFamily() {
80
		if ($this->record) {
81
			foreach ($this->record->getChildFamilies() as $family) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\GedcomRecord as the method getChildFamilies() does only exist in the following sub-classes of Fisharebest\Webtrees\GedcomRecord: Fisharebest\Webtrees\Individual. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
82
				return $family;
83
			}
84
			foreach ($this->record->getSpouseFamilies() as $family) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\GedcomRecord as the method getSpouseFamilies() does only exist in the following sub-classes of Fisharebest\Webtrees\GedcomRecord: Fisharebest\Webtrees\Individual. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
85
				return $family;
86
			}
87
		}
88
89
		return parent::getSignificantFamily();
90
	}
91
92
	/**
93
	 * Handle AJAX requests - to generate the tab content
94
	 */
95
	public function ajaxRequest() {
96
		// Search engines should not make AJAX requests
97
		if (Auth::isSearchEngine()) {
98
			http_response_code(403);
99
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method ajaxRequest() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
100
		}
101
102
		// Initialise tabs
103
		$tab = Filter::get('module');
104
105
		// A request for a non-existant tab?
106
		if (array_key_exists($tab, $this->tabs)) {
107
			$mod = $this->tabs[$tab];
108
		} else {
109
			http_response_code(404);
110
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method ajaxRequest() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
111
		}
112
113
		header("Content-Type: text/html; charset=UTF-8"); // AJAX calls do not have the meta tag headers and need this set
114
		header("X-Robots-Tag: noindex,follow"); // AJAX pages should not show up in search results, any links can be followed though
115
116
		echo $mod->getTabContent();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\Module\AbstractModule as the method getTabContent() does only exist in the following sub-classes of Fisharebest\Webtrees\Module\AbstractModule: Fisharebest\Webtrees\Module\AlbumModule, Fisharebest\Webtrees\Module\GoogleMapsModule, Fisharebest\Webtrees\Mod...ndividualFactsTabModule, Fisharebest\Webtrees\Module\InteractiveTreeModule, Fisharebest\Webtrees\Module\MediaTabModule, Fisharebest\Webtrees\Module\NotesTabModule, Fisharebest\Webtrees\Module\RelativesTabModule, Fisharebest\Webtrees\Module\SourcesTabModule, Fisharebest\Webtrees\Module\StoriesModule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
117
118
		if (WT_DEBUG_SQL) {
119
			echo Database::getQueryLog();
120
		}
121
	}
122
123
	/**
124
	 * print information for a name record
125
	 *
126
	 * @param Fact $event the event object
127
	 */
128
	public function printNameRecord(Fact $event) {
129
		$factrec = $event->getGedcom();
130
131
		// Create a dummy record, so we can extract the formatted NAME value from the event.
132
		$dummy = new Individual(
133
			'xref',
134
			"0 @xref@ INDI\n1 DEAT Y\n" . $factrec,
135
			null,
136
			$event->getParent()->getTree()
137
		);
138
		$all_names    = $dummy->getAllNames();
139
		$primary_name = $all_names[0];
140
141
		$this->name_count++;
142
		if ($this->name_count > 1) { echo '<h3 class="name_two">', $dummy->getFullName(), '</h3>'; } // Other names accordion element
143
		echo '<div class="indi_name_details';
144
		if ($event->isPendingDeletion()) {
145
			echo ' old';
146
		}
147
		if ($event->isPendingAddition()) {
148
			echo ' new';
149
		}
150
		echo '">';
151
152
		echo '<div class="name1">';
153
		echo '<dl><dt class="label">', I18N::translate('Name'), '</dt>';
154
		$dummy->setPrimaryName(0);
155
		echo '<dd class="field">', $dummy->getFullName();
156
		if ($this->name_count == 1) {
157
			if (Auth::isAdmin()) {
158
				$user = User::findByGenealogyRecord($this->record);
0 ignored issues
show
Bug introduced by
It seems like $this->record can also be of type object<Fisharebest\Webtrees\Family> or object<Fisharebest\Webtrees\GedcomRecord> or object<Fisharebest\Webtrees\Media> or object<Fisharebest\Webtrees\Note> or object<Fisharebest\Webtrees\Repository> or object<Fisharebest\Webtrees\Source>; however, Fisharebest\Webtrees\User::findByGenealogyRecord() does only seem to accept object<Fisharebest\Webtrees\Individual>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
159
				if ($user) {
160
					echo '<span> - <a class="warning" href="admin_users.php?filter=' . Filter::escapeHtml($user->getUserName()) . '">' . Filter::escapeHtml($user->getUserName()) . '</a></span>';
161
				}
162
			}
163
		}
164
		if ($this->record->canEdit() && !$event->isPendingDeletion()) {
165
			echo "<div class=\"deletelink\"><a class=\"deleteicon\" href=\"#\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '" . $this->record->getXref() . "', '" . $event->getFactId() . "');\" title=\"" . I18N::translate('Delete this name') . "\"><span class=\"link_text\">" . I18N::translate('Delete this name') . "</span></a></div>";
166
			echo "<div class=\"editlink\"><a href=\"#\" class=\"editicon\" onclick=\"edit_name('" . $this->record->getXref() . "', '" . $event->getFactId() . "'); return false;\" title=\"" . I18N::translate('Edit name') . "\"><span class=\"link_text\">" . I18N::translate('Edit name') . "</span></a></div>";
167
		}
168
		echo '</dd>';
169
		echo '</dl>';
170
		echo '</div>';
171
		$ct = preg_match_all('/\n2 (\w+) (.*)/', $factrec, $nmatch, PREG_SET_ORDER);
172
		for ($i = 0; $i < $ct; $i++) {
173
			echo '<div>';
174
				$fact = $nmatch[$i][1];
175
				if ($fact != 'SOUR' && $fact != 'NOTE' && $fact != 'SPFX') {
176
					echo '<dl><dt class="label">', GedcomTag::getLabel($fact, $this->record), '</dt>';
177
					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
178
					if (isset($nmatch[$i][2])) {
179
							$name = Filter::escapeHtml($nmatch[$i][2]);
180
							$name = str_replace('/', '', $name);
181
							$name = preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name);
182
							switch ($fact) {
183
							case 'TYPE':
184
								echo GedcomCodeName::getValue($name, $this->record);
185
								break;
186
							case 'SURN':
187
								// The SURN field is not necessarily the surname.
188
								// Where it is not a substring of the real surname, show it after the real surname.
189
								$surname = Filter::escapeHtml($primary_name['surname']);
190
								if (strpos($primary_name['surname'], str_replace(',', ' ', $nmatch[$i][2])) !== false) {
191
									echo '<span dir="auto">' . $surname . '</span>';
192
								} else {
193
									echo I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $surname . '</span>', '<span dir="auto">' . $name . '</span>');
194
								}
195
								break;
196
							default:
197
								echo '<span dir="auto">' . $name . '</span>';
198
								break;
199
							}
200
						}
201
					echo '</dd>';
202
					echo '</dl>';
203
				}
204
			echo '</div>';
205
		}
206
		if (preg_match("/\n2 SOUR/", $factrec)) {
207
			echo '<div id="indi_sour" class="clearfloat">', FunctionsPrintFacts::printFactSources($factrec, 2), '</div>';
0 ignored issues
show
Security Cross-Site Scripting introduced by
\Fisharebest\Webtrees\Fu...actSources($factrec, 2) can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
208
		}
209
		if (preg_match("/\n2 NOTE/", $factrec)) {
210
			echo '<div id="indi_note" class="clearfloat">', FunctionsPrint::printFactNotes($factrec, 2), '</div>';
0 ignored issues
show
Security Cross-Site Scripting introduced by
\Fisharebest\Webtrees\Fu...tFactNotes($factrec, 2) can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
211
		}
212
		echo '</div>';
213
	}
214
215
	/**
216
	 * print information for a sex record
217
	 *
218
	 * @param Fact $event the Event object
219
	 */
220
	public function printSexRecord(Fact $event) {
221
		$sex = $event->getValue();
222
		if (empty($sex)) {
223
			$sex = 'U';
224
		}
225
		echo '<span id="sex" class="';
226
		if ($event->isPendingDeletion()) {
227
			echo 'old ';
228
		}
229
		if ($event->isPendingAddition()) {
230
			echo 'new ';
231
		}
232
		switch ($sex) {
233 View Code Duplication
		case 'M':
234
			echo 'male_gender"';
235
			if ($event->canEdit()) {
236
				echo ' title="', I18N::translate('Male'), ' - ', I18N::translate('Edit'), '"';
237
				echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">';
238
			 } else {
239
				echo ' title="', I18N::translate('Male'), '">';
240
			 }
241
			break;
242 View Code Duplication
		case 'F':
243
			echo 'female_gender"';
244
			if ($event->canEdit()) {
245
				echo ' title="', I18N::translate('Female'), ' - ', I18N::translate('Edit'), '"';
246
				echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">';
247
			 } else {
248
				echo ' title="', I18N::translate('Female'), '">';
249
			 }
250
			break;
251 View Code Duplication
		default:
252
			echo 'unknown_gender"';
253
			if ($event->canEdit()) {
254
				echo ' title="', I18N::translateContext('unknown gender', 'Unknown'), ' - ', I18N::translate('Edit'), '"';
255
				echo ' onclick="edit_record(\'' . $this->record->getXref() . '\', \'' . $event->getFactId() . '\'); return false;">';
256
			 } else {
257
				echo ' title="', I18N::translateContext('unknown gender', 'Unknown'), '">';
258
			 }
259
			break;
260
		}
261
		echo '</span>';
262
	}
263
	/**
264
	 * get edit menu
265
	 */
266
	public function getEditMenu() {
267
		if (!$this->record || $this->record->isPendingDeletion()) {
268
			return null;
269
		}
270
		// edit menu
271
		$menu = new Menu(I18N::translate('Edit'), '#', 'menu-indi');
272
273
		if (Auth::isEditor($this->record->getTree())) {
274
			$menu->addSubmenu(new Menu(I18N::translate('Add a new name'), '#', 'menu-indi-addname', array(
275
				'onclick' => 'return add_name("' . $this->record->getXref() . '");',
276
			)));
277
278
			$has_sex_record = false;
279
			foreach ($this->record->getFacts() as $fact) {
280
				if ($fact->getTag() === 'SEX' && $fact->canEdit()) {
281
					$menu->addSubmenu(new Menu(I18N::translate('Edit gender'), '#', 'menu-indi-editsex', array(
282
						'onclick' => 'return edit_record("' . $this->record->getXref() . '", "' . $fact->getFactId() . '");',
283
					)));
284
					$has_sex_record = true;
285
					break;
286
				}
287
			}
288
			if (!$has_sex_record) {
289
				$menu->addSubmenu(new Menu(I18N::translate('Edit gender'), '#', 'menu-indi-editsex', array(
290
					'return add_new_record("' . $this->record->getXref() . '", "SEX");',
291
				)));
292
			}
293
294
			if (count($this->record->getSpouseFamilies()) > 1) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\GedcomRecord as the method getSpouseFamilies() does only exist in the following sub-classes of Fisharebest\Webtrees\GedcomRecord: Fisharebest\Webtrees\Individual. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
295
				$menu->addSubmenu(new Menu(I18N::translate('Re-order families'), '#', 'menu-indi-orderfam', array(
296
					'onclick' => 'return reorder_families("' . $this->record->getXref() . '");',
297
				)));
298
			}
299
300
			// delete
301
			$menu->addSubmenu(new Menu(I18N::translate('Delete'), '#', 'menu-indi-del', array(
302
				'onclick' => 'return delete_record("' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($this->record->getFullName()))) . '", "' . $this->record->getXref() . '");',
303
			)));
304
		}
305
306
		// edit raw
307
		if (Auth::isAdmin() || Auth::isEditor($this->record->getTree()) && $this->record->getTree()->getPreference('SHOW_GEDCOM_RECORD')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->record->getTree()...e('SHOW_GEDCOM_RECORD') of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
308
			$menu->addSubmenu(new Menu(I18N::translate('Edit raw GEDCOM'), '#', 'menu-indi-editraw', array(
309
				'onclick' => 'return edit_raw("' . $this->record->getXref() . '");',
310
			)));
311
		}
312
313
		return $menu;
314
	}
315
316
	/**
317
	 * get the person box stylesheet class for the given person
318
	 *
319
	 * @param Individual $person
320
	 *
321
	 * @return string returns 'person_box', 'person_boxF', or 'person_boxNN'
322
	 */
323
	public function getPersonStyle($person) {
324
		switch ($person->getSex()) {
325
		case 'M':
326
			$class = 'person_box';
327
			break;
328
		case 'F':
329
			$class = 'person_boxF';
330
			break;
331
		default:
332
			$class = 'person_boxNN';
333
			break;
334
		}
335
		if ($person->isPendingDeletion()) {
336
			$class .= ' old';
337
		} elseif ($person->isPendingAddtion()) {
338
			$class .= ' new';
339
		}
340
341
		return $class;
342
	}
343
344
	/**
345
	 * Get significant information from this page, to allow other pages such as
346
	 * charts and reports to initialise with the same records
347
	 *
348
	 * @return string
349
	 */
350 View Code Duplication
	public function getSignificantSurname() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
		if ($this->record) {
352
			list($surn) = explode(',', $this->record->getSortName());
353
354
			return $surn;
355
		} else {
356
			return '';
357
		}
358
	}
359
360
	/**
361
	 * Get the contents of sidebar.
362
	 *
363
	 * @return string
364
	 */
365
	public function getSideBarContent() {
366
		global $controller;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
367
368
		$html   = '';
369
		$active = 0;
370
		$n      = 0;
371
		foreach (Module::getActiveSidebars($this->record->getTree()) as $mod) {
372
			if ($mod->hasSidebarContent()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\Module\AbstractModule as the method hasSidebarContent() does only exist in the following sub-classes of Fisharebest\Webtrees\Module\AbstractModule: Fisharebest\Webtrees\Module\ClippingsCartModule, Fisharebest\Webtrees\Module\DescendancyModule, Fisharebest\Webtrees\Module\ExtraInformationModule, Fisharebest\Webtrees\Module\FamiliesSidebarModule, Fisharebest\Webtrees\Module\FamilyNavigatorModule, Fisharebest\Webtrees\Mod...IndividualSidebarModule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
373
				$html .= '<h3 id="' . $mod->getName() . '"><a href="#">' . $mod->getTitle() . '</a></h3>';
374
				$html .= '<div id="sb_content_' . $mod->getName() . '">' . $mod->getSidebarContent() . '</div>';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Fisharebest\Webtrees\Module\AbstractModule as the method getSidebarContent() does only exist in the following sub-classes of Fisharebest\Webtrees\Module\AbstractModule: Fisharebest\Webtrees\Module\ClippingsCartModule, Fisharebest\Webtrees\Module\DescendancyModule, Fisharebest\Webtrees\Module\ExtraInformationModule, Fisharebest\Webtrees\Module\FamiliesSidebarModule, Fisharebest\Webtrees\Module\FamilyNavigatorModule, Fisharebest\Webtrees\Mod...IndividualSidebarModule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
375
				// The family navigator should be opened by default
376
				if ($mod->getName() == 'family_nav') {
377
					$active = $n;
378
				}
379
				++$n;
380
			}
381
		}
382
383
		if ($html) {
384
			$controller
385
				->addInlineJavascript('
386
				jQuery("#sidebarAccordion").accordion({
387
					active:' . $active . ',
388
					heightStyle: "content",
389
					collapsible: true,
390
				});
391
			');
392
393
			return '<div id="sidebar"><div id="sidebarAccordion">' . $html . '</div></div>';
394
		} else {
395
			return '';
396
		}
397
	}
398
399
	/**
400
	 * Get the description for the family.
401
	 *
402
	 * For example, "XXX's family with new wife".
403
	 *
404
	 * @param Family     $family
405
	 * @param Individual $individual
406
	 *
407
	 * @return string
408
	 */
409
	public function getSpouseFamilyLabel(Family $family, Individual $individual) {
410
		$spouse = $family->getSpouse($individual);
411
		if ($spouse) {
412
			return
413
				/* I18N: %s is the spouse name */
414
				I18N::translate('Family with %s', $spouse->getFullName());
415
		} else {
416
			return $family->getFullName();
417
		}
418
	}
419
}
420