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