/** * Generate the likely value of this census column, based on available information. * * @param Individual $individual * @param Individual|null $head * * @return string */ public function generate(Individual $individual, Individual $head = null) { if ($individual->getSex() !== 'F') { return ''; } $count = 0; foreach ($individual->getSpouseFamilies() as $family) { foreach ($family->getChildren() as $child) { if ($child->getBirthDate()->isOK() && Date::compare($child->getBirthDate(), $this->date()) < 0 && $child->getBirthDate() != $child->getDeathDate() && (!$child->getDeathDate()->isOK() || Date::compare($child->getDeathDate(), $this->date()) > 0)) { $count++; } } } return (string) $count; }
/** * Generate the likely value of this census column, based on available information. * * @param Individual $individual * @param Individual|null $head * * @return string */ public function generate(Individual $individual, Individual $head = null) { $marriage_date = null; foreach ($individual->getSpouseFamilies() as $family) { foreach ($family->getFacts('MARR', true) as $fact) { if ($fact->getDate()->isOK() && Date::compare($fact->getDate(), $this->date()) <= 0) { $marriage_date = $fact->getDate(); } } } if ($marriage_date === null) { return ''; } else { return (string) Date::getAge($marriage_date, $this->date(), 0); } }
/** * Generate the likely value of this census column, based on available information. * * @param Individual $individual * @param Individual|null $head * * @return string */ public function generate(Individual $individual, Individual $head = null) { $place = $individual->getBirthPlace(); // Did we emigrate or naturalise? foreach ($individual->getFacts('IMMI|EMIG|NATU', true) as $fact) { if (Date::compare($fact->getDate(), $this->date()) <= 0) { $place = $fact->getPlace()->getGedcomName(); } } $place = explode(', ', $place); $place = end($place); if ($place === 'England' || $place === 'Scotland' || $place === 'Wales') { return 'British'; } else { return $place; } }
/** * What was an individual's likely name on a given date, allowing * for marriages and married names. * * @param Individual $individual * @param Date $census_date * * @return string[] */ protected function nameAtCensusDate(Individual $individual, Date $census_date) { $names = $individual->getAllNames(); $name = $names[0]; foreach ($individual->getSpouseFamilies() as $family) { foreach ($family->getFacts('MARR') as $marriage) { if ($marriage->getDate()->isOK() && Date::compare($marriage->getDate(), $census_date) < 0) { $spouse = $family->getSpouse($individual); foreach ($names as $individual_name) { foreach ($spouse->getAllNames() as $spouse_name) { if ($individual_name['type'] === '_MARNM' && $individual_name['surn'] === $spouse_name['surn']) { return $individual_name; } } } } } } return $name; }
$gedrec .= FunctionsEdit::addNewFact($match); } } $gedrec .= "\n" . GedcomCodePedi::createNewFamcPedi($PEDI, $xref); if (Filter::postBool('SOUR_INDI')) { $gedrec = FunctionsEdit::handleUpdates($gedrec); } else { $gedrec = FunctionsEdit::updateRest($gedrec); } // Create the new child $new_child = $family->getTree()->createRecord($gedrec); // Insert new child at the right place $done = false; foreach ($family->getFacts('CHIL') as $fact) { $old_child = $fact->getTarget(); if ($old_child && Date::compare($new_child->getEstimatedBirthDate(), $old_child->getEstimatedBirthDate()) < 0) { // Insert before this child $family->updateFact($fact->getFactId(), '1 CHIL @' . $new_child->getXref() . "@\n" . $fact->getGedcom(), !$keep_chan); $done = true; break; } } if (!$done) { // Append child at end $family->createFact('1 CHIL @' . $new_child->getXref() . '@', !$keep_chan); } if (Filter::post('goto') === 'new') { $controller->addInlineJavascript('closePopupAndReloadParent("' . $new_child->getRawUrl() . '");'); } else { $controller->addInlineJavascript('closePopupAndReloadParent();'); }
/** * Function checkFact * * Does this fact meet the search criteria? * * @todo This function is public to support the PHP5.3 closure workaround. * * @param Fact $fact * * @return bool */ public function checkFact(Fact $fact) { $valid = !in_array($fact->getTag(), $this->nonfacts); if ($valid && $this->place_obj) { $valid = stripos($fact->getPlace()->getGedcomName(), $this->place_obj->getGedcomName()) !== false; } if ($valid && $this->startDate) { if ($this->strictDate && $this->calendar !== $this->defaultCalendar) { $valid = stripos($fact->getAttribute('DATE'), $this->calendar) !== false; } if ($valid) { $date = $fact->getDate(); $valid = $date->isOK() && Date::compare($date, $this->startDate) >= 0 && Date::compare($date, $this->endDate) <= 0; } } return $valid; }
/** * {@inhericDoc} * @see \MyArtJaub\Webtrees\Mvc\View\AbstractView::renderContent() */ protected function renderContent() { if ($this->data->get('has_sosa', false)) { $table_id = $this->data->get('table_id'); ?> <div id="sosa-indi-list" class="sosa-list"> <table id="<?php echo $table_id; ?> "> <thead> <tr> <th colspan="22"> <div class="btn-toolbar"> <div class="btn-group"> <button class="ui-state-default" data-filter-column="18" data-filter-value="M" title="<?php echo I18N::translate('Show only males.'); ?> " type="button" ><?php echo Individual::sexImage('M', 'large'); ?> </button> <button class="ui-state-default" data-filter-column="18" data-filter-value="F" title="<?php echo I18N::translate('Show only females.'); ?> " type="button" > <?php echo Individual::sexImage('F', 'large'); ?> </button> <button class="ui-state-default" data-filter-column="18" data-filter-value="U" title="<?php echo I18N::translate('Show only individuals for whom the gender is not known.'); ?> " type="button" > <?php echo Individual::sexImage('U', 'large'); ?> </button> </div> <div class="btn-group"> <button class="ui-state-default" data-filter-column="20" data-filter-value="N" title="<?php echo I18N::translate('Show individuals who are alive or couples where both partners are alive.'); ?> " type="button" > <?php echo I18N::translate('Alive'); ?> </button> <button class="ui-state-default" data-filter-column="20" data-filter-value="Y" title="<?php echo I18N::translate('Show individuals who are dead or couples where both partners are deceased.'); ?> " type="button" > <?php echo I18N::translate('Dead'); ?> </button> <button class="ui-state-default" data-filter-column="20" data-filter-value="YES" title="<?php echo I18N::translate('Show individuals who died more than 100 years ago.'); ?> " type="button" ><?php echo GedcomTag::getLabel('DEAT'); ?> >100 </button> <button class="ui-state-default" data-filter-column="20" data-filter-value="Y100" title="<?php echo I18N::translate('Show individuals who died within the last 100 years.'); ?> " type="button" ><?php echo GedcomTag::getLabel('DEAT'); ?> <=100 </button> </div> <div class="btn-group"> <button class="ui-state-default" data-filter-column="19" data-filter-value="YES" title="<?php echo I18N::translate('Show individuals born more than 100 years ago.'); ?> " type="button" ><?php echo GedcomTag::getLabel('BIRT'); ?> >100 </button> <button class="ui-state-default" data-filter-column="19" data-filter-value="Y100" title="<?php echo I18N::translate('Show individuals born within the last 100 years.'); ?> " type="button" ><?php echo GedcomTag::getLabel('BIRT'); ?> <=100 </button> </div> <div class="btn-group"> <button class="ui-state-default" data-filter-column="21" data-filter-value="R" title="<?php echo I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.'); ?> " type="button" > <?php echo I18N::translate('Roots'); ?> </button> <button class="ui-state-default" data-filter-column="21" data-filter-value="L" title="<?php echo I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.'); ?> " type="button" > <?php echo I18N::translate('Leaves'); ?> </button> </div> </div> </th> </tr> <tr> <th><?php echo I18N::translate('Sosa'); ?> </th> <th><?php echo GedcomTag::getLabel('INDI'); ?> </th> <th><?php echo GedcomTag::getLabel('GIVN'); ?> </th> <th><?php echo GedcomTag::getLabel('SURN'); ?> </th> <th>GIVN</th> <th>SURN</th> <th><?php echo GedcomTag::getLabel('BIRT'); ?> </th> <th>SORT_BIRT</th> <th><?php echo GedcomTag::getLabel('PLAC'); ?> </th> <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { ?> <th><i class="icon-source" title="<?php echo I18N::translate('Sourced birth'); ?> " border="0"></i></th> <th>SORT_BIRTSC</th> <?php } else { ?> <th></th> <th></th> <?php } ?> <th><?php echo GedcomTag::getLabel('DEAT'); ?> </th> <th>SORT_DEAT</th> <th><?php echo GedcomTag::getLabel('AGE'); ?> </th> <th>AGE</th> <th><?php echo GedcomTag::getLabel('PLAC'); ?> </th> <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { ?> <th><i class="icon-source" title="<?php echo I18N::translate('Sourced death'); ?> " border="0"></i></th> <th>SORT_DEATSC</th> <?php } else { ?> <th></th> <th></th> <?php } ?> <th>SEX</th> <th>BIRT</th> <th>DEAT</th> <th>TREE</th> </tr> </thead> <tbody> <?php foreach ($this->data->get('sosa_list') as $sosa => $person) { /** @var \Fisharebest\Webtrees\Individual $person */ if ($person->isPendingAddtion()) { $class = ' class="new"'; } elseif ($person->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $dperson = new \MyArtJaub\Webtrees\Individual($person); ?> <tr <?php echo $class; ?> > <td class="transparent"><?php echo $sosa; ?> </td> <td class="transparent"><?php echo $person->getXref(); ?> </td> <td colspan="2"> <?php foreach ($person->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $person)) . '"'; } if ($num == $person->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $person->getSexImage(); list($surn, $givn) = explode(',', $name['sort']); } else { $class = ''; $sex_image = ''; } ?> <a <?php echo $title . ' ' . $class; ?> href="<?php echo $person->getHtmlUrl(); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($name['full']); ?> </a> <?php echo $sex_image . FunctionsPrint::formatSosaNumbers($dperson->getSosaNumbers(), 1, 'smaller'); ?> <br/> <?php } echo $person->getPrimaryParentsNames('parents details1', 'none'); ?> </td> <td style="display:none;"></td> <td> <?php echo Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)); ?> </td> <td> <?php echo Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)); ?> </td> <td> <?php if ($birth_dates = $person->getAllBirthDates()) { foreach ($birth_dates as $num => $birth_date) { if ($num) { ?> <br/><?php } ?> <?php echo $birth_date->display(true); } } else { $birth_date = new Date(''); if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES')) { $birth_date = $person->getEstimatedBirthDate(); echo $birth_date->display(true); } else { echo ' '; } $birth_dates[0] = new Date(''); } ?> </td> <td><?php echo $birth_date->julianDay(); ?> </td> <td> <?php foreach ($person->getAllBirthPlaces() as $n => $birth_place) { $tmp = new \Fisharebest\Webtrees\Place($birth_place, $person->getTree()); if ($n) { ?> <br><?php } ?> <a href="'<?php echo $tmp->getURL(); ?> " title="<?php echo strip_tags($tmp->getFullName()); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($tmp->getShortName()); ?> </a> <?php } ?> </td> <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { $isBSourced = $dperson->isBirthSourced(); ?> <td><?php echo FunctionsPrint::formatIsSourcedIcon('E', $isBSourced, 'BIRT', 1, 'medium'); ?> </td> <td><?php echo $isBSourced; ?> </td> <?php } else { ?> <td> </td> <td></td> <?php } ?> <td> <?php if ($death_dates = $person->getAllDeathDates()) { foreach ($death_dates as $num => $death_date) { if ($num) { ?> <br/><?php } ?> <?php echo $death_date->display(true); } } else { $death_date = $person->getEstimatedDeathDate(); if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES') && $death_date->minimumJulianDay() < WT_CLIENT_JD) { echo $death_date->display(true); } elseif ($person->isDead()) { echo I18N::translate('yes'); $death_date = new Date(''); } else { echo ' '; $death_date = new Date(''); } $death_dates[0] = new Date(''); } ?> </td> <td><?php echo $death_date->julianDay(); ?> </td> <td><?php echo Date::getAge($birth_dates[0], $death_dates[0], 2); ?> </td> <td><?php echo Date::getAge($birth_dates[0], $death_dates[0], 1); ?> </td> <td> <?php foreach ($person->getAllDeathPlaces() as $n => $death_place) { $tmp = new Place($death_place, $person->getTree()); if ($n) { ?> <br><?php } ?> <a href="'<?php echo $tmp->getURL(); ?> " title="<?php echo strip_tags($tmp->getFullName()); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($tmp->getShortName()); ?> </a> <?php } ?> </td> <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { $isDSourced = $dperson->isDeathSourced(); ?> <td><?php echo FunctionsPrint::formatIsSourcedIcon('E', $isDSourced, 'DEAT', 1, 'medium'); ?> </td> <td><?php echo $isDSourced; ?> </td> <?php } else { ?> <td> </td> <td></td> <?php } ?> <td><?php echo $person->getSex(); ?> </td> <td> <?php if (!$person->canShow() || Date::compare($birth_date, new Date(date('Y') - 100)) > 0) { echo 'Y100'; } else { echo 'YES'; } ?> </td> <td> <?php if (Date::compare($death_dates[0], new Date(date('Y') - 100)) > 0) { echo 'Y100'; } elseif ($death_dates[0]->minimumJulianDay() || $person->isDead()) { echo 'YES'; } else { echo 'N'; } ?> </td> <td> <?php if (!$person->getChildFamilies()) { echo 'R'; } elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) { echo 'L'; } else { echo ' '; } ?> </td> </tr> <?php } ?> </tbody> <tfoot> <tr> <th class="ui-state-default" colspan="22"> <div class="center"> <?php echo I18N::translate('Number of Sosa ancestors: %1$s known / %2$s theoretical (%3$s)', I18N::number($this->data->get('sosa_count')), I18N::number($this->data->get('sosa_theo')), I18N::percentage($this->data->get('sosa_ratio'), 2)); ?> <?php if ($this->data->get('sosa_hidden') > 0) { echo '[' . I18N::translate('%s hidden', I18N::number($this->data->get('sosa_hidden'))) . ']'; } ?> </div> </th> </tr> <tr> <th colspan="22"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" class="ui-state-default btn-toggle-parents"> <?php echo I18N::translate('Show parents'); ?> </button> <button id="btn-toggle-statistics-<?php echo $table_id; ?> " type="button" class="ui-state-default btn-toggle-statistics"> <?php echo I18N::translate('Show statistics charts'); ?> </button> </div> </div> </th> </tr> </tfoot> </table> <div id="indi_list_table-charts_<?php echo $table_id; ?> " style="display:none"> <table class="list-charts"> <tr> <td><?php echo $this->data->get('chart_births'); ?> </td> <td><?php echo $this->data->get('chart_deaths'); ?> </td> </tr> <tr> <td colspan="2"><?php echo $this->data->get('chart_ages'); ?> </td> </tr> </table> </div> </div> <?php } }
/** * Static helper function to sort an array of people by death date * * @param Individual $x * @param Individual $y * * @return int */ public static function compareDeathDate(Individual $x, Individual $y) { return Date::compare($x->getEstimatedDeathDate(), $y->getEstimatedDeathDate()); }
);'> <?php echo $gparent->getFullName(); ?> </a> </td> <tr> <?php } // Spouse Children foreach ($family->getChildren() as $child) { // Get Spouse child’s marriage status $married = ''; $marrdate = ''; foreach ($child->getSpouseFamilies() as $childfamily) { $marrdate = $childfamily->getMarriageDate(); $married = Date::compare($censdate, $marrdate); } // Get Child’s Children $chBLDarray = array(); foreach ($child->getSpouseFamilies() as $childfamily) { $chchildren = $childfamily->getChildren(); foreach ($chchildren as $chchild) { $chnam = $chchild->getAllNames(); $chfulln = strip_tags($chnam[0]['full']); // Child’s Full Name// Child’s Full Name $chdob = $chchild->getBirthDate()->julianDay(); // Child’s Date of Birth (Julian) $chdod = $chchild->getDeathDate()->julianDay(); // Child’s Date of Death (Julian) $chBLD = $chfulln . ', ' . $chdob . ', ' . $chdod; array_push($chBLDarray, $chBLD);
/** * Get any historical events. * * @param Individual $person * * @return Fact[] */ private static function historicalFacts(Individual $person) { $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS'); $facts = array(); if ($SHOW_RELATIVES_EVENTS) { // Only include events between birth and death $birt_date = $person->getEstimatedBirthDate(); $deat_date = $person->getEstimatedDeathDate(); if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) { $histo = array(); require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php'; foreach ($histo as $hist) { // Earlier versions of the WIKI encouraged people to use HTML entities, // rather than UTF8 encoding. $hist = html_entity_decode($hist, ENT_QUOTES, 'UTF-8'); $fact = new Fact($hist, $person, 'histo'); $sdate = $fact->getDate(); if ($sdate->isOK() && Date::compare($birt_date, $sdate) <= 0 && Date::compare($sdate, $deat_date) <= 0) { $facts[] = $fact; } } } } return $facts; }
/** * XML <List> * * @param array $attrs an array of key value pairs for the attributes */ private function listStartHandler($attrs) { global $WT_TREE; $this->process_repeats++; if ($this->process_repeats > 1) { return; } $match = array(); if (isset($attrs['sortby'])) { $sortby = $attrs['sortby']; if (preg_match("/\\\$(\\w+)/", $sortby, $match)) { $sortby = $this->vars[$match[1]]['id']; $sortby = trim($sortby); } } else { $sortby = "NAME"; } if (isset($attrs['list'])) { $listname = $attrs['list']; } else { $listname = "individual"; } // Some filters/sorts can be applied using SQL, while others require PHP switch ($listname) { case "pending": $rows = Database::prepare("SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" . " FROM `##change`" . " WHERE (xref, change_id) IN (" . " SELECT xref, MAX(change_id)" . " FROM `##change`" . " WHERE status = 'pending' AND gedcom_id = :tree_id" . " GROUP BY xref" . " )")->execute(array('tree_id' => $WT_TREE->getTreeId()))->fetchAll(); $this->list = array(); foreach ($rows as $row) { $this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom); } break; case 'individual': $sql_select = "SELECT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` "; $sql_join = ""; $sql_where = " WHERE i_file = :tree_id"; $sql_order_by = ""; $sql_params = array('tree_id' => $WT_TREE->getTreeId()); foreach ($attrs as $attr => $value) { if (strpos($attr, 'filter') === 0 && $value) { $value = $this->substituteVars($value, false); // Convert the various filters into SQL if (preg_match('/^(\\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)"; $sql_where .= " AND {$attr}.d_fact = :{$attr}fact"; $sql_params[$attr . 'fact'] = $match[1]; $date = new Date($match[3]); if ($match[2] == "LTE") { $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date"; $sql_params[$attr . 'date'] = $date->maximumJulianDay(); } else { $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date"; $sql_params[$attr . 'date'] = $date->minimumJulianDay(); } if ($sortby == $match[1]) { $sortby = ""; $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; } unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) { // Do nothing, unless you have to if ($match[1] != '' || $sortby == 'NAME') { $sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)"; // Search the DB only if there is any name supplied if ($match[1] != "") { $names = explode(" ", $match[1]); foreach ($names as $n => $name) { $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')"; $sql_params[$attr . 'name' . $n] = $name; } } // Let the DB do the name sorting even when no name was entered if ($sortby == "NAME") { $sortby = ""; $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; } } unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^REGEXP \\/(.+)\\//', $value, $match)) { $sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom"; // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT" $sql_params[$attr . 'gedcom'] = str_replace('\\n', "\n", $match[1]); unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^(?:\\w+):PLAC CONTAINS (.+)$/', $value, $match)) { $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)"; $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file = {$attr}b.pl_file AND {$attr}b.pl_p_id = {$attr}a.p_id AND {$attr}b.pl_gid = i_id)"; $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')"; $sql_params[$attr . 'place'] = $match[1]; // Don't unset this filter. This is just initial filtering } elseif (preg_match('/^(\\w*):*(\\w*) CONTAINS (.+)$/', $value, $match)) { $sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')"; $sql_params[$attr . 'contains1'] = $match[1]; $sql_params[$attr . 'contains2'] = $match[2]; $sql_params[$attr . 'contains3'] = $match[3]; // Don't unset this filter. This is just initial filtering } } } $this->list = array(); $rows = Database::prepare($sql_select . $sql_join . $sql_where . $sql_order_by)->execute($sql_params)->fetchAll(); foreach ($rows as $row) { $this->list[$row->xref] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom); } break; case 'family': $sql_select = "SELECT f_id AS xref, f_gedcom AS gedcom FROM `##families`"; $sql_join = ""; $sql_where = " WHERE f_file = :tree_id"; $sql_order_by = ""; $sql_params = array('tree_id' => $WT_TREE->getTreeId()); foreach ($attrs as $attr => $value) { if (strpos($attr, 'filter') === 0 && $value) { $value = $this->substituteVars($value, false); // Convert the various filters into SQL if (preg_match('/^(\\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) { $sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)"; $sql_where .= " AND {$attr}.d_fact = :{$attr}fact"; $sql_params[$attr . 'fact'] = $match[1]; $date = new Date($match[3]); if ($match[2] == "LTE") { $sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date"; $sql_params[$attr . 'date'] = $date->maximumJulianDay(); } else { $sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date"; $sql_params[$attr . 'date'] = $date->minimumJulianDay(); } if ($sortby == $match[1]) { $sortby = ""; $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1"; } unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^REGEXP \\/(.+)\\//', $value, $match)) { $sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom"; // PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT" $sql_params[$attr . 'gedcom'] = str_replace('\\n', "\n", $match[1]); unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) { // Do nothing, unless you have to if ($match[1] != '' || $sortby == 'NAME') { $sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)"; // Search the DB only if there is any name supplied if ($match[1] != "") { $names = explode(" ", $match[1]); foreach ($names as $n => $name) { $sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')"; $sql_params[$attr . 'name' . $n] = $name; } } // Let the DB do the name sorting even when no name was entered if ($sortby == "NAME") { $sortby = ""; $sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort"; } } unset($attrs[$attr]); // This filter has been fully processed } elseif (preg_match('/^(?:\\w+):PLAC CONTAINS (.+)$/', $value, $match)) { $sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)"; $sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file={$attr}b.pl_file AND {$attr}b.pl_p_id={$attr}a.p_id AND {$attr}b.pl_gid=f_id)"; $sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')"; $sql_params[$attr . 'place'] = $match[1]; // Don't unset this filter. This is just initial filtering } elseif (preg_match('/^(\\w*):*(\\w*) CONTAINS (.+)$/', $value, $match)) { $sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')"; $sql_params[$attr . 'contains1'] = $match[1]; $sql_params[$attr . 'contains2'] = $match[2]; $sql_params[$attr . 'contains3'] = $match[3]; // Don't unset this filter. This is just initial filtering } } } $this->list = array(); $rows = Database::prepare($sql_select . $sql_join . $sql_where . $sql_order_by)->execute($sql_params)->fetchAll(); foreach ($rows as $row) { $this->list[$row->xref] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom); } break; default: throw new \DomainException('Invalid list name: ' . $listname); } $filters = array(); $filters2 = array(); if (isset($attrs['filter1']) && count($this->list) > 0) { foreach ($attrs as $key => $value) { if (preg_match("/filter(\\d)/", $key)) { $condition = $value; if (preg_match("/@(\\w+)/", $condition, $match)) { $id = $match[1]; $value = "''"; if ($id == "ID") { if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) { $value = "'" . $match[1] . "'"; } } elseif ($id == "fact") { $value = "'" . $this->fact . "'"; } elseif ($id == "desc") { $value = "'" . $this->desc . "'"; } else { if (preg_match("/\\d {$id} (.+)/", $this->gedrec, $match)) { $value = "'" . str_replace("@", "", trim($match[1])) . "'"; } } $condition = preg_replace("/@{$id}/", $value, $condition); } //-- handle regular expressions if (preg_match("/([A-Z:]+)\\s*([^\\s]+)\\s*(.+)/", $condition, $match)) { $tag = trim($match[1]); $expr = trim($match[2]); $val = trim($match[3]); if (preg_match("/\\\$(\\w+)/", $val, $match)) { $val = $this->vars[$match[1]]['id']; $val = trim($val); } if ($val) { $searchstr = ""; $tags = explode(":", $tag); //-- only limit to a level number if we are specifically looking at a level if (count($tags) > 1) { $level = 1; foreach ($tags as $t) { if (!empty($searchstr)) { $searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n"; } //-- search for both EMAIL and _EMAIL... silly double gedcom standard if ($t == "EMAIL" || $t == "_EMAIL") { $t = "_?EMAIL"; } $searchstr .= $level . " " . $t; $level++; } } else { if ($tag == "EMAIL" || $tag == "_EMAIL") { $tag = "_?EMAIL"; } $t = $tag; $searchstr = "1 " . $tag; } switch ($expr) { case "CONTAINS": if ($t == "PLAC") { $searchstr .= "[^\n]*[, ]*" . $val; } else { $searchstr .= "[^\n]*" . $val; } $filters[] = $searchstr; break; default: $filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val); break; } } } } } } //-- apply other filters to the list that could not be added to the search string if ($filters) { foreach ($this->list as $key => $record) { foreach ($filters as $filter) { if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) { unset($this->list[$key]); break; } } } } if ($filters2) { $mylist = array(); foreach ($this->list as $indi) { $key = $indi->getXref(); $grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE)); $keep = true; foreach ($filters2 as $filter) { if ($keep) { $tag = $filter['tag']; $expr = $filter['expr']; $val = $filter['val']; if ($val == "''") { $val = ""; } $tags = explode(":", $tag); $t = end($tags); $v = $this->getGedcomValue($tag, 1, $grec); //-- check for EMAIL and _EMAIL (silly double gedcom standard :P) if ($t == "EMAIL" && empty($v)) { $tag = str_replace("EMAIL", "_EMAIL", $tag); $tags = explode(":", $tag); $t = end($tags); $v = Functions::getSubRecord(1, $tag, $grec); } switch ($expr) { case "GTE": if ($t == "DATE") { $date1 = new Date($v); $date2 = new Date($val); $keep = Date::compare($date1, $date2) >= 0; } elseif ($val >= $v) { $keep = true; } break; case "LTE": if ($t == "DATE") { $date1 = new Date($v); $date2 = new Date($val); $keep = Date::compare($date1, $date2) <= 0; } elseif ($val >= $v) { $keep = true; } break; default: if ($v == $val) { $keep = true; } else { $keep = false; } break; } } } if ($keep) { $mylist[$key] = $indi; } } $this->list = $mylist; } switch ($sortby) { case 'NAME': uasort($this->list, '\\Fisharebest\\Webtrees\\GedcomRecord::compare'); break; case 'CHAN': uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) { return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true); }); break; case 'BIRT:DATE': uasort($this->list, '\\Fisharebest\\Webtrees\\Individual::compareBirthDate'); break; case 'DEAT:DATE': uasort($this->list, '\\Fisharebest\\Webtrees\\Individual::compareDeathDate'); break; case 'MARR:DATE': uasort($this->list, '\\Fisharebest\\Webtrees\\Family::compareMarrDate'); break; default: // unsorted or already sorted by SQL break; } array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes)); $this->repeat_bytes = xml_get_current_line_number($this->parser) + 1; }
/** * Find the current spouse family of an individual * * @param Individual $individual * * @return Family|null */ public function spouseFamily(Individual $individual) { // Exclude families that were created after this census date $families = array(); foreach ($individual->getSpouseFamilies() as $family) { if (Date::compare($family->getMarriageDate(), $this->date()) <= 0) { $families[] = $family; } } if (empty($families)) { return null; } else { usort($families, function (Family $x, Family $y) { return Date::compare($x->getMarriageDate(), $y->getMarriageDate()); }); return end($families); } }
/** * Is the individual dead. * * @param Individual $individual * * @return bool */ private function isDead(Individual $individual) { return $individual->getDeathDate()->isOK() && Date::compare($individual->getDeathDate(), $this->date()) < 0; }
/** * Print a table of families * * @param Family[] $datalist * * @return string */ public static function familyTable($datalist) { global $WT_TREE, $controller; $table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page $controller->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)->addInlineJavascript(' jQuery.fn.dataTableExt.oSort["unicode-asc" ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))}; jQuery.fn.dataTableExt.oSort["unicode-desc"]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))}; jQuery("#' . $table_id . '").dataTable( { dom: \'<"H"<"filtersH_' . $table_id . '"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth: false, processing: true, retrieve: true, columns: [ /* 0 husb givn */ {dataSort: 2}, /* 1 husb surn */ {dataSort: 3}, /* 2 GIVN,SURN */ {type: "unicode", visible: false}, /* 3 SURN,GIVN */ {type: "unicode", visible: false}, /* 4 age */ {dataSort: 5, class: "center"}, /* 5 AGE */ {type: "num", visible: false}, /* 6 wife givn */ {dataSort: 8}, /* 7 wife surn */ {dataSort: 9}, /* 8 GIVN,SURN */ {type: "unicode", visible: false}, /* 9 SURN,GIVN */ {type: "unicode", visible: false}, /* 10 age */ {dataSort: 11, class: "center"}, /* 11 AGE */ {type: "num", visible: false}, /* 12 marr date */ {dataSort: 13}, /* 13 MARR:DATE */ {visible: false}, /* 14 anniv */ {dataSort: 13, class: "center"}, /* 15 marr plac */ {type: "unicode"}, /* 16 children */ {dataSort: 17, class: "center"}, /* 17 NCHI */ {type: "num", visible: false}, /* 18 CHAN */ {dataSort: 19, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . '}, /* 19 CHAN_sort */ {visible: false}, /* 20 MARR */ {visible: false}, /* 21 DEAT */ {visible: false}, /* 22 TREE */ {visible: false} ], sorting: [[1, "asc"]], displayLength: 20, pagingType: "full_numbers" }); jQuery("#' . $table_id . '") /* Hide/show parents */ .on("click", ".btn-toggle-parents", function() { jQuery(this).toggleClass("ui-state-active"); jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle(); }) /* Hide/show statistics */ .on("click", ".btn-toggle-statistics", function() { jQuery(this).toggleClass("ui-state-active"); jQuery("#fam_list_table-charts_' . $table_id . '").slideToggle(); }) /* Filter buttons in table header */ .on("click", "button[data-filter-column]", function() { var btn = $(this); // De-activate the other buttons in this button group btn.siblings().removeClass("ui-state-active"); // Apply (or clear) this filter var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column")); if (btn.hasClass("ui-state-active")) { btn.removeClass("ui-state-active"); col.search("").draw(); } else { btn.addClass("ui-state-active"); col.search(btn.data("filter-value")).draw(); } }); jQuery(".fam-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); $stats = new Stats($WT_TREE); $max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1; //-- init chart data $marr_by_age = array(); for ($age = 0; $age <= $max_age; $age++) { $marr_by_age[$age] = ''; } $birt_by_decade = array(); $marr_by_decade = array(); for ($year = 1550; $year < 2030; $year += 10) { $birt_by_decade[$year] = ''; $marr_by_decade[$year] = ''; } $html = ' <div class="loading-image"> </div> <div class="fam-list"> <table id="' . $table_id . '"> <thead> <tr> <th colspan="23"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" data-filter-column="21" data-filter-value="N" class="ui-state-default" title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '" > ' . I18N::translate('Both alive') . ' </button> <button type="button" data-filter-column="21" data-filter-value="W" class="ui-state-default" title="' . I18N::translate('Show couples where only the female partner is deceased.') . '" > ' . I18N::translate('Widower') . ' </button> <button type="button" data-filter-column="21" data-filter-value="H" class="ui-state-default" title="' . I18N::translate('Show couples where only the male partner is deceased.') . '" > ' . I18N::translate('Widow') . ' </button> <button type="button" data-filter-column="21" data-filter-value="Y" class="ui-state-default" title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '" > ' . I18N::translate('Both dead') . ' </button> </div> <div class="btn-group"> <button type="button" data-filter-column="22" data-filter-value="R" class="ui-state-default" title="' . I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') . '" > ' . I18N::translate('Roots') . ' </button> <button type="button" data-filter-column="22" data-filter-value="L" class="ui-state-default" title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '" > ' . I18N::translate('Leaves') . ' </button> </div> <div class="btn-group"> <button type="button" data-filter-column="20" data-filter-value="U" class="ui-state-default" title="' . I18N::translate('Show couples with an unknown marriage date.') . '" > ' . GedcomTag::getLabel('MARR') . ' </button> <button type="button" data-filter-column="20" data-filter-value="YES" class="ui-state-default" title="' . I18N::translate('Show couples who married more than 100 years ago.') . '" > ' . GedcomTag::getLabel('MARR') . '>100 </button> <button type="button" data-filter-column="20" data-filter-value="Y100" class="ui-state-default" title="' . I18N::translate('Show couples who married within the last 100 years.') . '" > ' . GedcomTag::getLabel('MARR') . '<=100 </button> <button type="button" data-filter-column="20" data-filter-value="D" class="ui-state-default" title="' . I18N::translate('Show divorced couples.') . '" > ' . GedcomTag::getLabel('DIV') . ' </button> <button type="button" data-filter-column="20" data-filter-value="M" class="ui-state-default" title="' . I18N::translate('Show couples where either partner married more than once.') . '" > ' . I18N::translate('Multiple marriages') . ' </button> </div> </div> </th> </tr> <tr> <th>' . GedcomTag::getLabel('GIVN') . '</th> <th>' . GedcomTag::getLabel('SURN') . '</th> <th>HUSB:GIVN_SURN</th> <th>HUSB:SURN_GIVN</th> <th>' . GedcomTag::getLabel('AGE') . '</th> <th>AGE</th> <th>' . GedcomTag::getLabel('GIVN') . '</th> <th>' . GedcomTag::getLabel('SURN') . '</th> <th>WIFE:GIVN_SURN</th> <th>WIFE:SURN_GIVN</th> <th>' . GedcomTag::getLabel('AGE') . '</th> <th>AGE</th> <th>' . GedcomTag::getLabel('MARR') . '</th> <th>MARR:DATE</th> <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th> <th>' . GedcomTag::getLabel('PLAC') . '</th> <th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th> <th>NCHI</th> <th>' . GedcomTag::getLabel('CHAN') . '</th> <th>CHAN</th> <th>MARR</th> <th>DEAT</th> <th>TREE</th> </tr> </thead> <tfoot> <tr> <th colspan="23"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" class="ui-state-default btn-toggle-parents"> ' . I18N::translate('Show parents') . ' </button> <button type="button" class="ui-state-default btn-toggle-statistics"> ' . I18N::translate('Show statistics charts') . ' </button> </div> </div> </th> </tr> </tfoot> <tbody>'; $d100y = new Date(date('Y') - 100); // 100 years ago foreach ($datalist as $family) { //-- Retrieve husband and wife $husb = $family->getHusband(); if (is_null($husb)) { $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree()); } $wife = $family->getWife(); if (is_null($wife)) { $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree()); } if (!$family->canShow()) { continue; } if ($family->isPendingAddtion()) { $class = ' class="new"'; } elseif ($family->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= '<tr' . $class . '>'; //-- Husband name(s) $html .= '<td colspan="2">'; foreach ($husb->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"'; } if ($num == $husb->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $husb->getSexImage(); list($surn, $givn) = explode(',', $name['sort']); } else { $class = ''; $sex_image = ''; } // Only show married names if they are the name we are filtering by. if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) { $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>'; } } // Husband parents $html .= $husb->getPrimaryParentsNames('parents details1', 'none'); $html .= '</td>'; // Dummy column to match colspan in header $html .= '<td style="display:none;"></td>'; //-- Husb GIVN // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it. // Similarly, @N.N. would sort as NN. $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>'; $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>'; $mdate = $family->getMarriageDate(); //-- Husband age $hdate = $husb->getBirthDate(); if ($hdate->isOK() && $mdate->isOK()) { if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) { $birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex(); } $hage = Date::getAge($hdate, $mdate, 0); if ($hage >= 0 && $hage <= $max_age) { $marr_by_age[$hage] .= $husb->getSex(); } } $html .= '<td>' . Date::getAge($hdate, $mdate, 2) . '</td><td>' . Date::getAge($hdate, $mdate, 1) . '</td>'; //-- Wife name(s) $html .= '<td colspan="2">'; foreach ($wife->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"'; } if ($num == $wife->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $wife->getSexImage(); list($surn, $givn) = explode(',', $name['sort']); } else { $class = ''; $sex_image = ''; } // Only show married names if they are the name we are filtering by. if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) { $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>'; } } // Wife parents $html .= $wife->getPrimaryParentsNames('parents details1', 'none'); $html .= '</td>'; // Dummy column to match colspan in header $html .= '<td style="display:none;"></td>'; //-- Wife GIVN //-- Husb GIVN // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it. // Similarly, @N.N. would sort as NN. $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>'; $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>'; $mdate = $family->getMarriageDate(); //-- Wife age $wdate = $wife->getBirthDate(); if ($wdate->isOK() && $mdate->isOK()) { if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) { $birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex(); } $wage = Date::getAge($wdate, $mdate, 0); if ($wage >= 0 && $wage <= $max_age) { $marr_by_age[$wage] .= $wife->getSex(); } } $html .= '<td>' . Date::getAge($wdate, $mdate, 2) . '</td><td>' . Date::getAge($wdate, $mdate, 1) . '</td>'; //-- Marriage date $html .= '<td>'; if ($marriage_dates = $family->getAllMarriageDates()) { foreach ($marriage_dates as $n => $marriage_date) { if ($n) { $html .= '<br>'; } $html .= '<div>' . $marriage_date->display(true) . '</div>'; } if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) { $marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex(); } } elseif ($family->getFacts('_NMR')) { $html .= I18N::translate('no'); } elseif ($family->getFacts('MARR')) { $html .= I18N::translate('yes'); } else { $html .= ' '; } $html .= '</td>'; //-- Event date (sortable)hidden by datatables code $html .= '<td>'; if ($marriage_dates) { $html .= $marriage_date->julianDay(); } else { $html .= 0; } $html .= '</td>'; //-- Marriage anniversary $html .= '<td>' . Date::getAge($mdate, null, 2) . '</td>'; //-- Marriage place $html .= '<td>'; foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) { $tmp = new Place($marriage_place, $family->getTree()); if ($n) { $html .= '<br>'; } $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">'; $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>'; } $html .= '</td>'; //-- Number of children $nchi = $family->getNumberOfChildren(); $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>'; //-- Last change $html .= '<td>' . $family->LastChangeTimestamp() . '</td>'; $html .= '<td>' . $family->LastChangeTimestamp(true) . '</td>'; //-- Sorting by marriage date $html .= '<td>'; if (!$family->canShow() || !$mdate->isOK()) { $html .= 'U'; } else { if (Date::compare($mdate, $d100y) > 0) { $html .= 'Y100'; } else { $html .= 'YES'; } } if ($family->getFacts(WT_EVENTS_DIV)) { $html .= 'D'; } if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) { $html .= 'M'; } $html .= '</td>'; //-- Sorting alive/dead $html .= '<td>'; if ($husb->isDead() && $wife->isDead()) { $html .= 'Y'; } if ($husb->isDead() && !$wife->isDead()) { if ($wife->getSex() == 'F') { $html .= 'H'; } if ($wife->getSex() == 'M') { $html .= 'W'; } // male partners } if (!$husb->isDead() && $wife->isDead()) { if ($husb->getSex() == 'M') { $html .= 'W'; } if ($husb->getSex() == 'F') { $html .= 'H'; } // female partners } if (!$husb->isDead() && !$wife->isDead()) { $html .= 'N'; } $html .= '</td>'; //-- Roots or Leaves $html .= '<td>'; if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) { $html .= 'R'; } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() < 1) { $html .= 'L'; } else { $html .= ' '; } $html .= '</td> </tr>'; } $html .= ' </tbody> </table> <div id="fam_list_table-charts_' . $table_id . '" style="display:none"> <table class="list-charts"> <tr> <td> ' . self::chartByDecade($birt_by_decade, I18N::translate('Decade of birth')) . ' </td> <td> ' . self::chartByDecade($marr_by_decade, I18N::translate('Decade of marriage')) . ' </td> </tr> <tr> <td colspan="2"> ' . self::chartByAge($marr_by_age, I18N::translate('Age in year of marriage')) . ' </td> </tr> </table> </div> </div>'; return $html; }
/** * Print fact DATE/TIME * * @param Fact $event event containing the date/age * @param GedcomRecord $record the person (or couple) whose ages should be printed * @param bool $anchor option to print a link to calendar * @param bool $time option to print TIME value * * @return string */ public static function formatFactDate(Fact $event, GedcomRecord $record, $anchor, $time) { global $pid; $factrec = $event->getGedcom(); $html = ''; // Recorded age if (preg_match('/\\n2 AGE (.+)/', $factrec, $match)) { $fact_age = $match[1]; } else { $fact_age = ''; } if (preg_match('/\\n2 HUSB\\n3 AGE (.+)/', $factrec, $match)) { $husb_age = $match[1]; } else { $husb_age = ''; } if (preg_match('/\\n2 WIFE\\n3 AGE (.+)/', $factrec, $match)) { $wife_age = $match[1]; } else { $wife_age = ''; } // Calculated age if (preg_match('/\\n2 DATE (.+)/', $factrec, $match)) { $date = new Date($match[1]); $html .= ' ' . $date->display($anchor); // time if ($time && preg_match('/\\n3 TIME (.+)/', $factrec, $match)) { $html .= ' – <span class="date">' . $match[1] . '</span>'; } $fact = $event->getTag(); if ($record instanceof Individual) { if ($fact === 'BIRT' && $record->getTree()->getPreference('SHOW_PARENTS_AGE')) { // age of parents at child birth $html .= self::formatParentsAges($record, $date); } elseif ($fact !== 'CHAN' && $fact !== '_TODO') { // age at event $birth_date = $record->getBirthDate(); // Can't use getDeathDate(), as this also gives BURI/CREM events, which // wouldn't give the correct "days after death" result for people with // no DEAT. $death_event = $record->getFirstFact('DEAT'); if ($death_event) { $death_date = $death_event->getDate(); } else { $death_date = new Date(''); } $ageText = ''; if (Date::compare($date, $death_date) <= 0 || !$record->isDead() || $fact == 'DEAT') { // Before death, print age $age = Date::getAgeGedcom($birth_date, $date); // Only show calculated age if it differs from recorded age if ($age != '') { if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $record->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $record->getSex() == 'F' && $wife_age != $age) { if ($age != "0d") { $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age, false) . ')'; } } } } if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) { // After death, print time since death $age = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($death_date, $date), true); if ($age != '') { if (Date::getAgeGedcom($death_date, $date) == "0d") { $ageText = '(' . I18N::translate('on the date of death') . ')'; } else { $ageText = '(' . $age . ' ' . I18N::translate('after death') . ')'; // Family events which occur after death are probably errors if ($event->getParent() instanceof Family) { $ageText .= '<i class="icon-warning"></i>'; } } } } if ($ageText) { $html .= ' <span class="age">' . $ageText . '</span>'; } } } elseif ($record instanceof Family) { $indi = Individual::getInstance($pid, $record->getTree()); if ($indi) { $birth_date = $indi->getBirthDate(); $death_date = $indi->getDeathDate(); $ageText = ''; if (Date::compare($date, $death_date) <= 0) { $age = Date::getAgeGedcom($birth_date, $date); // Only show calculated age if it differs from recorded age if ($age != '' && $age > 0) { if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $indi->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $indi->getSex() == 'F' && $wife_age != $age) { $ageText = '(' . I18N::translate('Age') . ' ' . FunctionsDate::getAgeAtEvent($age, false) . ')'; } } } if ($ageText) { $html .= ' <span class="age">' . $ageText . '</span>'; } } } } else { // 1 DEAT Y with no DATE => print YES // 1 BIRT 2 SOUR @S1@ => print YES // 1 DEAT N is not allowed // It is not proper GEDCOM form to use a N(o) value with an event tag to infer that it did not happen. $factdetail = explode(' ', trim($factrec)); if (isset($factdetail) && (count($factdetail) == 3 && strtoupper($factdetail[2]) == 'Y') || count($factdetail) == 4 && $factdetail[2] == 'SOUR') { $html .= I18N::translate('yes'); } } // print gedcom ages foreach (array(GedcomTag::getLabel('AGE') => $fact_age, GedcomTag::getLabel('HUSB') => $husb_age, GedcomTag::getLabel('WIFE') => $wife_age) as $label => $age) { if ($age != '') { $html .= ' <span class="label">' . $label . ':</span> <span class="age">' . FunctionsDate::getAgeAtEvent($age, false) . '</span>'; } } return $html; }
/** * Static Helper functions to sort events * * @param Fact $a Fact one * @param Fact $b Fact two * * @return int */ public static function compareDate(Fact $a, Fact $b) { if ($a->getDate()->isOK() && $b->getDate()->isOK()) { // If both events have dates, compare by date $ret = Date::compare($a->getDate(), $b->getDate()); if ($ret == 0) { // If dates are the same, compare by fact type $ret = self::compareType($a, $b); // If the fact type is also the same, retain the initial order if ($ret == 0) { $ret = $a->sortOrder - $b->sortOrder; } } return $ret; } else { // One or both events have no date - retain the initial order return $a->sortOrder - $b->sortOrder; } }
/** * {@inhericDoc} * @see \MyArtJaub\Webtrees\Mvc\View\AbstractView::renderContent() */ protected function renderContent() { if ($this->data->get('has_sosa', false)) { $table_id = $this->data->get('table_id'); ?> <div id="sosa-fam-list" class="sosa-list"> <table id="<?php echo $table_id; ?> "> <thead> <tr> <th colspan="24"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" data-filter-column="22" data-filter-value="N" class="ui-state-default" title="<?php echo I18N::translate('Show individuals who are alive or couples where both partners are alive.'); ?> " > <?php echo I18N::translate('Both alive'); ?> </button> <button type="button" data-filter-column="22" data-filter-value="W" class="ui-state-default" title="<?php echo I18N::translate('Show couples where only the female partner is deceased.'); ?> " > <?php echo I18N::translate('Widower'); ?> </button> <button type="button" data-filter-column="22" data-filter-value="H" class="ui-state-default" title="<?php echo I18N::translate('Show couples where only the male partner is deceased.'); ?> " > <?php echo I18N::translate('Widow'); ?> </button> <button type="button" data-filter-column="22" data-filter-value="Y" class="ui-state-default" title="<?php echo I18N::translate('Show individuals who are dead or couples where both partners are deceased.'); ?> " > <?php echo I18N::translate('Both dead'); ?> </button> </div> <div class="btn-group"> <button type="button" data-filter-column="23" data-filter-value="R" class="ui-state-default" title="<?php echo I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.'); ?> " > <?php echo I18N::translate('Roots'); ?> </button> <button type="button" data-filter-column="23" data-filter-value="L" class="ui-state-default" title="<?php echo I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.'); ?> " > <?php echo I18N::translate('Leaves'); ?> </button> </div> <div class="btn-group"> <button type="button" data-filter-column="21" data-filter-value="U" class="ui-state-default" title="<?php echo I18N::translate('Show couples with an unknown marriage date.'); ?> " > <?php echo GedcomTag::getLabel('MARR'); ?> </button> <button type="button" data-filter-column="21" data-filter-value="YES" class="ui-state-default" title="<?php echo I18N::translate('Show couples who married more than 100 years ago.'); ?> " > <?php echo GedcomTag::getLabel('MARR'); ?> >100 </button> <button type="button" data-filter-column="21" data-filter-value="Y100" class="ui-state-default" title="<?php echo I18N::translate('Show couples who married within the last 100 years.'); ?> " > <?php echo GedcomTag::getLabel('MARR'); ?> <=100 </button> <button type="button" data-filter-column="21" data-filter-value="D" class="ui-state-default" title="<?php echo I18N::translate('Show divorced couples.'); ?> " > <?php echo GedcomTag::getLabel('DIV'); ?> </button> <button type="button" data-filter-column="21" data-filter-value="M" class="ui-state-default" title="<?php echo I18N::translate('Show couples where either partner married more than once.'); ?> " > <?php echo I18N::translate('Multiple marriages'); ?> </button> </div> </div> </th> </tr> <tr> <th><?php echo I18N::translate('Sosa'); ?> </th> <th>SOSA</th> <th><?php echo GedcomTag::getLabel('GIVN'); ?> </th> <th><?php echo GedcomTag::getLabel('SURN'); ?> </th> <th>HUSB:GIVN_SURN</th> <th>HUSB:SURN_GIVN</th> <th><?php echo GedcomTag::getLabel('AGE'); ?> </th> <th>AGE</th> <th><?php echo GedcomTag::getLabel('GIVN'); ?> </th> <th><?php echo GedcomTag::getLabel('SURN'); ?> </th> <th>WIFE:GIVN_SURN</th> <th>WIFE:SURN_GIVN</th> <th><?php echo GedcomTag::getLabel('AGE'); ?> </th> <th>AGE</th> <th><?php echo GedcomTag::getLabel('MARR'); ?> </th> <th>MARR:DATE</th> <th><?php echo GedcomTag::getLabel('PLAC'); ?> </th>'; <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { ?> <th><i class="icon-source" title="<?php echo I18N::translate('Sourced marriage'); ?> " border="0"></i></th> <th>SORT_MARRSC</th> <?php } else { ?> <th> </th> <th></th> <?php } ?> <th><i class="icon-children" title="<?php echo I18N::translate('Children'); ?> "></i></th> <th>NCHI</th> <th>MARR</th> <th>DEAT</th> <th>TREE</th> </tr> </thead> <tbody> <?php foreach ($this->data->get('sosa_list') as $sosa => $family) { /** @var \Fisharebest\Webtrees\Family $person */ //PERSO Create decorator for Family $dfamily = new Family($family); $husb = $family->getHusband(); if (is_null($husb)) { $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree()); } $dhusb = new \MyArtJaub\Webtrees\Individual($husb); $wife = $family->getWife(); if (is_null($wife)) { $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree()); } $dwife = new \MyArtJaub\Webtrees\Individual($wife); $mdate = $family->getMarriageDate(); if ($family->isPendingAddtion()) { $class = ' class="new"'; } elseif ($family->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } ?> <tr <?php echo $class; ?> > <td class="transparent"><?php echo I18N::translate('%1$d/%2$d', $sosa, ($sosa + 1) % 10); ?> </td> <td class="transparent"><?php echo $sosa; ?> </td> <!-- HUSBAND --> <td colspan="2"> <?php foreach ($husb->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"'; } if ($num == $husb->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $husb->getSexImage(); list($surn, $givn) = explode(',', $name['sort']); } else { $class = ''; $sex_image = ''; } ?> <a <?php echo $title . ' ' . $class; ?> href="<?php echo $husb->getHtmlUrl(); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($name['full']); ?> </a> <?php echo $sex_image . FunctionsPrint::formatSosaNumbers($dhusb->getSosaNumbers(), 1, 'smaller'); ?> <br/> <?php } echo $husb->getPrimaryParentsNames('parents details1', 'none'); ?> </td> <!-- Dummy column to match colspan in header --> <td style="display:none;"></td> <td> <?php echo Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)); ?> </td> <td> <?php echo Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)); ?> </td> <?php $hdate = $husb->getBirthDate(); ?> <td><?php Date::getAge($hdate, $mdate, 2); ?> </td> <td><?php Date::getAge($hdate, $mdate, 1); ?> </td> <!-- WIFE --> <td colspan="2"> <?php foreach ($wife->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"'; } if ($num == $wife->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $wife->getSexImage(); list($surn, $givn) = explode(',', $name['sort']); } else { $class = ''; $sex_image = ''; } ?> <a <?php echo $title . ' ' . $class; ?> href="<?php echo $wife->getHtmlUrl(); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($name['full']); ?> </a> <?php echo $sex_image . FunctionsPrint::formatSosaNumbers($dwife->getSosaNumbers(), 1, 'smaller'); ?> <br/> <?php } echo $wife->getPrimaryParentsNames('parents details1', 'none'); ?> </td> <!-- Dummy column to match colspan in header --> <td style="display:none;"></td> <td> <?php echo Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)); ?> </td> <td> <?php echo Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)); ?> </td> <?php $wdate = $wife->getBirthDate(); ?> <td><?php Date::getAge($wdate, $mdate, 2); ?> </td> <td><?php Date::getAge($wdate, $mdate, 1); ?> </td> <td><?php if ($marriage_dates = $family->getAllMarriageDates()) { foreach ($marriage_dates as $n => $marriage_date) { if ($n) { echo '<br>'; } ?> <div><?php echo $marriage_date->display(true); ?> </div> <?php } } elseif ($family->getFacts('_NMR')) { echo I18N::translate('no'); } elseif ($family->getFacts('MARR')) { echo I18N::translate('yes'); } else { echo ' '; } ?> </td> <td><?php echo $marriage_dates ? $marriage_date->julianDay() : 0; ?> </td> <td><?php foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) { $tmp = new Place($marriage_place, $family->getTree()); if ($n) { ?> <br><?php } ?> <a href="'<?php echo $tmp->getURL(); ?> " title="<?php echo strip_tags($tmp->getFullName()); ?> "> <?php echo \Fisharebest\Webtrees\Functions\FunctionsPrint::highlightSearchHits($tmp->getShortName()); ?> </a> <?php } ?> </td> <?php if (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME)) { $isMSourced = $dfamily->isMarriageSourced(); ?> <td><?php echo FunctionsPrint::formatIsSourcedIcon('E', $isMSourced, 'MARR', 1, 'medium'); ?> </td> <td><?php echo $isMSourced; ?> </td> <?php } else { ?> <td> </td> <td></td> <?php } ?> <?php $nchi = $family->getNumberOfChildren(); ?> <td><?php echo I18N::number($nchi); ?> </td> <td><?php echo $nchi; ?> </td> <td><?php if (!$mdate->isOK()) { echo 'U'; } else { if (Date::compare($mdate, new Date(date('Y') - 100)) > 0) { echo 'Y100'; } else { echo 'YES'; } } if ($family->getFacts(WT_EVENTS_DIV)) { echo 'D'; } if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) { echo 'M'; } ?> </td> <td><?php if ($husb->isDead() && $wife->isDead()) { echo 'Y'; } if ($husb->isDead() && !$wife->isDead()) { if ($wife->getSex() == 'F') { echo 'H'; } if ($wife->getSex() == 'M') { echo 'W'; } // male partners } if (!$husb->isDead() && $wife->isDead()) { if ($husb->getSex() == 'M') { echo 'W'; } if ($husb->getSex() == 'F') { echo 'H'; } // female partners } if (!$husb->isDead() && !$wife->isDead()) { echo 'N'; } ?> </td> <td><?php if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) { echo 'R'; } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() < 1) { echo 'L'; } else { echo ' '; } ?> </td> </tr> <?php } ?> </tbody> <tfoot> <tr> <th colspan="24"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" class="ui-state-default btn-toggle-parents"> <?php echo I18N::translate('Show parents'); ?> </button> <button id="btn-toggle-statistics-<?php echo $table_id; ?> " type="button" class="ui-state-default btn-toggle-statistics"> <?php echo I18N::translate('Show statistics charts'); ?> </button> </div> </div> </th> </tr> </tfoot> </table> <div id="fam_list_table-charts_<?php echo $table_id; ?> " style="display:none"> <table class="list-charts"> <tr> <td><?php echo $this->data->get('chart_births'); ?> </td> <td><?php echo $this->data->get('chart_marriages'); ?> </td> </tr> <tr> <td colspan="2"><?php echo $this->data->get('chart_ages'); ?> </td> </tr> </table> </div> </div> <?php } else { ?> <p class="warning"><?php echo I18N::translate('No family has been found for generation %d', $this->data->get('generation')); ?> </p> <?php } }
/** * Print a table of families * * @param Family[] $families * * @return string */ public static function familyTable($families) { global $WT_TREE, $controller; $table_id = 'table-fam-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page $controller->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)->addInlineJavascript(' jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc; jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc; jQuery("#' . $table_id . '").dataTable( { dom: \'<"H"<"filtersH_' . $table_id . '"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth: false, processing: true, retrieve: true, columns: [ /* Given names */ { type: "text" }, /* Surnames */ { type: "text" }, /* Age */ { type: "num" }, /* Given names */ { type: "text" }, /* Surnames */ { type: "text" }, /* Age */ { type: "num" }, /* Marriage date */ { type: "num" }, /* Anniversary */ { type: "num" }, /* Marriage place */ { type: "text" }, /* Children */ { type: "num" }, /* Last change */ { visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* Filter marriage */ { sortable: false }, /* Filter alive/dead */ { sortable: false }, /* Filter tree */ { sortable: false } ], sorting: [[1, "asc"]], displayLength: 20, pagingType: "full_numbers" }); jQuery("#' . $table_id . '") /* Hide/show parents */ .on("click", ".btn-toggle-parents", function() { jQuery(this).toggleClass("ui-state-active"); jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle(); }) /* Hide/show statistics */ .on("click", ".btn-toggle-statistics", function() { jQuery(this).toggleClass("ui-state-active"); jQuery("#fam_list_table-charts_' . $table_id . '").slideToggle(); }) /* Filter buttons in table header */ .on("click", "button[data-filter-column]", function() { var btn = $(this); // De-activate the other buttons in this button group btn.siblings().removeClass("ui-state-active"); // Apply (or clear) this filter var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column")); if (btn.hasClass("ui-state-active")) { btn.removeClass("ui-state-active"); col.search("").draw(); } else { btn.addClass("ui-state-active"); col.search(btn.data("filter-value")).draw(); } }); jQuery(".fam-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); $stats = new Stats($WT_TREE); $max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1; // init chart data $marr_by_age = array(); for ($age = 0; $age <= $max_age; $age++) { $marr_by_age[$age] = ''; } $birt_by_decade = array(); $marr_by_decade = array(); for ($year = 1550; $year < 2030; $year += 10) { $birt_by_decade[$year] = ''; $marr_by_decade[$year] = ''; } $html = ' <div class="loading-image"></div> <div class="fam-list"> <table id="' . $table_id . '"> <thead> <tr> <th colspan="14"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" data-filter-column="12" data-filter-value="N" class="ui-state-default" title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '" > ' . I18N::translate('Both alive') . ' </button> <button type="button" data-filter-column="12" data-filter-value="W" class="ui-state-default" title="' . I18N::translate('Show couples where only the female partner is dead.') . '" > ' . I18N::translate('Widower') . ' </button> <button type="button" data-filter-column="12" data-filter-value="H" class="ui-state-default" title="' . I18N::translate('Show couples where only the male partner is dead.') . '" > ' . I18N::translate('Widow') . ' </button> <button type="button" data-filter-column="12" data-filter-value="Y" class="ui-state-default" title="' . I18N::translate('Show individuals who are dead or couples where both partners are dead.') . '" > ' . I18N::translate('Both dead') . ' </button> </div> <div class="btn-group"> <button type="button" data-filter-column="13" data-filter-value="R" class="ui-state-default" title="' . I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') . '" > ' . I18N::translate('Roots') . ' </button> <button type="button" data-filter-column="13" data-filter-value="L" class="ui-state-default" title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '" > ' . I18N::translate('Leaves') . ' </button> </div> <div class="btn-group"> <button type="button" data-filter-column="11" data-filter-value="U" class="ui-state-default" title="' . I18N::translate('Show couples with an unknown marriage date.') . '" > ' . GedcomTag::getLabel('MARR') . ' </button> <button type="button" data-filter-column="11" data-filter-value="YES" class="ui-state-default" title="' . I18N::translate('Show couples who married more than 100 years ago.') . '" > ' . GedcomTag::getLabel('MARR') . '>100 </button> <button type="button" data-filter-column="11" data-filter-value="Y100" class="ui-state-default" title="' . I18N::translate('Show couples who married within the last 100 years.') . '" > ' . GedcomTag::getLabel('MARR') . '<=100 </button> <button type="button" data-filter-column="11" data-filter-value="D" class="ui-state-default" title="' . I18N::translate('Show divorced couples.') . '" > ' . GedcomTag::getLabel('DIV') . ' </button> <button type="button" data-filter-column="11" data-filter-value="M" class="ui-state-default" title="' . I18N::translate('Show couples where either partner married more than once.') . '" > ' . I18N::translate('Multiple marriages') . ' </button> </div> </div> </th> </tr> <tr> <th>' . GedcomTag::getLabel('GIVN') . '</th> <th>' . GedcomTag::getLabel('SURN') . '</th> <th>' . GedcomTag::getLabel('AGE') . '</th> <th>' . GedcomTag::getLabel('GIVN') . '</th> <th>' . GedcomTag::getLabel('SURN') . '</th> <th>' . GedcomTag::getLabel('AGE') . '</th> <th>' . GedcomTag::getLabel('MARR') . '</th> <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th> <th>' . GedcomTag::getLabel('PLAC') . '</th> <th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th> <th>' . GedcomTag::getLabel('CHAN') . '</th> <th hidden></th> <th hidden></th> <th hidden></th> </tr> </thead> <tfoot> <tr> <th colspan="14"> <div class="btn-toolbar"> <div class="btn-group"> <button type="button" class="ui-state-default btn-toggle-parents"> ' . I18N::translate('Show parents') . ' </button> <button type="button" class="ui-state-default btn-toggle-statistics"> ' . I18N::translate('Show statistics charts') . ' </button> </div> </div> </th> </tr> </tfoot> <tbody>'; $hundred_years_ago = new Date(date('Y') - 100); foreach ($families as $family) { // Retrieve husband and wife $husb = $family->getHusband(); if (is_null($husb)) { $husb = new Individual('H', '0 @H@ INDI', null, $family->getTree()); } $wife = $family->getWife(); if (is_null($wife)) { $wife = new Individual('W', '0 @W@ INDI', null, $family->getTree()); } if (!$family->canShow()) { continue; } if ($family->isPendingAddtion()) { $class = ' class="new"'; } elseif ($family->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= '<tr' . $class . '>'; // Husband name(s) // Extract Given names and Surnames for sorting list($surn_givn, $givn_surn) = self::sortableNames($husb); $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">'; foreach ($husb->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"'; } if ($num == $husb->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $husb->getSexImage(); } else { $class = ''; $sex_image = ''; } // Only show married names if they are the name we are filtering by. if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) { $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>'; } } // Husband parents $html .= $husb->getPrimaryParentsNames('parents details1', 'none'); $html .= '</td>'; // Hidden column for sortable name $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>'; // Husband age $mdate = $family->getMarriageDate(); $hdate = $husb->getBirthDate(); if ($hdate->isOK() && $mdate->isOK()) { if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) { $birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex(); } $hage = Date::getAge($hdate, $mdate, 0); if ($hage >= 0 && $hage <= $max_age) { $marr_by_age[$hage] .= $husb->getSex(); } } $html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>'; // Wife name(s) // Extract Given names and Surnames for sorting list($surn_givn, $givn_surn) = self::sortableNames($wife); $html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">'; foreach ($wife->getAllNames() as $num => $name) { if ($name['type'] == 'NAME') { $title = ''; } else { $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"'; } if ($num == $wife->getPrimaryName()) { $class = ' class="name2"'; $sex_image = $wife->getSexImage(); } else { $class = ''; $sex_image = ''; } // Only show married names if they are the name we are filtering by. if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) { $html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>'; } } // Wife parents $html .= $wife->getPrimaryParentsNames('parents details1', 'none'); $html .= '</td>'; // Hidden column for sortable name $html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>'; // Wife age $mdate = $family->getMarriageDate(); $wdate = $wife->getBirthDate(); if ($wdate->isOK() && $mdate->isOK()) { if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) { $birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex(); } $wage = Date::getAge($wdate, $mdate, 0); if ($wage >= 0 && $wage <= $max_age) { $marr_by_age[$wage] .= $wife->getSex(); } } $html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>'; // Marriage date $html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">'; if ($marriage_dates = $family->getAllMarriageDates()) { foreach ($marriage_dates as $n => $marriage_date) { if ($n) { $html .= '<br>'; } $html .= '<div>' . $marriage_date->display(true) . '</div>'; } if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) { $marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex(); } } elseif ($family->getFacts('_NMR')) { $html .= I18N::translate('no'); } elseif ($family->getFacts('MARR')) { $html .= I18N::translate('yes'); } else { $html .= ' '; } $html .= '</td>'; // Marriage anniversary $html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>'; // Marriage place $html .= '<td>'; foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) { $tmp = new Place($marriage_place, $family->getTree()); if ($n) { $html .= '<br>'; } $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">'; $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>'; } $html .= '</td>'; // Number of children $html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>'; // Last change $html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>'; // Filter by marriage date $html .= '<td hidden>'; if (!$family->canShow() || !$mdate->isOK()) { $html .= 'U'; } else { if (Date::compare($mdate, $hundred_years_ago) > 0) { $html .= 'Y100'; } else { $html .= 'YES'; } } if ($family->getFacts(WT_EVENTS_DIV)) { $html .= 'D'; } if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) { $html .= 'M'; } $html .= '</td>'; // Filter by alive/dead $html .= '<td hidden>'; if ($husb->isDead() && $wife->isDead()) { $html .= 'Y'; } if ($husb->isDead() && !$wife->isDead()) { if ($wife->getSex() == 'F') { $html .= 'H'; } if ($wife->getSex() == 'M') { $html .= 'W'; } // male partners } if (!$husb->isDead() && $wife->isDead()) { if ($husb->getSex() == 'M') { $html .= 'W'; } if ($husb->getSex() == 'F') { $html .= 'H'; } // female partners } if (!$husb->isDead() && !$wife->isDead()) { $html .= 'N'; } $html .= '</td>'; // Filter by roots/leaves $html .= '<td hidden>'; if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) { $html .= 'R'; } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) { $html .= 'L'; } $html .= '</td> </tr>'; } $html .= ' </tbody> </table> <div id="fam_list_table-charts_' . $table_id . '" style="display:none"> <table class="list-charts"> <tr> <td>' . self::chartByDecade($birt_by_decade, I18N::translate('Decade of birth')) . '</td> <td>' . self::chartByDecade($marr_by_decade, I18N::translate('Decade of marriage')) . '</td> </tr> <tr> <td colspan="2">' . self::chartByAge($marr_by_age, I18N::translate('Age in year of marriage')) . '</td> </tr> </table> </div> </div>'; return $html; }