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

AdvancedSearchController::getLabel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
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\Date;
20
use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
21
use Fisharebest\Webtrees\GedcomTag;
22
use Fisharebest\Webtrees\I18N;
23
use Fisharebest\Webtrees\Individual;
24
use Fisharebest\Webtrees\Soundex;
25
26
/**
27
 * Controller for the advanced search page
28
 */
29
class AdvancedSearchController extends SearchController {
30
	/** @var string[] Fields to search */
31
	public $fields = [];
32
33
	/** @var string[] Field values to search */
34
	public $values = [];
35
36
	/** @var int[] Range of days either side of target date */
37
	public $plusminus = [];
38
39
	/**
40
	 * Startup activity
41
	 */
42
	public function __construct() {
43
		parent::__construct();
44
45
		$this->setPageTitle(I18N::translate('Advanced search'));
46
47
		if (empty($_REQUEST['action'])) {
48
			$this->action = 'advanced';
49
		}
50
		if ($this->action == 'advanced') {
51
			if (isset($_REQUEST['fields'])) {
52
				$this->fields = $_REQUEST['fields'];
53
				ksort($this->fields);
54
			}
55
			if (isset($_REQUEST['values'])) {
56
				$this->values = $_REQUEST['values'];
57
			}
58
			if (isset($_REQUEST['plusminus'])) {
59
				$this->plusminus = $_REQUEST['plusminus'];
60
			}
61
			$this->reorderFields();
62
			$this->advancedSearch();
63
		}
64
		if (empty($this->fields)) {
65
			$this->fields = [
66
				'NAME:GIVN:SDX',
67
				'NAME:SURN:SDX',
68
				'BIRT:DATE',
69
				'BIRT:PLAC',
70
				'FAMS:MARR:DATE',
71
				'FAMS:MARR:PLAC',
72
				'DEAT:DATE',
73
				'DEAT:PLAC',
74
				'FAMC:HUSB:NAME:GIVN:SDX',
75
				'FAMC:HUSB:NAME:SURN:SDX',
76
				'FAMC:WIFE:NAME:GIVN:SDX',
77
				'FAMC:WIFE:NAME:SURN:SDX',
78
			];
79
		}
80
	}
81
82
	/**
83
	 * A list of additional fields that can be added.
84
	 *
85
	 * @return string[]
86
	 */
87
	public function getOtherFields() {
88
		$ofields = [
89
			'ADDR', 'ADDR:CITY', 'ADDR:STAE', 'ADDR:CTRY', 'ADDR:POST',
90
			'ADOP:DATE', 'ADOP:PLAC',
91
			'AFN',
92
			'BAPL:DATE', 'BAPL:PLAC',
93
			'BAPM:DATE', 'BAPM:PLAC',
94
			'BARM:DATE', 'BARM:PLAC',
95
			'BASM:DATE', 'BASM:PLAC',
96
			'BLES:DATE', 'BLES:PLAC',
97
			'BURI:DATE', 'BURI:PLAC',
98
			'CAST',
99
			'CENS:DATE', 'CENS:PLAC',
100
			'CHAN:DATE', 'CHAN:_WT_USER',
101
			'CHR:DATE', 'CHR:PLAC',
102
			'CREM:DATE', 'CREM:PLAC',
103
			'DSCR',
104
			'EMAIL',
105
			'EMIG:DATE', 'EMIG:PLAC',
106
			'ENDL:DATE', 'ENDL:PLAC',
107
			'EVEN', 'EVEN:TYPE', 'EVEN:DATE', 'EVEN:PLAC',
108
			'FACT', 'FACT:TYPE',
109
			'FAMS:CENS:DATE', 'FAMS:CENS:PLAC',
110
			'FAMS:DIV:DATE',
111
			'FAMS:NOTE',
112
			'FAMS:SLGS:DATE', 'FAMS:SLGS:PLAC',
113
			'FAX',
114
			'FCOM:DATE', 'FCOM:PLAC',
115
			'IMMI:DATE', 'IMMI:PLAC',
116
			'NAME:NICK', 'NAME:_MARNM', 'NAME:_HEB', 'NAME:ROMN',
117
			'NATI',
118
			'NATU:DATE', 'NATU:PLAC',
119
			'NOTE',
120
			'OCCU',
121
			'ORDN:DATE', 'ORDN:PLAC',
122
			'REFN',
123
			'RELI',
124
			'RESI', 'RESI:DATE', 'RESI:PLAC',
125
			'SLGC:DATE', 'SLGC:PLAC',
126
			'TITL',
127
			'_BRTM:DATE', '_BRTM:PLAC',
128
			'_MILI',
129
		];
130
		// Allow (some of) the user-specified fields to be selected
131
		preg_match_all('/(' . WT_REGEX_TAG . ')/', $this->tree()->getPreference('INDI_FACTS_ADD'), $facts);
132
		foreach ($facts[1] as $fact) {
133
			if (
134
				$fact !== 'BIRT' &&
135
				$fact !== 'DEAT' &&
136
				$fact !== 'ASSO' &&
137
				!in_array($fact, $ofields) &&
138
				!in_array("{$fact}:DATE", $ofields) &&
139
				!in_array("{$fact}:PLAC", $ofields)
140
			) {
141
				$ofields[] = $fact;
142
			}
143
		}
144
		$fields = [];
145
		foreach ($ofields as $field) {
146
			$fields[$field] = strip_tags(GedcomTag::getLabel($field)); // Custom tags have error markup
147
		}
148
		uksort($fields, '\Fisharebest\Webtrees\Controller\AdvancedSearchController::tagSort');
149
150
		return $fields;
151
	}
152
153
	/**
154
	 * Compare two tags, for sorting
155
	 *
156
	 * @param string $x
157
	 * @param string $y
158
	 *
159
	 * @return int
160
	 */
161
	public static function tagSort($x, $y) {
162
		list($x1) = explode(':', $x . ':');
163
		list($y1) = explode(':', $y . ':');
164
		$tmp      = I18N::strcasecmp(GedcomTag::getLabel($x1), GedcomTag::getLabel($y1));
165
		if ($tmp) {
166
			return $tmp;
167
		} else {
168
			return I18N::strcasecmp(GedcomTag::getLabel($x), GedcomTag::getLabel($y));
169
		}
170
	}
171
172
	/**
173
	 * Get the value.
174
	 *
175
	 * @param int $i
176
	 *
177
	 * @return string
178
	 */
179
	public function getValue($i) {
180
		$val = '';
181
		if (isset($this->values[$i])) {
182
			$val = $this->values[$i];
183
		}
184
185
		return $val;
186
	}
187
188
	/**
189
	 * Get the field.
190
	 *
191
	 * @param int $i
192
	 *
193
	 * @return string
194
	 */
195
	public function getField($i) {
196
		$val = '';
197
		if (isset($this->fields[$i])) {
198
			$val = htmlentities($this->fields[$i]);
199
		}
200
201
		return $val;
202
	}
203
204
	/**
205
	 * Get the index.
206
	 *
207
	 * @param string $field
208
	 *
209
	 * @return int
210
	 */
211
	public function getIndex($field) {
212
		return array_search($field, $this->fields);
213
	}
214
215
	/**
216
	 * Get the label.
217
	 *
218
	 * @param string $tag
219
	 *
220
	 * @return string
221
	 */
222
	public function getLabel($tag) {
223
		return GedcomTag::getLabel(preg_replace('/:(SDX|BEGINS|EXACT|CONTAINS)$/', '', $tag));
224
	}
225
226
	/**
227
	 * Set the field order
228
	 */
229
	private function reorderFields() {
230
		$i         = 0;
231
		$newfields = [];
232
		$newvalues = [];
233
		$newplus   = [];
234
		$rels      = [];
235
		foreach ($this->fields as $j => $field) {
236
			if (strpos($this->fields[$j], 'FAMC:HUSB:NAME') === 0 || strpos($this->fields[$j], 'FAMC:WIFE:NAME') === 0) {
237
				$rels[$this->fields[$j]] = $this->values[$j];
238
				continue;
239
			}
240
			$newfields[$i] = $this->fields[$j];
241
			if (isset($this->values[$j])) {
242
				$newvalues[$i] = $this->values[$j];
243
			}
244
			if (isset($this->plusminus[$j])) {
245
				$newplus[$i] = $this->plusminus[$j];
246
			}
247
			$i++;
248
		}
249
		$this->fields    = $newfields;
250
		$this->values    = $newvalues;
251
		$this->plusminus = $newplus;
252
		foreach ($rels as $field => $value) {
253
			$this->fields[] = $field;
254
			$this->values[] = $value;
255
		}
256
	}
257
258
	/**
259
	 * Perform the search
260
	 */
261
	private function advancedSearch() {
262
		$this->myindilist = [];
263
		$fct              = count($this->fields);
264
		if (!array_filter($this->values)) {
265
			return;
266
		}
267
268
		// Dynamic SQL query, plus bind variables
269
		$sql  = 'SELECT DISTINCT ind.i_id AS xref, ind.i_gedcom AS gedcom FROM `##individuals` ind';
270
		$bind = [];
271
272
		// Join the following tables
273
		$father_name   = false;
274
		$mother_name   = false;
275
		$spouse_family = false;
276
		$indi_name     = false;
277
		$indi_date     = false;
278
		$fam_date      = false;
279
		$indi_plac     = false;
280
		$fam_plac      = false;
281
		foreach ($this->fields as $n => $field) {
282
			if ($this->values[$n]) {
283
				if (substr($field, 0, 14) == 'FAMC:HUSB:NAME') {
284
					$father_name = true;
285
				} elseif (substr($field, 0, 14) == 'FAMC:WIFE:NAME') {
286
					$mother_name = true;
287
				} elseif (substr($field, 0, 4) == 'NAME') {
288
					$indi_name = true;
289
				} elseif (strpos($field, ':DATE') !== false) {
290
					if (substr($field, 0, 4) == 'FAMS') {
291
						$fam_date      = true;
292
						$spouse_family = true;
293
					} else {
294
						$indi_date = true;
295
					}
296
				} elseif (strpos($field, ':PLAC') !== false) {
297
					if (substr($field, 0, 4) == 'FAMS') {
298
						$fam_plac      = true;
299
						$spouse_family = true;
300
					} else {
301
						$indi_plac = true;
302
					}
303
				} elseif ($field == 'FAMS:NOTE') {
304
					$spouse_family = true;
305
				}
306
			}
307
		}
308
309
		if ($father_name || $mother_name) {
310
			$sql .= " JOIN `##link`   l_1 ON (l_1.l_file=ind.i_file AND l_1.l_from=ind.i_id AND l_1.l_type='FAMC')";
311
		}
312
		if ($father_name) {
313
			$sql .= " JOIN `##link`   l_2 ON (l_2.l_file=ind.i_file AND l_2.l_from=l_1.l_to AND l_2.l_type='HUSB')";
314
			$sql .= " JOIN `##name`   f_n ON (f_n.n_file=ind.i_file AND f_n.n_id  =l_2.l_to)";
315
		}
316
		if ($mother_name) {
317
			$sql .= " JOIN `##link`   l_3 ON (l_3.l_file=ind.i_file AND l_3.l_from=l_1.l_to AND l_3.l_type='WIFE')";
318
			$sql .= " JOIN `##name`   m_n ON (m_n.n_file=ind.i_file AND m_n.n_id  =l_3.l_to)";
319
		}
320
		if ($spouse_family) {
321
			$sql .= " JOIN `##link`     l_4 ON (l_4.l_file=ind.i_file AND l_4.l_from=ind.i_id AND l_4.l_type='FAMS')";
322
			$sql .= " JOIN `##families` fam ON (fam.f_file=ind.i_file AND fam.f_id  =l_4.l_to)";
323
		}
324
		if ($indi_name) {
325
			$sql .= " JOIN `##name`   i_n ON (i_n.n_file=ind.i_file AND i_n.n_id=ind.i_id)";
326
		}
327
		if ($indi_date) {
328
			$sql .= " JOIN `##dates`  i_d ON (i_d.d_file=ind.i_file AND i_d.d_gid=ind.i_id)";
329
		}
330
		if ($fam_date) {
331
			$sql .= " JOIN `##dates`  f_d ON (f_d.d_file=ind.i_file AND f_d.d_gid=fam.f_id)";
332
		}
333
		if ($indi_plac) {
334
			$sql .= " JOIN `##placelinks`   i_pl ON (i_pl.pl_file=ind.i_file AND i_pl.pl_gid =ind.i_id)";
335
			$sql .= " JOIN (" .
336
					"SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" .
337
					" FROM      `##places` AS p1" .
338
					" LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" .
339
					" LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" .
340
					" LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" .
341
					" LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" .
342
					" LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" .
343
					" LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" .
344
					" LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" .
345
					" LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" .
346
					") AS i_p ON (i_p.file  =ind.i_file AND i_pl.pl_p_id= i_p.id)";
347
		}
348
		if ($fam_plac) {
349
			$sql .= " JOIN `##placelinks`   f_pl ON (f_pl.pl_file=ind.i_file AND f_pl.pl_gid =fam.f_id)";
350
			$sql .= " JOIN (" .
351
					"SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" .
352
					" FROM      `##places` AS p1" .
353
					" LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" .
354
					" LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" .
355
					" LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" .
356
					" LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" .
357
					" LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" .
358
					" LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" .
359
					" LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" .
360
					" LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" .
361
					") AS f_p ON (f_p.file  =ind.i_file AND f_pl.pl_p_id= f_p.id)";
362
		}
363
		// Add the where clause
364
		$sql .= " WHERE ind.i_file=?";
365
		$bind[] = $this->tree()->getTreeId();
366
		for ($i = 0; $i < $fct; $i++) {
367
			$field = $this->fields[$i];
368
			$value = $this->values[$i];
369
			if ($value === '') {
370
				continue;
371
			}
372
			$parts = preg_split('/:/', $field . '::::');
373
			if ($parts[0] == 'NAME') {
374
				// NAME:*
375
				switch ($parts[1]) {
376
					case 'GIVN':
377
						switch ($parts[2]) {
378
							case 'EXACT':
379
								$sql .= " AND i_n.n_givn=?";
380
								$bind[] = $value;
381
								break;
382
							case 'BEGINS':
383
								$sql .= " AND i_n.n_givn LIKE CONCAT(?, '%')";
384
								$bind[] = $value;
385
								break;
386
							case 'CONTAINS':
387
								$sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
388
								$bind[] = $value;
389
								break;
390
							case 'SDX_STD':
391
								$sdx = Soundex::russell($value);
392
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
393
									$sdx = explode(':', $sdx);
394
									foreach ($sdx as $k => $v) {
395
										$sdx[$k] = "i_n.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
396
										$bind[]  = $v;
397
									}
398
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
399
								} else {
400
									// No phonetic content? Use a substring match
401
									$sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
402
									$bind[] = $value;
403
								}
404
								break;
405
							case 'SDX': // SDX uses DM by default.
406
							case 'SDX_DM':
407
								$sdx = Soundex::daitchMokotoff($value);
408
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
409
									$sdx = explode(':', $sdx);
410
									foreach ($sdx as $k => $v) {
411
										$sdx[$k] = "i_n.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
412
										$bind[]  = $v;
413
									}
414
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
415
								} else {
416
									// No phonetic content? Use a substring match
417
									$sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
418
									$bind[] = $value;
419
								}
420
								break;
421
						}
422
					break;
423
					case 'SURN':
424
						switch ($parts[2]) {
425
							case 'EXACT':
426
								$sql .= " AND i_n.n_surname=?";
427
								$bind[] = $value;
428
								break;
429
							case 'BEGINS':
430
								$sql .= " AND i_n.n_surname LIKE CONCAT(?, '%')";
431
								$bind[] = $value;
432
								break;
433
							case 'CONTAINS':
434
								$sql .= " AND i_n.n_surname LIKE CONCAT('%', ?, '%')";
435
								$bind[] = $value;
436
								break;
437
							case 'SDX_STD':
438
								$sdx = Soundex::russell($value);
439
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
440
									$sdx = explode(':', $sdx);
441
									foreach ($sdx as $k => $v) {
442
										$sdx[$k] = "i_n.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
443
										$bind[]  = $v;
444
									}
445
									$sql .= " AND (" . implode(' OR ', $sdx) . ")";
446
								} else {
447
									// No phonetic content? Use a substring match
448
									$sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
449
									$bind[] = $value;
450
								}
451
								break;
452
							case 'SDX': // SDX uses DM by default.
453
							case 'SDX_DM':
454
								$sdx = Soundex::daitchMokotoff($value);
455
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
456
									$sdx = explode(':', $sdx);
457
									foreach ($sdx as $k => $v) {
458
										$sdx[$k] = "i_n.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
459
										$bind[]  = $v;
460
									}
461
									$sql .= " AND (" . implode(' OR ', $sdx) . ")";
462
									break;
463
								} else {
464
									// No phonetic content? Use a substring match
465
									$sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
466
									$bind[] = $value;
467
								}
468
						}
469
					break;
470
					case 'NICK':
471
					case '_MARNM':
472
					case '_HEB':
473
					case '_AKA':
474
						$sql .= " AND i_n.n_type=? AND i_n.n_full LIKE CONCAT('%', ?, '%')";
475
						$bind[] = $parts[1];
476
						$bind[] = $value;
477
						break;
478
				}
