. */ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Database; use Fisharebest\Webtrees\Date; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Filter; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Place; use Fisharebest\Webtrees\Repository; use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Stats; use Fisharebest\Webtrees\Tree; use Rhumsaa\Uuid\Uuid; /** * Print a table of individuals * * @param Individual[] $datalist * @param string $option * * @return string */ function format_indi_table($datalist, $option = '') { global $controller, $WT_TREE; $table_id = 'table-indi-' . 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.fn.dataTableExt.oSort["num-html-asc" ]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (ab ? 1 : 0);}; jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (aT<"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 givn */ { dataSort: 2 }, /* 1 surn */ { dataSort: 3 }, /* 2 GIVN,SURN */ { type: "unicode", visible: false }, /* 3 SURN,GIVN */ { type: "unicode", visible: false }, /* 4 sosa */ { dataSort: 5, class: "center", visible: ' . ($option == 'sosa' ? 'true' : 'false') . ' }, /* 5 SOSA */ { type: "num", visible: false }, /* 6 birt date */ { dataSort: 7 }, /* 7 BIRT:DATE */ { visible: false }, /* 8 anniv */ { dataSort: 7, class: "center" }, /* 9 birt plac */ { type: "unicode" }, /* 10 children */ { dataSort: 11, class: "center" }, /* 11 children */ { type: "num", visible: false }, /* 12 deat date */ { dataSort: 13 }, /* 13 DEAT:DATE */ { visible: false }, /* 14 anniv */ { dataSort: 13, class: "center" }, /* 15 age */ { dataSort: 16, class: "center" }, /* 16 AGE */ { type: "num", visible: false }, /* 17 deat plac */ { type: "unicode" }, /* 18 CHAN */ { dataSort: 19, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* 19 CHAN_sort */ { visible: false }, /* 20 SEX */ { visible: false }, /* 21 BIRT */ { visible: false }, /* 22 DEAT */ { visible: false }, /* 23 TREE */ { visible: false } ], sorting: [[' . ($option == 'sosa' ? '4, "asc"' : '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("#indi_list_table-charts_' . $table_id . '").slideToggle(); }) /* Filter buttons in table header */ .on("click", "button[data-filter-column]", function() { var btn = jQuery(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(".indi-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); $stats = new Stats($WT_TREE); // Bad data can cause "longest life" to be huge, blowing memory limits $max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->LongestLifeAge()) + 1; // Inititialise chart data $deat_by_age = array(); for ($age = 0; $age <= $max_age; $age++) { $deat_by_age[$age] = ''; } $birt_by_decade = array(); $deat_by_decade = array(); for ($year = 1550; $year < 2030; $year += 10) { $birt_by_decade[$year] = ''; $deat_by_decade[$year] = ''; } $html = '
 
'; $d100y = new Date(date('Y') - 100); // 100 years ago $unique_indis = array(); // Don't double-count indis with multiple names. foreach ($datalist as $key => $person) { if (!$person->canShowName()) { continue; } if ($person->isPendingAddtion()) { $class = ' class="new"'; } elseif ($person->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= ''; //-- Indi name(s) $html .= ''; // Dummy column to match colspan in header $html .= ''; //-- GIVN/SURN // 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 .= ''; $html .= ''; //-- SOSA if ($option == 'sosa') { $html .= ''; } else { $html .= ''; } //-- Birth date $html .= ''; //-- Event date (sortable)hidden by datatables code $html .= ''; //-- Birth anniversary $html .= ''; //-- Birth place $html .= ''; //-- Number of children $nchi = $person->getNumberOfChildren(); $html .= ''; //-- Death date $html .= ''; //-- Event date (sortable)hidden by datatables code $html .= ''; //-- Death anniversary $html .= ''; //-- Age at death $age = Date::getAge($birth_dates[0], $death_dates[0], 0); if (!isset($unique_indis[$person->getXref()]) && $age >= 0 && $age <= $max_age) { $deat_by_age[$age] .= $person->getSex(); } // Need both display and sortable age $html .= ''; //-- Death place $html .= ''; //-- Last change $html .= ''; $html .= ''; //-- Sorting by gender $html .= ''; //-- Filtering by birth date $html .= ''; //-- Filtering by death date $html .= ''; //-- Roots or Leaves ? $html .= ''; $html .= ''; $unique_indis[$person->getXref()] = true; } $html .= '
' . GedcomTag::getLabel('GIVN') . ' ' . GedcomTag::getLabel('SURN') . ' GIVN SURN ' . /* I18N: Abbreviation for “Sosa-Stradonitz number”. This is an individual’s surname, so may need transliterating into non-latin alphabets. */ I18N::translate('Sosa') . ' SOSA ' . GedcomTag::getLabel('BIRT') . ' SORT_BIRT ' . GedcomTag::getLabel('PLAC') . ' NCHI ' . GedcomTag::getLabel('DEAT') . ' SORT_DEAT ' . GedcomTag::getLabel('AGE') . ' AGE ' . GedcomTag::getLabel('PLAC') . ' ' . GedcomTag::getLabel('CHAN') . ' CHAN SEX BIRT DEAT TREE
'; 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 = ''; } $html .= '' . highlight_search_hits($name['full']) . '' . $sex_image . '
'; } // Indi parents $html .= $person->getPrimaryParentsNames('parents details1', 'none'); $html .= '
' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '' . I18N::number($key) . '' . $key . '0'; if ($birth_dates = $person->getAllBirthDates()) { foreach ($birth_dates as $num => $birth_date) { if ($num) { $html .= '
'; } $html .= $birth_date->display(true); } if ($birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) { $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex(); } } else { $birth_date = $person->getEstimatedBirthDate(); if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES')) { $html .= $birth_date->display(true); } else { $html .= ' '; } $birth_dates[0] = new Date(''); } $html .= '
' . $birth_date->julianDay() . '' . Date::getAge($birth_dates[0], null, 2) . ''; foreach ($person->getAllBirthPlaces() as $n => $birth_place) { $tmp = new Place($birth_place, $person->getTree()); if ($n) { $html .= '
'; } $html .= ''; $html .= highlight_search_hits($tmp->getShortName()) . ''; } $html .= '
' . I18N::number($nchi) . '' . $nchi . ''; if ($death_dates = $person->getAllDeathDates()) { foreach ($death_dates as $num => $death_date) { if ($num) { $html .= '
'; } $html .= $death_date->display(true); } if ($death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) { $deat_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex(); } } else { $death_date = $person->getEstimatedDeathDate(); // Estimated death dates are a fixed number of years after the birth date. // Don't show estimates in the future. if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES') && $death_date->minimumJulianDay() < WT_CLIENT_JD) { $html .= $death_date->display(true); } elseif ($person->isDead()) { $html .= I18N::translate('yes'); } else { $html .= ' '; } $death_dates[0] = new Date(''); } $html .= '
' . $death_date->julianDay() . '' . Date::getAge($death_dates[0], null, 2) . '' . Date::getAge($birth_dates[0], $death_dates[0], 2) . '' . Date::getAge($birth_dates[0], $death_dates[0], 1) . ''; foreach ($person->getAllDeathPlaces() as $n => $death_place) { $tmp = new Place($death_place, $person->getTree()); if ($n) { $html .= '
'; } $html .= ''; $html .= highlight_search_hits($tmp->getShortName()) . ''; } $html .= '
' . $person->lastChangeTimestamp() . '' . $person->lastChangeTimestamp(true) . '' . $person->getSex() . ''; if (!$person->canShow() || Date::compare($birth_date, $d100y) > 0) { $html .= 'Y100'; } else { $html .= 'YES'; } $html .= ''; // Died in last 100 years? Died? Not dead? if (Date::compare($death_dates[0], $d100y) > 0) { $html .= 'Y100'; } elseif ($death_dates[0]->minimumJulianDay() || $person->isDead()) { $html .= 'YES'; } else { $html .= 'N'; } $html .= ''; if (!$person->getChildFamilies()) { $html .= 'R'; } // roots elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) { $html .= 'L'; } // leaves else { $html .= ' '; } $html .= '
'; return $html; } /** * Print a table of families * * @param Family[] $datalist * * @return string */ function format_fam_table($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 = '
 
