1*a25f0a04SGreg Roach<?php 2*a25f0a04SGreg Roachnamespace Webtrees; 3*a25f0a04SGreg Roach 4*a25f0a04SGreg Roach/** 5*a25f0a04SGreg Roach * webtrees: online genealogy 6*a25f0a04SGreg Roach * Copyright (C) 2015 webtrees development team 7*a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 8*a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 9*a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 10*a25f0a04SGreg Roach * (at your option) any later version. 11*a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 12*a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 13*a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14*a25f0a04SGreg Roach * GNU General Public License for more details. 15*a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 16*a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 17*a25f0a04SGreg Roach */ 18*a25f0a04SGreg Roach 19*a25f0a04SGreg Roach/** 20*a25f0a04SGreg Roach * Class Family - Class file for a Family 21*a25f0a04SGreg Roach */ 22*a25f0a04SGreg Roachclass Family extends GedcomRecord { 23*a25f0a04SGreg Roach const RECORD_TYPE = 'FAM'; 24*a25f0a04SGreg Roach const URL_PREFIX = 'family.php?famid='; 25*a25f0a04SGreg Roach 26*a25f0a04SGreg Roach /** @var Individual|null The husband (or first spouse for same-sex couples) */ 27*a25f0a04SGreg Roach private $husb; 28*a25f0a04SGreg Roach 29*a25f0a04SGreg Roach /** @var Individual|null The wife (or second spouse for same-sex couples) */ 30*a25f0a04SGreg Roach private $wife; 31*a25f0a04SGreg Roach 32*a25f0a04SGreg Roach /** {@inheritdoc} */ 33*a25f0a04SGreg Roach function __construct($xref, $gedcom, $pending, $gedcom_id) { 34*a25f0a04SGreg Roach parent::__construct($xref, $gedcom, $pending, $gedcom_id); 35*a25f0a04SGreg Roach 36*a25f0a04SGreg Roach // Fetch husband and wife 37*a25f0a04SGreg Roach if (preg_match('/^1 HUSB @(.+)@/m', $gedcom . $pending, $match)) { 38*a25f0a04SGreg Roach $this->husb = Individual::getInstance($match[1], $gedcom_id); 39*a25f0a04SGreg Roach } 40*a25f0a04SGreg Roach if (preg_match('/^1 WIFE @(.+)@/m', $gedcom . $pending, $match)) { 41*a25f0a04SGreg Roach $this->wife = Individual::getInstance($match[1], $gedcom_id); 42*a25f0a04SGreg Roach } 43*a25f0a04SGreg Roach 44*a25f0a04SGreg Roach // Make sure husb/wife are the right way round. 45*a25f0a04SGreg Roach if ($this->husb && $this->husb->getSex() == 'F' || $this->wife && $this->wife->getSex() == 'M') { 46*a25f0a04SGreg Roach list($this->husb, $this->wife) = array($this->wife, $this->husb); 47*a25f0a04SGreg Roach } 48*a25f0a04SGreg Roach } 49*a25f0a04SGreg Roach 50*a25f0a04SGreg Roach /** 51*a25f0a04SGreg Roach * Get an instance of a family object. For single records, 52*a25f0a04SGreg Roach * we just receive the XREF. For bulk records (such as lists 53*a25f0a04SGreg Roach * and search results) we can receive the GEDCOM data as well. 54*a25f0a04SGreg Roach * 55*a25f0a04SGreg Roach * @param string $xref 56*a25f0a04SGreg Roach * @param integer|null $gedcom_id 57*a25f0a04SGreg Roach * @param string|null $gedcom 58*a25f0a04SGreg Roach * 59*a25f0a04SGreg Roach * @return Family|null 60*a25f0a04SGreg Roach */ 61*a25f0a04SGreg Roach public static function getInstance($xref, $gedcom_id = WT_GED_ID, $gedcom = null) { 62*a25f0a04SGreg Roach $record = parent::getInstance($xref, $gedcom_id, $gedcom); 63*a25f0a04SGreg Roach 64*a25f0a04SGreg Roach if ($record instanceof Family) { 65*a25f0a04SGreg Roach return $record; 66*a25f0a04SGreg Roach } else { 67*a25f0a04SGreg Roach return null; 68*a25f0a04SGreg Roach } 69*a25f0a04SGreg Roach } 70*a25f0a04SGreg Roach 71*a25f0a04SGreg Roach /** {@inheritdoc} */ 72*a25f0a04SGreg Roach protected function createPrivateGedcomRecord($access_level) { 73*a25f0a04SGreg Roach global $SHOW_PRIVATE_RELATIONSHIPS; 74*a25f0a04SGreg Roach 75*a25f0a04SGreg Roach $rec = '0 @' . $this->xref . '@ FAM'; 76*a25f0a04SGreg Roach // Just show the 1 CHIL/HUSB/WIFE tag, not any subtags, which may contain private data 77*a25f0a04SGreg Roach preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches, PREG_SET_ORDER); 78*a25f0a04SGreg Roach foreach ($matches as $match) { 79*a25f0a04SGreg Roach $rela = Individual::getInstance($match[1]); 80*a25f0a04SGreg Roach if ($rela && ($SHOW_PRIVATE_RELATIONSHIPS || $rela->canShow($access_level))) { 81*a25f0a04SGreg Roach $rec .= $match[0]; 82*a25f0a04SGreg Roach } 83*a25f0a04SGreg Roach } 84*a25f0a04SGreg Roach 85*a25f0a04SGreg Roach return $rec; 86*a25f0a04SGreg Roach } 87*a25f0a04SGreg Roach 88*a25f0a04SGreg Roach /** {@inheritdoc} */ 89*a25f0a04SGreg Roach protected static function fetchGedcomRecord($xref, $gedcom_id) { 90*a25f0a04SGreg Roach static $statement = null; 91*a25f0a04SGreg Roach 92*a25f0a04SGreg Roach if ($statement === null) { 93*a25f0a04SGreg Roach $statement = Database::prepare("SELECT f_gedcom FROM `##families` WHERE f_id=? AND f_file=?"); 94*a25f0a04SGreg Roach } 95*a25f0a04SGreg Roach 96*a25f0a04SGreg Roach return $statement->execute(array($xref, $gedcom_id))->fetchOne(); 97*a25f0a04SGreg Roach } 98*a25f0a04SGreg Roach 99*a25f0a04SGreg Roach /** 100*a25f0a04SGreg Roach * Get the male (or first female) partner of the family 101*a25f0a04SGreg Roach * 102*a25f0a04SGreg Roach * @return Individual|null 103*a25f0a04SGreg Roach */ 104*a25f0a04SGreg Roach function getHusband() { 105*a25f0a04SGreg Roach if ($this->husb && $this->husb->canShowName()) { 106*a25f0a04SGreg Roach return $this->husb; 107*a25f0a04SGreg Roach } else { 108*a25f0a04SGreg Roach return null; 109*a25f0a04SGreg Roach } 110*a25f0a04SGreg Roach } 111*a25f0a04SGreg Roach 112*a25f0a04SGreg Roach /** 113*a25f0a04SGreg Roach * Get the female (or second male) partner of the family 114*a25f0a04SGreg Roach * 115*a25f0a04SGreg Roach * @return Individual|null 116*a25f0a04SGreg Roach */ 117*a25f0a04SGreg Roach function getWife() { 118*a25f0a04SGreg Roach if ($this->wife && $this->wife->canShowName()) { 119*a25f0a04SGreg Roach return $this->wife; 120*a25f0a04SGreg Roach } else { 121*a25f0a04SGreg Roach return null; 122*a25f0a04SGreg Roach } 123*a25f0a04SGreg Roach } 124*a25f0a04SGreg Roach 125*a25f0a04SGreg Roach /** {@inheritdoc} */ 126*a25f0a04SGreg Roach protected function canShowByType($access_level) { 127*a25f0a04SGreg Roach // Hide a family if any member is private 128*a25f0a04SGreg Roach preg_match_all('/\n1 (?:CHIL|HUSB|WIFE) @(' . WT_REGEX_XREF . ')@/', $this->gedcom, $matches); 129*a25f0a04SGreg Roach foreach ($matches[1] as $match) { 130*a25f0a04SGreg Roach $person = Individual::getInstance($match); 131*a25f0a04SGreg Roach if ($person && !$person->canShow($access_level)) { 132*a25f0a04SGreg Roach return false; 133*a25f0a04SGreg Roach } 134*a25f0a04SGreg Roach } 135*a25f0a04SGreg Roach 136*a25f0a04SGreg Roach return true; 137*a25f0a04SGreg Roach } 138*a25f0a04SGreg Roach 139*a25f0a04SGreg Roach /** {@inheritdoc} */ 140*a25f0a04SGreg Roach public function canShowName($access_level = WT_USER_ACCESS_LEVEL) { 141*a25f0a04SGreg Roach // We can always see the name (Husband-name + Wife-name), however, 142*a25f0a04SGreg Roach // the name will often be "private + private" 143*a25f0a04SGreg Roach return true; 144*a25f0a04SGreg Roach } 145*a25f0a04SGreg Roach 146*a25f0a04SGreg Roach /** 147*a25f0a04SGreg Roach * Find the spouse of a person. 148*a25f0a04SGreg Roach * 149*a25f0a04SGreg Roach * @param Individual $person 150*a25f0a04SGreg Roach * 151*a25f0a04SGreg Roach * @return Individual|null 152*a25f0a04SGreg Roach */ 153*a25f0a04SGreg Roach function getSpouse(Individual $person) { 154*a25f0a04SGreg Roach if ($person === $this->wife) { 155*a25f0a04SGreg Roach return $this->husb; 156*a25f0a04SGreg Roach } else { 157*a25f0a04SGreg Roach return $this->wife; 158*a25f0a04SGreg Roach } 159*a25f0a04SGreg Roach } 160*a25f0a04SGreg Roach 161*a25f0a04SGreg Roach /** 162*a25f0a04SGreg Roach * Get the (zero, one or two) spouses from this family. 163*a25f0a04SGreg Roach * 164*a25f0a04SGreg Roach * @param integer $access_level 165*a25f0a04SGreg Roach * 166*a25f0a04SGreg Roach * @return Individual[] 167*a25f0a04SGreg Roach */ 168*a25f0a04SGreg Roach function getSpouses($access_level = WT_USER_ACCESS_LEVEL) { 169*a25f0a04SGreg Roach $spouses = array(); 170*a25f0a04SGreg Roach if ($this->husb && $this->husb->canShowName($access_level)) { 171*a25f0a04SGreg Roach $spouses[] = $this->husb; 172*a25f0a04SGreg Roach } 173*a25f0a04SGreg Roach if ($this->wife && $this->wife->canShowName($access_level)) { 174*a25f0a04SGreg Roach $spouses[] = $this->wife; 175*a25f0a04SGreg Roach } 176*a25f0a04SGreg Roach 177*a25f0a04SGreg Roach return $spouses; 178*a25f0a04SGreg Roach } 179*a25f0a04SGreg Roach 180*a25f0a04SGreg Roach /** 181*a25f0a04SGreg Roach * Get a list of this family’s children. 182*a25f0a04SGreg Roach * 183*a25f0a04SGreg Roach * @param integer $access_level 184*a25f0a04SGreg Roach * 185*a25f0a04SGreg Roach * @return Individual[] 186*a25f0a04SGreg Roach */ 187*a25f0a04SGreg Roach function getChildren($access_level = WT_USER_ACCESS_LEVEL) { 188*a25f0a04SGreg Roach global $SHOW_PRIVATE_RELATIONSHIPS; 189*a25f0a04SGreg Roach 190*a25f0a04SGreg Roach $children = array(); 191*a25f0a04SGreg Roach foreach ($this->getFacts('CHIL', false, $access_level, $SHOW_PRIVATE_RELATIONSHIPS) as $fact) { 192*a25f0a04SGreg Roach $child = $fact->getTarget(); 193*a25f0a04SGreg Roach if ($child && ($SHOW_PRIVATE_RELATIONSHIPS || $child->canShowName($access_level))) { 194*a25f0a04SGreg Roach $children[] = $child; 195*a25f0a04SGreg Roach } 196*a25f0a04SGreg Roach } 197*a25f0a04SGreg Roach 198*a25f0a04SGreg Roach return $children; 199*a25f0a04SGreg Roach } 200*a25f0a04SGreg Roach 201*a25f0a04SGreg Roach /** 202*a25f0a04SGreg Roach * Static helper function to sort an array of families by marriage date 203*a25f0a04SGreg Roach * 204*a25f0a04SGreg Roach * @param Family $x 205*a25f0a04SGreg Roach * @param Family $y 206*a25f0a04SGreg Roach * 207*a25f0a04SGreg Roach * @return integer 208*a25f0a04SGreg Roach */ 209*a25f0a04SGreg Roach public static function compareMarrDate(Family $x, Family $y) { 210*a25f0a04SGreg Roach return Date::Compare($x->getMarriageDate(), $y->getMarriageDate()); 211*a25f0a04SGreg Roach } 212*a25f0a04SGreg Roach 213*a25f0a04SGreg Roach /** 214*a25f0a04SGreg Roach * Number of children - for the individual list 215*a25f0a04SGreg Roach * 216*a25f0a04SGreg Roach * @return integer 217*a25f0a04SGreg Roach */ 218*a25f0a04SGreg Roach public function getNumberOfChildren() { 219*a25f0a04SGreg Roach $nchi = count($this->getChildren()); 220*a25f0a04SGreg Roach foreach ($this->getFacts('NCHI') as $fact) { 221*a25f0a04SGreg Roach $nchi = max($nchi, (int) $fact->getValue()); 222*a25f0a04SGreg Roach } 223*a25f0a04SGreg Roach 224*a25f0a04SGreg Roach return $nchi; 225*a25f0a04SGreg Roach } 226*a25f0a04SGreg Roach 227*a25f0a04SGreg Roach /** 228*a25f0a04SGreg Roach * get the marriage event 229*a25f0a04SGreg Roach * 230*a25f0a04SGreg Roach * @return Fact 231*a25f0a04SGreg Roach */ 232*a25f0a04SGreg Roach public function getMarriage() { 233*a25f0a04SGreg Roach return $this->getFirstFact('MARR'); 234*a25f0a04SGreg Roach } 235*a25f0a04SGreg Roach 236*a25f0a04SGreg Roach /** 237*a25f0a04SGreg Roach * Get marriage date 238*a25f0a04SGreg Roach * 239*a25f0a04SGreg Roach * @return Date 240*a25f0a04SGreg Roach */ 241*a25f0a04SGreg Roach public function getMarriageDate() { 242*a25f0a04SGreg Roach $marriage = $this->getMarriage(); 243*a25f0a04SGreg Roach if ($marriage) { 244*a25f0a04SGreg Roach return $marriage->getDate(); 245*a25f0a04SGreg Roach } else { 246*a25f0a04SGreg Roach return new Date(''); 247*a25f0a04SGreg Roach } 248*a25f0a04SGreg Roach } 249*a25f0a04SGreg Roach 250*a25f0a04SGreg Roach /** 251*a25f0a04SGreg Roach * Get the marriage year - displayed on lists of families 252*a25f0a04SGreg Roach * 253*a25f0a04SGreg Roach * @return integer 254*a25f0a04SGreg Roach */ 255*a25f0a04SGreg Roach public function getMarriageYear() { 256*a25f0a04SGreg Roach return $this->getMarriageDate()->MinDate()->y; 257*a25f0a04SGreg Roach } 258*a25f0a04SGreg Roach 259*a25f0a04SGreg Roach /** 260*a25f0a04SGreg Roach * Get the type for this marriage 261*a25f0a04SGreg Roach * 262*a25f0a04SGreg Roach * @return string|null 263*a25f0a04SGreg Roach */ 264*a25f0a04SGreg Roach public function getMarriageType() { 265*a25f0a04SGreg Roach $marriage = $this->getMarriage(); 266*a25f0a04SGreg Roach if ($marriage) { 267*a25f0a04SGreg Roach return $marriage->getAttribute('TYPE'); 268*a25f0a04SGreg Roach } else { 269*a25f0a04SGreg Roach return null; 270*a25f0a04SGreg Roach } 271*a25f0a04SGreg Roach } 272*a25f0a04SGreg Roach 273*a25f0a04SGreg Roach /** 274*a25f0a04SGreg Roach * Get the marriage place 275*a25f0a04SGreg Roach * 276*a25f0a04SGreg Roach * @return Place 277*a25f0a04SGreg Roach */ 278*a25f0a04SGreg Roach public function getMarriagePlace() { 279*a25f0a04SGreg Roach $marriage = $this->getMarriage(); 280*a25f0a04SGreg Roach 281*a25f0a04SGreg Roach return $marriage->getPlace(); 282*a25f0a04SGreg Roach } 283*a25f0a04SGreg Roach 284*a25f0a04SGreg Roach /** 285*a25f0a04SGreg Roach * Get a list of all marriage dates - for the family lists. 286*a25f0a04SGreg Roach * 287*a25f0a04SGreg Roach * @return Date[] 288*a25f0a04SGreg Roach */ 289*a25f0a04SGreg Roach public function getAllMarriageDates() { 290*a25f0a04SGreg Roach foreach (explode('|', WT_EVENTS_MARR) as $event) { 291*a25f0a04SGreg Roach if ($array = $this->getAllEventDates($event)) { 292*a25f0a04SGreg Roach return $array; 293*a25f0a04SGreg Roach } 294*a25f0a04SGreg Roach } 295*a25f0a04SGreg Roach 296*a25f0a04SGreg Roach return array(); 297*a25f0a04SGreg Roach } 298*a25f0a04SGreg Roach 299*a25f0a04SGreg Roach /** 300*a25f0a04SGreg Roach * Get a list of all marriage places - for the family lists. 301*a25f0a04SGreg Roach * 302*a25f0a04SGreg Roach * @return string[] 303*a25f0a04SGreg Roach */ 304*a25f0a04SGreg Roach public function getAllMarriagePlaces() { 305*a25f0a04SGreg Roach foreach (explode('|', WT_EVENTS_MARR) as $event) { 306*a25f0a04SGreg Roach if ($array = $this->getAllEventPlaces($event)) { 307*a25f0a04SGreg Roach return $array; 308*a25f0a04SGreg Roach } 309*a25f0a04SGreg Roach } 310*a25f0a04SGreg Roach 311*a25f0a04SGreg Roach return array(); 312*a25f0a04SGreg Roach } 313*a25f0a04SGreg Roach 314*a25f0a04SGreg Roach /** {@inheritdoc} */ 315*a25f0a04SGreg Roach public function getAllNames() { 316*a25f0a04SGreg Roach global $UNKNOWN_NN, $UNKNOWN_PN; 317*a25f0a04SGreg Roach 318*a25f0a04SGreg Roach if (is_null($this->_getAllNames)) { 319*a25f0a04SGreg Roach // Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc. 320*a25f0a04SGreg Roach if ($this->husb) { 321*a25f0a04SGreg Roach $husb_names = $this->husb->getAllNames(); 322*a25f0a04SGreg Roach } else { 323*a25f0a04SGreg Roach $husb_names = array( 324*a25f0a04SGreg Roach 0 => array( 325*a25f0a04SGreg Roach 'type' => 'BIRT', 326*a25f0a04SGreg Roach 'sort' => '@N.N.', 327*a25f0a04SGreg Roach 'full' => $UNKNOWN_PN, ' ', $UNKNOWN_NN, 328*a25f0a04SGreg Roach ), 329*a25f0a04SGreg Roach ); 330*a25f0a04SGreg Roach } 331*a25f0a04SGreg Roach foreach ($husb_names as $n => $husb_name) { 332*a25f0a04SGreg Roach $husb_names[$n]['script'] = I18N::textScript($husb_name['full']); 333*a25f0a04SGreg Roach } 334*a25f0a04SGreg Roach if ($this->wife) { 335*a25f0a04SGreg Roach $wife_names = $this->wife->getAllNames(); 336*a25f0a04SGreg Roach } else { 337*a25f0a04SGreg Roach $wife_names = array( 338*a25f0a04SGreg Roach 0 => array( 339*a25f0a04SGreg Roach 'type' => 'BIRT', 340*a25f0a04SGreg Roach 'sort' => '@N.N.', 341*a25f0a04SGreg Roach 'full' => $UNKNOWN_PN, ' ', $UNKNOWN_NN, 342*a25f0a04SGreg Roach ), 343*a25f0a04SGreg Roach ); 344*a25f0a04SGreg Roach } 345*a25f0a04SGreg Roach foreach ($wife_names as $n => $wife_name) { 346*a25f0a04SGreg Roach $wife_names[$n]['script'] = I18N::textScript($wife_name['full']); 347*a25f0a04SGreg Roach } 348*a25f0a04SGreg Roach // Add the matched names first 349*a25f0a04SGreg Roach foreach ($husb_names as $husb_name) { 350*a25f0a04SGreg Roach foreach ($wife_names as $wife_name) { 351*a25f0a04SGreg Roach if ($husb_name['type'] != '_MARNM' && $wife_name['type'] != '_MARNM' && $husb_name['script'] == $wife_name['script']) { 352*a25f0a04SGreg Roach $this->_getAllNames[] = array( 353*a25f0a04SGreg Roach 'type' => $husb_name['type'], 354*a25f0a04SGreg Roach 'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'], 355*a25f0a04SGreg Roach 'full' => $husb_name['full'] . ' + ' . $wife_name['full'], 356*a25f0a04SGreg Roach // No need for a fullNN entry - we do not currently store FAM names in the database 357*a25f0a04SGreg Roach ); 358*a25f0a04SGreg Roach } 359*a25f0a04SGreg Roach } 360*a25f0a04SGreg Roach } 361*a25f0a04SGreg Roach // Add the unmatched names second (there may be no matched names) 362*a25f0a04SGreg Roach foreach ($husb_names as $husb_name) { 363*a25f0a04SGreg Roach foreach ($wife_names as $wife_name) { 364*a25f0a04SGreg Roach if ($husb_name['type'] != '_MARNM' && $wife_name['type'] != '_MARNM' && $husb_name['script'] != $wife_name['script']) { 365*a25f0a04SGreg Roach $this->_getAllNames[] = array( 366*a25f0a04SGreg Roach 'type' => $husb_name['type'], 367*a25f0a04SGreg Roach 'sort' => $husb_name['sort'] . ' + ' . $wife_name['sort'], 368*a25f0a04SGreg Roach 'full' => $husb_name['full'] . ' + ' . $wife_name['full'], 369*a25f0a04SGreg Roach // No need for a fullNN entry - we do not currently store FAM names in the database 370*a25f0a04SGreg Roach ); 371*a25f0a04SGreg Roach } 372*a25f0a04SGreg Roach } 373*a25f0a04SGreg Roach } 374*a25f0a04SGreg Roach } 375*a25f0a04SGreg Roach 376*a25f0a04SGreg Roach return $this->_getAllNames; 377*a25f0a04SGreg Roach } 378*a25f0a04SGreg Roach 379*a25f0a04SGreg Roach /** {@inheritdoc} */ 380*a25f0a04SGreg Roach function formatListDetails() { 381*a25f0a04SGreg Roach return 382*a25f0a04SGreg Roach $this->format_first_major_fact(WT_EVENTS_MARR, 1) . 383*a25f0a04SGreg Roach $this->format_first_major_fact(WT_EVENTS_DIV, 1); 384*a25f0a04SGreg Roach } 385*a25f0a04SGreg Roach} 386