xref =$data['xref'];
$this->type =$data['type'];
$this->ged_id=$data['ged_id'];
$this->gedrec=$data['gedrec'];
} else {
// Construct from raw GEDCOM data
$this->gedrec=$data;
if (preg_match('/^0 (?:@('.WT_REGEX_XREF.')@ )?('.WT_REGEX_TAG.')/', $data, $match)) {
$this->xref=$match[1];
$this->type=$match[2];
}
}
//-- lookup the record from another gedcom
$remoterfn = get_gedcom_value('RFN', 1, $this->gedrec);
if (!empty($remoterfn)) {
$parts = explode(':', $remoterfn);
if (count($parts)==2) {
$servid = $parts[0];
$aliaid = $parts[1];
if (!empty($servid)&&!empty($aliaid)) {
$serviceClient = ServiceClient::getInstance($servid);
if (!is_null($serviceClient)) {
if (!$simple || $serviceClient->type=='local') {
$this->gedrec = $serviceClient->mergeGedcomRecord($aliaid, $this->gedrec, true);
}
}
}
}
}
//-- set the gedcom record a privatized version
$this->disp =canDisplayRecord($this->ged_id, $this->gedrec);
$this->gedrec=privatize_gedcom($this->gedrec);
}
// Get an instance of a GedcomRecord. We either specify
// an XREF (in the current gedcom), or we can provide a row
// from the database (if we anticipate the record hasn't
// been fetched previously).
static public function &getInstance($data) {
global $gedcom_record_cache, $GEDCOM;
$is_pending=false; // Did this record come from a pending edit
if (is_array($data)) {
$ged_id=$data['ged_id'];
$pid =$data['xref'];
} else {
$ged_id=get_id_from_gedcom($GEDCOM);
$pid =$data;
}
// Check the cache first
if (isset($gedcom_record_cache[$pid][$ged_id])) {
return $gedcom_record_cache[$pid][$ged_id];
}
// Look for the record in the database
if (!is_array($data)) {
if (version_compare(PHP_VERSION, '5.3', '>=')) {
// If we know what sort of object we are, we can query the table directly.
switch (get_called_class()) {
case 'Person':
$data=fetch_person_record($pid, $ged_id);
break;
case 'Family':
$data=fetch_family_record($pid, $ged_id);
break;
case 'Source':
$data=fetch_source_record($pid, $ged_id);
break;
case 'Media':
$data=fetch_media_record($pid, $ged_id);
break;
case 'Repository':
case 'Note':
$data=fetch_other_record($pid, $ged_id);
break;
default:
// Type unknown - try each of the five tables in turn....
$data=fetch_gedcom_record($pid, $ged_id);
break;
}
} else {
// Late-static-binding is unavailable in PHP 5.2, so we do not what what
// sort of object we are - try each of the five tables in turn....
$data=fetch_gedcom_record($pid, $ged_id);
}
// If we didn't find the record in the database, it may be remote
if (!$data && strpos($pid, ':')) {
list($servid, $remoteid)=explode(':', $pid);
$service=ServiceClient::getInstance($servid);
if ($service) {
// TYPE will be replaced with the type from the remote record
$data=$service->mergeGedcomRecord($remoteid, "0 @{$pid}@ TYPE\n1 RFN {$pid}", false);
}
}
// If we didn't find the record in the database, it may be new/pending
if (!$data && WT_USER_CAN_EDIT && ($data=find_gedcom_record($pid, $ged_id, true))!='') {
$is_pending=true;
}
// If we still didn't find it, it doesn't exist
if (!$data) {
return null;
}
}
// Create the object
if (is_array($data)) {
$type=$data['type'];
} elseif (preg_match('/^0 @'.WT_REGEX_XREF.'@ ('.WT_REGEX_TAG.')/', $data, $match)) {
$type=$match[1];
} else {
$type='';
}
switch($type) {
case 'INDI':
$object=new Person($data);
break;
case 'FAM':
$object=new Family($data);
break;
case 'SOUR':
$object=new Source($data);
break;
case 'OBJE':
$object=new Media($data);
break;
case 'REPO':
$object=new Repository($data);
break;
case 'NOTE':
$object=new Note($data);
break;
default:
$object=new GedcomRecord($data);
break;
}
// This is an object from the database, so indicate which gedcom it comes from.
$object->ged_id=$ged_id;
if ($is_pending) {
$object->setChanged(true);
}
// Store it in the cache
$gedcom_record_cache[$object->xref][$object->ged_id]=&$object;
//-- also store it using its reference id (sid:pid and local gedcom for remote links)
$gedcom_record_cache[$pid][$ged_id]=&$object;
return $object;
}
/**
* get the xref
* @return string returns the person ID
*/
public function getXref() {
return $this->xref;
}
/**
* get the gedcom file
* @return string returns the person ID
*/
public function getGedId() {
return $this->ged_id;
}
/**
* get the object type
* @return string returns the type of this object 'INDI','FAM', etc.
*/
public function getType() {
return $this->type;
}
/**
* get gedcom record
*/
public function getGedcomRecord() {
return $this->gedrec;
}
/**
* set gedcom record
*/
public function setGedcomRecord($gcRec) {
$this->gedrec = $gcRec;
}
/**
* set if this is a changed record from the gedcom file
* @param boolean $changed
*/
public function setChanged($changed) {
$this->changed = $changed;
}
/**
* get if this is a changed record from the gedcom file
* @return boolean
*/
public function getChanged() {
return $this->changed;
}
/**
* is this person from another server
* @return boolean return true if this person was linked from another server
*/
public function isRemote() {
if (is_null($this->rfn)) $this->rfn = get_gedcom_value('RFN', 1, $this->gedrec);
if (empty($this->rfn) || $this->xref!=$this->rfn) return false;
$parts = explode(':', $this->rfn);
if (count($parts)==2) {
return true;
}
return false;
}
/**
* check if this object is equal to the given object
* @param GedcomRecord $obj
*/
public function equals(&$obj) {
return !is_null($obj) && $this->xref==$obj->getXref();
}
// Generate a URL to this record, suitable for use in HTML
public function getHtmlUrl() {
return self::_getLinkUrl('gedcomrecord.php?famid=', '&');
}
// Generate a URL to this record, suitable for use in javascript, HTTP headers, etc.
public function getRawUrl() {
return self::_getLinkUrl('gedcomrecord.php?famid=', '&');
}
protected function _getLinkUrl($link, $separator) {
if ($this->ged_id) {
// If the record was created from the database, we know the gedcom
$url=$link.rawurlencode($this->getXref()).$separator.'ged='.rawurlencode(get_gedcom_from_id($this->ged_id));
} else {
// If the record was created from a text string, assume the current gedcom
$url=$link.rawurlencode($this->getXref()).$separator.'ged='.rawurlencode(WT_GEDCOM);
}
if ($this->isRemote()) {
list($servid, $aliaid)=explode(':', $this->rfn);
if ($aliaid && $servid) {
$serviceClient = ServiceClient::getInstance($servid);
if ($serviceClient) {
$surl = $serviceClient->getURL();
$url = $link.$aliaid;
if ($serviceClient->getType()=='remote') {
if (!empty($surl)) {
$url = dirname($surl).'/'.$url;
}
} else {
$url = $surl.$url;
}
$gedcom = $serviceClient->getGedfile();
if ($gedcom) {
$url .= $separator.'ged='.rawurlencode($gedcom);
}
}
}
}
return $url;
}
/**
* Get the title that should be used in the link
* @return string
*/
public function getLinkTitle() {
$title = get_gedcom_setting($this->ged_id, 'title');
if ($this->isRemote()) {
$parts = explode(':', $this->rfn);
if (count($parts)==2) {
$servid = $parts[0];
$aliaid = $parts[1];
if (!empty($servid)&&!empty($aliaid)) {
$serviceClient = ServiceClient::getInstance($servid);
if (!empty($serviceClient)) {
$title = $serviceClient->getTitle();
}
}
}
}
return $title;
}
// Get an HTML link to this object, for use in sortable lists.
public function getXrefLink($target='') {
global $SEARCH_SPIDER;
if (empty($SEARCH_SPIDER)) {
if ($target) {
$target='target="'.$target.'"';
}
return ''.$this->getXref().'';
} else {
return $this->getXref();
}
}
/**
* return an absolute url for linking to this record from another site
*
*/
public function getAbsoluteLinkUrl() {
return WT_SERVER_NAME.WT_SCRIPT_PATH.$this->getHtmlUrl();
}
/**
* check if this record has been marked for deletion
* @return boolean
*/
public function isMarkedDeleted() {
$tmp=WT_DB::prepare(
"SELECT new_gedcom".
" FROM `##change`".
" WHERE status='pending' AND gedcom_id=? AND xref=?".
" ORDER BY change_id desc".
" LIMIT 1"
)->execute(array($this->ged_id, $this->xref))->fetchOne();
return $tmp==='';
}
/**
* Can the details of this record be shown?
* @return boolean
*/
public function canDisplayDetails() {
return $this->disp;
}
/**
* Can the name of this record be shown?
* @return boolean
*/
public function canDisplayName() {
return $this->dispname;
}
// Convert a name record into sortable and listable versions. This default
// should be OK for simple record types. INDI records will need to redefine it.
protected function _addName($type, $value, $gedrec) {
$this->_getAllNames[]=array(
'type'=>$type,
'full'=>$value,
'list'=>$value,
'sort'=>preg_replace('/([0-9]+)/e', 'substr("000000000\\1", -10)', $value)
);
}
// Get all the names of a record, including ROMN, FONE and _HEB alternatives.
// Records without a name (e.g. FAM) will need to redefine this function.
//
// Parameters: the level 1 fact containing the name.
// Return value: an array of name structures, each containing
// ['type'] = the gedcom fact, e.g. NAME, TITL, FONE, _HEB, etc.
// ['full'] = the name as specified in the record, e.g. 'Vincent van Gogh' or 'John Unknown'
// ['list'] = a version of the name as might appear in lists, e.g. 'van Gogh, Vincent' or 'Unknown, John'
// ['sort'] = a sortable version of the name (not for display), e.g. 'Gogh, Vincent' or '@N.N., John'
protected function _getAllNames($fact='!', $level=1) {
global $WORD_WRAPPED_NOTES;
if (is_null($this->_getAllNames)) {
$this->_getAllNames=array();
if ($this->canDisplayName()) {
$sublevel=$level+1;
$subsublevel=$sublevel+1;
if (preg_match_all("/^{$level} ({$fact}) (.+)((\n[{$sublevel}-9].+)*)/m", $this->gedrec, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$this->_addName($match[1], $match[2] ? $match[2] : $this->getFallBackName(), $match[0]);
if ($match[3] && preg_match_all("/^{$sublevel} (ROMN|FONE|_\w+) (.+)((\n[{$subsublevel}-9].+)*)/m", $match[3], $submatches, PREG_SET_ORDER)) {
foreach ($submatches as $submatch) {
$this->_addName($submatch[1], $submatch[2] ? $submatch[2] : $this->getFallBackName(), $submatch[0]);
}
}
}
} else {
$this->_addName($this->getType(), $this->getFallBackName(), null);
}
} else {
$this->_addName($this->getType(), i18n::translate('Private'), null);
}
}
return $this->_getAllNames;
}
// Derived classes should redefine this function, otherwise the object will have no name
public function getAllNames() {
return $this->_getAllNames('!', 1);
}
// If this object has no name, what do we call it?
public function getFallBackName() {
return $this->getXref();
}
// Which of the (possibly several) names of this record is the primary one.
public function getPrimaryName() {
if (is_null($this->_getPrimaryName)) {
// Generally, the first name is the primary one....
$this->_getPrimaryName=0;
// ....except when the language/name use different character sets
if (count($this->getAllNames())>1) {
switch (WT_LOCALE) {
case 'el':
foreach ($this->getAllNames() as $n=>$name) {
if ($name['type']!='_MARNM' && utf8_script($name['sort'])=='greek') {
$this->_getPrimaryName=$n;
break;
}
}
break;
case 'ru':
foreach ($this->getAllNames() as $n=>$name) {
if ($name['type']!='_MARNM' && utf8_script($name['sort'])=='russian') {
$this->_getPrimaryName=$n;
break;
}
}
break;
case 'he':
foreach ($this->getAllNames() as $n=>$name) {
if ($name['type']!='_MARNM' && utf8_script($name['sort'])=='hebrew') {
$this->_getPrimaryName=$n;
break;
}
}
break;
case 'ar':
foreach ($this->getAllNames() as $n=>$name) {
if ($name['type']!='_MARNM' && utf8_script($name['sort'])=='arabic') {
$this->_getPrimaryName=$n;
break;
}
}
break;
default:
foreach ($this->getAllNames() as $n=>$name) {
if ($name['type']!='_MARNM' && utf8_script($name['sort'])=='latin') {
$this->_getPrimaryName=$n;
break;
}
}
break;
}
}
}
return $this->_getPrimaryName;
}
// Which of the (possibly several) names of this record is the secondary one.
public function getSecondaryName() {
if (is_null($this->_getSecondaryName)) {
// Generally, the primary and secondary names are the same
$this->_getSecondaryName=$this->getPrimaryName();
// ....except when there are names with different character sets
$all_names=$this->getAllNames();
if (count($all_names)>1) {
$primary_script=utf8_script($all_names[$this->getPrimaryName()]['sort']);
foreach ($all_names as $n=>$name) {
if ($n!=$this->getPrimaryName() && $name['type']!='_MARNM' && utf8_script($name['sort'])!=$primary_script) {
$this->_getSecondaryName=$n;
break;
}
}
}
}
return $this->_getSecondaryName;
}
// Allow the choice of primary name to be overidden, e.g. in a search result
public function setPrimaryName($n) {
$this->_getPrimaryName=$n;
$this->_getSecondaryName=null;
}
// Allow native PHP functions such as array_intersect() to work with objects
public function __toString() {
return $this->xref.'@'.$this->ged_id;
}
// Static helper function to sort an array of objects by name
// Records whose names cannot be displayed are sorted at the end.
static function Compare($x, $y) {
if ($x->canDisplayName()) {
if ($y->canDisplayName()) {
return utf8_strcmp($x->getSortName(), $y->getSortName());
} else {
return -1; // only $y is private
}
} else {
if ($y->canDisplayName()) {
return 1; // only $x is private
} else {
return 0; // both $x and $y private
}
}
}
// Static helper function to sort an array of objects by ID
static function CompareId($x, $y) {
return strnatcasecmp($x->getXref(), $y->getXref());
}
// Static helper function to sort an array of objects by Change Date
static function CompareChanDate($x, $y) {
$chan_x = $x->getChangeEvent();
$chan_y = $y->getChangeEvent();
$tmp=GedcomDate::Compare($chan_x->getDate(), $chan_y->getDate());
if ($tmp) {
return $tmp;
} else {
if (
preg_match('/^\d\d:\d\d:\d\d/', get_gedcom_value('DATE:TIME', 2, $chan_x->getGedcomRecord(), '', false).':00', $match_x) &&
preg_match('/^\d\d:\d\d:\d\d/', get_gedcom_value('DATE:TIME', 2, $chan_y->getGedcomRecord(), '', false).':00', $match_y)
) {
return strcmp($match_x[0], $match_y[0]);
} else {
return 0;
}
}
}
// Get the three variants of the name
public function getFullName() {
if ($this->canDisplayName()) {
$tmp=$this->getAllNames();
return $tmp[$this->getPrimaryName()]['full'];
} else {
return i18n::translate('Private');
}
}
public function getSortName() {
// The sortable name is never displayed, no need to call canDisplayName()
$tmp=$this->getAllNames();
return $tmp[$this->getPrimaryName()]['sort'];
}
public function getListName() {
if ($this->canDisplayName()) {
$tmp=$this->getAllNames();
return $tmp[$this->getPrimaryName()]['list'];
} else {
return i18n::translate('Private');
}
}
// Get the fullname in an alternative character set
public function getAddName() {
if ($this->canDisplayName() && $this->getPrimaryName()!=$this->getSecondaryName()) {
$all_names=$this->getAllNames();
return $all_names[$this->getSecondaryName()]['full'];
} else {
return null;
}
}
//////////////////////////////////////////////////////////////////////////////
// Format this object for display in a list
// If $find is set, then we are displaying items from a selection list.
// $name allows us to use something other than the record name.
//////////////////////////////////////////////////////////////////////////////
public function format_list($tag='li', $find=false, $name=null) {
if (is_null($name)) {
$name=($tag=='li') ? $this->getListName() : $this->getFullName();
}
$dir=begRTLText($name) ? 'rtl' : 'ltr';
$html=''.$name.'';
$html.=$this->format_list_details();
$html='<'.$tag.' class="'.$dir.'" dir="'.$dir.'">'.$html.''.$tag.'>';
return $html;
}
// This function should be redefined in derived classes to show any major
// identifying characteristics of this record.
public function format_list_details() {
return '';
}
// Extract/format the first fact from a list of facts.
public function format_first_major_fact($facts, $style) {
foreach ($this->getAllFactsByType(explode('|', $facts)) as $event) {
// Only display if it has a date or place (or both)
if ($event->getDate() || $event->getPlace()) {
switch ($style) {
case 1:
return '
'.$event->getLabel().' '.format_fact_date($event).format_fact_place($event).'';
case 2:
return '