479
			} elseif ($parts[1] == 'DATE') {
480
				// *:DATE
481
				$date = new Date($value);
482
				if ($date->isOK()) {
483
					$jd1 = $date->minimumJulianDay();
484
					$jd2 = $date->maximumJulianDay();
485
					if (!empty($this->plusminus[$i])) {
486
						$adjd = $this->plusminus[$i] * 365;
487
						$jd1 -= $adjd;
488
						$jd2 += $adjd;
489
					}
490
					$sql .= " AND i_d.d_fact=? AND i_d.d_julianday1>=? AND i_d.d_julianday2<=?";
491
					$bind[] = $parts[0];
492
					$bind[] = $jd1;
493
					$bind[] = $jd2;
494
				}
495
			} elseif ($parts[0] == 'FAMS' && $parts[2] == 'DATE') {
496
				// FAMS:*:DATE
497
				$date = new Date($value);
498
				if ($date->isOK()) {
499
					$jd1 = $date->minimumJulianDay();
500
					$jd2 = $date->maximumJulianDay();
501
					if (!empty($this->plusminus[$i])) {
502
						$adjd = $this->plusminus[$i] * 365;
503
						$jd1 -= $adjd;
504
						$jd2 += $adjd;
505
					}
506
					$sql .= " AND f_d.d_fact=? AND f_d.d_julianday1>=? AND f_d.d_julianday2<=?";
507
					$bind[] = $parts[1];
508
					$bind[] = $jd1;
509
					$bind[] = $jd2;
510
				}
511
			} elseif ($parts[1] == 'PLAC') {
512
				// *:PLAC
513
				// SQL can only link a place to a person/family, not to an event.
514
				$sql .= " AND i_p.place LIKE CONCAT('%', ?, '%')";
515
				$bind[] = $value;
516
			} elseif ($parts[0] == 'FAMS' && $parts[2] == 'PLAC') {
517
				// FAMS:*:PLAC
518
				// SQL can only link a place to a person/family, not to an event.
519
				$sql .= " AND f_p.place LIKE CONCAT('%', ?, '%')";
520
				$bind[] = $value;
521
			} elseif ($parts[0] == 'FAMC' && $parts[2] == 'NAME') {
522
				$table = $parts[1] == 'HUSB' ? 'f_n' : 'm_n';
523
				// NAME:*
524
				switch ($parts[3]) {
525
					case 'GIVN':
526
						switch ($parts[4]) {
527
							case 'EXACT':
528
								$sql .= " AND {$table}.n_givn=?";
529
								$bind[] = $value;
530
								break;
531
							case 'BEGINS':
532
								$sql .= " AND {$table}.n_givn LIKE CONCAT(?, '%')";
533
								$bind[] = $value;
534
								break;
535
							case 'CONTAINS':
536
								$sql .= " AND {$table}.n_givn LIKE CONCAT('%', ?, '%')";
537
								$bind[] = $value;
538
								break;
539
							case 'SDX_STD':
540
								$sdx = Soundex::russell($value);
541
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
542
									$sdx = explode(':', $sdx);
543
									foreach ($sdx as $k => $v) {
544
										$sdx[$k] = "{$table}.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
545
										$bind[]  = $v;
546
									}
547
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
548
								} else {
549
									// No phonetic content? Use a substring match
550
									$sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
551
									$bind[] = $value;
552
								}
553
								break;
554
							case 'SDX': // SDX uses DM by default.
555
							case 'SDX_DM':
556
								$sdx = Soundex::daitchMokotoff($value);
557
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
558
									$sdx = explode(':', $sdx);
559
									foreach ($sdx as $k => $v) {
560
										$sdx[$k] = "{$table}.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
561
										$bind[]  = $v;
562
									}
563
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
564
									break;
565
								} else {
566
									// No phonetic content? Use a substring match
567
									$sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
568
									$bind[] = $value;
569
								}
570
						}
