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