예제 #1
0
 /**
  * Format age of parents in HTML
  *
  * @param Individual $person child
  * @param Date $birth_date
  *
  * @return string HTML
  */
 public static function formatParentsAges(Individual $person, Date $birth_date)
 {
     $html = '';
     $families = $person->getChildFamilies();
     // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
     if ($birth_date->isOK() && count($families) == 1) {
         $family = current($families);
         foreach ($family->getSpouses() as $parent) {
             if ($parent->getBirthDate()->isOK()) {
                 $sex = $parent->getSexImage();
                 $age = Date::getAge($parent->getBirthDate(), $birth_date, 2);
                 $deatdate = $parent->getDeathDate();
                 switch ($parent->getSex()) {
                     case 'F':
                         // Highlight mothers who die in childbirth or shortly afterwards
                         if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
                             $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
                         } else {
                             $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
                         }
                         break;
                     case 'M':
                         // Highlight fathers who die before the birth
                         if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
                             $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
                         } else {
                             $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
                         }
                         break;
                     default:
                         $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
                         break;
                 }
             }
         }
         if ($html) {
             $html = '<span class="age">' . $html . '</span>';
         }
     }
     return $html;
 }
 /**
  * Perform the search
  */
 private function advancedSearch()
 {
     global $WT_TREE;
     $this->myindilist = array();
     $fct = count($this->fields);
     if (!array_filter($this->values)) {
         return;
     }
     // Dynamic SQL query, plus bind variables
     $sql = 'SELECT DISTINCT ind.i_id AS xref, ind.i_gedcom AS gedcom FROM `##individuals` ind';
     $bind = array();
     // Join the following tables
     $father_name = false;
     $mother_name = false;
     $spouse_family = false;
     $indi_name = false;
     $indi_date = false;
     $fam_date = false;
     $indi_plac = false;
     $fam_plac = false;
     foreach ($this->fields as $n => $field) {
         if ($this->values[$n]) {
             if (substr($field, 0, 14) == 'FAMC:HUSB:NAME') {
                 $father_name = true;
             } elseif (substr($field, 0, 14) == 'FAMC:WIFE:NAME') {
                 $mother_name = true;
             } elseif (substr($field, 0, 4) == 'NAME') {
                 $indi_name = true;
             } elseif (strpos($field, ':DATE') !== false) {
                 if (substr($field, 0, 4) == 'FAMS') {
                     $fam_date = true;
                     $spouse_family = true;
                 } else {
                     $indi_date = true;
                 }
             } elseif (strpos($field, ':PLAC') !== false) {
                 if (substr($field, 0, 4) == 'FAMS') {
                     $fam_plac = true;
                     $spouse_family = true;
                 } else {
                     $indi_plac = true;
                 }
             } elseif ($field == 'FAMS:NOTE') {
                 $spouse_family = true;
             }
         }
     }
     if ($father_name || $mother_name) {
         $sql .= " JOIN `##link`   l_1 ON (l_1.l_file=ind.i_file AND l_1.l_from=ind.i_id AND l_1.l_type='FAMC')";
     }
     if ($father_name) {
         $sql .= " JOIN `##link`   l_2 ON (l_2.l_file=ind.i_file AND l_2.l_from=l_1.l_to AND l_2.l_type='HUSB')";
         $sql .= " JOIN `##name`   f_n ON (f_n.n_file=ind.i_file AND f_n.n_id  =l_2.l_to)";
     }
     if ($mother_name) {
         $sql .= " JOIN `##link`   l_3 ON (l_3.l_file=ind.i_file AND l_3.l_from=l_1.l_to AND l_3.l_type='WIFE')";
         $sql .= " JOIN `##name`   m_n ON (m_n.n_file=ind.i_file AND m_n.n_id  =l_3.l_to)";
     }
     if ($spouse_family) {
         $sql .= " JOIN `##link`     l_4 ON (l_4.l_file=ind.i_file AND l_4.l_from=ind.i_id AND l_4.l_type='FAMS')";
         $sql .= " JOIN `##families` fam ON (fam.f_file=ind.i_file AND fam.f_id  =l_4.l_to)";
     }
     if ($indi_name) {
         $sql .= " JOIN `##name`   i_n ON (i_n.n_file=ind.i_file AND i_n.n_id=ind.i_id)";
     }
     if ($indi_date) {
         $sql .= " JOIN `##dates`  i_d ON (i_d.d_file=ind.i_file AND i_d.d_gid=ind.i_id)";
     }
     if ($fam_date) {
         $sql .= " JOIN `##dates`  f_d ON (f_d.d_file=ind.i_file AND f_d.d_gid=fam.f_id)";
     }
     if ($indi_plac) {
         $sql .= " JOIN `##placelinks`   i_pl ON (i_pl.pl_file=ind.i_file AND i_pl.pl_gid =ind.i_id)";
         $sql .= " JOIN (" . "SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" . " FROM      `##places` AS p1" . " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" . " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" . " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" . " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" . " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" . " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" . " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" . " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" . ") AS i_p ON (i_p.file  =ind.i_file AND i_pl.pl_p_id= i_p.id)";
     }
     if ($fam_plac) {
         $sql .= " JOIN `##placelinks`   f_pl ON (f_pl.pl_file=ind.i_file AND f_pl.pl_gid =fam.f_id)";
         $sql .= " JOIN (" . "SELECT CONCAT_WS(', ', p1.p_place, p2.p_place, p3.p_place, p4.p_place, p5.p_place, p6.p_place, p7.p_place, p8.p_place, p9.p_place) AS place, p1.p_id AS id, p1.p_file AS file" . " FROM      `##places` AS p1" . " LEFT JOIN `##places` AS p2 ON (p1.p_parent_id=p2.p_id)" . " LEFT JOIN `##places` AS p3 ON (p2.p_parent_id=p3.p_id)" . " LEFT JOIN `##places` AS p4 ON (p3.p_parent_id=p4.p_id)" . " LEFT JOIN `##places` AS p5 ON (p4.p_parent_id=p5.p_id)" . " LEFT JOIN `##places` AS p6 ON (p5.p_parent_id=p6.p_id)" . " LEFT JOIN `##places` AS p7 ON (p6.p_parent_id=p7.p_id)" . " LEFT JOIN `##places` AS p8 ON (p7.p_parent_id=p8.p_id)" . " LEFT JOIN `##places` AS p9 ON (p8.p_parent_id=p9.p_id)" . ") AS f_p ON (f_p.file  =ind.i_file AND f_pl.pl_p_id= f_p.id)";
     }
     // Add the where clause
     $sql .= " WHERE ind.i_file=?";
     $bind[] = $WT_TREE->getTreeId();
     for ($i = 0; $i < $fct; $i++) {
         $field = $this->fields[$i];
         $value = $this->values[$i];
         if ($value === '') {
             continue;
         }
         $parts = preg_split("/:/", $field . '::::');
         if ($parts[0] == 'NAME') {
             // NAME:*
             switch ($parts[1]) {
                 case 'GIVN':
                     switch ($parts[2]) {
                         case 'EXACT':
                             $sql .= " AND i_n.n_givn=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND i_n.n_givn LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_givn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                     }
                     break;
                 case 'SURN':
                     switch ($parts[2]) {
                         case 'EXACT':
                             $sql .= " AND i_n.n_surname=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND i_n.n_surname LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND i_n.n_surname LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= " AND (" . implode(' OR ', $sdx) . ")";
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "i_n.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= " AND (" . implode(' OR ', $sdx) . ")";
                                 break;
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND i_n.n_surn LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                     }
                     break;
                 case 'NICK':
                 case '_MARNM':
                 case '_HEB':
                 case '_AKA':
                     $sql .= " AND i_n.n_type=? AND i_n.n_full LIKE CONCAT('%', ?, '%')";
                     $bind[] = $parts[1];
                     $bind[] = $value;
                     break;
             }
         } elseif ($parts[1] == 'DATE') {
             // *:DATE
             $date = new Date($value);
             if ($date->isOK()) {
                 $jd1 = $date->minimumJulianDay();
                 $jd2 = $date->maximumJulianDay();
                 if (!empty($this->plusminus[$i])) {
                     $adjd = $this->plusminus[$i] * 365;
                     $jd1 -= $adjd;
                     $jd2 += $adjd;
                 }
                 $sql .= " AND i_d.d_fact=? AND i_d.d_julianday1>=? AND i_d.d_julianday2<=?";
                 $bind[] = $parts[0];
                 $bind[] = $jd1;
                 $bind[] = $jd2;
             }
         } elseif ($parts[0] == 'FAMS' && $parts[2] == 'DATE') {
             // FAMS:*:DATE
             $date = new Date($value);
             if ($date->isOK()) {
                 $jd1 = $date->minimumJulianDay();
                 $jd2 = $date->maximumJulianDay();
                 if (!empty($this->plusminus[$i])) {
                     $adjd = $this->plusminus[$i] * 365;
                     $jd1 -= $adjd;
                     $jd2 += $adjd;
                 }
                 $sql .= " AND f_d.d_fact=? AND f_d.d_julianday1>=? AND f_d.d_julianday2<=?";
                 $bind[] = $parts[1];
                 $bind[] = $jd1;
                 $bind[] = $jd2;
             }
         } elseif ($parts[1] == 'PLAC') {
             // *:PLAC
             // SQL can only link a place to a person/family, not to an event.
             $sql .= " AND i_p.place LIKE CONCAT('%', ?, '%')";
             $bind[] = $value;
         } elseif ($parts[0] == 'FAMS' && $parts[2] == 'PLAC') {
             // FAMS:*:PLAC
             // SQL can only link a place to a person/family, not to an event.
             $sql .= " AND f_p.place LIKE CONCAT('%', ?, '%')";
             $bind[] = $value;
         } elseif ($parts[0] == 'FAMC' && $parts[2] == 'NAME') {
             $table = $parts[1] == 'HUSB' ? 'f_n' : 'm_n';
             // NAME:*
             switch ($parts[3]) {
                 case 'GIVN':
                     switch ($parts[4]) {
                         case 'EXACT':
                             $sql .= " AND {$table}.n_givn=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND {$table}.n_givn LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND {$table}.n_givn LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_givn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_givn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                                 break;
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_givn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                     }
                     break;
                 case 'SURN':
                     switch ($parts[4]) {
                         case 'EXACT':
                             $sql .= " AND {$table}.n_surname=?";
                             $bind[] = $value;
                             break;
                         case 'BEGINS':
                             $sql .= " AND {$table}.n_surname LIKE CONCAT(?, '%')";
                             $bind[] = $value;
                             break;
                         case 'CONTAINS':
                             $sql .= " AND {$table}.n_surname LIKE CONCAT('%', ?, '%')";
                             $bind[] = $value;
                             break;
                         case 'SDX_STD':
                             $sdx = Soundex::russell($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_surn_std LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                         case 'SDX':
                             // SDX uses DM by default.
                         // SDX uses DM by default.
                         case 'SDX_DM':
                             $sdx = Soundex::daitchMokotoff($value);
                             if ($sdx !== null) {
                                 $sdx = explode(':', $sdx);
                                 foreach ($sdx as $k => $v) {
                                     $sdx[$k] = "{$table}.n_soundex_surn_dm LIKE CONCAT('%', ?, '%')";
                                     $bind[] = $v;
                                 }
                                 $sql .= ' AND (' . implode(' OR ', $sdx) . ')';
                             } else {
                                 // No phonetic content?  Use a substring match
                                 $sql .= " AND {$table}.n_surn = LIKE CONCAT('%', ?, '%')";
                                 $bind[] = $value;
                             }
                             break;
                     }
                     break;
             }
         } elseif ($parts[0] == 'FAMS') {
             // e.g. searches for occupation, religion, note, etc.
             $sql .= " AND fam.f_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
             $bind[] = $parts[1];
             $bind[] = $value;
         } else {
             // e.g. searches for occupation, religion, note, etc.
             $sql .= " AND ind.i_gedcom REGEXP CONCAT('\n[0-9] ', ?, '(.*\n[0-9] CONT)* [^\n]*', ?)";
             $bind[] = $parts[0];
             $bind[] = $value;
         }
     }
     $rows = Database::prepare($sql)->execute($bind)->fetchAll();
     foreach ($rows as $row) {
         $person = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
         // Check for XXXX:PLAC fields, which were only partially matched by SQL
         foreach ($this->fields as $n => $field) {
             if ($this->values[$n] && preg_match('/^(' . WT_REGEX_TAG . '):PLAC$/', $field, $match)) {
                 if (!preg_match('/\\n1 ' . $match[1] . '(\\n[2-9].*)*\\n2 PLAC .*' . preg_quote($this->values[$n], '/') . '/i', $person->getGedcom())) {
                     continue 2;
                 }
             }
         }
         $this->myindilist[] = $person;
     }
 }
예제 #3
0
 /**
  * Calculate whether this individual is living or dead.
  * If not known to be dead, then assume living.
  *
  * @return bool
  */
 public function isDead()
 {
     $MAX_ALIVE_AGE = $this->tree->getPreference('MAX_ALIVE_AGE');
     // "1 DEAT Y" or "1 DEAT/2 DATE" or "1 DEAT/2 PLAC"
     if (preg_match('/\\n1 (?:' . WT_EVENTS_DEAT . ')(?: Y|(?:\\n[2-9].+)*\\n2 (DATE|PLAC) )/', $this->gedcom)) {
         return true;
     }
     // If any event occured more than $MAX_ALIVE_AGE years ago, then assume the individual is dead
     if (preg_match_all('/\\n2 DATE (.+)/', $this->gedcom, $date_matches)) {
         foreach ($date_matches[1] as $date_match) {
             $date = new Date($date_match);
             if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * $MAX_ALIVE_AGE) {
                 return true;
             }
         }
         // The individual has one or more dated events.  All are less than $MAX_ALIVE_AGE years ago.
         // If one of these is a birth, the individual must be alive.
         if (preg_match('/\\n1 BIRT(?:\\n[2-9].+)*\\n2 DATE /', $this->gedcom)) {
             return false;
         }
     }
     // If we found no conclusive dates then check the dates of close relatives.
     // Check parents (birth and adopted)
     foreach ($this->getChildFamilies(Auth::PRIV_HIDE) as $family) {
         foreach ($family->getSpouses(Auth::PRIV_HIDE) as $parent) {
             // Assume parents are no more than 45 years older than their children
             preg_match_all('/\\n2 DATE (.+)/', $parent->gedcom, $date_matches);
             foreach ($date_matches[1] as $date_match) {
                 $date = new Date($date_match);
                 if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * ($MAX_ALIVE_AGE + 45)) {
                     return true;
                 }
             }
         }
     }
     // Check spouses
     foreach ($this->getSpouseFamilies(Auth::PRIV_HIDE) as $family) {
         preg_match_all('/\\n2 DATE (.+)/', $family->gedcom, $date_matches);
         foreach ($date_matches[1] as $date_match) {
             $date = new Date($date_match);
             // Assume marriage occurs after age of 10
             if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * ($MAX_ALIVE_AGE - 10)) {
                 return true;
             }
         }
         // Check spouse dates
         $spouse = $family->getSpouse($this);
         if ($spouse) {
             preg_match_all('/\\n2 DATE (.+)/', $spouse->gedcom, $date_matches);
             foreach ($date_matches[1] as $date_match) {
                 $date = new Date($date_match);
                 // Assume max age difference between spouses of 40 years
                 if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * ($MAX_ALIVE_AGE + 40)) {
                     return true;
                 }
             }
         }
         // Check child dates
         foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
             preg_match_all('/\\n2 DATE (.+)/', $child->gedcom, $date_matches);
             // Assume children born after age of 15
             foreach ($date_matches[1] as $date_match) {
                 $date = new Date($date_match);
                 if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * ($MAX_ALIVE_AGE - 15)) {
                     return true;
                 }
             }
             // Check grandchildren
             foreach ($child->getSpouseFamilies(Auth::PRIV_HIDE) as $child_family) {
                 foreach ($child_family->getChildren(Auth::PRIV_HIDE) as $grandchild) {
                     preg_match_all('/\\n2 DATE (.+)/', $grandchild->gedcom, $date_matches);
                     // Assume grandchildren born after age of 30
                     foreach ($date_matches[1] as $date_match) {
                         $date = new Date($date_match);
                         if ($date->isOK() && $date->maximumJulianDay() <= WT_CLIENT_JD - 365 * ($MAX_ALIVE_AGE - 30)) {
                             return true;
                         }
                     }
                 }
             }
         }
     }
     return false;
 }
