1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/webtrees_ynh.git synced 2024-09-03 18:26:37 +02:00
webtrees_ynh/sources/app/Fact.php

585 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* webtrees: online genealogy
* Copyright (C) 2016 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Fisharebest\Webtrees;
use Fisharebest\Webtrees\Functions\FunctionsPrint;
/**
* A GEDCOM fact or event object.
*/
class Fact {
/** @var string Unique identifier for this fact (currently implemented as a hash of the raw data). */
private $fact_id;
/** @var GedcomRecord The GEDCOM record from which this fact is taken */
private $parent;
/** @var string The raw GEDCOM data for this fact */
private $gedcom;
/** @var string The GEDCOM tag for this record */
private $tag;
/** @var bool Is this a recently deleted fact, pending approval? */
private $pending_deletion = false;
/** @var bool Is this a recently added fact, pending approval? */
private $pending_addition = false;
/** @var Date The date of this fact, from the “2 DATE …” attribute */
private $date;
/** @var Place The place of this fact, from the “2 PLAC …” attribute */
private $place;
/** @var int Temporary(!) variable Used by Functions::sortFacts() */
public $sortOrder;
/**
* Create an event object from a gedcom fragment.
* We need the parent object (to check privacy) and a (pseudo) fact ID to
* identify the fact within the record.
*
* @param string $gedcom
* @param GedcomRecord $parent
* @param string $fact_id
*
* @throws \InvalidArgumentException
*/
public function __construct($gedcom, GedcomRecord $parent, $fact_id) {
if (preg_match('/^1 (' . WT_REGEX_TAG . ')/', $gedcom, $match)) {
$this->gedcom = $gedcom;
$this->parent = $parent;
$this->fact_id = $fact_id;
$this->tag = $match[1];
} else {
throw new \InvalidArgumentException('Invalid GEDCOM data passed to Fact::_construct(' . $gedcom . ')');
}
}
/**
* Get the value of level 1 data in the fact
* Allow for multi-line values
*
* @return string|null
*/
public function getValue() {
if (preg_match('/^1 (?:' . $this->tag . ') ?(.*(?:(?:\n2 CONT ?.*)*))/', $this->gedcom, $match)) {
return preg_replace("/\n2 CONT ?/", "\n", $match[1]);
} else {
return null;
}
}
/**
* Get the record to which this fact links
*
* @return Individual|Family|Source|Repository|Media|Note|null
*/
public function getTarget() {
$xref = trim($this->getValue(), '@');
switch ($this->tag) {
case 'FAMC':
case 'FAMS':
return Family::getInstance($xref, $this->getParent()->getTree());
case 'HUSB':
case 'WIFE':
case 'CHIL':
return Individual::getInstance($xref, $this->getParent()->getTree());
case 'SOUR':
return Source::getInstance($xref, $this->getParent()->getTree());
case 'OBJE':
return Media::getInstance($xref, $this->getParent()->getTree());
case 'REPO':
return Repository::getInstance($xref, $this->getParent()->getTree());
case 'NOTE':
return Note::getInstance($xref, $this->getParent()->getTree());
default:
return GedcomRecord::getInstance($xref, $this->getParent()->getTree());
}
}
/**
* Get the value of level 2 data in the fact
*
* @param string $tag
*
* @return string|null
*/
public function getAttribute($tag) {
if (preg_match('/\n2 (?:' . $tag . ') ?(.*(?:(?:\n3 CONT ?.*)*)*)/', $this->gedcom, $match)) {
return preg_replace("/\n3 CONT ?/", "\n", $match[1]);
} else {
return null;
}
}
/**
* Do the privacy rules allow us to display this fact to the current user
*
* @param int|null $access_level
*
* @return bool
*/
public function canShow($access_level = null) {
if ($access_level === null) {
$access_level = Auth::accessLevel($this->getParent()->getTree());
}
// Does this record have an explicit RESN?
if (strpos($this->gedcom, "\n2 RESN confidential")) {
return Auth::PRIV_NONE >= $access_level;
}
if (strpos($this->gedcom, "\n2 RESN privacy")) {
return Auth::PRIV_USER >= $access_level;
}
if (strpos($this->gedcom, "\n2 RESN none")) {
return true;
}
// Does this record have a default RESN?
$xref = $this->parent->getXref();
$fact_privacy = $this->parent->getTree()->getFactPrivacy();
$individual_fact_privacy = $this->parent->getTree()->getIndividualFactPrivacy();
if (isset($individual_fact_privacy[$xref][$this->tag])) {
return $individual_fact_privacy[$xref][$this->tag] >= $access_level;
}
if (isset($fact_privacy[$this->tag])) {
return $fact_privacy[$this->tag] >= $access_level;
}
// No restrictions - it must be public
return true;
}
/**
* Check whether this fact is protected against edit
*
* @return bool
*/
public function canEdit() {
// Managers can edit anything
// Members cannot edit RESN, CHAN and locked records
return
$this->parent->canEdit() && !$this->isPendingDeletion() && (
Auth::isManager($this->parent->getTree()) ||
Auth::isEditor($this->parent->getTree()) && strpos($this->gedcom, "\n2 RESN locked") === false && $this->getTag() != 'RESN' && $this->getTag() != 'CHAN'
);
}
/**
* The place where the event occured.
*
* @return Place
*/
public function getPlace() {
if ($this->place === null) {
$this->place = new Place($this->getAttribute('PLAC'), $this->getParent()->getTree());
}
return $this->place;
}
/**
* Get the date for this fact.
* We can call this function many times, especially when sorting,
* so keep a copy of the date.
*
* @return Date
*/
public function getDate() {
if ($this->date === null) {
$this->date = new Date($this->getAttribute('DATE'));
}
return $this->date;
}
/**
* The raw GEDCOM data for this fact
*
* @return string
*/
public function getGedcom() {
return $this->gedcom;
}
/**
* Get a (pseudo) primary key for this fact.
*
* @return string
*/
public function getFactId() {
return $this->fact_id;
}
// What sort of fact is this?
/**
* What is the tag (type) of this fact, such as BIRT, MARR or DEAT.
*
* @return string
*/
public function getTag() {
return $this->tag;
}
/**
* Used to convert a real fact (e.g. BIRT) into a close-relatives fact (e.g. _BIRT_CHIL)
*
* @param string $tag
*/
public function setTag($tag) {
$this->tag = $tag;
}
//
/**
* The Person/Family record where this Fact came from
*
* @return Individual|Family|Source|Repository|Media|Note|GedcomRecord
*/
public function getParent() {
return $this->parent;
}
/**
* Get the name of this fact type, for use as a label.
*
* @return string
*/
public function getLabel() {
switch ($this->tag) {
case 'EVEN':
case 'FACT':
if ($this->getAttribute('TYPE')) {
// Custom FACT/EVEN - with a TYPE
return I18N::translate(Filter::escapeHtml($this->getAttribute('TYPE')));
}
// no break - drop into next case
default:
return GedcomTag::getLabel($this->tag, $this->parent);
}
}
/**
* This is a newly deleted fact, pending approval.
*/
public function setPendingDeletion() {
$this->pending_deletion = true;
$this->pending_addition = false;
}
/**
* Is this a newly deleted fact, pending approval.
*
* @return bool
*/
public function isPendingDeletion() {
return $this->pending_deletion;
}
/**
* This is a newly added fact, pending approval.
*/
public function setPendingAddition() {
$this->pending_addition = true;
$this->pending_deletion = false;
}
/**
* Is this a newly added fact, pending approval.
*
* @return bool
*/
public function isPendingAddition() {
return $this->pending_addition;
}
/**
* Source citations linked to this fact
*
* @return string[]
*/
public function getCitations() {
preg_match_all('/\n(2 SOUR @(' . WT_REGEX_XREF . ')@(?:\n[3-9] .*)*)/', $this->getGedcom(), $matches, PREG_SET_ORDER);
$citations = array();
foreach ($matches as $match) {
$source = Source::getInstance($match[2], $this->getParent()->getTree());
if ($source->canShow()) {
$citations[] = $match[1];
}
}
return $citations;
}
/**
* Notes (inline and objects) linked to this fact
*
* @return string[]|Note[]
*/
public function getNotes() {
$notes = array();
preg_match_all('/\n2 NOTE ?(.*(?:\n3.*)*)/', $this->getGedcom(), $matches);
foreach ($matches[1] as $match) {
$note = preg_replace("/\n3 CONT ?/", "\n", $match);
if (preg_match('/@(' . WT_REGEX_XREF . ')@/', $note, $nmatch)) {
$note = Note::getInstance($nmatch[1], $this->getParent()->getTree());
if ($note && $note->canShow()) {
// A note object
$notes[] = $note;
}
} else {
// An inline note
$notes[] = $note;
}
}
return $notes;
}
/**
* Media objects linked to this fact
*
* @return Media[]
*/
public function getMedia() {
$media = array();
preg_match_all('/\n2 OBJE @(' . WT_REGEX_XREF . ')@/', $this->getGedcom(), $matches);
foreach ($matches[1] as $match) {
$obje = Media::getInstance($match, $this->getParent()->getTree());
if ($obje->canShow()) {
$media[] = $obje;
}
}
return $media;
}
/**
* A one-line summary of the fact - for charts, etc.
*
* @return string
*/
public function summary() {
$attributes = array();
$target = $this->getTarget();
if ($target) {
$attributes[] = $target->getFullName();
} else {
// Fact value
$value = $this->getValue();
if ($value !== '' && $value !== 'Y') {
$attributes[] = '<span dir="auto">' . Filter::escapeHtml($value) . '</span>';
}
// Fact date
$date = $this->getDate();
if ($date->isOK()) {
if (in_array($this->getTag(), explode('|', WT_EVENTS_BIRT)) && $this->getParent() instanceof Individual && $this->getParent()->getTree()->getPreference('SHOW_PARENTS_AGE')) {
$attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->getParent(), $date);
} else {
$attributes[] = $date->display();
}
}
// Fact place
if (!$this->getPlace()->isEmpty()) {
$attributes[] = $this->getPlace()->getShortName();
}
}
$class = 'fact_' . $this->getTag();
if ($this->isPendingAddition()) {
$class .= ' new';
} elseif ($this->isPendingDeletion()) {
$class .= ' old';
}
return
'<div class="' . $class . '">' .
/* I18N: a label/value pair, such as “Occupation: Farmer”. Some languages may need to change the punctuation. */
I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $this->getLabel(), implode(' — ', $attributes)) .
'</div>';
}
/**
* Static Helper functions to sort events
*
* @param Fact $a Fact one
* @param Fact $b Fact two
*
* @return int
*/
public static function compareDate(Fact $a, Fact $b) {
if ($a->getDate()->isOK() && $b->getDate()->isOK()) {
// If both events have dates, compare by date
$ret = Date::compare($a->getDate(), $b->getDate());
if ($ret == 0) {
// If dates are the same, compare by fact type
$ret = self::compareType($a, $b);
// If the fact type is also the same, retain the initial order
if ($ret == 0) {
$ret = $a->sortOrder - $b->sortOrder;
}
}
return $ret;
} else {
// One or both events have no date - retain the initial order
return $a->sortOrder - $b->sortOrder;
}
}
/**
* Static method to compare two events by their type.
*
* @param Fact $a Fact one
* @param Fact $b Fact two
*
* @return int
*/
public static function compareType(Fact $a, Fact $b) {
global $factsort;
if (empty($factsort)) {
$factsort = array_flip(
array(
'BIRT',
'_HNM',
'ALIA', '_AKA', '_AKAN',
'ADOP', '_ADPF', '_ADPF',
'_BRTM',
'CHR', 'BAPM',
'FCOM',
'CONF',
'BARM', 'BASM',
'EDUC',
'GRAD',
'_DEG',
'EMIG', 'IMMI',
'NATU',
'_MILI', '_MILT',
'ENGA',
'MARB', 'MARC', 'MARL', '_MARI', '_MBON',
'MARR', 'MARR_CIVIL', 'MARR_RELIGIOUS', 'MARR_PARTNERS', 'MARR_UNKNOWN', '_COML',
'_STAT',
'_SEPR',
'DIVF',
'MARS',
'_BIRT_CHIL',
'DIV', 'ANUL',
'_BIRT_', '_MARR_', '_DEAT_', '_BURI_', // other events of close relatives
'CENS',
'OCCU',
'RESI',
'PROP',
'CHRA',
'RETI',
'FACT', 'EVEN',
'_NMR', '_NMAR', 'NMR',
'NCHI',
'WILL',
'_HOL',
'_????_',
'DEAT',
'_FNRL', 'CREM', 'BURI', '_INTE',
'_YART',
'_NLIV',
'PROB',
'TITL',
'COMM',
'NATI',
'CITN',
'CAST',
'RELI',
'SSN', 'IDNO',
'TEMP',
'SLGC', 'BAPL', 'CONL', 'ENDL', 'SLGS',
'ADDR', 'PHON', 'EMAIL', '_EMAIL', 'EMAL', 'FAX', 'WWW', 'URL', '_URL',
'FILE', // For media objects
'AFN', 'REFN', '_PRMN', 'REF', 'RIN', '_UID',
'OBJE', 'NOTE', 'SOUR',
'CHAN', '_TODO',
)
);
}
// Facts from same families stay grouped together
// Keep MARR and DIV from the same families from mixing with events from other FAMs
// Use the original order in which the facts were added
if ($a->parent instanceof Family && $b->parent instanceof Family && $a->parent !== $b->parent) {
return $a->sortOrder - $b->sortOrder;
}
$atag = $a->getTag();
$btag = $b->getTag();
// Events not in the above list get mapped onto one that is.
if (!array_key_exists($atag, $factsort)) {
if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $atag, $match)) {
$atag = $match[1];
} else {
$atag = "_????_";
}
}
if (!array_key_exists($btag, $factsort)) {
if (preg_match('/^(_(BIRT|MARR|DEAT|BURI)_)/', $btag, $match)) {
$btag = $match[1];
} else {
$btag = "_????_";
}
}
// - Don't let dated after DEAT/BURI facts sort non-dated facts before DEAT/BURI
// - Treat dated after BURI facts as BURI instead
if ($a->getAttribute('DATE') !== null && $factsort[$atag] > $factsort['BURI'] && $factsort[$atag] < $factsort['CHAN']) {
$atag = 'BURI';
}
if ($b->getAttribute('DATE') !== null && $factsort[$btag] > $factsort['BURI'] && $factsort[$btag] < $factsort['CHAN']) {
$btag = 'BURI';
}
$ret = $factsort[$atag] - $factsort[$btag];
// If facts are the same then put dated facts before non-dated facts
if ($ret == 0) {
if ($a->getAttribute('DATE') !== null && $b->getAttribute('DATE') === null) {
return -1;
}
if ($b->getAttribute('DATE') !== null && $a->getAttribute('DATE') === null) {
return 1;
}
// If no sorting preference, then keep original ordering
$ret = $a->sortOrder - $b->sortOrder;
}
return $ret;
}
/**
* Allow native PHP functions such as array_unique() to work with objects
*
* @return string
*/
public function __toString() {
return $this->fact_id . '@' . $this->parent->getXref();
}
}