Passed
Branch master (60084b)
by Greg
10:38
created

IndividualListController::surnameInitial()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 11
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
namespace Fisharebest\Webtrees\Controller;
17
18
use Fisharebest\Webtrees\Database;
19
use Fisharebest\Webtrees\Family;
20
use Fisharebest\Webtrees\Filter;
21
use Fisharebest\Webtrees\Html;
22
use Fisharebest\Webtrees\I18N;
23
use Fisharebest\Webtrees\Individual;
24
25
/**
26
 * Controller for the individual list
27
 */
28
class IndividualListController extends PageController {
29
	/**
30
	 * Get a list of initial letters, for lists of names.
31
	 *
32
	 * @param string $locale Return the alphabet for this locale
33
	 *
34
	 * @return string[]
35
	 */
36
	private function getAlphabetForLocale($locale) {
37
		switch ($locale) {
38
			case 'ar':
39
				return [
40
					'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ك', 'ل', 'م', 'ن', 'ه', 'و', 'ي', 'آ', 'ة', 'ى', 'ی',
41
				];
42
			case 'cs':
43
				return [
44
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'CH', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
45
				];
46
			case 'da':
47
			case 'nb':
48
			case 'nn':
49
				return [
50
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Æ', 'Ø', 'Å',
51
				];
52
			case 'el':
53
				return [
54
					'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π', 'Ρ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω',
55
				];
56
			case 'es':
57
				return [
58
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'Ñ', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
59
				];
60
			case 'et':
61
				return [
62
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'Š', 'Z', 'Ž', 'T', 'U', 'V', 'W', 'Õ', 'Ä', 'Ö', 'Ü', 'X', 'Y',
63
				];
64
			case 'fi':
65
			case 'sv':
66
				return [
67
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö',
68
				];
69
			case 'he':
70
				return [
71
					'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק', 'ר', 'ש', 'ת',
72
				];
73
			case 'hu':
74
				return [
75
					'A', 'B', 'C', 'CS', 'D', 'DZ', 'DZS', 'E', 'F', 'G', 'GY', 'H', 'I', 'J', 'K', 'L', 'LY', 'M', 'N', 'NY', 'O', 'Ö', 'P', 'Q', 'R', 'S', 'SZ', 'T', 'TY', 'U', 'Ü', 'V', 'W', 'X', 'Y', 'Z', 'ZS',
76
				];
77
			case 'lt':
78
				return [
79
					'A', 'Ą', 'B', 'C', 'Č', 'D', 'E', 'Ę', 'Ė', 'F', 'G', 'H', 'I', 'Y', 'Į', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'Š', 'T', 'U', 'Ų', 'Ū', 'V', 'Z', 'Ž',
80
				];
81
			case 'nl':
82
				return [
83
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'IJ',
84
				];
85
			case 'pl':
86
				return [
87
					'A', 'B', 'C', 'Ć', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'Ł', 'M', 'N', 'O', 'Ó', 'P', 'Q', 'R', 'S', 'Ś', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ź', 'Ż',
88
				];
89
			case 'ro':
90
				return [
91
					'A', 'Ă', 'Â', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'Î', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'Ş', 'T', 'Ţ', 'U', 'V', 'W', 'X', 'Y', 'Z',
92
				];
93
			case 'ru':
94
				return [
95
					'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я',
96
				];
97
			case 'sk':
98
				return [
99
					'A', 'Á', 'Ä', 'B', 'C', 'Č', 'D', 'Ď', 'E', 'É', 'F', 'G', 'H', 'I', 'Í', 'J', 'K', 'L', 'Ľ', 'Ĺ', 'M', 'N', 'Ň', 'O', 'Ó', 'Ô', 'P', 'Q', 'R', 'Ŕ', 'S', 'Š', 'T', 'Ť', 'U', 'Ú', 'V', 'W', 'X', 'Y', 'Ý', 'Z', 'Ž',
100
				];
101
			case 'sl':
102
				return [
103
					'A', 'B', 'C', 'Č', 'Ć', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'Š', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ž',
104
				];
105
			case 'sr':
106
				return [
107
					'A', 'B', 'C', 'Č', 'Ć', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'Š', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ž',
108
				];
109
			case 'tr':
110
				return [
111
					'A', 'B', 'C', 'Ç', 'D', 'E', 'F', 'G', 'Ğ', 'H', 'I', 'İ', 'J', 'K', 'L', 'M', 'N', 'O', 'Ö', 'P', 'R', 'S', 'Ş', 'T', 'U', 'Ü', 'V', 'Y', 'Z',
112
				];
113
			default:
114
				return [
115
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
116
				];
117
		}
118
	}
119
120
	/**
121
	 * Get the initial letter of a name, taking care of multi-letter sequences and equivalences.
122
	 *
123
	 * @param string $name
124
	 *
125
	 * @return string
126
	 */
127
	public function initialLetter($name) {
128
		$name = I18N::strtoupper($name);
129
		switch (WT_LOCALE) {
130
			case 'cs':
131
				if (substr($name, 0, 2) == 'CH') {
132
					return 'CH';
133
				}
134
				break;
135
			case 'da':
136
			case 'nb':
137
			case 'nn':
138
				if (substr($name, 0, 2) == 'AA') {
139
					return 'Å';
140
				}
141
				break;
142
			case 'hu':
143
				if (substr($name, 0, 2) == 'CS') {
144
					return 'CS';
145
				} elseif (substr($name, 0, 3) == 'DZS') {
146
					return 'DZS';
147
				} elseif (substr($name, 0, 2) == 'DZ') {
148
					return 'DZ';
149
				} elseif (substr($name, 0, 2) == 'GY') {
150
					return 'GY';
151
				} elseif (substr($name, 0, 2) == 'LY') {
152
					return 'LY';
153
				} elseif (substr($name, 0, 2) == 'NY') {
154
					return 'NY';
155
				} elseif (substr($name, 0, 2) == 'SZ') {
156
					return 'SZ';
157
				} elseif (substr($name, 0, 2) == 'TY') {
158
					return 'TY';
159
				} elseif (substr($name, 0, 2) == 'ZS') {
160
					return 'ZS';
161
				}
162
				break;
163
			case 'nl':
164
				if (substr($name, 0, 2) == 'IJ') {
165
					return 'IJ';
166
				}
167
				break;
168
		}
169
		// No special rules - just take the first character
170
		return mb_substr($name, 0, 1);
171
	}
172
173
	/**
174
	 * Generate SQL to match a given letter, taking care of cases that
175
	 * are not covered by the collation setting.
176
	 *
177
	 * We must consider:
178
	 * potential substrings, such as Czech "CH" and "C"
179
	 * equivalent letters, such as Danish "AA" and "Å"
180
	 *
181
	 * We COULD write something that handles all languages generically,
182
	 * but its performance would most likely be poor.
183
	 *
184
	 * For languages that don't appear in this list, we could write
185
	 * simpler versions of the surnameAlpha() and givenAlpha() functions,
186
	 * but it gives no noticable improvement in performance.
187
	 *
188
	 * @param string $field
189
	 * @param string $letter
190
	 *
191
	 * @return string
192
	 */
193
	private function getInitialSql($field, $letter) {
194
		switch (WT_LOCALE) {
195
			case 'cs':
196
				switch ($letter) {
197
					case 'C': return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CH%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
198
				}
199
			break;
200
			case 'da':
201
			case 'nb':
202
			case 'nn':
203
				switch ($letter) {
204
					// AA gets listed under Å
205
					case 'A': return $field . " LIKE 'A%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'AA%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
206
					case 'Å': return "(" . $field . " LIKE 'Å%' COLLATE " . I18N::collation() . " OR " . $field . " LIKE 'AA%' COLLATE " . I18N::collation() . ")";
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
207
				}
208
			break;
209
			case 'hu':
210
				switch ($letter) {
211
					case 'C':  return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CS%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
212
					case 'D':  return $field . " LIKE 'D%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZ%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
213
					case 'DZ': return $field . " LIKE 'DZ%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZS%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
214
					case 'G':  return $field . " LIKE 'G%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'GY%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
215
					case 'L':  return $field . " LIKE 'L%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'LY%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
216
					case 'N':  return $field . " LIKE 'N%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'NY%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
217
					case 'S':  return $field . " LIKE 'S%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'SZ%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
218
					case 'T':  return $field . " LIKE 'T%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'TY%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
219
					case 'Z':  return $field . " LIKE 'Z%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'ZS%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
220
				}
221
			break;
222
			case 'nl':
223
				switch ($letter) {
224
					case 'I': return $field . " LIKE 'I%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'IJ%' COLLATE " . I18N::collation();
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
225
				}
226
			break;
227
		}
228
229
		// Easy cases: the MySQL collation rules take care of it
230
		return "$field LIKE CONCAT('@'," . Database::quote($letter) . ",'%') COLLATE " . I18N::collation() . " ESCAPE '@'";
0 ignored issues
show
Deprecated Code introduced by
The function Fisharebest\Webtrees\Database::quote() has been deprecated: We should use bind-variables instead. ( Ignorable by Annotation )

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

230
		return "$field LIKE CONCAT('@'," . /** @scrutinizer ignore-deprecated */ Database::quote($letter) . ",'%') COLLATE " . I18N::collation() . " ESCAPE '@'";

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
231
	}
232
233
	/**
234
	 * Get a list of initial surname letters for indilist.php and famlist.php
235
	 *
236
	 * @param bool $marnm  if set, include married names
237
	 * @param bool $fams   if set, only consider individuals with FAMS records
238
	 * @param bool $totals if set, count the number of names beginning with each letter
239
	 *
240
	 * @return int[]
241
	 */
242
	public function surnameAlpha($marnm, $fams, $totals = true) {
243
		$alphas = [];
244
245
		$sql =
246
			"SELECT SQL_CACHE COUNT(n_id)" .
247
			" FROM `##name` " .
248
			($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") .
249
			" WHERE n_file=" . $this->tree()->getTreeId() .
250
			($marnm ? "" : " AND n_type!='_MARNM'");
251
252
		// Fetch all the letters in our alphabet, whether or not there
253
		// are any names beginning with that letter. It looks better to
254
		// show the full alphabet, rather than omitting rare letters such as X
255
		foreach ($this->getAlphabetForLocale(WT_LOCALE) as $letter) {
256
			$count = 1;
257
			if ($totals) {
258
				$count = Database::prepare($sql . " AND " . $this->getInitialSql('n_surn', $letter))->fetchOne();
259
			}
260
			$alphas[$letter] = $count;
261
		}
262
263
		// Now fetch initial letters that are not in our alphabet,
264
		// including "@" (for "@N.N.") and "" for no surname.
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
265
		$sql =
266
			"SELECT SQL_CACHE initial, count FROM (SELECT UPPER(LEFT(n_surn, 1)) AS initial, COUNT(n_id) AS count" .
267
			" FROM `##name` " .
268
			($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") .
269
			" WHERE n_file = :tree_id AND n_surn <> ''" .
270
			($marnm ? "" : " AND n_type != '_MARNM'");
271
272
		$args = [
273
			'tree_id' => $this->tree()->getTreeId(),
274
		];
275
276
		foreach ($this->getAlphabetForLocale(WT_LOCALE) as $n => $letter) {
277
			$sql .= " AND n_surn COLLATE :collate_" . $n . " NOT LIKE :letter_" . $n;
278
			$args['collate_' . $n] = I18N::collation();
279
			$args['letter_' . $n]  = $letter . '%';
280
		}
281
		$sql .= " GROUP BY UPPER(LEFT(n_surn, 1))) AS subquery ORDER BY initial = '', initial = '@', initial";
282
		foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) {
283
			$alphas[$alpha] = $count;
284
		}
285
286
		// Names with no surname
287
		$sql =
288
			"SELECT SQL_CACHE COUNT(n_id)" .
289
			" FROM `##name` " .
290
			($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") .
291
			" WHERE n_file = :tree_id AND n_surn = ''" .
292
			($marnm ? "" : " AND n_type != '_MARNM'");
293
294
		$args = [
295
			'tree_id' => $this->tree()->getTreeId(),
296
		];
297
298
		$count_no_surname = (int) Database::prepare($sql)->execute($args)->fetchOne();
299
		if ($count_no_surname !== 0) {
300
			// Special code to indicate "no surname"
301
			$alphas[','] = $count_no_surname;
302
		}
303
304
		return $alphas;
305
	}
306
307
	/**
308
	 * Get a list of initial given name letters for indilist.php and famlist.php
309
	 *
310
	 * @param string $surn   if set, only consider people with this surname
311
	 * @param string $salpha if set, only consider surnames starting with this letter
312
	 * @param bool   $marnm  if set, include married names
313
	 * @param bool   $fams   if set, only consider individuals with FAMS records
314
	 *
315
	 * @return int[]
316
	 */
317
	public function givenAlpha($surn, $salpha, $marnm, $fams) {
318
		$alphas = [];
319
320
		$sql =
321
			"SELECT SQL_CACHE COUNT(DISTINCT n_id)" .
322
			" FROM `##name`" .
323
			($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") .
324
			" WHERE n_file=" . $this->tree()->getTreeId() . " " .
325
			($marnm ? "" : " AND n_type!='_MARNM'");
326
327
		if ($surn) {
328
			$sql .= " AND n_surn=" . Database::quote($surn) . " COLLATE '" . I18N::collation() . "'";
0 ignored issues
show
Deprecated Code introduced by
The function Fisharebest\Webtrees\Database::quote() has been deprecated: We should use bind-variables instead. ( Ignorable by Annotation )

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

328
			$sql .= " AND n_surn=" . /** @scrutinizer ignore-deprecated */ Database::quote($surn) . " COLLATE '" . I18N::collation() . "'";

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
329
		} elseif ($salpha == ',') {
330
			$sql .= " AND n_surn=''";
331
		} elseif ($salpha == '@') {
332
			$sql .= " AND n_surn='@N.N.'";
333
		} elseif ($salpha) {
334
			$sql .= " AND " . $this->getInitialSql('n_surn', $salpha);
335
		} else {
336
			// All surnames
337
			$sql .= " AND n_surn NOT IN ('', '@N.N.')";
338
		}
339
340
		// Fetch all the letters in our alphabet, whether or not there
341
		// are any names beginning with that letter. It looks better to
342
		// show the full alphabet, rather than omitting rare letters such as X
343
		foreach ($this->getAlphabetForLocale(WT_LOCALE) as $letter) {
344
			$count           = Database::prepare($sql . " AND " . $this->getInitialSql('n_givn', $letter))->fetchOne();
345
			$alphas[$letter] = $count;
346
		}
347
348
		// Now fetch initial letters that are not in our alphabet,
349
		// including "@" (for "@N.N.") and "" for no surname
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
350
		$sql =
351
			"SELECT SQL_CACHE initial, total FROM (SELECT UPPER(LEFT(n_givn, 1)) AS initial, COUNT(DISTINCT n_id) AS total" .
352
			" FROM `##name` " .
353
			($fams ? " JOIN `##link` ON (n_id = l_from AND n_file = l_file AND l_type = 'FAMS') " : "") .
354
			" WHERE n_file = :tree_id" .
355
			($marnm ? "" : " AND n_type != '_MARNM'");
356
357
		$args = [
358
			'tree_id' => $this->tree()->getTreeId(),
359
		];
360
361
		if ($surn) {
362
			$sql .= " AND n_surn COLLATE :collate_1 = :surn";
363
			$args['collate_1'] = I18N::collation();
364
			$args['surn']      = $surn;
365
		} elseif ($salpha === ',') {
366
			$sql .= " AND n_surn = ''";
367
		} elseif ($salpha === '@') {
368
			$sql .= " AND n_surn = '@N.N.'";
369
		} elseif ($salpha) {
370
			$sql .= " AND " . $this->getInitialSql('n_surn', $salpha);
371
		} else {
372
			// All surnames
373
			$sql .= " AND n_surn NOT IN ('', '@N.N.')";
374
		}
375
376
		foreach ($this->getAlphabetForLocale(WT_LOCALE) as $letter) {
377
			$sql .= " AND n_givn NOT LIKE '" . $letter . "%' COLLATE " . I18N::collation();
378
		}
379
		$sql .= " GROUP BY UPPER(LEFT(n_givn, 1))) AS subquery ORDER BY initial = '@', initial = '', initial";
380
381
		foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) {
382
			$alphas[$alpha] = $count;
383
		}
384
385
		return $alphas;
386
	}
387
388
	/**
389
	 * Get a list of actual surnames and variants, based on a "root" surname.
390
	 *
391
	 * @param string $surn   if set, only fetch people with this surname
392
	 * @param string $salpha if set, only consider surnames starting with this letter
393
	 * @param bool   $marnm  if set, include married names
394
	 * @param bool   $fams   if set, only consider individuals with FAMS records
395
	 *
396
	 * @return array
397
	 */
398
	public function surnames($surn, $salpha, $marnm, $fams) {
399
		$sql =
400
			"SELECT SQL_CACHE n2.n_surn, n1.n_surname, n1.n_id" .
401
			" FROM `##name` n1 " .
402
			($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") .
403
			" JOIN (SELECT n_surn COLLATE :collate_0 AS n_surn, n_file FROM `##name`" .
404
			" WHERE n_file = :tree_id" .
405
			($marnm ? "" : " AND n_type != '_MARNM'");
406
407
		$args = [
408
			'tree_id'   => $this->tree()->getTreeId(),
409
			'collate_0' => I18N::collation(),
410
		];
411
412
		if ($surn) {
413
			$sql .= " AND n_surn COLLATE :collate_1 = :surn";
414
			$args['collate_1'] = I18N::collation();
415
			$args['surn']      = $surn;
416
		} elseif ($salpha === ',') {
417
			$sql .= " AND n_surn = ''";
418
		} elseif ($salpha === '@') {
419
			$sql .= " AND n_surn = '@N.N.'";
420
		} elseif ($salpha) {
421
			$sql .= " AND " . $this->getInitialSql('n_surn', $salpha);
422
		} else {
423
			// All surnames
424
			$sql .= " AND n_surn NOT IN ('', '@N.N.')";
425
		}
426
		$sql .= " GROUP BY n_surn COLLATE :collate_2, n_file) AS n2 ON (n1.n_surn = n2.n_surn COLLATE :collate_3 AND n1.n_file = n2.n_file)";
427
		$args['collate_2'] = I18N::collation();
428
		$args['collate_3'] = I18N::collation();
429
430
		$list = [];
431
		foreach (Database::prepare($sql)->execute($args)->fetchAll() as $row) {
432
			$list[I18N::strtoupper($row->n_surn)][$row->n_surname][$row->n_id] = true;
433
		}
434
435
		return $list;
436
	}
437
438
	/**
439
	 * Fetch a list of individuals with specified names
440
	 *
441
	 * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@"
442
	 * To search for names with no surnames, use $salpha=","
443
	 *
444
	 * @param string $surn   if set, only fetch people with this surname
445
	 * @param string $salpha if set, only fetch surnames starting with this letter
446
	 * @param string $galpha if set, only fetch given names starting with this letter
447
	 * @param bool   $marnm  if set, include married names
448
	 * @param bool   $fams   if set, only fetch individuals with FAMS records
449
	 *
450
	 * @return Individual[]
451
	 */
452
	public function individuals($surn, $salpha, $galpha, $marnm, $fams) {
453
		$sql =
454
			"SELECT i_id AS xref, i_gedcom AS gedcom, n_full " .
455
			"FROM `##individuals` " .
456
			"JOIN `##name` ON n_id = i_id AND n_file = i_file " .
457
			($fams ? "JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") .
458
			"WHERE n_file = :tree_id " .
459
			($marnm ? "" : "AND n_type != '_MARNM'");
460
461
		$args = [
462
			'tree_id' => $this->tree()->getTreeId(),
463
		];
464
465
		if ($surn) {
466
			$sql .= " AND n_surn COLLATE :collate_1 = :surn";
467
			$args['collate_1'] = I18N::collation();
468
			$args['surn']      = $surn;
469
		} elseif ($salpha === ',') {
470
			$sql .= " AND n_surn = ''";
471
		} elseif ($salpha === '@') {
472
			$sql .= " AND n_surn = '@N.N.'";
473
		} elseif ($salpha) {
474
			$sql .= " AND " . $this->getInitialSql('n_surn', $salpha);
475
		} else {
476
			// All surnames
477
			$sql .= " AND n_surn NOT IN ('', '@N.N.')";
478
		}
479
		if ($galpha) {
480
			$sql .= " AND " . $this->getInitialSql('n_givn', $galpha);
481
		}
482
483
		$sql .= " ORDER BY CASE n_surn WHEN '@N.N.' THEN 1 ELSE 0 END, n_surn COLLATE :collate_2, CASE n_givn WHEN '@P.N.' THEN 1 ELSE 0 END, n_givn COLLATE :collate_3";
484
		$args['collate_2'] = I18N::collation();
485
		$args['collate_3'] = I18N::collation();
486
487
		$list = [];
488
		$rows = Database::prepare($sql)->execute($args)->fetchAll();
489
		foreach ($rows as $row) {
490
			$person = Individual::getInstance($row->xref, $this->tree(), $row->gedcom);
491
			// The name from the database may be private - check the filtered list...
492
			foreach ($person->getAllNames() as $n => $name) {
493
				if ($name['fullNN'] == $row->n_full) {
494
					$person->setPrimaryName($n);
495
					// We need to clone $person, as we may have multiple references to the
496
					// same person in this list, and the "primary name" would otherwise
497
					// be shared amongst all of them.
498
					$list[] = clone $person;
499
					break;
500
				}
501
			}
502
		}
503
504
		return $list;
505
	}
506
507
	/**
508
	 * Fetch a list of families with specified names
509
	 *
510
	 * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@"
511
	 * To search for names with no surnames, use $salpha=","
512
	 *
513
	 * @param string $surn   if set, only fetch people with this surname
514
	 * @param string $salpha if set, only fetch surnames starting with this letter
515
	 * @param string $galpha if set, only fetch given names starting with this letter
516
	 * @param bool   $marnm  if set, include married names
517
	 *
518
	 * @return Family[]
519
	 */
520
	public function families($surn, $salpha, $galpha, $marnm) {
521
		$list = [];
522
		foreach ($this->individuals($surn, $salpha, $galpha, $marnm, true) as $indi) {
523
			foreach ($indi->getSpouseFamilies() as $family) {
524
				$list[$family->getXref()] = $family;
525
			}
526
		}
527
		usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
528
529
		return $list;
530
	}
531
532
	/**
533
	 * Some initial letters have a special meaning
534
	 *
535
	 * @param string $initial
536
	 *
537
	 * @return string
538
	 */
539
	public function givenNameInitial($initial) {
540
		switch ($initial) {
541
			case '@':
542
				return I18N::translateContext('Unknown given name', '…');
543
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
544
			default:
545
				return e($initial);
546
				break;
547
		}
548
	}
549
550
	/**
551
	 * Some initial letters have a special meaning
552
	 *
553
	 * @param string $initial
554
	 *
555
	 * @return string
556
	 */
557
	public function surnameInitial($initial) {
558
		switch ($initial) {
559
			case '@':
560
				return I18N::translateContext('Unknown surname', '…');
561
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
562
			case ',':
563
				return I18N::translate('None');
564
				break;
565
			default:
566
				return e($initial);
567
				break;
568
		}
569
	}
570
}
571