_getEstimatedDeathDate=$tmp2;
} else {
$this->_getEstimatedDeathDate=new WT_Date(''); // always return a date object
}
} else {
$this->_getEstimatedDeathDate=new WT_Date(''); // always return a date object
}
}
}
return $this->_getEstimatedDeathDate;
}
/**
* get the sex
* @return string return M, F, or U
*/
// Use the un-privatised gedcom record. We call this function during
// the privatize-gedcom function, and we are allowed to know this.
function getSex() {
if (is_null($this->sex)) {
if (preg_match('/\n1 SEX ([MF])/', $this->gedcom, $match)) {
$this->sex=$match[1];
} else {
$this->sex='U';
}
}
return $this->sex;
}
/**
* get the person's sex image
* @return string
*/
function getSexImage($size='small', $style='', $title='') {
return self::sexImage($this->getSex(), $size, $style, $title);
}
static function sexImage($sex, $size='small', $style='', $title='') {
return '';
}
function getBoxStyle() {
$tmp=array('M'=>'','F'=>'F', 'U'=>'NN');
return 'person_box'.$tmp[$this->getSex()];
}
/**
* set a label for this person
* The label can be used when building a list of people
* to display the relationship between this person
* and the person listed on the page
* @param string $label
*/
function setLabel($label) {
$this->label = $label;
}
/**
* get the label for this person
* The label can be used when building a list of people
* to display the relationship between this person
* and the person listed on the page
* @param string $elderdate optional elder sibling birthdate to calculate gap
* @param int $counter optional children counter
* @return string
*/
function getLabel($elderdate='', $counter=0) {
$label = '';
$gap = 0;
if (is_object($elderdate) && $elderdate->isOK()) {
$p2 = $this->getBirthDate();
if ($p2->isOK()) {
$gap = $p2->MinJD()-$elderdate->MinJD(); // days
$label .= "";
// warning if negative gap : wrong order
if ($gap<0 && $counter>0) $label .= ' ';
// warning if gap<6 months
if ($gap>1 && $gap<180 && $counter>0) $label .= ' ';
// children with same date means twin
/**if ($gap==0 && $counter>1) {
if ($this->getSex()=='M') $label .= WT_I18N::translate('Twin brother');
else if ($this->getSex()=='F') $label .= WT_I18N::translate('Twin sister');
else $label .= WT_I18N::translate('Twin');
}**/
// gap in years or months
$gap = round($gap*12/365.25); // months
if (($gap==12)||($gap==-12)) {
$label .= WT_I18N::plural('%d year', '%d years', round($gap/12), round($gap/12));
} elseif ($gap>23 or $gap<-23) {
$label .= WT_I18N::plural('%d year', '%d years', round($gap/12), round($gap/12));
} elseif ($gap!=0) {
$label .= WT_I18N::plural('%d month', '%d months', $gap, $gap);
}
$label .= '
';
}
}
// I18N: This is an abbreviation for a number. i.e. #7 means number 7
if ($counter) $label .= ''.WT_I18N::translate('#%d', $counter).'
';
$label .= $this->label;
if ($gap!=0 && $counter<1) $label .= '
';
return $label;
}
// Get a list of this person's spouse families
function getSpouseFamilies($access_level=WT_USER_ACCESS_LEVEL) {
global $SHOW_PRIVATE_RELATIONSHIPS;
$families = array();
foreach ($this->getFacts('FAMS', $access_level) as $fact) {
$family = $fact->getTarget();
if ($family && ($SHOW_PRIVATE_RELATIONSHIPS || $family->canShow($access_level))) {
$families[] = $family;
}
}
return $families;
}
/**
* get the current spouse of this person
* The current spouse is defined as the spouse from the latest family.
* The latest family is defined as the last family in the GEDCOM record
* @return Person this person's spouse
*/
function getCurrentSpouse() {
$tmp=$this->getSpouseFamilies();
$family = end($tmp);
if ($family) {
return $family->getSpouse($this);
} else {
return null;
}
}
// Get a count of the children for this individual
function getNumberOfChildren() {
if (preg_match('/\n1 NCHI (\d+)(?:\n|$)/', $this->getGedcom(), $match)) {
return $match[1];
} else {
$children=array();
foreach ($this->getSpouseFamilies() as $fam) {
foreach ($fam->getChildren() as $child) {
$children[$child->getXref()]=true;
}
}
return count($children);
}
}
// Get a list of this person's child families (i.e. their parents)
function getChildFamilies($access_level=WT_USER_ACCESS_LEVEL) {
global $SHOW_PRIVATE_RELATIONSHIPS;
$families = array();
foreach ($this->getFacts('FAMC', $access_level) as $fact) {
$family = $fact->getTarget();
if ($family && ($SHOW_PRIVATE_RELATIONSHIPS || $family->canShow($access_level))) {
$families[] = $family;
}
}
return $families;
}
/**
* get primary family with parents
* @return Family object
*/
function getPrimaryChildFamily() {
$families=$this->getChildFamilies();
switch (count($families)) {
case 0:
return null;
case 1:
return reset($families);
default:
// If there is more than one FAMC record, choose the preferred parents:
// a) records with '2 _PRIMARY'
foreach ($families as $famid=>$fam) {
if (preg_match("/\n1 FAMC @{$famid}@\n(?:[2-9].*\n)*(?:2 _PRIMARY Y)/", $this->getGedcom())) {
return $fam;
}
}
// b) records with '2 PEDI birt'
foreach ($families as $famid=>$fam) {
if (preg_match("/\n1 FAMC @{$famid}@\n(?:[2-9].*\n)*(?:2 PEDI birth)/", $this->getGedcom())) {
return $fam;
}
}
// c) records with no '2 PEDI'
foreach ($families as $famid=>$fam) {
if (!preg_match("/\n1 FAMC @{$famid}@\n(?:[2-9].*\n)*(?:2 PEDI)/", $this->getGedcom())) {
return $fam;
}
}
// d) any record
return reset($families);
}
}
// Get a list of step-parent families
function getChildStepFamilies() {
$step_families=array();
$families=$this->getChildFamilies();
foreach ($families as $family) {
$father=$family->getHusband();
if ($father) {
foreach ($father->getSpouseFamilies() as $step_family) {
if (!in_array($step_family, $families, true)) {
$step_families[]=$step_family;
}
}
}
$mother=$family->getWife();
if ($mother) {
foreach ($mother->getSpouseFamilies() as $step_family) {
if (!in_array($step_family, $families, true)) {
$step_families[]=$step_family;
}
}
}
}
return $step_families;
}
// Get a list of step-child families
function getSpouseStepFamilies() {
$step_families=array();
$families=$this->getSpouseFamilies();
foreach ($families as $family) {
foreach ($family->getSpouse($this)->getSpouseFamilies() as $step_family) {
if (!in_array($step_family, $families, true)) {
$step_families[]=$step_family;
}
}
}
return $step_families;
}
// A label for a parental family group
function getChildFamilyLabel(WT_Family $family) {
if (preg_match('/\n1 FAMC @'.$family->getXref().'@(?:\n[2-9].*)*\n2 PEDI (.+)/', $this->getGedcom(), $match)) {
// A specified pedigree
return WT_Gedcom_Code_Pedi::getChildFamilyLabel($match[1]);
} else {
// Default (birth) pedigree
return WT_Gedcom_Code_Pedi::getChildFamilyLabel('');
}
}
// Create a label for a step family
function getStepFamilyLabel(WT_Family $family) {
foreach ($this->getChildFamilies() as $fam) {
if (!$fam->equals($family)) {
if ((is_null($fam->getHusband()) || !$fam->getHusband()->equals($family->getHusband())) && (is_null($fam->getWife()) || $fam->getWife()->equals($family->getWife()))) {
if ($family->getHusband()) {
if ($family->getWife()->getSex()=='F') {
return /* I18N: A step-family. %s is an individual’s name */ WT_I18N::translate('Mother’s family with %s', $family->getHusband()->getFullName());
} else {
return /* I18N: A step-family. %s is an individual’s name */ WT_I18N::translate('Father’s family with %s', $family->getHusband()->getFullName());
}
} else {
if ($family->getWife()->getSex()=='F') {
return /* I18N: A step-family. */ WT_I18N::translate('Mother’s family with an unknown individual');
} else {
return /* I18N: A step-family. */ WT_I18N::translate('Father’s family with an unknown individual');
}
}
} elseif ((is_null($fam->getWife()) || !$fam->getWife()->equals($family->getWife())) && (is_null($fam->getHusband()) || $fam->getHusband()->equals($family->getHusband()))) {
if ($family->getWife()) {
if ($family->getHusband()->getSex()=='F') {
return /* I18N: A step-family. %s is an individual’s name */ WT_I18N::translate('Mother’s family with %s', $family->getWife()->getFullName());
} else {
return /* I18N: A step-family. %s is an individual’s name */ WT_I18N::translate('Father’s family with %s', $family->getWife()->getFullName());
}
} else {
if ($family->getHusband()->getSex()=='F') {
return /* I18N: A step-family. */ WT_I18N::translate('Mother’s family with an unknown individual');
} else {
return /* I18N: A step-family. */ WT_I18N::translate('Father’s family with an unknown individual');
}
}
} elseif ($family->getWife()==$fam->getWife() && $family->getHusband()==$fam->getHusband() || $family->getWife()==$fam->getHusband() && $family->getHusband()==$fam->getWife()) {
// Same parents - but a different family record.
return WT_I18N::translate('Family with parents');
}
}
}
// It should not be possible to get here
throw new Exception('Invalid family in WT_Individual::getStepFamilyLabel(' . $family . ')');
}
// TODO - this function doesn't belong in this class
function getSpouseFamilyLabel(WT_Family $family) {
return /* I18N: %s is the spouse name */ WT_I18N::translate('Family with %s', $family->getSpouse($this)->getFullName());
}
/**
* get primary parents names for this person
* @param string $classname optional css class
* @param string $display optional css style display
* @return string a div block with father & mother names
*/
function getPrimaryParentsNames($classname='', $display='') {
$fam = $this->getPrimaryChildFamily();
if (!$fam) return '';
$txt = 'getHusband();
if ($husb) {
// Temporarily reset the 'prefered' display name, as we always
// want the default name, not the one selected for display on the indilist.
$primary=$husb->getPrimaryName();
$husb->setPrimaryName(null);
$txt .= /* I18N: %s is the name of an individual’s father */ WT_I18N::translate('Father: %s', $husb->getFullName()).'
';
$husb->setPrimaryName($primary);
}
$wife = $fam->getWife();
if ($wife) {
// Temporarily reset the 'prefered' display name, as we always
// want the default name, not the one selected for display on the indilist.
$primary=$wife->getPrimaryName();
$wife->setPrimaryName(null);
$txt .= /* I18N: %s is the name of an individual’s mother */ WT_I18N::translate('Mother: %s', $wife->getFullName());
$wife->setPrimaryName($primary);
}
$txt .= '
';
return $txt;
}
// If this object has no name, what do we call it?
function getFallBackName() {
return '@P.N. /@N.N./';
}
// Convert a name record into 'full' and 'sort' versions.
// Use the NAME field to generate the 'full' version, as the
// gedcom spec says that this is the person's name, as they would write it.
// Use the SURN field to generate the sortable names. Note that this field
// may also be used for the 'true' surname, perhaps spelt differently to that
// recorded in the NAME field. e.g.
//
// 1 NAME Robert /de Gliderow/
// 2 GIVN Robert
// 2 SPFX de
// 2 SURN CLITHEROW
// 2 NICK The Bald
//
// full=>'Robert de Gliderow 'The Bald''
// sort=>'CLITHEROW, ROBERT'
//
// Handle multiple surnames, either as;
// 1 NAME Carlos /Vasquez/ y /Sante/
// or
// 1 NAME Carlos /Vasquez y Sante/
// 2 GIVN Carlos
// 2 SURN Vasquez,Sante
protected function _addName($type, $full, $gedcom) {
global $UNKNOWN_NN, $UNKNOWN_PN;
////////////////////////////////////////////////////////////////////////////
// Extract the structured name parts - use for "sortable" names and indexes
////////////////////////////////////////////////////////////////////////////
$sublevel=1+(int)$gedcom[0];
$NPFX=preg_match("/\n{$sublevel} NPFX (.+)/", $gedcom, $match) ? $match[1] : '';
$GIVN=preg_match("/\n{$sublevel} GIVN (.+)/", $gedcom, $match) ? $match[1] : '';
$SURN=preg_match("/\n{$sublevel} SURN (.+)/", $gedcom, $match) ? $match[1] : '';
$NSFX=preg_match("/\n{$sublevel} NSFX (.+)/", $gedcom, $match) ? $match[1] : '';
$NICK=preg_match("/\n{$sublevel} NICK (.+)/", $gedcom, $match) ? $match[1] : '';
// SURN is an comma-separated list of surnames...
if ($SURN) {
$SURNS=preg_split('/ *, */', $SURN);
} else {
$SURNS=array();
}
// ...so is GIVN - but nobody uses it like that
$GIVN=str_replace('/ *, */', ' ', $GIVN);
////////////////////////////////////////////////////////////////////////////
// Extract the components from NAME - use for the "full" names
////////////////////////////////////////////////////////////////////////////
// Fix bad slashes. e.g. 'John/Smith' => 'John/Smith/'
if (substr_count($full, '/')%2==1) {
$full=$full.'/';
} else {
$full=$full;
}
// GEDCOM uses "//" to indicate an unknown surname
$full=preg_replace('/\/\//', '/@N.N./', $full);
// Extract the surname.
// Note, there may be multiple surnames, e.g. Jean /Vasquez/ y /Cortes/
if (preg_match('/\/.*\//', $full, $match)) {
$surname=str_replace('/', '', $match[0]);
} else {
$surname='';
}
// If we don't have a SURN record, extract it from the NAME
if (!$SURNS) {
if (preg_match_all('/\/([^\/]*)\//', $full, $matches)) {
// There can be many surnames, each wrapped with '/'
$SURNS=$matches[1];
foreach ($SURNS as $n=>$SURN) {
// Remove surname prefixes, such as "van de ", "d'" and "'t " (lower case only)
$SURNS[$n]=preg_replace('/^(?:[a-z]+ |[a-z]+\' ?|\'[a-z]+ )+/', '', $SURN);
}
} else {
// It is valid not to have a surname at all
$SURNS=array('');
}
}
// If we don't have a GIVN record, extract it from the NAME
if (!$GIVN) {
$GIVN=preg_replace(
array(
'/ ?\/.*\/ ?/', // remove surname
'/ ?".+"/', // remove nickname
'/ {2,}/', // multiple spaces, caused by the above
'/^ | $/', // leading/trailing spaces, caused by the above
),
array(
' ',
' ',
' ',
'',
),
$full
);
}
// Add placeholder for unknown given name
if (!$GIVN) {
$GIVN='@P.N.';
$pos=strpos($full, '/');
$full=substr($full, 0, $pos).'@P.N. '.substr($full, $pos);
}
// The NPFX field might be present, but not appear in the NAME
if ($NPFX && strpos($full, "$NPFX ")!==0) {
$full="$NPFX $full";
}
// The NSFX field might be present, but not appear in the NAME
if ($NSFX && strrpos($full, " $NSFX")!==strlen($full)-strlen(" $NSFX")) {
$full="$full $NSFX";
}
// GEDCOM nicknames should be specificied in a NICK field, or in the
// NAME filed, surrounded by ASCII quotes (or both).
if ($NICK) {
// NICK field found. Add localised quotation marks.
// GREG 28/Jan/12 - these localised quotation marks apparantly cause problems with LTR names on RTL
// pages and vice-versa. Just use straight ASCII quotes. Keep the old code, so that we keep the
// translations.
if (false) {
$QNICK=/* I18N: Place a nickname in quotation marks */ WT_I18N::translate('“%s”', $NICK);
} else {
$QNICK='"'.$NICK.'"';
}
if (preg_match('/(^| |"|«|“|\'|‹|‘|„)'.preg_quote($NICK, '/').'( |"|»|”|\'|›|’|”|$)/', $full)) {
// NICK present in name. Localise ASCII quotes (but leave others).
// GREG 28/Jan/12 - redundant - see comment above.
// $full=str_replace('"'.$NICK.'"', $QNICK, $full);
} else {
// NICK not present in NAME.
$pos=strpos($full, '/');
if ($pos===false) {
// No surname - append it
$full.=' '.$QNICK;
} else {
// Insert before surname
$full=substr($full, 0, $pos).$QNICK.' '.substr($full, $pos);
}
}
}
// Remove slashes - they don't get displayed
// $fullNN keeps the @N.N. placeholders, for the database
// $full is for display on-screen
$fullNN=str_replace('/', '', $full);
// Insert placeholders for any missing/unknown names
if (strpos($full, '@N.N.')!==false) {
$full=str_replace('@N.N.', $UNKNOWN_NN, $full);
}
if (strpos($full, '@P.N.')!==false) {
$full=str_replace('@P.N.', $UNKNOWN_PN, $full);
}
$full=''.preg_replace('/\/([^\/]*)\//', '$1', htmlspecialchars($full)).'';
// The standards say you should use a suffix of '*' for preferred name
$full=preg_replace('/([^ >]*)\*/', '\\1', $full);
// Remove prefered-name indicater - they don't go in the database
$GIVN =str_replace('*', '', $GIVN);
$fullNN=str_replace('*', '', $fullNN);
foreach ($SURNS AS $SURN) {
// Scottish 'Mc and Mac ' prefixes both sort under 'Mac'
if (strcasecmp(substr($SURN, 0, 2), 'Mc')==0) {
$SURN=substr_replace($SURN, 'Mac', 0, 2);
} elseif (strcasecmp(substr($SURN, 0, 4), 'Mac ')==0) {
$SURN=substr_replace($SURN, 'Mac', 0, 4);
}
$this->_getAllNames[]=array(
'type'=>$type,
'sort'=>$SURN.','.$GIVN,
'full'=>$full, // This is used for display
'fullNN'=>$fullNN, // This goes into the database
'surname'=>$surname, // This goes into the database
'givn'=>$GIVN, // This goes into the database
'surn'=>$SURN, // This goes into the database
);
}
}
// Get an array of structures containing all the names in the record
public function getAllNames() {
return $this->_getAllNames('NAME', 1);
}
// Extra info to display when displaying this record in a list of
// selection items or favorites.
function format_list_details() {
return
$this->format_first_major_fact(WT_EVENTS_BIRT, 1).
$this->format_first_major_fact(WT_EVENTS_DEAT, 1);
}
// create a short name for compact display on charts
public function getShortName() {
global $bwidth, $SHOW_HIGHLIGHT_IMAGES, $UNKNOWN_NN, $UNKNOWN_PN;
// Estimate number of characters that can fit in box. Calulates to 28 characters in webtrees theme, or 34 if no thumbnail used.
if ($SHOW_HIGHLIGHT_IMAGES) {
$char = intval(($bwidth-40)/6.5);
} else {
$char = ($bwidth/6.5);
}
if ($this->canShowName()) {
$tmp=$this->getAllNames();
$givn = $tmp[$this->getPrimaryName()]['givn'];
$surn = $tmp[$this->getPrimaryName()]['surname'];
$new_givn = explode(' ', $givn);
$count_givn = count($new_givn);
$len_givn = utf8_strlen($givn);
$len_surn = utf8_strlen($surn);
$len = $len_givn + $len_surn;
$i = 1;
while ($len > $char && $i<=$count_givn) {
$new_givn[$count_givn-$i] = utf8_substr($new_givn[$count_givn-$i],0,1);
$givn = implode(' ', $new_givn);
$len_givn = utf8_strlen($givn);
$len = $len_givn + $len_surn;
$i++;
}
$max_surn = $char-$i*2;
if ($len_surn > $max_surn) {
$surn = substr($surn, 0, $max_surn).'…';
$len_surn = utf8_strlen($surn);
}
$shortname = str_replace(
array('@P.N.', '@N.N.'),
array($UNKNOWN_PN, $UNKNOWN_NN),
$givn.' '.$surn
);
return $shortname;
} else {
return WT_I18N::translate('Private');
}
}
}