'; $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 .= ''; //-- Husband name(s) $html .= ''; // Dummy column to match colspan in header $html .= ''; //-- 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 .= ''; $html .= ''; $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 .= ''; //-- Wife name(s) $html .= ''; // Dummy column to match colspan in header $html .= ''; //-- 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 .= ''; $html .= ''; $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 .= ''; //-- Marriage date $html .= ''; //-- Event date (sortable)hidden by datatables code $html .= ''; //-- Marriage anniversary $html .= ''; //-- Marriage place $html .= ''; //-- Number of children $nchi = $family->getNumberOfChildren(); $html .= ''; //-- Last change $html .= ''; $html .= ''; //-- Sorting by marriage date $html .= ''; //-- Sorting alive/dead $html .= ''; //-- Roots or Leaves $html .= ''; } $html .= '
' . GedcomTag::getLabel('GIVN') . ' ' . GedcomTag::getLabel('SURN') . ' HUSB:GIVN_SURN HUSB:SURN_GIVN ' . GedcomTag::getLabel('AGE') . ' AGE ' . GedcomTag::getLabel('GIVN') . ' ' . GedcomTag::getLabel('SURN') . ' WIFE:GIVN_SURN WIFE:SURN_GIVN ' . GedcomTag::getLabel('AGE') . ' AGE ' . GedcomTag::getLabel('MARR') . ' MARR:DATE ' . GedcomTag::getLabel('PLAC') . ' NCHI ' . GedcomTag::getLabel('CHAN') . ' CHAN MARR DEAT TREE
'; 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 .= '' . highlight_search_hits($name['full']) . '' . $sex_image . '
'; } } // Husband parents $html .= $husb->getPrimaryParentsNames('parents details1', 'none'); $html .= '
' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '' . Date::getAge($hdate, $mdate, 2) . '' . Date::getAge($hdate, $mdate, 1) . ''; 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 .= '' . highlight_search_hits($name['full']) . '' . $sex_image . '
'; } } // Wife parents $html .= $wife->getPrimaryParentsNames('parents details1', 'none'); $html .= '
' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '' . Date::getAge($wdate, $mdate, 2) . '' . Date::getAge($wdate, $mdate, 1) . ''; if ($marriage_dates = $family->getAllMarriageDates()) { foreach ($marriage_dates as $n => $marriage_date) { if ($n) { $html .= '
'; } $html .= '
' . $marriage_date->display(true) . '
'; } 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 .= '
'; if ($marriage_dates) { $html .= $marriage_date->julianDay(); } else { $html .= 0; } $html .= '' . Date::getAge($mdate, null, 2) . ''; foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) { $tmp = new Place($marriage_place, $family->getTree()); if ($n) { $html .= '
'; } $html .= ''; $html .= highlight_search_hits($tmp->getShortName()) . ''; } $html .= '
' . I18N::number($nchi) . '' . $nchi . '' . $family->LastChangeTimestamp() . '' . $family->LastChangeTimestamp(true) . ''; 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 .= ''; 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 .= ''; if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) { $html .= 'R'; } elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() < 1) { $html .= 'L'; } else { $html .= ' '; } $html .= '
'; return $html; } /** * Print a table of sources * * @param Source[] $datalist * * @return string */ function format_sour_table($datalist) { global $WT_TREE, $controller; // Count the number of linked records. These numbers include private records. // It is not good to bypass privacy, but many servers do not have the resources // to process privacy for every record in the tree $count_individuals = Database::prepare( "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##individuals` JOIN `##link` ON l_from = i_id AND l_file = i_file AND l_type = 'SOUR' GROUP BY l_to, l_file" )->fetchAssoc(); $count_families = Database::prepare( "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##families` JOIN `##link` ON l_from = f_id AND l_file = f_file AND l_type = 'SOUR' GROUP BY l_to, l_file" )->fetchAssoc(); $count_media = Database::prepare( "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##media` JOIN `##link` ON l_from = m_id AND l_file = m_file AND l_type = 'SOUR' GROUP BY l_to, l_file" )->fetchAssoc(); $count_notes = Database::prepare( "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##other` JOIN `##link` ON l_from = o_id AND l_file = o_file AND o_type = 'NOTE' AND l_type = 'SOUR' GROUP BY l_to, l_file" )->fetchAssoc(); $html = ''; $table_id = 'table-sour-' . 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"pf<"dt-clear">irl>t<"F"pl>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth: false, processing: true, columns: [ /* 0 title */ { dataSort: 1 }, /* 1 TITL */ { visible: false, type: "unicode" }, /* 2 author */ { type: "unicode" }, /* 3 #indi */ { dataSort: 4, class: "center" }, /* 4 #INDI */ { type: "num", visible: false }, /* 5 #fam */ { dataSort: 6, class: "center" }, /* 6 #FAM */ { type: "num", visible: false }, /* 7 #obje */ { dataSort: 8, class: "center" }, /* 8 #OBJE */ { type: "num", visible: false }, /* 9 #note */ { dataSort: 10, class: "center" }, /* 10 #NOTE */ { type: "num", visible: false }, /* 11 CHAN */ { dataSort: 12, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* 12 CHAN_sort */ { visible: false }, /* 13 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false } ], displayLength: 20, pagingType: "full_numbers" }); jQuery(".source-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); //--table wrapper $html .= '
 
