xref: /webtrees/app/Fact.php (revision bbe4546f1bdac54a85ad695a56563c98fca8fccb)
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 */
24c1010edaSGreg Roachclass Fact
25c1010edaSGreg Roach{
26*bbe4546fSGreg Roach    const FACT_ORDER = [
27*bbe4546fSGreg Roach        'BIRT',
28*bbe4546fSGreg Roach        '_HNM',
29*bbe4546fSGreg Roach        'ALIA',
30*bbe4546fSGreg Roach        '_AKA',
31*bbe4546fSGreg Roach        '_AKAN',
32*bbe4546fSGreg Roach        'ADOP',
33*bbe4546fSGreg Roach        '_ADPF',
34*bbe4546fSGreg Roach        '_ADPF',
35*bbe4546fSGreg Roach        '_BRTM',
36*bbe4546fSGreg Roach        'CHR',
37*bbe4546fSGreg Roach        'BAPM',
38*bbe4546fSGreg Roach        'FCOM',
39*bbe4546fSGreg Roach        'CONF',
40*bbe4546fSGreg Roach        'BARM',
41*bbe4546fSGreg Roach        'BASM',
42*bbe4546fSGreg Roach        'EDUC',
43*bbe4546fSGreg Roach        'GRAD',
44*bbe4546fSGreg Roach        '_DEG',
45*bbe4546fSGreg Roach        'EMIG',
46*bbe4546fSGreg Roach        'IMMI',
47*bbe4546fSGreg Roach        'NATU',
48*bbe4546fSGreg Roach        '_MILI',
49*bbe4546fSGreg Roach        '_MILT',
50*bbe4546fSGreg Roach        'ENGA',
51*bbe4546fSGreg Roach        'MARB',
52*bbe4546fSGreg Roach        'MARC',
53*bbe4546fSGreg Roach        'MARL',
54*bbe4546fSGreg Roach        '_MARI',
55*bbe4546fSGreg Roach        '_MBON',
56*bbe4546fSGreg Roach        'MARR',
57*bbe4546fSGreg Roach        'MARR_CIVIL',
58*bbe4546fSGreg Roach        'MARR_RELIGIOUS',
59*bbe4546fSGreg Roach        'MARR_PARTNERS',
60*bbe4546fSGreg Roach        'MARR_UNKNOWN',
61*bbe4546fSGreg Roach        '_COML',
62*bbe4546fSGreg Roach        '_STAT',
63*bbe4546fSGreg Roach        '_SEPR',
64*bbe4546fSGreg Roach        'DIVF',
65*bbe4546fSGreg Roach        'MARS',
66*bbe4546fSGreg Roach        '_BIRT_CHIL',
67*bbe4546fSGreg Roach        'DIV',
68*bbe4546fSGreg Roach        'ANUL',
69*bbe4546fSGreg Roach        '_BIRT_',
70*bbe4546fSGreg Roach        '_MARR_',
71*bbe4546fSGreg Roach        '_DEAT_',
72*bbe4546fSGreg Roach        '_BURI_',
73*bbe4546fSGreg Roach        'CENS',
74*bbe4546fSGreg Roach        'OCCU',
75*bbe4546fSGreg Roach        'RESI',
76*bbe4546fSGreg Roach        'PROP',
77*bbe4546fSGreg Roach        'CHRA',
78*bbe4546fSGreg Roach        'RETI',
79*bbe4546fSGreg Roach        'FACT',
80*bbe4546fSGreg Roach        'EVEN',
81*bbe4546fSGreg Roach        '_NMR',
82*bbe4546fSGreg Roach        '_NMAR',
83*bbe4546fSGreg Roach        'NMR',
84*bbe4546fSGreg Roach        'NCHI',
85*bbe4546fSGreg Roach        'WILL',
86*bbe4546fSGreg Roach        '_HOL',
87*bbe4546fSGreg Roach        '_????_',
88*bbe4546fSGreg Roach        'DEAT',
89*bbe4546fSGreg Roach        '_FNRL',
90*bbe4546fSGreg Roach        'CREM',
91*bbe4546fSGreg Roach        'BURI',
92*bbe4546fSGreg Roach        '_INTE',
93*bbe4546fSGreg Roach        '_YART',
94*bbe4546fSGreg Roach        '_NLIV',
95*bbe4546fSGreg Roach        'PROB',
96*bbe4546fSGreg Roach        'TITL',
97*bbe4546fSGreg Roach        'COMM',
98*bbe4546fSGreg Roach        'NATI',
99*bbe4546fSGreg Roach        'CITN',
100*bbe4546fSGreg Roach        'CAST',
101*bbe4546fSGreg Roach        'RELI',
102*bbe4546fSGreg Roach        'SSN',
103*bbe4546fSGreg Roach        'IDNO',
104*bbe4546fSGreg Roach        'TEMP',
105*bbe4546fSGreg Roach        'SLGC',
106*bbe4546fSGreg Roach        'BAPL',
107*bbe4546fSGreg Roach        'CONL',
108*bbe4546fSGreg Roach        'ENDL',
109*bbe4546fSGreg Roach        'SLGS',
110*bbe4546fSGreg Roach        'ADDR',
111*bbe4546fSGreg Roach        'PHON',
112*bbe4546fSGreg Roach        'EMAIL',
113*bbe4546fSGreg Roach        '_EMAIL',
114*bbe4546fSGreg Roach        'EMAL',
115*bbe4546fSGreg Roach        'FAX',
116*bbe4546fSGreg Roach        'WWW',
117*bbe4546fSGreg Roach        'URL',
118*bbe4546fSGreg Roach        '_URL',
119*bbe4546fSGreg Roach        'AFN',
120*bbe4546fSGreg Roach        'REFN',
121*bbe4546fSGreg Roach        '_PRMN',
122*bbe4546fSGreg Roach        'REF',
123*bbe4546fSGreg Roach        'RIN',
124*bbe4546fSGreg Roach        '_UID',
125*bbe4546fSGreg Roach        'OBJE',
126*bbe4546fSGreg Roach        'NOTE',
127*bbe4546fSGreg Roach        'SOUR',
128*bbe4546fSGreg Roach        'CHAN',
129*bbe4546fSGreg Roach        '_TODO',
130*bbe4546fSGreg Roach    ];
131*bbe4546fSGreg Roach
132a25f0a04SGreg Roach    /** @var string Unique identifier for this fact (currently implemented as a hash of the raw data). */
133a25f0a04SGreg Roach    private $fact_id;
134a25f0a04SGreg Roach
135a25f0a04SGreg Roach    /** @var GedcomRecord The GEDCOM record from which this fact is taken */
136a25f0a04SGreg Roach    private $parent;
137a25f0a04SGreg Roach
138a25f0a04SGreg Roach    /** @var string The raw GEDCOM data for this fact */
139a25f0a04SGreg Roach    private $gedcom;
140a25f0a04SGreg Roach
141a25f0a04SGreg Roach    /** @var string The GEDCOM tag for this record */
142a25f0a04SGreg Roach    private $tag;
143a25f0a04SGreg Roach
144cbc1590aSGreg Roach    /** @var bool Is this a recently deleted fact, pending approval? */
145a25f0a04SGreg Roach    private $pending_deletion = false;
146a25f0a04SGreg Roach
147cbc1590aSGreg Roach    /** @var bool Is this a recently added fact, pending approval? */
148a25f0a04SGreg Roach    private $pending_addition = false;
149a25f0a04SGreg Roach
150a25f0a04SGreg Roach    /** @var Date The date of this fact, from the “2 DATE …” attribute */
151a25f0a04SGreg Roach    private $date;
152a25f0a04SGreg Roach
153a25f0a04SGreg Roach    /** @var Place The place of this fact, from the “2 PLAC …” attribute */
154a25f0a04SGreg Roach    private $place;
155a25f0a04SGreg Roach
1563d7a8a4cSGreg Roach    /** @var int Temporary(!) variable Used by Functions::sortFacts() */
157a25f0a04SGreg Roach    public $sortOrder;
158a25f0a04SGreg Roach
159a25f0a04SGreg Roach    /**
160a25f0a04SGreg Roach     * Create an event object from a gedcom fragment.
161a25f0a04SGreg Roach     * We need the parent object (to check privacy) and a (pseudo) fact ID to
162a25f0a04SGreg Roach     * identify the fact within the record.
163a25f0a04SGreg Roach     *
164a25f0a04SGreg Roach     * @param string       $gedcom
165a25f0a04SGreg Roach     * @param GedcomRecord $parent
166a25f0a04SGreg Roach     * @param string       $fact_id
167a25f0a04SGreg Roach     *
1688f5f5da8SGreg Roach     * @throws InvalidArgumentException
169a25f0a04SGreg Roach     */
170c1010edaSGreg Roach    public function __construct($gedcom, GedcomRecord $parent, $fact_id)
171c1010edaSGreg Roach    {
172a25f0a04SGreg Roach        if (preg_match('/^1 (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
173a25f0a04SGreg Roach            $this->gedcom  = $gedcom;
174a25f0a04SGreg Roach            $this->parent  = $parent;
175a25f0a04SGreg Roach            $this->fact_id = $fact_id;
176a25f0a04SGreg Roach            $this->tag     = $match[1];
177a25f0a04SGreg Roach        } else {
1788f5f5da8SGreg Roach            throw new InvalidArgumentException('Invalid GEDCOM data passed to Fact::_construct(' . $gedcom . ')');
179a25f0a04SGreg Roach        }
180a25f0a04SGreg Roach    }
181a25f0a04SGreg Roach
182a25f0a04SGreg Roach    /**
183a25f0a04SGreg Roach     * Get the value of level 1 data in the fact
184a25f0a04SGreg Roach     * Allow for multi-line values
185a25f0a04SGreg Roach     *
186baacc364SGreg Roach     * @return string
187a25f0a04SGreg Roach     */
188c1010edaSGreg Roach    public function getValue()
189c1010edaSGreg Roach    {
190a25f0a04SGreg Roach        if (preg_match('/^1 (?:' . $this->tag . ') ?(.*(?:(?:\n2 CONT ?.*)*))/', $this->gedcom, $match)) {
191a25f0a04SGreg Roach            return preg_replace("/\n2 CONT ?/", "\n", $match[1]);
192a25f0a04SGreg Roach        } else {
193baacc364SGreg Roach            return '';
194a25f0a04SGreg Roach        }
195a25f0a04SGreg Roach    }
196a25f0a04SGreg Roach
197a25f0a04SGreg Roach    /**
198a25f0a04SGreg Roach     * Get the record to which this fact links
199a25f0a04SGreg Roach     *
200805a90eaSGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|null
201a25f0a04SGreg Roach     */
202c1010edaSGreg Roach    public function getTarget()
203c1010edaSGreg Roach    {
204a25f0a04SGreg Roach        $xref = trim($this->getValue(), '@');
205a25f0a04SGreg Roach        switch ($this->tag) {
206a25f0a04SGreg Roach            case 'FAMC':
207a25f0a04SGreg Roach            case 'FAMS':
20824ec66ceSGreg Roach                return Family::getInstance($xref, $this->getParent()->getTree());
209a25f0a04SGreg Roach            case 'HUSB':
210a25f0a04SGreg Roach            case 'WIFE':
211a25f0a04SGreg Roach            case 'CHIL':
21224ec66ceSGreg Roach                return Individual::getInstance($xref, $this->getParent()->getTree());
213a25f0a04SGreg Roach            case 'SOUR':
21424ec66ceSGreg Roach                return Source::getInstance($xref, $this->getParent()->getTree());
215a25f0a04SGreg Roach            case 'OBJE':
21624ec66ceSGreg Roach                return Media::getInstance($xref, $this->getParent()->getTree());
217a25f0a04SGreg Roach            case 'REPO':
21824ec66ceSGreg Roach                return Repository::getInstance($xref, $this->getParent()->getTree());
219a25f0a04SGreg Roach            case 'NOTE':
22024ec66ceSGreg Roach                return Note::getInstance($xref, $this->getParent()->getTree());
221a25f0a04SGreg Roach            default:
22224ec66ceSGreg Roach                return GedcomRecord::getInstance($xref, $this->getParent()->getTree());
223a25f0a04SGreg Roach        }
224a25f0a04SGreg Roach    }
225a25f0a04SGreg Roach
226a25f0a04SGreg Roach    /**
227a25f0a04SGreg Roach     * Get the value of level 2 data in the fact
228a25f0a04SGreg Roach     *
229a25f0a04SGreg Roach     * @param string $tag
230a25f0a04SGreg Roach     *
231baacc364SGreg Roach     * @return string
232a25f0a04SGreg Roach     */
233c1010edaSGreg Roach    public function getAttribute($tag)
234c1010edaSGreg Roach    {
235a25f0a04SGreg Roach        if (preg_match('/\n2 (?:' . $tag . ') ?(.*(?:(?:\n3 CONT ?.*)*)*)/', $this->gedcom, $match)) {
236a25f0a04SGreg Roach            return preg_replace("/\n3 CONT ?/", "\n", $match[1]);
237a25f0a04SGreg Roach        } else {
238baacc364SGreg Roach            return '';
239a25f0a04SGreg Roach        }
240a25f0a04SGreg Roach    }
241a25f0a04SGreg Roach
242a25f0a04SGreg Roach    /**
243a25f0a04SGreg Roach     * Do the privacy rules allow us to display this fact to the current user
244a25f0a04SGreg Roach     *
245cbc1590aSGreg Roach     * @param int|null $access_level
246a25f0a04SGreg Roach     *
247cbc1590aSGreg Roach     * @return bool
248a25f0a04SGreg Roach     */
249c1010edaSGreg Roach    public function canShow($access_level = null)
250c1010edaSGreg Roach    {
2514b9ff166SGreg Roach        if ($access_level === null) {
2524b9ff166SGreg Roach            $access_level = Auth::accessLevel($this->getParent()->getTree());
2534b9ff166SGreg Roach        }
2544b9ff166SGreg Roach
255a25f0a04SGreg Roach        // Does this record have an explicit RESN?
256a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN confidential")) {
2574b9ff166SGreg Roach            return Auth::PRIV_NONE >= $access_level;
258a25f0a04SGreg Roach        }
259a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN privacy")) {
2604b9ff166SGreg Roach            return Auth::PRIV_USER >= $access_level;
261a25f0a04SGreg Roach        }
262a25f0a04SGreg Roach        if (strpos($this->gedcom, "\n2 RESN none")) {
263a25f0a04SGreg Roach            return true;
264a25f0a04SGreg Roach        }
265a25f0a04SGreg Roach
266a25f0a04SGreg Roach        // Does this record have a default RESN?
267a25f0a04SGreg Roach        $xref                    = $this->parent->getXref();
268518bbdc1SGreg Roach        $fact_privacy            = $this->parent->getTree()->getFactPrivacy();
269518bbdc1SGreg Roach        $individual_fact_privacy = $this->parent->getTree()->getIndividualFactPrivacy();
270518bbdc1SGreg Roach        if (isset($individual_fact_privacy[$xref][$this->tag])) {
271518bbdc1SGreg Roach            return $individual_fact_privacy[$xref][$this->tag] >= $access_level;
272a25f0a04SGreg Roach        }
273518bbdc1SGreg Roach        if (isset($fact_privacy[$this->tag])) {
274518bbdc1SGreg Roach            return $fact_privacy[$this->tag] >= $access_level;
275a25f0a04SGreg Roach        }
276a25f0a04SGreg Roach
277a25f0a04SGreg Roach        // No restrictions - it must be public
278a25f0a04SGreg Roach        return true;
279a25f0a04SGreg Roach    }
280a25f0a04SGreg Roach
281a25f0a04SGreg Roach    /**
282a25f0a04SGreg Roach     * Check whether this fact is protected against edit
283a25f0a04SGreg Roach     *
284cbc1590aSGreg Roach     * @return bool
285a25f0a04SGreg Roach     */
286c1010edaSGreg Roach    public function canEdit()
287c1010edaSGreg Roach    {
288a25f0a04SGreg Roach        // Managers can edit anything
289a25f0a04SGreg Roach        // Members cannot edit RESN, CHAN and locked records
290a25f0a04SGreg Roach        return
291a25f0a04SGreg Roach            $this->parent->canEdit() && !$this->isPendingDeletion() && (
2924b9ff166SGreg Roach                Auth::isManager($this->parent->getTree()) ||
2934b9ff166SGreg Roach                Auth::isEditor($this->parent->getTree()) && strpos($this->gedcom, "\n2 RESN locked") === false && $this->getTag() != 'RESN' && $this->getTag() != 'CHAN'
294a25f0a04SGreg Roach            );
295a25f0a04SGreg Roach    }
296a25f0a04SGreg Roach
297a25f0a04SGreg Roach    /**
298a25f0a04SGreg Roach     * The place where the event occured.
299a25f0a04SGreg Roach     *
300a25f0a04SGreg Roach     * @return Place
301a25f0a04SGreg Roach     */
302c1010edaSGreg Roach    public function getPlace()
303c1010edaSGreg Roach    {
304a25f0a04SGreg Roach        if ($this->place === null) {
30584caa210SGreg Roach            $this->place = new Place($this->getAttribute('PLAC'), $this->getParent()->getTree());
306a25f0a04SGreg Roach        }
307a25f0a04SGreg Roach
308a25f0a04SGreg Roach        return $this->place;
309a25f0a04SGreg Roach    }
310a25f0a04SGreg Roach
311a25f0a04SGreg Roach    /**
312a25f0a04SGreg Roach     * Get the date for this fact.
313a25f0a04SGreg Roach     * We can call this function many times, especially when sorting,
314a25f0a04SGreg Roach     * so keep a copy of the date.
315a25f0a04SGreg Roach     *
316a25f0a04SGreg Roach     * @return Date
317a25f0a04SGreg Roach     */
318c1010edaSGreg Roach    public function getDate()
319c1010edaSGreg Roach    {
320a25f0a04SGreg Roach        if ($this->date === null) {
321a25f0a04SGreg Roach            $this->date = new Date($this->getAttribute('DATE'));
322a25f0a04SGreg Roach        }
323a25f0a04SGreg Roach
324a25f0a04SGreg Roach        return $this->date;
325a25f0a04SGreg Roach    }
326a25f0a04SGreg Roach
327a25f0a04SGreg Roach    /**
328a25f0a04SGreg Roach     * The raw GEDCOM data for this fact
329a25f0a04SGreg Roach     *
330a25f0a04SGreg Roach     * @return string
331a25f0a04SGreg Roach     */
332c1010edaSGreg Roach    public function getGedcom()
333c1010edaSGreg Roach    {
334a25f0a04SGreg Roach        return $this->gedcom;
335a25f0a04SGreg Roach    }
336a25f0a04SGreg Roach
337a25f0a04SGreg Roach    /**
338a25f0a04SGreg Roach     * Get a (pseudo) primary key for this fact.
339a25f0a04SGreg Roach     *
340a25f0a04SGreg Roach     * @return string
341a25f0a04SGreg Roach     */
342c1010edaSGreg Roach    public function getFactId()
343c1010edaSGreg Roach    {
344a25f0a04SGreg Roach        return $this->fact_id;
345a25f0a04SGreg Roach    }
346a25f0a04SGreg Roach
347a25f0a04SGreg Roach    /**
348a25f0a04SGreg Roach     * What is the tag (type) of this fact, such as BIRT, MARR or DEAT.
349a25f0a04SGreg Roach     *
350a25f0a04SGreg Roach     * @return string
351a25f0a04SGreg Roach     */
352c1010edaSGreg Roach    public function getTag()
353c1010edaSGreg Roach    {
354a25f0a04SGreg Roach        return $this->tag;
355a25f0a04SGreg Roach    }
356a25f0a04SGreg Roach
357a25f0a04SGreg Roach    /**
358a25f0a04SGreg Roach     * Used to convert a real fact (e.g. BIRT) into a close-relative’s fact (e.g. _BIRT_CHIL)
359a25f0a04SGreg Roach     *
360a25f0a04SGreg Roach     * @param string $tag
361a25f0a04SGreg Roach     */
362c1010edaSGreg Roach    public function setTag($tag)
363c1010edaSGreg Roach    {
364a25f0a04SGreg Roach        $this->tag = $tag;
365a25f0a04SGreg Roach    }
366a25f0a04SGreg Roach
367a25f0a04SGreg Roach    /**
368a25f0a04SGreg Roach     * The Person/Family record where this Fact came from
369a25f0a04SGreg Roach     *
3706e59b7c4SGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|GedcomRecord
371a25f0a04SGreg Roach     */
372c1010edaSGreg Roach    public function getParent()
373c1010edaSGreg Roach    {
374a25f0a04SGreg Roach        return $this->parent;
375a25f0a04SGreg Roach    }
376a25f0a04SGreg Roach
377a25f0a04SGreg Roach    /**
378a25f0a04SGreg Roach     * Get the name of this fact type, for use as a label.
379a25f0a04SGreg Roach     *
380a25f0a04SGreg Roach     * @return string
381a25f0a04SGreg Roach     */
382c1010edaSGreg Roach    public function getLabel()
383c1010edaSGreg Roach    {
384a25f0a04SGreg Roach        // Custom FACT/EVEN - with a TYPE
3854e5adf68SGreg Roach        if (($this->tag === 'FACT' || $this->tag === 'EVEN') && $this->getAttribute('TYPE') !== '') {
386d53324c9SGreg Roach            return I18N::translate(e($this->getAttribute('TYPE')));
387a25f0a04SGreg Roach        }
3884e5adf68SGreg Roach
389764a01d9SGreg Roach        return GedcomTag::getLabel($this->tag, $this->parent);
390a25f0a04SGreg Roach    }
391a25f0a04SGreg Roach
392a25f0a04SGreg Roach    /**
393a25f0a04SGreg Roach     * This is a newly deleted fact, pending approval.
394a25f0a04SGreg Roach     */
395c1010edaSGreg Roach    public function setPendingDeletion()
396c1010edaSGreg Roach    {
397a25f0a04SGreg Roach        $this->pending_deletion = true;
398a25f0a04SGreg Roach        $this->pending_addition = false;
399a25f0a04SGreg Roach    }
400a25f0a04SGreg Roach
401a25f0a04SGreg Roach    /**
402a25f0a04SGreg Roach     * Is this a newly deleted fact, pending approval.
403a25f0a04SGreg Roach     *
404cbc1590aSGreg Roach     * @return bool
405a25f0a04SGreg Roach     */
406c1010edaSGreg Roach    public function isPendingDeletion()
407c1010edaSGreg Roach    {
408a25f0a04SGreg Roach        return $this->pending_deletion;
409a25f0a04SGreg Roach    }
410a25f0a04SGreg Roach
411a25f0a04SGreg Roach    /**
412a25f0a04SGreg Roach     * This is a newly added fact, pending approval.
413a25f0a04SGreg Roach     */
414c1010edaSGreg Roach    public function setPendingAddition()
415c1010edaSGreg Roach    {
416a25f0a04SGreg Roach        $this->pending_addition = true;
417a25f0a04SGreg Roach        $this->pending_deletion = false;
418a25f0a04SGreg Roach    }
419a25f0a04SGreg Roach
420a25f0a04SGreg Roach    /**
421a25f0a04SGreg Roach     * Is this a newly added fact, pending approval.
422a25f0a04SGreg Roach     *
423cbc1590aSGreg Roach     * @return bool
424a25f0a04SGreg Roach     */
425c1010edaSGreg Roach    public function isPendingAddition()
426c1010edaSGreg Roach    {
427a25f0a04SGreg Roach        return $this->pending_addition;
428a25f0a04SGreg Roach    }
429a25f0a04SGreg Roach
430a25f0a04SGreg Roach    /**
431a25f0a04SGreg Roach     * Source citations linked to this fact
432a25f0a04SGreg Roach     *
433a25f0a04SGreg Roach     * @return string[]
434a25f0a04SGreg Roach     */
435c1010edaSGreg Roach    public function getCitations()
436c1010edaSGreg Roach    {
437a25f0a04SGreg Roach        preg_match_all('/\n(2 SOUR @(' . WT_REGEX_XREF . ')@(?:\n[3-9] .*)*)/', $this->getGedcom(), $matches, PREG_SET_ORDER);
43813abd6f3SGreg Roach        $citations = [];
439a25f0a04SGreg Roach        foreach ($matches as $match) {
44024ec66ceSGreg Roach            $source = Source::getInstance($match[2], $this->getParent()->getTree());
441a25f0a04SGreg Roach            if ($source->canShow()) {
442a25f0a04SGreg Roach                $citations[] = $match[1];
443a25f0a04SGreg Roach            }
444a25f0a04SGreg Roach        }
445a25f0a04SGreg Roach
446a25f0a04SGreg Roach        return $citations;
447a25f0a04SGreg Roach    }
448a25f0a04SGreg Roach
449a25f0a04SGreg Roach    /**
450a25f0a04SGreg Roach     * Notes (inline and objects) linked to this fact
451a25f0a04SGreg Roach     *
452a25f0a04SGreg Roach     * @return string[]|Note[]
453a25f0a04SGreg Roach     */
454c1010edaSGreg Roach    public function getNotes()
455c1010edaSGreg Roach    {
45613abd6f3SGreg Roach        $notes = [];
457a25f0a04SGreg Roach        preg_match_all('/\n2 NOTE ?(.*(?:\n3.*)*)/', $this->getGedcom(), $matches);
458a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
459a25f0a04SGreg Roach            $note = preg_replace("/\n3 CONT ?/", "\n", $match);
460a25f0a04SGreg Roach            if (preg_match('/@(' . WT_REGEX_XREF . ')@/', $note, $nmatch)) {
46124ec66ceSGreg Roach                $note = Note::getInstance($nmatch[1], $this->getParent()->getTree());
462a25f0a04SGreg Roach                if ($note && $note->canShow()) {
463a25f0a04SGreg Roach                    // A note object
464a25f0a04SGreg Roach                    $notes[] = $note;
465a25f0a04SGreg Roach                }
466a25f0a04SGreg Roach            } else {
467a25f0a04SGreg Roach                // An inline note
468a25f0a04SGreg Roach                $notes[] = $note;
469a25f0a04SGreg Roach            }
470a25f0a04SGreg Roach        }
471a25f0a04SGreg Roach
472a25f0a04SGreg Roach        return $notes;
473a25f0a04SGreg Roach    }
474a25f0a04SGreg Roach
475a25f0a04SGreg Roach    /**
476a25f0a04SGreg Roach     * Media objects linked to this fact
477a25f0a04SGreg Roach     *
478a25f0a04SGreg Roach     * @return Media[]
479a25f0a04SGreg Roach     */
480c1010edaSGreg Roach    public function getMedia()
481c1010edaSGreg Roach    {
48213abd6f3SGreg Roach        $media = [];
483a25f0a04SGreg Roach        preg_match_all('/\n2 OBJE @(' . WT_REGEX_XREF . ')@/', $this->getGedcom(), $matches);
484a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
48524ec66ceSGreg Roach            $obje = Media::getInstance($match, $this->getParent()->getTree());
486a25f0a04SGreg Roach            if ($obje->canShow()) {
487a25f0a04SGreg Roach                $media[] = $obje;
488a25f0a04SGreg Roach            }
489a25f0a04SGreg Roach        }
490a25f0a04SGreg Roach
491a25f0a04SGreg Roach        return $media;
492a25f0a04SGreg Roach    }
493a25f0a04SGreg Roach
494a25f0a04SGreg Roach    /**
495a25f0a04SGreg Roach     * A one-line summary of the fact - for charts, etc.
496a25f0a04SGreg Roach     *
497a25f0a04SGreg Roach     * @return string
498a25f0a04SGreg Roach     */
499c1010edaSGreg Roach    public function summary()
500c1010edaSGreg Roach    {
50113abd6f3SGreg Roach        $attributes = [];
502a25f0a04SGreg Roach        $target     = $this->getTarget();
503a25f0a04SGreg Roach        if ($target) {
504a25f0a04SGreg Roach            $attributes[] = $target->getFullName();
505a25f0a04SGreg Roach        } else {
506726d7b07SGreg Roach            // Fact value
507a25f0a04SGreg Roach            $value = $this->getValue();
508726d7b07SGreg Roach            if ($value !== '' && $value !== 'Y') {
509d53324c9SGreg Roach                $attributes[] = '<span dir="auto">' . e($value) . '</span>';
510a25f0a04SGreg Roach            }
511726d7b07SGreg Roach            // Fact date
512a25f0a04SGreg Roach            $date = $this->getDate();
513726d7b07SGreg Roach            if ($date->isOK()) {
514af459ec4SGreg Roach                if (in_array($this->getTag(), explode('|', WT_EVENTS_BIRT)) && $this->getParent() instanceof Individual && $this->getParent()->getTree()->getPreference('SHOW_PARENTS_AGE')) {
5153d7a8a4cSGreg Roach                    $attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->getParent(), $date);
516a25f0a04SGreg Roach                } else {
517a25f0a04SGreg Roach                    $attributes[] = $date->display();
518a25f0a04SGreg Roach                }
519726d7b07SGreg Roach            }
520726d7b07SGreg Roach            // Fact place
521726d7b07SGreg Roach            if (!$this->getPlace()->isEmpty()) {
522726d7b07SGreg Roach                $attributes[] = $this->getPlace()->getShortName();
523a25f0a04SGreg Roach            }
524a25f0a04SGreg Roach        }
5252d8f08abSGreg Roach
5262d8f08abSGreg Roach        $class = 'fact_' . $this->getTag();
527a25f0a04SGreg Roach        if ($this->isPendingAddition()) {
5282d8f08abSGreg Roach            $class .= ' new';
529a25f0a04SGreg Roach        } elseif ($this->isPendingDeletion()) {
5302d8f08abSGreg Roach            $class .= ' old';
531a25f0a04SGreg Roach        }
5322d8f08abSGreg Roach
5332d8f08abSGreg Roach        return
5342d8f08abSGreg Roach            '<div class="' . $class . '">' .
5352d8f08abSGreg Roach            /* I18N: a label/value pair, such as “Occupation: Farmer”. Some languages may need to change the punctuation. */
5362d8f08abSGreg Roach            I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $this->getLabel(), implode(' — ', $attributes)) .
5372d8f08abSGreg Roach            '</div>';
538a25f0a04SGreg Roach    }
539a25f0a04SGreg Roach
540a25f0a04SGreg Roach    /**
541a25f0a04SGreg Roach     * Static Helper functions to sort events
542a25f0a04SGreg Roach     *
543a25f0a04SGreg Roach     * @param Fact $a Fact one
544a25f0a04SGreg Roach     * @param Fact $b Fact two
545a25f0a04SGreg Roach     *
546cbc1590aSGreg Roach     * @return int
547a25f0a04SGreg Roach     */
548c1010edaSGreg Roach    public static function compareDate(Fact $a, Fact $b)
549c1010edaSGreg Roach    {
550a25f0a04SGreg Roach        if ($a->getDate()->isOK() && $b->getDate()->isOK()) {
551a25f0a04SGreg Roach            // If both events have dates, compare by date
552f5b60decSGreg Roach            $ret = Date::compare($a->getDate(), $b->getDate());
553a25f0a04SGreg Roach
554a25f0a04SGreg Roach            if ($ret == 0) {
555a25f0a04SGreg Roach                // If dates are the same, compare by fact type
556a25f0a04SGreg Roach                $ret = self::compareType($a, $b);
557a25f0a04SGreg Roach
558a25f0a04SGreg Roach                // If the fact type is also the same, retain the initial order
559a25f0a04SGreg Roach                if ($ret == 0) {
560a25f0a04SGreg Roach                    $ret = $a->sortOrder - $b->sortOrder;
561a25f0a04SGreg Roach                }
562a25f0a04SGreg Roach            }
563a25f0a04SGreg Roach
564a25f0a04SGreg Roach            return $ret;
565a25f0a04SGreg Roach        } else {
566a25f0a04SGreg Roach            // One or both events have no date - retain the initial order
567a25f0a04SGreg Roach            return $a->sortOrder - $b->sortOrder;
568a25f0a04SGreg Roach        }
569a25f0a04SGreg Roach    }
570a25f0a04SGreg Roach
571a25f0a04SGreg Roach    /**
572a25f0a04SGreg Roach     * Static method to compare two events by their type.
573a25f0a04SGreg Roach     *
574a25f0a04SGreg Roach     * @param Fact $a Fact one
575a25f0a04SGreg Roach     * @param Fact $b Fact two
576a25f0a04SGreg Roach     *
577cbc1590aSGreg Roach     * @return int
578a25f0a04SGreg Roach     */
579c1010edaSGreg Roach    public static function compareType(Fact $a, Fact $b)
580c1010edaSGreg Roach    {
581*bbe4546fSGreg Roach        static $factsort = [];
582a25f0a04SGreg Roach
583a25f0a04SGreg Roach        if (empty($factsort)) {
584*bbe4546fSGreg Roach            $factsort = array_flip(self::FACT_ORDER);
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     */
648c1010edaSGreg Roach    public function __toString()
649c1010edaSGreg Roach    {
650a25f0a04SGreg Roach        return $this->fact_id . '@' . $this->parent->getXref();
651a25f0a04SGreg Roach    }
652a25f0a04SGreg Roach}
653