571
					break;
572
					case 'SURN':
573
						switch ($parts[4]) {
574
							case 'EXACT':
575
								$sql .= " AND {$table}.n_surname=?";
576
								$bind[] = $value;
577
								break;
578
							case 'BEGINS':
579
								$sql .= " AND {$table}.n_surname LIKE CONCAT(?, '%')";
580
								$bind[] = $value;
581
								break;
582
							case 'CONTAINS':
583
								$sql .= " AND {$table}.n_surname LIKE CONCAT('%', ?, '%')";
584
								$bind[] = $value;
585
								break;
586
							case 'SDX_STD':
587
								$sdx = Soundex::russell($value);
588
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
589
									$sdx = explode(':', $sdx);
590
									foreach ($sdx as $k => $v) {
591
										$sdx[$k] = "{$table}.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
592
										$bind[]  = $v;
593
									}
594
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
595
								} else {
596
									// No phonetic content? Use a substring match
597
									$sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
598
									$bind[] = $value;
599
								}
600
								break;
601
							case 'SDX': // SDX uses DM by default.
602
							case 'SDX_DM':
603
								$sdx = Soundex::daitchMokotoff($value);
604
								if ($sdx !== null) {
0 ignored issues
show
introduced by
The condition $sdx !== null can never be false.
Loading history...
605
									$sdx = explode(':', $sdx);
606
									foreach ($sdx as $k => $v) {
607
										$sdx[$k] = "{$table}.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
608
										$bind[]  = $v;
609
									}
610
									$sql .= ' AND (' . implode(' OR ', $sdx) . ')';
611
								} else {
612
									// No phonetic content? Use a substring match
613
									$sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
614
									$bind[] = $value;
615
								}
