xref: /webtrees/app/Fact.php (revision 8d0ebef0d075981bd943e8256e2c81a3b1e92b4b)
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 */
16e7f56f2aSGreg Roachdeclare(strict_types=1);
17e7f56f2aSGreg Roach
1876692c8bSGreg Roachnamespace Fisharebest\Webtrees;
1976692c8bSGreg Roach
203d7a8a4cSGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsPrint;
218f5f5da8SGreg Roachuse InvalidArgumentException;
22a25f0a04SGreg Roach
23a25f0a04SGreg Roach/**
2476692c8bSGreg Roach * A GEDCOM fact or event object.
25a25f0a04SGreg Roach */
26c1010edaSGreg Roachclass Fact
27c1010edaSGreg Roach{
28bbe4546fSGreg Roach    const FACT_ORDER = [
29bbe4546fSGreg Roach        'BIRT',
30bbe4546fSGreg Roach        '_HNM',
31bbe4546fSGreg Roach        'ALIA',
32bbe4546fSGreg Roach        '_AKA',
33bbe4546fSGreg Roach        '_AKAN',
34bbe4546fSGreg Roach        'ADOP',
35bbe4546fSGreg Roach        '_ADPF',
36bbe4546fSGreg Roach        '_ADPF',
37bbe4546fSGreg Roach        '_BRTM',
38bbe4546fSGreg Roach        'CHR',
39bbe4546fSGreg Roach        'BAPM',
40bbe4546fSGreg Roach        'FCOM',
41bbe4546fSGreg Roach        'CONF',
42bbe4546fSGreg Roach        'BARM',
43bbe4546fSGreg Roach        'BASM',
44bbe4546fSGreg Roach        'EDUC',
45bbe4546fSGreg Roach        'GRAD',
46bbe4546fSGreg Roach        '_DEG',
47bbe4546fSGreg Roach        'EMIG',
48bbe4546fSGreg Roach        'IMMI',
49bbe4546fSGreg Roach        'NATU',
50bbe4546fSGreg Roach        '_MILI',
51bbe4546fSGreg Roach        '_MILT',
52bbe4546fSGreg Roach        'ENGA',
53bbe4546fSGreg Roach        'MARB',
54bbe4546fSGreg Roach        'MARC',
55bbe4546fSGreg Roach        'MARL',
56bbe4546fSGreg Roach        '_MARI',
57bbe4546fSGreg Roach        '_MBON',
58bbe4546fSGreg Roach        'MARR',
59bbe4546fSGreg Roach        'MARR_CIVIL',
60bbe4546fSGreg Roach        'MARR_RELIGIOUS',
61bbe4546fSGreg Roach        'MARR_PARTNERS',
62bbe4546fSGreg Roach        'MARR_UNKNOWN',
63bbe4546fSGreg Roach        '_COML',
64bbe4546fSGreg Roach        '_STAT',
65bbe4546fSGreg Roach        '_SEPR',
66bbe4546fSGreg Roach        'DIVF',
67bbe4546fSGreg Roach        'MARS',
68bbe4546fSGreg Roach        '_BIRT_CHIL',
69bbe4546fSGreg Roach        'DIV',
70bbe4546fSGreg Roach        'ANUL',
71bbe4546fSGreg Roach        '_BIRT_',
72bbe4546fSGreg Roach        '_MARR_',
73bbe4546fSGreg Roach        '_DEAT_',
74bbe4546fSGreg Roach        '_BURI_',
75bbe4546fSGreg Roach        'CENS',
76bbe4546fSGreg Roach        'OCCU',
77bbe4546fSGreg Roach        'RESI',
78bbe4546fSGreg Roach        'PROP',
79bbe4546fSGreg Roach        'CHRA',
80bbe4546fSGreg Roach        'RETI',
81bbe4546fSGreg Roach        'FACT',
82bbe4546fSGreg Roach        'EVEN',
83bbe4546fSGreg Roach        '_NMR',
84bbe4546fSGreg Roach        '_NMAR',
85bbe4546fSGreg Roach        'NMR',
86bbe4546fSGreg Roach        'NCHI',
87bbe4546fSGreg Roach        'WILL',
88bbe4546fSGreg Roach        '_HOL',
89bbe4546fSGreg Roach        '_????_',
90bbe4546fSGreg Roach        'DEAT',
91bbe4546fSGreg Roach        '_FNRL',
92bbe4546fSGreg Roach        'CREM',
93bbe4546fSGreg Roach        'BURI',
94bbe4546fSGreg Roach        '_INTE',
95bbe4546fSGreg Roach        '_YART',
96bbe4546fSGreg Roach        '_NLIV',
97bbe4546fSGreg Roach        'PROB',
98bbe4546fSGreg Roach        'TITL',
99bbe4546fSGreg Roach        'COMM',
100bbe4546fSGreg Roach        'NATI',
101bbe4546fSGreg Roach        'CITN',
102bbe4546fSGreg Roach        'CAST',
103bbe4546fSGreg Roach        'RELI',
104bbe4546fSGreg Roach        'SSN',
105bbe4546fSGreg Roach        'IDNO',
106bbe4546fSGreg Roach        'TEMP',
107bbe4546fSGreg Roach        'SLGC',
108bbe4546fSGreg Roach        'BAPL',
109bbe4546fSGreg Roach        'CONL',
110bbe4546fSGreg Roach        'ENDL',
111bbe4546fSGreg Roach        'SLGS',
112bbe4546fSGreg Roach        'ADDR',
113bbe4546fSGreg Roach        'PHON',
114bbe4546fSGreg Roach        'EMAIL',
115bbe4546fSGreg Roach        '_EMAIL',
116bbe4546fSGreg Roach        'EMAL',
117bbe4546fSGreg Roach        'FAX',
118bbe4546fSGreg Roach        'WWW',
119bbe4546fSGreg Roach        'URL',
120bbe4546fSGreg Roach        '_URL',
121bbe4546fSGreg Roach        'AFN',
122bbe4546fSGreg Roach        'REFN',
123bbe4546fSGreg Roach        '_PRMN',
124bbe4546fSGreg Roach        'REF',
125bbe4546fSGreg Roach        'RIN',
126bbe4546fSGreg Roach        '_UID',
127bbe4546fSGreg Roach        'OBJE',
128bbe4546fSGreg Roach        'NOTE',
129bbe4546fSGreg Roach        'SOUR',
130bbe4546fSGreg Roach        'CHAN',
131bbe4546fSGreg Roach        '_TODO',
132bbe4546fSGreg Roach    ];
133bbe4546fSGreg Roach
134a25f0a04SGreg Roach    /** @var string Unique identifier for this fact (currently implemented as a hash of the raw data). */
135905ab80aSGreg Roach    private $id;
136a25f0a04SGreg Roach
137a25f0a04SGreg Roach    /** @var GedcomRecord The GEDCOM record from which this fact is taken */
138e7766c08SGreg Roach    private $record;
139a25f0a04SGreg Roach
140a25f0a04SGreg Roach    /** @var string The raw GEDCOM data for this fact */
141a25f0a04SGreg Roach    private $gedcom;
142a25f0a04SGreg Roach
143a25f0a04SGreg Roach    /** @var string The GEDCOM tag for this record */
144a25f0a04SGreg Roach    private $tag;
145a25f0a04SGreg Roach
146cbc1590aSGreg Roach    /** @var bool Is this a recently deleted fact, pending approval? */
147a25f0a04SGreg Roach    private $pending_deletion = false;
148a25f0a04SGreg Roach
149cbc1590aSGreg Roach    /** @var bool Is this a recently added fact, pending approval? */
150a25f0a04SGreg Roach    private $pending_addition = false;
151a25f0a04SGreg Roach
152a25f0a04SGreg Roach    /** @var Date The date of this fact, from the “2 DATE …” attribute */
153a25f0a04SGreg Roach    private $date;
154a25f0a04SGreg Roach
155a25f0a04SGreg Roach    /** @var Place The place of this fact, from the “2 PLAC …” attribute */
156a25f0a04SGreg Roach    private $place;
157a25f0a04SGreg Roach
1583d7a8a4cSGreg Roach    /** @var int Temporary(!) variable Used by Functions::sortFacts() */
159a25f0a04SGreg Roach    public $sortOrder;
160a25f0a04SGreg Roach
161a25f0a04SGreg Roach    /**
162a25f0a04SGreg Roach     * Create an event object from a gedcom fragment.
163a25f0a04SGreg Roach     * We need the parent object (to check privacy) and a (pseudo) fact ID to
164a25f0a04SGreg Roach     * identify the fact within the record.
165a25f0a04SGreg Roach     *
166a25f0a04SGreg Roach     * @param string       $gedcom
167a25f0a04SGreg Roach     * @param GedcomRecord $parent
168905ab80aSGreg Roach     * @param string       $id
169a25f0a04SGreg Roach     *
1708f5f5da8SGreg Roach     * @throws InvalidArgumentException
171a25f0a04SGreg Roach     */
172905ab80aSGreg Roach    public function __construct($gedcom, GedcomRecord $parent, $id)
173c1010edaSGreg Roach    {
174*8d0ebef0SGreg Roach        if (preg_match('/^1 (' . Gedcom::REGEX_TAG . ')/', $gedcom, $match)) {
175a25f0a04SGreg Roach            $this->gedcom = $gedcom;
176e7766c08SGreg Roach            $this->record = $parent;
177905ab80aSGreg Roach            $this->id     = $id;
178a25f0a04SGreg Roach            $this->tag    = $match[1];
179a25f0a04SGreg Roach        } else {
1808f5f5da8SGreg Roach            throw new InvalidArgumentException('Invalid GEDCOM data passed to Fact::_construct(' . $gedcom . ')');
181a25f0a04SGreg Roach        }
182a25f0a04SGreg Roach    }
183a25f0a04SGreg Roach
184a25f0a04SGreg Roach    /**
185a25f0a04SGreg Roach     * Get the value of level 1 data in the fact
186a25f0a04SGreg Roach     * Allow for multi-line values
187a25f0a04SGreg Roach     *
188baacc364SGreg Roach     * @return string
189a25f0a04SGreg Roach     */
190dfa848b8SGreg Roach    public function value(): string
191c1010edaSGreg Roach    {
192a25f0a04SGreg Roach        if (preg_match('/^1 (?:' . $this->tag . ') ?(.*(?:(?:\n2 CONT ?.*)*))/', $this->gedcom, $match)) {
193a25f0a04SGreg Roach            return preg_replace("/\n2 CONT ?/", "\n", $match[1]);
194a25f0a04SGreg Roach        }
195b2ce94c6SRico Sonntag
196b2ce94c6SRico Sonntag        return '';
197a25f0a04SGreg Roach    }
198a25f0a04SGreg Roach
199a25f0a04SGreg Roach    /**
200a25f0a04SGreg Roach     * Get the record to which this fact links
201a25f0a04SGreg Roach     *
202188adf12SGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|GedcomRecord|null
203a25f0a04SGreg Roach     */
204dc124885SGreg Roach    public function target()
205c1010edaSGreg Roach    {
20684586c02SGreg Roach        $xref = trim($this->value(), '@');
207a25f0a04SGreg Roach        switch ($this->tag) {
208a25f0a04SGreg Roach            case 'FAMC':
209a25f0a04SGreg Roach            case 'FAMS':
210f4afa648SGreg Roach                return Family::getInstance($xref, $this->record()->tree());
211a25f0a04SGreg Roach            case 'HUSB':
212a25f0a04SGreg Roach            case 'WIFE':
213a25f0a04SGreg Roach            case 'CHIL':
214f4afa648SGreg Roach                return Individual::getInstance($xref, $this->record()->tree());
215a25f0a04SGreg Roach            case 'SOUR':
216f4afa648SGreg Roach                return Source::getInstance($xref, $this->record()->tree());
217a25f0a04SGreg Roach            case 'OBJE':
218f4afa648SGreg Roach                return Media::getInstance($xref, $this->record()->tree());
219a25f0a04SGreg Roach            case 'REPO':
220f4afa648SGreg Roach                return Repository::getInstance($xref, $this->record()->tree());
221a25f0a04SGreg Roach            case 'NOTE':
222f4afa648SGreg Roach                return Note::getInstance($xref, $this->record()->tree());
223a25f0a04SGreg Roach            default:
224f4afa648SGreg Roach                return GedcomRecord::getInstance($xref, $this->record()->tree());
225a25f0a04SGreg Roach        }
226a25f0a04SGreg Roach    }
227a25f0a04SGreg Roach
228a25f0a04SGreg Roach    /**
229a25f0a04SGreg Roach     * Get the value of level 2 data in the fact
230a25f0a04SGreg Roach     *
231a25f0a04SGreg Roach     * @param string $tag
232a25f0a04SGreg Roach     *
233baacc364SGreg Roach     * @return string
234a25f0a04SGreg Roach     */
235dfa848b8SGreg Roach    public function attribute($tag): string
236c1010edaSGreg Roach    {
237a25f0a04SGreg Roach        if (preg_match('/\n2 (?:' . $tag . ') ?(.*(?:(?:\n3 CONT ?.*)*)*)/', $this->gedcom, $match)) {
238a25f0a04SGreg Roach            return preg_replace("/\n3 CONT ?/", "\n", $match[1]);
239a25f0a04SGreg Roach        }
240b2ce94c6SRico Sonntag
241b2ce94c6SRico Sonntag        return '';
242a25f0a04SGreg Roach    }
243a25f0a04SGreg Roach
244a25f0a04SGreg Roach    /**
245a25f0a04SGreg Roach     * Do the privacy rules allow us to display this fact to the current user
246a25f0a04SGreg Roach     *
247cbc1590aSGreg Roach     * @param int|null $access_level
248a25f0a04SGreg Roach     *
249cbc1590aSGreg Roach     * @return bool
250a25f0a04SGreg Roach     */
25135584196SGreg Roach    public function canShow(int $access_level = null): bool
252c1010edaSGreg Roach    {
2534b9ff166SGreg Roach        if ($access_level === null) {
254f4afa648SGreg Roach            $access_level = Auth::accessLevel($this->record()->tree());
2554b9ff166SGreg Roach        }
2564b9ff166SGreg Roach
257a25f0a04SGreg Roach        // Does this record have an explicit RESN?
25820ff464cSGreg Roach        if (strpos($this->gedcom, "\n2 RESN confidential") !== false) {
2594b9ff166SGreg Roach            return Auth::PRIV_NONE >= $access_level;
260a25f0a04SGreg Roach        }
26120ff464cSGreg Roach        if (strpos($this->gedcom, "\n2 RESN privacy") !== false) {
2624b9ff166SGreg Roach            return Auth::PRIV_USER >= $access_level;
263a25f0a04SGreg Roach        }
26420ff464cSGreg Roach        if (strpos($this->gedcom, "\n2 RESN none") !== false) {
265a25f0a04SGreg Roach            return true;
266a25f0a04SGreg Roach        }
267a25f0a04SGreg Roach
268a25f0a04SGreg Roach        // Does this record have a default RESN?
269c0935879SGreg Roach        $xref                    = $this->record->xref();
270f4afa648SGreg Roach        $fact_privacy            = $this->record->tree()->getFactPrivacy();
271f4afa648SGreg Roach        $individual_fact_privacy = $this->record->tree()->getIndividualFactPrivacy();
272518bbdc1SGreg Roach        if (isset($individual_fact_privacy[$xref][$this->tag])) {
273518bbdc1SGreg Roach            return $individual_fact_privacy[$xref][$this->tag] >= $access_level;
274a25f0a04SGreg Roach        }
275518bbdc1SGreg Roach        if (isset($fact_privacy[$this->tag])) {
276518bbdc1SGreg Roach            return $fact_privacy[$this->tag] >= $access_level;
277a25f0a04SGreg Roach        }
278a25f0a04SGreg Roach
279a25f0a04SGreg Roach        // No restrictions - it must be public
280a25f0a04SGreg Roach        return true;
281a25f0a04SGreg Roach    }
282a25f0a04SGreg Roach
283a25f0a04SGreg Roach    /**
284a25f0a04SGreg Roach     * Check whether this fact is protected against edit
285a25f0a04SGreg Roach     *
286cbc1590aSGreg Roach     * @return bool
287a25f0a04SGreg Roach     */
2888f53f488SRico Sonntag    public function canEdit(): bool
289c1010edaSGreg Roach    {
290a25f0a04SGreg Roach        // Managers can edit anything
291a25f0a04SGreg Roach        // Members cannot edit RESN, CHAN and locked records
292a25f0a04SGreg Roach        return
293e7766c08SGreg Roach            $this->record->canEdit() && !$this->isPendingDeletion() && (
294f4afa648SGreg Roach                Auth::isManager($this->record->tree()) ||
295f4afa648SGreg Roach                Auth::isEditor($this->record->tree()) && strpos($this->gedcom, "\n2 RESN locked") === false && $this->getTag() != 'RESN' && $this->getTag() != 'CHAN'
296a25f0a04SGreg Roach            );
297a25f0a04SGreg Roach    }
298a25f0a04SGreg Roach
299a25f0a04SGreg Roach    /**
300a25f0a04SGreg Roach     * The place where the event occured.
301a25f0a04SGreg Roach     *
302a25f0a04SGreg Roach     * @return Place
303a25f0a04SGreg Roach     */
3044fb14fcbSGreg Roach    public function place(): Place
305c1010edaSGreg Roach    {
306a25f0a04SGreg Roach        if ($this->place === null) {
307f4afa648SGreg Roach            $this->place = new Place($this->attribute('PLAC'), $this->record()->tree());
308a25f0a04SGreg Roach        }
309a25f0a04SGreg Roach
310a25f0a04SGreg Roach        return $this->place;
311a25f0a04SGreg Roach    }
312a25f0a04SGreg Roach
313a25f0a04SGreg Roach    /**
314a25f0a04SGreg Roach     * Get the date for this fact.
315a25f0a04SGreg Roach     * We can call this function many times, especially when sorting,
316a25f0a04SGreg Roach     * so keep a copy of the date.
317a25f0a04SGreg Roach     *
318a25f0a04SGreg Roach     * @return Date
319a25f0a04SGreg Roach     */
3202decada7SGreg Roach    public function date(): Date
321c1010edaSGreg Roach    {
322a25f0a04SGreg Roach        if ($this->date === null) {
3233425616eSGreg Roach            $this->date = new Date($this->attribute('DATE'));
324a25f0a04SGreg Roach        }
325a25f0a04SGreg Roach
326a25f0a04SGreg Roach        return $this->date;
327a25f0a04SGreg Roach    }
328a25f0a04SGreg Roach
329a25f0a04SGreg Roach    /**
330a25f0a04SGreg Roach     * The raw GEDCOM data for this fact
331a25f0a04SGreg Roach     *
332a25f0a04SGreg Roach     * @return string
333a25f0a04SGreg Roach     */
334138ca96cSGreg Roach    public function gedcom(): string
335c1010edaSGreg Roach    {
336a25f0a04SGreg Roach        return $this->gedcom;
337a25f0a04SGreg Roach    }
338a25f0a04SGreg Roach
339a25f0a04SGreg Roach    /**
340a25f0a04SGreg Roach     * Get a (pseudo) primary key for this fact.
341a25f0a04SGreg Roach     *
342a25f0a04SGreg Roach     * @return string
343a25f0a04SGreg Roach     */
344905ab80aSGreg Roach    public function id(): string
345c1010edaSGreg Roach    {
346905ab80aSGreg Roach        return $this->id;
347a25f0a04SGreg Roach    }
348a25f0a04SGreg Roach
349a25f0a04SGreg Roach    /**
350a25f0a04SGreg Roach     * What is the tag (type) of this fact, such as BIRT, MARR or DEAT.
351a25f0a04SGreg Roach     *
352a25f0a04SGreg Roach     * @return string
353a25f0a04SGreg Roach     */
3548f53f488SRico Sonntag    public function getTag(): string
355c1010edaSGreg Roach    {
356a25f0a04SGreg Roach        return $this->tag;
357a25f0a04SGreg Roach    }
358a25f0a04SGreg Roach
359a25f0a04SGreg Roach    /**
360a25f0a04SGreg Roach     * Used to convert a real fact (e.g. BIRT) into a close-relative’s fact (e.g. _BIRT_CHIL)
361a25f0a04SGreg Roach     *
362a25f0a04SGreg Roach     * @param string $tag
3637e96c925SGreg Roach     *
3647e96c925SGreg Roach     * @return void
365a25f0a04SGreg Roach     */
366c1010edaSGreg Roach    public function setTag($tag)
367c1010edaSGreg Roach    {
368a25f0a04SGreg Roach        $this->tag = $tag;
369a25f0a04SGreg Roach    }
370a25f0a04SGreg Roach
371a25f0a04SGreg Roach    /**
372a25f0a04SGreg Roach     * The Person/Family record where this Fact came from
373a25f0a04SGreg Roach     *
3746e59b7c4SGreg Roach     * @return Individual|Family|Source|Repository|Media|Note|GedcomRecord
375a25f0a04SGreg Roach     */
376e7766c08SGreg Roach    public function record()
377c1010edaSGreg Roach    {
378e7766c08SGreg Roach        return $this->record;
379a25f0a04SGreg Roach    }
380a25f0a04SGreg Roach
381a25f0a04SGreg Roach    /**
382a25f0a04SGreg Roach     * Get the name of this fact type, for use as a label.
383a25f0a04SGreg Roach     *
384a25f0a04SGreg Roach     * @return string
385a25f0a04SGreg Roach     */
3867b7d8067SGreg Roach    public function label(): string
387c1010edaSGreg Roach    {
388a25f0a04SGreg Roach        // Custom FACT/EVEN - with a TYPE
3893425616eSGreg Roach        if (($this->tag === 'FACT' || $this->tag === 'EVEN') && $this->attribute('TYPE') !== '') {
3903425616eSGreg Roach            return I18N::translate(e($this->attribute('TYPE')));
391a25f0a04SGreg Roach        }
3924e5adf68SGreg Roach
393e7766c08SGreg Roach        return GedcomTag::getLabel($this->tag, $this->record);
394a25f0a04SGreg Roach    }
395a25f0a04SGreg Roach
396a25f0a04SGreg Roach    /**
397a25f0a04SGreg Roach     * This is a newly deleted fact, pending approval.
3987e96c925SGreg Roach     *
3997e96c925SGreg Roach     * @return void
400a25f0a04SGreg Roach     */
401c1010edaSGreg Roach    public function setPendingDeletion()
402c1010edaSGreg Roach    {
403a25f0a04SGreg Roach        $this->pending_deletion = true;
404a25f0a04SGreg Roach        $this->pending_addition = false;
405a25f0a04SGreg Roach    }
406a25f0a04SGreg Roach
407a25f0a04SGreg Roach    /**
408a25f0a04SGreg Roach     * Is this a newly deleted fact, pending approval.
409a25f0a04SGreg Roach     *
410cbc1590aSGreg Roach     * @return bool
411a25f0a04SGreg Roach     */
4128f53f488SRico Sonntag    public function isPendingDeletion(): bool
413c1010edaSGreg Roach    {
414a25f0a04SGreg Roach        return $this->pending_deletion;
415a25f0a04SGreg Roach    }
416a25f0a04SGreg Roach
417a25f0a04SGreg Roach    /**
418a25f0a04SGreg Roach     * This is a newly added fact, pending approval.
4197e96c925SGreg Roach     *
4207e96c925SGreg Roach     * @return void
421a25f0a04SGreg Roach     */
422c1010edaSGreg Roach    public function setPendingAddition()
423c1010edaSGreg Roach    {
424a25f0a04SGreg Roach        $this->pending_addition = true;
425a25f0a04SGreg Roach        $this->pending_deletion = false;
426a25f0a04SGreg Roach    }
427a25f0a04SGreg Roach
428a25f0a04SGreg Roach    /**
429a25f0a04SGreg Roach     * Is this a newly added fact, pending approval.
430a25f0a04SGreg Roach     *
431cbc1590aSGreg Roach     * @return bool
432a25f0a04SGreg Roach     */
4338f53f488SRico Sonntag    public function isPendingAddition(): bool
434c1010edaSGreg Roach    {
435a25f0a04SGreg Roach        return $this->pending_addition;
436a25f0a04SGreg Roach    }
437a25f0a04SGreg Roach
438a25f0a04SGreg Roach    /**
439a25f0a04SGreg Roach     * Source citations linked to this fact
440a25f0a04SGreg Roach     *
441a25f0a04SGreg Roach     * @return string[]
442a25f0a04SGreg Roach     */
4438f53f488SRico Sonntag    public function getCitations(): array
444c1010edaSGreg Roach    {
445*8d0ebef0SGreg Roach        preg_match_all('/\n(2 SOUR @(' . Gedcom::REGEX_XREF . ')@(?:\n[3-9] .*)*)/', $this->gedcom(), $matches, PREG_SET_ORDER);
44613abd6f3SGreg Roach        $citations = [];
447a25f0a04SGreg Roach        foreach ($matches as $match) {
448f4afa648SGreg Roach            $source = Source::getInstance($match[2], $this->record()->tree());
4492859ecedSJonathan Jaubart            if ($source && $source->canShow()) {
450a25f0a04SGreg Roach                $citations[] = $match[1];
451a25f0a04SGreg Roach            }
452a25f0a04SGreg Roach        }
453a25f0a04SGreg Roach
454a25f0a04SGreg Roach        return $citations;
455a25f0a04SGreg Roach    }
456a25f0a04SGreg Roach
457a25f0a04SGreg Roach    /**
458a25f0a04SGreg Roach     * Notes (inline and objects) linked to this fact
459a25f0a04SGreg Roach     *
460a25f0a04SGreg Roach     * @return string[]|Note[]
461a25f0a04SGreg Roach     */
4628f53f488SRico Sonntag    public function getNotes(): array
463c1010edaSGreg Roach    {
46413abd6f3SGreg Roach        $notes = [];
465138ca96cSGreg Roach        preg_match_all('/\n2 NOTE ?(.*(?:\n3.*)*)/', $this->gedcom(), $matches);
466a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
467a25f0a04SGreg Roach            $note = preg_replace("/\n3 CONT ?/", "\n", $match);
468*8d0ebef0SGreg Roach            if (preg_match('/@(' . Gedcom::REGEX_XREF . ')@/', $note, $nmatch)) {
469f4afa648SGreg Roach                $note = Note::getInstance($nmatch[1], $this->record()->tree());
470a25f0a04SGreg Roach                if ($note && $note->canShow()) {
471a25f0a04SGreg Roach                    // A note object
472a25f0a04SGreg Roach                    $notes[] = $note;
473a25f0a04SGreg Roach                }
474a25f0a04SGreg Roach            } else {
475a25f0a04SGreg Roach                // An inline note
476a25f0a04SGreg Roach                $notes[] = $note;
477a25f0a04SGreg Roach            }
478a25f0a04SGreg Roach        }
479a25f0a04SGreg Roach
480a25f0a04SGreg Roach        return $notes;
481a25f0a04SGreg Roach    }
482a25f0a04SGreg Roach
483a25f0a04SGreg Roach    /**
484a25f0a04SGreg Roach     * Media objects linked to this fact
485a25f0a04SGreg Roach     *
486a25f0a04SGreg Roach     * @return Media[]
487a25f0a04SGreg Roach     */
4888f53f488SRico Sonntag    public function getMedia(): array
489c1010edaSGreg Roach    {
49013abd6f3SGreg Roach        $media = [];
491*8d0ebef0SGreg Roach        preg_match_all('/\n2 OBJE @(' . Gedcom::REGEX_XREF . ')@/', $this->gedcom(), $matches);
492a25f0a04SGreg Roach        foreach ($matches[1] as $match) {
493f4afa648SGreg Roach            $obje = Media::getInstance($match, $this->record()->tree());
4942859ecedSJonathan Jaubart            if ($obje && $obje->canShow()) {
495a25f0a04SGreg Roach                $media[] = $obje;
496a25f0a04SGreg Roach            }
497a25f0a04SGreg Roach        }
498a25f0a04SGreg Roach
499a25f0a04SGreg Roach        return $media;
500a25f0a04SGreg Roach    }
501a25f0a04SGreg Roach
502a25f0a04SGreg Roach    /**
503a25f0a04SGreg Roach     * A one-line summary of the fact - for charts, etc.
504a25f0a04SGreg Roach     *
505a25f0a04SGreg Roach     * @return string
506a25f0a04SGreg Roach     */
5078f53f488SRico Sonntag    public function summary(): string
508c1010edaSGreg Roach    {
50913abd6f3SGreg Roach        $attributes = [];
510dc124885SGreg Roach        $target     = $this->target();
511db7bb364SGreg Roach        if ($target instanceof GedcomRecord) {
512a25f0a04SGreg Roach            $attributes[] = $target->getFullName();
513a25f0a04SGreg Roach        } else {
514726d7b07SGreg Roach            // Fact value
51584586c02SGreg Roach            $value = $this->value();
516726d7b07SGreg Roach            if ($value !== '' && $value !== 'Y') {
517d53324c9SGreg Roach                $attributes[] = '<span dir="auto">' . e($value) . '</span>';
518a25f0a04SGreg Roach            }
519726d7b07SGreg Roach            // Fact date
5202decada7SGreg Roach            $date = $this->date();
521726d7b07SGreg Roach            if ($date->isOK()) {
522*8d0ebef0SGreg Roach                if (in_array($this->getTag(), Gedcom::BIRTH_EVENTS) && $this->record() instanceof Individual && $this->record()->tree()->getPreference('SHOW_PARENTS_AGE')) {
523e7766c08SGreg Roach                    $attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->record(), $date);
524a25f0a04SGreg Roach                } else {
525a25f0a04SGreg Roach                    $attributes[] = $date->display();
526a25f0a04SGreg Roach                }
527726d7b07SGreg Roach            }
528726d7b07SGreg Roach            // Fact place
5294fb14fcbSGreg Roach            if (!$this->place()->isEmpty()) {
5304fb14fcbSGreg Roach                $attributes[] = $this->place()->getShortName();
531a25f0a04SGreg Roach            }
532a25f0a04SGreg Roach        }
5332d8f08abSGreg Roach
5342d8f08abSGreg Roach        $class = 'fact_' . $this->getTag();
535a25f0a04SGreg Roach        if ($this->isPendingAddition()) {
5362d8f08abSGreg Roach            $class .= ' new';
537a25f0a04SGreg Roach        } elseif ($this->isPendingDeletion()) {
5382d8f08abSGreg Roach            $class .= ' old';
539a25f0a04SGreg Roach        }
5402d8f08abSGreg Roach
5412d8f08abSGreg Roach        return
5422d8f08abSGreg Roach            '<div class="' . $class . '">' .
5432d8f08abSGreg Roach            /* I18N: a label/value pair, such as “Occupation: Farmer”. Some languages may need to change the punctuation. */
5447b7d8067SGreg Roach            I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $this->label(), implode(' — ', $attributes)) .
5452d8f08abSGreg Roach            '</div>';
546a25f0a04SGreg Roach    }
547a25f0a04SGreg Roach
548a25f0a04SGreg Roach    /**
549a25f0a04SGreg Roach     * Static Helper functions to sort events
550a25f0a04SGreg Roach     *
551a25f0a04SGreg Roach     * @param Fact $a Fact one
552a25f0a04SGreg Roach     * @param Fact $b Fact two
553a25f0a04SGreg Roach     *
554cbc1590aSGreg Roach     * @return int
555a25f0a04SGreg Roach     */
556dfa848b8SGreg Roach    public static function compareDate(Fact $a, Fact $b): int
557c1010edaSGreg Roach    {
5582decada7SGreg Roach        if ($a->date()->isOK() && $b->date()->isOK()) {
559a25f0a04SGreg Roach            // If both events have dates, compare by date
5602decada7SGreg Roach            $ret = Date::compare($a->date(), $b->date());
561a25f0a04SGreg Roach
562dfa848b8SGreg Roach            if ($ret === 0) {
563a25f0a04SGreg Roach                // If dates are the same, compare by fact type
564a25f0a04SGreg Roach                $ret = self::compareType($a, $b);
565a25f0a04SGreg Roach
566a25f0a04SGreg Roach                // If the fact type is also the same, retain the initial order
567dfa848b8SGreg Roach                if ($ret === 0) {
568a25f0a04SGreg Roach                    $ret = $a->sortOrder - $b->sortOrder;
569a25f0a04SGreg Roach                }
570a25f0a04SGreg Roach            }
571a25f0a04SGreg Roach
572a25f0a04SGreg Roach            return $ret;
573b2ce94c6SRico Sonntag        }
574b2ce94c6SRico Sonntag
575a25f0a04SGreg Roach        // One or both events have no date - retain the initial order
576a25f0a04SGreg Roach        return $a->sortOrder - $b->sortOrder;
577a25f0a04SGreg Roach    }
578a25f0a04SGreg Roach
579a25f0a04SGreg Roach    /**
580a25f0a04SGreg Roach     * Static method to compare two events by their type.
581a25f0a04SGreg Roach     *
582a25f0a04SGreg Roach     * @param Fact $a Fact one
583a25f0a04SGreg Roach     * @param Fact $b Fact two
584a25f0a04SGreg Roach     *
585cbc1590aSGreg Roach     * @return int
586a25f0a04SGreg Roach     */
5878f53f488SRico Sonntag    public static function compareType(Fact $a, Fact $b): int
588c1010edaSGreg Roach    {
589bbe4546fSGreg Roach        static $factsort = [];
590a25f0a04SGreg Roach
591a25f0a04SGreg Roach        if (empty($factsort)) {
592bbe4546fSGreg Roach            $factsort = array_flip(self::FACT_ORDER);
593a25f0a04SGreg Roach        }
594a25f0a04SGreg Roach
595a25f0a04SGreg Roach        // Facts from same families stay grouped together
596a25f0a04SGreg Roach        // Keep MARR and DIV from the same families from mixing with events from other FAMs
597a25f0a04SGreg Roach        // Use the original order in which the facts were added
598e7766c08SGreg Roach        if ($a->record instanceof Family && $b->record instanceof Family && $a->record !== $b->record) {
599a25f0a04SGreg Roach            return $a->sortOrder - $b->sortOrder;
600a25f0a04SGreg Roach        }
601a25f0a04SGreg Roach
602a25f0a04SGreg Roach        $atag = $a->getTag();
603a25f0a04SGreg Roach        $btag = $b->getTag();
604a25f0a04SGreg Roach
605a25f0a04SGreg Roach        // Events not in the above list get mapped onto one that is.
606a25f0a04SGreg Roach        if (!array_key_exists($atag, $factsort)) {
607a25f0a04SGreg Roach            if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
608a25f0a04SGreg Roach                $atag = $match[1];
609a25f0a04SGreg Roach            } else {
6107a6ee1acSGreg Roach                $atag = '_????_';
611a25f0a04SGreg Roach            }
612a25f0a04SGreg Roach        }
613a25f0a04SGreg Roach
614a25f0a04SGreg Roach        if (!array_key_exists($btag, $factsort)) {
615a25f0a04SGreg Roach            if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
616a25f0a04SGreg Roach                $btag = $match[1];
617a25f0a04SGreg Roach            } else {
6187a6ee1acSGreg Roach                $btag = '_????_';
619a25f0a04SGreg Roach            }
620a25f0a04SGreg Roach        }
621a25f0a04SGreg Roach
622a25f0a04SGreg Roach        // - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
623a25f0a04SGreg Roach        // - Treat dated after BURI facts as BURI instead
6243425616eSGreg Roach        if ($a->attribute('DATE') !== '' && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
625a25f0a04SGreg Roach            $atag = 'BURI';
626a25f0a04SGreg Roach        }
627a25f0a04SGreg Roach
6283425616eSGreg Roach        if ($b->attribute('DATE') !== '' && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
629a25f0a04SGreg Roach            $btag = 'BURI';
630a25f0a04SGreg Roach        }
631a25f0a04SGreg Roach
632a25f0a04SGreg Roach        $ret = $factsort[$atag] - $factsort[$btag];
633a25f0a04SGreg Roach
634a25f0a04SGreg Roach        // If facts are the same then put dated facts before non-dated facts
635a25f0a04SGreg Roach        if ($ret == 0) {
6363425616eSGreg Roach            if ($a->attribute('DATE') !== '' && $b->attribute('DATE') === '') {
637a25f0a04SGreg Roach                return -1;
638a25f0a04SGreg Roach            }
639a25f0a04SGreg Roach
6403425616eSGreg Roach            if ($b->attribute('DATE') !== '' && $a->attribute('DATE') === '') {
641a25f0a04SGreg Roach                return 1;
642a25f0a04SGreg Roach            }
643a25f0a04SGreg Roach
644a25f0a04SGreg Roach            // If no sorting preference, then keep original ordering
645a25f0a04SGreg Roach            $ret = $a->sortOrder - $b->sortOrder;
646a25f0a04SGreg Roach        }
647a25f0a04SGreg Roach
648a25f0a04SGreg Roach        return $ret;
649a25f0a04SGreg Roach    }
650a25f0a04SGreg Roach
651a25f0a04SGreg Roach    /**
652a25f0a04SGreg Roach     * Allow native PHP functions such as array_unique() to work with objects
653a25f0a04SGreg Roach     *
654a25f0a04SGreg Roach     * @return string
655a25f0a04SGreg Roach     */
656dfa848b8SGreg Roach    public function __toString(): string
657c1010edaSGreg Roach    {
658c0935879SGreg Roach        return $this->id . '@' . $this->record->xref();
659a25f0a04SGreg Roach    }
660a25f0a04SGreg Roach}
661