diff options
| author | Greg Roach <greg@subaqua.co.uk> | 2021-07-16 17:35:36 +0100 |
|---|---|---|
| committer | Greg Roach <greg@subaqua.co.uk> | 2021-07-16 17:35:44 +0100 |
| commit | 2f86083cc04612ee254f40b0d8f158aab531a29f (patch) | |
| tree | 7f3ea786fa96564e46cd15582fcd073579b70b43 /app | |
| parent | da3cb887318b253ac0a743309ac1b537880d6ba2 (diff) | |
| download | webtrees-2f86083cc04612ee254f40b0d8f158aab531a29f.tar.gz webtrees-2f86083cc04612ee254f40b0d8f158aab531a29f.tar.bz2 webtrees-2f86083cc04612ee254f40b0d8f158aab531a29f.zip | |
Fix: #3956, Fix: #3954 - update advanced search to use full GEDCOM tags
Diffstat (limited to 'app')
| -rw-r--r-- | app/Factories/ElementFactory.php | 5 | ||||
| -rw-r--r-- | app/Http/RequestHandlers/SearchAdvancedPage.php | 197 | ||||
| -rw-r--r-- | app/Services/SearchService.php | 139 |
3 files changed, 162 insertions, 179 deletions
diff --git a/app/Factories/ElementFactory.php b/app/Factories/ElementFactory.php index b26cdf69ac..1ad03ceda7 100644 --- a/app/Factories/ElementFactory.php +++ b/app/Factories/ElementFactory.php @@ -323,6 +323,7 @@ class ElementFactory implements ElementFactoryInterface 'FAM:CHAN:DATE:TIME' => new TimeValue(I18N::translate('Time of last change')), 'FAM:CHIL' => new XrefIndividual(I18N::translate('Child')), 'FAM:DIV' => new Divorce(I18N::translate('Divorce')), + 'FAM:DIV:DATE' => new DateValue(I18N::translate('Date of divorce')), 'FAM:DIVF' => new DivorceFiled(I18N::translate('Divorce filed')), 'FAM:ENGA' => new Engagement(I18N::translate('Engagement')), 'FAM:ENGA:DATE' => new DateValue(I18N::translate('Date of engagement')), @@ -349,8 +350,8 @@ class ElementFactory implements ElementFactoryInterface 'FAM:RESN' => new RestrictionNotice(I18N::translate('Restriction')), 'FAM:RIN' => new AutomatedRecordId(I18N::translate('Record ID number')), 'FAM:SLGS' => new LdsSpouseSealing(I18N::translate('LDS spouse sealing')), - 'FAM:SLGS:DATE' => new DateLdsOrd(I18N::translate('Date')), - 'FAM:SLGS:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place')), + 'FAM:SLGS:DATE' => new DateLdsOrd(I18N::translate('Date of LDS spouse sealing')), + 'FAM:SLGS:PLAC' => new PlaceLivingOrdinance(I18N::translate('Place of LDS spouse sealing')), 'FAM:SLGS:STAT' => new LdsSpouseSealingDateStatus(I18N::translate('Status')), 'FAM:SLGS:STAT:DATE' => new ChangeDate(I18N::translate('Status change date')), 'FAM:SLGS:TEMP' => new TempleCode(I18N::translate('Temple')), diff --git a/app/Http/RequestHandlers/SearchAdvancedPage.php b/app/Http/RequestHandlers/SearchAdvancedPage.php index d95a7a4ea2..e44f862976 100644 --- a/app/Http/RequestHandlers/SearchAdvancedPage.php +++ b/app/Http/RequestHandlers/SearchAdvancedPage.php @@ -22,6 +22,7 @@ namespace Fisharebest\Webtrees\Http\RequestHandlers; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\SearchService; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; @@ -33,7 +34,7 @@ use function array_fill_keys; use function array_filter; use function array_key_exists; use function assert; -use function explode; +use function ord; /** * Search for genealogy data @@ -43,84 +44,81 @@ class SearchAdvancedPage implements RequestHandlerInterface use ViewResponseTrait; private const DEFAULT_ADVANCED_FIELDS = [ - 'NAME:GIVN', - 'NAME:SURN', - 'BIRT:DATE', - 'BIRT:PLAC', - 'FAMS:MARR:DATE', - 'FAMS:MARR:PLAC', - 'DEAT:DATE', - 'DEAT:PLAC', - 'FAMC:HUSB:NAME:GIVN', - 'FAMC:HUSB:NAME:SURN', - 'FAMC:WIFE:NAME:GIVN', - 'FAMC:WIFE:NAME:SURN', + 'INDI:NAME:GIVN', + 'INDI:NAME:SURN', + 'INDI:BIRT:DATE', + 'INDI:BIRT:PLAC', + 'FAM:MARR:DATE', + 'FAM:MARR:PLAC', + 'INDI:DEAT:DATE', + 'INDI:DEAT:PLAC', + 'FATHER:NAME:GIVN', + 'FATHER:NAME:SURN', + 'MOTHER:NAME:GIVN', + 'MOTHER:NAME:SURN', ]; private const OTHER_ADVANCED_FIELDS = [ - 'ADOP:DATE', - 'ADOP:PLAC', - 'AFN', - 'BAPL:DATE', - 'BAPL:PLAC', - 'BAPM:DATE', - 'BAPM:PLAC', - 'BARM:DATE', - 'BARM:PLAC', - 'BASM:DATE', - 'BASM:PLAC', - 'BLES:DATE', - 'BLES:PLAC', - 'BURI:DATE', - 'BURI:PLAC', - 'CENS:DATE', - 'CENS:PLAC', - 'CHAN:DATE', - 'CHAN:_WT_USER', - 'CHR:DATE', - 'CHR:PLAC', - 'CREM:DATE', - 'CREM:PLAC', - 'DSCR', - 'EMIG:DATE', - 'EMIG:PLAC', - 'ENDL:DATE', - 'ENDL:PLAC', - 'EVEN', - 'EVEN:TYPE', - 'EVEN:DATE', - 'EVEN:PLAC', - 'FACT', - 'FACT:TYPE', - 'FAMS:CENS:DATE', - 'FAMS:CENS:PLAC', - 'FAMS:DIV:DATE', - 'FAMS:NOTE', - 'FAMS:SLGS:DATE', - 'FAMS:SLGS:PLAC', - 'FCOM:DATE', - 'FCOM:PLAC', - 'IMMI:DATE', - 'IMMI:PLAC', - 'NAME:NICK', - 'NAME:_MARNM', - 'NAME:_HEB', - 'NAME:ROMN', - 'NATI', - 'NATU:DATE', - 'NATU:PLAC', - 'NOTE', - 'OCCU', - 'ORDN:DATE', - 'ORDN:PLAC', - 'REFN', - 'RELI', - 'RESI:DATE', - 'RESI:EMAIL', - 'RESI:PLAC', - 'SLGC:DATE', - 'SLGC:PLAC', - 'TITL', + 'INDI:ADOP:DATE', + 'INDI:ADOP:PLAC', + 'INDI:AFN', + 'INDI:BAPL:DATE', + 'INDI:BAPL:PLAC', + 'INDI:BAPM:DATE', + 'INDI:BAPM:PLAC', + 'INDI:BARM:DATE', + 'INDI:BARM:PLAC', + 'INDI:BASM:DATE', + 'INDI:BASM:PLAC', + 'INDI:BLES:DATE', + 'INDI:BLES:PLAC', + 'INDI:BURI:DATE', + 'INDI:BURI:PLAC', + 'INDI:CENS:DATE', + 'INDI:CENS:PLAC', + 'INDI:CHAN:DATE', + 'INDI:CHAN:_WT_USER', + 'INDI:CHR:DATE', + 'INDI:CHR:PLAC', + 'INDI:CREM:DATE', + 'INDI:CREM:PLAC', + 'INDI:DSCR', + 'INDI:EMIG:DATE', + 'INDI:EMIG:PLAC', + 'INDI:ENDL:DATE', + 'INDI:ENDL:PLAC', + 'INDI:EVEN', + 'INDI:EVEN:TYPE', + 'INDI:EVEN:DATE', + 'INDI:EVEN:PLAC', + 'INDI:FACT', + 'INDI:FACT:TYPE', + 'INDI:FCOM:DATE', + 'INDI:FCOM:PLAC', + 'INDI:IMMI:DATE', + 'INDI:IMMI:PLAC', + 'INDI:NAME:NICK', + 'INDI:NAME:_MARNM', + 'INDI:NAME:_HEB', + 'INDI:NAME:ROMN', + 'INDI:NATI', + 'INDI:NATU:DATE', + 'INDI:NATU:PLAC', + 'INDI:NOTE', + 'INDI:OCCU', + 'INDI:ORDN:DATE', + 'INDI:ORDN:PLAC', + 'INDI:REFN', + 'INDI:RELI', + 'INDI:RESI:DATE', + 'INDI:RESI:EMAIL', + 'INDI:RESI:PLAC', + 'INDI:SLGC:DATE', + 'INDI:SLGC:PLAC', + 'INDI:TITL', + 'FAM:DIV:DATE', + 'FAM:SLGS:DATE', + 'FAM:SLGS:PLAC', ]; private SearchService $search_service; @@ -154,7 +152,7 @@ class SearchAdvancedPage implements RequestHandlerInterface $fields = $params['fields'] ?? $default_fields; $modifiers = $params['modifiers'] ?? []; - $other_fields = $this->otherFields($tree, $fields); + $other_fields = $this->otherFields($fields); $date_options = $this->dateOptions(); $name_options = $this->nameOptions(); @@ -182,30 +180,27 @@ class SearchAdvancedPage implements RequestHandlerInterface /** * Extra search fields to add to the advanced search * - * @param Tree $tree * @param string[] $fields * * @return array<string,string> */ - private function otherFields(Tree $tree, array $fields): array + private function otherFields(array $fields): array { - $default_facts = new Collection(self::OTHER_ADVANCED_FIELDS); - $indi_facts_add = new Collection(explode(',', $tree->getPreference('INDI_FACTS_ADD'))); - $indi_facts_unique = new Collection(explode(',', $tree->getPreference('INDI_FACTS_UNIQUE'))); + $default_facts = new Collection(self::OTHER_ADVANCED_FIELDS); + + $comparator = static function (string $x, string $y): int { + $element_factory = Registry::elementFactory(); + + $label1 = $element_factory->make(strtr($x, [':DATE' => '', ':PLAC' => '', ':TYPE' => '']))->label(); + $label2 = $element_factory->make(strtr($y, [':DATE' => '', ':PLAC' => '', ':TYPE' => '']))->label(); + + return I18N::comparator()($label1, $label2) ?: strcmp($x, $y); + }; return $default_facts - ->merge($indi_facts_add) - ->merge($indi_facts_unique) - ->unique() - ->reject(static function (string $field) use ($fields): bool { - return - array_key_exists($field, $fields) || - array_key_exists($field . ':DATE', $fields) || - array_key_exists($field . ':PLAC', $fields); - }) - ->mapWithKeys(static function (string $fact): array { - return [$fact => GedcomTag::getLabel($fact)]; - }) + ->reject(fn (string $field): bool => array_key_exists($field, $fields)) + ->sort($comparator) + ->mapWithKeys(fn (string $fact): array => [$fact => GedcomTag::getLabel($fact)]) ->all(); } @@ -218,16 +213,10 @@ class SearchAdvancedPage implements RequestHandlerInterface private function customFieldLabels(): array { return [ - 'FAMS:DIV:DATE' => I18N::translate('Date of divorce'), - 'FAMS:NOTE' => I18N::translate('Spouse note'), - 'FAMS:SLGS:DATE' => I18N::translate('Date of LDS spouse sealing'), - 'FAMS:SLGS:PLAC' => I18N::translate('Place of LDS spouse sealing'), - 'FAMS:MARR:DATE' => I18N::translate('Date of marriage'), - 'FAMS:MARR:PLAC' => I18N::translate('Place of marriage'), - 'FAMC:HUSB:NAME:GIVN' => I18N::translate('Given names'), - 'FAMC:HUSB:NAME:SURN' => I18N::translate('Surname'), - 'FAMC:WIFE:NAME:GIVN' => I18N::translate('Given names'), - 'FAMC:WIFE:NAME:SURN' => I18N::translate('Surname'), + 'FATHER:NAME:GIVN' => I18N::translate('Given names'), + 'FATHER:NAME:SURN' => I18N::translate('Surname'), + 'MOTHER:NAME:GIVN' => I18N::translate('Given names'), + 'MOTHER:NAME:SURN' => I18N::translate('Surname'), ]; } diff --git a/app/Services/SearchService.php b/app/Services/SearchService.php index 2bbeefdf6e..5e0a495359 100644 --- a/app/Services/SearchService.php +++ b/app/Services/SearchService.php @@ -56,6 +56,9 @@ use function preg_match; use function preg_quote; use function preg_replace; +use function str_ends_with; +use function str_starts_with; + use const PHP_INT_MAX; /** @@ -513,33 +516,25 @@ class SearchService foreach ($fields as $field_name => $field_value) { if ($field_value !== '') { - // Fields can have up to 4 parts, but we only need the first 3 to identify - // which tables to select - $field_parts = explode(':', $field_name . '::'); - - if ($field_parts[0] === 'FAMC') { - // Parent name - FAMC:[HUSB|WIFE]:NAME:[GIVN|SURN] - if ($field_parts[1] === 'HUSB') { - $father_name = true; - } else { - $mother_name = true; - } - } elseif ($field_parts[0] === 'NAME') { - // Individual name - NAME:[GIVN|SURN] + if (str_starts_with($field_name, 'FATHER:NAME')) { + $father_name = true; + } elseif (str_starts_with($field_name, 'MOTHER:NAME')) { + $mother_name = true; + } elseif (str_starts_with($field_name, 'INDI:NAME:GIVN')) { + $indi_name = true; + } elseif (str_starts_with($field_name, 'INDI:NAME:SURN')) { $indi_name = true; - } elseif ($field_parts[0] === 'FAMS') { - // Family facts - FAMS:NOTE or FAMS:[FACT]:[DATE|PLAC] + } elseif (str_starts_with($field_name, 'FAM:')) { $spouse_family = true; - if ($field_parts[2] === 'DATE') { - $fam_dates[] = $field_parts[1]; - } elseif ($field_parts[2] === 'PLAC') { + if (str_ends_with($field_name, ':DATE')) { + $fam_dates[] = explode(':', $field_name)[1]; + } elseif (str_ends_with($field_name, ':PLAC')) { $fam_plac = true; } - } else { - // Individual facts - [FACT] or [FACT]:[DATE|PLAC] - if ($field_parts[1] === 'DATE') { - $indi_dates[] = $field_parts[0]; - } elseif ($field_parts[1] === 'PLAC') { + } elseif (str_starts_with($field_name, 'INDI:')) { + if (str_ends_with($field_name, ':DATE')) { + $indi_dates[] = explode(':', $field_name)[1]; + } elseif (str_ends_with($field_name, ':PLAC')) { $indi_plac = true; } } @@ -649,10 +644,9 @@ class SearchService foreach ($fields as $field_name => $field_value) { $parts = explode(':', $field_name . ':::'); - if ($parts[0] === 'NAME') { - // NAME:* - switch ($parts[1]) { - case 'GIVN': + if (str_starts_with($field_name, 'INDI:NAME:')) { + switch ($field_name) { + case 'INDI:NAME:GIVN': switch ($modifiers[$field_name]) { case 'EXACT': $query->where('individual_name.n_givn', '=', $field_value); @@ -685,7 +679,7 @@ class SearchService } unset($fields[$field_name]); break; - case 'SURN': + case 'INDI:NAME:SURN': switch ($modifiers[$field_name]) { case 'EXACT': $query->where(function (Builder $query) use ($field_value): void { @@ -738,27 +732,25 @@ class SearchService } unset($fields[$field_name]); break; - case 'NICK': - case '_MARNM': - case '_HEB': - case '_AKA': - $like = "%\n1 " . $parts[0] . "%\n2 " . $parts[1] . ' %' . preg_quote($field_value, '/') . '%'; + case 'INDI:NAME:NICK': + case 'INDI:NAME:_MARNM': + case 'INDI:NAME:_HEB': + case 'INDI:NAME:_AKA': + $like = "%\n1 NAME%\n2 " . $parts[2] . ' %' . preg_quote($field_value, '/') . '%'; $query->where('individuals.i_gedcom', 'LIKE', $like); break; } - } elseif ($parts[1] === 'DATE') { - // *:DATE + } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':DATE')) { $date = new Date($field_value); if ($date->isOK()) { $delta = 365 * ($modifiers[$field_name] ?? 0); $query - ->where('date_' . $parts[0] . '.d_fact', '=', $parts[0]) - ->where('date_' . $parts[0] . '.d_julianday1', '>=', $date->minimumJulianDay() - $delta) - ->where('date_' . $parts[0] . '.d_julianday2', '<=', $date->maximumJulianDay() + $delta); + ->where('date_' . $parts[1] . '.d_fact', '=', $parts[1]) + ->where('date_' . $parts[1] . '.d_julianday1', '>=', $date->minimumJulianDay() - $delta) + ->where('date_' . $parts[1] . '.d_julianday2', '<=', $date->maximumJulianDay() + $delta); } unset($fields[$field_name]); - } elseif ($parts[0] === 'FAMS' && $parts[2] === 'DATE') { - // FAMS:*:DATE + } elseif (str_starts_with($field_name, 'FAM:') && str_ends_with($field_name, ':DATE')) { $date = new Date($field_value); if ($date->isOK()) { $delta = 365 * $modifiers[$field_name]; @@ -768,18 +760,15 @@ class SearchService ->where('date_' . $parts[1] . '.d_julianday2', '<=', $date->maximumJulianDay() + $delta); } unset($fields[$field_name]); - } elseif ($parts[1] === 'PLAC') { - // *:PLAC + } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':PLAC')) { // SQL can only link a place to a person/family, not to an event. $query->where('individual_places.p_place', 'LIKE', '%' . $field_value . '%'); - } elseif ($parts[0] === 'FAMS' && $parts[2] === 'PLAC') { - // FAMS:*:PLAC + } elseif (str_starts_with($field_name, 'FAM:') && str_ends_with($field_name, ':PLAC')) { // SQL can only link a place to a person/family, not to an event. $query->where('family_places.p_place', 'LIKE', '%' . $field_value . '%'); - } elseif ($parts[0] === 'FAMC' && $parts[2] === 'NAME') { - $table = $parts[1] === 'HUSB' ? 'father_name' : 'mother_name'; - // NAME:* - switch ($parts[3]) { + } elseif (str_starts_with($field_name, 'MOTHER:NAME:') || str_starts_with($field_name, 'FATHER:NAME:')) { + $table = str_starts_with($field_name, 'FATHER:NAME:') ? 'father_name' : 'mother_name'; + switch ($parts[2]) { case 'GIVN': switch ($modifiers[$field_name]) { case 'EXACT': @@ -846,18 +835,17 @@ class SearchService break; } unset($fields[$field_name]); - } elseif ($parts[0] === 'FAMS') { + } elseif (str_starts_with($field_name, 'FAM:')) { // e.g. searches for occupation, religion, note, etc. // Initial matching only. Need PHP to apply filter. $query->where('spouse_families.f_gedcom', 'LIKE', "%\n1 " . $parts[1] . ' %' . $field_value . '%'); - } elseif ($parts[1] === 'TYPE') { - // e.g. FACT:TYPE or EVEN:TYPE + } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':TYPE')) { // Initial matching only. Need PHP to apply filter. - $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[0] . "%\n2 TYPE %" . $field_value . '%'); - } else { + $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . "%\n2 TYPE %" . $field_value . '%'); + } elseif (str_starts_with($field_name, 'INDI:')) { // e.g. searches for occupation, religion, note, etc. // Initial matching only. Need PHP to apply filter. - $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[0] . '%' . $parts[1] . '%' . $field_value . '%'); + $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . '%' . $parts[2] . '%' . $field_value . '%'); } } @@ -871,9 +859,8 @@ class SearchService foreach ($fields as $field_name => $field_value) { $parts = explode(':', $field_name . '::::'); - // NAME:* - if ($parts[0] === 'NAME') { - $regex = '/\n1 NAME.*(?:\n2.*)*\n2 ' . $parts[1] . ' .*' . preg_quote($field_value, '/') . '/i'; + if (str_starts_with($field_name, 'INDI:NAME:') && $field_name !== 'INDI:NAME:GIVN' && $field_name !== 'INDI:NAME:SURN') { + $regex = '/\n1 NAME.*(?:\n2.*)*\n2 ' . $parts[2] . ' .*' . preg_quote($field_value, '/') . '/i'; if (preg_match($regex, $individual->gedcom())) { continue; @@ -884,9 +871,8 @@ class SearchService $regex = '/' . preg_quote($field_value, '/') . '/i'; - // *:PLAC - if ($parts[1] === 'PLAC') { - foreach ($individual->facts([$parts[0]]) as $fact) { + if (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':PLAC')) { + foreach ($individual->facts([$parts[1]]) as $fact) { if (preg_match($regex, $fact->place()->gedcomName())) { continue 2; } @@ -894,8 +880,7 @@ class SearchService return false; } - // FAMS:*:PLAC - if ($parts[0] === 'FAMS' && $parts[2] === 'PLAC') { + if (str_starts_with($field_name, 'FAM:') && str_ends_with($field_name, ':PLAC')) { foreach ($individual->spouseFamilies() as $family) { foreach ($family->facts([$parts[1]]) as $fact) { if (preg_match($regex, $fact->place()->gedcomName())) { @@ -906,28 +891,36 @@ class SearchService return false; } - // e.g. searches for occupation, religion, note, etc. - if ($parts[0] === 'FAMS') { - foreach ($individual->spouseFamilies() as $family) { - foreach ($family->facts([$parts[1]]) as $fact) { - if (preg_match($regex, $fact->value())) { - continue 3; - } + if ($field_name === 'INDI:FACT:TYPE' || $field_name === 'INDI:EVEN:TYPE' || $field_name === 'INDI:CHAN:_WT_USER') { + foreach ($individual->facts([$parts[1]]) as $fact) { + if (preg_match($regex, $fact->attribute($parts[2]))) { + continue 2; } } + return false; } - // e.g. FACT:TYPE or EVEN:TYPE - if ($parts[1] === 'TYPE' || $parts[1] === '_WT_USER') { - foreach ($individual->facts([$parts[0]]) as $fact) { - if (preg_match($regex, $fact->attribute($parts[1]))) { + if (str_starts_with($field_name, 'INDI:')) { + foreach ($individual->facts([$parts[1]]) as $fact) { + if (preg_match($regex, $fact->value())) { continue 2; } } return false; } + + if (str_starts_with($field_name, 'FAM:')) { + foreach ($individual->spouseFamilies() as $family) { + foreach ($family->facts([$parts[1]]) as $fact) { + if (preg_match($regex, $fact->value())) { + continue 3; + } + } + } + return false; + } } return true; |
