diff options
| author | fisharebest <fisharebest@gmail.com> | 2011-01-01 10:46:42 +0000 |
|---|---|---|
| committer | fisharebest <fisharebest@gmail.com> | 2011-01-01 10:46:42 +0000 |
| commit | 3f00e12930f917fcd52629041b942db9bec5f701 (patch) | |
| tree | 7f29e3ba19d410454032317a7df98de212cdc550 /library | |
| parent | 8bc6f5e19764516c1e997578ea04b08e77d182df (diff) | |
| download | webtrees-3f00e12930f917fcd52629041b942db9bec5f701.tar.gz webtrees-3f00e12930f917fcd52629041b942db9bec5f701.tar.bz2 webtrees-3f00e12930f917fcd52629041b942db9bec5f701.zip | |
Refactoring class names and use autoloading.
Diffstat (limited to 'library')
24 files changed, 9770 insertions, 0 deletions
diff --git a/library/WT/Controller/AdvancedSearch.php b/library/WT/Controller/AdvancedSearch.php new file mode 100644 index 0000000000..926205bdc6 --- /dev/null +++ b/library/WT/Controller/AdvancedSearch.php @@ -0,0 +1,447 @@ +<?php +// Controller for the Advanced Search Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_ADVANCED_SEARCH_PHP', ''); + +class WT_Controller_AdvancedSearch extends WT_Controller_Search { + + var $fields = array(); + var $values = array(); + var $plusminus = array(); + var $errors = array(); + + /** + * constructor + */ + function __construct() { + parent::__construct(); + } + /** + * Initialization function + */ + function init() { + parent :: init(); + if (empty($_REQUEST['action'])) $this->action="advanced"; + if ($this->action=="advanced") { + if (isset($_REQUEST['fields'])) $this->fields = $_REQUEST['fields']; + if (isset($_REQUEST['values'])) $this->values = $_REQUEST['values']; + if (isset($_REQUEST['plusminus'])) $this->plusminus = $_REQUEST['plusminus']; + $this->reorderFields(); + $this->advancedSearch(); + } + if (count($this->fields)==0) { + $this->fields = explode(",",get_gedcom_setting(WT_GED_ID, 'SEARCH_FACTS_DEFAULT')); + $this->fields[] = "FAMC:HUSB:NAME:GIVN:SDX"; + $this->fields[] = "FAMC:HUSB:NAME:SURN:SDX"; + $this->fields[] = "FAMC:WIFE:NAME:GIVN:SDX"; + $this->fields[] = "FAMC:WIFE:NAME:SURN:SDX"; + } + } + + function getOtherFields() { + $ofields = array("ADDR","ADDR:CITY","ADDR:STAE","ADDR:CTRY","ADDR:POST", + "AFN","EMAIL","FAX", + "CHR:DATE","CHR:PLAC", + "_BRTM:DATE","_BRTM:PLAC", + "BURI:DATE","BURI:PLAC", + "CREM:DATE","CREM:PLAC", + "ADOP:DATE","ADOP:PLAC", + "BAPM:DATE","BAPM:PLAC","BARM:DATE","BARM:PLAC","BASM:DATE","BASM:PLAC","BLES:DATE","BLES:PLAC", + "EVEN","EVEN:DATE","EVEN:PLAC", + "FCOM:DATE","FCOM:PLAC", + "_MILI","_MILI:DATE","_MILI:PLAC", + "ORDN:DATE","ORDN:PLAC", + "NATU:DATE","NATU:PLAC","EMIG:DATE","EMIG:PLAC","IMMI:DATE","IMMI:PLAC", + "CENS:DATE","CENS:PLAC", + "CAST","DSCR", + "NATI","OCCU","RELI","TITL", + "RESI","RESI:DATE","RESI:PLAC", + "NAME:NICK","NAME:_MARNM","NAME:_HEB","NAME:ROMN", + "FAMS:CENS:DATE","FAMS:CENS:PLAC","FAMS:DIV:DATE","FAMS:DIV:PLAC", + "NOTE","FAMS:NOTE", + "BAPL:DATE","BAPL:PLAC","BAPL:TEMP", + "ENDL:DATE","ENDL:PLAC","ENDL:TEMP", + "SLGC:DATE","SLGC:PLAC","SLGC:TEMP", + "FAMS:SLGS:DATE","FAMS:SLGS:PLAC","FAMS:SLGS:TEMP" + ); + // Allow (some of) the user-specified fields to be selected + foreach (explode(',', get_gedcom_setting(WT_GED_ID, 'INDI_FACTS_ADD')) as $fact) { + if ( + $fact!='BIRT' && + $fact!='DEAT' && + !in_array($fact, $ofields) && + !in_array("{$fact}:DATE", $ofields) && + !in_array("{$fact}:PLAC", $ofields) + ) { + $ofields[]=$fact; + } + } + return $ofields; + } + + function getPageTitle() { + if ($this->action=="advanced") return i18n::translate('Advanced search'); + else parent :: getPageTitle(); + } + + function getValue($i) { + $val = ""; + if (isset($this->values[$i])) $val = $this->values[$i]; + return $val; + } + + function getField($i) { + $val = ""; + if (isset($this->fields[$i])) $val = htmlentities($this->fields[$i]); + return $val; + } + + function getIndex($field) { + return array_search($field, $this->fields); + } + + function getLabel($tag) { + return translate_fact(str_replace(':SDX', '', $tag)); + } + + function reorderFields() { + $i = 0; + $newfields = array(); + $newvalues = array(); + $newplus = array(); + $rels = array(); + foreach ($this->fields as $j=>$field) { + if (strpos($this->fields[$j], "FAMC:HUSB:NAME")===0 || strpos($this->fields[$j], "FAMC:WIFE:NAME")===0) { + $rels[$this->fields[$j]] = $this->values[$j]; + continue; + } + $newfields[$i] = $this->fields[$j]; + if (isset($this->values[$j])) $newvalues[$i] = $this->values[$j]; + if (isset($this->plusminus[$j])) $newplus[$i] = $this->plusminus[$j]; + $i++; + } + $this->fields = $newfields; + $this->values = $newvalues; + $this->plusminus = $newplus; + foreach ($rels as $field=>$value) { + $this->fields[] = $field; + $this->values[] = $value; + } + } + + function advancedSearch($justSql=false, $table="individuals", $prefix="i") { + global $gedcom_record_cache; + + DMsoundex("", "opencache"); + $this->myindilist = array (); + $fct = count($this->fields); + if ($fct==0) return; + + $namesTable = false; + $datesTable = false; + $placesTable = false; + $famsTable = false; + $famcTable = false; + + $sql = ''; + if ($justSql) $sqlfields = "SELECT DISTINCT {$prefix}_id, {$prefix}_file"; + else $sqlfields = "SELECT i_id, i_gedcom, i_isdead, i_file, i_sex"; + $sqltables = " FROM `##".$table."`"; + $sqlwhere = " WHERE ".$prefix."_file=".WT_GED_ID; + $keepfields = $this->fields; + for ($i=0; $i<$fct; $i++) { + $field = $this->fields[$i]; + if (empty($field)) continue; + $value=''; + if (isset($this->values[$i])) $value = $this->values[$i]; + if (empty($value)) continue; + $parts = preg_split("/:/", $field); + //-- handle names seperately + if ($parts[0]=="NAME") { + // The pgv_name table contains both names and soundex values + if (!$namesTable) { + $sqltables.=" JOIN `##name` ON (i_file=n_file AND i_id=n_id) "; + $namesTable = true; + } + switch (end($parts)) { + case 'SDX_STD': + $sdx=explode(':', soundex_std($value)); + foreach ($sdx as $k=>$v) { + if ($parts[1]=='GIVN') { + $sdx[$k]="n_soundex_givn_std LIKE '%{$v}%'"; + } else { + $sdx[$k]="n_soundex_surn_std LIKE '%{$v}%'"; + } + } + $sqlwhere.=' AND ('.implode(' OR ', $sdx).')'; + break; + case 'SDX': + // SDX uses DM by default. + case 'SDX_DM': + $sdx=explode(':', soundex_dm($value)); + foreach ($sdx as $k=>$v) { + if ($parts[1]=='GIVN') { + $sdx[$k]="n_soundex_givn_dm LIKE '%{$v}%'"; + } else { + $sdx[$k]="n_soundex_surn_dm LIKE '%{$v}%'"; + } + } + $sqlwhere.=' AND ('.implode(' OR ', $sdx).')'; + break; + case 'EXACT': + // Exact match. + switch ($parts[1]) { + case 'GIVN': + // Allow for exact match on multiple given names. + $sqlwhere.=" AND (n_givn LIKE ".WT_DB::quote($value)." OR n_givn LIKE ".WT_DB::quote("{$value} %")." OR n_givn LIKE ".WT_DB::quote("% {$value}")." OR n_givn LIKE ".WT_DB::quote("% {$value} %").")"; + break; + case 'SURN': + $sqlwhere.=" AND n_surname LIKE ".WT_DB::quote($value); + break; + default: + $sqlwhere.=" AND n_full LIKE ".WT_DB::quote($value); + break; + } + break; + case 'BEGINS': + // "Begins with" match. + switch ($parts[1]) { + case 'GIVN': + // Allow for match on start of multiple given names + $sqlwhere.=" AND (n_givn LIKE ".WT_DB::quote("{$value}%")." OR n_givn LIKE ".WT_DB::quote("% {$value}%").")"; + break; + case 'SURN': + $sqlwhere.=" AND n_surname LIKE ".WT_DB::quote("{$value}%"); + break; + default: + $sqlwhere.=" AND n_full LIKE ".WT_DB::quote("{$value}%"); + break; + } + break; + case 'CONTAINS': + default: + // Partial match. + switch ($parts[1]) { + case 'GIVN': + $sqlwhere.=" AND n_givn LIKE ".WT_DB::quote("%{$value}%"); + break; + case 'SURN': + $sqlwhere.=" AND n_surname LIKE ".WT_DB::quote("%{$value}%"); + break; + default: + $sqlwhere.=" AND n_full LIKE ".WT_DB::quote("%{$value}%"); + break; + } + break; + } + } + //-- handle dates + else if (isset($parts[1]) && $parts[1]=="DATE") { + if (!$datesTable) { + $sqltables.=", `##dates`"; + $sqlwhere .= " AND ".$prefix."_file=d_file AND ".$prefix."_id=d_gid"; + $datesTable = true; + } + $sqlwhere .= " AND (d_fact='".$parts[0]."'"; + $date = new GedcomDate($value); + if ($date->isOK()) { + $jd1 = $date->date1->minJD; + if ($date->date2) $jd2 = $date->date2->maxJD; + else $jd2 = $date->date1->maxJD; + if (!empty($this->plusminus[$i])) { + $adjd = $this->plusminus[$i]*365; + //echo $jd1.":".$jd2.":".$adjd; + $jd1 = $jd1 - $adjd; + $jd2 = $jd2 + $adjd; + } + $sqlwhere .= " AND d_julianday1>=".$jd1." AND d_julianday2<=".$jd2; + } + $sqlwhere .= ") "; + } + //-- handle places + else if (isset($parts[1]) && $parts[1]=="PLAC") { + if (!$placesTable) { + $sqltables.=", `##places`, `##placelinks`"; + $sqlwhere .= " AND ".$prefix."_file=p_file AND p_file=pl_file AND ".$prefix."_id=pl_gid AND pl_p_id=p_id"; + $placesTable = true; + } + //-- soundex search + //if (end($parts)=="SDX") { + $places = preg_split("/[, ]+/", $value); + $parr = array(); + for ($j = 0; $j < count($places); $j ++) { + $parr[$j] = DMsoundex($places[$j]); + } + $sqlwhere .= " AND ("; + $fnc = 0; + $field = "p_dm_soundex"; + foreach ($parr as $name) { + foreach ($name as $name1) { + if ($fnc>0) + $sqlwhere .= " OR "; + $fnc++; + $sqlwhere .= $field." LIKE ".WT_DB::quote("%{$name1}%"); + } + } + $sqlwhere .= ") "; + //} + } + //-- handle parent/spouse names + else if ($parts[0]=='FAMS') { + if (!$famsTable) { + $sqltables.=", `##families` as FAMS"; + $sqlwhere .= " AND i_file=FAMS.f_file"; + $famsTable = true; + } + //-- alter the fields and recurse to generate a subquery for spouse/parent fields + $oldfields = $this->fields; + for ($j=0; $j<$fct; $j++) { + //-- if it doesn't start with FAMS or FAMC then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])==0) { + $this->fields[$j]=''; + } + else $this->fields[$j] = preg_replace("/^".$parts[0].":/","", $this->fields[$j]); + } + $sqlwhere .= " AND (FAMS.f_husb=i_id OR FAMS.f_wife=i_id)"; + $subsql = $this->advancedSearch(true,"families","f"); + $sqlwhere .= " AND ROW(FAMS.f_id, FAMS.f_file) IN (".$subsql.")"; + $this->fields = $oldfields; + //-- remove all of the fam fields so they don't show up again + for ($j=0; $j<$fct; $j++) { + //-- if it does start with FAMS or FAMC then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])>0) { + $this->fields[$j]=''; + } + } + } + else if ($parts[0]=='FAMC') { + if (!$famcTable) { + $sqltables.=", `##families` as FAMC"; + $sqlwhere .= " AND i_file=FAMC.f_file"; + $famcTable = true; + } + //-- alter the fields and recurse to generate a subquery for spouse/parent fields + $oldfields = $this->fields; + for ($j=0; $j<$fct; $j++) { + //-- if it doesn't start with FAMS or FAMC then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])==0) { + $this->fields[$j]=''; + } + else $this->fields[$j] = preg_replace("/^".$parts[0].":/","", $this->fields[$j]); + } + $sqlwhere .= " AND (FAMC.f_gedcom LIKE CONCAT('%1 CHIL @',i_id,'@%'))"; + $subsql = $this->advancedSearch(true,"families","f"); + $sqlwhere .= " AND ROW(FAMC.f_id, FAMC.f_file) IN (".$subsql.")"; + $this->fields = $oldfields; + //-- remove all of the fam fields so they don't show up again + for ($j=0; $j<$fct; $j++) { + //-- if it does start with FAMS or FAMC then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])>0) { + $this->fields[$j]=''; + } + } + } + else if ($parts[0]=='HUSB' || $parts[0]=='WIFE') { + if (!$famsTable) { + $sqltables.=", `##individuals`"; + $sqlwhere .= " AND i_file=f_file"; + $famsTable = true; + } + //-- alter the fields and recurse to generate a subquery for spouse/parent fields + $oldfields = $this->fields; + for ($j=0; $j<$fct; $j++) { + //-- if it doesn't start with FAMS or FAMC then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])==0) { + $this->fields[$j]=''; + } + else $this->fields[$j] = preg_replace("/^".$parts[0].":/","", $this->fields[$j]); + } + $subsql = $this->advancedSearch(true,"individuals","i"); + if ($parts[0]=='HUSB') $sqlwhere .= " AND ROW(f_husb, f_file) IN (".$subsql.")"; + if ($parts[0]=='WIFE') $sqlwhere .= " AND ROW(f_wife, f_file) IN (".$subsql.")"; + $this->fields = $oldfields; + //-- remove all of the fam fields so they don't show up again + for ($j=0; $j<$fct; $j++) { + //-- if it does start with HUSB or WIFE then remove that field + if (preg_match("/^".$parts[0].":/", $this->fields[$j])>0) { + $this->fields[$j]=''; + } + } + } + //-- handle everything else + else { + $sqlwhere .= " AND i_gedcom LIKE "; + + $ct = count($parts); + $liketmp=''; + for ($j=0; $j<$ct; $j++) { + $liketmp.= "%".($j+1)." ".$parts[$j]." %"; + //if ($j<$ct-1) { + //$sqlwhere .= "%"; + //} else { + $liketmp .= "%{$value}%"; + //} + } + $sqlwhere .= WT_DB::quote($liketmp); + } + } + $sql = $sqlfields.$sqltables.$sqlwhere; + //echo $sql; + if ($justSql) return $sql; + $rows=WT_DB::prepare($sql)->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + $row['xref']=$row['i_id']; + $row['ged_id']=$row['i_file']; + $row['type'] = 'INDI'; + $row['gedrec'] = $row['i_gedcom']; + $object = WT_Person::getInstance($row); + $this->myindilist[$row['i_id']] = $object; + } + $this->fields = $keepfields; + } + + function PrintResults() { + require_once WT_ROOT.'includes/functions/functions_print_lists.php'; + $ret = true; + if (count($this->myindilist)>0) { + echo '<br /><div class="center">'; + uasort($this->myindilist, array('GedcomRecord', 'Compare')); + print_indi_table($this->myindilist, i18n::translate('Individuals')." @ ".PrintReady(get_gedcom_setting(WT_GEDCOM, 'title'), true)); + echo "</div>"; + } + else { + $ret = false; + if ($this->isPostBack) { + echo '<br /><div class="warning" style=" text-align: center;"><i>', i18n::translate('No results found.'), '</i><br /></div>'; + } + } + return $ret; + } +} diff --git a/library/WT/Controller/Ancestry.php b/library/WT/Controller/Ancestry.php new file mode 100644 index 0000000000..57559fe96a --- /dev/null +++ b/library/WT/Controller/Ancestry.php @@ -0,0 +1,169 @@ +<?php +// Controller for the Ancestry Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_ANCESTRY_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +class WT_Controller_Ancestry extends WT_Controller_Base { + var $pid = ""; + var $user = false; + var $accept_success = false; + var $show_cousins; + var $rootid; + var $name; + var $addname; + var $OLD_PGENS; + var $chart_style; + var $show_full; + var $cellwidth; + + /** + * Initialization function + */ + function init() { + global $USE_RIN, $MAX_ALIVE_AGE, $GEDCOM, $bwidth, $bheight, $pbwidth, $pbheight, $PEDIGREE_FULL_DETAILS, $MAX_DESCENDANCY_GENERATIONS; + global $DEFAULT_PEDIGREE_GENERATIONS, $PEDIGREE_GENERATIONS, $MAX_PEDIGREE_GENERATIONS, $OLD_PGENS, $box_width, $Dbwidth, $Dbheight; + global $show_full; + + // Extract form parameters + $this->rootid =safe_GET_xref('rootid'); + $this->show_full =safe_GET('show_full', array('0', '1'), $PEDIGREE_FULL_DETAILS); + $this->show_cousins =safe_GET('show_cousins', array('0', '1'), '0'); + $this->chart_style =safe_GET_integer('chart_style', 0, 3, 0); + $box_width =safe_GET_integer('box_width', 50, 300, 100); + $PEDIGREE_GENERATIONS=safe_GET_integer('PEDIGREE_GENERATIONS', 2, $MAX_PEDIGREE_GENERATIONS, $DEFAULT_PEDIGREE_GENERATIONS); + + // This is passed as a global. A parameter would be better... + $show_full=$this->show_full; + + $OLD_PGENS = $PEDIGREE_GENERATIONS; + + // Validate form parameters + $this->rootid = check_rootid($this->rootid); + + // -- size of the boxes + $Dbwidth*=$box_width/100; + $bwidth=$Dbwidth; + if (!$this->show_full) { + $bwidth = $bwidth / 1.5; + } + $bheight=$Dbheight; + if (!$this->show_full) { + $bheight = $bheight / 1.5; + } + + $pbwidth = $bwidth+12; + $pbheight = $bheight+14; + + $this->ancestry = WT_Person::getInstance($this->rootid); + $this->name = $this->ancestry->getFullName(); + $this->addname = $this->ancestry->getAddName(); + + if (strlen($this->name)<30) $this->cellwidth="420"; + else $this->cellwidth=(strlen($this->name)*14); + } + + /** + * print a child ascendancy + * + * @param string $pid individual Gedcom Id + * @param int $sosa child sosa number + * @param int $depth the ascendancy depth to show + */ + function print_child_ascendancy($pid, $sosa, $depth) { + global $TEXT_DIRECTION, $OLD_PGENS, $WT_IMAGES, $Dindent, $SHOW_EMPTY_BOXES, $pidarr, $box_width; + + $person = WT_Person::getInstance($pid); + // child + echo "<li>"; + echo "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td><a name=\"sosa".$sosa."\"></a>"; + $new=($pid=="" or !isset($pidarr["$pid"])); + if ($sosa==1) echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"3\" width=\"$Dindent\" border=\"0\" alt=\"\" /></td><td>"; + else { + echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"3\" width=\"2\" border=\"0\" alt=\"\" />"; + echo "<img src=\"".$WT_IMAGES["hline"]."\" height=\"3\" width=\"".($Dindent-2)."\" border=\"0\" alt=\"\" /></td><td>"; + } + print_pedigree_person($pid, 1); + echo "</td>"; + echo "<td>"; + if ($TEXT_DIRECTION=="ltr") { + $label = i18n::translate('Ancestry chart').": ".$pid; + } else { + $label = $pid." :".i18n::translate('Ancestry chart'); + } + if ($sosa>1) print_url_arrow($pid, "?rootid={$pid}&PEDIGREE_GENERATIONS={$OLD_PGENS}&show_full={$this->show_full}&box_width={$box_width}&chart_style={$this->chart_style}", $label, 3); + echo "</td>"; + echo "<td class=\"details1\"> <span dir=\"ltr\" class=\"person_box". (($sosa==1)?"NN":(($sosa%2)?"F":"")) . "\"> $sosa </span> "; + echo "</td><td class=\"details1\">"; + $relation =""; + if (!$new) $relation = "<br />[=<a href=\"#sosa".$pidarr["$pid"]."\">".$pidarr["$pid"]."</a> - ".get_sosa_name($pidarr["$pid"])."]"; + else $pidarr["$pid"]=$sosa; + echo get_sosa_name($sosa).$relation; + echo "</td>"; + echo "</tr></table>"; + + if (is_null($person)) { + echo "</li>"; + return; + } + // parents + $famids = $person->getChildFamilies(); + $parents = false; + $famrec = ""; + $famid = ""; + foreach ($famids as $famid=>$family) { + if (!is_null($family)) { + $famrec = $family->getGedcomRecord(); + $parents = find_parents_in_record($famrec); + if ($parents) break; + } + } + + if (($parents || $SHOW_EMPTY_BOXES) && $new && $depth>0) { + // print marriage info + echo "<span class=\"details1\" style=\"white-space: nowrap;\" >"; + echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"2\" width=\"$Dindent\" border=\"0\" align=\"middle\" alt=\"\" /><a href=\"javascript: ".i18n::translate('View Family')."\" onclick=\"expand_layer('sosa_".$sosa."'); return false;\" class=\"top\"><img id=\"sosa_".$sosa."_img\" src=\"".$WT_IMAGES["minus"]."\" align=\"middle\" hspace=\"0\" vspace=\"3\" border=\"0\" alt=\"".i18n::translate('View Family')."\" /></a> "; + echo " <span class=\"person_box\"> ".($sosa*2)." </span> ".i18n::translate('and'); + echo " <span class=\"person_boxF\"> ".($sosa*2+1)." </span> "; + if (!empty($family)) { + $marriage = $family->getMarriage(); + if ($marriage->canShow()) $marriage->print_simple_fact(); else echo i18n::translate('Private'); + } + echo "</span>"; + // display parents recursively + echo "<ul style=\"list-style: none; display: block;\" id=\"sosa_$sosa\">"; + $this->print_child_ascendancy($parents["HUSB"], $sosa*2, $depth-1); + $this->print_child_ascendancy($parents["WIFE"], $sosa*2+1, $depth-1); + echo "</ul>"; + } + echo "</li>"; + } +} diff --git a/library/WT/Controller/Base.php b/library/WT/Controller/Base.php new file mode 100644 index 0000000000..7039031426 --- /dev/null +++ b/library/WT/Controller/Base.php @@ -0,0 +1,41 @@ +<?php +// Base controller for all controller classes +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_BASECONTROL_PHP', ''); + +class WT_Controller_Base { + var $action =null; + var $show_changes=null; + + function __construct() { + $this->action =safe_GET('action'); + $this->show_changes=safe_GET('show_changes', 'no', 'yes')=='yes'; // if not specified, then default to "yes" + } +} diff --git a/library/WT/Controller/Descendancy.php b/library/WT/Controller/Descendancy.php new file mode 100644 index 0000000000..76e091e724 --- /dev/null +++ b/library/WT/Controller/Descendancy.php @@ -0,0 +1,272 @@ +<?php +// Controller for the Descendancy Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_DESCENDANCY_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +class WT_Controller_Descendancy extends WT_Controller_Base { + var $pid = ""; + var $descPerson = null; + + var $diffindi = null; + var $NAME_LINENUM = 1; + var $accept_success = false; + var $canedit = false; + var $name_count = 0; + var $total_names = 0; + var $SEX_COUNT = 0; + var $show_full; + var $chart_style; + var $generations; + var $personcount; + var $box_width; + var $Dbwidth; + var $Dbheight; + var $pbwidth; + var $pbheight; + // d'Aboville numbering system [ http://www.saintclair.org/numbers/numdob.html ] + var $dabo_num=array(); + var $dabo_sex=array(); + var $name; + var $cellwidth; + var $show_cousins; + + /** + * Initialization function + */ + function init() { + global $USE_RIN, $MAX_ALIVE_AGE, $bwidth, $bheight, $pbwidth, $pbheight, $GEDCOM, $PEDIGREE_FULL_DETAILS, $MAX_DESCENDANCY_GENERATIONS, $DEFAULT_PEDIGREE_GENERATIONS, $show_full; + + // Extract parameters from form + $this->pid =safe_GET_xref('pid'); + $this->show_full =safe_GET('show_full', array('0', '1'), $PEDIGREE_FULL_DETAILS); + $this->chart_style=safe_GET_integer('chart_style', 0, 3, 0); + $this->generations=safe_GET_integer('generations', 2, $MAX_DESCENDANCY_GENERATIONS, $DEFAULT_PEDIGREE_GENERATIONS); + $this->box_width =safe_GET_integer('box_width', 50, 300, 100); + + // This is passed as a global. A parameter would be better... + $show_full=$this->show_full; + + if (!isset($this->personcount)) $this->personcount = 1; + + $this->Dbwidth*=$this->box_width/100; + + if (!$this->show_full) { + $bwidth *= $this->box_width / 150; + } else { + $bwidth*=$this->box_width/100; + } + + if (!$this->show_full) { + $bheight = $bheight / 1.5; + } + + $pbwidth = $bwidth+12; + $pbheight = $bheight+14; + + $this->show_changes=safe_GET('show_changes'); + $this->action =safe_GET('action'); + + // Validate form variables + $this->pid=check_rootid($this->pid); + + if (strlen($this->name)<30) $this->cellwidth="420"; + else $this->cellwidth=(strlen($this->name)*14); + + $this->descPerson = WT_Person::getInstance($this->pid); + $this->name=$this->descPerson->getFullName(); + } + + /** + * print a child family + * + * @param string $pid individual Gedcom Id + * @param int $depth the descendancy depth to show + */ + function print_child_family(&$person, $depth, $label="1.", $gpid="") { + global $personcount; + + if (is_null($person)) return; + $families = $person->getSpouseFamilies(); + if ($depth<2) return; + foreach ($families as $famid => $family) { + print_sosa_family($family->getXref(), "", -1, $label, $person->getXref(), $gpid, $personcount); + $personcount++; + $children = $family->getChildren(); + $i=1; + foreach ($children as $child) { + $this->print_child_family($child, $depth-1, $label.($i++).".", $person->getXref()); + } + } + } + +/** + * print a child descendancy + * + * @param string $pid individual Gedcom Id + * @param int $depth the descendancy depth to show + */ +function print_child_descendancy(&$person, $depth) { + global $WT_IMAGES, $Dindent, $personcount; + + if (is_null($person)) return; + //print_r($person); + echo "<li>"; + echo "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td>"; + if ($depth==$this->generations) echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"3\" width=\"$Dindent\" border=\"0\" alt=\"\" /></td><td>"; + else { + echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"3\" width=\"3\" border=\"0\" alt=\"\" />"; + echo "<img src=\"".$WT_IMAGES["hline"]."\" height=\"3\" width=\"".($Dindent-3)."\" border=\"0\" alt=\"\" /></td><td>"; + } + print_pedigree_person($person->getXref(), 1, 0, $personcount); + echo "</td>"; + + // check if child has parents and add an arrow + echo "<td> </td>"; + echo "<td>"; + $sfamids = $person->getChildFamilies(); + foreach ($sfamids as $famid => $family) { + $parents = find_parents($famid); + if ($parents) { + $parid=$parents["HUSB"]; + if ($parid=="") $parid=$parents["WIFE"]; + if ($parid!="") { + print_url_arrow($parid.$personcount.$person->getXref(), "?pid={$parid}&generations={$this->generations}&chart_style={$this->chart_style}&show_full={$this->show_full}&box_width={$this->box_width}", i18n::translate('Start at parents'), 2); + $personcount++; + } + } + } + + // d'Aboville child number + $level =$this->generations-$depth; + if ($this->show_full) echo "<br /><br /> "; + echo "<span dir=\"ltr\">"; //needed so that RTL languages will display this properly + if (!isset($this->dabo_num[$level])) $this->dabo_num[$level]=0; + $this->dabo_num[$level]++; + $this->dabo_num[$level+1]=0; + $this->dabo_sex[$level]=$person->getSex(); + for ($i=0; $i<=$level;$i++) { + $isf=$this->dabo_sex[$i]; + if ($isf=="M") $isf=""; + if ($isf=="U") $isf="NN"; + echo "<span class=\"person_box".$isf."\"> ".$this->dabo_num[$i]." </span>"; + if ($i<$level) echo "."; + } + echo "</span>"; + echo "</td></tr>"; + echo "</table>"; + echo "</li>"; + + // loop for each spouse + $sfam = $person->getSpouseFamilies(); + foreach ($sfam as $famid => $family) { + $personcount++; + $this->print_family_descendancy($person, $family, $depth); + } +} + +/** + * print a family descendancy + * + * @param string $pid individual Gedcom Id + * @param Family $famid family record + * @param int $depth the descendancy depth to show + */ +function print_family_descendancy(&$person, &$family, $depth) { + global $GEDCOM, $WT_IMAGES, $Dindent, $personcount; + + if (is_null($family)) return; + if (is_null($person)) return; + + $spouse=$family->getSpouse($person); + if ($spouse) { + // print marriage info + echo "<li>"; + echo "<img src=\"".$WT_IMAGES["spacer"]."\" height=\"2\" width=\"".($Dindent+4)."\" border=\"0\" alt=\"\" />"; + echo "<span class=\"details1\" style=\"white-space: nowrap; \" >"; + echo "<a href=\"#\" onclick=\"expand_layer('".$family->getXref().$personcount."'); return false;\" class=\"top\"><img id=\"".$family->getXref().$personcount."_img\" src=\"".$WT_IMAGES["minus"]."\" align=\"middle\" hspace=\"0\" vspace=\"3\" border=\"0\" alt=\"".i18n::translate('View Family')."\" /></a>"; + $marriage = $family->getMarriage(); + if ($marriage->canShow()) { + echo ' <a href="', $family->getHtmlUrl(), '" class="details1">'; + $marriage->print_simple_fact(); + echo '</a>'; + } + echo '</span>'; + + // print spouse + echo "<ul style=\"list-style: none; display: block;\" id=\"".$family->getXref().$personcount."\">"; + echo "<li>"; + echo "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td>"; + print_pedigree_person($spouse->getXref(), 1, 0, $personcount); + echo "</td>"; + + // check if spouse has parents and add an arrow + echo "<td> </td>"; + echo "<td>"; + foreach ($spouse->getChildFamilyIds() as $sfamid) { + $parents = find_parents($sfamid); + if ($parents) { + $parid=$parents["HUSB"]; + if ($parid=="") $parid=$parents["WIFE"]; + if ($parid!="") { + print_url_arrow($parid.$personcount.$person->getXref(), "?pid={$parid}&generations={$this->generations}&show_full={$this->show_full}&box_width={$this->box_width}", i18n::translate('Start at parents'), 2); + $personcount++; + } + } + } + if ($this->show_full) echo "<br /><br /> "; + echo "</td></tr>"; + + // children + $children = $family->getChildren(); + echo "<tr><td colspan=\"3\" class=\"details1\" > "; + if ($children) { + echo translate_fact('NCHI').": ".count($children); + } else { + // Distinguish between no children (NCHI 0) and no recorded + // children (no CHIL records) + if (strpos($family->getGedcomRecord(), "\n1 NCHI 0")) { + echo translate_fact('NCHI').": ".count($children); + } else { + echo i18n::translate('No children'); + } + } + echo "</td></tr></table>"; + echo "</li>"; + if ($depth>1) foreach ($children as $child) { + $personcount++; + $this->print_child_descendancy($child, $depth-1); + } + echo "</ul>"; + echo "</li>"; + } +} +} diff --git a/library/WT/Controller/Family.php b/library/WT/Controller/Family.php new file mode 100644 index 0000000000..5255148186 --- /dev/null +++ b/library/WT/Controller/Family.php @@ -0,0 +1,310 @@ +<?php +// Controller for the Family Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_FAMILY_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +class WT_Controller_Family extends WT_Controller_Base { + var $famid = ''; + var $family = null; + var $difffam = null; + var $accept_success = false; + var $user = null; + var $showLivingHusb = true; + var $showLivingWife = true; + var $parents = ''; + var $display = false; + var $show_changes = true; + var $famrec = ''; + var $link_relation = 0; + var $title = ''; + + function init() { + global $Dbwidth, $bwidth, $pbwidth, $pbheight, $bheight, $GEDCOM; + $bwidth = $Dbwidth; + $pbwidth = $bwidth + 12; + $pbheight = $bheight + 14; + + $this->famid = safe_GET_xref('famid'); + + $gedrec = find_family_record($this->famid, WT_GED_ID); + + if (empty($gedrec)) { + $gedrec = "0 @".$this->famid."@ FAM\n"; + } + + if (find_family_record($this->famid, WT_GED_ID) || find_updated_record($this->famid, WT_GED_ID)!==null) { + $this->family = new WT_Family($gedrec); + $this->family->ged_id=WT_GED_ID; // This record is from a file + } else if (!$this->family) { + return false; + } + + $this->famid=$this->family->getXref(); // Correct upper/lower case mismatch + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'FAM', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->famid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $gedrec = find_family_record($this->famid, WT_GED_ID); + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->family = new WT_Family($gedrec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->famid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $gedrec = find_family_record($this->famid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->family = new WT_Family($gedrec); + } + unset($_GET['action']); + break; + } + + //-- if the user can edit and there are changes then get the new changes + if ($this->show_changes && WT_USER_CAN_EDIT) { + $newrec = find_updated_record($this->famid, WT_GED_ID); + if (!empty($newrec)) { + $this->difffam = new WT_Family($newrec); + $this->difffam->setChanged(true); + } + } + + if ($this->show_changes) { + $this->family->diffMerge($this->difffam); + } + + $this->parents = array('HUSB'=>$this->family->getHusbId(), 'WIFE'=>$this->family->getWifeId()); + + //-- check if we can display both parents + if ($this->display == false) { + $this->showLivingHusb = showLivingNameById($this->parents['HUSB']); + $this->showLivingWife = showLivingNameById($this->parents['WIFE']); + } + + if ($this->showLivingHusb == false && $this->showLivingWife == false) { + print_header(i18n::translate('Family')); + print_privacy_error(); + print_footer(); + exit; + } + + if (empty($this->parents['HUSB']) || empty($this->parents['WIFE'])) { + $this->link_relation = 0; + } else { + $this->link_relation = 1; + } + } + + function getFamilyID() { + return $this->famid; + } + + function getHusband() { + if (!is_null($this->difffam)) return $this->difffam->getHusbId(); + if ($this->family) return $this->parents['HUSB']; + return null; + } + + function getWife() { + if (!is_null($this->difffam)) return $this->difffam->getWifeId(); + if ($this->family) return $this->parents['WIFE']; + return null; + } + + // $tags is an array of HUSB/WIFE/CHIL + function getTimelineIndis($tags) { + preg_match_all('/\n1 (?:'.implode('|', $tags).') @('.WT_REGEX_XREF.')@/', $this->family->getGedcomRecord(), $matches); + foreach ($matches[1] as &$match) { + $match='pids[]='.$match; + } + return implode('&', $matches[1]); + } + + /** + * return the title of this page + * @return string the title of the page to go in the <title> tags + */ + function getPageTitle() { + if ($this->family) { + return $this->family->getFullName(); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->family) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_fam'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + // edit_fam / members + $submenu = new Menu(i18n::translate('Change Family Members')); + $submenu->addOnclick("return change_family_members('".$this->getFamilyID()."');"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + // edit_fam / add child + $submenu = new Menu(i18n::translate('Add a child to this family')); + $submenu->addOnclick("return addnewchild('".$this->getFamilyID()."');"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + // edit_fam / reorder_children + if ($this->family->getNumberOfChildren() > 1) { + $submenu = new Menu(i18n::translate('Re-order children')); + $submenu->addOnclick("return reorder_children('".$this->getFamilyID()."');"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // show/hide changes + if (find_updated_record($this->getFamilyID(), WT_GED_ID)!==null) { + if (!$this->show_changes) { + $label = i18n::translate('This record has been updated. Click here to show changes.'); + $link = $this->family->getHtmlUrl().'&show_changes=yes'; + } else { + $label = i18n::translate('Click here to hide changes.'); + $link = $this->family->getHtmlUrl().'&show_changes=no'; + } + $submenu = new Menu($label, $link); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), "family.php?famid={$this->famid}&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('edit_fam'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), "family.php?famid={$this->famid}&action=accept"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->getFamilyID()."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Delete family')); + $submenu->addOnclick("if (confirm('".i18n::translate('Deleting the family will unlink all of the individuals from each other but will leave the individuals in place. Are you sure you want to delete this family?')."')) return delete_family('".$this->getFamilyID()."'); else return false;"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), 'family.php?action=addfav&famid='.$this->getFamilyID().'&gamp;id='.$this->getFamilyID()); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } +} diff --git a/library/WT/Controller/Hourglass.php b/library/WT/Controller/Hourglass.php new file mode 100644 index 0000000000..99a4ff7158 --- /dev/null +++ b/library/WT/Controller/Hourglass.php @@ -0,0 +1,557 @@ +<?php +// Controller for the Hourglass Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_HOURGLASS_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +// -- array of GEDCOM elements that will be found but should not be displayed +$nonfacts[] = "FAMS"; +$nonfacts[] = "FAMC"; +$nonfacts[] = "MAY"; +$nonfacts[] = "BLOB"; +$nonfacts[] = "CHIL"; +$nonfacts[] = "HUSB"; +$nonfacts[] = "WIFE"; +$nonfacts[] = "RFN"; +$nonfacts[] = ""; +$nonfamfacts[] = "UID"; +$nonfamfacts[] = ""; + +class WT_Controller_Hourglass extends WT_Controller_Base { + var $pid = ""; + + var $accept_success = false; + var $canedit = false; + var $name_count = 0; + var $total_names = 0; + var $SEX_COUNT = 0; + var $show_full = 0; + var $show_spouse = 0; + var $generations; + var $dgenerations; + var $box_width; + var $name; + // the following are ajax variables // + var $ARID; + var $arrwidth; + var $arrheight; + /////////////////////////////////////// + + /** + * Initialization function + */ + function init($rootid='', $show_full=1, $generations=3) { + global $USE_RIN, $MAX_ALIVE_AGE, $GEDCOM, $bheight, $bwidth, $bhalfheight, $PEDIGREE_FULL_DETAILS, $MAX_DESCENDANCY_GENERATIONS; + global $WT_IMAGES, $TEXT_DIRECTION, $show_full; + + // Extract parameters from from + $this->pid =safe_GET_xref('pid'); + $this->show_full =safe_GET('show_full', array('0', '1'), $PEDIGREE_FULL_DETAILS); + $this->show_spouse=safe_GET('show_spouse', array('0', '1'), '0'); + $this->generations=safe_GET_integer('generations', 2, $MAX_DESCENDANCY_GENERATIONS, 3); + $this->box_width =safe_GET_integer('box_width', 50, 300, 100); + + // This is passed as a global. A parameter would be better... + $show_full=$this->show_full; + + if (!empty($_REQUEST["action"])) $this->action = $_REQUEST["action"]; + if (!empty($rootid)) $this->pid = $rootid; + + //-- flip the arrows for RTL languages + if ($TEXT_DIRECTION=="rtl") { + $temp = $WT_IMAGES['larrow']; + $WT_IMAGES['larrow'] = $WT_IMAGES['rarrow']; + $WT_IMAGES['rarrow'] = $temp; + } + //-- get the width and height of the arrow images for adjusting spacing + if (file_exists($WT_IMAGES['larrow'])) { + $temp = getimagesize($WT_IMAGES['larrow']); + $this->arrwidth = $temp[0]; + $this->arrheight= $temp[1]; + } + + // -- Sets the sizes of the boxes + if (!$this->show_full) $bwidth *= $this->box_width / 150; + else $bwidth*=$this->box_width/100; + + if (!$this->show_full) $bheight = (int)($bheight / 2); + $bhalfheight = (int)($bheight / 2); + + // Validate parameters + $this->pid=check_rootid($this->pid); + + $this->hourPerson = WT_Person::getInstance($this->pid); + $this->name=$this->hourPerson->getFullName(); + + //Checks how many generations of descendency is for the person for formatting purposes + $this->dgenerations = $this->max_descendency_generations($this->pid, 0); + if ($this->dgenerations<1) $this->dgenerations=1; + } + + /** + * Prints pedigree of the person passed in. Which is the descendancy + * + * @param mixed $pid ID of person to print the pedigree for + * @param mixed $count generation count, so it recursively calls itself + * @access public + * @return void + */ + function print_person_pedigree($pid, $count) { + global $SHOW_EMPTY_BOXES, $WT_IMAGES, $bhalfheight; + + if ($count>=$this->generations) return; + $person = WT_Person::getInstance($pid); + if (is_null($person)) return; + $families = $person->getChildFamilies(); + //-- calculate how tall the lines should be + $lh = ($bhalfheight+3) * pow(2, ($this->generations-$count-1)); + foreach ($families as $famid => $family) { + echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"empty-cells: show;\">"; + $parents = find_parents($famid); + $height="100%"; + echo "<tr>"; + echo "<td valign=\"bottom\"><img name=\"pvline\" src=\"".$WT_IMAGES["vline"]."\" width=\"3\" height=\"$lh\" alt=\"\" /></td>"; + echo "<td><img src=\"".$WT_IMAGES["hline"]."\" width=\"7\" height=\"3\" alt=\"\" /></td>"; + echo "<td>"; + //-- print the father box + print_pedigree_person($parents["HUSB"]); + echo "</td>"; + $ARID = $parents["HUSB"]; + echo "<td id=\"td_".$ARID."\">"; + + //-- print an Ajax arrow on the last generation of the adult male + if ($count==$this->generations-1 && WT_Person::getInstance($ARID)->getChildFamilies()) { + echo "<a href=\"#\" onclick=\"return ChangeDiv('td_".$ARID."','".$ARID."','".$this->show_full."','".$this->show_spouse."','".$this->box_width."')\"><img src=\"".$WT_IMAGES["rarrow"]."\" border=\"0\" alt=\"\" /></a> "; + } + //-- recursively get the father's family + $this->print_person_pedigree($parents["HUSB"], $count+1); + echo "</td>"; + echo "</tr><tr>"; + echo "<td valign=\"top\"><img name=\"pvline\" src=\"".$WT_IMAGES["vline"]."\" width=\"3\" height=\"$lh\" alt=\"\" /></td>"; + echo "<td><img src=\"".$WT_IMAGES["hline"]."\" width=\"7\" height=\"3\" alt=\"\" /></td>"; + echo "<td>"; + //-- print the mother box + print_pedigree_person($parents["WIFE"]); + echo "</td>"; + $ARID = $parents["WIFE"]; + echo "<td id=\"td_".$ARID."\">"; + + + //-- print an ajax arrow on the last generation of the adult female + if ($count==$this->generations-1 && WT_Person::getInstance($ARID)->getChildFamilies()) { + echo "<a href=\"#\" onclick=\"ChangeDiv('td_".$ARID."','".$ARID."','".$this->show_full."','".$this->show_spouse."','".$this->box_width."'); return false;\"><img src=\"".$WT_IMAGES["rarrow"]."\" border=\"0\" alt=\"\" /></a> "; + } + + //-- recursively print the mother's family + $this->print_person_pedigree($parents["WIFE"], $count+1); + echo "</td>"; + echo "</tr>"; + echo "</table>"; + break; + } + } + + /** + * Prints descendency of passed in person + * + * @param mixed $pid ID of person to print descendency for + * @param mixed $count count of generations to print + * @access public + * @return void + */ + function print_descendency($pid, $count, $showNav=true) { + global $TEXT_DIRECTION, $WT_IMAGES, $bheight, $bwidth, $bhalfheight, $lastGenSecondFam; + + if ($count>$this->dgenerations) return 0; + $person = WT_Person::getInstance($pid); + if (is_null($person)) return; + + $tablealign = "right"; + $otablealign = "left"; + if ($TEXT_DIRECTION=="rtl") { + $tablealign = "left"; + $otablealign = "right"; + } + echo "<!-- print_descendency for $pid -->"; + //-- put a space between families on the last generation + if ($count==$this->dgenerations-1) { + if (isset($lastGenSecondFam)) echo "<br />"; + $lastGenSecondFam = true; + } + + echo "<table id=\"table_$pid\" align=\"".$tablealign."\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">"; + echo "<tr>"; + echo "<td align=\"$tablealign\" width=\"100%\">"; + $numkids = 0; + $families = $person->getSpouseFamilies(); + $famcount = count($families); + $famNum = 0; + $kidNum = 0; + $children = array(); + if ($count < $this->dgenerations) { + //-- put all of the children in a common array + foreach ($families as $famid => $family) { + $famNum ++; + $chs = $family->getChildren(); + foreach ($chs as $c=>$child) $children[] = $child; + } + + $ct = count($children); + if ($ct>0) { + echo "<table style=\"position: relative; top: auto; text-align: $tablealign;\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">"; + for ($i=0; $i<$ct; $i++) { + if (($i>0)&&($i<$ct-1)) $rowspan=1; + /* @var $person2 Person */ + $person2 = $children[$i]; + $chil = $person2->getXref(); + echo "<tr>"; + echo "<td id=\"td_$chil\" class=\"$TEXT_DIRECTION\" align=\"$tablealign\">"; + $kids = $this->print_descendency($chil, $count+1); + $numkids += $kids; + echo "</td>"; + + //-- print the lines + $twidth = 7; + if ($ct==1) $twidth+=3; + if ($ct>1) { + if ($i==0) { + //-- adjust for the number of kids + $h = ($bhalfheight+3)*$numkids; + echo "<td valign=\"bottom\"><img name=\"tvertline\" id=\"vline_$chil\" src=\"".$WT_IMAGES["vline"]."\" width=\"3\" height=\"$h\" alt=\"\" /></td>"; + } else if ($i==$ct-1) { + $h = ($bhalfheight+3)*$kids; + if ($count<$this->dgenerations-1) { + if ($this->show_spouse) $h-=15; + else $h += 15; + } + echo "<td valign=\"top\"><img name=\"bvertline\" id=\"vline_$chil\" src=\"".$WT_IMAGES["vline"]."\" width=\"3\" height=\"".$h."\" alt=\"\" /></td>"; + } else { + echo "<td style=\"background: url('".$WT_IMAGES["vline"]."');\"><img src=\"".$WT_IMAGES["spacer"]."\" width=\"3\" alt=\"\" /></td>"; + } + } + echo "</tr>"; + + } + echo "</table>"; + + } + echo "</td>"; + echo "<td width=\"$bwidth\">"; + } + + // Print the descendency expansion arrow + if ($count==$this->dgenerations) { + $numkids = 1; + $tbwidth = $bwidth+16; + for ($j=$count; $j<$this->dgenerations; $j++) { + echo "<div style=\"width: ".($tbwidth)."px;\"><br /></div></td><td width=\"$bwidth\">"; + } + $kcount = 0; + foreach ($families as $famid=>$family) $kcount+=$family->getNumberOfChildren(); + if ($kcount==0) { + echo "<div style=\"width: ".($this->arrwidth)."px;\"><br /></div></td><td width=\"$bwidth\">"; + } else { + echo "<div style=\"width: ".($this->arrwidth)."px;\"><a href=\"$pid\" onclick=\"return ChangeDis('td_".$pid."','".$pid."','".$this->show_full."','".$this->show_spouse."','".$this->box_width."')\"><img src=\"".$WT_IMAGES["larrow"]."\" border=\"0\" alt=\"\" /></a></div>"; + //-- move the arrow up to line up with the correct box + if ($this->show_spouse) { + foreach ($families as $famid => $family) { + /* @var $family Family */ + if (!is_null($family)) { + $spouse = $family->getSpouse($person); + if ($spouse!=null) { + echo "<br /><br /><br />"; + } + } + } + } + echo "</td><td width=\"$bwidth\">"; + } + } + + echo "<table id=\"table2_$pid\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td>"; + print_pedigree_person($pid); + echo "</td><td><img src=\"".$WT_IMAGES["hline"]."\" width=\"7\" height=\"3\" alt=\"\" />"; + + //----- Print the spouse + if ($this->show_spouse) { + foreach ($families as $famid => $family) { + /* @var $family Family */ + if (!is_null($family)) { + $spouse = $family->getSpouse($person); + if ($spouse!=null) { + echo "</td></tr><tr><td align=\"$otablealign\">"; + //-- shrink the box for the spouses + $tempw = $bwidth; + $temph = $bheight; + $bwidth -= 10; + $bheight -= 10; + print_pedigree_person($spouse->getXref()); + $bwidth = $tempw; + $bheight = $temph; + $numkids += 0.95; + echo "</td><td></td>"; + } + } + } + //-- add offset divs to make things line up better + if ($count==$this->dgenerations) echo "<tr><td colspan\"2\"><div style=\"height: ".($bhalfheight/2)."px; width: ".$bwidth."px;\"><br /></div>"; + } + echo "</td></tr></table>"; + + // For the root person, print a down arrow that allows changing the root of tree + if ($showNav && $count==1) { + // NOTE: If statement OK + if ($person->canDisplayName()) { + // -- print left arrow for decendants so that we can move down the tree + $famids = $person->getSpouseFamilies(); + //-- make sure there is more than 1 child in the family with parents + $cfamids = $person->getChildFamilies(); + $num=0; + foreach ($cfamids as $famid=>$family) { + if (!is_null($family)) { + $num += $family->getNumberOfChildren(); + } + } + // NOTE: If statement OK + if ($num>0) { + echo "<div class=\"center\" id=\"childarrow\" dir=\"".$TEXT_DIRECTION."\""; + echo " style=\"position:absolute; width:".$bwidth."px; \">"; + echo "<a href=\"javascript: ".i18n::translate('Show')."\" onclick=\"togglechildrenbox(); return false;\" onmouseover=\"swap_image('larrow',3);\" onmouseout=\"swap_image('larrow',3);\">"; + echo "<img id=\"larrow\" src=\"".$WT_IMAGES["darrow"]."\" border=\"0\" alt=\"\" />"; + echo "</a><br />"; + echo "<div id=\"childbox\" dir=\"".$TEXT_DIRECTION."\" style=\"width:".$bwidth."px; height:".$bheight."px; visibility: hidden;\">"; + echo "<table class=\"person_box\"><tr><td>"; + + foreach ($famids as $famid=>$family) { + if (!is_null($family)) { + $spouse = $family->getSpouse($person); + if (!empty($spouse)) { + $spid = $spouse->getXref(); + echo "<a href=\"hourglass.php?pid={$spid}&show_spouse={$this->show_spouse}&show_full={$this->show_full}&generations={$this->generations}&box_width={$this->box_width}\"><span "; + $name = $spouse->getFullName(); + $name = rtrim($name); + if (hasRTLText($name)) + echo "class=\"name2\">"; + else echo "class=\"name1\">"; + echo PrintReady($name); + echo "<br /></span></a>"; + + } + + $children = $family->getChildren(); + foreach ($children as $id=>$child) { + $cid = $child->getXref(); + echo " <a href=\"hourglass.php?pid={$cid}&show_spouse={$this->show_spouse}&show_full={$this->show_full}&generations={$this->generations}&box_width={$this->box_width}\"><span "; + $name = $child->getFullName(); + $name = rtrim($name); + if (hasRTLText($name)) + echo "class=\"name2\">< "; + else echo "class=\"name1\">< "; + echo PrintReady($name); + + echo "<br /></span></a>"; + + } + } + } + //-- do we need to print this arrow? + echo "<img src=\"".$WT_IMAGES["rarrow"]."\" border=\"0\" alt=\"\" /> "; + + //-- print the siblings + foreach ($cfamids as $famid=>$family) { + if (!is_null($family)) { + if (!is_null($family->getHusband()) || !is_null($family->getWife())) { + echo "<span class=\"name1\"><br />".i18n::translate('Parents')."<br /></span>"; + $husb = $family->getHusband(); + if (!empty($husb)) { + $spid = $husb->getXref(); + echo " <a href=\"hourglass.php?pid={$spid}&show_spouse={$this->show_spouse}&show_full={$this->show_full}&generations={$this->generations}&box_width={$this->box_width}\"><span "; + $name = $husb->getFullName(); + $name = rtrim($name); + if (hasRTLText($name)) + echo "class=\"name2\">"; + else echo "class=\"name1\">"; + echo PrintReady($name); + echo "<br /></span></a>"; + } + $husb = $family->getWife(); + if (!empty($husb)) { + $spid = $husb->getXref(); + echo " <a href=\"hourglass.php?pid={$spid}&show_spouse={$this->show_spouse}&show_full={$this->show_full}&generations={$this->generations}&box_width={$this->box_width}\"><span "; + $name = $husb->getFullName(); + $name = rtrim($name); + if (hasRTLText($name)) + echo "class=\"name2\">"; + else echo "class=\"name1\">"; + echo PrintReady($name); + echo "<br /></span></a>"; + } + } + $children = $family->getChildren(); + $num = $family->getNumberOfChildren(); + if ($num>2) echo "<span class=\"name1\"><br />".i18n::translate('Siblings')."<br /></span>"; + if ($num==2) echo "<span class=\"name1\"><br />".i18n::translate('Sibling')."<br /></span>"; + foreach ($children as $id=>$child) { + $cid = $child->getXref(); + if ($cid!=$pid) { + echo " <a href=\"hourglass.php?pid={$cid}&show_spouse={$this->show_spouse}&show_full={$this->show_full}&generations={$this->generations}&box_width={$this->box_width}\"><span "; + $name = $child->getFullName(); + $name = rtrim($name); + if (hasRTLText($name)) + echo "class=\"name2\"> "; + else echo "class=\"name1\"> "; + echo PrintReady($name); + echo "<br /></span></a>"; + + } + } + } + } + echo "</td></tr></table>"; + echo "</div>"; + echo "</div>"; + } + } + } + echo "</td></tr>"; + echo "</table>"; + return $numkids; + } + + /** + * Calculates number of generations a person has + * + * @param mixed $pid ID of person to see how far down the descendency goes + * @param mixed $depth Pass in 0 and it calculates how far down descendency goes + * @access public + * @return maxdc Amount of generations the descendency actually goes + */ + function max_descendency_generations($pid, $depth) { + if ($depth > $this->generations) return $depth; + $person = WT_Person::getInstance($pid); + if (is_null($person)) return $depth; + $famids = $person->getSpouseFamilies(); + if ($person->getNumberOfChildren()==0) return $depth-1; + $maxdc = $depth; + foreach ($famids as $famid => $family) { + $ct = preg_match_all("/1 CHIL @(.*)@/", $family->getGedcomRecord(), $match, PREG_SET_ORDER); + for ($i=0; $i<$ct; $i++) { + $chil = trim($match[$i][1]); + $dc = $this->max_descendency_generations($chil, $depth+1); + if ($dc >= $this->generations) return $dc; + if ($dc > $maxdc) $maxdc = $dc; + } + } + + $maxdc++; + if ($maxdc==1) $maxdc++; + return $maxdc; + } + + /** + * setup all of the javascript that is needed for the hourglass chart + * + */ + function setupJavascript() { + global $bhalfheight; +?> +<script language="JavaScript" type="text/javascript"> +<!-- + var pastefield; + function paste_id(value) { + pastefield.value=value; + } + + // Hourglass control..... Ajax arrows at the end of chart + function ChangeDiv(div_id, ARID, full, spouse, width) { + var divelement = document.getElementById(div_id); + var oXmlHttp = createXMLHttp(); + oXmlHttp.open("get", "hourglass_ajax.php?show_full="+full+"&pid="+ ARID + "&generations=1&box_width="+width+"&show_spouse="+spouse, true); + oXmlHttp.onreadystatechange=function() + { + if (oXmlHttp.readyState==4) + { + divelement.innerHTML = oXmlHttp.responseText; + sizeLines(); + } + }; + oXmlHttp.send(null); + return false; + } + + // Hourglass control..... Ajax arrows at the end of descendants chart + function ChangeDis(div_id, ARID, full, spouse, width) { + var divelement = document.getElementById(div_id); + var oXmlHttp = createXMLHttp(); + oXmlHttp.open("get", "hourglass_ajax.php?type=desc&show_full="+full+"&pid="+ ARID + "&generations=1&box_width="+width+"&show_spouse="+spouse, true); + oXmlHttp.onreadystatechange=function() + { + if (oXmlHttp.readyState==4) + { + divelement.innerHTML = oXmlHttp.responseText; + sizeLines(); + } + }; + oXmlHttp.send(null); + return false; + } + + function sizeLines() { + var vlines; + vlines = document.getElementsByName("tvertline"); + for (i=0; i < vlines.length; i++) { + var pid = vlines[i].id.substr(vlines[i].id.indexOf("_")+1); + var hline = document.getElementById("table_"+pid); + var hline2 = document.getElementById("table2_"+pid); + var newHeight = Math.abs(hline.offsetHeight - (hline2.offsetTop + <?php echo $bhalfheight+2; ?>)); + vlines[i].style.height=newHeight+'px'; + } + + vlines = document.getElementsByName("bvertline"); + for (i=0; i < vlines.length; i++) { + var pid = vlines[i].id.substr(vlines[i].id.indexOf("_")+1); + var hline = document.getElementById("table_"+pid); + var hline2 = document.getElementById("table2_"+pid); + vlines[i].style.height=(hline.offsetTop+hline2.offsetTop + <?php echo $bhalfheight+2; ?>)+'px'; + } + + vlines = document.getElementsByName("pvline"); + //alert(vlines[0].parentNode.parentNode.parentNode); + for (i=0; i < vlines.length; i++) { + //vlines[i].parentNode.style.height="50%"; + vlines[i].style.height=(vlines[i].parentNode.offsetHeight/2)+'px'; + } + } +//--> +</script> +<?php +} +} diff --git a/library/WT/Controller/Individual.php b/library/WT/Controller/Individual.php new file mode 100644 index 0000000000..b7f31d5e15 --- /dev/null +++ b/library/WT/Controller/Individual.php @@ -0,0 +1,867 @@ +<?php +// Controller for the Individual Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_INDIVIDUAL_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; +require_once WT_ROOT.'includes/classes/class_module.php'; + +class WT_Controller_Individual extends WT_Controller_Base { + var $pid = ''; + var $indi = null; + var $diffindi = null; + var $accept_success = false; + var $default_tab = ''; + + var $name_count = 0; + var $total_names = 0; + var $SEX_COUNT = 0; + var $tabs; + var $Fam_Navigator = 'YES'; + var $NAME_LINENUM = 1; + var $SEX_LINENUM = null; + var $globalfacts = null; + + function init() { + global $USE_RIN, $MAX_ALIVE_AGE, $GEDCOM; + global $DEFAULT_PIN_STATE, $DEFAULT_SB_CLOSED_STATE; + global $Fam_Navigator; + + $this->pid = safe_GET_xref('pid'); + + $gedrec = find_person_record($this->pid, WT_GED_ID); + + if ($USE_RIN && $gedrec==false) { + $this->pid = find_rin_id($this->pid); + $gedrec = find_person_record($this->pid, WT_GED_ID); + } + if (empty($gedrec)) { + $gedrec = "0 @".$this->pid."@ INDI\n"; + } + + if (WT_USER_ID) { + // Start with the user's default tab + $this->default_tab=get_user_setting(WT_USER_ID, 'defaulttab'); + } else { + // Start with the gedcom's default tab + $this->default_tab=get_gedcom_setting(WT_GED_ID, 'GEDCOM_DEFAULT_TAB'); + } + + if (find_person_record($this->pid, WT_GED_ID) || find_updated_record($this->pid, WT_GED_ID)!==null) { + $this->indi = new WT_Person($gedrec); + $this->indi->ged_id=WT_GED_ID; // This record is from a file + } else if (!$this->indi) { + return false; + } + + $this->pid=$this->indi->getXref(); // Correct upper/lower case mismatch + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'INDI', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->pid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $gedrec = find_person_record($this->pid, WT_GED_ID); + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->indi = new WT_Person($gedrec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->pid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $gedrec = find_person_record($this->pid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->indi = new WT_Person($gedrec); + } + unset($_GET['action']); + break; + } + + //-- if the user can edit and there are changes then get the new changes + if ($this->show_changes && WT_USER_CAN_EDIT) { + $newrec = find_updated_record($this->pid, WT_GED_ID); + if (!empty($newrec)) { + $this->diffindi = new WT_Person($newrec); + $this->diffindi->setChanged(true); + } + } + + if ($this->show_changes) { + $this->indi->diffMerge($this->diffindi); + } + + // Initialise tabs + $this->tabs = WT_Module::getActiveTabs(); + foreach ($this->tabs as $mod) { + $mod->setController($this); + if ($mod->hasTabContent()) { + if (empty($this->default_tab)) { + $this->default_tab=$mod->getName(); + } + } + } + + if (!isset($_SESSION['WT_pin']) && $DEFAULT_PIN_STATE) + $_SESSION['WT_pin'] = true; + + if (!isset($_SESSION['WT_sb_closed']) && $DEFAULT_SB_CLOSED_STATE) + $_SESSION['WT_sb_closed'] = true; + + //-- handle ajax calls + if ($this->action=="ajax") { + $tab = 0; + if (isset($_REQUEST['module'])) { + $tabname = $_REQUEST['module']; + header("Content-Type: text/html; charset=UTF-8"); //AJAX calls do not have the meta tag headers and need this set + $mod = $this->tabs[$tabname]; + if ($mod) { + echo $mod->getTabContent(); + // Allow the other tabs to modify this one - e.g. lightbox does this. + echo WT_JS_START; + foreach (WT_Module::getActiveTabs() as $module) { + echo $module->getJSCallback(); + } + echo WT_JS_END; + } + } + + if (isset($_REQUEST['pin'])) { + if ($_REQUEST['pin']=='true') $_SESSION['WT_pin'] = true; + else $_SESSION['WT_pin'] = false; + } + + if (isset($_REQUEST['sb_closed'])) { + if ($_REQUEST['sb_closed']=='true') $_SESSION['WT_sb_closed'] = true; + else $_SESSION['WT_sb_closed'] = false; + } + + //-- only get the requested tab and then exit + if (WT_DEBUG_SQL) { + echo WT_DB::getQueryLog(); + } + exit; + } + } + + /** + * return the title of this page + * @return string the title of the page to go in the <title> tags + */ + function getPageTitle() { + if ($this->indi) { + return $this->indi->getFullName(); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + /** + * check if we can show the highlighted media object + * @return boolean + */ + function canShowHighlightedObject() { + global $MULTI_MEDIA, $SHOW_HIGHLIGHT_IMAGES, $USE_SILHOUETTE, $WT_IMAGES; + + if (($this->indi->canDisplayDetails()) && ($MULTI_MEDIA && $SHOW_HIGHLIGHT_IMAGES)) { + $firstmediarec = $this->indi->findHighlightedMedia(); + if ($firstmediarec) return true; + } + if ($USE_SILHOUETTE && isset($WT_IMAGES["default_image_U"])) { return true; } + return false; + } + /** + * check if we can show the gedcom record + * @return boolean + */ + function canShowGedcomRecord() { + global $SHOW_GEDCOM_RECORD; + if (WT_USER_CAN_EDIT && $SHOW_GEDCOM_RECORD && $this->indi->canDisplayDetails()) + return true; + } + + /** + * get the highlighted object HTML + * @return string HTML string for the <img> tag + */ + function getHighlightedObject() { + global $USE_THUMBS_MAIN, $THUMBNAIL_WIDTH, $USE_MEDIA_VIEWER, $GEDCOM, $WT_IMAGES, $USE_SILHOUETTE, $sex; + + if ($this->canShowHighlightedObject()) { + $firstmediarec = $this->indi->findHighlightedMedia(); + if (!empty($firstmediarec)) { + $filename = thumb_or_main($firstmediarec); // Do we send the main image or a thumbnail? + if (!$USE_THUMBS_MAIN || $firstmediarec["_THUM"]=='Y') { + $class = "image"; + } else { + $class = "thumbnail"; + } + $isExternal = isFileExternal($filename); + if ($isExternal && $class=="thumbnail") $class .= "\" width=\"".$THUMBNAIL_WIDTH; + if (!empty($filename)) { + $result = ""; + $imgsize = findImageSize($firstmediarec["file"]); + $imgwidth = $imgsize[0]+40; + $imgheight = $imgsize[1]+150; + //Gets the Media View Link Information and Concatenate + $mid = $firstmediarec['mid']; + + $name = $this->indi->getFullName(); + if (WT_USE_LIGHTBOX) { + echo "<a href=\"" . $firstmediarec["file"] . "\" rel=\"clearbox[general_1]\" rev=\"" . $mid . "::" . $GEDCOM . "::" . PrintReady(htmlspecialchars($name)) . "\">"; + } else if (!$USE_MEDIA_VIEWER && $imgsize) { + $result .= "<a href=\"javascript:;\" onclick=\"return openImage('".urlencode($firstmediarec["file"])."', $imgwidth, $imgheight);\">"; + } else { + $result .= "<a href=\"mediaviewer.php?mid={$mid}\">"; + } + $result .= "<img src=\"$filename\" align=\"left\" class=\"".$class."\" border=\"none\" title=\"".PrintReady(htmlspecialchars(strip_tags($name)))."\" alt=\"".PrintReady(htmlspecialchars(strip_tags($name)))."\" />"; + $result .= "</a>"; + return $result; + } + } + } + if ($USE_SILHOUETTE && isset($WT_IMAGES["default_image_U"])) { + $class = "\" width=\"".$THUMBNAIL_WIDTH; + $sex = $this->indi->getSex(); + $result = "<img src=\""; + if ($sex == 'F') { + $result .= $WT_IMAGES["default_image_F"]; + } elseif ($sex == 'M') { + $result .= $WT_IMAGES["default_image_M"]; + } else { + $result .= $WT_IMAGES["default_image_U"]; + } + $result .="\" class=\"".$class."\" border=\"none\" alt=\"\" />"; + return $result; + } + } + + /** + * print information for a name record + * + * Called from the individual information page + * @see individual.php + * @param Event $event the event object + */ + function print_name_record(&$event) { + global $UNDERLINE_NAME_QUOTES; + + if (!$event->canShow()) { + return false; + } + $factrec = $event->getGedComRecord(); + $linenum = $event->getLineNumber(); + + $this->name_count++; + echo '<div id="nameparts', $this->name_count, '"'; + if (strpos($factrec, "WT_OLD")!==false) { + echo " class=\"namered\""; + } + if (strpos($factrec, "WT_NEW")!==false) { + echo " class=\"nameblue\""; + } + echo ">"; + $dummy=new WT_Person($factrec); + $dummy->setPrimaryName(0); + echo '<div id="name1">'; + echo '<dl><dt class="label">', i18n::translate('Name'), '</dt>'; + echo '<dd class="field">', PrintReady($dummy->getFullName()); + if ($this->indi->canEdit() && !strpos($factrec, 'WT_OLD') && $this->name_count > 1) { + echo " <a href=\"javascript:;\" class=\"font9\" onclick=\"edit_name('".$this->pid."', ".$linenum."); return false;\">", i18n::translate('Edit'), "</a> | "; + echo "<a class=\"font9\" href=\"javascript:;\" onclick=\"delete_record('".$this->pid."', ".$linenum."); return false;\">", i18n::translate('Delete'), "</a>"; + } + echo '</dd>'; + echo '</dl>'; + echo '</div>'; + $ct = preg_match_all('/\n2 (\w+) (.*)/', $factrec, $nmatch, PREG_SET_ORDER); + for ($i=0; $i<$ct; $i++) { + echo '<div>'; + $fact = trim($nmatch[$i][1]); + if (($fact!="SOUR")&&($fact!="NOTE")&&($fact!="GIVN")&&($fact!="SURN")&&($fact!="SPFX")) { + echo '<dl><dt class="label">', translate_fact($fact, $this->indi), '</dt>'; + echo '<dd class="field">'; + if (isset($nmatch[$i][2])) { + $name = trim($nmatch[$i][2]); + $name = preg_replace("'/,'", ",", $name); + $name = preg_replace("'/'", " ", $name); + if ($UNDERLINE_NAME_QUOTES) { + $name=preg_replace('/"([^"]*)"/', '<span class="starredname">\\1</span>', $name); + } + $name=preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $name); + echo PrintReady($name); + } + echo '</dd>'; + echo '</dl>'; + } + echo '</div>'; + } + if (preg_match("/\d (NOTE)|(SOUR)/", $factrec)>0) { + // -- find sources for this name + echo '<div id="indi_note" class="clearfloat">'; + print_fact_sources($factrec, 2); + //-- find the notes for this name + print_fact_notes($factrec, 2); + echo '</div>'; + } + echo '</div>'; + } + + /** + * print information for a sex record + * + * Called from the individual information page + * @see individual.php + * @param Event $event the Event object + */ + function print_sex_record(&$event) { + global $sex; + + if (!$event->canShow()) return false; + $factrec = $event->getGedComRecord(); + $sex = $event->getDetail(); + if (empty($sex)) $sex = 'U'; + echo '<div id="sex"'; + if (strpos($factrec, 'WT_OLD')!==false) { + echo ' class="namered"'; + } + if (strpos($factrec, "WT_NEW")!==false) { + echo ' class="nameblue"'; + } + echo '>'; + echo '<dl><dt class="label">', i18n::translate('Gender'), '</dt>'; + echo '<dd class="field">'; + switch ($sex) { + case 'M': + echo i18n::translate('Male'), WT_Person::sexImage('M', 'small', '', i18n::translate('Male')); + break; + case 'F': + echo i18n::translate('Female'), WT_Person::sexImage('F', 'small', '', i18n::translate('Female')); + break; + case 'U': + echo i18n::translate_c('unknown gender', 'Unknown'), WT_Person::sexImage('U', 'small', '', i18n::translate_c('unknown gender', 'Unknown')); + break; + } + if ($this->SEX_COUNT>1) { + if ($this->indi->canEdit() && strpos($factrec, "WT_OLD")===false) { + if ($event->getLineNumber()=="new") { + echo "<a class=\"font9\" href=\"javascript:;\" onclick=\"add_new_record('".$this->pid."', 'SEX'); return false;\">".i18n::translate('Edit')."</a>"; + } else { + echo "<a class=\"font9\" href=\"javascript:;\" onclick=\"edit_record('".$this->pid."', ".$event->getLineNumber()."); return false;\">".i18n::translate('Edit')."</a> | "; + echo "<a class=\"font9\" href=\"javascript:;\" onclick=\"delete_record('".$this->pid."', ".$event->getLineNumber()."); return false;\">".i18n::translate('Delete')."</a>"; + } + } + } + echo '</dd>'; + echo '</dl>'; + // -- find sources + print_fact_sources($event->getGedComRecord(), 2); + //-- find the notes + print_fact_notes($event->getGedComRecord(), 2); + echo '</div>'; + } + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->indi) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_indi'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + //--make sure the totals are correct + $this->getGlobalFacts(); + if ($this->total_names<2) { + $submenu = new Menu(i18n::translate('Edit name')); + $submenu->addOnclick("return edit_name('".$this->pid."', $this->NAME_LINENUM);"); + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $submenu = new Menu(i18n::translate('Add new Name')); + $submenu->addOnclick("return add_name('".$this->pid."');"); + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if ($this->SEX_COUNT<2) { + $submenu = new Menu(i18n::translate('Edit gender')); + if ($this->SEX_LINENUM=="new") { + $submenu->addOnclick("return add_new_record('".$this->pid."', 'SEX');"); + } else { + $submenu->addOnclick("return edit_record('".$this->pid."', $this->SEX_LINENUM);"); + } + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + if (count($this->indi->getSpouseFamilyIds())>1) { + $submenu = new Menu(i18n::translate('Reorder families')); + $submenu->addOnclick("return reorder_families('".$this->pid."');"); + $submenu->addIcon('edit_fam'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // show/hide changes + if (find_updated_record($this->pid, WT_GED_ID)!==null) { + if (!$this->show_changes) { + $label = i18n::translate('This record has been updated. Click here to show changes.'); + $link = $this->indi->getHtmlUrl().'&show_changes=yes'; + } else { + $label = i18n::translate('Click here to hide changes.'); + $link = $this->indi->getHtmlUrl().'&show_changes=no'; + } + $submenu = new Menu($label, $link); + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), $this->indi->getHtmlUrl()."&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('edit_indi'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), $this->indi->getHtmlUrl()."&action=accept"); + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $this->canShowGedcomRecord()) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->pid."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Delete this individual')); + $submenu->addOnclick("return deleteperson('".$this->pid."');"); + $submenu->addIcon('edit_indi'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), $this->indi->getHtmlUrl()."&action=addfav&gid=".$this->pid); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } + + /** + * get global facts + * global facts are NAME and SEX + * @return array return the array of global facts + */ + function getGlobalFacts() { + if ($this->globalfacts==null) { + $this->globalfacts = $this->indi->getGlobalFacts(); + foreach ($this->globalfacts as $key => $value) { + $fact = $value->getTag(); + if ($fact=="SEX") { + $this->SEX_COUNT++; + $this->SEX_LINENUM = $value->getLineNumber(); + } + if ($fact=="NAME") { + $this->total_names++; + $this->NAME_LINENUM = $value->getLineNumber(); + } + } + } + return $this->globalfacts; + } + /** + * get the individual facts shown on tab 1 + * @return array + */ + function getIndiFacts() { + $indifacts = $this->indi->getIndiFacts(); + sort_facts($indifacts); + return $indifacts; + } + /** + * get the other facts shown on tab 2 + * @return array + */ + function getOtherFacts() { + $otherfacts = $this->indi->getOtherFacts(); + return $otherfacts; + } + + /** + * get the person box stylesheet class + * for the given person + * @param Person $person + * @return string returns 'person_box', 'person_boxF', or 'person_boxNN' + */ + function getPersonStyle(&$person) { + $sex = $person->getSex(); + switch($sex) { + case "M": + $isf = ""; + break; + case "F": + $isf = "F"; + break; + default: + $isf = "NN"; + break; + } + return "person_box".$isf; + } + + /** + * build an array of Person that will be used to build a list + * of family members on the close relatives tab + * @param Family $family the family we are building for + * @return array an array of Person that will be used to iterate through on the indivudal.php page + */ + function buildFamilyList(&$family, $type) { + global $PEDI_CODES, $PEDI_CODES_F, $PEDI_CODES_M, $WT_IMAGES; + + $people = array(); + if (!is_object($family)) return $people; + $labels = array(); + if ($type=="parents") { + $labels["parent"] = i18n::translate('Parent'); + $labels["mother"] = i18n::translate('Mother'); + $labels["father"] = i18n::translate('Father'); + $labels["sibling"] = i18n::translate('Sibling'); + $labels["sister"] = i18n::translate('Sister'); + $labels["brother"] = i18n::translate('Brother'); + } + if ($type=="step") { + $labels["parent"] = i18n::translate('Step-Parent'); + $labels["mother"] = i18n::translate('Step-Mother'); + $labels["father"] = i18n::translate('Step-Father'); + $labels["sibling"] = i18n::translate('Half-Sibling'); + $labels["sister"] = i18n::translate('Half-Sister'); + $labels["brother"] = i18n::translate('Half-Brother'); + } + if ($type=="spouse") { + if ($family->isNotMarried()) { + $labels["parent"] = i18n::translate('Partner'); + $labels["mother"] = i18n::translate('Partner'); + $labels["father"] = i18n::translate('Partner'); + } elseif ($family->isDivorced()) { + $labels["parent"] = i18n::translate('Ex-Spouse'); + $labels["mother"] = i18n::translate('Ex-Wife'); + $labels["father"] = i18n::translate('Ex-Husband'); + } else { + $marr_rec = $family->getMarriageRecord(); + if (!empty($marr_rec)) { + $type = $family->getMarriageType(); + if (empty($type) || stristr($type, "partner")===false) { + $labels["parent"] = i18n::translate('Spouse'); + $labels["mother"] = i18n::translate('Wife'); + $labels["father"] = i18n::translate('Husband'); + } else { + $labels["parent"] = i18n::translate('Partner'); + $labels["mother"] = i18n::translate('Partner'); + $labels["father"] = i18n::translate('Partner'); + } + } else { + $labels["parent"] = i18n::translate('Spouse'); + $labels["mother"] = i18n::translate('Wife'); + $labels["father"] = i18n::translate('Husband'); + } + } + $labels["sibling"] = i18n::translate('Child'); + $labels["sister"] = i18n::translate('Daughter'); + $labels["brother"] = i18n::translate('Son'); + } + $newhusb = null; + $newwife = null; + $newchildren = array(); + $delchildren = array(); + $children = array(); + $husb = null; + $wife = null; + if (!$family->getChanged()) { + $husb = $family->getHusband(); + $wife = $family->getWife(); + $children = $family->getChildren(); + } + //-- step families : set the label for the common parent + if ($type=="step") { + $fams = $this->indi->getChildFamilies(); + foreach ($fams as $key=>$fam) { + if ($fam->hasParent($husb)) $labels["father"] = i18n::translate('Father'); + if ($fam->hasParent($wife)) $labels["mother"] = i18n::translate('Mother'); + } + } + //-- set the label for the husband + if (!is_null($husb)) { + $label = $labels["parent"]; + $sex = $husb->getSex(); + if ($sex=="F") { + $label = $labels["mother"]; + } + if ($sex=="M") { + $label = $labels["father"]; + } + if ($husb->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $husb->setLabel($label); + } + //-- set the label for the wife + if (!is_null($wife)) { + $label = $labels["parent"]; + $sex = $wife->getSex(); + if ($sex=="F") { + $label = $labels["mother"]; + } + if ($sex=="M") { + $label = $labels["father"]; + } + if ($wife->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $wife->setLabel($label); + } + if ($this->show_changes) { + $newfamily = $family->getUpdatedFamily(); + if (!is_null($newfamily)) { + $newhusb = $newfamily->getHusband(); + //-- check if the husband in the family has changed + if (!is_null($newhusb) && !$newhusb->equals($husb)) { + $label = $labels["parent"]; + $sex = $newhusb->getSex(); + if ($sex=="F") { + $label = $labels["mother"]; + } + if ($sex=="M") { + $label = $labels["father"]; + } + if ($newhusb->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $newhusb->setLabel($label); + } + else $newhusb = null; + $newwife = $newfamily->getWife(); + //-- check if the wife in the family has changed + if (!is_null($newwife) && !$newwife->equals($wife)) { + $label = $labels["parent"]; + $sex = $newwife->getSex(); + if ($sex=="F") { + $label = $labels["mother"]; + } + if ($sex=="M") { + $label = $labels["father"]; + } + if ($newwife->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $newwife->setLabel($label); + } + else $newwife = null; + //-- check for any new children + $merged_children = array(); + $new_children = $newfamily->getChildren(); + $num = count($children); + for ($i=0; $i<$num; $i++) { + $child = $children[$i]; + if (!is_null($child)) { + $found = false; + foreach ($new_children as $key=>$newchild) { + if (!is_null($newchild)) { + if ($child->equals($newchild)) { + $found = true; + break; + } + } + } + if (!$found) $delchildren[] = $child; + else $merged_children[] = $child; + } + } + foreach ($new_children as $key=>$newchild) { + if (!is_null($newchild)) { + $found = false; + foreach ($children as $key1=>$child) { + if (!is_null($child)) { + if ($child->equals($newchild)) { + $found = true; + break; + } + } + } + if (!$found) $newchildren[] = $newchild; + } + } + $children = $merged_children; + } + } + //-- set the labels for the children + $num = count($children); + for ($i=0; $i<$num; $i++) { + if (!is_null($children[$i])) { + $label = $labels["sibling"]; + $sex = $children[$i]->getSex(); + if ($sex=="F") { + $label = $labels["sister"]; + } + if ($sex=="M") { + $label = $labels["brother"]; + } + if ($children[$i]->getXref()==$this->pid) { + $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + } + $famcrec = get_sub_record(1, "1 FAMC @".$family->getXref()."@", $children[$i]->getGedcomRecord()); + $pedi = get_gedcom_value("PEDI", 2, $famcrec, '', false); + if ($pedi) { + if ($sex=="F" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_F[$pedi].")"; + else if ($sex=="M" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_M[$pedi].")"; + else if (isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES[$pedi].")"; + } + $children[$i]->setLabel($label); + } + } + $num = count($newchildren); + for ($i=0; $i<$num; $i++) { + $label = $labels["sibling"]; + $sex = $newchildren[$i]->getSex(); + if ($sex=="F") { + $label = $labels["sister"]; + } + if ($sex=="M") { + $label = $labels["brother"]; + } + if ($newchildren[$i]->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $pedi = $newchildren[$i]->getChildFamilyPedigree($family->getXref()); + if ($sex=="F" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_F[$pedi].")"; + else if ($sex=="M" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_M[$pedi].")"; + else if (isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES[$pedi].")"; + $newchildren[$i]->setLabel($label); + } + $num = count($delchildren); + for ($i=0; $i<$num; $i++) { + $label = $labels["sibling"]; + $sex = $delchildren[$i]->getSex(); + if ($sex=="F") { + $label = $labels["sister"]; + } + if ($sex=="M") { + $label = $labels["brother"]; + } + if ($delchildren[$i]->getXref()==$this->pid) $label = "<img src=\"". $WT_IMAGES["selected"]. "\" alt=\"\" />"; + $pedi = $delchildren[$i]->getChildFamilyPedigree($family->getXref()); + if ($sex=="F" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_F[$pedi].")"; + else if ($sex=="M" && isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES_M[$pedi].")"; + else if (isset($PEDI_CODES[$pedi])) $label .= "<br />(".$PEDI_CODES[$pedi].")"; + $delchildren[$i]->setLabel($label); + } + if (!is_null($newhusb)) $people['newhusb'] = $newhusb; + if (!is_null($husb)) $people['husb'] = $husb; + if (!is_null($newwife)) $people['newwife'] = $newwife; + if (!is_null($wife)) $people['wife'] = $wife; + $people['children'] = $children; + $people['newchildren'] = $newchildren; + $people['delchildren'] = $delchildren; + return $people; + } + +// ----------------------------------------------------------------------------- +// Functions for GedFact Assistant +// ----------------------------------------------------------------------------- + /** + * include GedFact controller + */ + function census_assistant() { + require WT_ROOT.'modules/GEDFact_assistant/_CENS/census_1_ctrl.php'; + } + function medialink_assistant() { + require WT_ROOT.'modules/GEDFact_assistant/_MEDIA/media_1_ctrl.php'; + } +// ----------------------------------------------------------------------------- +// End GedFact Assistant Functions +// ----------------------------------------------------------------------------- +} diff --git a/library/WT/Controller/Lifespan.php b/library/WT/Controller/Lifespan.php new file mode 100644 index 0000000000..e8d278bc41 --- /dev/null +++ b/library/WT/Controller/Lifespan.php @@ -0,0 +1,567 @@ +<?php +// Controller for the timeline chart +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_LIFESPAN_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +function compare_people($a, $b) { + return GedcomDate::Compare($a->getEstimatedBirthDate(), $b->getEstimatedBirthDate()); +} + +class WT_Controller_Lifespan extends WT_Controller_Base { + var $pids = array (); + var $people = array(); + var $scale = 2; + var $YrowLoc = 125; + var $minYear = 0; + + // The following colours are deliberately omitted from the $colors list: + // Blue, Red, Black, White, Green + var $colors = array ('Aliceblue', 'Antiquewhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Blanchedalmond', 'Blueviolet', 'Brown', 'Burlywood', 'Cadetblue', 'Chartreuse', 'Chocolate', 'Coral', 'Cornflowerblue', 'Cornsilk', 'Crimson', 'Cyan', 'Darkcyan', 'Darkgoldenrod', 'Darkgray', 'Darkgreen', 'Darkkhaki', 'Darkmagenta', 'Darkolivegreen', 'Darkorange', 'Darkorchid', 'Darkred', 'Darksalmon', 'Darkseagreen', 'Darkslateblue', 'Darkturquoise', 'Darkviolet', 'Deeppink', 'Deepskyblue', 'Dimgray', 'Dodgerblue', 'Firebrick', 'Floralwhite', 'Forestgreen', 'Fuchsia', 'Gainsboro', 'Ghostwhite', 'Gold', 'Goldenrod', 'Gray', 'Greenyellow', 'Honeydew', 'Hotpink', 'Indianred', 'Ivory', 'Khaki', 'Lavender', 'Lavenderblush', 'Lawngreen', 'Lemonchiffon', 'Lightblue', 'Lightcoral', 'Lightcyan', 'Lightgoldenrodyellow', 'Lightgreen', 'Lightgrey', 'Lightpink', 'Lightsalmon', 'Lightseagreen', 'Lightskyblue', 'Lightslategray', 'Lightsteelblue', 'Lightyellow', 'Lime', 'Limegreen', 'Linen', 'Magenta', 'Maroon', 'Mediumaqamarine', ' Mediumblue', 'Mediumorchid', 'Mediumpurple', 'Mediumseagreen', 'Mediumslateblue', 'Mediumspringgreen', 'Mediumturquoise', 'Mediumvioletred', 'Mintcream', 'Mistyrose', 'Moccasin', 'Navajowhite', 'Oldlace', 'Olive', 'Olivedrab', 'Orange', 'Orangered', 'Orchid', 'Palegoldenrod', 'Palegreen', 'Paleturquoise', 'Palevioletred', 'Papayawhip', 'Peachpuff', 'Peru', 'Pink', 'Plum', 'Powderblue', 'Purple', 'Rosybrown', 'Royalblue', 'Saddlebrown', 'Salmon', 'Sandybrown', 'Seagreen', 'Seashell', 'Sienna', 'Silver', 'Skyblue', 'Slateblue', 'Slategray', 'Snow', 'Springgreen', 'Steelblue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'Whitesmoke', 'Yellow', 'YellowGreen'); + var $malecolorR = array('000', ' 010', ' 020', ' 030', ' 040', ' 050', ' 060', ' 070', ' 080', ' 090', ' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); + var $malecolorG = array('000', ' 010', ' 020', ' 030', ' 040', ' 050', ' 060', ' 070', ' 080', ' 090', ' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); + var $malecolorB = 255; + var $femalecolorR = 255; + var $femalecolorG = array('000', ' 010', ' 020', ' 030', ' 040', ' 050', ' 060', ' 070', ' 080', ' 090', ' 100', ' 110', ' 120', ' 130', ' 140', ' 150', ' 160', ' 170', ' 180', ' 190', ' 200', ' 210', ' 220', ' 230', ' 240', ' 250'); + var $femalecolorB = array('250', ' 240', ' 230', ' 220', ' 210', ' 200', ' 190', ' 180', ' 170', ' 160', ' 150', ' 140', ' 130', ' 120', ' 110', ' 100', ' 090', ' 080', ' 070', ' 060', ' 050', ' 040', ' 030', ' 020', ' 010', '000'); + var $color; + var $colorindex; + var $Fcolorindex; + var $Mcolorindex; + var $zoomfactor; + var $timelineMinYear; + var $timelineMaxYear; + var $birthMod; + var $deathMod; + var $endMod = 0; + var $modTest; + var $currentYear; + var $endDate; + var $startDate; + var $currentsex; + + /** + * Initialization function + */ + function init() { + global $GEDCOM_ID_PREFIX; + $this->colorindex = 0; + $this->Fcolorindex = 0; + $this->Mcolorindex = 0; + $this->zoomfactor = 10; + $this->color = "#0000FF"; + $this->currentYear = date("Y"); + $this->deathMod = 0; + $this->endDate = $this->currentYear; + + + //--new pid + $newpid=safe_GET_xref('newpid'); + if ($newpid) { + $person = WT_Person::getInstance($newpid); + if (is_null($person) && $GEDCOM_ID_PREFIX) { + //-- allow the user to enter the id without the "I" prefix + $newpid = $GEDCOM_ID_PREFIX.$newpid; + $person = WT_Person::getInstance($newpid); + } + //-- make sure we have the id from the gedcom record + else $newpid = $person->getXref(); + } + + if (safe_GET('clear', '1')=='1') { + unset($_SESSION['timeline_pids']); + } else { + if (isset($_SESSION['timeline_pids'])) + $this->pids = $_SESSION['timeline_pids']; + + if (!empty ($newpid)) + $this->pids[] = $newpid; + + //-- pids array + $pids=safe_GET_xref('pids'); + if ($pids) { + $this->pids = $pids; + if (!empty ($newpid)) + $this->pids[] = $newpid; + } + + //-- gets the immediate family for the individual being added if the include immediate family checkbox is checked. + if (safe_GET('addFamily', 'yes')=='yes') { + if (isset($newpid)) $this->addFamily($newpid); + } + + $remove = safe_GET_xref('remove'); + + //-- always start with someone on the chart + if (count($this->pids)==0) { + $this->pids[] = $this->addFamily(check_rootid("")); + } + + //-- limit to a certain place + $searchplace=safe_GET('place'); + if (!empty($searchplace)) { + $place_pids = get_place_positions($searchplace); + if (count($place_pids)>0) { + $this->pids = $place_pids; + } + } + + //-- store the people in the session + $_SESSION['timeline_pids'] = $this->pids; + + $beginYear =safe_GET_integer('beginYear', 0, date('Y')+100, 0); + $endYear =safe_GET_integer('endYear', 0, date('Y')+100, 0); + if ($beginYear==0 || $endYear==0) { + //-- cleanup user input + $this->pids = array_unique($this->pids); //removes duplicates + foreach ($this->pids as $key => $value) { + if ($value != $remove) { + $this->pids[$key] = $value; + $person = WT_Person::getInstance($value); + // get_place_positions() returns families as well as individuals. + if ($person && $person->getType()=='INDI') { + $bdate = $person->getEstimatedBirthDate(); + $ddate = $person->getEstimatedDeathDate(); + + //--Checks to see if the details of that person can be viewed + if ($bdate->isOK() && $person->canDisplayDetails()) { + $this->people[] = $person; + } + } + } + } + } + + + //--Finds if the begin year and end year textboxes are not empty + else { + //-- reset the people array when doing a year range search + $this->people = array(); + //Takes the begining year and end year passed by the postback and modifies them and uses them to populate + //the time line + + //Variables to restrict the person boxes to the year searched. + //--Searches for individuals who had an even between the year begin and end years + $indis = search_indis_year_range($beginYear, $endYear); + //--Populates an array of people that had an event within those years + + foreach ($indis as $person) { + if (empty($searchplace) || in_array($person->getXref(), $this->pids)) { + $bdate = $person->getEstimatedBirthDate(); + $ddate = $person->getEstimatedDeathDate(); + //--Checks to see if the details of that person can be viewed + if ($bdate->isOK() && $person->canDisplayDetails()) { + $this->people[] = $person; + } + } + } + unset($_SESSION['timeline_pids']); + } + + //--Sort the arrar in order of being year + uasort($this->people, "compare_people"); + //If there is people in the array posted back this if occurs + if (isset ($this->people[0])) { + //Find the maximum Death year and mimimum Birth year for each individual returned in the array. + $bdate = $this->people[0]->getEstimatedBirthDate(); + $ddate = $this->people[0]->getEstimatedDeathDate(); + $this->timelineMinYear=$bdate->gregorianYear(); + $this->timelineMaxYear=$ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y'); + foreach ($this->people as $key => $value) { + $bdate = $value->getEstimatedBirthDate(); + $ddate = $value->getEstimatedDeathDate(); + $this->timelineMinYear=min($this->timelineMinYear, $bdate->gregorianYear()); + $this->timelineMaxYear=max($this->timelineMaxYear, $ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y')); + } + + if ($this->timelineMaxYear > $this->currentYear) { + $this->timelineMaxYear = $this->currentYear; + } + + } + else { + // Sets the default timeline length + $this->timelineMinYear = date("Y") - 101; + $this->timelineMaxYear = date("Y"); + } + } + } + + /** + * Add a person and his or her immediate family members to + * the pids array + * @param string $newpid + */ + function addFamily($newpid, $gen=0) { + if (!empty ($newpid)) { + $person = WT_Person::getInstance($newpid); + if (is_null($person)) return; + $this->pids[] = $newpid; + $families = $person->getSpouseFamilies(); + //-- foreach gets the spouse and children of the individual. + foreach ($families as $famID => $family) { + if ($newpid != $family->getHusbId()) { + if ($gen>0) $this->pids[] = addFamily($family->getHusbId(), $gen-1); + else $this->pids[] = $family->getHusbId(); + } + if ($newpid != $family->getWifeId()) { + if ($gen>0) $this->pids[] = addFamily($family->getWifeId(), $gen-1); + else $this->pids[] = $family->getWifeId(); + } + $children = $family->getChildren(); + foreach ($children as $childID => $child) { + if ($gen>0) $this->pids[] = addFamily($child->getXref(), $gen-1); + else $this->pids[] = $child->getXref(); + } + } + $families = $person->getChildFamilies(); + //-- foreach gets the father, mother and sibblings of the individual. + foreach ($families as $famID => $family) { + if ($gen>0) $this->pids[] = addFamily($family->getHusbId(), $gen-1); + else $this->pids[] = $family->getHusbId(); + if ($gen>0) $this->pids[] = addFamily($family->getWifeId(), $gen-1); + else $this->pids[] = $family->getWifeId(); + $children = $family->getChildren(); + foreach ($children as $childID => $child) { + if ($newpid != $child->getXref()) { + if ($gen>0) $this->pids[] = addFamily($child->getXref(), $gen-1); + else $this->pids[] = $child->getXref(); + } + } + } + } + } + + // sets the start year and end year to a factor of 5 + function ModifyYear($year, $key) { + $temp = $year; + switch ($key) { + case 1 : //rounds beginning year + $this->birthMod = ($year % 5); + $year = $year - ($this->birthMod); + if ($temp == $year) { + $this->modTest = 0; + } + else $this->modTest = 1; + break; + case 2 : //rounds end year + $this->deathMod = ($year % 5); + //Only executed if the year needs to be modified + if ($this->deathMod > 0) { + $this->endMod = (5 - ($this->deathMod)); + } + else { + $this->endMod = 0; + } + $year = $year + ($this->endMod); + break; + } + return $year; + } + //Prints the time line + function PrintTimeline($startYear, $endYear) { + $leftPosition = 14; //start point + $width = 8; //base width + $height = 10; //standard height + $tickDistance = 50; //length of one timeline section + $top = 65; //top starting position + $yearSpan = 5; //default zoom level + $newStartYear = $this->ModifyYear($startYear, 1); //starting date for timeline + $this->timelineMinYear = $newStartYear; + $newEndYear = $this->ModifyYear($endYear, 2); //ending date for timeline + $totalYears = $newEndYear - $newStartYear; //length of timeline + $timelineTick = $totalYears / $yearSpan; //calculates the length of the timeline + + for ($i = 0; $i < $timelineTick; $i ++) { //prints the timeline + echo "<div class=\"sublinks_cell\" style=\"text-align: left; position: absolute; top: ", $top, "px; left: ", $leftPosition, "px; width: ", $tickDistance, "px;\">$newStartYear<img src=\"images/timelineChunk.gif\" alt=\"\" /></div>"; //onclick="zoomToggle('100px', '100px', '200px', '200px', this);" + $leftPosition += $tickDistance; + $newStartYear += $yearSpan; + + } + echo "<div class=\"sublinks_cell\" style=\"text-align: left; position: absolute; top: ", $top, "px; left: ", $leftPosition, "px; width: ", $tickDistance, "px;\">$newStartYear</div>"; + } + + //method used to place the person boxes onto the timeline + function fillTL($ar, $int, $top) { + global $maxX, $zindex; + + $zindex = count($ar); + + $rows = array(); + $modFix = 0; + if ($this->modTest == 1) { + $modFix = (9 * $this->birthMod); + } + //base case + if (count($ar) == 0) return $top; + $maxY = $top; + + foreach ($ar as $key => $value) { + //Creates appropriate color scheme to show relationships + $this->currentsex = $value->getSex(); + if ($this->currentsex == "M") { + $this->Mcolorindex++; + if (!isset($this->malecolorR[$this->Mcolorindex])) $this->Mcolorindex=0; + $this->malecolorR[$this->Mcolorindex]; + $this->Mcolorindex++; + if (!isset($this->malecolorG[$this->Mcolorindex])) $this->Mcolorindex=0; + $this->malecolorG[$this->Mcolorindex]; + $red = dechex($this->malecolorR[$this->Mcolorindex]); + $green =dechex($this->malecolorR[$this->Mcolorindex]); + if (strlen($red)<2) { + $red = "0".$red; + } + if (strlen($green)<2) { + $green = "0".$green; + } + + $this->color = "#".$red.$green.dechex($this->malecolorB); + } + else if ($this->currentsex == "F") { + $this->Fcolorindex++; + if (!isset($this->femalecolorG[$this->Fcolorindex])) $this->Fcolorindex = 0; + $this->femalecolorG[$this->Fcolorindex]; + $this->Fcolorindex++; + if (!isset($this->femalecolorB[$this->Fcolorindex])) $this->Fcolorindex = 0; + $this->femalecolorB[$this->Fcolorindex]; + $this->color = "#".dechex($this->femalecolorR).dechex($this->femalecolorG[$this->Fcolorindex]).dechex($this->femalecolorB[$this->Fcolorindex]); + } + else { + $this->color = $this->colors[$this->colorindex]; + } + + //set start position and size of person-box according to zoomfactor + /* @var $value Person */ + $bdate=$value->getEstimatedBirthDate(); + $ddate=$value->getEstimatedDeathDate(); + $birthYear = $bdate->gregorianYear(); + $deathYear = $ddate->gregorianYear() ? $ddate->gregorianYear() : date('Y'); + + $width = ($deathYear - $birthYear) * $this->zoomfactor; + $height = 2 * $this->zoomfactor; + + $startPos = (($birthYear - $this->timelineMinYear) * $this->zoomfactor) + 14 + $modFix; + if (stristr($value->getFullName(), "starredname")) + $minlength = (utf8_strlen($value->getFullName())-34) * $this->zoomfactor; + else + $minlength = utf8_strlen($value->getFullName()) * $this->zoomfactor; + + if ($startPos > 15) { + $startPos = (($birthYear - $this->timelineMinYear) * $this->zoomfactor) + 15 + $modFix; + $startPos = (($birthYear - $this->timelineMinYear) * $this->zoomfactor) + 15; + $width = (($deathYear - $birthYear) * $this->zoomfactor) - 2; + } + //set start position to deathyear + $int = $deathYear; + //set minimum width for single year lifespans + if ($width < 10) + { + $width = 10; + $int = $birthYear+1; + } + + $lifespan = "<span dir=\"ltr\">$birthYear-</span>"; + $deathReal = $value->getDeathDate()->isOK(); + $birthReal = $value->getBirthDate()->isOK(); + if ($value->isDead() && $deathReal) $lifespan .= "<span dir=\"ltr\">$deathYear</span>"; + $lifespannumeral = $deathYear - $birthYear; + + //-- calculate a good Y top value + $Y = $top; + $Z = $zindex; + $ready = false; + while (!$ready) { + if (!isset($rows[$Y])) { + $ready = true; + $rows[$Y]["x1"] = $startPos; + $rows[$Y]["x2"] = $startPos+$width; + $rows[$Y]["z"] = $zindex; + } + else { + if ($rows[$Y]["x1"] > $startPos+$width) { + $ready = true; + $rows[$Y]["x1"] = $startPos; + $Z = $rows[$Y]["z"]; + } + else if ($rows[$Y]["x2"] < $startPos) { + $ready = true; + $rows[$Y]["x2"] = $startPos+$width; + $Z = $rows[$Y]["z"]; + } + else { + //move down 25 pixels + if ($this->zoomfactor > 10)$Y += 25 + $this->zoomfactor; + else $Y += 25; + } + } + } + + //Need to calculate each event and the spacing between them + // event1 distance will be event - birthyear that will be the distance. then each distance will chain off that + + //$event[][] = {"Cell 1 will hold events"}{"cell2 will hold time between that and the next value"}; + //$value->add_historical_facts(); + $value->add_family_facts(false); + $unparsedEvents = $value->getIndiFacts(); + sort_facts($unparsedEvents); + + $eventinformation = Array(); + $eventspacing = Array(); + foreach ($unparsedEvents as $index=>$val) { + $date = $val->getDate(); + if (!empty($date)) { + $fact = $val->getTag(); + $yearsin = $date->date1->y-$birthYear; + if ($lifespannumeral==0) { + $lifespannumeral = 1; + } + $eventwidth = ($yearsin/$lifespannumeral)* 100; // percent of the lifespan before the event occured used for determining div spacing + // figure out some schema + $evntwdth = $eventwidth."%"; + //-- if the fact is a generic EVENt then get the qualifying TYPE + if ($fact=="EVEN") { + $fact = $val->getType(); + } + $place = $val->getPlace(); + $trans = translate_fact($fact); + if (isset($eventinformation[$evntwdth])) { + $eventinformation[$evntwdth] .= "<br />".$trans."<br />".strip_tags($date->Display(false, '', NULL, false))." ".$place; + } else { + $eventinformation[$evntwdth]= $fact."-fact, ".$trans."<br />".strip_tags($date->Display(false, '', NULL, false))." ".$place; + } + } + } + + $bdate=$value->getEstimatedBirthDate(); + $ddate=$value->getEstimatedDeathDate(); + if ($width > ($minlength +110)) { + echo "<div id=\"bar_", $value->getXref(), "\" style=\"position: absolute; top:", $Y, "px; left:", $startPos, "px; width:", $width, "px; height:", $height, "px; background-color:", $this->color, "; border: solid blue 1px; z-index:$Z;\">"; + foreach ($eventinformation as $evtwidth=>$val) { + echo "<div style=\"position:absolute; left:", $evtwidth, ";\"><a class=\"showit\" href=\"#\" style=\"top:-2px; font-size:10px;\"><b>"; + $text = explode("-fact, ", $val); + $fact = $text[0]; + $val = $text[1]; + echo abbreviate_fact($fact); + echo "</b><span>", PrintReady($val), "</span></a></div>"; + } + $indiName = PrintReady(str_replace(array('<span class="starredname">', '</span>'), array('<u>', '</u>'), $value->getFullName())); + echo "<table><tr><td width=\"15\"><a class=\"showit\" href=\"#\"><b>"; + echo abbreviate_fact('BIRT'); + echo "</b><span>", $value->getSexImage(), $indiName, "<br/>", translate_fact('BIRT'), " ", strip_tags($bdate->Display(false)), " ", PrintReady($value->getBirthPlace()), "</span></a></td>" , + "<td align=\"left\" width=\"100%\"><a href=\"", $value->getHtmlUrl(), "\">", $value->getSexImage(), $indiName, ": $lifespan </a></td>" , + "<td width=\"15\">"; + if ($value->isDead()) { + if ($deathReal || $value->isDead()) { + echo "<a class=\"showit\" href=\"#\"><b>"; + echo abbreviate_fact('DEAT'); + if (!$deathReal) echo "*"; + echo "</b><span>".$value->getSexImage().$indiName."<br/>".translate_fact('DEAT')." ".strip_tags($ddate->Display(false))." ".PrintReady($value->getDeathPlace())."</span></a>"; + } + } + echo "</td></tr></table>"; + echo '</div>'; + + } else { + if ($width > $minlength +5) { + echo "<div style=\"text-align: left; position: absolute; top:", $Y, "px; left:", $startPos, "px; width:", $width, "px; height:", $height, "px; background-color:", $this->color, "; border: solid blue 1px; z-index:$Z;\">"; + foreach ($eventinformation as $evtwidth=>$val) { + echo "<div style=\"position:absolute; left:".$evtwidth." \"><a class=\"showit\" href=\"#\" style=\"top:-2px; font-size:10px;\"><b>"; + $text = explode("-fact,", $val); + $fact = $text[0]; + $val = $text[1]; + echo abbreviate_fact($fact); + echo "</b><span>".PrintReady($val)."</span></a></div>"; + } + $indiName = PrintReady(str_replace(array('<span class="starredname">', '</span>'), array('<u>', '</u>'), $value->getFullName())); + echo "<table dir=\"ltr\"><tr><td width=\"15\"><a class=\"showit\" href=\"#\"><b>"; + echo abbreviate_fact('BIRT'); + if (!$birthReal) echo "*"; + echo "</b><span>".$value->getSexImage().$indiName."<br/>".translate_fact('BIRT')." ".strip_tags($bdate->Display(false))." ".PrintReady($value->getBirthPlace())."</span></a></td>" . + "<td align=\"left\" width=\"100%\"><a href=\"".$value->getHtmlUrl()."\">".$value->getSexImage().$indiName."</a></td>" . + "<td width=\"15\">"; + if ($value->isDead()) { + if ($deathReal || $value->isDead()) { + echo "<a class=\"showit\" href=\"#\"><b>"; + echo abbreviate_fact('DEAT'); + if (!$deathReal) echo "*"; + echo "</b><span>".$value->getSexImage().$indiName."<br/>".translate_fact('DEAT')." ".strip_tags($ddate->Display(false))." ".PrintReady($value->getDeathPlace())."</span></a>"; + } + } + echo "</td></tr></table>"; + echo '</div>'; + } else { + echo "<div style=\"text-align: left; position: absolute;top:", $Y, "px; left:", $startPos, "px;width:", $width, "px; height:", $height, "px; background-color:", $this->color, "; border: solid blue 1px; z-index:$Z;\">" ; + + $indiName = PrintReady(str_replace(array('<span class="starredname">', '</span>'), array('<u>', '</u>'), $value->getFullName())); + echo "<a class=\"showit\" href=\"".$value->getHtmlUrl()."\"><b>"; + echo abbreviate_fact('BIRT'); + echo "</b><span>".$value->getSexImage().$indiName."<br/>".translate_fact('BIRT')." ".strip_tags($bdate->Display(false))." ".PrintReady($value->getBirthPlace())."<br/>"; + foreach ($eventinformation as $evtwidth=>$val) { + $text = explode("-fact,", $val); + $val = $text[1]; + echo $val."<br />"; + } + if ($value->isDead() && $deathReal) echo translate_fact('DEAT')." ".strip_tags($ddate->Display(false))." ".PrintReady($value->getDeathPlace()); + echo "</span></a>"; + echo '</div>'; + } + } + $zindex--; + + if ($maxX < $startPos + $width) + $maxX = $startPos + $width; + if ($maxY < $Y) $maxY = $Y; + } + return $maxY; + } + + /** + * check the privacy of the incoming people to make sure they can be shown + */ + function checkPrivacy() { + $printed = false; + for ($i = 0; $i < count($this->people); $i ++) { + if (!$this->people[$i]->canDisplayDetails()) { + if ($this->people[$i]->canDisplayName()) { + $indiName = PrintReady(str_replace(array('<span class="starredname">', '</span>'), array('<u>', '</u>'), $this->people[$i]->getFullName())); + echo " <a href=\"".$this->people[$i]->getHtmlUrl()."\">".$indiName."</a>"; + print_privacy_error(); + echo "<br />"; + $printed = true; + } else + if (!$printed) { + print_privacy_error(); + echo "<br />"; + } + } + } + } +} diff --git a/library/WT/Controller/Media.php b/library/WT/Controller/Media.php new file mode 100644 index 0000000000..990e797645 --- /dev/null +++ b/library/WT/Controller/Media.php @@ -0,0 +1,412 @@ +<?php +// Controller for the Media Menu +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_MEDIA_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; + +class WT_Controller_Media extends WT_Controller_Base { + var $mid; + var $mediaobject; + var $show_changes=true; + + function init() { + global $MEDIA_DIRECTORY, $USE_MEDIA_FIREWALL, $GEDCOM; + + $filename = safe_GET('filename'); + $this->mid = safe_GET_xref('mid'); + + if ($USE_MEDIA_FIREWALL && empty($filename) && empty($this->mid)) { + // this section used by mediafirewall.php to determine what media file was requested + + if (isset($_SERVER['REQUEST_URI'])) { + // NOTE: format of this server variable: + // Apache: /phpGedView/media/a.jpg + // IIS: /phpGedView/mediafirewall.php?404;http://server/phpGedView/media/a.jpg + $requestedfile = $_SERVER['REQUEST_URI']; + // urldecode the request + $requestedfile = rawurldecode($requestedfile); + // make sure the requested file is in the media directory + if (strpos($requestedfile, $MEDIA_DIRECTORY) !== false) { + // strip off the wt directory and media directory from the requested url so just the image information is left + $filename = substr($requestedfile, strpos($requestedfile, $MEDIA_DIRECTORY) + strlen($MEDIA_DIRECTORY) - 1); + // strip the ged param if it was passed on the querystring + // would be better if this could remove any querystring, but '?' are valid in unix filenames + if (strpos($filename, '?ged=') !== false) { + $filename = substr($filename, 0, strpos($filename, '?ged=')); + } + // if user requested a thumbnail, lookup permissions based on the original image + $filename = str_replace('/thumbs', '', $filename); + } else { + // the MEDIA_DIRECTORY of the current GEDCOM was not part of the requested file + // either the requested file is in a different GEDCOM (with a different MEDIA_DIRECTORY) + // or the Media Firewall is being called from outside the MEDIA_DIRECTORY + // this condition can be detected by the media firewall by calling controller->getServerFilename() + } + } + } + + //Checks to see if the File Name ($filename) exists + if (!empty($filename)) { + //If the File Name ($filename) is set, then it will call the method to get the Media ID ($this->mid) from the File Name ($filename) + $this->mid = get_media_id_from_file($filename); + if (!$this->mid) { + //This will set the Media ID to be false if the File given doesn't match to anything in the database + $this->mid = false; + // create a very basic gedcom record for this file so that the functions of the media object will work + // this is used by the media firewall when requesting an object that exists in the media firewall directory but not in the gedcom + $this->mediaobject = new WT_Media("0 @"."0"."@ OBJE\n1 FILE ".$filename); + } + } + + //checks to see if the Media ID ($this->mid) is set. If the Media ID isn't set then there isn't any information avaliable for that picture the picture doesn't exist. + if ($this->mid) { + //This creates a Media Object from the getInstance method of the Media Class. It takes the Media ID ($this->mid) and creates the object. + $this->mediaobject = WT_Media::getInstance($this->mid); + //This sets the controller ID to be the Media ID + $this->pid = $this->mid; + } + + if (is_null($this->mediaobject)) return false; + $this->mediaobject->ged_id=WT_GED_ID; // This record is from a file + + $this->mid=$this->mediaobject->getXref(); // Correct upper/lower case mismatch + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'OBJE', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->pid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $mediarec = find_media_record($this->pid, WT_GED_ID); + if (empty($mediarec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->mediaobject = new WT_Media($mediarec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->pid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $mediarec = find_media_record($this->pid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($mediarec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->mediaobject = new WT_Media($mediarec); + } + unset($_GET['action']); + break; + } + } + + /** + * return the title of this page + * @return string the title of the page to go in the <title> tags + */ + function getPageTitle() { + if ($this->mediaobject) { + return $this->mediaobject->getFullName()." - ".i18n::translate('Multimedia Information'); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + function canDisplayDetails() { + return $this->mediaobject->canDisplayDetails(); + } + + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->mediaobject) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + + $links = get_media_relations($this->pid); + $linktoid = "new"; + foreach ($links as $linktoid => $type) { + break; // we're only interested in the key of the first list entry + } + + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_media'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Edit media')); + $submenu->addOnclick("window.open('addmedia.php?action=editmedia&pid={$this->pid}', '_blank', 'top=50,left=50,width=600,height=500,resizable=1,scrollbars=1')"); + $submenu->addIcon('edit_media'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + // main link displayed on page + if (WT_USER_GEDCOM_ADMIN && file_exists(WT_ROOT.'modules/GEDFact_assistant/_MEDIA/media_1_ctrl.php')) { + $submenu = new Menu(i18n::translate('Manage links')); + } else { + $submenu = new Menu(i18n::translate('Set link')); + } + + // GEDFact assistant Add Media Links ======================= + if (WT_USER_GEDCOM_ADMIN && file_exists(WT_ROOT.'modules/GEDFact_assistant/_MEDIA/media_1_ctrl.php')) { + $submenu->addOnclick("return ilinkitem('".$this->pid."','manage');"); + $submenu->addIcon('edit_media'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + // Do not print ssubmunu + } else { + $submenu->addOnclick("return ilinkitem('".$this->pid."','person');"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + + $ssubmenu = new Menu(i18n::translate('To Person')); + $ssubmenu->addOnclick("return ilinkitem('".$this->pid."','person');"); + $ssubmenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addSubMenu($ssubmenu); + + $ssubmenu = new Menu(i18n::translate('To Family')); + $ssubmenu->addOnclick("return ilinkitem('".$this->pid."','family');"); + $ssubmenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addSubMenu($ssubmenu); + + $ssubmenu = new Menu(i18n::translate('To Source')); + $ssubmenu->addOnclick("return ilinkitem('".$this->pid."','source');"); + $ssubmenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addSubMenu($ssubmenu); + } + $menu->addSubmenu($submenu); + + $menu->addSeparator(); + } + + // show/hide changes + if (find_updated_record($this->pid, WT_GED_ID)!==null) { + if (!$this->show_changes) { + $label = i18n::translate('This record has been updated. Click here to show changes.'); + $link = "mediaviewer.php?mid={$this->pid}&show_changes=yes"; + } else { + $label = i18n::translate('Click here to hide changes.'); + $link = "mediaviewer.php?mid={$this->pid}&samp;how_changes=no"; + } + $submenu = new Menu($label, $link); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), "mediaviewer.php?mid={$this->pid}&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('notes'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), "mediaviewer.php?mid={$this->pid}&action=accept"); + $submenu->addIcon('notes'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->pid."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_GEDCOM_ADMIN) { + $submenu = new Menu(i18n::translate('Remove object'), "media.php?action=removeobject&xref=".$this->pid); + $submenu->addOnclick("return confirm('".i18n::translate('Are you sure you want to remove this object from the database?')."')"); + $submenu->addIcon('edit_media'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), "mediaviewer.php?action=addfav&mid={$this->mid}&gid={$this->mid}"); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } + + /** + * check if we can show the gedcom record + * @return boolean + */ + function canShowGedcomRecord() { + global $SHOW_GEDCOM_RECORD; + if ($SHOW_GEDCOM_RECORD && $this->mediaobject->canDisplayDetails()) + return true; + } + + /** + * return a list of facts + * @return array + */ + function getFacts($includeFileName=true) { + global $GEDCOM, $MEDIA_TYPES; + + $ignore = array("TITL","FILE"); + if ($this->show_changes) { + $ignore = array(); + } elseif (WT_USER_GEDCOM_ADMIN) { + $ignore = array("TITL"); + } + + $facts = $this->mediaobject->getFacts($ignore); + sort_facts($facts); + //if ($includeFileName) $facts[] = new Event("1 FILE ".$this->mediaobject->getFilename()); + $mediaType = $this->mediaobject->getMediatype(); + if (array_key_exists($mediaType, $MEDIA_TYPES)) $facts[] = new Event("1 TYPE ".$MEDIA_TYPES[$mediaType]); + else $facts[] = new Event("1 TYPE ".i18n::translate('Other')); + + if ($this->show_changes && ($newrec=find_updated_record($this->pid, WT_GED_ID))!==null) { + $newmedia = new WT_Media($newrec); + $newfacts = $newmedia->getFacts($ignore); + if ($includeFileName) $newfacts[] = new Event("1 TYPE ".$MEDIA_TYPES[$mediaType]); + $newfacts[] = new Event("1 FORM ".$newmedia->getFiletype()); + $mediaType = $newmedia->getMediatype(); + if (array_key_exists($mediaType, $MEDIA_TYPES)) $newfacts[] = new Event("1 TYPE ".$mediaType); + else $newfacts[] = new Event("1 TYPE ".i18n::translate('Other')); + //-- loop through new facts and add them to the list if they are any changes + //-- compare new and old facts of the Personal Fact and Details tab 1 + for ($i=0; $i<count($facts); $i++) { + $found=false; + foreach ($newfacts as $indexval => $newfact) { + if (trim($newfact->gedcomRecord)==trim($facts[$i]->gedcomRecord)) { + $found=true; + break; + } + } + if (!$found) { + $facts[$i]->gedcomRecord.="\nWT_OLD\n"; + } + } + foreach ($newfacts as $indexval => $newfact) { + $found=false; + foreach ($facts as $indexval => $fact) { + if (trim($fact->gedcomRecord)==trim($newfact->gedcomRecord)) { + $found=true; + break; + } + } + if (!$found) { + $newfact->gedcomRecord.="\nWT_NEW\n"; + $facts[]=$newfact; + } + } + } + + if ($this->mediaobject->fileExists()) { + // get height and width of image, when available + if ($this->mediaobject->getWidth()) { + $facts[] = new Event("1 EVEN " . '<span dir="ltr">' . $this->mediaobject->getWidth()." x ".$this->mediaobject->getHeight() . '</span>' . "\n2 TYPE image_size"); + } + //Prints the file size + //Rounds the size of the image to 2 decimal places + $facts[] = new Event("1 EVEN " . '<span dir="ltr">' . round($this->mediaobject->getFilesizeraw()/1024, 2)." kb" . '</span>' . "\n2 TYPE file_size"); + } + + sort_facts($facts); + return $facts; + } + + /** + * get the relative file path of the image on the server + * @return string + */ + function getLocalFilename() { + if ($this->mediaobject) { + return $this->mediaobject->getLocalFilename(); + } else { + return false; + } + } + + /** + * get the file name on the server + * @return string + */ + function getServerFilename() { + if ($this->mediaobject) { + return $this->mediaobject->getServerFilename(); + } else { + return false; + } + } +} diff --git a/library/WT/Controller/Note.php b/library/WT/Controller/Note.php new file mode 100644 index 0000000000..0ee2f95a36 --- /dev/null +++ b/library/WT/Controller/Note.php @@ -0,0 +1,233 @@ +<?php +// Controller for the Shared Note Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_NOTE_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; + +class WT_Controller_Note extends WT_Controller_Base { + var $nid; + var $note = null; + var $diffnote = null; + var $accept_success = false; + + function init() { + $this->nid = safe_GET_xref('nid'); + + $gedrec = find_other_record($this->nid, WT_GED_ID); + + if (find_other_record($this->nid, WT_GED_ID) || find_updated_record($this->nid, WT_GED_ID)!==null) { + $this->note = new WT_Note($gedrec); + $this->note->ged_id=WT_GED_ID; // This record is from a file + } else if (!$gedrec) { + return false; + } + + $this->nid=$this->note->getXref(); // Correct upper/lower case mismatch + + if (!$this->note->canDisplayDetails()) { + print_header(i18n::translate('Shared note')); + print_privacy_error(); + print_footer(); + exit; + } + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'NOTE', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->nid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $gedrec = find_other_record($this->nid, WT_GED_ID); + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->note = new WT_Note($gedrec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->nid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $gedrec = find_other_record($this->nid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->note = new WT_Note($gedrec); + } + unset($_GET['action']); + break; + } + + //-- if the user can edit and there are changes then get the new changes + if ($this->show_changes && WT_USER_CAN_EDIT) { + $newrec = find_updated_record($this->nid, WT_GED_ID); + if (!empty($newrec)) { + $this->diffnote = new WT_Note($newrec); + $this->diffnote->setChanged(true); + } + } + + if ($this->show_changes) { + $this->note->diffMerge($this->diffnote); + } + } + + /** + * get the title for this page + * @return string + */ + function getPageTitle() { + if ($this->note) { + return $this->note->getFullName()." - ".i18n::translate('Shared Note Information'); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->note) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_note'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Edit note')); + $submenu->addOnclick('return edit_note(\''.$this->nid.'\');'); + $submenu->addIcon('edit_note'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + $menu->addSeparator(); + } + + // show/hide changes + if (find_updated_record($this->nid, WT_GED_ID)!==null) { + if (!$this->show_changes) { + $submenu = new Menu(i18n::translate('This record has been updated. Click here to show changes.'), "note.php?nid={$this->nid}&show_changes=yes"); + $submenu->addIcon('edit_note'); + } else { + $submenu = new Menu(i18n::translate('Click here to hide changes.'), "note.php?nid={$this->nid}&show_changes=no"); + $submenu->addIcon('edit_note'); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), "note.php?nid={$this->nid}&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('notes'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), "note.php?nid={$this->nid}&action=accept"); + $submenu->addIcon('notes'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->nid."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Delete this Shared Note')); + $submenu->addOnclick("if (confirm('".i18n::translate('Are you sure you want to delete this Shared Note?')."')) return deletenote('".$this->nid."'); else return false;"); + $submenu->addIcon('edit_note'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), "note.php?action=addfav&nid={$this->nid}&gid={$this->nid}"); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } +} diff --git a/library/WT/Controller/Pedigree.php b/library/WT/Controller/Pedigree.php new file mode 100644 index 0000000000..8f12fee31c --- /dev/null +++ b/library/WT/Controller/Pedigree.php @@ -0,0 +1,317 @@ +<?php +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_PEDIGREE_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +class WT_Controller_Pedigree extends WT_Controller_Base { + var $log2; + var $rootid; + var $name; + var $addname; + var $rootPerson; + var $show_full; + var $talloffset; + var $PEDIGREE_GENERATIONS; + var $pbwidth; + var $pbheight; + var $treeid; + var $treesize; + var $curgen; + var $yoffset; + var $xoffset; + var $prevyoffset; + var $offsetarray; + var $minyoffset; + + /** + * Initialization function + */ + function init() { + global $PEDIGREE_FULL_DETAILS, $PEDIGREE_LAYOUT, $MAX_PEDIGREE_GENERATIONS; + global $DEFAULT_PEDIGREE_GENERATIONS, $SHOW_EMPTY_BOXES; + global $bwidth, $bheight, $baseyoffset, $basexoffset, $byspacing, $bxspacing; + global $TEXT_DIRECTION, $BROWSER_TYPE, $show_full, $talloffset; + + $this->log2 = log(2); + + $this->rootid =safe_GET_xref('rootid'); + $this->show_full =safe_GET('show_full', array('0', '1'), $PEDIGREE_FULL_DETAILS); + $this->talloffset=safe_GET('talloffset', array('0', '1', '2', '3'), $PEDIGREE_LAYOUT); + $this->PEDIGREE_GENERATIONS=safe_GET_integer('PEDIGREE_GENERATIONS', 2, $MAX_PEDIGREE_GENERATIONS, $DEFAULT_PEDIGREE_GENERATIONS); + + if ($this->talloffset==1) $this->talloffset=1; // Make SURE this is an integer + if ($this->talloffset>1 && $this->PEDIGREE_GENERATIONS>8) $this->PEDIGREE_GENERATIONS=8; + + // TODO: some library functions expect this as a global. + // Passing a function parameter would be much better. + global $PEDIGREE_GENERATIONS; + $PEDIGREE_GENERATIONS=$this->PEDIGREE_GENERATIONS; + + // This is passed as a global. A parameter would be better... + $this->show_full = ($this->show_full) ? 1 : 0; // Make SURE this is an integer + if ($this->talloffset>3) { + $this->talloffset=3; + } elseif ($this->talloffset<0) { + $this->talloffset=0; + } + $show_full = $this->show_full; + $talloffset = $this->talloffset; + + // Validate parameters + $this->rootid=check_rootid($this->rootid); + + $this->rootPerson = WT_Person::getInstance($this->rootid); + if (is_null($this->rootPerson)) $this->rootPerson = new WT_Person(''); + $this->name = $this->rootPerson->getFullName(); + $this->addname = $this->rootPerson->getAddName(); + + //-- adjustments for hide details + if ($this->show_full==false) { + $bheight=30; + if ($this->talloffset < 2) { + $bwidth-=30; + } + else { + $bwidth-=50; + } + } + //-- adjustments for portrait mode + if ($this->talloffset==0) { + $bxspacing+=12; + $bwidth+=20; + $baseyoffset -= 20*($this->PEDIGREE_GENERATIONS-1); + } + + $this->pbwidth = $bwidth+6; + $this->pbheight = $bheight+5; + + $this->treeid = ancestry_array($this->rootid); + $this->treesize = pow(2, (int)($this->PEDIGREE_GENERATIONS))-1; + + //-- ancestry_array puts everyone at $i+1 + for ($i=0; $i<$this->treesize; $i++) { + $this->treeid[$i] = $this->treeid[$i+1]; + } + + if (!$this->show_full) { + if ($this->talloffset==0) { + $baseyoffset = 160+$bheight*2; + } elseif ($this->talloffset==1) { + $baseyoffset = 180+$bheight*2; + } elseif ($this->talloffset>1) { + if ($this->PEDIGREE_GENERATIONS==3) { + $baseyoffset = 30; + } else { + $baseyoffset = -85; + } + } + } else { + if ($this->talloffset==0) { + $baseyoffset = 100+$bheight/2; + } elseif ($this->talloffset==1) { + $baseyoffset = 160+$bheight/2; + } elseif ($this->talloffset>1) { + if ($this->PEDIGREE_GENERATIONS==3) { + $baseyoffset = 30; + } else { + $baseyoffset = -85; + } + } + } + // -- this next section will create and position the DIV layers for the pedigree tree + $this->curgen = 1; // -- variable to track which generation the algorithm is currently working on + $this->yoffset=0; // -- used to offset the position of each box as it is generated + $this->xoffset=0; + $this->prevyoffset=0; // -- used to track the y position of the previous box + $this->offsetarray = array(); + $this->minyoffset = 0; + if ($this->treesize<3) $this->treesize=3; + // -- loop through all of id's in the array starting at the last and working to the first + //-- calculation the box positions + for ($i=($this->treesize-1); $i>=0; $i--) { + // -- check to see if we have moved to the next generation + if ($i < floor($this->treesize / (pow(2, $this->curgen)))) { + $this->curgen++; + } + //-- box position in current generation + $boxpos = $i-pow(2, $this->PEDIGREE_GENERATIONS-$this->curgen); + //-- offset multiple for current generation + if ($this->talloffset < 2) { + $genoffset = pow(2, $this->curgen-$this->talloffset); + $boxspacing = $this->pbheight+$byspacing; + } + else { + $genoffset = pow(2, $this->curgen-1); + $boxspacing = $this->pbwidth+$byspacing; + } + // -- calculate the yoffset Position in the generation Spacing between boxes put child between parents + $this->yoffset = $baseyoffset+($boxpos * ($boxspacing * $genoffset))+(($boxspacing/2)*$genoffset)+($boxspacing * $genoffset); + // -- calculate the xoffset + if ($this->talloffset==0) { + if ($this->PEDIGREE_GENERATIONS<6) { + $addxoffset = $basexoffset+(10+60*(5-$this->PEDIGREE_GENERATIONS)); + $this->xoffset = ($this->PEDIGREE_GENERATIONS - $this->curgen) * (($this->pbwidth+$bxspacing) / 2)+$addxoffset; + } + else { + $addxoffset = $basexoffset+10; + $this->xoffset = ($this->PEDIGREE_GENERATIONS - $this->curgen) * (($this->pbwidth+$bxspacing) / 2)+$addxoffset; + } + //-- compact the tree + if ($this->curgen<$this->PEDIGREE_GENERATIONS) { + $parent = floor(($i-1)/2); + if ($i%2 == 0) $this->yoffset=$this->yoffset - (($boxspacing/2) * ($this->curgen-1)); + else $this->yoffset=$this->yoffset + (($boxspacing/2) * ($this->curgen-1)); + $pgen = $this->curgen; + while ($parent>0) { + if ($parent%2 == 0) $this->yoffset=$this->yoffset - (($boxspacing/2) * $pgen); + else $this->yoffset=$this->yoffset + (($boxspacing/2) * $pgen); + $pgen++; + if ($pgen>3) { + $temp=0; + for ($j=1; $j<($pgen-2); $j++) $temp += (pow(2, $j)-1); + if ($parent%2 == 0) $this->yoffset=$this->yoffset - (($boxspacing/2) * $temp); + else $this->yoffset=$this->yoffset + (($boxspacing/2) * $temp); + } + $parent = floor(($parent-1)/2); + } + if ($this->curgen>3) { + $temp=0; + for ($j=1; $j<($this->curgen-2); $j++) $temp += (pow(2, $j)-1); + if ($i%2 == 0) $this->yoffset=$this->yoffset - (($boxspacing/2) * $temp); + else $this->yoffset=$this->yoffset + (($boxspacing/2) * $temp); + } + + } + $this->yoffset-=(($boxspacing/2)*pow(2,($this->PEDIGREE_GENERATIONS-2))-($boxspacing/2)); + } + else if ($this->talloffset==1) { + $this->xoffset = 10 + $basexoffset + (($this->PEDIGREE_GENERATIONS - $this->curgen) * ($this->pbwidth+$bxspacing)); + if ($this->curgen == $this->PEDIGREE_GENERATIONS) $this->xoffset += 10; + if ($this->PEDIGREE_GENERATIONS<4) $this->xoffset += 60; + } + else if ($this->talloffset==2) { + if ($this->show_full) $this->xoffset = ($this->curgen) * (($this->pbwidth+$bxspacing) / 2)+($this->curgen)*10+136.5; + else $this->xoffset = ($this->curgen) * (($this->pbwidth+$bxspacing) / 4)+($this->curgen)*10+215.75; + } + else { + if ($this->show_full) $this->xoffset = ($this->PEDIGREE_GENERATIONS - $this->curgen) * (($this->pbwidth+$bxspacing) / 2)+260; + else $this->xoffset = ($this->PEDIGREE_GENERATIONS - $this->curgen) * (($this->pbwidth+$bxspacing) / 4)+270; + } + if ($this->curgen == 1 && $this->talloffset==1) $this->xoffset += 10; + $this->offsetarray[$i]["x"]=$this->xoffset; + $this->offsetarray[$i]["y"]=$this->yoffset; + } + + //-- collapse the tree if boxes are missing + if (!$SHOW_EMPTY_BOXES) { + if ($this->PEDIGREE_GENERATIONS>1) $this->collapse_tree(0, 1, 0); + } + + //-- calculate the smallest yoffset and adjust the tree to that offset + $minyoffset = 0; + for ($i=0; $i<count($this->treeid); $i++) { + if ($SHOW_EMPTY_BOXES || !empty($treeid[$i])) { + if (!empty($offsetarray[$i])) { + if (($minyoffset==0)||($minyoffset>$this->offsetarray[$i]["y"])) $minyoffset = $this->offsetarray[$i]["y"]; + } + } + } + + $ydiff = $baseyoffset+35-$minyoffset; + $this->adjust_subtree(0, $ydiff); + } + + /** + * return the title of this page + * @return string the title of the page to go in the <title> tags + */ + function getPageTitle() { + return $this->getPersonName()." ".i18n::translate('Pedigree Tree'); + } + + function getPersonName() { + if (is_null($this->rootPerson)) { + return i18n::translate('unknown'); + } else { + return $this->rootPerson->getFullName(); + } + } + + function adjust_subtree($index, $diff) { + global $offsetarray, $treeid, $log2, $talloffset,$boxspacing, $mdiff, $SHOW_EMPTY_BOXES; + $f = ($index*2)+1; //-- father index + $m = $f+1; //-- mother index + + if (!$SHOW_EMPTY_BOXES && empty($treeid[$index])) return; + if (empty($offsetarray[$index])) return; + $offsetarray[$index]["y"] += $diff; + if ($f<count($treeid)) adjust_subtree($f, $diff); + if ($m<count($treeid)) adjust_subtree($m, $diff); + } + + function collapse_tree($index, $curgen, $diff) { + global $offsetarray, $treeid, $log2, $talloffset,$boxspacing, $mdiff, $minyoffset; + + //print "$index:$curgen:$diff<br />\n"; + $f = ($index*2)+1; //-- father index + $m = $f+1; //-- mother index + if (empty($treeid[$index])) { + $pgen=$curgen; + $genoffset=0; + while ($pgen<=$this->PEDIGREE_GENERATIONS) { + $genoffset += pow(2, ($this->PEDIGREE_GENERATIONS-$pgen)); + $pgen++; + } + if ($talloffset==1) $diff+=.5*$genoffset; + else $diff+=$genoffset; + if (isset($offsetarray[$index]["y"])) $offsetarray[$index]["y"]-=($boxspacing*$diff)/2; + return $diff; + } + if ($curgen==$this->PEDIGREE_GENERATIONS) { + $offsetarray[$index]["y"] -= $boxspacing*$diff; + //print "UP $index BY $diff<br />\n"; + return $diff; + } + $odiff=$diff; + $fdiff = collapse_tree($f, $curgen+1, $diff); + if (($curgen<($this->PEDIGREE_GENERATIONS-1))||($index%2==1)) $diff=$fdiff; + if (isset($offsetarray[$index]["y"])) $offsetarray[$index]["y"] -= $boxspacing*$diff; + //print "UP $index BY $diff<br />\n"; + $mdiff = collapse_tree($m, $curgen+1, $diff); + $zdiff = $mdiff - $fdiff; + if (($zdiff>0)&&($curgen<$this->PEDIGREE_GENERATIONS-2)) { + $offsetarray[$index]["y"] -= $boxspacing*$zdiff/2; + //print "UP $index BY ".($zdiff/2)."<br />\n"; + if ((empty($treeid[$m]))&&(!empty($treeid[$f]))) adjust_subtree($f, -1*($boxspacing*$zdiff/4)); + $diff+=($zdiff/2); + } + return $diff; + } +} diff --git a/library/WT/Controller/Repository.php b/library/WT/Controller/Repository.php new file mode 100644 index 0000000000..d637360f80 --- /dev/null +++ b/library/WT/Controller/Repository.php @@ -0,0 +1,227 @@ +<?php +// Controller for the Repository Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_REPOSITORY_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; + +class WT_Controller_Repository extends WT_Controller_Base { + var $rid; + var $repository = null; + var $diffrepository = null; + var $accept_success = false; + + function init() { + $this->rid = safe_GET_xref('rid'); + + $gedrec = find_other_record($this->rid, WT_GED_ID); + + if (find_other_record($this->rid, WT_GED_ID) || find_updated_record($this->rid, WT_GED_ID)!==null) { + $this->repository = new WT_Repository($gedrec); + $this->repository->ged_id=WT_GED_ID; // This record is from a file + } else if (!$gedrec) { + return false; + } + + $this->rid=$this->repository->getXref(); // Correct upper/lower case mismatch + + if (!$this->repository->canDisplayDetails()) { + print_header(i18n::translate('Repository')); + print_privacy_error(); + print_footer(); + exit; + } + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'REPO', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->rid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $gedrec = find_other_record($this->rid, WT_GED_ID); + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->repository = new WT_Repository($gedrec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->rid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $gedrec = find_other_record($this->rid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->repository = new WT_Repository($gedrec); + } + unset($_GET['action']); + break; + } + + //-- if the user can edit and there are changes then get the new changes + if ($this->show_changes && WT_USER_CAN_EDIT) { + $newrec = find_updated_record($this->rid, WT_GED_ID); + if (!empty($newrec)) { + $this->diffrepository = new WT_Repository($newrec); + $this->diffrepository->setChanged(true); + } + } + + if ($this->show_changes) { + $this->repository->diffMerge($this->diffrepository); + } + } + + /** + * get the title for this page + * @return string + */ + function getPageTitle() { + if ($this->repository) { + return $this->repository->getFullName()." - ".$this->rid." - ".i18n::translate('Repository information'); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->repository) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_repo'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + // For consistency with other controllers, we need an "edit repo" option + } + + // show/hide changes + if (find_updated_record($this->rid, WT_GED_ID)!==null) { + if (!$this->show_changes) { + $submenu = new Menu(i18n::translate('This record has been updated. Click here to show changes.'), "repo.php?rid={$this->rid}&show_changes=yes"); + $submenu->addIcon('edit_repo'); + } else { + $submenu = new Menu(i18n::translate('Click here to hide changes.'), "repo.php?rid={$this->rid}&show_changes=no"); + $submenu->addIcon('edit_repo'); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), "repo.php?rid={$this->rid}&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('edit_repo'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), "repo.php?rid={$this->rid}&action=accept"); + $submenu->addIcon('edit_repo'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->rid."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Delete repository')); + $submenu->addOnclick("if (confirm('".i18n::translate('Are you sure you want to delete this Repository?')."')) return deleterepository('".$this->rid."'); else return false;"); + $submenu->addIcon('edit_repo'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), "repo.php?action=addfav&rid={$this->rid}&gid={$this->rid}"); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } +} diff --git a/library/WT/Controller/Search.php b/library/WT/Controller/Search.php new file mode 100644 index 0000000000..40187915d9 --- /dev/null +++ b/library/WT/Controller/Search.php @@ -0,0 +1,820 @@ +<?php +// Controller for the Search Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_SEARCH_CTRL_PHP', ''); + +class WT_Controller_Search extends WT_Controller_Base { + var $isPostBack = false; + var $topsearch; + var $srfams; + var $srindi; + var $srnote; + var $srsour; + var $resultsPageNum = 0; + var $resultsPerPage = 50; + var $totalResults = -1; + var $totalGeneralResults = -1; + var $indiResultsPrinted = -1; + var $famResultsPrinted = -1; + var $srcResultsPrinted = -1; + var $query; + var $myquery = ""; + //var $soundex = "Russell"; + var $soundex = "DaitchM"; + var $subaction = ""; + var $nameprt = ""; + var $tagfilter = "on"; + var $showasso = "off"; + var $name=""; + var $myname; + var $birthdate=""; + var $mybirthdate; + var $birthplace=""; + var $mybirthplace; + var $deathdate=""; + var $mydeathdate; + var $deathplace=""; + var $mydeathplace; + var $gender=""; + var $mygender; + var $firstname=""; + var $myfirstname; + var $lastname=""; + var $mylastname; + var $place=""; + var $myplace; + var $year=""; + var $myyear; + var $sgeds = array (); + var $myindilist = array (); + var $mysourcelist = array (); + var $myfamlist = array (); + var $mynotelist = array (); + var $inputFieldNames = array (); + var $replace = false; + var $replaceNames = false; + var $replacePlaces = false; + var $replaceAll = false; + var $replacePlacesWord = false; + var $printplace = array(); + + /** + * Initialization function + */ + function init() { + global $GEDCOM; + + if ($this->action=='') { + $this->action='general'; + } + + if (!empty ($_REQUEST["topsearch"])) { + $this->topsearch = true; + $this->isPostBack = true; + $this->srfams = 'yes'; + $this->srindi = 'yes'; + $this->srsour = 'yes'; + $this->srnote = 'yes'; + } + + // Get the query and remove slashes + if (isset ($_REQUEST["query"])) { + // Reset the "Search" text from the page header + if ($_REQUEST["query"] == i18n::translate('Search') || strlen($_REQUEST["query"])<2 || preg_match("/^\.+$/", $_REQUEST["query"])>0) { + $this->query=""; + $this->myquery=""; + } else { + $this->query = $_REQUEST["query"]; + $this->myquery = htmlspecialchars($this->query); + } + } + if (isset ($_REQUEST["replace"])) { + $this->replace = $_REQUEST["replace"]; + + if (isset($_REQUEST["replaceNames"])) $this->replaceNames = true; + if (isset($_REQUEST["replacePlaces"])) $this->replacePlaces = true; + if (isset($_REQUEST["replacePlacesWord"])) $this->replacePlacesWord = true; + if (isset($_REQUEST["replaceAll"])) $this->replaceAll = true; + } + + // Aquire all the variables values from the $_REQUEST + $varNames = array ("isPostBack", "action", "topsearch", "srfams", "srindi", "srsour", "srnote", "view", "soundex", "subaction", "nameprt", "tagfilter", "showasso", "resultsPageNum", "resultsPerPage", "totalResults", "totalGeneralResults", "indiResultsPrinted", "famResultsPrinted", "srcResultsPrinted", "myindilist", "mysourcelist", "mynotelist", "myfamlist"); + $this->setRequestValues($varNames); + + if (!$this->isPostBack) { + // Enable the default gedcom for search + $str = str_replace(array (".", "-", " "), array ("_", "_", "_"), $GEDCOM); + $_REQUEST["$str"] = $str; + } + + // Retrieve the gedcoms to search in + $all_gedcoms=get_all_gedcoms(); + if (count($all_gedcoms)>1 && get_site_setting('ALLOW_CHANGE_GEDCOM')) { + foreach ($all_gedcoms as $ged_id=>$gedcom) { + $str = str_replace(array (".", "-", " "), array ("_", "_", "_"), $gedcom); + if (isset ($_REQUEST["$str"]) || isset ($this->topsearch)) { + $this->sgeds[$ged_id] = $gedcom; + $_REQUEST["$str"] = 'yes'; + } + } + } else { + $this->sgeds[WT_GED_ID] = $GEDCOM; + } + + // vars use for soundex search + if (!empty ($_REQUEST["firstname"])) { + $this->firstname = $_REQUEST["firstname"]; + $this->myfirstname = $this->firstname; + } else { + $this->firstname=""; + $this->myfirstname = ""; + } + if (!empty ($_REQUEST["lastname"])) { + $this->lastname = $_REQUEST["lastname"]; + $this->mylastname = $this->lastname; + } else { + $this->lastname=""; + $this->mylastname = ""; + } + if (!empty ($_REQUEST["place"])) { + $this->place = $_REQUEST["place"]; + $this->myplace = $this->place; + } else { + $this->place=""; + $this->myplace = ""; + } + if (!empty ($_REQUEST["year"])) { + $this->year = $_REQUEST["year"]; + $this->myyear = $this->year; + } else { + $this->year=""; + $this->myyear = ""; + } + // Set the search result titles for soundex searches + if ($this->firstname || $this->lastname || $this->place) { + $this->myquery=htmlspecialchars(implode(' ', array($this->firstname, $this->lastname, $this->place))); + }; + + if (!empty ($_REQUEST["name"])) { + $this->name = $_REQUEST["name"]; + $this->myname = $this->name; + } else { + $this->name=""; + $this->myname = ""; + } + if (!empty ($_REQUEST["birthdate"])) { + $this->birthdate = $_REQUEST["birthdate"]; + $this->mybirthdate = $this->birthdate; + } else { + $this->birthdate=""; + $this->mybirthdate = ""; + } + if (!empty ($_REQUEST["birthplace"])) { + $this->birthplace = $_REQUEST["birthplace"]; + $this->mybirthplace = $this->birthplace; + } else { + $this->birthplace=""; + $this->mybirthplace = ""; + } + if (!empty ($_REQUEST["deathdate"])) { + $this->deathdate = $_REQUEST["deathdate"]; + $this->mydeathdate = $this->deathdate; + } else { + $this->deathdate=""; + $this->mydeathdate = ""; + } + if (!empty ($_REQUEST["deathplace"])) { + $this->deathplace = $_REQUEST["deathplace"]; + $this->mydeathplace = $this->deathplace; + } else { + $this->deathplace=""; + $this->mydeathplace = ""; + } + if (!empty ($_REQUEST["gender"])) { + $this->gender = $_REQUEST["gender"]; + $this->mygender = $this->gender; + } else { + $this->gender=""; + $this->mygender = ""; + } + + $this->inputFieldNames[] = "action"; + $this->inputFieldNames[] = "isPostBack"; + $this->inputFieldNames[] = "resultsPerPage"; + $this->inputFieldNames[] = "query"; + $this->inputFieldNames[] = "srindi"; + $this->inputFieldNames[] = "srfams"; + $this->inputFieldNames[] = "srsour"; + $this->inputFieldNames[] = "srnote"; + $this->inputFieldNames[] = "showasso"; + $this->inputFieldNames[] = "firstname"; + $this->inputFieldNames[] = "lastname"; + $this->inputFieldNames[] = "place"; + $this->inputFieldNames[] = "year"; + $this->inputFieldNames[] = "soundex"; + $this->inputFieldNames[] = "nameprt"; + $this->inputFieldNames[] = "subaction"; + $this->inputFieldNames[] = "name"; + $this->inputFieldNames[] = "birthdate"; + $this->inputFieldNames[] = "birthplace"; + $this->inputFieldNames[] = "deathdate"; + $this->inputFieldNames[] = "deathplace"; + $this->inputFieldNames[] = "gender"; + $this->inputFieldNames[] = "tagfilter"; + + // Get the search results based on the action + if (isset ($this->topsearch)) { + $this->TopSearch(); + } + // If we want to show associated persons, build the list + switch ($this->action) { + case 'general': + $this->GeneralSearch(); + break; + case 'soundex': + $this->SoundexSearch(); + break; + case 'replace': + $this->SearchAndReplace(); + return; + } + } + + function getPageTitle() { + switch ($this->action) { + case 'general': + return i18n::translate('General Search'); + case 'soundex': + return i18n::translate('Soundex Search'); + case 'replace': + return i18n::translate('Search and replace'); + } + } + + /** + * setRequestValues - Checks if the variable names ($varNames) are in + * the $_REQUEST and if so assigns their values to + * $this based on the variable name ($this->$varName). + * + * @param array $varNames - Array of variable names(strings). + */ + function setRequestValues($varNames) { + foreach ($varNames as $key => $varName) { + if (isset ($_REQUEST[$varName])) + { + if ($varName == "action") + if ($_REQUEST[$varName] == "replace") + if (!WT_USER_CAN_ACCEPT) + { + $this->action = "general"; + continue; + } + $this-> $varName = $_REQUEST[$varName]; + } + } + } + + /** + * setRequestValues - Prints out all of the variable names and their + * values based on the variable name ($this->$varName). + * + * @param array $varNames - Array of variable names(strings). + */ + function printVars($varNames) { + foreach ($varNames as $key => $varName) { + echo $varName.": ".$this-> $varName."<br/>"; + } + } + + /** + * Handles searches entered in the top search box in the themes and + * prepares the search to do a general search on indi's, fams, and sources. + */ + function TopSearch() { + global $GEDCOM; + // first set some required variables. Search only in current gedcom, only in indi's. + $this->srindi = "yes"; + + // Enable the default gedcom for search + $str = str_replace(array (".", "-", " "), array ("_", "_", "_"), $GEDCOM); + $_REQUEST["$str"] = "yes"; + + // Then see if an ID is typed in. If so, we might want to jump there. + if (isset ($this->query)) { + $record=WT_GedcomRecord::getInstance($this->query); + if ($record && $record->canDisplayDetails()) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$record->getRawUrl()); + exit; + } + } + } + + /** + * Gathers results for a general search + */ + function GeneralSearch() { + global $GEDCOM; + $oldged = $GEDCOM; + + // Split search terms into an array + $query_terms=array(); + $query=$this->query; + // Words in double quotes stay together + while (preg_match('/"([^"]+)"/', $query, $match)) { + $query_terms[]=trim($match[1]); + $query=str_replace($match[0], '', $query); + } + // Other words get treated separately + while (preg_match('/[\S]+/', $query, $match)) { + $query_terms[]=trim($match[0]); + $query=str_replace($match[0], '', $query); + } + + //-- perform the search + if ($query_terms && $this->sgeds) { + // Write a log entry + $logstring = "Type: General\nQuery: ".$this->query; + AddToSearchlog($logstring, $this->sgeds); + + // Search the indi's + if (isset ($this->srindi)) { + $this->myindilist=search_indis($query_terms, array_keys($this->sgeds), 'AND', $this->tagfilter=='on'); + } else { + $this->myindilist=array(); + } + + // Search the fams + if (isset ($this->srfams)) { + $this->myfamlist=array_merge( + search_fams($query_terms, array_keys($this->sgeds), 'AND', $this->tagfilter=='on'), + search_fams_names($query_terms, array_keys($this->sgeds), 'AND') + ); + $this->myfamlist=array_unique($this->myfamlist); + } else { + $this->myfamlist=array(); + } + + // Search the sources + if (isset ($this->srsour)) { + if (!empty ($this->query)) + $this->mysourcelist=search_sources($query_terms, array_keys($this->sgeds), 'AND', $this->tagfilter=='on'); + } else { + $this->mysourcelist=array(); + } + + // Search the notes + if (isset ($this->srnote)) { + if (!empty ($this->query)) + $this->mynotelist=search_notes($query_terms, array_keys($this->sgeds), 'AND', $this->tagfilter=='on'); + } else { + $this->mynotelist=array(); + } + + // If only 1 item is returned, automatically forward to that item + // If ID cannot be displayed, continue to the search page. + if (count($this->myindilist)==1 && !$this->myfamlist && !$this->mysourcelist && !$this->mynotelist) { + $indi=$this->myindilist[0]; + if (!count_linked_indi($indi->getXref(), 'ASSO', $indi->getGedId()) && !count_linked_fam($indi->getXref(), 'ASSO', $indi->getGedId()) && $indi->canDisplayName()) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$indi->getRawUrl()); + exit; + } + } + if (!$this->myindilist && count($this->myfamlist)==1 && !$this->mysourcelist && !$this->mynotelist) { + $fam=$this->myfamlist[0]; + if ($fam->canDisplayName()) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$fam->getRawUrl()); + exit; + } + } + if (!$this->myindilist && !$this->myfamlist && count($this->mysourcelist)==1 && !$this->mynotelist) { + $sour=$this->mysourcelist[0]; + if ($sour->canDisplayName()) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$sour->getRawUrl()); + exit; + } + } + if (!$this->myindilist && !$this->myfamlist && !$this->mysourcelist && count($this->mynotelist)==1) { + $note=$this->mynotelist[0]; + if ($note->canDisplayName()) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$note->getRawUrl()); + exit; + } + } + } + } + + /** + * Preforms a search and replace + */ + function SearchAndReplace() + { + global $GEDCOM, $manual_save, $STANDARD_NAME_FACTS, $ADVANCED_NAME_FACTS; + + $this->sgeds = array(WT_GED_ID=>WT_GEDCOM); + $this->srindi = "yes"; + $this->srfams = "yes"; + $this->srsour = "yes"; + $this->srnote = "yes"; + $oldquery = $this->query; + $this->GeneralSearch(); + + //-- don't try to make any changes if nothing was found + if (!$this->myindilist && !$this->myfamlist && !$this->mysourcelist && !$this->mynotelist) { + return; + } + + AddToLog("Search And Replace old:".$oldquery." new:".$this->replace, 'edit'); + $manual_save = true; + // Include edit functions. + require_once WT_ROOT.'includes/functions/functions_edit.php'; + // These contain the search query and the replace string + // $this->replace; + // $this->query; + + // These contain the search results + // We need to iterate through them and do the replaces + //$this->myindilist; + $adv_name_tags = preg_split("/[\s,;: ]+/", $ADVANCED_NAME_FACTS); + $name_tags = array_unique(array_merge($STANDARD_NAME_FACTS, $adv_name_tags)); + $name_tags[] = "_MARNM"; + foreach ($this->myindilist as $id=>$individual) { + $indirec=find_gedcom_record($individual->getXref(), WT_GED_ID, true); + $oldRecord = $indirec; + $newRecord = $indirec; + if ($this->replaceAll) { + $newRecord = preg_replace("~".$oldquery."~i", $this->replace, $newRecord); + } else { + if ($this->replaceNames) { + foreach ($name_tags as $f=>$tag) { + $newRecord = preg_replace("~(\d) ".$tag." (.*)".$oldquery."(.*)~i", "$1 ".$tag." $2".$this->replace."$3", $newRecord); + } + } + if ($this->replacePlaces) { + if ($this->replacePlacesWord) $newRecord = preg_replace('~(\d) PLAC (.*)([,\W\s])'.$oldquery.'([,\W\s])~i', "$1 PLAC $2$3".$this->replace."$4",$newRecord); + else $newRecord = preg_replace("~(\d) PLAC (.*)".$oldquery."(.*)~i", "$1 PLAC $2".$this->replace."$3",$newRecord); + } + } + //-- if the record changed replace the record otherwise remove it from the search results + if ($newRecord != $oldRecord) { + replace_gedrec($individual->getXref(), WT_GED_ID, $newRecord); + } else { + unset($this->myindilist[$id]); + } + } + + foreach ($this->myfamlist as $id=>$family) { + $indirec=find_gedcom_record($family->getXref(), WT_GED_ID, true); + $oldRecord = $indirec; + $newRecord = $indirec; + + if ($this->replaceAll) { + $newRecord = preg_replace("~".$oldquery."~i", $this->replace, $newRecord); + } + else { + if ($this->replacePlaces) { + if ($this->replacePlacesWord) $newRecord = preg_replace('~(\d) PLAC (.*)([,\W\s])'.$oldquery.'([,\W\s])~i', "$1 PLAC $2$3".$this->replace."$4",$newRecord); + else $newRecord = preg_replace("~(\d) PLAC (.*)".$oldquery."(.*)~i", "$1 PLAC $2".$this->replace."$3",$newRecord); + } + } + //-- if the record changed replace the record otherwise remove it from the search results + if ($newRecord != $oldRecord) { + replace_gedrec($family->getXref(), WT_GED_ID, $newRecord); + } else { + unset($this->myfamlist[$id]); + } + } + + foreach ($this->mysourcelist as $id=>$source) { + $indirec=find_gedcom_record($source->getXref(), WT_GED_ID, true); + $oldRecord = $indirec; + $newRecord = $indirec; + + if ($this->replaceAll) { + $newRecord = preg_replace("~".$oldquery."~i", $this->replace, $newRecord); + } else { + if ($this->replaceNames) { + $newRecord = preg_replace("~(\d) TITL (.*)".$oldquery."(.*)~i", "$1 TITL $2".$this->replace."$3", $newRecord); + $newRecord = preg_replace("~(\d) ABBR (.*)".$oldquery."(.*)~i", "$1 ABBR $2".$this->replace."$3", $newRecord); + } + if ($this->replacePlaces) { + if ($this->replacePlacesWord) $newRecord = preg_replace('~(\d) PLAC (.*)([,\W\s])'.$oldquery.'([,\W\s])~i', "$1 PLAC $2$3".$this->replace."$4",$newRecord); + else $newRecord = preg_replace("~(\d) PLAC (.*)".$oldquery."(.*)~i", "$1 PLAC $2".$this->replace."$3",$newRecord); + } + } + //-- if the record changed replace the record otherwise remove it from the search results + if ($newRecord != $oldRecord) { + replace_gedrec($source->getXref(), WT_GED_ID, $newRecord); + } else { + unset($this->mysourcelist[$id]); + } + } + + foreach ($this->mynotelist as $id=>$note) { + $indirec=find_gedcom_record($note->getXref(), WT_GED_ID, true); + $oldRecord = $indirec; + $newRecord = $indirec; + + if ($this->replaceAll) { + $newRecord = preg_replace("~".$oldquery."~i", $this->replace, $newRecord); + } + //-- if the record changed replace the record otherwise remove it from the search results + if ($newRecord != $oldRecord) { + replace_gedrec($note->getXref(), WT_GED_ID, $newRecord); + } else { + unset($this->mynotelist[$id]); + } + } + } + + /** + * Gathers results for a soundex search + * + * TODO + * ==== + * Does not search on the selected gedcoms, searches on all the gedcoms + * Does not work on first names, instead of the code, value array is used in the search + * Returns all the names even when Names with hit selected + * Does not sort results by first name + * Does not work on separate double word surnames + * Does not work on duplicate code values of the searched text and does not give the correct code + * Cohen should give DM codes 556000, 456000, 460000 and 560000, in 4.1 we search only on 560000?? + * + * The names' Soundex SQL table contains all the soundex values twice + * The places table contains only one value + * + * The code should be improved - see RFE + * + */ + function SoundexSearch() { + if (((!empty ($this->lastname)) || (!empty ($this->firstname)) || (!empty ($this->place))) && (count($this->sgeds) > 0)) { + $logstring = "Type: Soundex\n"; + if (!empty ($this->lastname)) + $logstring .= "Last name: ".$this->lastname."\n"; + if (!empty ($this->firstname)) + $logstring .= "First name: ".$this->firstname."\n"; + if (!empty ($this->place)) + $logstring .= "Place: ".$this->place."\n"; + if (!empty ($this->year)) + $logstring .= "Year: ".$this->year."\n"; + AddToSearchlog($logstring, $this->sgeds); + + if ($this->sgeds) { + $this->myindilist=search_indis_soundex($this->soundex, $this->lastname, $this->firstname, $this->place, array_keys($this->sgeds)); + } else { + $this->myindilist=array(); + } + } + + // Now we have the final list of indi's to be printed. + // We may add the assos at this point. + + if ($this->showasso == "on") { + foreach ($this->myindilist as $indi) { + foreach (fetch_linked_indi($indi->getXref(), 'ASSO', $indi->getGedId()) as $asso) { + $this->myindilist[]=$asso; + } + foreach (fetch_linked_fam($indi->getXref(), 'ASSO', $indi->getGedId()) as $asso) { + $this->myfamlist[]=$asso; + } + } + } + + //-- if only 1 item is returned, automatically forward to that item + if (count($this->myindilist)==1 && $this->action!="replace") { + $indi=$this->myindilist[0]; + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH.$indi->getRawUrl()); + exit; + } + usort($this->myindilist, array('GedcomRecord', 'Compare')); + usort($this->myfamlist, array('GedcomRecord', 'Compare')); + } + + function printResults() { + require_once WT_ROOT.'includes/functions/functions_print_lists.php'; + global $GEDCOM, $TEXT_DIRECTION, $WT_IMAGES; + + $somethingPrinted = false; // whether anything printed + // ---- section to search and display results on a general keyword search + if ($this->action=="general" || $this->action=="soundex" || $this->action=="replace") { + if ($this->myindilist || $this->myfamlist || $this->mysourcelist || $this->mynotelist) { + echo '<br />'; + + // Split individuals by gedcom + foreach ($this->sgeds as $ged_id=>$gedcom) { + $datalist = array(); + foreach ($this->myindilist as $individual) { + if ($individual->getGedId()==$ged_id) { + $datalist[]=$individual; + } + } + if ($datalist) { + $somethingPrinted = true; + usort($datalist, array('GedcomRecord', 'Compare')); + $GEDCOM=$gedcom; + load_gedcom_settings($ged_id); + print_indi_table($datalist, i18n::translate('Individuals').' : «'.$this->myquery.'» @ '.PrintReady(get_gedcom_setting($ged_id, 'title'), true)); + } + } + // Split families by gedcom + foreach ($this->sgeds as $ged_id=>$gedcom) { + $datalist = array(); + foreach ($this->myfamlist as $family) { + if ($family->getGedId()==$ged_id) { + $datalist[]=$family; + } + } + if ($datalist) { + $somethingPrinted = true; + usort($datalist, array('GedcomRecord', 'Compare')); + $GEDCOM=$gedcom; + load_gedcom_settings($ged_id); + print_fam_table($datalist, i18n::translate('Families').' : «'.$this->myquery.'» @ '.PrintReady(get_gedcom_setting($ged_id, 'title'), true)); + } + } + // Split sources by gedcom + foreach ($this->sgeds as $ged_id=>$gedcom) { + $datalist = array(); + foreach ($this->mysourcelist as $source) { + if ($source->getGedId()==$ged_id) { + $datalist[]=$source; + } + } + if ($datalist) { + $somethingPrinted = true; + usort($datalist, array('GedcomRecord', 'Compare')); + $GEDCOM=$gedcom; + load_gedcom_settings($ged_id); + print_sour_table($datalist, i18n::translate('Sources').' : «'.$this->myquery.'» @ '.PrintReady(get_gedcom_setting($ged_id, 'title'), true)); + } + } + // Split notes by gedcom + foreach ($this->sgeds as $ged_id=>$gedcom) { + $datalist = array(); + foreach ($this->mynotelist as $note) { + if ($note->getGedId()==$ged_id) { + $datalist[]=$note; + } + } + if ($datalist) { + $somethingPrinted = true; + usort($datalist, array('GedcomRecord', 'Compare')); + $GEDCOM=$gedcom; + load_gedcom_settings($ged_id); + print_note_table($datalist, i18n::translate('Notes').' : «'.$this->myquery.'» @ '.PrintReady(get_gedcom_setting($ged_id, 'title'), true)); + } + } + $GEDCOM=WT_GEDCOM; + load_gedcom_settings(WT_GED_ID); + } else + if (isset ($this->query)) { + echo "<br /><div class=\"warning\" style=\" text-align: center;\"><i>".i18n::translate('No results found.')."</i><br />"; + if (!isset ($this->srindi) && !isset ($this->srfams) && !isset ($this->srsour) && !isset ($this->srnote)) { + echo "<i>".i18n::translate('Be sure to select an option to search for.')."</i><br />"; + } + echo '</div>'; + } + // Prints the Paged Results: << 1 2 3 4 >> links if there are more than $this->resultsPerPage results + if ($this->resultsPerPage >= 1 && $this->totalGeneralResults > $this->resultsPerPage) { + $this->printPageResultsLinks($this->inputFieldNames, $this->totalGeneralResults, $this->resultsPerPage); + } + } + return $somethingPrinted; // whether anything printed + } + + /************************************************ Helper Methods ****************************************************************/ + + /** + * Function that returns only the results for the current page + * i.e. if $controller->resultsPageNum == 2 and $resultsPerPage == 10 this + * function would return results 11 - 20. + * + * @param array() $results - the original results. + * @param int $resultsPerPage - If $results count is less + * than $resultsPerPage it will simply return $results. + * @return array - the filtered results i.e. 11-20. + */ + function getPagedResults($results, $resultsPerPage) { + $len = count($results); + if ($len <= $resultsPerPage) { + if ($this->resultsPageNum==0) return $results; + else return array(); + } + $pagedResults = array (); + $startPosition = $this->resultsPageNum * $resultsPerPage; + $endPosition = ($this->resultsPageNum + 1) * $resultsPerPage; + $i = 0; + if (isset ($results) && $len > 0) { + foreach ($results as $key => $value) { + if ($i >= $startPosition) + $pagedResults[$key] = $value; + $i ++; + if ($i >= $endPosition) + break; + } + return $pagedResults; + } + return array(); + } + + /** + * prints out the paging links for a page with many results i.e. Result Page: << 1 2 3 4 5 >> + * + * @param $this->inputFieldNames - an array of strings representing the names of the variables to include + * in the query string usually from input values in a form i.e. 'action', 'query', 'showasso' etc. + */ + function printPageResultsLinks($inputFieldNames, $totalResults, $resultsPerPage) { + echo "<br /><table align='center'><tr><td>".i18n::translate('Result Page')." "; + // Prints the '<<' linking to the previous page if it's not on the first page + if ($this->resultsPageNum > 0) { + echo " <a href='"; + $this->printQueryString($inputFieldNames, 0); + echo "'><<</a> "; + echo " <a href='"; + $this->printQueryString($inputFieldNames, ($this->resultsPageNum - 1)); + echo "'><</a>"; + } + + // Prints out each number linking to that page number. + // If it's on that page number it is printed out bold instead of a link + for ($i = 1; $i < (($totalResults / $resultsPerPage) + 1); $i ++) { + if ($i != $this->resultsPageNum + 1) { + echo " <a href='"; + $this->printQueryString($inputFieldNames, ($i -1)); + echo "'>".$i."</a>"; + } else + echo " <b>".$i."</b>"; + } + + // Prints the '>>' linking to the next page if it's not on the last page + if ($this->resultsPageNum < (($totalResults / $resultsPerPage) - 1)) { + echo " <a href='"; + $this->printQueryString($inputFieldNames, ($this->resultsPageNum + 1)); + echo "'>></a>"; + echo " <a href='"; + $this->printQueryString($inputFieldNames, (int)($totalResults / $resultsPerPage)); + echo "'>>></a>"; + } + + echo "</td></tr></table>"; + } + + /** + * Prints the query string that goes ... <a href' HERE '> for each paging result link + * + * @param $inputFieldNames - an array of strings representing the names of the variables to include + * in the query string usually from input values in a form i.e. 'action', 'query', 'showasso' etc. + * @param $pageNum - the page number to link to in the paged results + */ + function printQueryString($inputFieldNames, $pageNum) { + global $GEDCOM; + $tempURL = "search.php?ged=".rawurlencode($GEDCOM); + foreach ($inputFieldNames as $key => $value) { + $controllerVar = $this->getValue($value); + if (!empty ($controllerVar)) { + $tempURL .= "&{$value}={$controllerVar}"; + } + } + $tempURL .= "&resultsPageNum={$pageNum}"; + foreach ($this->sgeds as $i=>$key) { + $str = str_replace(array (".", "-", " "), array ("_", "_", "_"), $key); + $tempURL .= "&{$str}=yes"; + } + echo $tempURL; + } + + function getValue($varName) { + if (isset ($this-> $varName)) { + $value = $this-> $varName; + return $value; + } else + return ""; + } +} diff --git a/library/WT/Controller/Source.php b/library/WT/Controller/Source.php new file mode 100644 index 0000000000..1b0fcffa3b --- /dev/null +++ b/library/WT/Controller/Source.php @@ -0,0 +1,233 @@ +<?php +// Controller for the Source Page +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2010 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_SOURCE_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_print_facts.php'; +require_once WT_ROOT.'includes/classes/class_menu.php'; +require_once WT_ROOT.'includes/functions/functions_import.php'; + +class WT_Controller_Source extends WT_Controller_Base { + var $sid; + var $source = null; + var $diffsource = null; + var $accept_success = false; + + function init() { + $this->sid = safe_GET_xref('sid'); + + $gedrec = find_source_record($this->sid, WT_GED_ID); + + if (find_source_record($this->sid, WT_GED_ID) || find_updated_record($this->sid, WT_GED_ID)!==null) { + $this->source = new WT_Source($gedrec); + $this->source->ged_id=WT_GED_ID; // This record is from a file + } else if (!$gedrec) { + return false; + } + + $this->rid=$this->source->getXref(); // Correct upper/lower case mismatch + + if (!$this->source->canDisplayDetails()) { + print_header(i18n::translate('Source')); + print_privacy_error(); + print_footer(); + exit; + } + + //-- perform the desired action + switch($this->action) { + case 'addfav': + if (WT_USER_ID && !empty($_REQUEST['gid']) && array_key_exists('user_favorites', WT_Module::getActiveModules())) { + $favorite = array( + 'username' => WT_USER_NAME, + 'gid' => $_REQUEST['gid'], + 'type' => 'SOUR', + 'file' => WT_GEDCOM, + 'url' => '', + 'note' => '', + 'title' => '' + ); + user_favorites_WT_Module::addFavorite($favorite); + } + unset($_GET['action']); + break; + case 'accept': + if (WT_USER_CAN_ACCEPT) { + accept_all_changes($this->sid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + //-- check if we just deleted the record and redirect to index + $gedrec = find_source_record($this->sid, WT_GED_ID); + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->source = new WT_Source($gedrec); + } + unset($_GET['action']); + break; + case 'undo': + if (WT_USER_CAN_ACCEPT) { + reject_all_changes($this->sid, WT_GED_ID); + $this->show_changes=false; + $this->accept_success=true; + $gedrec = find_source_record($this->sid, WT_GED_ID); + //-- check if we just deleted the record and redirect to index + if (empty($gedrec)) { + header('Location: '.WT_SERVER_NAME.WT_SCRIPT_PATH); + exit; + } + $this->source = new WT_Source($gedrec); + } + unset($_GET['action']); + break; + } + + //-- if the user can edit and there are changes then get the new changes + if ($this->show_changes && WT_USER_CAN_EDIT) { + $newrec = find_updated_record($this->sid, WT_GED_ID); + if (!empty($newrec)) { + $this->diffsource = new WT_Source($newrec); + $this->diffsource->setChanged(true); + } + } + + if ($this->show_changes) { + $this->source->diffMerge($this->diffsource); + } + } + + /** + * get the title for this page + * @return string + */ + function getPageTitle() { + if ($this->source) { + return $this->source->getFullName()." - ".i18n::translate('Source Information'); + } else { + return i18n::translate('Unable to find record with ID'); + } + } + + /** + * get edit menu + */ + function getEditMenu() { + global $TEXT_DIRECTION, $WT_IMAGES, $GEDCOM, $SHOW_GEDCOM_RECORD; + + if (!$this->source) return null; + if ($TEXT_DIRECTION=="rtl") { + $ff="_rtl"; + } else { + $ff=""; + } + // edit menu + $menu = new Menu(i18n::translate('Edit')); + $menu->addIcon('edit_sour'); + $menu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}", 'icon_large_gedcom'); + + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Edit Source')); + $submenu->addOnclick('return edit_source(\''.$this->sid.'\');'); + $submenu->addIcon('edit_sour'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + $menu->addSeparator(); + } + + // show/hide changes + if (find_updated_record($this->sid, WT_GED_ID)!==null) { + if (!$this->show_changes) { + $submenu = new Menu(i18n::translate('This record has been updated. Click here to show changes.'), "source.php?sid={$this->sid}&show_changes=yes"); + $submenu->addIcon('edit_sour'); + } else { + $submenu = new Menu(i18n::translate('Click here to hide changes.'), "source.php?sid={$this->sid}&show_changes=no"); + $submenu->addIcon('edit_sour'); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + if (WT_USER_CAN_ACCEPT) { + $submenu = new Menu(i18n::translate('Undo all changes'), "source.php?sid={$this->sid}&action=undo"); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $submenu->addIcon('edit_sour'); + $menu->addSubmenu($submenu); + $submenu = new Menu(i18n::translate('Approve all changes'), "source.php?sid={$this->sid}&action=accept"); + $submenu->addIcon('edit_sour'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + $menu->addSeparator(); + } + + // edit/view raw gedcom + if (WT_USER_IS_ADMIN || $SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('Edit raw GEDCOM record')); + $submenu->addOnclick("return edit_raw('".$this->sid."');"); + $submenu->addIcon('gedcom'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } elseif ($SHOW_GEDCOM_RECORD) { + $submenu = new Menu(i18n::translate('View GEDCOM Record')); + $submenu->addIcon('gedcom'); + if ($this->show_changes && WT_USER_CAN_EDIT) { + $submenu->addOnclick("return show_gedcom_record('new');"); + } else { + $submenu->addOnclick("return show_gedcom_record();"); + } + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // delete + if (WT_USER_CAN_EDIT) { + $submenu = new Menu(i18n::translate('Delete this Source')); + $submenu->addOnclick("if (confirm('".i18n::translate('Are you sure you want to delete this Source?')."')) return deletesource('".$this->sid."'); else return false;"); + $submenu->addIcon('edit_sour'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + } + + // add to favorites + $submenu = new Menu(i18n::translate('Add to My Favorites'), "source.php?action=addfav&sid={$this->sid}&gid={$this->sid}"); + $submenu->addIcon('favorites'); + $submenu->addClass("submenuitem{$ff}", "submenuitem_hover{$ff}", "submenu{$ff}"); + $menu->addSubmenu($submenu); + + //-- get the link for the first submenu and set it as the link for the main menu + if (isset($menu->submenus[0])) { + $link = $menu->submenus[0]->onclick; + $menu->addOnclick($link); + } + return $menu; + } +} diff --git a/library/WT/Controller/Timeline.php b/library/WT/Controller/Timeline.php new file mode 100644 index 0000000000..d3a0151fcd --- /dev/null +++ b/library/WT/Controller/Timeline.php @@ -0,0 +1,322 @@ +<?php +// Controller for the timeline chart +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_TIMELINE_CTRL_PHP', ''); + +require_once WT_ROOT.'includes/functions/functions_charts.php'; + +class WT_Controller_Timeline extends WT_Controller_Base { + var $bheight = 30; + var $placements = array(); + var $familyfacts = array(); + var $indifacts = array(); // array to store the fact records in for sorting and displaying + var $birthyears=array(); + var $birthmonths=array(); + var $birthdays=array(); + var $baseyear=0; + var $topyear=0; + var $pids = array(); + var $people = array(); + var $pidlinks = ""; + var $scale = 2; + // GEDCOM elements that may have DATE data, but should not be displayed + var $nonfacts = array("BAPL","ENDL","SLGC","SLGS","_TODO","CHAN"); + + /** + * Initialization function + */ + function init() { + $this->baseyear = date("Y"); + //-- new pid + $newpid=safe_GET_xref('newpid'); + if ($newpid) { + $indirec = find_person_record($newpid, WT_GED_ID); + } + + if (safe_GET('clear', '1')=='1') { + unset($_SESSION['timeline_pids']); + } else { + if (isset($_SESSION['timeline_pids'])) $this->pids = $_SESSION['timeline_pids']; + //-- pids array + $this->pids=safe_GET_xref('pids'); + } + if (!is_array($this->pids)) $this->pids = array(); + else { + //-- make sure that arrays are indexed by numbers + $this->pids = array_values($this->pids); + } + if (!empty($newpid) && !in_array($newpid, $this->pids)) $this->pids[] = $newpid; + if (count($this->pids)==0) $this->pids[] = check_rootid(""); + $remove = safe_GET_xref('remove'); + //-- cleanup user input + $newpids = array(); + foreach ($this->pids as $key=>$value) { + if ($value!=$remove) { + $newpids[] = $value; + $person = WT_Person::getInstance($value); + if (!is_null($person)) $this->people[] = $person; + } + } + $this->pids = $newpids; + $this->pidlinks = ""; + /* @var $indi Person */ + foreach ($this->people as $p=>$indi) { + if (!is_null($indi) && $indi->canDisplayDetails()) { + //-- setup string of valid pids for links + $this->pidlinks .= "pids[]=".$indi->getXref()."&"; + $bdate = $indi->getBirthDate(); + if ($bdate->isOK()) { + $date = $bdate->MinDate(); + $date = $date->convert_to_cal('gregorian'); + if ($date->y) { + $this->birthyears [$indi->getXref()] = $date->y; + $this->birthmonths[$indi->getXref()] = max(1, $date->m); + $this->birthdays [$indi->getXref()] = max(1, $date->d); + } + } + // find all the fact information + $indi->add_family_facts(false); + foreach ($indi->getIndiFacts() as $event) { + //-- get the fact type + $fact = $event->getTag(); + if (!in_array($fact, $this->nonfacts)) { + //-- check for a date + $date = $event->getDate(); + $date=$date->MinDate(); + $date=$date->convert_to_cal('gregorian'); + if ($date->y) { + $this->baseyear=min($this->baseyear, $date->y); + $this->topyear =max($this->topyear, $date->y); + + if (!$indi->isDead()) + $this->topyear=max($this->topyear, date('Y')); + $event->temp = $p; + //-- do not add the same fact twice (prevents marriages from being added multiple times) + if (!in_array($event, $this->indifacts, true)) $this->indifacts[] = $event; + } + } + } + } + } + $_SESSION['timeline_pids'] = $this->pids; + $scale=safe_GET_integer('scale', 0, 200, 0); + if ($scale==0) { + $this->scale = round(($this->topyear-$this->baseyear)/20 * count($this->indifacts)/4); + if ($this->scale<6) $this->scale = 6; + } + else $this->scale = $scale; + if ($this->scale<2) $this->scale=2; + $this->baseyear -= 5; + $this->topyear += 5; + } + /** + * check the privacy of the incoming people to make sure they can be shown + */ + function checkPrivacy() { + $printed = false; + for ($i=0; $i<count($this->people); $i++) { + if (!is_null($this->people[$i])) { + if (!$this->people[$i]->canDisplayDetails()) { + if ($this->people[$i]->canDisplayName()) { + echo " <a href=\"".$this->people[$i]->getHtmlUrl()."\">".PrintReady($this->people[$i]->getFullName())."</a>"; + print_privacy_error(); + echo "<br />"; + $printed = true; + } + else if (!$printed) { + print_privacy_error(); + echo "<br />"; + } + } + } + } + } + + function print_time_fact($event) { + global $basexoffset, $baseyoffset, $factcount, $TEXT_DIRECTION, $WT_IMAGES, $SHOW_PEDIGREE_PLACES, $placements, $familyfacts; + + /* @var $event Event */ + $factrec = $event->getGedComRecord(); + $fact = $event->getTag(); + $desc = $event->getDetail(); + if ($fact=="EVEN" || $fact=="FACT") { + $fact = $event->getType(); + } + //-- check if this is a family fact + $famid = $event->getFamilyId(); + if ($famid!=null) { + //-- if we already showed this family fact then don't print it + if (isset($familyfacts[$famid.$fact])&&($familyfacts[$famid.$fact]!=$event->temp)) return; + $familyfacts[$famid.$fact] = $event->temp; + } + $gdate=$event->getDate(); + $date=$gdate->MinDate(); + $date=$date->convert_to_cal('gregorian'); + $year = $date->y; + $month = max(1, $date->m); + $day = max(1, $date->d); + $xoffset = $basexoffset+22; + $yoffset = $baseyoffset+(($year-$this->baseyear) * $this->scale)-($this->scale); + $yoffset = $yoffset + (($month / 12) * $this->scale); + $yoffset = $yoffset + (($day / 30) * ($this->scale/12)); + $yoffset = floor($yoffset); + $place = round($yoffset / $this->bheight); + $i=1; + $j=0; + $tyoffset = 0; + while (isset($placements[$place])) { + if ($i==$j) { + $tyoffset = $this->bheight * $i; + $i++; + } + else { + $tyoffset = -1 * $this->bheight * $j; + $j++; + } + $place = round(($yoffset+$tyoffset) / ($this->bheight)); + } + $yoffset += $tyoffset; + $xoffset += abs($tyoffset); + $placements[$place] = $yoffset; + + echo "<div id=\"fact$factcount\" style=\"position:absolute; ".($TEXT_DIRECTION =="ltr"?"left: ".($xoffset):"right: ".($xoffset))."px; top:".($yoffset)."px; font-size: 8pt; height: ".($this->bheight)."px; \" onmousedown=\"factMD(this, '".$factcount."', ".($yoffset-$tyoffset).");\">"; + echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"cursor: hand;\"><tr><td>"; + echo "<img src=\"".$WT_IMAGES["hline"]."\" name=\"boxline$factcount\" id=\"boxline$factcount\" height=\"3\" align=\"left\" hspace=\"0\" width=\"10\" vspace=\"0\" alt=\"\" style=\"padding-"; + if ($TEXT_DIRECTION=="ltr") echo "left"; + else echo "right"; + echo ": 3px;\" />"; + $col = $event->temp % 6; + echo "</td><td valign=\"top\" class=\"person".$col."\">"; + if (count($this->pids) > 6) echo $event->getParentObject()->getFullName()." - "; + $indi=$event->getParentObject(); + echo $event->getLabel(); + echo " -- "; + if (get_class($indi)=="Person") { + echo format_fact_date($event); + } + if (get_class($indi)=="Family") { + echo $gdate->Display(false); + $family=$indi; + $husbid=$family->getHusbId(); + $wifeid=$family->getWifeId(); + //-- Retrieve husband and wife age + for ($p=0; $p<count($this->pids); $p++) { + if ($this->pids[$p]==$husbid) { + $husb=$family->getHusband(); + if (is_null($husb)) $husb = new WT_Person(''); + $hdate=$husb->getBirthDate(); + if ($hdate->isOK()) $ageh=get_age_at_event(GedcomDate::GetAgeGedcom($hdate, $gdate), false); + } + else if ($this->pids[$p]==$wifeid) { + $wife=$family->getWife(); + if (is_null($wife)) $wife = new WT_Person(''); + $wdate=$wife->getBirthDate(); + if ($wdate->isOK()) $agew=get_age_at_event(GedcomDate::GetAgeGedcom($wdate, $gdate), false); + } + } + if (!empty($ageh) && $ageh > 0) { + if (empty($agew)) { + echo '<span class="age"> ', i18n::translate('Age'), ' ', $ageh, '</span>'; + } else { + echo '<span class="age"> ', i18n::translate('Husband\'s age'), ' ', $ageh, ' '; + } + } + if (!empty($agew) && $agew > 0) { + if (empty($ageh)) { + echo '<span class="age"> ', i18n::translate('Age'), ' ', $agew, '</span>'; + } else { + echo i18n::translate('Wife\'s age'), ' ', $agew, '</span>'; + } + } + } + echo " ".PrintReady($desc); + if ($SHOW_PEDIGREE_PLACES>0) { + $place = $event->getPlace(); + if ($place!=null) { + if ($desc!=null) echo " - "; + $plevels = explode(',', $place); + for ($plevel=0; $plevel<$SHOW_PEDIGREE_PLACES; $plevel++) { + if (!empty($plevels[$plevel])) { + if ($plevel>0) echo ", "; + echo PrintReady($plevels[$plevel]); + } + } + } + } + //-- print spouse name for marriage events + $spouse = WT_Person::getInstance($event->getSpouseId()); + if ($spouse) { + for ($p=0; $p<count($this->pids); $p++) { + if ($this->pids[$p]==$spouse->getXref()) break; + } + if ($p==count($this->pids)) $p = $event->temp; + $col = $p % 6; + if ($spouse->getXref()!=$this->pids[$p]) { + echo ' <a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a>'; + } + else { + $ct = preg_match("/2 _WTFS @(.*)@/", $factrec, $match); + if ($ct>0) { + echo " <a href=\"family.php?famid={$match[1]}&ged=".WT_GEDURL."\">"; + if ($event->getParentObject()->canDisplayName()) echo $event->getParentObject()->getFullName(); + else echo i18n::translate('Private'); + echo "</a>"; + } + } + } + echo "</td></tr></table>"; + echo "</div>"; + if ($TEXT_DIRECTION=='ltr') { + $img = "dline2"; + $ypos = "0%"; + } + else { + $img = "dline"; + $ypos = "100%"; + } + $dyoffset = ($yoffset-$tyoffset)+$this->bheight/3; + if ($tyoffset<0) { + $dyoffset = $yoffset+$this->bheight/3; + if ($TEXT_DIRECTION=='ltr') { + $img = "dline"; + $ypos = "100%"; + } + else { + $img = "dline2"; + $ypos = "0%"; + } + } + //-- print the diagnal line + echo "<div id=\"dbox$factcount\" style=\"position:absolute; ".($TEXT_DIRECTION =="ltr"?"left: ".($basexoffset+25):"right: ".($basexoffset+25))."px; top:".($dyoffset)."px; font-size: 8pt; height: ".(abs($tyoffset))."px; width: ".(abs($tyoffset))."px;"; + echo " background-image: url('".$WT_IMAGES[$img]."');"; + echo " background-position: 0% $ypos; \" >"; + echo "</div>"; + } +} diff --git a/library/WT/DB.php b/library/WT/DB.php new file mode 100644 index 0000000000..489413d041 --- /dev/null +++ b/library/WT/DB.php @@ -0,0 +1,274 @@ +<?php +// +// Class file for the database access. Extend PHP's native PDO and +// PDOStatement classes to provide database access with logging, etc. +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (c) 2009-2010 Greg Roach +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @package webtrees +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_DB_PHP', ''); + +class WT_DB { + ////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTION + // Implement a singleton to decorate a PDO object. + // See http://en.wikipedia.org/wiki/Singleton_pattern + // See http://en.wikipedia.org/wiki/Decorator_pattern + ////////////////////////////////////////////////////////////////////////////// + private static $instance=null; + private static $pdo=null; + + // Prevent instantiation via new WT_DB + private final function __construct() { + } + + // Prevent instantiation via clone() + public final function __clone() { + trigger_error('WT_DB::clone() is not allowed.', E_USER_ERROR); + } + + // Prevent instantiation via serialize() + public final function __wakeup() { + trigger_error('WT_DB::unserialize() is not allowed.', E_USER_ERROR); + } + + // Disconnect from the server, so we can connect to another one + public static function disconnect() { + self::$pdo=null; + } + + // Implement the singleton pattern + public static function createInstance($DBHOST, $DBPORT, $DBNAME, $DBUSER, $DBPASS) { + if (self::$pdo instanceof PDO) { + trigger_error('WT_DB::createInstance() can only be called once.', E_USER_ERROR); + } + // Create the underlying PDO object + self::$pdo=new PDO( + "mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}", $DBUSER, $DBPASS, + array( + PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_OBJ, + PDO::ATTR_CASE=>PDO::CASE_LOWER, + PDO::ATTR_AUTOCOMMIT=>true + ) + ); + self::$pdo->exec("SET NAMES UTF8"); + + // Assign the singleton + self::$instance=new self; + } + + // We don't access this directly, only via query(), exec() and prepare() + public static function getInstance() { + if (self::$pdo instanceof PDO) { + return self::$instance; + } else { + trigger_error('WT_DB::createInstance() must be called before WT_DB::getInstance().', E_USER_ERROR); + } + } + + public static function isConnected() { + return (self::$pdo instanceof PDO); + } + + ////////////////////////////////////////////////////////////////////////////// + // LOGGING + // Keep a log of the statements executed using this connection + ////////////////////////////////////////////////////////////////////////////// + private static $log=array(); + + // Add an entry to the log + public static function logQuery($query, $rows, $microtime, $bind_variables) { + if (WT_DEBUG_SQL) { + // Full logging + // Trace + $trace=debug_backtrace(); + array_shift($trace); + array_shift($trace); + foreach ($trace as $n=>$frame) { + if (isset($frame['file']) && isset($frame['line'])) { + $trace[$n]=basename($frame['file']).':'.$frame['line'].' '.$frame['function'].'('./*implode(',', $frame['args']).*/')'; + } else { + unset($trace[$n]); + } + } + $stack='<abbr title="'.htmlspecialchars(implode(" / ", $trace)).'">'.(count(self::$log)+1).'</abbr>'; + // Bind variables + $query2=''; + foreach ($bind_variables as $key=>$value) { + if (is_null($value)) { + $bind_variables[$key]='[NULL]'; + } + } + foreach (str_split(htmlspecialchars($query)) as $char) { + if ($char=='?') { + $query2.='<abbr title="'.htmlspecialchars(array_shift($bind_variables)).'">'.$char.'</abbr>'; + } else { + $query2.=$char; + } + } + // Highlight embedded literal strings. + if (preg_match('/[\'"]/', $query)) { + $query2='<span style="background-color:yellow;">'.$query2.'</span>'; + } + // Highlight slow queries + $microtime*=1000; // convert to milliseconds + if ($microtime>1000) { + $microtime=sprintf('<span style="background-color:red">%.3f</span>', $microtime); + } elseif ($microtime>100) { + $microtime=sprintf('<span style="background-color:orange">%.3f</span>', $microtime); + } elseif ($microtime>1) { + $microtime=sprintf('<span style="background-color:yellow">%.3f</span>', $microtime); + } else { + $microtime=sprintf('%.3f', $microtime); + } + self::$log[]="<tr><td>{$stack}</td><td>{$query2}</td><td>{$rows}</td><td>{$microtime}</td></tr>"; + } else { + // Just log query count for statistics + self::$log[]=true; + } + } + + // Total number of queries executed, for the page statistics + public static function getQueryCount() { + return count(self::$log); + } + + // Display the query log as a table, for debugging + public static function getQueryLog() { + $html='<table border="1"><col span="3"/><col align="char"/><thead><tr><th>#</th><th>Query</th><th>Rows</th><th>Time (ms)</th></tr><tbody/>'.implode('', self::$log).'</table>'; + self::$log=array(); + return $html; + } + + ////////////////////////////////////////////////////////////////////////////// + // INTERROGATE DATA DICTIONARY + ////////////////////////////////////////////////////////////////////////////// + public static function table_exists($table) { + global $DBNAME; + + switch (self::$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + // Mysql 4.x does not support the information schema + default: + // Catch-all for other databases + try { + WT_DB::prepare("SELECT 1 FROM {$table}")->fetchOne(); + return true; + } catch (PDOException $ex) { + return false; + } + } + } + + public static function column_exists($table, $column) { + global $DBNAME; + + switch (self::$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { + case 'mysql': + // Mysql 4.x does not support the information schema + default: + // Catch-all for other databases + try { + WT_DB::prepare("SELECT {$column} FROM {$table}")->fetchOne(); + return true; + } catch (PDOException $ex) { + return false; + } + } + } + + ////////////////////////////////////////////////////////////////////////////// + // FUNCTIONALITY ENHANCEMENTS + ////////////////////////////////////////////////////////////////////////////// + + // The native quote() function does not convert PHP nulls to DB nulls + public static function quote($string, $parameter_type=PDO::PARAM_STR) { + if (is_null($string)) { + return 'NULL'; + } else { + return self::$pdo->quote($string, $parameter_type); + } + } + + // Add logging to query() + public static function query($statement, $parameter_type= PDO::PARAM_STR) { + $statement=str_replace('##', WT_TBLPREFIX, $statement); + $start=microtime(true); + $result=self::$pdo->query($statement, $parameter_type); + $end=microtime(true); + self::logQuery($statement, count($result), $end-$start, array()); + return $result; + } + + // Add logging to exec() + public static function exec($statement) { + $statement=str_replace('##', WT_TBLPREFIX, $statement); + $start=microtime(true); + $result=self::$pdo->exec($statement); + $end=microtime(true); + self::logQuery($statement, $result, $end-$start, array()); + return $result; + } + + // Add logging/functionality to prepare() + public static function prepare($statement) { + if (!self::$pdo instanceof PDO) { + throw new PDOException("No Connection Established"); + } + $statement=str_replace('##', WT_TBLPREFIX, $statement); + return new WT_DBStatement(self::$pdo->prepare($statement)); + } + + // Map all other functions onto the base PDO object + public function __call($function, $params) { + return call_user_func_array(array(self::$pdo, $function), $params); + } + + ////////////////////////////////////////////////////////////////////////////// + // Create/update tables, indexes, etc. + ////////////////////////////////////////////////////////////////////////////// + public static function updateSchema($schema_dir, $schema_name, $target_version) { + try { + $current_version=(int)get_site_setting($schema_name); + } catch (PDOException $e) { + // During initial installation, this table won't exist. + // It will only be a problem if we can't subsequently create it. + $current_version=0; + } + while ($current_version<$target_version) { + $next_version=$current_version+1; + require $schema_dir.'db_schema_'.$current_version.'_'.$next_version.'.php'; + // The updatescript should update the version or throw an exception + $current_version=(int)get_site_setting($schema_name); + if ($current_version!=$next_version) { + die("Internal error while updating {$schema_name} to {$next_version}"); + } + } + } +} diff --git a/library/WT/DBStatement.php b/library/WT/DBStatement.php new file mode 100644 index 0000000000..8263d8a38a --- /dev/null +++ b/library/WT/DBStatement.php @@ -0,0 +1,168 @@ +<?php +// +// Class file for the database access. Extend PHP's native PDO and +// PDOStatement classes to provide database access with logging, etc. +// +// webtrees: Web based Family History software +// Copyright (C) 2010 webtrees development team. +// +// Derived from PhpGedView +// Copyright (c) 2009-2010 Greg Roach +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// @package webtrees +// @version $Id$ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_DB_STATEMENT_PHP', ''); + +class WT_DBStatement { + ////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTION + // Decorate a PDOStatement object. + // See http://en.wikipedia.org/wiki/Decorator_pattern + ////////////////////////////////////////////////////////////////////////////// + private $pdostatement=null; + + // Keep track of calls to execute(), so we can do it automatically + private $executed=false; + + // Keep a copy of the bind variables, for logging + private $bind_variables=array(); + + // Our constructor just takes a copy of the object to be decorated + public function __construct(PDOStatement $statement) { + $this->pdostatement=$statement; + } + + // Need this function to load BLOB values from streams + public function bindParam($num, &$value, $type) { + $this->pdostatement->bindParam($num, $value, $type); + return $this; + } + + ////////////////////////////////////////////////////////////////////////////// + // FLUENT INTERFACE + // Add automatic calling of execute() and closeCursor() + // See http://en.wikipedia.org/wiki/Fluent_interface + ////////////////////////////////////////////////////////////////////////////// + public function __call($function, $params) { + switch ($function) { + case 'closeCursor': + $this->executed=false; + // no break; + case 'bindColumn': + case 'bindParam': + case 'bindValue': + // TODO: bind variables need to be stored in $this->bind_variables so we can log them + case 'setAttribute': + case 'setFetchMode': + // Functions that return no values become fluent + call_user_func_array(array($this->pdostatement, $function), $params); + return $this; + case 'execute': + if ($this->executed) { + trigger_error('WT_DBStatement::execute() called twice.', E_USER_ERROR); + } else { + if ($params) { + $this->bind_variables=$params[0]; + foreach ($params[0] as &$param) { + if ($param===false) { + // For consistency, otherwise true=>'1' and false=>'' + $param=0; + } + } + } + $start=microtime(true); + $result=call_user_func_array(array($this->pdostatement, $function), $params); + $end=microtime(true); + $this->executed=!preg_match('/^(insert|delete|update|create|alter) /i', $this->pdostatement->queryString); + WT_DB::logQuery($this->pdostatement->queryString, $this->pdostatement->rowCount(), $end-$start, $this->bind_variables); + return $this; + } + case 'fetch': + case 'fetchColumn': + case 'fetchObject': + case 'fetchAll': + // Automatically execute the query + if (!$this->executed) { + $this->execute(); + $this->executed=true; + } + // no break; + default: + return call_user_func_array(array($this->pdostatement, $function), $params); + } + } + + ////////////////////////////////////////////////////////////////////////////// + // FUNCTIONALITY ENHANCEMENTS + ////////////////////////////////////////////////////////////////////////////// + + // Fetch one row, and close the cursor. e.g. SELECT * FROM foo WHERE pk=bar + public function fetchOneRow($fetch_style=PDO::FETCH_OBJ) { + if (!$this->executed) { + $this->execute(); + } + $row=$this->pdostatement->fetch($fetch_style); + $this->pdostatement->closeCursor(); + $this->executed=false; + return $row ? $row : null; + } + + // Fetch one value and close the cursor. e.g. SELECT MAX(foo) FROM bar + public function fetchOne($default=null) { + if (!$this->executed) { + $this->execute(); + } + $row=$this->pdostatement->fetch(PDO::FETCH_NUM); + $this->pdostatement->closeCursor(); + $this->executed=false; + return is_array($row) ? $row[0] : $default; + } + + // Fetch two columns, and return an associative array of col1=>col2 + public function fetchAssoc() { + if (!$this->executed) { + $this->execute(); + } + $rows=array(); + while ($row=$this->pdostatement->fetch(PDO::FETCH_NUM)) { + $rows[$row[0]]=$row[1]; + } + $this->pdostatement->closeCursor(); + $this->executed=false; + return $rows; + } + + // Fetch all the first column, as an array + public function fetchOneColumn() { + if (!$this->executed) { + $this->execute(); + } + $list=array(); + while ($row=$this->pdostatement->fetch(PDO::FETCH_NUM)) { + $list[]=$row[0]; + } + $this->pdostatement->closeCursor(); + $this->executed=false; + return $list; + } +} diff --git a/library/WT/Family.php b/library/WT/Family.php new file mode 100644 index 0000000000..f64034be22 --- /dev/null +++ b/library/WT/Family.php @@ -0,0 +1,430 @@ +<?php +/** + * Class file for a Family + * + * webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView + * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @package webtrees + * @subpackage DataModel + * @version $Id$ + */ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_FAMILY_PHP', ''); + +class WT_Family extends WT_GedcomRecord { + private $husb = null; + private $wife = null; + private $children = array(); + private $childrenIds = array(); + private $marriage = null; + private $children_loaded = false; + private $numChildren = false; + private $_isDivorced = null; + private $_isNotMarried = null; + + // Create a Family object from either raw GEDCOM data or a database row + function __construct($data) { + if (is_array($data)) { + // Construct from a row from the database + if ($data['f_husb']) { + $this->husb=WT_Person::getInstance($data['f_husb']); + } + if ($data['f_wife']) { + $this->wife=WT_Person::getInstance($data['f_wife']); + } + if (preg_match_all('/\n1 CHIL @('.WT_REGEX_XREF.')@/', $data['gedrec'], $matches)) { + $this->childrenIds=$matches[1]; + } + $this->numChildren=$data['f_numchil']; + // Check for divorce, etc. *before* we privatize the data so + // we can correctly label spouses/ex-spouses/partners + $this->_isDivorced=(bool)preg_match('/\n1 ('.WT_EVENTS_DIV.')( Y|\n)/', $data['gedrec']); + $this->_isNotMarried=(bool)preg_match('/\n1 _NMR( Y|\n)/', $data['gedrec']); + } else { + // Construct from raw GEDCOM data + if (preg_match('/^1 HUSB @(.+)@/m', $data, $match)) { + $this->husb=WT_Person::getInstance($match[1]); + } + if (preg_match('/^1 WIFE @(.+)@/m', $data, $match)) { + $this->wife=WT_Person::getInstance($match[1]); + } + if (preg_match_all('/^1 CHIL @(.+)@/m', $data, $match)) { + $this->childrenIds=$match[1]; + } + if (preg_match('/^1 NCHI (\d+)/m', $data, $match)) { + $this->numChildren=$match[1]; + } else { + $this->numChildren=count($this->childrenIds); + } + // Check for divorce, etc. *before* we privatize the data so + // we can correctly label spouses/ex-spouses/partners + $this->_isDivorced=(bool)preg_match('/\n1 ('.WT_EVENTS_DIV.')( Y|\n)/', $data); + $this->_isNotMarried=(bool)preg_match('/\n1 _NMR( Y|\n)/', $data); + } + + // Make sure husb/wife are the right way round. + if ($this->husb && $this->husb->getSex()=='F' || $this->wife && $this->wife->getSex()=='M') { + list($this->husb, $this->wife)=array($this->wife, $this->husb); + } + + parent::__construct($data); + } + + /** + * get the husbands ID + * @return string + */ + function getHusbId() { + if (!is_null($this->husb)) return $this->husb->getXref(); + else return ''; + } + + /** + * get the wife ID + * @return string + */ + function getWifeId() { + if (!is_null($this->wife)) return $this->wife->getXref(); + else return ''; + } + + /** + * get the husband's person object + * @return Person + */ + function &getHusband() { + return $this->husb; + } + /** + * get the wife's person object + * @return Person + */ + function &getWife() { + return $this->wife; + } + + /** + * return the spouse of the given person + * @param Person $person + * @return Person + */ + function &getSpouse(&$person) { + if (is_null($this->wife) or is_null($this->husb)) return null; + if ($this->wife->equals($person)) return $this->husb; + if ($this->husb->equals($person)) return $this->wife; + return null; + } + + /** + * return the spouse id of the given person id + * @param string $pid + * @return string + */ + function getSpouseId($pid) { + if (is_null($this->wife) or is_null($this->husb)) return null; + if ($this->wife->getXref()==$pid) return $this->husb->getXref(); + if ($this->husb->getXref()==$pid) return $this->wife->getXref(); + return null; + } + + /** + * get the children + * @return array array of children Persons + */ + function getChildren() { + if (!$this->children_loaded) $this->loadChildren(); + return $this->children; + } + + /** + * get the children ids + * @return array array of children ids + */ + function getChildrenIds() { + if (!$this->children_loaded) $this->loadChildren(); + return $this->childrenIds; + } + + // Static helper function to sort an array of families by marriage date + static function CompareMarrDate($x, $y) { + return GedcomDate::Compare($x->getMarriageDate(), $y->getMarriageDate()); + } + + /** + * Load the children from the database + * We used to load the children when the family was created, but that has performance issues + * because we often don't need all the children + * now, children are only loaded as needed + */ + function loadChildren() { + if ($this->children_loaded) return; + $this->childrenIds = array(); + $this->numChildren = preg_match_all('/\n1 CHIL @('.WT_REGEX_XREF.')@/', $this->gedrec, $smatch, PREG_SET_ORDER); + for ($i=0; $i<$this->numChildren; $i++) { + $this->childrenIds[] = $smatch[$i][1]; + } + foreach ($this->childrenIds as $t=>$chil) { + $child=WT_Person::getInstance($chil); + if ($child) { + $this->children[] = $child; + } + } + $this->children_loaded = true; + } + + /** + * get the number of children in this family + * @return int the number of children + */ + function getNumberOfChildren() { + + $nchi1=(int)get_gedcom_value('NCHI', 1, $this->gedrec); + $nchi2=(int)get_gedcom_value('NCHI', 2, $this->gedrec); + $nchi3=preg_match_all('/\n1 CHIL @(.*)@/', $this->gedrec, $smatch); + return $this->numChildren=max($nchi1, $nchi2, $nchi3); + } + + /** + * get updated Family + * If there is an updated family record in the gedcom file + * return a new family object for it + */ + function getUpdatedFamily() { + if ($this->getChanged()) { + return $this; + } + if (WT_USER_CAN_EDIT && $this->canDisplayDetails()) { + $newrec = find_updated_record($this->xref, $this->ged_id); + if (!is_null($newrec)) { + $newfamily = new WT_Family($newrec); + $newfamily->setChanged(true); + return $newfamily; + } + } + return null; + } + /** + * check if this family has the given person + * as a parent in the family + * @param Person $person + */ + function hasParent(&$person) { + if (is_null($person)) return false; + if ($person->equals($this->husb)) return true; + if ($person->equals($this->wife)) return true; + return false; + } + /** + * check if this family has the given person + * as a child in the family + * @param Person $person + */ + function hasChild(&$person) { + if (is_null($person)) return false; + $this->loadChildren(); + foreach ($this->children as $key=>$child) { + if ($person->equals($child)) return true; + } + return false; + } + + /** + * parse marriage record + */ + function _parseMarriageRecord() { + $this->marriage = new Event(trim(get_sub_record(1, '1 MARR', $this->gedrec)), -1); + $this->marriage->setParentObject($this); + } + + /** + * get the marriage event + * + * @return Event + */ + function getMarriage() { + if (is_null($this->marriage)) $this->_parseMarriageRecord(); + return $this->marriage; + } + + /** + * get marriage record + * @return string + */ + function getMarriageRecord() { + if (is_null($this->marriage)) $this->_parseMarriageRecord(); + return $this->marriage->getGedcomRecord(); + } + + // Return whether or not this family ended in a divorce or was never married. + // Note that this is calculated prior to privatizing the data, so we can + // always distinguish spouses from ex-spouses. This apparant leaking of + // private data was discussed and agreed on the pgv forum. + function isDivorced() { + return $this->_isDivorced; + } + function isNotMarried() { + return $this->_isNotMarried; + } + + /** + * get marriage date + * @return string + */ + function getMarriageDate() { + if (!$this->canDisplayDetails()) { + return new GedcomDate(''); + } + if (is_null($this->marriage)) { + $this->_parseMarriageRecord(); + } + return $this->marriage->getDate(); + } + + /** + * get the marriage year + * @return string + */ + function getMarriageYear($est = true, $cal = '') { + // TODO - change the design to use julian days, not gregorian years. + $mdate = $this->getMarriageDate(); + $mdate = $mdate->MinDate(); + if ($cal) $mdate = $mdate->convert_to_cal($cal); + return $mdate->y; + } + + /** + * get the marriage month + * @return string + */ + function getMarriageMonth($est = true, $cal = '') { + // TODO - change the design to use julian days, not gregorian years. + $mdate = $this->getMarriageDate(); + $mdate=$mdate->MinDate(); + if ($cal) $mdate = $mdate->convert_to_cal($cal); + return $mdate->m; + } + + /** + * get the type for this marriage + * @return string + */ + function getMarriageType() { + if (is_null($this->marriage)) $this->_parseMarriageRecord(); + return $this->marriage->getType(); + } + + /** + * get the marriage place + * @return string + */ + function getMarriagePlace() { + $marriage = $this->getMarriage(); + return $marriage->getPlace(); + } + + // Get all the dates/places for marriages - for the FAM lists + function getAllMarriageDates() { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_MARR) as $event) { + if ($array=$this->getAllEventDates($event)) { + return $array; + } + } + } + return array(); + } + function getAllMarriagePlaces() { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_MARR) as $event) { + if ($array=$this->getAllEventPlaces($event)) { + return $array; + } + } + } + return array(); + } + + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('family.php?famid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('family.php?famid=', '&'); + } + + // Get an array of structures containing all the names in the record + public function getAllNames() { + if (is_null($this->_getAllNames)) { + $husb=$this->husb ? $this->husb : new WT_Person('1 SEX M'); + $wife=$this->wife ? $this->wife : new WT_Person('1 SEX F'); + // Check the script used by each name, so we can match cyrillic with cyrillic, greek with greek, etc. + $husb_names=$husb->getAllNames(); + foreach ($husb_names as $n=>$husb_name) { + $husb_names[$n]['script']=utf8_script($husb_name['surn']); + } + $wife_names=$wife->getAllNames(); + foreach ($wife_names as $n=>$wife_name) { + $wife_names[$n]['script']=utf8_script($wife_name['surn']); + } + // Add the matched names first + foreach ($husb_names as $husb_name) { + foreach ($wife_names as $wife_name) { + if ($husb_name['type']!='_MARNM' && $wife_name['type']!='_MARNM' && $husb_name['script']==$wife_name['script']) { + $this->_getAllNames[]=array( + 'type'=>$husb_name['type'], + 'full'=>$husb_name['full'].' + '.$wife_name['full'], + 'list'=>$husb_name['list'].$husb->getSexImage().'<br />'.$wife_name['list'].$wife->getSexImage(), + 'sort'=>$husb_name['sort'].' + '.$wife_name['sort'], + ); + } + } + } + // Add the unmatched names second (there may be no matched names) + foreach ($husb_names as $husb_name) { + foreach ($wife_names as $wife_name) { + if ($husb_name['type']!='_MARNM' && $wife_name['type']!='_MARNM' && $husb_name['script']!=$wife_name['script']) { + $this->_getAllNames[]=array( + 'type'=>$husb_name['type'], + 'full'=>$husb_name['full'].' + '.$wife_name['full'], + 'list'=>$husb_name['list'].$husb->getSexImage().'<br />'.$wife_name['list'].$wife->getSexImage(), + 'sort'=>$husb_name['sort'].' + '.$wife_name['sort'], + ); + } + } + } + } + return $this->_getAllNames; + } + + // 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_MARR, 1). + $this->format_first_major_fact(WT_EVENTS_DIV, 1); + } +} diff --git a/library/WT/GedcomRecord.php b/library/WT/GedcomRecord.php new file mode 100644 index 0000000000..9f3180fb07 --- /dev/null +++ b/library/WT/GedcomRecord.php @@ -0,0 +1,862 @@ +<?php +/** +* Base class for all gedcom records +* +* webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView +* Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +* +* 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 2 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, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +* @package webtrees +* @subpackage DataModel +* @version $Id$ +*/ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_GEDCOMRECORD_PHP', ''); + +require_once WT_ROOT.'includes/classes/class_event.php'; + +class WT_GedcomRecord { + protected $xref =null; // The record identifier + protected $type =null; // INDI, FAM, etc. + public $ged_id =null; // The gedcom file, only set if this record comes from the database + protected $gedrec =null; // Raw gedcom text (privatised) + protected $facts =null; + protected $changeEvent=null; + public $disp =true; // Can we display details of this object + private $can_edit =null; + public $dispname =true; // Can we display the name of this object + private $changed =false; // Is this a new record, pending approval + + // Cached results from various functions. + protected $_getAllNames =null; + protected $_getPrimaryName =null; + protected $_getSecondaryName=null; + + // Create a GedcomRecord object from either raw GEDCOM data or a database row + public function __construct($data) { + if (is_array($data)) { + // Construct from a row from the database + $this->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]; + $this->ged_id=WT_GED_ID; + } + } + + //-- 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 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 WT_Person($data); + break; + case 'FAM': + $object=new WT_Family($data); + break; + case 'SOUR': + $object=new WT_Source($data); + break; + case 'OBJE': + $object=new WT_Media($data); + break; + case 'REPO': + $object=new WT_Repository($data); + break; + case 'NOTE': + $object=new WT_Note($data); + break; + default: + $object=new WT_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; + 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; + } + + /** + * 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 + return $link.$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 + return $link.$this->getXref().$separator.'ged='.WT_GEDURL; + } + } + + // 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 '<a href="'.$this->getHtmlUrl().'#content" name="'.preg_replace('/\D/','',$this->getXref()).'" '.$target.'>'.$this->getXref().'</a>'; + } 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; + } + + // Can we edit this record? + public function canEdit() { + if ($this->can_edit===null) { + $this->can_edit= + get_gedcom_setting($this->ged_id, 'ALLOW_EDIT_GEDCOM') && ( + WT_USER_GEDCOM_ADMIN || + WT_USER_CAN_EDIT && strpos($this->gedrec, "\n1 RESN locked")===false + ); + } + return $this->can_edit; + } + + // 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='<a href="'.$this->getHtmlUrl().'"'; + if ($find) { + $html.=' onclick="pasteid(\''.$this->getXref().'\');"'; + } + $html.=' class="list_item"><b>'.$name.'</b>'; + $html.=$this->format_list_details(); + $html='<'.$tag.' class="'.$dir.'" dir="'.$dir.'">'.$html.'</a></'.$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()) && $event->canShow()) { + switch ($style) { + case 1: + return '<br /><i>'.$event->getLabel().' '.format_fact_date($event).format_fact_place($event).'</i>'; + case 2: + return '<dl><dt class="label">'.$event->getLabel().'</dt><dd class="field">'.format_fact_date($event).format_fact_place($event).'</dd></dl>'; + case 3: + return $event->getDate()->MinDate()->Format('%Y'); + } + } + } + return ''; + } + + // Count the number of records that link to this one + public function countLinkedIndividuals() { + return count_linked_indi($this->getXref(), $this->getType(), $this->ged_id); + } + public function countLinkedFamilies() { + return count_linked_fam($this->getXref(), $this->getType(), $this->ged_id); + } + public function countLinkedNotes() { + return count_linked_note($this->getXref(), $this->getType(), $this->ged_id); + } + public function countLinkedSources() { + return count_linked_sour($this->getXref(), $this->getType(), $this->ged_id); + } + public function countLinkedMedia() { + return count_linked_obje($this->getXref(), $this->getType(), $this->ged_id); + } + + // Fetch the records that link to this one + public function fetchLinkedIndividuals() { + return fetch_linked_indi($this->getXref(), $this->getType(), $this->ged_id); + } + public function fetchLinkedFamilies() { + return fetch_linked_fam($this->getXref(), $this->getType(), $this->ged_id); + } + public function fetchLinkedNotes() { + return fetch_linked_note($this->getXref(), $this->getType(), $this->ged_id); + } + public function fetchLinkedSources() { + return fetch_linked_sour($this->getXref(), $this->getType(), $this->ged_id); + } + public function fetchLinkedMedia() { + return fetch_linked_obje($this->getXref(), $this->getType(), $this->ged_id); + } + + // Get all attributes (e.g. DATE or PLAC) from an event (e.g. BIRT or MARR). + // This is used to display multiple events on the individual/family lists. + // Multiple events can exist because of uncertainty in dates, dates in different + // calendars, place-names in both latin and hebrew character sets, etc. + // It also allows us to combine dates/places from different events in the summaries. + public function getAllEventDates($event) { + $dates=array(); + foreach ($this->getAllFactsByType($event) as $event) { + if ($event->getDate()->isOK()) { + $dates[]=$event->getDate(); + } + } + return $dates; + } + public function getAllEventPlaces($event) { + $places=array(); + foreach ($this->getAllFactsByType($event) as $event) { + if (preg_match_all('/\n(?:2 PLAC|3 (?:ROMN|FONE|_HEB)) +(.+)/', $event->getGedcomRecord(), $ged_places)) { + foreach ($ged_places[1] as $ged_place) { + $places[]=$ged_place; + } + } + } + return $places; + } + + /** + * Get the first Event for the given Fact type + * + * @param string $fact + * @return Event + */ + public function getFactByType($factType) { + $this->parseFacts(); + if (empty($this->facts)) { + return null; + } + foreach ($this->facts as $f=>$fact) { + if ($fact->getTag()==$factType || $fact->getType()==$factType) { + return $fact; + } + } + return null; + } + + /** + * Return an array of events that match the given types + * + * @param mixed $factTypes may be a single string or an array of strings + * @return Event + */ + public function getAllFactsByType($factTypes) { + $this->parseFacts(); + if (is_string($factTypes)) { + $factTypes = array($factTypes); + } + $facts = array(); + foreach ($factTypes as $factType) { + foreach ($this->facts as $fact) { + if ($fact->getTag()==$factType) { + $facts[]=$fact; + } + } + } + return $facts; + } + + /** + * returns an array of all of the facts + * @return Array + */ + public function getFacts($nfacts=NULL) { + $this->parseFacts($nfacts); + return $this->facts; + } + + /** + * Get the CHAN event for this record + * + * @return Event + */ + public function getChangeEvent() { + if (is_null($this->changeEvent)) { + $this->changeEvent = $this->getFactByType('CHAN'); + } + return $this->changeEvent; + } + + /** + * Parse the facts from the record + */ + public function parseFacts($nfacts=NULL) { + //-- only run this function once + if (!is_null($this->facts) && is_array($this->facts)) { + return; + } + $this->facts=array(); + //-- don't run this function if privacy does not allow viewing of details + if (!$this->canDisplayDetails()) { + return; + } + //-- must trim the record here because the record is trimmed in edit and it could mess up line numbers + $this->gedrec = trim($this->gedrec); + //-- find all the fact information + $indilines = explode("\n", $this->gedrec); // -- find the number of lines in the individuals record + $lct = count($indilines); + $factrec = ''; // -- complete fact record + $line = ''; // -- temporary line buffer + $linenum=1; + for ($i=1; $i<=$lct; $i++) { + if ($i<$lct) { + $line = $indilines[$i]; + } else { + $line=' '; + } + if (empty($line)) { + $line=' '; + } + if ($i==$lct||$line{0}==1) { + if ($i>1) { + $event = new Event($factrec, $linenum); + $fact = $event->getTag(); + if ($nfacts==NULL || !in_array($fact, $nfacts)) { + $event->setParentObject($this); + $this->facts[] = $event; + } + } + $factrec = $line; + $linenum = $i; + } + else $factrec .= "\n".$line; + } + } + + /** + * Merge the facts from another GedcomRecord object into this object + * for generating a diff view + * @param GedcomRecord $diff the record to compare facts with + */ + public function diffMerge(&$diff) { + if (is_null($diff)) { + return; + } + $this->parseFacts(); + $diff->parseFacts(); + + //-- update old facts + foreach ($this->facts as $key=>$event) { + $found = false; + foreach ($diff->facts as $indexval => $newevent) { + $newfact = $newevent->getGedcomRecord(); + $newfact=preg_replace("/\\\/", '/', $newfact); + if (trim($newfact)==trim($event->getGedcomRecord())) { + $found = true; + break; + } + } + if (!$found) { + $this->facts[$key]->gedcomRecord.="\nWT_OLD\n"; + } + } + //-- look for new facts + foreach ($diff->facts as $key=>$newevent) { + $found = false; + foreach ($this->facts as $indexval => $event) { + $newfact = $newevent->getGedcomRecord(); + $newfact=preg_replace("/\\\/", '/', $newfact); + if (trim($newfact)==trim($event->getGedcomRecord())) { + $found = true; + break; + } + } + if (!$found) { + $newevent->gedcomRecord.="\nWT_NEW\n"; + $this->facts[]=$newevent; + } + } + } + + public function getEventDate($event) { + $srec = $this->getAllEvents($event); + if (!$srec) { + return ''; + } + $srec = $srec[0]; + return get_gedcom_value('DATE', 2, $srec); + } + public function getEventSource($event) { + $srec = $this->getAllEvents($event); + if (!$srec) { + return ''; + } + $srec = $srec[0]; + return get_sub_record('SOUR', 2, $srec); + } + + ////////////////////////////////////////////////////////////////////////////// + // Get the last-change timestamp for this record - optionally wrapped in a + // link to ourself. + ////////////////////////////////////////////////////////////////////////////// + public function LastChangeTimestamp($add_url) { + global $DATE_FORMAT, $TIME_FORMAT; + + $chan = $this->getChangeEvent(); + + if (is_null($chan)) { + return ' '; + } + + $d = $chan->getDate(); + if (preg_match('/^(\d\d):(\d\d):(\d\d)/', get_gedcom_value('DATE:TIME', 2, $chan->getGedcomRecord(), '', false).':00', $match)) { + $t=mktime($match[1], $match[2], $match[3]); + $sort=$d->MinJD().$match[1].$match[2].$match[3]; + $text=strip_tags($d->Display(false, "{$DATE_FORMAT} - ", array()).date(str_replace('%', '', $TIME_FORMAT), $t)); + } else { + $sort=$d->MinJD().'000000'; + $text=strip_tags($d->Display(false, "{$DATE_FORMAT}", array())); + } + if ($add_url) { + $text='<a name="'.$sort.'" href="'.$this->getHtmlUrl().'">'.$text.'</a>'; + } + return $text; + } + + ////////////////////////////////////////////////////////////////////////////// + // Get the last-change user for this record + ////////////////////////////////////////////////////////////////////////////// + public function LastchangeUser() { + $chan = $this->getChangeEvent(); + + if (is_null($chan)) { + return ' '; + } + + $chan_user = $chan->getValue('_WT_USER'); + if (empty($chan_user)) { + return ' '; + } + return $chan_user; + } +} diff --git a/library/WT/Media.php b/library/WT/Media.php new file mode 100644 index 0000000000..9189d89e95 --- /dev/null +++ b/library/WT/Media.php @@ -0,0 +1,319 @@ +<?php +/** + * Class that defines a media object + * + * webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView + * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. + * + * Modifications Copyright (c) 2010 Greg Roach + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @package webtrees + * @subpackage Charts + * @version $Id$ + */ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_MEDIA_PHP', ''); + +class WT_Media extends WT_GedcomRecord { + var $title =null; + var $file =null; + var $ext =''; + var $mime =''; + var $note =null; + var $filesizeraw =-1; + var $width =0; + var $height =0; + var $serverfilename=''; + var $fileexists =false; + var $filepropset =false; + + // Create a Media object from either raw GEDCOM data or a database row + function __construct($data) { + if (is_array($data)) { + // Construct from a row from the database + $this->title=$data['m_titl']; + $this->file =$data['m_file']; + } else { + // Construct from raw GEDCOM data + $this->title = get_gedcom_value('TITL', 1, $data); + if (empty($this->title)) { + $this->title = get_gedcom_value('TITL', 2, $data); + } + $this->file = get_gedcom_value('FILE', 1, $data); + } + if (empty($this->title)) $this->title = $this->file; + + parent::__construct($data); + } + + /** + * get the media note + * @return string + */ + function getNote() { + if (is_null($this->note)) { + $this->note=get_gedcom_value('NOTE', 1, $this->getGedcomRecord()); + } + return $this->note; + } + + /** + * get the thumbnail filename + * @return string + */ + function getThumbnail($generateThumb = true) { + return thumbnail_file($this->file,$generateThumb); + } + + /** + * get the media icon filename + * @return string + */ + function getMediaIcon() { + return media_icon_file($this->file); + } + + /** + * get the media file name + * @return string + */ + function getFilename() { + return $this->file; + } + + /** + * get the relative file path of the image on the server + * @return string + */ + function getLocalFilename() { + return check_media_depth($this->file); + } + + /** + * get the file name on the server + * @return string + */ + function getServerFilename() { + global $USE_MEDIA_FIREWALL; + if ($this->serverfilename) return $this->serverfilename; + $localfilename = $this->getLocalFilename(); + if (!empty($localfilename)) { + if (file_exists($localfilename)) { + // found image in unprotected directory + $this->fileexists = 2; + $this->serverfilename = $localfilename; + return $this->serverfilename; + } + if ($USE_MEDIA_FIREWALL) { + $protectedfilename = get_media_firewall_path($localfilename); + if (file_exists($protectedfilename)) { + // found image in protected directory + $this->fileexists = 3; + $this->serverfilename = $protectedfilename; + return $this->serverfilename; + } + } + } + // file doesn't exist, return the standard localfilename for backwards compatibility + $this->fileexists = false; + $this->serverfilename = $localfilename; + return $this->serverfilename; + } + + /** + * check if the file exists on this server + * @return boolean + */ + function fileExists() { + if (!$this->serverfilename) $this->getServerFilename(); + return $this->fileexists; + } + + /** + * get the media file size + * @return string + */ + function getFilesize() { + if (!$this->filepropset) $this->setFileProperties(); + return sprintf('%.2f', @$this->filesizeraw/1024); + } + + /** + * get the media file size, unformatted + * @return number + */ + function getFilesizeraw() { + if (!$this->filepropset) $this->setFileProperties(); + return $this->filesizeraw; + } + + /** + * get the media type + * @return string + */ + function getMediatype() { + $mediaType = strtolower(get_gedcom_value('FORM:TYPE', 2, $this->gedrec)); + return $mediaType; + } + + /** + * get the media file type + * @return string + */ + function getFiletype() { + if (!$this->filepropset) $this->setFileProperties(); + return $this->ext; + } + + /** + * get the media mime type + * @return string + */ + function getMimetype() { + if (!$this->filepropset) $this->setFileProperties(); + return $this->mime; + } + + /** + * get the width of the image + * @return number (0 if not an image) + */ + function getWidth() { + if (!$this->filepropset) $this->setFileProperties(); + return $this->width; + } + + /** + * get the height of the image + * @return number (0 if not an image) + */ + function getHeight() { + if (!$this->filepropset) $this->setFileProperties(); + return $this->height; + } + + /** + * internal function, sets a number of properties + * no need to call directly + * @return nothing + */ + function setFileProperties() { + if ($this->fileExists()) { + $this->filesizeraw = @filesize($this->getServerFilename()); + $imgsize=@getimagesize($this->getServerFilename()); // [0]=width [1]=height [2]=filetype ['mime']=mimetype + if (is_array($imgsize)) { + // this is an image + $this->width =0+$imgsize[0]; + $this->height=0+$imgsize[1]; + $imageTypes =array('','GIF','JPG','PNG','SWF','PSD','BMP','TIFF','TIFF','JPC','JP2','JPX','JB2','SWC','IFF','WBMP','XBM'); + $this->ext =$imageTypes[0+$imgsize[2]]; + $this->mime =$imgsize['mime']; + } + } + if (!$this->mime) { + // this is not an image, OR the file doesn't exist OR it is a url + // set file type equal to the file extension - can't use parse_url because this may not be a full url + $exp = explode('?', $this->file); + $pathinfo = pathinfo($exp[0]); + $this->ext = @strtoupper($pathinfo['extension']); + // all mimetypes we wish to serve with the media firewall must be added to this array. + $mime=array('DOC'=>'application/msword', 'MOV'=>'video/quicktime', 'MP3'=>'audio/mpeg', 'PDF'=>'application/pdf', + 'PPT'=>'application/vnd.ms-powerpoint', 'RTF'=>'text/rtf', 'SID'=>'image/x-mrsid', 'TXT'=>'text/plain', 'XLS'=>'application/vnd.ms-excel'); + if (empty($mime[$this->ext])) { + // if we don't know what the mimetype is, use something ambiguous + $this->mime='application/octet-stream'; + if ($this->fileExists()) { + // alert the admin if we cannot determine the mime type of an existing file + // as the media firewall will be unable to serve this file properly + AddToLog('Media Firewall error: >Unknown Mimetype< for file >'.$this->file.'<', 'media'); + } + } else { + $this->mime=$mime[$this->ext]; + } + } + $this->filepropset = true; + } + + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('mediaviewer.php?mid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('mediaviewer.php?mid=', '&'); + } + + /** + * check if the given Media object is in the objectlist + * @param Media $obje + * @return mixed returns the ID for the for the matching media or null if not found + */ + static function in_obje_list($obje, $ged_id) { + return + WT_DB::prepare("SELECT m_media FROM `##media` WHERE m_file=? AND m_titl LIKE ? AND m_gedfile=?") + ->execute(array($obje->file, $obje->title, $ged_id)) + ->fetchOne(); + } + + /** + * check if this object is equal to the given object + * basically just checks if the IDs are the same + * @param GedcomRecord $obj + */ + function equals(&$obj) { + if (is_null($obj)) return false; + if ($this->xref==$obj->getXref()) return true; + if ($this->title==$obj->title && $this->file==$obj->file) return true; + return false; + } + + // If this object has no name, what do we call it? + function getFallBackName() { + if ($this->canDisplayDetails()) { + return utf8_strtoupper(basename($this->file)); + } else { + return $this->getXref(); + } + } + + // Get an array of structures containing all the names in the record + public function getAllNames() { + if (strpos($this->gedrec, "\n1 TITL ")) { + // Earlier gedcom versions had level 1 titles + return parent::_getAllNames('TITL', 1); + } else { + // Later gedcom versions had level 2 titles + return parent::_getAllNames('TITL', 2); + } + } + + // Extra info to display when displaying this record in a list of + // selection items or favorites. + function format_list_details() { + ob_start(); + print_media_links('1 OBJE @'.$this->getXref().'@', 1, $this->getXref()); + return ob_get_clean(); + } +} diff --git a/library/WT/Note.php b/library/WT/Note.php new file mode 100644 index 0000000000..002814e360 --- /dev/null +++ b/library/WT/Note.php @@ -0,0 +1,63 @@ +<?php +/** + * Class file for a Shared Note (NOTE) object + * + * webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView + * Copyright (C) 2009 PGV Development Team. All rights reserved. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @package webtrees + * @subpackage DataModel + * @version $Id$ + */ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_NOTE_PHP', ''); + +class WT_Note extends WT_GedcomRecord { + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('note.php?nid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('note.php?nid=', '&'); + } + + // The 'name' of a note record is the first line. This can be + // somewhat unwieldy if lots of CONC records are used. Limit to 100 chars + protected function _addName($type, $value, $gedrec) { + if (utf8_strlen($value)<100) { + parent::_addName($type, $value, $gedrec); + } else { + parent::_addName($type, utf8_substr($value, 0, 100).i18n::translate('…'), $gedrec); + } + } + + // Get an array of structures containing all the names in the record + public function getAllNames() { + // Uniquely, the NOTE objects have data in their level 0 record. + // Hence the REGEX passed in the second parameter + return parent::_getAllNames('NOTE', '0 @'.WT_REGEX_XREF.'@'); + } +} diff --git a/library/WT/Person.php b/library/WT/Person.php new file mode 100644 index 0000000000..a482d3d629 --- /dev/null +++ b/library/WT/Person.php @@ -0,0 +1,1750 @@ +<?php +/** +* Class file for a person +* +* webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView +* Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. +* +* 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 2 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, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +* @package webtrees +* @subpackage DataModel +* @version $Id$ +*/ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_PERSON_PHP', ''); + +require_once WT_ROOT.'includes/classes/class_event.php'; + +class WT_Person extends WT_GedcomRecord { + var $indifacts = array(); + var $otherfacts = array(); + var $globalfacts = array(); + var $mediafacts = array(); + var $facts_parsed = false; + var $fams = null; + var $famc = null; + var $spouseFamilies = null; + var $childFamilies = null; + var $label = ''; + var $highlightedimage = null; + var $file = ''; + var $age = null; + var $isdead = -1; + var $sex=null; + var $generation; // used in some lists to keep track of this Person's generation in that list + + // Cached results from various functions. + private $_getBirthDate=null; + private $_getBirthPlace=null; + private $_getAllBirthDates=null; + private $_getAllBirthPlaces=null; + private $_getEstimatedBirthDate=null; + private $_getDeathDate=null; + private $_getDeathPlace=null; + private $_getAllDeathDates=null; + private $_getAllDeathPlaces=null; + private $_getEstimatedDeathDate=null; + + // Create a Person object from either raw GEDCOM data or a database row + function __construct($data) { + if (is_array($data)) { + // Construct from a row from the database + $this->isdead=$data['i_isdead']; + $this->sex =$data['i_sex']; + } else { + // Construct from raw GEDCOM data + } + + parent::__construct($data); + + $this->dispname=$this->disp || showLivingNameById($this->xref); + } + + // Static helper function to sort an array of people by birth date + static function CompareBirtDate($x, $y) { + return GedcomDate::Compare($x->getEstimatedBirthDate(), $y->getEstimatedBirthDate()); + } + + // Static helper function to sort an array of people by death date + static function CompareDeatDate($x, $y) { + return GedcomDate::Compare($x->getEstimatedDeathDate(), $y->getEstimatedDeathDate()); + } + + /** + * Return whether or not this person is already dead + * @return boolean true if dead, false if alive + */ + function isDead() { + if ($this->isdead==-1) { + $this->isdead=is_dead($this->gedrec, $this->ged_id); + WT_DB::prepare( + "UPDATE `##individuals` SET i_isdead=? WHERE i_id=? AND i_file=?" + )->execute(array($this->isdead, $this->xref, $this->ged_id)); + } + return $this->isdead; + } + + /** + * get highlighted media + * @return array + */ + function findHighlightedMedia() { + if (is_null($this->highlightedimage)) { + $this->highlightedimage = find_highlighted_object($this->xref, $this->ged_id, $this->gedrec); + } + return $this->highlightedimage; + } + + /** + * get birth date + * @return GedcomDate the birth date + */ + function getBirthDate() { + if (is_null($this->_getBirthDate)) { + if ($this->canDisplayDetails()) { + foreach ($this->getAllBirthDates() as $date) { + if ($date->isOK()) { + $this->_getBirthDate=$date; + break; + } + } + if (is_null($this->_getBirthDate)) { + $this->_getBirthDate=new GedcomDate(''); + } + } else { + $this->_getBirthDate=new GedcomDate("(".i18n::translate('Private').")"); + } + } + return $this->_getBirthDate; + } + + /** + * get the birth place + * @return string + */ + function getBirthPlace() { + if (is_null($this->_getBirthPlace)) { + if ($this->canDisplayDetails()) { + foreach ($this->getAllBirthPlaces() as $place) { + if ($place) { + $this->_getBirthPlace=$place; + break; + } + } + if (is_null($this->_getBirthPlace)) { + $this->_getBirthPlace=''; + } + } else { + $this->_getBirthPlace=i18n::translate('Private'); + } + } + return $this->_getBirthPlace; + } + + /** + * get the Census birth place (Town and County (reversed)) + * @return string + */ + function getCensBirthPlace() { + if (is_null($this->_getBirthPlace)) { + if ($this->canDisplayDetails()) { + foreach ($this->getAllBirthPlaces() as $place) { + if ($place) { + $this->_getBirthPlace=$place; + break; + } + } + if (is_null($this->_getBirthPlace)) { + $this->_getBirthPlace=''; + } + } else { + $this->_getBirthPlace=i18n::translate('Private'); + } + } + $censbirthplace = $this->_getBirthPlace; + $censbirthplace = explode(', ', $censbirthplace); + $censbirthplace = array_reverse($censbirthplace); + $censbirthplace = array_slice($censbirthplace, 1); + $censbirthplace = array_slice($censbirthplace, 0, 2); + $censbirthplace = implode(', ', $censbirthplace); + return $censbirthplace; + } + + /** + * get the birth year + * @return string the year of birth + */ + function getBirthYear() { + return $this->getBirthDate()->MinDate()->Format('%Y'); + } + + /** + * get death date + * @return GedcomDate the death date in the GEDCOM format of '1 JAN 2006' + */ + function getDeathDate($estimate = true) { + if (is_null($this->_getDeathDate)) { + if ($this->canDisplayDetails()) { + foreach ($this->getAllDeathDates() as $date) { + if ($date->isOK()) { + $this->_getDeathDate=$date; + break; + } + } + if (is_null($this->_getDeathDate)) { + $this->_getDeathDate=new GedcomDate(''); + } + } else { + $this->_getDeathDate=new GedcomDate("(".i18n::translate('Private').")"); + } + } + return $this->_getDeathDate; + } + + /** + * get the death place + * @return string + */ + function getDeathPlace() { + if (is_null($this->_getDeathPlace)) { + if ($this->canDisplayDetails()) { + foreach ($this->getAllDeathPlaces() as $place) { + if ($place) { + $this->_getDeathPlace=$place; + break; + } + } + if (is_null($this->_getDeathPlace)) { + $this->_getDeathPlace=''; + } + } else { + $this->_getDeathPlace=i18n::translate('Private'); + } + } + return $this->_getDeathPlace; + } + + /** + * get the death year + * @return string the year of death + */ + function getDeathYear() { + return $this->getDeathDate()->MinDate()->Format('%Y'); + } + + /** + * get the birth and death years + * @return string + */ + function getBirthDeathYears($age_at_death=true, $classname='details1') { + if (!$this->getBirthYear()) { + return ''; + } + $tmp = '<span dir="ltr" title="'.strip_tags($this->getBirthDate()->Display()).'">'.$this->getBirthYear().'-</span>'; + $tmp .= '<span title="'.strip_tags($this->getDeathDate()->Display()).'">'.$this->getDeathYear().'</span>'; + // display age only for exact dates (empty date qualifier) + if ($age_at_death + && $this->getBirthYear() && empty($this->getBirthDate()->qual1) + && $this->getDeathYear() && empty($this->getDeathDate()->qual1)) { + $age = get_age_at_event(GedcomDate::GetAgeGedcom($this->getBirthDate(), $this->getDeathDate()), false); + if (!empty($age)) { + $tmp .= '<span class="age"> ('.i18n::translate('Age').' '.$age.')</span>'; + } + } + if ($classname) { + return '<span class="'.$classname.'">'.$tmp.'</span>'; + } + return $tmp; + } + + // Get all the dates/places for births/deaths - for the INDI lists + function getAllBirthDates() { + if (is_null($this->_getAllBirthDates)) { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_BIRT) as $event) { + if ($this->_getAllBirthDates=$this->getAllEventDates($event)) { + break; + } + } + } else { + $this->_getAllBirthDates=array(); + } + } + return $this->_getAllBirthDates; + } + function getAllBirthPlaces() { + if (is_null($this->_getAllBirthPlaces)) { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_BIRT) as $event) { + if ($this->_getAllBirthPlaces=$this->getAllEventPlaces($event)) { + break; + } + } + } else { + $this->_getAllBirthPlaces=array(); + } + } + return $this->_getAllBirthPlaces; + } + function getAllDeathDates() { + if (is_null($this->_getAllDeathDates)) { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_DEAT) as $event) { + if ($this->_getAllDeathDates=$this->getAllEventDates($event)) { + break; + } + } + } else { + $this->_getAllDeathDates=array(); + } + } + return $this->_getAllDeathDates; + } + function getAllDeathPlaces() { + if (is_null($this->_getAllDeathPlaces)) { + if ($this->canDisplayDetails()) { + foreach (explode('|', WT_EVENTS_DEAT) as $event) { + if ($this->_getAllDeathPlaces=$this->getAllEventPlaces($event)) { + break; + } + } + } else { + $this->_getAllDeathPlaces=array(); + } + } + return $this->_getAllDeathPlaces; + } + + // Generate an estimate for birth/death dates, based on dates of parents/children/spouses + function getEstimatedBirthDate() { + if (is_null($this->_getEstimatedBirthDate)) { + foreach ($this->getAllBirthDates() as $date) { + if ($date->isOK()) { + $this->_getEstimatedBirthDate=$date; + break; + } + } + if (is_null($this->_getEstimatedBirthDate)) { + $min=array(); + $max=array(); + $tmp=$this->getDeathDate(); + if ($tmp->MinJD()) { + global $MAX_ALIVE_AGE; + $min[]=$tmp->MinJD()-$MAX_ALIVE_AGE*365; + $max[]=$tmp->MaxJD(); + } + foreach ($this->getChildFamilies() as $family) { + $tmp=$family->getMarriageDate(); + if (is_object($tmp) && $tmp->MinJD()) { + $min[]=$tmp->MaxJD()-365*1; + $max[]=$tmp->MinJD()+365*30; + } + if ($parent=$family->getHusband()) { + $tmp=$parent->getBirthDate(); + if (is_object($tmp) && $tmp->MinJD()) { + $min[]=$tmp->MaxJD()+365*15; + $max[]=$tmp->MinJD()+365*65; + } + } + if ($parent=$family->getWife()) { + $tmp=$parent->getBirthDate(); + if (is_object($tmp) && $tmp->MinJD()) { + $min[]=$tmp->MaxJD()+365*15; + $max[]=$tmp->MinJD()+365*45; + } + } + foreach ($family->getChildren() as $child) { + $tmp=$child->getBirthDate(); + if ($tmp->MinJD()) { + $min[]=$tmp->MaxJD()-365*30; + $max[]=$tmp->MinJD()+365*30; + } + } + } + foreach ($this->getSpouseFamilies() as $family) { + $tmp=$family->getMarriageDate(); + if (is_object($tmp) && $tmp->MinJD()) { + $min[]=$tmp->MaxJD()-365*45; + $max[]=$tmp->MinJD()-365*15; + } + if ($spouse=$family->getSpouse($this)) { + $tmp=$spouse->getBirthDate(); + if (is_object($tmp) && $tmp->MinJD()) { + $min[]=$tmp->MaxJD()-365*25; + $max[]=$tmp->MinJD()+365*25; + } + } + foreach ($family->getChildren() as $child) { + $tmp=$child->getBirthDate(); + if ($tmp->MinJD()) { + $min[]=$tmp->MaxJD()-365*($this->getSex()=='F'?45:65); + $max[]=$tmp->MinJD()-365*15; + } + } + } + if ($min && $max) { + list($y)=GregorianDate::JDtoYMD(floor((max($min)+min($max))/2)); + $this->_getEstimatedBirthDate=new GedcomDate("EST {$y}"); + } else { + $this->_getEstimatedBirthDate=new GedcomDate(''); // always return a date object + } + } + } + return $this->_getEstimatedBirthDate; + } + function getEstimatedDeathDate() { + if (is_null($this->_getEstimatedDeathDate)) { + foreach ($this->getAllDeathDates() as $date) { + if ($date->isOK()) { + $this->_getEstimatedDeathDate=$date; + break; + } + } + if (is_null($this->_getEstimatedDeathDate)) { + $tmp=$this->getEstimatedBirthDate(); + if ($tmp->MinJD()) { + global $MAX_ALIVE_AGE; + $tmp2=$tmp->AddYears($MAX_ALIVE_AGE, 'bef'); + if ($tmp2->MaxJD()<WT_SERVER_JD) { + $this->_getEstimatedDeathDate=$tmp2; + } else { + $this->_getEstimatedDeathDate=new GedcomDate(''); // always return a date object + } + } else { + $this->_getEstimatedDeathDate=new GedcomDate(''); // always return a date object + } + } + } + return $this->_getEstimatedDeathDate; + } + + /** + * get the sex + * @return string return M, F, or U + */ + function getSex() { + if (is_null($this->sex)) { + if (preg_match('/\n1 SEX ([MF])/', $this->gedrec, $match)) { + $this->sex=$match[1]; + } else { + $this->sex='U'; + } + } + return $this->sex; + } + + /** + * get the person's sex image + * @return string <img ... /> + */ + function getSexImage($size='small', $style='', $title='') { + return self::sexImage($this->getSex(), $size, $style, $title); + } + + static function sexImage($sex, $size='small', $style='', $title='') { + global $WT_IMAGES; + + if ($size=='small') { + $image='sex_'.strtolower($sex).'_9x9'; + } else { + $image='sex_'.strtolower($sex).'_15x15'; + } + + switch ($sex) { + case 'M': + if (isset($WT_IMAGES[$image])) { + return "<img src=\"{$WT_IMAGES[$image]}\" class=\"gender_image\" style=\"{$style}\" alt=\"{$title}\" title=\"{$title}\" />"; + } else { + return '<span style="size:'.$size.'">'.WT_UTF8_MALE.'</span>'; + } + case 'F': + if (isset($WT_IMAGES[$image])) { + return "<img src=\"{$WT_IMAGES[$image]}\" class=\"gender_image\" style=\"{$style}\" alt=\"{$title}\" title=\"{$title}\" />"; + } else { + return '<span style="size:'.$size.'">'.WT_UTF8_FEMALE.'</span>'; + } + default: + if (isset($WT_IMAGES[$image])) { + return "<img src=\"{$WT_IMAGES[$image]}\" class=\"gender_image\" style=\"{$style}\" alt=\"{$title}\" title=\"{$title}\" />"; + } else { + return '<span style="size:'.$size.'">?</span>'; + } + } + } + + 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) { + global $TEXT_DIRECTION; + $label = ''; + $gap = 0; + if (is_object($elderdate) && $elderdate->isOK()) { + $p2 = $this->getBirthDate(); + if ($p2->isOK()) { + $gap = $p2->MinJD()-$elderdate->MinJD(); // days + $label .= "<div class=\"elderdate age $TEXT_DIRECTION\">"; + // warning if negative gap : wrong order + if ($gap<0 && $counter>0) $label .= "<img alt=\"\" src=\"images/warning.gif\" /> "; + // warning if gap<6 months + if ($gap>1 && $gap<180 && $counter>0) $label .= "<img alt=\"\" src=\"images/warning.gif\" /> "; + // children with same date means twin + /**if ($gap==0 && $counter>1) { + if ($this->getSex()=='M') $label .= i18n::translate('Twin brother'); + else if ($this->getSex()=='F') $label .= i18n::translate('Twin sister'); + else $label .= i18n::translate('Twin'); + }**/ + // gap in years or months + $gap = round($gap*12/365.25); // months + if (($gap==12)||($gap==-12)) { + $label .= i18n::plural('%d year', '%d years', round($gap/12), round($gap/12)); + } elseif ($gap>23 or $gap<-23) { + $label .= i18n::plural('%d year', '%d years', round($gap/12), round($gap/12)); + } elseif ($gap!=0) { + $label .= i18n::plural('%d month', '%d months', $gap, $gap); + } + $label .= '</div>'; + } + } + // I18N: This is an abbreviation for a number. i.e. #7 means number 7 + if ($counter) $label .= '<div class="'.$TEXT_DIRECTION.'">'.i18n::translate('#%d', $counter).'</div>'; + $label .= $this->label; + if ($gap!=0 && $counter<1) $label .= '<br /> '; + return $label; + } + /** + * get family with spouse ids + * @return array array of the FAMS ids + */ + function getSpouseFamilyIds() { + if (is_null($this->fams)) { + preg_match_all('/\n1 FAMS @('.WT_REGEX_XREF.')@/', $this->gedrec, $match); + $this->fams=$match[1]; + } + return $this->fams; + } + /** + * get the families with spouses + * @return array array of Family objects + */ + function getSpouseFamilies() { + global $SHOW_LIVING_NAMES; + if (is_null($this->spouseFamilies)) { + $this->spouseFamilies=array(); + foreach ($this->getSpouseFamilyIds() as $famid) { + $family=WT_Family::getInstance($famid); + if (is_null($family)) { + echo '<span class="warning">', i18n::translate('Unable to find family with ID'), ' ', $famid, '</span>'; + } else { + // only include family if it is displayable by current user + if ($SHOW_LIVING_NAMES || $family->canDisplayDetails()) { + $this->spouseFamilies[$famid] = $family; + } + } + } + } + return $this->spouseFamilies; + } + + /** + * 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() { + $nchi1=(int)get_gedcom_value('NCHI', 1, $this->gedrec); + $nchi2=(int)get_gedcom_value('NCHI', 2, $this->gedrec); + $nchi3=count(fetch_child_ids($this->xref, $this->ged_id)); + return max($nchi1, $nchi2, $nchi3); + } + /** + * get family with child ids + * @return array array of the FAMC ids + */ + function getChildFamilyIds() { + if (is_null($this->famc)) { + preg_match_all('/\n1 FAMC @('.WT_REGEX_XREF.')@/', $this->gedrec, $match); + $this->famc=$match[1]; + } + return $this->famc; + } + /** + * get an array of families with parents + * @return array array of Family objects indexed by family id + */ + function getChildFamilies() { + global $SHOW_LIVING_NAMES; + + if (is_null($this->childFamilies)) { + $this->childFamilies=array(); + foreach ($this->getChildFamilyIds() as $famid) { + $family=WT_Family::getInstance($famid); + if (is_null($family)) { + echo '<span class="warning">', i18n::translate('Unable to find family with ID'), ' ', $famid, '</span>'; + } else { + // only include family if it is displayable by current user + if ($SHOW_LIVING_NAMES || $family->canDisplayDetails()) { + $this->childFamilies[$famid]=$family; + } + } + } + } + return $this->childFamilies; + } + /** + * 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->gedrec)) { + 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->gedrec)) { + 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->gedrec)) { + return $fam; + } + } + // d) any record + return reset($families); + } + } + /** + * get family with child pedigree + * @return string FAMC:PEDI value [ adopted | birth | foster | sealing ] + */ + function getChildFamilyPedigree($famid) { + $subrec = get_sub_record(1, '1 FAMC @'.$famid.'@', $this->gedrec); + $pedi = get_gedcom_value('PEDI', 2, $subrec, '', false); + // birth=default => return an empty string + return ($pedi=='birth') ? '' : $pedi; + } + /** + * get the step families from the parents + * @return array array of Family objects + */ + function getStepFamilies() { + $families = array(); + $fams = $this->getChildFamilies(); + foreach ($fams as $family) { + if (!is_null($family)) { + $father = $family->getHusband(); + if (!is_null($father)) { + $pfams = $father->getSpouseFamilies(); + foreach ($pfams as $key1=>$fam) { + if (!is_null($fam) && !isset($fams[$key1]) && ($fam->getNumberOfChildren() > 0)) { + $families[$key1] = $fam; + } + } + } + $mother = $family->getWife(); + if (!is_null($mother)) { + $pfams = $mother->getSpouseFamilies(); + foreach ($pfams as $key1=>$fam) { + if (!is_null($fam) && !isset($fams[$key1]) && ($fam->getNumberOfChildren() > 0)) { + $families[$key1] = $fam; + } + } + } + } + } + return $families; + } + /** + * get global facts + * @return array + */ + function getGlobalFacts() { + $this->parseFacts(); + return $this->globalfacts; + } + /** + * get indi facts + * @return array + */ + function getIndiFacts($nfacts=NULL) { + $this->parseFacts($nfacts); + return $this->indifacts; + } + + /** + * get other facts + * @return array + */ + function getOtherFacts() { + $this->parseFacts(); + return $this->otherfacts; + } + /** + * get the correct label for a family + * @param Family $family the family to get the label for + * @return string + */ + function getChildFamilyLabel($family) { + if (!is_null($family)) { + $famlink = get_sub_record(1, '1 FAMC @'.$family->getXref().'@', $this->gedrec); + if (preg_match('/2 PEDI (.*)/', $famlink, $fmatch)) { + switch ($fmatch[1]) { + case 'adopted': return i18n::translate('Family with adoptive parents'); + case 'foster': return i18n::translate('Family with foster parents'); + case 'sealing': return i18n::translate('Family with sealing parents'); + } + } + } + return i18n::translate('Family with parents'); + } + /** + * get the correct label for a step family + * @param Family $family the family to get the label for + * @return string + */ + function getStepFamilyLabel($family) { + $label = 'Unknown Family'; + if (is_null($family)) return $label; + $childfams = $this->getChildFamilies(); + $mother = $family->getWife(); + $father = $family->getHusband(); + foreach ($childfams as $fam) { + if (!$fam->equals($family)) { + $wife = $fam->getWife(); + $husb = $fam->getHusband(); + if ((is_null($husb) || !$husb->equals($father)) && (is_null($wife)||$wife->equals($mother))) { + if ($mother->getSex()=='M') $label = i18n::translate('Father\'s Family with '); + else $label = i18n::translate('Mother\'s Family with '); + if (!is_null($father)) $label .= $father->getFullName(); + else $label .= i18n::translate('unknown person'); + } + else if ((is_null($wife) || !$wife->equals($mother)) && (is_null($husb)||$husb->equals($father))) { + if ($father->getSex()=='F') $label = i18n::translate('Mother\'s Family with '); + else $label = i18n::translate('Father\'s Family with '); + if (!is_null($mother)) $label .= $mother->getFullName(); + else $label .= i18n::translate('unknown person'); + } + if ($label!='Unknown Family') return $label; + } + } + return $label; + } + /** + * get the correct label for a family + * @param Family $family the family to get the label for + * @return string + */ + function getSpouseFamilyLabel($family) { + if (is_null($family)) { + $spouse=i18n::translate('unknown person'); + } else { + $husb = $family->getHusband(); + $wife = $family->getWife(); + if ($this->equals($husb) && !is_null($wife)) { + $spouse = $wife->getFullName(); + } elseif ($this->equals($wife) && !is_null($husb)) { + $spouse = $husb->getFullName(); + } else { + $spouse = i18n::translate('unknown person'); + } + } + // I18N: %s is the spouse name + return i18n::translate('Family with %s', $spouse); + } + /** + * get updated Person + * If there is an updated individual record in the gedcom file + * return a new person object for it + * @return Person + */ + function getUpdatedPerson() { + if ($this->getChanged()) { + return null; + } + if (WT_USER_CAN_EDIT && $this->canDisplayDetails()) { + $newrec = find_updated_record($this->xref, $this->ged_id); + if (!is_null($newrec)) { + $new = new WT_Person($newrec); + $new->setChanged(true); + return $new; + } + } + return null; + } + /** + * Parse the facts from the individual record + */ + function parseFacts($nfacts=NULL) { + global $nonfacts; + parent::parseFacts(); + if ($nfacts!=NULL) $nonfacts = $nfacts; + //-- only run this function once + if ($this->facts_parsed) return; + //-- don't run this function if privacy does not allow viewing of details + if (!$this->canDisplayDetails()) return; + $sexfound = false; + //-- run the parseFacts() method from the parent class + $this->facts_parsed = true; + + //-- sort the fact info into different categories for people + foreach ($this->facts as $f=>$event) { + $fact = $event->getTag(); + // -- handle special name fact case + if ($fact=='NAME') { + $this->globalfacts[] = $event; + } + // -- handle special source fact case + else if ($fact=='SOUR') { + $this->otherfacts[] = $event; + } + // -- handle special note fact case + else if ($fact=='NOTE') { + $this->otherfacts[] = $event; + } + // -- handle special sex case + else if ($fact=='SEX') { + $this->globalfacts[] = $event; + $sexfound = true; + } + else if ($fact=='OBJE') {} + else if (!isset($nonfacts) || !in_array($fact, $nonfacts)) { + $this->indifacts[] = $event; + } + } + //-- add a new sex fact if one was not found + if (!$sexfound) { + $this->globalfacts[] = new Event('1 SEX U', 'new'); + } + } + /** + * add facts from the family record + * @param boolean $otherfacts whether or not to add other related facts such as parents facts, associated people facts, and historical facts + */ + function add_family_facts($otherfacts = true) { + global $GEDCOM, $nonfacts, $nonfamfacts; + + if (!isset($nonfacts)) $nonfacts = array(); + if (!isset($nonfamfacts)) $nonfamfacts = array(); + + if (!$this->canDisplayDetails()) return; + $this->parseFacts(); + //-- Get the facts from the family with spouse (FAMS) + foreach ($this->getSpouseFamilies() as $family) { + if (is_null($family)) continue; + $updfamily = $family->getUpdatedFamily(); //-- updated family ? + $spouse = $family->getSpouse($this); + + if ($updfamily) { + $family->diffMerge($updfamily); + } + $facts = $family->getFacts(); + $hasdiv = false; + /* @var $event Event */ + foreach ($facts as $event) { + $fact = $event->getTag(); + if ($fact=='DIV') $hasdiv = true; + // -- handle special source fact case + if (($fact!='SOUR') && ($fact!='NOTE') && ($fact!='CHAN') && ($fact!='_UID') && ($fact!='RIN')) { + if ((!in_array($fact, $nonfacts))&&(!in_array($fact, $nonfamfacts))) { + $factrec = $event->getGedcomRecord(); + if (!is_null($spouse)) $factrec.="\n2 _WTS @".$spouse->getXref().'@'; + $factrec.="\n2 _WTFS @".$family->getXref()."@\n"; + $event->gedcomRecord = $factrec; + if ($fact!='OBJE') $this->indifacts[] = $event; + else $this->otherfacts[]=$event; + } + } + } + if ($otherfacts) { + if (!$hasdiv && !is_null($spouse)) $this->add_spouse_facts($spouse, $family->getGedcomRecord()); + $this->add_children_facts($family, '_CHIL', ''); + } + } + if ($otherfacts) { + $this->add_parents_facts($this, 1); + $this->add_historical_facts(); + $this->add_asso_facts(); + } + } + /** + * add parents events to individual facts array + * + * @param Person $person Person + * @param int $sosa 1=parents, 2=father's parents, 3=mother's parents + * @return records added to indifacts array + */ + function add_parents_facts(&$person, $sosa) { + global $SHOW_RELATIVES_EVENTS; + + // Deal with recursion. + switch ($sosa) { + case 1: + // Add siblings and half-siblings + foreach ($person->getChildFamilies() as $family) { + $husb_wife = array(); + if ($family->getHusband()) $husb_wife[]=$family->getHusband(); + if ($family->getWife()) $husb_wife[]=$family->getWife(); + foreach ($husb_wife as $spouse) { + foreach ($spouse->getSpouseFamilies() as $sfamily) { + if ($family->getHusbId()==$sfamily->getHusbId() && $family->getWifeId()==$sfamily->getWifeId()) { + // Both parents the same - siblings + $this->add_children_facts($sfamily, '_SIBL', ''); + } else { + // One parent the same - half-siblings + $this->add_children_facts($sfamily, '_HSIB', 'par'); + } + } + } + } + // Add grandparents + foreach ($person->getChildFamilies() as $family) { + if ($family->getHusband()) $this->add_parents_facts($family->getHusband(), 2); + if ($family->getWife()) $this->add_parents_facts($family->getWife (), 3); + } + break; + } + + switch ($sosa) { + case 1: $rela=''; break; + case 2: $rela='fat'; break; + case 3: $rela='mot'; break; + } + + // Only include events between birth and death + $bDate=$this->getEstimatedBirthDate(); + $dDate=$this->getEstimatedDeathDate(); + + foreach ($person->getChildFamilies() as $famid=>$family) { + foreach (array($family->getWife(), $family->getHusband()) as $parent) { + if ($parent) { + if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT'.($sosa==1 ? '_PARE' : '_GPAR'))) { + foreach ($parent->getAllFactsByType(explode('|', WT_EVENTS_DEAT)) as $sEvent) { + $srec = $sEvent->getGedcomRecord(); + if (GedcomDate::Compare($bDate, $sEvent->getDate())<0 && GedcomDate::Compare($sEvent->getDate(), $dDate)<=0) { + switch ($sosa) { + case 1: + $factrec='1 _'.$sEvent->getTag().'_PARE'; + break; + case 2: + $factrec='1 _'.$sEvent->getTag().'_GPA1'; + break; + case 3: + $factrec='1 _'.$sEvent->getTag().'_GPA2'; + break; + } + $factrec.="\n".get_sub_record(2, '2 DATE', $srec)."\n".get_sub_record(2, '2 PLAC', $srec); + if (!$sEvent->canShow()) { + $factrec .= "\n2 RESN privacy"; + } + if ($parent->getSex()=='F') { + $factrec.="\n2 ASSO @".$parent->getXref()."@\n3 RELA ".$rela."mot"; + } else { + $factrec.="\n2 ASSO @".$parent->getXref()."@\n3 RELA ".$rela."fat"; + } + $event=new Event($factrec, 0); + $event->setParentObject($this); + $this->indifacts[] = $event; + } + } + } + } + } + if ($sosa==1) { + // add father/mother marriages + foreach (array($family->getHusband(), $family->getWife()) as $parent) { + if (is_null($parent)) { + continue; + } + foreach ($parent->getSpouseFamilies() as $sfamid=>$sfamily) { + if ($sfamid==$famid) { + if ($parent->getSex()=='F') { + // show current family marriage only once + continue; + } + $fact='_MARR_FAMC'; // marriage of parents (to each other) + $rela1='fat'; + $rela2='mot'; + } else { + if ($parent->getSex()=='M') { + $fact='_MARR_PARE'; // marriage of a parent (to another spouse) + $rela1='fat'; + $rela2='fatwif'; + } else { + $fact='_MARR_PARE'; + $rela1='mot'; + $rela2='mothus'; + } + } + if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) { + $sEvent = $sfamily->getMarriage(); + $srec = $sEvent->getGedcomRecord(); + if (GedcomDate::Compare($bDate, $sEvent->getDate())<0 && GedcomDate::Compare($sEvent->getDate(), $dDate)<=0) { + $factrec = '1 '.$fact; + $factrec.="\n".get_sub_record(2, '2 DATE', $srec)."\n".get_sub_record(2, '2 PLAC', $srec); + $factrec .= "\n2 ASSO @".$parent->getXref().'@'; + $factrec .= "\n3 RELA ".$rela1; + $factrec .= "\n2 ASSO @".$sfamily->getSpouseId($parent->getXref()).'@'; + $factrec .= "\n3 RELA ".$rela2; + if (!$sEvent->canShow()) { + $factrec .= "\n2 RESN privacy"; + } + $event = new Event($factrec, 0); + $event->setParentObject($this); + $this->indifacts[] = $event; + } + } + } + } + } + } + } + + /** + * add children events to individual facts array + * + * @param string $family Family object + * @param string $option Family level indicator + * @param string $relation Relationship path indicator + * @return records added to indifacts array + */ + function add_children_facts(&$family, $option, $relation) { + global $SHOW_RELATIVES_EVENTS; + + // Deal with recursion. + switch ($option) { + case '_CHIL': + // Add grandchildren + foreach ($family->getChildren() as $child) { + foreach ($child->getSpouseFamilies() as $cfamily) { + switch ($child->getSex()) { + case 'M': + $this->add_children_facts($cfamily, '_GCHI', 'son'); + break; + case 'F': + $this->add_children_facts($cfamily, '_GCHI', 'dau'); + break; + case 'U': + $this->add_children_facts($cfamily, '_GCHI', 'chi'); + break; + } + } + } + break; + } + + // For each child in the family + foreach ($family->getChildren() as $child) { + if ($child->getXref()==$this->getXref()) { + // We are not our own sibling! + continue; + } + switch ($child->getSex()) { + case 'M': + $rela=$option=='_SIBL' ? 'bro' : $relation.'son'; + break; + case 'F': + $rela=$option=='_SIBL' ? 'sis' : $relation.'dau'; + break; + case 'U': + $rela=$option=='_SIBL' ? 'sib' : $relation.'chi'; + break; + } + // add child's birth + if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT'.str_replace('_HSIB', '_SIBL', $option))!==false) { + foreach ($child->getAllFactsByType(explode('|', WT_EVENTS_BIRT)) as $sEvent) { + $srec = $sEvent->getGedcomRecord(); + $sgdate=$sEvent->getDate(); + // Always show _BIRT_CHIL, even if the dates are not known + if ($option=='_CHIL' || $sgdate->isOK() && GedcomDate::Compare($this->getEstimatedBirthDate(), $sgdate)<=0 && GedcomDate::Compare($sgdate, $this->getEstimatedDeathDate())<=0) { + $factrec='1 _'.$sEvent->getTag(); + if ($option=='_GCHI' && $relation=='son') { + $factrec.='_GCH1'; + } elseif ($option=='_GCHI' && $relation=='dau') { + $factrec.='_GCH2'; + } else { + $factrec.=$option; + } + $factrec.="\n".get_sub_record(2, '2 DATE', $srec)."\n".get_sub_record(2, '2 PLAC', $srec); + if (!$sEvent->canShow()) { + $factrec.='\n2 RESN privacy'; + } + $factrec.="\n2 ASSO @".$child->getXref()."@\n3 RELA ".$rela; + $event = new Event($factrec, 0); + $event->setParentObject($this); + if (!in_array($event, $this->indifacts)) { + $this->indifacts[]=$event; + } + } + } + } + // add child's death + if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT'.str_replace('_HSIB', '_SIBL', $option))!==false) { + foreach ($child->getAllFactsByType(explode('|', WT_EVENTS_DEAT)) as $sEvent) { + $sgdate=$sEvent->getDate(); + $srec = $sEvent->getGedcomRecord(); + if ($sgdate->isOK() && GedcomDate::Compare($this->getEstimatedBirthDate(), $sgdate)<=0 && GedcomDate::Compare($sgdate, $this->getEstimatedDeathDate())<=0) { + $factrec='1 _'.$sEvent->getTag(); + if ($option=='_GCHI' && $relation=='son') { + $factrec.='_GCH1'; + } elseif ($option=='_GCHI' && $relation=='dau') { + $factrec.='_GCH2'; + } else { + $factrec.=$option; + } + $factrec.="\n".get_sub_record(2, '2 DATE', $srec)."\n".get_sub_record(2, '2 PLAC', $srec); + if (!$sEvent->canShow()) { + $factrec.='\n2 RESN privacy'; + } + $factrec.="\n2 ASSO @".$child->getXref()."@\n3 RELA ".$rela; + $event = new Event($factrec, 0); + $event->setParentObject($this); + if (!in_array($event, $this->indifacts)) { + $this->indifacts[]=$event; + } + } + } + } + // add child's marriage + if (strstr($SHOW_RELATIVES_EVENTS, '_MARR'.str_replace('_HSIB', '_SIBL', $option))) { + foreach ($child->getSpouseFamilies() as $sfamily) { + $sEvent = $sfamily->getMarriage(); + $sgdate=$sEvent->getDate(); + $srec = $sEvent->getGedcomRecord(); + if ($sgdate->isOK() && GedcomDate::Compare($this->getEstimatedBirthDate(), $sgdate)<=0 && GedcomDate::Compare($sgdate, $this->getEstimatedDeathDate())<=0) { + $factrec='1 _'.$sEvent->getTag(); + if ($option=='_GCHI' && $relation=='son') { + $factrec.='_GCH1'; + } elseif ($option=='_GCHI' && $relation=='dau') { + $factrec.='_GCH2'; + } else { + $factrec.=$option; + } + $factrec.="\n".get_sub_record(2, '2 DATE', $srec)."\n".get_sub_record(2, '2 PLAC', $srec); + if (!$sEvent->canShow()) { + $factrec.='\n2 RESN privacy'; + } + switch ($child->getSex()) { + case 'M': $rela2=$rela.'wif'; break; + case 'F': $rela2=$rela.'hus'; break; + case 'U': $rela2=$rela.'spo'; break; + } + $factrec.="\n2 ASSO @".$child->getXref()."@\n3 RELA ".$rela; + $factrec.="\n2 ASSO @".$sfamily->getSpouseId($child->getXref())."@\n3 RELA ".$rela2; + $event = new Event($factrec, 0); + $event->setParentObject($this); + if (!in_array($event, $this->indifacts)) { + $this->indifacts[]=$event; + } + } + } + } + } + } + /** + * add spouse events to individual facts array + * + * bdate = indi birth date record + * ddate = indi death date record + * + * @param string $spouse Person object + * @param string $famrec family Gedcom record + * @return records added to indifacts array + */ + function add_spouse_facts(&$spouse, $famrec='') { + global $SHOW_RELATIVES_EVENTS; + + // do not show if divorced + if (preg_match('/\n1 (?:'.WT_EVENTS_DIV.')\b/', $famrec)) { + return; + } + // Only include events between birth and death + $bDate=$this->getEstimatedBirthDate(); + $dDate=$this->getEstimatedDeathDate(); + + // add spouse death + if ($spouse && strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) { + foreach ($spouse->getAllFactsByType(explode('|', WT_EVENTS_DEAT)) as $sEvent) { + $sdate=$sEvent->getDate(); + $srec = $sEvent->getGedcomRecord(); + if ($sdate->isOK() && GedcomDate::Compare($this->getEstimatedBirthDate(), $sdate)<=0 && GedcomDate::Compare($sdate, $this->getEstimatedDeathDate())<=0) { + $srec=preg_replace('/^1 .*/', '1 _'.$sEvent->getTag().'_SPOU ', $srec); + $srec.="\n".get_sub_record(2, '2 ASSO @'.$this->xref.'@', $srec); + switch ($spouse->getSex()) { + case 'M': $srec.="\n2 ASSO @".$spouse->getXref()."@\n3 RELA hus"; break; + case 'F': $srec.="\n2 ASSO @".$spouse->getXref()."@\n3 RELA wif"; break; + case 'U': $srec.="\n2 ASSO @".$spouse->getXref()."@\n3 RELA spo"; break; + } + $event = new Event($srec, 0); + $event->setParentObject($this); + $this->indifacts[] = $event; + } + } + } + } + + /** + * add historical events to individual facts array + * + * @return records added to indifacts array + * + * Historical facts are imported from optional language file : histo.xx.php + * where xx is language code + * This file should contain records similar to : + * + * $histo[]="1 EVEN\n2 TYPE History\n2 DATE 11 NOV 1918\n2 NOTE WW1 Armistice"; + * $histo[]="1 EVEN\n2 TYPE History\n2 DATE 8 MAY 1945\n2 NOTE WW2 Armistice"; + * etc... + * + */ + function add_historical_facts() { + global $SHOW_RELATIVES_EVENTS; + if (!$SHOW_RELATIVES_EVENTS) return; + + // Only include events between birth and death + $bDate=$this->getEstimatedBirthDate(); + $dDate=$this->getEstimatedDeathDate(); + if (!$bDate->isOK()) return; + + if (file_exists(get_site_setting('INDEX_DIRECTORY').'histo.'.WT_LOCALE.'.php')) { + require get_site_setting('INDEX_DIRECTORY').'histo.'.WT_LOCALE.'.php'; + foreach ($histo as $indexval=>$hrec) { + $sdate=new GedcomDate(get_gedcom_value('DATE', 2, $hrec, '', false)); + if ($sdate->isOK() && GedcomDate::Compare($this->getEstimatedBirthDate(), $sdate)<=0 && GedcomDate::Compare($sdate, $this->getEstimatedDeathDate())<=0) { + $event = new Event($hrec); + $event->setParentObject($this); + $this->indifacts[] = $event; + } + } + } + } + /** + * add events where pid is an ASSOciate + * + * @return records added to indifacts array + * + */ + function add_asso_facts() { + $associates=array_merge( + fetch_linked_indi($this->getXref(), 'ASSO', $this->ged_id), + fetch_linked_fam ($this->getXref(), 'ASSO', $this->ged_id) + ); + foreach ($associates as $associate) { + foreach ($associate->getFacts() as $event) { + $srec = $event->getGedcomRecord(); + $arec = get_sub_record(2, '2 ASSO @'.$this->getXref().'@', $srec); + if ($arec) { + $fact = $event->getTag(); + $label = $event->getLabel(); + $sdate = get_sub_record(2, '2 DATE', $srec); + // relationship ? + $rrec = get_sub_record(3, '3 RELA', $arec); + $rela = trim(substr($rrec, 7)); + if (empty($rela)) { + $rela = 'ASSO'; + } + // add an event record + $factrec = "1 EVEN\n2 TYPE ".$label.'<br/>[ <span class="details_label">'; + $factrec .= i18n::translate($rela); + $factrec.='</span> ]'.$sdate."\n".get_sub_record(2, '2 PLAC', $srec); + if (!$event->canShow()) $factrec .= "\n2 RESN privacy"; + if ($associate->getType()=='FAM') { + $famrec = find_family_record($associate->getXref(), $this->ged_id); + if ($famrec) { + $parents = find_parents_in_record($famrec); + if ($parents['HUSB']) $factrec .= "\n2 ASSO @".$parents['HUSB'].'@'; + if ($parents['WIFE']) $factrec .= "\n2 ASSO @".$parents['WIFE'].'@'; + } + } elseif ($fact=='BIRT') { + $sex = $associate->getSex(); + if ($sex == 'M') { + $rela_b='twin_brother'; + } elseif ($sex == 'F') { + $rela_b='twin_sister'; + } else { + $rela_b='twin'; + } + $factrec .= "\n2 ASSO @".$associate->getXref()."@\n3 RELA ".$rela_b; + } elseif ($fact=='CHR') { + $sex = $associate->getSex(); + if ($sex == 'M') { + $rela_chr='godson'; + } elseif ($sex == 'F') { + $rela_chr='goddaughter'; + } else { + $rela_chr='godchild'; + } + $factrec .= "\n2 ASSO @".$associate->getXref()."@\n3 RELA ".$rela_chr; + } else { + $factrec .= "\n2 ASSO @".$associate->getXref()."@\n3 RELA ".$fact; + } + //$factrec .= "\n3 NOTE ".$rela; + $factrec .= "\n2 ASSO @".$this->getXref()."@\n3 RELA *".$rela; + // check if this fact already exists in the list + $found = false; + if ($sdate) foreach ($this->indifacts as $k=>$v) { + if (strpos($v->getGedcomRecord(), $sdate) + && strpos($v->getGedcomRecord(), '2 ASSO @'.$this->getXref().'@')) { + $found = true; + break; + } + } + if (!$found) { + $event = new Event($factrec); + $event->setParentObject($this); + $this->indifacts[] = $event; + } + } + } + } + } + + /** + * Merge the facts from another Person object into this object + * for generating a diff view + * @param Person $diff the person to compare facts with + */ + function diffMerge(&$diff) { + if (is_null($diff)) return; + $this->parseFacts(); + $diff->parseFacts(); + //-- loop through new facts and add them to the list if they are any changes + //-- compare new and old facts of the Personal Fact and Details tab 1 + for ($i=0; $i<count($this->indifacts); $i++) { + $found=false; + $oldfactrec = $this->indifacts[$i]->getGedcomRecord(); + foreach ($diff->indifacts as $newfact) { + $newfactrec = $newfact->getGedcomRecord(); + //-- remove all whitespace for comparison + $tnf = preg_replace('/\s+/', ' ', $newfactrec); + $tif = preg_replace('/\s+/', ' ', $oldfactrec); + if ($tnf==$tif) { + $this->indifacts[$i] = $newfact; //-- make sure the correct linenumber is used + $found=true; + break; + } + } + //-- fact was deleted? + if (!$found) { + $this->indifacts[$i]->gedcomRecord.="\nWT_OLD\n"; + } + } + //-- check for any new facts being added + foreach ($diff->indifacts as $newfact) { + $found=false; + foreach ($this->indifacts as $fact) { + $tif = preg_replace('/\s+/', ' ', $fact->getGedcomRecord()); + $tnf = preg_replace('/\s+/', ' ', $newfact->getGedcomRecord()); + if ($tif==$tnf) { + $found=true; + break; + } + } + if (!$found) { + $newfact->gedcomRecord.="\nWT_NEW\n"; + $this->indifacts[]=$newfact; + } + } + //-- compare new and old facts of the Notes Sources and Media tab 2 + for ($i=0; $i<count($this->otherfacts); $i++) { + $found=false; + foreach ($diff->otherfacts as $newfact) { + if (trim($newfact->getGedcomRecord())==trim($this->otherfacts[$i]->getGedcomRecord())) { + $this->otherfacts[$i] = $newfact; //-- make sure the correct linenumber is used + $found=true; + break; + } + } + if (!$found) { + $this->otherfacts[$i]->gedcomRecord.="\nWT_OLD\n"; + } + } + foreach ($diff->otherfacts as $indexval => $newfact) { + $found=false; + foreach ($this->otherfacts as $indexval => $fact) { + if (trim($fact->getGedcomRecord())==trim($newfact->getGedcomRecord())) { + $found=true; + break; + } + } + if (!$found) { + $newfact->gedcomRecord.="\nWT_NEW\n"; + $this->otherfacts[]=$newfact; + } + } + + //-- compare new and old facts of the Global facts + for ($i=0; $i<count($this->globalfacts); $i++) { + $found=false; + foreach ($diff->globalfacts as $indexval => $newfact) { + if (trim($newfact->getGedcomRecord())==trim($this->globalfacts[$i]->getGedcomRecord())) { + $this->globalfacts[$i] = $newfact; //-- make sure the correct linenumber is used + $found=true; + break; + } + } + if (!$found) { + $this->globalfacts[$i]->gedcomRecord.="\nWT_OLD\n"; + } + } + foreach ($diff->globalfacts as $indexval => $newfact) { + $found=false; + foreach ($this->globalfacts as $indexval => $fact) { + if (trim($fact->getGedcomRecord())==trim($newfact->getGedcomRecord())) { + $found=true; + break; + } + } + if (!$found) { + $newfact->gedcomRecord.="\nWT_NEW\n"; + $this->globalfacts[]=$newfact; + } + } + $newfamids = $diff->getChildFamilyIds(); + if (is_null($this->famc)) $this->getChildFamilyIds(); + foreach ($newfamids as $id) { + if (!in_array($id, $this->famc)) $this->famc[]=$id; + } + + $newfamids = $diff->getSpouseFamilyIds(); + if (is_null($this->fams)) $this->getSpouseFamilyIds(); + foreach ($newfamids as $id) { + if (!in_array($id, $this->fams)) $this->fams[]=$id; + } + } + + /** + * 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 = '<div'; + if ($classname) $txt .= " class=\"$classname\""; + if ($display) $txt .= " style=\"display:$display\""; + $txt .= '>'; + $husb = $fam->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::translate('Father').': '.PrintReady($husb->getListName()).'<br />'; + $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::translate('Mother').': '.PrintReady($wife->getListName()); + $wife->setPrimaryName($primary); + } + $txt .= '</div>'; + return $txt; + } + + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('individual.php?pid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('individual.php?pid=', '&'); + } + + // If this object has no name, what do we call it? + function getFallBackName() { + return '@P.N. /@N.N./'; + } + + // Convert a name record into 'full', 'sort' and 'list' versions. + // Use the NAME field to generate the 'full' and 'list' versions, 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'' + // list=>'de Gliderow, Robert '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, $gedrec) { + global $UNDERLINE_NAME_QUOTES, $UNKNOWN_NN, $UNKNOWN_PN; + + // Look for GIVN/SURN at level n+1 + $sublevel=1+(int)$gedrec[0]; + + // Fix bad slashes. e.g. 'John/Smith' => 'John/Smith/' + if (substr_count($full, '/')%2==1) { + $full.='/'; + } + + // Need the GIVN and SURN to generate the sortable name. + $givn=preg_match("/\n{$sublevel} GIVN (.+)/", $gedrec, $match) ? $match[1] : ''; + $surn=preg_match("/\n{$sublevel} SURN (.+)/", $gedrec, $match) ? $match[1] : ''; + $spfx=preg_match("/\n{$sublevel} SPFX (.+)/", $gedrec, $match) ? $match[1] : ''; + if ($givn || $surn) { + // An empty surname won't have a SURN field + if (strpos($full, '//')) { + $surn='@N.N.'; + } + // GIVN and SURN can be comma-separated lists. + $surns=preg_split('/ *, */', $surn); + $givn=str_replace(array(', ', ','), ' ', $givn); + // SPFX+SURN for lists + $surn=($spfx?$spfx.' ':'').$surn; + } else { + $name=$full; + // We do not have a structured name - extract the GIVN and SURN(s) ourselves + // Strip the NPFX + if (preg_match('/^(?:(?:(?:ADM|AMB|BRIG|CAN|CAPT|CHAN|CHAPLN|CMDR|COL|CPL|CPT|DR|GEN|GOV|HON|LADY|LORD|LT|MR|MRS|MS|MSGR|PFC|PRES|PROF|PVT|RABBI|REP|REV|SEN|SGT|SIR|SR|SRA|SRTA|VEN)\.? +)+)(.+)/i', $name, $match)) { + $name=$match[1]; + } + // Strip the NSFX + if (preg_match('/(.+)(?:(?: +(?:ESQ|ESQUIRE|JR|JUNIOR|SR|SENIOR|[IVX]+)\.?)+)$/i', $name, $match)) { + $name=$match[1]; + } + // Extract GIVN/SURN. + if (strpos($full, '/')===false) { + $givn=trim($name); + $spfx=''; + $surns=array(''); + } else { + // Extract SURN. Split at '/'. Odd numbered parts are SURNs. + $spfx=''; + $surns=array(); + foreach (preg_split(': */ *:', $name) as $key=>$value) { + if ($key%2==1) { + if ($value) { + // Strip SPFX + if (preg_match('/^((?:(?:A|AAN|AB|AF|AL|AP|AS|AUF|AV|BAT|BIJ|BIN|BINT|DA|DE|DEL|DELLA|DEM|DEN|DER|DI|DU|EL|FITZ|HET|IBN|LA|LAS|LE|LES|LOS|ONDER|OP|OVER|\'S|ST|\'T|TE|TEN|TER|TILL|TOT|UIT|UIJT|VAN|VANDEN|VON|VOOR|VOR) )+(?:[DL]\')?)(.+)$/i', $value, $match)) { + $spfx=trim($match[1]); + $value=$match[2]; + } + $surns[]=$value ? $value : '@N.N.'; + } else { + $surns[]='@N.N.'; + } + } + } + // SPFX+SURN for lists + $surn=($spfx ? $spfx.' ' : '').implode(' ', $surns); + // Extract the GIVN. Before first '/' and after last. + $pos1=strpos($name, '/'); + if ($pos1===false) { + $givn=$name; + } else { + $pos2=strrpos($name, '/'); + $givn=trim(substr($name, 0, $pos1).' '.substr($name, $pos2+1)); + } + } + } + + // Tidy up whitespace + $full=preg_replace('/ +/', ' ', trim($full)); + + // Add placeholder for unknown surname + if (preg_match(':/ */:', $full)) { + $full=preg_replace(':/ */:', '/@N.N./', $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); + } + + // Some systems don't include the NPFX in the NAME record. + $npfx=preg_match('/^'.$sublevel.' NPFX (.+)/m', $gedrec, $match) ? $match[1] : ''; + if ($npfx && stristr($full, $npfx)===false) { + $full=$npfx.' '.$full; + } + + // Make sure the NICK is included in the NAME record. + if (preg_match('/^'.$sublevel.' NICK (.+)/m', $gedrec, $match)) { + $pos=strpos($full, '/'); + if ($pos===false) { + $full.=' "'.$match[1].'"'; + } else { + $full=substr($full, 0, $pos).'"'.$match[1].'" '.substr($full, $pos); + } + } + + // Convert 'user-defined' unknowns into WT unknowns + $full=preg_replace('/\/(_+|\?+|-+)\//', '/@N.N./', $full); + $full=preg_replace('/(?<= |^)(_+|\?+|-+)(?= |$)/', '@P.N.', $full); + $surn=preg_replace('/^(_+|\?+|-+)$/', '@N.N.', $surn); + $givn=preg_replace('/(?<= |^)(_+|\?+|-+)(?= |$)/', '@P.N.', $givn); + foreach ($surns as $key=>$value) { + $surns[$key]=preg_replace('/^(_+|\?+|-+)$/', '@N.N.', $value); + } + + // Create the list (surname first) version of the name. Note that zero + // slashes are valid; they indicate NO surname as opposed to missing surname. + $pos1=strpos($full, '/'); + if ($pos1===false) { + $list=$full; + } else { + $pos2=strrpos($full, '/'); + $list=trim(substr($full, $pos1+1, $pos2-$pos1-1)).', '.substr($full, 0, $pos1).substr($full, $pos2+1); + $list=trim(str_replace(array('/', ' ,', ' '), array('', ',', ' '), $list)); + $full=trim(str_replace(array('/', ' ,', ' '), array('', ',', ' '), $full)); + } + + // Need the 'not known' place holders for the database + $fullNN=$full; + $listNN=$list; + $surname=$surn; + + // Some people put preferred names in quotes + if ($UNDERLINE_NAME_QUOTES) { + $full=preg_replace('/"([^"]*)"/', '<span class="starredname">\\1</span>', $full); + $list=preg_replace('/"([^"]*)"/', '<span class="starredname">\\1</span>', $list); + } + + // The standards say you should use a suffix of '*' + $full=preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $full); + $list=preg_replace('/(\S*)\*/', '<span class="starredname">\\1</span>', $list); + + // If the name is written in greek/cyrillic/hebrew/etc., use the 'unknown' name + // from that character set. Otherwise use the one in the language file. + if (strpos($givn, '@P.N.')!==false || $surn=='@N.N.' || $surns[0]=='@N.N.') { + if (strpos($givn, '@P.N.')!==false && ($surn=='@N.N.' || $surns[0]=='@N.N.')) { + $PN=i18n::translate('(unknown)'); + $NN=i18n::translate('(unknown)'); + } else { + if ($surn!=='') + $PN=$UNKNOWN_PN[utf8_script($surn)]; + else + $PN=$UNKNOWN_PN[utf8_script($surns[0])]; + $NN=$UNKNOWN_NN[utf8_script($givn)]; + } + $list=str_replace(array('@N.N.','@P.N.'), array($NN, $PN), $list); + $full=str_replace(array('@N.N.','@P.N.'), array($NN, $PN), $full); + } + // A comma separated list of surnames (from the SURN, not from the NAME) indicates + // multiple surnames (e.g. Spanish). Each one is a separate sortable name. + + // Where nicknames are entered in the given name field, these will break + // sorting, so strip them out. + $GIVN=preg_replace('/["\'()]/', '', $givn); + + foreach ($surns as $n=>$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, 'full'=>$full, 'list'=>$list, 'sort'=>$surn.','.$givn, + // These extra parts used to populate the wt_name table and the indi list + // For these, we don't want to translate the @N.N. into local text + 'fullNN'=>$fullNN, + 'listNN'=>$listNN, + 'surname'=>$surname, + 'givn'=>$givn, + 'spfx'=>($n?'':$spfx), + 'surn'=>$surn + ); + } + } + + // 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); + } +} diff --git a/library/WT/Repository.php b/library/WT/Repository.php new file mode 100644 index 0000000000..b24a52e478 --- /dev/null +++ b/library/WT/Repository.php @@ -0,0 +1,51 @@ +<?php +/** + * Class file for a Repository (REPO) object + * + * webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView + * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @package webtrees + * @subpackage DataModel + * @version $Id$ + */ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_REPOSITORY_PHP', ''); + +class WT_Repository extends WT_GedcomRecord { + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('repo.php?rid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('repo.php?rid=', '&'); + } + + // Get an array of structures containing all the names in the record + public function getAllNames() { + return parent::_getAllNames('NAME', 1); + } +} diff --git a/library/WT/Source.php b/library/WT/Source.php new file mode 100644 index 0000000000..976c9e2036 --- /dev/null +++ b/library/WT/Source.php @@ -0,0 +1,59 @@ +<?php +/** + * Class file for a Source (SOUR) object + * + * webtrees: Web based Family History software + * Copyright (C) 2010 webtrees development team. + * + * Derived from PhpGedView + * Copyright (C) 2002 to 2009 PGV Development Team. All rights reserved. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @package webtrees + * @subpackage DataModel + * @version $Id$ + */ + +if (!defined('WT_WEBTREES')) { + header('HTTP/1.0 403 Forbidden'); + exit; +} + +define('WT_WT_SOURCE_PHP', ''); + +class WT_Source extends WT_GedcomRecord { + /** + * get the author of this source record + * @return string + */ + public function getAuth() { + return get_gedcom_value('AUTH', 1, $this->gedrec, '', false); + } + + // Generate a URL to this record, suitable for use in HTML + public function getHtmlUrl() { + return parent::_getLinkUrl('source.php?sid=', '&'); + } + // Generate a URL to this record, suitable for use in javascript, HTTP headers, etc. + public function getRawUrl() { + return parent::_getLinkUrl('source.php?sid=', '&'); + } + + // Get an array of structures containing all the names in the record + public function getAllNames() { + return parent::_getAllNames('TITL', 1); + } +} |
