xref: /webtrees/app/Family.php (revision 64d9078a3a1fe7f0c5c5c13973b3b90b6329590e)
1<?php
2namespace Fisharebest\Webtrees;
3
4/**
5 * webtrees: online genealogy
6 * Copyright (C) 2015 webtrees development team
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * Class Family - Class file for a Family
21 */
22class Family extends GedcomRecord {
23	const RECORD_TYPE = 'FAM';
24	const URL_PREFIX = 'family.php?famid=';
25
26	/** @var Individual|null The husband (or first spouse for same-sex couples) */
27	private $husb;
28
29	/** @var Individual|null The wife (or second spouse for same-sex couples) */
30	private $wife;
31
32	/** {@inheritdoc} */
33	public function __construct($xref, $gedcom, $pending, $tree) {
34		parent::__construct($xref, $gedcom, $pending, $tree);
35
36		// Fetch family members
37		if (preg_match_all('/^1 (?:HUSB|WIFE|CHIL) @(.+)@/m', $gedcom . $pending, $match)) {
38			Individual::load($tree, $match[1]);
39		}
40
41		if (preg_match('/^1 HUSB @(.+)@/m', $gedcom . $pending, $match)) {
42			$this->husb = Individual::getInstance($match[1], $tree);
43		}
44		if (preg_match('/^1 WIFE @(.+)@/m', $gedcom . $pending, $match)) {
45			$this->wife = Individual::getInstance($match[1], $tree);
46		}
47
48		// Make sure husb/wife are the right way round.
49		if ($this->husb && $this->husb->getSex() === 'F' || $this->wife && $this->wife->getSex() === 'M') {
50			list($this->husb, $this->wife) = array($this->wife, $this->husb);
51		}
52	}
53
54	/**
55	 * Get an instance of a family object.  For single records,
56	 * we just receive the XREF.  For bulk records (such as lists
57	 * and search results) we can receive the GEDCOM data as well.
58	 *
59	 * @param string      $xref
60	 * @param Tree        $tree
61	 * @param string|null $gedcom
62	 *
63	 * @return Family|null
64	 */
65	public static function getInstance($xref, Tree $tree, $gedcom = null) {
66		$record = parent::getInstance($xref, $tree, $gedcom);
67
68		if ($record instanceof Family) {
69			return $record;
70		} else {
71			return null;
72		}
73	}
74
75	/** {@inheritdoc} */
76	protected function createPrivateGedcomRecord($access_level) {
77		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
78
79		$rec = '0 @' . $this->xref . '@ FAM';
80		// Just show the 1 CHIL/HUSB/WIFE tag, not any subtags, which may contain private data
81		preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches, PREG_SET_ORDER);
82		foreach ($matches as $match) {
83			$rela = Individual::getInstance($match[1], $this->tree);
84			if ($rela && ($SHOW_PRIVATE_RELATIONSHIPS || $rela->canShow($access_level))) {
85				$rec .= $match[0];
86			}
87		}
88
89		return $rec;
90	}
91
92	/** {@inheritdoc} */
93	protected static function fetchGedcomRecord($xref, $tree_id) {
94		return Database::prepare(
95			"SELECT f_gedcom FROM `##families` WHERE f_id = :xref AND f_file = :tree_id"
96		)->execute(array(
97			'xref'    => $xref,
98			'tree_id' => $tree_id,
99		))->fetchOne();
100	}
101
102	/**
103	 * Get the male (or first female) partner of the family
104	 *
105	 * @return Individual|null
106	 */
107	function getHusband() {
108		if ($this->husb && $this->husb->canShowName()) {
109			return $this->husb;
110		} else {
111			return null;
112		}
113	}
114
115	/**
116	 * Get the female (or second male) partner of the family
117	 *
118	 * @return Individual|null
119	 */
120	function getWife() {
121		if ($this->wife && $this->wife->canShowName()) {
122			return $this->wife;
123		} else {
124			return null;
125		}
126	}
127
128	/** {@inheritdoc} */
129	protected function canShowByType($access_level) {
130		// Hide a family if any member is private
131		preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches);
132		foreach ($matches[1] as $match) {
133			$person = Individual::getInstance($match, $this->tree);
134			if ($person && !$person->canShow($access_level)) {
135				return false;
136			}
137		}
138
139		return true;
140	}
141
142	/** {@inheritdoc} */
143	public function canShowName($access_level = null) {
144		// We can always see the name (Husband-name + Wife-name), however,
145		// the name will often be "private + private"
146		return true;
147	}
148
149	/**
150	 * Find the spouse of a person.
151	 *
152	 * @param Individual $person
153	 *
154	 * @return Individual|null
155	 */
156	function getSpouse(Individual $person) {
157		if ($person === $this->wife) {
158			return $this->husb;
159		} else {
160			return $this->wife;
161		}
162	}
163
164	/**
165	 * Get the (zero, one or two) spouses from this family.
166	 *
167	 * @param integer|null $access_level
168	 *
169	 * @return Individual[]
170	 */
171	function getSpouses($access_level = null) {
172		if ($access_level === null) {
173			$access_level = Auth::accessLevel($this->tree);
174		}
175
176		$spouses = array();
177		if ($this->husb && $this->husb->canShowName($access_level)) {
178			$spouses[] = $this->husb;
179		}
180		if ($this->wife && $this->wife->canShowName($access_level)) {
181			$spouses[] = $this->wife;
182		}
183
184		return $spouses;
185	}
186
187	/**
188	 * Get a list of this family’s children.
189	 *
190	 * @param integer|null $access_level
191	 *
192	 * @return Individual[]
193	 */
194	function getChildren($access_level = null) {
195		if ($access_level === null) {
196			$access_level = Auth::accessLevel($this->tree);
197		}
198
199		$SHOW_PRIVATE_RELATIONSHIPS = $this->tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS');
200
201		$children = array();
202		foreach ($this->getFacts('CHIL', false, $access_level, $SHOW_PRIVATE_RELATIONSHIPS) as $fact) {
203			$child = $fact->getTarget();
204			if ($child && ($SHOW_PRIVATE_RELATIONSHIPS || $child->canShowName($access_level))) {
205				$children[] = $child;
206			}
207		}
208
209		return $children;
210	}
211
212	/**
213	 * Static helper function to sort an array of families by marriage date
214	 *
215	 * @param Family $x
216	 * @param Family $y
217	 *
218	 * @return integer
219	 */
220	public static function compareMarrDate(Family $x, Family $y) {
221		return Date::compare($x->getMarriageDate(), $y->getMarriageDate());
222	}
223
224	/**
225	 * Number of children - for the individual list
226	 *
227	 * @return integer
228	 */
229	public function getNumberOfChildren() {
230		$nchi = count($this->getChildren());
231		foreach ($this->getFacts('NCHI') as $fact) {
232			$nchi = max($nchi, (int) $fact->getValue());
233		}
234
235		return $nchi;
236	}
237
238	/**
239	 * get the marriage event
240	 *
241	 * @return Fact
242	 */
243	public function getMarriage() {
244		return $this->getFirstFact('MARR');
245	}
246
247	/**
248	 * Get marriage date
249	 *
250	 * @return Date
251	 */
252	public function getMarriageDate() {
253		$marriage = $this->getMarriage();
254		if ($marriage) {
255			return $marriage->getDate();
256		} else {
257			return new Date('');
258		}
259	}
260
261	/**
262	 * Get the marriage year - displayed on lists of families
263	 *
264	 * @return integer
265	 */
266	public function getMarriageYear() {
267		return $this->getMarriageDate()->minimumDate()->y;
268	}
269
270	/**
271	 * Get the type for this marriage
272	 *
273	 * @return string|null
274	 */
275	public function getMarriageType() {
276		$marriage = $this->getMarriage();
277		if ($marriage) {
278			return $marriage->getAttribute('TYPE');
279		} else {
280			return null;
281		}
282	}
283
284	/**
285	 * Get the marriage place
286	 *
287	 * @return Place
288	 */
289	public function getMarriagePlace() {
290		$marriage = $this->getMarriage();
291
292		return $marriage->getPlace();
293	}
294
295	/**
296	 * Get a list of all marriage dates - for the family lists.
297	 *
298	 * @return Date[]
299	 */
300	public function getAllMarriageDates() {
301		foreach (explode('|', WT_EVENTS_MARR) as $event) {
302			if ($array = $this->getAllEventDates($event)) {
303				return $array;
304			}
305		}
306
307		return array();
308	}
309
310	/**
311	 * Get a list of all marriage places - for the family lists.
312	 *
313	 * @return string[]
314	 */
315	public function getAllMarriagePlaces() {
316		foreach (explode('|', WT_EVENTS_MARR) as $event) {
317			if ($array = $this->getAllEventPlaces($event)) {
318				return $array;
319			}
320		}
321
322		return array();
323	}
324
325	/** {@inheritdoc} */
326	public function getAllNames() {
327		global $UNKNOWN_NN, $UNKNOWN_PN;
328
329		if (is_null($this->_getAllNames)) {
330			// Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc.
331			if ($this->husb) {
332				$husb_names = $this->husb->getAllNames();
333			} else {
334				$husb_names = array(
335					0 => array(
336						'type' => 'BIRT',
337						'sort' => '@N.N.',
338						'full' => $UNKNOWN_PN, ' ', $UNKNOWN_NN,
339					),
340				);
341			}
342			foreach ($husb_names as $n => $husb_name) {
343				$husb_names[$n]['script'] = I18N::textScript($husb_name['full']);
344			}
345			if ($this->wife) {
346				$wife_names = $this->wife->getAllNames();
347			} else {
348				$wife_names = array(
349					0 => array(
350						'type' => 'BIRT',
351						'sort' => '@N.N.',
352						'full' => $UNKNOWN_PN, ' ', $UNKNOWN_NN,
353					),
354				);
355			}
356			foreach ($wife_names as $n => $wife_name) {
357				$wife_names[$n]['script'] = I18N::textScript($wife_name['full']);
358			}
359			// Add the matched names first
360			foreach ($husb_names as $husb_name) {
361				foreach ($wife_names as $wife_name) {
362					if ($husb_name['type'] != '_MARNM' && $wife_name['type'] != '_MARNM' && $husb_name['script'] == $wife_name['script']) {
363						$this->_getAllNames[] = array(
364							'type' => $husb_name['type'],
365							'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
366							'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
367							// No need for a fullNN entry - we do not currently store FAM names in the database
368						);
369					}
370				}
371			}
372			// Add the unmatched names second (there may be no matched names)
373			foreach ($husb_names as $husb_name) {
374				foreach ($wife_names as $wife_name) {
375					if ($husb_name['type'] != '_MARNM' && $wife_name['type'] != '_MARNM' && $husb_name['script'] != $wife_name['script']) {
376						$this->_getAllNames[] = array(
377							'type' => $husb_name['type'],
378							'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'],
379							'full' => $husb_name['full'] . ' + ' . $wife_name['full'],
380							// No need for a fullNN entry - we do not currently store FAM names in the database
381						);
382					}
383				}
384			}
385		}
386
387		return $this->_getAllNames;
388	}
389
390	/** {@inheritdoc} */
391	function formatListDetails() {
392		return
393			$this->formatFirstMajorFact(WT_EVENTS_MARR, 1) .
394			$this->formatFirstMajorFact(WT_EVENTS_DIV, 1);
395	}
396}
397