xref: /webtrees/app/Fact.php (revision a25f0a04682c4c39c1947220c90af4118c713952)
1*a25f0a04SGreg Roach<?php
2*a25f0a04SGreg Roachnamespace Webtrees;
3*a25f0a04SGreg Roach
4*a25f0a04SGreg Roach/**
5*a25f0a04SGreg Roach * webtrees: online genealogy
6*a25f0a04SGreg Roach * Copyright (C) 2015 webtrees development team
7*a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify
8*a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by
9*a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or
10*a25f0a04SGreg Roach * (at your option) any later version.
11*a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful,
12*a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
13*a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14*a25f0a04SGreg Roach * GNU General Public License for more details.
15*a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License
16*a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
17*a25f0a04SGreg Roach */
18*a25f0a04SGreg Roach
19*a25f0a04SGreg Roach/**
20*a25f0a04SGreg Roach * Class Fact - Class that defines an event details object
21*a25f0a04SGreg Roach */
22*a25f0a04SGreg Roachclass Fact {
23*a25f0a04SGreg Roach	/** @var string Unique identifier for this fact (currently implemented as a hash of the raw data). */
24*a25f0a04SGreg Roach	private $fact_id;
25*a25f0a04SGreg Roach
26*a25f0a04SGreg Roach	/** @var GedcomRecord The GEDCOM record from which this fact is taken */
27*a25f0a04SGreg Roach	private $parent;
28*a25f0a04SGreg Roach
29*a25f0a04SGreg Roach	/** @var string The raw GEDCOM data for this fact */
30*a25f0a04SGreg Roach	private $gedcom;
31*a25f0a04SGreg Roach
32*a25f0a04SGreg Roach	/** @var string The GEDCOM tag for this record */
33*a25f0a04SGreg Roach	private $tag;
34*a25f0a04SGreg Roach
35*a25f0a04SGreg Roach	/** @var boolean Is this a recently deleted fact, pending approval? */
36*a25f0a04SGreg Roach	private $pending_deletion = false;
37*a25f0a04SGreg Roach
38*a25f0a04SGreg Roach	/** @var boolean Is this a recently added fact, pending approval? */
39*a25f0a04SGreg Roach	private $pending_addition = false;
40*a25f0a04SGreg Roach
41*a25f0a04SGreg Roach	/** @var Date The date of this fact, from the “2 DATE …” attribute */
42*a25f0a04SGreg Roach	private $date;
43*a25f0a04SGreg Roach
44*a25f0a04SGreg Roach	/** @var Place The place of this fact, from the “2 PLAC …” attribute */
45*a25f0a04SGreg Roach	private $place;
46*a25f0a04SGreg Roach
47*a25f0a04SGreg Roach	/** @var integer Temporary(!) variable Used by sort_facts() */
48*a25f0a04SGreg Roach	public $sortOrder;
49*a25f0a04SGreg Roach
50*a25f0a04SGreg Roach	/**
51*a25f0a04SGreg Roach	 * Create an event object from a gedcom fragment.
52*a25f0a04SGreg Roach	 * We need the parent object (to check privacy) and a (pseudo) fact ID to
53*a25f0a04SGreg Roach	 * identify the fact within the record.
54*a25f0a04SGreg Roach	 *
55*a25f0a04SGreg Roach	 * @param string          $gedcom
56*a25f0a04SGreg Roach	 * @param GedcomRecord $parent
57*a25f0a04SGreg Roach	 * @param string          $fact_id
58*a25f0a04SGreg Roach	 *
59*a25f0a04SGreg Roach	 * @throws \InvalidArgumentException
60*a25f0a04SGreg Roach	 */
61*a25f0a04SGreg Roach	public function __construct($gedcom, GedcomRecord $parent, $fact_id) {
62*a25f0a04SGreg Roach		if (preg_match('/^1 (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
63*a25f0a04SGreg Roach			$this->gedcom  = $gedcom;
64*a25f0a04SGreg Roach			$this->parent  = $parent;
65*a25f0a04SGreg Roach			$this->fact_id = $fact_id;
66*a25f0a04SGreg Roach			$this->tag     = $match[1];
67*a25f0a04SGreg Roach		} else {
68*a25f0a04SGreg Roach			throw new \InvalidArgumentException('Invalid GEDCOM data passed to Fact::_construct(' . $gedcom . ')');
69*a25f0a04SGreg Roach		}
70*a25f0a04SGreg Roach	}
71*a25f0a04SGreg Roach
72*a25f0a04SGreg Roach	/**
73*a25f0a04SGreg Roach	 * Get the value of level 1 data in the fact
74*a25f0a04SGreg Roach	 * Allow for multi-line values
75*a25f0a04SGreg Roach	 *
76*a25f0a04SGreg Roach	 * @return string|null
77*a25f0a04SGreg Roach	 */
78*a25f0a04SGreg Roach	public function getValue() {
79*a25f0a04SGreg Roach		if (preg_match('/^1 (?:' . $this->tag . ') ?(.*(?:(?:\n2 CONT ?.*)*))/', $this->gedcom, $match)) {
80*a25f0a04SGreg Roach			return preg_replace("/\n2 CONT ?/", "\n", $match[1]);
81*a25f0a04SGreg Roach		} else {
82*a25f0a04SGreg Roach			return null;
83*a25f0a04SGreg Roach		}
84*a25f0a04SGreg Roach	}
85*a25f0a04SGreg Roach
86*a25f0a04SGreg Roach	/**
87*a25f0a04SGreg Roach	 * Get the record to which this fact links
88*a25f0a04SGreg Roach	 *
89*a25f0a04SGreg Roach	 * @return GedcomRecord|null
90*a25f0a04SGreg Roach	 */
91*a25f0a04SGreg Roach	public function getTarget() {
92*a25f0a04SGreg Roach		$xref = trim($this->getValue(), '@');
93*a25f0a04SGreg Roach		switch ($this->tag) {
94*a25f0a04SGreg Roach		case 'FAMC':
95*a25f0a04SGreg Roach		case 'FAMS':
96*a25f0a04SGreg Roach			return Family::getInstance($xref, $this->getParent()->getGedcomId());
97*a25f0a04SGreg Roach		case 'HUSB':
98*a25f0a04SGreg Roach		case 'WIFE':
99*a25f0a04SGreg Roach		case 'CHIL':
100*a25f0a04SGreg Roach			return Individual::getInstance($xref, $this->getParent()->getGedcomId());
101*a25f0a04SGreg Roach		case 'SOUR':
102*a25f0a04SGreg Roach			return Source::getInstance($xref, $this->getParent()->getGedcomId());
103*a25f0a04SGreg Roach		case 'OBJE':
104*a25f0a04SGreg Roach			return Media::getInstance($xref, $this->getParent()->getGedcomId());
105*a25f0a04SGreg Roach		case 'REPO':
106*a25f0a04SGreg Roach			return Repository::getInstance($xref, $this->getParent()->getGedcomId());
107*a25f0a04SGreg Roach		case 'NOTE':
108*a25f0a04SGreg Roach			return Note::getInstance($xref, $this->getParent()->getGedcomId());
109*a25f0a04SGreg Roach		default:
110*a25f0a04SGreg Roach			return GedcomRecord::getInstance($xref, $this->getParent()->getGedcomId());
111*a25f0a04SGreg Roach		}
112*a25f0a04SGreg Roach	}
113*a25f0a04SGreg Roach
114*a25f0a04SGreg Roach	/**
115*a25f0a04SGreg Roach	 * Get the value of level 2 data in the fact
116*a25f0a04SGreg Roach	 *
117*a25f0a04SGreg Roach	 * @param string $tag
118*a25f0a04SGreg Roach	 *
119*a25f0a04SGreg Roach	 * @return string|null
120*a25f0a04SGreg Roach	 */
121*a25f0a04SGreg Roach	public function getAttribute($tag) {
122*a25f0a04SGreg Roach		if (preg_match('/\n2 (?:' . $tag . ') ?(.*(?:(?:\n3 CONT ?.*)*)*)/', $this->gedcom, $match)) {
123*a25f0a04SGreg Roach			return preg_replace("/\n3 CONT ?/", "\n", $match[1]);
124*a25f0a04SGreg Roach		} else {
125*a25f0a04SGreg Roach			return null;
126*a25f0a04SGreg Roach		}
127*a25f0a04SGreg Roach	}
128*a25f0a04SGreg Roach
129*a25f0a04SGreg Roach	/**
130*a25f0a04SGreg Roach	 * Do the privacy rules allow us to display this fact to the current user
131*a25f0a04SGreg Roach	 *
132*a25f0a04SGreg Roach	 * @param integer $access_level
133*a25f0a04SGreg Roach	 *
134*a25f0a04SGreg Roach	 * @return boolean
135*a25f0a04SGreg Roach	 */
136*a25f0a04SGreg Roach	public function canShow($access_level = WT_USER_ACCESS_LEVEL) {
137*a25f0a04SGreg Roach		// TODO - use the privacy settings for $this->gedcom_id, not the default gedcom.
138*a25f0a04SGreg Roach		global $person_facts, $global_facts;
139*a25f0a04SGreg Roach
140*a25f0a04SGreg Roach		// Does this record have an explicit RESN?
141*a25f0a04SGreg Roach		if (strpos($this->gedcom, "\n2 RESN confidential")) {
142*a25f0a04SGreg Roach			return WT_PRIV_NONE >= $access_level;
143*a25f0a04SGreg Roach		}
144*a25f0a04SGreg Roach		if (strpos($this->gedcom, "\n2 RESN privacy")) {
145*a25f0a04SGreg Roach			return WT_PRIV_USER >= $access_level;
146*a25f0a04SGreg Roach		}
147*a25f0a04SGreg Roach		if (strpos($this->gedcom, "\n2 RESN none")) {
148*a25f0a04SGreg Roach			return true;
149*a25f0a04SGreg Roach		}
150*a25f0a04SGreg Roach
151*a25f0a04SGreg Roach		// Does this record have a default RESN?
152*a25f0a04SGreg Roach		$xref = $this->parent->getXref();
153*a25f0a04SGreg Roach		if (isset($person_facts[$xref][$this->tag])) {
154*a25f0a04SGreg Roach			return $person_facts[$xref][$this->tag] >= $access_level;
155*a25f0a04SGreg Roach		}
156*a25f0a04SGreg Roach		if (isset($global_facts[$this->tag])) {
157*a25f0a04SGreg Roach			return $global_facts[$this->tag] >= $access_level;
158*a25f0a04SGreg Roach		}
159*a25f0a04SGreg Roach
160*a25f0a04SGreg Roach		// No restrictions - it must be public
161*a25f0a04SGreg Roach		return true;
162*a25f0a04SGreg Roach	}
163*a25f0a04SGreg Roach
164*a25f0a04SGreg Roach	/**
165*a25f0a04SGreg Roach	 * Check whether this fact is protected against edit
166*a25f0a04SGreg Roach	 *
167*a25f0a04SGreg Roach	 * @return boolean
168*a25f0a04SGreg Roach	 */
169*a25f0a04SGreg Roach	public function canEdit() {
170*a25f0a04SGreg Roach		// Managers can edit anything
171*a25f0a04SGreg Roach		// Members cannot edit RESN, CHAN and locked records
172*a25f0a04SGreg Roach		return
173*a25f0a04SGreg Roach			$this->parent->canEdit() && !$this->isPendingDeletion() && (
174*a25f0a04SGreg Roach				WT_USER_GEDCOM_ADMIN ||
175*a25f0a04SGreg Roach				WT_USER_CAN_EDIT && strpos($this->gedcom, "\n2 RESN locked") === false && $this->getTag() != 'RESN' && $this->getTag() != 'CHAN'
176*a25f0a04SGreg Roach			);
177*a25f0a04SGreg Roach	}
178*a25f0a04SGreg Roach
179*a25f0a04SGreg Roach	/**
180*a25f0a04SGreg Roach	 * The place where the event occured.
181*a25f0a04SGreg Roach	 *
182*a25f0a04SGreg Roach	 * @return Place
183*a25f0a04SGreg Roach	 */
184*a25f0a04SGreg Roach	public function getPlace() {
185*a25f0a04SGreg Roach		if ($this->place === null) {
186*a25f0a04SGreg Roach			$this->place = new Place($this->getAttribute('PLAC'), $this->getParent()->getGedcomId());
187*a25f0a04SGreg Roach		}
188*a25f0a04SGreg Roach
189*a25f0a04SGreg Roach		return $this->place;
190*a25f0a04SGreg Roach	}
191*a25f0a04SGreg Roach
192*a25f0a04SGreg Roach	/**
193*a25f0a04SGreg Roach	 * Get the date for this fact.
194*a25f0a04SGreg Roach	 * We can call this function many times, especially when sorting,
195*a25f0a04SGreg Roach	 * so keep a copy of the date.
196*a25f0a04SGreg Roach	 *
197*a25f0a04SGreg Roach	 * @return Date
198*a25f0a04SGreg Roach	 */
199*a25f0a04SGreg Roach	public function getDate() {
200*a25f0a04SGreg Roach		if ($this->date === null) {
201*a25f0a04SGreg Roach			$this->date = new Date($this->getAttribute('DATE'));
202*a25f0a04SGreg Roach		}
203*a25f0a04SGreg Roach
204*a25f0a04SGreg Roach		return $this->date;
205*a25f0a04SGreg Roach	}
206*a25f0a04SGreg Roach
207*a25f0a04SGreg Roach	/**
208*a25f0a04SGreg Roach	 * The raw GEDCOM data for this fact
209*a25f0a04SGreg Roach	 *
210*a25f0a04SGreg Roach	 * @return string
211*a25f0a04SGreg Roach	 */
212*a25f0a04SGreg Roach	public function getGedcom() {
213*a25f0a04SGreg Roach		return $this->gedcom;
214*a25f0a04SGreg Roach	}
215*a25f0a04SGreg Roach
216*a25f0a04SGreg Roach	/**
217*a25f0a04SGreg Roach	 * Get a (pseudo) primary key for this fact.
218*a25f0a04SGreg Roach	 *
219*a25f0a04SGreg Roach	 * @return string
220*a25f0a04SGreg Roach	 */
221*a25f0a04SGreg Roach	public function getFactId() {
222*a25f0a04SGreg Roach		return $this->fact_id;
223*a25f0a04SGreg Roach	}
224*a25f0a04SGreg Roach
225*a25f0a04SGreg Roach	// What sort of fact is this?
226*a25f0a04SGreg Roach	/**
227*a25f0a04SGreg Roach	 * What is the tag (type) of this fact, such as BIRT, MARR or DEAT.
228*a25f0a04SGreg Roach	 *
229*a25f0a04SGreg Roach	 * @return string
230*a25f0a04SGreg Roach	 */
231*a25f0a04SGreg Roach	public function getTag() {
232*a25f0a04SGreg Roach		return $this->tag;
233*a25f0a04SGreg Roach	}
234*a25f0a04SGreg Roach
235*a25f0a04SGreg Roach	/**
236*a25f0a04SGreg Roach	 * Used to convert a real fact (e.g. BIRT) into a close-relative’s fact (e.g. _BIRT_CHIL)
237*a25f0a04SGreg Roach	 *
238*a25f0a04SGreg Roach	 * @param string $tag
239*a25f0a04SGreg Roach	 */
240*a25f0a04SGreg Roach	public function setTag($tag) {
241*a25f0a04SGreg Roach		$this->tag = $tag;
242*a25f0a04SGreg Roach	}
243*a25f0a04SGreg Roach
244*a25f0a04SGreg Roach	//
245*a25f0a04SGreg Roach	/**
246*a25f0a04SGreg Roach	 * The Person/Family record where this Fact came from
247*a25f0a04SGreg Roach	 *
248*a25f0a04SGreg Roach	 * @return GedcomRecord
249*a25f0a04SGreg Roach	 */
250*a25f0a04SGreg Roach	public function getParent() {
251*a25f0a04SGreg Roach		return $this->parent;
252*a25f0a04SGreg Roach	}
253*a25f0a04SGreg Roach
254*a25f0a04SGreg Roach	/**
255*a25f0a04SGreg Roach	 * Get the name of this fact type, for use as a label.
256*a25f0a04SGreg Roach	 *
257*a25f0a04SGreg Roach	 * @return string
258*a25f0a04SGreg Roach	 */
259*a25f0a04SGreg Roach	public function getLabel() {
260*a25f0a04SGreg Roach		switch ($this->tag) {
261*a25f0a04SGreg Roach		case 'EVEN':
262*a25f0a04SGreg Roach		case 'FACT':
263*a25f0a04SGreg Roach			if ($this->getAttribute('TYPE')) {
264*a25f0a04SGreg Roach				// Custom FACT/EVEN - with a TYPE
265*a25f0a04SGreg Roach				return I18N::translate(Filter::escapeHtml($this->getAttribute('TYPE')));
266*a25f0a04SGreg Roach			}
267*a25f0a04SGreg Roach			// no break - drop into next case
268*a25f0a04SGreg Roach		default:
269*a25f0a04SGreg Roach			return WT_Gedcom_Tag::getLabel($this->tag, $this->parent);
270*a25f0a04SGreg Roach		}
271*a25f0a04SGreg Roach	}
272*a25f0a04SGreg Roach
273*a25f0a04SGreg Roach	/**
274*a25f0a04SGreg Roach	 * This is a newly deleted fact, pending approval.
275*a25f0a04SGreg Roach	 */
276*a25f0a04SGreg Roach	public function setPendingDeletion() {
277*a25f0a04SGreg Roach		$this->pending_deletion = true;
278*a25f0a04SGreg Roach		$this->pending_addition = false;
279*a25f0a04SGreg Roach	}
280*a25f0a04SGreg Roach
281*a25f0a04SGreg Roach	/**
282*a25f0a04SGreg Roach	 * Is this a newly deleted fact, pending approval.
283*a25f0a04SGreg Roach	 *
284*a25f0a04SGreg Roach	 * @return boolean
285*a25f0a04SGreg Roach	 */
286*a25f0a04SGreg Roach	public function isPendingDeletion() {
287*a25f0a04SGreg Roach		return $this->pending_deletion;
288*a25f0a04SGreg Roach	}
289*a25f0a04SGreg Roach
290*a25f0a04SGreg Roach	/**
291*a25f0a04SGreg Roach	 * This is a newly added fact, pending approval.
292*a25f0a04SGreg Roach	 */
293*a25f0a04SGreg Roach	public function setPendingAddition() {
294*a25f0a04SGreg Roach		$this->pending_addition = true;
295*a25f0a04SGreg Roach		$this->pending_deletion = false;
296*a25f0a04SGreg Roach	}
297*a25f0a04SGreg Roach
298*a25f0a04SGreg Roach	/**
299*a25f0a04SGreg Roach	 * Is this a newly added fact, pending approval.
300*a25f0a04SGreg Roach	 *
301*a25f0a04SGreg Roach	 * @return boolean
302*a25f0a04SGreg Roach	 */
303*a25f0a04SGreg Roach	public function isPendingAddition() {
304*a25f0a04SGreg Roach		return $this->pending_addition;
305*a25f0a04SGreg Roach	}
306*a25f0a04SGreg Roach
307*a25f0a04SGreg Roach	/**
308*a25f0a04SGreg Roach	 * Source citations linked to this fact
309*a25f0a04SGreg Roach	 *
310*a25f0a04SGreg Roach	 * @return string[]
311*a25f0a04SGreg Roach	 */
312*a25f0a04SGreg Roach	public function getCitations() {
313*a25f0a04SGreg Roach		preg_match_all('/\n(2 SOUR @(' . WT_REGEX_XREF . ')@(?:\n[3-9] .*)*)/', $this->getGedcom(), $matches, PREG_SET_ORDER);
314*a25f0a04SGreg Roach		$citations = array();
315*a25f0a04SGreg Roach		foreach ($matches as $match) {
316*a25f0a04SGreg Roach			$source = Source::getInstance($match[2], $this->getParent()->getGedcomId());
317*a25f0a04SGreg Roach			if ($source->canShow()) {
318*a25f0a04SGreg Roach				$citations[] = $match[1];
319*a25f0a04SGreg Roach			}
320*a25f0a04SGreg Roach		}
321*a25f0a04SGreg Roach
322*a25f0a04SGreg Roach		return $citations;
323*a25f0a04SGreg Roach	}
324*a25f0a04SGreg Roach
325*a25f0a04SGreg Roach	/**
326*a25f0a04SGreg Roach	 * Notes (inline and objects) linked to this fact
327*a25f0a04SGreg Roach	 *
328*a25f0a04SGreg Roach	 * @return string[]|Note[]
329*a25f0a04SGreg Roach	 */
330*a25f0a04SGreg Roach	public function getNotes() {
331*a25f0a04SGreg Roach		$notes = array();
332*a25f0a04SGreg Roach		preg_match_all('/\n2 NOTE ?(.*(?:\n3.*)*)/', $this->getGedcom(), $matches);
333*a25f0a04SGreg Roach		foreach ($matches[1] as $match) {
334*a25f0a04SGreg Roach			$note = preg_replace("/\n3 CONT ?/", "\n", $match);
335*a25f0a04SGreg Roach			if (preg_match('/@(' . WT_REGEX_XREF . ')@/', $note, $nmatch)) {
336*a25f0a04SGreg Roach				$note = Note::getInstance($nmatch[1], $this->getParent()->getGedcomId());
337*a25f0a04SGreg Roach				if ($note && $note->canShow()) {
338*a25f0a04SGreg Roach					// A note object
339*a25f0a04SGreg Roach					$notes[] = $note;
340*a25f0a04SGreg Roach				}
341*a25f0a04SGreg Roach			} else {
342*a25f0a04SGreg Roach				// An inline note
343*a25f0a04SGreg Roach				$notes[] = $note;
344*a25f0a04SGreg Roach			}
345*a25f0a04SGreg Roach		}
346*a25f0a04SGreg Roach
347*a25f0a04SGreg Roach		return $notes;
348*a25f0a04SGreg Roach	}
349*a25f0a04SGreg Roach
350*a25f0a04SGreg Roach	/**
351*a25f0a04SGreg Roach	 * Media objects linked to this fact
352*a25f0a04SGreg Roach	 *
353*a25f0a04SGreg Roach	 * @return Media[]
354*a25f0a04SGreg Roach	 */
355*a25f0a04SGreg Roach	public function getMedia() {
356*a25f0a04SGreg Roach		$media = array();
357*a25f0a04SGreg Roach		preg_match_all('/\n2 OBJE @(' . WT_REGEX_XREF . ')@/', $this->getGedcom(), $matches);
358*a25f0a04SGreg Roach		foreach ($matches[1] as $match) {
359*a25f0a04SGreg Roach			$obje = Media::getInstance($match, $this->getParent()->getGedcomId());
360*a25f0a04SGreg Roach			if ($obje->canShow()) {
361*a25f0a04SGreg Roach				$media[] = $obje;
362*a25f0a04SGreg Roach			}
363*a25f0a04SGreg Roach		}
364*a25f0a04SGreg Roach
365*a25f0a04SGreg Roach		return $media;
366*a25f0a04SGreg Roach	}
367*a25f0a04SGreg Roach
368*a25f0a04SGreg Roach	/**
369*a25f0a04SGreg Roach	 * A one-line summary of the fact - for charts, etc.
370*a25f0a04SGreg Roach	 *
371*a25f0a04SGreg Roach	 * @return string
372*a25f0a04SGreg Roach	 */
373*a25f0a04SGreg Roach	public function summary() {
374*a25f0a04SGreg Roach		global $SHOW_PARENTS_AGE;
375*a25f0a04SGreg Roach
376*a25f0a04SGreg Roach		$attributes = array();
377*a25f0a04SGreg Roach		$target     = $this->getTarget();
378*a25f0a04SGreg Roach		if ($target) {
379*a25f0a04SGreg Roach			$attributes[] = $target->getFullName();
380*a25f0a04SGreg Roach		} else {
381*a25f0a04SGreg Roach			$value = $this->getValue();
382*a25f0a04SGreg Roach			if ($value && $value != 'Y') {
383*a25f0a04SGreg Roach				$attributes[] = '<span dir="auto">' . Filter::escapeHtml($value) . '</span>';
384*a25f0a04SGreg Roach			}
385*a25f0a04SGreg Roach			$date = $this->getDate();
386*a25f0a04SGreg Roach			if ($this->getTag() == 'BIRT' && $SHOW_PARENTS_AGE && $this->getParent() instanceof Individual) {
387*a25f0a04SGreg Roach				$attributes[] = $date->display() . format_parents_age($this->getParent(), $date);
388*a25f0a04SGreg Roach			} else {
389*a25f0a04SGreg Roach				$attributes[] = $date->display();
390*a25f0a04SGreg Roach			}
391*a25f0a04SGreg Roach			$place = $this->getPlace()->getShortName();
392*a25f0a04SGreg Roach			if ($place) {
393*a25f0a04SGreg Roach				$attributes[] = $place;
394*a25f0a04SGreg Roach			}
395*a25f0a04SGreg Roach		}
396*a25f0a04SGreg Roach		$html = WT_Gedcom_Tag::getLabelValue($this->getTag(), implode(' — ', $attributes), $this->getParent());
397*a25f0a04SGreg Roach		if ($this->isPendingAddition()) {
398*a25f0a04SGreg Roach			return '<div class="new">' . $html . '</div>';
399*a25f0a04SGreg Roach		} elseif ($this->isPendingDeletion()) {
400*a25f0a04SGreg Roach			return '<div class="old">' . $html . '</div>';
401*a25f0a04SGreg Roach		} else {
402*a25f0a04SGreg Roach			return $html;
403*a25f0a04SGreg Roach		}
404*a25f0a04SGreg Roach	}
405*a25f0a04SGreg Roach
406*a25f0a04SGreg Roach	/**
407*a25f0a04SGreg Roach	 * Static Helper functions to sort events
408*a25f0a04SGreg Roach	 *
409*a25f0a04SGreg Roach	 * @param Fact $a Fact one
410*a25f0a04SGreg Roach	 * @param Fact $b Fact two
411*a25f0a04SGreg Roach	 *
412*a25f0a04SGreg Roach	 * @return integer
413*a25f0a04SGreg Roach	 */
414*a25f0a04SGreg Roach	public static function compareDate(Fact $a, Fact $b) {
415*a25f0a04SGreg Roach		if ($a->getDate()->isOK() && $b->getDate()->isOK()) {
416*a25f0a04SGreg Roach			// If both events have dates, compare by date
417*a25f0a04SGreg Roach			$ret = Date::Compare($a->getDate(), $b->getDate());
418*a25f0a04SGreg Roach
419*a25f0a04SGreg Roach			if ($ret == 0) {
420*a25f0a04SGreg Roach				// If dates are the same, compare by fact type
421*a25f0a04SGreg Roach				$ret = self::compareType($a, $b);
422*a25f0a04SGreg Roach
423*a25f0a04SGreg Roach				// If the fact type is also the same, retain the initial order
424*a25f0a04SGreg Roach				if ($ret == 0) {
425*a25f0a04SGreg Roach					$ret = $a->sortOrder - $b->sortOrder;
426*a25f0a04SGreg Roach				}
427*a25f0a04SGreg Roach			}
428*a25f0a04SGreg Roach
429*a25f0a04SGreg Roach			return $ret;
430*a25f0a04SGreg Roach		} else {
431*a25f0a04SGreg Roach			// One or both events have no date - retain the initial order
432*a25f0a04SGreg Roach			return $a->sortOrder - $b->sortOrder;
433*a25f0a04SGreg Roach		}
434*a25f0a04SGreg Roach	}
435*a25f0a04SGreg Roach
436*a25f0a04SGreg Roach	/**
437*a25f0a04SGreg Roach	 * Static method to compare two events by their type.
438*a25f0a04SGreg Roach	 *
439*a25f0a04SGreg Roach	 * @param Fact $a Fact one
440*a25f0a04SGreg Roach	 * @param Fact $b Fact two
441*a25f0a04SGreg Roach	 *
442*a25f0a04SGreg Roach	 * @return integer
443*a25f0a04SGreg Roach	 */
444*a25f0a04SGreg Roach	public static function compareType(Fact $a, Fact $b) {
445*a25f0a04SGreg Roach		global $factsort;
446*a25f0a04SGreg Roach
447*a25f0a04SGreg Roach		if (empty($factsort)) {
448*a25f0a04SGreg Roach			$factsort = array_flip(
449*a25f0a04SGreg Roach				array(
450*a25f0a04SGreg Roach					'BIRT',
451*a25f0a04SGreg Roach					'_HNM',
452*a25f0a04SGreg Roach					'ALIA', '_AKA', '_AKAN',
453*a25f0a04SGreg Roach					'ADOP', '_ADPF', '_ADPF',
454*a25f0a04SGreg Roach					'_BRTM',
455*a25f0a04SGreg Roach					'CHR', 'BAPM',
456*a25f0a04SGreg Roach					'FCOM',
457*a25f0a04SGreg Roach					'CONF',
458*a25f0a04SGreg Roach					'BARM', 'BASM',
459*a25f0a04SGreg Roach					'EDUC',
460*a25f0a04SGreg Roach					'GRAD',
461*a25f0a04SGreg Roach					'_DEG',
462*a25f0a04SGreg Roach					'EMIG', 'IMMI',
463*a25f0a04SGreg Roach					'NATU',
464*a25f0a04SGreg Roach					'_MILI', '_MILT',
465*a25f0a04SGreg Roach					'ENGA',
466*a25f0a04SGreg Roach					'MARB', 'MARC', 'MARL', '_MARI', '_MBON',
467*a25f0a04SGreg Roach					'MARR', 'MARR_CIVIL', 'MARR_RELIGIOUS', 'MARR_PARTNERS', 'MARR_UNKNOWN', '_COML',
468*a25f0a04SGreg Roach					'_STAT',
469*a25f0a04SGreg Roach					'_SEPR',
470*a25f0a04SGreg Roach					'DIVF',
471*a25f0a04SGreg Roach					'MARS',
472*a25f0a04SGreg Roach					'_BIRT_CHIL',
473*a25f0a04SGreg Roach					'DIV', 'ANUL',
474*a25f0a04SGreg Roach					'_BIRT_', '_MARR_', '_DEAT_', '_BURI_', // other events of close relatives
475*a25f0a04SGreg Roach					'CENS',
476*a25f0a04SGreg Roach					'OCCU',
477*a25f0a04SGreg Roach					'RESI',
478*a25f0a04SGreg Roach					'PROP',
479*a25f0a04SGreg Roach					'CHRA',
480*a25f0a04SGreg Roach					'RETI',
481*a25f0a04SGreg Roach					'FACT', 'EVEN',
482*a25f0a04SGreg Roach					'_NMR', '_NMAR', 'NMR',
483*a25f0a04SGreg Roach					'NCHI',
484*a25f0a04SGreg Roach					'WILL',
485*a25f0a04SGreg Roach					'_HOL',
486*a25f0a04SGreg Roach					'_????_',
487*a25f0a04SGreg Roach					'DEAT',
488*a25f0a04SGreg Roach					'_FNRL', 'CREM', 'BURI', '_INTE',
489*a25f0a04SGreg Roach					'_YART',
490*a25f0a04SGreg Roach					'_NLIV',
491*a25f0a04SGreg Roach					'PROB',
492*a25f0a04SGreg Roach					'TITL',
493*a25f0a04SGreg Roach					'COMM',
494*a25f0a04SGreg Roach					'NATI',
495*a25f0a04SGreg Roach					'CITN',
496*a25f0a04SGreg Roach					'CAST',
497*a25f0a04SGreg Roach					'RELI',
498*a25f0a04SGreg Roach					'SSN', 'IDNO',
499*a25f0a04SGreg Roach					'TEMP',
500*a25f0a04SGreg Roach					'SLGC', 'BAPL', 'CONL', 'ENDL', 'SLGS',
501*a25f0a04SGreg Roach					'ADDR', 'PHON', 'EMAIL', '_EMAIL', 'EMAL', 'FAX', 'WWW', 'URL', '_URL',
502*a25f0a04SGreg Roach					'FILE', // For media objects
503*a25f0a04SGreg Roach					'AFN', 'REFN', '_PRMN', 'REF', 'RIN', '_UID',
504*a25f0a04SGreg Roach					'OBJE', 'NOTE', 'SOUR',
505*a25f0a04SGreg Roach					'CHAN', '_TODO',
506*a25f0a04SGreg Roach				)
507*a25f0a04SGreg Roach			);
508*a25f0a04SGreg Roach		}
509*a25f0a04SGreg Roach
510*a25f0a04SGreg Roach		// Facts from same families stay grouped together
511*a25f0a04SGreg Roach		// Keep MARR and DIV from the same families from mixing with events from other FAMs
512*a25f0a04SGreg Roach		// Use the original order in which the facts were added
513*a25f0a04SGreg Roach		if ($a->parent instanceof Family && $b->parent instanceof Family && $a->parent !== $b->parent) {
514*a25f0a04SGreg Roach			return $a->sortOrder - $b->sortOrder;
515*a25f0a04SGreg Roach		}
516*a25f0a04SGreg Roach
517*a25f0a04SGreg Roach		$atag = $a->getTag();
518*a25f0a04SGreg Roach		$btag = $b->getTag();
519*a25f0a04SGreg Roach
520*a25f0a04SGreg Roach		// Events not in the above list get mapped onto one that is.
521*a25f0a04SGreg Roach		if (!array_key_exists($atag, $factsort)) {
522*a25f0a04SGreg Roach			if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
523*a25f0a04SGreg Roach				$atag = $match[1];
524*a25f0a04SGreg Roach			} else {
525*a25f0a04SGreg Roach				$atag = "_????_";
526*a25f0a04SGreg Roach			}
527*a25f0a04SGreg Roach		}
528*a25f0a04SGreg Roach
529*a25f0a04SGreg Roach		if (!array_key_exists($btag, $factsort)) {
530*a25f0a04SGreg Roach			if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
531*a25f0a04SGreg Roach				$btag = $match[1];
532*a25f0a04SGreg Roach			} else {
533*a25f0a04SGreg Roach				$btag = "_????_";
534*a25f0a04SGreg Roach			}
535*a25f0a04SGreg Roach		}
536*a25f0a04SGreg Roach
537*a25f0a04SGreg Roach		// - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
538*a25f0a04SGreg Roach		// - Treat dated after BURI facts as BURI instead
539*a25f0a04SGreg Roach		if ($a->getAttribute('DATE') != null && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
540*a25f0a04SGreg Roach			$atag = 'BURI';
541*a25f0a04SGreg Roach		}
542*a25f0a04SGreg Roach
543*a25f0a04SGreg Roach		if ($b->getAttribute('DATE') != null && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
544*a25f0a04SGreg Roach			$btag = 'BURI';
545*a25f0a04SGreg Roach		}
546*a25f0a04SGreg Roach
547*a25f0a04SGreg Roach		$ret = $factsort[$atag] - $factsort[$btag];
548*a25f0a04SGreg Roach
549*a25f0a04SGreg Roach		// If facts are the same then put dated facts before non-dated facts
550*a25f0a04SGreg Roach		if ($ret == 0) {
551*a25f0a04SGreg Roach			if ($a->getAttribute('DATE') != null && $b->getAttribute('DATE') == null) {
552*a25f0a04SGreg Roach				return -1;
553*a25f0a04SGreg Roach			}
554*a25f0a04SGreg Roach
555*a25f0a04SGreg Roach			if ($b->getAttribute('DATE') != null && $a->getAttribute('DATE') == null) {
556*a25f0a04SGreg Roach				return 1;
557*a25f0a04SGreg Roach			}
558*a25f0a04SGreg Roach
559*a25f0a04SGreg Roach			// If no sorting preference, then keep original ordering
560*a25f0a04SGreg Roach			$ret = $a->sortOrder - $b->sortOrder;
561*a25f0a04SGreg Roach		}
562*a25f0a04SGreg Roach
563*a25f0a04SGreg Roach		return $ret;
564*a25f0a04SGreg Roach	}
565*a25f0a04SGreg Roach
566*a25f0a04SGreg Roach	/**
567*a25f0a04SGreg Roach	 * Allow native PHP functions such as array_unique() to work with objects
568*a25f0a04SGreg Roach	 *
569*a25f0a04SGreg Roach	 * @return string
570*a25f0a04SGreg Roach	 */
571*a25f0a04SGreg Roach	public function __toString() {
572*a25f0a04SGreg Roach		return $this->fact_id . '@' . $this->parent->getXref();
573*a25f0a04SGreg Roach	}
574*a25f0a04SGreg Roach}
575