예제 #4
0
    /**
     * Print a family group.
     *
     * @param Family $family
     * @param string $type
     * @param string $label
     */
    private function printFamily(Family $family, $type, $label)
    {
        global $controller;
        if ($family->getTree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS')) {
            $access_level = Auth::PRIV_HIDE;
        } else {
            $access_level = Auth::accessLevel($family->getTree());
        }
        ?>
		<table>
			<tr>
				<td>
					<i class="icon-cfamily"></i>
				</td>
				<td>
					<span class="subheaders"> <?php 
        echo $label;
        ?>
</span>
					<a class="noprint" href="<?php 
        echo $family->getHtmlUrl();
        ?>
"> - <?php 
        echo I18N::translate('View this family');
        ?>
</a>
				</td>
			</tr>
		</table>
		<table class="facts_table">
		<?php 
        ///// HUSB /////
        $found = false;
        foreach ($family->getFacts('HUSB', false, $access_level) as $fact) {
            $found |= !$fact->isPendingDeletion();
            $person = $fact->getTarget();
            if ($person instanceof Individual) {
                if ($fact->isPendingAddition()) {
                    $class = 'facts_label new';
                } elseif ($fact->isPendingDeletion()) {
                    $class = 'facts_label old';
                } else {
                    $class = 'facts_label';
                }
                ?>
					<tr>
					<td class="<?php 
                echo $class;
                ?>
">
						<?php 
                echo Functions::getCloseRelationshipName($controller->record, $person);
                ?>
					</td>
					<td class="<?php 
                echo $controller->getPersonStyle($person);
                ?>
">
						<?php 
                echo Theme::theme()->individualBoxLarge($person);
                ?>
					</td>
					</tr>
				<?php 
            }
        }
        if (!$found && $family->canEdit()) {
            ?>
			<tr>
				<td class="facts_label"></td>
				<td class="facts_value"><a href="#" onclick="return add_spouse_to_family('<?php 
            echo $family->getXref();
            ?>
', 'HUSB');"><?php 
            echo I18N::translate('Add a husband to this family');
            ?>
</a></td>
			</tr>
			<?php 
        }
        ///// WIFE /////
        $found = false;
        foreach ($family->getFacts('WIFE', false, $access_level) as $fact) {
            $person = $fact->getTarget();
            if ($person instanceof Individual) {
                $found |= !$fact->isPendingDeletion();
                if ($fact->isPendingAddition()) {
                    $class = 'facts_label new';
                } elseif ($fact->isPendingDeletion()) {
                    $class = 'facts_label old';
                } else {
                    $class = 'facts_label';
                }
                ?>
				<tr>
					<td class="<?php 
                echo $class;
                ?>
">
						<?php 
                echo Functions::getCloseRelationshipName($controller->record, $person);
                ?>
					</td>
					<td class="<?php 
                echo $controller->getPersonStyle($person);
                ?>
">
						<?php 
                echo Theme::theme()->individualBoxLarge($person);
                ?>
					</td>
				</tr>
				<?php 
            }
        }
        if (!$found && $family->canEdit()) {
            ?>
			<tr>
				<td class="facts_label"></td>
				<td class="facts_value"><a href="#" onclick="return add_spouse_to_family('<?php 
            echo $family->getXref();
            ?>
', 'WIFE');"><?php 
            echo I18N::translate('Add a wife to this family');
            ?>
</a></td>
			</tr>
			<?php 
        }
        ///// MARR /////
        $found = false;
        $prev = new Date('');
        foreach ($family->getFacts(WT_EVENTS_MARR . '|' . WT_EVENTS_DIV, true) as $fact) {
            $found |= !$fact->isPendingDeletion();
            if ($fact->isPendingAddition()) {
                $class = ' new';
            } elseif ($fact->isPendingDeletion()) {
                $class = ' old';
            } else {
                $class = '';
            }
            ?>
			<tr>
				<td class="facts_label">
				</td>
				<td class="facts_value<?php 
            echo $class;
            ?>
">
					<?php 
            echo GedcomTag::getLabelValue($fact->getTag(), $fact->getDate()->display() . ' — ' . $fact->getPlace()->getFullName());
            ?>
				</td>
			</tr>
			<?php 
            if (!$prev->isOK() && $fact->getDate()->isOK()) {
                $prev = $fact->getDate();
            }
        }
        if (!$found && $family->canShow() && $family->canEdit()) {
            // Add a new marriage
            ?>
			<tr>
				<td class="facts_label">
				</td>
				<td class="facts_value">
					<a href="#" onclick="return add_new_record('<?php 
            echo $family->getXref();
            ?>
', 'MARR');">
						<?php 
            echo I18N::translate('Add marriage details');
            ?>
					</a>
				</td>
			</tr>
			<?php 
        }
        ///// CHIL /////
        $child_number = 0;
        foreach ($family->getFacts('CHIL', false, $access_level) as $fact) {
            $person = $fact->getTarget();
            if ($person instanceof Individual) {
                if ($fact->isPendingAddition()) {
                    $child_number++;
                    $class = 'facts_label new';
                } elseif ($fact->isPendingDeletion()) {
                    $class = 'facts_label old';
                } else {
                    $child_number++;
                    $class = 'facts_label';
                }
                $next = new Date('');
                foreach ($person->getFacts(WT_EVENTS_BIRT, true) as $bfact) {
                    if ($bfact->getDate()->isOK()) {
                        $next = $bfact->getDate();
                        break;
                    }
                }
                ?>
				<tr>
					<td class="<?php 
                echo $class;
                ?>
">
						<?php 
                echo self::ageDifference($prev, $next, $child_number);
                ?>
						<?php 
                echo Functions::getCloseRelationshipName($controller->record, $person);
                ?>
					</td>
					<td class="<?php 
                echo $controller->getPersonStyle($person);
                ?>
">
						<?php 
                echo Theme::theme()->individualBoxLarge($person);
                ?>
					</td>
				</tr>
				<?php 
                $prev = $next;
            }
        }
        // Re-order children / add a new child
        if ($family->canEdit()) {
            if ($type == 'FAMS') {
                $add_child_text = I18N::translate('Add a son or daughter');
            } else {
                $add_child_text = I18N::translate('Add a brother or sister');
            }
            ?>
			<tr class="noprint">
				<td class="facts_label">
					<?php 
            if (count($family->getChildren()) > 1) {
                ?>
					<a href="#" onclick="reorder_children('<?php 
                echo $family->getXref();
                ?>
');tabswitch(5);"><i class="icon-media-shuffle"></i> <?php 
                echo I18N::translate('Re-order children');
                ?>
</a>
					<?php 
            }
            ?>
				</td>
				<td class="facts_value">
					<a href="#" onclick="return add_child_to_family('<?php 
            echo $family->getXref();
            ?>
');"><?php 
            echo $add_child_text;
            ?>
</a>
					<span style='white-space:nowrap;'>
						<a href="#" class="icon-sex_m_15x15" onclick="return add_child_to_family('<?php 
            echo $family->getXref();
            ?>
','M');"></a>
						<a href="#" class="icon-sex_f_15x15" onclick="return add_child_to_family('<?php 
            echo $family->getXref();
            ?>
','F');"></a>
					</span>
				</td>
			</tr>
			<?php 
        }
        echo '</table>';
        return;
    }