'; $html .= '
'; //-- table header $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN'; $html .= ''; //delete $html .= ''; //-- table body $html .= ''; foreach ($datalist as $source) { if (!$source->canShow()) { continue; } if ($source->isPendingAddtion()) { $class = ' class="new"'; } elseif ($source->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= ''; //-- Source name(s) $html .= ''; // Sortable name $html .= ''; //-- Author $auth = $source->getFirstFact('AUTH'); if ($auth) { $author = $auth->getValue(); } else { $author = ''; } $html .= ''; $key = $source->getXref() . '@' . $source->getTree()->getTreeId(); //-- Linked INDIs $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0; $html .= ''; //-- Linked FAMs $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0; $html .= ''; //-- Linked OBJEcts $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0; $html .= ''; //-- Linked NOTEs $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0; $html .= ''; //-- Last change if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Last change hidden sort column if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Delete if (Auth::isManager($WT_TREE)) { $html .= ''; } else { $html .= ''; } $html .= ''; } $html .= '
' . GedcomTag::getLabel('TITL') . 'TITL' . GedcomTag::getLabel('AUTH') . '' . I18N::translate('Individuals') . '#INDI' . I18N::translate('Families') . '#FAM' . I18N::translate('Media objects') . '#OBJE' . I18N::translate('Shared notes') . '#NOTE
'; foreach ($source->getAllNames() as $n => $name) { if ($n) { $html .= '
'; } if ($n == $source->getPrimaryName()) { $html .= '' . highlight_search_hits($name['full']) . ''; } else { $html .= '' . highlight_search_hits($name['full']) . ''; } } $html .= '
' . strip_tags($source->getFullName()) . '' . highlight_search_hits($author) . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . $source->LastChangeTimestamp() . '' . $source->LastChangeTimestamp(true) . '
getXref() . '\');">' . I18N::translate('Delete') . '
'; return $html; } /** * Print a table of shared notes * * @param Note[] $datalist * * @return string */ function format_note_table($datalist) { global $WT_TREE, $controller; $html = ''; $table_id = 'table-note-' . 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"pf<"dt-clear">irl>t<"F"pl>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth: false, processing: true, columns: [ /* 0 title */ { type: "unicode" }, /* 1 #indi */ { dataSort: 2, class: "center" }, /* 2 #INDI */ { type: "num", visible: false }, /* 3 #fam */ { dataSort: 4, class: "center" }, /* 4 #FAM */ { type: "num", visible: false }, /* 5 #obje */ { dataSort: 6, class: "center" }, /* 6 #OBJE */ { type: "num", visible: false }, /* 7 #sour */ { dataSort: 8, class: "center" }, /* 8 #SOUR */ { type: "num", visible: false }, /* 9 CHAN */ { dataSort: 10, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* 10 CHAN_sort */ { visible: false }, /* 11 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false } ], displayLength: 20, pagingType: "full_numbers" }); jQuery(".note-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); //--table wrapper $html .= '
 