616
								break;
617
						}
618
					break;
619
				}
620
			} elseif ($parts[0] === 'FAMS') {
621
				// e.g. searches for occupation, religion, note, etc.
622
				$sql .= " AND fam.f_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
623
				$bind[] = $parts[1];
624
				$bind[] = $value;
625
			} elseif ($parts[1] === 'TYPE') {
626
				// e.g. FACT:TYPE or EVEN:TYPE
627
				$sql .= " AND ind.i_gedcom REGEXP CONCAT('\n1 ', ?, '.*(\n[2-9] .*)*\n2 TYPE .*', ?)";
628
				$bind[] = $parts[0];
629
				$bind[] = $value;
630
			} else {
631
				// e.g. searches for occupation, religion, note, etc.
632
				$sql .= " AND ind.i_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
633
				$bind[] = $parts[0];
634
				$bind[] = $value;
635
			}
636
		}
637
		$rows = Database::prepare($sql)->execute($bind)->fetchAll();
638
		foreach ($rows as $row) {
639
			$person = Individual::getInstance($row->xref, $this->tree(), $row->gedcom);
640
			// Check for XXXX:PLAC fields, which were only partially matched by SQL
641
			foreach ($this->fields as $n => $field) {
642
				if ($this->values[$n] && preg_match('/^(' . WT_REGEX_TAG . '):PLAC$/', $field, $match)) {
643
					if (!preg_match('/\n1 ' . $match[1] . '(\n[2-9].*)*\n2 PLAC .*' . preg_quote($this->values[$n], '/') . '/i', $person->getGedcom())) {
644
						continue 2;
645
					}
646
				}
647
			}
648
			$this->myindilist[] = $person;
649
		}
650
	}
651
652
	/**
653
	 * Display the search results
654
	 */
655
	public function printResults() {
656
		if (!empty($this->myindilist)) {
657
			uasort($this->myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare');
658
			echo FunctionsPrintLists::individualTable($this->myindilist);
659
		} elseif (array_filter($this->values)) {
660
			echo '<p class="alert alert-info">', I18N::translate('No results found.'), '</p>';
661
		}
662
	}
663
}
664