diff options
Diffstat (limited to 'app/Query/QueryName.php')
| -rw-r--r-- | app/Query/QueryName.php | 1520 |
1 files changed, 1048 insertions, 472 deletions
diff --git a/app/Query/QueryName.php b/app/Query/QueryName.php index 998ddf2fbf..febe155993 100644 --- a/app/Query/QueryName.php +++ b/app/Query/QueryName.php @@ -24,512 +24,1088 @@ use Fisharebest\Webtrees\Tree; /** * Generate lists for indilist.php and famlist.php */ -class QueryName { - /** - * Get a list of initial letters, for lists of names. - * - * @param string $locale Return the alphabet for this locale - * - * @return string[] - */ - private static function getAlphabetForLocale($locale) { - switch ($locale) { - case 'ar': - return [ - 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ك', 'ل', 'م', 'ن', 'ه', 'و', 'ي', 'آ', 'ة', 'ى', 'ی', - ]; - case 'cs': - return [ - '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', - ]; - case 'da': - case 'nb': - case 'nn': - return [ - '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', 'Æ', 'Ø', 'Å', - ]; - case 'el': - return [ - 'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π', 'Ρ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', - ]; - case 'es': - return [ - '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', - ]; - case 'et': - return [ - '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', - ]; - case 'fi': - case 'sv': - return [ - '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', 'Å', 'Ä', 'Ö', - ]; - case 'he': - return [ - 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק', 'ר', 'ש', 'ת', - ]; - case 'hu': - return [ - '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', - ]; - case 'lt': - return [ - 'A', 'Ą', 'B', 'C', 'Č', 'D', 'E', 'Ę', 'Ė', 'F', 'G', 'H', 'I', 'Y', 'Į', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'Š', 'T', 'U', 'Ų', 'Ū', 'V', 'Z', 'Ž', - ]; - case 'nl': - return [ - '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', - ]; - case 'pl': - return [ - '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', 'Ź', 'Ż', - ]; - case 'ro': - return [ - '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', - ]; - case 'ru': - return [ - 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', - ]; - case 'sk': - return [ - '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', 'Ž', - ]; - case 'sl': - return [ - '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', 'Ž', - ]; - case 'sr': - return [ - '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', 'Ž', - ]; - case 'tr': - return [ - 'A', 'B', 'C', 'Ç', 'D', 'E', 'F', 'G', 'Ğ', 'H', 'I', 'İ', 'J', 'K', 'L', 'M', 'N', 'O', 'Ö', 'P', 'R', 'S', 'Ş', 'T', 'U', 'Ü', 'V', 'Y', 'Z', - ]; - default: - return [ - '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', - ]; - } - } +class QueryName +{ + /** + * Get a list of initial letters, for lists of names. + * + * @param string $locale Return the alphabet for this locale + * + * @return string[] + */ + private static function getAlphabetForLocale($locale) + { + switch ($locale) { + case 'ar': + return [ + 'ا', + 'ب', + 'ت', + 'ث', + 'ج', + 'ح', + 'خ', + 'د', + 'ذ', + 'ر', + 'ز', + 'س', + 'ش', + 'ص', + 'ض', + 'ط', + 'ظ', + 'ع', + 'غ', + 'ف', + 'ق', + 'ك', + 'ل', + 'م', + 'ن', + 'ه', + 'و', + 'ي', + 'آ', + 'ة', + 'ى', + 'ی', + ]; + case 'cs': + return [ + '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', + ]; + case 'da': + case 'nb': + case 'nn': + return [ + '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', + 'Æ', + 'Ø', + 'Å', + ]; + case 'el': + return [ + 'Α', + 'Β', + 'Γ', + 'Δ', + 'Ε', + 'Ζ', + 'Η', + 'Θ', + 'Ι', + 'Κ', + 'Λ', + 'Μ', + 'Ν', + 'Ξ', + 'Ο', + 'Π', + 'Ρ', + 'Σ', + 'Τ', + 'Υ', + 'Φ', + 'Χ', + 'Ψ', + 'Ω', + ]; + case 'es': + return [ + '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', + ]; + case 'et': + return [ + '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', + ]; + case 'fi': + case 'sv': + return [ + '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', + 'Å', + 'Ä', + 'Ö', + ]; + case 'he': + return [ + 'א', + 'ב', + 'ג', + 'ד', + 'ה', + 'ו', + 'ז', + 'ח', + 'ט', + 'י', + 'כ', + 'ל', + 'מ', + 'נ', + 'ס', + 'ע', + 'פ', + 'צ', + 'ק', + 'ר', + 'ש', + 'ת', + ]; + case 'hu': + return [ + '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', + ]; + case 'lt': + return [ + 'A', + 'Ą', + 'B', + 'C', + 'Č', + 'D', + 'E', + 'Ę', + 'Ė', + 'F', + 'G', + 'H', + 'I', + 'Y', + 'Į', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'R', + 'S', + 'Š', + 'T', + 'U', + 'Ų', + 'Ū', + 'V', + 'Z', + 'Ž', + ]; + case 'nl': + return [ + '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', + ]; + case 'pl': + return [ + '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', + 'Ź', + 'Ż', + ]; + case 'ro': + return [ + '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', + ]; + case 'ru': + return [ + 'А', + 'Б', + 'В', + 'Г', + 'Д', + 'Е', + 'Ё', + 'Ж', + 'З', + 'И', + 'Й', + 'К', + 'Л', + 'М', + 'Н', + 'О', + 'П', + 'Р', + 'С', + 'Т', + 'У', + 'Ф', + 'Х', + 'Ц', + 'Ч', + 'Ш', + 'Щ', + 'Ъ', + 'Ы', + 'Ь', + 'Э', + 'Ю', + 'Я', + ]; + case 'sk': + return [ + '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', + 'Ž', + ]; + case 'sl': + return [ + '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', + 'Ž', + ]; + case 'sr': + return [ + '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', + 'Ž', + ]; + case 'tr': + return [ + 'A', + 'B', + 'C', + 'Ç', + 'D', + 'E', + 'F', + 'G', + 'Ğ', + 'H', + 'I', + 'İ', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'Ö', + 'P', + 'R', + 'S', + 'Ş', + 'T', + 'U', + 'Ü', + 'V', + 'Y', + 'Z', + ]; + default: + return [ + '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', + ]; + } + } - /** - * Get the initial letter of a name, taking care of multi-letter sequences and equivalences. - * - * @param string $name - * - * @return string - */ - public static function initialLetter($name) { - $name = I18N::strtoupper($name); - switch (WT_LOCALE) { - case 'cs': - if (substr($name, 0, 2) == 'CH') { - return 'CH'; - } - break; - case 'da': - case 'nb': - case 'nn': - if (substr($name, 0, 2) == 'AA') { - return 'Å'; - } - break; - case 'hu': - if (substr($name, 0, 2) == 'CS') { - return 'CS'; - } elseif (substr($name, 0, 3) == 'DZS') { - return 'DZS'; - } elseif (substr($name, 0, 2) == 'DZ') { - return 'DZ'; - } elseif (substr($name, 0, 2) == 'GY') { - return 'GY'; - } elseif (substr($name, 0, 2) == 'LY') { - return 'LY'; - } elseif (substr($name, 0, 2) == 'NY') { - return 'NY'; - } elseif (substr($name, 0, 2) == 'SZ') { - return 'SZ'; - } elseif (substr($name, 0, 2) == 'TY') { - return 'TY'; - } elseif (substr($name, 0, 2) == 'ZS') { - return 'ZS'; - } - break; - case 'nl': - if (substr($name, 0, 2) == 'IJ') { - return 'IJ'; - } - break; - } - // No special rules - just take the first character - return mb_substr($name, 0, 1); - } + /** + * Get the initial letter of a name, taking care of multi-letter sequences and equivalences. + * + * @param string $name + * + * @return string + */ + public static function initialLetter($name) + { + $name = I18N::strtoupper($name); + switch (WT_LOCALE) { + case 'cs': + if (substr($name, 0, 2) == 'CH') { + return 'CH'; + } + break; + case 'da': + case 'nb': + case 'nn': + if (substr($name, 0, 2) == 'AA') { + return 'Å'; + } + break; + case 'hu': + if (substr($name, 0, 2) == 'CS') { + return 'CS'; + } elseif (substr($name, 0, 3) == 'DZS') { + return 'DZS'; + } elseif (substr($name, 0, 2) == 'DZ') { + return 'DZ'; + } elseif (substr($name, 0, 2) == 'GY') { + return 'GY'; + } elseif (substr($name, 0, 2) == 'LY') { + return 'LY'; + } elseif (substr($name, 0, 2) == 'NY') { + return 'NY'; + } elseif (substr($name, 0, 2) == 'SZ') { + return 'SZ'; + } elseif (substr($name, 0, 2) == 'TY') { + return 'TY'; + } elseif (substr($name, 0, 2) == 'ZS') { + return 'ZS'; + } + break; + case 'nl': + if (substr($name, 0, 2) == 'IJ') { + return 'IJ'; + } + break; + } - /** - * Generate SQL to match a given letter, taking care of cases that - * are not covered by the collation setting. - * - * We must consider: - * potential substrings, such as Czech "CH" and "C" - * equivalent letters, such as Danish "AA" and "Å" - * - * We COULD write something that handles all languages generically, - * but its performance would most likely be poor. - * - * For languages that don't appear in this list, we could write - * simpler versions of the surnameAlpha() and givenAlpha() functions, - * but it gives no noticable improvement in performance. - * - * @param string $field - * @param string $letter - * - * @return string - */ - private static function getInitialSql($field, $letter) { - switch (WT_LOCALE) { - case 'cs': - switch ($letter) { - case 'C': return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CH%' COLLATE " . I18N::collation(); - } - break; - case 'da': - case 'nb': - case 'nn': - switch ($letter) { - // AA gets listed under Å - case 'A': return $field . " LIKE 'A%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'AA%' COLLATE " . I18N::collation(); - case 'Å': return "(" . $field . " LIKE 'Å%' COLLATE " . I18N::collation() . " OR " . $field . " LIKE 'AA%' COLLATE " . I18N::collation() . ")"; - } - break; - case 'hu': - switch ($letter) { - case 'C': return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CS%' COLLATE " . I18N::collation(); - case 'D': return $field . " LIKE 'D%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZ%' COLLATE " . I18N::collation(); - case 'DZ': return $field . " LIKE 'DZ%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZS%' COLLATE " . I18N::collation(); - case 'G': return $field . " LIKE 'G%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'GY%' COLLATE " . I18N::collation(); - case 'L': return $field . " LIKE 'L%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'LY%' COLLATE " . I18N::collation(); - case 'N': return $field . " LIKE 'N%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'NY%' COLLATE " . I18N::collation(); - case 'S': return $field . " LIKE 'S%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'SZ%' COLLATE " . I18N::collation(); - case 'T': return $field . " LIKE 'T%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'TY%' COLLATE " . I18N::collation(); - case 'Z': return $field . " LIKE 'Z%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'ZS%' COLLATE " . I18N::collation(); - } - break; - case 'nl': - switch ($letter) { - case 'I': return $field . " LIKE 'I%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'IJ%' COLLATE " . I18N::collation(); - } - break; - } + // No special rules - just take the first character + return mb_substr($name, 0, 1); + } - // Easy cases: the MySQL collation rules take care of it - return "$field LIKE CONCAT('@'," . Database::quote($letter) . ",'%') COLLATE " . I18N::collation() . " ESCAPE '@'"; - } + /** + * Generate SQL to match a given letter, taking care of cases that + * are not covered by the collation setting. + * + * We must consider: + * potential substrings, such as Czech "CH" and "C" + * equivalent letters, such as Danish "AA" and "Å" + * + * We COULD write something that handles all languages generically, + * but its performance would most likely be poor. + * + * For languages that don't appear in this list, we could write + * simpler versions of the surnameAlpha() and givenAlpha() functions, + * but it gives no noticable improvement in performance. + * + * @param string $field + * @param string $letter + * + * @return string + */ + private static function getInitialSql($field, $letter) + { + switch (WT_LOCALE) { + case 'cs': + switch ($letter) { + case 'C': + return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CH%' COLLATE " . I18N::collation(); + } + break; + case 'da': + case 'nb': + case 'nn': + switch ($letter) { + // AA gets listed under Å + case 'A': + return $field . " LIKE 'A%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'AA%' COLLATE " . I18N::collation(); + case 'Å': + return "(" . $field . " LIKE 'Å%' COLLATE " . I18N::collation() . " OR " . $field . " LIKE 'AA%' COLLATE " . I18N::collation() . ")"; + } + break; + case 'hu': + switch ($letter) { + case 'C': + return $field . " LIKE 'C%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'CS%' COLLATE " . I18N::collation(); + case 'D': + return $field . " LIKE 'D%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZ%' COLLATE " . I18N::collation(); + case 'DZ': + return $field . " LIKE 'DZ%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'DZS%' COLLATE " . I18N::collation(); + case 'G': + return $field . " LIKE 'G%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'GY%' COLLATE " . I18N::collation(); + case 'L': + return $field . " LIKE 'L%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'LY%' COLLATE " . I18N::collation(); + case 'N': + return $field . " LIKE 'N%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'NY%' COLLATE " . I18N::collation(); + case 'S': + return $field . " LIKE 'S%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'SZ%' COLLATE " . I18N::collation(); + case 'T': + return $field . " LIKE 'T%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'TY%' COLLATE " . I18N::collation(); + case 'Z': + return $field . " LIKE 'Z%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'ZS%' COLLATE " . I18N::collation(); + } + break; + case 'nl': + switch ($letter) { + case 'I': + return $field . " LIKE 'I%' COLLATE " . I18N::collation() . " AND " . $field . " NOT LIKE 'IJ%' COLLATE " . I18N::collation(); + } + break; + } - /** - * Get a list of initial surname letters for indilist.php and famlist.php - * - * @param Tree $tree Find surnames from this tree - * @param bool $marnm if set, include married names - * @param bool $fams if set, only consider individuals with FAMS records - * @param bool $totals if set, count the number of names beginning with each letter - * - * @return int[] - */ - public static function surnameAlpha(Tree $tree, $marnm, $fams, $totals = true) { - $alphas = []; + // Easy cases: the MySQL collation rules take care of it + return "$field LIKE CONCAT('@'," . Database::quote($letter) . ",'%') COLLATE " . I18N::collation() . " ESCAPE '@'"; + } - $sql = - "SELECT COUNT(n_id)" . - " FROM `##name` " . - ($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") . - " WHERE n_file=" . $tree->getTreeId() . - ($marnm ? "" : " AND n_type!='_MARNM'"); + /** + * Get a list of initial surname letters for indilist.php and famlist.php + * + * @param Tree $tree Find surnames from this tree + * @param bool $marnm if set, include married names + * @param bool $fams if set, only consider individuals with FAMS records + * @param bool $totals if set, count the number of names beginning with each letter + * + * @return int[] + */ + public static function surnameAlpha(Tree $tree, $marnm, $fams, $totals = true) + { + $alphas = []; - // Fetch all the letters in our alphabet, whether or not there - // are any names beginning with that letter. It looks better to - // show the full alphabet, rather than omitting rare letters such as X - foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { - $count = 1; - if ($totals) { - $count = Database::prepare($sql . " AND " . self::getInitialSql('n_surn', $letter))->fetchOne(); - } - $alphas[$letter] = $count; - } + $sql = + "SELECT COUNT(n_id)" . + " FROM `##name` " . + ($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") . + " WHERE n_file=" . $tree->getTreeId() . + ($marnm ? "" : " AND n_type!='_MARNM'"); - // Now fetch initial letters that are not in our alphabet, - // including "@" (for "@N.N.") and "" for no surname. - $sql = - "SELECT initial, count FROM (SELECT UPPER(LEFT(n_surn, 1)) AS initial, COUNT(n_id) AS count" . - " FROM `##name` " . - ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . - " WHERE n_file = :tree_id AND n_surn <> ''" . - ($marnm ? "" : " AND n_type != '_MARNM'"); + // Fetch all the letters in our alphabet, whether or not there + // are any names beginning with that letter. It looks better to + // show the full alphabet, rather than omitting rare letters such as X + foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { + $count = 1; + if ($totals) { + $count = Database::prepare($sql . " AND " . self::getInitialSql('n_surn', $letter))->fetchOne(); + } + $alphas[$letter] = $count; + } - $args = [ - 'tree_id' => $tree->getTreeId(), - ]; + // Now fetch initial letters that are not in our alphabet, + // including "@" (for "@N.N.") and "" for no surname. + $sql = + "SELECT initial, count FROM (SELECT UPPER(LEFT(n_surn, 1)) AS initial, COUNT(n_id) AS count" . + " FROM `##name` " . + ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . + " WHERE n_file = :tree_id AND n_surn <> ''" . + ($marnm ? "" : " AND n_type != '_MARNM'"); - foreach (self::getAlphabetForLocale(WT_LOCALE) as $n => $letter) { - $sql .= " AND n_surn COLLATE :collate_" . $n . " NOT LIKE :letter_" . $n; - $args['collate_' . $n] = I18N::collation(); - $args['letter_' . $n] = $letter . '%'; - } - $sql .= " GROUP BY UPPER(LEFT(n_surn, 1))) AS subquery ORDER BY initial = '', initial = '@', initial"; - foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) { - $alphas[$alpha] = $count; - } + $args = [ + 'tree_id' => $tree->getTreeId(), + ]; - // Names with no surname - $sql = - "SELECT COUNT(n_id)" . - " FROM `##name` " . - ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . - " WHERE n_file = :tree_id AND n_surn = ''" . - ($marnm ? "" : " AND n_type != '_MARNM'"); + foreach (self::getAlphabetForLocale(WT_LOCALE) as $n => $letter) { + $sql .= " AND n_surn COLLATE :collate_" . $n . " NOT LIKE :letter_" . $n; + $args['collate_' . $n] = I18N::collation(); + $args['letter_' . $n] = $letter . '%'; + } + $sql .= " GROUP BY UPPER(LEFT(n_surn, 1))) AS subquery ORDER BY initial = '', initial = '@', initial"; + foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) { + $alphas[$alpha] = $count; + } - $args = [ - 'tree_id' => $tree->getTreeId(), - ]; + // Names with no surname + $sql = + "SELECT COUNT(n_id)" . + " FROM `##name` " . + ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . + " WHERE n_file = :tree_id AND n_surn = ''" . + ($marnm ? "" : " AND n_type != '_MARNM'"); - $count_no_surname = (int) Database::prepare($sql)->execute($args)->fetchOne(); - if ($count_no_surname !== 0) { - // Special code to indicate "no surname" - $alphas[','] = $count_no_surname; - } + $args = [ + 'tree_id' => $tree->getTreeId(), + ]; - return $alphas; - } + $count_no_surname = (int)Database::prepare($sql)->execute($args)->fetchOne(); + if ($count_no_surname !== 0) { + // Special code to indicate "no surname" + $alphas[','] = $count_no_surname; + } - /** - * Get a list of initial given name letters for indilist.php and famlist.php - * - * @param Tree $tree Find names in this tree - * @param string $surn if set, only consider people with this surname - * @param string $salpha if set, only consider surnames starting with this letter - * @param bool $marnm if set, include married names - * @param bool $fams if set, only consider individuals with FAMS records - * - * @return int[] - */ - public static function givenAlpha(Tree $tree, $surn, $salpha, $marnm, $fams) { - $alphas = []; + return $alphas; + } - $sql = - "SELECT COUNT(DISTINCT n_id)" . - " FROM `##name`" . - ($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") . - " WHERE n_file=" . $tree->getTreeId() . " " . - ($marnm ? "" : " AND n_type!='_MARNM'"); + /** + * Get a list of initial given name letters for indilist.php and famlist.php + * + * @param Tree $tree Find names in this tree + * @param string $surn if set, only consider people with this surname + * @param string $salpha if set, only consider surnames starting with this letter + * @param bool $marnm if set, include married names + * @param bool $fams if set, only consider individuals with FAMS records + * + * @return int[] + */ + public static function givenAlpha(Tree $tree, $surn, $salpha, $marnm, $fams) + { + $alphas = []; - if ($surn) { - $sql .= " AND n_surn=" . Database::quote($surn) . " COLLATE '" . I18N::collation() . "'"; - } elseif ($salpha == ',') { - $sql .= " AND n_surn=''"; - } elseif ($salpha == '@') { - $sql .= " AND n_surn='@N.N.'"; - } elseif ($salpha) { - $sql .= " AND " . self::getInitialSql('n_surn', $salpha); - } else { - // All surnames - $sql .= " AND n_surn NOT IN ('', '@N.N.')"; - } + $sql = + "SELECT COUNT(DISTINCT n_id)" . + " FROM `##name`" . + ($fams ? " JOIN `##link` ON (n_id=l_from AND n_file=l_file AND l_type='FAMS') " : "") . + " WHERE n_file=" . $tree->getTreeId() . " " . + ($marnm ? "" : " AND n_type!='_MARNM'"); - // Fetch all the letters in our alphabet, whether or not there - // are any names beginning with that letter. It looks better to - // show the full alphabet, rather than omitting rare letters such as X - foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { - $count = Database::prepare($sql . " AND " . self::getInitialSql('n_givn', $letter))->fetchOne(); - $alphas[$letter] = $count; - } + if ($surn) { + $sql .= " AND n_surn=" . Database::quote($surn) . " COLLATE '" . I18N::collation() . "'"; + } elseif ($salpha == ',') { + $sql .= " AND n_surn=''"; + } elseif ($salpha == '@') { + $sql .= " AND n_surn='@N.N.'"; + } elseif ($salpha) { + $sql .= " AND " . self::getInitialSql('n_surn', $salpha); + } else { + // All surnames + $sql .= " AND n_surn NOT IN ('', '@N.N.')"; + } - // Now fetch initial letters that are not in our alphabet, - // including "@" (for "@N.N.") and "" for no surname - $sql = - "SELECT initial, total FROM (SELECT UPPER(LEFT(n_givn, 1)) AS initial, COUNT(DISTINCT n_id) AS total" . - " FROM `##name` " . - ($fams ? " JOIN `##link` ON (n_id = l_from AND n_file = l_file AND l_type = 'FAMS') " : "") . - " WHERE n_file = :tree_id" . - ($marnm ? "" : " AND n_type != '_MARNM'"); + // Fetch all the letters in our alphabet, whether or not there + // are any names beginning with that letter. It looks better to + // show the full alphabet, rather than omitting rare letters such as X + foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { + $count = Database::prepare($sql . " AND " . self::getInitialSql('n_givn', $letter))->fetchOne(); + $alphas[$letter] = $count; + } - $args = [ - 'tree_id' => $tree->getTreeId(), - ]; + // Now fetch initial letters that are not in our alphabet, + // including "@" (for "@N.N.") and "" for no surname + $sql = + "SELECT initial, total FROM (SELECT UPPER(LEFT(n_givn, 1)) AS initial, COUNT(DISTINCT n_id) AS total" . + " FROM `##name` " . + ($fams ? " JOIN `##link` ON (n_id = l_from AND n_file = l_file AND l_type = 'FAMS') " : "") . + " WHERE n_file = :tree_id" . + ($marnm ? "" : " AND n_type != '_MARNM'"); - if ($surn) { - $sql .= " AND n_surn COLLATE :collate_1 = :surn"; - $args['collate_1'] = I18N::collation(); - $args['surn'] = $surn; - } elseif ($salpha === ',') { - $sql .= " AND n_surn = ''"; - } elseif ($salpha === '@') { - $sql .= " AND n_surn = '@N.N.'"; - } elseif ($salpha) { - $sql .= " AND " . self::getInitialSql('n_surn', $salpha); - } else { - // All surnames - $sql .= " AND n_surn NOT IN ('', '@N.N.')"; - } + $args = [ + 'tree_id' => $tree->getTreeId(), + ]; - foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { - $sql .= " AND n_givn NOT LIKE '" . $letter . "%' COLLATE " . I18N::collation(); - } - $sql .= " GROUP BY UPPER(LEFT(n_givn, 1))) AS subquery ORDER BY initial = '@', initial = '', initial"; + if ($surn) { + $sql .= " AND n_surn COLLATE :collate_1 = :surn"; + $args['collate_1'] = I18N::collation(); + $args['surn'] = $surn; + } elseif ($salpha === ',') { + $sql .= " AND n_surn = ''"; + } elseif ($salpha === '@') { + $sql .= " AND n_surn = '@N.N.'"; + } elseif ($salpha) { + $sql .= " AND " . self::getInitialSql('n_surn', $salpha); + } else { + // All surnames + $sql .= " AND n_surn NOT IN ('', '@N.N.')"; + } - foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) { - $alphas[$alpha] = $count; - } + foreach (self::getAlphabetForLocale(WT_LOCALE) as $letter) { + $sql .= " AND n_givn NOT LIKE '" . $letter . "%' COLLATE " . I18N::collation(); + } + $sql .= " GROUP BY UPPER(LEFT(n_givn, 1))) AS subquery ORDER BY initial = '@', initial = '', initial"; - return $alphas; - } + foreach (Database::prepare($sql)->execute($args)->fetchAssoc() as $alpha => $count) { + $alphas[$alpha] = $count; + } - /** - * Get a list of actual surnames and variants, based on a "root" surname. - * - * @param Tree $tree only fetch individuals from this tree - * @param string $surn if set, only fetch people with this surname - * @param string $salpha if set, only consider surnames starting with this letter - * @param bool $marnm if set, include married names - * @param bool $fams if set, only consider individuals with FAMS records - * - * @return array - */ - public static function surnames(Tree $tree, $surn, $salpha, $marnm, $fams) { - $sql = - "SELECT n2.n_surn, n1.n_surname, n1.n_id" . - " FROM `##name` n1 " . - ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . - " JOIN (SELECT n_surn COLLATE :collate_0 AS n_surn, n_file FROM `##name`" . - " WHERE n_file = :tree_id" . - ($marnm ? "" : " AND n_type != '_MARNM'"); + return $alphas; + } - $args = [ - 'tree_id' => $tree->getTreeId(), - 'collate_0' => I18N::collation(), - ]; + /** + * Get a list of actual surnames and variants, based on a "root" surname. + * + * @param Tree $tree only fetch individuals from this tree + * @param string $surn if set, only fetch people with this surname + * @param string $salpha if set, only consider surnames starting with this letter + * @param bool $marnm if set, include married names + * @param bool $fams if set, only consider individuals with FAMS records + * + * @return array + */ + public static function surnames(Tree $tree, $surn, $salpha, $marnm, $fams) + { + $sql = + "SELECT n2.n_surn, n1.n_surname, n1.n_id" . + " FROM `##name` n1 " . + ($fams ? " JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . + " JOIN (SELECT n_surn COLLATE :collate_0 AS n_surn, n_file FROM `##name`" . + " WHERE n_file = :tree_id" . + ($marnm ? "" : " AND n_type != '_MARNM'"); - if ($surn) { - $sql .= " AND n_surn COLLATE :collate_1 = :surn"; - $args['collate_1'] = I18N::collation(); - $args['surn'] = $surn; - } elseif ($salpha === ',') { - $sql .= " AND n_surn = ''"; - } elseif ($salpha === '@') { - $sql .= " AND n_surn = '@N.N.'"; - } elseif ($salpha) { - $sql .= " AND " . self::getInitialSql('n_surn', $salpha); - } else { - // All surnames - $sql .= " AND n_surn NOT IN ('', '@N.N.')"; - } - $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)"; - $args['collate_2'] = I18N::collation(); - $args['collate_3'] = I18N::collation(); + $args = [ + 'tree_id' => $tree->getTreeId(), + 'collate_0' => I18N::collation(), + ]; - $list = []; - foreach (Database::prepare($sql)->execute($args)->fetchAll() as $row) { - $list[I18N::strtoupper($row->n_surn)][$row->n_surname][$row->n_id] = true; - } + if ($surn) { + $sql .= " AND n_surn COLLATE :collate_1 = :surn"; + $args['collate_1'] = I18N::collation(); + $args['surn'] = $surn; + } elseif ($salpha === ',') { + $sql .= " AND n_surn = ''"; + } elseif ($salpha === '@') { + $sql .= " AND n_surn = '@N.N.'"; + } elseif ($salpha) { + $sql .= " AND " . self::getInitialSql('n_surn', $salpha); + } else { + // All surnames + $sql .= " AND n_surn NOT IN ('', '@N.N.')"; + } + $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)"; + $args['collate_2'] = I18N::collation(); + $args['collate_3'] = I18N::collation(); - return $list; - } + $list = []; + foreach (Database::prepare($sql)->execute($args)->fetchAll() as $row) { + $list[I18N::strtoupper($row->n_surn)][$row->n_surname][$row->n_id] = true; + } - /** - * Fetch a list of individuals with specified names - * - * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" - * To search for names with no surnames, use $salpha="," - * - * @param Tree $tree only fetch individuals from this tree - * @param string $surn if set, only fetch people with this surname - * @param string $salpha if set, only fetch surnames starting with this letter - * @param string $galpha if set, only fetch given names starting with this letter - * @param bool $marnm if set, include married names - * @param bool $fams if set, only fetch individuals with FAMS records - * - * @return Individual[] - */ - public static function individuals(Tree $tree, $surn, $salpha, $galpha, $marnm, $fams) { - $sql = - "SELECT i_id AS xref, i_gedcom AS gedcom, n_full " . - "FROM `##individuals` " . - "JOIN `##name` ON n_id = i_id AND n_file = i_file " . - ($fams ? "JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . - "WHERE n_file = :tree_id " . - ($marnm ? "" : "AND n_type != '_MARNM'"); + return $list; + } - $args = [ - 'tree_id' => $tree->getTreeId(), - ]; + /** + * Fetch a list of individuals with specified names + * + * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" + * To search for names with no surnames, use $salpha="," + * + * @param Tree $tree only fetch individuals from this tree + * @param string $surn if set, only fetch people with this surname + * @param string $salpha if set, only fetch surnames starting with this letter + * @param string $galpha if set, only fetch given names starting with this letter + * @param bool $marnm if set, include married names + * @param bool $fams if set, only fetch individuals with FAMS records + * + * @return Individual[] + */ + public static function individuals(Tree $tree, $surn, $salpha, $galpha, $marnm, $fams) + { + $sql = + "SELECT i_id AS xref, i_gedcom AS gedcom, n_full " . + "FROM `##individuals` " . + "JOIN `##name` ON n_id = i_id AND n_file = i_file " . + ($fams ? "JOIN `##link` ON n_id = l_from AND n_file = l_file AND l_type = 'FAMS' " : "") . + "WHERE n_file = :tree_id " . + ($marnm ? "" : "AND n_type != '_MARNM'"); - if ($surn) { - $sql .= " AND n_surn COLLATE :collate_1 = :surn"; - $args['collate_1'] = I18N::collation(); - $args['surn'] = $surn; - } elseif ($salpha === ',') { - $sql .= " AND n_surn = ''"; - } elseif ($salpha === '@') { - $sql .= " AND n_surn = '@N.N.'"; - } elseif ($salpha) { - $sql .= " AND " . self::getInitialSql('n_surn', $salpha); - } else { - // All surnames - $sql .= " AND n_surn NOT IN ('', '@N.N.')"; - } - if ($galpha) { - $sql .= " AND " . self::getInitialSql('n_givn', $galpha); - } + $args = [ + 'tree_id' => $tree->getTreeId(), + ]; - $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"; - $args['collate_2'] = I18N::collation(); - $args['collate_3'] = I18N::collation(); + if ($surn) { + $sql .= " AND n_surn COLLATE :collate_1 = :surn"; + $args['collate_1'] = I18N::collation(); + $args['surn'] = $surn; + } elseif ($salpha === ',') { + $sql .= " AND n_surn = ''"; + } elseif ($salpha === '@') { + $sql .= " AND n_surn = '@N.N.'"; + } elseif ($salpha) { + $sql .= " AND " . self::getInitialSql('n_surn', $salpha); + } else { + // All surnames + $sql .= " AND n_surn NOT IN ('', '@N.N.')"; + } + if ($galpha) { + $sql .= " AND " . self::getInitialSql('n_givn', $galpha); + } - $list = []; - $rows = Database::prepare($sql)->execute($args)->fetchAll(); - foreach ($rows as $row) { - $person = Individual::getInstance($row->xref, $tree, $row->gedcom); - // The name from the database may be private - check the filtered list... - foreach ($person->getAllNames() as $n => $name) { - if ($name['fullNN'] == $row->n_full) { - $person->setPrimaryName($n); - // We need to clone $person, as we may have multiple references to the - // same person in this list, and the "primary name" would otherwise - // be shared amongst all of them. - $list[] = clone $person; - break; - } - } - } + $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"; + $args['collate_2'] = I18N::collation(); + $args['collate_3'] = I18N::collation(); - return $list; - } + $list = []; + $rows = Database::prepare($sql)->execute($args)->fetchAll(); + foreach ($rows as $row) { + $person = Individual::getInstance($row->xref, $tree, $row->gedcom); + // The name from the database may be private - check the filtered list... + foreach ($person->getAllNames() as $n => $name) { + if ($name['fullNN'] == $row->n_full) { + $person->setPrimaryName($n); + // We need to clone $person, as we may have multiple references to the + // same person in this list, and the "primary name" would otherwise + // be shared amongst all of them. + $list[] = clone $person; + break; + } + } + } - /** - * Fetch a list of families with specified names - * - * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" - * To search for names with no surnames, use $salpha="," - * - * @param Tree $tree only fetch individuals from this tree - * @param string $surn if set, only fetch people with this surname - * @param string $salpha if set, only fetch surnames starting with this letter - * @param string $galpha if set, only fetch given names starting with this letter - * @param bool $marnm if set, include married names - * - * @return Family[] - */ - public static function families(Tree $tree, $surn, $salpha, $galpha, $marnm) { - $list = []; - foreach (self::individuals($tree, $surn, $salpha, $galpha, $marnm, true) as $indi) { - foreach ($indi->getSpouseFamilies() as $family) { - $list[$family->getXref()] = $family; - } - } - usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare'); + return $list; + } - return $list; - } + /** + * Fetch a list of families with specified names + * + * To search for unknown names, use $surn="@N.N.", $salpha="@" or $galpha="@" + * To search for names with no surnames, use $salpha="," + * + * @param Tree $tree only fetch individuals from this tree + * @param string $surn if set, only fetch people with this surname + * @param string $salpha if set, only fetch surnames starting with this letter + * @param string $galpha if set, only fetch given names starting with this letter + * @param bool $marnm if set, include married names + * + * @return Family[] + */ + public static function families(Tree $tree, $surn, $salpha, $galpha, $marnm) + { + $list = []; + foreach (self::individuals($tree, $surn, $salpha, $galpha, $marnm, true) as $indi) { + foreach ($indi->getSpouseFamilies() as $family) { + $list[$family->getXref()] = $family; + } + } + usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare'); + + return $list; + } } |