'; $html .= '
'; //-- table header $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN'; $html .= ''; //delete $html .= ''; //-- table body $html .= ''; foreach ($datalist as $note) { if (!$note->canShow()) { continue; } if ($note->isPendingAddtion()) { $class = ' class="new"'; } elseif ($note->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= ''; //-- Shared Note name $html .= ''; //-- Linked INDIs $num = count($note->linkedIndividuals('NOTE')); $html .= ''; //-- Linked FAMs $num = count($note->linkedfamilies('NOTE')); $html .= ''; //-- Linked OBJEcts $num = count($note->linkedMedia('NOTE')); $html .= ''; //-- Linked SOURs $num = count($note->linkedSources('NOTE')); $html .= ''; //-- Last change if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Last change hidden sort column if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Delete if (Auth::isManager($WT_TREE)) { $html .= ''; } else { $html .= ''; } $html .= ''; } $html .= '
' . GedcomTag::getLabel('TITL') . '' . I18N::translate('Individuals') . '#INDI' . I18N::translate('Families') . '#FAM' . I18N::translate('Media objects') . '#OBJE' . I18N::translate('Sources') . '#SOUR
' . highlight_search_hits($note->getFullName()) . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . $note->LastChangeTimestamp() . '' . $note->LastChangeTimestamp(true) . '
getXref() . '\');">' . I18N::translate('Delete') . '
'; return $html; } /** * Print a table of repositories * * @param Repository[] $repositories * * @return string */ function format_repo_table($repositories) { global $WT_TREE, $controller; // Count the number of linked records. These numbers include private records. // It is not good to bypass privacy, but many servers do not have the resources // to process privacy for every record in the tree $count_sources = Database::prepare( "SELECT CONCAT(l_to, '@', l_file), COUNT(*) FROM `##sources` JOIN `##link` ON l_from = s_id AND l_file = s_file AND l_type = 'REPO' GROUP BY l_to, l_file" )->fetchAssoc(); $html = ''; $table_id = 'table-repo-' . 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"pf<"dt-clear">irl>t<"F"pl>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth: false, processing: true, columns: [ /* 0 name */ { type: "unicode" }, /* 1 #sour */ { dataSort: 2, class: "center" }, /* 2 #SOUR */ { type: "num", visible: false }, /* 3 CHAN */ { dataSort: 4, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* 4 CHAN_sort */ { visible: false }, /* 5 DELETE */ { visible: ' . (Auth::isManager($WT_TREE) ? 'true' : 'false') . ', sortable: false } ], displayLength: 20, pagingType: "full_numbers" }); jQuery(".repo-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); //--table wrapper $html .= '
 
'; $html .= '
'; //-- table header $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN'; $html .= ''; //delete $html .= ''; //-- table body $html .= ''; foreach ($repositories as $repository) { if (!$repository->canShow()) { continue; } if ($repository->isPendingAddtion()) { $class = ' class="new"'; } elseif ($repository->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= ''; //-- Repository name(s) $html .= ''; $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId(); //-- Linked SOURces $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0; $html .= ''; //-- Last change if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Last change hidden sort column if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Delete if (Auth::isManager($WT_TREE)) { $html .= ''; } else { $html .= ''; } $html .= ''; } $html .= '
' . I18N::translate('Repository name') . '' . I18N::translate('Sources') . '#SOUR
'; foreach ($repository->getAllNames() as $n => $name) { if ($n) { $html .= '
'; } if ($n == $repository->getPrimaryName()) { $html .= '' . highlight_search_hits($name['full']) . ''; } else { $html .= '' . highlight_search_hits($name['full']) . ''; } } $html .= '
' . I18N::number($num) . '' . $num . '' . $repository->LastChangeTimestamp() . '' . $repository->LastChangeTimestamp(true) . '
getXref() . '\');">' . I18N::translate('Delete') . '
'; return $html; } /** * Print a table of media objects * * @param Media[] $media_objects * * @return string */ function format_media_table($media_objects) { global $WT_TREE, $controller; $html = ''; $table_id = 'table-obje-' . 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"pf<"dt-clear">irl>t<"F"pl>\', ' . I18N::datatablesI18N() . ', jQueryUI: true, autoWidth:false, processing: true, columns: [ /* 0 media */ { sortable: false }, /* 1 title */ { type: "unicode" }, /* 2 #indi */ { dataSort: 3, class: "center" }, /* 3 #INDI */ { type: "num", visible: false }, /* 4 #fam */ { dataSort: 5, class: "center" }, /* 5 #FAM */ { type: "num", visible: false }, /* 6 #sour */ { dataSort: 7, class: "center" }, /* 7 #SOUR */ { type: "num", visible: false }, /* 8 CHAN */ { dataSort: 9, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' }, /* 9 CHAN_sort */ { visible: false }, ], displayLength: 20, pagingType: "full_numbers" }); jQuery(".media-list").css("visibility", "visible"); jQuery(".loading-image").css("display", "none"); '); //--table wrapper $html .= '
 
