xref: /webtrees/app/Fact.php (revision c1010eda29c0909ed4d5d463f32d32bfefdd4dfe)
1a25f0a04SGreg Roach<?php
2a25f0a04SGreg Roach/**
3a25f0a04SGreg Roach * webtrees: online genealogy
41062a142SGreg Roach * Copyright (C) 2018 webtrees development team
5a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify
6a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by
7a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or
8a25f0a04SGreg Roach * (at your option) any later version.
9a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful,
10a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
11a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12a25f0a04SGreg Roach * GNU General Public License for more details.
13a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License
14a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
15a25f0a04SGreg Roach */
1676692c8bSGreg Roachnamespace Fisharebest\Webtrees;
1776692c8bSGreg Roach
183d7a8a4cSGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsPrint;
198f5f5da8SGreg Roachuse InvalidArgumentException;
20a25f0a04SGreg Roach
21a25f0a04SGreg Roach/**
2276692c8bSGreg Roach * A GEDCOM fact or event object.
23a25f0a04SGreg Roach */
24*c1010edaSGreg Roachclass Fact
25*c1010edaSGreg Roach{
26a25f0a04SGreg Roach    /** @var string Unique identifier for this fact (currently implemented as a hash of the raw data). */
27a25f0a04SGreg Roach    private $fact_id;
28a25f0a04SGreg Roach
29a25f0a04SGreg Roach    /** @var GedcomRecord The GEDCOM record from which this fact is taken */
30a25f0a04SGreg Roach    private $parent;
31a25f0a04SGreg Roach
32a25f0a04SGreg Roach    /** @var string The raw GEDCOM data for this fact */
33a25f0a04SGreg Roach    private $gedcom;
34a25f0a04SGreg Roach
35a25f0a04SGreg Roach    /** @var string The GEDCOM tag for this record */
36a25f0a04SGreg Roach    private $tag;
37a25f0a04SGreg Roach
38cbc1590aSGreg Roach    /** @var bool Is this a recently deleted fact, pending approval? */
39a25f0a04SGreg Roach    private $pending_deletion = false;
40a25f0a04SGreg Roach
41cbc1590aSGreg Roach    /** @var bool Is this a recently added fact, pending approval? */
42a25f0a04SGreg Roach    private $pending_addition = false;
43a25f0a04SGreg Roach
44a25f0a04SGreg Roach    /** @var Date The date of this fact, from the “2 DATE …” attribute */
45a25f0a04SGreg Roach    private $date;
46a25f0a04SGreg Roach
47a25f0a04SGreg Roach    /** @var Place The place of this fact, from the “2 PLAC …” attribute */
48a25f0a04SGreg Roach    private $place;
49a25f0a04SGreg Roach
503d7a8a4cSGreg Roach    /** @var int Temporary(!) variable Used by Functions::sortFacts() */
51a25f0a04SGreg Roach    public $sortOrder;
52a25f0a04SGreg Roach
53a25f0a04SGreg Roach    /**
54a25f0a04SGreg Roach     * Create an event object from a gedcom fragment.
55a25f0a04SGreg Roach     * We need the parent object (to check privacy) and a (pseudo) fact ID to
56a25f0a04SGreg Roach     * identify the fact within the record.
57a25f0a04SGreg Roach     *
58a25f0a04SGreg Roach     * @param string       $gedcom
59a25f0a04SGreg Roach     * @param GedcomRecord $parent
60a25f0a04SGreg Roach     * @param string       $fact_id
61a25f0a04SGreg Roach     *
628f5f5da8SGreg Roach     * @throws InvalidArgumentException
63a25f0a04SGreg Roach     */
64*c1010edaSGreg Roach    public function __construct($gedcom, GedcomRecord $parent, $fact_id)
65*c1010edaSGreg Roach    {
66a25f0a04SGreg Roach        if (preg_match('/^1 (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
67a25f0a04SGreg Roach            $this->gedcom  = $gedcom;
68a25f0a04SGreg Roach            $this->parent  = $parent;
69a25f0a04SGreg Roach            $this->fact_id = $fact_id;
70a25f0a04SGreg Roach            $this->tag     = $match[1];
71a25f0a04SGreg Roach        } else {
728f5f5da8SGreg Roach            throw new InvalidArgumentException('Invalid GEDCOM data passed to Fact::_construct(' . $gedcom . ')');
73a25f0a04SGreg Roach        }
74a25f0a04SGreg Roach    }
75a25f0a04SGreg Roach
76a25f0a04SGreg Roach    /**
77a25f0a04SGreg Roach     * Get the value of level 1 data in the fact
78a25f0a04SGreg Roach     * Allow for multi-line values
79a25f0a04SGreg Roach     *
80baacc364SGreg Roach     * @return string
81a25f0a04SGreg Roach     */
82*c1010edaSGreg Roach    public function getValue()
83*c1010edaSGreg Roach    {
84a25f0a04SGreg Roach        if (preg_match('/^1 (?:' . $this->tag . ') ?(.*(?:(?:\n2 CONT ?.*)*))/', $this->gedcom, $match)) {
85a25f0a04SGreg Roach            return preg_replace("/\n2 CONT ?/", "\n", $match[1]);
86a25f0a04SGreg Roach        } else {
87baacc364SGreg Roach            return '';
88a25f0a04SGreg Roach        }
89a25f0a04SGreg Roach    }
90a25f0a04SGreg Roach
91a25f0a04SGreg Roach    /**
92a25f0a04SGreg Roach     * Get the record to which this fact links
93a25f0a04SGreg Roach     *
94805a90eaSGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|null
95a25f0a04SGreg Roach     */
96*c1010edaSGreg Roach    public function getTarget()
97*c1010edaSGreg Roach    {
98a25f0a04SGreg Roach        $xref = trim($this->getValue(), '@');
99a25f0a04SGreg Roach        switch ($this->tag) {
100a25f0a04SGreg Roach            case 'FAMC':
101a25f0a04SGreg Roach            case 'FAMS':
10224ec66ceSGreg Roach                return Family::getInstance($xref, $this->getParent()->getTree());
103a25f0a04SGreg Roach            case 'HUSB':
104a25f0a04SGreg Roach            case 'WIFE':
105a25f0a04SGreg Roach            case 'CHIL':
10624ec66ceSGreg Roach                return Individual::getInstance($xref, $this->getParent()->getTree());
107a25f0a04SGreg Roach            case 'SOUR':
10824ec66ceSGreg Roach                return Source::getInstance($xref, $this->getParent()->getTree());
109a25f0a04SGreg Roach            case 'OBJE':
11024ec66ceSGreg Roach                return Media::getInstance($xref, $this->getParent()->getTree());
111a25f0a04SGreg Roach            case 'REPO':
11224ec66ceSGreg Roach                return Repository::getInstance($xref, $this->getParent()->getTree());
113a25f0a04SGreg Roach            case 'NOTE':
11424ec66ceSGreg Roach                return Note::getInstance($xref, $this->getParent()->getTree());
115a25f0a04SGreg Roach            default:
11624ec66ceSGreg Roach                return GedcomRecord::getInstance($xref, $this->getParent()->getTree());
117a25f0a04SGreg Roach        }
118a25f0a04SGreg Roach    }
119a25f0a04SGreg Roach
120a25f0a04SGreg Roach    /**
121a25f0a04SGreg Roach     * Get the value of level 2 data in the fact
122a25f0a04SGreg Roach     *
123a25f0a04SGreg Roach     * @param string $tag
124a25f0a04SGreg Roach     *
125baacc364SGreg Roach     * @return string
126a25f0a04SGreg Roach     */
127*c1010edaSGreg Roach    public function getAttribute($tag)
128*c1010edaSGreg Roach    {
129a25f0a04SGreg Roach        if (preg_match('/\n2 (?:' . $tag . ') ?(.*(?:(?:\n3 CONT ?.*)*)*)/', $this->gedcom, $match)) {
130a25f0a04SGreg Roach            return preg_replace("/\n3 CONT ?/", "\n", $match[1]);
131a25f0a04SGreg Roach        } else {
132baacc364SGreg Roach            return '';
133a25f0a04SGreg Roach        }
134a25f0a04SGreg Roach    }
135a25f0a04SGreg Roach
136a25f0a04SGreg Roach    /**
137a25f0a04SGreg Roach     * Do the privacy rules allow us to display this fact to the current user
138a25f0a04SGreg Roach     *
139cbc1590aSGreg Roach     * @param int|null $access_level
140a25f0a04SGreg Roach     *
141cbc1590aSGreg Roach     * @return bool
142a25f0a04SGreg Roach     */
143*c1010edaSGreg Roach    public function canShow($access_level = null)
144*c1010edaSGreg Roach    {
1454b9ff166SGreg Roach        if ($access_level === null) {
1464b9ff166SGreg Roach            $access_level = Auth::accessLevel($this->getParent()->getTree());
1474b9ff166SGreg Roach        }
1484b9ff166SGreg Roach
149a25f0a04SGreg Roach        // Does this record have an explicit RESN?
150a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN confidential")) {
1514b9ff166SGreg Roach            return Auth::PRIV_NONE >= $access_level;
152a25f0a04SGreg Roach        }
153a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN privacy")) {
1544b9ff166SGreg Roach            return Auth::PRIV_USER >= $access_level;
155a25f0a04SGreg Roach        }
156a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN none")) {
157a25f0a04SGreg Roach            return true;
158a25f0a04SGreg Roach        }
159a25f0a04SGreg Roach
160a25f0a04SGreg Roach        // Does this record have a default RESN?
161a25f0a04SGreg Roach        $xref                    = $this->parent->getXref();
162518bbdc1SGreg Roach        $fact_privacy            = $this->parent->getTree()->getFactPrivacy();
163518bbdc1SGreg Roach        $individual_fact_privacy = $this->parent->getTree()->getIndividualFactPrivacy();
164518bbdc1SGreg Roach        if (isset($individual_fact_privacy[$xref][$this->tag])) {
165518bbdc1SGreg Roach            return $individual_fact_privacy[$xref][$this->tag] >= $access_level;
166a25f0a04SGreg Roach        }
167518bbdc1SGreg Roach        if (isset($fact_privacy[$this->tag])) {
168518bbdc1SGreg Roach            return $fact_privacy[$this->tag] >= $access_level;
169a25f0a04SGreg Roach        }
170a25f0a04SGreg Roach
171a25f0a04SGreg Roach        // No restrictions - it must be public
172a25f0a04SGreg Roach        return true;
173a25f0a04SGreg Roach    }
174a25f0a04SGreg Roach
175a25f0a04SGreg Roach    /**
176a25f0a04SGreg Roach     * Check whether this fact is protected against edit
177a25f0a04SGreg Roach     *
178cbc1590aSGreg Roach     * @return bool
179a25f0a04SGreg Roach     */
180*c1010edaSGreg Roach    public function canEdit()
181*c1010edaSGreg Roach    {
182a25f0a04SGreg Roach        // Managers can edit anything
183a25f0a04SGreg Roach        // Members cannot edit RESN, CHAN and locked records
184a25f0a04SGreg Roach        return
185a25f0a04SGreg Roach            $this->parent->canEdit() && !$this->isPendingDeletion() && (
1864b9ff166SGreg Roach                Auth::isManager($this->parent->getTree()) ||
1874b9ff166SGreg Roach                Auth::isEditor($this->parent->getTree()) && strpos($this->gedcom, "\n2 RESN locked") === false && $this->getTag() != 'RESN' && $this->getTag() != 'CHAN'
188a25f0a04SGreg Roach            );
189a25f0a04SGreg Roach    }
190a25f0a04SGreg Roach
191a25f0a04SGreg Roach    /**
192a25f0a04SGreg Roach     * The place where the event occured.
193a25f0a04SGreg Roach     *
194a25f0a04SGreg Roach     * @return Place
195a25f0a04SGreg Roach     */
196*c1010edaSGreg Roach    public function getPlace()
197*c1010edaSGreg Roach    {
198a25f0a04SGreg Roach        if ($this->place === null) {
19984caa210SGreg Roach            $this->place = new Place($this->getAttribute('PLAC'), $this->getParent()->getTree());
200a25f0a04SGreg Roach        }
201a25f0a04SGreg Roach
202a25f0a04SGreg Roach        return $this->place;
203a25f0a04SGreg Roach    }
204a25f0a04SGreg Roach
205a25f0a04SGreg Roach    /**
206a25f0a04SGreg Roach     * Get the date for this fact.
207a25f0a04SGreg Roach     * We can call this function many times, especially when sorting,
208a25f0a04SGreg Roach     * so keep a copy of the date.
209a25f0a04SGreg Roach     *
210a25f0a04SGreg Roach     * @return Date
211a25f0a04SGreg Roach     */
212*c1010edaSGreg Roach    public function getDate()
213*c1010edaSGreg Roach    {
214a25f0a04SGreg Roach        if ($this->date === null) {
215a25f0a04SGreg Roach            $this->date = new Date($this->getAttribute('DATE'));
216a25f0a04SGreg Roach        }
217a25f0a04SGreg Roach
218a25f0a04SGreg Roach        return $this->date;
219a25f0a04SGreg Roach    }
220a25f0a04SGreg Roach
221a25f0a04SGreg Roach    /**
222a25f0a04SGreg Roach     * The raw GEDCOM data for this fact
223a25f0a04SGreg Roach     *
224a25f0a04SGreg Roach     * @return string
225a25f0a04SGreg Roach     */
226*c1010edaSGreg Roach    public function getGedcom()
227*c1010edaSGreg Roach    {
228a25f0a04SGreg Roach        return $this->gedcom;
229a25f0a04SGreg Roach    }
230a25f0a04SGreg Roach
231a25f0a04SGreg Roach    /**
232a25f0a04SGreg Roach     * Get a (pseudo) primary key for this fact.
233a25f0a04SGreg Roach     *
234a25f0a04SGreg Roach     * @return string
235a25f0a04SGreg Roach     */
236*c1010edaSGreg Roach    public function getFactId()
237*c1010edaSGreg Roach    {
238a25f0a04SGreg Roach        return $this->fact_id;
239a25f0a04SGreg Roach    }
240a25f0a04SGreg Roach
241a25f0a04SGreg Roach    /**
242a25f0a04SGreg Roach     * What is the tag (type) of this fact, such as BIRT, MARR or DEAT.
243a25f0a04SGreg Roach     *
244a25f0a04SGreg Roach     * @return string
245a25f0a04SGreg Roach     */
246*c1010edaSGreg Roach    public function getTag()
247*c1010edaSGreg Roach    {
248a25f0a04SGreg Roach        return $this->tag;
249a25f0a04SGreg Roach    }
250a25f0a04SGreg Roach
251a25f0a04SGreg Roach    /**
252a25f0a04SGreg Roach     * Used to convert a real fact (e.g. BIRT) into a close-relative’s fact (e.g. _BIRT_CHIL)
253a25f0a04SGreg Roach     *
254a25f0a04SGreg Roach     * @param string $tag
255a25f0a04SGreg Roach     */
256*c1010edaSGreg Roach    public function setTag($tag)
257*c1010edaSGreg Roach    {
258a25f0a04SGreg Roach        $this->tag = $tag;
259a25f0a04SGreg Roach    }
260a25f0a04SGreg Roach
261a25f0a04SGreg Roach    /**
262a25f0a04SGreg Roach     * The Person/Family record where this Fact came from
263a25f0a04SGreg Roach     *
2646e59b7c4SGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|GedcomRecord
265a25f0a04SGreg Roach     */
266*c1010edaSGreg Roach    public function getParent()
267*c1010edaSGreg Roach    {
268a25f0a04SGreg Roach        return $this->parent;
269a25f0a04SGreg Roach    }
270a25f0a04SGreg Roach
271a25f0a04SGreg Roach    /**
272a25f0a04SGreg Roach     * Get the name of this fact type, for use as a label.
273a25f0a04SGreg Roach     *
274a25f0a04SGreg Roach     * @return string
275a25f0a04SGreg Roach     */
276*c1010edaSGreg Roach    public function getLabel()
277*c1010edaSGreg Roach    {
278a25f0a04SGreg Roach        // Custom FACT/EVEN - with a TYPE
2794e5adf68SGreg Roach        if (($this->tag === 'FACT' || $this->tag === 'EVEN') && $this->getAttribute('TYPE') !== '') {
280d53324c9SGreg Roach            return I18N::translate(e($this->getAttribute('TYPE')));
281a25f0a04SGreg Roach        }
2824e5adf68SGreg Roach
283764a01d9SGreg Roach        return GedcomTag::getLabel($this->tag, $this->parent);
284a25f0a04SGreg Roach    }
285a25f0a04SGreg Roach
286a25f0a04SGreg Roach    /**
287a25f0a04SGreg Roach     * This is a newly deleted fact, pending approval.
288a25f0a04SGreg Roach     */
289*c1010edaSGreg Roach    public function setPendingDeletion()
290*c1010edaSGreg Roach    {
291a25f0a04SGreg Roach        $this->pending_deletion = true;
292a25f0a04SGreg Roach        $this->pending_addition = false;
293a25f0a04SGreg Roach    }
294a25f0a04SGreg Roach
295a25f0a04SGreg Roach    /**
296a25f0a04SGreg Roach     * Is this a newly deleted fact, pending approval.
297a25f0a04SGreg Roach     *
298cbc1590aSGreg Roach     * @return bool
299a25f0a04SGreg Roach     */
300*c1010edaSGreg Roach    public function isPendingDeletion()
301*c1010edaSGreg Roach    {
302a25f0a04SGreg Roach        return $this->pending_deletion;
303a25f0a04SGreg Roach    }
304a25f0a04SGreg Roach
305a25f0a04SGreg Roach    /**
306a25f0a04SGreg Roach     * This is a newly added fact, pending approval.
307a25f0a04SGreg Roach     */
308*c1010edaSGreg Roach    public function setPendingAddition()
309*c1010edaSGreg Roach    {
310a25f0a04SGreg Roach        $this->pending_addition = true;
311a25f0a04SGreg Roach        $this->pending_deletion = false;
312a25f0a04SGreg Roach    }
313a25f0a04SGreg Roach
314a25f0a04SGreg Roach    /**
315a25f0a04SGreg Roach     * Is this a newly added fact, pending approval.
316a25f0a04SGreg Roach     *
317cbc1590aSGreg Roach     * @return bool
318a25f0a04SGreg Roach     */
319*c1010edaSGreg Roach    public function isPendingAddition()
320*c1010edaSGreg Roach    {
321a25f0a04SGreg Roach        return $this->pending_addition;
322a25f0a04SGreg Roach    }
323a25f0a04SGreg Roach
324a25f0a04SGreg Roach    /**
325a25f0a04SGreg Roach     * Source citations linked to this fact
326a25f0a04SGreg Roach     *
327a25f0a04SGreg Roach     * @return string[]
328a25f0a04SGreg Roach     */
329*c1010edaSGreg Roach    public function getCitations()
330*c1010edaSGreg Roach    {
331a25f0a04SGreg Roach        preg_match_all('/\n(2 SOUR @(' . WT_REGEX_XREF . ')@(?:\n[3-9] .*)*)/', $this->getGedcom(), $matches, PREG_SET_ORDER);
33213abd6f3SGreg Roach        $citations = [];
333a25f0a04SGreg Roach        foreach ($matches as $match) {
33424ec66ceSGreg Roach            $source = Source::getInstance($match[2], $this->getParent()->getTree());
335a25f0a04SGreg Roach            if ($source->canShow()) {
336a25f0a04SGreg Roach                $citations[] = $match[1];
337a25f0a04SGreg Roach            }
338a25f0a04SGreg Roach        }
339a25f0a04SGreg Roach
340a25f0a04SGreg Roach        return $citations;
341a25f0a04SGreg Roach    }
342a25f0a04SGreg Roach
343a25f0a04SGreg Roach    /**
344a25f0a04SGreg Roach     * Notes (inline and objects) linked to this fact
345a25f0a04SGreg Roach     *
346a25f0a04SGreg Roach     * @return string[]|Note[]
347a25f0a04SGreg Roach     */
348*c1010edaSGreg Roach    public function getNotes()
349*c1010edaSGreg Roach    {
35013abd6f3SGreg Roach        $notes = [];
351a25f0a04SGreg Roach        preg_match_all('/\n2 NOTE ?(.*(?:\n3.*)*)/', $this->getGedcom(), $matches);
352a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
353a25f0a04SGreg Roach            $note = preg_replace("/\n3 CONT ?/", "\n", $match);
354a25f0a04SGreg Roach            if (preg_match('/@(' . WT_REGEX_XREF . ')@/', $note, $nmatch)) {
35524ec66ceSGreg Roach                $note = Note::getInstance($nmatch[1], $this->getParent()->getTree());
356a25f0a04SGreg Roach                if ($note && $note->canShow()) {
357a25f0a04SGreg Roach                    // A note object
358a25f0a04SGreg Roach                    $notes[] = $note;
359a25f0a04SGreg Roach                }
360a25f0a04SGreg Roach            } else {
361a25f0a04SGreg Roach                // An inline note
362a25f0a04SGreg Roach                $notes[] = $note;
363a25f0a04SGreg Roach            }
364a25f0a04SGreg Roach        }
365a25f0a04SGreg Roach
366a25f0a04SGreg Roach        return $notes;
367a25f0a04SGreg Roach    }
368a25f0a04SGreg Roach
369a25f0a04SGreg Roach    /**
370a25f0a04SGreg Roach     * Media objects linked to this fact
371a25f0a04SGreg Roach     *
372a25f0a04SGreg Roach     * @return Media[]
373a25f0a04SGreg Roach     */
374*c1010edaSGreg Roach    public function getMedia()
375*c1010edaSGreg Roach    {
37613abd6f3SGreg Roach        $media = [];
377a25f0a04SGreg Roach        preg_match_all('/\n2 OBJE @(' . WT_REGEX_XREF . ')@/', $this->getGedcom(), $matches);
378a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
37924ec66ceSGreg Roach            $obje = Media::getInstance($match, $this->getParent()->getTree());
380a25f0a04SGreg Roach            if ($obje->canShow()) {
381a25f0a04SGreg Roach                $media[] = $obje;
382a25f0a04SGreg Roach            }
383a25f0a04SGreg Roach        }
384a25f0a04SGreg Roach
385a25f0a04SGreg Roach        return $media;
386a25f0a04SGreg Roach    }
387a25f0a04SGreg Roach
388a25f0a04SGreg Roach    /**
389a25f0a04SGreg Roach     * A one-line summary of the fact - for charts, etc.
390a25f0a04SGreg Roach     *
391a25f0a04SGreg Roach     * @return string
392a25f0a04SGreg Roach     */
393*c1010edaSGreg Roach    public function summary()
394*c1010edaSGreg Roach    {
39513abd6f3SGreg Roach        $attributes = [];
396a25f0a04SGreg Roach        $target     = $this->getTarget();
397a25f0a04SGreg Roach        if ($target) {
398a25f0a04SGreg Roach            $attributes[] = $target->getFullName();
399a25f0a04SGreg Roach        } else {
400726d7b07SGreg Roach            // Fact value
401a25f0a04SGreg Roach            $value = $this->getValue();
402726d7b07SGreg Roach            if ($value !== '' && $value !== 'Y') {
403d53324c9SGreg Roach                $attributes[] = '<span dir="auto">' . e($value) . '</span>';
404a25f0a04SGreg Roach            }
405726d7b07SGreg Roach            // Fact date
406a25f0a04SGreg Roach            $date = $this->getDate();
407726d7b07SGreg Roach            if ($date->isOK()) {
408af459ec4SGreg Roach                if (in_array($this->getTag(), explode('|', WT_EVENTS_BIRT)) && $this->getParent() instanceof Individual && $this->getParent()->getTree()->getPreference('SHOW_PARENTS_AGE')) {
4093d7a8a4cSGreg Roach                    $attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->getParent(), $date);
410a25f0a04SGreg Roach                } else {
411a25f0a04SGreg Roach                    $attributes[] = $date->display();
412a25f0a04SGreg Roach                }
413726d7b07SGreg Roach            }
414726d7b07SGreg Roach            // Fact place
415726d7b07SGreg Roach            if (!$this->getPlace()->isEmpty()) {
416726d7b07SGreg Roach                $attributes[] = $this->getPlace()->getShortName();
417a25f0a04SGreg Roach            }
418a25f0a04SGreg Roach        }
4192d8f08abSGreg Roach
4202d8f08abSGreg Roach        $class = 'fact_' . $this->getTag();
421a25f0a04SGreg Roach        if ($this->isPendingAddition()) {
4222d8f08abSGreg Roach            $class .= ' new';
423a25f0a04SGreg Roach        } elseif ($this->isPendingDeletion()) {
4242d8f08abSGreg Roach            $class .= ' old';
425a25f0a04SGreg Roach        }
4262d8f08abSGreg Roach
4272d8f08abSGreg Roach        return
4282d8f08abSGreg Roach            '<div class="' . $class . '">' .
4292d8f08abSGreg Roach            /* I18N: a label/value pair, such as “Occupation: Farmer”. Some languages may need to change the punctuation. */
4302d8f08abSGreg Roach            I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $this->getLabel(), implode(' — ', $attributes)) .
4312d8f08abSGreg Roach            '</div>';
432a25f0a04SGreg Roach    }
433a25f0a04SGreg Roach
434a25f0a04SGreg Roach    /**
435a25f0a04SGreg Roach     * Static Helper functions to sort events
436a25f0a04SGreg Roach     *
437a25f0a04SGreg Roach     * @param Fact $a Fact one
438a25f0a04SGreg Roach     * @param Fact $b Fact two
439a25f0a04SGreg Roach     *
440cbc1590aSGreg Roach     * @return int
441a25f0a04SGreg Roach     */
442*c1010edaSGreg Roach    public static function compareDate(Fact $a, Fact $b)
443*c1010edaSGreg Roach    {
444a25f0a04SGreg Roach        if ($a->getDate()->isOK() && $b->getDate()->isOK()) {
445a25f0a04SGreg Roach            // If both events have dates, compare by date
446f5b60decSGreg Roach            $ret = Date::compare($a->getDate(), $b->getDate());
447a25f0a04SGreg Roach
448a25f0a04SGreg Roach            if ($ret == 0) {
449a25f0a04SGreg Roach                // If dates are the same, compare by fact type
450a25f0a04SGreg Roach                $ret = self::compareType($a, $b);
451a25f0a04SGreg Roach
452a25f0a04SGreg Roach                // If the fact type is also the same, retain the initial order
453a25f0a04SGreg Roach                if ($ret == 0) {
454a25f0a04SGreg Roach                    $ret = $a->sortOrder - $b->sortOrder;
455a25f0a04SGreg Roach                }
456a25f0a04SGreg Roach            }
457a25f0a04SGreg Roach
458a25f0a04SGreg Roach            return $ret;
459a25f0a04SGreg Roach        } else {
460a25f0a04SGreg Roach            // One or both events have no date - retain the initial order
461a25f0a04SGreg Roach            return $a->sortOrder - $b->sortOrder;
462a25f0a04SGreg Roach        }
463a25f0a04SGreg Roach    }
464a25f0a04SGreg Roach
465a25f0a04SGreg Roach    /**
466a25f0a04SGreg Roach     * Static method to compare two events by their type.
467a25f0a04SGreg Roach     *
468a25f0a04SGreg Roach     * @param Fact $a Fact one
469a25f0a04SGreg Roach     * @param Fact $b Fact two
470a25f0a04SGreg Roach     *
471cbc1590aSGreg Roach     * @return int
472a25f0a04SGreg Roach     */
473*c1010edaSGreg Roach    public static function compareType(Fact $a, Fact $b)
474*c1010edaSGreg Roach    {
475a25f0a04SGreg Roach        global $factsort;
476a25f0a04SGreg Roach
477a25f0a04SGreg Roach        if (empty($factsort)) {
478a25f0a04SGreg Roach            $factsort = array_flip(
47913abd6f3SGreg Roach                [
480a25f0a04SGreg Roach                    'BIRT',
481a25f0a04SGreg Roach                    '_HNM',
482*c1010edaSGreg Roach                    'ALIA',
483*c1010edaSGreg Roach                    '_AKA',
484*c1010edaSGreg Roach                    '_AKAN',
485*c1010edaSGreg Roach                    'ADOP',
486*c1010edaSGreg Roach                    '_ADPF',
487*c1010edaSGreg Roach                    '_ADPF',
488a25f0a04SGreg Roach                    '_BRTM',
489*c1010edaSGreg Roach                    'CHR',
490*c1010edaSGreg Roach                    'BAPM',
491a25f0a04SGreg Roach                    'FCOM',
492a25f0a04SGreg Roach                    'CONF',
493*c1010edaSGreg Roach                    'BARM',
494*c1010edaSGreg Roach                    'BASM',
495a25f0a04SGreg Roach                    'EDUC',
496a25f0a04SGreg Roach                    'GRAD',
497a25f0a04SGreg Roach                    '_DEG',
498*c1010edaSGreg Roach                    'EMIG',
499*c1010edaSGreg Roach                    'IMMI',
500a25f0a04SGreg Roach                    'NATU',
501*c1010edaSGreg Roach                    '_MILI',
502*c1010edaSGreg Roach                    '_MILT',
503a25f0a04SGreg Roach                    'ENGA',
504*c1010edaSGreg Roach                    'MARB',
505*c1010edaSGreg Roach                    'MARC',
506*c1010edaSGreg Roach                    'MARL',
507*c1010edaSGreg Roach                    '_MARI',
508*c1010edaSGreg Roach                    '_MBON',
509*c1010edaSGreg Roach                    'MARR',
510*c1010edaSGreg Roach                    'MARR_CIVIL',
511*c1010edaSGreg Roach                    'MARR_RELIGIOUS',
512*c1010edaSGreg Roach                    'MARR_PARTNERS',
513*c1010edaSGreg Roach                    'MARR_UNKNOWN',
514*c1010edaSGreg Roach                    '_COML',
515a25f0a04SGreg Roach                    '_STAT',
516a25f0a04SGreg Roach                    '_SEPR',
517a25f0a04SGreg Roach                    'DIVF',
518a25f0a04SGreg Roach                    'MARS',
519a25f0a04SGreg Roach                    '_BIRT_CHIL',
520*c1010edaSGreg Roach                    'DIV',
521*c1010edaSGreg Roach                    'ANUL',
522*c1010edaSGreg Roach                    '_BIRT_',
523*c1010edaSGreg Roach                    '_MARR_',
524*c1010edaSGreg Roach                    '_DEAT_',
525*c1010edaSGreg Roach                    '_BURI_',
526a25f0a04SGreg Roach                    'CENS',
527a25f0a04SGreg Roach                    'OCCU',
528a25f0a04SGreg Roach                    'RESI',
529a25f0a04SGreg Roach                    'PROP',
530a25f0a04SGreg Roach                    'CHRA',
531a25f0a04SGreg Roach                    'RETI',
532*c1010edaSGreg Roach                    'FACT',
533*c1010edaSGreg Roach                    'EVEN',
534*c1010edaSGreg Roach                    '_NMR',
535*c1010edaSGreg Roach                    '_NMAR',
536*c1010edaSGreg Roach                    'NMR',
537a25f0a04SGreg Roach                    'NCHI',
538a25f0a04SGreg Roach                    'WILL',
539a25f0a04SGreg Roach                    '_HOL',
540a25f0a04SGreg Roach                    '_????_',
541a25f0a04SGreg Roach                    'DEAT',
542*c1010edaSGreg Roach                    '_FNRL',
543*c1010edaSGreg Roach                    'CREM',
544*c1010edaSGreg Roach                    'BURI',
545*c1010edaSGreg Roach                    '_INTE',
546a25f0a04SGreg Roach                    '_YART',
547a25f0a04SGreg Roach                    '_NLIV',
548a25f0a04SGreg Roach                    'PROB',
549a25f0a04SGreg Roach                    'TITL',
550a25f0a04SGreg Roach                    'COMM',
551a25f0a04SGreg Roach                    'NATI',
552a25f0a04SGreg Roach                    'CITN',
553a25f0a04SGreg Roach                    'CAST',
554a25f0a04SGreg Roach                    'RELI',
555*c1010edaSGreg Roach                    'SSN',
556*c1010edaSGreg Roach                    'IDNO',
557a25f0a04SGreg Roach                    'TEMP',
558*c1010edaSGreg Roach                    'SLGC',
559*c1010edaSGreg Roach                    'BAPL',
560*c1010edaSGreg Roach                    'CONL',
561*c1010edaSGreg Roach                    'ENDL',
562*c1010edaSGreg Roach                    'SLGS',
563*c1010edaSGreg Roach                    'ADDR',
564*c1010edaSGreg Roach                    'PHON',
565*c1010edaSGreg Roach                    'EMAIL',
566*c1010edaSGreg Roach                    '_EMAIL',
567*c1010edaSGreg Roach                    'EMAL',
568*c1010edaSGreg Roach                    'FAX',
569*c1010edaSGreg Roach                    'WWW',
570*c1010edaSGreg Roach                    'URL',
571*c1010edaSGreg Roach                    '_URL',
572*c1010edaSGreg Roach                    'AFN',
573*c1010edaSGreg Roach                    'REFN',
574*c1010edaSGreg Roach                    '_PRMN',
575*c1010edaSGreg Roach                    'REF',
576*c1010edaSGreg Roach                    'RIN',
577*c1010edaSGreg Roach                    '_UID',
578*c1010edaSGreg Roach                    'OBJE',
579*c1010edaSGreg Roach                    'NOTE',
580*c1010edaSGreg Roach                    'SOUR',
581*c1010edaSGreg Roach                    'CHAN',
582*c1010edaSGreg Roach                    '_TODO',
58313abd6f3SGreg Roach                ]
584a25f0a04SGreg Roach            );
585a25f0a04SGreg Roach        }
586a25f0a04SGreg Roach
587a25f0a04SGreg Roach        // Facts from same families stay grouped together
588a25f0a04SGreg Roach        // Keep MARR and DIV from the same families from mixing with events from other FAMs
589a25f0a04SGreg Roach        // Use the original order in which the facts were added
590a25f0a04SGreg Roach        if ($a->parent instanceof Family && $b->parent instanceof Family && $a->parent !== $b->parent) {
591a25f0a04SGreg Roach            return $a->sortOrder - $b->sortOrder;
592a25f0a04SGreg Roach        }
593a25f0a04SGreg Roach
594a25f0a04SGreg Roach        $atag = $a->getTag();
595a25f0a04SGreg Roach        $btag = $b->getTag();
596a25f0a04SGreg Roach
597a25f0a04SGreg Roach        // Events not in the above list get mapped onto one that is.
598a25f0a04SGreg Roach        if (!array_key_exists($atag, $factsort)) {
599a25f0a04SGreg Roach            if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
600a25f0a04SGreg Roach                $atag = $match[1];
601a25f0a04SGreg Roach            } else {
6027a6ee1acSGreg Roach                $atag = '_????_';
603a25f0a04SGreg Roach            }
604a25f0a04SGreg Roach        }
605a25f0a04SGreg Roach
606a25f0a04SGreg Roach        if (!array_key_exists($btag, $factsort)) {
607a25f0a04SGreg Roach            if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
608a25f0a04SGreg Roach                $btag = $match[1];
609a25f0a04SGreg Roach            } else {
6107a6ee1acSGreg Roach                $btag = '_????_';
611a25f0a04SGreg Roach            }
612a25f0a04SGreg Roach        }
613a25f0a04SGreg Roach
614a25f0a04SGreg Roach        // - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
615a25f0a04SGreg Roach        // - Treat dated after BURI facts as BURI instead
616baacc364SGreg Roach        if ($a->getAttribute('DATE') !== '' && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
617a25f0a04SGreg Roach            $atag = 'BURI';
618a25f0a04SGreg Roach        }
619a25f0a04SGreg Roach
620baacc364SGreg Roach        if ($b->getAttribute('DATE') !== '' && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
621a25f0a04SGreg Roach            $btag = 'BURI';
622a25f0a04SGreg Roach        }
623a25f0a04SGreg Roach
624a25f0a04SGreg Roach        $ret = $factsort[$atag] - $factsort[$btag];
625a25f0a04SGreg Roach
626a25f0a04SGreg Roach        // If facts are the same then put dated facts before non-dated facts
627a25f0a04SGreg Roach        if ($ret == 0) {
628baacc364SGreg Roach            if ($a->getAttribute('DATE') !== '' && $b->getAttribute('DATE') === '') {
629a25f0a04SGreg Roach                return -1;
630a25f0a04SGreg Roach            }
631a25f0a04SGreg Roach
632baacc364SGreg Roach            if ($b->getAttribute('DATE') !== '' && $a->getAttribute('DATE') === '') {
633a25f0a04SGreg Roach                return 1;
634a25f0a04SGreg Roach            }
635a25f0a04SGreg Roach
636a25f0a04SGreg Roach            // If no sorting preference, then keep original ordering
637a25f0a04SGreg Roach            $ret = $a->sortOrder - $b->sortOrder;
638a25f0a04SGreg Roach        }
639a25f0a04SGreg Roach
640a25f0a04SGreg Roach        return $ret;
641a25f0a04SGreg Roach    }
642a25f0a04SGreg Roach
643a25f0a04SGreg Roach    /**
644a25f0a04SGreg Roach     * Allow native PHP functions such as array_unique() to work with objects
645a25f0a04SGreg Roach     *
646a25f0a04SGreg Roach     * @return string
647a25f0a04SGreg Roach     */
648*c1010edaSGreg Roach    public function __toString()
649*c1010edaSGreg Roach    {
650a25f0a04SGreg Roach        return $this->fact_id . '@' . $this->parent->getXref();
651a25f0a04SGreg Roach    }
652a25f0a04SGreg Roach}
653