'; $html .= '
'; //-- table header $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . ''; $html .= 'getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN'; $html .= ''; //-- table body $html .= ''; foreach ($media_objects as $media_object) { if ($media_object->canShow()) { $name = $media_object->getFullName(); if ($media_object->isPendingAddtion()) { $class = ' class="new"'; } elseif ($media_object->isPendingDeletion()) { $class = ' class="old"'; } else { $class = ''; } $html .= ''; //-- Object thumbnail $html .= ''; //-- Object name(s) $html .= ''; //-- Linked INDIs $num = count($media_object->linkedIndividuals('OBJE')); $html .= ''; //-- Linked FAMs $num = count($media_object->linkedfamilies('OBJE')); $html .= ''; //-- Linked SOURces $num = count($media_object->linkedSources('OBJE')); $html .= ''; //-- Last change if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } //-- Last change hidden sort column if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) { $html .= ''; } else { $html .= ''; } $html .= ''; } } $html .= '
' . I18N::translate('Media') . '' . GedcomTag::getLabel('TITL') . '' . I18N::translate('Individuals') . '#INDI' . I18N::translate('Families') . '#FAM' . I18N::translate('Sources') . '#SOUR
' . $media_object->displayImage() . ''; $html .= ''; $html .= highlight_search_hits($name) . ''; if (Auth::isEditor($media_object->getTree())) { $html .= '
' . basename($media_object->getFilename()) . ''; } $html .= '
' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . I18N::number($num) . '' . $num . '' . $media_object->LastChangeTimestamp() . '' . $media_object->LastChangeTimestamp(true) . '
'; return $html; } /** * Print a table of surnames, for the top surnames block, the indi/fam lists, etc. * * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID) * @param string $script "indilist.php" (counts of individuals) or "famlist.php" (counts of spouses) * @param Tree $tree generate links for this tree * * @return string */ function format_surname_table($surnames, $script, Tree $tree) { global $controller; $html = ''; $controller ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL) ->addInlineJavascript(' jQuery.fn.dataTableExt.oSort["num-asc" ]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (ab ? 1 : 0);}; jQuery.fn.dataTableExt.oSort["num-desc"]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (a>b) ? -1 : (a' . '' . GedcomTag::getLabel('SURN') . '' . '' . '' . $col_heading . '' . '' . ''; $html .= ''; foreach ($surnames as $surn => $surns) { // Each surname links back to the indi/fam surname list if ($surn) { $url = $script . '?surname=' . rawurlencode($surn) . '&ged=' . $tree->getNameUrl(); } else { $url = $script . '?alpha=,&ged=' . $tree->getNameUrl(); } // Row counter $html .= ''; // Surname $html .= ''; // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc. foreach ($surns as $spfxsurn => $indis) { if ($spfxsurn) { $html .= '' . Filter::escapeHtml($spfxsurn) . '
'; } else { // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc. $html .= '' . Filter::escapeHtml($surn) . '
'; } } $html .= ''; // Sort column for name $html .= '' . $surn . ''; // Surname count $html .= ''; $subtotal = 0; foreach ($surns as $indis) { $subtotal += count($indis); $html .= I18N::number(count($indis)) . '
'; } // More than one surname variant? Show a subtotal if (count($surns) > 1) { $html .= I18N::number($subtotal); } $html .= ''; // add hidden numeric sort column $html .= '' . $subtotal . ''; } $html .= ''; return $html; } /** * Print a tagcloud of surnames. * * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID) * @param string $script indilist or famlist * @param bool $totals show totals after each name * @param Tree $tree generate links to this tree * * @return string */ function format_surname_tagcloud($surnames, $script, $totals, Tree $tree) { global $WT_TREE; $minimum = PHP_INT_MAX; $maximum = 1; foreach ($surnames as $surn => $surns) { foreach ($surns as $spfxsurn => $indis) { $maximum = max($maximum, count($indis)); $minimum = min($minimum, count($indis)); } } $html = ''; foreach ($surnames as $surn => $surns) { foreach ($surns as $spfxsurn => $indis) { $size = 75.0 + 125.0 * (count($indis) - $minimum) / ($maximum - $minimum); $html .= '' . $spfxsurn . '', I18N::number(count($indis))); } else { $html .= $spfxsurn; } $html .= ' '; } } return '
' . $html . '
'; } /** * Print a list of surnames. * * @param string[][] $surnames array (of SURN, of array of SPFX_SURN, of array of PID) * @param int $style 1=bullet list, 2=semicolon-separated list, 3=tabulated list with up to 4 columns * @param bool $totals show totals after each name * @param string $script indilist or famlist * @param Tree $tree Link back to the individual list in this tree * * @return string */ function format_surname_list($surnames, $style, $totals, $script, Tree $tree) { $html = array(); foreach ($surnames as $surn => $surns) { // Each surname links back to the indilist if ($surn) { $url = $script . '?surname=' . urlencode($surn) . '&ged=' . $tree->getNameUrl(); } else { $url = $script . '?alpha=,&ged=' . $tree->getNameUrl(); } // If all the surnames are just case variants, then merge them into one // Comment out this block if you want SMITH listed separately from Smith $first_spfxsurn = null; foreach ($surns as $spfxsurn => $indis) { if ($first_spfxsurn) { if (I18N::strtoupper($spfxsurn) == I18N::strtoupper($first_spfxsurn)) { $surns[$first_spfxsurn] = array_merge($surns[$first_spfxsurn], $surns[$spfxsurn]); unset($surns[$spfxsurn]); } } else { $first_spfxsurn = $spfxsurn; } } $subhtml = '' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . ''; if ($totals) { $subtotal = 0; foreach ($surns as $indis) { $subtotal += count($indis); } $subhtml .= ' (' . I18N::number($subtotal) . ')'; } $html[] = $subhtml; } switch ($style) { case 1: return '
  • ' . implode('
  • ', $html) . '
'; case 2: return implode(I18N::$list_separator, $html); case 3: $i = 0; $count = count($html); if ($count > 36) { $col = 4; } elseif ($count > 18) { $col = 3; } elseif ($count > 6) { $col = 2; } else { $col = 1; } $newcol = ceil($count / $col); $html2 = ''; $html2 .= '
'; foreach ($html as $surns) { $html2 .= $surns . '
'; $i++; if ($i == $newcol && $i < $count) { $html2 .= '
'; $newcol = $i + ceil($count / $col); } } $html2 .= '
'; return $html2; } } /** * Print a table of events * * @param string[] $change_ids * @param string $sort * * @return string */ function print_changes_list($change_ids, $sort) { global $WT_TREE; $n = 0; $arr = array(); foreach ($change_ids as $change_id) { $record = GedcomRecord::getInstance($change_id, $WT_TREE); if (!$record || !$record->canShow()) { continue; } // setup sorting parameters $arr[$n]['record'] = $record; $arr[$n]['jd'] = ($sort == 'name') ? 1 : $n; $arr[$n]['anniv'] = $record->lastChangeTimestamp(true); $arr[$n++]['fact'] = $record->getSortName(); // in case two changes have same timestamp } switch ($sort) { case 'name': uasort($arr, 'event_sort_name'); break; case 'date_asc': uasort($arr, 'event_sort'); $arr = array_reverse($arr); break; case 'date_desc': uasort($arr, 'event_sort'); } $html = ''; foreach ($arr as $value) { $html .= '' . $value['record']->getFullName() . ''; $html .= '
'; if ($value['record'] instanceof Individual) { if ($value['record']->getAddName()) { $html .= '' . $value['record']->getAddName() . ''; } } $html .= /* I18N: [a record was] Changed on by */ I18N::translate('Changed on %1$s by %2$s', $value['record']->lastChangeTimestamp(), Filter::escapeHtml($value['record']->lastChangeUser())); $html .= '
'; } return $html; } /** * Print a table of events * * @param string[] $change_ids * @param string $sort * * @return string */ function print_changes_table($change_ids, $sort) { global $controller, $WT_TREE; $n = 0; $table_id = 'table-chan-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page switch ($sort) { case 'name': //name $aaSorting = "[5,'asc'], [4,'desc']"; break; case 'date_asc': //date ascending $aaSorting = "[4,'asc'], [5,'asc']"; break; case 'date_desc': //date descending $aaSorting = "[4,'desc'], [5,'asc']"; break; } $html = ''; $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: \'t\', paging: false, autoWidth:false, lengthChange: false, filter: false, ' . I18N::datatablesI18N() . ', jQueryUI: true, sorting: [' . $aaSorting . '], columns: [ /* 0-Type */ { sortable: false, class: "center" }, /* 1-Record */ { dataSort: 5 }, /* 2-Change */ { dataSort: 4 }, /* 3-By */ null, /* 4-DATE */ { visible: false }, /* 5-SORTNAME */{ type: "unicode", visible: false } ] }); '); //-- table header $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //hidden by datatables code $html .= ''; //hidden by datatables code $html .= ''; //-- table body foreach ($change_ids as $change_id) { $record = GedcomRecord::getInstance($change_id, $WT_TREE); if (!$record || !$record->canShow()) { continue; } $html .= ''; ++$n; //-- Record name(s) $name = $record->getFullName(); $html .= '"; //-- Last change date/time $html .= ''; //-- Last change user $html .= ''; //-- change date (sortable) hidden by datatables code $html .= ''; //-- names (sortable) hidden by datatables code $html .= ''; } $html .= '
' . I18N::translate('Record') . '' . GedcomTag::getLabel('CHAN') . '' . GedcomTag::getLabel('_WT_USER') . 'DATESORTNAME
'; switch ($record::RECORD_TYPE) { case 'INDI': $icon = $record->getSexImage('small'); break; case 'FAM': $icon = ''; break; case 'OBJE': $icon = ''; break; case 'NOTE': $icon = ''; break; case 'SOUR': $icon = ''; break; case 'REPO': $icon = ''; break; default: $icon = ' '; break; } $html .= '' . $icon . ''; $html .= ''; $html .= '' . $name . ''; if ($record instanceof Individual) { $addname = $record->getAddName(); if ($addname) { $html .= ''; } } $html .= "' . $record->lastChangeTimestamp() . '' . Filter::escapeHtml($record->lastChangeUser()) . '' . $record->lastChangeTimestamp(true) . '' . $record->getSortName() . '
'; return $html; } /** * Print a table of events * * @param int $startjd * @param int $endjd * @param string $events * @param bool $only_living * @param string $sort_by * * @return string */ function print_events_table($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') { global $controller, $WT_TREE; $html = ''; $table_id = 'table-even-' . Uuid::uuid4(); // lists requires a unique ID in case there are multiple lists per page $controller ->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL) ->addInlineJavascript(' jQuery("#' . $table_id . '").dataTable({ dom: \'t\', ' . I18N::datatablesI18N() . ', autoWidth: false, paging: false, lengthChange: false, filter: false, info: true, jQueryUI: true, sorting: [[ ' . ($sort_by == 'alpha' ? 1 : 3) . ', "asc"]], columns: [ /* 0-Record */ { dataSort: 1 }, /* 1-NAME */ { visible: false }, /* 2-Date */ { dataSort: 3 }, /* 3-DATE */ { visible: false }, /* 4-Anniv. */ { dataSort: 5, class: "center" }, /* 5-ANNIV */ { type: "num", visible: false }, /* 6-Event */ { class: "center" } ] }); '); // Did we have any output? Did we skip anything? $output = 0; $filter = 0; $filtered_events = array(); foreach (get_events_list($startjd, $endjd, $events, $WT_TREE) as $fact) { $record = $fact->getParent(); //-- only living people ? if ($only_living) { if ($record instanceof Individual && $record->isDead()) { $filter++; continue; } if ($record instanceof Family) { $husb = $record->getHusband(); if (is_null($husb) || $husb->isDead()) { $filter++; continue; } $wife = $record->getWife(); if (is_null($wife) || $wife->isDead()) { $filter++; continue; } } } //-- Counter $output++; if ($output == 1) { //-- table body $html .= ''; $html .= ''; $html .= ''; $html .= ''; //hidden by datatables code $html .= ''; $html .= ''; //hidden by datatables code $html .= ''; $html .= ''; $html .= ''; $html .= ''; } $filtered_events[] = $fact; } foreach ($filtered_events as $n => $fact) { $record = $fact->getParent(); $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } if ($output != 0) { $html .= '
' . I18N::translate('Record') . 'NAME' . GedcomTag::getLabel('DATE') . 'DATEANNIV' . GedcomTag::getLabel('EVEN') . '
'; $html .= '' . $record->getFullName() . ''; if ($record instanceof Individual) { $html .= $record->getSexImage(); } $html .= '' . $record->getSortName() . '' . $fact->getDate()->display(!Auth::isSearchEngine()) . '' . $n . '' . I18N::number($fact->anniv) . '' . $fact->anniv . '' . $fact->getLabel() . '
'; } // Print a final summary message about restricted/filtered facts $summary = ''; if ($endjd == WT_CLIENT_JD) { // We're dealing with the Today’s Events block if ($output == 0) { if ($filter == 0) { $summary = I18N::translate('No events exist for today.'); } else { $summary = I18N::translate('No events for living individuals exist for today.'); } } } else { // We're dealing with the Upcoming Events block if ($output == 0) { if ($filter == 0) { if ($endjd == $startjd) { $summary = I18N::translate('No events exist for tomorrow.'); } else { // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1)); } } else { if ($endjd == $startjd) { $summary = I18N::translate('No events for living individuals exist for tomorrow.'); } else { // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1)); } } } } if ($summary != "") { $html .= '' . $summary . ''; } return $html; } /** * Print a list of events * * This performs the same function as print_events_table(), but formats the output differently. * * @param int $startjd * @param int $endjd * @param string $events * @param bool $only_living * @param string $sort_by * * @return string */ function print_events_list($startjd, $endjd, $events = 'BIRT MARR DEAT', $only_living = false, $sort_by = 'anniv') { global $WT_TREE; // Did we have any output? Did we skip anything? $output = 0; $filter = 0; $filtered_events = array(); $html = ''; foreach (get_events_list($startjd, $endjd, $events, $WT_TREE) as $fact) { $record = $fact->getParent(); //-- only living people ? if ($only_living) { if ($record instanceof Individual && $record->isDead()) { $filter++; continue; } if ($record instanceof Family) { $husb = $record->getHusband(); if (is_null($husb) || $husb->isDead()) { $filter++; continue; } $wife = $record->getWife(); if (is_null($wife) || $wife->isDead()) { $filter++; continue; } } } $output++; $filtered_events[] = $fact; } // Now we've filtered the list, we can sort by event, if required switch ($sort_by) { case 'anniv': // Data is already sorted by anniversary date break; case 'alpha': uasort($filtered_events, function (Fact $x, Fact $y) { return GedcomRecord::compare($x->getParent(), $y->getParent()); }); break; } foreach ($filtered_events as $fact) { $record = $fact->getParent(); $html .= '' . $record->getFullName() . ''; if ($record instanceof Individual) { $html .= $record->getSexImage(); } $html .= '
'; $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true); if ($fact->anniv) { $html .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')'; } if (!$fact->getPlace()->isEmpty()) { $html .= ' — ' . $fact->getPlace()->getFullName() . ''; } $html .= '
'; } // Print a final summary message about restricted/filtered facts $summary = ''; if ($endjd == WT_CLIENT_JD) { // We're dealing with the Today’s Events block if ($output == 0) { if ($filter == 0) { $summary = I18N::translate('No events exist for today.'); } else { $summary = I18N::translate('No events for living individuals exist for today.'); } } } else { // We're dealing with the Upcoming Events block if ($output == 0) { if ($filter == 0) { if ($endjd == $startjd) { $summary = I18N::translate('No events exist for tomorrow.'); } else { // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” $summary = I18N::plural('No events exist for the next %s day.', 'No events exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1)); } } else { if ($endjd == $startjd) { $summary = I18N::translate('No events for living individuals exist for tomorrow.'); } else { // I18N: translation for %s==1 is unused; it is translated separately as “tomorrow” $summary = I18N::plural('No events for living people exist for the next %s day.', 'No events for living people exist for the next %s days.', $endjd - $startjd + 1, I18N::number($endjd - $startjd + 1)); } } } } if ($summary) { $html .= "" . $summary . ""; } return $html; } /** * Print a chart by age using Google chart API * * @param integer[] $data * @param string $title * * @return string */ function print_chart_by_age($data, $title) { $count = 0; $agemax = 0; $vmax = 0; $avg = 0; foreach ($data as $age => $v) { $n = strlen($v); $vmax = max($vmax, $n); $agemax = max($agemax, $age); $count += $n; $avg += $age * $n; } if ($count < 1) { return; } $avg = round($avg / $count); $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type $chart_url .= "&chs=725x150"; // size $chart_url .= "&chbh=3,2,2"; // bvg : 4,1,2 $chart_url .= "&chf=bg,s,FFFFFF99"; //background color $chart_url .= "&chco=0000FF,FFA0CB,FF0000"; // bar color $chart_url .= "&chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age $chart_url .= "&chtt=" . rawurlencode($title); // title $chart_url .= "&chxt=x,y,r"; // axis labels specification $chart_url .= "&chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker $chart_url .= "&chxl=0:|"; // label for ($age = 0; $age <= $agemax; $age += 5) { $chart_url .= $age . "|||||"; // x axis } $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis $chart_url .= "|2:||"; $step = $vmax; for ($d = $vmax; $d > 0; $d--) { if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) { $step = $d; } } if ($step == $vmax) { for ($d = $vmax - 1; $d > 0; $d--) { if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) { $step = $d; } } } for ($n = $step; $n < $vmax; $n += $step) { $chart_url .= $n . "|"; } $chart_url .= rawurlencode($vmax . " / " . $count); // r axis $chart_url .= "&chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid $chart_url .= "&chd=s:"; // data : simple encoding from A=0 to 9=61 $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for ($age = 0; $age <= $agemax; $age++) { $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "M") * 61 / $vmax)]; } $chart_url .= ","; for ($age = 0; $age <= $agemax; $age++) { $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$age], "F") * 61 / $vmax)]; } $html = '' . $title . ''; return $html; } /** * Print a chart by decade using Google chart API * * @param integer[] $data * @param string $title * * @return string */ function print_chart_by_decade($data, $title) { $count = 0; $vmax = 0; foreach ($data as $v) { $n = strlen($v); $vmax = max($vmax, $n); $count += $n; } if ($count < 1) { return; } $chart_url = "https://chart.googleapis.com/chart?cht=bvs"; // chart type $chart_url .= "&chs=360x150"; // size $chart_url .= "&chbh=3,3"; // bvg : 4,1,2 $chart_url .= "&chf=bg,s,FFFFFF99"; //background color $chart_url .= "&chco=0000FF,FFA0CB"; // bar color $chart_url .= "&chtt=" . rawurlencode($title); // title $chart_url .= "&chxt=x,y,r"; // axis labels specification $chart_url .= "&chxl=0:|<|||"; // <1570 for ($y = 1600; $y < 2030; $y += 50) { $chart_url .= $y . "|||||"; // x axis } $chart_url .= "|1:||" . rawurlencode(I18N::percentage($vmax / $count)); // y axis $chart_url .= "|2:||"; $step = $vmax; for ($d = $vmax; $d > 0; $d--) { if ($vmax < ($d * 10 + 1) && ($vmax % $d) == 0) { $step = $d; } } if ($step == $vmax) { for ($d = $vmax - 1; $d > 0; $d--) { if (($vmax - 1) < ($d * 10 + 1) && (($vmax - 1) % $d) == 0) { $step = $d; } } } for ($n = $step; $n < $vmax; $n += $step) { $chart_url .= $n . "|"; } $chart_url .= rawurlencode($vmax . " / " . $count); // r axis $chart_url .= "&chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid $chart_url .= "&chd=s:"; // data : simple encoding from A=0 to 9=61 $CHART_ENCODING61 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for ($y = 1570; $y < 2030; $y += 10) { $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "M") * 61 / $vmax)]; } $chart_url .= ","; for ($y = 1570; $y < 2030; $y += 10) { $chart_url .= $CHART_ENCODING61[(int) (substr_count($data[$y], "F") * 61 / $vmax)]; } $html = '' . $title . ''; return $html; }