summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--action.php16
-rw-r--r--addmedia.php72
-rw-r--r--admin.php3
-rw-r--r--admin_media.php5
-rw-r--r--admin_media_upload.php3
-rw-r--r--admin_module_blocks.php3
-rw-r--r--admin_module_menus.php3
-rw-r--r--admin_module_reports.php3
-rw-r--r--admin_module_sidebar.php3
-rw-r--r--admin_module_tabs.php3
-rw-r--r--admin_modules.php3
-rw-r--r--admin_site_access.php3
-rw-r--r--admin_site_change.php7
-rw-r--r--admin_site_config.php21
-rw-r--r--admin_site_logs.php7
-rw-r--r--admin_site_merge.php30
-rw-r--r--admin_site_upgrade.php6
-rw-r--r--admin_trees_config.php91
-rw-r--r--admin_trees_download.php5
-rw-r--r--admin_trees_merge.php5
-rw-r--r--admin_users.php17
-rw-r--r--ancestry.php18
-rw-r--r--app/ConfigData.php2
-rw-r--r--app/Controller/AdvancedSearchController.php3
-rw-r--r--app/Controller/AncestryController.php8
-rw-r--r--app/Controller/DescendancyController.php12
-rw-r--r--app/Controller/FamilyBookController.php9
-rw-r--r--app/Controller/FamilyController.php11
-rw-r--r--app/Controller/HourglassController.php9
-rw-r--r--app/Controller/IndividualController.php9
-rw-r--r--app/Controller/LifespanController.php3
-rw-r--r--app/Controller/MediaController.php3
-rw-r--r--app/Controller/PageController.php3
-rw-r--r--app/Controller/PedigreeController.php2
-rw-r--r--app/Controller/SearchController.php22
-rw-r--r--app/Controller/TimelineController.php8
-rw-r--r--app/Fact.php5
-rw-r--r--app/Functions/FunctionsCharts.php556
-rw-r--r--app/Functions/FunctionsDate.php130
-rw-r--r--app/Functions/FunctionsDb.php1081
-rw-r--r--app/Functions/FunctionsEdit.php1715
-rw-r--r--app/Functions/FunctionsExport.php314
-rw-r--r--app/Functions/FunctionsImport.php1162
-rw-r--r--app/Functions/FunctionsMedia.php97
-rw-r--r--app/Functions/FunctionsPrint.php873
-rw-r--r--app/Functions/FunctionsPrintFacts.php1150
-rw-r--r--app/Functions/FunctionsPrintLists.php2297
-rw-r--r--app/Functions/FunctionsRtl.php1154
-rw-r--r--app/Functions/functions.php2225
-rw-r--r--app/GedcomRecord.php18
-rw-r--r--app/I18N.php2
-rw-r--r--app/Media.php6
-rw-r--r--app/Module/AlbumModule.php3
-rw-r--r--app/Module/BatchUpdateModule.php3
-rw-r--r--app/Module/CensusAssistantModule.php5
-rw-r--r--app/Module/ChartsBlockModule.php10
-rw-r--r--app/Module/ClippingsCart/ClippingsCartController.php5
-rw-r--r--app/Module/ClippingsCartModule.php13
-rw-r--r--app/Module/ExtraInformationModule.php3
-rw-r--r--app/Module/FamilyNavigatorModule.php5
-rw-r--r--app/Module/FamilyTreeFavoritesModule.php16
-rw-r--r--app/Module/FamilyTreeNewsModule.php6
-rw-r--r--app/Module/FamilyTreeStatisticsModule.php8
-rw-r--r--app/Module/FrequentlyAskedQuestionsModule.php7
-rw-r--r--app/Module/GoogleMapsModule.php34
-rw-r--r--app/Module/HtmlBlockModule.php8
-rw-r--r--app/Module/IndividualFactsTabModule.php9
-rw-r--r--app/Module/LoggedInUsersModule.php3
-rw-r--r--app/Module/MediaTabModule.php11
-rw-r--r--app/Module/NotesTabModule.php8
-rw-r--r--app/Module/OnThisDayModule.php16
-rw-r--r--app/Module/RecentChangesModule.php17
-rw-r--r--app/Module/RelativesTabModule.php7
-rw-r--r--app/Module/ResearchTaskModule.php12
-rw-r--r--app/Module/ReviewChangesModule.php10
-rw-r--r--app/Module/SlideShowModule.php10
-rw-r--r--app/Module/SourcesTabModule.php8
-rw-r--r--app/Module/StoriesModule.php8
-rw-r--r--app/Module/TopGivenNamesModule.php5
-rw-r--r--app/Module/TopPageViewsModule.php5
-rw-r--r--app/Module/TopSurnamesModule.php17
-rw-r--r--app/Module/UpcomingAnniversariesModule.php16
-rw-r--r--app/Module/UserJournalModule.php3
-rw-r--r--app/Module/UserMessagesModule.php6
-rw-r--r--app/Module/YahrzeitModule.php10
-rw-r--r--app/Report/ReportHtml.php3
-rw-r--r--app/Report/ReportParserGenerate.php69
-rw-r--r--app/Report/ReportPdfCell.php3
-rw-r--r--app/Report/ReportPdfText.php3
-rw-r--r--app/Schema/Migration22.php2
-rw-r--r--app/Stats.php43
-rw-r--r--app/Theme/AdministrationTheme.php2
-rw-r--r--app/Theme/BaseTheme.php13
-rw-r--r--app/Theme/ColorsTheme.php3
-rw-r--r--app/Tree.php8
-rw-r--r--calendar.php10
-rw-r--r--compact.php3
-rw-r--r--composer.json14
-rw-r--r--composer.lock2
-rw-r--r--descendancy.php13
-rw-r--r--edit_changes.php9
-rw-r--r--edit_interface.php258
-rw-r--r--edituser.php10
-rw-r--r--expand_view.php9
-rw-r--r--family.php14
-rw-r--r--familybook.php6
-rw-r--r--famlist.php9
-rw-r--r--fanchart.php8
-rw-r--r--find.php19
-rw-r--r--help_text.php2
-rw-r--r--hourglass.php8
-rw-r--r--import.php3
-rw-r--r--includes/functions/functions.php2219
-rw-r--r--includes/functions/functions_charts.php550
-rw-r--r--includes/functions/functions_date.php116
-rw-r--r--includes/functions/functions_db.php1079
-rw-r--r--includes/functions/functions_edit.php1706
-rw-r--r--includes/functions/functions_export.php308
-rw-r--r--includes/functions/functions_import.php1153
-rw-r--r--includes/functions/functions_mediadb.php91
-rw-r--r--includes/functions/functions_print.php867
-rw-r--r--includes/functions/functions_print_facts.php1144
-rw-r--r--includes/functions/functions_print_lists.php2289
-rw-r--r--includes/functions/functions_rtl.php1141
-rw-r--r--index.php8
-rw-r--r--index_edit.php9
-rw-r--r--indilist.php9
-rw-r--r--individual.php14
-rw-r--r--inverselink.php9
-rw-r--r--lifespan.php3
-rw-r--r--login.php3
-rw-r--r--mediafirewall.php4
-rw-r--r--medialist.php9
-rw-r--r--mediaviewer.php23
-rw-r--r--modules_v3/GEDFact_assistant/_MEDIA/media_0_inverselink.php8
-rw-r--r--modules_v3/GEDFact_assistant/census/census-edit.php29
-rw-r--r--note.php23
-rw-r--r--notelist.php4
-rw-r--r--pedigree.php16
-rw-r--r--placelist.php7
-rw-r--r--relationship.php23
-rw-r--r--repo.php17
-rw-r--r--repolist.php4
-rw-r--r--reportengine.php11
-rw-r--r--search.php3
-rw-r--r--setup.php3
-rw-r--r--source.php25
-rw-r--r--sourcelist.php4
-rw-r--r--statistics.php3
-rw-r--r--statisticsplot.php2
-rw-r--r--tests/app/Functions/FunctionsChartsTest.php28
-rw-r--r--tests/app/Functions/FunctionsDateTest.php (renamed from tests/includes/functions/FunctionsDateTest.php)26
-rw-r--r--tests/app/Functions/FunctionsDbTest.php28
-rw-r--r--tests/app/Functions/FunctionsEditTest.php28
-rw-r--r--tests/app/Functions/FunctionsExportTest.php28
-rw-r--r--tests/app/Functions/FunctionsImportTest.php28
-rw-r--r--tests/app/Functions/FunctionsMediaTest.php (renamed from tests/includes/functions/FunctionsMediaDbTest.php)38
-rw-r--r--tests/app/Functions/FunctionsPrintFactsTest.php28
-rw-r--r--tests/app/Functions/FunctionsPrintListsTest.php28
-rw-r--r--tests/app/Functions/FunctionsPrintTest.php28
-rw-r--r--tests/app/Functions/FunctionsRtlTest.php28
-rw-r--r--tests/app/Functions/FunctionsTest.php35
-rw-r--r--tests/includes/functions/FunctionsChartsTest.php76
-rw-r--r--tests/includes/functions/FunctionsDbTest.php188
-rw-r--r--tests/includes/functions/FunctionsEditTest.php280
-rw-r--r--tests/includes/functions/FunctionsExportTest.php55
-rw-r--r--tests/includes/functions/FunctionsImportTest.php90
-rw-r--r--tests/includes/functions/FunctionsPrintFactsTest.php90
-rw-r--r--tests/includes/functions/FunctionsPrintListsTest.php132
-rw-r--r--tests/includes/functions/FunctionsPrintTest.php196
-rw-r--r--tests/includes/functions/FunctionsRtlTest.php83
-rw-r--r--tests/includes/functions/FunctionsTest.php152
-rw-r--r--timeline.php6
-rw-r--r--vendor/composer/autoload_files.php12
174 files changed, 13952 insertions, 14764 deletions
diff --git a/action.php b/action.php
index 94a096dbbd..b385ac9b24 100644
--- a/action.php
+++ b/action.php
@@ -23,6 +23,10 @@ namespace Fisharebest\Webtrees;
*/
global $WT_TREE;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsImport;
+
define('WT_SCRIPT_NAME', 'action.php');
require './includes/session.php';
@@ -40,7 +44,7 @@ case 'accept-changes':
$record = GedcomRecord::getInstance(Filter::post('xref', WT_REGEX_XREF), $WT_TREE);
if ($record && Auth::isModerator($record->getTree()) && $record->canShow() && $record->canEdit()) {
FlashMessages::addMessage(/* I18N: %s is the name of an individual, source or other record */ I18N::translate('The changes to “%s” have been accepted.', $record->getFullName()));
- accept_all_changes($record->getXref(), $record->getTree()->getTreeId());
+ FunctionsImport::acceptAllChanges($record->getXref(), $record->getTree()->getTreeId());
} else {
http_response_code(406);
}
@@ -126,11 +130,11 @@ case 'delete-source':
$record = GedcomRecord::getInstance(Filter::post('xref', WT_REGEX_XREF), $WT_TREE);
if ($record && Auth::isEditor($record->getTree()) && $record->canShow() && $record->canEdit()) {
// Delete links to this record
- foreach (fetch_all_links($record->getXref(), $record->getTree()->getTreeId()) as $xref) {
+ foreach (FunctionsDb::fetchAllLinks($record->getXref(), $record->getTree()->getTreeId()) as $xref) {
$linker = GedcomRecord::getInstance($xref, $WT_TREE);
$old_gedcom = $linker->getGedcom();
- $new_gedcom = remove_links($old_gedcom, $record->getXref());
- // fetch_all_links() does not take account of pending changes. The links (or even the
+ $new_gedcom = FunctionsEdit::removeLinks($old_gedcom, $record->getXref());
+ // FunctionsDb::fetch_all_links() does not take account of pending changes. The links (or even the
// record itself) may have already been deleted.
if ($old_gedcom !== $new_gedcom) {
// If we have removed a link from a family to an individual, and it has only one member
@@ -143,7 +147,7 @@ case 'delete-source':
if ($match) {
$relict = GedcomRecord::getInstance($match[2][0], $WT_TREE);
$new_gedcom = $relict->getGedcom();
- $new_gedcom = remove_links($new_gedcom, $linker->getXref());
+ $new_gedcom = FunctionsEdit::removeLinks($new_gedcom, $linker->getXref());
$relict->updateRecord($new_gedcom, false);
FlashMessages::addMessage(/* I18N: %s are names of records, such as sources, repositories or individuals */ I18N::translate('The link from “%1$s” to “%2$s” has been deleted.', $relict->getFullName(), $family->getFullName()));
}
@@ -213,7 +217,7 @@ case 'reject-changes':
$record = GedcomRecord::getInstance(Filter::post('xref', WT_REGEX_XREF), $WT_TREE);
if ($record && $record->canEdit() && Auth::isModerator($record->getTree())) {
FlashMessages::addMessage(/* I18N: %s is the name of an individual, source or other record */ I18N::translate('The changes to “%s” have been rejected.', $record->getFullName()));
- reject_all_changes($record);
+ FunctionsImport::rejectAllChanges($record);
} else {
http_response_code(406);
}
diff --git a/addmedia.php b/addmedia.php
index e268bd0785..7dcad4ce45 100644
--- a/addmedia.php
+++ b/addmedia.php
@@ -24,6 +24,10 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\Query\QueryMedia;
define('WT_SCRIPT_NAME', 'addmedia.php');
@@ -184,7 +188,7 @@ case 'create': // Save the information from the “showcreateform” action
FlashMessages::addMessage(
I18N::translate('There was an error uploading your file.') .
'<br>' .
- file_upload_error_text($_FILES['mediafile']['error'])
+ Functions::fileUploadErrorText($_FILES['mediafile']['error'])
);
$filename = '';
break;
@@ -218,7 +222,7 @@ case 'create': // Save the information from the “showcreateform” action
$newged .= "\n1 FILE " . $folderName . $fileName;
}
- $newged = handle_updates($newged);
+ $newged = FunctionsEdit::handleUpdates($newged);
$new_media = $WT_TREE->createRecord($newged);
if ($linktoid) {
@@ -312,7 +316,7 @@ case 'update': // Save the information from the “editmedia” action
$newFilename = $folderName . $fileName;
// Cannot rename local to external or vice-versa
- if (isFileExternal($oldFilename) != isFileExternal($filename)) {
+ if (Functions::isFileExternal($oldFilename) != Functions::isFileExternal($filename)) {
FlashMessages::addMessage(I18N::translate('The media file %1$s could not be renamed to %2$s.', Html::filename($oldFilename), Html::filename($newFilename)));
break;
}
@@ -331,7 +335,7 @@ case 'update': // Save the information from the “editmedia” action
if ($oldServerFile !== $newServerFile) {
//-- check if the file is used in more than one gedcom
//-- do not allow it to be moved or renamed if it is
- if (!$media->isExternal() && is_media_used_in_other_gedcom($media->getFilename(), $WT_TREE->getTreeId())) {
+ if (!$media->isExternal() && FunctionsDb::isMediaUsedInOtherTree($media->getFilename(), $WT_TREE->getTreeId())) {
FlashMessages::addMessage(I18N::translate('This file is linked to another family tree on this server. It cannot be deleted, moved, or renamed until these links have been removed.'));
break;
}
@@ -367,7 +371,7 @@ case 'update': // Save the information from the “editmedia” action
}
}
- // Insert the 1 FILE xxx record into the arrays used by function handle_updates()
+ // Insert the 1 FILE xxx record into the arrays used by function FunctionsEdit::handle_updatesges()
$glevels = array_merge(array('1'), $glevels);
$tag = array_merge(array('FILE'), $tag);
$islink = array_merge(array(0), $islink);
@@ -375,7 +379,7 @@ case 'update': // Save the information from the “editmedia” action
$record = GedcomRecord::getInstance($pid, $WT_TREE);
$newrec = "0 @$pid@ OBJE\n";
- $newrec = handle_updates($newrec);
+ $newrec = FunctionsEdit::handleUpdates($newrec);
$record->updateRecord($newrec, $update_CHAN);
if ($pid && $linktoid) {
@@ -415,15 +419,15 @@ if ($linktoid) {
}
echo '<table class="facts_table">';
echo '<tr><td class="topbottombar" colspan="2">';
-echo $controller->getPageTitle(), help_link('OBJE');
+echo $controller->getPageTitle(), FunctionsPrint::helpLink('OBJE');
echo '</td></tr>';
if (!$linktoid && $action == 'create') {
echo '<tr><td class="descriptionbox wrap width25">';
echo I18N::translate('Enter an individual, family, or source ID');
echo '</td><td class="optionbox wrap"><input type="text" data-autocomplete-type="IFS" name="linktoid" id="linktoid" size="6" value="">';
- echo ' ', print_findindi_link('linktoid');
- echo ' ', print_findfamily_link('linktoid');
- echo ' ', print_findsource_link('linktoid');
+ echo ' ', FunctionsPrint::printFindIndividualLink('linktoid');
+ echo ' ', FunctionsPrint::printFindFamilyLink('linktoid');
+ echo ' ', FunctionsPrint::printFindSourceLink('linktoid');
echo '<p class="small text-muted">', I18N::translate('Enter or search for the ID of the individual, family, or source to which this media item should be linked.'), '</p></td></tr>';
}
@@ -454,10 +458,10 @@ if ($gedfile == 'FILE') {
}
// Filename on server
-$isExternal = isFileExternal($gedfile);
+$isExternal = Functions::isFileExternal($gedfile);
if ($gedfile == 'FILE') {
if (Auth::isManager($WT_TREE)) {
- add_simple_tag(
+ FunctionsEdit::addSimpleTag(
"1 $gedfile",
'',
I18N::translate('Filename on server'),
@@ -545,7 +549,7 @@ if (preg_match('/\n(2 FORM .*)/', $gedrec, $match)) {
} else {
$gedform = '2 FORM';
}
-$formid = add_simple_tag($gedform);
+$formid = FunctionsEdit::addSimpleTag($gedform);
// automatically set the format field from the filename
$controller->addInlineJavascript('
@@ -569,7 +573,7 @@ if (preg_match('/\n(3 TYPE .*)/', $gedrec, $match)) {
} else {
$gedtype = '3 TYPE photo'; // default to ‘Photo’
}
-add_simple_tag($gedtype);
+FunctionsEdit::addSimpleTag($gedtype);
// 1 FILE / 2 TITL
if (preg_match('/\n(2 TITL .*)/', $gedrec, $match)) {
@@ -577,7 +581,7 @@ if (preg_match('/\n(2 TITL .*)/', $gedrec, $match)) {
} else {
$gedtitl = '2 TITL';
}
-add_simple_tag($gedtitl);
+FunctionsEdit::addSimpleTag($gedtitl);
// 1 FILE / 2 TITL / 3 _HEB
if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), '_HEB') !== false) {
@@ -586,7 +590,7 @@ if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), '_HEB') !== false) {
} else {
$gedtitl = '3 _HEB';
}
- add_simple_tag($gedtitl);
+ FunctionsEdit::addSimpleTag($gedtitl);
}
// 1 FILE / 2 TITL / 3 ROMN
@@ -596,7 +600,7 @@ if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), 'ROMN') !== false) {
} else {
$gedtitl = '3 ROMN';
}
- add_simple_tag($gedtitl);
+ FunctionsEdit::addSimpleTag($gedtitl);
}
// 1 _PRIM
@@ -605,7 +609,7 @@ if (preg_match('/\n(1 _PRIM .*)/', $gedrec, $match)) {
} else {
$gedprim = '1 _PRIM';
}
-add_simple_tag($gedprim);
+FunctionsEdit::addSimpleTag($gedprim);
//-- print out editing fields for any other data in the media record
$sourceLevel = 0;
@@ -627,15 +631,15 @@ if (!empty($gedrec)) {
$fact = trim($match[2]);
$event = trim($match[3]);
if ($fact === 'NOTE' || $fact === 'TEXT') {
- $event .= get_cont($subLevel + 1, $subrec);
+ $event .= Functions::getCont($subLevel + 1, $subrec);
}
if ($sourceSOUR !== '' && $subLevel <= $sourceLevel) {
// Get rid of all saved Source data
- add_simple_tag($sourceLevel . ' SOUR ' . $sourceSOUR);
- add_simple_tag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
- add_simple_tag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
- add_simple_tag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
- add_simple_tag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
+ FunctionsEdit::addSimpleTag($sourceLevel . ' SOUR ' . $sourceSOUR);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
+ FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
$sourceSOUR = '';
}
@@ -672,18 +676,18 @@ if (!empty($gedrec)) {
// Output anything that isn’t part of a source reference
if (!empty($fact) && $fact !== 'CONC' && $fact !== 'CONT' && $fact !== 'DATA') {
- add_simple_tag($subLevel . ' ' . $fact . ' ' . $event);
+ FunctionsEdit::addSimpleTag($subLevel . ' ' . $fact . ' ' . $event);
}
}
}
if ($sourceSOUR !== '') {
// Get rid of all saved Source data
- add_simple_tag($sourceLevel . ' SOUR ' . $sourceSOUR);
- add_simple_tag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
- add_simple_tag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
- add_simple_tag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
- add_simple_tag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
+ FunctionsEdit::addSimpleTag($sourceLevel . ' SOUR ' . $sourceSOUR);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' PAGE ' . $sourcePAGE);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' TEXT ' . $sourceTEXT);
+ FunctionsEdit::addSimpleTag(($sourceLevel + 2) . ' DATE ' . $sourceDATE, '', GedcomTag::getLabel('DATA:DATE'));
+ FunctionsEdit::addSimpleTag(($sourceLevel + 1) . ' QUAY ' . $sourceQUAY);
}
}
if (Auth::isAdmin()) {
@@ -698,10 +702,10 @@ if (Auth::isAdmin()) {
echo '</td></tr>';
}
echo '</table>';
-print_add_layer('SOUR', 1);
-print_add_layer('NOTE', 1);
-print_add_layer('SHARED_NOTE', 1);
-print_add_layer('RESN', 1);
+FunctionsEdit::printAddLayer('SOUR', 1);
+FunctionsEdit::printAddLayer('NOTE', 1);
+FunctionsEdit::printAddLayer('SHARED_NOTE', 1);
+FunctionsEdit::printAddLayer('RESN', 1);
?>
<p id="save-cancel">
<input type="submit" class="save" value="<?php echo I18N::translate('save'); ?>">
diff --git a/admin.php b/admin.php
index 8e474cff59..4f61ed7a39 100644
--- a/admin.php
+++ b/admin.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\Functions;
define('WT_SCRIPT_NAME', 'admin.php');
@@ -429,7 +430,7 @@ $controller
->pageHeader();
// Check for updates
-$latest_version_txt = fetch_latest_version();
+$latest_version_txt = Functions::fetchLatestVersion();
if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) {
list($latest_version) = explode('|', $latest_version_txt);
} else {
diff --git a/admin_media.php b/admin_media.php
index 397bdb6925..f7dc6da0a4 100644
--- a/admin_media.php
+++ b/admin_media.php
@@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees;
use Fisharebest\Webtrees\Controller\AjaxController;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
define('WT_SCRIPT_NAME', 'admin_media.php');
require './includes/session.php';
@@ -642,7 +643,7 @@ $controller
<div dir="ltr">
<?php if (count($media_folders) > 1): ?>
- <?php echo WT_DATA_DIR, select_edit_control('media_folder', $media_folders, null, $media_folder, 'onchange="this.form.submit();"'); ?>
+ <?php echo WT_DATA_DIR, FunctionsEdit::selectEditControl('media_folder', $media_folders, null, $media_folder, 'onchange="this.form.submit();"'); ?>
<?php else: ?>
<?php echo WT_DATA_DIR, Filter::escapeHtml($media_folder); ?>
<input type="hidden" name="media_folder" value="<?php echo Filter::escapeHtml($media_folder); ?>">
@@ -650,7 +651,7 @@ $controller
</div>
<?php if (count($media_paths) > 1): ?>
- <?php echo select_edit_control('media_path', $media_paths, null, $media_path, 'onchange="this.form.submit();"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('media_path', $media_paths, null, $media_path, 'onchange="this.form.submit();"'); ?>
<?php else: ?>
<?php echo Filter::escapeHtml($media_path); ?>
<input type="hidden" name="media_path" value="<?php echo Filter::escapeHtml($media_path); ?>">
diff --git a/admin_media_upload.php b/admin_media_upload.php
index ffd8b7c6a2..a972ba5ba1 100644
--- a/admin_media_upload.php
+++ b/admin_media_upload.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\Query\QueryMedia;
define('WT_SCRIPT_NAME', 'admin_media_upload.php');
@@ -144,7 +145,7 @@ if ($action == "upload") {
FlashMessages::addMessage(
I18N::translate('There was an error uploading your file.') .
'<br>' .
- file_upload_error_text($_FILES['mediafile' . $i]['error'])
+ Functions::fileUploadErrorText($_FILES['mediafile' . $i]['error'])
);
$filename = '';
break;
diff --git a/admin_module_blocks.php b/admin_module_blocks.php
index 9e19e3535a..89d4bdc5fd 100644
--- a/admin_module_blocks.php
+++ b/admin_module_blocks.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
define('WT_SCRIPT_NAME', 'admin_module_blocks.php');
@@ -95,7 +96,7 @@ $controller
<?php echo $tree->getTitleHtml(); ?>
</td>
<td>
- <?php echo edit_field_access_level('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'block')); ?>
+ <?php echo FunctionsEdit::editFieldAccessLevel('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'block')); ?>
</td>
</tr>
<?php endforeach; ?>
diff --git a/admin_module_menus.php b/admin_module_menus.php
index b8126c562f..a7f454f5ee 100644
--- a/admin_module_menus.php
+++ b/admin_module_menus.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
define('WT_SCRIPT_NAME', 'admin_module_menus.php');
@@ -113,7 +114,7 @@ $controller
<?php echo $tree->getTitleHtml(); ?>
</td>
<td>
- <?php echo edit_field_access_level('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'menu')); ?>
+ <?php echo FunctionsEdit::editFieldAccessLevel('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'menu')); ?>
</td>
</tr>
<?php endforeach; ?>
diff --git a/admin_module_reports.php b/admin_module_reports.php
index 249aadcd84..b83ab56751 100644
--- a/admin_module_reports.php
+++ b/admin_module_reports.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
define('WT_SCRIPT_NAME', 'admin_module_reports.php');
@@ -88,7 +89,7 @@ $controller
<?php echo $tree->getTitleHtml(); ?>
</td>
<td>
- <?php echo edit_field_access_level('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'report')); ?>
+ <?php echo FunctionsEdit::editFieldAccessLevel('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'report')); ?>
</td>
</tr>
<?php endforeach; ?>
diff --git a/admin_module_sidebar.php b/admin_module_sidebar.php
index 54efc6ff59..ce8eb5e8eb 100644
--- a/admin_module_sidebar.php
+++ b/admin_module_sidebar.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
define('WT_SCRIPT_NAME', 'admin_module_sidebar.php');
@@ -113,7 +114,7 @@ $controller
<?php echo $tree->getTitleHtml(); ?>
</td>
<td>
- <?php echo edit_field_access_level('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'sidebar')); ?>
+ <?php echo FunctionsEdit::editFieldAccessLevel('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'sidebar')); ?>
</td>
</tr>
<?php endforeach; ?>
diff --git a/admin_module_tabs.php b/admin_module_tabs.php
index 7a8d765110..9e081f351d 100644
--- a/admin_module_tabs.php
+++ b/admin_module_tabs.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
define('WT_SCRIPT_NAME', 'admin_module_tabs.php');
@@ -113,7 +114,7 @@ $controller
<?php echo $tree->getTitleHtml(); ?>
</td>
<td>
- <?php echo edit_field_access_level('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'tab')); ?>
+ <?php echo FunctionsEdit::editFieldAccessLevel('access-' . $module->getName() . '-' . $tree->getTreeId(), $module->getAccessLevel($tree, 'tab')); ?>
</td>
</tr>
<?php endforeach; ?>
diff --git a/admin_modules.php b/admin_modules.php
index f2d4eabe28..f49ccae743 100644
--- a/admin_modules.php
+++ b/admin_modules.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
use Fisharebest\Webtrees\Module\ModuleChartInterface;
use Fisharebest\Webtrees\Module\ModuleConfigInterface;
@@ -165,7 +166,7 @@ $controller
foreach ($modules as $module_name => $module) {
$status = $module_status[$module_name];
echo
- '<tr><td class="text-center">', two_state_checkbox('status-' . $module->getName(), $status === 'enabled'), '</td>',
+ '<tr><td class="text-center">', FunctionsEdit::twoStateCheckbox('status-' . $module->getName(), $status === 'enabled'), '</td>',
'<td>';
if ($module instanceof ModuleConfigInterface) {
echo '<a href="', $module->getConfigLink(), '">';
diff --git a/admin_site_access.php b/admin_site_access.php
index c0d9c1792f..fe40dd42a5 100644
--- a/admin_site_access.php
+++ b/admin_site_access.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use PDO;
define('WT_SCRIPT_NAME', 'admin_site_access.php');
@@ -287,7 +288,7 @@ case 'create':
<?php echo /* I18N: A configuration setting */ I18N::translate('Rule'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('rule', $rules_edit, null, $rule, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('rule', $rules_edit, null, $rule, 'class="form-control"'); ?>
</div>
</div>
diff --git a/admin_site_change.php b/admin_site_change.php
index 03273da62d..07dd3eaa5d 100644
--- a/admin_site_change.php
+++ b/admin_site_change.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use PDO;
/**
@@ -294,7 +295,7 @@ foreach (User::all() as $tmp_user) {
<label for="type">
<?php echo I18N::translate('Status'); ?>
</label>
- <?php echo select_edit_control('type', $statuses, null, $type, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('type', $statuses, null, $type, 'class="form-control"'); ?>
</div>
<div class="form-group col-xs-6 col-md-3">
@@ -324,14 +325,14 @@ foreach (User::all() as $tmp_user) {
<label for="user">
<?php echo I18N::translate('User'); ?>
</label>
- <?php echo select_edit_control('user', $users_array, '', $user, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('user', $users_array, '', $user, 'class="form-control"'); ?>
</div>
<div class="form-group col-xs-6 col-md-3">
<label for="gedc">
<?php echo I18N::translate('Family tree'); ?>
</label>
- <?php echo select_edit_control('gedc', Tree::getNameList(), '', $gedc, Auth::isAdmin() ? 'class="form-control"' : 'disabled class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('gedc', Tree::getNameList(), '', $gedc, Auth::isAdmin() ? 'class="form-control"' : 'disabled class="form-control"'); ?>
</div>
</div>
diff --git a/admin_site_config.php b/admin_site_config.php
index 311b1a2427..801bca16cb 100644
--- a/admin_site_config.php
+++ b/admin_site_config.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
define('WT_SCRIPT_NAME', 'admin_site_config.php');
require './includes/session.php';
@@ -229,7 +230,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Default theme'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('THEME_DIR', Theme::themeNames(), null, Site::getPreference('THEME_DIR'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('THEME_DIR', Theme::themeNames(), null, Site::getPreference('THEME_DIR'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the "Default theme" site configuration setting */ I18N::translate('You can change the appearance of webtrees using “themes”. Each theme has a different style, layout, color scheme, etc.'); ?>
</p>
@@ -245,7 +246,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Allow users to select their own theme'); ?>
</legend>
<div class="col-sm-9">
- <?php echo edit_field_yes_no('ALLOW_USER_THEMES', Site::getPreference('ALLOW_USER_THEMES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('ALLOW_USER_THEMES', Site::getPreference('ALLOW_USER_THEMES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Allow users to select their own theme” site configuration setting */ I18N::translate('Gives users the option of selecting their own theme.'); ?>
</p>
@@ -258,7 +259,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Show list of family trees'); ?>
</legend>
<div class="col-sm-9">
- <?php echo edit_field_yes_no('ALLOW_CHANGE_GEDCOM', Site::getPreference('ALLOW_CHANGE_GEDCOM'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('ALLOW_CHANGE_GEDCOM', Site::getPreference('ALLOW_CHANGE_GEDCOM'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php /* I18N: Help text for the “Show list of family trees” site configuration setting */ I18N::translate('For websites with more than one family tree, this option will show the list of family trees in the main menu, the search pages, etc.'); ?>
</p>
@@ -285,7 +286,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Website URL'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('SERVER_URL', array(WT_BASE_URL => WT_BASE_URL), '', Site::getPreference('SERVER_URL'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SERVER_URL', array(WT_BASE_URL => WT_BASE_URL), '', Site::getPreference('SERVER_URL'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the "Website URL" site configuration setting */ I18N::translate('If your website can be reached using more than one URL, such as <b>http://www.example.com/webtrees/</b> and <b>http://webtrees.example.com/</b>, you can specify the preferred URL. Requests for the other URLs will be redirected to the preferred one.'); ?>
<?php echo I18N::translate('If you leave this setting empty, the default value will be used.'); ?>
@@ -302,7 +303,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Messages'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('SMTP_ACTIVE', $SMTP_ACTIVE_OPTIONS, null, Site::getPreference('SMTP_ACTIVE'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SMTP_ACTIVE', $SMTP_ACTIVE_OPTIONS, null, Site::getPreference('SMTP_ACTIVE'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Messages” site configuration setting */ I18N::translate('webtrees needs to send emails, such as password reminders and website notifications. To do this, it can use this server’s built in PHP mail facility (which is not always available) or an external SMTP (mail-relay) service, for which you will need to provide the connection details.'); ?>
</p>
@@ -356,7 +357,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Use password'); ?>
</legend>
<div class="col-sm-9">
- <?php echo edit_field_yes_no('SMTP_AUTH', Site::getPreference('SMTP_AUTH'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('SMTP_AUTH', Site::getPreference('SMTP_AUTH'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Use password” site configuration setting */ I18N::translate('Most SMTP servers require a password.'); ?>
</p>
@@ -395,7 +396,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Secure connection'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('SMTP_SSL', $SMTP_SSL_OPTIONS, null, Site::getPreference('SMTP_SSL'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SMTP_SSL', $SMTP_SSL_OPTIONS, null, Site::getPreference('SMTP_SSL'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Secure connection” site configuration setting */ I18N::translate('Most servers do not use secure connections.'); ?>
</p>
@@ -445,7 +446,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Welcome text on login page'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('WELCOME_TEXT_AUTH_MODE', $WELCOME_TEXT_AUTH_MODE_OPTIONS, null, Site::getPreference('WELCOME_TEXT_AUTH_MODE'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('WELCOME_TEXT_AUTH_MODE', $WELCOME_TEXT_AUTH_MODE_OPTIONS, null, Site::getPreference('WELCOME_TEXT_AUTH_MODE'), 'class="form-control"'); ?>
<p class="small text-muted">
</p>
</div>
@@ -470,7 +471,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Allow visitors to request a new user account'); ?>
</legend>
<div class="col-sm-9">
- <?php echo edit_field_yes_no('USE_REGISTRATION_MODULE', Site::getPreference('USE_REGISTRATION_MODULE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('USE_REGISTRATION_MODULE', Site::getPreference('USE_REGISTRATION_MODULE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo I18N::translate('The new user will be asked to confirm their email address before the account is created.'); ?>
<?php echo I18N::translate('Details of the new user will be sent to the genealogy contact for the corresponding family tree.'); ?>
@@ -485,7 +486,7 @@ $controller->pageHeader();
<?php echo /* I18N: A site configuration setting */ I18N::translate('Show acceptable use agreement on “Request new user account” page'); ?>
</legend>
<div class="col-sm-9">
- <?php echo edit_field_yes_no('SHOW_REGISTER_CAUTION', Site::getPreference('SHOW_REGISTER_CAUTION'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('SHOW_REGISTER_CAUTION', Site::getPreference('SHOW_REGISTER_CAUTION'), 'class="radio-inline"'); ?>
<p class="small text-muted">
</p>
</div>
diff --git a/admin_site_logs.php b/admin_site_logs.php
index 6413633947..d06cdf60c9 100644
--- a/admin_site_logs.php
+++ b/admin_site_logs.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use PDO;
/**
@@ -267,7 +268,7 @@ foreach (User::all() as $tmp_user) {
<label for="type">
<?php echo I18N::translate('Type'); ?>
</label>
- <?php echo select_edit_control('type', array('' => '', 'auth' => 'auth', 'config' => 'config', 'debug' => 'debug', 'edit' => 'edit', 'error' => 'error', 'media' => 'media', 'search' => 'search'), null, $type, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('type', array('' => '', 'auth' => 'auth', 'config' => 'config', 'debug' => 'debug', 'edit' => 'edit', 'error' => 'error', 'media' => 'media', 'search' => 'search'), null, $type, 'class="form-control"'); ?>
</div>
<div class="form-group col-xs-6 col-sm-4">
@@ -290,14 +291,14 @@ foreach (User::all() as $tmp_user) {
<label for="user">
<?php echo I18N::translate('User'); ?>
</label>
- <?php echo select_edit_control('user', $users_array, '', $user, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('user', $users_array, '', $user, 'class="form-control"'); ?>
</div>
<div class="form-group col-sm-4">
<label for="gedc">
<?php echo I18N::translate('Family tree'); ?>
</label>
- <?php echo select_edit_control('gedc', Tree::getNameList(), '', $gedc, Auth::isAdmin() ? 'class="form-control"' : 'disabled class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('gedc', Tree::getNameList(), '', $gedc, Auth::isAdmin() ? 'class="form-control"' : 'disabled class="form-control"'); ?>
</div>
</div>
diff --git a/admin_site_merge.php b/admin_site_merge.php
index 4ec5d9efcb..e7be4fb258 100644
--- a/admin_site_merge.php
+++ b/admin_site_merge.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'admin_site_merge.php');
require './includes/session.php';
@@ -91,7 +93,7 @@ foreach ($facts1 as $id1 => $fact1) {
}
if ($rec1 && $rec2 && $rec1->getXref() !== $rec2->getXref() && $rec1::RECORD_TYPE === $rec2::RECORD_TYPE && Filter::post('action') === 'merge' && Filter::checkCsrf()) {
- $ids = fetch_all_links($gid2, $WT_TREE->getTreeId());
+ $ids = FunctionsDb::fetchAllLinks($gid2, $WT_TREE->getTreeId());
// If we are not auto-accepting, then we can show a link to the pending deletion
if (Auth::user()->getPreference('auto_accept')) {
@@ -161,7 +163,7 @@ if ($rec1 && $rec2 && $rec1->getXref() !== $rec2->getXref() && $rec1::RECORD_TYP
$rec1->updateRecord($gedcom, true);
$rec2->deleteRecord();
- update_favorites($gid2, $gid1, $WT_TREE);
+ FunctionsDb::updateFavorites($gid2, $gid1, $WT_TREE);
FlashMessages::addMessage(I18N::translate(
/* I18N: Records are individuals, sources, etc. */
'The records “%1$s” and “%2$s” have been merged.',
@@ -354,12 +356,12 @@ $controller->pageHeader();
</div>
<div class="col-sm-9">
<input data-autocomplete-type="IFSRO" type="text" name="gid1" id="gid1" maxlength="20" value="<?php echo $gid1; ?>">
- <?php echo print_findindi_link('gid1'); ?>
- <?php echo print_findfamily_link('gid1'); ?>
- <?php echo print_findsource_link('gid1'); ?>
- <?php echo print_findrepository_link('gid1'); ?>
- <?php echo print_findmedia_link('gid1'); ?>
- <?php echo print_findnote_link('gid1'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('gid1'); ?>
+ <?php echo FunctionsPrint::printFindFamilyLink('gid1'); ?>
+ <?php echo FunctionsPrint::printFindSourceLink('gid1'); ?>
+ <?php echo FunctionsPrint::printFindRepositoryLink('gid1'); ?>
+ <?php echo FunctionsPrint::printFindMediaLink('gid1'); ?>
+ <?php echo FunctionsPrint::printFindNoteLink('gid1'); ?>
</div>
</div>
@@ -371,12 +373,12 @@ $controller->pageHeader();
</div>
<div class="col-sm-9">
<input data-autocomplete-type="IFSRO" type="text" name="gid2" id="gid2" maxlength="20" value="<?php echo $gid2; ?>" >
- <?php echo print_findindi_link('gid2'); ?>
- <?php echo print_findfamily_link('gid2'); ?>
- <?php echo print_findsource_link('gid2'); ?>
- <?php echo print_findrepository_link('gid2'); ?>
- <?php echo print_findmedia_link('gid2'); ?>
- <?php echo print_findnote_link('gid2'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('gid2'); ?>
+ <?php echo FunctionsPrint::printFindFamilyLink('gid2'); ?>
+ <?php echo FunctionsPrint::printFindSourceLink('gid2'); ?>
+ <?php echo FunctionsPrint::printFindRepositoryLink('gid2'); ?>
+ <?php echo FunctionsPrint::printFindMediaLink('gid2'); ?>
+ <?php echo FunctionsPrint::printFindNoteLink('gid2'); ?>
</div>
</div>
diff --git a/admin_site_upgrade.php b/admin_site_upgrade.php
index 2479a15f21..50646c122b 100644
--- a/admin_site_upgrade.php
+++ b/admin_site_upgrade.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
use PclZip;
define('WT_SCRIPT_NAME', 'admin_site_upgrade.php');
@@ -24,7 +26,7 @@ define('WT_SCRIPT_NAME', 'admin_site_upgrade.php');
require './includes/session.php';
// Check for updates
-$latest_version_txt = fetch_latest_version();
+$latest_version_txt = Functions::fetchLatestVersion();
if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) {
list($latest_version, $earliest_version, $download_url) = explode('|', $latest_version_txt);
} else {
@@ -37,7 +39,7 @@ $download_url_html = '<b dir="auto"><a href="' . Filter::escapeHtml($download_
// Show a friendly message while the site is being upgraded
$lock_file = __DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'offline.txt';
-$lock_file_text = I18N::translate('This website is being upgraded. Try again in a few minutes.') . PHP_EOL . format_timestamp(WT_TIMESTAMP) . /* I18N: Timezone - http://en.wikipedia.org/wiki/UTC */ I18N::translate('UTC');
+$lock_file_text = I18N::translate('This website is being upgraded. Try again in a few minutes.') . PHP_EOL . FunctionsDate::formatTimestamp(WT_TIMESTAMP) . /* I18N: Timezone - http://en.wikipedia.org/wiki/UTC */ I18N::translate('UTC');
// Success/failure indicators
$icon_success = '<i class="icon-yes"></i>';
diff --git a/admin_trees_config.php b/admin_trees_config.php
index c923c48a87..451371fd2a 100644
--- a/admin_trees_config.php
+++ b/admin_trees_config.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
define('WT_SCRIPT_NAME', 'admin_trees_config.php');
@@ -397,7 +398,7 @@ $controller
</div>
</div>
<div class="col-sm-8">
- <?php echo select_edit_control('REQUIRE_AUTHENTICATION', array('0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')), null, $WT_TREE->getPreference('REQUIRE_AUTHENTICATION'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('REQUIRE_AUTHENTICATION', array('0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')), null, $WT_TREE->getPreference('REQUIRE_AUTHENTICATION'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Family tree” configuration setting */ I18N::translate('Enabling this option will force all visitors to login before they can view any data on the website.'); ?>
</p>
@@ -421,7 +422,7 @@ $controller
</div>
</div>
<div class="col-sm-8">
- <?php echo select_edit_control('SHOW_DEAD_PEOPLE', array_slice($privacy, 0, 2, true), null, $WT_TREE->getPreference('SHOW_DEAD_PEOPLE'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SHOW_DEAD_PEOPLE', array_slice($privacy, 0, 2, true), null, $WT_TREE->getPreference('SHOW_DEAD_PEOPLE'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show dead individuals” configuration setting */ I18N::translate('Set the privacy access level for all dead individuals.'); ?>
</p>
@@ -461,7 +462,7 @@ $controller
</div>
</div>
<div class="col-sm-8">
- <?php echo select_edit_control('HIDE_LIVE_PEOPLE', array('0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')), null, $WT_TREE->getPreference('HIDE_LIVE_PEOPLE'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('HIDE_LIVE_PEOPLE', array('0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')), null, $WT_TREE->getPreference('HIDE_LIVE_PEOPLE'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show living individuals” configuration setting */ I18N::translate('If you show living individuals to visitors, all other privacy restrictions are ignored. Do this only if all the data in your tree is public.'); ?>
</p>
@@ -502,7 +503,7 @@ $controller
</div>
</div>
<div class="col-sm-8">
- <?php echo select_edit_control('SHOW_LIVING_NAMES', array_slice($privacy, 0, 3, true), null, $WT_TREE->getPreference('SHOW_LIVING_NAMES'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SHOW_LIVING_NAMES', array_slice($privacy, 0, 3, true), null, $WT_TREE->getPreference('SHOW_LIVING_NAMES'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show names of private individuals” configuration setting */ I18N::translate('This option will show the names (but no other details) of private individuals. Individuals are private if they are still alive or if a privacy restriction has been added to their individual record. To hide a specific name, add a privacy restriction to that name record.'); ?>
</p>
@@ -521,7 +522,7 @@ $controller
</div>
</div>
<div class="col-sm-8">
- <?php echo select_edit_control('SHOW_PRIVATE_RELATIONSHIPS', array('0' => I18N::translate('Hide from everyone'), '1' => I18N::translate('Show to visitors')), null, $WT_TREE->getPreference('SHOW_PRIVATE_RELATIONSHIPS'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SHOW_PRIVATE_RELATIONSHIPS', array('0' => I18N::translate('Hide from everyone'), '1' => I18N::translate('Show to visitors')), null, $WT_TREE->getPreference('SHOW_PRIVATE_RELATIONSHIPS'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show private relationships” configuration setting */ I18N::translate('This option will retain family links in private records. This means that you will see empty “private” boxes on the pedigree chart and on other charts with private individuals.'); ?>
</p>
@@ -535,10 +536,10 @@ $controller
<input data-autocomplete-type="IFSRO" id="xref" maxlength="20" name="xref[]" type="text">
</td>
<td>
- <?php echo select_edit_control('tag_type[]', $all_tags, '', null, null); ?>
+ <?php echo FunctionsEdit::selectEditControl('tag_type[]', $all_tags, '', null, null); ?>
</td>
<td>
- <?php echo select_edit_control('resn[]', $PRIVACY_CONSTANTS, null, 'privacy', null); ?>
+ <?php echo FunctionsEdit::selectEditControl('resn[]', $PRIVACY_CONSTANTS, null, 'privacy', null); ?>
</td>
<td>
&nbsp;
@@ -726,10 +727,10 @@ $controller
<div class="col-sm-9">
<div class=row">
<div class="col-sm-6" style="padding-left: 0;">
- <?php echo select_edit_control('CALENDAR_FORMAT0', $calendars, null, $CALENDAR_FORMATS[0], 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('CALENDAR_FORMAT0', $calendars, null, $CALENDAR_FORMATS[0], 'class="form-control"'); ?>
</div>
<div class="col-sm-6" style="padding-right: 0;">
- <?php echo select_edit_control('CALENDAR_FORMAT1', $calendars, null, $CALENDAR_FORMATS[1], 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('CALENDAR_FORMAT1', $calendars, null, $CALENDAR_FORMATS[1], 'class="form-control"'); ?>
</div>
</div>
<p class="small text-muted">
@@ -750,7 +751,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Use RIN number instead of GEDCOM ID'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('USE_RIN', $no_yes, $WT_TREE->getPreference('USE_RIN'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('USE_RIN', $no_yes, $WT_TREE->getPreference('USE_RIN'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Use RIN number instead of GEDCOM ID” configuration setting */ I18N::translate('Set to <b>Yes</b> to use the RIN number instead of the GEDCOM ID when asked for individual IDs in configuration files, user settings, and charts. This is useful for genealogy programs that do not consistently export GEDCOMs with the same ID assigned to each individual but always use the same RIN.'); ?>
</p>
@@ -763,7 +764,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Automatically create globally unique IDs'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('GENERATE_UIDS', $no_yes, $WT_TREE->getPreference('GENERATE_UIDS'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('GENERATE_UIDS', $no_yes, $WT_TREE->getPreference('GENERATE_UIDS'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Automatically create globally unique IDs” configuration setting */ I18N::translate('<b>GUID</b> in this context is an acronym for “Globally Unique ID”.<br><br>GUIDs are intended to help identify each individual in a manner that is repeatable, so that central organizations such as the Family History Center of the LDS church in Salt Lake City, or even compatible programs running on your own server, can determine whether they are dealing with the same individual no matter where the GEDCOM file originates. The goal of the Family History Center is to have a central repository of genealogy data and expose it through web services. This will enable any program to access the data and update their data within it.<br><br>If you do not intend to share this GEDCOM file with anyone else, you do not need to let webtrees create these GUIDs; however, doing so will do no harm other than increasing the size of your GEDCOM file.'); ?>
</p>
@@ -1001,7 +1002,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Theme dropdown selector for theme changes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('ALLOW_THEME_DROPDOWN', $hide_show, $WT_TREE->getPreference('ALLOW_THEME_DROPDOWN'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('ALLOW_THEME_DROPDOWN', $hide_show, $WT_TREE->getPreference('ALLOW_THEME_DROPDOWN'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Theme dropdown selector for theme changes” configuration setting */ I18N::translate('Gives users the option of selecting their own theme from a menu.<br><br>Even with this option set, the theme currently in effect may not provide for such a menu. To be effective, this option requires the <b>Allow users to select their own theme</b> option to be set as well.'); ?>
</p>
@@ -1014,7 +1015,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Default theme'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('THEME_DIR', Theme::themeNames(), I18N::translate('<default theme>'), $WT_TREE->getPreference('THEME_DIR'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('THEME_DIR', Theme::themeNames(), I18N::translate('<default theme>'), $WT_TREE->getPreference('THEME_DIR'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Default theme” configuration setting */ I18N::translate('You can change the appearance of webtrees using “themes”. Each theme has a different style, layout, color scheme, etc.'); ?>
</p>
@@ -1060,7 +1061,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Who can upload new media files?'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('MEDIA_UPLOAD', array(Auth::PRIV_USER => I18N::translate('Show to members'), Auth::PRIV_NONE => I18N::translate('Show to managers'), Auth::PRIV_HIDE => I18N::translate('Hide from everyone')), null, $WT_TREE->getPreference('MEDIA_UPLOAD'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('MEDIA_UPLOAD', array(Auth::PRIV_USER => I18N::translate('Show to members'), Auth::PRIV_NONE => I18N::translate('Show to managers'), Auth::PRIV_HIDE => I18N::translate('Hide from everyone')), null, $WT_TREE->getPreference('MEDIA_UPLOAD'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Who can upload new media files?” configuration setting */ I18N::translate('If you are concerned that users might upload inappropriate images, you can restrict media uploads to managers only.'); ?>
</p>
@@ -1073,7 +1074,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Show download link in media viewer'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_MEDIA_DOWNLOAD', $no_yes, $WT_TREE->getPreference('SHOW_MEDIA_DOWNLOAD'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_MEDIA_DOWNLOAD', $no_yes, $WT_TREE->getPreference('SHOW_MEDIA_DOWNLOAD'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show download link in media viewer” configuration setting */ I18N::translate('The Media Viewer can show a link which, when clicked, will download the media file to the local PC.<br><br>You may want to hide the download link for security reasons.'); ?>
</p>
@@ -1114,7 +1115,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Use silhouettes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('USE_SILHOUETTE', $no_yes, $WT_TREE->getPreference('USE_SILHOUETTE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('USE_SILHOUETTE', $no_yes, $WT_TREE->getPreference('USE_SILHOUETTE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Use silhouettes” configuration setting */ I18N::translate('Use silhouette images when no highlighted image for that individual has been specified. The images used are specific to the gender of the individual in question.'); ?>
</p>
@@ -1127,7 +1128,7 @@ $controller
<?php echo I18N::translate('Show highlight images in individual boxes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_HIGHLIGHT_IMAGES', $no_yes, $WT_TREE->getPreference('SHOW_HIGHLIGHT_IMAGES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_HIGHLIGHT_IMAGES', $no_yes, $WT_TREE->getPreference('SHOW_HIGHLIGHT_IMAGES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
</p>
</div>
@@ -1141,7 +1142,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Add watermarks to thumbnails?'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('WATERMARK_THUMB', $no_yes, $WT_TREE->getPreference('WATERMARK_THUMB'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('WATERMARK_THUMB', $no_yes, $WT_TREE->getPreference('WATERMARK_THUMB'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Add watermarks to thumbnails” configuration setting */ I18N::translate('A watermark is text that is added to an image, to discourage others from copying it without permission.'); ?>
</p>
@@ -1154,7 +1155,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Store watermarked full size images on server?'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SAVE_WATERMARK_IMAGE', $no_yes, $WT_TREE->getPreference('SAVE_WATERMARK_IMAGE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SAVE_WATERMARK_IMAGE', $no_yes, $WT_TREE->getPreference('SAVE_WATERMARK_IMAGE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Store watermarked full size images on server?” configuration setting */ I18N::translate('Watermarks can be slow to generate for large images. Busy websites may prefer to generate them once and store the watermarked image on the server.'); ?>
</p>
@@ -1167,7 +1168,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Store watermarked thumbnails on server?'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SAVE_WATERMARK_THUMB', $no_yes, $WT_TREE->getPreference('SAVE_WATERMARK_THUMB'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SAVE_WATERMARK_THUMB', $no_yes, $WT_TREE->getPreference('SAVE_WATERMARK_THUMB'), 'class="radio-inline"'); ?>
</div>
</fieldset>
@@ -1177,7 +1178,7 @@ $controller
<?php echo I18N::translate('Images without watermarks'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('SHOW_NO_WATERMARK', $privacy, null, $WT_TREE->getPreference('SHOW_NO_WATERMARK'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SHOW_NO_WATERMARK', $privacy, null, $WT_TREE->getPreference('SHOW_NO_WATERMARK'), 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Images without watermarks” configuration setting */ I18N::translate('Watermarks are optional and normally shown just to visitors.'); ?>
</p>
@@ -1257,7 +1258,7 @@ $controller
<?php echo I18N::translate('Surname list style'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('SURNAME_LIST_STYLE', $surname_list_styles, null, $WT_TREE->getPreference('SURNAME_LIST_STYLE'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('SURNAME_LIST_STYLE', $surname_list_styles, null, $WT_TREE->getPreference('SURNAME_LIST_STYLE'), 'class="form-control"'); ?>
<p class="small text-muted">
</p>
</div>
@@ -1290,7 +1291,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Estimated dates for birth and death'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_EST_LIST_DATES', $hide_show, $WT_TREE->getPreference('SHOW_EST_LIST_DATES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_EST_LIST_DATES', $hide_show, $WT_TREE->getPreference('SHOW_EST_LIST_DATES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Estimated dates for birth and death” configuration setting */ I18N::translate('This option controls whether or not to show estimated dates for birth and death instead of leaving blanks on individual lists and charts for individuals whose dates are not known.'); ?>
</p>
@@ -1303,7 +1304,7 @@ $controller
<?php echo I18N::translate('The date and time of the last update'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_LAST_CHANGE', $hide_show, $WT_TREE->getPreference('SHOW_LAST_CHANGE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_LAST_CHANGE', $hide_show, $WT_TREE->getPreference('SHOW_LAST_CHANGE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
</p>
</div>
@@ -1317,7 +1318,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Default pedigree chart layout'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('PEDIGREE_LAYOUT', $layouts, $WT_TREE->getPreference('PEDIGREE_LAYOUT'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('PEDIGREE_LAYOUT', $layouts, $WT_TREE->getPreference('PEDIGREE_LAYOUT'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Default pedigree chart layout” tree configuration setting */ I18N::translate('This option indicates whether the pedigree chart should be generated in landscape or portrait mode.'); ?>
</p>
@@ -1391,7 +1392,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Show chart details by default'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('PEDIGREE_FULL_DETAILS', $no_yes, $WT_TREE->getPreference('PEDIGREE_FULL_DETAILS'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('PEDIGREE_FULL_DETAILS', $no_yes, $WT_TREE->getPreference('PEDIGREE_FULL_DETAILS'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show chart details by default” configuration setting */ I18N::translate('This is the initial setting for the “show details” option on the charts.'); ?>
</p>
@@ -1404,7 +1405,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Gender icon on charts'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('PEDIGREE_SHOW_GENDER', $hide_show, $WT_TREE->getPreference('PEDIGREE_SHOW_GENDER'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('PEDIGREE_SHOW_GENDER', $hide_show, $WT_TREE->getPreference('PEDIGREE_SHOW_GENDER'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Gender icon on charts” configuration setting */ I18N::translate('This option controls whether or not to show the individual’s gender icon on charts.<br><br>Since the gender is also indicated by the color of the box, this option doesn’t conceal the gender. The option simply removes some duplicate information from the box.'); ?>
</p>
@@ -1417,7 +1418,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Age of parents next to child’s birthdate'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_PARENTS_AGE', $hide_show, $WT_TREE->getPreference('SHOW_PARENTS_AGE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_PARENTS_AGE', $hide_show, $WT_TREE->getPreference('SHOW_PARENTS_AGE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Age of parents next to child’s birthdate” configuration setting */ I18N::translate('This option controls whether or not to show age of father and mother next to child’s birthdate on charts.'); ?>
</p>
@@ -1430,7 +1431,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('LDS ordinance codes in chart boxes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_LDS_AT_GLANCE', $hide_show, $WT_TREE->getPreference('SHOW_LDS_AT_GLANCE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_LDS_AT_GLANCE', $hide_show, $WT_TREE->getPreference('SHOW_LDS_AT_GLANCE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “LDS ordinance codes in chart boxes” configuration setting. "B", "E", "S" and "P" should not be translated. */ I18N::translate('This is a summary of the <abbr title="The Church of Jesus Christ of Latter-day Saints">LDS</abbr> ordinances for the individual. “B” indicates an LDS baptism. “E” indicates an LDS endowment. “S” indicates an LDS spouse sealing. “P” indicates an LDS child-to-parent sealing.'); ?>
</p>
@@ -1474,7 +1475,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Automatically expand list of events of close relatives'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('EXPAND_RELATIVES_EVENTS', $no_yes, $WT_TREE->getPreference('EXPAND_RELATIVES_EVENTS'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('EXPAND_RELATIVES_EVENTS', $no_yes, $WT_TREE->getPreference('EXPAND_RELATIVES_EVENTS'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Automatically expand list of events of close relatives” configuration setting */ I18N::translate('This option controls whether or not to automatically expand the <i>Events of close relatives</i> list.'); ?>
</p>
@@ -1578,7 +1579,7 @@ $controller
I18N::translate('Fact icons'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_FACT_ICONS', $hide_show, $WT_TREE->getPreference('SHOW_FACT_ICONS'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_FACT_ICONS', $hide_show, $WT_TREE->getPreference('SHOW_FACT_ICONS'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Fact icons” configuration setting */
I18N::translate('Some themes can display icons on the “Facts and events” tab.'); ?>
@@ -1593,7 +1594,7 @@ $controller
I18N::translate('Automatically expand notes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('EXPAND_NOTES', $no_yes, $WT_TREE->getPreference('EXPAND_NOTES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('EXPAND_NOTES', $no_yes, $WT_TREE->getPreference('EXPAND_NOTES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Automatically expand notes” configuration setting */
I18N::translate('This option controls whether or not to automatically display content of a <i>Note</i> record on the Individual page.'); ?>
@@ -1608,7 +1609,7 @@ $controller
I18N::translate('Automatically expand sources'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('EXPAND_SOURCES', $no_yes, $WT_TREE->getPreference('EXPAND_SOURCES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('EXPAND_SOURCES', $no_yes, $WT_TREE->getPreference('EXPAND_SOURCES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Automatically expand sources” configuration setting */
I18N::translate('This option controls whether or not to automatically display content of a <i>Source</i> record on the Individual page.'); ?>
@@ -1623,7 +1624,7 @@ $controller
I18N::translate('Show all notes and source references on notes and sources tabs'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_LEVEL2_NOTES', $no_yes, $WT_TREE->getPreference('SHOW_LEVEL2_NOTES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_LEVEL2_NOTES', $no_yes, $WT_TREE->getPreference('SHOW_LEVEL2_NOTES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Show all notes and source references on notes and sources tabs” configuration setting */
I18N::translate('This option controls whether Notes and Source references that are attached to Facts should be shown on the Notes and Sources tabs of the Individual page.<br><br>Ordinarily, the Notes and Sources tabs show only Notes and Source references that are attached directly to the individual’s database record. These are <i>level 1</i> Notes and Source references.<br><br>The <b>Yes</b> option causes these tabs to also show Notes and Source references that are part of the various Facts in the individual’s database record. These are <i>level 2</i> Notes and Source references because the various Facts are at level 1.'); ?>
@@ -1638,7 +1639,7 @@ $controller
I18N::translate('Date differences'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_AGE_DIFF', $hide_show, $WT_TREE->getPreference('SHOW_AGE_DIFF'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_AGE_DIFF', $hide_show, $WT_TREE->getPreference('SHOW_AGE_DIFF'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Date differences” configuration setting */
I18N::translate('When this option is selected, webtrees will calculate the age differences between siblings, children, spouses, etc.'); ?>
@@ -1663,13 +1664,13 @@ $controller
<div class="col-sm-9">
<?php echo /* I18N: The placeholders are edit controls. Show the [first/last] [1/2/3/4/5] parts of a place name */ I18N::translate(
'Show the %1$s %2$s parts of a place name.',
- select_edit_control('SHOW_PEDIGREE_PLACES_SUFFIX',
+ FunctionsEdit::selectEditControl('SHOW_PEDIGREE_PLACES_SUFFIX',
array(
false => I18N::translateContext('Show the [first/last] [N] parts of a place name.', 'first'),
true => I18N::translateContext('Show the [first/last] [N] parts of a place name.', 'last'),
), null, $WT_TREE->getPreference('SHOW_PEDIGREE_PLACES_SUFFIX')
),
- select_edit_control('SHOW_PEDIGREE_PLACES', $one_to_nine, null, $WT_TREE->getPreference('SHOW_PEDIGREE_PLACES'))
+ FunctionsEdit::selectEditControl('SHOW_PEDIGREE_PLACES', $one_to_nine, null, $WT_TREE->getPreference('SHOW_PEDIGREE_PLACES'))
); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Abbreviate place names” configuration setting */ I18N::translate('Place names are frequently too long to fit on charts, lists, etc. They can be abbreviated by showing just the first few parts of the name, such as <i>village, county</i>, or the last few part of it, such as <i>region, country</i>.'); ?>
@@ -1685,7 +1686,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Format text and notes'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('FORMAT_TEXT', $formats, $WT_TREE->getPreference('FORMAT_TEXT'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('FORMAT_TEXT', $formats, $WT_TREE->getPreference('FORMAT_TEXT'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Format text and notes” configuration setting */ I18N::translate('To ensure compatibility with other genealogy applications, notes, text, and transcripts should be recorded in simple, unformatted text. However, formatting is often desirable to aid presentation, comprehension, etc.'); ?>
</p>
@@ -1703,7 +1704,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Allow users to see raw GEDCOM records'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_GEDCOM_RECORD', $no_yes, $WT_TREE->getPreference('SHOW_GEDCOM_RECORD'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_GEDCOM_RECORD', $no_yes, $WT_TREE->getPreference('SHOW_GEDCOM_RECORD'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Allow users to see raw GEDCOM records” configuration setting */ I18N::translate('Setting this to <b>Yes</b> will place links on individuals, sources, and families to let users bring up another window containing the raw data taken right out of the GEDCOM file.'); ?>
</p>
@@ -1716,7 +1717,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('GEDCOM errors'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('HIDE_GEDCOM_ERRORS', $hide_show, $WT_TREE->getPreference('HIDE_GEDCOM_ERRORS'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('HIDE_GEDCOM_ERRORS', $hide_show, $WT_TREE->getPreference('HIDE_GEDCOM_ERRORS'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “GEDCOM errors” configuration setting */ I18N::translate('Many genealogy programs create GEDCOM files with custom tags, and webtrees understands most of them. When unrecognized tags are found, this option lets you choose whether to ignore them or display a warning message.'); ?>
</p>
@@ -1729,7 +1730,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Hit counters'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SHOW_COUNTER', $hide_show, $WT_TREE->getPreference('SHOW_COUNTER'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SHOW_COUNTER', $hide_show, $WT_TREE->getPreference('SHOW_COUNTER'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Hit counters” configuration setting */ I18N::translate('Show hit counters on Portal and Individual pages.'); ?>
</p>
@@ -2220,7 +2221,7 @@ $controller
<?php echo I18N::translate('Surname tradition'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('SURNAME_TRADITION', $surname_traditions, $WT_TREE->getPreference('SURNAME_TRADITION'), 'class="radio" style="padding-left:20px;font-weight:normal;"'); ?>
+ <?php echo FunctionsEdit::radioButtons('SURNAME_TRADITION', $surname_traditions, $WT_TREE->getPreference('SURNAME_TRADITION'), 'class="radio" style="padding-left:20px;font-weight:normal;"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Surname tradition” configuration setting */ I18N::translate('When you add a new family member, a default surname can be provided. This surname will depend on the local tradition.'); ?>
</p>
@@ -2233,7 +2234,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Use full source citations'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('FULL_SOURCES', $no_yes, $WT_TREE->getPreference('FULL_SOURCES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('FULL_SOURCES', $no_yes, $WT_TREE->getPreference('FULL_SOURCES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Use full source citations” configuration setting */ I18N::translate('Source citations can include fields to record the quality of the data (primary, secondary, etc.) and the date the event was recorded in the source. If you don’t use these fields, you can disable them when creating new source citations.'); ?>
</p>
@@ -2246,7 +2247,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Source type'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('PREFER_LEVEL2_SOURCES', $source_types, $WT_TREE->getPreference('PREFER_LEVEL2_SOURCES'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('PREFER_LEVEL2_SOURCES', $source_types, $WT_TREE->getPreference('PREFER_LEVEL2_SOURCES'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Source type” configuration setting */ I18N::translate('When adding new close relatives, you can add source citations to the records (e.g. INDI, FAM) or the facts (BIRT, MARR, DEAT). This option controls which checkboxes are ticked by default.'); ?>
</p>
@@ -2280,7 +2281,7 @@ $controller
<?php echo /* I18N: A configuration setting */ I18N::translate('Do not update the “last change” record'); ?>
</legend>
<div class="col-sm-9">
- <?php echo radio_buttons('NO_UPDATE_CHAN', $no_yes, $WT_TREE->getPreference('NO_UPDATE_CHAN'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::radioButtons('NO_UPDATE_CHAN', $no_yes, $WT_TREE->getPreference('NO_UPDATE_CHAN'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Do not update the ‘last change’ record” configuration setting */ I18N::translate('Administrators sometimes need to clean up and correct the data submitted by users. For example, they might need to correct the PLAC location to include the country. When administrators make such corrections, information about the original change is normally replaced. This may not be desirable.<br><br>When this option is selected, webtrees will retain the original change information instead of replacing it with that of the current session. With this option selected, administrators also have the ability to modify or delete the information associated with the original CHAN tag.'); ?>
</p>
diff --git a/admin_trees_download.php b/admin_trees_download.php
index 150c03fc92..88927a0f26 100644
--- a/admin_trees_download.php
+++ b/admin_trees_download.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsExport;
use PclZip;
define('WT_SCRIPT_NAME', 'admin_trees_download.php');
@@ -66,7 +67,7 @@ if ($action === 'download') {
// Create the unzipped GEDCOM on disk, so we can ZIP it.
$stream = fopen($temp_dir . $download_filename, "w");
- export_gedcom($WT_TREE, $stream, $exportOptions);
+ FunctionsExport::exportGedcom($WT_TREE, $stream, $exportOptions);
fclose($stream);
// Create a ZIP file containing the GEDCOM file.
@@ -88,7 +89,7 @@ if ($action === 'download') {
// Stream the GEDCOM file straight to the browser.
// We could open "php://compress.zlib" to create a .gz file or "php://compress.bzip2" to create a .bz2 file
$stream = fopen('php://output', 'w');
- export_gedcom($WT_TREE, $stream, $exportOptions);
+ FunctionsExport::exportGedcom($WT_TREE, $stream, $exportOptions);
fclose($stream);
}
diff --git a/admin_trees_merge.php b/admin_trees_merge.php
index 40ba5af1f9..a92f1f6e36 100644
--- a/admin_trees_merge.php
+++ b/admin_trees_merge.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
define('WT_SCRIPT_NAME', 'admin_trees_merge.php');
require './includes/session.php';
@@ -193,8 +194,8 @@ if ($tree1_id && $tree2_id != $tree1_id) {
echo '<input type="hidden" name="go" value="1">';
echo '<p>', I18N::translate(/* I18N: Copy all the records from [family tree 1] into [family tree 2] */
'Copy all the records from %1$s into %2$s.',
- select_edit_control('tree1_id', Tree::getIdList(), '', null),
- select_edit_control('tree2_id', Tree::getIdList(), '', null)
+ FunctionsEdit::selectEditControl('tree1_id', Tree::getIdList(), '', null),
+ FunctionsEdit::selectEditControl('tree2_id', Tree::getIdList(), '', null)
),
'</p>';
diff --git a/admin_users.php b/admin_users.php
index 04e538fbf3..31e4ddfd0c 100644
--- a/admin_users.php
+++ b/admin_users.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use PDO;
/**
@@ -225,13 +228,13 @@ case 'load_json':
$datum[5] = $installed_languages[$datum[5]];
}
// $datum[6] is the sortable registration timestamp
- $datum[7] = $datum[7] ? format_timestamp($datum[7]) : '';
+ $datum[7] = $datum[7] ? FunctionsDate::formatTimestamp($datum[7]) : '';
if (date("U") - $datum[6] > 604800 && !$datum[10]) {
$datum[7] = '<span class="red">' . $datum[7] . '</span>';
}
// $datum[8] is the sortable last-login timestamp
if ($datum[8]) {
- $datum[9] = format_timestamp($datum[8]) . '<br>' . I18N::timeAgo(WT_TIMESTAMP - $datum[8]);
+ $datum[9] = FunctionsDate::formatTimestamp($datum[8]) . '<br>' . I18N::timeAgo(WT_TIMESTAMP - $datum[8]);
} else {
$datum[9] = I18N::translate('Never');
}
@@ -454,7 +457,7 @@ case 'edit':
<?php echo /* I18N: A configuration setting */ I18N::translate('Preferred contact method'); ?>
</label>
<div class="col-sm-9">
- <?php echo edit_field_contact('contact_method', $user->getPreference('contactmethod')); ?>
+ <?php echo FunctionsEdit::editFieldContact('contact_method', $user->getPreference('contactmethod')); ?>
<p class="small text-muted">
<?php echo /* I18N: Help text for the “Preferred contact method” configuration setting */
I18N::translate('Site members can send each other messages. You can choose to how these messages are sent to you, or choose not receive them at all.'); ?>
@@ -468,7 +471,7 @@ case 'edit':
<?php echo I18N::translate('Theme'); ?>
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('theme', Theme::themeNames(), I18N::translate('<default theme>'), $user->getPreference('theme'), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('theme', Theme::themeNames(), I18N::translate('<default theme>'), $user->getPreference('theme'), 'class="form-control"'); ?>
</div>
</div>
@@ -621,7 +624,7 @@ case 'edit':
id="rootid<?php echo $tree->getTreeId(); ?>"
value="<?php echo Filter::escapeHtml($tree->getUserPreference($user, 'rootid')); ?>"
>
- <?php echo print_findindi_link('rootid' . $tree->getTreeId(), '', $tree); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid' . $tree->getTreeId(), '', $tree); ?>
</td>
<td>
<input
@@ -633,7 +636,7 @@ case 'edit':
id="gedcomid<?php echo $tree->getTreeId(); ?>"
value="<?php echo Filter::escapeHtml($tree->getUserPreference($user, 'gedcomid')); ?>"
>
- <?php echo print_findindi_link('gedcomid' . $tree->getTreeId(), '', $tree); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('gedcomid' . $tree->getTreeId(), '', $tree); ?>
</td>
<td>
<select name="RELATIONSHIP_PATH_LENGTH<?php echo $tree->getTreeId(); ?>" id="RELATIONSHIP_PATH_LENGTH<?php echo $tree->getTreeId(); ?>" class="relpath">
@@ -711,7 +714,7 @@ case 'cleanup':
</a>
</td>
<td>
- <?php echo I18N::translate('User’s account has been inactive too long: ') . timestamp_to_gedcom_date($datelogin)->display(); ?>
+ <?php echo I18N::translate('User’s account has been inactive too long: ') . FunctionsDate::timestampToGedcomDate($datelogin)->display(); ?>
</td>
<td>
<input type="checkbox" name="del_<?php echo $user->getUserId(); ?>" value="1">
diff --git a/ancestry.php b/ancestry.php
index 0328e12e58..a208f89c6c 100644
--- a/ancestry.php
+++ b/ancestry.php
@@ -24,6 +24,10 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\AncestryController;
+use Fisharebest\Webtrees\Functions\FunctionsCharts;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'ancestry.php');
require './includes/session.php';
@@ -49,7 +53,7 @@ $controller
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="rootid" id="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td rowspan="3" class="descriptionbox">
<label><?php echo I18N::translate('Layout'); ?></label>
@@ -67,7 +71,7 @@ $controller
<?php echo I18N::translate('Booklet'); ?>
</label>
<label>
- <?php echo two_state_checkbox('show_cousins', $controller->show_cousins, "id='cousins' " . ($controller->chart_style === 1 ? '' : 'disabled')); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox('show_cousins', $controller->show_cousins, "id='cousins' " . ($controller->chart_style === 1 ? '' : 'disabled')); ?>
<?php echo I18N::translate('Show cousins'); ?>
</label>
</div>
@@ -111,7 +115,7 @@ $controller
<?php echo '<label>', I18N::translate('Show details'), '</label>'; ?>
</td>
<td class="optionbox">
- <?php echo checkbox('show_full', $controller->showFull()); ?>
+ <?php echo FunctionsEdit::checkbox('show_full', $controller->showFull()); ?>
</td>
</tr>
</tbody>
@@ -136,14 +140,14 @@ case 1:
echo '<div id="ancestry_booklet">';
// Booklet
// first page : show indi facts
- print_pedigree_person($controller->root, $controller->showFull());
+ FunctionsPrint::printPedigreePerson($controller->root, $controller->showFull());
// process the tree
$ancestors = $controller->sosaAncestors($controller->generations - 1);
$ancestors = array_filter($ancestors); // The SOSA array includes empty placeholders
foreach ($ancestors as $sosa => $individual) {
foreach ($individual->getChildFamilies() as $family) {
- print_sosa_family($family->getXref(), $individual->getXref(), $sosa, '', '', '', $controller->show_cousins, $controller->showFull());
+ FunctionsCharts::printSosaFamily($family->getXref(), $individual->getXref(), $sosa, '', '', '', $controller->show_cousins, $controller->showFull());
}
}
echo '</div>';
@@ -152,7 +156,7 @@ case 2:
// Individual list
$ancestors = $controller->sosaAncestors($controller->generations);
$ancestors = array_filter($ancestors); // The SOSA array includes empty placeholders
- echo '<div id="ancestry-list">', format_indi_table($ancestors, 'sosa'), '</div>';
+ echo '<div id="ancestry-list">', FunctionsPrintLists::individualTable($ancestors, 'sosa'), '</div>';
break;
case 3:
// Family list
@@ -164,7 +168,7 @@ case 3:
$families[$family->getXref()] = $family;
}
}
- echo '<div id="ancestry-list">', format_fam_table($families), '</div>';
+ echo '<div id="ancestry-list">', FunctionsPrintLists::familyTable($families), '</div>';
break;
}
echo '</div>';
diff --git a/app/ConfigData.php b/app/ConfigData.php
index 1a4fdc1356..3cf8ec9c5a 100644
--- a/app/ConfigData.php
+++ b/app/ConfigData.php
@@ -204,7 +204,7 @@ class ConfigData {
*/
public static function standardNameFacts() {
return array(
- 'NAME', 'NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX'
+ 'NAME', 'NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX',
);
}
}
diff --git a/app/Controller/AdvancedSearchController.php b/app/Controller/AdvancedSearchController.php
index 120323f121..51911f08d1 100644
--- a/app/Controller/AdvancedSearchController.php
+++ b/app/Controller/AdvancedSearchController.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Controller;
*/
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -638,7 +639,7 @@ class AdvancedSearchController extends SearchController {
public function printResults() {
if ($this->myindilist) {
uasort($this->myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare');
- echo format_indi_table($this->myindilist);
+ echo FunctionsPrintLists::individualTable($this->myindilist);
} elseif (array_filter($this->values)) {
echo '<p class="ui-state-highlight">', I18N::translate('No results found.'), '</p>';
}
diff --git a/app/Controller/AncestryController.php b/app/Controller/AncestryController.php
index 2d374664aa..51f31f0491 100644
--- a/app/Controller/AncestryController.php
+++ b/app/Controller/AncestryController.php
@@ -16,6 +16,8 @@ namespace Fisharebest\Webtrees\Controller;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsCharts;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -72,13 +74,13 @@ class AncestryController extends ChartController {
echo '<img src="', Theme::theme()->parameter('image-spacer'), '" height="3" width="2" alt="">';
echo '<img src="', Theme::theme()->parameter('image-hline'), '" height="3" width="', Theme::theme()->parameter('chart-descendancy-indent') - 2, '"></td><td>';
}
- print_pedigree_person($individual, $this->showFull());
+ FunctionsPrint::printPedigreePerson($individual, $this->showFull());
echo '</td><td>';
if ($sosa > 1) {
- print_url_arrow('?rootid=' . $individual->getXref() . '&amp;PEDIGREE_GENERATIONS=' . $this->generations . '&amp;show_full=' . $this->showFull() . '&amp;chart_style=' . $this->chart_style . '&amp;ged=' . $individual->getTree()->getNameUrl(), I18N::translate('Ancestors of %s', $individual->getFullName()), 3);
+ FunctionsCharts::printUrlArrow('?rootid=' . $individual->getXref() . '&amp;PEDIGREE_GENERATIONS=' . $this->generations . '&amp;show_full=' . $this->showFull() . '&amp;chart_style=' . $this->chart_style . '&amp;ged=' . $individual->getTree()->getNameUrl(), I18N::translate('Ancestors of %s', $individual->getFullName()), 3);
}
echo '</td><td class="details1">&nbsp;<span class="person_box' . ($sosa === 1 ? 'NN' : ($sosa % 2 ? 'F' : '')) . '">', I18N::number($sosa), '</span> ';
- echo '</td><td class="details1">&nbsp;', get_sosa_name($sosa), '</td>';
+ echo '</td><td class="details1">&nbsp;', FunctionsCharts::getSosaName($sosa), '</td>';
echo '</tr></tbody></table>';
// Parents
diff --git a/app/Controller/DescendancyController.php b/app/Controller/DescendancyController.php
index 3b45faf911..d2bfe2424a 100644
--- a/app/Controller/DescendancyController.php
+++ b/app/Controller/DescendancyController.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Controller;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsCharts;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -77,7 +79,7 @@ class DescendancyController extends ChartController {
return;
}
foreach ($person->getSpouseFamilies() as $family) {
- print_sosa_family($family->getXref(), '', -1, $label, $person->getXref(), $gpid, 0, $this->showFull());
+ FunctionsCharts::printSosaFamily($family->getXref(), '', -1, $label, $person->getXref(), $gpid, 0, $this->showFull());
$i = 1;
foreach ($family->getChildren() as $child) {
$this->printChildFamily($child, $depth - 1, $label . ($i++) . '.', $person->getXref());
@@ -100,7 +102,7 @@ class DescendancyController extends ChartController {
echo "<img src=\"" . Theme::theme()->parameter('image-spacer') . "\" height=\"3\" width=\"3\" alt=\"\">";
echo "<img src=\"" . Theme::theme()->parameter('image-hline') . "\" height=\"3\" width=\"", Theme::theme()->parameter('chart-descendancy-indent') - 3, "\" alt=\"\"></td><td>";
}
- print_pedigree_person($person, $this->showFull());
+ FunctionsPrint::printPedigreePerson($person, $this->showFull());
echo '</td>';
// check if child has parents and add an arrow
@@ -108,7 +110,7 @@ class DescendancyController extends ChartController {
echo '<td>';
foreach ($person->getChildFamilies() as $cfamily) {
foreach ($cfamily->getSpouses() as $parent) {
- print_url_arrow('?rootid=' . $parent->getXref() . '&amp;generations=' . $this->generations . '&amp;chart_style=' . $this->chart_style . '&amp;show_full=' . $this->showFull() . '&amp;ged=' . $parent->getTree()->getNameUrl(), I18N::translate('Start at parents'), 2);
+ FunctionsCharts::printUrlArrow('?rootid=' . $parent->getXref() . '&amp;generations=' . $this->generations . '&amp;chart_style=' . $this->chart_style . '&amp;show_full=' . $this->showFull() . '&amp;ged=' . $parent->getTree()->getNameUrl(), I18N::translate('Start at parents'), 2);
// only show the arrow for one of the parents
break;
}
@@ -176,7 +178,7 @@ class DescendancyController extends ChartController {
echo '<ul id="' . $uid . '" class="generation">';
echo '<li>';
echo '<table><tr><td>';
- print_pedigree_person($spouse, $this->showFull());
+ FunctionsPrint::printPedigreePerson($spouse, $this->showFull());
echo '</td>';
// check if spouse has parents and add an arrow
@@ -185,7 +187,7 @@ class DescendancyController extends ChartController {
if ($spouse) {
foreach ($spouse->getChildFamilies() as $cfamily) {
foreach ($cfamily->getSpouses() as $parent) {
- print_url_arrow('?rootid=' . $parent->getXref() . '&amp;generations=' . $this->generations . '&amp;chart_style=' . $this->chart_style . '&amp;show_full=' . $this->showFull() . '&amp;ged=' . $parent->getTree()->getNameUrl(), I18N::translate('Start at parents'), 2);
+ FunctionsCharts::printUrlArrow('?rootid=' . $parent->getXref() . '&amp;generations=' . $this->generations . '&amp;chart_style=' . $this->chart_style . '&amp;show_full=' . $this->showFull() . '&amp;ged=' . $parent->getTree()->getNameUrl(), I18N::translate('Start at parents'), 2);
// only show the arrow for one of the parents
break;
}
diff --git a/app/Controller/FamilyBookController.php b/app/Controller/FamilyBookController.php
index ad2dd42f72..2aac9d6ef9 100644
--- a/app/Controller/FamilyBookController.php
+++ b/app/Controller/FamilyBookController.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Controller;
*/
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -152,7 +153,7 @@ class FamilyBookController extends ChartController {
}
echo '<table><tr><td>';
if ($person) {
- print_pedigree_person($person, $this->showFull());
+ FunctionsPrint::printPedigreePerson($person, $this->showFull());
echo '</td><td>',
'<img class="line2" src="', Theme::theme()->parameter('image-hline'), '" width="8" height="3" alt="">';
} else {
@@ -166,7 +167,7 @@ class FamilyBookController extends ChartController {
foreach ($person->getSpouseFamilies() as $family) {
$spouse = $family->getSpouse($person);
echo '</td></tr><tr><td>';
- print_pedigree_person($spouse, $this->showFull());
+ FunctionsPrint::printPedigreePerson($spouse, $this->showFull());
$numkids += 0.95;
echo '</td><td>';
}
@@ -259,7 +260,7 @@ class FamilyBookController extends ChartController {
'<td>';
$lh = $savlh; // restore original line height
//-- print the father box
- print_pedigree_person($family->getHusband(), $this->showFull());
+ FunctionsPrint::printPedigreePerson($family->getHusband(), $this->showFull());
echo '</td>';
if ($family->getHusband()) {
echo '<td>';
@@ -282,7 +283,7 @@ class FamilyBookController extends ChartController {
'<td><img class="line4" src="', Theme::theme()->parameter('image-hline'), '" height="3"></td>',
'<td>';
//-- print the mother box
- print_pedigree_person($family->getWife(), $this->showFull());
+ FunctionsPrint::printPedigreePerson($family->getWife(), $this->showFull());
echo '</td>';
if ($family->getWife()) {
echo '<td>';
diff --git a/app/Controller/FamilyController.php b/app/Controller/FamilyController.php
index e657168ee3..d0d31f3403 100644
--- a/app/Controller/FamilyController.php
+++ b/app/Controller/FamilyController.php
@@ -18,6 +18,9 @@ namespace Fisharebest\Webtrees\Controller;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Menu;
@@ -177,16 +180,16 @@ class FamilyController extends GedcomRecordController {
$indifacts = $this->record->getFacts();
if ($indifacts) {
- sort_facts($indifacts);
+ Functions::sortFacts($indifacts);
foreach ($indifacts as $fact) {
- print_fact($fact, $this->record);
+ FunctionsPrintFacts::printFact($fact, $this->record);
}
} else {
echo '<tr><td class="messagebox" colspan="2">', I18N::translate('No facts for this family.'), '</td></tr>';
}
if (Auth::isEditor($this->record->getTree())) {
- print_add_new_fact($this->record->getXref(), $indifacts, 'FAM');
+ FunctionsPrint::printAddNewFact($this->record->getXref(), $indifacts, 'FAM');
echo '<tr><td class="descriptionbox">';
echo I18N::translate('Note');
@@ -205,7 +208,7 @@ class FamilyController extends GedcomRecordController {
echo I18N::translate('Media object');
echo '</td><td class="optionbox">';
echo "<a href=\"#\" onclick=\"window.open('addmedia.php?action=showmediaform&amp;linktoid=" . $this->record->getXref() . "', '_blank', edit_window_specs); return false;\">", I18N::translate('Add a new media object'), '</a>';
- echo help_link('OBJE');
+ echo FunctionsPrint::helpLink('OBJE');
echo '<br>';
echo "<a href=\"#\" onclick=\"window.open('inverselink.php?linktoid=" . $this->record->getXref() . "&amp;linkto=family', '_blank', find_window_specs); return false;\">", I18N::translate('Link to an existing media object'), '</a>';
echo '</td></tr>';
diff --git a/app/Controller/HourglassController.php b/app/Controller/HourglassController.php
index e0073a3908..abcf723008 100644
--- a/app/Controller/HourglassController.php
+++ b/app/Controller/HourglassController.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Controller;
*/
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -120,7 +121,7 @@ class HourglassController extends ChartController {
echo '<td><img class="line4" src="' . Theme::theme()->parameter('image-hline') . '" width="7" height="3"></td>';
echo '<td>';
//-- print the father box
- print_pedigree_person($family->getHusband(), $this->showFull());
+ FunctionsPrint::printPedigreePerson($family->getHusband(), $this->showFull());
echo "</td>";
if ($family->getHusband()) {
$ARID = $family->getHusband()->getXref();
@@ -152,7 +153,7 @@ class HourglassController extends ChartController {
'<td><img class="line4" src="' . Theme::theme()->parameter('image-hline') . '" width="7" height="3" alt=""></td>',
'<td>';
//-- print the mother box
- print_pedigree_person($family->getWife(), $this->showFull());
+ FunctionsPrint::printPedigreePerson($family->getWife(), $this->showFull());
echo '</td>';
if ($family->getWife()) {
$ARID = $family->getWife()->getXref();
@@ -285,7 +286,7 @@ class HourglassController extends ChartController {
}
echo '<table id="table2_' . $pid . '"><tr><td>';
- print_pedigree_person($person, $this->showFull());
+ FunctionsPrint::printPedigreePerson($person, $this->showFull());
echo '</td><td><img class="line2" src="' . Theme::theme()->parameter('image-hline') . '" width="7" height="3">';
//----- Print the spouse
@@ -297,7 +298,7 @@ class HourglassController extends ChartController {
$temph = $this->getBoxDimensions()->height;
$this->getBoxDimensions()->width -= 10;
$this->getBoxDimensions()->height -= 10;
- print_pedigree_person($family->getSpouse($person), $this->showFull());
+ FunctionsPrint::printPedigreePerson($family->getSpouse($person), $this->showFull());
$this->getBoxDimensions()->width = $tempw;
$this->getBoxDimensions()->height = $temph;
$numkids += 0.95;
diff --git a/app/Controller/IndividualController.php b/app/Controller/IndividualController.php
index 995f764779..8777a44a3f 100644
--- a/app/Controller/IndividualController.php
+++ b/app/Controller/IndividualController.php
@@ -21,6 +21,9 @@ use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -48,7 +51,7 @@ class IndividualController extends GedcomRecordController {
$this->record = Individual::getInstance($xref, $WT_TREE);
if (!$this->record && $WT_TREE->getPreference('USE_RIN')) {
- $rin = find_rin_id($xref);
+ $rin = FunctionsDb::findRin($xref);
$this->record = Individual::getInstance($rin, $WT_TREE);
}
@@ -209,10 +212,10 @@ class IndividualController extends GedcomRecordController {
echo '</div>';
}
if (preg_match("/\n2 SOUR/", $factrec)) {
- echo '<div id="indi_sour" class="clearfloat">', print_fact_sources($factrec, 2), '</div>';
+ echo '<div id="indi_sour" class="clearfloat">', FunctionsPrintFacts::printFactSources($factrec, 2), '</div>';
}
if (preg_match("/\n2 NOTE/", $factrec)) {
- echo '<div id="indi_note" class="clearfloat">', print_fact_notes($factrec, 2), '</div>';
+ echo '<div id="indi_note" class="clearfloat">', FunctionsPrint::printFactNotes($factrec, 2), '</div>';
}
echo '</div>';
}
diff --git a/app/Controller/LifespanController.php b/app/Controller/LifespanController.php
index f0468bcd8f..bcd062a384 100644
--- a/app/Controller/LifespanController.php
+++ b/app/Controller/LifespanController.php
@@ -20,6 +20,7 @@ use Fisharebest\ExtCalendar\GregorianCalendar;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -430,7 +431,7 @@ class LifespanController extends PageController {
$unparsedEvents[] = $fact;
}
}
- sort_facts($unparsedEvents);
+ Functions::sortFacts($unparsedEvents);
$eventinformation = array();
foreach ($unparsedEvents as $val) {
diff --git a/app/Controller/MediaController.php b/app/Controller/MediaController.php
index eba6c2d301..b7b968a473 100644
--- a/app/Controller/MediaController.php
+++ b/app/Controller/MediaController.php
@@ -19,6 +19,7 @@ namespace Fisharebest\Webtrees\Controller;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Menu;
@@ -131,7 +132,7 @@ class MediaController extends GedcomRecordController {
$facts[] = new Fact('1 __FILE_SIZE__ ' . $this->record->getFilesize(), $this->record, 0);
}
- sort_facts($facts);
+ Functions::sortFacts($facts);
return $facts;
}
diff --git a/app/Controller/PageController.php b/app/Controller/PageController.php
index 309a196d00..848774552a 100644
--- a/app/Controller/PageController.php
+++ b/app/Controller/PageController.php
@@ -20,6 +20,7 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -115,7 +116,7 @@ class PageController extends BaseController {
*/
public function restrictAccess($condition) {
if ($condition !== true) {
- header('Location: ' . WT_LOGIN_URL . '?url=' . rawurlencode(get_query_url()));
+ header('Location: ' . WT_LOGIN_URL . '?url=' . rawurlencode(Functions::getQueryUrl()));
exit;
}
diff --git a/app/Controller/PedigreeController.php b/app/Controller/PedigreeController.php
index 1de6c89f66..09b7559683 100644
--- a/app/Controller/PedigreeController.php
+++ b/app/Controller/PedigreeController.php
@@ -257,7 +257,7 @@ class PedigreeController extends ChartController {
*
* @return string
*/
- public function get_menu() {
+ public function getMenu() {
$famids = $this->root->getSpouseFamilies();
$html = '';
if ($famids) {
diff --git a/app/Controller/SearchController.php b/app/Controller/SearchController.php
index e33cbb220f..946c3875cb 100644
--- a/app/Controller/SearchController.php
+++ b/app/Controller/SearchController.php
@@ -21,6 +21,8 @@ use Fisharebest\Webtrees\ConfigData;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -238,26 +240,26 @@ class SearchController extends PageController {
// Search the individuals
if ($this->srindi && $query_terms) {
- $this->myindilist = search_indis($query_terms, $this->search_trees);
+ $this->myindilist = FunctionsDb::searchIndividuals($query_terms, $this->search_trees);
}
// Search the fams
if ($this->srfams && $query_terms) {
$this->myfamlist = array_merge(
- search_fams($query_terms, $this->search_trees),
- search_fams_names($query_terms, $this->search_trees)
+ FunctionsDb::searchFamilies($query_terms, $this->search_trees),
+ FunctionsDb::searchFamilyNames($query_terms, $this->search_trees)
);
$this->myfamlist = array_unique($this->myfamlist);
}
// Search the sources
if ($this->srsour && $query_terms) {
- $this->mysourcelist = search_sources($query_terms, $this->search_trees);
+ $this->mysourcelist = FunctionsDb::searchSources($query_terms, $this->search_trees);
}
// Search the notes
if ($this->srnote && $query_terms) {
- $this->mynotelist = search_notes($query_terms, $this->search_trees);
+ $this->mynotelist = FunctionsDb::searchNotes($query_terms, $this->search_trees);
}
// If only 1 item is returned, automatically forward to that item
@@ -462,7 +464,7 @@ class SearchController extends PageController {
Log::addSearchLog($logstring, $this->search_trees);
if ($this->search_trees) {
- $this->myindilist = search_indis_soundex($this->soundex, $this->lastname, $this->firstname, $this->place, $this->search_trees);
+ $this->myindilist = FunctionsDb::searchIndividualsPhonetic($this->soundex, $this->lastname, $this->firstname, $this->place, $this->search_trees);
} else {
$this->myindilist = array();
}
@@ -524,16 +526,16 @@ class SearchController extends PageController {
}
echo '</ul>';
if (!empty($this->myindilist)) {
- echo '<div id="individual-results-tab">', format_indi_table($this->myindilist), '</div>';
+ echo '<div id="individual-results-tab">', FunctionsPrintLists::individualTable($this->myindilist), '</div>';
}
if (!empty($this->myfamlist)) {
- echo '<div id="families-results-tab">', format_fam_table($this->myfamlist), '</div>';
+ echo '<div id="families-results-tab">', FunctionsPrintLists::familyTable($this->myfamlist), '</div>';
}
if (!empty($this->mysourcelist)) {
- echo '<div id="sources-results-tab">', format_sour_table($this->mysourcelist), '</div>';
+ echo '<div id="sources-results-tab">', FunctionsPrintLists::sourceTable($this->mysourcelist), '</div>';
}
if (!empty($this->mynotelist)) {
- echo '<div id="notes-results-tab">', format_note_table($this->mynotelist), '</div>';
+ echo '<div id="notes-results-tab">', FunctionsPrintLists::noteTable($this->mynotelist), '</div>';
}
} else {
// One or more search terms were specified, but no results were found.
diff --git a/app/Controller/TimelineController.php b/app/Controller/TimelineController.php
index 4696ddb011..85a8095065 100644
--- a/app/Controller/TimelineController.php
+++ b/app/Controller/TimelineController.php
@@ -20,6 +20,8 @@ use Fisharebest\Webtrees\Date\GregorianDate;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -225,16 +227,16 @@ class TimelineController extends PageController {
echo $event->getLabel();
echo ' — ';
if ($record instanceof Individual) {
- echo format_fact_date($event, $record, false, false);
+ echo FunctionsPrint::formatFactDate($event, $record, false, false);
} elseif ($record instanceof Family) {
echo $gdate->display();
if ($record->getHusband() && $record->getHusband()->getBirthDate()->isOK()) {
- $ageh = get_age_at_event(Date::getAgeGedcom($record->getHusband()->getBirthDate(), $gdate), false);
+ $ageh = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->getHusband()->getBirthDate(), $gdate), false);
} else {
$ageh = null;
}
if ($record->getWife() && $record->getWife()->getBirthDate()->isOK()) {
- $agew = get_age_at_event(Date::getAgeGedcom($record->getWife()->getBirthDate(), $gdate), false);
+ $agew = FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($record->getWife()->getBirthDate(), $gdate), false);
} else {
$agew = null;
}
diff --git a/app/Fact.php b/app/Fact.php
index 3bd0989c55..95470d5432 100644
--- a/app/Fact.php
+++ b/app/Fact.php
@@ -15,6 +15,7 @@ namespace Fisharebest\Webtrees;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
/**
* Class Fact - Class that defines an event details object
@@ -44,7 +45,7 @@ class Fact {
/** @var Place The place of this fact, from the “2 PLAC …” attribute */
private $place;
- /** @var int Temporary(!) variable Used by sort_facts() */
+ /** @var int Temporary(!) variable Used by Functions::sortFacts() */
public $sortOrder;
/**
@@ -385,7 +386,7 @@ class Fact {
}
$date = $this->getDate();
if ($this->getTag() == 'BIRT' && $this->getParent() instanceof Individual && $this->getParent()->getTree()->getPreference('SHOW_PARENTS_AGE')) {
- $attributes[] = $date->display() . format_parents_age($this->getParent(), $date);
+ $attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->getParent(), $date);
} else {
$attributes[] = $date->display();
}
diff --git a/app/Functions/FunctionsCharts.php b/app/Functions/FunctionsCharts.php
new file mode 100644
index 0000000000..174af25c9a
--- /dev/null
+++ b/app/Functions/FunctionsCharts.php
@@ -0,0 +1,556 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Theme;
+
+/**
+ * Class FunctionsCharts - common functions
+ */
+class FunctionsCharts {
+ /**
+ * print a table cell with sosa number
+ *
+ * @param int $sosa
+ * @param string $pid optional pid
+ * @param string $arrowDirection direction of link arrow
+ */
+ public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up") {
+ if (substr($sosa, -1, 1) == ".") {
+ $personLabel = substr($sosa, 0, -1);
+ } else {
+ $personLabel = $sosa;
+ }
+ if ($arrowDirection == "blank") {
+ $visibility = "hidden";
+ } else {
+ $visibility = "normal";
+ }
+ echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
+ echo $personLabel;
+ if ($sosa != "1" && $pid != "") {
+ if ($arrowDirection == "left") {
+ $dir = 0;
+ } elseif ($arrowDirection == "right") {
+ $dir = 1;
+ } elseif ($arrowDirection == "down") {
+ $dir = 3;
+ } else {
+ $dir = 2; // either 'blank' or 'up'
+ }
+ echo '<br>';
+ self::printUrlArrow('#' . $pid, $pid, $dir);
+ }
+ echo '</td>';
+ }
+
+ /**
+ * print the parents table for a family
+ *
+ * @param Family $family family gedcom ID
+ * @param int $sosa child sosa number
+ * @param string $label indi label (descendancy booklet)
+ * @param string $parid parent ID (descendancy booklet)
+ * @param string $gparid gd-parent ID (descendancy booklet)
+ * @param int $show_full large or small box
+ */
+ public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
+
+ if ($show_full) {
+ $pbheight = Theme::theme()->parameter('chart-box-y') + 14;
+ } else {
+ $pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
+ }
+
+ $husb = $family->getHusband();
+ if ($husb) {
+ echo '<a name="', $husb->getXref(), '"></a>';
+ } else {
+ $husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
+ }
+ $wife = $family->getWife();
+ if ($wife) {
+ echo '<a name="', $wife->getXref(), '"></a>';
+ } else {
+ $wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
+ }
+
+ if ($sosa) {
+ echo '<p class="name_head">', $family->getFullName(), '</p>';
+ }
+
+ /**
+ * husband side
+ */
+ echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
+ echo "<table border='0'><tr>";
+
+ if ($parid) {
+ if ($husb->getXref() == $parid) {
+ self::printSosaNumber($label);
+ } else {
+ self::printSosaNumber($label, "", "blank");
+ }
+ } elseif ($sosa) {
+ self::printSosaNumber($sosa * 2);
+ }
+ if ($husb->isPendingAddtion()) {
+ echo '<td valign="top" class="facts_value new">';
+ } elseif ($husb->isPendingDeletion()) {
+ echo '<td valign="top" class="facts_value old">';
+ } else {
+ echo '<td valign="top">';
+ }
+ FunctionsPrint::printPedigreePerson($husb, $show_full);
+ echo "</td></tr></table>";
+ echo "</td>";
+ // husband’s parents
+ $hfam = $husb->getPrimaryChildFamily();
+ if ($hfam) {
+ // remove the|| test for $sosa
+ echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
+ echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
+ // husband’s father
+ if ($hfam && $hfam->getHusband()) {
+ echo "<table border='0'><tr>";
+ if ($sosa > 0) {
+ self::printSosaNumber($sosa * 4, $hfam->getHusband()->getXref(), "down");
+ }
+ if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
+ self::printSosaNumber(trim(substr($label, 0, -3), ".") . ".");
+ }
+ echo "<td valign=\"top\">";
+ FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
+ echo "</td></tr></table>";
+ } elseif ($hfam && !$hfam->getHusband()) {
+ // Empty box for grandfather
+ echo "<table border='0'><tr>";
+ echo '<td valign="top">';
+ FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
+ echo '</td></tr></table>';
+ }
+ echo "</td>";
+ }
+ if ($hfam && ($sosa != -1)) {
+ echo '<td valign="middle" rowspan="2">';
+ self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
+ echo '</td>';
+ }
+ if ($hfam) {
+ // husband’s mother
+ echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
+ if ($hfam && $hfam->getWife()) {
+ echo "<table border='0'><tr>";
+ if ($sosa > 0) {
+ self::printSosaNumber($sosa * 4 + 1, $hfam->getWife()->getXref(), "down");
+ }
+ if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
+ self::printSosaNumber(trim(substr($label, 0, -3), ".") . ".");
+ }
+ echo '<td valign="top">';
+ FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
+ echo '</td></tr></table>';
+ } elseif ($hfam && !$hfam->getWife()) {
+ // Empty box for grandmother
+ echo "<table border='0'><tr>";
+ echo '<td valign="top">';
+ FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
+ echo '</td></tr></table>';
+ }
+ echo '</td>';
+ }
+ echo '</tr></table>';
+ if ($sosa && $family->canShow()) {
+ foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
+ echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
+ echo str_repeat('&nbsp;', 10);
+ echo $fact->summary();
+ echo '</a>';
+ }
+ } else {
+ echo '<br>';
+ }
+
+ /**
+ * wife side
+ */
+ echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
+ echo "<table><tr>";
+ if ($parid) {
+ if ($wife->getXref() == $parid) {
+ self::printSosaNumber($label);
+ } else {
+ self::printSosaNumber($label, "", "blank");
+ }
+ } elseif ($sosa) {
+ self::printSosaNumber($sosa * 2 + 1);
+ }
+ if ($wife->isPendingAddtion()) {
+ echo '<td valign="top" class="facts_value new">';
+ } elseif ($wife->isPendingDeletion()) {
+ echo '<td valign="top" class="facts_value old">';
+ } else {
+ echo '<td valign="top">';
+ }
+ FunctionsPrint::printPedigreePerson($wife, $show_full);
+ echo "</td></tr></table>";
+ echo "</td>";
+ // wife’s parents
+ $hfam = $wife->getPrimaryChildFamily();
+
+ if ($hfam) {
+ echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
+ echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
+ // wife’s father
+ if ($hfam && $hfam->getHusband()) {
+ echo "<table><tr>";
+ if ($sosa > 0) {
+ self::printSosaNumber($sosa * 4 + 2, $hfam->getHusband()->getXref(), "down");
+ }
+ if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
+ self::printSosaNumber(trim(substr($label, 0, -3), ".") . ".");
+ }
+ echo "<td valign=\"top\">";
+ FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
+ echo "</td></tr></table>";
+ } elseif ($hfam && !$hfam->getHusband()) {
+ // Empty box for grandfather
+ echo "<table border='0'><tr>";
+ echo '<td valign="top">';
+ FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
+ echo '</td></tr></table>';
+ }
+ echo "</td>";
+ }
+ if ($hfam && ($sosa != -1)) {
+ echo '<td valign="middle" rowspan="2">';
+ self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
+ echo '</td>';
+ }
+ if ($hfam) {
+ // wife’s mother
+ echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
+ if ($hfam && $hfam->getWife()) {
+ echo "<table><tr>";
+ if ($sosa > 0) {
+ self::printSosaNumber($sosa * 4 + 3, $hfam->getWife()->getXref(), "down");
+ }
+ if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
+ self::printSosaNumber(trim(substr($label, 0, -3), ".") . ".");
+ }
+ echo "<td valign=\"top\">";
+ FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
+ echo "</td></tr></table>";
+ } elseif ($hfam && !$hfam->getWife()) {
+ // Empty box for grandmother
+ echo "<table border='0'><tr>";
+ echo '<td valign="top">';
+ FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
+ echo '</td></tr></table>';
+ }
+ echo '</td>';
+ }
+ echo "</tr></table>";
+ }
+
+ /**
+ * print the children table for a family
+ *
+ * @param Family $family family
+ * @param string $childid child ID
+ * @param int $sosa child sosa number
+ * @param string $label indi label (descendancy booklet)
+ * @param int $show_cousins display cousins on chart
+ * @param int $show_full large or small box
+ */
+ public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
+
+ if ($show_full) {
+ $bheight = Theme::theme()->parameter('chart-box-y');
+ } else {
+ $bheight = Theme::theme()->parameter('compact-chart-box-y');
+ }
+
+ $pbheight = $bheight + 14;
+
+ $children = $family->getChildren();
+ $numchil = count($children);
+
+ echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
+ if ($sosa > 0) {
+ echo '<td></td>';
+ }
+ echo '<td><span class="subheaders">';
+ if ($numchil == 0) {
+ echo I18N::translate('No children');
+ } else {
+ echo I18N::plural('%s child', '%s children', $numchil, $numchil);
+ }
+ echo '</span>';
+
+ if ($sosa == 0 && Auth::isEditor($family->getTree())) {
+ echo '<br>';
+ echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
+ echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
+ echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
+ echo '<br><br>';
+ }
+ echo '</td>';
+ if ($sosa > 0) {
+ echo '<td></td><td></td>';
+ }
+ echo '</tr>';
+
+ $nchi = 1;
+ if ($children) {
+ foreach ($children as $child) {
+ echo '<tr>';
+ if ($sosa != 0) {
+ if ($child->getXref() == $childid) {
+ self::printSosaNumber($sosa, $childid);
+ } elseif (empty($label)) {
+ self::printSosaNumber("");
+ } else {
+ self::printSosaNumber($label . ($nchi++) . ".");
+ }
+ }
+ if ($child->isPendingAddtion()) {
+ echo '<td valign="middle" class="new">';
+ } elseif ($child->isPendingDeletion()) {
+ echo '<td valign="middle" class="old">';
+ } else {
+ echo '<td valign="middle">';
+ }
+ FunctionsPrint::printPedigreePerson($child, $show_full);
+ echo "</td>";
+ if ($sosa != 0) {
+ // loop for all families where current child is a spouse
+ $famids = $child->getSpouseFamilies();
+
+ $maxfam = count($famids) - 1;
+ for ($f = 0; $f <= $maxfam; $f++) {
+ $famid_child = $famids[$f]->getXref();
+ // multiple marriages
+ if ($f > 0) {
+ echo '</tr><tr><td></td>';
+ echo '<td valign="top"';
+ if (I18N::direction() === 'rtl') {
+ echo ' align="left">';
+ } else {
+ echo ' align="right">';
+ }
+
+ //find out how many cousins there are to establish vertical line on second families
+ $fchildren = $famids[$f]->getChildren();
+ $kids = count($fchildren);
+ $Pheader = ($bheight - 1) * $kids;
+ $PBadj = 6; // default
+ if ($show_cousins > 0) {
+ if ($kids) {
+ $PBadj = max(0, $Pheader / 2 + $kids * 4.5);
+ }
+ }
+
+ if ($f == $maxfam) {
+ echo "<img height=\"" . ((($bheight / 2)) + $PBadj) . "px\"";
+ } else {
+ echo "<img height=\"" . $pbheight . "px\"";
+ }
+ echo " width=\"3\" src=\"" . Theme::theme()->parameter('image-vline') . "\" alt=\"\">";
+ echo "</td>";
+ }
+ echo "<td class=\"details1\" valign=\"middle\" align=\"center\">";
+ $spouse = $famids[$f]->getSpouse($child);
+
+ $marr = $famids[$f]->getFirstFact('MARR');
+ $div = $famids[$f]->getFirstFact('DIV');
+ if ($marr) {
+ // marriage date
+ echo $marr->getDate()->minimumDate()->format('%Y');
+ // divorce date
+ if ($div) {
+ echo '–', $div->getDate()->minimumDate()->format('%Y');
+ }
+ }
+ echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
+ echo "</td>";
+ // spouse information
+ echo "<td style=\"vertical-align: center;";
+ if (!empty($divrec)) {
+ echo " filter:alpha(opacity=40);opacity:0.4;\">";
+ } else {
+ echo "\">";
+ }
+ FunctionsPrint::printPedigreePerson($spouse, $show_full);
+ echo "</td>";
+ // cousins
+ if ($show_cousins) {
+ print_cousins($famid_child, $show_full);
+ }
+ }
+ }
+ echo "</tr>";
+ }
+ } elseif ($sosa < 1) {
+ // message 'no children' except for sosa
+ if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
+ echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
+ }
+ }
+ echo "</table><br>";
+ }
+
+ /**
+ * print a family with Sosa-Stradonitz numbering system
+ * ($rootid=1, father=2, mother=3 ...)
+ *
+ * @param string $famid family gedcom ID
+ * @param string $childid tree root ID
+ * @param int $sosa starting sosa number
+ * @param string $label indi label (descendancy booklet)
+ * @param string $parid parent ID (descendancy booklet)
+ * @param string $gparid gd-parent ID (descendancy booklet)
+ * @param int $show_cousins display cousins on chart
+ * @param int $show_full large or small box
+ */
+ public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
+ global $WT_TREE;
+
+ echo '<hr>';
+ echo '<p style="page-break-before: always;">';
+ if (!empty($famid)) {
+ echo '<a name="', $famid, '"></a>';
+ }
+ self::printFamilyParents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
+ echo '<br>';
+ echo '<table><tr><td valign="top">';
+ self::printFamilyChildren(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
+ echo '</td></tr></table>';
+ echo '<br>';
+ }
+
+ /**
+ * print an arrow to a new url
+ *
+ * @param string $url target url
+ * @param string $label arrow label
+ * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
+ */
+ public static function printUrlArrow($url, $label, $dir = 2) {
+ if ($url === '') {
+ return;
+ }
+
+ // arrow direction
+ $adir = $dir;
+ if (I18N::direction() === 'rtl' && $dir === 0) {
+ $adir = 1;
+ }
+ if (I18N::direction() === 'rtl' && $dir === 1) {
+ $adir = 0;
+ }
+
+ // arrow style 0 1 2 3
+ $array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
+ $astyle = $array_style[$adir];
+
+ // Labels include people’s names, which may contain markup
+ echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
+ }
+
+ /**
+ * builds and returns sosa relationship name in the active language
+ *
+ * @param string $sosa sosa number
+ *
+ * @return string
+ */
+ public static function getSosaName($sosa) {
+ $path = '';
+ while ($sosa > 1) {
+ if ($sosa % 2 == 1) {
+ $sosa -= 1;
+ $path = 'mot' . $path;
+ } else {
+ $path = 'fat' . $path;
+ }
+ $sosa /= 2;
+ }
+
+ return Functions::getRelationshipNameFromPath($path, null, null);
+ }
+
+ /**
+ * print cousins list
+ *
+ * @param string $famid family ID
+ * @param int $show_full large or small box
+ */
+ public static function printCousins($famid, $show_full = 1) {
+ global $WT_TREE;
+
+ if ($show_full) {
+ $bheight = Theme::theme()->parameter('chart-box-y');
+ } else {
+ $bheight = Theme::theme()->parameter('compact-chart-box-y');
+ }
+
+ $family = Family::getInstance($famid, $WT_TREE);
+ $fchildren = $family->getChildren();
+
+ $kids = count($fchildren);
+
+ echo '<td valign="middle" height="100%">';
+ if ($kids) {
+ echo '<table cellspacing="0" cellpadding="0" border="0" ><tr valign="middle">';
+ if ($kids > 1) {
+ echo '<td rowspan="', $kids, '" valign="middle" align="right"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '" alt=""></td>';
+ }
+ $ctkids = count($fchildren);
+ $i = 1;
+ foreach ($fchildren as $fchil) {
+ if ($i == 1) {
+ echo '<td><img width="10px" height="3px" align="top"';
+ } else {
+ echo '<td><img width="10px" height="3px"';
+ }
+ if (I18N::direction() === 'ltr') {
+ echo ' style="padding-right: 2px;"';
+ } else {
+ echo ' style="padding-left: 2px;"';
+ }
+ echo ' src="', Theme::theme()->parameter('image-hline'), '" alt=""></td><td>';
+ FunctionsPrint::printPedigreePerson($fchil, $show_full);
+ echo '</td></tr>';
+ if ($i < $ctkids) {
+ echo '<tr>';
+ $i++;
+ }
+ }
+ echo '</table>';
+ } else {
+ // If there is known that there are no children (as opposed to no known children)
+ if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
+ echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
+ }
+ }
+ echo '</td>';
+ }
+}
diff --git a/app/Functions/FunctionsDate.php b/app/Functions/FunctionsDate.php
new file mode 100644
index 0000000000..1a6cf66ded
--- /dev/null
+++ b/app/Functions/FunctionsDate.php
@@ -0,0 +1,130 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * Class FunctionsDate - common functions
+ */
+class FunctionsDate {
+ /**
+ * @param string $age_string
+ * @param bool $show_years
+ *
+ * @return string
+ */
+ public static function getAgeAtEvent($age_string, $show_years) {
+ switch (strtoupper($age_string)) {
+ case 'CHILD':
+ return I18N::translate('Child');
+ case 'INFANT':
+ return I18N::translate('Infant');
+ case 'STILLBORN':
+ return I18N::translate('Stillborn');
+ default:
+ return preg_replace_callback(
+ array(
+ '/(\d+)([ymwd])/',
+ ),
+ function ($match) use ($age_string, $show_years) {
+ switch ($match[2]) {
+ case 'y':
+ if ($show_years || preg_match('/[dm]/', $age_string)) {
+ return I18N::plural('%s year', '%s years', $match[1], I18N::digits($match[1]));
+ } else {
+ return I18N::digits($match[1]);
+ }
+ case 'm':
+ return I18N::plural('%s month', '%s months', $match[1], I18N::digits($match[1]));
+ case 'w':
+ return I18N::plural('%s week', '%s weeks', $match[1], I18N::digits($match[1]));
+ case 'd':
+ return I18N::plural('%s day', '%s days', $match[1], I18N::digits($match[1]));
+ }
+ },
+ $age_string
+ );
+ }
+ }
+
+ /**
+ * Convert a unix timestamp into a formatted date-time value, for logs, etc.
+ * Don't attempt to convert into other calendars, as not all days start at
+ * midnight, and we can only get it wrong.
+ *
+ * @param int $time
+ *
+ * @return string
+ */
+ public static function formatTimestamp($time) {
+ $time_fmt = I18N::timeFormat();
+ // PHP::date() doesn't do I18N. Do it ourselves....
+ preg_match_all('/%[^%]/', $time_fmt, $matches);
+ foreach ($matches[0] as $match) {
+ switch ($match) {
+ case '%a':
+ $t = gmdate('His', $time);
+ if ($t == '000000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 00:00:00 */
+ I18N::translate('midnight'), $time_fmt);
+ } elseif ($t < '120000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%a” - between 00:00:01 and 11:59:59 */
+ I18N::translate('a.m.'), $time_fmt);
+ } elseif ($t == '120000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 12:00:00 */
+ I18N::translate('noon'), $time_fmt);
+ } else {
+ $time_fmt = str_replace($match, /* I18N: time format “%a” - between 12:00:01 and 23:59:59 */
+ I18N::translate('p.m.'), $time_fmt);
+ }
+ break;
+ case '%A':
+ $t = gmdate('His', $time);
+ if ($t == '000000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 00:00:00 */
+ I18N::translate('Midnight'), $time_fmt);
+ } elseif ($t < '120000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%A” - between 00:00:01 and 11:59:59 */
+ I18N::translate('A.M.'), $time_fmt);
+ } elseif ($t == '120000') {
+ $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 12:00:00 */
+ I18N::translate('Noon'), $time_fmt);
+ } else {
+ $time_fmt = str_replace($match, /* I18N: time format “%A” - between 12:00:01 and 23:59:59 */
+ I18N::translate('P.M.'), $time_fmt);
+ }
+ break;
+ default:
+ $time_fmt = str_replace($match, I18N::digits(gmdate(substr($match, -1), $time)), $time_fmt);
+ }
+ }
+
+ return self::timestampToGedcomDate($time)->display() . '<span class="date"> - ' . $time_fmt . '</span>';
+ }
+
+ /**
+ * Convert a unix-style timestamp into a Date object
+ *
+ * @param int $time
+ *
+ * @return Date
+ */
+ public static function timestampToGedcomDate($time) {
+ return new Date(strtoupper(gmdate('j M Y', $time)));
+ }
+}
diff --git a/app/Functions/FunctionsDb.php b/app/Functions/FunctionsDb.php
new file mode 100644
index 0000000000..f0bdabe01a
--- /dev/null
+++ b/app/Functions/FunctionsDb.php
@@ -0,0 +1,1081 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Database;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Date\FrenchDate;
+use Fisharebest\Webtrees\Date\GregorianDate;
+use Fisharebest\Webtrees\Date\HijriDate;
+use Fisharebest\Webtrees\Date\JalaliDate;
+use Fisharebest\Webtrees\Date\JewishDate;
+use Fisharebest\Webtrees\Date\JulianDate;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Repository;
+use Fisharebest\Webtrees\Soundex;
+use Fisharebest\Webtrees\Source;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * Class FunctionsDb - common functions
+ */
+class FunctionsDb {
+ /**
+ * Fetch all records linked to a record - when deleting an object, we must
+ * also delete all links to it.
+ *
+ * @param string $xref
+ * @param int $gedcom_id
+ *
+ * @return string[]
+ */
+ public static function fetchAllLinks($xref, $gedcom_id) {
+ return
+ Database::prepare(
+ "SELECT l_from FROM `##link` WHERE l_file = ? AND l_to = ?" .
+ " UNION " .
+ "SELECT xref FROM `##change` WHERE status = 'pending' AND gedcom_id = ? AND new_gedcom LIKE" .
+ " CONCAT('%@', ?, '@%')"
+ )->execute(array(
+ $gedcom_id,
+ $xref,
+ $gedcom_id,
+ $xref,
+ ))->fetchOneColumn();
+ }
+
+ /**
+ * Get a list of all the sources.
+ *
+ * @param Tree $tree
+ *
+ * @return Source[] array
+ */
+ public static function getSourceList(Tree $tree) {
+ $rows = Database::prepare(
+ "SELECT s_id AS xref, s_gedcom AS gedcom FROM `##sources` WHERE s_file = :tree_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ $list = array();
+ foreach ($rows as $row) {
+ $list[] = Source::getInstance($row->xref, $tree, $row->gedcom);
+ }
+ usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
+
+ return $list;
+ }
+
+ /**
+ * Get a list of all the repositories.
+ *
+ * @param Tree $tree
+ *
+ * @return Repository[] array
+ */
+ public static function getRepositoryList(Tree $tree) {
+ $rows = Database::prepare(
+ "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO' AND o_file = ?"
+ )->execute(array(
+ $tree->getTreeId(),
+ ))->fetchAll();
+
+ $list = array();
+ foreach ($rows as $row) {
+ $list[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
+ }
+ usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
+
+ return $list;
+ }
+
+ /**
+ * Get a list of all the shared notes.
+ *
+ * @param Tree $tree
+ *
+ * @return Note[] array
+ */
+ public static function getNoteList(Tree $tree) {
+ $rows = Database::prepare(
+ "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE' AND o_file = :tree_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ $list = array();
+ foreach ($rows as $row) {
+ $list[] = Note::getInstance($row->xref, $tree, $row->gedcom);
+ }
+ usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
+
+ return $list;
+ }
+
+ /**
+ * Search all individuals
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The trees to search
+ *
+ * @return Individual[]
+ */
+ public static function searchIndividuals(array $query, array $trees) {
+ // Convert the query into a regular expression
+ $queryregex = array();
+
+ $sql = "SELECT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals` WHERE 1";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
+ $sql .= " AND i_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND i_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ // SQL may have matched on private data or gedcom tags, so check again against privatized data.
+ $record = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // Ignore non-genealogy data
+ $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
+ // Ignore links and tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
+ // Re-apply the filtering
+ $gedrec = I18N::strtoupper($gedrec);
+ foreach ($queryregex as $regex) {
+ if (!preg_match('/' . $regex . '/', $gedrec)) {
+ continue 2;
+ }
+ }
+ $list[] = $record;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search the names of individuals
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The trees to search
+ *
+ * @return Individual[]
+ */
+ public static function searchIndividualNames(array $query, array $trees) {
+ $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom, n_full FROM `##individuals` JOIN `##name` ON i_id=n_id AND i_file=n_file WHERE 1";
+ $args = array();
+
+ // Convert the query into a SQL expression
+ foreach ($query as $n => $q) {
+ $sql .= " AND n_full COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND i_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // The individual may have private names - and the DB search may have found it.
+ if ($indi->canShowName()) {
+ foreach ($indi->getAllNames() as $num => $name) {
+ if ($name['fullNN'] === $row->n_full) {
+ $indi->setPrimaryName($num);
+ // We need to clone $indi, as we may have multiple references to the
+ // same person in this list, and the "primary name" would otherwise
+ // be shared amongst all of them.
+ $list[] = clone $indi;
+ // Only need to match an individual on one name
+ break;
+ }
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search for individuals names/places using phonetic matching
+ *
+ * @param string $soundex
+ * @param string $lastname
+ * @param string $firstname
+ * @param string $place
+ * @param Tree[] $trees
+ *
+ * @return Individual[]
+ */
+ public static function searchIndividualsPhonetic($soundex, $lastname, $firstname, $place, array $trees) {
+ switch ($soundex) {
+ case 'Russell':
+ $givn_sdx = Soundex::russell($firstname);
+ $surn_sdx = Soundex::russell($lastname);
+ $plac_sdx = Soundex::russell($place);
+ break;
+ case 'DaitchM':
+ $givn_sdx = Soundex::daitchMokotoff($firstname);
+ $surn_sdx = Soundex::daitchMokotoff($lastname);
+ $plac_sdx = Soundex::daitchMokotoff($place);
+ break;
+ default:
+ throw new \DomainException('soundex: ' . $soundex);
+ }
+
+ // Nothing to search for? Return nothing.
+ if (!$givn_sdx && !$surn_sdx && !$plac_sdx) {
+ return array();
+ }
+
+ $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals`";
+ $args = array();
+
+ if ($place) {
+ $sql .= " JOIN `##placelinks` ON pl_file = i_file AND pl_gid = i_id";
+ $sql .= " JOIN `##places` ON p_file = pl_file AND pl_p_id = p_id";
+ }
+ if ($firstname || $lastname) {
+ $sql .= " JOIN `##name` ON i_file=n_file AND i_id=n_id";
+ }
+ $sql .= " AND i_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ if ($firstname && $givn_sdx) {
+ $sql .= " AND (";
+ $givn_sdx = explode(':', $givn_sdx);
+ foreach ($givn_sdx as $n => $sdx) {
+ $sql .= $n ? " OR " : "";
+ switch ($soundex) {
+ case 'Russell':
+ $sql .= "n_soundex_givn_std LIKE CONCAT('%', :given_name_" . $n . ", '%')";
+ break;
+ case 'DaitchM':
+ $sql .= "n_soundex_givn_dm LIKE CONCAT('%', :given_name_" . $n . ", '%')";
+ break;
+ }
+ $args['given_name_' . $n] = $sdx;
+ }
+ $sql .= ")";
+ }
+
+ if ($lastname && $surn_sdx) {
+ $sql .= " AND (";
+ $surn_sdx = explode(':', $surn_sdx);
+ foreach ($surn_sdx as $n => $sdx) {
+ $sql .= $n ? " OR " : "";
+ switch ($soundex) {
+ case 'Russell':
+ $sql .= "n_soundex_surn_std LIKE CONCAT('%', :surname_" . $n . ", '%')";
+ break;
+ case 'DaitchM':
+ $sql .= "n_soundex_surn_dm LIKE CONCAT('%', :surname_" . $n . ", '%')";
+ break;
+ }
+ $args['surname_' . $n] = $sdx;
+ }
+ $sql .= ")";
+ }
+
+ if ($place && $plac_sdx) {
+ $sql .= " AND (";
+ $plac_sdx = explode(':', $plac_sdx);
+ foreach ($plac_sdx as $n => $sdx) {
+ $sql .= $n ? " OR " : "";
+ switch ($soundex) {
+ case 'Russell':
+ $sql .= "p_std_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
+ break;
+ case 'DaitchM':
+ $sql .= "p_dm_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
+ break;
+ }
+ $args['place_' . $n] = $sdx;
+ }
+ $sql .= ")";
+ }
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ if ($indi->canShowName()) {
+ $list[] = $indi;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * get recent changes since the given julian day inclusive
+ *
+ * @param int $jd leave empty to include all
+ * @param bool $allgeds
+ *
+ * @return string[] List of XREFs of records with changes
+ */
+ public static function getRecentChanges($jd = 0, $allgeds = false) {
+ global $WT_TREE;
+
+ $sql = "SELECT d_gid FROM `##dates` WHERE d_fact='CHAN' AND d_julianday1>=?";
+ $vars = array($jd);
+ if (!$allgeds) {
+ $sql .= " AND d_file=?";
+ $vars[] = $WT_TREE->getTreeId();
+ }
+ $sql .= " ORDER BY d_julianday1 DESC";
+
+ return Database::prepare($sql)->execute($vars)->fetchOneColumn();
+ }
+
+ /**
+ * Search family records
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The trees to search
+ *
+ * @return Family[]
+ */
+ public static function searchFamilies(array $query, array $trees) {
+ // Convert the query into a regular expression
+ $queryregex = array();
+
+ $sql = "SELECT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom FROM `##families` WHERE 1";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
+ $sql .= " AND f_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND f_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ // SQL may have matched on private data or gedcom tags, so check again against privatized data.
+ $record = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // Ignore non-genealogy data
+ $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
+ // Ignore links and tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
+ // Ignore tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
+ // Re-apply the filtering
+ $gedrec = I18N::strtoupper($gedrec);
+ foreach ($queryregex as $regex) {
+ if (!preg_match('/' . $regex . '/', $gedrec)) {
+ continue 2;
+ }
+ }
+ $list[] = $record;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search the names of the husb/wife in a family
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The trees to search
+ *
+ * @return Family[]
+ */
+ public static function searchFamilyNames(array $query, array $trees) {
+ // No query => no results
+ if (!$query) {
+ return array();
+ }
+
+ $sql =
+ "SELECT DISTINCT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom" .
+ " FROM `##families`" .
+ " LEFT JOIN `##name` husb ON f_husb = husb.n_id AND f_file = husb.n_file" .
+ " LEFT JOIN `##name` wife ON f_wife = wife.n_id AND f_file = wife.n_file" .
+ " WHERE 1";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $sql .= " AND (husb.n_full COLLATE :husb_collate_" . $n . " LIKE CONCAT('%', :husb_query_" . $n . ", '%') OR wife.n_full COLLATE :wife_collate_" . $n . " LIKE CONCAT('%', :wife_query_" . $n . ", '%'))";
+ $args['husb_collate_' . $n] = I18N::collation();
+ $args['husb_query_' . $n] = Filter::escapeLike($q);
+ $args['wife_collate_' . $n] = I18N::collation();
+ $args['wife_query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND f_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ $indi = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ if ($indi->canShowName()) {
+ $list[] = $indi;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search the sources
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The tree to search
+ *
+ * @return Source[]
+ */
+ public static function searchSources($query, $trees) {
+ // Convert the query into a regular expression
+ $queryregex = array();
+
+ $sql = "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom FROM `##sources` WHERE 1";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
+ $sql .= " AND s_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND s_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ // SQL may have matched on private data or gedcom tags, so check again against privatized data.
+ $record = Source::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // Ignore non-genealogy data
+ $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
+ // Ignore links and tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
+ // Ignore tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
+ // Re-apply the filtering
+ $gedrec = I18N::strtoupper($gedrec);
+ foreach ($queryregex as $regex) {
+ if (!preg_match('/' . $regex . '/', $gedrec)) {
+ continue 2;
+ }
+ }
+ $list[] = $record;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search the shared notes
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The tree to search
+ *
+ * @return Note[]
+ */
+ public static function searchNotes(array $query, array $trees) {
+ // Convert the query into a regular expression
+ $queryregex = array();
+
+ $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE'";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
+ $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND o_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ // SQL may have matched on private data or gedcom tags, so check again against privatized data.
+ $record = Note::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // Ignore non-genealogy data
+ $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
+ // Ignore links and tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
+ // Ignore tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
+ // Re-apply the filtering
+ $gedrec = I18N::strtoupper($gedrec);
+ foreach ($queryregex as $regex) {
+ if (!preg_match('/' . $regex . '/', $gedrec)) {
+ continue 2;
+ }
+ }
+ $list[] = $record;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Search the repositories
+ *
+ * @param string[] $query Search terms
+ * @param Tree[] $trees The trees to search
+ *
+ * @return Repository[]
+ */
+ public static function searchRepositories(array $query, array $trees) {
+ // Convert the query into a regular expression
+ $queryregex = array();
+
+ $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO'";
+ $args = array();
+
+ foreach ($query as $n => $q) {
+ $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
+ $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
+ $args['collate_' . $n] = I18N::collation();
+ $args['query_' . $n] = Filter::escapeLike($q);
+ }
+
+ $sql .= " AND o_file IN (";
+ foreach ($trees as $n => $tree) {
+ $sql .= $n ? ", " : "";
+ $sql .= ":tree_id_" . $n;
+ $args['tree_id_' . $n] = $tree->getTreeId();
+ }
+ $sql .= ")";
+
+ $list = array();
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ // SQL may have matched on private data or gedcom tags, so check again against privatized data.
+ $record = Repository::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
+ // Ignore non-genealogy data
+ $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
+ // Ignore links and tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
+ // Ignore tags
+ $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
+ // Re-apply the filtering
+ $gedrec = I18N::strtoupper($gedrec);
+ foreach ($queryregex as $regex) {
+ if (!preg_match('/' . $regex . '/', $gedrec)) {
+ continue 2;
+ }
+ }
+ $list[] = $record;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Find the record for the given rin.
+ *
+ * @param string $rin
+ *
+ * @return string
+ */
+ public static function findRin($rin) {
+ global $WT_TREE;
+
+ $xref =
+ Database::prepare("SELECT i_id FROM `##individuals` WHERE i_rin=? AND i_file=?")
+ ->execute(array($rin, $WT_TREE->getTreeId()))
+ ->fetchOne();
+
+ return $xref ? $xref : $rin;
+ }
+
+ /**
+ * Get array of common surnames
+ *
+ * This function returns a simple array of the most common surnames
+ * found in the individuals list.
+ *
+ * @param int $min The number of times a surname must occur before it is added to the array
+ * @param Tree $tree
+ *
+ * @return mixed[][]
+ */
+ public static function getCommonSurnames($min, Tree $tree) {
+ $COMMON_NAMES_ADD = $tree->getPreference('COMMON_NAMES_ADD');
+ $COMMON_NAMES_REMOVE = $tree->getPreference('COMMON_NAMES_REMOVE');
+
+ $topsurns = self::getTopSurnames($tree->getTreeId(), $min, 0);
+ foreach (explode(',', $COMMON_NAMES_ADD) as $surname) {
+ if ($surname && !array_key_exists($surname, $topsurns)) {
+ $topsurns[$surname] = $min;
+ }
+ }
+ foreach (explode(',', $COMMON_NAMES_REMOVE) as $surname) {
+ unset($topsurns[I18N::strtoupper($surname)]);
+ }
+
+ //-- check if we found some, else recurse
+ if (empty($topsurns) && $min > 2) {
+ return self::getCommonSurnames($min / 2, $tree);
+ } else {
+ uksort($topsurns, '\Fisharebest\Webtrees\I18N::strcasecmp');
+ foreach ($topsurns as $key => $value) {
+ $topsurns[$key] = array('name' => $key, 'match' => $value);
+ }
+
+ return $topsurns;
+ }
+ }
+
+ /**
+ * get the top surnames
+ *
+ * @param int $ged_id fetch surnames from this gedcom
+ * @param int $min only fetch surnames occuring this many times
+ * @param int $max only fetch this number of surnames (0=all)
+ *
+ * @return string[]
+ */
+ public static function getTopSurnames($ged_id, $min, $max) {
+ // Use n_surn, rather than n_surname, as it is used to generate URLs for
+ // the indi-list, etc.
+ $max = (int) $max;
+ if ($max == 0) {
+ return
+ Database::prepare(
+ "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
+ " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
+ " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
+ " ORDER BY 2 DESC"
+ )->execute(array(
+ 'tree_id' => $ged_id,
+ 'min' => $min,
+ ))->fetchAssoc();
+ } else {
+ return
+ Database::prepare(
+ "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
+ " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
+ " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
+ " ORDER BY 2 DESC" .
+ " LIMIT :limit"
+ )->execute(array(
+ 'tree_id' => $ged_id,
+ 'min' => $min,
+ 'limit' => $max,
+ ))->fetchAssoc();
+ }
+ }
+
+ /**
+ * Get a list of events whose anniversary occured on a given julian day.
+ * Used on the on-this-day/upcoming blocks and the day/month calendar views.
+ *
+ * @param int $jd the julian day
+ * @param string $facts restrict the search to just these facts or leave blank for all
+ * @param Tree $tree the tree to search
+ *
+ * @return Fact[]
+ */
+ public static function getAnniversaryEvents($jd, $facts, Tree $tree) {
+ $found_facts = array();
+ foreach (array(
+ new GregorianDate($jd),
+ new JulianDate($jd),
+ new FrenchDate($jd),
+ new JewishDate($jd),
+ new HijriDate($jd),
+ new JalaliDate($jd),
+ ) as $anniv) {
+ // Build a SQL where clause to match anniversaries in the appropriate calendar.
+ $ind_sql =
+ "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
+ " FROM `##dates` JOIN `##individuals` ON d_gid = i_id AND d_file = i_file" .
+ " WHERE d_type = :type AND d_file = :tree_id";
+ $fam_sql =
+ "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
+ " FROM `##dates` JOIN `##families` ON d_gid = f_id AND d_file = f_file" .
+ " WHERE d_type = :type AND d_file = :tree_id";
+ $args = array(
+ 'type' => $anniv->format('%@'),
+ 'tree_id' => $tree->getTreeId(),
+ );
+
+ $where = "";
+ // SIMPLE CASES:
+ // a) Non-hebrew anniversaries
+ // b) Hebrew months TVT, SHV, IYR, SVN, TMZ, AAV, ELL
+ if (!$anniv instanceof JewishDate || in_array($anniv->m, array(1, 5, 6, 9, 10, 11, 12, 13))) {
+ // Dates without days go on the first day of the month
+ // Dates with invalid days go on the last day of the month
+ if ($anniv->d === 1) {
+ $where .= " AND d_day <= 1";
+ } elseif ($anniv->d === $anniv->daysInMonth()) {
+ $where .= " AND d_day >= :day";
+ $args['day'] = $anniv->d;
+ } else {
+ $where .= " AND d_day = :day";
+ $args['day'] = $anniv->d;
+ }
+ $where .= " AND d_mon = :month";
+ $args['month'] = $anniv->m;
+ } else {
+ // SPECIAL CASES:
+ switch ($anniv->m) {
+ case 2:
+ // 29 CSH does not include 30 CSH (but would include an invalid 31 CSH if there were no 30 CSH)
+ if ($anniv->d === 1) {
+ $where .= " AND d_day <= 1 AND d_mon = 2";
+ } elseif ($anniv->d === 30) {
+ $where .= " AND d_day >= 30 AND d_mon = 2";
+ } elseif ($anniv->d === 29 && $anniv->daysInMonth() === 29) {
+ $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 2";
+ } else {
+ $where .= " AND d_day = :day AND d_mon = 2";
+ $args['day'] = $anniv->d;
+ }
+ break;
+ case 3:
+ // 1 KSL includes 30 CSH (if this year didn’t have 30 CSH)
+ // 29 KSL does not include 30 KSL (but would include an invalid 31 KSL if there were no 30 KSL)
+ if ($anniv->d === 1) {
+ $tmp = new JewishDate(array($anniv->y, 'CSH', 1));
+ if ($tmp->daysInMonth() === 29) {
+ $where .= " AND (d_day <= 1 AND d_mon = 3 OR d_day = 30 AND d_mon = 2)";
+ } else {
+ $where .= " AND d_day <= 1 AND d_mon = 3";
+ }
+ } elseif ($anniv->d === 30) {
+ $where .= " AND d_day >= 30 AND d_mon = 3";
+ } elseif ($anniv->d == 29 && $anniv->daysInMonth() === 29) {
+ $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 3";
+ } else {
+ $where .= " AND d_day = :day AND d_mon = 3";
+ $args['day'] = $anniv->d;
+ }
+ break;
+ case 4:
+ // 1 TVT includes 30 KSL (if this year didn’t have 30 KSL)
+ if ($anniv->d === 1) {
+ $tmp = new JewishDate(array($anniv->y, 'KSL', 1));
+ if ($tmp->daysInMonth() === 29) {
+ $where .= " AND (d_day <=1 AND d_mon = 4 OR d_day = 30 AND d_mon = 3)";
+ } else {
+ $where .= " AND d_day <= 1 AND d_mon = 4";
+ }
+ } elseif ($anniv->d === $anniv->daysInMonth()) {
+ $where .= " AND d_day >= :day AND d_mon=4";
+ $args['day'] = $anniv->d;
+ } else {
+ $where .= " AND d_day = :day AND d_mon=4";
+ $args['day'] = $anniv->d;
+ }
+ break;
+ case 7: // ADS includes ADR (non-leap)
+ if ($anniv->d === 1) {
+ $where .= " AND d_day <= 1";
+ } elseif ($anniv->d === $anniv->daysInMonth()) {
+ $where .= " AND d_day >= :day";
+ $args['day'] = $anniv->d;
+ } else {
+ $where .= " AND d_day = :day";
+ $args['day'] = $anniv->d;
+ }
+ $where .= " AND (d_mon = 6 AND MOD(7 * d_year + 1, 19) >= 7 OR d_mon = 7)";
+ break;
+ case 8: // 1 NSN includes 30 ADR, if this year is non-leap
+ if ($anniv->d === 1) {
+ if ($anniv->isLeapYear()) {
+ $where .= " AND d_day <= 1 AND d_mon = 8";
+ } else {
+ $where .= " AND (d_day <= 1 AND d_mon = 8 OR d_day = 30 AND d_mon = 6)";
+ }
+ } elseif ($anniv->d === $anniv->daysInMonth()) {
+ $where .= " AND d_day >= :day AND d_mon = 8";
+ $args['day'] = $anniv->d;
+ } else {
+ $where .= " AND d_day = :day AND d_mon = 8";
+ $args['day'] = $anniv->d;
+ }
+ break;
+ }
+ }
+ // Only events in the past (includes dates without a year)
+ $where .= " AND d_year <= :year";
+ $args['year'] = $anniv->y;
+
+ if ($facts) {
+ // Restrict to certain types of fact
+ $where .= " AND d_fact IN (";
+ preg_match_all('/([_A-Z]+)/', $facts, $matches);
+ foreach ($matches[1] as $n => $fact) {
+ $where .= $n ? ", " : "";
+ $where .= ":fact_" . $n;
+ $args['fact_' . $n] = $fact;
+ }
+ $where .= ")";
+ } else {
+ // If no facts specified, get all except these
+ $where .= " AND d_fact NOT IN ('CHAN', 'BAPL', 'SLGC', 'SLGS', 'ENDL', 'CENS', 'RESI', '_TODO')";
+ }
+
+ $order_by = " ORDER BY d_day, d_year DESC";
+
+ // Now fetch these anniversaries
+ foreach (array('INDI' => $ind_sql . $where . $order_by, 'FAM' => $fam_sql . $where . $order_by) as $type => $sql) {
+ $rows = Database::prepare($sql)->execute($args)->fetchAll();
+ foreach ($rows as $row) {
+ if ($type === 'INDI') {
+ $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
+ } else {
+ $record = Family::getInstance($row->xref, $tree, $row->gedcom);
+ }
+ $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
+ foreach ($record->getFacts() as $fact) {
+ if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
+ $fact->anniv = $row->d_year === 0 ? 0 : $anniv->y - $row->d_year;
+ $found_facts[] = $fact;
+ }
+ }
+ }
+ }
+ }
+
+ return $found_facts;
+ }
+
+ /**
+ * Get a list of events which occured during a given date range.
+ *
+ * @param int $jd1 the start range of julian day
+ * @param int $jd2 the end range of julian day
+ * @param string $facts restrict the search to just these facts or leave blank for all
+ * @param Tree $tree the tree to search
+ *
+ * @return Fact[]
+ */
+ public static function getCalendarEvents($jd1, $jd2, $facts, Tree $tree) {
+ // If no facts specified, get all except these
+ $skipfacts = "CHAN,BAPL,SLGC,SLGS,ENDL,CENS,RESI,NOTE,ADDR,OBJE,SOUR,PAGE,DATA,TEXT";
+ if ($facts != '_TODO') {
+ $skipfacts .= ',_TODO';
+ }
+
+ $found_facts = array();
+
+ // Events that start or end during the period
+ $where = "WHERE (d_julianday1>={$jd1} AND d_julianday1<={$jd2} OR d_julianday2>={$jd1} AND d_julianday2<={$jd2})";
+
+ // Restrict to certain types of fact
+ if (empty($facts)) {
+ $excl_facts = "'" . preg_replace('/\W+/', "','", $skipfacts) . "'";
+ $where .= " AND d_fact NOT IN ({$excl_facts})";
+ } else {
+ $incl_facts = "'" . preg_replace('/\W+/', "','", $facts) . "'";
+ $where .= " AND d_fact IN ({$incl_facts})";
+ }
+ // Only get events from the current gedcom
+ $where .= " AND d_file=" . $tree->getTreeId();
+
+ // Now fetch these events
+ $ind_sql = "SELECT d_gid AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##individuals` {$where} AND d_gid=i_id AND d_file=i_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
+ $fam_sql = "SELECT d_gid AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##families` {$where} AND d_gid=f_id AND d_file=f_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
+ foreach (array('INDI' => $ind_sql, 'FAM' => $fam_sql) as $type => $sql) {
+ $rows = Database::prepare($sql)->fetchAll();
+ foreach ($rows as $row) {
+ if ($type === 'INDI') {
+ $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
+ } else {
+ $record = Family::getInstance($row->xref, $tree, $row->gedcom);
+ }
+ $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
+ foreach ($record->getFacts() as $fact) {
+ if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
+ $fact->anniv = 0;
+ $found_facts[] = $fact;
+ }
+ }
+ }
+ }
+
+ return $found_facts;
+ }
+
+ /**
+ * Get the list of current and upcoming events, sorted by anniversary date
+ *
+ * @param int $jd1
+ * @param int $jd2
+ * @param string $events
+ * @param Tree $tree
+ *
+ * @return Fact[]
+ */
+ public static function getEventsList($jd1, $jd2, $events, Tree $tree) {
+ $found_facts = array();
+ for ($jd = $jd1; $jd <= $jd2; ++$jd) {
+ $found_facts = array_merge($found_facts, self::getAnniversaryEvents($jd, $events, $tree));
+ }
+
+ return $found_facts;
+ }
+
+ /**
+ * Check if a media file is shared (i.e. used by another gedcom)
+ *
+ * @param string $file_name
+ * @param int $ged_id
+ *
+ * @return bool
+ */
+ public static function isMediaUsedInOtherTree($file_name, $ged_id) {
+ return
+ (bool) Database::prepare("SELECT COUNT(*) FROM `##media` WHERE m_filename LIKE ? AND m_file<>?")
+ ->execute(array("%{$file_name}", $ged_id))
+ ->fetchOne();
+ }
+
+ /**
+ * @param int $user_id
+ *
+ * @return string[][]
+ */
+ public static function getUserBlocks($user_id) {
+ global $WT_TREE;
+
+ $blocks = array('main' => array(), 'side' => array());
+ $rows = Database::prepare(
+ "SELECT SQL_CACHE location, block_id, module_name" .
+ " FROM `##block`" .
+ " JOIN `##module` USING (module_name)" .
+ " JOIN `##module_privacy` USING (module_name)" .
+ " WHERE user_id=?" .
+ " AND status='enabled'" .
+ " AND `##module_privacy`.gedcom_id=?" .
+ " AND access_level>=?" .
+ " ORDER BY location, block_order"
+ )->execute(array($user_id, $WT_TREE->getTreeId(), Auth::accessLevel($WT_TREE)))->fetchAll();
+ foreach ($rows as $row) {
+ $blocks[$row->location][$row->block_id] = $row->module_name;
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Get the blocks for the specified tree
+ *
+ * @param int $gedcom_id
+ *
+ * @return string[][]
+ */
+ public static function getTreeBlocks($gedcom_id) {
+ if ($gedcom_id < 0) {
+ $access_level = Auth::PRIV_NONE;
+ } else {
+ $access_level = Auth::accessLevel(Tree::findById($gedcom_id));
+ }
+
+ $blocks = array('main' => array(), 'side' => array());
+ $rows = Database::prepare(
+ "SELECT SQL_CACHE location, block_id, module_name" .
+ " FROM `##block`" .
+ " JOIN `##module` USING (module_name)" .
+ " JOIN `##module_privacy` USING (module_name, gedcom_id)" .
+ " WHERE gedcom_id = :tree_id" .
+ " AND status='enabled'" .
+ " AND access_level >= :access_level" .
+ " ORDER BY location, block_order"
+ )->execute(array(
+ 'tree_id' => $gedcom_id,
+ 'access_level' => $access_level,
+ ))->fetchAll();
+ foreach ($rows as $row) {
+ $blocks[$row->location][$row->block_id] = $row->module_name;
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Update favorites after merging records.
+ *
+ * @param string $xref_from
+ * @param string $xref_to
+ * @param Tree $tree
+ *
+ * @return int
+ */
+ public static function updateFavorites($xref_from, $xref_to, Tree $tree) {
+ return
+ Database::prepare("UPDATE `##favorite` SET xref=? WHERE xref=? AND gedcom_id=?")
+ ->execute(array($xref_to, $xref_from, $tree->getTreeId()))
+ ->rowCount();
+ }
+}
diff --git a/app/Functions/FunctionsEdit.php b/app/Functions/FunctionsEdit.php
new file mode 100644
index 0000000000..b395f25037
--- /dev/null
+++ b/app/Functions/FunctionsEdit.php
@@ -0,0 +1,1715 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\ConfigData;
+use Fisharebest\Webtrees\Database;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\GedcomTag;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Media;
+use Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Module\CensusAssistantModule;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Repository;
+use Fisharebest\Webtrees\Source;
+use Rhumsaa\Uuid\Uuid;
+
+/**
+ * Class FunctionsEdit - common functions
+ */
+class FunctionsEdit {
+ /**
+ * Create a <select> control for a form.
+ *
+ * @param string $name
+ * @param string[] $values
+ * @param string|null $empty
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function selectEditControl($name, $values, $empty, $selected, $extra = '') {
+ if (is_null($empty)) {
+ $html = '';
+ } else {
+ if (empty($selected)) {
+ $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
+ } else {
+ $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
+ }
+ }
+ // A completely empty list would be invalid, and break various things
+ if (empty($values) && empty($html)) {
+ $html = '<option value=""></option>';
+ }
+ foreach ($values as $key => $value) {
+ // PHP array keys are cast to integers! Cast them back
+ if ((string) $key === (string) $selected) {
+ $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
+ } else {
+ $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
+ }
+ }
+ if (substr($name, -2) === '[]') {
+ // id attribute is not used for arrays
+ return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
+ } else {
+ return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
+ }
+ }
+
+ /**
+ * Create a set of radio buttons for a form
+ *
+ * @param string $name The ID for the form element
+ * @param string[] $values Array of value=>display items
+ * @param string $selected The currently selected item
+ * @param string $extra Additional markup for the label
+ *
+ * @return string
+ */
+ public static function radioButtons($name, $values, $selected, $extra = '') {
+ $html = '';
+ foreach ($values as $key => $value) {
+ $html .=
+ '<label ' . $extra . '>' .
+ '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
+ // PHP array keys are cast to integers! Cast them back
+ if ((string) $key === (string) $selected) {
+ $html .= ' checked';
+ }
+ $html .= '>' . Filter::escapeHtml($value) . '</label>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Print an edit control for a Yes/No field
+ *
+ * @param string $name
+ * @param bool $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldYesNo($name, $selected = false, $extra = '') {
+ return self::radioButtons(
+ $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
+ );
+ }
+
+ /**
+ * Print an edit control for a checkbox.
+ *
+ * @param string $name
+ * @param bool $is_checked
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function checkbox($name, $is_checked = false, $extra = '') {
+ return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
+ }
+
+ /**
+ * Print an edit control for a checkbox, with a hidden field to store one of the two states.
+ * By default, a checkbox is either set, or not sent.
+ * This function gives us a three options, set, unset or not sent.
+ * Useful for dynamically generated forms where we don't know what elements are present.
+ *
+ * @param string $name
+ * @param int $is_checked 0 or 1
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function twoStateCheckbox($name, $is_checked = 0, $extra = '') {
+ return
+ '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
+ '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
+ ($is_checked ? ' checked' : '') .
+ ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
+ }
+
+ /**
+ * Function edit_language_checkboxes
+ *
+ * @param string $parameter_name
+ * @param array $accepted_languages
+ *
+ * @return string
+ */
+ public static function editLanguageCheckboxes($parameter_name, $accepted_languages) {
+ $html = '';
+ foreach (I18N::activeLocales() as $locale) {
+ $html .= '<div class="checkbox">';
+ $html .= '<label title="' . $locale->languageTag() . '">';
+ $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
+ $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
+ $html .= $locale->endonym();
+ $html .= '</label>';
+ $html .= '</div>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Print an edit control for access level.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldAccessLevel($name, $selected = '', $extra = '') {
+ $ACCESS_LEVEL = array(
+ Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
+ Auth::PRIV_USER => I18N::translate('Show to members'),
+ Auth::PRIV_NONE => I18N::translate('Show to managers'),
+ Auth::PRIV_HIDE => I18N::translate('Hide from everyone'),
+ );
+
+ return self::selectEditControl($name, $ACCESS_LEVEL, null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a RESN field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldRestriction($name, $selected = '', $extra = '') {
+ $RESN = array(
+ '' => '',
+ 'none' => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
+ 'privacy' => I18N::translate('Show to members'),
+ 'confidential' => I18N::translate('Show to managers'),
+ 'locked' => I18N::translate('Only managers can edit'),
+ );
+
+ return self::selectEditControl($name, $RESN, null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a contact method field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldContact($name, $selected = '', $extra = '') {
+ // Different ways to contact the users
+ $CONTACT_METHODS = array(
+ 'messaging' => I18N::translate('Internal messaging'),
+ 'messaging2' => I18N::translate('Internal messaging with emails'),
+ 'messaging3' => I18N::translate('webtrees sends emails with no storage'),
+ 'mailto' => I18N::translate('Mailto link'),
+ 'none' => I18N::translate('No contact'),
+ );
+
+ return self::selectEditControl($name, $CONTACT_METHODS, null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a language field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldLanguage($name, $selected = '', $extra = '') {
+ $languages = array();
+ foreach (I18N::activeLocales() as $locale) {
+ $languages[$locale->languageTag()] = $locale->endonym();
+ }
+
+ return self::selectEditControl($name, $languages, null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a range of integers.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param int $min
+ * @param int $max
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldInteger($name, $selected = '', $min, $max, $extra = '') {
+ $array = array();
+ for ($i = $min; $i <= $max; ++$i) {
+ $array[$i] = I18N::number($i);
+ }
+
+ return self::selectEditControl($name, $array, null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a username.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldUsername($name, $selected = '', $extra = '') {
+ $all_users = Database::prepare(
+ "SELECT user_name, CONCAT_WS(' ', real_name, '-', user_name) FROM `##user` ORDER BY real_name"
+ )->fetchAssoc();
+ // The currently selected user may not exist
+ if ($selected && !array_key_exists($selected, $all_users)) {
+ $all_users[$selected] = $selected;
+ }
+
+ return self::selectEditControl($name, $all_users, '-', $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a ADOP field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ * @param Individual $individual
+ *
+ * @return string
+ */
+ public static function editFieldAdoption($name, $selected = '', $extra = '', Individual $individual = null) {
+ return self::selectEditControl($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a PEDI field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ * @param Individual $individual
+ *
+ * @return string
+ */
+ public static function editFieldPedigree($name, $selected = '', $extra = '', Individual $individual = null) {
+ return self::selectEditControl($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a NAME TYPE field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ * @param Individual $individual
+ *
+ * @return string
+ */
+ public static function editFieldNameType($name, $selected = '', $extra = '', Individual $individual = null) {
+ return self::selectEditControl($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
+ }
+
+ /**
+ * Print an edit control for a RELA field.
+ *
+ * @param string $name
+ * @param string $selected
+ * @param string $extra
+ *
+ * @return string
+ */
+ public static function editFieldRelationship($name, $selected = '', $extra = '') {
+ $rela_codes = GedcomCodeRela::getValues();
+ // The user is allowed to specify values that aren't in the list.
+ if (!array_key_exists($selected, $rela_codes)) {
+ $rela_codes[$selected] = $selected;
+ }
+
+ return self::selectEditControl($name, $rela_codes, '', $selected, $extra);
+ }
+
+ /**
+ * Remove all links from $gedrec to $xref, and any sub-tags.
+ *
+ * @param string $gedrec
+ * @param string $xref
+ *
+ * @return string
+ */
+ public static function removeLinks($gedrec, $xref) {
+ $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
+ $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
+ $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
+ $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
+ $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
+
+ return $gedrec;
+ }
+
+ /**
+ * Generates javascript code for calendar popup in user’s language.
+ *
+ * @param string $id
+ *
+ * @return string
+ */
+ public static function printCalendarPopup($id) {
+ return
+ ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
+ '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printAddNewMediaLink($element_id) {
+ return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a new media object') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printAddNewRepositoryLink($element_id) {
+ return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a new repository') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printAddNewNoteLink($element_id) {
+ return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a new shared note') . '"></a>';
+ }
+
+ /**
+ * @param string $note_id
+ *
+ * @return string
+ */
+ public static function printEditNoteLink($note_id) {
+ return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit shared note') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printAddNewSourceLink($element_id) {
+ return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a new source') . '"></a>';
+ }
+
+ /**
+ * add a new tag input field
+ *
+ * called for each fact to be edited on a form.
+ * Fact level=0 means a new empty form : data are POSTed by name
+ * else data are POSTed using arrays :
+ * glevels[] : tag level
+ * islink[] : tag is a link
+ * tag[] : tag name
+ * text[] : tag value
+ *
+ * @param string $tag fact record to edit (eg 2 DATE xxxxx)
+ * @param string $upperlevel optional upper level tag (eg BIRT)
+ * @param string $label An optional label to echo instead of the default
+ * @param string $extra optional text to display after the input field
+ * @param Individual $person For male/female translations
+ *
+ * @return string
+ */
+ public static function addSimpleTag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
+ global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
+
+ // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
+ static $source_element_id;
+
+ $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
+ preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
+ list(, $level, $fact, $value) = $match;
+ $level = (int) $level;
+
+ // element name : used to POST data
+ if ($level === 0) {
+ if ($upperlevel) {
+ $element_name = $upperlevel . '_' . $fact;
+ } else {
+ $element_name = $fact;
+ }
+ } else {
+ $element_name = 'text[]';
+ }
+ if ($level === 1) {
+ $main_fact = $fact;
+ }
+
+ // element id : used by javascript functions
+ if ($level === 0) {
+ $element_id = $fact;
+ } else {
+ $element_id = $fact . Uuid::uuid4();
+ }
+ if ($upperlevel) {
+ $element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
+ }
+
+ // field value
+ $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
+ if ($islink) {
+ $value = trim(substr($tag, strlen($fact) + 3), ' @\r');
+ } else {
+ $value = (string) substr($tag, strlen($fact) + 3);
+ }
+ if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
+ $islink = true;
+ }
+
+ if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
+ $islink = true;
+ $fact = 'NOTE';
+ }
+
+ // label
+ echo '<tr id="', $element_id, '_tr"';
+ if ($fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
+ echo ' style="display:none;"';
+ }
+ echo '>';
+
+ if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
+ echo '<td class="optionbox wrap width25">';
+ } else {
+ echo '<td class="descriptionbox wrap width25">';
+ }
+
+ // tag name
+ if ($label) {
+ echo $label;
+ } elseif ($upperlevel) {
+ echo GedcomTag::getLabel($upperlevel . ':' . $fact);
+ } else {
+ echo GedcomTag::getLabel($fact);
+ }
+
+ // If using GEDFact-assistant window
+ if ($action === 'addnewnote_assisted') {
+ // Do not print on GEDFact Assistant window
+ } else {
+ // Not all facts have help text.
+ switch ($fact) {
+ case 'NAME':
+ if ($upperlevel !== 'REPO') {
+ echo FunctionsPrint::helpLink($fact);
+ }
+ break;
+ case 'DATE':
+ case 'PLAC':
+ case 'RESN':
+ case 'ROMN':
+ case 'SURN':
+ case '_HEB':
+ echo FunctionsPrint::helpLink($fact);
+ break;
+ }
+ }
+ // tag level
+ if ($level > 0) {
+ if ($fact === 'TEXT' && $level > 1) {
+ echo '<input type="hidden" name="glevels[]" value="', $level - 1, '">';
+ echo '<input type="hidden" name="islink[]" value="0">';
+ echo '<input type="hidden" name="tag[]" value="DATA">';
+ // leave data text[] value empty because the following TEXT line will cause the DATA to be added
+ echo '<input type="hidden" name="text[]" value="">';
+ }
+ echo '<input type="hidden" name="glevels[]" value="', $level, '">';
+ echo '<input type="hidden" name="islink[]" value="', $islink, '">';
+ echo '<input type="hidden" name="tag[]" value="', $fact, '">';
+ }
+ echo '</td>';
+
+ // value
+ echo '<td class="optionbox wrap">';
+
+ // retrieve linked NOTE
+ if ($fact === 'NOTE' && $islink) {
+ $note1 = Note::getInstance($value, $WT_TREE);
+ if ($note1) {
+ $noterec = $note1->getGedcom();
+ preg_match('/' . $value . '/i', $noterec, $notematch);
+ $value = $notematch[0];
+ }
+ }
+
+ if (in_array($fact, ConfigData::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
+ echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
+ if ($level <= 1) {
+ echo '<input type="checkbox" ';
+ if ($value) {
+ echo 'checked';
+ }
+ echo ' onclick="if (this.checked) ', $element_id, '.value=\'Y\'; else ', $element_id, '.value=\'\';">';
+ echo I18N::translate('yes');
+ }
+
+ } elseif ($fact === 'TEMP') {
+ echo self::selectEditControl($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
+ } elseif ($fact === 'ADOP') {
+ echo self::editFieldAdoption($element_name, $value, '', $person);
+ } elseif ($fact === 'PEDI') {
+ echo self::editFieldPedigree($element_name, $value, '', $person);
+ } elseif ($fact === 'STAT') {
+ echo self::selectEditControl($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
+ } elseif ($fact === 'RELA') {
+ echo self::editFieldRelationship($element_name, strtolower($value));
+ } elseif ($fact === 'QUAY') {
+ echo self::selectEditControl($element_name, GedcomCodeQuay::getValues(), '', $value);
+ } elseif ($fact === '_WT_USER') {
+ echo self::editFieldUsername($element_name, $value);
+ } elseif ($fact === 'RESN') {
+ echo self::editFieldRestriction($element_name, $value);
+ } elseif ($fact === '_PRIM') {
+ echo '<select id="', $element_id, '" name="', $element_name, '" >';
+ echo '<option value=""></option>';
+ echo '<option value="Y" ';
+ if ($value === 'Y') {
+ echo ' selected';
+ }
+ echo '>', /* I18N: option in list box “always use this image” */
+ I18N::translate('always'), '</option>';
+ echo '<option value="N" ';
+ if ($value === 'N') {
+ echo 'selected';
+ }
+ echo '>', /* I18N: option in list box “never use this image” */
+ I18N::translate('never'), '</option>';
+ echo '</select>';
+ echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
+ } elseif ($fact === 'SEX') {
+ echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
+ if ($value === 'M') {
+ echo 'selected';
+ }
+ echo '>', I18N::translate('Male'), '</option><option value="F" ';
+ if ($value === 'F') {
+ echo 'selected';
+ }
+ echo '>', I18N::translate('Female'), '</option><option value="U" ';
+ if ($value === 'U' || empty($value)) {
+ echo 'selected';
+ }
+ echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
+ } elseif ($fact === 'TYPE' && $level === 3) {
+ //-- Build the selector for the Media 'TYPE' Fact
+ echo '<select name="text[]"><option selected value="" ></option>';
+ $selectedValue = strtolower($value);
+ if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
+ echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
+ }
+ foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
+ echo '<option value="', $typeName, '" ';
+ if ($selectedValue === $typeName) {
+ echo 'selected';
+ }
+ echo '>', $typeValue, '</option>';
+ }
+ echo '</select>';
+ } elseif (($fact === 'NAME' && $upperlevel !== 'REPO') || $fact === '_MARNM') {
+ // Populated in javascript from sub-tags
+ echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
+ echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
+ echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit name') . '"></a>';
+ } else {
+ // textarea
+ if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
+ echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
+ } else {
+ // text
+ // If using GEDFact-assistant window
+ if ($action === 'addnewnote_assisted') {
+ echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
+ } else {
+ echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
+ }
+ echo ' class="', $fact, '"';
+ if (in_array($fact, $subnamefacts)) {
+ echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
+ }
+
+ // Extra markup for specific fact types
+ switch ($fact) {
+ case 'ALIA':
+ case 'ASSO':
+ echo ' data-autocomplete-type="INDI"';
+ break;
+ case '_ASSO':
+ echo ' data-autocomplete-type="ASSO"';
+ break;
+ case 'DATE':
+ echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
+ break;
+ case 'GIVN':
+ echo ' autofocus data-autocomplete-type="GIVN"';
+ break;
+ case 'LATI':
+ echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
+ break;
+ case 'LONG':
+ echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
+ break;
+ case 'NOTE':
+ // Shared notes. Inline notes are handled elsewhere.
+ echo ' data-autocomplete-type="NOTE"';
+ break;
+ case 'OBJE':
+ echo ' data-autocomplete-type="OBJE"';
+ break;
+ case 'PAGE':
+ echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="' . $source_element_id . '"';
+ break;
+ case 'PLAC':
+ echo ' data-autocomplete-type="PLAC"';
+ break;
+ case 'REPO':
+ echo ' data-autocomplete-type="REPO"';
+ break;
+ case 'SOUR':
+ $source_element_id = $element_id;
+ echo ' data-autocomplete-type="SOUR"';
+ break;
+ case 'SURN':
+ case '_MARNM_SURN':
+ echo ' data-autocomplete-type="SURN"';
+ break;
+ case 'TIME':
+ echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5]0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */
+ I18N::translate('hh:mm or hh:mm:ss') . '"';
+ break;
+ }
+ echo '>';
+ }
+
+ $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
+
+ // split PLAC
+ if ($fact === 'PLAC') {
+ echo '<div id="', $element_id, '_pop" style="display: inline;">';
+ echo FunctionsPrint::printSpecialCharacterLink($element_id), ' ', self::print_findplace_link($element_id);
+ echo '<span onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
+ echo '</div>';
+ if (Module::getModuleByName('places_assistant')) {
+ \PlacesAssistantModule::setup_place_subfields($element_id);
+ \PlacesAssistantModule::print_place_subfields($element_id);
+ }
+ } elseif (!in_array($fact, $tmp_array)) {
+ echo FunctionsPrint::printSpecialCharacterLink($element_id);
+ }
+ }
+ // MARRiage TYPE : hide text field and show a selection list
+ if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
+ echo '<script>';
+ echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
+ echo '</script>';
+ echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
+ foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
+ if ($key === 'Unknown') {
+ echo '<option value="" ';
+ } else {
+ echo '<option value="', $key, '" ';
+ }
+ $a = strtolower($key);
+ $b = strtolower($value);
+ if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
+ echo 'selected';
+ }
+ echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
+ }
+ echo '</select>';
+ } elseif ($fact === 'TYPE' && $level === 0) {
+ // NAME TYPE : hide text field and show a selection list
+ $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
+ echo self::editFieldNameType($element_name, $value, $onchange, $person);
+ echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
+ }
+
+ // popup links
+ switch ($fact) {
+ case 'DATE':
+ echo self::printCalendarPopup($element_id);
+
+ // Allow the GEDFact_assistant module to show a census-date selector
+ if (Module::getModuleByName('GEDFact_assistant')) {
+ echo CensusAssistantModule::censusDateSelector($action, $upperlevel, $element_id);
+ }
+ break;
+ case 'FAMC':
+ case 'FAMS':
+ echo FunctionsPrint::printFindFamilyLink($element_id);
+ break;
+ case 'ALIA':
+ case 'ASSO':
+ case '_ASSO':
+ echo FunctionsPrint::printFindIndividualLink($element_id, $element_id . '_description');
+ break;
+ case 'FILE':
+ FunctionsPrint::printFindMediaLink($element_id, '0file');
+ break;
+ case 'SOUR':
+ echo FunctionsPrint::printFindSourceLink($element_id, $element_id . '_description'), ' ', self::printAddNewSourceLink($element_id);
+ //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
+ if ($level === 1) {
+ echo '<br>';
+ switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
+ case '2': // records
+ $level1_checked = 'checked';
+ $level2_checked = '';
+ break;
+ case '1': // facts
+ $level1_checked = '';
+ $level2_checked = 'checked';
+ break;
+ case '0': // none
+ default:
+ $level1_checked = '';
+ $level2_checked = '';
+ break;
+ }
+ if (strpos($bdm, 'B') !== false) {
+ echo '&nbsp;<input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">';
+ echo I18N::translate('Individual');
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
+ foreach ($matches[1] as $match) {
+ if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
+ echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">';
+ echo GedcomTag::getLabel($match);
+ }
+ }
+ }
+ }
+ if (strpos($bdm, 'D') !== false) {
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
+ foreach ($matches[1] as $match) {
+ if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
+ echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
+ echo GedcomTag::getLabel($match);
+ }
+ }
+ }
+ }
+ if (strpos($bdm, 'M') !== false) {
+ echo '&nbsp;<input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">';
+ echo I18N::translate('Family');
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
+ foreach ($matches[1] as $match) {
+ echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
+ echo GedcomTag::getLabel($match);
+ }
+ }
+ }
+ }
+ break;
+ case 'REPO':
+ echo FunctionsPrint::printFindRepositoryLink($element_id), ' ', self::printAddNewRepositoryLink($element_id);
+ break;
+ case 'NOTE':
+ // Shared Notes Icons ========================================
+ if ($islink) {
+ // Print regular Shared Note icons ---------------------------
+ echo ' ', FunctionsPrint::printFindNoteLink($element_id, $element_id . '_description'), ' ', self::printAddNewNoteLink($element_id);
+ if ($value) {
+ echo ' ', self::printEditNoteLink($value);
+ }
+
+ // Allow the GEDFact_assistant module to create a formatted shared note.
+ if ($upperlevel === 'CENS' && Module::getModuleByName('GEDFact_assistant')) {
+ echo CensusAssistantModule::addNoteWithAssistantLink($element_id, $xref, $action);
+ }
+ }
+ break;
+ case 'OBJE':
+ echo FunctionsPrint::printFindMediaLink($element_id, '1media');
+ if (!$value) {
+ echo ' ', self::printAddNewMediaLink($element_id);
+ $value = 'new';
+ }
+ break;
+ }
+
+ echo '<div id="' . $element_id . '_description">';
+
+ // current value
+ if ($fact === 'DATE') {
+ $date = new Date($value);
+ echo $date->display();
+ }
+ if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
+ if ($level === 1) {
+ echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
+ } else {
+ echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
+ }
+ }
+
+ if ($value && $value !== 'new' && $islink) {
+ switch ($fact) {
+ case 'ALIA':
+ case 'ASSO':
+ case '_ASSO':
+ $tmp = Individual::getInstance($value, $WT_TREE);
+ if ($tmp) {
+ echo ' ', $tmp->getFullname();
+ }
+ break;
+ case 'SOUR':
+ $tmp = Source::getInstance($value, $WT_TREE);
+ if ($tmp) {
+ echo ' ', $tmp->getFullname();
+ }
+ break;
+ case 'NOTE':
+ $tmp = Note::getInstance($value, $WT_TREE);
+ if ($tmp) {
+ echo ' ', $tmp->getFullname();
+ }
+ break;
+ case 'OBJE':
+ $tmp = Media::getInstance($value, $WT_TREE);
+ if ($tmp) {
+ echo ' ', $tmp->getFullname();
+ }
+ break;
+ case 'REPO':
+ $tmp = Repository::getInstance($value, $WT_TREE);
+ if ($tmp) {
+ echo ' ', $tmp->getFullname();
+ }
+ break;
+ }
+ }
+
+ // pastable values
+ if ($fact === 'FORM' && $upperlevel === 'OBJE') {
+ FunctionsPrint::printAutoPasteLink($element_id, ConfigData::fileFormats());
+ }
+ echo '</div>', $extra, '</td></tr>';
+
+ return $element_id;
+ }
+
+ /**
+ * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
+ *
+ * @param string $tag
+ * @param int $level
+ * @param string $parent_tag
+ */
+ public static function printAddLayer($tag, $level = 2, $parent_tag = '') {
+ global $WT_TREE;
+
+ switch ($tag) {
+ case 'SOUR':
+ echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a new source citation'), '</a>';
+ echo '<br>';
+ echo '<div id="newsource" style="display: none;">';
+ echo '<table class="facts_table">';
+ // 2 SOUR
+ self::addSimpleTag($level . ' SOUR @');
+ // 3 PAGE
+ self::addSimpleTag(($level + 1) . ' PAGE');
+ // 3 DATA
+ // 4 TEXT
+ self::addSimpleTag(($level + 2) . ' TEXT');
+ if ($WT_TREE->getPreference('FULL_SOURCES')) {
+ // 4 DATE
+ self::addSimpleTag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
+ // 3 QUAY
+ self::addSimpleTag(($level + 1) . ' QUAY');
+ }
+ // 3 OBJE
+ self::addSimpleTag(($level + 1) . ' OBJE');
+ // 3 SHARED_NOTE
+ self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
+ echo '</table></div>';
+ break;
+
+ case 'ASSO':
+ case 'ASSO2':
+ //-- Add a new ASSOciate
+ if ($tag === 'ASSO') {
+ echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
+ echo '<br>';
+ echo '<div id="newasso" style="display: none;">';
+ } else {
+ echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
+ echo '<br>';
+ echo '<div id="newasso2" style="display: none;">';
+ }
+ echo '<table class="facts_table">';
+ // 2 ASSO
+ self::addSimpleTag($level . ' ASSO @');
+ // 3 RELA
+ self::addSimpleTag(($level + 1) . ' RELA');
+ // 3 NOTE
+ self::addSimpleTag(($level + 1) . ' NOTE');
+ // 3 SHARED_NOTE
+ self::addSimpleTag(($level + 1) . ' SHARED_NOTE');
+ echo '</table></div>';
+ break;
+
+ case 'NOTE':
+ //-- Retrieve existing note or add new note to fact
+ echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new note'), '</a>';
+ echo '<br>';
+ echo '<div id="newnote" style="display: none;">';
+ echo '<table class="facts_table">';
+ // 2 NOTE
+ self::addSimpleTag($level . ' NOTE');
+ echo '</table></div>';
+ break;
+
+ case 'SHARED_NOTE':
+ echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new shared note'), '</a>';
+ echo '<br>';
+ echo '<div id="newshared_note" style="display: none;">';
+ echo '<table class="facts_table">';
+ // 2 SHARED NOTE
+ self::addSimpleTag($level . ' SHARED_NOTE', $parent_tag);
+ echo '</table></div>';
+ break;
+
+ case 'OBJE':
+ if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
+ echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new media object'), '</a>';
+ echo '<br>';
+ echo '<div id="newobje" style="display: none;">';
+ echo '<table class="facts_table">';
+ self::addSimpleTag($level . ' OBJE');
+ echo '</table></div>';
+ }
+ break;
+
+ case 'RESN':
+ echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new restriction'), '</a>';
+ echo '<br>';
+ echo '<div id="newresn" style="display: none;">';
+ echo '<table class="facts_table">';
+ // 2 RESN
+ self::addSimpleTag($level . ' RESN');
+ echo '</table></div>';
+ break;
+ }
+ }
+
+ /**
+ * Add some empty tags to create a new fact.
+ *
+ * @param string $fact
+ */
+ public static function addSimpleTags($fact) {
+ global $WT_TREE;
+
+ // For new individuals, these facts default to "Y"
+ if ($fact === 'MARR') {
+ self::addSimpleTag('0 ' . $fact . ' Y');
+ } else {
+ self::addSimpleTag('0 ' . $fact);
+ }
+
+ if (!in_array($fact, ConfigData::nonDateFacts())) {
+ self::addSimpleTag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
+ }
+
+ if (!in_array($fact, ConfigData::nonPlaceFacts())) {
+ self::addSimpleTag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
+
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
+ foreach ($match[1] as $tag) {
+ self::addSimpleTag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
+ }
+ }
+ self::addSimpleTag('0 MAP', $fact);
+ self::addSimpleTag('0 LATI', $fact);
+ self::addSimpleTag('0 LONG', $fact);
+ }
+ }
+
+ /**
+ * Assemble the pieces of a newly created record into gedcom
+ *
+ * @return string
+ */
+ public static function addNewName() {
+ global $WT_TREE;
+
+ $gedrec = "\n1 NAME " . Filter::post('NAME');
+
+ $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
+
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
+ $tags = array_merge($tags, $match[1]);
+ }
+
+ // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
+ $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
+ if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
+ $tags[] = '_MARNM';
+ }
+
+ foreach (array_unique($tags) as $tag) {
+ $TAG = Filter::post($tag);
+ if ($TAG) {
+ $gedrec .= "\n2 {$tag} {$TAG}";
+ }
+ }
+
+ return $gedrec;
+ }
+
+ /**
+ * @return string
+ */
+ public static function addNewSex() {
+ switch (Filter::post('SEX', '[MF]', 'U')) {
+ case 'M':
+ return "\n1 SEX M";
+ case 'F':
+ return "\n1 SEX F";
+ default:
+ return "\n1 SEX U";
+ }
+ }
+
+ /**
+ * @param string $fact
+ *
+ * @return string
+ */
+ public static function addNewFact($fact) {
+ global $WT_TREE;
+
+ $FACT = Filter::post($fact);
+ $DATE = Filter::post($fact . '_DATE');
+ $PLAC = Filter::post($fact . '_PLAC');
+ if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
+ if ($FACT && $FACT !== 'Y') {
+ $gedrec = "\n1 " . $fact . ' ' . $FACT;
+ } else {
+ $gedrec = "\n1 " . $fact;
+ }
+ if ($DATE) {
+ $gedrec .= "\n2 DATE " . $DATE;
+ }
+ if ($PLAC) {
+ $gedrec .= "\n2 PLAC " . $PLAC;
+
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
+ foreach ($match[1] as $tag) {
+ $TAG = Filter::post($fact . '_' . $tag);
+ if ($TAG) {
+ $gedrec .= "\n3 " . $tag . ' ' . $TAG;
+ }
+ }
+ }
+ $LATI = Filter::post($fact . '_LATI');
+ $LONG = Filter::post($fact . '_LONG');
+ if ($LATI || $LONG) {
+ $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
+ }
+ }
+ if (Filter::postBool('SOUR_' . $fact)) {
+ return self::updateSource($gedrec, 2);
+ } else {
+ return $gedrec;
+ }
+ } elseif ($FACT === 'Y') {
+ if (Filter::postBool('SOUR_' . $fact)) {
+ return self::updateSource("\n1 " . $fact . ' Y', 2);
+ } else {
+ return "\n1 " . $fact . ' Y';
+ }
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * This function splits the $glevels, $tag, $islink, and $text arrays so that the
+ * entries associated with a SOUR record are separate from everything else.
+ *
+ * Input arrays:
+ * - $glevels[] - an array of the gedcom level for each line that was edited
+ * - $tag[] - an array of the tags for each gedcom line that was edited
+ * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
+ * - $text[] - an array of the text data for each line
+ *
+ * Output arrays:
+ * ** For the SOUR record:
+ * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
+ * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
+ * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
+ * - $textSOUR[] - an array of the text data for each line
+ * ** For the remaining records:
+ * - $glevelsRest[] - an array of the gedcom level for each line that was edited
+ * - $tagRest[] - an array of the tags for each gedcom line that was edited
+ * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
+ * - $textRest[] - an array of the text data for each line
+ */
+ public static function splitSource() {
+ global $glevels, $tag, $islink, $text;
+ global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
+ global $glevelsRest, $tagRest, $islinkRest, $textRest;
+
+ $glevelsSOUR = array();
+ $tagSOUR = array();
+ $islinkSOUR = array();
+ $textSOUR = array();
+
+ $glevelsRest = array();
+ $tagRest = array();
+ $islinkRest = array();
+ $textRest = array();
+
+ $inSOUR = false;
+
+ for ($i = 0; $i < count($glevels); $i++) {
+ if ($inSOUR) {
+ if ($levelSOUR < $glevels[$i]) {
+ $dest = 'S';
+ } else {
+ $inSOUR = false;
+ $dest = 'R';
+ }
+ } else {
+ if ($tag[$i] === 'SOUR') {
+ $inSOUR = true;
+ $levelSOUR = $glevels[$i];
+ $dest = 'S';
+ } else {
+ $dest = 'R';
+ }
+ }
+ if ($dest === 'S') {
+ $glevelsSOUR[] = $glevels[$i];
+ $tagSOUR[] = $tag[$i];
+ $islinkSOUR[] = $islink[$i];
+ $textSOUR[] = $text[$i];
+ } else {
+ $glevelsRest[] = $glevels[$i];
+ $tagRest[] = $tag[$i];
+ $islinkRest[] = $islink[$i];
+ $textRest[] = $text[$i];
+ }
+ }
+ }
+
+ /**
+ * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
+ * were produced by the splitSOUR() function.
+ * See the FunctionsEdit::handle_updatesges() function for details.
+ *
+ * @param string $inputRec
+ * @param string $levelOverride
+ *
+ * @return string
+ */
+ public static function updateSource($inputRec, $levelOverride = 'no') {
+ global $glevels, $tag, $islink, $text;
+ global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
+
+ if (count($tagSOUR) === 0) {
+ return $inputRec; // No update required
+ }
+
+ // Save original interface update arrays before replacing them with the xxxSOUR ones
+ $glevelsSave = $glevels;
+ $tagSave = $tag;
+ $islinkSave = $islink;
+ $textSave = $text;
+
+ $glevels = $glevelsSOUR;
+ $tag = $tagSOUR;
+ $islink = $islinkSOUR;
+ $text = $textSOUR;
+
+ $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
+
+ // Restore the original interface update arrays (just in case ...)
+ $glevels = $glevelsSave;
+ $tag = $tagSave;
+ $islink = $islinkSave;
+ $text = $textSave;
+
+ return $myRecord;
+ }
+
+ /**
+ * Add new GEDCOM lines from the $xxxRest interface update arrays, which
+ * were produced by the splitSOUR() function.
+ * See the FunctionsEdit::handle_updatesges() function for details.
+ *
+ * @param string $inputRec
+ * @param string $levelOverride
+ *
+ * @return string
+ */
+ public static function updateRest($inputRec, $levelOverride = 'no') {
+ global $glevels, $tag, $islink, $text;
+ global $glevelsRest, $tagRest, $islinkRest, $textRest;
+
+ if (count($tagRest) === 0) {
+ return $inputRec; // No update required
+ }
+
+ // Save original interface update arrays before replacing them with the xxxRest ones
+ $glevelsSave = $glevels;
+ $tagSave = $tag;
+ $islinkSave = $islink;
+ $textSave = $text;
+
+ $glevels = $glevelsRest;
+ $tag = $tagRest;
+ $islink = $islinkRest;
+ $text = $textRest;
+
+ $myRecord = self::handleUpdates($inputRec, $levelOverride); // Now do the update
+
+ // Restore the original interface update arrays (just in case ...)
+ $glevels = $glevelsSave;
+ $tag = $tagSave;
+ $islink = $islinkSave;
+ $text = $textSave;
+
+ return $myRecord;
+ }
+
+ /**
+ * Add new gedcom lines from interface update arrays
+ * The edit_interface and FunctionsEdit::add_simple_tag function produce the following
+ * arrays incoming from the $_POST form
+ * - $glevels[] - an array of the gedcom level for each line that was edited
+ * - $tag[] - an array of the tags for each gedcom line that was edited
+ * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
+ * - $text[] - an array of the text data for each line
+ * With these arrays you can recreate the gedcom lines like this
+ * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
+ * There will be an index in each of these arrays for each line of the gedcom
+ * fact that is being edited.
+ * If the $text[] array is empty for the given line, then it means that the
+ * user removed that line during editing or that the line is supposed to be
+ * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
+ * there is a section of code that looks ahead to the next lines to see if there
+ * are sub lines. For example we don't want to remove the 1 DEAT line if it has
+ * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
+ * can be safely removed.
+ *
+ * @param string $newged the new gedcom record to add the lines to
+ * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
+ *
+ * @return string The updated gedcom record
+ */
+ public static function handleUpdates($newged, $levelOverride = 'no') {
+ global $glevels, $islink, $tag, $uploaded_files, $text;
+
+ if ($levelOverride === 'no' || count($glevels) === 0) {
+ $levelAdjust = 0;
+ } else {
+ $levelAdjust = $levelOverride - $glevels[0];
+ }
+
+ for ($j = 0; $j < count($glevels); $j++) {
+
+ // Look for empty SOUR reference with non-empty sub-records.
+ // This can happen when the SOUR entry is deleted but its sub-records
+ // were incorrectly left intact.
+ // The sub-records should be deleted.
+ if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
+ $text[$j] = '';
+ $k = $j + 1;
+ while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
+ $text[$k] = '';
+ $k++;
+ }
+ }
+
+ if (trim($text[$j]) !== '') {
+ $pass = true;
+ } else {
+ //-- for facts with empty values they must have sub records
+ //-- this section checks if they have subrecords
+ $k = $j + 1;
+ $pass = false;
+ while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
+ if ($text[$k] !== '') {
+ if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
+ $pass = true;
+ break;
+ }
+ }
+ if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
+ $filename = array_shift($uploaded_files);
+ if (!empty($filename)) {
+ $text[$k] = $filename;
+ $pass = true;
+ break;
+ }
+ }
+ $k++;
+ }
+ }
+
+ //-- if the value is not empty or it has sub lines
+ //--- then write the line to the gedcom record
+ //if ((($text[trim($j)]!='')||($pass==true)) && (strlen($text[$j]) > 0)) {
+ //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
+ if ($pass) {
+ $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
+ //-- check and translate the incoming dates
+ if ($tag[$j] === 'DATE' && $text[$j] !== '') {
+ }
+ if ($text[$j] !== '') {
+ if ($islink[$j]) {
+ $newline .= ' @' . $text[$j] . '@';
+ } else {
+ $newline .= ' ' . $text[$j];
+ }
+ }
+ $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
+ }
+ }
+
+ return $newged;
+ }
+
+ /**
+ * builds the form for adding new facts
+ *
+ * @param string $fact the new fact we are adding
+ */
+ public static function createAddForm($fact) {
+ global $tags, $WT_TREE;
+
+ $tags = array();
+
+ // handle MARRiage TYPE
+ if (substr($fact, 0, 5) === 'MARR_') {
+ $tags[0] = 'MARR';
+ self::addSimpleTag('1 MARR');
+ self::insertMissingSubtags($fact);
+ } else {
+ $tags[0] = $fact;
+ if ($fact === '_UID') {
+ $fact .= ' ' . GedcomTag::createUid();
+ }
+ // These new level 1 tags need to be turned into links
+ if (in_array($fact, array('ALIA', 'ASSO'))) {
+ $fact .= ' @';
+ }
+ if (in_array($fact, ConfigData::emptyFacts())) {
+ self::addSimpleTag('1 ' . $fact . ' Y');
+ } else {
+ self::addSimpleTag('1 ' . $fact);
+ }
+ self::insertMissingSubtags($tags[0]);
+ //-- handle the special SOURce case for level 1 sources [ 1759246 ]
+ if ($fact === 'SOUR') {
+ self::addSimpleTag('2 PAGE');
+ self::addSimpleTag('3 TEXT');
+ if ($WT_TREE->getPreference('FULL_SOURCES')) {
+ self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
+ self::addSimpleTag('2 QUAY');
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a form to edit a Fact object.
+ *
+ * @param GedcomRecord $record
+ * @param Fact $fact
+ *
+ * @return string
+ */
+ public static function createEditForm(GedcomRecord $record, Fact $fact) {
+ global $tags, $WT_TREE;
+
+ $pid = $record->getXref();
+
+ $tags = array();
+ $gedlines = explode("\n", $fact->getGedcom());
+
+ $linenum = 0;
+ $fields = explode(' ', $gedlines[$linenum]);
+ $glevel = $fields[0];
+ $level = $glevel;
+
+ $type = $fact->getTag();
+ $parent = $fact->getParent();
+ $level0type = $parent::RECORD_TYPE;
+ $level1type = $type;
+
+ $i = $linenum;
+ $inSource = false;
+ $levelSource = 0;
+ $add_date = true;
+ // List of tags we would expect at the next level
+ // NB add_missing_subtags() already takes care of the simple cases
+ // where a level 1 tag is missing a level 2 tag. Here we only need to
+ // handle the more complicated cases.
+ $expected_subtags = array(
+ 'SOUR' => array('PAGE', 'DATA'),
+ 'DATA' => array('TEXT'),
+ 'PLAC' => array('MAP'),
+ 'MAP' => array('LATI', 'LONG'),
+ );
+ if ($record->getTree()->getPreference('FULL_SOURCES')) {
+ $expected_subtags['SOUR'][] = 'QUAY';
+ $expected_subtags['DATA'][] = 'DATE';
+ }
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
+ $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
+ }
+
+ $stack = array(0 => $level0type);
+ // Loop on existing tags :
+ while (true) {
+ // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
+ $stack[(int) $level] = $type;
+ // Merge them together, e.g. BIRT:PLAC:FONE
+ $label = implode(':', array_slice($stack, 1, $level));
+
+ $text = '';
+ for ($j = 2; $j < count($fields); $j++) {
+ if ($j > 2) {
+ $text .= ' ';
+ }
+ $text .= $fields[$j];
+ }
+ $text = rtrim($text);
+ while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
+ $text .= "\n" . $cmatch[1];
+ $i++;
+ }
+
+ if ($type === 'SOUR') {
+ $inSource = true;
+ $levelSource = $level;
+ } elseif ($levelSource >= $level) {
+ $inSource = false;
+ }
+
+ if ($type !== 'DATA' && $type !== 'CONT') {
+ $tags[] = $type;
+ $person = Individual::getInstance($pid, $WT_TREE);
+ $subrecord = $level . ' ' . $type . ' ' . $text;
+ if ($inSource && $type === 'DATE') {
+ self::addSimpleTag($subrecord, '', GedcomTag::getLabel($label, $person));
+ } elseif (!$inSource && $type === 'DATE') {
+ self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
+ $add_date = false;
+ } elseif ($type === 'STAT') {
+ self::addSimpleTag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
+ } elseif ($level0type === 'REPO') {
+ $repo = Repository::getInstance($pid, $WT_TREE);
+ self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $repo));
+ } else {
+ self::addSimpleTag($subrecord, $level0type, GedcomTag::getLabel($label, $person));
+ }
+ }
+
+ // Get a list of tags present at the next level
+ $subtags = array();
+ for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^\s*(\d+)\s+(\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
+ if ($mm[1] == $level + 1) {
+ $subtags[] = $mm[2];
+ }
+ }
+
+ // Insert missing tags
+ if (!empty($expected_subtags[$type])) {
+ foreach ($expected_subtags[$type] as $subtag) {
+ if (!in_array($subtag, $subtags)) {
+ if (!$inSource || $subtag !== 'DATA') {
+ self::addSimpleTag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
+ }
+ if (!empty($expected_subtags[$subtag])) {
+ foreach ($expected_subtags[$subtag] as $subsubtag) {
+ self::addSimpleTag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
+ }
+ }
+ }
+ }
+ }
+
+ // Awkward special cases
+ if ($level == 2 && $type === 'DATE' && in_array($level1type, ConfigData::dateAndTime()) && !in_array('TIME', $subtags)) {
+ self::addSimpleTag('3 TIME'); // TIME is NOT a valid 5.5.1 tag
+ }
+ if ($level == 2 && $type === 'STAT' && GedcomCodeTemp::isTagLDS($level1type) && !in_array('DATE', $subtags)) {
+ self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
+ }
+
+ $i++;
+ if (isset($gedlines[$i])) {
+ $fields = explode(' ', $gedlines[$i]);
+ $level = $fields[0];
+ if (isset($fields[1])) {
+ $type = trim($fields[1]);
+ } else {
+ $level = 0;
+ }
+ } else {
+ $level = 0;
+ }
+ if ($level <= $glevel) {
+ break;
+ }
+ }
+
+ if ($level1type !== '_PRIM') {
+ self::insertMissingSubtags($level1type, $add_date);
+ }
+
+ return $level1type;
+ }
+
+ /**
+ * Populates the global $tags array with any missing sub-tags.
+ *
+ * @param string $level1tag the type of the level 1 gedcom record
+ * @param bool $add_date
+ */
+ public static function insertMissingSubtags($level1tag, $add_date = false) {
+ global $tags, $WT_TREE;
+
+ // handle MARRiage TYPE
+ $type_val = '';
+ if (substr($level1tag, 0, 5) === 'MARR_') {
+ $type_val = substr($level1tag, 5);
+ $level1tag = 'MARR';
+ }
+
+ foreach (ConfigData::levelTwoTags() as $key => $value) {
+ if ($key === 'DATE' && in_array($level1tag, ConfigData::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, ConfigData::nonPlaceFacts())) {
+ continue;
+ }
+ if (in_array($level1tag, $value) && !in_array($key, $tags)) {
+ if ($key === 'TYPE') {
+ self::addSimpleTag('2 TYPE ' . $type_val, $level1tag);
+ } elseif ($level1tag === '_TODO' && $key === 'DATE') {
+ self::addSimpleTag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
+ } elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
+ self::addSimpleTag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
+ } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
+ self::addSimpleTag('2 ' . $key, $level1tag);
+ } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
+ self::addSimpleTag('2 ' . $key, $level1tag);
+ } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
+ self::addSimpleTag('2 ' . $key, $level1tag);
+ }
+ // Add level 3/4 tags as appropriate
+ switch ($key) {
+ case 'PLAC':
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
+ foreach ($match[1] as $tag) {
+ self::addSimpleTag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
+ }
+ }
+ self::addSimpleTag('3 MAP');
+ self::addSimpleTag('4 LATI');
+ self::addSimpleTag('4 LONG');
+ break;
+ case 'FILE':
+ self::addSimpleTag('3 FORM');
+ break;
+ case 'EVEN':
+ self::addSimpleTag('3 DATE');
+ self::addSimpleTag('3 PLAC');
+ break;
+ case 'STAT':
+ if (GedcomCodeTemp::isTagLDS($level1tag)) {
+ self::addSimpleTag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
+ }
+ break;
+ case 'DATE':
+ // TIME is NOT a valid 5.5.1 tag
+ if (in_array($level1tag, ConfigData::dateAndTime())) {
+ self::addSimpleTag('3 TIME');
+ }
+ break;
+ case 'HUSB':
+ case 'WIFE':
+ self::addSimpleTag('3 AGE');
+ break;
+ case 'FAMC':
+ if ($level1tag === 'ADOP') {
+ self::addSimpleTag('3 ADOP BOTH');
+ }
+ break;
+ }
+ } elseif ($key === 'DATE' && $add_date) {
+ self::addSimpleTag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
+ }
+ }
+ // Do something (anything!) with unrecognized custom tags
+ if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
+ foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
+ if (!in_array($tag, $tags)) {
+ self::addSimpleTag('2 ' . $tag);
+ if ($tag === 'PLAC') {
+ if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
+ foreach ($match[1] as $ptag) {
+ self::addSimpleTag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
+ }
+ }
+ self::addSimpleTag('3 MAP');
+ self::addSimpleTag('4 LATI');
+ self::addSimpleTag('4 LONG');
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/Functions/FunctionsExport.php b/app/Functions/FunctionsExport.php
new file mode 100644
index 0000000000..03b0c63ad2
--- /dev/null
+++ b/app/Functions/FunctionsExport.php
@@ -0,0 +1,314 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Database;
+use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Media;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Repository;
+use Fisharebest\Webtrees\Source;
+use Fisharebest\Webtrees\Tree;
+
+/**
+ * Class FunctionsExport - common functions
+ */
+class FunctionsExport {
+ /**
+ * Tidy up a gedcom record on export, for compatibility/portability.
+ *
+ * @param string $rec
+ *
+ * @return string
+ */
+ public static function reformatRecord($rec) {
+ global $WT_TREE;
+
+ $newrec = '';
+ foreach (preg_split('/[\r\n]+/', $rec, -1, PREG_SPLIT_NO_EMPTY) as $line) {
+ // Split long lines
+ // The total length of a GEDCOM line, including level number, cross-reference number,
+ // tag, value, delimiters, and terminator, must not exceed 255 (wide) characters.
+ if (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH) {
+ list($level, $tag) = explode(' ', $line, 3);
+ if ($tag != 'CONT' && $tag != 'CONC') {
+ $level++;
+ }
+ do {
+ // Split after $pos chars
+ $pos = WT_GEDCOM_LINE_LENGTH;
+ if ($WT_TREE->getPreference('WORD_WRAPPED_NOTES')) {
+ // Split on a space, and remove it (for compatibility with some desktop apps)
+ while ($pos && mb_substr($line, $pos - 1, 1) != ' ') {
+ --$pos;
+ }
+ if ($pos == strpos($line, ' ', 3) + 1) {
+ // No spaces in the data! Can’t split it :-(
+ break;
+ } else {
+ $newrec .= mb_substr($line, 0, $pos - 1) . WT_EOL;
+ $line = $level . ' CONC ' . mb_substr($line, $pos);
+ }
+ } else {
+ // Split on a non-space (standard gedcom behaviour)
+ while ($pos && mb_substr($line, $pos - 1, 1) == ' ') {
+ --$pos;
+ }
+ if ($pos == strpos($line, ' ', 3)) {
+ // No non-spaces in the data! Can’t split it :-(
+ break;
+ }
+ $newrec .= mb_substr($line, 0, $pos) . WT_EOL;
+ $line = $level . ' CONC ' . mb_substr($line, $pos);
+ }
+ } while (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH);
+ }
+ $newrec .= $line . WT_EOL;
+ }
+
+ return $newrec;
+ }
+
+ /**
+ * Create a header for a (newly-created or already-imported) gedcom file.
+ *
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public static function gedcomHeader(Tree $tree) {
+ // Default values for a new header
+ $HEAD = "0 HEAD";
+ $SOUR = "\n1 SOUR " . WT_WEBTREES . "\n2 NAME " . WT_WEBTREES . "\n2 VERS " . WT_VERSION;
+ $DEST = "\n1 DEST DISKETTE";
+ $DATE = "\n1 DATE " . strtoupper(date("d M Y")) . "\n2 TIME " . date("H:i:s");
+ $GEDC = "\n1 GEDC\n2 VERS 5.5.1\n2 FORM Lineage-Linked";
+ $CHAR = "\n1 CHAR UTF-8";
+ $FILE = "\n1 FILE " . $tree->getName();
+ $LANG = "";
+ $PLAC = "\n1 PLAC\n2 FORM City, County, State/Province, Country";
+ $COPR = "";
+ $SUBN = "";
+ $SUBM = "\n1 SUBM @SUBM@\n0 @SUBM@ SUBM\n1 NAME " . Auth::user()->getUserName(); // The SUBM record is mandatory
+
+ // Preserve some values from the original header
+ $record = GedcomRecord::getInstance('HEAD', $tree);
+ if ($fact = $record->getFirstFact('PLAC')) {
+ $PLAC = "\n1 PLAC\n2 FORM " . $fact->getAttribute('FORM');
+ }
+ if ($fact = $record->getFirstFact('LANG')) {
+ $LANG = $fact->getValue();
+ }
+ if ($fact = $record->getFirstFact('SUBN')) {
+ $SUBN = $fact->getValue();
+ }
+ if ($fact = $record->getFirstFact('COPR')) {
+ $COPR = $fact->getValue();
+ }
+ // Link to actual SUBM/SUBN records, if they exist
+ $subn =
+ Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
+ ->execute(array('SUBN', $tree->getTreeId()))
+ ->fetchOne();
+ if ($subn) {
+ $SUBN = "\n1 SUBN @{$subn}@";
+ }
+ $subm =
+ Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
+ ->execute(array('SUBM', $tree->getTreeId()))
+ ->fetchOne();
+ if ($subm) {
+ $SUBM = "\n1 SUBM @{$subm}@";
+ }
+
+ return $HEAD . $SOUR . $DEST . $DATE . $GEDC . $CHAR . $FILE . $COPR . $LANG . $PLAC . $SUBN . $SUBM . "\n";
+ }
+
+ /**
+ * Prepend the GEDCOM_MEDIA_PATH to media filenames.
+ *
+ * @param string $rec
+ * @param string $path
+ *
+ * @return string
+ */
+ public static function convertMediaPath($rec, $path) {
+ if ($path && preg_match('/\n1 FILE (.+)/', $rec, $match)) {
+ $old_file_name = $match[1];
+ // Don’t modify external links
+ if (!preg_match('~^(https?|ftp):~', $old_file_name)) {
+ // Adding a windows path? Convert the slashes.
+ if (strpos($path, '\\') !== false) {
+ $new_file_name = preg_replace('~/+~', '\\', $old_file_name);
+ } else {
+ $new_file_name = $old_file_name;
+ }
+ // Path not present - add it.
+ if (strpos($new_file_name, $path) === false) {
+ $new_file_name = $path . $new_file_name;
+ }
+ $rec = str_replace("\n1 FILE " . $old_file_name, "\n1 FILE " . $new_file_name, $rec);
+ }
+ }
+
+ return $rec;
+ }
+
+ /**
+ * Export the database in GEDCOM format
+ *
+ * @param Tree $tree Which tree to export
+ * @param resource $gedout Handle to a writable stream
+ * @param string[] $exportOptions Export options are as follows:
+ * 'privatize': which Privacy rules apply? (none, visitor, user, manager)
+ * 'toANSI': should the output be produced in ISO-8859-1 instead of UTF-8? (yes, no)
+ * 'path': what constant should prefix all media file paths? (eg: media/ or c:\my pictures\my family
+ * 'slashes': what folder separators apply to media file paths? (forward, backward)
+ */
+ public static function exportGedcom(Tree $tree, $gedout, $exportOptions) {
+ switch ($exportOptions['privatize']) {
+ case 'gedadmin':
+ $access_level = Auth::PRIV_NONE;
+ break;
+ case 'user':
+ $access_level = Auth::PRIV_USER;
+ break;
+ case 'visitor':
+ $access_level = Auth::PRIV_PRIVATE;
+ break;
+ case 'none':
+ $access_level = Auth::PRIV_HIDE;
+ break;
+ }
+
+ $head = self::gedcomHeader($tree);
+ if ($exportOptions['toANSI'] == 'yes') {
+ $head = str_replace('UTF-8', 'ANSI', $head);
+ $head = utf8_decode($head);
+ }
+ $head = self::reformatRecord($head);
+ fwrite($gedout, $head);
+
+ // Buffer the output. Lots of small fwrite() calls can be very slow when writing large gedcoms.
+ $buffer = '';
+
+ // Generate the OBJE/SOUR/REPO/NOTE records first, as their privacy calcualations involve
+ // database queries, and we wish to avoid large gaps between queries due to MySQL connection timeouts.
+ $tmp_gedcom = '';
+ $rows = Database::prepare(
+ "SELECT m_id AS xref, m_gedcom AS gedcom" .
+ " FROM `##media` WHERE m_file = :tree_id ORDER BY m_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ foreach ($rows as $row) {
+ $rec = Media::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
+ $rec = self::convertMediaPath($rec, $exportOptions['path']);
+ if ($exportOptions['toANSI'] === 'yes') {
+ $rec = utf8_decode($rec);
+ }
+ $tmp_gedcom .= self::reformatRecord($rec);
+ }
+
+ $rows = Database::prepare(
+ "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom" .
+ " FROM `##sources` WHERE s_file = :tree_id ORDER BY s_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ foreach ($rows as $row) {
+ $rec = Source::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
+ if ($exportOptions['toANSI'] === 'yes') {
+ $rec = utf8_decode($rec);
+ }
+ $tmp_gedcom .= self::reformatRecord($rec);
+ }
+
+ $rows = Database::prepare(
+ "SELECT o_type AS type, o_id AS xref, o_gedcom AS gedcom" .
+ " FROM `##other` WHERE o_file = :tree_id AND o_type NOT IN ('HEAD', 'TRLR') ORDER BY o_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ foreach ($rows as $row) {
+ switch ($row->type) {
+ case 'NOTE':
+ $record = Note::getInstance($row->xref, $tree, $row->gedcom);
+ break;
+ case 'REPO':
+ $record = Repository::getInstance($row->xref, $tree, $row->gedcom);
+ break;
+ default:
+ $record = GedcomRecord::getInstance($row->xref, $tree, $row->gedcom);
+ break;
+ }
+
+ $rec = $record->privatizeGedcom($access_level);
+ if ($exportOptions['toANSI'] === 'yes') {
+ $rec = utf8_decode($rec);
+ }
+ $tmp_gedcom .= self::reformatRecord($rec);
+ }
+
+ $rows = Database::prepare(
+ "SELECT i_id AS xref, i_gedcom AS gedcom" .
+ " FROM `##individuals` WHERE i_file = :tree_id ORDER BY i_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ foreach ($rows as $row) {
+ $rec = Individual::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
+ if ($exportOptions['toANSI'] === 'yes') {
+ $rec = utf8_decode($rec);
+ }
+ $buffer .= self::reformatRecord($rec);
+ if (strlen($buffer) > 65536) {
+ fwrite($gedout, $buffer);
+ $buffer = '';
+ }
+ }
+
+ $rows = Database::prepare(
+ "SELECT f_id AS xref, f_gedcom AS gedcom" .
+ " FROM `##families` WHERE f_file = :tree_id ORDER BY f_id"
+ )->execute(array(
+ 'tree_id' => $tree->getTreeId(),
+ ))->fetchAll();
+
+ foreach ($rows as $row) {
+ $rec = Family::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
+ if ($exportOptions['toANSI'] === 'yes') {
+ $rec = utf8_decode($rec);
+ }
+ $buffer .= self::reformatRecord($rec);
+ if (strlen($buffer) > 65536) {
+ fwrite($gedout, $buffer);
+ $buffer = '';
+ }
+ }
+
+ fwrite($gedout, $buffer);
+ fwrite($gedout, $tmp_gedcom);
+ fwrite($gedout, '0 TRLR' . WT_EOL);
+ }
+}
diff --git a/app/Functions/FunctionsImport.php b/app/Functions/FunctionsImport.php
new file mode 100644
index 0000000000..c9959635fd
--- /dev/null
+++ b/app/Functions/FunctionsImport.php
@@ -0,0 +1,1162 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use Fisharebest\Webtrees\Database;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\GedcomTag;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Log;
+use Fisharebest\Webtrees\Media;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Repository;
+use Fisharebest\Webtrees\Soundex;
+use Fisharebest\Webtrees\Source;
+use Fisharebest\Webtrees\Tree;
+use PDOException;
+
+/**
+ * Class FunctionsImport - common functions
+ */
+class FunctionsImport {
+ /**
+ * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
+ *
+ * @param string $rec
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public static function reformatRecord($rec, Tree $tree) {
+ // Strip out UTF8 formatting characters
+ $rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
+
+ // Strip out mac/msdos line endings
+ $rec = preg_replace("/[\r\n]+/", "\n", $rec);
+
+ // Extract lines from the record; lines consist of: level + optional xref + tag + optional data
+ $num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER);
+
+ // Process the record line-by-line
+ $newrec = '';
+ foreach ($matches as $n => $match) {
+ list(, $level, $xref, $tag, $data) = $match;
+ $tag = strtoupper($tag); // Tags should always be upper case
+ switch ($tag) {
+ // Convert PhpGedView tags to WT
+ case '_PGVU':
+ $tag = '_WT_USER';
+ break;
+ case '_PGV_OBJS':
+ $tag = '_WT_OBJE_SORT';
+ break;
+ // Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
+ case 'ABBREVIATION':
+ $tag = 'ABBR';
+ break;
+ case 'ADDRESS':
+ $tag = 'ADDR';
+ break;
+ case 'ADDRESS1':
+ $tag = 'ADR1';
+ break;
+ case 'ADDRESS2':
+ $tag = 'ADR2';
+ break;
+ case 'ADDRESS3':
+ $tag = 'ADR3';
+ break;
+ case 'ADOPTION':
+ $tag = 'ADOP';
+ break;
+ case 'ADULT_CHRISTENING':
+ $tag = 'CHRA';
+ break;
+ case 'AFN':
+ // AFN values are upper case
+ $data = strtoupper($data);
+ break;
+ case 'AGENCY':
+ $tag = 'AGNC';
+ break;
+ case 'ALIAS':
+ $tag = 'ALIA';
+ break;
+ case 'ANCESTORS':
+ $tag = 'ANCE';
+ break;
+ case 'ANCES_INTEREST':
+ $tag = 'ANCI';
+ break;
+ case 'ANNULMENT':
+ $tag = 'ANUL';
+ break;
+ case 'ASSOCIATES':
+ $tag = 'ASSO';
+ break;
+ case 'AUTHOR':
+ $tag = 'AUTH';
+ break;
+ case 'BAPTISM':
+ $tag = 'BAPM';
+ break;
+ case 'BAPTISM_LDS':
+ $tag = 'BAPL';
+ break;
+ case 'BAR_MITZVAH':
+ $tag = 'BARM';
+ break;
+ case 'BAS_MITZVAH':
+ $tag = 'BASM';
+ break;
+ case 'BIRTH':
+ $tag = 'BIRT';
+ break;
+ case 'BLESSING':
+ $tag = 'BLES';
+ break;
+ case 'BURIAL':
+ $tag = 'BURI';
+ break;
+ case 'CALL_NUMBER':
+ $tag = 'CALN';
+ break;
+ case 'CASTE':
+ $tag = 'CAST';
+ break;
+ case 'CAUSE':
+ $tag = 'CAUS';
+ break;
+ case 'CENSUS':
+ $tag = 'CENS';
+ break;
+ case 'CHANGE':
+ $tag = 'CHAN';
+ break;
+ case 'CHARACTER':
+ $tag = 'CHAR';
+ break;
+ case 'CHILD':
+ $tag = 'CHIL';
+ break;
+ case 'CHILDREN_COUNT':
+ $tag = 'NCHI';
+ break;
+ case 'CHRISTENING':
+ $tag = 'CHR';
+ break;
+ case 'CONCATENATION':
+ $tag = 'CONC';
+ break;
+ case 'CONFIRMATION':
+ $tag = 'CONF';
+ break;
+ case 'CONFIRMATION_LDS':
+ $tag = 'CONL';
+ break;
+ case 'CONTINUED':
+ $tag = 'CONT';
+ break;
+ case 'COPYRIGHT':
+ $tag = 'COPR';
+ break;
+ case 'CORPORATE':
+ $tag = 'CORP';
+ break;
+ case 'COUNTRY':
+ $tag = 'CTRY';
+ break;
+ case 'CREMATION':
+ $tag = 'CREM';
+ break;
+ case 'DATE':
+ // Preserve text from INT dates
+ if (strpos($data, '(') !== false) {
+ list($date, $text) = explode('(', $data, 2);
+ $text = ' (' . $text;
+ } else {
+ $date = $data;
+ $text = '';
+ }
+ // Capitals
+ $date = strtoupper($date);
+ // Temporarily add leading/trailing spaces, to allow efficient matching below
+ $date = " {$date} ";
+ // Ensure space digits and letters
+ $date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
+ $date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
+ // Ensure space before/after calendar escapes
+ $date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
+ // "BET." => "BET"
+ $date = preg_replace('/(\w\w)\./', '$1', $date);
+ // "CIR" => "ABT"
+ $date = str_replace(' CIR ', ' ABT ', $date);
+ $date = str_replace(' APX ', ' ABT ', $date);
+ // B.C. => BC (temporarily, to allow easier handling of ".")
+ $date = str_replace(' B.C. ', ' BC ', $date);
+ // "BET X - Y " => "BET X AND Y"
+ $date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
+ $date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
+ // "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
+ $date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
+ $date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
+ // "@#ESC@ AFT X" => "AFT @#ESC@ X"
+ $date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
+ // Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
+ // (don't change "/" - it is used in NS/OS dates)
+ $date = preg_replace('/[.,:;-]/', ' ', $date);
+ // BC => B.C.
+ $date = str_replace(' BC ', ' B.C. ', $date);
+ // Append the "INT" text
+ $data = $date . $text;
+ break;
+ case 'DEATH':
+ $tag = 'DEAT';
+ break;
+ case '_DEATH_OF_SPOUSE':
+ $tag = '_DETS';
+ break;
+ case '_DEGREE':
+ $tag = '_DEG';
+ break;
+ case 'DESCENDANTS':
+ $tag = 'DESC';
+ break;
+ case 'DESCENDANT_INT':
+ $tag = 'DESI';
+ break;
+ case 'DESTINATION':
+ $tag = 'DEST';
+ break;
+ case 'DIVORCE':
+ $tag = 'DIV';
+ break;
+ case 'DIVORCE_FILED':
+ $tag = 'DIVF';
+ break;
+ case 'EDUCATION':
+ $tag = 'EDUC';
+ break;
+ case 'EMIGRATION':
+ $tag = 'EMIG';
+ break;
+ case 'ENDOWMENT':
+ $tag = 'ENDL';
+ break;
+ case 'ENGAGEMENT':
+ $tag = 'ENGA';
+ break;
+ case 'EVENT':
+ $tag = 'EVEN';
+ break;
+ case 'FACSIMILE':
+ $tag = 'FAX';
+ break;
+ case 'FAMILY':
+ $tag = 'FAM';
+ break;
+ case 'FAMILY_CHILD':
+ $tag = 'FAMC';
+ break;
+ case 'FAMILY_FILE':
+ $tag = 'FAMF';
+ break;
+ case 'FAMILY_SPOUSE':
+ $tag = 'FAMS';
+ break;
+ case 'FIRST_COMMUNION':
+ $tag = 'FCOM';
+ break;
+ case '_FILE':
+ $tag = 'FILE';
+ break;
+ case 'FORMAT':
+ $tag = 'FORM';
+ case 'FORM':
+ // Consistent commas
+ $data = preg_replace('/ *, */', ', ', $data);
+ break;
+ case 'GEDCOM':
+ $tag = 'GEDC';
+ break;
+ case 'GIVEN_NAME':
+ $tag = 'GIVN';
+ break;
+ case 'GRADUATION':
+ $tag = 'GRAD';
+ break;
+ case 'HEADER':
+ $tag = 'HEAD';
+ case 'HEAD':
+ // HEAD records don't have an XREF or DATA
+ if ($level == '0') {
+ $xref = '';
+ $data = '';
+ }
+ break;
+ case 'HUSBAND':
+ $tag = 'HUSB';
+ break;
+ case 'IDENT_NUMBER':
+ $tag = 'IDNO';
+ break;
+ case 'IMMIGRATION':
+ $tag = 'IMMI';
+ break;
+ case 'INDIVIDUAL':
+ $tag = 'INDI';
+ break;
+ case 'LANGUAGE':
+ $tag = 'LANG';
+ break;
+ case 'LATITUDE':
+ $tag = 'LATI';
+ break;
+ case 'LONGITUDE':
+ $tag = 'LONG';
+ break;
+ case 'MARRIAGE':
+ $tag = 'MARR';
+ break;
+ case 'MARRIAGE_BANN':
+ $tag = 'MARB';
+ break;
+ case 'MARRIAGE_COUNT':
+ $tag = 'NMR';
+ break;
+ case 'MARRIAGE_CONTRACT':
+ $tag = 'MARC';
+ break;
+ case 'MARRIAGE_LICENSE':
+ $tag = 'MARL';
+ break;
+ case 'MARRIAGE_SETTLEMENT':
+ $tag = 'MARS';
+ break;
+ case 'MEDIA':
+ $tag = 'MEDI';
+ break;
+ case '_MEDICAL':
+ $tag = '_MDCL';
+ break;
+ case '_MILITARY_SERVICE':
+ $tag = '_MILT';
+ break;
+ case 'NAME':
+ // Tidy up whitespace
+ $data = preg_replace('/ +/', ' ', trim($data));
+ break;
+ case 'NAME_PREFIX':
+ $tag = 'NPFX';
+ break;
+ case 'NAME_SUFFIX':
+ $tag = 'NSFX';
+ break;
+ case 'NATIONALITY':
+ $tag = 'NATI';
+ break;
+ case 'NATURALIZATION':
+ $tag = 'NATU';
+ break;
+ case 'NICKNAME':
+ $tag = 'NICK';
+ break;
+ case 'OBJECT':
+ $tag = 'OBJE';
+ break;
+ case 'OCCUPATION':
+ $tag = 'OCCU';
+ break;
+ case 'ORDINANCE':
+ $tag = 'ORDI';
+ break;
+ case 'ORDINATION':
+ $tag = 'ORDN';
+ break;
+ case 'PEDIGREE':
+ $tag = 'PEDI';
+ case 'PEDI':
+ // PEDI values are lower case
+ $data = strtolower($data);
+ break;
+ case 'PHONE':
+ $tag = 'PHON';
+ break;
+ case 'PHONETIC':
+ $tag = 'FONE';
+ break;
+ case 'PHY_DESCRIPTION':
+ $tag = 'DSCR';
+ break;
+ case 'PLACE':
+ $tag = 'PLAC';
+ case 'PLAC':
+ // Consistent commas
+ $data = preg_replace('/ *(،|,) */', ', ', $data);
+ // The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
+ if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
+ $data =
+ $match[1] . "\n" .
+ ($level + 1) . " MAP\n" .
+ ($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
+ ($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
+ }
+ break;
+ case 'POSTAL_CODE':
+ $tag = 'POST';
+ break;
+ case 'PROBATE':
+ $tag = 'PROB';
+ break;
+ case 'PROPERTY':
+ $tag = 'PROP';
+ break;
+ case 'PUBLICATION':
+ $tag = 'PUBL';
+ break;
+ case 'QUALITY_OF_DATA':
+ $tag = 'QUAL';
+ break;
+ case 'REC_FILE_NUMBER':
+ $tag = 'RFN';
+ break;
+ case 'REC_ID_NUMBER':
+ $tag = 'RIN';
+ break;
+ case 'REFERENCE':
+ $tag = 'REFN';
+ break;
+ case 'RELATIONSHIP':
+ $tag = 'RELA';
+ break;
+ case 'RELIGION':
+ $tag = 'RELI';
+ break;
+ case 'REPOSITORY':
+ $tag = 'REPO';
+ break;
+ case 'RESIDENCE':
+ $tag = 'RESI';
+ break;
+ case 'RESTRICTION':
+ $tag = 'RESN';
+ case 'RESN':
+ // RESN values are lower case (confidential, privacy, locked, none)
+ $data = strtolower($data);
+ if ($data == 'invisible') {
+ $data = 'confidential'; // From old versions of Legacy.
+ }
+ break;
+ case 'RETIREMENT':
+ $tag = 'RETI';
+ break;
+ case 'ROMANIZED':
+ $tag = 'ROMN';
+ break;
+ case 'SEALING_CHILD':
+ $tag = 'SLGC';
+ break;
+ case 'SEALING_SPOUSE':
+ $tag = 'SLGS';
+ break;
+ case 'SOC_SEC_NUMBER':
+ $tag = 'SSN';
+ break;
+ case 'SEX':
+ $data = strtoupper($data);
+ break;
+ case 'SOURCE':
+ $tag = 'SOUR';
+ break;
+ case 'STATE':
+ $tag = 'STAE';
+ break;
+ case 'STATUS':
+ $tag = 'STAT';
+ case 'STAT':
+ if ($data == 'CANCELLED') {
+ // PhpGedView mis-spells this tag - correct it.
+ $data = 'CANCELED';
+ }
+ break;
+ case 'SUBMISSION':
+ $tag = 'SUBN';
+ break;
+ case 'SUBMITTER':
+ $tag = 'SUBM';
+ break;
+ case 'SURNAME':
+ $tag = 'SURN';
+ break;
+ case 'SURN_PREFIX':
+ $tag = 'SPFX';
+ break;
+ case 'TEMPLE':
+ $tag = 'TEMP';
+ case 'TEMP':
+ // Temple codes are upper case
+ $data = strtoupper($data);
+ break;
+ case 'TITLE':
+ $tag = 'TITL';
+ break;
+ case 'TRAILER':
+ $tag = 'TRLR';
+ case 'TRLR':
+ // TRLR records don't have an XREF or DATA
+ if ($level == '0') {
+ $xref = '';
+ $data = '';
+ }
+ break;
+ case 'VERSION':
+ $tag = 'VERS';
+ break;
+ case 'WEB':
+ $tag = 'WWW';
+ break;
+ }
+ // Suppress "Y", for facts/events with a DATE or PLAC
+ if ($data == 'y') {
+ $data = 'Y';
+ }
+ if ($level == '1' && $data == 'Y') {
+ for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] != '1'; ++$i) {
+ if ($matches[$i][3] == 'DATE' || $matches[$i][3] == 'PLAC') {
+ $data = '';
+ break;
+ }
+ }
+ }
+ // Reassemble components back into a single line
+ switch ($tag) {
+ default:
+ // Remove tabs and multiple/leading/trailing spaces
+ if (strpos($data, "\t") !== false) {
+ $data = str_replace("\t", ' ', $data);
+ }
+ if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
+ $data = trim($data);
+ }
+ while (strpos($data, ' ')) {
+ $data = str_replace(' ', ' ', $data);
+ }
+ $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
+ break;
+ case 'NOTE':
+ case 'TEXT':
+ case 'DATA':
+ case 'CONT':
+ $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
+ break;
+ case 'FILE':
+ // Strip off the user-defined path prefix
+ $GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
+ if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
+ $data = substr($data, strlen($GEDCOM_MEDIA_PATH));
+ }
+ // convert backslashes in filenames to forward slashes
+ $data = preg_replace("/\\\/", "/", $data);
+
+ $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
+ break;
+ case 'CONC':
+ // Merge CONC lines, to simplify access later on.
+ $newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
+ break;
+ }
+ }
+
+ return $newrec;
+ }
+
+ /**
+ * import record into database
+ *
+ * this function will parse the given gedcom record and add it to the database
+ *
+ * @param string $gedrec the raw gedcom record to parse
+ * @param Tree $tree import the record into this tree
+ * @param bool $update whether or not this is an updated record that has been accepted
+ */
+ public static function importRecord($gedrec, Tree $tree, $update) {
+ $tree_id = $tree->getTreeId();
+
+ // Escaped @ signs (only if importing from file)
+ if (!$update) {
+ $gedrec = str_replace('@@', '@', $gedrec);
+ }
+
+ // Standardise gedcom format
+ $gedrec = self::reformatRecord($gedrec, $tree);
+
+ // import different types of records
+ if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
+ list(, $xref, $type) = $match;
+ // check for a _UID, if the record doesn't have one, add one
+ if ($tree->getPreference('GENERATE_UIDS') && !strpos($gedrec, "\n1 _UID ")) {
+ $gedrec .= "\n1 _UID " . GedcomTag::createUid();
+ }
+ } elseif (preg_match('/0 (HEAD|TRLR)/', $gedrec, $match)) {
+ $type = $match[1];
+ $xref = $type; // For HEAD/TRLR, use type as pseudo XREF.
+ } else {
+ echo I18N::translate('Invalid GEDCOM format'), '<br><pre>', $gedrec, '</pre>';
+
+ return;
+ }
+
+ // If the user has downloaded their GEDCOM data (containing media objects) and edited it
+ // using an application which does not support (and deletes) media objects, then add them
+ // back in.
+ if ($tree->getPreference('keep_media') && $xref) {
+ $old_linked_media =
+ Database::prepare("SELECT l_to FROM `##link` WHERE l_from=? AND l_file=? AND l_type='OBJE'")
+ ->execute(array($xref, $tree_id))
+ ->fetchOneColumn();
+ foreach ($old_linked_media as $media_id) {
+ $gedrec .= "\n1 OBJE @" . $media_id . "@";
+ }
+ }
+
+ switch ($type) {
+ case 'INDI':
+ // Convert inline media into media objects
+ $gedrec = self::convertInlineMedia($tree, $gedrec);
+
+ $record = new Individual($xref, $gedrec, null, $tree);
+ if ($tree->getPreference('USE_RIN') && preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
+ $rin = $match[1];
+ } else {
+ $rin = $xref;
+ }
+ Database::prepare(
+ "INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
+ )->execute(array(
+ $xref, $tree_id, $rin, $record->getSex(), $gedrec,
+ ));
+ // Update the cross-reference/index tables.
+ self::updatePlaces($xref, $tree_id, $gedrec);
+ self::updateDates($xref, $tree_id, $gedrec);
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ case 'FAM':
+ // Convert inline media into media objects
+ $gedrec = self::convertInlineMedia($tree, $gedrec);
+
+ if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
+ $husb = $match[1];
+ } else {
+ $husb = '';
+ }
+ if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
+ $wife = $match[1];
+ } else {
+ $wife = '';
+ }
+ $nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
+ if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
+ $nchi = max($nchi, $match[1]);
+ }
+ Database::prepare(
+ "INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
+ )->execute(array(
+ $xref, $tree_id, $husb, $wife, $gedrec, $nchi,
+ ));
+ // Update the cross-reference/index tables.
+ self::updatePlaces($xref, $tree_id, $gedrec);
+ self::updateDates($xref, $tree_id, $gedrec);
+ self::updateLinks($xref, $tree_id, $gedrec);
+ break;
+ case 'SOUR':
+ // Convert inline media into media objects
+ $gedrec = self::convertInlineMedia($tree, $gedrec);
+
+ $record = new Source($xref, $gedrec, null, $tree);
+ if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
+ $name = $match[1];
+ } elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
+ $name = $match[1];
+ } else {
+ $name = $xref;
+ }
+ Database::prepare(
+ "INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
+ )->execute(array(
+ $xref, $tree_id, $name, $gedrec,
+ ));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ case 'REPO':
+ // Convert inline media into media objects
+ $gedrec = self::convertInlineMedia($tree, $gedrec);
+
+ $record = new Repository($xref, $gedrec, null, $tree);
+ Database::prepare(
+ "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
+ )->execute(array(
+ $xref, $tree_id, $gedrec,
+ ));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ case 'NOTE':
+ $record = new Note($xref, $gedrec, null, $tree);
+ Database::prepare(
+ "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
+ )->execute(array(
+ $xref, $tree_id, $gedrec,
+ ));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ case 'OBJE':
+ $record = new Media($xref, $gedrec, null, $tree);
+ Database::prepare(
+ "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
+ )->execute(array(
+ $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
+ ));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ case 'HEAD':
+ // Force HEAD records to have a creation date.
+ if (!strpos($gedrec, "\n1 DATE ")) {
+ $gedrec .= "\n1 DATE " . date('j M Y');
+ }
+ // No break;
+ case 'TRLR':
+ case 'SUBM':
+ case 'SUBN':
+ Database::prepare(
+ "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
+ )->execute(array($xref, $tree_id, $type, $gedrec));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ break;
+ default:
+ $record = new GedcomRecord($xref, $gedrec, null, $tree);
+ Database::prepare(
+ "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
+ )->execute(array($xref, $tree_id, $type, $gedrec));
+ // Update the cross-reference/index tables.
+ self::updateLinks($xref, $tree_id, $gedrec);
+ self::updateNames($xref, $tree_id, $record);
+ break;
+ }
+ }
+
+ /**
+ * extract all places from the given record and insert them into the places table
+ *
+ * @param string $gid
+ * @param int $ged_id
+ * @param string $gedrec
+ */
+ public static function updatePlaces($gid, $ged_id, $gedrec) {
+ global $placecache;
+
+ if (!isset($placecache)) {
+ $placecache = array();
+ }
+ $personplace = array();
+ // import all place locations, but not control info such as
+ // 0 HEAD/1 PLAC or 0 _EVDEF/1 PLAC
+ $pt = preg_match_all("/^[2-9] PLAC (.+)/m", $gedrec, $match, PREG_SET_ORDER);
+ for ($i = 0; $i < $pt; $i++) {
+ $place = trim($match[$i][1]);
+ $lowplace = I18N::strtolower($place);
+ //-- if we have already visited this place for this person then we don't need to again
+ if (isset($personplace[$lowplace])) {
+ continue;
+ }
+ $personplace[$lowplace] = 1;
+ $places = explode(',', $place);
+ //-- reverse the array to start at the highest level
+ $secalp = array_reverse($places);
+ $parent_id = 0;
+ $search = true;
+
+ foreach ($secalp as $place) {
+ $place = trim($place);
+ $key = strtolower(mb_substr($place, 0, 150) . "_" . $parent_id);
+ //-- if this place has already been added then we don't need to add it again
+ if (isset($placecache[$key])) {
+ $parent_id = $placecache[$key];
+ if (!isset($personplace[$key])) {
+ $personplace[$key] = 1;
+ // Use INSERT IGNORE as a (temporary) fix for https://bugs.launchpad.net/webtrees/+bug/582226
+ // It ignores places that utf8_unicode_ci consider to be the same (i.e. accents).
+ // For example Québec and Quebec
+ // We need a better solution that attaches multiple names to single places
+ Database::prepare(
+ "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
+ )->execute(array(
+ $parent_id, $gid, $ged_id,
+ ));
+ }
+ continue;
+ }
+
+ //-- only search the database while we are finding places in it
+ if ($search) {
+ //-- check if this place and level has already been added
+ $tmp = Database::prepare(
+ "SELECT p_id FROM `##places` WHERE p_file = ? AND p_parent_id = ? AND p_place = LEFT(?, 150)"
+ )->execute(array(
+ $ged_id, $parent_id, $place,
+ ))->fetchOne();
+ if ($tmp) {
+ $p_id = $tmp;
+ } else {
+ $search = false;
+ }
+ }
+
+ //-- if we are not searching then we have to insert the place into the db
+ if (!$search) {
+ $std_soundex = Soundex::russell($place);
+ $dm_soundex = Soundex::daitchMokotoff($place);
+ Database::prepare(
+ "INSERT INTO `##places` (p_place, p_parent_id, p_file, p_std_soundex, p_dm_soundex) VALUES (LEFT(?, 150), ?, ?, ?, ?)"
+ )->execute(array(
+ $place, $parent_id, $ged_id, $std_soundex, $dm_soundex,
+ ));
+ $p_id = Database::getInstance()->lastInsertId();
+ }
+
+ Database::prepare(
+ "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
+ )->execute(array(
+ $p_id, $gid, $ged_id,
+ ));
+ //-- increment the level and assign the parent id for the next place level
+ $parent_id = $p_id;
+ $placecache[$key] = $p_id;
+ $personplace[$key] = 1;
+ }
+ }
+ }
+
+ /**
+ * Extract all the dates from the given record and insert them into the database.
+ *
+ * @param string $xref
+ * @param int $ged_id
+ * @param string $gedrec
+ */
+ public static function updateDates($xref, $ged_id, $gedrec) {
+ if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $fact = $match[1];
+ if (($fact == 'FACT' || $fact == 'EVEN') && preg_match("/\n2 TYPE ([A-Z]{3,5})/", $match[0], $tmatch)) {
+ $fact = $tmatch[1];
+ }
+ $date = new Date($match[2]);
+ Database::prepare(
+ "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
+ )->execute(array(
+ $date->minimumDate()->d,
+ $date->minimumDate()->format('%O'),
+ $date->minimumDate()->m,
+ $date->minimumDate()->y,
+ $date->minimumDate()->minJD,
+ $date->minimumDate()->maxJD,
+ $fact,
+ $xref,
+ $ged_id,
+ $date->minimumDate()->format('%@'),
+ ));
+ if ($date->minimumDate() !== $date->maximumDate()) {
+ Database::prepare(
+ "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
+ )->execute(array(
+ $date->maximumDate()->d,
+ $date->maximumDate()->format('%O'),
+ $date->maximumDate()->m,
+ $date->maximumDate()->y,
+ $date->maximumDate()->minJD,
+ $date->maximumDate()->maxJD,
+ $fact,
+ $xref,
+ $ged_id,
+ $date->maximumDate()->format('%@'),
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Extract all the links from the given record and insert them into the database
+ *
+ * @param string $xref
+ * @param int $ged_id
+ * @param string $gedrec
+ */
+ public static function updateLinks($xref, $ged_id, $gedrec) {
+ if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
+ $data = array();
+ foreach ($matches as $match) {
+ // Include each link once only.
+ if (!in_array($match[1] . $match[2], $data)) {
+ $data[] = $match[1] . $match[2];
+ // Ignore any errors, which may be caused by "duplicates" that differ on case/collation, e.g. "S1" and "s1"
+ try {
+ Database::prepare(
+ "INSERT INTO `##link` (l_from, l_to, l_type, l_file) VALUES (?, ?, ?, ?)"
+ )->execute(array(
+ $xref, $match[2], $match[1], $ged_id,
+ ));
+ } catch (PDOException $e) {
+ // We could display a warning here....
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Extract all the names from the given record and insert them into the database.
+ *
+ * @param string $xref
+ * @param int $ged_id
+ * @param GedcomRecord $record
+ */
+ public static function updateNames($xref, $ged_id, GedcomRecord $record) {
+ foreach ($record->getAllNames() as $n => $name) {
+ if ($record instanceof Individual) {
+ if ($name['givn'] === '@P.N.') {
+ $soundex_givn_std = null;
+ $soundex_givn_dm = null;
+ } else {
+ $soundex_givn_std = Soundex::russell($name['givn']);
+ $soundex_givn_dm = Soundex::daitchMokotoff($name['givn']);
+ }
+ if ($name['surn'] === '@N.N.') {
+ $soundex_surn_std = null;
+ $soundex_surn_dm = null;
+ } else {
+ $soundex_surn_std = Soundex::russell($name['surname']);
+ $soundex_surn_dm = Soundex::daitchMokotoff($name['surname']);
+ }
+ Database::prepare(
+ "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full,n_surname,n_surn,n_givn,n_soundex_givn_std,n_soundex_surn_std,n_soundex_givn_dm,n_soundex_surn_dm) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), ?, ?, ?, ?, ?)"
+ )->execute(array(
+ $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'], $name['surname'], $name['surn'], $name['givn'], $soundex_givn_std, $soundex_surn_std, $soundex_givn_dm, $soundex_surn_dm,
+ ));
+ } else {
+ Database::prepare(
+ "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255))"
+ )->execute(array(
+ $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'],
+ ));
+ }
+ }
+ }
+
+ /**
+ * Extract inline media data, and convert to media objects.
+ *
+ * @param Tree $tree
+ * @param string $gedrec
+ *
+ * @return string
+ */
+ public static function convertInlineMedia(Tree $tree, $gedrec) {
+ while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
+ $gedrec = str_replace($match[0], self::createMediaObject(1, $match[0], $tree), $gedrec);
+ }
+ while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedrec, $match)) {
+ $gedrec = str_replace($match[0], self::createMediaObject(2, $match[0], $tree), $gedrec);
+ }
+ while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedrec, $match)) {
+ $gedrec = str_replace($match[0], self::createMediaObject(3, $match[0], $tree), $gedrec);
+ }
+
+ return $gedrec;
+ }
+
+ /**
+ * Create a new media object, from inline media data.
+ *
+ * @param int $level
+ * @param string $gedrec
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public static function createMediaObject($level, $gedrec, Tree $tree) {
+ if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
+ $file = $file_match[1];
+ } else {
+ $file = '';
+ }
+
+ if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
+ $titl = $file_match[1];
+ } else {
+ $titl = $file;
+ }
+
+ // Have we already created a media object with the same title/filename?
+ $xref = Database::prepare(
+ "SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?"
+ )->execute(array(
+ $file, $titl, $tree->getTreeId(),
+ ))->fetchOne();
+
+ if (!$xref) {
+ $xref = $tree->getNewXref('OBJE');
+ // renumber the lines
+ $gedrec = preg_replace_callback('/\n(\d+)/', function ($m) use ($level) {
+ return "\n" . ($m[1] - $level);
+ }, $gedrec);
+ // convert to an object
+ $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
+ // Fix Legacy GEDCOMS
+ $gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);
+ // Create new record
+ $record = new Media($xref, $gedrec, null, $tree);
+ Database::prepare(
+ "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
+ )->execute(array(
+ $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec,
+ ));
+ }
+
+ return "\n" . $level . ' OBJE @' . $xref . '@';
+ }
+
+ /**
+ * Accept all pending changes for a specified record.
+ *
+ * @param string $xref
+ * @param int $ged_id
+ */
+ public static function acceptAllChanges($xref, $ged_id) {
+ $changes = Database::prepare(
+ "SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
+ " FROM `##change` c" .
+ " JOIN `##gedcom` g USING (gedcom_id)" .
+ " WHERE c.status='pending' AND xref=? AND gedcom_id=?" .
+ " ORDER BY change_id"
+ )->execute(array($xref, $ged_id))->fetchAll();
+ foreach ($changes as $change) {
+ if (empty($change->new_gedcom)) {
+ // delete
+ self::updateRecord($change->old_gedcom, $ged_id, true);
+ } else {
+ // add/update
+ self::updateRecord($change->new_gedcom, $ged_id, false);
+ }
+ Database::prepare(
+ "UPDATE `##change`" .
+ " SET status='accepted'" .
+ " WHERE status='pending' AND xref=? AND gedcom_id=?"
+ )->execute(array($xref, $ged_id));
+ Log::addEditLog("Accepted change {$change->change_id} for {$xref} / {$change->gedcom_name} into database");
+ }
+ }
+
+ /**
+ * Accept all pending changes for a specified record.
+ *
+ * @param GedcomRecord $record
+ */
+ public static function rejectAllChanges(GedcomRecord $record) {
+ Database::prepare(
+ "UPDATE `##change`" .
+ " SET status = 'rejected'" .
+ " WHERE status = 'pending' AND xref = :xref AND gedcom_id = :tree_id"
+ )->execute(array(
+ 'xref' => $record->getXref(),
+ 'tree_id' => $record->getTree()->getTreeId(),
+ ));
+ }
+
+ /**
+ * update a record in the database
+ *
+ * @param string $gedrec
+ * @param int $ged_id
+ * @param bool $delete
+ */
+ public static function updateRecord($gedrec, $ged_id, $delete) {
+ if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
+ list(, $gid, $type) = $match;
+ } else {
+ echo "ERROR: Invalid gedcom record.";
+
+ return;
+ }
+
+ // TODO deleting unlinked places can be done more efficiently in a single query
+ $placeids =
+ Database::prepare("SELECT pl_p_id FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")
+ ->execute(array($gid, $ged_id))
+ ->fetchOneColumn();
+
+ Database::prepare("DELETE FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")->execute(array($gid, $ged_id));
+ Database::prepare("DELETE FROM `##dates` WHERE d_gid =? AND d_file =?")->execute(array($gid, $ged_id));
+
+ //-- delete any unlinked places
+ foreach ($placeids as $p_id) {
+ $num =
+ Database::prepare("SELECT count(pl_p_id) FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?")
+ ->execute(array($p_id, $ged_id))
+ ->fetchOne();
+ if ($num == 0) {
+ Database::prepare("DELETE FROM `##places` WHERE p_id=? AND p_file=?")->execute(array($p_id, $ged_id));
+ }
+ }
+
+ Database::prepare("DELETE FROM `##name` WHERE n_id=? AND n_file=?")->execute(array($gid, $ged_id));
+ Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
+
+ switch ($type) {
+ case 'INDI':
+ Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
+ break;
+ case 'FAM':
+ Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
+ break;
+ case 'SOUR':
+ Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
+ break;
+ case 'OBJE':
+ Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
+ break;
+ default:
+ Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
+ break;
+ }
+
+ if (!$delete) {
+ self::importRecord($gedrec, Tree::FindById($ged_id), true);
+ }
+ }
+}
diff --git a/app/Functions/FunctionsMedia.php b/app/Functions/FunctionsMedia.php
new file mode 100644
index 0000000000..0b06852a61
--- /dev/null
+++ b/app/Functions/FunctionsMedia.php
@@ -0,0 +1,97 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Log;
+
+/**
+ * Class FunctionsMedia - common functions
+ */
+class FunctionsMedia {
+ /**
+ * Convert raw values from php.ini file into bytes
+ *
+ * @param string $val
+ *
+ * @return int
+ */
+ public static function sizeToBytes($val) {
+ if (!$val) {
+ // no value was passed in, assume no limit and return -1
+ $val = -1;
+ }
+ switch (substr($val, -1)) {
+ case 'g':
+ case 'G':
+ return (int) $val * 1024 * 1024 * 1024;
+ case 'm':
+ case 'M':
+ return (int) $val * 1024 * 1024;
+ case 'k':
+ case 'K':
+ return (int) $val * 1024;
+ default:
+ return (int) $val;
+ }
+ }
+
+ /**
+ * Determine whether there is enough memory to load a particular image.
+ *
+ * @param string $serverFilename
+ *
+ * @return bool
+ */
+ public static function hasMemoryForImage($serverFilename) {
+ // find out how much total memory this script can access
+ $memoryAvailable = self::sizeToBytes(ini_get('memory_limit'));
+ // if memory is unlimited, it will return -1 and we don’t need to worry about it
+ if ($memoryAvailable == -1) {
+ return true;
+ }
+
+ // find out how much memory we are already using
+ $memoryUsed = memory_get_usage();
+
+ try {
+ $imgsize = getimagesize($serverFilename);
+ } catch (\ErrorException $ex) {
+ // Not an image, or not a valid image?
+ $imgsize = false;
+ }
+
+ // find out how much memory this image needs for processing, probably only works for jpegs
+ // from comments on http://www.php.net/imagecreatefromjpeg
+ if ($imgsize && isset($imgsize['bits']) && (isset($imgsize['channels']))) {
+ $memoryNeeded = round(($imgsize[0] * $imgsize[1] * $imgsize['bits'] * $imgsize['channels'] / 8 + Pow(2, 16)) * 1.65);
+ $memorySpare = $memoryAvailable - $memoryUsed - $memoryNeeded;
+ if ($memorySpare > 0) {
+ // we have enough memory to load this file
+ return true;
+ } else {
+ // not enough memory to load this file
+ $image_info = sprintf('%.2fKB, %d × %d %d bits %d channels', filesize($serverFilename) / 1024, $imgsize[0], $imgsize[1], $imgsize['bits'], $imgsize['channels']);
+ Log::addMediaLog('Cannot create thumbnail ' . $serverFilename . ' (' . $image_info . ') memory avail: ' . $memoryAvailable . ' used: ' . $memoryUsed . ' needed: ' . $memoryNeeded . ' spare: ' . $memorySpare);
+
+ return false;
+ }
+ } else {
+ // assume there is enough memory
+ // TODO find out how to check memory needs for gif and png
+ return true;
+ }
+ }
+}
diff --git a/app/Functions/FunctionsPrint.php b/app/Functions/FunctionsPrint.php
new file mode 100644
index 0000000000..b926ddabdc
--- /dev/null
+++ b/app/Functions/FunctionsPrint.php
@@ -0,0 +1,873 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Controller\SearchController;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\GedcomTag;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Module\CensusAssistantModule;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Place;
+use Fisharebest\Webtrees\Session;
+use Fisharebest\Webtrees\Theme;
+use Fisharebest\Webtrees\Tree;
+use Rhumsaa\Uuid\Uuid;
+
+/**
+ * Class FunctionsPrint - common functions
+ */
+class FunctionsPrint {
+ /**
+ * print the information for an individual chart box
+ *
+ * find and print a given individuals information for a pedigree chart
+ *
+ * @param Individual $person The person to print
+ * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
+ */
+ public static function printPedigreePerson(Individual $person = null, $show_full = 1) {
+
+ switch ($show_full) {
+ case 0:
+ if ($person) {
+ echo Theme::theme()->individualBoxSmall($person);
+ } else {
+ echo Theme::theme()->individualBoxSmallEmpty();
+ }
+ break;
+ case 1:
+ if ($person) {
+ echo Theme::theme()->individualBox($person);
+ } else {
+ echo Theme::theme()->individualBoxEmpty();
+ }
+ break;
+ }
+ }
+
+ /**
+ * print a note record
+ *
+ * @param string $text
+ * @param int $nlevel the level of the note record
+ * @param string $nrec the note record to print
+ * @param bool $textOnly Don't print the "Note: " introduction
+ *
+ * @return string
+ */
+ public static function printNoteRecord($text, $nlevel, $nrec, $textOnly = false) {
+ global $WT_TREE;
+
+ $text .= Functions::getCont($nlevel, $nrec);
+
+ // Check if shared note (we have already checked that it exists)
+ if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
+ $note = Note::getInstance($match[1], $WT_TREE);
+ $label = 'SHARED_NOTE';
+ // If Census assistant installed, allow it to format the note
+ if (Module::getModuleByName('GEDFact_assistant')) {
+ $html = CensusAssistantModule::formatCensusNote($note);
+ } else {
+ $html = Filter::formatText($note->getNote(), $WT_TREE);
+ }
+ } else {
+ $note = null;
+ $label = 'NOTE';
+ $html = Filter::formatText($text, $WT_TREE);
+ }
+
+ if ($textOnly) {
+ return strip_tags($text);
+ }
+
+ if (strpos($text, "\n") === false) {
+ // A one-line note? strip the block-level tags, so it displays inline
+ return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
+ } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) {
+ // A multi-line note, and we're expanding notes by default
+ return GedcomTag::getLabelValue($label, $html);
+ } else {
+ // A multi-line note, with an expand/collapse option
+ $element_id = Uuid::uuid4();
+ // NOTE: class "note-details" is (currently) used only by some third-party themes
+ if ($note) {
+ $first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>';
+ } else {
+ list($text) = explode("\n", strip_tags($html));
+ $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text;
+ }
+
+ return
+ '<div class="fact_NOTE"><span class="label">' .
+ '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
+ '</div>' .
+ '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
+ }
+ }
+
+ /**
+ * Print all of the notes in this fact record
+ *
+ * @param string $factrec The factrecord to print the notes from
+ * @param int $level The level of the factrecord
+ * @param bool $textOnly Don't print the "Note: " introduction
+ *
+ * @return string HTML
+ */
+ public static function printFactNotes($factrec, $level, $textOnly = false) {
+ global $WT_TREE;
+
+ $data = '';
+ $previous_spos = 0;
+ $nlevel = $level + 1;
+ $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
+ for ($j = 0; $j < $ct; $j++) {
+ $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
+ $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
+ if (!$spos2) {
+ $spos2 = strlen($factrec);
+ }
+ $previous_spos = $spos2;
+ $nrec = substr($factrec, $spos1, $spos2 - $spos1);
+ if (!isset($match[$j][1])) {
+ $match[$j][1] = '';
+ }
+ if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) {
+ $data .= self::printNoteRecord($match[$j][1], $nlevel, $nrec, $textOnly);
+ } else {
+ $note = Note::getInstance($nmatch[1], $WT_TREE);
+ if ($note) {
+ if ($note->canShow()) {
+ $noterec = $note->getGedcom();
+ $nt = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
+ $data .= self::printNoteRecord(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly);
+ if (!$textOnly) {
+ if (strpos($noterec, '1 SOUR') !== false) {
+ $data .= FunctionsPrintFacts::printFactSources($noterec, 1);
+ }
+ }
+ }
+ } else {
+ $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
+ }
+ }
+ if (!$textOnly) {
+ if (strpos($factrec, "$nlevel SOUR") !== false) {
+ $data .= "<div class=\"indent\">";
+ $data .= FunctionsPrintFacts::printFactSources($nrec, $nlevel);
+ $data .= "</div>";
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Print a link for a popup help window.
+ *
+ * @param string $help_topic
+ * @param string $module
+ *
+ * @return string
+ */
+ public static function helpLink($help_topic, $module = '') {
+ return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
+ }
+
+ /**
+ * Print an external help link to the wiki site, in a new window
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ public static function wikiHelpLink($topic) {
+ return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '" target="_blank">&nbsp;</a>';
+ }
+
+ /**
+ * When a user has searched for text, highlight any matches in
+ * the displayed string.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ public static function highlightSearchHits($string) {
+ global $controller;
+
+ if ($controller instanceof SearchController && $controller->query) {
+ // TODO: when a search contains multiple words, we search independently.
+ // e.g. searching for "FOO BAR" will find records containing both FOO and BAR.
+ // However, we only highlight the original search string, not the search terms.
+ // The controller needs to provide its "query_terms" array.
+ $regex = array();
+ foreach (array($controller->query) as $search_term) {
+ $regex[] = preg_quote($search_term, '/');
+ }
+ // Match these strings, provided they do not occur inside HTML tags
+ $regex = '(' . implode('|', $regex) . ')(?![^<]*>)';
+
+ return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string);
+ } else {
+ return $string;
+ }
+ }
+
+ /**
+ * Print the associations from the associated individuals in $event to the individuals in $record
+ *
+ * @param Fact $event
+ *
+ * @return string
+ */
+ public static function formatAssociateRelationship(Fact $event) {
+ $parent = $event->getParent();
+ // To whom is this record an assocate?
+ if ($parent instanceof Individual) {
+ // On an individual page, we just show links to the person
+ $associates = array($parent);
+ } elseif ($parent instanceof Family) {
+ // On a family page, we show links to both spouses
+ $associates = $parent->getSpouses();
+ } else {
+ // On other pages, it does not make sense to show associates
+ return '';
+ }
+
+ preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
+ preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
+
+ $html = '';
+ // For each ASSO record
+ foreach (array_merge($amatches1, $amatches2) as $amatch) {
+ $person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
+ if ($person && $person->canShowName()) {
+ // Is there a "RELA" tag
+ if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
+ // Use the supplied relationship as a label
+ $label = GedcomCodeRela::getValue($rmatch[1], $person);
+ } else {
+ // Use a default label
+ $label = GedcomTag::getLabel('ASSO', $person);
+ }
+
+ $values = array('<a href="' . $person->getHtmlUrl() . '">' . $person->getFullName() . '</a>');
+ if (!Auth::isSearchEngine()) {
+ foreach ($associates as $associate) {
+ $relationship_name = Functions::getAssociateRelationshipName($associate, $person);
+ if (!$relationship_name) {
+ $relationship_name = GedcomTag::getLabel('RELA');
+ }
+
+ if ($parent instanceof Family) {
+ // For family ASSO records (e.g. MARR), identify the spouse with a sex icon
+ $relationship_name .= $associate->getSexImage();
+ }
+
+ $values[] = '<a href="relationship.php?pid1=' . $associate->getXref() . '&amp;pid2=' . $person->getXref() . '&amp;ged=' . $associate->getTree()->getNameUrl() . '">' . $relationship_name . '</a>';
+ }
+ }
+ $value = implode(' — ', $values);
+
+ // Use same markup as GedcomTag::getLabelValue()
+ $asso = I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $label, $value);
+ } elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
+ $asso = GedcomTag::getLabelValue('ASSO', '<span class="error">' . $amatch[1] . '</span>');
+ } else {
+ $asso = '';
+ }
+ $html .= '<div class="fact_ASSO">' . $asso . '</div>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Format age of parents in HTML
+ *
+ * @param Individual $person child
+ * @param Date $birth_date
+ *
+ * @return string HTML
+ */
+ public static function formatParentsAges(Individual $person, Date $birth_date) {
+ $html = '';
+ $families = $person->getChildFamilies();
+ // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
+ if ($birth_date->isOK() && count($families) == 1) {
+ $family = current($families);
+ foreach ($family->getSpouses() as $parent) {
+ if ($parent->getBirthDate()->isOK()) {
+ $sex = $parent->getSexImage();
+ $age = Date::getAge($parent->getBirthDate(), $birth_date, 2);
+ $deatdate = $parent->getDeathDate();
+ switch ($parent->getSex()) {
+ case 'F':
+ // Highlight mothers who die in childbirth or shortly afterwards
+ if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
+ $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
+ } else {
+ $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
+ }
+ break;
+ case 'M':
+ // Highlight fathers who die before the birth
+ if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
+ $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
+ } else {
+ $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
+ }
+ break;
+ default:
+ $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
+ break;
+ }
+ }
+ }
+ if ($html) {
+ $html = '<span class="age">' . $html . '</span>';
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * 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 && !Auth::isSearchEngine());
+ // 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;
+ }
+
+ /**
+ * print fact PLACe TEMPle STATus
+ *
+ * @param Fact $event gedcom fact record
+ * @param bool $anchor to print a link to placelist
+ * @param bool $sub_records to print place subrecords
+ * @param bool $lds to print LDS TEMPle and STATus
+ *
+ * @return string HTML
+ */
+ public static function formatFactPlace(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
+ if ($anchor) {
+ // Show the full place name, for facts/events tab
+ $html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
+ } else {
+ // Abbreviate the place name, for chart boxes
+ return ' - ' . $event->getPlace()->getShortName();
+ }
+
+ if ($sub_records) {
+ $placerec = Functions::getSubRecord(2, '2 PLAC', $event->getGedcom());
+ if (!empty($placerec)) {
+ if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
+ foreach ($matches[1] as $match) {
+ $wt_place = new Place($match, $event->getParent()->getTree());
+ $html .= ' - ' . $wt_place->getFullName();
+ }
+ }
+ $map_lati = "";
+ $cts = preg_match('/\d LATI (.*)/', $placerec, $match);
+ if ($cts > 0) {
+ $map_lati = $match[1];
+ $html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati;
+ }
+ $map_long = '';
+ $cts = preg_match('/\d LONG (.*)/', $placerec, $match);
+ if ($cts > 0) {
+ $map_long = $match[1];
+ $html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long;
+ }
+ if ($map_lati && $map_long) {
+ $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789
+ $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456
+ $html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>';
+ $html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>';
+ $html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>';
+ }
+ if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
+ $html .= '<br>' . self::printFactNotes($placerec, 3);
+ }
+ }
+ }
+ if ($lds) {
+ if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) {
+ $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
+ }
+ if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) {
+ $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
+ if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) {
+ $date = new Date($match[1]);
+ $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
+ }
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Check for facts that may exist only once for a certain record type.
+ * If the fact already exists in the second array, delete it from the first one.
+ *
+ * @param string[] $uniquefacts
+ * @param Fact[] $recfacts
+ * @param string $type
+ *
+ * @return string[]
+ */
+ public static function checkFactUnique($uniquefacts, $recfacts, $type) {
+ foreach ($recfacts as $factarray) {
+ $fact = false;
+ if (is_object($factarray)) {
+ $fact = $factarray->getTag();
+ } else {
+ if ($type === 'SOUR' || $type === 'REPO') {
+ $factrec = $factarray[0];
+ }
+ if ($type === 'FAM' || $type === 'INDI') {
+ $factrec = $factarray[1];
+ }
+
+ $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match);
+ if ($ft > 0) {
+ $fact = trim($match[1]);
+ }
+ }
+ if ($fact !== false) {
+ $key = array_search($fact, $uniquefacts);
+ if ($key !== false) {
+ unset($uniquefacts[$key]);
+ }
+ }
+ }
+
+ return $uniquefacts;
+ }
+
+ /**
+ * Print a new fact box on details pages
+ *
+ * @param string $id the id of the person, family, source etc the fact will be added to
+ * @param array $usedfacts an array of facts already used in this record
+ * @param string $type the type of record INDI, FAM, SOUR etc
+ */
+ public static function printAddNewFact($id, $usedfacts, $type) {
+ global $WT_TREE;
+
+ // -- Add from clipboard
+ if (is_array(Session::get('clipboard'))) {
+ $newRow = true;
+ foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) {
+ if ($fact["type"] == $type || $fact["type"] == 'all') {
+ if ($newRow) {
+ $newRow = false;
+ echo '<tr><td class="descriptionbox">';
+ echo I18N::translate('Add from clipboard'), '</td>';
+ echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">';
+ echo '<select id="newClipboardFact">';
+ }
+ echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']);
+ // TODO use the event class to store/parse the clipboard events
+ if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) {
+ $tmp = new Date($match[1]);
+ echo '; ', $tmp->minimumDate()->format('%Y');
+ }
+ if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) {
+ echo '; ', $match[1];
+ }
+ echo '</option>';
+ }
+ }
+ if (!$newRow) {
+ echo '</select>';
+ echo '&nbsp;&nbsp;<input type="button" value="', I18N::translate('Add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> ";
+ echo '</form></td></tr>', "\n";
+ }
+ }
+
+ // -- Add from pick list
+ switch ($type) {
+ case "INDI":
+ $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
+ $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
+ $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
+ break;
+ case "FAM":
+ $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
+ $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
+ $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
+ break;
+ case "SOUR":
+ $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
+ $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
+ $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
+ break;
+ case "NOTE":
+ $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
+ $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
+ $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
+ break;
+ case "REPO":
+ $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
+ $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
+ $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
+ break;
+ default:
+ return;
+ }
+ $addfacts = array_merge(self::checkFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
+ $quickfacts = array_intersect($quickfacts, $addfacts);
+ $translated_addfacts = array();
+ foreach ($addfacts as $addfact) {
+ $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
+ }
+ uasort($translated_addfacts, function ($x, $y) {
+ return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
+ });
+ echo '<tr><td class="descriptionbox">';
+ echo I18N::translate('Fact or event');
+ echo '</td>';
+ echo '<td class="optionbox wrap">';
+ echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">';
+ echo '<select id="newfact" name="newfact">';
+ echo '<option value="" disabled selected>' . I18N::translate('&lt;select&gt;') . '</option>';
+ foreach ($translated_addfacts as $fact => $fact_name) {
+ echo '<option value="', $fact, '">', $fact_name, '</option>';
+ }
+ if ($type == 'INDI' || $type == 'FAM') {
+ echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>';
+ echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>';
+ }
+ echo '</select>';
+ echo '<input type="button" value="', I18N::translate('Add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">';
+ echo '<span class="quickfacts">';
+ foreach ($quickfacts as $fact) {
+ echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>';
+ }
+ echo '</span></form>';
+ echo '</td></tr>';
+ }
+
+ /**
+ * javascript declaration for calendar popup
+ */
+ public static function initializeCalendarPopup() {
+ global $controller;
+
+ $controller->addInlineJavascript('
+ cal_setMonthNames(
+ "' . I18N::translateContext('NOMINATIVE', 'January') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'February') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'March') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'April') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'May') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'June') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'July') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'August') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'September') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'October') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'November') . '",
+ "' . I18N::translateContext('NOMINATIVE', 'December') . '"
+ )
+ cal_setDayHeaders(
+ "' . I18N::translate('Sun') . '",
+ "' . I18N::translate('Mon') . '",
+ "' . I18N::translate('Tue') . '",
+ "' . I18N::translate('Wed') . '",
+ "' . I18N::translate('Thu') . '",
+ "' . I18N::translate('Fri') . '",
+ "' . I18N::translate('Sat') . '"
+ )
+ cal_setWeekStart(' . I18N::firstDay() . ');
+ ');
+ }
+
+ /**
+ * @param string $element_id
+ * @param string $indiname
+ * @param Tree $tree
+ *
+ * @return string
+ */
+ public static function printFindIndividualLink($element_id, $indiname = '', $tree = null) {
+ global $WT_TREE;
+
+ if ($tree === null) {
+ $tree = $WT_TREE;
+ }
+
+ return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printFindPlaceLink($element_id) {
+ return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printFindFamilyLink($element_id) {
+ return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printSpecialCharacterLink($element_id) {
+ return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
+ }
+
+ /**
+ * @param string $element_id
+ * @param string[] $choices
+ */
+ public static function printAutoPasteLink($element_id, $choices) {
+ echo '<small>';
+ foreach ($choices as $choice) {
+ echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
+ echo '\'', $choice, '\';';
+ echo " return false;\">", $choice, '</span> ';
+ }
+ echo '</small>';
+ }
+
+ /**
+ * @param string $element_id
+ * @param string $sourcename
+ *
+ * @return string
+ */
+ public static function printFindSourceLink($element_id, $sourcename = '') {
+ return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ * @param string $notename
+ *
+ * @return string
+ */
+ public static function printFindNoteLink($element_id, $notename = '') {
+ return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printFindRepositoryLink($element_id) {
+ return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ * @param string $choose
+ *
+ * @return string
+ */
+ public static function printFindMediaLink($element_id, $choose = '') {
+ return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
+ }
+
+ /**
+ * @param string $element_id
+ *
+ * @return string
+ */
+ public static function printFindFactLink($element_id) {
+ return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
+ }
+
+ /**
+ * Summary of LDS ordinances.
+ *
+ * @param Individual $individual
+ *
+ * @return string
+ */
+ public static function getLdsSummary(Individual $individual) {
+ $BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
+ $ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
+ $SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
+ $SLGS = '_';
+
+ foreach ($individual->getSpouseFamilies() as $family) {
+ if ($family->getFacts('SLGS')) {
+ $SLGS = '';
+ }
+ }
+
+ return $BAPL . $ENDL . $SLGS . $SLGC;
+ }
+}
diff --git a/app/Functions/FunctionsPrintFacts.php b/app/Functions/FunctionsPrintFacts.php
new file mode 100644
index 0000000000..f8634859c2
--- /dev/null
+++ b/app/Functions/FunctionsPrintFacts.php
@@ -0,0 +1,1150 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Date;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
+use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\GedcomTag;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Media;
+use Fisharebest\Webtrees\Module;
+use Fisharebest\Webtrees\Module\CensusAssistantModule;
+use Fisharebest\Webtrees\Note;
+use Fisharebest\Webtrees\Repository;
+use Fisharebest\Webtrees\Source;
+use Fisharebest\Webtrees\Theme;
+use Fisharebest\Webtrees\User;
+use Rhumsaa\Uuid\Uuid;
+
+/**
+ * Class FunctionsPrintFacts - common functions
+ */
+class FunctionsPrintFacts {
+ /**
+ * Print a fact record, for the individual/family/source/repository/etc. pages.
+ *
+ * Although a Fact has a parent object, we also need to know
+ * the GedcomRecord for which we are printing it. For example,
+ * we can show the death of X on the page of Y, or the marriage
+ * of X+Y on the page of Z. We need to know both records to
+ * calculate ages, relationships, etc.
+ *
+ * @param Fact $fact
+ * @param GedcomRecord $record
+ */
+ public static function printFact(Fact $fact, GedcomRecord $record) {
+ static $n_chil = 0, $n_gchi = 0;
+
+ $parent = $fact->getParent();
+
+ // Some facts don't get printed here ...
+ switch ($fact->getTag()) {
+ case 'NOTE':
+ self::printMainNotes($fact, 1);
+
+ return;
+ case 'SOUR':
+ self::printMainSources($fact, 1);
+
+ return;
+ case 'OBJE':
+ self::printMainMedia($fact, 1);
+
+ return;
+ case 'FAMC':
+ case 'FAMS':
+ case 'CHIL':
+ case 'HUSB':
+ case 'WIFE':
+ // These are internal links, not facts
+ return;
+ case '_WT_OBJE_SORT':
+ // These links are used internally to record the sort order.
+ return;
+ default:
+ // Hide unrecognized/custom tags?
+ if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') && !GedcomTag::isTag($fact->getTag())) {
+ return;
+ }
+ break;
+ }
+
+ // Who is this fact about? Need it to translate fact label correctly
+ if ($parent instanceof Family && $record instanceof Individual) {
+ // Family event
+ $label_person = $fact->getParent()->getSpouse($record);
+ } else {
+ // Individual event
+ $label_person = $parent;
+ }
+
+ // New or deleted facts need different styling
+ $styleadd = '';
+ if ($fact->isPendingAddition()) {
+ $styleadd = 'new';
+ }
+ if ($fact->isPendingDeletion()) {
+ $styleadd = 'old';
+ }
+
+ // Event of close relative
+ if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) {
+ $styleadd = trim($styleadd . ' rela');
+ }
+
+ // Event of close associates
+ if ($fact->getFactId() == 'asso') {
+ $styleadd = trim($styleadd . ' rela');
+ }
+
+ // historical facts
+ if ($fact->getFactId() == 'histo') {
+ $styleadd = trim($styleadd . ' histo');
+ }
+
+ // Does this fact have a type?
+ if (preg_match('/\n2 TYPE (.+)/', $fact->getGedcom(), $match)) {
+ $type = $match[1];
+ } else {
+ $type = '';
+ }
+
+ switch ($fact->getTag()) {
+ case 'EVEN':
+ case 'FACT':
+ if (GedcomTag::isTag($type)) {
+ // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
+ $label = GedcomTag::getLabel($type, $label_person);
+ $type = ''; // Do not print this again
+ } elseif ($type) {
+ // We don't have a translation for $type - but a custom translation might exist.
+ $label = I18N::translate(Filter::escapeHtml($type));
+ $type = ''; // Do not print this again
+ } else {
+ // An unspecified fact/event
+ $label = $fact->getLabel();
+ }
+ break;
+ case 'MARR':
+ // This is a hack for a proprietory extension. Is it still used/needed?
+ $utype = strtoupper($type);
+ if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
+ $label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
+ $type = ''; // Do not print this again
+ } else {
+ $label = $fact->getLabel();
+ }
+ break;
+ default:
+ // Normal fact/event
+ $label = $fact->getLabel();
+ break;
+ }
+
+ echo '<tr class="', $styleadd, '">';
+ echo '<td class="descriptionbox width20">';
+
+ if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
+ echo Theme::theme()->icon($fact), ' ';
+ }
+
+ if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
+ ?>
+ <a
+ href="#"
+ title="<?php echo I18N::translate('Edit'); ?>"
+ onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
+ ><?php echo $label; ?></a>
+ <div class="editfacts">
+ <div class="editlink">
+ <a
+ href="#"
+ title="<?php echo I18N::translate('Edit'); ?>"
+ class="editicon"
+ onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
+ ><span class="link_text"><?php echo I18N::translate('Edit'); ?></span></a>
+ </div>
+ <div class="copylink">
+ <a
+ href="#"
+ title="<?php echo I18N::translate('Copy'); ?>"
+ class="copyicon"
+ onclick="return copy_fact('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
+ ><span class="link_text"><?php echo I18N::translate('Copy'); ?></span></a>
+ </div>
+ <div class="deletelink">
+ <a
+ href="#"
+ title="<?php echo I18N::translate('Delete'); ?>"
+ class="deleteicon"
+ onclick="return delete_fact('<?php echo I18N::translate('Are you sure you want to delete this fact?'); ?>', '<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
+ ><span class="link_text"><?php echo I18N::translate('Delete'); ?></span></a>
+ </div>
+ </div>
+ <?php
+ } else {
+ echo $label;
+ }
+
+ switch ($fact->getTag()) {
+ case '_BIRT_CHIL':
+ echo '<br>', /* I18N: Abbreviation for "number %s" */
+ I18N::translate('#%s', ++$n_chil);
+ break;
+ case '_BIRT_GCHI':
+ case '_BIRT_GCH1':
+ case '_BIRT_GCH2':
+ echo '<br>', I18N::translate('#%s', ++$n_gchi);
+ break;
+ }
+
+ echo '</td><td class="optionbox ', $styleadd, ' wrap">';
+
+ // Event from another record?
+ if ($parent !== $record) {
+ if ($parent instanceof Family) {
+ foreach ($parent->getSpouses() as $spouse) {
+ if ($record !== $spouse) {
+ echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — ';
+ }
+ }
+ echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View family'), '</a><br>';
+ } elseif ($parent instanceof Individual) {
+ echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>';
+ }
+ }
+
+ // Print the value of this fact/event
+ switch ($fact->getTag()) {
+ case 'ADDR':
+ echo $fact->getValue();
+ break;
+ case 'AFN':
+ echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', rawurlencode($fact->getValue()), '" target="new">', Filter::escapeHtml($fact->getValue()), '</a></div>';
+ break;
+ case 'ASSO':
+ // we handle this later, in format_asso_rela_record()
+ break;
+ case 'EMAIL':
+ case 'EMAI':
+ case '_EMAIL':
+ echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
+ break;
+ case 'FILE':
+ if (Auth::isEditor($fact->getParent()->getTree())) {
+ echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
+ }
+ break;
+ case 'RESN':
+ echo '<div class="field">';
+ switch ($fact->getValue()) {
+ case 'none':
+ // Note: "1 RESN none" is not valid gedcom.
+ // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
+ echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
+ break;
+ case 'privacy':
+ echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
+ break;
+ case 'confidential':
+ echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
+ break;
+ case 'locked':
+ echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
+ break;
+ default:
+ echo Filter::escapeHtml($fact->getValue());
+ break;
+ }
+ echo '</div>';
+ break;
+ case 'PUBL': // Publication details might contain URLs.
+ echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
+ break;
+ case 'REPO':
+ if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
+ self::printRepositoryRecord($match[1]);
+ } else {
+ echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
+ }
+ break;
+ case 'URL':
+ case '_URL':
+ case 'WWW':
+ echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
+ break;
+ case 'TEXT': // 0 SOUR / 1 TEXT
+ echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
+ break;
+ default:
+ // Display the value for all other facts/events
+ switch ($fact->getValue()) {
+ case '':
+ // Nothing to display
+ break;
+ case 'N':
+ // Not valid GEDCOM
+ echo '<div class="field">', I18N::translate('No'), '</div>';
+ break;
+ case 'Y':
+ // Do not display "Yes".
+ break;
+ default:
+ if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
+ $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
+ if ($target) {
+ echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
+ } else {
+ echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
+ }
+ } else {
+ echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
+ }
+ break;
+ }
+ break;
+ }
+
+ // Print the type of this fact/event
+ if ($type) {
+ $utype = strtoupper($type);
+ // Events of close relatives, e.g. _MARR_CHIL
+ if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) {
+ // Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags
+ $type = GedcomTag::getLabel('MARR_' . $utype);
+ } else {
+ // Allow (custom) translations for other types
+ $type = I18N::translate($type);
+ }
+ echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type));
+ }
+
+ // Print the date of this fact/event
+ echo FunctionsPrint::formatFactDate($fact, $record, true, true);
+
+ // Print the place of this fact/event
+ echo '<div class="place">', FunctionsPrint::formatFactPlace($fact, true, true, true), '</div>';
+ // A blank line between the primary attributes (value, date, place) and the secondary ones
+ echo '<br>';
+
+ $addr = $fact->getAttribute('ADDR');
+ if ($addr) {
+ echo GedcomTag::getLabelValue('ADDR', $addr);
+ }
+
+ // Print the associates of this fact/event
+ echo FunctionsPrint::formatAssociateRelationship($fact);
+
+ // Print any other "2 XXXX" attributes, in the order in which they appear.
+ preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ switch ($match[1]) {
+ case 'DATE':
+ case 'TIME':
+ case 'AGE':
+ case 'PLAC':
+ case 'ADDR':
+ case 'ALIA':
+ case 'ASSO':
+ case '_ASSO':
+ case 'DESC':
+ case 'RELA':
+ case 'STAT':
+ case 'TEMP':
+ case 'TYPE':
+ case 'FAMS':
+ case 'CONT':
+ // These were already shown at the beginning
+ break;
+ case 'NOTE':
+ case 'OBJE':
+ case 'SOUR':
+ // These will be shown at the end
+ break;
+ case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
+ $events = array();
+ foreach (preg_split('/ *, */', $match[2]) as $event) {
+ $events[] = GedcomTag::getLabel($event);
+ }
+ if (count($events) == 1) {
+ echo GedcomTag::getLabelValue('EVEN', $event);
+ } else {
+ echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
+ }
+ if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
+ $date = new Date($date_match[1]);
+ echo GedcomTag::getLabelValue('DATE', $date->display());
+ }
+ if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
+ echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
+ }
+ break;
+ case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
+ $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
+ if ($family) {
+ echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
+ if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
+ echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
+ }
+ } else {
+ echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
+ }
+ break;
+ case '_WT_USER':
+ $user = User::findByIdentifier($match[2]); // may not exist
+ if ($user) {
+ echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
+ } else {
+ echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
+ }
+ break;
+ case 'RESN':
+ switch ($match[2]) {
+ case 'none':
+ // Note: "2 RESN none" is not valid gedcom.
+ // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
+ echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
+ break;
+ case 'privacy':
+ echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
+ break;
+ case 'confidential':
+ echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
+ break;
+ case 'locked':
+ echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
+ break;
+ default:
+ echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
+ break;
+ }
+ break;
+ case 'CALN':
+ echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
+ break;
+ case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
+ echo GedcomTag::getLabelValue('FORM', $match[2]);
+ if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
+ echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
+ }
+ break;
+ case 'URL':
+ case '_URL':
+ case 'WWW':
+ $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
+ echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
+ break;
+ default:
+ if (!$fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') || GedcomTag::isTag($match[1])) {
+ if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
+ // Links
+ $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
+ if ($linked_record) {
+ $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
+ echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
+ } else {
+ echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
+ }
+ } else {
+ // Non links
+ echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
+ }
+ }
+ break;
+ }
+ }
+ echo FunctionsPrintFacts::printFactSources($fact->getGedcom(), 2);
+ echo FunctionsPrint::printFactNotes($fact->getGedcom(), 2);
+ self::printMediaLinks($fact->getGedcom(), 2);
+ echo '</td></tr>';
+ }
+
+ /**
+ * print a repository record
+ *
+ * find and print repository information attached to a source
+ *
+ * @param string $xref the Gedcom Xref ID of the repository to print
+ */
+ public static function printRepositoryRecord($xref) {
+ global $WT_TREE;
+
+ $repository = Repository::getInstance($xref, $WT_TREE);
+ if ($repository && $repository->canShow()) {
+ echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>';
+ echo '<br>';
+ echo FunctionsPrint::printFactNotes($repository->getGedcom(), 1);
+ }
+ }
+
+ /**
+ * print a source linked to a fact (2 SOUR)
+ *
+ * this function is called by the FunctionsPrintFacts::print_fact function and other functions to
+ * print any source information attached to the fact
+ *
+ * @param string $factrec The fact record to look for sources in
+ * @param int $level The level to look for sources at
+ *
+ * @return string HTML text
+ */
+ public static function printFactSources($factrec, $level) {
+ global $WT_TREE;
+
+ $data = '';
+ $nlevel = $level + 1;
+
+ // -- Systems not using source records [ 1046971 ]
+ $ct = preg_match_all("/$level SOUR (.*)/", $factrec, $match, PREG_SET_ORDER);
+ for ($j = 0; $j < $ct; $j++) {
+ if (strpos($match[$j][1], '@') === false) {
+ $data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">' . Filter::escapeHtml($match[$j][1]) . '</span></div>';
+ }
+ }
+ // -- find source for each fact
+ $ct = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
+ $spos2 = 0;
+ for ($j = 0; $j < $ct; $j++) {
+ $sid = $match[$j][1];
+ $source = Source::getInstance($sid, $WT_TREE);
+ if ($source) {
+ if ($source->canShow()) {
+ $spos1 = strpos($factrec, "$level SOUR @" . $sid . "@", $spos2);
+ $spos2 = strpos($factrec, "\n$level", $spos1);
+ if (!$spos2) {
+ $spos2 = strlen($factrec);
+ }
+ $srec = substr($factrec, $spos1, $spos2 - $spos1);
+ $lt = preg_match_all("/$nlevel \w+/", $srec, $matches);
+ $data .= '<div class="fact_SOUR">';
+ $data .= '<span class="label">';
+ $elementID = Uuid::uuid4();
+ if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
+ $plusminus = 'icon-minus';
+ } else {
+ $plusminus = 'icon-plus';
+ }
+ if ($lt > 0) {
+ $data .= '<a href="#" onclick="return expand_layer(\'' . $elementID . '\');"><i id="' . $elementID . '_img" class="' . $plusminus . '"></i></a> ';
+ }
+ $data .= I18N::translate('Source') . ':</span> <span class="field">';
+ $data .= '<a href="' . $source->getHtmlUrl() . '">' . $source->getFullName() . '</a>';
+ $data .= '</span></div>';
+
+ $data .= "<div id=\"$elementID\"";
+ if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
+ $data .= ' style="display:block"';
+ }
+ $data .= ' class="source_citations">';
+ // PUBL
+ $publ = $source->getFirstFact('PUBL');
+ if ($publ) {
+ $data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
+ }
+ $data .= self::printSourceStructure(self::getSourceStructure($srec));
+ $data .= '<div class="indent">';
+ ob_start();
+ self::printMediaLinks($srec, $nlevel);
+ $data .= ob_get_clean();
+ $data .= FunctionsPrint::printFactNotes($srec, $nlevel, false);
+ $data .= '</div>';
+ $data .= '</div>';
+ } else {
+ // Here we could show that we do actually have sources for this data,
+ // but not the details. For example “Sources: ”.
+ // But not by default, based on user feedback.
+ // http://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
+ }
+ } else {
+ $data .= GedcomTag::getLabelValue('SOUR', '<span class="error">' . $sid . '</span>');
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Print the links to media objects
+ *
+ * @param string $factrec
+ * @param int $level
+ */
+ public static function printMediaLinks($factrec, $level) {
+ global $WT_TREE;
+
+ $nlevel = $level + 1;
+ if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) == 0) {
+ return;
+ }
+ $objectNum = 0;
+ while ($objectNum < count($omatch)) {
+ $media_id = $omatch[$objectNum][1];
+ $media = Media::getInstance($media_id, $WT_TREE);
+ if ($media) {
+ if ($media->canShow()) {
+ if ($objectNum > 0) {
+ echo '<br class="media-separator" style="clear:both;">';
+ }
+ echo '<div class="media-display"><div class="media-display-image">';
+ echo $media->displayImage();
+ echo '</div>';
+ echo '<div class="media-display-title">';
+ echo '<a href="', $media->getHtmlUrl(), '">', $media->getFullName(), '</a>';
+ // NOTE: echo the notes of the media
+ echo '<p>';
+ echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
+ $ttype = preg_match("/" . ($nlevel + 1) . " TYPE (.*)/", $media->getGedcom(), $match);
+ if ($ttype > 0) {
+ $mediaType = GedcomTag::getFileFormTypeValue($match[1]);
+ echo '<p class="label">', I18N::translate('Type'), ': </span> <span class="field">', $mediaType, '</p>';
+ }
+ echo '</p>';
+ //-- print spouse name for marriage events
+ $ct = preg_match("/WT_SPOUSE: (.*)/", $factrec, $match);
+ if ($ct > 0) {
+ $spouse = Individual::getInstance($match[1], $media->getTree());
+ if ($spouse) {
+ echo '<a href="', $spouse->getHtmlUrl(), '">';
+ echo $spouse->getFullName();
+ echo '</a>';
+ }
+ $ct = preg_match("/WT_FAMILY_ID: (.*)/", $factrec, $match);
+ if ($ct > 0) {
+ $famid = trim($match[1]);
+ $family = Family::getInstance($famid, $spouse->getTree());
+ if ($family) {
+ if ($spouse) {
+ echo " - ";
+ }
+ echo '<a href="', $family->getHtmlUrl(), '">', I18N::translate('View family'), '</a>';
+ }
+ }
+ }
+ echo FunctionsPrint::printFactNotes($media->getGedcom(), $nlevel);
+ echo FunctionsPrintFacts::printFactSources($media->getGedcom(), $nlevel);
+ echo '</div>'; //close div "media-display-title"
+ echo '</div>'; //close div "media-display"
+ }
+ } elseif (!$WT_TREE->getPreference('HIDE_GEDCOM_ERRORS')) {
+ echo '<p class="ui-state-error">', $media_id, '</p>';
+ }
+ $objectNum++;
+ }
+ }
+
+ /**
+ * Print a row for the sources tab on the individual page.
+ *
+ * @param Fact $fact
+ * @param int $level
+ */
+ public static function printMainSources(Fact $fact, $level) {
+ $factrec = $fact->getGedcom();
+ $fact_id = $fact->getFactId();
+ $parent = $fact->getParent();
+ $pid = $parent->getXref();
+
+ $nlevel = $level + 1;
+ if ($fact->isPendingAddition()) {
+ $styleadd = 'new';
+ $can_edit = $level == 1 && $fact->canEdit();
+ } elseif ($fact->isPendingDeletion()) {
+ $styleadd = 'old';
+ $can_edit = false;
+ } else {
+ $styleadd = '';
+ $can_edit = $level == 1 && $fact->canEdit();
+ }
+
+ // -- find source for each fact
+ $ct = preg_match_all("/($level SOUR (.+))/", $factrec, $match, PREG_SET_ORDER);
+ $spos2 = 0;
+ for ($j = 0; $j < $ct; $j++) {
+ $sid = trim($match[$j][2], '@');
+ $spos1 = strpos($factrec, $match[$j][1], $spos2);
+ $spos2 = strpos($factrec, "\n$level", $spos1);
+ if (!$spos2) {
+ $spos2 = strlen($factrec);
+ }
+ $srec = substr($factrec, $spos1, $spos2 - $spos1);
+ $source = Source::getInstance($sid, $fact->getParent()->getTree());
+ // Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
+ if (!$source || $source->canShow()) {
+ if ($level > 1) {
+ echo '<tr class="row_sour2">';
+ } else {
+ echo '<tr>';
+ }
+ echo '<td class="descriptionbox';
+ if ($level > 1) {
+ echo ' rela';
+ }
+ echo ' ', $styleadd, ' width20">';
+ $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
+ $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
+ $factname = $factwords[1]; // BIRT
+ if ($factname == 'EVEN' || $factname == 'FACT') {
+ // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
+ $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
+ if ($ct > 0) {
+ $factname = trim($ematch[1]);
+ echo $factname;
+ } else {
+ echo GedcomTag::getLabel($factname, $parent);
+ }
+ } elseif ($can_edit) {
+ echo "<a onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"", I18N::translate('Edit'), '">';
+ if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
+ if ($level == 1) {
+ echo '<i class="icon-source"></i> ';
+ }
+ }
+ echo GedcomTag::getLabel($factname, $parent), '</a>';
+ echo '<div class="editfacts">';
+ if (preg_match('/^@.+@$/', $match[$j][2])) {
+ // Inline sources can't be edited. Attempting to save one will convert it
+ // into a link, and delete it.
+ // e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
+ echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
+ echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
+ }
+ echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
+ echo '</div>';
+ } else {
+ echo GedcomTag::getLabel($factname, $parent);
+ }
+ echo '</td>';
+ echo '<td class="optionbox ', $styleadd, ' wrap">';
+ if ($source) {
+ echo '<a href="', $source->getHtmlUrl(), '">', $source->getFullName(), '</a>';
+ // PUBL
+ $publ = $source->getFirstFact('PUBL');
+ if ($publ) {
+ echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
+ }
+ // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
+ if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
+ foreach ($rmatches[1] as $rmatch) {
+ echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
+ switch ($rmatch) {
+ case 'none':
+ // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
+ // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
+ echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
+ break;
+ case 'privacy':
+ echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
+ break;
+ case 'confidential':
+ echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
+ break;
+ case 'locked':
+ echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
+ break;
+ default:
+ echo $rmatch;
+ break;
+ }
+ echo '</span>';
+ }
+ }
+ $cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
+ if ($cs > 0) {
+ echo '<br><span class="label">', GedcomTag::getLabel('EVEN'), ' </span><span class="field">', $cmatch[1], '</span>';
+ $cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
+ if ($cs > 0) {
+ echo '<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="label">', GedcomTag::getLabel('ROLE'), ' </span><span class="field">', $cmatch[1], '</span>';
+ }
+ }
+ echo self::printSourceStructure(self::getSourceStructure($srec));
+ echo '<div class="indent">';
+ self::printMediaLinks($srec, $nlevel);
+ if ($nlevel == 2) {
+ self::printMediaLinks($source->getGedcom(), 1);
+ }
+ echo FunctionsPrint::printFactNotes($srec, $nlevel);
+ if ($nlevel == 2) {
+ echo FunctionsPrint::printFactNotes($source->getGedcom(), 1);
+ }
+ echo '</div>';
+ } else {
+ echo $sid;
+ }
+ echo '</td></tr>';
+ }
+ }
+ }
+
+ /**
+ * Print SOUR structure
+ * This function prints the input array of SOUR sub-records built by the
+ * getSourceStructure() function.
+ *
+ * @param string[] $textSOUR
+ *
+ * @return string
+ */
+ public static function printSourceStructure($textSOUR) {
+ global $WT_TREE;
+ $html = '';
+
+ if ($textSOUR['PAGE']) {
+ $html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
+ }
+
+ if ($textSOUR['EVEN']) {
+ $html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
+ if ($textSOUR['ROLE']) {
+ $html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
+ }
+ }
+
+ if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
+ if ($textSOUR['DATE']) {
+ $date = new Date($textSOUR['DATE']);
+ $html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
+ }
+ foreach ($textSOUR['TEXT'] as $text) {
+ $html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
+ }
+ }
+
+ if ($textSOUR['QUAY'] != '') {
+ $html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
+ }
+
+ return '<div class="indent">' . $html . '</div>';
+ }
+
+ /**
+ * Extract SOUR structure from the incoming Source sub-record
+ * The output array is defined as follows:
+ * $textSOUR['PAGE'] = Source citation
+ * $textSOUR['EVEN'] = Event type
+ * $textSOUR['ROLE'] = Role in event
+ * $textSOUR['DATA'] = place holder (no text in this sub-record)
+ * $textSOUR['DATE'] = Entry recording date
+ * $textSOUR['TEXT'] = (array) Text from source
+ * $textSOUR['QUAY'] = Certainty assessment
+ *
+ * @param string $srec
+ *
+ * @return string[]
+ */
+ public static function getSourceStructure($srec) {
+ // Set up the output array
+ $textSOUR = array(
+ 'PAGE' => '',
+ 'EVEN' => '',
+ 'ROLE' => '',
+ 'DATA' => '',
+ 'DATE' => '',
+ 'TEXT' => array(),
+ 'QUAY' => '',
+ );
+
+ if ($srec) {
+ $subrecords = explode("\n", $srec);
+ for ($i = 0; $i < count($subrecords); $i++) {
+ $tag = substr($subrecords[$i], 2, 4);
+ $text = substr($subrecords[$i], 7);
+ $i++;
+ for (; $i < count($subrecords); $i++) {
+ $nextTag = substr($subrecords[$i], 2, 4);
+ if ($nextTag != 'CONT') {
+ $i--;
+ break;
+ }
+ if ($nextTag == 'CONT') {
+ $text .= "\n";
+ }
+ $text .= rtrim(substr($subrecords[$i], 7));
+ }
+ if ($tag == 'TEXT') {
+ $textSOUR[$tag][] = $text;
+ } else {
+ $textSOUR[$tag] = $text;
+ }
+ }
+ }
+
+ return $textSOUR;
+ }
+
+ /**
+ * Print a row for the notes tab on the individual page.
+ *
+ * @param Fact $fact
+ * @param int $level
+ */
+ public static function printMainNotes(Fact $fact, $level) {
+ $factrec = $fact->getGedcom();
+ $fact_id = $fact->getFactId();
+ $parent = $fact->getParent();
+ $pid = $parent->getXref();
+
+ if ($fact->isPendingAddition()) {
+ $styleadd = ' new';
+ $can_edit = $level == 1 && $fact->canEdit();
+ } elseif ($fact->isPendingDeletion()) {
+ $styleadd = ' old';
+ $can_edit = false;
+ } else {
+ $styleadd = '';
+ $can_edit = $level == 1 && $fact->canEdit();
+ }
+
+ $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
+ for ($j = 0; $j < $ct; $j++) {
+ // Note object, or inline note?
+ if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
+ $note = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
+ if ($note && !$note->canShow()) {
+ continue;
+ }
+ } else {
+ $note = null;
+ }
+
+ if ($level >= 2) {
+ echo '<tr class="row_note2"><td class="descriptionbox rela ', $styleadd, ' width20">';
+ } else {
+ echo '<tr><td class="descriptionbox ', $styleadd, ' width20">';
+ }
+ if ($can_edit) {
+ echo '<a onclick="return edit_record(\'', $pid, '\', \'', $fact_id, '\');" href="#" title="', I18N::translate('Edit'), '">';
+ if ($level < 2) {
+ if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
+ echo '<i class="icon-note"></i> ';
+ }
+ if ($note) {
+ echo GedcomTag::getLabel('SHARED_NOTE');
+ } else {
+ echo GedcomTag::getLabel('NOTE');
+ }
+ echo '</a>';
+ echo '<div class="editfacts">';
+ echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
+ echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
+ echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
+ if ($note) {
+ echo '<a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a>';
+ }
+ echo '</div>';
+ }
+ } else {
+ if ($level < 2) {
+ if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
+ echo '<i class="icon-note"></i> ';
+ }
+ if ($note) {
+ echo GedcomTag::getLabel('SHARED_NOTE');
+ } else {
+ echo GedcomTag::getLabel('NOTE');
+ }
+ }
+ $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
+ $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
+ $factname = $factwords[1]; // BIRT
+ $parent = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
+ if ($factname == 'EVEN' || $factname == 'FACT') {
+ // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
+ $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
+ if ($ct > 0) {
+ $factname = trim($ematch[1]);
+ echo $factname;
+ } else {
+ echo GedcomTag::getLabel($factname, $parent);
+ }
+ } elseif ($factname != 'NOTE') {
+ // Note is already printed
+ echo GedcomTag::getLabel($factname, $parent);
+ if ($note) {
+ echo '<div class="editfacts"><a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a></div>';
+
+ }
+ }
+ }
+ echo '</td>';
+ if ($note) {
+ // Note objects
+ if (Module::getModuleByName('GEDFact_assistant')) {
+ // If Census assistant installed, allow it to format the note
+ $text = CensusAssistantModule::formatCensusNote($note);
+ } else {
+ $text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
+ }
+ } else {
+ // Inline notes
+ $nrec = Functions::getSubRecord($level, "$level NOTE", $factrec, $j + 1);
+ $text = $match[$j][1] . Functions::getCont($level + 1, $nrec);
+ $text = Filter::formatText($text, $fact->getParent()->getTree());
+ }
+
+ echo '<td class="optionbox', $styleadd, ' wrap">';
+ echo $text;
+
+ if (!empty($noterec)) {
+ echo FunctionsPrintFacts::printFactSources($noterec, 1);
+ }
+
+ // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
+ if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
+ foreach ($matches[1] as $match) {
+ echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
+ switch ($match) {
+ case 'none':
+ // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
+ // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
+ echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
+ break;
+ case 'privacy':
+ echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
+ break;
+ case 'confidential':
+ echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
+ break;
+ case 'locked':
+ echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
+ break;
+ default:
+ echo $match;
+ break;
+ }
+ echo '</span>';
+ }
+ }
+ echo '</td></tr>';
+ }
+ }
+
+ /**
+ * Print a row for the media tab on the individual page.
+ *
+ * @param Fact $fact
+ * @param int $level
+ */
+ public static function printMainMedia(Fact $fact, $level) {
+ $factrec = $fact->getGedcom();
+ $parent = $fact->getParent();
+
+ if ($fact->isPendingAddition()) {
+ $styleadd = 'new';
+ $can_edit = $level == 1 && $fact->canEdit();
+ } elseif ($fact->isPendingDeletion()) {
+ $styleadd = 'old';
+ $can_edit = false;
+ } else {
+ $styleadd = '';
+ $can_edit = $level == 1 && $fact->canEdit();
+ }
+
+ // -- find source for each fact
+ preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
+ foreach ($matches[1] as $xref) {
+ $media = Media::getInstance($xref, $fact->getParent()->getTree());
+ // Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
+ if (!$media || $media->canShow()) {
+ if ($level > 1) {
+ echo '<tr class="row_obje2">';
+ } else {
+ echo '<tr>';
+ }
+ echo '<td class="descriptionbox';
+ if ($level > 1) {
+ echo ' rela';
+ }
+ echo ' ', $styleadd, ' width20">';
+ preg_match("/^\d (\w*)/", $factrec, $factname);
+ $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
+ $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
+ $factname = $factwords[1]; // BIRT
+ if ($factname == 'EVEN' || $factname == 'FACT') {
+ // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
+ $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
+ if ($ct > 0) {
+ $factname = $ematch[1];
+ echo $factname;
+ } else {
+ echo GedcomTag::getLabel($factname, $parent);
+ }
+ } elseif ($can_edit) {
+ echo '<a onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '">';
+ echo GedcomTag::getLabel($factname, $parent), '</a>';
+ echo '<div class="editfacts">';
+ echo '<div class="editlink"><a class="editicon" onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '"><span class="link_text">', I18N::translate('Edit'), '</span></a></div>';
+ echo '<div class="copylink"><a class="copyicon" href="#" onclick="jQuery.post(\'action.php\',{action:\'copy-fact\', type:\'\', factgedcom:\'' . rawurlencode($factrec) . '\'},function(){location.reload();})" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
+ echo '<div class="deletelink"><a class="deleteicon" onclick="return delete_fact(\'', I18N::translate('Are you sure you want to delete this fact?'), '\', \'', $parent->getXref(), '\', \'', $fact->getFactId(), '\');" href="#" title="', I18N::translate('Delete'), '"><span class="link_text">', I18N::translate('Delete'), '</span></a></div>';
+ echo '</div>';
+ } else {
+ echo GedcomTag::getLabel($factname, $parent);
+ }
+ echo '</td>';
+ echo '<td class="optionbox ', $styleadd, ' wrap">';
+ if ($media) {
+ echo '<span class="field">';
+ echo $media->displayImage();
+ echo '<a href="' . $media->getHtmlUrl() . '">';
+ echo '<em>';
+ foreach ($media->getAllNames() as $name) {
+ if ($name['type'] != 'TITL') {
+ echo '<br>';
+ }
+ echo $name['full'];
+ }
+ echo '</em>';
+ echo '</a>';
+ echo '</span>';
+
+ echo GedcomTag::getLabelValue('FORM', $media->mimeType());
+ $imgsize = $media->getImageAttributes('main');
+ if (!empty($imgsize['WxH'])) {
+ echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
+ }
+ if ($media->getFilesizeraw() > 0) {
+ echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
+ }
+ $mediatype = $media->getMediaType();
+ if ($mediatype) {
+ echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
+ }
+
+ switch ($media->isPrimary()) {
+ case 'Y':
+ echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
+ break;
+ case 'N':
+ echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
+ break;
+ }
+ echo FunctionsPrint::printFactNotes($media->getGedcom(), 1);
+ echo FunctionsPrintFacts::printFactSources($media->getGedcom(), 1);
+ } else {
+ echo $xref;
+ }
+ echo '</td></tr>';
+ }
+ }
+ }
+}
diff --git a/app/Functions/FunctionsPrintLists.php b/app/Functions/FunctionsPrintLists.php
new file mode 100644
index 0000000000..912c2b88e9
--- /dev/null
+++ b/app/Functions/FunctionsPrintLists.php
@@ -0,0 +1,2297 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * Class FunctionsPrintLists - common functions
+ */
+class FunctionsPrintLists {
+ /**
+ * Print a table of individuals
+ *
+ * @param Individual[] $datalist
+ * @param string $option
+ *
+ * @return string
+ */
+ public static function individualTable($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 (a<b) ? -1 : (a>b ? 1 : 0);};
+ jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (a<b ? 1 : 0);};
+ jQuery("#' . $table_id . '").dataTable( {
+ dom: \'<"H"<"filtersH_' . $table_id . '">T<"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 = '
+ <div class="loading-image">&nbsp;</div>
+ <div class="indi-list">
+ <table id="' . $table_id . '">
+ <thead>
+ <tr>
+ <th colspan="24">
+ <div class="btn-toolbar">
+ <div class="btn-group">
+ <button
+ class="ui-state-default"
+ data-filter-column="20"
+ data-filter-value="M"
+ title="' . I18N::translate('Show only males.') . '"
+ type="button"
+ >
+ ' . Individual::sexImage('M', 'large') . '
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="20"
+ data-filter-value="F"
+ title="' . I18N::translate('Show only females.') . '"
+ type="button"
+ >
+ ' . Individual::sexImage('F', 'large') . '
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="20"
+ data-filter-value="U"
+ title="' . I18N::translate('Show only individuals for whom the gender is not known.') . '"
+ type="button"
+ >
+ ' . Individual::sexImage('U', 'large') . '
+ </button>
+ </div>
+ <div class="btn-group">
+ <button
+ class="ui-state-default"
+ data-filter-column="22"
+ data-filter-value="N"
+ title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
+ type="button"
+ >
+ ' . I18N::translate('Alive') . '
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="22"
+ data-filter-value="Y"
+ title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '"
+ type="button"
+ >
+ ' . I18N::translate('Dead') . '
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="22"
+ data-filter-value="YES"
+ title="' . I18N::translate('Show individuals who died more than 100 years ago.') . '"
+ type="button"
+ >
+ ' . GedcomTag::getLabel('DEAT') . '&gt;100
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="22"
+ data-filter-value="Y100"
+ title="' . I18N::translate('Show individuals who died within the last 100 years.') . '"
+ type="button"
+ >
+ ' . GedcomTag::getLabel('DEAT') . '&lt;=100
+ </button>
+ </div>
+ <div class="btn-group">
+ <button
+ class="ui-state-default"
+ data-filter-column="21"
+ data-filter-value="YES"
+ title="' . I18N::translate('Show individuals born more than 100 years ago.') . '"
+ type="button"
+ >
+ ' . GedcomTag::getLabel('BIRT') . '&gt;100
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="21"
+ data-filter-value="Y100"
+ title="' . I18N::translate('Show individuals born within the last 100 years.') . '"
+ type="button"
+ >
+ ' . GedcomTag::getLabel('BIRT') . '&lt;=100
+ </button>
+ </div>
+ <div class="btn-group">
+ <button
+ class="ui-state-default"
+ data-filter-column="23"
+ data-filter-value="R"
+ 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.') . '"
+ type="button"
+ >
+ ' . I18N::translate('Roots') . '
+ </button>
+ <button
+ class="ui-state-default"
+ data-filter-column="23"
+ data-filter-value="L"
+ title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
+ type="button"
+ >
+ ' . I18N::translate('Leaves') . '
+ </button>
+ </div>
+ </div>
+ </th>
+ </tr>
+ <tr>
+ <th>' . GedcomTag::getLabel('GIVN') . '</th>
+ <th>' . GedcomTag::getLabel('SURN') . '</th>
+ <th>GIVN</th>
+ <th>SURN</th>
+ <th>' . /* I18N: Abbreviation for “Sosa-Stradonitz number”. This is an individual’s surname, so may need transliterating into non-latin alphabets. */
+ I18N::translate('Sosa') . '</th>
+ <th>SOSA</th>
+ <th>' . GedcomTag::getLabel('BIRT') . '</th>
+ <th>SORT_BIRT</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('DEAT') . '</th>
+ <th>SORT_DEAT</th>
+ <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
+ <th>' . GedcomTag::getLabel('AGE') . '</th>
+ <th>AGE</th>
+ <th>' . GedcomTag::getLabel('PLAC') . '</th>
+ <th>' . GedcomTag::getLabel('CHAN') . '</th>
+ <th>CHAN</th>
+ <th>SEX</th>
+ <th>BIRT</th>
+ <th>DEAT</th>
+ <th>TREE</th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <th colspan="24">
+ <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
+ $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 .= '<tr' . $class . '>';
+ //-- Indi name(s)
+ $html .= '<td colspan="2">';
+ 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 .= '<a ' . $title . ' href="' . $person->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
+ }
+ // Indi parents
+ $html .= $person->getPrimaryParentsNames('parents details1', 'none');
+ $html .= '</td>';
+ // Dummy column to match colspan in header
+ $html .= '<td style="display:none;"></td>';
+ //-- 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 .= '<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>';
+ //-- SOSA
+ if ($option == 'sosa') {
+ $html .= '<td><a href="../../relationship.php?pid1=' . $datalist[1] . '&amp;pid2=' . $person->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a></td><td>' . $key . '</td>';
+ } else {
+ $html .= '<td></td><td>0</td>';
+ }
+ //-- Birth date
+ $html .= '<td>';
+ if ($birth_dates = $person->getAllBirthDates()) {
+ foreach ($birth_dates as $num => $birth_date) {
+ if ($num) {
+ $html .= '<br>';
+ }
+ $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 .= '&nbsp;';
+ }
+ $birth_dates[0] = new Date('');
+ }
+ $html .= '</td>';
+ //-- Event date (sortable)hidden by datatables code
+ $html .= '<td>' . $birth_date->julianDay() . '</td>';
+ //-- Birth anniversary
+ $html .= '<td>' . Date::getAge($birth_dates[0], null, 2) . '</td>';
+ //-- Birth place
+ $html .= '<td>';
+ foreach ($person->getAllBirthPlaces() as $n => $birth_place) {
+ $tmp = new Place($birth_place, $person->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 = $person->getNumberOfChildren();
+ $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>';
+ //-- Death date
+ $html .= '<td>';
+ if ($death_dates = $person->getAllDeathDates()) {
+ foreach ($death_dates as $num => $death_date) {
+ if ($num) {
+ $html .= '<br>';
+ }
+ $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 .= '&nbsp;';
+ }
+ $death_dates[0] = new Date('');
+ }
+ $html .= '</td>';
+ //-- Event date (sortable)hidden by datatables code
+ $html .= '<td>' . $death_date->julianDay() . '</td>';
+ //-- Death anniversary
+ $html .= '<td>' . Date::getAge($death_dates[0], null, 2) . '</td>';
+ //-- 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 .= '<td>' . Date::getAge($birth_dates[0], $death_dates[0], 2) . '</td><td>' . Date::getAge($birth_dates[0], $death_dates[0], 1) . '</td>';
+ //-- Death place
+ $html .= '<td>';
+ foreach ($person->getAllDeathPlaces() as $n => $death_place) {
+ $tmp = new Place($death_place, $person->getTree());
+ if ($n) {
+ $html .= '<br>';
+ }
+ $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
+ $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
+ }
+ $html .= '</td>';
+ //-- Last change
+ $html .= '<td>' . $person->lastChangeTimestamp() . '</td>';
+ $html .= '<td>' . $person->lastChangeTimestamp(true) . '</td>';
+ //-- Sorting by gender
+ $html .= '<td>' . $person->getSex() . '</td>';
+ //-- Filtering by birth date
+ $html .= '<td>';
+ if (!$person->canShow() || Date::compare($birth_date, $d100y) > 0) {
+ $html .= 'Y100';
+ } else {
+ $html .= 'YES';
+ }
+ $html .= '</td>';
+ //-- Filtering by death date
+ $html .= '<td>';
+ // 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 .= '</td>';
+ //-- Roots or Leaves ?
+ $html .= '<td>';
+ if (!$person->getChildFamilies()) {
+ $html .= 'R';
+ } // roots
+ elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) {
+ $html .= 'L';
+ } // leaves
+ else {
+ $html .= '&nbsp;';
+ }
+ $html .= '</td>';
+ $html .= '</tr>';
+ $unique_indis[$person->getXref()] = true;
+ }
+ $html .= '
+ </tbody>
+ </table>
+ <div id="indi_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($deat_by_decade, I18N::translate('Decade of death')) . '
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ ' . self::chartByAge($deat_by_age, I18N::translate('Age related to death year')) . '
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>';
+
+ return $html;
+ }
+
+ /**
+ * 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">&nbsp;</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') . '&gt;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') . '&lt;=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 .= '&nbsp;';
+ }
+ $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 .= '&nbsp;';
+ }
+ $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 a table of sources
+ *
+ * @param Source[] $datalist
+ *
+ * @return string
+ */
+ public static function sourceTable($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 .= '<div class="loading-image">&nbsp;</div>';
+ $html .= '<div class="source-list">';
+ //-- table header
+ $html .= '<table id="' . $table_id . '"><thead><tr>';
+ $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
+ $html .= '<th>TITL</th>';
+ $html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
+ $html .= '<th>' . I18N::translate('Individuals') . '</th>';
+ $html .= '<th>#INDI</th>';
+ $html .= '<th>' . I18N::translate('Families') . '</th>';
+ $html .= '<th>#FAM</th>';
+ $html .= '<th>' . I18N::translate('Media objects') . '</th>';
+ $html .= '<th>#OBJE</th>';
+ $html .= '<th>' . I18N::translate('Shared notes') . '</th>';
+ $html .= '<th>#NOTE</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
+ $html .= '<th></th>'; //delete
+ $html .= '</tr></thead>';
+ //-- table body
+ $html .= '<tbody>';
+
+ foreach ($datalist as $source) {
+ if (!$source->canShow()) {
+ continue;
+ }
+ if ($source->isPendingAddtion()) {
+ $class = ' class="new"';
+ } elseif ($source->isPendingDeletion()) {
+ $class = ' class="old"';
+ } else {
+ $class = '';
+ }
+ $html .= '<tr' . $class . '>';
+ //-- Source name(s)
+ $html .= '<td>';
+ foreach ($source->getAllNames() as $n => $name) {
+ if ($n) {
+ $html .= '<br>';
+ }
+ if ($n == $source->getPrimaryName()) {
+ $html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
+ } else {
+ $html .= '<a href="' . $source->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
+ }
+ }
+ $html .= '</td>';
+ // Sortable name
+ $html .= '<td>' . strip_tags($source->getFullName()) . '</td>';
+ //-- Author
+ $auth = $source->getFirstFact('AUTH');
+ if ($auth) {
+ $author = $auth->getValue();
+ } else {
+ $author = '';
+ }
+ $html .= '<td>' . FunctionsPrint::highlightSearchHits($author) . '</td>';
+ $key = $source->getXref() . '@' . $source->getTree()->getTreeId();
+ //-- Linked INDIs
+ $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked FAMs
+ $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked OBJEcts
+ $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked NOTEs
+ $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Last change
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $source->LastChangeTimestamp() . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Last change hidden sort column
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $source->LastChangeTimestamp(true) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Delete
+ if (Auth::isManager($WT_TREE)) {
+ $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_source(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ $html .= '</tr>';
+ }
+ $html .= '</tbody></table></div>';
+
+ return $html;
+ }
+
+ /**
+ * Print a table of shared notes
+ *
+ * @param Note[] $datalist
+ *
+ * @return string
+ */
+ public static function noteTable($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 .= '<div class="loading-image">&nbsp;</div>';
+ $html .= '<div class="note-list">';
+ //-- table header
+ $html .= '<table id="' . $table_id . '"><thead><tr>';
+ $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
+ $html .= '<th>' . I18N::translate('Individuals') . '</th>';
+ $html .= '<th>#INDI</th>';
+ $html .= '<th>' . I18N::translate('Families') . '</th>';
+ $html .= '<th>#FAM</th>';
+ $html .= '<th>' . I18N::translate('Media objects') . '</th>';
+ $html .= '<th>#OBJE</th>';
+ $html .= '<th>' . I18N::translate('Sources') . '</th>';
+ $html .= '<th>#SOUR</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
+ $html .= '<th></th>'; //delete
+ $html .= '</tr></thead>';
+ //-- table body
+ $html .= '<tbody>';
+ foreach ($datalist as $note) {
+ if (!$note->canShow()) {
+ continue;
+ }
+ if ($note->isPendingAddtion()) {
+ $class = ' class="new"';
+ } elseif ($note->isPendingDeletion()) {
+ $class = ' class="old"';
+ } else {
+ $class = '';
+ }
+ $html .= '<tr' . $class . '>';
+ //-- Shared Note name
+ $html .= '<td><a class="name2" href="' . $note->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($note->getFullName()) . '</a></td>';
+ //-- Linked INDIs
+ $num = count($note->linkedIndividuals('NOTE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked FAMs
+ $num = count($note->linkedfamilies('NOTE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked OBJEcts
+ $num = count($note->linkedMedia('NOTE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked SOURs
+ $num = count($note->linkedSources('NOTE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Last change
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $note->LastChangeTimestamp() . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Last change hidden sort column
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $note->LastChangeTimestamp(true) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Delete
+ if (Auth::isManager($WT_TREE)) {
+ $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_note(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ $html .= '</tr>';
+ }
+ $html .= '</tbody></table></div>';
+
+ return $html;
+ }
+
+ /**
+ * Print a table of repositories
+ *
+ * @param Repository[] $repositories
+ *
+ * @return string
+ */
+ public static function repositoryTable($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 .= '<div class="loading-image">&nbsp;</div>';
+ $html .= '<div class="repo-list">';
+ //-- table header
+ $html .= '<table id="' . $table_id . '"><thead><tr>';
+ $html .= '<th>' . I18N::translate('Repository name') . '</th>';
+ $html .= '<th>' . I18N::translate('Sources') . '</th>';
+ $html .= '<th>#SOUR</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
+ $html .= '<th></th>'; //delete
+ $html .= '</tr></thead>';
+ //-- table body
+ $html .= '<tbody>';
+
+ foreach ($repositories as $repository) {
+ if (!$repository->canShow()) {
+ continue;
+ }
+ if ($repository->isPendingAddtion()) {
+ $class = ' class="new"';
+ } elseif ($repository->isPendingDeletion()) {
+ $class = ' class="old"';
+ } else {
+ $class = '';
+ }
+ $html .= '<tr' . $class . '>';
+ //-- Repository name(s)
+ $html .= '<td>';
+ foreach ($repository->getAllNames() as $n => $name) {
+ if ($n) {
+ $html .= '<br>';
+ }
+ if ($n == $repository->getPrimaryName()) {
+ $html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
+ } else {
+ $html .= '<a href="' . $repository->getHtmlUrl() . '">' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>';
+ }
+ }
+ $html .= '</td>';
+ $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
+ //-- Linked SOURces
+ $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Last change
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $repository->LastChangeTimestamp() . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Last change hidden sort column
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $repository->LastChangeTimestamp(true) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Delete
+ if (Auth::isManager($WT_TREE)) {
+ $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_repository(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ $html .= '</tr>';
+ }
+ $html .= '</tbody></table></div>';
+
+ return $html;
+ }
+
+ /**
+ * Print a table of media objects
+ *
+ * @param Media[] $media_objects
+ *
+ * @return string
+ */
+ public static function mediaTable($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 .= '<div class="loading-image">&nbsp;</div>';
+ $html .= '<div class="media-list">';
+ //-- table header
+ $html .= '<table id="' . $table_id . '"><thead><tr>';
+ $html .= '<th>' . I18N::translate('Media') . '</th>';
+ $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
+ $html .= '<th>' . I18N::translate('Individuals') . '</th>';
+ $html .= '<th>#INDI</th>';
+ $html .= '<th>' . I18N::translate('Families') . '</th>';
+ $html .= '<th>#FAM</th>';
+ $html .= '<th>' . I18N::translate('Sources') . '</th>';
+ $html .= '<th>#SOUR</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
+ $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
+ $html .= '</tr></thead>';
+ //-- table body
+ $html .= '<tbody>';
+
+ 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 .= '<tr' . $class . '>';
+ //-- Object thumbnail
+ $html .= '<td>' . $media_object->displayImage() . '</td>';
+ //-- Object name(s)
+ $html .= '<td>';
+ $html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
+ $html .= FunctionsPrint::highlightSearchHits($name) . '</a>';
+ if (Auth::isEditor($media_object->getTree())) {
+ $html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
+ }
+ $html .= '</td>';
+
+ //-- Linked INDIs
+ $num = count($media_object->linkedIndividuals('OBJE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked FAMs
+ $num = count($media_object->linkedfamilies('OBJE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Linked SOURces
+ $num = count($media_object->linkedSources('OBJE'));
+ $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
+ //-- Last change
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $media_object->LastChangeTimestamp() . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ //-- Last change hidden sort column
+ if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
+ $html .= '<td>' . $media_object->LastChangeTimestamp(true) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ $html .= '</tr>';
+ }
+ }
+ $html .= '</tbody></table></div>';
+
+ 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
+ */
+ public static function surnameTable($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 (a<b) ? -1 : (a>b ? 1 : 0);};
+ jQuery.fn.dataTableExt.oSort["num-desc"]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (a>b) ? -1 : (a<b ? 1 : 0);};
+ jQuery(".surname-list").dataTable( {
+ dom: \'t\',
+ jQueryUI: true,
+ autoWidth: false,
+ paging: false,
+ sorting: [],
+ columns: [
+ /* 0 name */ { dataSort: 1 },
+ /* 1 NAME */ { visible: false },
+ /* 2 count */ { dataSort: 3, class: "center" },
+ /* 3 COUNT */ { visible: false }
+ ],
+ });
+ ');
+
+ if ($script == 'famlist.php') {
+ $col_heading = I18N::translate('Spouses');
+ } else {
+ $col_heading = I18N::translate('Individuals');
+ }
+
+ $html .= '<table class="surname-list">' .
+ '<thead><tr>' .
+ '<th>' . GedcomTag::getLabel('SURN') . '</th>' .
+ '<th></th>' .
+ '<th>' . $col_heading . '</th>' .
+ '<th></th>' .
+ '</tr></thead>';
+
+ $html .= '<tbody>';
+ foreach ($surnames as $surn => $surns) {
+ // Each surname links back to the indi/fam surname list
+ if ($surn) {
+ $url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
+ } else {
+ $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
+ }
+ // Row counter
+ $html .= '<tr>';
+ // Surname
+ $html .= '<td>';
+ // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
+ foreach ($surns as $spfxsurn => $indis) {
+ if ($spfxsurn) {
+ $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
+ } else {
+ // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
+ $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
+ }
+ }
+ $html .= '</td>';
+ // Sort column for name
+ $html .= '<td>' . $surn . '</td>';
+ // Surname count
+ $html .= '<td>';
+ $subtotal = 0;
+ foreach ($surns as $indis) {
+ $subtotal += count($indis);
+ $html .= I18N::number(count($indis)) . '<br>';
+ }
+ // More than one surname variant? Show a subtotal
+ if (count($surns) > 1) {
+ $html .= I18N::number($subtotal);
+ }
+ $html .= '</td>';
+ // add hidden numeric sort column
+ $html .= '<td>' . $subtotal . '</td></tr>';
+ }
+ $html .= '</tbody></table>';
+
+ 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
+ */
+ public static function surnameTagCloud($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 .= '<a style="font-size:' . $size . '%" href="' . $script . '?';
+ if ($surn) {
+ $html .= 'surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
+ } else {
+ $html .= '?alpha=,&amp;ged=' . $WT_TREE->getNameUrl();
+ }
+ if ($totals) {
+ $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
+ } else {
+ $html .= $spfxsurn;
+ }
+ $html .= '</a> ';
+ }
+ }
+
+ return '<div class="tag_cloud">' . $html . '</div>';
+ }
+
+ /**
+ * 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
+ */
+ public static function surnameList($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) . '&amp;ged=' . $tree->getNameUrl();
+ } else {
+ $url = $script . '?alpha=,&amp;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 = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
+
+ if ($totals) {
+ $subtotal = 0;
+ foreach ($surns as $indis) {
+ $subtotal += count($indis);
+ }
+ $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
+ }
+ $html[] = $subhtml;
+
+ }
+ switch ($style) {
+ case 1:
+ return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
+ 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 = '<table class="list_table"><tr>';
+ $html2 .= '<td class="list_value" style="padding: 14px;">';
+
+ foreach ($html as $surns) {
+ $html2 .= $surns . '<br>';
+ $i++;
+ if ($i == $newcol && $i < $count) {
+ $html2 .= '</td><td class="list_value" style="padding: 14px;">';
+ $newcol = $i + ceil($count / $col);
+ }
+ }
+ $html2 .= '</td></tr></table>';
+
+ return $html2;
+ }
+ }
+
+ /**
+ * Print a table of events
+ *
+ * @param string[] $change_ids
+ * @param string $sort
+ *
+ * @return string
+ */
+ public static function changesList($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, '\Fisharebest\Webtrees\Functions\Functions::eventSortName');
+ break;
+ case 'date_asc':
+ uasort($arr, '\Fisharebest\Webtrees\Functions\Functions::eventSort');
+ $arr = array_reverse($arr);
+ break;
+ case 'date_desc':
+ uasort($arr, '\Fisharebest\Webtrees\Functions\Functions::eventSort');
+ }
+ $html = '';
+ foreach ($arr as $value) {
+ $html .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item name2">' . $value['record']->getFullName() . '</a>';
+ $html .= '<div class="indent" style="margin-bottom: 5px;">';
+ if ($value['record'] instanceof Individual) {
+ if ($value['record']->getAddName()) {
+ $html .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item">' . $value['record']->getAddName() . '</a>';
+ }
+ }
+ $html .= /* I18N: [a record was] Changed on <date/time> by <user> */
+ I18N::translate('Changed on %1$s by %2$s', $value['record']->lastChangeTimestamp(), Filter::escapeHtml($value['record']->lastChangeUser()));
+ $html .= '</div>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Print a table of events
+ *
+ * @param string[] $change_ids
+ * @param string $sort
+ *
+ * @return string
+ */
+ public static function changesTable($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 .= '<table id="' . $table_id . '" class="width100">';
+ $html .= '<thead><tr>';
+ $html .= '<th></th>';
+ $html .= '<th>' . I18N::translate('Record') . '</th>';
+ $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
+ $html .= '<th>' . GedcomTag::getLabel('_WT_USER') . '</th>';
+ $html .= '<th>DATE</th>'; //hidden by datatables code
+ $html .= '<th>SORTNAME</th>'; //hidden by datatables code
+ $html .= '</tr></thead><tbody>';
+
+ //-- table body
+ foreach ($change_ids as $change_id) {
+ $record = GedcomRecord::getInstance($change_id, $WT_TREE);
+ if (!$record || !$record->canShow()) {
+ continue;
+ }
+ $html .= '<tr><td>';
+ switch ($record::RECORD_TYPE) {
+ case 'INDI':
+ $icon = $record->getSexImage('small');
+ break;
+ case 'FAM':
+ $icon = '<i class="icon-button_family"></i>';
+ break;
+ case 'OBJE':
+ $icon = '<i class="icon-button_media"></i>';
+ break;
+ case 'NOTE':
+ $icon = '<i class="icon-button_note"></i>';
+ break;
+ case 'SOUR':
+ $icon = '<i class="icon-button_source"></i>';
+ break;
+ case 'REPO':
+ $icon = '<i class="icon-button_repository"></i>';
+ break;
+ default:
+ $icon = '&nbsp;';
+ break;
+ }
+ $html .= '<a href="' . $record->getHtmlUrl() . '">' . $icon . '</a>';
+ $html .= '</td>';
+ ++$n;
+ //-- Record name(s)
+ $name = $record->getFullName();
+ $html .= '<td class="wrap">';
+ $html .= '<a href="' . $record->getHtmlUrl() . '">' . $name . '</a>';
+ if ($record instanceof Individual) {
+ $addname = $record->getAddName();
+ if ($addname) {
+ $html .= '<div class="indent"><a href="' . $record->getHtmlUrl() . '">' . $addname . '</a></div>';
+ }
+ }
+ $html .= "</td>";
+ //-- Last change date/time
+ $html .= '<td class="wrap">' . $record->lastChangeTimestamp() . '</td>';
+ //-- Last change user
+ $html .= '<td class="wrap">' . Filter::escapeHtml($record->lastChangeUser()) . '</td>';
+ //-- change date (sortable) hidden by datatables code
+ $html .= '<td>' . $record->lastChangeTimestamp(true) . '</td>';
+ //-- names (sortable) hidden by datatables code
+ $html .= '<td>' . $record->getSortName() . '</td></tr>';
+ }
+
+ $html .= '</tbody></table>';
+
+ 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
+ */
+ public static function eventsTable($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 (FunctionsDb::getEventsList($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 .= '<table id="' . $table_id . '" class="width100">';
+ $html .= '<thead><tr>';
+ $html .= '<th>' . I18N::translate('Record') . '</th>';
+ $html .= '<th>NAME</th>'; //hidden by datatables code
+ $html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
+ $html .= '<th>DATE</th>'; //hidden by datatables code
+ $html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
+ $html .= '<th>ANNIV</th>';
+ $html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
+ $html .= '</tr></thead><tbody>';
+ }
+
+ $filtered_events[] = $fact;
+ }
+
+ foreach ($filtered_events as $n => $fact) {
+ $record = $fact->getParent();
+ $html .= '<tr>';
+ $html .= '<td>';
+ $html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
+ if ($record instanceof Individual) {
+ $html .= $record->getSexImage();
+ }
+ $html .= '</td>';
+ $html .= '<td>' . $record->getSortName() . '</td>';
+ $html .= '<td>' . $fact->getDate()->display(!Auth::isSearchEngine()) . '</td>';
+ $html .= '<td>' . $n . '</td>';
+ $html .= '<td>' . I18N::number($fact->anniv) . '</td>';
+ $html .= '<td>' . $fact->anniv . '</td>';
+ $html .= '<td>' . $fact->getLabel() . '</td>';
+ $html .= '</tr>';
+ }
+
+ if ($output != 0) {
+ $html .= '</tbody></table>';
+ }
+
+ // 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 .= '<strong>' . $summary . '</strong>';
+ }
+
+ 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
+ */
+ public static function eventsList($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 (FunctionsDb::getEventsList($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 .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
+ if ($record instanceof Individual) {
+ $html .= $record->getSexImage();
+ }
+ $html .= '<br><div class="indent">';
+ $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
+ if ($fact->anniv) {
+ $html .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')';
+ }
+ if (!$fact->getPlace()->isEmpty()) {
+ $html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
+ }
+ $html .= '</div>';
+ }
+
+ // 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 .= "<b>" . $summary . "</b>";
+ }
+
+ return $html;
+ }
+
+ /**
+ * Print a chart by age using Google chart API
+ *
+ * @param integer[] $data
+ * @param string $title
+ *
+ * @return string
+ */
+ public static function chartByAge($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 .= "&amp;chs=725x150"; // size
+ $chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
+ $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
+ $chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
+ $chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
+ $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
+ $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
+ $chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
+ $chart_url .= "&amp;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 .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
+ $chart_url .= "&amp;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 = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
+
+ return $html;
+ }
+
+ /**
+ * Print a chart by decade using Google chart API
+ *
+ * @param integer[] $data
+ * @param string $title
+ *
+ * @return string
+ */
+ public static function chartByDecade($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 .= "&amp;chs=360x150"; // size
+ $chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
+ $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
+ $chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
+ $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
+ $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
+ $chart_url .= "&amp;chxl=0:|&lt;|||"; // <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 .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
+ $chart_url .= "&amp;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 = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
+
+ return $html;
+ }
+}
diff --git a/app/Functions/FunctionsRtl.php b/app/Functions/FunctionsRtl.php
new file mode 100644
index 0000000000..b9cced5edf
--- /dev/null
+++ b/app/Functions/FunctionsRtl.php
@@ -0,0 +1,1154 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// RTL Functions for use in the PDF/HTML reports
+
+use Fisharebest\Webtrees\I18N;
+
+/**
+ * Class FunctionsRtl - common functions
+ */
+class FunctionsRtl {
+ const OPEN_PARENTHESES = '([{';
+
+ const CLOSE_PARENTHESES = ')]}';
+
+ const NUMBERS = '0123456789';
+
+ const NUMBER_PREFIX = '+-'; // Treat these like numbers when at beginning or end of numeric strings
+
+ const NUMBER_PUNCTUATION = '- ,.:/'; // Treat these like numbers when inside numeric strings
+
+ const PUNCTUATION = ',.:;?!';
+
+ private static $previousState;
+
+ private static $currentState;
+
+ private static $waitingText;
+
+ private static $startLTR;
+
+ private static $endLTR;
+
+ private static $startRTL;
+
+ private static $endRTL;
+
+ private static $lenStart;
+
+ private static $lenEnd;
+
+ private static $spanNumber = 0;
+
+ private static $posSpanStart;
+
+ /**
+ * This function strips &lrm; and &rlm; from the input string. It should be used for all
+ * text that has been passed through the PrintReady() function before that text is stored
+ * in the database. The database should NEVER contain these characters.
+ *
+ * @param string $inputText The string from which the &lrm; and &rlm; characters should be stripped
+ *
+ * @return string The input string, with &lrm; and &rlm; stripped
+ */
+ public static function stripLrmRlm($inputText) {
+ return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
+ }
+
+ /**
+ * This function encapsulates all texts in the input with <span dir='xxx'> and </span>
+ * according to the directionality specified.
+ *
+ * @param string $inputText Raw input
+ * @param string $direction Directionality (LTR, BOTH, RTL) default BOTH
+ * @param string $class Additional text to insert into output <span dir="xxx"> (such as 'class="yyy"')
+ *
+ * @return string The string with all texts encapsulated as required
+ */
+ public static function spanLtrRtl($inputText, $direction = 'BOTH', $class = '') {
+ if ($inputText == '') {
+ // Nothing to do
+ return '';
+ }
+ self::$spanNumber++;
+
+ $workingText = str_replace("\n", '<br>', $inputText);
+ $workingText = str_replace(array('<span class="starredname"><br>', '<span<br>class="starredname">'), '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks
+ $workingText = self::stripLrmRlm($workingText); // Get rid of any existing UTF8 control codes
+
+ // $nothing = '&zwnj;'; // Zero Width Non-Joiner (not sure whether this is still needed to work around a TCPDF bug)
+ $nothing = '';
+
+ self::$startLTR = '<LTR>'; // This will become '<span dir="ltr">' at the end
+ self::$endLTR = '</LTR>'; // This will become '</span>' at the end
+ self::$startRTL = '<RTL>'; // This will become '<span dir="rtl">' at the end
+ self::$endRTL = '</RTL>'; // This will become '</span>' at the end
+ self::$lenStart = strlen(self::$startLTR); // RTL version MUST have same length
+ self::$lenEnd = strlen(self::$endLTR); // RTL version MUST have same length
+
+ self::$previousState = '';
+ self::$currentState = strtoupper(I18N::direction());
+ $numberState = false; // Set when we're inside a numeric string
+ $result = '';
+ self::$waitingText = '';
+ $openParDirection = array();
+
+ self::beginCurrentSpan($result);
+
+ while ($workingText != '') {
+ $charArray = self::getChar($workingText, 0); // Get the next ASCII or UTF-8 character
+ $currentLetter = $charArray['letter'];
+ $currentLen = $charArray['length'];
+
+ $openParIndex = strpos(self::OPEN_PARENTHESES, $currentLetter); // Which opening parenthesis is this?
+ $closeParIndex = strpos(self::CLOSE_PARENTHESES, $currentLetter); // Which closing parenthesis is this?
+
+ switch ($currentLetter) {
+ case '<':
+ // Assume this '<' starts an HTML element
+ $endPos = strpos($workingText, '>'); // look for the terminating '>'
+ if ($endPos === false) {
+ $endPos = 0;
+ }
+ $currentLen += $endPos;
+ $element = substr($workingText, 0, $currentLen);
+ $temp = strtolower(substr($element, 0, 3));
+ if (strlen($element) < 7 && $temp == '<br') {
+ if ($numberState) {
+ $numberState = false;
+ if (self::$currentState == 'RTL') {
+ self::$waitingText .= WT_UTF8_PDF;
+ }
+ }
+ self::breakCurrentSpan($result);
+ } elseif (self::$waitingText == '') {
+ $result .= $element;
+ } else {
+ self::$waitingText .= $element;
+ }
+ $workingText = substr($workingText, $currentLen);
+ break;
+ case '&':
+ // Assume this '&' starts an HTML entity
+ $endPos = strpos($workingText, ';'); // look for the terminating ';'
+ if ($endPos === false) {
+ $endPos = 0;
+ }
+ $currentLen += $endPos;
+ $entity = substr($workingText, 0, $currentLen);
+ if (strtolower($entity) == '&nbsp;') {
+ $entity .= '&nbsp;'; // Ensure consistent case for this entity
+ }
+ if (self::$waitingText == '') {
+ $result .= $entity;
+ } else {
+ self::$waitingText .= $entity;
+ }
+ $workingText = substr($workingText, $currentLen);
+ break;
+ case '{':
+ if (substr($workingText, 1, 1) == '{') {
+ // Assume this '{{' starts a TCPDF directive
+ $endPos = strpos($workingText, '}}'); // look for the terminating '}}'
+ if ($endPos === false) {
+ $endPos = 0;
+ }
+ $currentLen = $endPos + 2;
+ $directive = substr($workingText, 0, $currentLen);
+ $workingText = substr($workingText, $currentLen);
+ $result = $result . self::$waitingText . $directive;
+ self::$waitingText = '';
+ break;
+ }
+ default:
+ // Look for strings of numbers with optional leading or trailing + or -
+ // and with optional embedded numeric punctuation
+ if ($numberState) {
+ // If we're inside a numeric string, look for reasons to end it
+ $offset = 0; // Be sure to look at the current character first
+ $charArray = self::getChar($workingText . "\n", $offset);
+ if (strpos(self::NUMBERS, $charArray['letter']) === false) {
+ // This is not a digit. Is it numeric punctuation?
+ if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
+ $offset += 6; // This could be numeric punctuation
+ } elseif (strpos(self::NUMBER_PUNCTUATION, $charArray['letter']) !== false) {
+ $offset += $charArray['length']; // This could be numeric punctuation
+ }
+ // If the next character is a digit, the current character is numeric punctuation
+ $charArray = self::getChar($workingText . "\n", $offset);
+ if (strpos(self::NUMBERS, $charArray['letter']) === false) {
+ // This is not a digit. End the run of digits and punctuation.
+ $numberState = false;
+ if (self::$currentState == 'RTL') {
+ if (strpos(self::NUMBER_PREFIX, $currentLetter) === false) {
+ $currentLetter = WT_UTF8_PDF . $currentLetter;
+ } else {
+ $currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
+ }
+ }
+ }
+ }
+ } else {
+ // If we're outside a numeric string, look for reasons to start it
+ if (strpos(self::NUMBER_PREFIX, $currentLetter) !== false) {
+ // This might be a number lead-in
+ $offset = $currentLen;
+ $nextChar = substr($workingText . "\n", $offset, 1);
+ if (strpos(self::NUMBERS, $nextChar) !== false) {
+ $numberState = true; // We found a digit: the lead-in is therefore numeric
+ if (self::$currentState == 'RTL') {
+ $currentLetter = WT_UTF8_LRE . $currentLetter;
+ }
+ }
+ } elseif (strpos(self::NUMBERS, $currentLetter) !== false) {
+ $numberState = true; // The current letter is a digit
+ if (self::$currentState == 'RTL') {
+ $currentLetter = WT_UTF8_LRE . $currentLetter;
+ }
+ }
+ }
+
+ // Determine the directionality of the current UTF-8 character
+ $newState = self::$currentState;
+ while (true) {
+ if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
+ if (self::$currentState == '') {
+ $newState = 'RTL';
+ break;
+ }
+
+ if (self::$currentState == 'RTL') {
+ break;
+ }
+ // Switch to RTL only if this isn't a solitary RTL letter
+ $tempText = substr($workingText, $currentLen);
+ while ($tempText != '') {
+ $nextCharArray = self::getChar($tempText, 0);
+ $nextLetter = $nextCharArray['letter'];
+ $nextLen = $nextCharArray['length'];
+ $tempText = substr($tempText, $nextLen);
+
+ if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
+ $newState = 'RTL';
+ break 2;
+ }
+
+ if (strpos(self::PUNCTUATION, $nextLetter) !== false || strpos(self::OPEN_PARENTHESES, $nextLetter) !== false) {
+ $newState = 'RTL';
+ break 2;
+ }
+
+ if ($nextLetter === ' ') {
+ break;
+ }
+ $nextLetter .= substr($tempText . "\n", 0, 5);
+ if ($nextLetter === '&nbsp;') {
+ break;
+ }
+ }
+ // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
+ $currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
+ $newState = 'LTR';
+ break;
+ }
+ if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
+ // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
+ $newState = 'LTR';
+ break;
+ }
+ if ($closeParIndex !== false) {
+ // This closing parenthesis has to inherit the matching opening parenthesis' directionality
+ if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
+ $newState = $openParDirection[$closeParIndex];
+ }
+ $openParDirection[$closeParIndex] = '';
+ break;
+ }
+ if ($openParIndex !== false) {
+ // Opening parentheses always inherit the following directionality
+ self::$waitingText .= $currentLetter;
+ $workingText = substr($workingText, $currentLen);
+ while (true) {
+ if ($workingText === '') {
+ break;
+ }
+ if (substr($workingText, 0, 1) === ' ') {
+ // Spaces following this left parenthesis inherit the following directionality too
+ self::$waitingText .= ' ';
+ $workingText = substr($workingText, 1);
+ continue;
+ }
+ if (substr($workingText, 0, 6) === '&nbsp;') {
+ // Spaces following this left parenthesis inherit the following directionality too
+ self::$waitingText .= '&nbsp;';
+ $workingText = substr($workingText, 6);
+ continue;
+ }
+ break;
+ }
+ $openParDirection[$openParIndex] = '?';
+ break 2; // double break because we're waiting for more information
+ }
+
+ // We have a digit or a "normal" special character.
+ //
+ // When this character is not at the start of the input string, it inherits the preceding directionality;
+ // at the start of the input string, it assumes the following directionality.
+ //
+ // Exceptions to this rule will be handled later during final clean-up.
+ //
+ self::$waitingText .= $currentLetter;
+ $workingText = substr($workingText, $currentLen);
+ if (self::$currentState != '') {
+ $result .= self::$waitingText;
+ self::$waitingText = '';
+ }
+ break 2; // double break because we're waiting for more information
+ }
+ if ($newState != self::$currentState) {
+ // A direction change has occurred
+ self::finishCurrentSpan($result, false);
+ self::$previousState = self::$currentState;
+ self::$currentState = $newState;
+ self::beginCurrentSpan($result);
+ }
+ self::$waitingText .= $currentLetter;
+ $workingText = substr($workingText, $currentLen);
+ $result .= self::$waitingText;
+ self::$waitingText = '';
+
+ foreach ($openParDirection as $index => $value) {
+ // Since we now know the proper direction, remember it for all waiting opening parentheses
+ if ($value === '?') {
+ $openParDirection[$index] = self::$currentState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ // We're done. Finish last <span> if necessary
+ if ($numberState) {
+ if (self::$waitingText === '') {
+ if (self::$currentState === 'RTL') {
+ $result .= WT_UTF8_PDF;
+ }
+ } else {
+ if (self::$currentState === 'RTL') {
+ self::$waitingText .= WT_UTF8_PDF;
+ }
+ }
+ }
+ self::finishCurrentSpan($result, true);
+
+ // Get rid of any waiting text
+ if (self::$waitingText != '') {
+ if (I18N::direction() === 'rtl' && self::$currentState === 'LTR') {
+ $result .= self::$startRTL;
+ $result .= self::$waitingText;
+ $result .= self::$endRTL;
+ } else {
+ $result .= self::$startLTR;
+ $result .= self::$waitingText;
+ $result .= self::$endLTR;
+ }
+ self::$waitingText = '';
+ }
+
+ // Lastly, do some more cleanups
+
+ // Move leading RTL numeric strings to following LTR text
+ // (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text)
+ while (substr($result, 0, self::$lenStart + 3) === self::$startRTL . WT_UTF8_LRE) {
+ $spanEnd = strpos($result, self::$endRTL . self::$startLTR);
+ if ($spanEnd === false) {
+ break;
+ }
+ $textSpan = self::stripLrmRlm(substr($result, self::$lenStart + 3, $spanEnd - self::$lenStart - 3));
+ if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') {
+ break;
+ }
+ $result = self::$startLTR . substr($result, self::$lenStart, $spanEnd - self::$lenStart) . substr($result, $spanEnd + self::$lenStart + self::$lenEnd);
+ break;
+ }
+
+ // On RTL pages, put trailing "." in RTL numeric strings into its own RTL span
+ if (I18N::direction() === 'rtl') {
+ $result = str_replace(WT_UTF8_PDF . '.' . self::$endRTL, WT_UTF8_PDF . self::$endRTL . self::$startRTL . '.' . self::$endRTL, $result);
+ }
+
+ // Trim trailing blanks preceding <br> in LTR text
+ while (self::$previousState != 'RTL') {
+ if (strpos($result, ' <LTRbr>') !== false) {
+ $result = str_replace(' <LTRbr>', '<LTRbr>', $result);
+ continue;
+ }
+ if (strpos($result, '&nbsp;<LTRbr>') !== false) {
+ $result = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $result);
+ continue;
+ }
+ if (strpos($result, ' <br>') !== false) {
+ $result = str_replace(' <br>', '<br>', $result);
+ continue;
+ }
+ if (strpos($result, '&nbsp;<br>') !== false) {
+ $result = str_replace('&nbsp;<br>', '<br>', $result);
+ continue;
+ }
+ break; // Neither space nor &nbsp; : we're done
+ }
+
+ // Trim trailing blanks preceding <br> in RTL text
+ while (true) {
+ if (strpos($result, ' <RTLbr>') !== false) {
+ $result = str_replace(' <RTLbr>', '<RTLbr>', $result);
+ continue;
+ }
+ if (strpos($result, '&nbsp;<RTLbr>') !== false) {
+ $result = str_replace('&nbsp;<RTLbr>', '<RTLbr>', $result);
+ continue;
+ }
+ break; // Neither space nor &nbsp; : we're done
+ }
+
+ // Convert '<LTRbr>' and '<RTLbr /'
+ $result = str_replace(array('<LTRbr>', '<RTLbr>'), array(self::$endLTR . '<br>' . self::$startLTR, self::$endRTL . '<br>' . self::$startRTL), $result);
+
+ // Include leading indeterminate directional text in whatever follows
+ if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL && substr($result . "\n", 0, 6) != '<br>') {
+ $leadingText = '';
+ while (true) {
+ if ($result == '') {
+ $result = $leadingText;
+ break;
+ }
+ if (substr($result . "\n", 0, self::$lenStart) != self::$startLTR && substr($result . "\n", 0, self::$lenStart) != self::$startRTL) {
+ $leadingText .= substr($result, 0, 1);
+ $result = substr($result, 1);
+ continue;
+ }
+ $result = substr($result, 0, self::$lenStart) . $leadingText . substr($result, self::$lenStart);
+ break;
+ }
+ }
+
+ // Include solitary "-" and "+" in surrounding RTL text
+ $result = str_replace(array(self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL, self::$endRTL . self::$startLTR . '-' . self::$endLTR . self::$startRTL), array('-', '+'), $result);
+
+ // Remove empty spans
+ $result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
+
+ // Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
+ switch ($direction) {
+ case 'BOTH':
+ case 'both':
+ // LTR text: <span dir="ltr"> text </span>
+ // RTL text: <span dir="rtl"> text </span>
+ $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
+ $eLTR = $nothing . '</span>';
+ $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
+ $eRTL = $nothing . '</span>';
+ break;
+ case 'LTR':
+ case 'ltr':
+ // LTR text: <span dir="ltr"> text </span>
+ // RTL text: text
+ $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
+ $eLTR = $nothing . '</span>';
+ $sRTL = '';
+ $eRTL = '';
+ break;
+ case 'RTL':
+ case 'rtl':
+ default:
+ // LTR text: text
+ // RTL text: <span dir="rtl"> text </span>
+ $sLTR = '';
+ $eLTR = '';
+ $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
+ $eRTL = $nothing . '</span>';
+ break;
+ }
+ $result = str_replace(array(self::$startLTR, self::$endLTR, self::$startRTL, self::$endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
+
+ return $result;
+ }
+
+ /**
+ * Wrap words that have an asterisk suffix in <u> and </u> tags.
+ * This should underline starred names to show the preferred name.
+ *
+ * @param string $textSpan
+ * @param string $direction
+ *
+ * @return string
+ */
+ public static function starredName($textSpan, $direction) {
+ // To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
+ // only when page and span directions are identical.
+ if ($direction === strtoupper(I18N::direction())) {
+ while (true) {
+ $starPos = strpos($textSpan, '*');
+ if ($starPos === false) {
+ break;
+ }
+ $trailingText = substr($textSpan, $starPos + 1);
+ $textSpan = substr($textSpan, 0, $starPos);
+ $wordStart = strrpos($textSpan, ' '); // Find the start of the word
+ if ($wordStart !== false) {
+ $leadingText = substr($textSpan, 0, $wordStart + 1);
+ $wordText = substr($textSpan, $wordStart + 1);
+ } else {
+ $leadingText = '';
+ $wordText = $textSpan;
+ }
+ $textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText;
+ }
+ $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan);
+ // The &nbsp; is a work-around for a TCPDF bug eating blanks.
+ $textSpan = str_replace(array(' <u>', '</u> '), array('&nbsp;<u>', '</u>&nbsp;'), $textSpan);
+ } else {
+ // Text and page directions differ: remove the <span> and </span>
+ $textSpan = preg_replace('~(.*)\*~', '\1', $textSpan);
+ $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan);
+ }
+
+ return $textSpan;
+ }
+
+ /**
+ * Get the next character from an input string
+ *
+ * @param string $text
+ * @param string $offset
+ *
+ * @return array
+ */
+ public static function getChar($text, $offset) {
+
+ if ($text == '') {
+ return array('letter' => '', 'length' => 0);
+ }
+
+ $char = substr($text, $offset, 1);
+ $length = 1;
+ if ((ord($char) & 0xE0) == 0xC0) {
+ $length = 2;
+ }
+ if ((ord($char) & 0xF0) == 0xE0) {
+ $length = 3;
+ }
+ if ((ord($char) & 0xF8) == 0xF0) {
+ $length = 4;
+ }
+ $letter = substr($text, $offset, $length);
+
+ return array('letter' => $letter, 'length' => $length);
+ }
+
+ /**
+ * Insert <br> into current span
+ *
+ * @param string $result
+ */
+ public static function breakCurrentSpan(&$result) {
+ // Interrupt the current span, insert that <br>, and then continue the current span
+ $result .= self::$waitingText;
+ self::$waitingText = '';
+
+ $breakString = '<' . self::$currentState . 'br>';
+ $result .= $breakString;
+
+ return;
+ }
+
+ /**
+ * Begin current span
+ *
+ * @param string $result
+ */
+ public static function beginCurrentSpan(&$result) {
+ if (self::$currentState == 'LTR') {
+ $result .= self::$startLTR;
+ }
+ if (self::$currentState == 'RTL') {
+ $result .= self::$startRTL;
+ }
+
+ self::$posSpanStart = strlen($result);
+ }
+
+ /**
+ * Finish current span
+ *
+ * @param string $result
+ * @param bool $theEnd
+ */
+ public static function finishCurrentSpan(&$result, $theEnd = false) {
+ $textSpan = substr($result, self::$posSpanStart);
+ $result = substr($result, 0, self::$posSpanStart);
+
+ // Get rid of empty spans, so that our check for presence of RTL will work
+ $result = str_replace(array(self::$startLTR . self::$endLTR, self::$startRTL . self::$endRTL), '', $result);
+
+ // Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers.
+ $tempResult = '';
+ while ($textSpan != '') {
+ $posColon = strpos($textSpan, ':');
+ if ($posColon === false) {
+ break;
+ } // No more possible time strings
+ $posLRE = strpos($textSpan, WT_UTF8_LRE);
+ if ($posLRE === false) {
+ break;
+ } // No more numeric strings
+ $posPDF = strpos($textSpan, WT_UTF8_PDF, $posLRE);
+ if ($posPDF === false) {
+ break;
+ } // No more numeric strings
+
+ $tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string
+ $numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string
+ $textSpan = substr($textSpan, $posPDF + 3);
+ $posColon = strpos($numericString, ':');
+ if ($posColon === false) {
+ // Nothing that looks like a time here
+ $tempResult .= $numericString;
+ continue;
+ }
+ $posBlank = strpos($numericString . ' ', ' ');
+ $posNbsp = strpos($numericString . '&nbsp;', '&nbsp;');
+ if ($posBlank < $posNbsp) {
+ $posSeparator = $posBlank;
+ $lengthSeparator = 1;
+ } else {
+ $posSeparator = $posNbsp;
+ $lengthSeparator = 6;
+ }
+ if ($posColon > $posSeparator) {
+ // We have a time string preceded by a blank: Exclude that blank from the numeric string
+ $tempResult .= substr($numericString, 0, $posSeparator);
+ $tempResult .= WT_UTF8_PDF;
+ $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
+ $tempResult .= WT_UTF8_LRE;
+ $numericString = substr($numericString, $posSeparator + $lengthSeparator);
+ }
+
+ $posBlank = strpos($numericString, ' ');
+ $posNbsp = strpos($numericString, '&nbsp;');
+ if ($posBlank === false && $posNbsp === false) {
+ // The time string isn't followed by a blank
+ $textSpan = $numericString . $textSpan;
+ continue;
+ }
+
+ // We have a time string followed by a blank: Exclude that blank from the numeric string
+ if ($posBlank === false) {
+ $posSeparator = $posNbsp;
+ $lengthSeparator = 6;
+ } elseif ($posNbsp === false) {
+ $posSeparator = $posBlank;
+ $lengthSeparator = 1;
+ } elseif ($posBlank < $posNbsp) {
+ $posSeparator = $posBlank;
+ $lengthSeparator = 1;
+ } else {
+ $posSeparator = $posNbsp;
+ $lengthSeparator = 6;
+ }
+ $tempResult .= substr($numericString, 0, $posSeparator);
+ $tempResult .= WT_UTF8_PDF;
+ $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
+ $posSeparator += $lengthSeparator;
+ $numericString = substr($numericString, $posSeparator);
+ $textSpan = WT_UTF8_LRE . $numericString . $textSpan;
+ }
+ $textSpan = $tempResult . $textSpan;
+ $trailingBlanks = '';
+ $trailingBreaks = '';
+
+ /* ****************************** LTR text handling ******************************** */
+
+ if (self::$currentState === 'LTR') {
+ // Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too.
+ if (I18N::direction() === 'rtl' && self::$previousState === 'RTL' && !$theEnd) {
+ $trailingString = '';
+ $savedSpan = $textSpan;
+ while ($textSpan !== '') {
+ // Look for trailing spaces and tentatively move them
+ if (substr($textSpan, -1) === ' ') {
+ $trailingString = ' ' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) === '&nbsp;') {
+ $trailingString = '&nbsp;' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -3) !== WT_UTF8_PDF) {
+ // There is no trailing numeric string
+ $textSpan = $savedSpan;
+ break;
+ }
+
+ // We have a numeric string
+ $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
+ if ($posStartNumber === false) {
+ $posStartNumber = 0;
+ }
+ $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
+ $textSpan = substr($textSpan, 0, $posStartNumber);
+
+ // Look for more spaces and move them too
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingString = ' ' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) == '&nbsp;') {
+ $trailingString = '&nbsp;' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ break;
+ }
+
+ self::$waitingText = $trailingString . self::$waitingText;
+ break;
+ }
+ }
+
+ $savedSpan = $textSpan;
+ // Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingBlanks = ' ' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr('......' . $textSpan, -6) == '&nbsp;') {
+ $trailingBlanks = '&nbsp;' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -6);
+ continue;
+ }
+ break;
+ }
+ while (substr($textSpan, -9) == '<LTRbr>') {
+ $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
+ $textSpan = substr($textSpan, 0, -9);
+ }
+ if ($trailingBreaks != '') {
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingBreaks = ' ' . $trailingBreaks;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr('......' . $textSpan, -6) == '&nbsp;') {
+ $trailingBreaks = '&nbsp;' . $trailingBreaks;
+ $textSpan = substr($textSpan, 0, -6);
+ continue;
+ }
+ break;
+ }
+ self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
+ } else {
+ $textSpan = $savedSpan;
+ }
+
+ $trailingBlanks = '';
+ $trailingPunctuation = '';
+ $trailingID = '';
+ $trailingSeparator = '';
+ $leadingSeparator = '';
+ while (I18N::direction() === 'rtl') {
+ if (strpos($result, self::$startRTL) !== false) {
+ // Remove trailing blanks for inclusion in a separate LTR span
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) === ' ') {
+ $trailingBlanks = ' ' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) === '&nbsp;') {
+ $trailingBlanks = '&nbsp;' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ break;
+ }
+
+ // Remove trailing punctuation for inclusion in a separate LTR span
+ if ($textSpan == '') {
+ $trailingChar = "\n";
+ } else {
+ $trailingChar = substr($textSpan, -1);
+ }
+ if (strpos(self::PUNCTUATION, $trailingChar) !== false) {
+ $trailingPunctuation = $trailingChar;
+ $textSpan = substr($textSpan, 0, -1);
+ }
+ }
+
+ // Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span
+ while (true) {
+ if (substr($textSpan, -1) != ')') {
+ break;
+ } // There is no trailing ')'
+ $posLeftParen = strrpos($textSpan, '(');
+ if ($posLeftParen === false) {
+ break;
+ } // There is no leading '('
+ $temp = self::stripLrmRlm(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes
+
+ // If the parenthesized text doesn't look like an ID number,
+ // we don't want to touch it.
+ // This check won’t work if somebody uses ID numbers with an unusual format.
+ $offset = 1;
+ $charArray = self::getchar($temp, $offset); // Get 1st character of parenthesized text
+ if (strpos(self::NUMBERS, $charArray['letter']) !== false) {
+ break;
+ }
+ $offset += $charArray['length']; // Point at 2nd character of parenthesized text
+ if (strpos(self::NUMBERS, substr($temp, $offset, 1)) === false) {
+ break;
+ }
+ // 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too
+ if (strpos(self::NUMBERS, substr($temp, -2, 1)) === false) {
+ break;
+ }
+
+ $trailingID = substr($textSpan, $posLeftParen);
+ $textSpan = substr($textSpan, 0, $posLeftParen);
+ break;
+ }
+
+ // Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span
+ if ($trailingID != '') {
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingSeparator = ' ' . $trailingSeparator;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) == '&nbsp;') {
+ $trailingSeparator = '&nbsp;' . $trailingSeparator;
+ $textSpan = substr($textSpan, 0, -6);
+ continue;
+ }
+ if (substr($textSpan, -1) == '-') {
+ $trailingSeparator = '-' . $trailingSeparator;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ break;
+ }
+ }
+
+ // Look for " - " preceding the text and remove it for inclusion in a separate LTR span
+ $foundSeparator = false;
+ $savedSpan = $textSpan;
+ while ($textSpan != '') {
+ if (substr($textSpan, 0, 1) == ' ') {
+ $leadingSeparator = ' ' . $leadingSeparator;
+ $textSpan = substr($textSpan, 1);
+ continue;
+ }
+ if (substr($textSpan, 0, 6) == '&nbsp;') {
+ $leadingSeparator = '&nbsp;' . $leadingSeparator;
+ $textSpan = substr($textSpan, 6);
+ continue;
+ }
+ if (substr($textSpan, 0, 1) == '-') {
+ $leadingSeparator = '-' . $leadingSeparator;
+ $textSpan = substr($textSpan, 1);
+ $foundSeparator = true;
+ continue;
+ }
+ break;
+ }
+ if (!$foundSeparator) {
+ $textSpan = $savedSpan;
+ $leadingSeparator = '';
+ }
+ break;
+ }
+
+ // We're done: finish the span
+ $textSpan = self::starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags
+ while (true) {
+ // Remove blanks that precede <LTRbr>
+ if (strpos($textSpan, ' <LTRbr>') !== false) {
+ $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan);
+ continue;
+ }
+ if (strpos($textSpan, '&nbsp;<LTRbr>') !== false) {
+ $textSpan = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $textSpan);
+ continue;
+ }
+ break;
+ }
+ if ($leadingSeparator != '') {
+ $result = $result . self::$startLTR . $leadingSeparator . self::$endLTR;
+ }
+ $result = $result . $textSpan . self::$endLTR;
+ if ($trailingSeparator != '') {
+ $result = $result . self::$startLTR . $trailingSeparator . self::$endLTR;
+ }
+ if ($trailingID != '') {
+ $result = $result . self::$startLTR . $trailingID . self::$endLTR;
+ }
+ if ($trailingPunctuation != '') {
+ $result = $result . self::$startLTR . $trailingPunctuation . self::$endLTR;
+ }
+ if ($trailingBlanks != '') {
+ $result = $result . self::$startLTR . $trailingBlanks . self::$endLTR;
+ }
+ }
+
+ /* ****************************** RTL text handling ******************************** */
+
+ if (self::$currentState == 'RTL') {
+ $savedSpan = $textSpan;
+
+ // Move any trailing <br>, optionally followed by blanks, outside this RTL span
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingBlanks = ' ' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr('......' . $textSpan, -6) == '&nbsp;') {
+ $trailingBlanks = '&nbsp;' . $trailingBlanks;
+ $textSpan = substr($textSpan, 0, -6);
+ continue;
+ }
+ break;
+ }
+ while (substr($textSpan, -9) == '<RTLbr>') {
+ $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
+ $textSpan = substr($textSpan, 0, -9);
+ }
+ if ($trailingBreaks != '') {
+ self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span
+ } else {
+ $textSpan = $savedSpan;
+ }
+
+ // Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too.
+ if (!$theEnd && I18N::direction() !== 'rtl') {
+ $trailingString = '';
+ $savedSpan = $textSpan;
+ while ($textSpan != '') {
+ // Look for trailing spaces and tentatively move them
+ if (substr($textSpan, -1) === ' ') {
+ $trailingString = ' ' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) === '&nbsp;') {
+ $trailingString = '&nbsp;' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -3) !== WT_UTF8_PDF) {
+ // There is no trailing numeric string
+ $textSpan = $savedSpan;
+ break;
+ }
+
+ // We have a numeric string
+ $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
+ if ($posStartNumber === false) {
+ $posStartNumber = 0;
+ }
+ $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
+ $textSpan = substr($textSpan, 0, $posStartNumber);
+
+ // Look for more spaces and move them too
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $trailingString = ' ' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) == '&nbsp;') {
+ $trailingString = '&nbsp;' . $trailingString;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ break;
+ }
+
+ self::$waitingText = $trailingString . self::$waitingText;
+ break;
+ }
+ }
+
+ // Trailing " - " needs to be prefixed to the following span
+ if (!$theEnd && substr('...' . $textSpan, -3) == ' - ') {
+ $textSpan = substr($textSpan, 0, -3);
+ self::$waitingText = ' - ' . self::$waitingText;
+ }
+
+ while (I18N::direction() === 'rtl') {
+ // Look for " - " preceding <RTLbr> and relocate it to the front of the string
+ $posDashString = strpos($textSpan, ' - <RTLbr>');
+ if ($posDashString === false) {
+ break;
+ }
+ $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
+ if ($posStringStart === false) {
+ $posStringStart = 0;
+ } else {
+ $posStringStart += 9;
+ } // Point to the first char following the last <RTLbr>
+
+ $textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3);
+ }
+
+ // Strip leading spaces from the RTL text
+ $countLeadingSpaces = 0;
+ while ($textSpan != '') {
+ if (substr($textSpan, 0, 1) == ' ') {
+ $countLeadingSpaces++;
+ $textSpan = substr($textSpan, 1);
+ continue;
+ }
+ if (substr($textSpan, 0, 6) == '&nbsp;') {
+ $countLeadingSpaces++;
+ $textSpan = substr($textSpan, 6);
+ continue;
+ }
+ break;
+ }
+
+ // Strip trailing spaces from the RTL text
+ $countTrailingSpaces = 0;
+ while ($textSpan != '') {
+ if (substr($textSpan, -1) == ' ') {
+ $countTrailingSpaces++;
+ $textSpan = substr($textSpan, 0, -1);
+ continue;
+ }
+ if (substr($textSpan, -6) == '&nbsp;') {
+ $countTrailingSpaces++;
+ $textSpan = substr($textSpan, 0, -6);
+ continue;
+ }
+ break;
+ }
+
+ // Look for trailing " -", reverse it, and relocate it to the front of the string
+ if (substr($textSpan, -2) === ' -') {
+ $posDashString = strlen($textSpan) - 2;
+ $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
+ if ($posStringStart === false) {
+ $posStringStart = 0;
+ } else {
+ $posStringStart += 9;
+ } // Point to the first char following the last <RTLbr>
+
+ $textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2);
+ }
+
+ if ($countLeadingSpaces != 0) {
+ $newLength = strlen($textSpan) + $countLeadingSpaces;
+ $textSpan = str_pad($textSpan, $newLength, ' ', (I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT));
+ }
+ if ($countTrailingSpaces != 0) {
+ if (I18N::direction() === 'ltr') {
+ if ($trailingBreaks === '') {
+ // Move trailing RTL spaces to front of following LTR span
+ $newLength = strlen(self::$waitingText) + $countTrailingSpaces;
+ self::$waitingText = str_pad(self::$waitingText, $newLength, ' ', STR_PAD_LEFT);
+ }
+ } else {
+ $newLength = strlen($textSpan) + $countTrailingSpaces;
+ $textSpan = str_pad($textSpan, $newLength, ' ', STR_PAD_RIGHT);
+ }
+ }
+
+ // We're done: finish the span
+ $textSpan = self::starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags
+ $result = $result . $textSpan . self::$endRTL;
+ }
+
+ if (self::$currentState != 'LTR' && self::$currentState != 'RTL') {
+ $result = $result . $textSpan;
+ }
+
+ $result .= $trailingBreaks; // Get rid of any waiting <br>
+
+ return;
+ }
+
+ /**
+ * @param string $string
+ * @param int $width
+ * @param string $sep
+ * @param bool $cut
+ *
+ * @return string
+ */
+ public static function utf8WordWrap($string, $width = 75, $sep = "\n", $cut = false) {
+ $out = '';
+ while ($string) {
+ if (mb_strlen($string) <= $width) {
+ // Do not wrap any text that is less than the output area.
+ $out .= $string;
+ $string = '';
+ } else {
+ $sub1 = mb_substr($string, 0, $width + 1);
+ if (mb_substr($string, mb_strlen($sub1) - 1, 1) == ' ') {
+ // include words that end by a space immediately after the area.
+ $sub = $sub1;
+ } else {
+ $sub = mb_substr($string, 0, $width);
+ }
+ $spacepos = strrpos($sub, ' ');
+ if ($spacepos === false) {
+ // No space on line?
+ if ($cut) {
+ $out .= $sub . $sep;
+ $string = mb_substr($string, mb_strlen($sub));
+ } else {
+ $spacepos = strpos($string, ' ');
+ if ($spacepos === false) {
+ $out .= $string;
+ $string = '';
+ } else {
+ $out .= substr($string, 0, $spacepos) . $sep;
+ $string = substr($string, $spacepos + 1);
+ }
+ }
+ } else {
+ // Split at space;
+ $out .= substr($string, 0, $spacepos) . $sep;
+ $string = substr($string, $spacepos + 1);
+ }
+ }
+ }
+
+ return $out;
+ }
+}
diff --git a/app/Functions/functions.php b/app/Functions/functions.php
new file mode 100644
index 0000000000..8a5aa29dd1
--- /dev/null
+++ b/app/Functions/functions.php
@@ -0,0 +1,2225 @@
+<?php
+namespace Fisharebest\Webtrees\Functions;
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Database;
+use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\File;
+use Fisharebest\Webtrees\GedcomRecord;
+use Fisharebest\Webtrees\I18N;
+use Fisharebest\Webtrees\Individual;
+use Fisharebest\Webtrees\Site;
+
+/**
+ * Class Functions - common functions
+ */
+class Functions {
+ /**
+ * Check with the webtrees.net server for the latest version of webtrees.
+ * Fetching the remote file can be slow, so check infrequently, and cache the result.
+ * Pass the current versions of webtrees, PHP and MySQL, as the response
+ * may be different for each. The server logs are used to generate
+ * installation statistics which can be found at http://svn.webtrees.net/statistics.html
+ *
+ * @return null|string
+ */
+ public static function fetchLatestVersion() {
+ $last_update_timestamp = Site::getPreference('LATEST_WT_VERSION_TIMESTAMP');
+ if ($last_update_timestamp < WT_TIMESTAMP - 24 * 60 * 60) {
+ $row = Database::prepare("SHOW VARIABLES LIKE 'version'")->fetchOneRow();
+ $params = '?w=' . WT_VERSION . '&p=' . PHP_VERSION . '&m=' . $row->value . '&o=' . (DIRECTORY_SEPARATOR === '/' ? 'u' : 'w');
+ $latest_version_txt = File::fetchUrl('http://dev.webtrees.net/build/latest-version.txt' . $params);
+ if ($latest_version_txt) {
+ Site::setPreference('LATEST_WT_VERSION', $latest_version_txt);
+ Site::setPreference('LATEST_WT_VERSION_TIMESTAMP', WT_TIMESTAMP);
+
+ return $latest_version_txt;
+ } else {
+ // Cannot connect to server - use cached version (if we have one)
+ return Site::getPreference('LATEST_WT_VERSION');
+ }
+ } else {
+ return Site::getPreference('LATEST_WT_VERSION');
+ }
+ }
+
+ /**
+ * Convert a file upload PHP error code into user-friendly text.
+ *
+ * @param int $error_code
+ *
+ * @return string
+ */
+ public static function fileUploadErrorText($error_code) {
+ switch ($error_code) {
+ case UPLOAD_ERR_OK:
+ return I18N::translate('File successfully uploaded');
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('The uploaded file exceeds the allowed size.');
+ case UPLOAD_ERR_PARTIAL:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('The file was only partially uploaded. Please try again.');
+ case UPLOAD_ERR_NO_FILE:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('No file was received. Please try again.');
+ case UPLOAD_ERR_NO_TMP_DIR:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('The PHP temporary folder is missing.');
+ case UPLOAD_ERR_CANT_WRITE:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('PHP failed to write to disk.');
+ case UPLOAD_ERR_EXTENSION:
+ // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
+ return I18N::translate('PHP blocked the file because of its extension.');
+ default:
+ return 'Error: ' . $error_code;
+ }
+ }
+
+ /**
+ * get a gedcom subrecord
+ *
+ * searches a gedcom record and returns a subrecord of it. A subrecord is defined starting at a
+ * line with level N and all subsequent lines greater than N until the next N level is reached.
+ * For example, the following is a BIRT subrecord:
+ * <code>1 BIRT
+ * 2 DATE 1 JAN 1900
+ * 2 PLAC Phoenix, Maricopa, Arizona</code>
+ * The following example is the DATE subrecord of the above BIRT subrecord:
+ * <code>2 DATE 1 JAN 1900</code>
+ *
+ * @param int $level the N level of the subrecord to get
+ * @param string $tag a gedcom tag or string to search for in the record (ie 1 BIRT or 2 DATE)
+ * @param string $gedrec the parent gedcom record to search in
+ * @param int $num this allows you to specify which matching <var>$tag</var> to get. Oftentimes a
+ * gedcom record will have more that 1 of the same type of subrecord. An individual may have
+ * multiple events for example. Passing $num=1 would get the first 1. Passing $num=2 would get the
+ * second one, etc.
+ *
+ * @return string the subrecord that was found or an empty string "" if not found.
+ */
+ public static function getSubRecord($level, $tag, $gedrec, $num = 1) {
+ if (empty($gedrec)) {
+ return '';
+ }
+ // -- adding \n before and after gedrec
+ $gedrec = "\n" . $gedrec . "\n";
+ $tag = trim($tag);
+ $searchTarget = "~[\n]" . $tag . "[\s]~";
+ $ct = preg_match_all($searchTarget, $gedrec, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+ if ($ct == 0) {
+ return '';
+ }
+ if ($ct < $num) {
+ return '';
+ }
+ $pos1 = $match[$num - 1][0][1];
+ $pos2 = strpos($gedrec, "\n$level", $pos1 + 1);
+ if (!$pos2) {
+ $pos2 = strpos($gedrec, "\n1", $pos1 + 1);
+ }
+ if (!$pos2) {
+ $pos2 = strpos($gedrec, "\nWT_", $pos1 + 1); // WT_SPOUSE, WT_FAMILY_ID ...
+ }
+ if (!$pos2) {
+ return ltrim(substr($gedrec, $pos1));
+ }
+ $subrec = substr($gedrec, $pos1, $pos2 - $pos1);
+
+ return ltrim($subrec);
+ }
+
+ /**
+ * get CONT lines
+ *
+ * get the N+1 CONT or CONC lines of a gedcom subrecord
+ *
+ * @param int $nlevel the level of the CONT lines to get
+ * @param string $nrec the gedcom subrecord to search in
+ *
+ * @return string a string with all CONT lines merged
+ */
+ public static function getCont($nlevel, $nrec) {
+ $text = '';
+
+ $subrecords = explode("\n", $nrec);
+ foreach ($subrecords as $thisSubrecord) {
+ if (substr($thisSubrecord, 0, 2) !== $nlevel . ' ') {
+ continue;
+ }
+ $subrecordType = substr($thisSubrecord, 2, 4);
+ if ($subrecordType === 'CONT') {
+ $text .= "\n" . substr($thisSubrecord, 7);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Sort a list events for the today/upcoming blocks
+ *
+ * @param array $a
+ * @param array $b
+ *
+ * @return int
+ */
+ public static function eventSort($a, $b) {
+ if ($a['jd'] == $b['jd']) {
+ if ($a['anniv'] == $b['anniv']) {
+ return I18N::strcasecmp($a['fact'], $b['fact']);
+ } else {
+ return $a['anniv'] - $b['anniv'];
+ }
+ } else {
+ return $a['jd'] - $b['jd'];
+ }
+ }
+
+ /**
+ * Sort a list events for the today/upcoming blocks
+ *
+ * @param array $a
+ * @param array $b
+ *
+ * @return int
+ */
+ public static function eventSortName($a, $b) {
+ if ($a['jd'] == $b['jd']) {
+ return GedcomRecord::compare($a['record'], $b['record']);
+ } else {
+ return $a['jd'] - $b['jd'];
+ }
+ }
+
+ /**
+ * A multi-key sort
+ * 1. First divide the facts into two arrays one set with dates and one set without dates
+ * 2. Sort each of the two new arrays, the date using the compare date function, the non-dated
+ * using the compare type function
+ * 3. Then merge the arrays back into the original array using the compare type function
+ *
+ * @param Fact[] $arr
+ */
+ public static function sortFacts(&$arr) {
+ $dated = array();
+ $nondated = array();
+ //-- split the array into dated and non-dated arrays
+ $order = 0;
+ foreach ($arr as $event) {
+ $event->sortOrder = $order;
+ $order++;
+ if ($event->getDate()->isOk()) {
+ $dated[] = $event;
+ } else {
+ $nondated[] = $event;
+ }
+ }
+
+ //-- sort each type of array
+ usort($dated, '\Fisharebest\Webtrees\Fact::compareDate');
+ usort($nondated, '\Fisharebest\Webtrees\Fact::compareType');
+
+ //-- merge the arrays back together comparing by Facts
+ $dc = count($dated);
+ $nc = count($nondated);
+ $i = 0;
+ $j = 0;
+ $k = 0;
+ // while there is anything in the dated array continue merging
+ while ($i < $dc) {
+ // compare each fact by type to merge them in order
+ if ($j < $nc && Fact::compareType($dated[$i], $nondated[$j]) > 0) {
+ $arr[$k] = $nondated[$j];
+ $j++;
+ } else {
+ $arr[$k] = $dated[$i];
+ $i++;
+ }
+ $k++;
+ }
+
+ // get anything that might be left in the nondated array
+ while ($j < $nc) {
+ $arr[$k] = $nondated[$j];
+ $j++;
+ $k++;
+ }
+
+ }
+
+ /**
+ * For close family relationships, such as the families tab and the family navigator
+ * Display a tick if both individuals are the same.
+ *
+ * @param Individual $person1
+ * @param Individual $person2
+ *
+ * @return string
+ */
+ public static function getCloseRelationshipName(Individual $person1, Individual $person2) {
+ if ($person1 === $person2) {
+ $label = '<i class="icon-selected" title="' . I18N::translate('self') . '"></i>';
+ } else {
+ $label = self::getRelationshipName(self::getRelationship($person1, $person2));
+ }
+
+ return $label;
+ }
+
+ /**
+ * For facts on the individual/family pages.
+ *
+ * @param Individual $person1
+ * @param Individual $person2
+ *
+ * @return string
+ */
+ public static function getAssociateRelationshipName(Individual $person1, Individual $person2) {
+ if ($person1 === $person2) {
+ $label = I18N::translate('self');
+ } else {
+ $label = get_relationship_name(get_relationship($person1, $person2));
+ }
+
+ return $label;
+ }
+
+ /**
+ * Get relationship between two individuals in the gedcom
+ *
+ * @param Individual $person1 The person to compute the relationship from
+ * @param Individual $person2 The person to compute the relatiohip to
+ * @param int $maxlength The maximum length of path
+ *
+ * @return array|bool An array of nodes on the relationship path, or false if no path found
+ */
+ public static function getRelationship(Individual $person1, Individual $person2, $maxlength = 4) {
+ if ($person1 === $person2) {
+ return false;
+ }
+
+ $spouse_codes = array('M' => 'hus', 'F' => 'wif', 'U' => 'spo');
+ $parent_codes = array('M' => 'fat', 'F' => 'mot', 'U' => 'par');
+ $child_codes = array('M' => 'son', 'F' => 'dau', 'U' => 'chi');
+ $sibling_codes = array('M' => 'bro', 'F' => 'sis', 'U' => 'sib');
+
+ //-- current path nodes
+ $p1nodes = array();
+ //-- ids visited
+ $visited = array();
+
+ //-- set up first node for person1
+ $node1 = array(
+ 'path' => array($person1),
+ 'length' => 0,
+ 'indi' => $person1,
+ 'relations' => array('self'),
+ );
+ $p1nodes[] = $node1;
+
+ $visited[$person1->getXref()] = true;
+
+ $found = false;
+ while (!$found) {
+ //-- search the node list for the shortest path length
+ $shortest = -1;
+ foreach ($p1nodes as $index => $node) {
+ if ($shortest == -1) {
+ $shortest = $index;
+ } else {
+ $node1 = $p1nodes[$shortest];
+ if ($node1['length'] > $node['length']) {
+ $shortest = $index;
+ }
+ }
+ }
+ if ($shortest === -1) {
+ return false;
+ }
+ $node = $p1nodes[$shortest];
+ if ($maxlength == 0 || count($node['path']) <= $maxlength) {
+ $indi = $node['indi'];
+ //-- check all parents and siblings of this node
+ foreach ($indi->getChildFamilies(Auth::PRIV_HIDE) as $family) {
+ $visited[$family->getXref()] = true;
+ foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
+ if (!isset($visited[$spouse->getXref()])) {
+ $node1 = $node;
+ $node1['length']++;
+ $node1['path'][] = $spouse;
+ $node1['indi'] = $spouse;
+ $node1['relations'][] = $parent_codes[$spouse->getSex()];
+ $p1nodes[] = $node1;
+ if ($spouse === $person2) {
+ $found = true;
+ $resnode = $node1;
+ } else {
+ $visited[$spouse->getXref()] = true;
+ }
+ }
+ }
+ foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
+ if (!isset($visited[$child->getXref()])) {
+ $node1 = $node;
+ $node1['length']++;
+ $node1['path'][] = $child;
+ $node1['indi'] = $child;
+ $node1['relations'][] = $sibling_codes[$child->getSex()];
+ $p1nodes[] = $node1;
+ if ($child === $person2) {
+ $found = true;
+ $resnode = $node1;
+ } else {
+ $visited[$child->getXref()] = true;
+ }
+ }
+ }
+ }
+ //-- check all spouses and children of this node
+ foreach ($indi->getSpouseFamilies(Auth::PRIV_HIDE) as $family) {
+ $visited[$family->getXref()] = true;
+ foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
+ if (!in_array($spouse->getXref(), $node1) || !isset($visited[$spouse->getXref()])) {
+ $node1 = $node;
+ $node1['length']++;
+ $node1['path'][] = $spouse;
+ $node1['indi'] = $spouse;
+ $node1['relations'][] = $spouse_codes[$spouse->getSex()];
+ $p1nodes[] = $node1;
+ if ($spouse === $person2) {
+ $found = true;
+ $resnode = $node1;
+ } else {
+ $visited[$spouse->getXref()] = true;
+ }
+ }
+ }
+ foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
+ if (!isset($visited[$child->getXref()])) {
+ $node1 = $node;
+ $node1['length']++;
+ $node1['path'][] = $child;
+ $node1['indi'] = $child;
+ $node1['relations'][] = $child_codes[$child->getSex()];
+ $p1nodes[] = $node1;
+ if ($child === $person2) {
+ $found = true;
+ $resnode = $node1;
+ } else {
+ $visited[$child->getXref()] = true;
+ }
+ }
+ }
+ }
+ }
+ unset($p1nodes[$shortest]);
+ }
+
+ return $resnode;
+ }
+
+ /**
+ * Convert the result of get_relationship() into a relationship name.
+ *
+ * @param mixed[][] $nodes
+ *
+ * @return string
+ */
+ public static function getRelationshipName($nodes) {
+ if (!is_array($nodes)) {
+ return '';
+ }
+ $person1 = $nodes['path'][0];
+ $person2 = $nodes['path'][count($nodes['path']) - 1];
+ $path = array_slice($nodes['relations'], 1);
+ // Look for paths with *specific* names first.
+ // Note that every combination must be listed separately, as the same English
+ // name can be used for many different relationships. e.g.
+ // brother’s wife & husband’s sister = sister-in-law.
+ //
+ // $path is an array of the 12 possible gedcom family relationships:
+ // mother/father/parent
+ // brother/sister/sibling
+ // husband/wife/spouse
+ // son/daughter/child
+ //
+ // This is always the shortest path, so “father, daughter” is “half-sister”, not “sister”.
+ //
+ // This is very repetitive in English, but necessary in order to handle the
+ // complexities of other languages.
+
+ return self::getRelationshipNameFromPath(
+ implode('', array_slice($nodes['relations'], 1)),
+ $nodes['path'][0],
+ $nodes['path'][count($nodes['path']) - 1]
+ );
+ }
+
+ /**
+ * @param int $n
+ * @param string $sex
+ *
+ * @return string
+ */
+ public static function cousinName($n, $sex) {
+ switch ($sex) {
+ case 'M':
+ switch ($n) {
+ case 1:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'first cousin');
+ case 2:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'second cousin');
+ case 3:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'third cousin');
+ case 4:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'fourth cousin');
+ case 5:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'fifth cousin');
+ case 6:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'sixth cousin');
+ case 7:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'seventh cousin');
+ case 8:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'eighth cousin');
+ case 9:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'ninth cousin');
+ case 10:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'tenth cousin');
+ case 11:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'eleventh cousin');
+ case 12:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'twelfth cousin');
+ case 13:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'thirteenth cousin');
+ case 14:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'fourteenth cousin');
+ case 15:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', 'fifteenth cousin');
+ default:
+ /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
+ return I18N::translateContext('MALE', '%s × cousin', I18N::number($n));
+ }
+ case 'F':
+ switch ($n) {
+ case 1:
+ return I18N::translateContext('FEMALE', 'first cousin');
+ case 2:
+ return I18N::translateContext('FEMALE', 'second cousin');
+ case 3:
+ return I18N::translateContext('FEMALE', 'third cousin');
+ case 4:
+ return I18N::translateContext('FEMALE', 'fourth cousin');
+ case 5:
+ return I18N::translateContext('FEMALE', 'fifth cousin');
+ case 6:
+ return I18N::translateContext('FEMALE', 'sixth cousin');
+ case 7:
+ return I18N::translateContext('FEMALE', 'seventh cousin');
+ case 8:
+ return I18N::translateContext('FEMALE', 'eighth cousin');
+ case 9:
+ return I18N::translateContext('FEMALE', 'ninth cousin');
+ case 10:
+ return I18N::translateContext('FEMALE', 'tenth cousin');
+ case 11:
+ return I18N::translateContext('FEMALE', 'eleventh cousin');
+ case 12:
+ return I18N::translateContext('FEMALE', 'twelfth cousin');
+ case 13:
+ return I18N::translateContext('FEMALE', 'thirteenth cousin');
+ case 14:
+ return I18N::translateContext('FEMALE', 'fourteenth cousin');
+ case 15:
+ return I18N::translateContext('FEMALE', 'fifteenth cousin');
+ default:
+ return I18N::translateContext('FEMALE', '%s × cousin', I18N::number($n));
+ }
+ default:
+ switch ($n) {
+ case 1:
+ return I18N::translateContext('MALE/FEMALE', 'first cousin');
+ case 2:
+ return I18N::translateContext('MALE/FEMALE', 'second cousin');
+ case 3:
+ return I18N::translateContext('MALE/FEMALE', 'third cousin');
+ case 4:
+ return I18N::translateContext('MALE/FEMALE', 'fourth cousin');
+ case 5:
+ return I18N::translateContext('MALE/FEMALE', 'fifth cousin');
+ case 6:
+ return I18N::translateContext('MALE/FEMALE', 'sixth cousin');
+ case 7:
+ return I18N::translateContext('MALE/FEMALE', 'seventh cousin');
+ case 8:
+ return I18N::translateContext('MALE/FEMALE', 'eighth cousin');
+ case 9:
+ return I18N::translateContext('MALE/FEMALE', 'ninth cousin');
+ case 10:
+ return I18N::translateContext('MALE/FEMALE', 'tenth cousin');
+ case 11:
+ return I18N::translateContext('MALE/FEMALE', 'eleventh cousin');
+ case 12:
+ return I18N::translateContext('MALE/FEMALE', 'twelfth cousin');
+ case 13:
+ return I18N::translateContext('MALE/FEMALE', 'thirteenth cousin');
+ case 14:
+ return I18N::translateContext('MALE/FEMALE', 'fourteenth cousin');
+ case 15:
+ return I18N::translateContext('MALE/FEMALE', 'fifteenth cousin');
+ default:
+ return I18N::translateContext('MALE/FEMALE', '%s × cousin', I18N::number($n));
+ }
+ }
+ }
+
+ /**
+ * A variation on cousin_name(), for constructs such as “sixth great-nephew”
+ * Currently used only by Spanish relationship names.
+ *
+ * @param int $n
+ * @param string $sex
+ * @param string $relation
+ *
+ * @return string
+ */
+ public static function cousinName2($n, $sex, $relation) {
+ switch ($sex) {
+ case 'M':
+ switch ($n) {
+ case 1: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('MALE', 'first %s', $relation);
+ case 2:
+ return I18N::translateContext('MALE', 'second %s', $relation);
+ case 3:
+ return I18N::translateContext('MALE', 'third %s', $relation);
+ case 4:
+ return I18N::translateContext('MALE', 'fourth %s', $relation);
+ case 5:
+ return I18N::translateContext('MALE', 'fifth %s', $relation);
+ default: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('MALE', '%1$s × %2$s', I18N::number($n), $relation);
+ }
+ case 'F':
+ switch ($n) {
+ case 1: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('FEMALE', 'first %s', $relation);
+ case 2:
+ return I18N::translateContext('FEMALE', 'second %s', $relation);
+ case 3:
+ return I18N::translateContext('FEMALE', 'third %s', $relation);
+ case 4:
+ return I18N::translateContext('FEMALE', 'fourth %s', $relation);
+ case 5:
+ return I18N::translateContext('FEMALE', 'fifth %s', $relation);
+ default: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
+ }
+ default:
+ switch ($n) {
+ case 1: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('MALE/FEMALE', 'first %s', $relation);
+ case 2:
+ return I18N::translateContext('MALE/FEMALE', 'second %s', $relation);
+ case 3:
+ return I18N::translateContext('MALE/FEMALE', 'third %s', $relation);
+ case 4:
+ return I18N::translateContext('MALE/FEMALE', 'fourth %s', $relation);
+ case 5:
+ return I18N::translateContext('MALE/FEMALE', 'fifth %s', $relation);
+ default: // I18N: A Spanish relationship name, such as third great-nephew
+ return I18N::translateContext('MALE/FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
+ }
+ }
+ }
+
+ /**
+ * @param string $path
+ * @param Individual $person1
+ * @param Individual $person2
+ *
+ * @return string
+ */
+ public static function getRelationshipNameFromPath($path, Individual $person1 = null, Individual $person2 = null) {
+ if (!preg_match('/^(mot|fat|par|hus|wif|spo|son|dau|chi|bro|sis|sib)*$/', $path)) {
+ // TODO: Update all the “3 RELA ” values in class_person
+ return '<span class="error">' . $path . '</span>';
+ }
+ // The path does not include the starting person. In some languages, the
+ // translation for a man’s (relative) is different from a woman’s (relative),
+ // due to inflection.
+ $sex1 = $person1 ? $person1->getSex() : 'U';
+
+ // The sex of the last person in the relationship determines the name in
+ // many cases. e.g. great-aunt / great-uncle
+ if (preg_match('/(fat|hus|son|bro)$/', $path)) {
+ $sex2 = 'M';
+ } elseif (preg_match('/(mot|wif|dau|sis)$/', $path)) {
+ $sex2 = 'F';
+ } else {
+ $sex2 = 'U';
+ }
+
+ switch ($path) {
+ case '':
+ return I18N::translate('self');
+ // Level One relationships
+ case 'mot':
+ return I18N::translate('mother');
+ case 'fat':
+ return I18N::translate('father');
+ case 'par':
+ return I18N::translate('parent');
+ case 'hus':
+ if ($person1 && $person2) {
+ foreach ($person1->getSpouseFamilies() as $family) {
+ if ($person2 === $family->getSpouse($person1)) {
+ if ($family->getFacts('_NMR')) {
+ if ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translateContext('MALE', 'ex-partner');
+ } else {
+ return I18N::translateContext('MALE', 'partner');
+ }
+ } elseif ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translate('ex-husband');
+ }
+ }
+ }
+ }
+
+ return I18N::translate('husband');
+ case 'wif':
+ if ($person1 && $person1) {
+ foreach ($person1->getSpouseFamilies() as $family) {
+ if ($person2 === $family->getSpouse($person1)) {
+ if ($family->getFacts('_NMR')) {
+ if ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translateContext('FEMALE', 'ex-partner');
+ } else {
+ return I18N::translateContext('FEMALE', 'partner');
+ }
+ } elseif ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translate('ex-wife');
+ }
+ }
+ }
+ }
+
+ return I18N::translate('wife');
+ case 'spo':
+ if ($person1 && $person2) {
+ foreach ($person1->getSpouseFamilies() as $family) {
+ if ($person2 === $family->getSpouse($person1)) {
+ if ($family->getFacts('_NMR')) {
+ if ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translateContext('MALE/FEMALE', 'ex-partner');
+ } else {
+ return I18N::translateContext('MALE/FEMALE', 'partner');
+ }
+ } elseif ($family->getFacts(WT_EVENTS_DIV)) {
+ return I18N::translate('ex-spouse');
+ }
+ }
+ }
+ }
+
+ return I18N::translate('spouse');
+ case 'son':
+ return I18N::translate('son');
+ case 'dau':
+ return I18N::translate('daughter');
+ case 'chi':
+ return I18N::translate('child');
+ case 'bro':
+ if ($person1 && $person2) {
+ $dob1 = $person1->getBirthDate();
+ $dob2 = $person2->getBirthDate();
+ if ($dob1->isOK() && $dob2->isOK()) {
+ if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
+ // Exclude BEF, AFT, etc.
+ return I18N::translate('twin brother');
+ } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
+ return I18N::translate('younger brother');
+ } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
+ return I18N::translate('elder brother');
+ }
+ }
+ }
+
+ return I18N::translate('brother');
+ case 'sis':
+ if ($person1 && $person2) {
+ $dob1 = $person1->getBirthDate();
+ $dob2 = $person2->getBirthDate();
+ if ($dob1->isOK() && $dob2->isOK()) {
+ if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
+ // Exclude BEF, AFT, etc.
+ return I18N::translate('twin sister');
+ } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
+ return I18N::translate('younger sister');
+ } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
+ return I18N::translate('elder sister');
+ }
+ }
+ }
+
+ return I18N::translate('sister');
+ case 'sib':
+ if ($person1 && $person2) {
+ $dob1 = $person1->getBirthDate();
+ $dob2 = $person2->getBirthDate();
+ if ($dob1->isOK() && $dob2->isOK()) {
+ if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
+ // Exclude BEF, AFT, etc.
+ return I18N::translate('twin sibling');
+ } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
+ return I18N::translate('younger sibling');
+ } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
+ return I18N::translate('elder sibling');
+ }
+ }
+ }
+
+ return I18N::translate('sibling');
+
+ // Level Two relationships
+ case 'brochi':
+ return I18N::translateContext('brother’s child', 'nephew/niece');
+ case 'brodau':
+ return I18N::translateContext('brother’s daughter', 'niece');
+ case 'broson':
+ return I18N::translateContext('brother’s son', 'nephew');
+ case 'browif':
+ return I18N::translateContext('brother’s wife', 'sister-in-law');
+ case 'chichi':
+ return I18N::translateContext('child’s child', 'grandchild');
+ case 'chidau':
+ return I18N::translateContext('child’s daughter', 'granddaughter');
+ case 'chihus':
+ return I18N::translateContext('child’s husband', 'son-in-law');
+ case 'chison':
+ return I18N::translateContext('child’s son', 'grandson');
+ case 'chispo':
+ return I18N::translateContext('child’s spouse', 'son/daughter-in-law');
+ case 'chiwif':
+ return I18N::translateContext('child’s wife', 'daughter-in-law');
+ case 'dauchi':
+ return I18N::translateContext('daughter’s child', 'grandchild');
+ case 'daudau':
+ return I18N::translateContext('daughter’s daughter', 'granddaughter');
+ case 'dauhus':
+ return I18N::translateContext('daughter’s husband', 'son-in-law');
+ case 'dauson':
+ return I18N::translateContext('daughter’s son', 'grandson');
+ case 'fatbro':
+ return I18N::translateContext('father’s brother', 'uncle');
+ case 'fatchi':
+ return I18N::translateContext('father’s child', 'half-sibling');
+ case 'fatdau':
+ return I18N::translateContext('father’s daughter', 'half-sister');
+ case 'fatfat':
+ return I18N::translateContext('father’s father', 'paternal grandfather');
+ case 'fatmot':
+ return I18N::translateContext('father’s mother', 'paternal grandmother');
+ case 'fatpar':
+ return I18N::translateContext('father’s parent', 'paternal grandparent');
+ case 'fatsib':
+ return I18N::translateContext('father’s sibling', 'aunt/uncle');
+ case 'fatsis':
+ return I18N::translateContext('father’s sister', 'aunt');
+ case 'fatson':
+ return I18N::translateContext('father’s son', 'half-brother');
+ case 'fatwif':
+ return I18N::translateContext('father’s wife', 'step-mother');
+ case 'husbro':
+ return I18N::translateContext('husband’s brother', 'brother-in-law');
+ case 'huschi':
+ return I18N::translateContext('husband’s child', 'step-child');
+ case 'husdau':
+ return I18N::translateContext('husband’s daughter', 'step-daughter');
+ case 'husfat':
+ return I18N::translateContext('husband’s father', 'father-in-law');
+ case 'husmot':
+ return I18N::translateContext('husband’s mother', 'mother-in-law');
+ case 'hussib':
+ return I18N::translateContext('husband’s sibling', 'brother/sister-in-law');
+ case 'hussis':
+ return I18N::translateContext('husband’s sister', 'sister-in-law');
+ case 'husson':
+ return I18N::translateContext('husband’s son', 'step-son');
+ case 'motbro':
+ return I18N::translateContext('mother’s brother', 'uncle');
+ case 'motchi':
+ return I18N::translateContext('mother’s child', 'half-sibling');
+ case 'motdau':
+ return I18N::translateContext('mother’s daughter', 'half-sister');
+ case 'motfat':
+ return I18N::translateContext('mother’s father', 'maternal grandfather');
+ case 'mothus':
+ return I18N::translateContext('mother’s husband', 'step-father');
+ case 'motmot':
+ return I18N::translateContext('mother’s mother', 'maternal grandmother');
+ case 'motpar':
+ return I18N::translateContext('mother’s parent', 'maternal grandparent');
+ case 'motsib':
+ return I18N::translateContext('mother’s sibling', 'aunt/uncle');
+ case 'motsis':
+ return I18N::translateContext('mother’s sister', 'aunt');
+ case 'motson':
+ return I18N::translateContext('mother’s son', 'half-brother');
+ case 'parbro':
+ return I18N::translateContext('parent’s brother', 'uncle');
+ case 'parchi':
+ return I18N::translateContext('parent’s child', 'half-sibling');
+ case 'pardau':
+ return I18N::translateContext('parent’s daughter', 'half-sister');
+ case 'parfat':
+ return I18N::translateContext('parent’s father', 'grandfather');
+ case 'parmot':
+ return I18N::translateContext('parent’s mother', 'grandmother');
+ case 'parpar':
+ return I18N::translateContext('parent’s parent', 'grandparent');
+ case 'parsib':
+ return I18N::translateContext('parent’s sibling', 'aunt/uncle');
+ case 'parsis':
+ return I18N::translateContext('parent’s sister', 'aunt');
+ case 'parson':
+ return I18N::translateContext('parent’s son', 'half-brother');
+ case 'parspo':
+ return I18N::translateContext('parent’s spouse', 'step-parent');
+ case 'sibchi':
+ return I18N::translateContext('sibling’s child', 'nephew/niece');
+ case 'sibdau':
+ return I18N::translateContext('sibling’s daughter', 'niece');
+ case 'sibson':
+ return I18N::translateContext('sibling’s son', 'nephew');
+ case 'sibspo':
+ return I18N::translateContext('sibling’s spouse', 'brother/sister-in-law');
+ case 'sischi':
+ return I18N::translateContext('sister’s child', 'nephew/niece');
+ case 'sisdau':
+ return I18N::translateContext('sister’s daughter', 'niece');
+ case 'sishus':
+ return I18N::translateContext('sister’s husband', 'brother-in-law');
+ case 'sisson':
+ return I18N::translateContext('sister’s son', 'nephew');
+ case 'sonchi':
+ return I18N::translateContext('son’s child', 'grandchild');
+ case 'sondau':
+ return I18N::translateContext('son’s daughter', 'granddaughter');
+ case 'sonson':
+ return I18N::translateContext('son’s son', 'grandson');
+ case 'sonwif':
+ return I18N::translateContext('son’s wife', 'daughter-in-law');
+ case 'spobro':
+ return I18N::translateContext('spouse’s brother', 'brother-in-law');
+ case 'spochi':
+ return I18N::translateContext('spouse’s child', 'step-child');
+ case 'spodau':
+ return I18N::translateContext('spouse’s daughter', 'step-daughter');
+ case 'spofat':
+ return I18N::translateContext('spouse’s father', 'father-in-law');
+ case 'spomot':
+ return I18N::translateContext('spouse’s mother', 'mother-in-law');
+ case 'sposis':
+ return I18N::translateContext('spouse’s sister', 'sister-in-law');
+ case 'sposon':
+ return I18N::translateContext('spouse’s son', 'step-son');
+ case 'spopar':
+ return I18N::translateContext('spouse’s parent', 'mother/father-in-law');
+ case 'sposib':
+ return I18N::translateContext('spouse’s sibling', 'brother/sister-in-law');
+ case 'wifbro':
+ return I18N::translateContext('wife’s brother', 'brother-in-law');
+ case 'wifchi':
+ return I18N::translateContext('wife’s child', 'step-child');
+ case 'wifdau':
+ return I18N::translateContext('wife’s daughter', 'step-daughter');
+ case 'wiffat':
+ return I18N::translateContext('wife’s father', 'father-in-law');
+ case 'wifmot':
+ return I18N::translateContext('wife’s mother', 'mother-in-law');
+ case 'wifsib':
+ return I18N::translateContext('wife’s sibling', 'brother/sister-in-law');
+ case 'wifsis':
+ return I18N::translateContext('wife’s sister', 'sister-in-law');
+ case 'wifson':
+ return I18N::translateContext('wife’s son', 'step-son');
+
+ // Level Three relationships
+ // I have commented out some of the unknown-sex relationships that are unlikely to to occur.
+ // Feel free to add them in, if you think they might be needed
+ case 'brochichi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s child’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s child’s child', 'great-nephew/niece');
+ }
+ case 'brochidau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s child’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s child’s daughter', 'great-niece');
+ }
+ case 'brochison':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s child’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s child’s son', 'great-nephew');
+ }
+ case 'brodauchi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s daughter’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s daughter’s child', 'great-nephew/niece');
+ }
+ case 'brodaudau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s daughter’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s daughter’s daughter', 'great-niece');
+ }
+ case 'brodauhus':
+ return I18N::translateContext('brother’s daughter’s husband', 'nephew-in-law');
+ case 'brodauson':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s daughter’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s daughter’s son', 'great-nephew');
+ }
+ case 'brosonchi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s son’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s son’s child', 'great-nephew/niece');
+ }
+ case 'brosondau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s son’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s son’s daughter', 'great-niece');
+ }
+ case 'brosonson':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s son’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) brother’s son’s son', 'great-nephew');
+ }
+ case 'brosonwif':
+ return I18N::translateContext('brother’s son’s wife', 'niece-in-law');
+ case 'browifbro':
+ return I18N::translateContext('brother’s wife’s brother', 'brother-in-law');
+ case 'browifsib':
+ return I18N::translateContext('brother’s wife’s sibling', 'brother/sister-in-law');
+ case 'browifsis':
+ return I18N::translateContext('brother’s wife’s sister', 'sister-in-law');
+ case 'chichichi':
+ return I18N::translateContext('child’s child’s child', 'great-grandchild');
+ case 'chichidau':
+ return I18N::translateContext('child’s child’s daughter', 'great-granddaughter');
+ case 'chichison':
+ return I18N::translateContext('child’s child’s son', 'great-grandson');
+ case 'chidauchi':
+ return I18N::translateContext('child’s daughter’s child', 'great-grandchild');
+ case 'chidaudau':
+ return I18N::translateContext('child’s daughter’s daughter', 'great-granddaughter');
+ case 'chidauhus':
+ return I18N::translateContext('child’s daughter’s husband', 'granddaughter’s husband');
+ case 'chidauson':
+ return I18N::translateContext('child’s daughter’s son', 'great-grandson');
+ case 'chisonchi':
+ return I18N::translateContext('child’s son’s child', 'great-grandchild');
+ case 'chisondau':
+ return I18N::translateContext('child’s son’s daughter', 'great-granddaughter');
+ case 'chisonson':
+ return I18N::translateContext('child’s son’s son', 'great-grandson');
+ case 'chisonwif':
+ return I18N::translateContext('child’s son’s wife', 'grandson’s wife');
+ case 'dauchichi':
+ return I18N::translateContext('daughter’s child’s child', 'great-grandchild');
+ case 'dauchidau':
+ return I18N::translateContext('daughter’s child’s daughter', 'great-granddaughter');
+ case 'dauchison':
+ return I18N::translateContext('daughter’s child’s son', 'great-grandson');
+ case 'daudauchi':
+ return I18N::translateContext('daughter’s daughter’s child', 'great-grandchild');
+ case 'daudaudau':
+ return I18N::translateContext('daughter’s daughter’s daughter', 'great-granddaughter');
+ case 'daudauhus':
+ return I18N::translateContext('daughter’s daughter’s husband', 'granddaughter’s husband');
+ case 'daudauson':
+ return I18N::translateContext('daughter’s daughter’s son', 'great-grandson');
+ case 'dauhusfat':
+ return I18N::translateContext('daughter’s husband’s father', 'son-in-law’s father');
+ case 'dauhusmot':
+ return I18N::translateContext('daughter’s husband’s mother', 'son-in-law’s mother');
+ case 'dauhuspar':
+ return I18N::translateContext('daughter’s husband’s parent', 'son-in-law’s parent');
+ case 'dausonchi':
+ return I18N::translateContext('daughter’s son’s child', 'great-grandchild');
+ case 'dausondau':
+ return I18N::translateContext('daughter’s son’s daughter', 'great-granddaughter');
+ case 'dausonson':
+ return I18N::translateContext('daughter’s son’s son', 'great-grandson');
+ case 'dausonwif':
+ return I18N::translateContext('daughter’s son’s wife', 'grandson’s wife');
+ case 'fatbrochi':
+ return I18N::translateContext('father’s brother’s child', 'first cousin');
+ case 'fatbrodau':
+ return I18N::translateContext('father’s brother’s daughter', 'first cousin');
+ case 'fatbroson':
+ return I18N::translateContext('father’s brother’s son', 'first cousin');
+ case 'fatbrowif':
+ return I18N::translateContext('father’s brother’s wife', 'aunt');
+ case 'fatfatbro':
+ return I18N::translateContext('father’s father’s brother', 'great-uncle');
+ case 'fatfatfat':
+ return I18N::translateContext('father’s father’s father', 'great-grandfather');
+ case 'fatfatmot':
+ return I18N::translateContext('father’s father’s mother', 'great-grandmother');
+ case 'fatfatpar':
+ return I18N::translateContext('father’s father’s parent', 'great-grandparent');
+ case 'fatfatsib':
+ return I18N::translateContext('father’s father’s sibling', 'great-aunt/uncle');
+ case 'fatfatsis':
+ return I18N::translateContext('father’s father’s sister', 'great-aunt');
+ case 'fatmotbro':
+ return I18N::translateContext('father’s mother’s brother', 'great-uncle');
+ case 'fatmotfat':
+ return I18N::translateContext('father’s mother’s father', 'great-grandfather');
+ case 'fatmotmot':
+ return I18N::translateContext('father’s mother’s mother', 'great-grandmother');
+ case 'fatmotpar':
+ return I18N::translateContext('father’s mother’s parent', 'great-grandparent');
+ case 'fatmotsib':
+ return I18N::translateContext('father’s mother’s sibling', 'great-aunt/uncle');
+ case 'fatmotsis':
+ return I18N::translateContext('father’s mother’s sister', 'great-aunt');
+ case 'fatparbro':
+ return I18N::translateContext('father’s parent’s brother', 'great-uncle');
+ case 'fatparfat':
+ return I18N::translateContext('father’s parent’s father', 'great-grandfather');
+ case 'fatparmot':
+ return I18N::translateContext('father’s parent’s mother', 'great-grandmother');
+ case 'fatparpar':
+ return I18N::translateContext('father’s parent’s parent', 'great-grandparent');
+ case 'fatparsib':
+ return I18N::translateContext('father’s parent’s sibling', 'great-aunt/uncle');
+ case 'fatparsis':
+ return I18N::translateContext('father’s parent’s sister', 'great-aunt');
+ case 'fatsischi':
+ return I18N::translateContext('father’s sister’s child', 'first cousin');
+ case 'fatsisdau':
+ return I18N::translateContext('father’s sister’s daughter', 'first cousin');
+ case 'fatsishus':
+ return I18N::translateContext('father’s sister’s husband', 'uncle');
+ case 'fatsisson':
+ return I18N::translateContext('father’s sister’s son', 'first cousin');
+ case 'fatwifchi':
+ return I18N::translateContext('father’s wife’s child', 'step-sibling');
+ case 'fatwifdau':
+ return I18N::translateContext('father’s wife’s daughter', 'step-sister');
+ case 'fatwifson':
+ return I18N::translateContext('father’s wife’s son', 'step-brother');
+ case 'husbrowif':
+ return I18N::translateContext('husband’s brother’s wife', 'sister-in-law');
+ case 'hussishus':
+ return I18N::translateContext('husband’s sister’s husband', 'brother-in-law');
+ case 'motbrochi':
+ return I18N::translateContext('mother’s brother’s child', 'first cousin');
+ case 'motbrodau':
+ return I18N::translateContext('mother’s brother’s daughter', 'first cousin');
+ case 'motbroson':
+ return I18N::translateContext('mother’s brother’s son', 'first cousin');
+ case 'motbrowif':
+ return I18N::translateContext('mother’s brother’s wife', 'aunt');
+ case 'motfatbro':
+ return I18N::translateContext('mother’s father’s brother', 'great-uncle');
+ case 'motfatfat':
+ return I18N::translateContext('mother’s father’s father', 'great-grandfather');
+ case 'motfatmot':
+ return I18N::translateContext('mother’s father’s mother', 'great-grandmother');
+ case 'motfatpar':
+ return I18N::translateContext('mother’s father’s parent', 'great-grandparent');
+ case 'motfatsib':
+ return I18N::translateContext('mother’s father’s sibling', 'great-aunt/uncle');
+ case 'motfatsis':
+ return I18N::translateContext('mother’s father’s sister', 'great-aunt');
+ case 'mothuschi':
+ return I18N::translateContext('mother’s husband’s child', 'step-sibling');
+ case 'mothusdau':
+ return I18N::translateContext('mother’s husband’s daughter', 'step-sister');
+ case 'mothusson':
+ return I18N::translateContext('mother’s husband’s son', 'step-brother');
+ case 'motmotbro':
+ return I18N::translateContext('mother’s mother’s brother', 'great-uncle');
+ case 'motmotfat':
+ return I18N::translateContext('mother’s mother’s father', 'great-grandfather');
+ case 'motmotmot':
+ return I18N::translateContext('mother’s mother’s mother', 'great-grandmother');
+ case 'motmotpar':
+ return I18N::translateContext('mother’s mother’s parent', 'great-grandparent');
+ case 'motmotsib':
+ return I18N::translateContext('mother’s mother’s sibling', 'great-aunt/uncle');
+ case 'motmotsis':
+ return I18N::translateContext('mother’s mother’s sister', 'great-aunt');
+ case 'motparbro':
+ return I18N::translateContext('mother’s parent’s brother', 'great-uncle');
+ case 'motparfat':
+ return I18N::translateContext('mother’s parent’s father', 'great-grandfather');
+ case 'motparmot':
+ return I18N::translateContext('mother’s parent’s mother', 'great-grandmother');
+ case 'motparpar':
+ return I18N::translateContext('mother’s parent’s parent', 'great-grandparent');
+ case 'motparsib':
+ return I18N::translateContext('mother’s parent’s sibling', 'great-aunt/uncle');
+ case 'motparsis':
+ return I18N::translateContext('mother’s parent’s sister', 'great-aunt');
+ case 'motsischi':
+ return I18N::translateContext('mother’s sister’s child', 'first cousin');
+ case 'motsisdau':
+ return I18N::translateContext('mother’s sister’s daughter', 'first cousin');
+ case 'motsishus':
+ return I18N::translateContext('mother’s sister’s husband', 'uncle');
+ case 'motsisson':
+ return I18N::translateContext('mother’s sister’s son', 'first cousin');
+ case 'parbrowif':
+ return I18N::translateContext('parent’s brother’s wife', 'aunt');
+ case 'parfatbro':
+ return I18N::translateContext('parent’s father’s brother', 'great-uncle');
+ case 'parfatfat':
+ return I18N::translateContext('parent’s father’s father', 'great-grandfather');
+ case 'parfatmot':
+ return I18N::translateContext('parent’s father’s mother', 'great-grandmother');
+ case 'parfatpar':
+ return I18N::translateContext('parent’s father’s parent', 'great-grandparent');
+ case 'parfatsib':
+ return I18N::translateContext('parent’s father’s sibling', 'great-aunt/uncle');
+ case 'parfatsis':
+ return I18N::translateContext('parent’s father’s sister', 'great-aunt');
+ case 'parmotbro':
+ return I18N::translateContext('parent’s mother’s brother', 'great-uncle');
+ case 'parmotfat':
+ return I18N::translateContext('parent’s mother’s father', 'great-grandfather');
+ case 'parmotmot':
+ return I18N::translateContext('parent’s mother’s mother', 'great-grandmother');
+ case 'parmotpar':
+ return I18N::translateContext('parent’s mother’s parent', 'great-grandparent');
+ case 'parmotsib':
+ return I18N::translateContext('parent’s mother’s sibling', 'great-aunt/uncle');
+ case 'parmotsis':
+ return I18N::translateContext('parent’s mother’s sister', 'great-aunt');
+ case 'parparbro':
+ return I18N::translateContext('parent’s parent’s brother', 'great-uncle');
+ case 'parparfat':
+ return I18N::translateContext('parent’s parent’s father', 'great-grandfather');
+ case 'parparmot':
+ return I18N::translateContext('parent’s parent’s mother', 'great-grandmother');
+ case 'parparpar':
+ return I18N::translateContext('parent’s parent’s parent', 'great-grandparent');
+ case 'parparsib':
+ return I18N::translateContext('parent’s parent’s sibling', 'great-aunt/uncle');
+ case 'parparsis':
+ return I18N::translateContext('parent’s parent’s sister', 'great-aunt');
+ case 'parsishus':
+ return I18N::translateContext('parent’s sister’s husband', 'uncle');
+ case 'parspochi':
+ return I18N::translateContext('parent’s spouse’s child', 'step-sibling');
+ case 'parspodau':
+ return I18N::translateContext('parent’s spouse’s daughter', 'step-sister');
+ case 'parsposon':
+ return I18N::translateContext('parent’s spouse’s son', 'step-brother');
+ case 'sibchichi':
+ return I18N::translateContext('sibling’s child’s child', 'great-nephew/niece');
+ case 'sibchidau':
+ return I18N::translateContext('sibling’s child’s daughter', 'great-niece');
+ case 'sibchison':
+ return I18N::translateContext('sibling’s child’s son', 'great-nephew');
+ case 'sibdauchi':
+ return I18N::translateContext('sibling’s daughter’s child', 'great-nephew/niece');
+ case 'sibdaudau':
+ return I18N::translateContext('sibling’s daughter’s daughter', 'great-niece');
+ case 'sibdauhus':
+ return I18N::translateContext('sibling’s daughter’s husband', 'nephew-in-law');
+ case 'sibdauson':
+ return I18N::translateContext('sibling’s daughter’s son', 'great-nephew');
+ case 'sibsonchi':
+ return I18N::translateContext('sibling’s son’s child', 'great-nephew/niece');
+ case 'sibsondau':
+ return I18N::translateContext('sibling’s son’s daughter', 'great-niece');
+ case 'sibsonson':
+ return I18N::translateContext('sibling’s son’s son', 'great-nephew');
+ case 'sibsonwif':
+ return I18N::translateContext('sibling’s son’s wife', 'niece-in-law');
+ case 'sischichi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s child’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s child’s child', 'great-nephew/niece');
+ }
+ case 'sischidau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s child’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s child’s daughter', 'great-niece');
+ }
+ case 'sischison':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s child’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s child’s son', 'great-nephew');
+ }
+ case 'sisdauchi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s daughter’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s daughter’s child', 'great-nephew/niece');
+ }
+ case 'sisdaudau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s daughter’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s daughter’s daughter', 'great-niece');
+ }
+ case 'sisdauhus':
+ return I18N::translateContext('sisters’s daughter’s husband', 'nephew-in-law');
+ case 'sisdauson':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s daughter’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s daughter’s son', 'great-nephew');
+ }
+ case 'sishusbro':
+ return I18N::translateContext('sister’s husband’s brother', 'brother-in-law');
+ case 'sishussib':
+ return I18N::translateContext('sister’s husband’s sibling', 'brother/sister-in-law');
+ case 'sishussis':
+ return I18N::translateContext('sister’s husband’s sister', 'sister-in-law');
+ case 'sissonchi':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s son’s child', 'great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s son’s child', 'great-nephew/niece');
+ }
+ case 'sissondau':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s son’s daughter', 'great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s son’s daughter', 'great-niece');
+ }
+ case 'sissonson':
+ if ($sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s son’s son', 'great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) sister’s son’s son', 'great-nephew');
+ }
+ case 'sissonwif':
+ return I18N::translateContext('sisters’s son’s wife', 'niece-in-law');
+ case 'sonchichi':
+ return I18N::translateContext('son’s child’s child', 'great-grandchild');
+ case 'sonchidau':
+ return I18N::translateContext('son’s child’s daughter', 'great-granddaughter');
+ case 'sonchison':
+ return I18N::translateContext('son’s child’s son', 'great-grandson');
+ case 'sondauchi':
+ return I18N::translateContext('son’s daughter’s child', 'great-grandchild');
+ case 'sondaudau':
+ return I18N::translateContext('son’s daughter’s daughter', 'great-granddaughter');
+ case 'sondauhus':
+ return I18N::translateContext('son’s daughter’s husband', 'granddaughter’s husband');
+ case 'sondauson':
+ return I18N::translateContext('son’s daughter’s son', 'great-grandson');
+ case 'sonsonchi':
+ return I18N::translateContext('son’s son’s child', 'great-grandchild');
+ case 'sonsondau':
+ return I18N::translateContext('son’s son’s daughter', 'great-granddaughter');
+ case 'sonsonson':
+ return I18N::translateContext('son’s son’s son', 'great-grandson');
+ case 'sonsonwif':
+ return I18N::translateContext('son’s son’s wife', 'grandson’s wife');
+ case 'sonwiffat':
+ return I18N::translateContext('son’s wife’s father', 'daughter-in-law’s father');
+ case 'sonwifmot':
+ return I18N::translateContext('son’s wife’s mother', 'daughter-in-law’s mother');
+ case 'sonwifpar':
+ return I18N::translateContext('son’s wife’s parent', 'daughter-in-law’s parent');
+ case 'wifbrowif':
+ return I18N::translateContext('wife’s brother’s wife', 'sister-in-law');
+ case 'wifsishus':
+ return I18N::translateContext('wife’s sister’s husband', 'brother-in-law');
+
+ // Some “special case” level four relationships that have specific names in certain languages
+ case 'fatfatbrowif':
+ return I18N::translateContext('father’s father’s brother’s wife', 'great-aunt');
+ case 'fatfatsibspo':
+ return I18N::translateContext('father’s father’s sibling’s spouse', 'great-aunt/uncle');
+ case 'fatfatsishus':
+ return I18N::translateContext('father’s father’s sister’s husband', 'great-uncle');
+ case 'fatmotbrowif':
+ return I18N::translateContext('father’s mother’s brother’s wife', 'great-aunt');
+ case 'fatmotsibspo':
+ return I18N::translateContext('father’s mother’s sibling’s spouse', 'great-aunt/uncle');
+ case 'fatmotsishus':
+ return I18N::translateContext('father’s mother’s sister’s husband', 'great-uncle');
+ case 'fatparbrowif':
+ return I18N::translateContext('father’s parent’s brother’s wife', 'great-aunt');
+ case 'fatparsibspo':
+ return I18N::translateContext('father’s parent’s sibling’s spouse', 'great-aunt/uncle');
+ case 'fatparsishus':
+ return I18N::translateContext('father’s parent’s sister’s husband', 'great-uncle');
+ case 'motfatbrowif':
+ return I18N::translateContext('mother’s father’s brother’s wife', 'great-aunt');
+ case 'motfatsibspo':
+ return I18N::translateContext('mother’s father’s sibling’s spouse', 'great-aunt/uncle');
+ case 'motfatsishus':
+ return I18N::translateContext('mother’s father’s sister’s husband', 'great-uncle');
+ case 'motmotbrowif':
+ return I18N::translateContext('mother’s mother’s brother’s wife', 'great-aunt');
+ case 'motmotsibspo':
+ return I18N::translateContext('mother’s mother’s sibling’s spouse', 'great-aunt/uncle');
+ case 'motmotsishus':
+ return I18N::translateContext('mother’s mother’s sister’s husband', 'great-uncle');
+ case 'motparbrowif':
+ return I18N::translateContext('mother’s parent’s brother’s wife', 'great-aunt');
+ case 'motparsibspo':
+ return I18N::translateContext('mother’s parent’s sibling’s spouse', 'great-aunt/uncle');
+ case 'motparsishus':
+ return I18N::translateContext('mother’s parent’s sister’s husband', 'great-uncle');
+ case 'parfatbrowif':
+ return I18N::translateContext('parent’s father’s brother’s wife', 'great-aunt');
+ case 'parfatsibspo':
+ return I18N::translateContext('parent’s father’s sibling’s spouse', 'great-aunt/uncle');
+ case 'parfatsishus':
+ return I18N::translateContext('parent’s father’s sister’s husband', 'great-uncle');
+ case 'parmotbrowif':
+ return I18N::translateContext('parent’s mother’s brother’s wife', 'great-aunt');
+ case 'parmotsibspo':
+ return I18N::translateContext('parent’s mother’s sibling’s spouse', 'great-aunt/uncle');
+ case 'parmotsishus':
+ return I18N::translateContext('parent’s mother’s sister’s husband', 'great-uncle');
+ case 'parparbrowif':
+ return I18N::translateContext('parent’s parent’s brother’s wife', 'great-aunt');
+ case 'parparsibspo':
+ return I18N::translateContext('parent’s parent’s sibling’s spouse', 'great-aunt/uncle');
+ case 'parparsishus':
+ return I18N::translateContext('parent’s parent’s sister’s husband', 'great-uncle');
+ case 'fatfatbrodau':
+ return I18N::translateContext('father’s father’s brother’s daughter', 'first cousin once removed ascending');
+ case 'fatfatbroson':
+ return I18N::translateContext('father’s father’s brother’s son', 'first cousin once removed ascending');
+ case 'fatfatbrochi':
+ return I18N::translateContext('father’s father’s brother’s child', 'first cousin once removed ascending');
+ case 'fatfatsisdau':
+ return I18N::translateContext('father’s father’s sister’s daughter', 'first cousin once removed ascending');
+ case 'fatfatsisson':
+ return I18N::translateContext('father’s father’s sister’s son', 'first cousin once removed ascending');
+ case 'fatfatsischi':
+ return I18N::translateContext('father’s father’s sister’s child', 'first cousin once removed ascending');
+ case 'fatmotbrodau':
+ return I18N::translateContext('father’s mother’s brother’s daughter', 'first cousin once removed ascending');
+ case 'fatmotbroson':
+ return I18N::translateContext('father’s mother’s brother’s son', 'first cousin once removed ascending');
+ case 'fatmotbrochi':
+ return I18N::translateContext('father’s mother’s brother’s child', 'first cousin once removed ascending');
+ case 'fatmotsisdau':
+ return I18N::translateContext('father’s mother’s sister’s daughter', 'first cousin once removed ascending');
+ case 'fatmotsisson':
+ return I18N::translateContext('father’s mother’s sister’s son', 'first cousin once removed ascending');
+ case 'fatmotsischi':
+ return I18N::translateContext('father’s mother’s sister’s child', 'first cousin once removed ascending');
+ case 'motfatbrodau':
+ return I18N::translateContext('mother’s father’s brother’s daughter', 'first cousin once removed ascending');
+ case 'motfatbroson':
+ return I18N::translateContext('mother’s father’s brother’s son', 'first cousin once removed ascending');
+ case 'motfatbrochi':
+ return I18N::translateContext('mother’s father’s brother’s child', 'first cousin once removed ascending');
+ case 'motfatsisdau':
+ return I18N::translateContext('mother’s father’s sister’s daughter', 'first cousin once removed ascending');
+ case 'motfatsisson':
+ return I18N::translateContext('mother’s father’s sister’s son', 'first cousin once removed ascending');
+ case 'motfatsischi':
+ return I18N::translateContext('mother’s father’s sister’s child', 'first cousin once removed ascending');
+ case 'motmotbrodau':
+ return I18N::translateContext('mother’s mother’s brother’s daughter', 'first cousin once removed ascending');
+ case 'motmotbroson':
+ return I18N::translateContext('mother’s mother’s brother’s son', 'first cousin once removed ascending');
+ case 'motmotbrochi':
+ return I18N::translateContext('mother’s mother’s brother’s child', 'first cousin once removed ascending');
+ case 'motmotsisdau':
+ return I18N::translateContext('mother’s mother’s sister’s daughter', 'first cousin once removed ascending');
+ case 'motmotsisson':
+ return I18N::translateContext('mother’s mother’s sister’s son', 'first cousin once removed ascending');
+ case 'motmotsischi':
+ return I18N::translateContext('mother’s mother’s sister’s child', 'first cousin once removed ascending');
+ }
+
+ // Some “special case” level five relationships that have specific names in certain languages
+ if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandfather’s brother’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandfather’s brother’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandfather’s brother’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandfather’s sister’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandfather’s sister’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandfather’s sister’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandfather’s sibling’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandfather’s sibling’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandfather’s sibling’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandmother’s brother’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandmother’s brother’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandmother’s brother’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandmother’s sister’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandmother’s sister’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandmother’s sister’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandmother’s sibling’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandmother’s sibling’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandmother’s sibling’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandparent’s brother’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandparent’s brother’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandparent’s brother’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandparent’s sister’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandparent’s sister’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandparent’s sister’s grandchild', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)dau$/', $path)) {
+ return I18N::translateContext('grandparent’s sibling’s granddaughter', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)son$/', $path)) {
+ return I18N::translateContext('grandparent’s sibling’s grandson', 'second cousin');
+ } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)chi$/', $path)) {
+ return I18N::translateContext('grandparent’s sibling’s grandchild', 'second cousin');
+ }
+
+ // Look for generic/pattern relationships.
+ if (preg_match('/^((?:mot|fat|par)+)(bro|sis|sib)$/', $path, $match)) {
+ // siblings of direct ancestors
+ $up = strlen($match[1]) / 3;
+ $bef_last = substr($path, -6, 3);
+ switch ($up) {
+ case 3:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great-grandfather’s brother', 'great-great-uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great-grandmother’s brother', 'great-great-uncle');
+ } else {
+ return I18N::translateContext('great-grandparent’s brother', 'great-great-uncle');
+ }
+ case 'F':
+ return I18N::translate('great-great-aunt');
+ default:
+ return I18N::translate('great-great-aunt/uncle');
+ }
+ case 4:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great-great-grandfather’s brother', 'great-great-great-uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great-great-grandmother’s brother', 'great-great-great-uncle');
+ } else {
+ return I18N::translateContext('great-great-grandparent’s brother', 'great-great-great-uncle');
+ }
+ case 'F':
+ return I18N::translate('great-great-great-aunt');
+ default:
+ return I18N::translate('great-great-great-aunt/uncle');
+ }
+ case 5:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great-great-great-grandfather’s brother', 'great ×4 uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great-great-great-grandmother’s brother', 'great ×4 uncle');
+ } else {
+ return I18N::translateContext('great-great-great-grandparent’s brother', 'great ×4 uncle');
+ }
+ case 'F':
+ return I18N::translate('great ×4 aunt');
+ default:
+ return I18N::translate('great ×4 aunt/uncle');
+ }
+ case 6:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great ×4 grandfather’s brother', 'great ×5 uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great ×4 grandmother’s brother', 'great ×5 uncle');
+ } else {
+ return I18N::translateContext('great ×4 grandparent’s brother', 'great ×5 uncle');
+ }
+ case 'F':
+ return I18N::translate('great ×5 aunt');
+ default:
+ return I18N::translate('great ×5 aunt/uncle');
+ }
+ case 7:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great ×5 grandfather’s brother', 'great ×6 uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great ×5 grandmother’s brother', 'great ×6 uncle');
+ } else {
+ return I18N::translateContext('great ×5 grandparent’s brother', 'great ×6 uncle');
+ }
+ case 'F':
+ return I18N::translate('great ×6 aunt');
+ default:
+ return I18N::translate('great ×6 aunt/uncle');
+ }
+ case 8:
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great ×6 grandfather’s brother', 'great ×7 uncle');
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great ×6 grandmother’s brother', 'great ×7 uncle');
+ } else {
+ return I18N::translateContext('great ×6 grandparent’s brother', 'great ×7 uncle');
+ }
+ case 'F':
+ return I18N::translate('great ×7 aunt');
+ default:
+ return I18N::translate('great ×7 aunt/uncle');
+ }
+ default:
+ // Different languages have different rules for naming generations.
+ // An English great ×12 uncle is a Danish great ×10 uncle.
+ //
+ // Need to find out which languages use which rules.
+ switch (WT_LOCALE) {
+ case 'da':
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s uncle', I18N::number($up - 4));
+ case 'F':
+ return I18N::translate('great ×%s aunt', I18N::number($up - 4));
+ default:
+ return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 4));
+ }
+ case 'pl':
+ switch ($sex2) {
+ case 'M':
+ if ($bef_last === 'fat') {
+ return I18N::translateContext('great ×(%s-1) grandfather’s brother', 'great ×%s uncle', I18N::number($up - 2));
+ } elseif ($bef_last === 'mot') {
+ return I18N::translateContext('great ×(%s-1) grandmother’s brother', 'great ×%s uncle', I18N::number($up - 2));
+ } else {
+ return I18N::translateContext('great ×(%s-1) grandparent’s brother', 'great ×%s uncle', I18N::number($up - 2));
+ }
+ case 'F':
+ return I18N::translate('great ×%s aunt', I18N::number($up - 2));
+ default:
+ return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 2));
+ }
+ case 'it': // Source: Michele Locati
+ case 'en_AU':
+ case 'en_GB':
+ case 'en_US':
+ default:
+ switch ($sex2) {
+ case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
+ return I18N::translate('great ×%s uncle', I18N::number($up - 1));
+ case 'F':
+ return I18N::translate('great ×%s aunt', I18N::number($up - 1));
+ default:
+ return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 1));
+ }
+ }
+ }
+ }
+ if (preg_match('/^(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
+ // direct descendants of siblings
+ $down = strlen($match[1]) / 3 + 1; // Add one, as we count generations from the common ancestor
+ $first = substr($path, 0, 3);
+ switch ($down) {
+ case 4:
+ switch ($sex2) {
+ case 'M':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-grandson', 'great-great-nephew');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-grandson', 'great-great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-nephew', 'great-great-nephew');
+ }
+ case 'F':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-granddaughter', 'great-great-niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-granddaughter', 'great-great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-niece', 'great-great-niece');
+ }
+ default:
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-grandchild', 'great-great-nephew/niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-grandchild', 'great-great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-nephew/niece', 'great-great-nephew/niece');
+ }
+ }
+ case 5:
+ switch ($sex2) {
+ case 'M':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-grandson', 'great-great-great-nephew');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-grandson', 'great-great-great-nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-great-nephew', 'great-great-great-nephew');
+ }
+ case 'F':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-granddaughter', 'great-great-great-niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-granddaughter', 'great-great-great-niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-great-niece', 'great-great-great-niece');
+ }
+ default:
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-grandchild', 'great-great-great-nephew/niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-grandchild', 'great-great-great-nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great-great-great-nephew/niece', 'great-great-great-nephew/niece');
+ }
+ }
+ case 6:
+ switch ($sex2) {
+ case 'M':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-great-grandson', 'great ×4 nephew');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-great-grandson', 'great ×4 nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×4 nephew', 'great ×4 nephew');
+ }
+ case 'F':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-great-granddaughter', 'great ×4 niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-great-granddaughter', 'great ×4 niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×4 niece', 'great ×4 niece');
+ }
+ default:
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great-great-great-grandchild', 'great ×4 nephew/niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great-great-great-grandchild', 'great ×4 nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×4 nephew/niece', 'great ×4 nephew/niece');
+ }
+ }
+ case 7:
+ switch ($sex2) {
+ case 'M':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×4 grandson', 'great ×5 nephew');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×4 grandson', 'great ×5 nephew');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×5 nephew', 'great ×5 nephew');
+ }
+ case 'F':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×4 granddaughter', 'great ×5 niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×4 granddaughter', 'great ×5 niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×5 niece', 'great ×5 niece');
+ }
+ default:
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×4 grandchild', 'great ×5 nephew/niece');
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×4 grandchild', 'great ×5 nephew/niece');
+ } else {
+ return I18N::translateContext('(a woman’s) great ×5 nephew/niece', 'great ×5 nephew/niece');
+ }
+ }
+ default:
+ // Different languages have different rules for naming generations.
+ // An English great ×12 nephew is a Polish great ×11 nephew.
+ //
+ // Need to find out which languages use which rules.
+ switch (WT_LOCALE) {
+ case 'pl': // Source: Lukasz Wilenski
+ switch ($sex2) {
+ case 'M':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
+ } else {
+ return I18N::translateContext('(a woman’s) great ×%s nephew', 'great ×%s nephew', I18N::number($down - 3));
+ }
+ case 'F':
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
+ } else {
+ return I18N::translateContext('(a woman’s) great ×%s niece', 'great ×%s niece', I18N::number($down - 3));
+ }
+ default:
+ if ($first === 'bro' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
+ } elseif ($first === 'sis' && $sex1 === 'M') {
+ return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
+ } else {
+ return I18N::translateContext('(a woman’s) great ×%s nephew/niece', 'great ×%s nephew/niece', I18N::number($down - 3));
+ }
+ }
+ case 'he': // Source: Meliza Amity
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s nephew', I18N::number($down - 1));
+ case 'F':
+ return I18N::translate('great ×%s niece', I18N::number($down - 1));
+ default:
+ return I18N::translate('great ×%s nephew/niece', I18N::number($down - 1));
+ }
+ case 'it': // Source: Michele Locati.
+ case 'en_AU':
+ case 'en_GB':
+ case 'en_US':
+ default:
+ switch ($sex2) {
+ case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
+ return I18N::translate('great ×%s nephew', I18N::number($down - 2));
+ case 'F':
+ return I18N::translate('great ×%s niece', I18N::number($down - 2));
+ default:
+ return I18N::translate('great ×%s nephew/niece', I18N::number($down - 2));
+ }
+ }
+ }
+ }
+ if (preg_match('/^((?:mot|fat|par)*)$/', $path, $match)) {
+ // direct ancestors
+ $up = strlen($match[1]) / 3;
+ switch ($up) {
+ case 4:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great-great-grandfather');
+ case 'F':
+ return I18N::translate('great-great-grandmother');
+ default:
+ return I18N::translate('great-great-grandparent');
+ }
+ case 5:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great-great-great-grandfather');
+ case 'F':
+ return I18N::translate('great-great-great-grandmother');
+ default:
+ return I18N::translate('great-great-great-grandparent');
+ }
+ case 6:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×4 grandfather');
+ case 'F':
+ return I18N::translate('great ×4 grandmother');
+ default:
+ return I18N::translate('great ×4 grandparent');
+ }
+ case 7:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×5 grandfather');
+ case 'F':
+ return I18N::translate('great ×5 grandmother');
+ default:
+ return I18N::translate('great ×5 grandparent');
+ }
+ case 8:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×6 grandfather');
+ case 'F':
+ return I18N::translate('great ×6 grandmother');
+ default:
+ return I18N::translate('great ×6 grandparent');
+ }
+ case 9:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×7 grandfather');
+ case 'F':
+ return I18N::translate('great ×7 grandmother');
+ default:
+ return I18N::translate('great ×7 grandparent');
+ }
+ default:
+ // Different languages have different rules for naming generations.
+ // An English great ×12 grandfather is a Danish great ×11 grandfather.
+ //
+ // Need to find out which languages use which rules.
+ switch (WT_LOCALE) {
+ case 'da': // Source: Patrick Sorensen
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
+ case 'F':
+ return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
+ default:
+ return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
+ }
+ case 'it': // Source: Michele Locati
+ case 'es': // Source: Wes Groleau
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s grandfather', I18N::number($up));
+ case 'F':
+ return I18N::translate('great ×%s grandmother', I18N::number($up));
+ default:
+ return I18N::translate('great ×%s grandparent', I18N::number($up));
+ }
+ case 'fr': // Source: Jacqueline Tetreault
+ case 'fr_CA':
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s grandfather', I18N::number($up - 1));
+ case 'F':
+ return I18N::translate('great ×%s grandmother', I18N::number($up - 1));
+ default:
+ return I18N::translate('great ×%s grandparent', I18N::number($up - 1));
+ }
+ case 'nn': // Source: Hogne Røed Nilsen (https://bugs.launchpad.net/webtrees/+bug/1168553)
+ case 'nb':
+ switch ($sex2) {
+ case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
+ return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
+ case 'F':
+ return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
+ default:
+ return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
+ }
+ case 'en_AU':
+ case 'en_GB':
+ case 'en_US':
+ default:
+ switch ($sex2) {
+ case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
+ return I18N::translate('great ×%s grandfather', I18N::number($up - 2));
+ case 'F':
+ return I18N::translate('great ×%s grandmother', I18N::number($up - 2));
+ default:
+ return I18N::translate('great ×%s grandparent', I18N::number($up - 2));
+ }
+ }
+ }
+ }
+ if (preg_match('/^((?:son|dau|chi)*)$/', $path, $match)) {
+ // direct descendants
+ $up = strlen($match[1]) / 3;
+ switch ($up) {
+ case 4:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great-great-grandson');
+ case 'F':
+ return I18N::translate('great-great-granddaughter');
+ default:
+ return I18N::translate('great-great-grandchild');
+ }
+
+ case 5:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great-great-great-grandson');
+ case 'F':
+ return I18N::translate('great-great-great-granddaughter');
+ default:
+ return I18N::translate('great-great-great-grandchild');
+ }
+
+ case 6:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×4 grandson');
+ case 'F':
+ return I18N::translate('great ×4 granddaughter');
+ default:
+ return I18N::translate('great ×4 grandchild');
+ }
+
+ case 7:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×5 grandson');
+ case 'F':
+ return I18N::translate('great ×5 granddaughter');
+ default:
+ return I18N::translate('great ×5 grandchild');
+ }
+
+ case 8:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×6 grandson');
+ case 'F':
+ return I18N::translate('great ×6 granddaughter');
+ default:
+ return I18N::translate('great ×6 grandchild');
+ }
+
+ case 9:
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×7 grandson');
+ case 'F':
+ return I18N::translate('great ×7 granddaughter');
+ default:
+ return I18N::translate('great ×7 grandchild');
+ }
+
+ default:
+ // Different languages have different rules for naming generations.
+ // An English great ×12 grandson is a Danish great ×11 grandson.
+ //
+ // Need to find out which languages use which rules.
+ switch (WT_LOCALE) {
+ case 'nn': // Source: Hogne Røed Nilsen
+ case 'nb':
+ case 'da': // Source: Patrick Sorensen
+ switch ($sex2) {
+ case 'M':
+ return I18N::translate('great ×%s grandson', I18N::number($up - 3));
+ case 'F':
+ return I18N::translate('great ×%s granddaughter', I18N::number($up - 3));
+ default:
+ return I18N::translate('great ×%s grandchild', I18N::number($up - 3));
+ }
+ case 'it': // Source: Michele Locati
+ case 'es': // Source: Wes Groleau (adding doesn’t change behavior, but needs to be better researched)
+ case 'en_AU':
+ case 'en_GB':
+ case 'en_US':
+ default:
+ switch ($sex2) {
+
+ case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
+ return I18N::translate('great ×%s grandson', I18N::number($up - 2));
+ case 'F':
+ return I18N::translate('great ×%s granddaughter', I18N::number($up - 2));
+ default:
+ return I18N::translate('great ×%s grandchild', I18N::number($up - 2));
+ }
+ }
+ }
+ }
+ if (preg_match('/^((?:mot|fat|par)+)(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
+ // cousins in English
+ $ascent = $match[1];
+ $descent = $match[2];
+ $up = strlen($ascent) / 3;
+ $down = strlen($descent) / 3;
+ $cousin = min($up, $down); // Moved out of switch (en/default case) so that
+ $removed = abs($down - $up); // Spanish (and other languages) can use it, too.
+
+ // Different languages have different rules for naming cousins. For example,
+ // an English “second cousin once removed” is a Polish “cousin of 7th degree”.
+ //
+ // Need to find out which languages use which rules.
+ switch (WT_LOCALE) {
+ case 'pl': // Source: Lukasz Wilenski
+ return self::cousinName($up + $down + 2, $sex2);
+ case 'it':
+ // Source: Michele Locati. See italian_cousins_names.zip
+ // http://webtrees.net/forums/8-translation/1200-great-xn-grandparent?limit=6&start=6
+ return self::cousinName($up + $down - 3, $sex2);
+ case 'es':
+ // Source: Wes Groleau. See http://UniGen.us/Parentesco.html & http://UniGen.us/Parentesco-D.html
+ if ($down == $up) {
+ return self::cousinName($cousin, $sex2);
+ } elseif ($down < $up) {
+ return self::cousinName2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
+ } else {
+ switch ($sex2) {
+ case 'M':
+ return self::cousinName2($cousin + 1, $sex2, get_relationship_name_from_path('bro' . $descent, null, null));
+ case 'F':
+ return self::cousinName2($cousin + 1, $sex2, get_relationship_name_from_path('sis' . $descent, null, null));
+ default:
+ return self::cousinName2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
+ }
+ }
+ case 'en_AU': // See: http://en.wikipedia.org/wiki/File:CousinTree.svg
+ case 'en_GB':
+ case 'en_US':
+ default:
+ switch ($removed) {
+ case 0:
+ return self::cousinName($cousin, $sex2);
+ case 1:
+ if ($up > $down) {
+ /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
+ return I18N::translate('%s once removed ascending', self::cousinName($cousin, $sex2));
+ } else {
+ /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
+ return I18N::translate('%s once removed descending', self::cousinName($cousin, $sex2));
+ }
+ case 2:
+ if ($up > $down) {
+ /* I18N: %s=“fifth cousin”, etc. */
+ return I18N::translate('%s twice removed ascending', self::cousinName($cousin, $sex2));
+ } else {
+ /* I18N: %s=“fifth cousin”, etc. */
+ return I18N::translate('%s twice removed descending', self::cousinName($cousin, $sex2));
+ }
+ case 3:
+ if ($up > $down) {
+ /* I18N: %s=“fifth cousin”, etc. */
+ return I18N::translate('%s three times removed ascending', self::cousinName($cousin, $sex2));
+ } else {
+ /* I18N: %s=“fifth cousin”, etc. */
+ return I18N::translate('%s three times removed descending', self::cousinName($cousin, $sex2));
+ }
+ default:
+ if ($up > $down) {
+ /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
+ return I18N::translate('%1$s %2$s times removed ascending', self::cousinName($cousin, $sex2), I18N::number($removed));
+ } else {
+ /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
+ return I18N::translate('%1$s %2$s times removed descending', self::cousinName($cousin, $sex2), I18N::number($removed));
+ }
+ }
+ }
+ }
+
+ // Split the relationship into sub-relationships, e.g., third-cousin’s great-uncle.
+ // Try splitting at every point, and choose the path with the shorted translated name.
+
+ $relationship = null;
+ $path1 = substr($path, 0, 3);
+ $path2 = substr($path, 3);
+ while ($path2) {
+ $tmp = I18N::translate(
+ // I18N: A complex relationship, such as “third-cousin’s great-uncle”
+ '%1$s’s %2$s',
+ self::getRelationshipNameFromPath($path1, null, null), // TODO: need the actual people
+ self::getRelationshipNameFromPath($path2, null, null)
+ );
+ if (!$relationship || strlen($tmp) < strlen($relationship)) {
+ $relationship = $tmp;
+ }
+ $path1 .= substr($path2, 0, 3);
+ $path2 = substr($path2, 3);
+ }
+
+ return $relationship;
+ }
+
+ /**
+ * Function to build an URL querystring from GET variables
+ * Optionally, add/replace specified values
+ *
+ * @param null|string[] $overwrite
+ * @param null|string $separator
+ *
+ * @return string
+ */
+ public static function getQueryUrl($overwrite = null, $separator = '&') {
+ if (empty($_GET)) {
+ $get = array();
+ } else {
+ $get = $_GET;
+ }
+ if (is_array($overwrite)) {
+ foreach ($overwrite as $key => $value) {
+ $get[$key] = $value;
+ }
+ }
+
+ $query_string = '';
+ foreach ($get as $key => $value) {
+ if (!is_array($value)) {
+ $query_string .= $separator . rawurlencode($key) . '=' . rawurlencode($value);
+ } else {
+ foreach ($value as $k => $v) {
+ $query_string .= $separator . rawurlencode($key) . '%5B' . rawurlencode($k) . '%5D=' . rawurlencode($v);
+ }
+ }
+ }
+ $query_string = substr($query_string, strlen($separator)); // Remove leading “&amp;”
+ if ($query_string) {
+ return WT_SCRIPT_NAME . '?' . $query_string;
+ } else {
+ return WT_SCRIPT_NAME;
+ }
+ }
+
+ /**
+ * Determines whether the passed in filename is a link to an external source (i.e. contains “://”)
+ *
+ * @param string $file
+ *
+ * @return bool
+ */
+ public static function isFileExternal($file) {
+ return strpos($file, '://') !== false;
+ }
+}
diff --git a/app/GedcomRecord.php b/app/GedcomRecord.php
index 74a92adf65..51241f0cd8 100644
--- a/app/GedcomRecord.php
+++ b/app/GedcomRecord.php
@@ -15,6 +15,10 @@ namespace Fisharebest\Webtrees;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsImport;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
/**
* Class GedcomRecord - Base class for all gedcom records
@@ -809,9 +813,9 @@ class GedcomRecord {
if ($event->getDate()->isOK() || !$event->getPlace()->isEmpty()) {
switch ($style) {
case 1:
- return '<br><em>' . $event->getLabel() . ' ' . format_fact_date($event, $this, false, false) . ' ' . format_fact_place($event) . '</em>';
+ return '<br><em>' . $event->getLabel() . ' ' . FunctionsPrint::formatFactDate($event, $this, false, false) . ' ' . FunctionsPrint::formatFactPlace($event) . '</em>';
case 2:
- return '<dl><dt class="label">' . $event->getLabel() . '</dt><dd class="field">' . format_fact_date($event, $this, false, false) . ' ' . format_fact_place($event) . '</dd></dl>';
+ return '<dl><dt class="label">' . $event->getLabel() . '</dt><dd class="field">' . FunctionsPrint::formatFactDate($event, $this, false, false) . ' ' . FunctionsPrint::formatFactPlace($event) . '</dd></dl>';
}
}
}
@@ -1096,7 +1100,7 @@ class GedcomRecord {
}
}
if ($sort) {
- sort_facts($facts);
+ Functions::sortFacts($facts);
}
return $facts;
@@ -1126,7 +1130,7 @@ class GedcomRecord {
if ($sorting) {
return $t;
} else {
- return strip_tags(format_timestamp($t));
+ return strip_tags(FunctionsDate::formatTimestamp($t));
}
} else {
// The record does not have a CHAN event
@@ -1245,7 +1249,7 @@ class GedcomRecord {
$this->pending = $new_gedcom;
if (Auth::user()->getPreference('auto_accept')) {
- accept_all_changes($this->xref, $this->tree->getTreeId());
+ FunctionsImport::acceptAllChanges($this->xref, $this->tree->getTreeId());
$this->gedcom = $new_gedcom;
$this->pending = null;
}
@@ -1286,7 +1290,7 @@ class GedcomRecord {
// Accept this pending change
if (Auth::user()->getPreference('auto_accept')) {
- accept_all_changes($this->xref, $this->tree->getTreeId());
+ FunctionsImport::acceptAllChanges($this->xref, $this->tree->getTreeId());
$this->gedcom = $gedcom;
$this->pending = null;
}
@@ -1312,7 +1316,7 @@ class GedcomRecord {
// Accept this pending change
if (Auth::user()->getPreference('auto_accept')) {
- accept_all_changes($this->xref, $this->tree->getTreeId());
+ FunctionsImport::acceptAllChanges($this->xref, $this->tree->getTreeId());
}
// Clear the cache
diff --git a/app/I18N.php b/app/I18N.php
index 16a81bc528..6b94a84904 100644
--- a/app/I18N.php
+++ b/app/I18N.php
@@ -276,7 +276,7 @@ class I18N {
*
* NB: The import function will have normalised this, so we don't need
* to worry about badly formatted strings
- * NOTE: this function is not yet complete - eventually it will replace get_age_at_event()
+ * NOTE: this function is not yet complete - eventually it will replace FunctionsDate::get_age_at_event()
*
* @param $string
*
diff --git a/app/Media.php b/app/Media.php
index bef4091329..33a26485f6 100644
--- a/app/Media.php
+++ b/app/Media.php
@@ -15,6 +15,8 @@ namespace Fisharebest\Webtrees;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsMedia;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
/**
* Class Media - Class that defines a media object
@@ -163,7 +165,7 @@ class Media extends GedcomRecord {
Log::addMediaLog('Thumbnail could not be created for ' . $main_file . ' (copy of main image)');
}
} else {
- if (hasMemoryForImage($main_file)) {
+ if (FunctionsMedia::hasMemoryForImage($main_file)) {
try {
switch ($imgsize['mime']) {
case 'image/png':
@@ -576,7 +578,7 @@ class Media extends GedcomRecord {
/** {@inheritdoc} */
public function formatListDetails() {
ob_start();
- print_media_links('1 OBJE @' . $this->getXref() . '@', 1);
+ FunctionsPrintFacts::printMediaLinks('1 OBJE @' . $this->getXref() . '@', 1);
return ob_get_clean();
}
diff --git a/app/Module/AlbumModule.php b/app/Module/AlbumModule.php
index adc6e171dd..7090e37b3e 100644
--- a/app/Module/AlbumModule.php
+++ b/app/Module/AlbumModule.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Menu;
@@ -97,7 +98,7 @@ class AlbumModule extends AbstractModule implements ModuleTabInterface {
$needle = '1 NOTE';
$before = substr($haystack, 0, strpos($haystack, $needle));
$after = substr(strstr($haystack, $needle), strlen($needle));
- $notes = print_fact_notes($before . $needle . $after, 1, true);
+ $notes = FunctionsPrint::printFactNotes($before . $needle . $after, 1, true);
// Prepare Below Thumbnail menu ----------------------------------------------------
$menu = new Menu('<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap">' . $media->getFullName() . '</div>');
diff --git a/app/Module/BatchUpdateModule.php b/app/Module/BatchUpdateModule.php
index 3d0a5751f3..81dc4913d9 100644
--- a/app/Module/BatchUpdateModule.php
+++ b/app/Module/BatchUpdateModule.php
@@ -20,6 +20,7 @@ use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -181,7 +182,7 @@ class BatchUpdateModule extends AbstractModule implements ModuleConfigInterface
'<input type="hidden" name="data" value="">' . // will be set by javascript for next update
'<table id="batch_update"><tr>' .
'<th>' . I18N::translate('Family tree') . '</th>' .
- '<td>' . select_edit_control('ged', Tree::getNameList(), '', $WT_TREE->getName(), 'onchange="reset_reload();"') .
+ '<td>' . FunctionsEdit::selectEditControl('ged', Tree::getNameList(), '', $WT_TREE->getName(), 'onchange="reset_reload();"') .
'</td></tr><tr><th>' . I18N::translate('Batch update') . '</th><td><select name="plugin" onchange="reset_reload();">';
if (!$this->plugin) {
$html .= '<option value="" selected></option>';
diff --git a/app/Module/CensusAssistantModule.php b/app/Module/CensusAssistantModule.php
index 24160ef5c3..33c70f62a5 100644
--- a/app/Module/CensusAssistantModule.php
+++ b/app/Module/CensusAssistantModule.php
@@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Controller\SimpleController;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -112,7 +113,7 @@ class CensusAssistantModule extends AbstractModule {
// Output Individual for GEDFact Assistant ======================
echo "<table class=\"tabs_table width90\">";
- $myindilist = search_indis_names($filter_array, array($WT_TREE));
+ $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE));
if ($myindilist) {
echo "<tr><td class=\"list_value_wrap\"><ul>";
usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare');
@@ -290,7 +291,7 @@ class CensusAssistantModule extends AbstractModule {
$filter = trim($filter);
$filter_array = explode(' ', preg_replace('/ {2,}/', ' ', $filter));
echo "<table class=\"tabs_table width90\"><tr>";
- $myindilist = search_indis_names($filter_array, array($WT_TREE));
+ $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE));
if ($myindilist) {
echo "<td class=\"list_value_wrap\"><ul>";
usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare');
diff --git a/app/Module/ChartsBlockModule.php b/app/Module/ChartsBlockModule.php
index ba7a1dd465..e2b5d1b2ad 100644
--- a/app/Module/ChartsBlockModule.php
+++ b/app/Module/ChartsBlockModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Controller\HourglassController;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Module\InteractiveTree\TreeView;
@@ -80,7 +82,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface {
$controller->addInlineJavascript($chartController->setupJavascript());
$content .= '<td valign="middle">';
ob_start();
- print_pedigree_person($person, $details);
+ FunctionsPrint::printPedigreePerson($person, $details);
$content .= ob_get_clean();
$content .= '</td>';
$content .= '<td valign="middle">';
@@ -177,7 +179,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface {
<tr>
<td class="descriptionbox wrap width33"><?php echo I18N::translate('Chart type'); ?></td>
<td class="optionbox">
- <?php echo select_edit_control('type',
+ <?php echo FunctionsEdit::selectEditControl('type',
array(
'pedigree' => I18N::translate('Pedigree'),
'descendants' => I18N::translate('Descendants'),
@@ -190,7 +192,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface {
<tr>
<td class="descriptionbox wrap width33"><?php echo I18N::translate('Show details'); ?></td>
<td class="optionbox">
- <?php echo edit_field_yes_no('details', $details); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('details', $details); ?>
</td>
</tr>
<tr>
@@ -198,7 +200,7 @@ class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface {
<td class="optionbox">
<input data-autocomplete-type="INDI" type="text" name="pid" id="pid" value="<?php echo $pid; ?>" size="5">
<?php
- echo print_findindi_link('pid');
+ echo FunctionsPrint::printFindIndividualLink('pid');
$root = Individual::getInstance($pid, $WT_TREE);
if ($root) {
echo ' <span class="list_item">', $root->getFullName(), $root->formatFirstMajorFact(WT_EVENTS_BIRT, 1), '</span>';
diff --git a/app/Module/ClippingsCart/ClippingsCartController.php b/app/Module/ClippingsCart/ClippingsCartController.php
index 241ab25043..30dd81d7a0 100644
--- a/app/Module/ClippingsCart/ClippingsCartController.php
+++ b/app/Module/ClippingsCart/ClippingsCartController.php
@@ -20,6 +20,7 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsExport;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -164,7 +165,7 @@ class ClippingsCartController {
} elseif ($this->action === 'download') {
$media = array();
$mediacount = 0;
- $filetext = gedcom_header($WT_TREE);
+ $filetext = FunctionsExport::gedcomHeader($WT_TREE);
// Include SUBM/SUBN records, if they exist
$subn =
Database::prepare("SELECT o_gedcom FROM `##other` WHERE o_type=? AND o_file=?")
@@ -224,7 +225,7 @@ class ClippingsCartController {
$record = str_replace($match[0], '', $record);
}
}
- $record = convert_media_path($record, $this->conv_path);
+ $record = FunctionsExport::convertMediaPath($record, $this->conv_path);
$savedRecord = $record; // Save this for the "does this file exist" check
if ($convert === 'yes') {
$record = utf8_decode($record);
diff --git a/app/Module/ClippingsCartModule.php b/app/Module/ClippingsCartModule.php
index d1b7aaf16b..10bd9f01ff 100644
--- a/app/Module/ClippingsCartModule.php
+++ b/app/Module/ClippingsCartModule.php
@@ -19,6 +19,7 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -304,9 +305,9 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface,
<input type="text" data-autocomplete-type="IFSRO" name="id" id="cart_item_id" size="5">
</td>
<td class="optionbox">
- <?php echo print_findindi_link('cart_item_id'); ?>
- <?php echo print_findfamily_link('cart_item_id'); ?>
- <?php echo print_findsource_link('cart_item_id', ''); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('cart_item_id'); ?>
+ <?php echo FunctionsPrint::printFindFamilyLink('cart_item_id'); ?>
+ <?php echo FunctionsPrint::printFindSourceLink('cart_item_id', ''); ?>
<input type="submit" value="<?php echo I18N::translate('Add'); ?>">
</td>
</tr>
@@ -399,9 +400,9 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface,
<input type="text" data-autocomplete-type="IFSRO" name="id" id="cart_item_id" size="8">
</td>
<td class="optionbox">
- <?php echo print_findindi_link('cart_item_id'); ?>
- <?php echo print_findfamily_link('cart_item_id'); ?>
- <?php echo print_findsource_link('cart_item_id'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('cart_item_id'); ?>
+ <?php echo FunctionsPrint::printFindFamilyLink('cart_item_id'); ?>
+ <?php echo FunctionsPrint::printFindSourceLink('cart_item_id'); ?>
<input type="submit" value="<?php echo I18N::translate('Add'); ?>">
</td>
</tr>
diff --git a/app/Module/ExtraInformationModule.php b/app/Module/ExtraInformationModule.php
index d923c7ff1c..364597140f 100644
--- a/app/Module/ExtraInformationModule.php
+++ b/app/Module/ExtraInformationModule.php
@@ -16,6 +16,7 @@ namespace Fisharebest\Webtrees\Module;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\I18N;
/**
@@ -60,7 +61,7 @@ class ExtraInformationModule extends AbstractModule implements ModuleSidebarInte
echo I18N::translate('There are no facts for this individual.');
} else {
foreach ($indifacts as $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
}
diff --git a/app/Module/FamilyNavigatorModule.php b/app/Module/FamilyNavigatorModule.php
index b34e59cb07..dc7483a3bd 100644
--- a/app/Module/FamilyNavigatorModule.php
+++ b/app/Module/FamilyNavigatorModule.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Menu;
@@ -117,7 +118,7 @@ class FamilyNavigatorModule extends AbstractModule implements ModuleSidebarInter
</tr>
<?php
foreach ($family->getSpouses() as $spouse) {
- $menu = new Menu(get_close_relationship_name($controller->record, $spouse));
+ $menu = new Menu(Functions::getCloseRelationshipName($controller->record, $spouse));
$menu->addClass('', 'submenu flyout');
$menu->addSubmenu(new Menu($this->getParents($spouse)));
?>
@@ -138,7 +139,7 @@ class FamilyNavigatorModule extends AbstractModule implements ModuleSidebarInter
}
foreach ($family->getChildren() as $child) {
- $menu = new Menu(get_close_relationship_name($controller->record, $child));
+ $menu = new Menu(Functions::getCloseRelationshipName($controller->record, $child));
$menu->addClass('', 'submenu flyout');
$menu->addSubmenu(new Menu($this->getFamily($child)));
?>
diff --git a/app/Module/FamilyTreeFavoritesModule.php b/app/Module/FamilyTreeFavoritesModule.php
index 61d83832e6..9cf2f685a7 100644
--- a/app/Module/FamilyTreeFavoritesModule.php
+++ b/app/Module/FamilyTreeFavoritesModule.php
@@ -20,6 +20,8 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -188,12 +190,12 @@ class FamilyTreeFavoritesModule extends AbstractModule implements ModuleBlockInt
$content .= '<input type="radio" name="fav_category" value="record" checked onclick="jQuery(\'#gid' . $uniqueID . '\').removeAttr(\'disabled\'); jQuery(\'#url, #favtitle\').attr(\'disabled\',\'disabled\').val(\'\');">';
$content .= '<label for="gid' . $uniqueID . '">' . I18N::translate('Enter an individual, family, or source ID') . '</label>';
$content .= '<input class="pedigree_form" data-autocomplete-type="IFSRO" type="text" name="gid" id="gid' . $uniqueID . '" size="5" value="">';
- $content .= ' ' . print_findindi_link('gid' . $uniqueID);
- $content .= ' ' . print_findfamily_link('gid' . $uniqueID);
- $content .= ' ' . print_findsource_link('gid' . $uniqueID);
- $content .= ' ' . print_findrepository_link('gid' . $uniqueID);
- $content .= ' ' . print_findnote_link('gid' . $uniqueID);
- $content .= ' ' . print_findmedia_link('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindIndividualLink('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindFamilyLink('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindSourceLink('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindRepositoryLink('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindNoteLink('gid' . $uniqueID);
+ $content .= ' ' . FunctionsPrint::printFindMediaLink('gid' . $uniqueID);
$content .= '</div>';
$content .= '<div class="add_fav_url">';
$content .= '<input type="radio" name="fav_category" value="url" onclick="jQuery(\'#url, #favtitle\').removeAttr(\'disabled\'); jQuery(\'#gid' . $uniqueID . '\').attr(\'disabled\',\'disabled\').val(\'\');">';
@@ -243,7 +245,7 @@ class FamilyTreeFavoritesModule extends AbstractModule implements ModuleBlockInt
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
diff --git a/app/Module/FamilyTreeNewsModule.php b/app/Module/FamilyTreeNewsModule.php
index fbb1077720..142b2f8028 100644
--- a/app/Module/FamilyTreeNewsModule.php
+++ b/app/Module/FamilyTreeNewsModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -106,7 +108,7 @@ class FamilyTreeNewsModule extends AbstractModule implements ModuleBlockInterfac
}
$content .= '<div class="news_box" id="article' . $news->news_id . '">';
$content .= '<div class="news_title">' . Filter::escapeHtml($news->subject) . '</div>';
- $content .= '<div class="news_date">' . format_timestamp($news->updated) . '</div>';
+ $content .= '<div class="news_date">' . FunctionsDate::formatTimestamp($news->updated) . '</div>';
if ($news->body == strip_tags($news->body)) {
$news->body = nl2br($news->body, false);
}
@@ -127,7 +129,7 @@ class FamilyTreeNewsModule extends AbstractModule implements ModuleBlockInterfac
$content .= '&nbsp;&nbsp;|&nbsp;&nbsp;';
}
$content .= '<a href="index.php?gedcom_news_archive=yes&amp;ctype=' . $ctype . '">' . I18N::translate('View archive') . "</a>";
- $content .= help_link('gedcom_news_archive') . '<br>';
+ $content .= FunctionsPrint::helpLink('gedcom_news_archive') . '<br>';
}
if ($template) {
diff --git a/app/Module/FamilyTreeStatisticsModule.php b/app/Module/FamilyTreeStatisticsModule.php
index 3365a92d7c..1825afd6cb 100644
--- a/app/Module/FamilyTreeStatisticsModule.php
+++ b/app/Module/FamilyTreeStatisticsModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Stats;
use Fisharebest\Webtrees\Theme;
@@ -187,7 +189,7 @@ class FamilyTreeStatisticsModule extends AbstractModule implements ModuleBlockIn
}
// NOTE: Print the most common surnames
if ($show_common_surnames) {
- $surnames = get_common_surnames($WT_TREE->getPreference('COMMON_NAMES_THRESHOLD'), $WT_TREE);
+ $surnames = FunctionsDb::getCommonSurnames($WT_TREE->getPreference('COMMON_NAMES_THRESHOLD'), $WT_TREE);
if (count($surnames) > 0) {
$content .= '<p><b>' . I18N::translate('Most common surnames') . '</b></p>';
$content .= '<div class="common_surnames">';
@@ -272,13 +274,13 @@ class FamilyTreeStatisticsModule extends AbstractModule implements ModuleBlockIn
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for yes/no option */ I18N::translate('Show date of last update?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_last_update', $show_last_update);
+ echo FunctionsEdit::editFieldYesNo('show_last_update', $show_last_update);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show common surnames?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_common_surnames', $show_common_surnames);
+ echo FunctionsEdit::editFieldYesNo('show_common_surnames', $show_common_surnames);
echo '</td></tr>';
?>
diff --git a/app/Module/FrequentlyAskedQuestionsModule.php b/app/Module/FrequentlyAskedQuestionsModule.php
index 08fcf34a87..f65c829fec 100644
--- a/app/Module/FrequentlyAskedQuestionsModule.php
+++ b/app/Module/FrequentlyAskedQuestionsModule.php
@@ -19,6 +19,7 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Menu;
use Fisharebest\Webtrees\Module;
@@ -175,7 +176,7 @@ class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMen
</label>
<div class="col-sm-9">
- <?php echo edit_language_checkboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?>
+ <?php echo FunctionsEdit::editLanguageCheckboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?>
</div>
</div>
@@ -195,7 +196,7 @@ class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMen
</label>
<div class="col-sm-9">
- <?php echo select_edit_control('gedcom_id', Tree::getIdList(), I18N::translate('All'), $gedcom_id, 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('gedcom_id', Tree::getIdList(), I18N::translate('All'), $gedcom_id, 'class="form-control"'); ?>
<p class="small text-muted">
<?php echo I18N::translate('A FAQ item can be displayed on just one of the family trees, or on all the family trees.'); ?>
</p>
@@ -431,7 +432,7 @@ class FrequentlyAskedQuestionsModule extends AbstractModule implements ModuleMen
I18N::translate('Family tree'), ' ',
'<input type="hidden" name="mod", value="', $this->getName(), '">',
'<input type="hidden" name="mod_action" value="admin_config">',
- select_edit_control('ged', Tree::getNameList(), null, $WT_TREE->getNameHtml()),
+ FunctionsEdit::selectEditControl('ged', Tree::getNameList(), null, $WT_TREE->getNameHtml()),
'<input type="submit" value="', I18N::translate('show'), '">',
'</form></p>';
diff --git a/app/Module/GoogleMapsModule.php b/app/Module/GoogleMapsModule.php
index 88185cad5d..830468a396 100644
--- a/app/Module/GoogleMapsModule.php
+++ b/app/Module/GoogleMapsModule.php
@@ -24,6 +24,10 @@ use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsCharts;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -299,7 +303,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
</tr>
<tr>
<th><?php echo /* I18N: http://en.wikipedia.org/wiki/Google_street_view */ I18N::translate('Google Street View™'); ?></th>
- <td><?php echo radio_buttons('GM_USE_STREETVIEW', array(false => I18N::translate('hide'), true => I18N::translate('show')), $this->getSetting('GM_USE_STREETVIEW'), 'class="radio-inline"'); ?></td>
+ <td><?php echo FunctionsEdit::radioButtons('GM_USE_STREETVIEW', array(false => I18N::translate('hide'), true => I18N::translate('show')), $this->getSetting('GM_USE_STREETVIEW'), 'class="radio-inline"'); ?></td>
</tr>
<tr>
<th><?php echo I18N::translate('Size of map (in pixels)'); ?></th>
@@ -440,7 +444,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<table class="gm_edit_config">
<tr>
<th><?php echo I18N::translate('Use Google Maps™ for the place hierarchy'); ?></th>
- <td><?php echo edit_field_yes_no('GM_PLACE_HIERARCHY', $this->getSetting('GM_PLACE_HIERARCHY'), 'class="radio-inline"'); ?></td>
+ <td><?php echo FunctionsEdit::editFieldYesNo('GM_PLACE_HIERARCHY', $this->getSetting('GM_PLACE_HIERARCHY'), 'class="radio-inline"'); ?></td>
</tr>
<tr>
<th><?php echo I18N::translate('Size of map (in pixels)'); ?></th>
@@ -465,7 +469,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<?php echo I18N::translate('Display short placenames'); ?>
</th>
<td>
- <?php echo edit_field_yes_no('GM_DISP_SHORT_PLACE', $this->getSetting('GM_DISP_SHORT_PLACE'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('GM_DISP_SHORT_PLACE', $this->getSetting('GM_DISP_SHORT_PLACE'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo I18N::translate('Hide the flags that are configured in the googlemap module. Usually these are for countries and states. This serves as a visual cue that the markers around the flag are from the general area, and not the specific spot.'); ?>
</p>
@@ -476,7 +480,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<?php echo I18N::translate('Display map coordinates'); ?>
</th>
<td>
- <?php echo edit_field_yes_no('GM_COORD', $this->getSetting('GM_COORD'), 'class="radio-inline"'); ?>
+ <?php echo FunctionsEdit::editFieldYesNo('GM_COORD', $this->getSetting('GM_COORD'), 'class="radio-inline"'); ?>
<p class="small text-muted">
<?php echo I18N::translate('This options sets whether latitude and longitude are displayed on the pop-up window attached to map markers.'); ?>
</p>
@@ -758,7 +762,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" id="rootid" name="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td class="topbottombar" rowspan="2">
<input type="submit" value="<?php echo I18N::translate('View'); ?>">
@@ -1313,7 +1317,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
$curgen++;
}
- $relationship = get_sosa_name($i + 1);
+ $relationship = FunctionsCharts::getSosaName($i + 1);
$event = '<img src="' . WT_STATIC_URL . WT_MODULES_DIR . 'googlemap/images/sq' . $curgen . '.png" width="10" height="10"> ' .
'<strong>' . $relationship . '</strong>';
@@ -1521,7 +1525,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<input type="hidden" name="mod_action" value="admin_placecheck">
<div class="gm_check">
<label>', I18N::translate('Family tree'), '</label>';
- echo select_edit_control('gedcom_id', Tree::getIdList(), null, $gedcom_id, ' onchange="this.form.submit();"');
+ echo FunctionsEdit::selectEditControl('gedcom_id', Tree::getIdList(), null, $gedcom_id, ' onchange="this.form.submit();"');
echo '<label>', I18N::translate('Country'), '</label>
<select name="country" onchange="this.form.submit();">
<option value="XYZ" selected>', /* I18N: first/default option in a drop-down listbox */ I18N::translate('&lt;select&gt;'), '</option>
@@ -1918,7 +1922,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
$indifacts = array_merge($indifacts, $family->getFacts());
}
- sort_facts($indifacts);
+ Functions::sortFacts($indifacts);
// Create the markers list array
$gmarks = array();
@@ -3952,7 +3956,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<td class="descriptionbox"><?php echo GedcomTag::getLabel('PLAC'); ?></td>
<td class="optionbox"><input type="text" id="new_pl_name" name="NEW_PLACE_NAME" value="<?php echo Filter::escapeHtml($place_name); ?>" size="25" class="address_input">
<div id="INDI_PLAC_pop" style="display: inline;">
- <?php echo print_specialchar_link('new_pl_name'); ?></div></td><td class="optionbox">
+ <?php echo FunctionsPrint::printSpecialCharacterLink('new_pl_name'); ?></div></td><td class="optionbox">
<label for="new_pl_name"><a href="#" onclick="showLocation_all(document.getElementById('new_pl_name').value); return false">&nbsp;<?php echo I18N::translate('Search globally'); ?></a></label>
&nbsp;&nbsp;|&nbsp;&nbsp;
<label for="new_pl_name"><a href="#" onclick="showLocation_level(document.getElementById('new_pl_name').value); return false">&nbsp;<?php echo I18N::translate('Search locally'); ?></a></label>
@@ -4138,7 +4142,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
->fetchOneColumn();
foreach ($gedcom_records as $gedrec) {
$i = 1;
- $placerec = get_sub_record(2, '2 PLAC', $gedrec, $i);
+ $placerec = Functions::getSubRecord(2, '2 PLAC', $gedrec, $i);
while (!empty($placerec)) {
if (preg_match("/2 PLAC (.+)/", $placerec, $match)) {
$placelist[$j] = array();
@@ -4168,7 +4172,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
$j = $j + 1;
}
$i = $i + 1;
- $placerec = get_sub_record(2, '2 PLAC', $gedrec, $i);
+ $placerec = Functions::getSubRecord(2, '2 PLAC', $gedrec, $i);
}
}
asort($placelist);
@@ -4489,7 +4493,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
function delete_place(placeid) {
var answer=confirm('<?php echo I18N::translate('Remove this location?'); ?>');
if (answer == true) {
- window.location = '<?php echo get_query_url(array('action' => 'DeleteRecord')); ?>&action=DeleteRecord&deleteRecord=' + placeid;
+ window.location = '<?php echo Functions::getQueryUrl(array('action' => 'DeleteRecord')); ?>&action=DeleteRecord&deleteRecord=' + placeid;
}
}
</script>
@@ -4583,7 +4587,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
</td>
<td>
<form action="?" onsubmit="add_place_location(this.parent_id.options[this.parent_id.selectedIndex].value); return false;">
- <?php echo select_edit_control('parent_id', $where_am_i, I18N::translate('Top level'), $parent); ?>
+ <?php echo FunctionsEdit::selectEditControl('parent_id', $where_am_i, I18N::translate('Top level'), $parent); ?>
<input type="submit" value="<?php echo I18N::translate('Add'); ?>">
</form>
</td>
@@ -4597,7 +4601,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<input type="hidden" name="mod" value="googlemap">
<input type="hidden" name="mod_action" value="admin_places">
<input type="hidden" name="action" value="ImportGedcom">
- <?php echo select_edit_control('ged', Tree::getNameList(), null, $WT_TREE->getName()); ?>
+ <?php echo FunctionsEdit::selectEditControl('ged', Tree::getNameList(), null, $WT_TREE->getName()); ?>
<input type="submit" value="<?php echo I18N::translate('Import'); ?>">
</form>
</td>
@@ -4624,7 +4628,7 @@ class GoogleMapsModule extends AbstractModule implements ModuleConfigInterface,
<input type="hidden" name="mod" value="googlemap">
<input type="hidden" name="mod_action" value="admin_places">
<input type="hidden" name="action" value="ExportFile">
- <?php echo select_edit_control('parent', $where_am_i, I18N::translate('All'), $WT_TREE->getTreeId()); ?>
+ <?php echo FunctionsEdit::selectEditControl('parent', $where_am_i, I18N::translate('All'), $WT_TREE->getTreeId()); ?>
<input type="submit" value="<?php echo I18N::translate('Download'); ?>">
</form>
</td>
diff --git a/app/Module/HtmlBlockModule.php b/app/Module/HtmlBlockModule.php
index 48b47bb0a0..d4002fc419 100644
--- a/app/Module/HtmlBlockModule.php
+++ b/app/Module/HtmlBlockModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Module;
@@ -99,7 +101,7 @@ class HtmlBlockModule extends AbstractModule implements ModuleBlockInterface {
$content = $html;
if ($show_timestamp) {
- $content .= '<br>' . format_timestamp($this->getBlockSetting($block_id, 'timestamp', WT_TIMESTAMP) + WT_TIMESTAMP_OFFSET);
+ $content .= '<br>' . FunctionsDate::formatTimestamp($this->getBlockSetting($block_id, 'timestamp', WT_TIMESTAMP) + WT_TIMESTAMP_OFFSET);
}
if ($template) {
@@ -310,14 +312,14 @@ class HtmlBlockModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap">';
echo I18N::translate('Show the date and time of update');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_timestamp', $show_timestamp);
+ echo FunctionsEdit::editFieldYesNo('show_timestamp', $show_timestamp);
echo '<input type="hidden" name="timestamp" value="', WT_TIMESTAMP, '">';
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap">';
echo I18N::translate('Show this block for which languages?');
echo '</td><td class="optionbox">';
- echo edit_language_checkboxes('lang', $languages);
+ echo FunctionsEdit::editLanguageCheckboxes('lang', $languages);
echo '</td></tr>';
}
}
diff --git a/app/Module/IndividualFactsTabModule.php b/app/Module/IndividualFactsTabModule.php
index 3454f15042..4b83605908 100644
--- a/app/Module/IndividualFactsTabModule.php
+++ b/app/Module/IndividualFactsTabModule.php
@@ -19,6 +19,9 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Module;
@@ -113,7 +116,7 @@ class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterf
$indifacts[] = $fact;
}
- sort_facts($indifacts);
+ Functions::sortFacts($indifacts);
ob_start();
@@ -134,12 +137,12 @@ class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterf
echo '</form></td></tr>';
foreach ($indifacts as $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
//-- new fact link
if ($controller->record->canEdit()) {
- print_add_new_fact($controller->record->getXref(), $indifacts, 'INDI');
+ FunctionsPrint::printAddNewFact($controller->record->getXref(), $indifacts, 'INDI');
}
echo '</tbody>';
echo '</table>';
diff --git a/app/Module/LoggedInUsersModule.php b/app/Module/LoggedInUsersModule.php
index e4a4d2e074..34798457c6 100644
--- a/app/Module/LoggedInUsersModule.php
+++ b/app/Module/LoggedInUsersModule.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Theme;
@@ -78,7 +79,7 @@ class LoggedInUsersModule extends AbstractModule implements ModuleBlockInterface
}
$content .= ' - ' . Filter::escapeHtml($user->getUserName());
if (Auth::id() != $user->getUserId() && $user->getPreference('contactmethod') != 'none') {
- $content .= ' <a class="icon-email" href="#" onclick="return message(\'' . Filter::escapeHtml($user->getUserName()) . '\', \'\', \'' . Filter::escapeHtml(get_query_url()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
+ $content .= ' <a class="icon-email" href="#" onclick="return message(\'' . Filter::escapeHtml($user->getUserName()) . '\', \'\', \'' . Filter::escapeHtml(Functions::getQueryUrl()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
}
$content .= '</div>';
}
diff --git a/app/Module/MediaTabModule.php b/app/Module/MediaTabModule.php
index c44aada02f..c1b6535a10 100644
--- a/app/Module/MediaTabModule.php
+++ b/app/Module/MediaTabModule.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -61,10 +64,10 @@ class MediaTabModule extends AbstractModule implements ModuleTabInterface {
echo '<table class="facts_table">';
foreach ($this->getFactsWithMedia() as $fact) {
if ($fact->getTag() == 'OBJE') {
- print_main_media($fact, 1);
+ FunctionsPrintFacts::printMainMedia($fact, 1);
} else {
for ($i = 2; $i < 4; ++$i) {
- print_main_media($fact, $i);
+ FunctionsPrintFacts::printMainMedia($fact, $i);
}
}
}
@@ -82,7 +85,7 @@ class MediaTabModule extends AbstractModule implements ModuleTabInterface {
<a href="#" onclick="window.open('addmedia.php?action=showmediaform&amp;linktoid=<?php echo $controller->record->getXref(); ?>&amp;ged=<?php echo $controller->record->getTree()->getNameUrl(); ?>', '_blank', edit_window_specs); return false;">
<?php echo I18N::translate('Add a new media object'); ?>
</a>
- <?php echo help_link('OBJE'); ?>
+ <?php echo FunctionsPrint::helpLink('OBJE'); ?>
<br>
<a href="#" onclick="window.open('inverselink.php?linktoid=<?php echo $controller->record->getXref(); ?>&amp;ged=<?php echo $WT_TREE->getNameUrl(); ?>&amp;linkto=person', '_blank', find_window_specs); return false;">
<?php echo I18N::translate('Link to an existing media object'); ?>
@@ -120,7 +123,7 @@ class MediaTabModule extends AbstractModule implements ModuleTabInterface {
$this->facts[] = $fact;
}
}
- sort_facts($this->facts);
+ Functions::sortFacts($this->facts);
}
return $this->facts;
diff --git a/app/Module/NotesTabModule.php b/app/Module/NotesTabModule.php
index 3baae34ed0..e2ed55fccf 100644
--- a/app/Module/NotesTabModule.php
+++ b/app/Module/NotesTabModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -69,10 +71,10 @@ class NotesTabModule extends AbstractModule implements ModuleTabInterface {
<?php
foreach ($this->getFactsWithNotes() as $fact) {
if ($fact->getTag() == 'NOTE') {
- print_main_notes($fact, 1);
+ FunctionsPrintFacts::printMainNotes($fact, 1);
} else {
for ($i = 2; $i < 4; ++$i) {
- print_main_notes($fact, $i);
+ FunctionsPrintFacts::printMainNotes($fact, $i);
}
}
}
@@ -138,7 +140,7 @@ class NotesTabModule extends AbstractModule implements ModuleTabInterface {
$this->facts[] = $fact;
}
}
- sort_facts($this->facts);
+ Functions::sortFacts($this->facts);
}
return $this->facts;
diff --git a/app/Module/OnThisDayModule.php b/app/Module/OnThisDayModule.php
index 56080143e1..c78b1c1c3b 100644
--- a/app/Module/OnThisDayModule.php
+++ b/app/Module/OnThisDayModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -67,12 +69,12 @@ class OnThisDayModule extends AbstractModule implements ModuleBlockInterface {
switch ($infoStyle) {
case 'list':
// Output style 1: Old format, no visible tables, much smaller text. Better suited to right side of page.
- $content .= print_events_list($todayjd, $todayjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
+ $content .= FunctionsPrintLists::eventsList($todayjd, $todayjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
break;
case 'table':
// Style 2: New format, tables, big text, etc. Not too good on right side of page
ob_start();
- $content .= print_events_table($todayjd, $todayjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
+ $content .= FunctionsPrintLists::eventsTable($todayjd, $todayjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
$content .= ob_get_clean();
break;
}
@@ -122,25 +124,25 @@ class OnThisDayModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show only events of living individuals?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('filter', $filter);
+ echo FunctionsEdit::editFieldYesNo('filter', $filter);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show only births, deaths, and marriages?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('onlyBDM', $onlyBDM);
+ echo FunctionsEdit::editFieldYesNo('onlyBDM', $onlyBDM);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Sort order');
echo '</td><td class="optionbox">';
- echo select_edit_control('sortStyle', array(
+ echo FunctionsEdit::selectEditControl('sortStyle', array(
/* I18N: An option in a list-box */ 'alpha' => I18N::translate('sort by name'),
/* I18N: An option in a list-box */ 'anniv' => I18N::translate('sort by date'),
), null, $sortStyle, '');
@@ -149,7 +151,7 @@ class OnThisDayModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/RecentChangesModule.php b/app/Module/RecentChangesModule.php
index fa61f21736..190d358a17 100644
--- a/app/Module/RecentChangesModule.php
+++ b/app/Module/RecentChangesModule.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -55,7 +58,7 @@ class RecentChangesModule extends AbstractModule implements ModuleBlockInterface
}
}
- $found_facts = get_recent_changes(WT_CLIENT_JD - $days);
+ $found_facts = FunctionsDb::getRecentChanges(WT_CLIENT_JD - $days);
if (!$found_facts && $hide_empty) {
return '';
@@ -78,11 +81,11 @@ class RecentChangesModule extends AbstractModule implements ModuleBlockInterface
ob_start();
switch ($infoStyle) {
case 'list':
- $content .= print_changes_list($found_facts, $sortStyle);
+ $content .= FunctionsPrintLists::changesList($found_facts, $sortStyle);
break;
case 'table':
// sortable table
- $content .= print_changes_table($found_facts, $sortStyle);
+ $content .= FunctionsPrintLists::changesTable($found_facts, $sortStyle);
break;
}
$content .= ob_get_clean();
@@ -140,13 +143,13 @@ class RecentChangesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Sort order');
echo '</td><td class="optionbox">';
- echo select_edit_control('sortStyle', array(
+ echo FunctionsEdit::selectEditControl('sortStyle', array(
'name' => /* I18N: An option in a list-box */ I18N::translate('sort by name'),
'date_asc' => /* I18N: An option in a list-box */ I18N::translate('sort by date, oldest first'),
'date_desc' => /* I18N: An option in a list-box */ I18N::translate('sort by date, newest first'),
@@ -156,13 +159,13 @@ class RecentChangesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Should this block be hidden when it is empty?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('hide_empty', $hide_empty);
+ echo FunctionsEdit::editFieldYesNo('hide_empty', $hide_empty);
echo '</td></tr>';
echo '<tr><td colspan="2" class="optionbox wrap">';
echo '<span class="error">', I18N::translate('If you hide an empty block, you will not be able to change its configuration until it becomes visible by no longer being empty.'), '</span>';
diff --git a/app/Module/RelativesTabModule.php b/app/Module/RelativesTabModule.php
index bbb17801a8..4be0781440 100644
--- a/app/Module/RelativesTabModule.php
+++ b/app/Module/RelativesTabModule.php
@@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
@@ -120,7 +121,7 @@ class RelativesTabModule extends AbstractModule implements ModuleTabInterface {
?>
<tr>
<td class="<?php echo $class; ?>">
- <?php echo get_close_relationship_name($controller->record, $person); ?>
+ <?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
</td>
<td class="<?php echo $controller->getPersonStyle($person); ?>">
<?php echo Theme::theme()->individualBoxLarge($person); ?>
@@ -154,7 +155,7 @@ class RelativesTabModule extends AbstractModule implements ModuleTabInterface {
?>
<tr>
<td class="<?php echo $class; ?>">
- <?php echo get_close_relationship_name($controller->record, $person); ?>
+ <?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
</td>
<td class="<?php echo $controller->getPersonStyle($person); ?>">
<?php echo Theme::theme()->individualBoxLarge($person); ?>
@@ -239,7 +240,7 @@ class RelativesTabModule extends AbstractModule implements ModuleTabInterface {
<tr>
<td class="<?php echo $class; ?>">
<?php echo self::ageDifference($prev, $next, $child_number); ?>
- <?php echo get_close_relationship_name($controller->record, $person); ?>
+ <?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
</td>
<td class="<?php echo $controller->getPersonStyle($person); ?>">
<?php echo Theme::theme()->individualBoxLarge($person); ?>
diff --git a/app/Module/ResearchTaskModule.php b/app/Module/ResearchTaskModule.php
index 8fbd921984..351c1b5ba1 100644
--- a/app/Module/ResearchTaskModule.php
+++ b/app/Module/ResearchTaskModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -104,7 +106,7 @@ class ResearchTaskModule extends AbstractModule implements ModuleBlockInterface
$found = false;
$end_jd = $show_future ? 99999999 : WT_CLIENT_JD;
- foreach (get_calendar_events(0, $end_jd, '_TODO', $WT_TREE) as $fact) {
+ foreach (FunctionsDb::getCalendarEvents(0, $end_jd, '_TODO', $WT_TREE) as $fact) {
$record = $fact->getParent();
$user_name = $fact->getAttribute('_WT_USER');
if ($user_name === Auth::user()->getUserName() || !$user_name && $show_unassigned || $user_name && $show_other) {
@@ -183,25 +185,25 @@ class ResearchTaskModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show research tasks that are assigned to other users');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_other', $show_other);
+ echo FunctionsEdit::editFieldYesNo('show_other', $show_other);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show research tasks that are not assigned to any user');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_unassigned', $show_unassigned);
+ echo FunctionsEdit::editFieldYesNo('show_unassigned', $show_unassigned);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show research tasks that have a date in the future');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('show_future', $show_future);
+ echo FunctionsEdit::editFieldYesNo('show_future', $show_future);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/ReviewChangesModule.php b/app/Module/ReviewChangesModule.php
index 42373a2935..3200b5c37e 100644
--- a/app/Module/ReviewChangesModule.php
+++ b/app/Module/ReviewChangesModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Mail;
@@ -103,8 +105,8 @@ class ReviewChangesModule extends AbstractModule implements ModuleBlockInterface
$content .= "<a href=\"#\" onclick=\"window.open('edit_changes.php','_blank', chan_window_specs); return false;\">" . I18N::translate('There are pending changes for you to moderate.') . "</a><br>";
}
if ($sendmail == "yes") {
- $content .= I18N::translate('Last email reminder was sent ') . format_timestamp(Site::getPreference('LAST_CHANGE_EMAIL')) . "<br>";
- $content .= I18N::translate('Next email reminder will be sent after ') . format_timestamp(Site::getPreference('LAST_CHANGE_EMAIL') + (60 * 60 * 24 * $days)) . "<br><br>";
+ $content .= I18N::translate('Last email reminder was sent ') . FunctionsDate::formatTimestamp(Site::getPreference('LAST_CHANGE_EMAIL')) . "<br>";
+ $content .= I18N::translate('Next email reminder will be sent after ') . FunctionsDate::formatTimestamp(Site::getPreference('LAST_CHANGE_EMAIL') + (60 * 60 * 24 * $days)) . "<br><br>";
}
$content .= '<ul>';
$changes = Database::prepare(
@@ -172,7 +174,7 @@ class ReviewChangesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Send out reminder emails?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('sendmail', $sendmail);
+ echo FunctionsEdit::editFieldYesNo('sendmail', $sendmail);
echo '<br>';
echo I18N::translate('Reminder email frequency (days)') . "&nbsp;<input type='text' name='days' value='" . $days . "' size='2'>";
echo '</td></tr>';
@@ -180,7 +182,7 @@ class ReviewChangesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/SlideShowModule.php b/app/Module/SlideShowModule.php
index c4f2b20203..42ebec0c7a 100644
--- a/app/Module/SlideShowModule.php
+++ b/app/Module/SlideShowModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Media;
@@ -173,7 +175,7 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface {
$content .= '<a href="' . $source->getHtmlUrl() . '">' . I18N::translate('View source') . ' — ' . $source->getFullname() . '</a><br>';
}
$content .= '<br><div class="indent">';
- $content .= print_fact_notes($random_media->getGedcom(), "1", false);
+ $content .= FunctionsPrint::printFactNotes($random_media->getGedcom(), "1", false);
$content .= '</div>';
$content .= '</td></tr></table>';
$content .= '</div>'; // random_picture_content
@@ -247,7 +249,7 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show only individuals, events, or all?');
echo '</td><td class="optionbox">';
- echo select_edit_control('filter', array('indi' => I18N::translate('Individuals'), 'event' => I18N::translate('Facts and events'), 'all' => I18N::translate('All')), null, $filter, '');
+ echo FunctionsEdit::selectEditControl('filter', array('indi' => I18N::translate('Individuals'), 'event' => I18N::translate('Facts and events'), 'all' => I18N::translate('All')), null, $filter, '');
echo '</td></tr>';
$filters = array(
@@ -393,13 +395,13 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show slide show controls?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('controls', $controls);
+ echo FunctionsEdit::editFieldYesNo('controls', $controls);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Start slide show on page load?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('start', $start);
+ echo FunctionsEdit::editFieldYesNo('start', $start);
echo '</td></tr>';
}
}
diff --git a/app/Module/SourcesTabModule.php b/app/Module/SourcesTabModule.php
index 4840385366..b0a0c3add0 100644
--- a/app/Module/SourcesTabModule.php
+++ b/app/Module/SourcesTabModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Fact;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -69,9 +71,9 @@ class SourcesTabModule extends AbstractModule implements ModuleTabInterface {
<?php
foreach ($this->getFactsWithSources() as $fact) {
if ($fact->getTag() == 'SOUR') {
- print_main_sources($fact, 1);
+ FunctionsPrintFacts::printMainSources($fact, 1);
} else {
- print_main_sources($fact, 2);
+ FunctionsPrintFacts::printMainSources($fact, 2);
}
}
if (!$this->getFactsWithSources()) {
@@ -126,7 +128,7 @@ class SourcesTabModule extends AbstractModule implements ModuleTabInterface {
$this->facts[] = $fact;
}
}
- sort_facts($this->facts);
+ Functions::sortFacts($this->facts);
}
return $this->facts;
diff --git a/app/Module/StoriesModule.php b/app/Module/StoriesModule.php
index f97375fbba..61d7d8b40a 100644
--- a/app/Module/StoriesModule.php
+++ b/app/Module/StoriesModule.php
@@ -19,6 +19,8 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Menu;
@@ -240,7 +242,7 @@ class StoriesModule extends AbstractModule implements ModuleTabInterface, Module
</label>
<div class="col-sm-9">
<input data-autocomplete-type="INDI" type="text" name="xref" id="xref" size="4" value="<?php echo $xref; ?>">
- <?php echo print_findindi_link('xref'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('xref'); ?>
<?php if ($individual): ?>
<?php echo $individual->formatList('span'); ?>
<?php endif; ?>
@@ -252,7 +254,7 @@ class StoriesModule extends AbstractModule implements ModuleTabInterface, Module
<?php echo I18N::translate('Show this block for which languages?'); ?>
</label>
<div class="col-sm-9">
- <?php echo edit_language_checkboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?>
+ <?php echo FunctionsEdit::editLanguageCheckboxes('lang', explode(',', $this->getBlockSetting($block_id, 'languages'))); ?>
</div>
</div>
@@ -350,7 +352,7 @@ class StoriesModule extends AbstractModule implements ModuleTabInterface, Module
</label>
<input type="hidden" name="mod" value="<?php echo $this->getName(); ?>">
<input type="hidden" name="mod_action" value="admin_config">
- <?php echo select_edit_control('ged', Tree::getNameList(), null, $WT_TREE->getName(), 'class="form-control"'); ?>
+ <?php echo FunctionsEdit::selectEditControl('ged', Tree::getNameList(), null, $WT_TREE->getName(), 'class="form-control"'); ?>
<input type="submit" class="btn btn-primary" value="<?php echo I18N::translate('show'); ?>">
</form>
diff --git a/app/Module/TopGivenNamesModule.php b/app/Module/TopGivenNamesModule.php
index 1be79fb682..c7e93f1a79 100644
--- a/app/Module/TopGivenNamesModule.php
+++ b/app/Module/TopGivenNamesModule.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Stats;
use Fisharebest\Webtrees\Theme;
@@ -147,13 +148,13 @@ class TopGivenNamesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/TopPageViewsModule.php b/app/Module/TopPageViewsModule.php
index b4b143ad82..641c41e142 100644
--- a/app/Module/TopPageViewsModule.php
+++ b/app/Module/TopPageViewsModule.php
@@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -141,13 +142,13 @@ class TopPageViewsModule extends AbstractModule implements ModuleBlockInterface
echo "<tr><td class=\"descriptionbox wrap width33\">";
echo I18N::translate('Place counts before or after name?');
echo "</td><td class=\"optionbox\">";
- echo select_edit_control('count_placement', array('before' => I18N::translate('before'), 'after' => I18N::translate('after')), null, $count_placement, '');
+ echo FunctionsEdit::selectEditControl('count_placement', array('before' => I18N::translate('before'), 'after' => I18N::translate('after')), null, $count_placement, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/TopSurnamesModule.php b/app/Module/TopSurnamesModule.php
index f64906de43..85e0423737 100644
--- a/app/Module/TopSurnamesModule.php
+++ b/app/Module/TopSurnamesModule.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Query\QueryName;
use Fisharebest\Webtrees\Theme;
@@ -55,7 +58,7 @@ class TopSurnamesModule extends AbstractModule implements ModuleBlockInterface {
}
// This next function is a bit out of date, and doesn't cope well with surname variants
- $top_surnames = get_top_surnames($WT_TREE->getTreeId(), $COMMON_NAMES_THRESHOLD, $num);
+ $top_surnames = FunctionsDb::getTopSurnames($WT_TREE->getTreeId(), $COMMON_NAMES_THRESHOLD, $num);
// Remove names found in the "Remove Names" list
if ($COMMON_NAMES_REMOVE) {
@@ -95,20 +98,20 @@ class TopSurnamesModule extends AbstractModule implements ModuleBlockInterface {
switch ($infoStyle) {
case 'tagcloud':
uksort($all_surnames, '\Fisharebest\Webtrees\I18N::strcasecmp');
- $content = format_surname_tagcloud($all_surnames, 'indilist.php', true, $WT_TREE);
+ $content = FunctionsPrintLists::surnameTagCloud($all_surnames, 'indilist.php', true, $WT_TREE);
break;
case 'list':
uasort($all_surnames, '\Fisharebest\Webtrees\Module\TopSurnamesModule::surnameCountSort');
- $content = format_surname_list($all_surnames, '1', true, 'indilist.php', $WT_TREE);
+ $content = FunctionsPrintLists::surnameList($all_surnames, '1', true, 'indilist.php', $WT_TREE);
break;
case 'array':
uasort($all_surnames, '\Fisharebest\Webtrees\Module\TopSurnamesModule::surnameCountSort');
- $content = format_surname_list($all_surnames, '2', true, 'indilist.php', $WT_TREE);
+ $content = FunctionsPrintLists::surnameList($all_surnames, '2', true, 'indilist.php', $WT_TREE);
break;
case 'table':
default:
uasort($all_surnames, '\Fisharebest\Webtrees\Module\TopSurnamesModule::surnameCountSort');
- $content = format_surname_table($all_surnames, 'indilist.php', $WT_TREE);
+ $content = FunctionsPrintLists::surnameTable($all_surnames, 'indilist.php', $WT_TREE);
break;
}
@@ -160,13 +163,13 @@ class TopSurnamesModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('bullet list'), 'array' => I18N::translate('compact list'), 'table' => I18N::translate('table'), 'tagcloud' => I18N::translate('tag cloud')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('bullet list'), 'array' => I18N::translate('compact list'), 'table' => I18N::translate('table'), 'tagcloud' => I18N::translate('tag cloud')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
diff --git a/app/Module/UpcomingAnniversariesModule.php b/app/Module/UpcomingAnniversariesModule.php
index 64351d1977..a41a6c210f 100644
--- a/app/Module/UpcomingAnniversariesModule.php
+++ b/app/Module/UpcomingAnniversariesModule.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees\Module;
*/
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -69,12 +71,12 @@ class UpcomingAnniversariesModule extends AbstractModule implements ModuleBlockI
switch ($infoStyle) {
case 'list':
// Output style 1: Old format, no visible tables, much smaller text. Better suited to right side of page.
- $content .= print_events_list($startjd, $endjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
+ $content .= FunctionsPrintLists::eventsList($startjd, $endjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
break;
case 'table':
// Style 2: New format, tables, big text, etc. Not too good on right side of page
ob_start();
- $content .= print_events_table($startjd, $endjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
+ $content .= FunctionsPrintLists::eventsTable($startjd, $endjd, $onlyBDM ? 'BIRT MARR DEAT' : '', $filter, $sortStyle);
$content .= ob_get_clean();
break;
}
@@ -133,25 +135,25 @@ class UpcomingAnniversariesModule extends AbstractModule implements ModuleBlockI
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show only events of living individuals?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('filter', $filter);
+ echo FunctionsEdit::editFieldYesNo('filter', $filter);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Show only births, deaths, and marriages?');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('onlyBDM', $onlyBDM);
+ echo FunctionsEdit::editFieldYesNo('onlyBDM', $onlyBDM);
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Sort order');
echo '</td><td class="optionbox">';
- echo select_edit_control('sortStyle', array(
+ echo FunctionsEdit::selectEditControl('sortStyle', array(
/* I18N: An option in a list-box */ 'alpha' => I18N::translate('sort by name'),
/* I18N: An option in a list-box */ 'anniv' => I18N::translate('sort by date'),
), null, $sortStyle, '');
@@ -160,7 +162,7 @@ class UpcomingAnniversariesModule extends AbstractModule implements ModuleBlockI
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/UserJournalModule.php b/app/Module/UserJournalModule.php
index 7330517656..026d5e3730 100644
--- a/app/Module/UserJournalModule.php
+++ b/app/Module/UserJournalModule.php
@@ -18,6 +18,7 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -78,7 +79,7 @@ class UserJournalModule extends AbstractModule implements ModuleBlockInterface {
foreach ($usernews as $news) {
$content .= '<div class="journal_box">';
$content .= '<div class="news_title">' . $news->subject . '</div>';
- $content .= '<div class="news_date">' . format_timestamp($news->updated) . '</div>';
+ $content .= '<div class="news_date">' . FunctionsDate::formatTimestamp($news->updated) . '</div>';
if ($news->body == strip_tags($news->body)) {
// No HTML?
$news->body = nl2br($news->body, false);
diff --git a/app/Module/UserMessagesModule.php b/app/Module/UserMessagesModule.php
index 780f62cf49..01739de07a 100644
--- a/app/Module/UserMessagesModule.php
+++ b/app/Module/UserMessagesModule.php
@@ -18,6 +18,8 @@ namespace Fisharebest\Webtrees\Module;
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
use Fisharebest\Webtrees\User;
@@ -89,7 +91,7 @@ class UserMessagesModule extends AbstractModule implements ModuleBlockInterface
$content .= '<tr>';
$content .= '<td class="list_value_wrap"><input type="checkbox" id="cb_message' . $message->message_id . '" name="message_id[]" value="' . $message->message_id . '"></td>';
$content .= '<td class="list_value_wrap"><a href="#" onclick="return expand_layer(\'message' . $message->message_id . '\');"><i id="message' . $message->message_id . '_img" class="icon-plus"></i> <b dir="auto">' . Filter::escapeHtml($message->subject) . '</b></a></td>';
- $content .= '<td class="list_value_wrap">' . format_timestamp($message->created + WT_TIMESTAMP_OFFSET) . '</td>';
+ $content .= '<td class="list_value_wrap">' . FunctionsDate::formatTimestamp($message->created + WT_TIMESTAMP_OFFSET) . '</td>';
$content .= '<td class="list_value_wrap">';
$user = User::findByIdentifier($message->sender);
if ($user) {
@@ -151,7 +153,7 @@ class UserMessagesModule extends AbstractModule implements ModuleBlockInterface
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Module/YahrzeitModule.php b/app/Module/YahrzeitModule.php
index 231e667cc2..301d5c3f61 100644
--- a/app/Module/YahrzeitModule.php
+++ b/app/Module/YahrzeitModule.php
@@ -22,6 +22,8 @@ use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Date\GregorianDate;
use Fisharebest\Webtrees\Date\JewishDate;
use Fisharebest\Webtrees\Filter;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Theme;
@@ -76,7 +78,7 @@ class YahrzeitModule extends AbstractModule implements ModuleBlockInterface {
// Fetch normal anniversaries...
$yahrzeits = array();
for ($jd = $startjd - 1; $jd <= $endjd + $days; ++$jd) {
- foreach (get_anniversary_events($jd, 'DEAT _YART', $WT_TREE) as $fact) {
+ foreach (FunctionsDb::getAnniversaryEvents($jd, 'DEAT _YART', $WT_TREE) as $fact) {
// Exact hebrew dates only
$date = $fact->getDate();
if ($date->minimumDate() instanceof JewishDate && $date->minimumJulianDay() === $date->maximumJulianDay()) {
@@ -262,13 +264,13 @@ class YahrzeitModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Presentation style');
echo '</td><td class="optionbox">';
- echo select_edit_control('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
+ echo FunctionsEdit::selectEditControl('infoStyle', array('list' => I18N::translate('list'), 'table' => I18N::translate('table')), null, $infoStyle, '');
echo '</td></tr>';
echo '<tr><td class="descriptionbox wrap width33">';
echo I18N::translate('Calendar');
echo '</td><td class="optionbox">';
- echo select_edit_control('calendar', array(
+ echo FunctionsEdit::selectEditControl('calendar', array(
'jewish' => /* I18N: The Hebrew/Jewish calendar */ I18N::translate('Jewish'),
'gregorian' => /* I18N: The gregorian calendar */ I18N::translate('Gregorian'),
), null, $calendar, '');
@@ -277,7 +279,7 @@ class YahrzeitModule extends AbstractModule implements ModuleBlockInterface {
echo '<tr><td class="descriptionbox wrap width33">';
echo /* I18N: label for a yes/no option */ I18N::translate('Add a scrollbar when block contents grow');
echo '</td><td class="optionbox">';
- echo edit_field_yes_no('block', $block);
+ echo FunctionsEdit::editFieldYesNo('block', $block);
echo '</td></tr>';
}
}
diff --git a/app/Report/ReportHtml.php b/app/Report/ReportHtml.php
index e88cda069d..aa5ad15eb3 100644
--- a/app/Report/ReportHtml.php
+++ b/app/Report/ReportHtml.php
@@ -16,6 +16,7 @@ namespace Fisharebest\Webtrees\Report;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\FunctionsRtl;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Media;
@@ -671,7 +672,7 @@ class ReportHtml extends ReportBase {
$lfct = count($lines);
$wraptext = '';
foreach ($lines as $line) {
- $wtext = utf8_wordwrap($line, $lw, "\n", true);
+ $wtext = FunctionsRtl::utf8WordWrap($line, $lw, "\n", true);
$wraptext .= $wtext;
// Add a new line as long as it’s not the last line
if ($lfct > 1) {
diff --git a/app/Report/ReportParserGenerate.php b/app/Report/ReportParserGenerate.php
index ce257dd067..4fa56c07f1 100644
--- a/app/Report/ReportParserGenerate.php
+++ b/app/Report/ReportParserGenerate.php
@@ -19,6 +19,8 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Date;
use Fisharebest\Webtrees\Family;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\I18N;
@@ -533,7 +535,7 @@ class ReportParserGenerate extends ReportParserBase {
* XML <Now /> element handler
*/
private function nowStartHandler() {
- $g = timestamp_to_gedcom_date(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET);
+ $g = FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET);
$this->current_element->addText($g->display());
}
@@ -598,7 +600,7 @@ class ReportParserGenerate extends ReportParserBase {
} else {
$temp = explode(" ", trim($tgedrec));
$level = $temp[0] + 1;
- $newgedrec = get_sub_record($level, "$level $tag", $tgedrec);
+ $newgedrec = Functions::getSubRecord($level, "$level $tag", $tgedrec);
$tgedrec = $newgedrec;
}
}
@@ -919,7 +921,7 @@ class ReportParserGenerate extends ReportParserBase {
$level = $attrs['level'];
}
$tags = preg_split('/[: ]/', $tag);
- $value = $this->get_gedcom_value($tag, $level, $this->gedrec);
+ $value = $this->getGedcomValue($tag, $level, $this->gedrec);
switch (end($tags)) {
case 'DATE':
$tmp = new Date($value);
@@ -985,10 +987,10 @@ class ReportParserGenerate extends ReportParserBase {
$t = $tags[$i];
if (!empty($t)) {
if ($i < ($count - 1)) {
- $subrec = get_sub_record($level, "$level $t", $subrec);
+ $subrec = Functions::getSubRecord($level, "$level $t", $subrec);
if (empty($subrec)) {
$level--;
- $subrec = get_sub_record($level, "@ $t", $this->gedrec);
+ $subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec);
if (empty($subrec)) {
return;
}
@@ -1002,7 +1004,7 @@ class ReportParserGenerate extends ReportParserBase {
$count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER);
$i = 0;
while ($i < $count) {
- $this->repeats[] = get_sub_record($level, "$level $t", $subrec, $i + 1);
+ $this->repeats[] = Functions::getSubRecord($level, "$level $t", $subrec, $i + 1);
$i++;
}
}
@@ -1158,7 +1160,7 @@ class ReportParserGenerate extends ReportParserBase {
$record = GedcomRecord::getInstance($id, $WT_TREE);
if (empty($attrs['diff']) && !empty($id)) {
$facts = $record->getFacts();
- sort_facts($facts);
+ Functions::sortFacts($facts);
$this->repeats = array();
$nonfacts = explode(',', $tag);
foreach ($facts as $event) {
@@ -1231,7 +1233,7 @@ class ReportParserGenerate extends ReportParserBase {
}
}
$this->desc = trim($match[2]);
- $this->desc .= get_cont(2, $this->gedrec);
+ $this->desc .= Functions::getCont(2, $this->gedrec);
}
$repeat_parser = xml_parser_create();
$this->parser = $repeat_parser;
@@ -1343,7 +1345,7 @@ class ReportParserGenerate extends ReportParserBase {
}
$condition = $attrs['condition'];
- $condition = $this->substitute_vars($condition);
+ $condition = $this->substituteVars($condition);
$condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition);
// Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT
$condition = str_replace("@fact", $this->fact, $condition);
@@ -1370,10 +1372,10 @@ class ReportParserGenerate extends ReportParserBase {
if ($level == 0) {
$level++;
}
- $value = $this->get_gedcom_value($id, $level, $this->gedrec);
+ $value = $this->getGedcomValue($id, $level, $this->gedrec);
if (empty($value)) {
$level++;
- $value = $this->get_gedcom_value($id, $level, $this->gedrec);
+ $value = $this->getGedcomValue($id, $level, $this->gedrec);
}
$value = preg_replace("/^@(" . WT_REGEX_XREF . ")@$/", "$1", $value);
$value = "\"" . addslashes($value) . "\"";
@@ -1456,7 +1458,7 @@ class ReportParserGenerate extends ReportParserBase {
* XML <AgeAtDeath /> element handler
*/
private function ageAtDeathStartHandler() {
- // This duplicates functionality in format_fact_date()
+ // This duplicates functionality in FunctionsPrint::format_fact_date()
global $factrec, $WT_TREE;
$match = array();
@@ -1497,7 +1499,7 @@ class ReportParserGenerate extends ReportParserBase {
if ($age != '' && $age != "0d") {
if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age
) {
- $value = get_age_at_event($age, false);
+ $value = FunctionsDate::getAgeAtEvent($age, false);
$abbrev = substr($value, 0, strpos($value, ' ') + 5);
if ($value !== $abbrev) {
$value = $abbrev . '.';
@@ -1843,7 +1845,7 @@ class ReportParserGenerate extends ReportParserBase {
$sql_order_by = "";
foreach ($attrs as $attr => $value) {
if (strpos($attr, 'filter') === 0 && $value) {
- $value = $this->substitute_vars($value);
+ $value = $this->substituteVars($value);
// 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)";
@@ -1922,7 +1924,7 @@ class ReportParserGenerate extends ReportParserBase {
$sql_order_by = "";
foreach ($attrs as $attr => $value) {
if (strpos($attr, 'filter') === 0 && $value) {
- $value = $this->substitute_vars($value);
+ $value = $this->substituteVars($value);
// 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)";
@@ -2092,13 +2094,13 @@ class ReportParserGenerate extends ReportParserBase {
}
$tags = explode(":", $tag);
$t = end($tags);
- $v = $this->get_gedcom_value($tag, 1, $grec);
+ $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 = get_sub_record(1, $tag, $grec);
+ $v = Functions::getSubRecord(1, $tag, $grec);
}
switch ($expr) {
@@ -2296,7 +2298,7 @@ class ReportParserGenerate extends ReportParserBase {
if (isset($attrs['id'])) {
$id = $attrs['id'];
}
- $match = $this->substitute_vars($match);
+ $match = $this->substituteVars($match);
if (preg_match("/\\$(\w+)/", $id, $match)) {
$id = $this->vars[$match[1]]['id'];
$id = trim($id);
@@ -2344,18 +2346,18 @@ class ReportParserGenerate extends ReportParserBase {
}
break;
case "direct-ancestors":
- $this->add_ancestors($this->list, $id, false, $maxgen);
+ $this->addAncestors($this->list, $id, false, $maxgen);
break;
case "ancestors":
- $this->add_ancestors($this->list, $id, true, $maxgen);
+ $this->addAncestors($this->list, $id, true, $maxgen);
break;
case "descendants":
$this->list[$id]->generation = 1;
- $this->add_descendancy($this->list, $id, false, $maxgen);
+ $this->addDescendancy($this->list, $id, false, $maxgen);
break;
case "all":
- $this->add_ancestors($this->list, $id, true, $maxgen);
- $this->add_descendancy($this->list, $id, true, $maxgen);
+ $this->addAncestors($this->list, $id, true, $maxgen);
+ $this->addDescendancy($this->list, $id, true, $maxgen);
break;
}
}
@@ -2568,7 +2570,7 @@ class ReportParserGenerate extends ReportParserBase {
* @param bool $parents
* @param int $generations
*/
- function add_descendancy(&$list, $pid, $parents = false, $generations = -1) {
+ public function addDescendancy(&$list, $pid, $parents = false, $generations = -1) {
global $WT_TREE;
$person = Individual::getInstance($pid, $WT_TREE);
@@ -2615,7 +2617,7 @@ class ReportParserGenerate extends ReportParserBase {
}
if ($generations == -1 || $list[$pid]->generation + 1 < $generations) {
foreach ($children as $child) {
- $this->add_descendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family
+ $this->addDescendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family
}
}
}
@@ -2627,7 +2629,7 @@ class ReportParserGenerate extends ReportParserBase {
* @param bool $children
* @param int $generations
*/
- function add_ancestors(&$list, $pid, $children = false, $generations = -1) {
+ public function addAncestors(&$list, $pid, $children = false, $generations = -1) {
global $WT_TREE;
$genlist = array($pid);
@@ -2680,7 +2682,7 @@ class ReportParserGenerate extends ReportParserBase {
*
* @return string the value of a gedcom tag from the given gedcom record
*/
- function get_gedcom_value($tag, $level, $gedrec) {
+ public function getGedcomValue($tag, $level, $gedrec) {
global $WT_TREE;
if (empty($gedrec)) {
@@ -2695,14 +2697,14 @@ class ReportParserGenerate extends ReportParserBase {
$subrec = $gedrec;
foreach ($tags as $t) {
$lastsubrec = $subrec;
- $subrec = get_sub_record($level, "$level $t", $subrec);
+ $subrec = Functions::getSubRecord($level, "$level $t", $subrec);
if (empty($subrec) && $origlevel == 0) {
$level--;
- $subrec = get_sub_record($level, "$level $t", $lastsubrec);
+ $subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec);
}
if (empty($subrec)) {
if ($t == "TITL") {
- $subrec = get_sub_record($level, "$level ABBR", $lastsubrec);
+ $subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec);
if (!empty($subrec)) {
$t = "ABBR";
}
@@ -2711,7 +2713,7 @@ class ReportParserGenerate extends ReportParserBase {
if ($level > 0) {
$level--;
}
- $subrec = get_sub_record($level, "@ $t", $gedrec);
+ $subrec = Functions::getSubRecord($level, "@ $t", $gedrec);
if (empty($subrec)) {
return '';
}
@@ -2739,7 +2741,7 @@ class ReportParserGenerate extends ReportParserBase {
}
}
if ($level != 0 || $t != "NOTE") {
- $value .= get_cont($level + 1, $subrec);
+ $value .= Functions::getCont($level + 1, $subrec);
}
return $value;
@@ -2755,7 +2757,7 @@ class ReportParserGenerate extends ReportParserBase {
*
* @return string
*/
- private function substitute_vars($expression) {
+ private function substituteVars($expression) {
$tmp = $this->vars; // PHP5.3 cannot use $this in closures
return preg_replace_callback(
@@ -2765,6 +2767,7 @@ class ReportParserGenerate extends ReportParserBase {
return '"' . $tmp[$matches[1]]['id'] . '"';
} else {
Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1]));
+
return '$' . $matches[1];
}
},
diff --git a/app/Report/ReportPdfCell.php b/app/Report/ReportPdfCell.php
index 0f367df039..b25d5972ba 100644
--- a/app/Report/ReportPdfCell.php
+++ b/app/Report/ReportPdfCell.php
@@ -15,6 +15,7 @@ namespace Fisharebest\Webtrees\Report;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsRtl;
/**
* Class ReportPdfCell
@@ -108,7 +109,7 @@ class ReportPdfCell extends ReportBaseCell {
if ($renderer->checkPageBreakPDF($cHT)) {
$this->top = $renderer->GetY();
}
- $temptext = spanLTRRTL($temptext, "BOTH");
+ $temptext = FunctionsRtl::spanLtrRtl($temptext, "BOTH");
}
// HTML ready - last value is true
$renderer->MultiCell(
diff --git a/app/Report/ReportPdfText.php b/app/Report/ReportPdfText.php
index 6c00a3d493..f129532a5c 100644
--- a/app/Report/ReportPdfText.php
+++ b/app/Report/ReportPdfText.php
@@ -15,6 +15,7 @@ namespace Fisharebest\Webtrees\Report;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsRtl;
/**
* Class ReportPdfText
@@ -44,7 +45,7 @@ class ReportPdfText extends ReportBaseText {
} else {
$renderer->SetTextColor(0, 0, 0);
}
- $temptext = spanLTRRTL($temptext, "BOTH");
+ $temptext = FunctionsRtl::spanLtrRtl($temptext, "BOTH");
$temptext = str_replace(
array('<br><span dir="rtl" >', '<br><span dir="ltr" >', '> ', ' <'),
array('<span dir="rtl" ><br>', '<span dir="ltr" ><br>', '>&nbsp;', '&nbsp;<'),
diff --git a/app/Schema/Migration22.php b/app/Schema/Migration22.php
index 8a73348adf..1f14eb27ef 100644
--- a/app/Schema/Migration22.php
+++ b/app/Schema/Migration22.php
@@ -51,7 +51,7 @@ class Migration22 implements MigrationInterface {
// $WT_DATA_DIR = /home/fisharebest/public_html/webtrees/data/
// Therefore we need to calculate ../../../my_pictures/
$_media_dir = '';
- $_tmp_dir = $WT_DATA_DIR;
+ $_tmp_dir = $WT_DATA_DIR;
while (strpos($_mf_dir, $_tmp_dir) !== 0) {
$_media_dir .= '../';
$_tmp_dir = preg_replace('~[^/\\\\]+[/\\\\]$~', '', $_tmp_dir);
diff --git a/app/Stats.php b/app/Stats.php
index 53e3119767..5666d8f4c8 100644
--- a/app/Stats.php
+++ b/app/Stats.php
@@ -16,6 +16,11 @@ namespace Fisharebest\Webtrees;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\Module\FamilyTreeFavoritesModule;
use Fisharebest\Webtrees\Module\UserFavoritesModule;
use Fisharebest\Webtrees\Query\QueryName;
@@ -1291,7 +1296,7 @@ class Stats {
case 'place':
$fact = GedcomRecord::getInstance($row['d_gid'], $this->tree)->getFirstFact($row['d_fact']);
if ($fact) {
- $result = format_fact_place($fact, true, true, true);
+ $result = FunctionsPrint::formatFactPlace($fact, true, true, true);
} else {
$result = I18N::translate('Private');
}
@@ -2108,7 +2113,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $age = get_age_at_event($age, true);
+ $age = FunctionsDate::getAgeAtEvent($age, true);
if ($person->canShow()) {
if ($type == 'list') {
$top10[] = "<li><a href=\"" . $person->getHtmlUrl() . "\">" . $person->getFullName() . "</a> (" . $age . ")" . "</li>";
@@ -2185,7 +2190,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $age = get_age_at_event($age, true);
+ $age = FunctionsDate::getAgeAtEvent($age, true);
if ($type === 'list') {
$top10[] = "<li><a href=\"" . $person->getHtmlUrl() . "\">" . $person->getFullName() . "</a> (" . $age . ")" . "</li>";
} else {
@@ -2254,7 +2259,7 @@ class Stats {
$age = $age . 'd';
}
- return get_age_at_event($age, true);
+ return FunctionsDate::getAgeAtEvent($age, true);
} else {
return I18N::number($age / 365.25);
}
@@ -2682,7 +2687,7 @@ class Stats {
case 'place':
$fact = $record->getFirstFact($row['fact']);
if ($fact) {
- $result = format_fact_place($fact, true, true, true);
+ $result = FunctionsPrint::formatFactPlace($fact, true, true, true);
} else {
$result = I18N::translate('Private');
}
@@ -2832,7 +2837,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $result = get_age_at_event($age, true);
+ $result = FunctionsDate::getAgeAtEvent($age, true);
} else {
$result = I18N::number((int) ($age / 365.25));
}
@@ -2941,7 +2946,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $age = get_age_at_event($age, true);
+ $age = FunctionsDate::getAgeAtEvent($age, true);
if ($type === 'age') {
return $age;
}
@@ -3036,7 +3041,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $age = get_age_at_event($age, true);
+ $age = FunctionsDate::getAgeAtEvent($age, true);
if ($family->canShow()) {
if ($type === 'list') {
$top10[] = '<li><a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a> (' . $age . ')' . "</li>";
@@ -3124,7 +3129,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $result = get_age_at_event($age, true);
+ $result = FunctionsDate::getAgeAtEvent($age, true);
} else {
$result = (int) ($age / 365.25);
}
@@ -4110,7 +4115,7 @@ class Stats {
} else {
$age = $age . 'd';
}
- $age = get_age_at_event($age, true);
+ $age = FunctionsDate::getAgeAtEvent($age, true);
if ($type == 'age') {
return $age;
}
@@ -4835,7 +4840,7 @@ class Stats {
} else {
$sorting = 'alpha';
}
- $surname_list = get_common_surnames($threshold, $this->tree);
+ $surname_list = FunctionsDb::getCommonSurnames($threshold, $this->tree);
if (count($surname_list) == 0) {
return '';
}
@@ -4863,14 +4868,14 @@ class Stats {
$surnames = array_merge($surnames, QueryName::surnames($this->tree, $surname, '', false, false));
}
- return format_surname_list($surnames, ($type == 'list' ? 1 : 2), $show_tot, 'indilist.php', $this->tree);
+ return FunctionsPrintLists::surnameList($surnames, ($type == 'list' ? 1 : 2), $show_tot, 'indilist.php', $this->tree);
}
/**
* @return string
*/
public function getCommonSurname() {
- $surnames = array_keys(get_top_surnames($this->tree->getTreeId(), 1, 1));
+ $surnames = array_keys(FunctionsDb::getTopSurnames($this->tree->getTreeId(), 1, 1));
return array_shift($surnames);
}
@@ -4949,7 +4954,7 @@ class Stats {
}
$sizes = explode('x', $size);
$tot_indi = $this->totalIndividualsQuery();
- $surnames = get_common_surnames($threshold, $this->tree);
+ $surnames = FunctionsDb::getCommonSurnames($threshold, $this->tree);
if (count($surnames) <= 0) {
return '';
}
@@ -5425,9 +5430,9 @@ class Stats {
}
if (Auth::id() != $user->getUserId() && $user->getPreference('contactmethod') != 'none') {
if ($type == 'list') {
- $content .= '<br><a class="icon-email" href="#" onclick="return message(\'' . $user->getUserId() . '\', \'\', \'' . Filter::escapeJs(get_query_url()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
+ $content .= '<br><a class="icon-email" href="#" onclick="return message(\'' . $user->getUserId() . '\', \'\', \'' . Filter::escapeJs(Functions::getQueryUrl()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
} else {
- $content .= ' <a class="icon-email" href="#" onclick="return message(\'' . $user->getUserId() . '\', \'\', \'' . Filter::escapeJs(get_query_url()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
+ $content .= ' <a class="icon-email" href="#" onclick="return message(\'' . $user->getUserId() . '\', \'\', \'' . Filter::escapeJs(Functions::getQueryUrl()) . '\');" title="' . I18N::translate('Send a message') . '"></a>';
}
}
if ($type == 'list') {
@@ -5561,7 +5566,7 @@ class Stats {
$datestamp = I18N::dateFormat();
}
- return timestamp_to_gedcom_date($user->getPreference('reg_timestamp'))->display(false, $datestamp);
+ return FunctionsDate::timestampToGedcomDate($user->getPreference('reg_timestamp'))->display(false, $datestamp);
case 'regtime':
if (is_array($params) && isset($params[0]) && $params[0] != '') {
$datestamp = $params[0];
@@ -5664,7 +5669,7 @@ class Stats {
* @return string
*/
public function serverDate() {
- return timestamp_to_gedcom_date(WT_TIMESTAMP)->display();
+ return FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP)->display();
}
/**
@@ -5695,7 +5700,7 @@ class Stats {
* @return string
*/
public function browserDate() {
- return timestamp_to_gedcom_date(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET)->display();
+ return FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET)->display();
}
/**
diff --git a/app/Theme/AdministrationTheme.php b/app/Theme/AdministrationTheme.php
index ea3823e144..5eddaa8b2d 100644
--- a/app/Theme/AdministrationTheme.php
+++ b/app/Theme/AdministrationTheme.php
@@ -212,7 +212,7 @@ class AdministrationTheme extends BaseTheme {
/** {@inheritdoc} */
protected function secondaryMenuContent(array $menus) {
- return implode('', array_map(function ($menu) { return $menu->bootstrap(); }, $menus));
+ return implode('', array_map(function (Menu $menu) { return $menu->bootstrap(); }, $menus));
}
/** {@inheritdoc} */
diff --git a/app/Theme/BaseTheme.php b/app/Theme/BaseTheme.php
index 1d5f4ec78a..d9e6785b5d 100644
--- a/app/Theme/BaseTheme.php
+++ b/app/Theme/BaseTheme.php
@@ -21,6 +21,7 @@ use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\FlashMessages;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\GedcomTag;
use Fisharebest\Webtrees\HitCounter;
@@ -253,7 +254,7 @@ abstract class BaseTheme {
case 'mailto':
return '<a href="mailto:' . Filter::escapeHtml($user->getEmail()) . '">' . $user->getRealNameHtml() . '</a>';
default:
- return "<a href='#' onclick='message(\"" . Filter::escapeHtml($user->getUserName()) . "\", \"" . $method . "\", \"" . WT_BASE_URL . Filter::escapeHtml(get_query_url()) . "\", \"\");return false;'>" . $user->getRealNameHtml() . '</a>';
+ return "<a href='#' onclick='message(\"" . Filter::escapeHtml($user->getUserName()) . "\", \"" . $method . "\", \"" . WT_BASE_URL . Filter::escapeHtml(Functions::getQueryUrl()) . "\", \"\");return false;'>" . $user->getRealNameHtml() . '</a>';
}
}
@@ -1355,7 +1356,7 @@ abstract class BaseTheme {
$language_tag = $locale->languageTag();
$menu->addSubmenu(new Menu(
$locale->endonym(),
- get_query_url(array('lang' => $language_tag), '&amp;'),
+ Functions::getQueryUrl(array('lang' => $language_tag), '&amp;'),
'menu-language-' . $language_tag . (WT_LOCALE === $language_tag ? ' active' : '')
));
}
@@ -1509,7 +1510,7 @@ abstract class BaseTheme {
if (Auth::check() || Auth::isSearchEngine() || WT_SCRIPT_NAME === 'login.php') {
return null;
} else {
- return new Menu(I18N::translate('Login'), WT_LOGIN_URL . '?url=' . rawurlencode(get_query_url()));
+ return new Menu(I18N::translate('Login'), WT_LOGIN_URL . '?url=' . rawurlencode(Functions::getQueryUrl()));
}
}
@@ -1695,7 +1696,7 @@ abstract class BaseTheme {
foreach (Theme::installedThemes() as $theme) {
$submenu = new Menu(
$theme->themeName(),
- get_query_url(array('theme' => $theme->themeId()), '&amp;'),
+ Functions::getQueryUrl(array('theme' => $theme->themeId()), '&amp;'),
'menu-theme-' . $theme->themeId() . ($theme === $this ? ' active' : '')
);
$submenus[] = $submenu;
@@ -1957,7 +1958,7 @@ abstract class BaseTheme {
* @return string
*/
protected function primaryMenuContent(array $menus) {
- return implode('', array_map(function ($menu) { return $menu->getMenuAsList(); }, $menus));
+ return implode('', array_map(function (Menu $menu) { return $menu->getMenuAsList(); }, $menus));
}
/**
@@ -1996,7 +1997,7 @@ abstract class BaseTheme {
* @return string
*/
protected function secondaryMenuContent(array $menus) {
- return implode('', array_map(function ($menu) { return $menu->getMenuAsList(); }, $menus));
+ return implode('', array_map(function (Menu $menu) { return $menu->getMenuAsList(); }, $menus));
}
/**
diff --git a/app/Theme/ColorsTheme.php b/app/Theme/ColorsTheme.php
index 0c04b80a4e..2d081bd537 100644
--- a/app/Theme/ColorsTheme.php
+++ b/app/Theme/ColorsTheme.php
@@ -16,6 +16,7 @@ namespace Fisharebest\Webtrees\Theme;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Auth;
+use Fisharebest\Webtrees\Functions\Functions;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Menu;
use Fisharebest\Webtrees\Session;
@@ -138,7 +139,7 @@ class ColorsTheme extends CloudsTheme {
foreach ($this->palettes as $palette_id => $palette_name) {
$menu->addSubmenu(new Menu(
$palette_name,
- get_query_url(array('themecolor' => $palette_id), '&amp;'),
+ Functions::getQueryUrl(array('themecolor' => $palette_id), '&amp;'),
'menu-color-' . $palette_id . ($this->palette === $palette_id ? ' active' : '')
));
}
diff --git a/app/Tree.php b/app/Tree.php
index b8e8b18982..660c92efe4 100644
--- a/app/Tree.php
+++ b/app/Tree.php
@@ -16,6 +16,8 @@ namespace Fisharebest\Webtrees;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsExport;
+use Fisharebest\Webtrees\Functions\FunctionsImport;
use PDOException;
/**
@@ -677,9 +679,9 @@ class Tree {
'tree_id_5' => $this->tree_id,
));
- $buffer = reformat_record_export(gedcom_header($this));
+ $buffer = FunctionsExport::reformatRecord(FunctionsExport::gedcomHeader($this));
while ($row = $stmt->fetch()) {
- $buffer .= reformat_record_export($row->gedcom);
+ $buffer .= FunctionsExport::reformatRecord($row->gedcom);
if (strlen($buffer) > 65535) {
fwrite($stream, $buffer);
$buffer = '';
@@ -858,7 +860,7 @@ class Tree {
// Accept this pending change
if (Auth::user()->getPreference('auto_accept')) {
- accept_all_changes($xref, $this->tree_id);
+ FunctionsImport::acceptAllChanges($xref, $this->tree_id);
}
// Return the newly created record. Note that since GedcomRecord
// has a cache of pending changes, we cannot use it to create a
diff --git a/calendar.php b/calendar.php
index 65e06b5f59..d7855cb27f 100644
--- a/calendar.php
+++ b/calendar.php
@@ -30,6 +30,8 @@ use Fisharebest\Webtrees\Date\HijriDate;
use Fisharebest\Webtrees\Date\JalaliDate;
use Fisharebest\Webtrees\Date\JewishDate;
use Fisharebest\Webtrees\Date\JulianDate;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'calendar.php');
require './includes/session.php';
@@ -191,7 +193,7 @@ echo '<a href="?cal=', $cal, '&amp;day=', $cal_date->d, '&amp;month=', $cal_mont
echo ' <input type="text" name="year" value="', $year, '" size="4"> ';
echo '<a href="?cal=', $cal, '&amp;day=', $cal_date->d, '&amp;month=', $cal_month, '&amp;year=', $cal_date->y === -1 ? 1 : $cal_date->y + 1, '&amp;filterev=', $filterev, '&amp;filterof=', $filterof, '&amp;filtersx=', $filtersx, '&amp;view=', $view, '">+1</a>';
echo ' | <a href="?cal=', $cal, '&amp;day=', $cal_date->d, '&amp;month=', $cal_month, '&amp;year=', $today->y, '&amp;filterev=', $filterev, '&amp;filterof=', $filterof, '&amp;filtersx=', $filtersx, '&amp;view=', $view, '"><b>' . $today->format('%Y') . '</b></a>';
-echo help_link('annivers_year_select');
+echo FunctionsPrint::helpLink('annivers_year_select');
echo '</td> ';
// Filtering options
@@ -377,7 +379,7 @@ if ($filterev === 'all') {
// Fetch data for day/month/year views
switch ($view) {
case 'day':
- $found_facts = apply_filter(get_anniversary_events($cal_date->minJD, $events, $WT_TREE), $filterof, $filtersx);
+ $found_facts = apply_filter(FunctionsDb::getAnniversaryEvents($cal_date->minJD, $events, $WT_TREE), $filterof, $filtersx);
break;
case 'month':
$cal_date->d = 0;
@@ -389,7 +391,7 @@ case 'month':
}
// Fetch events for each day
for ($jd = $cal_date->minJD; $jd <= $cal_date->maxJD; ++$jd) {
- foreach (apply_filter(get_anniversary_events($jd, $events, $WT_TREE), $filterof, $filtersx) as $fact) {
+ foreach (apply_filter(FunctionsDb::getAnniversaryEvents($jd, $events, $WT_TREE), $filterof, $filtersx) as $fact) {
$tmp = $fact->getDate()->minimumDate();
if ($tmp->d >= 1 && $tmp->d <= $tmp->daysInMonth()) {
// If the day is valid (for its own calendar), display it in the
@@ -405,7 +407,7 @@ case 'month':
case 'year':
$cal_date->m = 0;
$cal_date->setJdFromYmd();
- $found_facts = apply_filter(get_calendar_events($ged_date->minimumJulianDay(), $ged_date->maximumJulianDay(), $events, $WT_TREE), $filterof, $filtersx);
+ $found_facts = apply_filter(FunctionsDb::getCalendarEvents($ged_date->minimumJulianDay(), $ged_date->maximumJulianDay(), $events, $WT_TREE), $filterof, $filtersx);
// Eliminate duplicates (e.g. BET JUL 1900 AND SEP 1900 will appear twice in 1900)
$found_facts = array_unique($found_facts);
break;
diff --git a/compact.php b/compact.php
index 66ef969bc9..dfb1d936e0 100644
--- a/compact.php
+++ b/compact.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\CompactController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'compact.php');
require './includes/session.php';
@@ -47,7 +48,7 @@ $controller
</td>
<td class="optionbox vmiddle">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="rootid" id="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td <?php echo $WT_TREE->getPreference('SHOW_HIGHLIGHT_IMAGES') ? 'rowspan="2"' : ''; ?> class="facts_label03">
<input type="submit" value="<?php echo I18N::translate('View'); ?>">
diff --git a/composer.json b/composer.json
index abc89807eb..da281356d3 100644
--- a/composer.json
+++ b/composer.json
@@ -4,20 +4,6 @@
"keywords": ["webtrees", "genealogy"],
"license": "GPL-3.0",
"autoload": {
- "files": [
- "includes/functions/functions.php",
- "includes/functions/functions_charts.php",
- "includes/functions/functions_date.php",
- "includes/functions/functions_db.php",
- "includes/functions/functions_edit.php",
- "includes/functions/functions_export.php",
- "includes/functions/functions_import.php",
- "includes/functions/functions_mediadb.php",
- "includes/functions/functions_print.php",
- "includes/functions/functions_print_facts.php",
- "includes/functions/functions_print_lists.php",
- "includes/functions/functions_rtl.php"
- ],
"psr-4": {
"Fisharebest\\Webtrees\\": "app/"
}
diff --git a/composer.lock b/composer.lock
index 4e9c534249..f300b14e34 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "1316ff1474379c159949f295d57524a8",
+ "hash": "04b82f415dff853254bb91505ad80ae1",
"packages": [
{
"name": "bombayworks/zendframework1",
diff --git a/descendancy.php b/descendancy.php
index 6dfc6b4ff8..4f4a4d0433 100644
--- a/descendancy.php
+++ b/descendancy.php
@@ -24,6 +24,9 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\DescendancyController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'descendancy.php');
require './includes/session.php';
@@ -46,7 +49,7 @@ $controller
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" id="rootid" name="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td rowspan="3" class="descriptionbox">
<?php echo I18N::translate('Layout'); ?>
@@ -73,7 +76,7 @@ $controller
<?php echo I18N::translate('Generations'); ?>
</td>
<td class="optionbox">
- <?php echo edit_field_integers('generations', $controller->generations, 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS')); ?>
+ <?php echo FunctionsEdit::editFieldInteger('generations', $controller->generations, 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS')); ?>
</td>
</tr>
<tr>
@@ -81,7 +84,7 @@ $controller
<?php echo I18N::translate('Show details'); ?>
</td>
<td class="optionbox">
- <?php echo two_state_checkbox('show_full', $controller->showFull()); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox('show_full', $controller->showFull()); ?>
</td>
</tr>
</tbody>
@@ -106,11 +109,11 @@ if ($controller->error_message) {
break;
case 2: // Individual list
$descendants = $controller->individualDescendancy($controller->root, $controller->generations, array());
- echo '<div id="descendancy-list">', format_indi_table($descendants), '</div>';
+ echo '<div id="descendancy-list">', FunctionsPrintLists::individualTable($descendants), '</div>';
break;
case 3: // Family list
$descendants = $controller->familyDescendancy($controller->root, $controller->generations, array());
- echo '<div id="descendancy-list">', format_fam_table($descendants), '</div>';
+ echo '<div id="descendancy-list">', FunctionsPrintLists::familyTable($descendants), '</div>';
break;
}
}
diff --git a/edit_changes.php b/edit_changes.php
index f42002e2be..53dba039ae 100644
--- a/edit_changes.php
+++ b/edit_changes.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\FunctionsImport;
define('WT_SCRIPT_NAME', 'edit_changes.php');
require './includes/session.php';
@@ -78,10 +79,10 @@ case 'accept':
foreach ($changes as $change) {
if (empty($change->new_gedcom)) {
// delete
- update_record($change->old_gedcom, $gedcom_id, true);
+ FunctionsImport::updateRecord($change->old_gedcom, $gedcom_id, true);
} else {
// add/update
- update_record($change->new_gedcom, $gedcom_id, false);
+ FunctionsImport::updateRecord($change->new_gedcom, $gedcom_id, false);
}
Database::prepare("UPDATE `##change` SET status='accepted' WHERE change_id=?")->execute(array($change->change_id));
Log::addEditLog("Accepted change {$change->change_id} for {$change->xref} / {$change->gedcom_name} into database");
@@ -105,10 +106,10 @@ case 'acceptall':
foreach ($changes as $change) {
if (empty($change->new_gedcom)) {
// delete
- update_record($change->old_gedcom, $change->gedcom_id, true);
+ FunctionsImport::updateRecord($change->old_gedcom, $change->gedcom_id, true);
} else {
// add/update
- update_record($change->new_gedcom, $change->gedcom_id, false);
+ FunctionsImport::updateRecord($change->new_gedcom, $change->gedcom_id, false);
}
Database::prepare("UPDATE `##change` SET status='accepted' WHERE change_id=?")->execute(array($change->change_id));
Log::addEditLog("Accepted change {$change->change_id} for {$change->xref} / {$change->gedcom_name} into database");
diff --git a/edit_interface.php b/edit_interface.php
index 22b260f1b6..3f1f70d652 100644
--- a/edit_interface.php
+++ b/edit_interface.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
define('WT_SCRIPT_NAME', 'edit_interface.php');
@@ -55,7 +57,7 @@ case 'editraw':
<div id="edit_interface-page">
<h4>
<?php echo $controller->getPageTitle(); ?>
- <?php echo help_link('edit_edit_raw'); ?>
+ <?php echo FunctionsPrint::helpLink('edit_edit_raw'); ?>
</h4>
<pre> <?php echo '0 @' . $record->getXref() . '@ ' . $record::RECORD_TYPE; ?></pre>
<form method="post" action="edit_interface.php">
@@ -165,8 +167,8 @@ case 'editrawfact':
<div id="edit_interface-page">
<h4>
<?php echo $controller->getPageTitle(); ?>
- <?php echo help_link('edit_edit_raw'); ?>
- <?php print_specialchar_link('gedcom'); ?>
+ <?php echo FunctionsPrint::helpLink('edit_edit_raw'); ?>
+ <?php FunctionsPrint::printSpecialCharacterLink('gedcom'); ?>
</h4>
<form method="post" action="edit_interface.php">
<input type="hidden" name="ged" value="<?php echo $WT_TREE->getNameHtml(); ?>">
@@ -262,7 +264,7 @@ case 'edit':
echo '<div id="edit_interface-page">';
echo '<h4>', $controller->getPageTitle(), '</h4>';
- init_calendar_popup();
+ FunctionsPrint::initializeCalendarPopup();
echo '<form name="editform" method="post" action="edit_interface.php" enctype="multipart/form-data">';
echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
echo '<input type="hidden" name="action" value="update">';
@@ -271,7 +273,7 @@ case 'edit':
echo '<input type="hidden" name="prev_action" value="edit">';
echo Filter::getCsrf();
echo '<table class="facts_table">';
- create_edit_form($record, $edit_fact);
+ FunctionsEdit::createEditForm($record, $edit_fact);
echo keep_chan($record);
echo '</table>';
@@ -280,8 +282,8 @@ case 'edit':
case 'REPO':
// REPO:NAME facts may take a NOTE (but the REPO record may not).
if ($level1type === 'NAME') {
- print_add_layer('NOTE');
- print_add_layer('SHARED_NOTE');
+ FunctionsEdit::printAddLayer('NOTE');
+ FunctionsEdit::printAddLayer('SHARED_NOTE');
}
break;
case 'FAM':
@@ -289,22 +291,22 @@ case 'edit':
// FAM and INDI records have real facts. They can take NOTE/SOUR/OBJE/etc.
if ($level1type !== 'SEX' && $level1type !== 'NOTE' && $level1type !== 'ALIA') {
if ($level1type !== 'SOUR') {
- print_add_layer('SOUR');
+ FunctionsEdit::printAddLayer('SOUR');
}
if ($level1type !== 'OBJE') {
- print_add_layer('OBJE');
+ FunctionsEdit::printAddLayer('OBJE');
}
- print_add_layer('NOTE');
- print_add_layer('SHARED_NOTE', 2, $level1type);
+ FunctionsEdit::printAddLayer('NOTE');
+ FunctionsEdit::printAddLayer('SHARED_NOTE', 2, $level1type);
if ($level1type !== 'ASSO' && $level1type !== 'NOTE' && $level1type !== 'SOUR') {
- print_add_layer('ASSO');
+ FunctionsEdit::printAddLayer('ASSO');
}
// allow to add godfather and godmother for CHR fact or best man and bridesmaid for MARR fact in one window
if ($level1type === 'CHR' || $level1type === 'MARR') {
- print_add_layer('ASSO2');
+ FunctionsEdit::printAddLayer('ASSO2');
}
if ($level1type !== 'SOUR') {
- print_add_layer('RESN');
+ FunctionsEdit::printAddLayer('RESN');
}
}
break;
@@ -345,7 +347,7 @@ case 'add':
echo '<div id="edit_interface-page">';
echo '<h4>', $controller->getPageTitle(), '</h4>';
- init_calendar_popup();
+ FunctionsPrint::initializeCalendarPopup();
echo '<form name="addform" method="post" action="edit_interface.php" enctype="multipart/form-data">';
echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
echo '<input type="hidden" name="action" value="update">';
@@ -355,7 +357,7 @@ case 'add':
echo Filter::getCsrf();
echo '<table class="facts_table">';
- create_add_form($fact);
+ FunctionsEdit::createAddForm($fact);
echo keep_chan($record);
echo '</table>';
@@ -364,19 +366,19 @@ case 'add':
if ($level0type === 'INDI' || $level0type === 'FAM') {
// ... but not facts which are simply links to other records
if ($fact !== 'OBJE' && $fact !== 'NOTE' && $fact !== 'SHARED_NOTE' && $fact !== 'REPO' && $fact !== 'SOUR' && $fact !== 'ASSO' && $fact !== 'ALIA') {
- print_add_layer('SOUR');
- print_add_layer('OBJE');
+ FunctionsEdit::printAddLayer('SOUR');
+ FunctionsEdit::printAddLayer('OBJE');
// Don’t add notes to notes!
if ($fact !== 'NOTE') {
- print_add_layer('NOTE');
- print_add_layer('SHARED_NOTE', 2, $fact);
+ FunctionsEdit::printAddLayer('NOTE');
+ FunctionsEdit::printAddLayer('SHARED_NOTE', 2, $fact);
}
- print_add_layer('ASSO');
+ FunctionsEdit::printAddLayer('ASSO');
// allow to add godfather and godmother for CHR fact or best man and bridesmaid for MARR fact in one window
if ($fact === 'CHR' || $fact === 'MARR') {
- print_add_layer('ASSO2');
+ FunctionsEdit::printAddLayer('ASSO2');
}
- print_add_layer('RESN');
+ FunctionsEdit::printAddLayer('RESN');
}
}
?>
@@ -479,7 +481,7 @@ case 'update':
}
}
- $newged = handle_updates($newged);
+ $newged = FunctionsEdit::handleUpdates($newged);
$newged = substr($newged, 1); // Remove leading newline
$record->updateFact($fact_id, $newged, !$keep_chan);
@@ -537,20 +539,20 @@ case 'add_child_to_family_action':
$controller->pageHeader();
- splitSOUR();
+ FunctionsEdit::splitSource();
$gedrec = '0 @REF@ INDI';
- $gedrec .= addNewName();
- $gedrec .= addNewSex();
+ $gedrec .= FunctionsEdit::addNewName();
+ $gedrec .= FunctionsEdit::addNewSex();
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $gedrec .= addNewFact($match);
+ $gedrec .= FunctionsEdit::addNewFact($match);
}
}
$gedrec .= "\n" . GedcomCodePedi::createNewFamcPedi($PEDI, $xref);
if (Filter::postBool('SOUR_INDI')) {
- $gedrec = handle_updates($gedrec);
+ $gedrec = FunctionsEdit::handleUpdates($gedrec);
} else {
- $gedrec = updateRest($gedrec);
+ $gedrec = FunctionsEdit::updateRest($gedrec);
}
// Create the new child
@@ -626,21 +628,21 @@ case 'add_child_to_individual_action':
$person->createFact('1 FAMS @' . $family->getXref() . '@', true);
// Create a child
- splitSOUR(); // separate SOUR record from the rest
+ FunctionsEdit::splitSource(); // separate SOUR record from the rest
$gedcom = '0 @NEW@ INDI';
- $gedcom .= addNewName();
- $gedcom .= addNewSex();
+ $gedcom .= FunctionsEdit::addNewName();
+ $gedcom .= FunctionsEdit::addNewSex();
$gedcom .= "\n" . GedcomCodePedi::createNewFamcPedi($PEDI, $family->getXref());
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $gedcom .= addNewFact($match);
+ $gedcom .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_INDI')) {
- $gedcom = handle_updates($gedcom);
+ $gedcom = FunctionsEdit::handleUpdates($gedcom);
} else {
- $gedcom = updateRest($gedcom);
+ $gedcom = FunctionsEdit::updateRest($gedcom);
}
$child = $person->getTree()->createRecord($gedcom);
@@ -705,20 +707,20 @@ case 'add_parent_to_individual_action':
$person->createFact('1 FAMC @' . $family->getXref() . '@', true);
// Create a child
- splitSOUR(); // separate SOUR record from the rest
+ FunctionsEdit::splitSource(); // separate SOUR record from the rest
$gedcom = '0 @NEW@ INDI';
- $gedcom .= addNewName();
- $gedcom .= addNewSex();
+ $gedcom .= FunctionsEdit::addNewName();
+ $gedcom .= FunctionsEdit::addNewSex();
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $gedcom .= addNewFact($match);
+ $gedcom .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_INDI')) {
- $gedcom = handle_updates($gedcom);
+ $gedcom = FunctionsEdit::handleUpdates($gedcom);
} else {
- $gedcom = updateRest($gedcom);
+ $gedcom = FunctionsEdit::updateRest($gedcom);
}
$gedcom .= "\n1 FAMS @" . $family->getXref() . '@';
@@ -766,19 +768,19 @@ case 'add_unlinked_indi_action':
->restrictAccess(Auth::isManager($WT_TREE))
->pageHeader();
- splitSOUR();
+ FunctionsEdit::splitSource();
$gedrec = '0 @REF@ INDI';
- $gedrec .= addNewName();
- $gedrec .= addNewSex();
+ $gedrec .= FunctionsEdit::addNewName();
+ $gedrec .= FunctionsEdit::addNewSex();
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $gedrec .= addNewFact($match);
+ $gedrec .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_INDI')) {
- $gedrec = handle_updates($gedrec);
+ $gedrec = FunctionsEdit::handleUpdates($gedrec);
} else {
- $gedrec = updateRest($gedrec);
+ $gedrec = FunctionsEdit::updateRest($gedrec);
}
$new_indi = $WT_TREE->createRecord($gedrec);
@@ -834,31 +836,31 @@ case 'add_spouse_to_individual_action':
->setPageTitle(I18N::translate('Add a new spouse'))
->pageHeader();
- splitSOUR();
+ FunctionsEdit::splitSource();
$indi_gedcom = '0 @REF@ INDI';
- $indi_gedcom .= addNewName();
- $indi_gedcom .= addNewSex();
+ $indi_gedcom .= FunctionsEdit::addNewName();
+ $indi_gedcom .= FunctionsEdit::addNewSex();
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $indi_gedcom .= addNewFact($match);
+ $indi_gedcom .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_INDI')) {
- $indi_gedcom = handle_updates($indi_gedcom);
+ $indi_gedcom = FunctionsEdit::handleUpdates($indi_gedcom);
} else {
- $indi_gedcom = updateRest($indi_gedcom);
+ $indi_gedcom = FunctionsEdit::updateRest($indi_gedcom);
}
$fam_gedcom = '';
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $fam_gedcom .= addNewFact($match);
+ $fam_gedcom .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_FAM')) {
- $fam_gedcom = handle_updates($fam_gedcom);
+ $fam_gedcom = FunctionsEdit::handleUpdates($fam_gedcom);
} else {
- $fam_gedcom = updateRest($fam_gedcom);
+ $fam_gedcom = FunctionsEdit::updateRest($fam_gedcom);
}
// Create the new spouse
@@ -922,21 +924,21 @@ case 'add_spouse_to_family_action':
$controller->pageHeader();
// Create the new spouse
- splitSOUR(); // separate SOUR record from the rest
+ FunctionsEdit::splitSource(); // separate SOUR record from the rest
$gedrec = '0 @REF@ INDI';
- $gedrec .= addNewName();
- $gedrec .= addNewSex();
+ $gedrec .= FunctionsEdit::addNewName();
+ $gedrec .= FunctionsEdit::addNewSex();
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $gedrec .= addNewFact($match);
+ $gedrec .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_INDI')) {
- $gedrec = handle_updates($gedrec);
+ $gedrec = FunctionsEdit::handleUpdates($gedrec);
} else {
- $gedrec = updateRest($gedrec);
+ $gedrec = FunctionsEdit::updateRest($gedrec);
}
$gedrec .= "\n1 FAMS @" . $family->getXref() . '@';
$spouse = $family->getTree()->createRecord($gedrec);
@@ -950,13 +952,13 @@ case 'add_spouse_to_family_action':
$famrec = '';
if (preg_match_all('/([A-Z0-9_]+)/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
foreach ($matches[1] as $match) {
- $famrec .= addNewFact($match);
+ $famrec .= FunctionsEdit::addNewFact($match);
}
}
if (Filter::postBool('SOUR_FAM')) {
- $famrec = handle_updates($famrec);
+ $famrec = FunctionsEdit::handleUpdates($famrec);
} else {
- $famrec = updateRest($famrec);
+ $famrec = FunctionsEdit::updateRest($famrec);
}
$family->createFact(trim($famrec), true); // trim leading \n
@@ -995,7 +997,7 @@ case 'addfamlink':
</td>
<td class="facts_value">
<input data-autocomplete-type="FAM" type="text" id="famid" name="famid" size="8">
- <?php echo print_findfamily_link('famid'); ?>
+ <?php echo FunctionsPrint::printFindFamilyLink('famid'); ?>
</td>
</tr>
<tr>
@@ -1003,7 +1005,7 @@ case 'addfamlink':
<?php echo GedcomTag::getLabel('PEDI'); ?>
</td>
<td class="facts_value">
- <?php echo edit_field_pedi('PEDI', '', '', $person); ?>
+ <?php echo FunctionsEdit::editFieldPedigree('PEDI', '', '', $person); ?>
<p class="small text-muted">
<?php echo I18N::translate('A child may have more than one set of parents. The relationship between the child and the parents can be biological, legal, or based on local culture and tradition. If no pedigree is specified, then a biological relationship will be assumed.'); ?>
</p>
@@ -1086,7 +1088,7 @@ case 'linkspouse':
}
$controller->pageHeader();
- init_calendar_popup();
+ FunctionsPrint::initializeCalendarPopup();
?>
<div id="edit_interface-page">
@@ -1104,21 +1106,21 @@ case 'linkspouse':
</td>
<td class="facts_value">
<input data-autocomplete-type="INDI" id="spouseid" type="text" name="spid" size="8">
- <?php echo print_findindi_link('spouseid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('spouseid'); ?>
</td>
</tr>
- <?php add_simple_tag('0 MARR Y'); ?>
- <?php add_simple_tag('0 DATE', 'MARR'); ?>
- <?php add_simple_tag('0 PLAC', 'MARR'); ?>
+ <?php FunctionsEdit::addSimpleTag('0 MARR Y'); ?>
+ <?php FunctionsEdit::addSimpleTag('0 DATE', 'MARR'); ?>
+ <?php FunctionsEdit::addSimpleTag('0 PLAC', 'MARR'); ?>
<?php echo keep_chan($person); ?>
</table>
- <?php print_add_layer('SOUR'); ?>
- <?php print_add_layer('OBJE'); ?>
- <?php print_add_layer('NOTE'); ?>
- <?php print_add_layer('SHARED_NOTE'); ?>
- <?php print_add_layer('ASSO'); ?>
- <?php print_add_layer('ASSO2'); ?>
- <?php print_add_layer('RESN'); ?>
+ <?php FunctionsEdit::printAddLayer('SOUR'); ?>
+ <?php FunctionsEdit::printAddLayer('OBJE'); ?>
+ <?php FunctionsEdit::printAddLayer('NOTE'); ?>
+ <?php FunctionsEdit::printAddLayer('SHARED_NOTE'); ?>
+ <?php FunctionsEdit::printAddLayer('ASSO'); ?>
+ <?php FunctionsEdit::printAddLayer('ASSO2'); ?>
+ <?php FunctionsEdit::printAddLayer('RESN'); ?>
<p id="save-cancel">
<input type="submit" class="save" value="<?php echo I18N::translate('save'); ?>">
<input type="button" class="cancel" value="<?php echo I18N::translate('close'); ?>" onclick="window.close();">
@@ -1161,21 +1163,21 @@ case 'linkspouseaction':
} else {
$gedcom = "0 @new@ FAM\n1 HUSB @" . $spouse->getXref() . "@\n1 WIFE @" . $person->getXref() . '@';
}
- splitSOUR();
- $gedcom .= addNewFact('MARR');
+ FunctionsEdit::splitSource();
+ $gedcom .= FunctionsEdit::addNewFact('MARR');
if (Filter::postBool('SOUR_FAM') || count($tagSOUR) > 0) {
// before adding 2 SOUR it needs to add 1 MARR Y first
- if (addNewFact('MARR') === '') {
+ if (FunctionsEdit::addNewFact('MARR') === '') {
$gedcom .= "\n1 MARR Y";
}
- $gedcom = handle_updates($gedcom);
+ $gedcom = FunctionsEdit::handleUpdates($gedcom);
} else {
// before adding level 2 facts it needs to add 1 MARR Y first
- if (addNewFact('MARR') === '') {
+ if (FunctionsEdit::addNewFact('MARR') === '') {
$gedcom .= "\n1 MARR Y";
}
- $gedcom = updateRest($gedcom);
+ $gedcom = FunctionsEdit::updateRest($gedcom);
}
$family = $person->getTree()->createRecord($gedcom);
@@ -1213,23 +1215,23 @@ case 'addnewsource':
<?php echo Filter::getCsrf(); ?>
<table class="facts_table">
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('TITL'); ?></td>
- <td class="optionbox wrap"><input type="text" data-autocomplete-type="SOUR_TITL" name="TITL" id="TITL" value="" size="60"> <?php echo print_specialchar_link('TITL'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" data-autocomplete-type="SOUR_TITL" name="TITL" id="TITL" value="" size="60"> <?php echo FunctionsPrint::printSpecialCharacterLink('TITL'); ?></td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('ABBR'); ?></td>
- <td class="optionbox wrap"><input type="text" name="ABBR" id="ABBR" value="" size="40" maxlength="255"> <?php echo print_specialchar_link('ABBR'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="ABBR" id="ABBR" value="" size="40" maxlength="255"> <?php echo FunctionsPrint::printSpecialCharacterLink('ABBR'); ?></td></tr>
<?php if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), "_HEB") !== false) { ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('_HEB'); ?></td>
- <td class="optionbox wrap"><input type="text" name="_HEB" id="_HEB" value="" size="60"> <?php echo print_specialchar_link('_HEB'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="_HEB" id="_HEB" value="" size="60"> <?php echo FunctionsPrint::printSpecialCharacterLink('_HEB'); ?></td></tr>
<?php } ?>
<?php if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), "ROMN") !== false) { ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('ROMN'); ?></td>
- <td class="optionbox wrap"><input type="text" name="ROMN" id="ROMN" value="" size="60"> <?php echo print_specialchar_link('ROMN'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="ROMN" id="ROMN" value="" size="60"> <?php echo FunctionsPrint::printSpecialCharacterLink('ROMN'); ?></td></tr>
<?php } ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('AUTH'); ?></td>
- <td class="optionbox wrap"><input type="text" name="AUTH" id="AUTH" value="" size="40" maxlength="255"> <?php echo print_specialchar_link('AUTH'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="AUTH" id="AUTH" value="" size="40" maxlength="255"> <?php echo FunctionsPrint::printSpecialCharacterLink('AUTH'); ?></td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('PUBL'); ?></td>
- <td class="optionbox wrap"><textarea name="PUBL" id="PUBL" rows="5" cols="60"></textarea><br><?php echo print_specialchar_link('PUBL'); ?></td></tr>
+ <td class="optionbox wrap"><textarea name="PUBL" id="PUBL" rows="5" cols="60"></textarea><br><?php echo FunctionsPrint::printSpecialCharacterLink('PUBL'); ?></td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('REPO'); ?></td>
- <td class="optionbox wrap"><input type="text" data-autocomplete-type="REPO" name="REPO" id="REPO" value="" size="10"> <?php echo print_findrepository_link('REPO'), ' ', print_addnewrepository_link('REPO'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" data-autocomplete-type="REPO" name="REPO" id="REPO" value="" size="10"> <?php echo FunctionsPrint::printFindRepositoryLink('REPO'), ' ', FunctionsEdit::printAddNewRepositoryLink('REPO'); ?></td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('CALN'); ?></td>
<td class="optionbox wrap"><input type="text" name="CALN" id="CALN" value=""></td></tr>
<?php echo keep_chan(); ?>
@@ -1239,7 +1241,7 @@ case 'addnewsource':
<div id="events" style="display: none;">
<table class="facts_table">
<tr>
- <td class="descriptionbox wrap width25"><?php echo I18N::translate('Select events'), help_link('edit_SOUR_EVEN'); ?></td>
+ <td class="descriptionbox wrap width25"><?php echo I18N::translate('Select events'), FunctionsPrint::helpLink('edit_SOUR_EVEN'); ?></td>
<td class="optionbox wrap"><select name="EVEN[]" multiple="multiple" size="5">
<?php
$parts = explode(',', $WT_TREE->getPreference('INDI_FACTS_ADD'));
@@ -1256,9 +1258,9 @@ case 'addnewsource':
</select></td>
</tr>
<?php
- add_simple_tag('0 DATE', 'EVEN');
- add_simple_tag('0 PLAC', 'EVEN');
- add_simple_tag('0 AGNC');
+ FunctionsEdit::addSimpleTag('0 DATE', 'EVEN');
+ FunctionsEdit::addSimpleTag('0 PLAC', 'EVEN');
+ FunctionsEdit::addSimpleTag('0 AGNC');
?>
</table>
</div>
@@ -1361,7 +1363,7 @@ case 'addnewnote':
echo I18N::translate('Shared note');
echo '</td>';
echo '<td class="optionbox wrap"><textarea name="NOTE" id="NOTE" rows="15" cols="87"></textarea>';
- echo print_specialchar_link('NOTE');
+ echo FunctionsPrint::printSpecialCharacterLink('NOTE');
echo '</td>';
echo '</tr>';
echo keep_chan();
@@ -1455,7 +1457,7 @@ case 'editnote':
<td class="optionbox wrap">
<textarea name="NOTE" id="NOTE" rows="15" cols="90"><?php echo Filter::escapeHtml($note->getNote()); ?></textarea>
<br>
- <?php echo print_specialchar_link('NOTE'); ?>
+ <?php echo FunctionsPrint::printSpecialCharacterLink('NOTE'); ?>
</td>
</tr>
<?php echo keep_chan($note); ?>
@@ -1532,17 +1534,17 @@ case 'addnewrepository':
<?php echo Filter::getCsrf(); ?>
<table class="facts_table">
<tr><td class="descriptionbox wrap width25"><?php echo I18N::translate('Repository name'); ?></td>
- <td class="optionbox wrap"><input type="text" name="REPO_NAME" id="REPO_NAME" value="" size="40" maxlength="255"> <?php echo print_specialchar_link('REPO_NAME'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="REPO_NAME" id="REPO_NAME" value="" size="40" maxlength="255"> <?php echo FunctionsPrint::printSpecialCharacterLink('REPO_NAME'); ?></td></tr>
<?php if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), "_HEB") !== false) { ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('_HEB'); ?></td>
- <td class="optionbox wrap"><input type="text" name="_HEB" id="_HEB" value="" size="40" maxlength="255"> <?php echo print_specialchar_link('_HEB'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="_HEB" id="_HEB" value="" size="40" maxlength="255"> <?php echo FunctionsPrint::printSpecialCharacterLink('_HEB'); ?></td></tr>
<?php } ?>
<?php if (strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), "ROMN") !== false) { ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('ROMN'); ?></td>
- <td class="optionbox wrap"><input type="text" name="ROMN" id="ROMN" value="" size="40" maxlength="255"> <?php echo print_specialchar_link('ROMN'); ?></td></tr>
+ <td class="optionbox wrap"><input type="text" name="ROMN" id="ROMN" value="" size="40" maxlength="255"> <?php echo FunctionsPrint::printSpecialCharacterLink('ROMN'); ?></td></tr>
<?php } ?>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('ADDR'); ?></td>
- <td class="optionbox wrap"><textarea name="ADDR" id="ADDR" rows="5" cols="60"></textarea><?php echo print_specialchar_link('ADDR'); ?> </td></tr>
+ <td class="optionbox wrap"><textarea name="ADDR" id="ADDR" rows="5" cols="60"></textarea><?php echo FunctionsPrint::printSpecialCharacterLink('ADDR'); ?> </td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('PHON'); ?></td>
<td class="optionbox wrap"><input type="text" name="PHON" id="PHON" value="" size="40" maxlength="255"> </td></tr>
<tr><td class="descriptionbox wrap width25"><?php echo GedcomTag::getLabel('EMAIL'); ?></td>
@@ -2344,7 +2346,7 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
echo '<div id="edit_interface-page">';
echo '<h4>', $controller->getPageTitle(), '</h4>';
- init_calendar_popup();
+ FunctionsPrint::initializeCalendarPopup();
echo '<form method="post" name="addchildform" onsubmit="return checkform();">';
echo '<input type="hidden" name="ged" value="', $WT_TREE->getNameHtml(), '">';
echo '<input type="hidden" name="action" value="', $nextaction, '">';
@@ -2360,11 +2362,11 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
case 'add_child_to_family_action':
case 'add_child_to_individual_action':
// When adding a new child, specify the pedigree
- add_simple_tag('0 PEDI');
+ FunctionsEdit::addSimpleTag('0 PEDI');
break;
case 'update':
// When adding/editing a name, specify the type
- add_simple_tag('0 TYPE ' . $name_type, '', '', null, $person);
+ FunctionsEdit::addSimpleTag('0 TYPE ' . $name_type, '', '', null, $person);
break;
}
@@ -2623,7 +2625,7 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
// Edit the standard name fields
foreach ($name_fields as $tag => $value) {
- add_simple_tag('0 ' . $tag . ' ' . $value);
+ FunctionsEdit::addSimpleTag('0 ' . $tag . ' ' . $value);
}
// Get the advanced name fields
@@ -2650,20 +2652,20 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
if ($mnsct > 0) {
$marnm_surn = $match2[1];
}
- add_simple_tag('2 _MARNM ' . $value);
- add_simple_tag('2 _MARNM_SURN ' . $marnm_surn);
+ FunctionsEdit::addSimpleTag('2 _MARNM ' . $value);
+ FunctionsEdit::addSimpleTag('2 _MARNM_SURN ' . $marnm_surn);
} else {
- add_simple_tag('2 ' . $tag . ' ' . $value, '', GedcomTag::getLabel('NAME:' . $tag, $person));
+ FunctionsEdit::addSimpleTag('2 ' . $tag . ' ' . $value, '', GedcomTag::getLabel('NAME:' . $tag, $person));
}
}
}
// Allow a new row to be entered if there was no row provided
if (count($match[1]) === 0 && empty($name_fields[$tag]) || $tag !== '_HEB' && $tag !== 'NICK') {
if ($tag === '_MARNM') {
- add_simple_tag('0 _MARNM');
- add_simple_tag('0 _MARNM_SURN ' . $new_marnm);
+ FunctionsEdit::addSimpleTag('0 _MARNM');
+ FunctionsEdit::addSimpleTag('0 _MARNM_SURN ' . $new_marnm);
} else {
- add_simple_tag('0 ' . $tag, '', GedcomTag::getLabel('NAME:' . $tag, $person));
+ FunctionsEdit::addSimpleTag('0 ' . $tag, '', GedcomTag::getLabel('NAME:' . $tag, $person));
}
}
}
@@ -2693,7 +2695,7 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
$text .= $cmatch[2];
$i++;
}
- add_simple_tag($level . ' ' . $type . ' ' . $text);
+ FunctionsEdit::addSimpleTag($level . ' ' . $type . ' ' . $text);
}
$tags[] = $type;
$i++;
@@ -2712,17 +2714,17 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
echo '</table><br><table class="facts_table">';
// 1 SEX
if ($famtag === 'HUSB' || $gender === 'M') {
- add_simple_tag("0 SEX M");
+ FunctionsEdit::addSimpleTag("0 SEX M");
} elseif ($famtag === 'WIFE' || $gender === 'F') {
- add_simple_tag('0 SEX F');
+ FunctionsEdit::addSimpleTag('0 SEX F');
} else {
- add_simple_tag('0 SEX');
+ FunctionsEdit::addSimpleTag('0 SEX');
}
$bdm = 'BD';
if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
- addSimpleTags($match);
+ FunctionsEdit::addSimpleTags($match);
}
}
}
@@ -2731,14 +2733,14 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
$bdm .= 'M';
if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
foreach ($matches[1] as $match) {
- addSimpleTags($match);
+ FunctionsEdit::addSimpleTags($match);
}
}
}
if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
foreach ($matches[1] as $match) {
if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
- addSimpleTags($match);
+ FunctionsEdit::addSimpleTags($match);
}
}
}
@@ -2747,15 +2749,15 @@ function print_indi_form($nextaction, Individual $person = null, Family $family
echo '</table>';
if ($nextaction === 'update') {
// GEDCOM 5.5.1 spec says NAME doesn’t get a OBJE
- print_add_layer('SOUR');
- print_add_layer('NOTE');
- print_add_layer('SHARED_NOTE');
- print_add_layer('RESN');
+ FunctionsEdit::printAddLayer('SOUR');
+ FunctionsEdit::printAddLayer('NOTE');
+ FunctionsEdit::printAddLayer('SHARED_NOTE');
+ FunctionsEdit::printAddLayer('RESN');
} else {
- print_add_layer('SOUR', 1);
- print_add_layer('NOTE', 1);
- print_add_layer('SHARED_NOTE', 1);
- print_add_layer('RESN', 1);
+ FunctionsEdit::printAddLayer('SOUR', 1);
+ FunctionsEdit::printAddLayer('NOTE', 1);
+ FunctionsEdit::printAddLayer('SHARED_NOTE', 1);
+ FunctionsEdit::printAddLayer('RESN', 1);
}
// If we are editing an existing name, allow raw GEDCOM editing
diff --git a/edituser.php b/edituser.php
index 5b21f1b013..f4239aded5 100644
--- a/edituser.php
+++ b/edituser.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'edituser.php');
require './includes/session.php';
@@ -189,7 +191,7 @@ function checkform(frm) {
</div>
<div class="value">
<input data-autocomplete-type="INDI" type="text" name="form_rootid" id="form_rootid" value="<?php echo $WT_TREE->getUserPreference(Auth::user(), 'rootid'); ?>">
- <?php echo print_findindi_link('form_rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('form_rootid'); ?>
<br>
<?php if ($default_individual): ?>
<?php echo $default_individual->formatList('span'); ?>
@@ -227,7 +229,7 @@ function checkform(frm) {
</label>
</div>
<div class="value">
- <?php echo edit_field_language('form_language', Auth::user()->getPreference('language')); ?>
+ <?php echo FunctionsEdit::editFieldLanguage('form_language', Auth::user()->getPreference('language')); ?>
</div>
<div class="label">
<label for="form_email">
@@ -268,7 +270,7 @@ function checkform(frm) {
</label>
</div>
<div class="value">
- <?php echo edit_field_contact('form_contact_method', Auth::user()->getPreference('contactmethod')); ?>
+ <?php echo FunctionsEdit::editFieldContact('form_contact_method', Auth::user()->getPreference('contactmethod')); ?>
<p class="small text-muted">
<?php echo I18N::translate('Site members can send each other messages. You can choose to how these messages are sent to you, or choose not receive them at all.'); ?>
</p>
@@ -279,7 +281,7 @@ function checkform(frm) {
</label>
</div>
<div class="value">
- <?php echo checkbox('form_visible_online', Auth::user()->getPreference('visibleonline')); ?>
+ <?php echo FunctionsEdit::checkbox('form_visible_online', Auth::user()->getPreference('visibleonline')); ?>
<p class="small text-muted">
<?php echo I18N::translate('This checkbox controls your visibility to other users while you’re online. It also controls your ability to see other online users who are configured to be visible.<br><br>When this box is unchecked, you will be completely invisible to others, and you will also not be able to see other online users. When this box is checked, exactly the opposite is true. You will be visible to others, and you will also be able to see others who are configured to be visible.'); ?>
</p>
diff --git a/expand_view.php b/expand_view.php
index 2b8c842eeb..9641bc5094 100644
--- a/expand_view.php
+++ b/expand_view.php
@@ -23,6 +23,9 @@ namespace Fisharebest\Webtrees;
*/
global $WT_TREE;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+
define('WT_SCRIPT_NAME', 'expand_view.php');
require './includes/session.php';
@@ -38,7 +41,7 @@ foreach ($individual->getSpouseFamilies() as $family) {
$facts[] = $fact;
}
}
-sort_facts($facts);
+Functions::sortFacts($facts);
foreach ($facts as $fact) {
switch ($fact->getTag()) {
@@ -72,10 +75,10 @@ foreach ($facts as $fact) {
break;
case 'ASSO':
// Associates
- echo format_asso_rela_record($fact);
+ echo FunctionsPrint::formatAssociateRelationship($fact);
break;
default:
- // Simple version of print_fact()
+ // Simple version of FunctionsPrintFacts::print_fact()
echo $fact->summary();
break;
}
diff --git a/family.php b/family.php
index d6a83bcb7d..3e3114f548 100644
--- a/family.php
+++ b/family.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\FamilyController;
+use Fisharebest\Webtrees\Functions\FunctionsCharts;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'family.php');
require './includes/session.php';
@@ -41,13 +43,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This family has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -59,13 +61,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This family has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -88,7 +90,7 @@ if ($controller->record && $controller->record->canShow()) {
<table id="family-table" align="center" width="95%">
<tr valign="top">
<td valign="top" style="width: <?php echo Theme::theme()->parameter('chart-box-x') + 30; ?>px;"><!--//List of children//-->
- <?php print_family_children($controller->record); ?>
+ <?php FunctionsCharts::printFamilyChildren($controller->record); ?>
</td>
<td> <!--//parents pedigree chart and Family Details//-->
<table width="100%">
@@ -99,7 +101,7 @@ if ($controller->record && $controller->record->canShow()) {
<tr>
<td colspan="2">
<?php
- print_family_parents($controller->record);
+ FunctionsCharts::printFamilyParents($controller->record);
if (Auth::isEditor($controller->record->getTree())) {
$husb = $controller->record->getHusband();
if (!$husb) {
diff --git a/familybook.php b/familybook.php
index ece18d38c1..751279cbd9 100644
--- a/familybook.php
+++ b/familybook.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\FamilyBookController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'familybook.php');
require './includes/session.php';
@@ -47,13 +49,13 @@ $controller
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="rootid" id="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td class="descriptionbox">
<?php echo I18N::translate('Show details'); ?>
</td>
<td class="optionbox">
- <?php echo two_state_checkbox("show_full", $controller->showFull()); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox("show_full", $controller->showFull()); ?>
</td>
<td rowspan="3" class="topbottombar vmiddle">
<input type="submit" value="<?php echo /* I18N: Submit button, on a form */ I18N::translate('View'); ?>">
diff --git a/famlist.php b/famlist.php
index f994dd42f4..7100ce2bb4 100644
--- a/famlist.php
+++ b/famlist.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\Query\QueryName;
define('WT_SCRIPT_NAME', 'famlist.php');
@@ -190,14 +191,14 @@ if ($show === 'indi' || $show === 'surn') {
// Show the surname list
switch ($WT_TREE->getPreference('SURNAME_LIST_STYLE')) {
case 'style1';
- echo format_surname_list($surns, 3, true, WT_SCRIPT_NAME, $WT_TREE);
+ echo FunctionsPrintLists::surnameList($surns, 3, true, WT_SCRIPT_NAME, $WT_TREE);
break;
case 'style3':
- echo format_surname_tagcloud($surns, WT_SCRIPT_NAME, true, $WT_TREE);
+ echo FunctionsPrintLists::surnameTagCloud($surns, WT_SCRIPT_NAME, true, $WT_TREE);
break;
case 'style2':
default:
- echo format_surname_table($surns, WT_SCRIPT_NAME, $WT_TREE);
+ echo FunctionsPrintLists::surnameTable($surns, WT_SCRIPT_NAME, $WT_TREE);
break;
}
} else {
@@ -253,7 +254,7 @@ if ($show === 'indi' || $show === 'surn') {
}
}
if ($show === 'indi') {
- echo format_fam_table(QueryName::families($WT_TREE, $surname, $alpha, $falpha, $show_marnm === 'yes'));
+ echo FunctionsPrintLists::familyTable(QueryName::families($WT_TREE, $surname, $alpha, $falpha, $show_marnm === 'yes'));
}
}
}
diff --git a/fanchart.php b/fanchart.php
index c223196bd3..3739f0ae8c 100644
--- a/fanchart.php
+++ b/fanchart.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\FanchartController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'fanchart.php');
require './includes/session.php';
@@ -78,7 +80,7 @@ $controller
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="rootid" id="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td class="descriptionbox">
<label for="fan_style">
@@ -86,7 +88,7 @@ $controller
</label>
</td>
<td class="optionbox">
- <?php echo select_edit_control('fan_style', $controller->getFanStyles(), null, $controller->fan_style); ?>
+ <?php echo FunctionsEdit::selectEditControl('fan_style', $controller->getFanStyles(), null, $controller->fan_style); ?>
</td>
<td rowspan="2" class="topbottombar vmiddle">
<input type="submit" value="<?php echo I18N::translate('View'); ?>">
@@ -99,7 +101,7 @@ $controller
</label>
</td>
<td class="optionbox">
- <?php echo edit_field_integers('generations', $controller->generations, 2, 9); ?>
+ <?php echo FunctionsEdit::editFieldInteger('generations', $controller->generations, 2, 9); ?>
</td>
<td class="descriptionbox">
<label for="fan_width">
diff --git a/find.php b/find.php
index c1348f58ed..b7b7d03557 100644
--- a/find.php
+++ b/find.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
use Fisharebest\Webtrees\Query\QueryMedia;
define('WT_SCRIPT_NAME', 'find.php');
@@ -480,7 +481,7 @@ if ($action == "filter") {
// Output Individual
if ($type == "indi") {
echo '<div id="find-output">';
- $myindilist = search_indis_names($filter_array, array($WT_TREE));
+ $myindilist = FunctionsDb::searchIndividualNames($filter_array, array($WT_TREE));
if ($myindilist) {
echo '<ul>';
usort($myindilist, '\Fisharebest\Webtrees\GedcomRecord::compare');
@@ -501,8 +502,8 @@ if ($action == "filter") {
// Get the famrecs with hits on names from the family table
// Get the famrecs with hits in the gedcom record from the family table
$myfamlist = array_unique(array_merge(
- search_fams_names($filter_array, array($WT_TREE)),
- search_fams($filter_array, array($WT_TREE))
+ FunctionsDb::searchFamilyNames($filter_array, array($WT_TREE)),
+ FunctionsDb::searchFamilies($filter_array, array($WT_TREE))
));
if ($myfamlist) {
@@ -606,9 +607,9 @@ if ($action == "filter") {
if ($type == "repo") {
echo '<div id="find-output">';
if ($filter) {
- $repo_list = search_repos($filter_array, array($WT_TREE));
+ $repo_list = FunctionsDb::searchRepositories($filter_array, array($WT_TREE));
} else {
- $repo_list = get_repo_list($WT_TREE);
+ $repo_list = FunctionsDb::getRepositoryList($WT_TREE);
}
if ($repo_list) {
usort($repo_list, '\Fisharebest\Webtrees\GedcomRecord::compare');
@@ -628,9 +629,9 @@ if ($action == "filter") {
if ($type == "note") {
echo '<div id="find-output">';
if ($filter) {
- $mynotelist = search_notes($filter_array, array($WT_TREE));
+ $mynotelist = FunctionsDb::searchNotes($filter_array, array($WT_TREE));
} else {
- $mynotelist = get_note_list($WT_TREE);
+ $mynotelist = FunctionsDb::getNoteList($WT_TREE);
}
if ($mynotelist) {
usort($mynotelist, '\Fisharebest\Webtrees\GedcomRecord::compare');
@@ -650,9 +651,9 @@ if ($action == "filter") {
if ($type == "source") {
echo '<div id="find-output">';
if ($filter) {
- $mysourcelist = search_sources($filter_array, array($WT_TREE));
+ $mysourcelist = FunctionsDb::searchSources($filter_array, array($WT_TREE));
} else {
- $mysourcelist = get_source_list($WT_TREE);
+ $mysourcelist = FunctionsDb::getSourceList($WT_TREE);
}
if ($mysourcelist) {
usort($mysourcelist, '\Fisharebest\Webtrees\GedcomRecord::compare');
diff --git a/help_text.php b/help_text.php
index 901fd0d86a..fe7ad7aca1 100644
--- a/help_text.php
+++ b/help_text.php
@@ -30,7 +30,7 @@ switch ($help) {
//
// Tags such as _BIRT_CHIL are pseudo-tags, used to create family events.
//
- // Generally, these tags need to be lists explicitly in add_simple_tag()
+ // Generally, these tags need to be lists explicitly in FunctionsEdit::add_simple_tag()
//////////////////////////////////////////////////////////////////////////////
case 'DATE':
diff --git a/hourglass.php b/hourglass.php
index 223a24da7b..d397357582 100644
--- a/hourglass.php
+++ b/hourglass.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\HourglassController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'hourglass.php');
require './includes/session.php';
@@ -48,13 +50,13 @@ $controller
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="rootid" id="rootid" size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('pid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('pid'); ?>
</td>
<td class="descriptionbox">
<?php echo I18N::translate('Show details'); ?>
</td>
<td class="optionbox">
- <?php echo two_state_checkbox("show_full", $controller->showFull()); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox("show_full", $controller->showFull()); ?>
</td>
<td rowspan="3" class="topbottombar vmiddle">
<input type="submit" value="<?php echo I18N::translate('View'); ?>">
@@ -65,7 +67,7 @@ $controller
<?php echo I18N::translate('Generations'); ?>
</td>
<td class="optionbox">
- <?php echo edit_field_integers('generations', $controller->generations, 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS')); ?>
+ <?php echo FunctionsEdit::editFieldInteger('generations', $controller->generations, 2, $WT_TREE->getPreference('MAX_DESCENDANCY_GENERATIONS')); ?>
</td>
<td class="descriptionbox">
<?php echo I18N::translate('Show spouses'); ?>
diff --git a/import.php b/import.php
index 5317a016e0..bb1a3d0249 100644
--- a/import.php
+++ b/import.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\AjaxController;
+use Fisharebest\Webtrees\Functions\FunctionsImport;
use PDOException;
define('WT_SCRIPT_NAME', 'import.php');
@@ -211,7 +212,7 @@ for ($end_time = microtime(true) + 1.0; microtime(true) < $end_time;) {
try {
// Import all the records in this chunk of data
foreach (preg_split('/\n+(?=0)/', $data->chunk_data) as $rec) {
- import_record($rec, $tree, false);
+ FunctionsImport::importRecord($rec, $tree, false);
}
// Mark the chunk as imported
Database::prepare(
diff --git a/includes/functions/functions.php b/includes/functions/functions.php
deleted file mode 100644
index 63ad6d59c4..0000000000
--- a/includes/functions/functions.php
+++ /dev/null
@@ -1,2219 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\File;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Site;
-
-/**
- * Check with the webtrees.net server for the latest version of webtrees.
- * Fetching the remote file can be slow, so check infrequently, and cache the result.
- * Pass the current versions of webtrees, PHP and MySQL, as the response
- * may be different for each. The server logs are used to generate
- * installation statistics which can be found at http://svn.webtrees.net/statistics.html
- *
- * @return null|string
- */
-function fetch_latest_version() {
- $last_update_timestamp = Site::getPreference('LATEST_WT_VERSION_TIMESTAMP');
- if ($last_update_timestamp < WT_TIMESTAMP - 24 * 60 * 60) {
- $row = Database::prepare("SHOW VARIABLES LIKE 'version'")->fetchOneRow();
- $params = '?w=' . WT_VERSION . '&p=' . PHP_VERSION . '&m=' . $row->value . '&o=' . (DIRECTORY_SEPARATOR === '/' ? 'u' : 'w');
- $latest_version_txt = File::fetchUrl('http://dev.webtrees.net/build/latest-version.txt' . $params);
- if ($latest_version_txt) {
- Site::setPreference('LATEST_WT_VERSION', $latest_version_txt);
- Site::setPreference('LATEST_WT_VERSION_TIMESTAMP', WT_TIMESTAMP);
-
- return $latest_version_txt;
- } else {
- // Cannot connect to server - use cached version (if we have one)
- return Site::getPreference('LATEST_WT_VERSION');
- }
- } else {
- return Site::getPreference('LATEST_WT_VERSION');
- }
-}
-
-/**
- * Convert a file upload PHP error code into user-friendly text.
- *
- * @param int $error_code
- *
- * @return string
- */
-function file_upload_error_text($error_code) {
- switch ($error_code) {
- case UPLOAD_ERR_OK:
- return I18N::translate('File successfully uploaded');
- case UPLOAD_ERR_INI_SIZE:
- case UPLOAD_ERR_FORM_SIZE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The uploaded file exceeds the allowed size.');
- case UPLOAD_ERR_PARTIAL:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The file was only partially uploaded. Please try again.');
- case UPLOAD_ERR_NO_FILE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('No file was received. Please try again.');
- case UPLOAD_ERR_NO_TMP_DIR:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('The PHP temporary folder is missing.');
- case UPLOAD_ERR_CANT_WRITE:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('PHP failed to write to disk.');
- case UPLOAD_ERR_EXTENSION:
- // I18N: PHP internal error message - php.net/manual/en/features.file-upload.errors.php
- return I18N::translate('PHP blocked the file because of its extension.');
- default:
- return 'Error: ' . $error_code;
- }
-}
-
-/**
- * get a gedcom subrecord
- *
- * searches a gedcom record and returns a subrecord of it. A subrecord is defined starting at a
- * line with level N and all subsequent lines greater than N until the next N level is reached.
- * For example, the following is a BIRT subrecord:
- * <code>1 BIRT
- * 2 DATE 1 JAN 1900
- * 2 PLAC Phoenix, Maricopa, Arizona</code>
- * The following example is the DATE subrecord of the above BIRT subrecord:
- * <code>2 DATE 1 JAN 1900</code>
- *
- * @param int $level the N level of the subrecord to get
- * @param string $tag a gedcom tag or string to search for in the record (ie 1 BIRT or 2 DATE)
- * @param string $gedrec the parent gedcom record to search in
- * @param int $num this allows you to specify which matching <var>$tag</var> to get. Oftentimes a
- * gedcom record will have more that 1 of the same type of subrecord. An individual may have
- * multiple events for example. Passing $num=1 would get the first 1. Passing $num=2 would get the
- * second one, etc.
- *
- * @return string the subrecord that was found or an empty string "" if not found.
- */
-function get_sub_record($level, $tag, $gedrec, $num = 1) {
- if (empty($gedrec)) {
- return '';
- }
- // -- adding \n before and after gedrec
- $gedrec = "\n" . $gedrec . "\n";
- $tag = trim($tag);
- $searchTarget = "~[\n]" . $tag . "[\s]~";
- $ct = preg_match_all($searchTarget, $gedrec, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
- if ($ct == 0) {
- return '';
- }
- if ($ct < $num) {
- return '';
- }
- $pos1 = $match[$num - 1][0][1];
- $pos2 = strpos($gedrec, "\n$level", $pos1 + 1);
- if (!$pos2) {
- $pos2 = strpos($gedrec, "\n1", $pos1 + 1);
- }
- if (!$pos2) {
- $pos2 = strpos($gedrec, "\nWT_", $pos1 + 1); // WT_SPOUSE, WT_FAMILY_ID ...
- }
- if (!$pos2) {
- return ltrim(substr($gedrec, $pos1));
- }
- $subrec = substr($gedrec, $pos1, $pos2 - $pos1);
-
- return ltrim($subrec);
-}
-
-/**
- * get CONT lines
- *
- * get the N+1 CONT or CONC lines of a gedcom subrecord
- *
- * @param int $nlevel the level of the CONT lines to get
- * @param string $nrec the gedcom subrecord to search in
- *
- * @return string a string with all CONT lines merged
- */
-function get_cont($nlevel, $nrec) {
- $text = '';
-
- $subrecords = explode("\n", $nrec);
- foreach ($subrecords as $thisSubrecord) {
- if (substr($thisSubrecord, 0, 2) !== $nlevel . ' ') {
- continue;
- }
- $subrecordType = substr($thisSubrecord, 2, 4);
- if ($subrecordType === 'CONT') {
- $text .= "\n" . substr($thisSubrecord, 7);
- }
- }
-
- return $text;
-}
-
-/**
- * Sort a list events for the today/upcoming blocks
- *
- * @param array $a
- * @param array $b
- *
- * @return int
- */
-function event_sort($a, $b) {
- if ($a['jd'] == $b['jd']) {
- if ($a['anniv'] == $b['anniv']) {
- return I18N::strcasecmp($a['fact'], $b['fact']);
- } else {
- return $a['anniv'] - $b['anniv'];
- }
- } else {
- return $a['jd'] - $b['jd'];
- }
-}
-
-/**
- * Sort a list events for the today/upcoming blocks
- *
- * @param array $a
- * @param array $b
- *
- * @return int
- */
-function event_sort_name($a, $b) {
- if ($a['jd'] == $b['jd']) {
- return GedcomRecord::compare($a['record'], $b['record']);
- } else {
- return $a['jd'] - $b['jd'];
- }
-}
-
-/**
- * A multi-key sort
- * 1. First divide the facts into two arrays one set with dates and one set without dates
- * 2. Sort each of the two new arrays, the date using the compare date function, the non-dated
- * using the compare type function
- * 3. Then merge the arrays back into the original array using the compare type function
- *
- * @param Fact[] $arr
- */
-function sort_facts(&$arr) {
- $dated = array();
- $nondated = array();
- //-- split the array into dated and non-dated arrays
- $order = 0;
- foreach ($arr as $event) {
- $event->sortOrder = $order;
- $order++;
- if ($event->getDate()->isOk()) {
- $dated[] = $event;
- } else {
- $nondated[] = $event;
- }
- }
-
- //-- sort each type of array
- usort($dated, '\Fisharebest\Webtrees\Fact::compareDate');
- usort($nondated, '\Fisharebest\Webtrees\Fact::compareType');
-
- //-- merge the arrays back together comparing by Facts
- $dc = count($dated);
- $nc = count($nondated);
- $i = 0;
- $j = 0;
- $k = 0;
- // while there is anything in the dated array continue merging
- while ($i < $dc) {
- // compare each fact by type to merge them in order
- if ($j < $nc && Fact::compareType($dated[$i], $nondated[$j]) > 0) {
- $arr[$k] = $nondated[$j];
- $j++;
- } else {
- $arr[$k] = $dated[$i];
- $i++;
- }
- $k++;
- }
-
- // get anything that might be left in the nondated array
- while ($j < $nc) {
- $arr[$k] = $nondated[$j];
- $j++;
- $k++;
- }
-
-}
-
-/**
- * For close family relationships, such as the families tab and the family navigator
- * Display a tick if both individuals are the same.
- *
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_close_relationship_name(Individual $person1, Individual $person2) {
- if ($person1 === $person2) {
- $label = '<i class="icon-selected" title="' . I18N::translate('self') . '"></i>';
- } else {
- $label = get_relationship_name(get_relationship($person1, $person2));
- }
-
- return $label;
-}
-
-/**
- * For facts on the individual/family pages.
- *
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_associate_relationship_name(Individual $person1, Individual $person2) {
- if ($person1 === $person2) {
- $label = I18N::translate('self');
- } else {
- $label = get_relationship_name(get_relationship($person1, $person2));
- }
-
- return $label;
-}
-
-/**
- * Get relationship between two individuals in the gedcom
- *
- * @param Individual $person1 The person to compute the relationship from
- * @param Individual $person2 The person to compute the relatiohip to
- * @param int $maxlength The maximum length of path
- *
- * @return array|bool An array of nodes on the relationship path, or false if no path found
- */
-function get_relationship(Individual $person1, Individual $person2, $maxlength = 4) {
- if ($person1 === $person2) {
- return false;
- }
-
- $spouse_codes = array('M' => 'hus', 'F' => 'wif', 'U' => 'spo');
- $parent_codes = array('M' => 'fat', 'F' => 'mot', 'U' => 'par');
- $child_codes = array('M' => 'son', 'F' => 'dau', 'U' => 'chi');
- $sibling_codes = array('M' => 'bro', 'F' => 'sis', 'U' => 'sib');
-
- //-- current path nodes
- $p1nodes = array();
- //-- ids visited
- $visited = array();
-
- //-- set up first node for person1
- $node1 = array(
- 'path' => array($person1),
- 'length' => 0,
- 'indi' => $person1,
- 'relations' => array('self'),
- );
- $p1nodes[] = $node1;
-
- $visited[$person1->getXref()] = true;
-
- $found = false;
- while (!$found) {
- //-- search the node list for the shortest path length
- $shortest = -1;
- foreach ($p1nodes as $index => $node) {
- if ($shortest == -1) {
- $shortest = $index;
- } else {
- $node1 = $p1nodes[$shortest];
- if ($node1['length'] > $node['length']) {
- $shortest = $index;
- }
- }
- }
- if ($shortest === -1) {
- return false;
- }
- $node = $p1nodes[$shortest];
- if ($maxlength == 0 || count($node['path']) <= $maxlength) {
- $indi = $node['indi'];
- //-- check all parents and siblings of this node
- foreach ($indi->getChildFamilies(Auth::PRIV_HIDE) as $family) {
- $visited[$family->getXref()] = true;
- foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
- if (!isset($visited[$spouse->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $spouse;
- $node1['indi'] = $spouse;
- $node1['relations'][] = $parent_codes[$spouse->getSex()];
- $p1nodes[] = $node1;
- if ($spouse === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$spouse->getXref()] = true;
- }
- }
- }
- foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
- if (!isset($visited[$child->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $child;
- $node1['indi'] = $child;
- $node1['relations'][] = $sibling_codes[$child->getSex()];
- $p1nodes[] = $node1;
- if ($child === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$child->getXref()] = true;
- }
- }
- }
- }
- //-- check all spouses and children of this node
- foreach ($indi->getSpouseFamilies(Auth::PRIV_HIDE) as $family) {
- $visited[$family->getXref()] = true;
- foreach ($family->getSpouses(Auth::PRIV_HIDE) as $spouse) {
- if (!in_array($spouse->getXref(), $node1) || !isset($visited[$spouse->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $spouse;
- $node1['indi'] = $spouse;
- $node1['relations'][] = $spouse_codes[$spouse->getSex()];
- $p1nodes[] = $node1;
- if ($spouse === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$spouse->getXref()] = true;
- }
- }
- }
- foreach ($family->getChildren(Auth::PRIV_HIDE) as $child) {
- if (!isset($visited[$child->getXref()])) {
- $node1 = $node;
- $node1['length']++;
- $node1['path'][] = $child;
- $node1['indi'] = $child;
- $node1['relations'][] = $child_codes[$child->getSex()];
- $p1nodes[] = $node1;
- if ($child === $person2) {
- $found = true;
- $resnode = $node1;
- } else {
- $visited[$child->getXref()] = true;
- }
- }
- }
- }
- }
- unset($p1nodes[$shortest]);
- }
-
- return $resnode;
-}
-
-/**
- * Convert the result of get_relationship() into a relationship name.
- *
- * @param mixed[][] $nodes
- *
- * @return string
- */
-function get_relationship_name($nodes) {
- if (!is_array($nodes)) {
- return '';
- }
- $person1 = $nodes['path'][0];
- $person2 = $nodes['path'][count($nodes['path']) - 1];
- $path = array_slice($nodes['relations'], 1);
- // Look for paths with *specific* names first.
- // Note that every combination must be listed separately, as the same English
- // name can be used for many different relationships. e.g.
- // brother’s wife & husband’s sister = sister-in-law.
- //
- // $path is an array of the 12 possible gedcom family relationships:
- // mother/father/parent
- // brother/sister/sibling
- // husband/wife/spouse
- // son/daughter/child
- //
- // This is always the shortest path, so “father, daughter” is “half-sister”, not “sister”.
- //
- // This is very repetitive in English, but necessary in order to handle the
- // complexities of other languages.
-
- return get_relationship_name_from_path(
- implode('', array_slice($nodes['relations'], 1)),
- $nodes['path'][0],
- $nodes['path'][count($nodes['path']) - 1]
- );
-}
-
-/**
- * @param int $n
- * @param string $sex
- *
- * @return string
- */
-function cousin_name($n, $sex) {
- switch ($sex) {
- case 'M':
- switch ($n) {
- case 1:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'first cousin');
- case 2:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'second cousin');
- case 3:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'third cousin');
- case 4:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fourth cousin');
- case 5:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fifth cousin');
- case 6:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'sixth cousin');
- case 7:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'seventh cousin');
- case 8:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'eighth cousin');
- case 9:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'ninth cousin');
- case 10:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'tenth cousin');
- case 11:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'eleventh cousin');
- case 12:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'twelfth cousin');
- case 13:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'thirteenth cousin');
- case 14:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fourteenth cousin');
- case 15:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', 'fifteenth cousin');
- default:
- /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
- return I18N::translateContext('MALE', '%s × cousin', I18N::number($n));
- }
- case 'F':
- switch ($n) {
- case 1:
- return I18N::translateContext('FEMALE', 'first cousin');
- case 2:
- return I18N::translateContext('FEMALE', 'second cousin');
- case 3:
- return I18N::translateContext('FEMALE', 'third cousin');
- case 4:
- return I18N::translateContext('FEMALE', 'fourth cousin');
- case 5:
- return I18N::translateContext('FEMALE', 'fifth cousin');
- case 6:
- return I18N::translateContext('FEMALE', 'sixth cousin');
- case 7:
- return I18N::translateContext('FEMALE', 'seventh cousin');
- case 8:
- return I18N::translateContext('FEMALE', 'eighth cousin');
- case 9:
- return I18N::translateContext('FEMALE', 'ninth cousin');
- case 10:
- return I18N::translateContext('FEMALE', 'tenth cousin');
- case 11:
- return I18N::translateContext('FEMALE', 'eleventh cousin');
- case 12:
- return I18N::translateContext('FEMALE', 'twelfth cousin');
- case 13:
- return I18N::translateContext('FEMALE', 'thirteenth cousin');
- case 14:
- return I18N::translateContext('FEMALE', 'fourteenth cousin');
- case 15:
- return I18N::translateContext('FEMALE', 'fifteenth cousin');
- default:
- return I18N::translateContext('FEMALE', '%s × cousin', I18N::number($n));
- }
- default:
- switch ($n) {
- case 1:
- return I18N::translateContext('MALE/FEMALE', 'first cousin');
- case 2:
- return I18N::translateContext('MALE/FEMALE', 'second cousin');
- case 3:
- return I18N::translateContext('MALE/FEMALE', 'third cousin');
- case 4:
- return I18N::translateContext('MALE/FEMALE', 'fourth cousin');
- case 5:
- return I18N::translateContext('MALE/FEMALE', 'fifth cousin');
- case 6:
- return I18N::translateContext('MALE/FEMALE', 'sixth cousin');
- case 7:
- return I18N::translateContext('MALE/FEMALE', 'seventh cousin');
- case 8:
- return I18N::translateContext('MALE/FEMALE', 'eighth cousin');
- case 9:
- return I18N::translateContext('MALE/FEMALE', 'ninth cousin');
- case 10:
- return I18N::translateContext('MALE/FEMALE', 'tenth cousin');
- case 11:
- return I18N::translateContext('MALE/FEMALE', 'eleventh cousin');
- case 12:
- return I18N::translateContext('MALE/FEMALE', 'twelfth cousin');
- case 13:
- return I18N::translateContext('MALE/FEMALE', 'thirteenth cousin');
- case 14:
- return I18N::translateContext('MALE/FEMALE', 'fourteenth cousin');
- case 15:
- return I18N::translateContext('MALE/FEMALE', 'fifteenth cousin');
- default:
- return I18N::translateContext('MALE/FEMALE', '%s × cousin', I18N::number($n));
- }
- }
-}
-
-/**
- * A variation on cousin_name(), for constructs such as “sixth great-nephew”
- * Currently used only by Spanish relationship names.
- *
- * @param int $n
- * @param string $sex
- * @param string $relation
- *
- * @return string
- */
-function cousin_name2($n, $sex, $relation) {
- switch ($sex) {
- case 'M':
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('MALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('MALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('MALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('MALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- case 'F':
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('FEMALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('FEMALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('FEMALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('FEMALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('FEMALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- default:
- switch ($n) {
- case 1: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE/FEMALE', 'first %s', $relation);
- case 2:
- return I18N::translateContext('MALE/FEMALE', 'second %s', $relation);
- case 3:
- return I18N::translateContext('MALE/FEMALE', 'third %s', $relation);
- case 4:
- return I18N::translateContext('MALE/FEMALE', 'fourth %s', $relation);
- case 5:
- return I18N::translateContext('MALE/FEMALE', 'fifth %s', $relation);
- default: // I18N: A Spanish relationship name, such as third great-nephew
- return I18N::translateContext('MALE/FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
- }
- }
-}
-
-/**
- * @param string $path
- * @param Individual $person1
- * @param Individual $person2
- *
- * @return string
- */
-function get_relationship_name_from_path($path, Individual $person1 = null, Individual $person2 = null) {
- if (!preg_match('/^(mot|fat|par|hus|wif|spo|son|dau|chi|bro|sis|sib)*$/', $path)) {
- // TODO: Update all the “3 RELA ” values in class_person
- return '<span class="error">' . $path . '</span>';
- }
- // The path does not include the starting person. In some languages, the
- // translation for a man’s (relative) is different from a woman’s (relative),
- // due to inflection.
- $sex1 = $person1 ? $person1->getSex() : 'U';
-
- // The sex of the last person in the relationship determines the name in
- // many cases. e.g. great-aunt / great-uncle
- if (preg_match('/(fat|hus|son|bro)$/', $path)) {
- $sex2 = 'M';
- } elseif (preg_match('/(mot|wif|dau|sis)$/', $path)) {
- $sex2 = 'F';
- } else {
- $sex2 = 'U';
- }
-
- switch ($path) {
- case '':
- return I18N::translate('self');
- // Level One relationships
- case 'mot':
- return I18N::translate('mother');
- case 'fat':
- return I18N::translate('father');
- case 'par':
- return I18N::translate('parent');
- case 'hus':
- if ($person1 && $person2) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('MALE', 'ex-partner');
- } else {
- return I18N::translateContext('MALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-husband');
- }
- }
- }
- }
-
- return I18N::translate('husband');
- case 'wif':
- if ($person1 && $person1) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('FEMALE', 'ex-partner');
- } else {
- return I18N::translateContext('FEMALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-wife');
- }
- }
- }
- }
-
- return I18N::translate('wife');
- case 'spo':
- if ($person1 && $person2) {
- foreach ($person1->getSpouseFamilies() as $family) {
- if ($person2 === $family->getSpouse($person1)) {
- if ($family->getFacts('_NMR')) {
- if ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translateContext('MALE/FEMALE', 'ex-partner');
- } else {
- return I18N::translateContext('MALE/FEMALE', 'partner');
- }
- } elseif ($family->getFacts(WT_EVENTS_DIV)) {
- return I18N::translate('ex-spouse');
- }
- }
- }
- }
-
- return I18N::translate('spouse');
- case 'son':
- return I18N::translate('son');
- case 'dau':
- return I18N::translate('daughter');
- case 'chi':
- return I18N::translate('child');
- case 'bro':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin brother');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger brother');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder brother');
- }
- }
- }
-
- return I18N::translate('brother');
- case 'sis':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin sister');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger sister');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder sister');
- }
- }
- }
-
- return I18N::translate('sister');
- case 'sib':
- if ($person1 && $person2) {
- $dob1 = $person1->getBirthDate();
- $dob2 = $person2->getBirthDate();
- if ($dob1->isOK() && $dob2->isOK()) {
- if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && !$dob1->minimumDate()->d !== 0 && !$dob2->minimumDate()->d !== 0) {
- // Exclude BEF, AFT, etc.
- return I18N::translate('twin sibling');
- } elseif ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
- return I18N::translate('younger sibling');
- } elseif ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
- return I18N::translate('elder sibling');
- }
- }
- }
-
- return I18N::translate('sibling');
-
- // Level Two relationships
- case 'brochi':
- return I18N::translateContext('brother’s child', 'nephew/niece');
- case 'brodau':
- return I18N::translateContext('brother’s daughter', 'niece');
- case 'broson':
- return I18N::translateContext('brother’s son', 'nephew');
- case 'browif':
- return I18N::translateContext('brother’s wife', 'sister-in-law');
- case 'chichi':
- return I18N::translateContext('child’s child', 'grandchild');
- case 'chidau':
- return I18N::translateContext('child’s daughter', 'granddaughter');
- case 'chihus':
- return I18N::translateContext('child’s husband', 'son-in-law');
- case 'chison':
- return I18N::translateContext('child’s son', 'grandson');
- case 'chispo':
- return I18N::translateContext('child’s spouse', 'son/daughter-in-law');
- case 'chiwif':
- return I18N::translateContext('child’s wife', 'daughter-in-law');
- case 'dauchi':
- return I18N::translateContext('daughter’s child', 'grandchild');
- case 'daudau':
- return I18N::translateContext('daughter’s daughter', 'granddaughter');
- case 'dauhus':
- return I18N::translateContext('daughter’s husband', 'son-in-law');
- case 'dauson':
- return I18N::translateContext('daughter’s son', 'grandson');
- case 'fatbro':
- return I18N::translateContext('father’s brother', 'uncle');
- case 'fatchi':
- return I18N::translateContext('father’s child', 'half-sibling');
- case 'fatdau':
- return I18N::translateContext('father’s daughter', 'half-sister');
- case 'fatfat':
- return I18N::translateContext('father’s father', 'paternal grandfather');
- case 'fatmot':
- return I18N::translateContext('father’s mother', 'paternal grandmother');
- case 'fatpar':
- return I18N::translateContext('father’s parent', 'paternal grandparent');
- case 'fatsib':
- return I18N::translateContext('father’s sibling', 'aunt/uncle');
- case 'fatsis':
- return I18N::translateContext('father’s sister', 'aunt');
- case 'fatson':
- return I18N::translateContext('father’s son', 'half-brother');
- case 'fatwif':
- return I18N::translateContext('father’s wife', 'step-mother');
- case 'husbro':
- return I18N::translateContext('husband’s brother', 'brother-in-law');
- case 'huschi':
- return I18N::translateContext('husband’s child', 'step-child');
- case 'husdau':
- return I18N::translateContext('husband’s daughter', 'step-daughter');
- case 'husfat':
- return I18N::translateContext('husband’s father', 'father-in-law');
- case 'husmot':
- return I18N::translateContext('husband’s mother', 'mother-in-law');
- case 'hussib':
- return I18N::translateContext('husband’s sibling', 'brother/sister-in-law');
- case 'hussis':
- return I18N::translateContext('husband’s sister', 'sister-in-law');
- case 'husson':
- return I18N::translateContext('husband’s son', 'step-son');
- case 'motbro':
- return I18N::translateContext('mother’s brother', 'uncle');
- case 'motchi':
- return I18N::translateContext('mother’s child', 'half-sibling');
- case 'motdau':
- return I18N::translateContext('mother’s daughter', 'half-sister');
- case 'motfat':
- return I18N::translateContext('mother’s father', 'maternal grandfather');
- case 'mothus':
- return I18N::translateContext('mother’s husband', 'step-father');
- case 'motmot':
- return I18N::translateContext('mother’s mother', 'maternal grandmother');
- case 'motpar':
- return I18N::translateContext('mother’s parent', 'maternal grandparent');
- case 'motsib':
- return I18N::translateContext('mother’s sibling', 'aunt/uncle');
- case 'motsis':
- return I18N::translateContext('mother’s sister', 'aunt');
- case 'motson':
- return I18N::translateContext('mother’s son', 'half-brother');
- case 'parbro':
- return I18N::translateContext('parent’s brother', 'uncle');
- case 'parchi':
- return I18N::translateContext('parent’s child', 'half-sibling');
- case 'pardau':
- return I18N::translateContext('parent’s daughter', 'half-sister');
- case 'parfat':
- return I18N::translateContext('parent’s father', 'grandfather');
- case 'parmot':
- return I18N::translateContext('parent’s mother', 'grandmother');
- case 'parpar':
- return I18N::translateContext('parent’s parent', 'grandparent');
- case 'parsib':
- return I18N::translateContext('parent’s sibling', 'aunt/uncle');
- case 'parsis':
- return I18N::translateContext('parent’s sister', 'aunt');
- case 'parson':
- return I18N::translateContext('parent’s son', 'half-brother');
- case 'parspo':
- return I18N::translateContext('parent’s spouse', 'step-parent');
- case 'sibchi':
- return I18N::translateContext('sibling’s child', 'nephew/niece');
- case 'sibdau':
- return I18N::translateContext('sibling’s daughter', 'niece');
- case 'sibson':
- return I18N::translateContext('sibling’s son', 'nephew');
- case 'sibspo':
- return I18N::translateContext('sibling’s spouse', 'brother/sister-in-law');
- case 'sischi':
- return I18N::translateContext('sister’s child', 'nephew/niece');
- case 'sisdau':
- return I18N::translateContext('sister’s daughter', 'niece');
- case 'sishus':
- return I18N::translateContext('sister’s husband', 'brother-in-law');
- case 'sisson':
- return I18N::translateContext('sister’s son', 'nephew');
- case 'sonchi':
- return I18N::translateContext('son’s child', 'grandchild');
- case 'sondau':
- return I18N::translateContext('son’s daughter', 'granddaughter');
- case 'sonson':
- return I18N::translateContext('son’s son', 'grandson');
- case 'sonwif':
- return I18N::translateContext('son’s wife', 'daughter-in-law');
- case 'spobro':
- return I18N::translateContext('spouse’s brother', 'brother-in-law');
- case 'spochi':
- return I18N::translateContext('spouse’s child', 'step-child');
- case 'spodau':
- return I18N::translateContext('spouse’s daughter', 'step-daughter');
- case 'spofat':
- return I18N::translateContext('spouse’s father', 'father-in-law');
- case 'spomot':
- return I18N::translateContext('spouse’s mother', 'mother-in-law');
- case 'sposis':
- return I18N::translateContext('spouse’s sister', 'sister-in-law');
- case 'sposon':
- return I18N::translateContext('spouse’s son', 'step-son');
- case 'spopar':
- return I18N::translateContext('spouse’s parent', 'mother/father-in-law');
- case 'sposib':
- return I18N::translateContext('spouse’s sibling', 'brother/sister-in-law');
- case 'wifbro':
- return I18N::translateContext('wife’s brother', 'brother-in-law');
- case 'wifchi':
- return I18N::translateContext('wife’s child', 'step-child');
- case 'wifdau':
- return I18N::translateContext('wife’s daughter', 'step-daughter');
- case 'wiffat':
- return I18N::translateContext('wife’s father', 'father-in-law');
- case 'wifmot':
- return I18N::translateContext('wife’s mother', 'mother-in-law');
- case 'wifsib':
- return I18N::translateContext('wife’s sibling', 'brother/sister-in-law');
- case 'wifsis':
- return I18N::translateContext('wife’s sister', 'sister-in-law');
- case 'wifson':
- return I18N::translateContext('wife’s son', 'step-son');
-
- // Level Three relationships
- // I have commented out some of the unknown-sex relationships that are unlikely to to occur.
- // Feel free to add them in, if you think they might be needed
- case 'brochichi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s child', 'great-nephew/niece');
- }
- case 'brochidau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s daughter', 'great-niece');
- }
- case 'brochison':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s child’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s child’s son', 'great-nephew');
- }
- case 'brodauchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s child', 'great-nephew/niece');
- }
- case 'brodaudau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s daughter', 'great-niece');
- }
- case 'brodauhus':
- return I18N::translateContext('brother’s daughter’s husband', 'nephew-in-law');
- case 'brodauson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s daughter’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s daughter’s son', 'great-nephew');
- }
- case 'brosonchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s child', 'great-nephew/niece');
- }
- case 'brosondau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s daughter', 'great-niece');
- }
- case 'brosonson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s son’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) brother’s son’s son', 'great-nephew');
- }
- case 'brosonwif':
- return I18N::translateContext('brother’s son’s wife', 'niece-in-law');
- case 'browifbro':
- return I18N::translateContext('brother’s wife’s brother', 'brother-in-law');
- case 'browifsib':
- return I18N::translateContext('brother’s wife’s sibling', 'brother/sister-in-law');
- case 'browifsis':
- return I18N::translateContext('brother’s wife’s sister', 'sister-in-law');
- case 'chichichi':
- return I18N::translateContext('child’s child’s child', 'great-grandchild');
- case 'chichidau':
- return I18N::translateContext('child’s child’s daughter', 'great-granddaughter');
- case 'chichison':
- return I18N::translateContext('child’s child’s son', 'great-grandson');
- case 'chidauchi':
- return I18N::translateContext('child’s daughter’s child', 'great-grandchild');
- case 'chidaudau':
- return I18N::translateContext('child’s daughter’s daughter', 'great-granddaughter');
- case 'chidauhus':
- return I18N::translateContext('child’s daughter’s husband', 'granddaughter’s husband');
- case 'chidauson':
- return I18N::translateContext('child’s daughter’s son', 'great-grandson');
- case 'chisonchi':
- return I18N::translateContext('child’s son’s child', 'great-grandchild');
- case 'chisondau':
- return I18N::translateContext('child’s son’s daughter', 'great-granddaughter');
- case 'chisonson':
- return I18N::translateContext('child’s son’s son', 'great-grandson');
- case 'chisonwif':
- return I18N::translateContext('child’s son’s wife', 'grandson’s wife');
- case 'dauchichi':
- return I18N::translateContext('daughter’s child’s child', 'great-grandchild');
- case 'dauchidau':
- return I18N::translateContext('daughter’s child’s daughter', 'great-granddaughter');
- case 'dauchison':
- return I18N::translateContext('daughter’s child’s son', 'great-grandson');
- case 'daudauchi':
- return I18N::translateContext('daughter’s daughter’s child', 'great-grandchild');
- case 'daudaudau':
- return I18N::translateContext('daughter’s daughter’s daughter', 'great-granddaughter');
- case 'daudauhus':
- return I18N::translateContext('daughter’s daughter’s husband', 'granddaughter’s husband');
- case 'daudauson':
- return I18N::translateContext('daughter’s daughter’s son', 'great-grandson');
- case 'dauhusfat':
- return I18N::translateContext('daughter’s husband’s father', 'son-in-law’s father');
- case 'dauhusmot':
- return I18N::translateContext('daughter’s husband’s mother', 'son-in-law’s mother');
- case 'dauhuspar':
- return I18N::translateContext('daughter’s husband’s parent', 'son-in-law’s parent');
- case 'dausonchi':
- return I18N::translateContext('daughter’s son’s child', 'great-grandchild');
- case 'dausondau':
- return I18N::translateContext('daughter’s son’s daughter', 'great-granddaughter');
- case 'dausonson':
- return I18N::translateContext('daughter’s son’s son', 'great-grandson');
- case 'dausonwif':
- return I18N::translateContext('daughter’s son’s wife', 'grandson’s wife');
- case 'fatbrochi':
- return I18N::translateContext('father’s brother’s child', 'first cousin');
- case 'fatbrodau':
- return I18N::translateContext('father’s brother’s daughter', 'first cousin');
- case 'fatbroson':
- return I18N::translateContext('father’s brother’s son', 'first cousin');
- case 'fatbrowif':
- return I18N::translateContext('father’s brother’s wife', 'aunt');
- case 'fatfatbro':
- return I18N::translateContext('father’s father’s brother', 'great-uncle');
- case 'fatfatfat':
- return I18N::translateContext('father’s father’s father', 'great-grandfather');
- case 'fatfatmot':
- return I18N::translateContext('father’s father’s mother', 'great-grandmother');
- case 'fatfatpar':
- return I18N::translateContext('father’s father’s parent', 'great-grandparent');
- case 'fatfatsib':
- return I18N::translateContext('father’s father’s sibling', 'great-aunt/uncle');
- case 'fatfatsis':
- return I18N::translateContext('father’s father’s sister', 'great-aunt');
- case 'fatmotbro':
- return I18N::translateContext('father’s mother’s brother', 'great-uncle');
- case 'fatmotfat':
- return I18N::translateContext('father’s mother’s father', 'great-grandfather');
- case 'fatmotmot':
- return I18N::translateContext('father’s mother’s mother', 'great-grandmother');
- case 'fatmotpar':
- return I18N::translateContext('father’s mother’s parent', 'great-grandparent');
- case 'fatmotsib':
- return I18N::translateContext('father’s mother’s sibling', 'great-aunt/uncle');
- case 'fatmotsis':
- return I18N::translateContext('father’s mother’s sister', 'great-aunt');
- case 'fatparbro':
- return I18N::translateContext('father’s parent’s brother', 'great-uncle');
- case 'fatparfat':
- return I18N::translateContext('father’s parent’s father', 'great-grandfather');
- case 'fatparmot':
- return I18N::translateContext('father’s parent’s mother', 'great-grandmother');
- case 'fatparpar':
- return I18N::translateContext('father’s parent’s parent', 'great-grandparent');
- case 'fatparsib':
- return I18N::translateContext('father’s parent’s sibling', 'great-aunt/uncle');
- case 'fatparsis':
- return I18N::translateContext('father’s parent’s sister', 'great-aunt');
- case 'fatsischi':
- return I18N::translateContext('father’s sister’s child', 'first cousin');
- case 'fatsisdau':
- return I18N::translateContext('father’s sister’s daughter', 'first cousin');
- case 'fatsishus':
- return I18N::translateContext('father’s sister’s husband', 'uncle');
- case 'fatsisson':
- return I18N::translateContext('father’s sister’s son', 'first cousin');
- case 'fatwifchi':
- return I18N::translateContext('father’s wife’s child', 'step-sibling');
- case 'fatwifdau':
- return I18N::translateContext('father’s wife’s daughter', 'step-sister');
- case 'fatwifson':
- return I18N::translateContext('father’s wife’s son', 'step-brother');
- case 'husbrowif':
- return I18N::translateContext('husband’s brother’s wife', 'sister-in-law');
- case 'hussishus':
- return I18N::translateContext('husband’s sister’s husband', 'brother-in-law');
- case 'motbrochi':
- return I18N::translateContext('mother’s brother’s child', 'first cousin');
- case 'motbrodau':
- return I18N::translateContext('mother’s brother’s daughter', 'first cousin');
- case 'motbroson':
- return I18N::translateContext('mother’s brother’s son', 'first cousin');
- case 'motbrowif':
- return I18N::translateContext('mother’s brother’s wife', 'aunt');
- case 'motfatbro':
- return I18N::translateContext('mother’s father’s brother', 'great-uncle');
- case 'motfatfat':
- return I18N::translateContext('mother’s father’s father', 'great-grandfather');
- case 'motfatmot':
- return I18N::translateContext('mother’s father’s mother', 'great-grandmother');
- case 'motfatpar':
- return I18N::translateContext('mother’s father’s parent', 'great-grandparent');
- case 'motfatsib':
- return I18N::translateContext('mother’s father’s sibling', 'great-aunt/uncle');
- case 'motfatsis':
- return I18N::translateContext('mother’s father’s sister', 'great-aunt');
- case 'mothuschi':
- return I18N::translateContext('mother’s husband’s child', 'step-sibling');
- case 'mothusdau':
- return I18N::translateContext('mother’s husband’s daughter', 'step-sister');
- case 'mothusson':
- return I18N::translateContext('mother’s husband’s son', 'step-brother');
- case 'motmotbro':
- return I18N::translateContext('mother’s mother’s brother', 'great-uncle');
- case 'motmotfat':
- return I18N::translateContext('mother’s mother’s father', 'great-grandfather');
- case 'motmotmot':
- return I18N::translateContext('mother’s mother’s mother', 'great-grandmother');
- case 'motmotpar':
- return I18N::translateContext('mother’s mother’s parent', 'great-grandparent');
- case 'motmotsib':
- return I18N::translateContext('mother’s mother’s sibling', 'great-aunt/uncle');
- case 'motmotsis':
- return I18N::translateContext('mother’s mother’s sister', 'great-aunt');
- case 'motparbro':
- return I18N::translateContext('mother’s parent’s brother', 'great-uncle');
- case 'motparfat':
- return I18N::translateContext('mother’s parent’s father', 'great-grandfather');
- case 'motparmot':
- return I18N::translateContext('mother’s parent’s mother', 'great-grandmother');
- case 'motparpar':
- return I18N::translateContext('mother’s parent’s parent', 'great-grandparent');
- case 'motparsib':
- return I18N::translateContext('mother’s parent’s sibling', 'great-aunt/uncle');
- case 'motparsis':
- return I18N::translateContext('mother’s parent’s sister', 'great-aunt');
- case 'motsischi':
- return I18N::translateContext('mother’s sister’s child', 'first cousin');
- case 'motsisdau':
- return I18N::translateContext('mother’s sister’s daughter', 'first cousin');
- case 'motsishus':
- return I18N::translateContext('mother’s sister’s husband', 'uncle');
- case 'motsisson':
- return I18N::translateContext('mother’s sister’s son', 'first cousin');
- case 'parbrowif':
- return I18N::translateContext('parent’s brother’s wife', 'aunt');
- case 'parfatbro':
- return I18N::translateContext('parent’s father’s brother', 'great-uncle');
- case 'parfatfat':
- return I18N::translateContext('parent’s father’s father', 'great-grandfather');
- case 'parfatmot':
- return I18N::translateContext('parent’s father’s mother', 'great-grandmother');
- case 'parfatpar':
- return I18N::translateContext('parent’s father’s parent', 'great-grandparent');
- case 'parfatsib':
- return I18N::translateContext('parent’s father’s sibling', 'great-aunt/uncle');
- case 'parfatsis':
- return I18N::translateContext('parent’s father’s sister', 'great-aunt');
- case 'parmotbro':
- return I18N::translateContext('parent’s mother’s brother', 'great-uncle');
- case 'parmotfat':
- return I18N::translateContext('parent’s mother’s father', 'great-grandfather');
- case 'parmotmot':
- return I18N::translateContext('parent’s mother’s mother', 'great-grandmother');
- case 'parmotpar':
- return I18N::translateContext('parent’s mother’s parent', 'great-grandparent');
- case 'parmotsib':
- return I18N::translateContext('parent’s mother’s sibling', 'great-aunt/uncle');
- case 'parmotsis':
- return I18N::translateContext('parent’s mother’s sister', 'great-aunt');
- case 'parparbro':
- return I18N::translateContext('parent’s parent’s brother', 'great-uncle');
- case 'parparfat':
- return I18N::translateContext('parent’s parent’s father', 'great-grandfather');
- case 'parparmot':
- return I18N::translateContext('parent’s parent’s mother', 'great-grandmother');
- case 'parparpar':
- return I18N::translateContext('parent’s parent’s parent', 'great-grandparent');
- case 'parparsib':
- return I18N::translateContext('parent’s parent’s sibling', 'great-aunt/uncle');
- case 'parparsis':
- return I18N::translateContext('parent’s parent’s sister', 'great-aunt');
- case 'parsishus':
- return I18N::translateContext('parent’s sister’s husband', 'uncle');
- case 'parspochi':
- return I18N::translateContext('parent’s spouse’s child', 'step-sibling');
- case 'parspodau':
- return I18N::translateContext('parent’s spouse’s daughter', 'step-sister');
- case 'parsposon':
- return I18N::translateContext('parent’s spouse’s son', 'step-brother');
- case 'sibchichi':
- return I18N::translateContext('sibling’s child’s child', 'great-nephew/niece');
- case 'sibchidau':
- return I18N::translateContext('sibling’s child’s daughter', 'great-niece');
- case 'sibchison':
- return I18N::translateContext('sibling’s child’s son', 'great-nephew');
- case 'sibdauchi':
- return I18N::translateContext('sibling’s daughter’s child', 'great-nephew/niece');
- case 'sibdaudau':
- return I18N::translateContext('sibling’s daughter’s daughter', 'great-niece');
- case 'sibdauhus':
- return I18N::translateContext('sibling’s daughter’s husband', 'nephew-in-law');
- case 'sibdauson':
- return I18N::translateContext('sibling’s daughter’s son', 'great-nephew');
- case 'sibsonchi':
- return I18N::translateContext('sibling’s son’s child', 'great-nephew/niece');
- case 'sibsondau':
- return I18N::translateContext('sibling’s son’s daughter', 'great-niece');
- case 'sibsonson':
- return I18N::translateContext('sibling’s son’s son', 'great-nephew');
- case 'sibsonwif':
- return I18N::translateContext('sibling’s son’s wife', 'niece-in-law');
- case 'sischichi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s child', 'great-nephew/niece');
- }
- case 'sischidau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s daughter', 'great-niece');
- }
- case 'sischison':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s child’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s child’s son', 'great-nephew');
- }
- case 'sisdauchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s child', 'great-nephew/niece');
- }
- case 'sisdaudau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s daughter', 'great-niece');
- }
- case 'sisdauhus':
- return I18N::translateContext('sisters’s daughter’s husband', 'nephew-in-law');
- case 'sisdauson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s daughter’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s daughter’s son', 'great-nephew');
- }
- case 'sishusbro':
- return I18N::translateContext('sister’s husband’s brother', 'brother-in-law');
- case 'sishussib':
- return I18N::translateContext('sister’s husband’s sibling', 'brother/sister-in-law');
- case 'sishussis':
- return I18N::translateContext('sister’s husband’s sister', 'sister-in-law');
- case 'sissonchi':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s child', 'great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s child', 'great-nephew/niece');
- }
- case 'sissondau':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s daughter', 'great-niece');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s daughter', 'great-niece');
- }
- case 'sissonson':
- if ($sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s son’s son', 'great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) sister’s son’s son', 'great-nephew');
- }
- case 'sissonwif':
- return I18N::translateContext('sisters’s son’s wife', 'niece-in-law');
- case 'sonchichi':
- return I18N::translateContext('son’s child’s child', 'great-grandchild');
- case 'sonchidau':
- return I18N::translateContext('son’s child’s daughter', 'great-granddaughter');
- case 'sonchison':
- return I18N::translateContext('son’s child’s son', 'great-grandson');
- case 'sondauchi':
- return I18N::translateContext('son’s daughter’s child', 'great-grandchild');
- case 'sondaudau':
- return I18N::translateContext('son’s daughter’s daughter', 'great-granddaughter');
- case 'sondauhus':
- return I18N::translateContext('son’s daughter’s husband', 'granddaughter’s husband');
- case 'sondauson':
- return I18N::translateContext('son’s daughter’s son', 'great-grandson');
- case 'sonsonchi':
- return I18N::translateContext('son’s son’s child', 'great-grandchild');
- case 'sonsondau':
- return I18N::translateContext('son’s son’s daughter', 'great-granddaughter');
- case 'sonsonson':
- return I18N::translateContext('son’s son’s son', 'great-grandson');
- case 'sonsonwif':
- return I18N::translateContext('son’s son’s wife', 'grandson’s wife');
- case 'sonwiffat':
- return I18N::translateContext('son’s wife’s father', 'daughter-in-law’s father');
- case 'sonwifmot':
- return I18N::translateContext('son’s wife’s mother', 'daughter-in-law’s mother');
- case 'sonwifpar':
- return I18N::translateContext('son’s wife’s parent', 'daughter-in-law’s parent');
- case 'wifbrowif':
- return I18N::translateContext('wife’s brother’s wife', 'sister-in-law');
- case 'wifsishus':
- return I18N::translateContext('wife’s sister’s husband', 'brother-in-law');
-
- // Some “special case” level four relationships that have specific names in certain languages
- case 'fatfatbrowif':
- return I18N::translateContext('father’s father’s brother’s wife', 'great-aunt');
- case 'fatfatsibspo':
- return I18N::translateContext('father’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatfatsishus':
- return I18N::translateContext('father’s father’s sister’s husband', 'great-uncle');
- case 'fatmotbrowif':
- return I18N::translateContext('father’s mother’s brother’s wife', 'great-aunt');
- case 'fatmotsibspo':
- return I18N::translateContext('father’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatmotsishus':
- return I18N::translateContext('father’s mother’s sister’s husband', 'great-uncle');
- case 'fatparbrowif':
- return I18N::translateContext('father’s parent’s brother’s wife', 'great-aunt');
- case 'fatparsibspo':
- return I18N::translateContext('father’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'fatparsishus':
- return I18N::translateContext('father’s parent’s sister’s husband', 'great-uncle');
- case 'motfatbrowif':
- return I18N::translateContext('mother’s father’s brother’s wife', 'great-aunt');
- case 'motfatsibspo':
- return I18N::translateContext('mother’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'motfatsishus':
- return I18N::translateContext('mother’s father’s sister’s husband', 'great-uncle');
- case 'motmotbrowif':
- return I18N::translateContext('mother’s mother’s brother’s wife', 'great-aunt');
- case 'motmotsibspo':
- return I18N::translateContext('mother’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'motmotsishus':
- return I18N::translateContext('mother’s mother’s sister’s husband', 'great-uncle');
- case 'motparbrowif':
- return I18N::translateContext('mother’s parent’s brother’s wife', 'great-aunt');
- case 'motparsibspo':
- return I18N::translateContext('mother’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'motparsishus':
- return I18N::translateContext('mother’s parent’s sister’s husband', 'great-uncle');
- case 'parfatbrowif':
- return I18N::translateContext('parent’s father’s brother’s wife', 'great-aunt');
- case 'parfatsibspo':
- return I18N::translateContext('parent’s father’s sibling’s spouse', 'great-aunt/uncle');
- case 'parfatsishus':
- return I18N::translateContext('parent’s father’s sister’s husband', 'great-uncle');
- case 'parmotbrowif':
- return I18N::translateContext('parent’s mother’s brother’s wife', 'great-aunt');
- case 'parmotsibspo':
- return I18N::translateContext('parent’s mother’s sibling’s spouse', 'great-aunt/uncle');
- case 'parmotsishus':
- return I18N::translateContext('parent’s mother’s sister’s husband', 'great-uncle');
- case 'parparbrowif':
- return I18N::translateContext('parent’s parent’s brother’s wife', 'great-aunt');
- case 'parparsibspo':
- return I18N::translateContext('parent’s parent’s sibling’s spouse', 'great-aunt/uncle');
- case 'parparsishus':
- return I18N::translateContext('parent’s parent’s sister’s husband', 'great-uncle');
- case 'fatfatbrodau':
- return I18N::translateContext('father’s father’s brother’s daughter', 'first cousin once removed ascending');
- case 'fatfatbroson':
- return I18N::translateContext('father’s father’s brother’s son', 'first cousin once removed ascending');
- case 'fatfatbrochi':
- return I18N::translateContext('father’s father’s brother’s child', 'first cousin once removed ascending');
- case 'fatfatsisdau':
- return I18N::translateContext('father’s father’s sister’s daughter', 'first cousin once removed ascending');
- case 'fatfatsisson':
- return I18N::translateContext('father’s father’s sister’s son', 'first cousin once removed ascending');
- case 'fatfatsischi':
- return I18N::translateContext('father’s father’s sister’s child', 'first cousin once removed ascending');
- case 'fatmotbrodau':
- return I18N::translateContext('father’s mother’s brother’s daughter', 'first cousin once removed ascending');
- case 'fatmotbroson':
- return I18N::translateContext('father’s mother’s brother’s son', 'first cousin once removed ascending');
- case 'fatmotbrochi':
- return I18N::translateContext('father’s mother’s brother’s child', 'first cousin once removed ascending');
- case 'fatmotsisdau':
- return I18N::translateContext('father’s mother’s sister’s daughter', 'first cousin once removed ascending');
- case 'fatmotsisson':
- return I18N::translateContext('father’s mother’s sister’s son', 'first cousin once removed ascending');
- case 'fatmotsischi':
- return I18N::translateContext('father’s mother’s sister’s child', 'first cousin once removed ascending');
- case 'motfatbrodau':
- return I18N::translateContext('mother’s father’s brother’s daughter', 'first cousin once removed ascending');
- case 'motfatbroson':
- return I18N::translateContext('mother’s father’s brother’s son', 'first cousin once removed ascending');
- case 'motfatbrochi':
- return I18N::translateContext('mother’s father’s brother’s child', 'first cousin once removed ascending');
- case 'motfatsisdau':
- return I18N::translateContext('mother’s father’s sister’s daughter', 'first cousin once removed ascending');
- case 'motfatsisson':
- return I18N::translateContext('mother’s father’s sister’s son', 'first cousin once removed ascending');
- case 'motfatsischi':
- return I18N::translateContext('mother’s father’s sister’s child', 'first cousin once removed ascending');
- case 'motmotbrodau':
- return I18N::translateContext('mother’s mother’s brother’s daughter', 'first cousin once removed ascending');
- case 'motmotbroson':
- return I18N::translateContext('mother’s mother’s brother’s son', 'first cousin once removed ascending');
- case 'motmotbrochi':
- return I18N::translateContext('mother’s mother’s brother’s child', 'first cousin once removed ascending');
- case 'motmotsisdau':
- return I18N::translateContext('mother’s mother’s sister’s daughter', 'first cousin once removed ascending');
- case 'motmotsisson':
- return I18N::translateContext('mother’s mother’s sister’s son', 'first cousin once removed ascending');
- case 'motmotsischi':
- return I18N::translateContext('mother’s mother’s sister’s child', 'first cousin once removed ascending');
- }
-
- // Some “special case” level five relationships that have specific names in certain languages
- if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandfather’s sibling’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)motsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandmother’s sibling’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parbro(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s brother’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsis(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s sister’s grandchild', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)dau$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s granddaughter', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)son$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s grandson', 'second cousin');
- } elseif (preg_match('/^(mot|fat|par)parsib(son|dau|chi)chi$/', $path)) {
- return I18N::translateContext('grandparent’s sibling’s grandchild', 'second cousin');
- }
-
- // Look for generic/pattern relationships.
- if (preg_match('/^((?:mot|fat|par)+)(bro|sis|sib)$/', $path, $match)) {
- // siblings of direct ancestors
- $up = strlen($match[1]) / 3;
- $bef_last = substr($path, -6, 3);
- switch ($up) {
- case 3:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-grandfather’s brother', 'great-great-uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-grandmother’s brother', 'great-great-uncle');
- } else {
- return I18N::translateContext('great-grandparent’s brother', 'great-great-uncle');
- }
- case 'F':
- return I18N::translate('great-great-aunt');
- default:
- return I18N::translate('great-great-aunt/uncle');
- }
- case 4:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-great-grandfather’s brother', 'great-great-great-uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-great-grandmother’s brother', 'great-great-great-uncle');
- } else {
- return I18N::translateContext('great-great-grandparent’s brother', 'great-great-great-uncle');
- }
- case 'F':
- return I18N::translate('great-great-great-aunt');
- default:
- return I18N::translate('great-great-great-aunt/uncle');
- }
- case 5:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great-great-great-grandfather’s brother', 'great ×4 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great-great-great-grandmother’s brother', 'great ×4 uncle');
- } else {
- return I18N::translateContext('great-great-great-grandparent’s brother', 'great ×4 uncle');
- }
- case 'F':
- return I18N::translate('great ×4 aunt');
- default:
- return I18N::translate('great ×4 aunt/uncle');
- }
- case 6:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×4 grandfather’s brother', 'great ×5 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×4 grandmother’s brother', 'great ×5 uncle');
- } else {
- return I18N::translateContext('great ×4 grandparent’s brother', 'great ×5 uncle');
- }
- case 'F':
- return I18N::translate('great ×5 aunt');
- default:
- return I18N::translate('great ×5 aunt/uncle');
- }
- case 7:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×5 grandfather’s brother', 'great ×6 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×5 grandmother’s brother', 'great ×6 uncle');
- } else {
- return I18N::translateContext('great ×5 grandparent’s brother', 'great ×6 uncle');
- }
- case 'F':
- return I18N::translate('great ×6 aunt');
- default:
- return I18N::translate('great ×6 aunt/uncle');
- }
- case 8:
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×6 grandfather’s brother', 'great ×7 uncle');
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×6 grandmother’s brother', 'great ×7 uncle');
- } else {
- return I18N::translateContext('great ×6 grandparent’s brother', 'great ×7 uncle');
- }
- case 'F':
- return I18N::translate('great ×7 aunt');
- default:
- return I18N::translate('great ×7 aunt/uncle');
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 uncle is a Danish great ×10 uncle.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'da':
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s uncle', I18N::number($up - 4));
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 4));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 4));
- }
- case 'pl':
- switch ($sex2) {
- case 'M':
- if ($bef_last === 'fat') {
- return I18N::translateContext('great ×(%s-1) grandfather’s brother', 'great ×%s uncle', I18N::number($up - 2));
- } elseif ($bef_last === 'mot') {
- return I18N::translateContext('great ×(%s-1) grandmother’s brother', 'great ×%s uncle', I18N::number($up - 2));
- } else {
- return I18N::translateContext('great ×(%s-1) grandparent’s brother', 'great ×%s uncle', I18N::number($up - 2));
- }
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 2));
- }
- case 'it': // Source: Michele Locati
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s uncle', I18N::number($up - 1));
- case 'F':
- return I18N::translate('great ×%s aunt', I18N::number($up - 1));
- default:
- return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 1));
- }
- }
- }
- }
- if (preg_match('/^(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
- // direct descendants of siblings
- $down = strlen($match[1]) / 3 + 1; // Add one, as we count generations from the common ancestor
- $first = substr($path, 0, 3);
- switch ($down) {
- case 4:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-grandson', 'great-great-nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-grandson', 'great-great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) great-great-nephew', 'great-great-nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-granddaughter', 'great-great-niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-granddaughter', 'great-great-niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-niece', 'great-great-niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-grandchild', 'great-great-nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-grandchild', 'great-great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-nephew/niece', 'great-great-nephew/niece');
- }
- }
- case 5:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-grandson', 'great-great-great-nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-grandson', 'great-great-great-nephew');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-nephew', 'great-great-great-nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-granddaughter', 'great-great-great-niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-granddaughter', 'great-great-great-niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-niece', 'great-great-great-niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-grandchild', 'great-great-great-nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-grandchild', 'great-great-great-nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great-great-great-nephew/niece', 'great-great-great-nephew/niece');
- }
- }
- case 6:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-grandson', 'great ×4 nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-grandson', 'great ×4 nephew');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 nephew', 'great ×4 nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-granddaughter', 'great ×4 niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-granddaughter', 'great ×4 niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 niece', 'great ×4 niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great-great-great-grandchild', 'great ×4 nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great-great-great-grandchild', 'great ×4 nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×4 nephew/niece', 'great ×4 nephew/niece');
- }
- }
- case 7:
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 grandson', 'great ×5 nephew');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 grandson', 'great ×5 nephew');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 nephew', 'great ×5 nephew');
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 granddaughter', 'great ×5 niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 granddaughter', 'great ×5 niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 niece', 'great ×5 niece');
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×4 grandchild', 'great ×5 nephew/niece');
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×4 grandchild', 'great ×5 nephew/niece');
- } else {
- return I18N::translateContext('(a woman’s) great ×5 nephew/niece', 'great ×5 nephew/niece');
- }
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 nephew is a Polish great ×11 nephew.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'pl': // Source: Lukasz Wilenski
- switch ($sex2) {
- case 'M':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s nephew', 'great ×%s nephew', I18N::number($down - 3));
- }
- case 'F':
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s niece', 'great ×%s niece', I18N::number($down - 3));
- }
- default:
- if ($first === 'bro' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
- } elseif ($first === 'sis' && $sex1 === 'M') {
- return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
- } else {
- return I18N::translateContext('(a woman’s) great ×%s nephew/niece', 'great ×%s nephew/niece', I18N::number($down - 3));
- }
- }
- case 'he': // Source: Meliza Amity
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s nephew', I18N::number($down - 1));
- case 'F':
- return I18N::translate('great ×%s niece', I18N::number($down - 1));
- default:
- return I18N::translate('great ×%s nephew/niece', I18N::number($down - 1));
- }
- case 'it': // Source: Michele Locati.
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s nephew', I18N::number($down - 2));
- case 'F':
- return I18N::translate('great ×%s niece', I18N::number($down - 2));
- default:
- return I18N::translate('great ×%s nephew/niece', I18N::number($down - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:mot|fat|par)*)$/', $path, $match)) {
- // direct ancestors
- $up = strlen($match[1]) / 3;
- switch ($up) {
- case 4:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-grandfather');
- case 'F':
- return I18N::translate('great-great-grandmother');
- default:
- return I18N::translate('great-great-grandparent');
- }
- case 5:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-great-grandfather');
- case 'F':
- return I18N::translate('great-great-great-grandmother');
- default:
- return I18N::translate('great-great-great-grandparent');
- }
- case 6:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×4 grandfather');
- case 'F':
- return I18N::translate('great ×4 grandmother');
- default:
- return I18N::translate('great ×4 grandparent');
- }
- case 7:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×5 grandfather');
- case 'F':
- return I18N::translate('great ×5 grandmother');
- default:
- return I18N::translate('great ×5 grandparent');
- }
- case 8:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×6 grandfather');
- case 'F':
- return I18N::translate('great ×6 grandmother');
- default:
- return I18N::translate('great ×6 grandparent');
- }
- case 9:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×7 grandfather');
- case 'F':
- return I18N::translate('great ×7 grandmother');
- default:
- return I18N::translate('great ×7 grandparent');
- }
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 grandfather is a Danish great ×11 grandfather.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'da': // Source: Patrick Sorensen
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
- }
- case 'it': // Source: Michele Locati
- case 'es': // Source: Wes Groleau
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up));
- }
- case 'fr': // Source: Jacqueline Tetreault
- case 'fr_CA':
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandfather', I18N::number($up - 1));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 1));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 1));
- }
- case 'nn': // Source: Hogne Røed Nilsen (https://bugs.launchpad.net/webtrees/+bug/1168553)
- case 'nb':
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
- }
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandfather', I18N::number($up - 2));
- case 'F':
- return I18N::translate('great ×%s grandmother', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s grandparent', I18N::number($up - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:son|dau|chi)*)$/', $path, $match)) {
- // direct descendants
- $up = strlen($match[1]) / 3;
- switch ($up) {
- case 4:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-grandson');
- case 'F':
- return I18N::translate('great-great-granddaughter');
- default:
- return I18N::translate('great-great-grandchild');
- }
-
- case 5:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great-great-great-grandson');
- case 'F':
- return I18N::translate('great-great-great-granddaughter');
- default:
- return I18N::translate('great-great-great-grandchild');
- }
-
- case 6:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×4 grandson');
- case 'F':
- return I18N::translate('great ×4 granddaughter');
- default:
- return I18N::translate('great ×4 grandchild');
- }
-
- case 7:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×5 grandson');
- case 'F':
- return I18N::translate('great ×5 granddaughter');
- default:
- return I18N::translate('great ×5 grandchild');
- }
-
- case 8:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×6 grandson');
- case 'F':
- return I18N::translate('great ×6 granddaughter');
- default:
- return I18N::translate('great ×6 grandchild');
- }
-
- case 9:
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×7 grandson');
- case 'F':
- return I18N::translate('great ×7 granddaughter');
- default:
- return I18N::translate('great ×7 grandchild');
- }
-
- default:
- // Different languages have different rules for naming generations.
- // An English great ×12 grandson is a Danish great ×11 grandson.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'nn': // Source: Hogne Røed Nilsen
- case 'nb':
- case 'da': // Source: Patrick Sorensen
- switch ($sex2) {
- case 'M':
- return I18N::translate('great ×%s grandson', I18N::number($up - 3));
- case 'F':
- return I18N::translate('great ×%s granddaughter', I18N::number($up - 3));
- default:
- return I18N::translate('great ×%s grandchild', I18N::number($up - 3));
- }
- case 'it': // Source: Michele Locati
- case 'es': // Source: Wes Groleau (adding doesn’t change behavior, but needs to be better researched)
- case 'en_AU':
- case 'en_GB':
- case 'en_US':
- default:
- switch ($sex2) {
-
- case 'M': // I18N: if you need a different number for %s, contact the developers, as a code-change is required
- return I18N::translate('great ×%s grandson', I18N::number($up - 2));
- case 'F':
- return I18N::translate('great ×%s granddaughter', I18N::number($up - 2));
- default:
- return I18N::translate('great ×%s grandchild', I18N::number($up - 2));
- }
- }
- }
- }
- if (preg_match('/^((?:mot|fat|par)+)(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
- // cousins in English
- $ascent = $match[1];
- $descent = $match[2];
- $up = strlen($ascent) / 3;
- $down = strlen($descent) / 3;
- $cousin = min($up, $down); // Moved out of switch (en/default case) so that
- $removed = abs($down - $up); // Spanish (and other languages) can use it, too.
-
- // Different languages have different rules for naming cousins. For example,
- // an English “second cousin once removed” is a Polish “cousin of 7th degree”.
- //
- // Need to find out which languages use which rules.
- switch (WT_LOCALE) {
- case 'pl': // Source: Lukasz Wilenski
- return cousin_name($up + $down + 2, $sex2);
- case 'it':
- // Source: Michele Locati. See italian_cousins_names.zip
- // http://webtrees.net/forums/8-translation/1200-great-xn-grandparent?limit=6&start=6
- return cousin_name($up + $down - 3, $sex2);
- case 'es':
- // Source: Wes Groleau. See http://UniGen.us/Parentesco.html & http://UniGen.us/Parentesco-D.html
- if ($down == $up) {
- return cousin_name($cousin, $sex2);
- } elseif ($down < $up) {
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
- } else {
- switch ($sex2) {
- case 'M':
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('bro' . $descent, null, null));
- case 'F':
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sis' . $descent, null, null));
- default:
- return cousin_name2($cousin + 1, $sex2, get_relationship_name_from_path('sib' . $descent, null, null));
- }
- }
- case 'en_AU': // See: http://en.wikipedia.org/wiki/File:CousinTree.svg
- case 'en_GB':
- case 'en_US':
- default:
- switch ($removed) {
- case 0:
- return cousin_name($cousin, $sex2);
- case 1:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
- return I18N::translate('%s once removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. http://www.ancestry.com/learn/library/article.aspx?article=2856 */
- return I18N::translate('%s once removed descending', cousin_name($cousin, $sex2));
- }
- case 2:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s twice removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s twice removed descending', cousin_name($cousin, $sex2));
- }
- case 3:
- if ($up > $down) {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s three times removed ascending', cousin_name($cousin, $sex2));
- } else {
- /* I18N: %s=“fifth cousin”, etc. */
- return I18N::translate('%s three times removed descending', cousin_name($cousin, $sex2));
- }
- default:
- if ($up > $down) {
- /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
- return I18N::translate('%1$s %2$s times removed ascending', cousin_name($cousin, $sex2), I18N::number($removed));
- } else {
- /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
- return I18N::translate('%1$s %2$s times removed descending', cousin_name($cousin, $sex2), I18N::number($removed));
- }
- }
- }
- }
-
- // Split the relationship into sub-relationships, e.g., third-cousin’s great-uncle.
- // Try splitting at every point, and choose the path with the shorted translated name.
-
- $relationship = null;
- $path1 = substr($path, 0, 3);
- $path2 = substr($path, 3);
- while ($path2) {
- $tmp = I18N::translate(
- // I18N: A complex relationship, such as “third-cousin’s great-uncle”
- '%1$s’s %2$s',
- get_relationship_name_from_path($path1, null, null), // TODO: need the actual people
- get_relationship_name_from_path($path2, null, null)
- );
- if (!$relationship || strlen($tmp) < strlen($relationship)) {
- $relationship = $tmp;
- }
- $path1 .= substr($path2, 0, 3);
- $path2 = substr($path2, 3);
- }
-
- return $relationship;
-}
-
-/**
- * Function to build an URL querystring from GET variables
- * Optionally, add/replace specified values
- *
- * @param null|string[] $overwrite
- * @param null|string $separator
- *
- * @return string
- */
-function get_query_url($overwrite = null, $separator = '&') {
- if (empty($_GET)) {
- $get = array();
- } else {
- $get = $_GET;
- }
- if (is_array($overwrite)) {
- foreach ($overwrite as $key => $value) {
- $get[$key] = $value;
- }
- }
-
- $query_string = '';
- foreach ($get as $key => $value) {
- if (!is_array($value)) {
- $query_string .= $separator . rawurlencode($key) . '=' . rawurlencode($value);
- } else {
- foreach ($value as $k => $v) {
- $query_string .= $separator . rawurlencode($key) . '%5B' . rawurlencode($k) . '%5D=' . rawurlencode($v);
- }
- }
- }
- $query_string = substr($query_string, strlen($separator)); // Remove leading “&amp;”
- if ($query_string) {
- return WT_SCRIPT_NAME . '?' . $query_string;
- } else {
- return WT_SCRIPT_NAME;
- }
-}
-
-/**
- * Determines whether the passed in filename is a link to an external source (i.e. contains “://”)
- *
- * @param string $file
- *
- * @return bool
- */
-function isFileExternal($file) {
- return strpos($file, '://') !== false;
-}
diff --git a/includes/functions/functions_charts.php b/includes/functions/functions_charts.php
deleted file mode 100644
index ab4e24f7fb..0000000000
--- a/includes/functions/functions_charts.php
+++ /dev/null
@@ -1,550 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Theme;
-
-/**
- * print a table cell with sosa number
- *
- * @param int $sosa
- * @param string $pid optional pid
- * @param string $arrowDirection direction of link arrow
- */
-function print_sosa_number($sosa, $pid = "", $arrowDirection = "up") {
- if (substr($sosa, -1, 1) == ".") {
- $personLabel = substr($sosa, 0, -1);
- } else {
- $personLabel = $sosa;
- }
- if ($arrowDirection == "blank") {
- $visibility = "hidden";
- } else {
- $visibility = "normal";
- }
- echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
- echo $personLabel;
- if ($sosa != "1" && $pid != "") {
- if ($arrowDirection == "left") {
- $dir = 0;
- } elseif ($arrowDirection == "right") {
- $dir = 1;
- } elseif ($arrowDirection == "down") {
- $dir = 3;
- } else {
- $dir = 2; // either 'blank' or 'up'
- }
- echo '<br>';
- print_url_arrow('#' . $pid, $pid, $dir);
- }
- echo '</td>';
-}
-
-/**
- * print the parents table for a family
- *
- * @param Family $family family gedcom ID
- * @param int $sosa child sosa number
- * @param string $label indi label (descendancy booklet)
- * @param string $parid parent ID (descendancy booklet)
- * @param string $gparid gd-parent ID (descendancy booklet)
- * @param int $show_full large or small box
- */
-function print_family_parents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1) {
-
- if ($show_full) {
- $pbheight = Theme::theme()->parameter('chart-box-y') + 14;
- } else {
- $pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
- }
-
- $husb = $family->getHusband();
- if ($husb) {
- echo '<a name="', $husb->getXref(), '"></a>';
- } else {
- $husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
- }
- $wife = $family->getWife();
- if ($wife) {
- echo '<a name="', $wife->getXref(), '"></a>';
- } else {
- $wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
- }
-
- if ($sosa) {
- echo '<p class="name_head">', $family->getFullName(), '</p>';
- }
-
- /**
- * husband side
- */
- echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
- echo "<table border='0'><tr>";
-
- if ($parid) {
- if ($husb->getXref() == $parid) {
- print_sosa_number($label);
- } else {
- print_sosa_number($label, "", "blank");
- }
- } elseif ($sosa) {
- print_sosa_number($sosa * 2);
- }
- if ($husb->isPendingAddtion()) {
- echo '<td valign="top" class="facts_value new">';
- } elseif ($husb->isPendingDeletion()) {
- echo '<td valign="top" class="facts_value old">';
- } else {
- echo '<td valign="top">';
- }
- print_pedigree_person($husb, $show_full);
- echo "</td></tr></table>";
- echo "</td>";
- // husband’s parents
- $hfam = $husb->getPrimaryChildFamily();
- if ($hfam) {
- // remove the|| test for $sosa
- echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
- echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- // husband’s father
- if ($hfam && $hfam->getHusband()) {
- echo "<table border='0'><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4, $hfam->getHusband()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getHusband()) {
- // Empty box for grandfather
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo '</td></tr></table>';
- }
- echo "</td>";
- }
- if ($hfam && ($sosa != -1)) {
- echo '<td valign="middle" rowspan="2">';
- print_url_arrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
- echo '</td>';
- }
- if ($hfam) {
- // husband’s mother
- echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- if ($hfam && $hfam->getWife()) {
- echo "<table border='0'><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 1, $hfam->getWife()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- } elseif ($hfam && !$hfam->getWife()) {
- // Empty box for grandmother
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- }
- echo '</td>';
- }
- echo '</tr></table>';
- if ($sosa && $family->canShow()) {
- foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
- echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
- echo str_repeat('&nbsp;', 10);
- echo $fact->summary();
- echo '</a>';
- }
- } else {
- echo '<br>';
- }
-
- /**
- * wife side
- */
- echo "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td rowspan=\"2\">";
- echo "<table><tr>";
- if ($parid) {
- if ($wife->getXref() == $parid) {
- print_sosa_number($label);
- } else {
- print_sosa_number($label, "", "blank");
- }
- } elseif ($sosa) {
- print_sosa_number($sosa * 2 + 1);
- }
- if ($wife->isPendingAddtion()) {
- echo '<td valign="top" class="facts_value new">';
- } elseif ($wife->isPendingDeletion()) {
- echo '<td valign="top" class="facts_value old">';
- } else {
- echo '<td valign="top">';
- }
- print_pedigree_person($wife, $show_full);
- echo "</td></tr></table>";
- echo "</td>";
- // wife’s parents
- $hfam = $wife->getPrimaryChildFamily();
-
- if ($hfam) {
- echo "<td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td rowspan=\"2\"><img src=\"" . Theme::theme()->parameter('image-vline') . "\" width=\"3\" height=\"" . ($pbheight + 9) . "\" alt=\"\"></td>";
- echo "<td><img class=\"line5\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- // wife’s father
- if ($hfam && $hfam->getHusband()) {
- echo "<table><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 2, $hfam->getHusband()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getHusband()) {
- // Empty box for grandfather
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getHusband(), $show_full);
- echo '</td></tr></table>';
- }
- echo "</td>";
- }
- if ($hfam && ($sosa != -1)) {
- echo '<td valign="middle" rowspan="2">';
- print_url_arrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
- echo '</td>';
- }
- if ($hfam) {
- // wife’s mother
- echo "</tr><tr><td><img src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\"></td><td>";
- if ($hfam && $hfam->getWife()) {
- echo "<table><tr>";
- if ($sosa > 0) {
- print_sosa_number($sosa * 4 + 3, $hfam->getWife()->getXref(), "down");
- }
- if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
- print_sosa_number(trim(substr($label, 0, -3), ".") . ".");
- }
- echo "<td valign=\"top\">";
- print_pedigree_person($hfam->getWife(), $show_full);
- echo "</td></tr></table>";
- } elseif ($hfam && !$hfam->getWife()) {
- // Empty box for grandmother
- echo "<table border='0'><tr>";
- echo '<td valign="top">';
- print_pedigree_person($hfam->getWife(), $show_full);
- echo '</td></tr></table>';
- }
- echo '</td>';
- }
- echo "</tr></table>";
-}
-
-/**
- * print the children table for a family
- *
- * @param Family $family family
- * @param string $childid child ID
- * @param int $sosa child sosa number
- * @param string $label indi label (descendancy booklet)
- * @param int $show_cousins display cousins on chart
- * @param int $show_full large or small box
- */
-function print_family_children(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1) {
-
- if ($show_full) {
- $bheight = Theme::theme()->parameter('chart-box-y');
- } else {
- $bheight = Theme::theme()->parameter('compact-chart-box-y');
- }
-
- $pbheight = $bheight + 14;
-
- $children = $family->getChildren();
- $numchil = count($children);
-
- echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
- if ($sosa > 0) {
- echo '<td></td>';
- }
- echo '<td><span class="subheaders">';
- if ($numchil == 0) {
- echo I18N::translate('No children');
- } else {
- echo I18N::plural('%s child', '%s children', $numchil, $numchil);
- }
- echo '</span>';
-
- if ($sosa == 0 && Auth::isEditor($family->getTree())) {
- echo '<br>';
- echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
- echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
- echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
- echo '<br><br>';
- }
- echo '</td>';
- if ($sosa > 0) {
- echo '<td></td><td></td>';
- }
- echo '</tr>';
-
- $nchi = 1;
- if ($children) {
- foreach ($children as $child) {
- echo '<tr>';
- if ($sosa != 0) {
- if ($child->getXref() == $childid) {
- print_sosa_number($sosa, $childid);
- } elseif (empty($label)) {
- print_sosa_number("");
- } else {
- print_sosa_number($label . ($nchi++) . ".");
- }
- }
- if ($child->isPendingAddtion()) {
- echo '<td valign="middle" class="new">';
- } elseif ($child->isPendingDeletion()) {
- echo '<td valign="middle" class="old">';
- } else {
- echo '<td valign="middle">';
- }
- print_pedigree_person($child, $show_full);
- echo "</td>";
- if ($sosa != 0) {
- // loop for all families where current child is a spouse
- $famids = $child->getSpouseFamilies();
-
- $maxfam = count($famids) - 1;
- for ($f = 0; $f <= $maxfam; $f++) {
- $famid_child = $famids[$f]->getXref();
- // multiple marriages
- if ($f > 0) {
- echo '</tr><tr><td></td>';
- echo '<td valign="top"';
- if (I18N::direction() === 'rtl') {
- echo ' align="left">';
- } else {
- echo ' align="right">';
- }
-
- //find out how many cousins there are to establish vertical line on second families
- $fchildren = $famids[$f]->getChildren();
- $kids = count($fchildren);
- $Pheader = ($bheight - 1) * $kids;
- $PBadj = 6; // default
- if ($show_cousins > 0) {
- if ($kids) {
- $PBadj = max(0, $Pheader / 2 + $kids * 4.5);
- }
- }
-
- if ($f == $maxfam) {
- echo "<img height=\"" . ((($bheight / 2)) + $PBadj) . "px\"";
- } else {
- echo "<img height=\"" . $pbheight . "px\"";
- }
- echo " width=\"3\" src=\"" . Theme::theme()->parameter('image-vline') . "\" alt=\"\">";
- echo "</td>";
- }
- echo "<td class=\"details1\" valign=\"middle\" align=\"center\">";
- $spouse = $famids[$f]->getSpouse($child);
-
- $marr = $famids[$f]->getFirstFact('MARR');
- $div = $famids[$f]->getFirstFact('DIV');
- if ($marr) {
- // marriage date
- echo $marr->getDate()->minimumDate()->format('%Y');
- // divorce date
- if ($div) {
- echo '–', $div->getDate()->minimumDate()->format('%Y');
- }
- }
- echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
- echo "</td>";
- // spouse information
- echo "<td style=\"vertical-align: center;";
- if (!empty($divrec)) {
- echo " filter:alpha(opacity=40);opacity:0.4;\">";
- } else {
- echo "\">";
- }
- print_pedigree_person($spouse, $show_full);
- echo "</td>";
- // cousins
- if ($show_cousins) {
- print_cousins($famid_child, $show_full);
- }
- }
- }
- echo "</tr>";
- }
- } elseif ($sosa < 1) {
- // message 'no children' except for sosa
- if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
- echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
- }
- }
- echo "</table><br>";
-}
-
-/**
- * print a family with Sosa-Stradonitz numbering system
- * ($rootid=1, father=2, mother=3 ...)
- *
- * @param string $famid family gedcom ID
- * @param string $childid tree root ID
- * @param int $sosa starting sosa number
- * @param string $label indi label (descendancy booklet)
- * @param string $parid parent ID (descendancy booklet)
- * @param string $gparid gd-parent ID (descendancy booklet)
- * @param int $show_cousins display cousins on chart
- * @param int $show_full large or small box
- */
-function print_sosa_family($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1) {
- global $WT_TREE;
-
- echo '<hr>';
- echo '<p style="page-break-before: always;">';
- if (!empty($famid)) {
- echo '<a name="', $famid, '"></a>';
- }
- print_family_parents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
- echo '<br>';
- echo '<table><tr><td valign="top">';
- print_family_children(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
- echo '</td></tr></table>';
- echo '<br>';
-}
-
-/**
- * print an arrow to a new url
- *
- * @param string $url target url
- * @param string $label arrow label
- * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
- */
-function print_url_arrow($url, $label, $dir = 2) {
- if ($url === '') {
- return;
- }
-
- // arrow direction
- $adir = $dir;
- if (I18N::direction() === 'rtl' && $dir === 0) {
- $adir = 1;
- }
- if (I18N::direction() === 'rtl' && $dir === 1) {
- $adir = 0;
- }
-
- // arrow style 0 1 2 3
- $array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
- $astyle = $array_style[$adir];
-
- // Labels include people’s names, which may contain markup
- echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
-}
-
-/**
- * builds and returns sosa relationship name in the active language
- *
- * @param string $sosa sosa number
- *
- * @return string
- */
-function get_sosa_name($sosa) {
- $path = '';
- while ($sosa > 1) {
- if ($sosa % 2 == 1) {
- $sosa -= 1;
- $path = 'mot' . $path;
- } else {
- $path = 'fat' . $path;
- }
- $sosa /= 2;
- }
-
- return get_relationship_name_from_path($path, null, null);
-}
-
-/**
- * print cousins list
- *
- * @param string $famid family ID
- * @param int $show_full large or small box
- */
-function print_cousins($famid, $show_full = 1) {
- global $WT_TREE;
-
- if ($show_full) {
- $bheight = Theme::theme()->parameter('chart-box-y');
- } else {
- $bheight = Theme::theme()->parameter('compact-chart-box-y');
- }
-
- $family = Family::getInstance($famid, $WT_TREE);
- $fchildren = $family->getChildren();
-
- $kids = count($fchildren);
-
- echo '<td valign="middle" height="100%">';
- if ($kids) {
- echo '<table cellspacing="0" cellpadding="0" border="0" ><tr valign="middle">';
- if ($kids > 1) {
- echo '<td rowspan="', $kids, '" valign="middle" align="right"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '" alt=""></td>';
- }
- $ctkids = count($fchildren);
- $i = 1;
- foreach ($fchildren as $fchil) {
- if ($i == 1) {
- echo '<td><img width="10px" height="3px" align="top"';
- } else {
- echo '<td><img width="10px" height="3px"';
- }
- if (I18N::direction() === 'ltr') {
- echo ' style="padding-right: 2px;"';
- } else {
- echo ' style="padding-left: 2px;"';
- }
- echo ' src="', Theme::theme()->parameter('image-hline'), '" alt=""></td><td>';
- print_pedigree_person($fchil, $show_full);
- echo '</td></tr>';
- if ($i < $ctkids) {
- echo '<tr>';
- $i++;
- }
- }
- echo '</table>';
- } else {
- // If there is known that there are no children (as opposed to no known children)
- if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
- echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
- }
- }
- echo '</td>';
-}
diff --git a/includes/functions/functions_date.php b/includes/functions/functions_date.php
deleted file mode 100644
index ff40204fd3..0000000000
--- a/includes/functions/functions_date.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\I18N;
-
-/**
- * @param string $age_string
- * @param bool $show_years
- *
- * @return string
- */
-function get_age_at_event($age_string, $show_years) {
- switch (strtoupper($age_string)) {
- case 'CHILD':
- return I18N::translate('Child');
- case 'INFANT':
- return I18N::translate('Infant');
- case 'STILLBORN':
- return I18N::translate('Stillborn');
- default:
- return preg_replace_callback(
- array(
- '/(\d+)([ymwd])/',
- ),
- function ($match) use ($age_string, $show_years) {
- switch ($match[2]) {
- case 'y':
- if ($show_years || preg_match('/[dm]/', $age_string)) {
- return I18N::plural('%s year', '%s years', $match[1], I18N::digits($match[1]));
- } else {
- return I18N::digits($match[1]);
- }
- case 'm':
- return I18N::plural('%s month', '%s months', $match[1], I18N::digits($match[1]));
- case 'w':
- return I18N::plural('%s week', '%s weeks', $match[1], I18N::digits($match[1]));
- case 'd':
- return I18N::plural('%s day', '%s days', $match[1], I18N::digits($match[1]));
- }
- },
- $age_string
- );
- }
-}
-
-/**
- * Convert a unix timestamp into a formatted date-time value, for logs, etc.
- * Don't attempt to convert into other calendars, as not all days start at
- * midnight, and we can only get it wrong.
- *
- * @param int $time
- *
- * @return string
- */
-function format_timestamp($time) {
- $time_fmt = I18N::timeFormat();
- // PHP::date() doesn't do I18N. Do it ourselves....
- preg_match_all('/%[^%]/', $time_fmt, $matches);
- foreach ($matches[0] as $match) {
- switch ($match) {
- case '%a':
- $t = gmdate('His', $time);
- if ($t == '000000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 00:00:00 */ I18N::translate('midnight'), $time_fmt);
- } elseif ($t < '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - between 00:00:01 and 11:59:59 */ I18N::translate('a.m.'), $time_fmt);
- } elseif ($t == '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - exactly 12:00:00 */ I18N::translate('noon'), $time_fmt);
- } else {
- $time_fmt = str_replace($match, /* I18N: time format “%a” - between 12:00:01 and 23:59:59 */ I18N::translate('p.m.'), $time_fmt);
- }
- break;
- case '%A':
- $t = gmdate('His', $time);
- if ($t == '000000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 00:00:00 */ I18N::translate('Midnight'), $time_fmt);
- } elseif ($t < '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - between 00:00:01 and 11:59:59 */ I18N::translate('A.M.'), $time_fmt);
- } elseif ($t == '120000') {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - exactly 12:00:00 */ I18N::translate('Noon'), $time_fmt);
- } else {
- $time_fmt = str_replace($match, /* I18N: time format “%A” - between 12:00:01 and 23:59:59 */ I18N::translate('P.M.'), $time_fmt);
- }
- break;
- default:
- $time_fmt = str_replace($match, I18N::digits(gmdate(substr($match, -1), $time)), $time_fmt);
- }
- }
-
- return timestamp_to_gedcom_date($time)->display() . '<span class="date"> - ' . $time_fmt . '</span>';
-}
-
-/**
- * Convert a unix-style timestamp into a Date object
- *
- * @param int $time
- *
- * @return Date
- */
-function timestamp_to_gedcom_date($time) {
- return new Date(strtoupper(gmdate('j M Y', $time)));
-}
diff --git a/includes/functions/functions_db.php b/includes/functions/functions_db.php
deleted file mode 100644
index d387a081cf..0000000000
--- a/includes/functions/functions_db.php
+++ /dev/null
@@ -1,1079 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Date\FrenchDate;
-use Fisharebest\Webtrees\Date\GregorianDate;
-use Fisharebest\Webtrees\Date\HijriDate;
-use Fisharebest\Webtrees\Date\JalaliDate;
-use Fisharebest\Webtrees\Date\JewishDate;
-use Fisharebest\Webtrees\Date\JulianDate;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Soundex;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Fetch all records linked to a record - when deleting an object, we must
- * also delete all links to it.
- *
- * @param string $xref
- * @param int $gedcom_id
- *
- * @return string[]
- */
-function fetch_all_links($xref, $gedcom_id) {
- return
- Database::prepare(
- "SELECT l_from FROM `##link` WHERE l_file = ? AND l_to = ?" .
- " UNION " .
- "SELECT xref FROM `##change` WHERE status = 'pending' AND gedcom_id = ? AND new_gedcom LIKE" .
- " CONCAT('%@', ?, '@%')"
- )->execute(array(
- $gedcom_id,
- $xref,
- $gedcom_id,
- $xref,
- ))->fetchOneColumn();
-}
-
-/**
- * Get a list of all the sources.
- *
- * @param Tree $tree
- *
- * @return Source[] array
- */
-function get_source_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT s_id AS xref, s_gedcom AS gedcom FROM `##sources` WHERE s_file = :tree_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Source::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Get a list of all the repositories.
- *
- * @param Tree $tree
- *
- * @return Repository[] array
- */
-function get_repo_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO' AND o_file = ?"
- )->execute(array(
- $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Repository::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Get a list of all the shared notes.
- *
- * @param Tree $tree
- *
- * @return Note[] array
- */
-function get_note_list(Tree $tree) {
- $rows = Database::prepare(
- "SELECT o_id AS xref, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE' AND o_file = :tree_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- $list = array();
- foreach ($rows as $row) {
- $list[] = Note::getInstance($row->xref, $tree, $row->gedcom);
- }
- usort($list, '\Fisharebest\Webtrees\GedcomRecord::compare');
-
- return $list;
-}
-
-/**
- * Search all individuals
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Individual[]
- */
-function search_indis(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND i_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the names of individuals
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Individual[]
- */
-function search_indis_names(array $query, array $trees) {
- $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom, n_full FROM `##individuals` JOIN `##name` ON i_id=n_id AND i_file=n_file WHERE 1";
- $args = array();
-
- // Convert the query into a SQL expression
- foreach ($query as $n => $q) {
- $sql .= " AND n_full COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // The individual may have private names - and the DB search may have found it.
- if ($indi->canShowName()) {
- foreach ($indi->getAllNames() as $num => $name) {
- if ($name['fullNN'] === $row->n_full) {
- $indi->setPrimaryName($num);
- // We need to clone $indi, as we may have multiple references to the
- // same person in this list, and the "primary name" would otherwise
- // be shared amongst all of them.
- $list[] = clone $indi;
- // Only need to match an individual on one name
- break;
- }
- }
- }
- }
-
- return $list;
-}
-
-/**
- * Search for individuals names/places using soundex
- *
- * @param string $soundex
- * @param string $lastname
- * @param string $firstname
- * @param string $place
- * @param Tree[] $trees
- *
- * @return Individual[]
- */
-function search_indis_soundex($soundex, $lastname, $firstname, $place, array $trees) {
- switch ($soundex) {
- case 'Russell':
- $givn_sdx = Soundex::russell($firstname);
- $surn_sdx = Soundex::russell($lastname);
- $plac_sdx = Soundex::russell($place);
- break;
- case 'DaitchM':
- $givn_sdx = Soundex::daitchMokotoff($firstname);
- $surn_sdx = Soundex::daitchMokotoff($lastname);
- $plac_sdx = Soundex::daitchMokotoff($place);
- break;
- default:
- throw new \DomainException('soundex: ' . $soundex);
- }
-
- // Nothing to search for? Return nothing.
- if (!$givn_sdx && !$surn_sdx && !$plac_sdx) {
- return array();
- }
-
- $sql = "SELECT DISTINCT i_id AS xref, i_file AS gedcom_id, i_gedcom AS gedcom FROM `##individuals`";
- $args = array();
-
- if ($place) {
- $sql .= " JOIN `##placelinks` ON pl_file = i_file AND pl_gid = i_id";
- $sql .= " JOIN `##places` ON p_file = pl_file AND pl_p_id = p_id";
- }
- if ($firstname || $lastname) {
- $sql .= " JOIN `##name` ON i_file=n_file AND i_id=n_id";
- }
- $sql .= " AND i_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- if ($firstname && $givn_sdx) {
- $sql .= " AND (";
- $givn_sdx = explode(':', $givn_sdx);
- foreach ($givn_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "n_soundex_givn_std LIKE CONCAT('%', :given_name_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "n_soundex_givn_dm LIKE CONCAT('%', :given_name_" . $n . ", '%')";
- break;
- }
- $args['given_name_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- if ($lastname && $surn_sdx) {
- $sql .= " AND (";
- $surn_sdx = explode(':', $surn_sdx);
- foreach ($surn_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "n_soundex_surn_std LIKE CONCAT('%', :surname_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "n_soundex_surn_dm LIKE CONCAT('%', :surname_" . $n . ", '%')";
- break;
- }
- $args['surname_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- if ($place && $plac_sdx) {
- $sql .= " AND (";
- $plac_sdx = explode(':', $plac_sdx);
- foreach ($plac_sdx as $n => $sdx) {
- $sql .= $n ? " OR " : "";
- switch ($soundex) {
- case 'Russell':
- $sql .= "p_std_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
- break;
- case 'DaitchM':
- $sql .= "p_dm_soundex LIKE CONCAT('%', :place_" . $n . ", '%')";
- break;
- }
- $args['place_' . $n] = $sdx;
- }
- $sql .= ")";
- }
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Individual::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- if ($indi->canShowName()) {
- $list[] = $indi;
- }
- }
-
- return $list;
-}
-
-/**
- * get recent changes since the given julian day inclusive
- *
- * @param int $jd leave empty to include all
- * @param bool $allgeds
- *
- * @return string[] List of XREFs of records with changes
- */
-function get_recent_changes($jd = 0, $allgeds = false) {
- global $WT_TREE;
-
- $sql = "SELECT d_gid FROM `##dates` WHERE d_fact='CHAN' AND d_julianday1>=?";
- $vars = array($jd);
- if (!$allgeds) {
- $sql .= " AND d_file=?";
- $vars[] = $WT_TREE->getTreeId();
- }
- $sql .= " ORDER BY d_julianday1 DESC";
-
- return Database::prepare($sql)->execute($vars)->fetchOneColumn();
-}
-
-/**
- * Search family records
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Family[]
- */
-function search_fams(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom FROM `##families` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND f_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND f_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the names of the husb/wife in a family
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Family[]
- */
-function search_fams_names(array $query, array $trees) {
- // No query => no results
- if (!$query) {
- return array();
- }
-
- $sql =
- "SELECT DISTINCT f_id AS xref, f_file AS gedcom_id, f_gedcom AS gedcom" .
- " FROM `##families`" .
- " LEFT JOIN `##name` husb ON f_husb = husb.n_id AND f_file = husb.n_file" .
- " LEFT JOIN `##name` wife ON f_wife = wife.n_id AND f_file = wife.n_file" .
- " WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $sql .= " AND (husb.n_full COLLATE :husb_collate_" . $n . " LIKE CONCAT('%', :husb_query_" . $n . ", '%') OR wife.n_full COLLATE :wife_collate_" . $n . " LIKE CONCAT('%', :wife_query_" . $n . ", '%'))";
- $args['husb_collate_' . $n] = I18N::collation();
- $args['husb_query_' . $n] = Filter::escapeLike($q);
- $args['wife_collate_' . $n] = I18N::collation();
- $args['wife_query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND f_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- $indi = Family::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- if ($indi->canShowName()) {
- $list[] = $indi;
- }
- }
-
- return $list;
-}
-
-/**
- * Search the sources
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The tree to search
- *
- * @return Source[]
- */
-function search_sources($query, $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom FROM `##sources` WHERE 1";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND s_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND s_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Source::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the shared notes
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The tree to search
- *
- * @return Note[]
- */
-function search_notes(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'NOTE'";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND o_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Note::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Search the repositories
- *
- * @param string[] $query Search terms
- * @param Tree[] $trees The trees to search
- *
- * @return Repository[]
- */
-function search_repos(array $query, array $trees) {
- // Convert the query into a regular expression
- $queryregex = array();
-
- $sql = "SELECT o_id AS xref, o_file AS gedcom_id, o_gedcom AS gedcom FROM `##other` WHERE o_type = 'REPO'";
- $args = array();
-
- foreach ($query as $n => $q) {
- $queryregex[] = preg_quote(I18N::strtoupper($q), '/');
- $sql .= " AND o_gedcom COLLATE :collate_" . $n . " LIKE CONCAT('%', :query_" . $n . ", '%')";
- $args['collate_' . $n] = I18N::collation();
- $args['query_' . $n] = Filter::escapeLike($q);
- }
-
- $sql .= " AND o_file IN (";
- foreach ($trees as $n => $tree) {
- $sql .= $n ? ", " : "";
- $sql .= ":tree_id_" . $n;
- $args['tree_id_' . $n] = $tree->getTreeId();
- }
- $sql .= ")";
-
- $list = array();
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- // SQL may have matched on private data or gedcom tags, so check again against privatized data.
- $record = Repository::getInstance($row->xref, Tree::findById($row->gedcom_id), $row->gedcom);
- // Ignore non-genealogy data
- $gedrec = preg_replace('/\n\d (_UID|_WT_USER|FILE|FORM|TYPE|CHAN|REFN|RESN) .*/', '', $record->getGedcom());
- // Ignore links and tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . '( @' . WT_REGEX_XREF . '@)?/', '', $gedrec);
- // Ignore tags
- $gedrec = preg_replace('/\n\d ' . WT_REGEX_TAG . ' ?/', '', $gedrec);
- // Re-apply the filtering
- $gedrec = I18N::strtoupper($gedrec);
- foreach ($queryregex as $regex) {
- if (!preg_match('/' . $regex . '/', $gedrec)) {
- continue 2;
- }
- }
- $list[] = $record;
- }
-
- return $list;
-}
-
-/**
- * Find the record for the given rin.
- *
- * @param string $rin
- *
- * @return string
- */
-function find_rin_id($rin) {
- global $WT_TREE;
-
- $xref =
- Database::prepare("SELECT i_id FROM `##individuals` WHERE i_rin=? AND i_file=?")
- ->execute(array($rin, $WT_TREE->getTreeId()))
- ->fetchOne();
-
- return $xref ? $xref : $rin;
-}
-
-/**
- * Get array of common surnames
- *
- * This function returns a simple array of the most common surnames
- * found in the individuals list.
- *
- * @param int $min The number of times a surname must occur before it is added to the array
- * @param Tree $tree
- *
- * @return mixed[][]
- */
-function get_common_surnames($min, Tree $tree) {
- $COMMON_NAMES_ADD = $tree->getPreference('COMMON_NAMES_ADD');
- $COMMON_NAMES_REMOVE = $tree->getPreference('COMMON_NAMES_REMOVE');
-
- $topsurns = get_top_surnames($tree->getTreeId(), $min, 0);
- foreach (explode(',', $COMMON_NAMES_ADD) as $surname) {
- if ($surname && !array_key_exists($surname, $topsurns)) {
- $topsurns[$surname] = $min;
- }
- }
- foreach (explode(',', $COMMON_NAMES_REMOVE) as $surname) {
- unset($topsurns[I18N::strtoupper($surname)]);
- }
-
- //-- check if we found some, else recurse
- if (empty($topsurns) && $min > 2) {
- return get_common_surnames($min / 2, $tree);
- } else {
- uksort($topsurns, '\Fisharebest\Webtrees\I18N::strcasecmp');
- foreach ($topsurns as $key => $value) {
- $topsurns[$key] = array('name' => $key, 'match' => $value);
- }
-
- return $topsurns;
- }
-}
-
-/**
- * get the top surnames
- *
- * @param int $ged_id fetch surnames from this gedcom
- * @param int $min only fetch surnames occuring this many times
- * @param int $max only fetch this number of surnames (0=all)
- *
- * @return string[]
- */
-function get_top_surnames($ged_id, $min, $max) {
- // Use n_surn, rather than n_surname, as it is used to generate URLs for
- // the indi-list, etc.
- $max = (int) $max;
- if ($max == 0) {
- return
- Database::prepare(
- "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
- " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
- " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
- " ORDER BY 2 DESC"
- )->execute(array(
- 'tree_id' => $ged_id,
- 'min' => $min,
- ))->fetchAssoc();
- } else {
- return
- Database::prepare(
- "SELECT SQL_CACHE n_surn, COUNT(n_surn) FROM `##name`" .
- " WHERE n_file = :tree_id AND n_type != '_MARNM' AND n_surn NOT IN ('@N.N.', '', '?', 'UNKNOWN')" .
- " GROUP BY n_surn HAVING COUNT(n_surn) >= :min" .
- " ORDER BY 2 DESC" .
- " LIMIT :limit"
- )->execute(array(
- 'tree_id' => $ged_id,
- 'min' => $min,
- 'limit' => $max,
- ))->fetchAssoc();
- }
-}
-
-/**
- * Get a list of events whose anniversary occured on a given julian day.
- * Used on the on-this-day/upcoming blocks and the day/month calendar views.
- *
- * @param int $jd the julian day
- * @param string $facts restrict the search to just these facts or leave blank for all
- * @param Tree $tree the tree to search
- *
- * @return Fact[]
- */
-function get_anniversary_events($jd, $facts, Tree $tree) {
- $found_facts = array();
- foreach (array(
- new GregorianDate($jd),
- new JulianDate($jd),
- new FrenchDate($jd),
- new JewishDate($jd),
- new HijriDate($jd),
- new JalaliDate($jd),
- ) as $anniv) {
- // Build a SQL where clause to match anniversaries in the appropriate calendar.
- $ind_sql =
- "SELECT DISTINCT i_id AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
- " FROM `##dates` JOIN `##individuals` ON d_gid = i_id AND d_file = i_file" .
- " WHERE d_type = :type AND d_file = :tree_id";
- $fam_sql =
- "SELECT DISTINCT f_id AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact" .
- " FROM `##dates` JOIN `##families` ON d_gid = f_id AND d_file = f_file" .
- " WHERE d_type = :type AND d_file = :tree_id";
- $args = array(
- 'type' => $anniv->format('%@'),
- 'tree_id' => $tree->getTreeId(),
- );
-
- $where = "";
- // SIMPLE CASES:
- // a) Non-hebrew anniversaries
- // b) Hebrew months TVT, SHV, IYR, SVN, TMZ, AAV, ELL
- if (!$anniv instanceof JewishDate || in_array($anniv->m, array(1, 5, 6, 9, 10, 11, 12, 13))) {
- // Dates without days go on the first day of the month
- // Dates with invalid days go on the last day of the month
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1";
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day";
- $args['day'] = $anniv->d;
- }
- $where .= " AND d_mon = :month";
- $args['month'] = $anniv->m;
- } else {
- // SPECIAL CASES:
- switch ($anniv->m) {
- case 2:
- // 29 CSH does not include 30 CSH (but would include an invalid 31 CSH if there were no 30 CSH)
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1 AND d_mon = 2";
- } elseif ($anniv->d === 30) {
- $where .= " AND d_day >= 30 AND d_mon = 2";
- } elseif ($anniv->d === 29 && $anniv->daysInMonth() === 29) {
- $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 2";
- } else {
- $where .= " AND d_day = :day AND d_mon = 2";
- $args['day'] = $anniv->d;
- }
- break;
- case 3:
- // 1 KSL includes 30 CSH (if this year didn’t have 30 CSH)
- // 29 KSL does not include 30 KSL (but would include an invalid 31 KSL if there were no 30 KSL)
- if ($anniv->d === 1) {
- $tmp = new JewishDate(array($anniv->y, 'CSH', 1));
- if ($tmp->daysInMonth() === 29) {
- $where .= " AND (d_day <= 1 AND d_mon = 3 OR d_day = 30 AND d_mon = 2)";
- } else {
- $where .= " AND d_day <= 1 AND d_mon = 3";
- }
- } elseif ($anniv->d === 30) {
- $where .= " AND d_day >= 30 AND d_mon = 3";
- } elseif ($anniv->d == 29 && $anniv->daysInMonth() === 29) {
- $where .= " AND (d_day = 29 OR d_day > 30) AND d_mon = 3";
- } else {
- $where .= " AND d_day = :day AND d_mon = 3";
- $args['day'] = $anniv->d;
- }
- break;
- case 4:
- // 1 TVT includes 30 KSL (if this year didn’t have 30 KSL)
- if ($anniv->d === 1) {
- $tmp = new JewishDate(array($anniv->y, 'KSL', 1));
- if ($tmp->daysInMonth() === 29) {
- $where .= " AND (d_day <=1 AND d_mon = 4 OR d_day = 30 AND d_mon = 3)";
- } else {
- $where .= " AND d_day <= 1 AND d_mon = 4";
- }
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day AND d_mon=4";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day AND d_mon=4";
- $args['day'] = $anniv->d;
- }
- break;
- case 7: // ADS includes ADR (non-leap)
- if ($anniv->d === 1) {
- $where .= " AND d_day <= 1";
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day";
- $args['day'] = $anniv->d;
- }
- $where .= " AND (d_mon = 6 AND MOD(7 * d_year + 1, 19) >= 7 OR d_mon = 7)";
- break;
- case 8: // 1 NSN includes 30 ADR, if this year is non-leap
- if ($anniv->d === 1) {
- if ($anniv->isLeapYear()) {
- $where .= " AND d_day <= 1 AND d_mon = 8";
- } else {
- $where .= " AND (d_day <= 1 AND d_mon = 8 OR d_day = 30 AND d_mon = 6)";
- }
- } elseif ($anniv->d === $anniv->daysInMonth()) {
- $where .= " AND d_day >= :day AND d_mon = 8";
- $args['day'] = $anniv->d;
- } else {
- $where .= " AND d_day = :day AND d_mon = 8";
- $args['day'] = $anniv->d;
- }
- break;
- }
- }
- // Only events in the past (includes dates without a year)
- $where .= " AND d_year <= :year";
- $args['year'] = $anniv->y;
-
- if ($facts) {
- // Restrict to certain types of fact
- $where .= " AND d_fact IN (";
- preg_match_all('/([_A-Z]+)/', $facts, $matches);
- foreach ($matches[1] as $n => $fact) {
- $where .= $n ? ", " : "";
- $where .= ":fact_" . $n;
- $args['fact_' . $n] = $fact;
- }
- $where .= ")";
- } else {
- // If no facts specified, get all except these
- $where .= " AND d_fact NOT IN ('CHAN', 'BAPL', 'SLGC', 'SLGS', 'ENDL', 'CENS', 'RESI', '_TODO')";
- }
-
- $order_by = " ORDER BY d_day, d_year DESC";
-
- // Now fetch these anniversaries
- foreach (array('INDI' => $ind_sql . $where . $order_by, 'FAM' => $fam_sql . $where . $order_by) as $type => $sql) {
- $rows = Database::prepare($sql)->execute($args)->fetchAll();
- foreach ($rows as $row) {
- if ($type === 'INDI') {
- $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
- } else {
- $record = Family::getInstance($row->xref, $tree, $row->gedcom);
- }
- $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
- foreach ($record->getFacts() as $fact) {
- if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
- $fact->anniv = $row->d_year === 0 ? 0 : $anniv->y - $row->d_year;
- $found_facts[] = $fact;
- }
- }
- }
- }
- }
-
- return $found_facts;
-}
-
-/**
- * Get a list of events which occured during a given date range.
- *
- * @param int $jd1 the start range of julian day
- * @param int $jd2 the end range of julian day
- * @param string $facts restrict the search to just these facts or leave blank for all
- * @param Tree $tree the tree to search
- *
- * @return Fact[]
- */
-function get_calendar_events($jd1, $jd2, $facts, Tree $tree) {
- // If no facts specified, get all except these
- $skipfacts = "CHAN,BAPL,SLGC,SLGS,ENDL,CENS,RESI,NOTE,ADDR,OBJE,SOUR,PAGE,DATA,TEXT";
- if ($facts != '_TODO') {
- $skipfacts .= ',_TODO';
- }
-
- $found_facts = array();
-
- // Events that start or end during the period
- $where = "WHERE (d_julianday1>={$jd1} AND d_julianday1<={$jd2} OR d_julianday2>={$jd1} AND d_julianday2<={$jd2})";
-
- // Restrict to certain types of fact
- if (empty($facts)) {
- $excl_facts = "'" . preg_replace('/\W+/', "','", $skipfacts) . "'";
- $where .= " AND d_fact NOT IN ({$excl_facts})";
- } else {
- $incl_facts = "'" . preg_replace('/\W+/', "','", $facts) . "'";
- $where .= " AND d_fact IN ({$incl_facts})";
- }
- // Only get events from the current gedcom
- $where .= " AND d_file=" . $tree->getTreeId();
-
- // Now fetch these events
- $ind_sql = "SELECT d_gid AS xref, i_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##individuals` {$where} AND d_gid=i_id AND d_file=i_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
- $fam_sql = "SELECT d_gid AS xref, f_gedcom AS gedcom, d_type, d_day, d_month, d_year, d_fact, d_type FROM `##dates`, `##families` {$where} AND d_gid=f_id AND d_file=f_file GROUP BY d_julianday1, d_gid ORDER BY d_julianday1";
- foreach (array('INDI' => $ind_sql, 'FAM' => $fam_sql) as $type => $sql) {
- $rows = Database::prepare($sql)->fetchAll();
- foreach ($rows as $row) {
- if ($type === 'INDI') {
- $record = Individual::getInstance($row->xref, $tree, $row->gedcom);
- } else {
- $record = Family::getInstance($row->xref, $tree, $row->gedcom);
- }
- $anniv_date = new Date($row->d_type . ' ' . $row->d_day . ' ' . $row->d_month . ' ' . $row->d_year);
- foreach ($record->getFacts() as $fact) {
- if (($fact->getDate()->minimumDate() == $anniv_date->minimumDate() || $fact->getDate()->maximumDate() == $anniv_date->minimumDate()) && $fact->getTag() === $row->d_fact) {
- $fact->anniv = 0;
- $found_facts[] = $fact;
- }
- }
- }
- }
-
- return $found_facts;
-}
-
-/**
- * Get the list of current and upcoming events, sorted by anniversary date
- *
- * @param int $jd1
- * @param int $jd2
- * @param string $events
- * @param Tree $tree
- *
- * @return Fact[]
- */
-function get_events_list($jd1, $jd2, $events, Tree $tree) {
- $found_facts = array();
- for ($jd = $jd1; $jd <= $jd2; ++$jd) {
- $found_facts = array_merge($found_facts, get_anniversary_events($jd, $events, $tree));
- }
-
- return $found_facts;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Check if a media file is shared (i.e. used by another gedcom)
- *
- * @param string $file_name
- * @param int $ged_id
- *
- * @return bool
- */
-function is_media_used_in_other_gedcom($file_name, $ged_id) {
- return
- (bool) Database::prepare("SELECT COUNT(*) FROM `##media` WHERE m_filename LIKE ? AND m_file<>?")
- ->execute(array("%{$file_name}", $ged_id))
- ->fetchOne();
-}
-
-/**
- * @param int $user_id
- *
- * @return string[][]
- */
-function get_user_blocks($user_id) {
- global $WT_TREE;
-
- $blocks = array('main' => array(), 'side' => array());
- $rows = Database::prepare(
- "SELECT SQL_CACHE location, block_id, module_name" .
- " FROM `##block`" .
- " JOIN `##module` USING (module_name)" .
- " JOIN `##module_privacy` USING (module_name)" .
- " WHERE user_id=?" .
- " AND status='enabled'" .
- " AND `##module_privacy`.gedcom_id=?" .
- " AND access_level>=?" .
- " ORDER BY location, block_order"
- )->execute(array($user_id, $WT_TREE->getTreeId(), Auth::accessLevel($WT_TREE)))->fetchAll();
- foreach ($rows as $row) {
- $blocks[$row->location][$row->block_id] = $row->module_name;
- }
-
- return $blocks;
-}
-
-/**
- * Get the blocks for the specified tree
- *
- * @param int $gedcom_id
- *
- * @return string[][]
- */
-function get_gedcom_blocks($gedcom_id) {
- if ($gedcom_id < 0) {
- $access_level = Auth::PRIV_NONE;
- } else {
- $access_level = Auth::accessLevel(Tree::findById($gedcom_id));
- }
-
- $blocks = array('main' => array(), 'side' => array());
- $rows = Database::prepare(
- "SELECT SQL_CACHE location, block_id, module_name" .
- " FROM `##block`" .
- " JOIN `##module` USING (module_name)" .
- " JOIN `##module_privacy` USING (module_name, gedcom_id)" .
- " WHERE gedcom_id = :tree_id" .
- " AND status='enabled'" .
- " AND access_level >= :access_level" .
- " ORDER BY location, block_order"
- )->execute(array(
- 'tree_id' => $gedcom_id,
- 'access_level' => $access_level,
- ))->fetchAll();
- foreach ($rows as $row) {
- $blocks[$row->location][$row->block_id] = $row->module_name;
- }
-
- return $blocks;
-}
-
-/**
- * Update favorites after merging records.
- *
- * @param string $xref_from
- * @param string $xref_to
- * @param Tree $tree
- *
- * @return int
- */
-function update_favorites($xref_from, $xref_to, Tree $tree) {
- return
- Database::prepare("UPDATE `##favorite` SET xref=? WHERE xref=? AND gedcom_id=?")
- ->execute(array($xref_to, $xref_from, $tree->getTreeId()))
- ->rowCount();
-}
diff --git a/includes/functions/functions_edit.php b/includes/functions/functions_edit.php
deleted file mode 100644
index cef7c08aa0..0000000000
--- a/includes/functions/functions_edit.php
+++ /dev/null
@@ -1,1706 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\ConfigData;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeName;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodePedi;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * Create a <select> control for a form.
- *
- * @param string $name
- * @param string[] $values
- * @param string|null $empty
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function select_edit_control($name, $values, $empty, $selected, $extra = '') {
- if (is_null($empty)) {
- $html = '';
- } else {
- if (empty($selected)) {
- $html = '<option value="" selected>' . Filter::escapeHtml($empty) . '</option>';
- } else {
- $html = '<option value="">' . Filter::escapeHtml($empty) . '</option>';
- }
- }
- // A completely empty list would be invalid, and break various things
- if (empty($values) && empty($html)) {
- $html = '<option value=""></option>';
- }
- foreach ($values as $key => $value) {
- // PHP array keys are cast to integers! Cast them back
- if ((string) $key === (string) $selected) {
- $html .= '<option value="' . Filter::escapeHtml($key) . '" selected dir="auto">' . Filter::escapeHtml($value) . '</option>';
- } else {
- $html .= '<option value="' . Filter::escapeHtml($key) . '" dir="auto">' . Filter::escapeHtml($value) . '</option>';
- }
- }
- if (substr($name, -2) === '[]') {
- // id attribute is not used for arrays
- return '<select name="' . $name . '" ' . $extra . '>' . $html . '</select>';
- } else {
- return '<select id="' . $name . '" name="' . $name . '" ' . $extra . '>' . $html . '</select>';
- }
-}
-
-/**
- * Create a set of radio buttons for a form
- *
- * @param string $name The ID for the form element
- * @param string[] $values Array of value=>display items
- * @param string $selected The currently selected item
- * @param string $extra Additional markup for the label
- *
- * @return string
- */
-function radio_buttons($name, $values, $selected, $extra = '') {
- $html = '';
- foreach ($values as $key => $value) {
- $html .=
- '<label ' . $extra . '>' .
- '<input type="radio" name="' . $name . '" value="' . Filter::escapeHtml($key) . '"';
- // PHP array keys are cast to integers! Cast them back
- if ((string) $key === (string) $selected) {
- $html .= ' checked';
- }
- $html .= '>' . Filter::escapeHtml($value) . '</label>';
- }
-
- return $html;
-}
-
-/**
- * Print an edit control for a Yes/No field
- *
- * @param string $name
- * @param bool $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_yes_no($name, $selected = false, $extra = '') {
- return radio_buttons(
- $name, array(I18N::translate('no'), I18N::translate('yes')), $selected, $extra
- );
-}
-
-/**
- * Print an edit control for a checkbox.
- *
- * @param string $name
- * @param bool $is_checked
- * @param string $extra
- *
- * @return string
- */
-function checkbox($name, $is_checked = false, $extra = '') {
- return '<input type="checkbox" name="' . $name . '" value="1" ' . ($is_checked ? 'checked ' : '') . $extra . '>';
-}
-
-/**
- * Print an edit control for a checkbox, with a hidden field to store one of the two states.
- * By default, a checkbox is either set, or not sent.
- * This function gives us a three options, set, unset or not sent.
- * Useful for dynamically generated forms where we don't know what elements are present.
- *
- * @param string $name
- * @param int $is_checked 0 or 1
- * @param string $extra
- *
- * @return string
- */
-function two_state_checkbox($name, $is_checked = 0, $extra = '') {
- return
- '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . ($is_checked ? 1 : 0) . '">' .
- '<input type="checkbox" name="' . $name . '-GUI-ONLY" value="1"' .
- ($is_checked ? ' checked' : '') .
- ' onclick="document.getElementById(\'' . $name . '\').value=(this.checked?1:0);" ' . $extra . '>';
-}
-
-/**
- * Function edit_language_checkboxes
- *
- * @param string $parameter_name
- * @param array $accepted_languages
- *
- * @return string
- */
-function edit_language_checkboxes($parameter_name, $accepted_languages) {
- $html = '';
- foreach (I18N::activeLocales() as $locale) {
- $html .= '<div class="checkbox">';
- $html .= '<label title="' . $locale->languageTag() . '">';
- $html .= '<input type="checkbox" name="' . $parameter_name . '[]" value="' . $locale->languageTag() . '"';
- $html .= in_array($locale->languageTag(), $accepted_languages) ? ' checked>' : '>';
- $html .= $locale->endonym();
- $html .= '</label>';
- $html .= '</div>';
- }
-
- return $html;
-}
-
-/**
- * Print an edit control for access level.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_access_level($name, $selected = '', $extra = '') {
- $ACCESS_LEVEL = array(
- Auth::PRIV_PRIVATE => I18N::translate('Show to visitors'),
- Auth::PRIV_USER => I18N::translate('Show to members'),
- Auth::PRIV_NONE => I18N::translate('Show to managers'),
- Auth::PRIV_HIDE => I18N::translate('Hide from everyone'),
- );
-
- return select_edit_control($name, $ACCESS_LEVEL, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a RESN field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_resn($name, $selected = '', $extra = '') {
- $RESN = array(
- '' => '',
- 'none' => I18N::translate('Show to visitors'), // Not valid GEDCOM, but very useful
- 'privacy' => I18N::translate('Show to members'),
- 'confidential' => I18N::translate('Show to managers'),
- 'locked' => I18N::translate('Only managers can edit'),
- );
-
- return select_edit_control($name, $RESN, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a contact method field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_contact($name, $selected = '', $extra = '') {
- // Different ways to contact the users
- $CONTACT_METHODS = array(
- 'messaging' => I18N::translate('Internal messaging'),
- 'messaging2' => I18N::translate('Internal messaging with emails'),
- 'messaging3' => I18N::translate('webtrees sends emails with no storage'),
- 'mailto' => I18N::translate('Mailto link'),
- 'none' => I18N::translate('No contact'),
- );
-
- return select_edit_control($name, $CONTACT_METHODS, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a language field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_language($name, $selected = '', $extra = '') {
- $languages = array();
- foreach (I18N::activeLocales() as $locale) {
- $languages[$locale->languageTag()] = $locale->endonym();
- }
-
- return select_edit_control($name, $languages, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a range of integers.
- *
- * @param string $name
- * @param string $selected
- * @param int $min
- * @param int $max
- * @param string $extra
- *
- * @return string
- */
-function edit_field_integers($name, $selected = '', $min, $max, $extra = '') {
- $array = array();
- for ($i = $min; $i <= $max; ++$i) {
- $array[$i] = I18N::number($i);
- }
-
- return select_edit_control($name, $array, null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a username.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_username($name, $selected = '', $extra = '') {
- $all_users = Database::prepare(
- "SELECT user_name, CONCAT_WS(' ', real_name, '-', user_name) FROM `##user` ORDER BY real_name"
- )->fetchAssoc();
- // The currently selected user may not exist
- if ($selected && !array_key_exists($selected, $all_users)) {
- $all_users[$selected] = $selected;
- }
-
- return select_edit_control($name, $all_users, '-', $selected, $extra);
-}
-
-/**
- * Print an edit control for a ADOP field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_adop($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodeAdop::getValues($individual), null, $selected, $extra);
-}
-
-/**
- * Print an edit control for a PEDI field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_pedi($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodePedi::getValues($individual), '', $selected, $extra);
-}
-
-/**
- * Print an edit control for a NAME TYPE field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- * @param Individual $individual
- *
- * @return string
- */
-function edit_field_name_type($name, $selected = '', $extra = '', Individual $individual = null) {
- return select_edit_control($name, GedcomCodeName::getValues($individual), '', $selected, $extra);
-}
-
-/**
- * Print an edit control for a RELA field.
- *
- * @param string $name
- * @param string $selected
- * @param string $extra
- *
- * @return string
- */
-function edit_field_rela($name, $selected = '', $extra = '') {
- $rela_codes = GedcomCodeRela::getValues();
- // The user is allowed to specify values that aren't in the list.
- if (!array_key_exists($selected, $rela_codes)) {
- $rela_codes[$selected] = $selected;
- }
-
- return select_edit_control($name, $rela_codes, '', $selected, $extra);
-}
-
-/**
- * Remove all links from $gedrec to $xref, and any sub-tags.
- *
- * @param string $gedrec
- * @param string $xref
- *
- * @return string
- */
-function remove_links($gedrec, $xref) {
- $gedrec = preg_replace('/\n1 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[2-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n2 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[3-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n3 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[4-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n4 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[5-9].*)*/', '', $gedrec);
- $gedrec = preg_replace('/\n5 ' . WT_REGEX_TAG . ' @' . $xref . '@(\n[6-9].*)*/', '', $gedrec);
-
- return $gedrec;
-}
-
-/**
- * Generates javascript code for calendar popup in user’s language.
- *
- * @param string $id
- *
- * @return string
- */
-function print_calendar_popup($id) {
- return
- ' <a href="#" onclick="cal_toggleDate(\'caldiv' . $id . '\', \'' . $id . '\'); return false;" class="icon-button_calendar" title="' . I18N::translate('Select a date') . '"></a>' .
- '<div id="caldiv' . $id . '" style="position:absolute;visibility:hidden;background-color:white;z-index:1000;"></div>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewmedia_link($element_id) {
- return '<a href="#" onclick="pastefield=document.getElementById(\'' . $element_id . '\'); window.open(\'addmedia.php?action=showmediaform\', \'_blank\', edit_window_specs); return false;" class="icon-button_addmedia" title="' . I18N::translate('Create a new media object') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewrepository_link($element_id) {
- return '<a href="#" onclick="addnewrepository(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addrepository" title="' . I18N::translate('Create a new repository') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewnote_link($element_id) {
- return '<a href="#" onclick="addnewnote(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addnote" title="' . I18N::translate('Create a new shared note') . '"></a>';
-}
-
-/**
- * @param string $note_id
- *
- * @return string
- */
-function print_editnote_link($note_id) {
- return '<a href="#" onclick="edit_note(\'' . $note_id . '\'); return false;" class="icon-button_note" title="' . I18N::translate('Edit shared note') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_addnewsource_link($element_id) {
- return '<a href="#" onclick="addnewsource(document.getElementById(\'' . $element_id . '\')); return false;" class="icon-button_addsource" title="' . I18N::translate('Create a new source') . '"></a>';
-}
-
-/**
- * add a new tag input field
- *
- * called for each fact to be edited on a form.
- * Fact level=0 means a new empty form : data are POSTed by name
- * else data are POSTed using arrays :
- * glevels[] : tag level
- * islink[] : tag is a link
- * tag[] : tag name
- * text[] : tag value
- *
- * @param string $tag fact record to edit (eg 2 DATE xxxxx)
- * @param string $upperlevel optional upper level tag (eg BIRT)
- * @param string $label An optional label to echo instead of the default
- * @param string $extra optional text to display after the input field
- * @param Individual $person For male/female translations
- *
- * @return string
- */
-function add_simple_tag($tag, $upperlevel = '', $label = '', $extra = null, Individual $person = null) {
- global $tags, $main_fact, $xref, $bdm, $action, $WT_TREE;
-
- // Keep track of SOUR fields, so we can reference them in subsequent PAGE fields.
- static $source_element_id;
-
- $subnamefacts = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX', '_MARNM_SURN');
- preg_match('/^(?:(\d+) (' . WT_REGEX_TAG . ') ?(.*))/', $tag, $match);
- list(, $level, $fact, $value) = $match;
- $level = (int) $level;
-
- // element name : used to POST data
- if ($level === 0) {
- if ($upperlevel) {
- $element_name = $upperlevel . '_' . $fact;
- } else {
- $element_name = $fact;
- }
- } else {
- $element_name = 'text[]';
- }
- if ($level === 1) {
- $main_fact = $fact;
- }
-
- // element id : used by javascript functions
- if ($level === 0) {
- $element_id = $fact;
- } else {
- $element_id = $fact . Uuid::uuid4();
- }
- if ($upperlevel) {
- $element_id = $upperlevel . '_' . $fact . Uuid::uuid4();
- }
-
- // field value
- $islink = (substr($value, 0, 1) === '@' && substr($value, 0, 2) !== '@#');
- if ($islink) {
- $value = trim(substr($tag, strlen($fact) + 3), ' @\r');
- } else {
- $value = (string) substr($tag, strlen($fact) + 3);
- }
- if ($fact === 'REPO' || $fact === 'SOUR' || $fact === 'OBJE' || $fact === 'FAMC') {
- $islink = true;
- }
-
- if ($fact === 'SHARED_NOTE_EDIT' || $fact === 'SHARED_NOTE') {
- $islink = true;
- $fact = 'NOTE';
- }
-
- // label
- echo '<tr id="', $element_id, '_tr"';
- if ($fact === 'MAP' || ($fact === 'LATI' || $fact === 'LONG') && $value === '') {
- echo ' style="display:none;"';
- }
- echo '>';
-
- if (in_array($fact, $subnamefacts) || $fact === 'LATI' || $fact === 'LONG') {
- echo '<td class="optionbox wrap width25">';
- } else {
- echo '<td class="descriptionbox wrap width25">';
- }
-
- // tag name
- if ($label) {
- echo $label;
- } elseif ($upperlevel) {
- echo GedcomTag::getLabel($upperlevel . ':' . $fact);
- } else {
- echo GedcomTag::getLabel($fact);
- }
-
- // If using GEDFact-assistant window
- if ($action === 'addnewnote_assisted') {
- // Do not print on GEDFact Assistant window
- } else {
- // Not all facts have help text.
- switch ($fact) {
- case 'NAME':
- if ($upperlevel !== 'REPO') {
- echo help_link($fact);
- }
- break;
- case 'DATE':
- case 'PLAC':
- case 'RESN':
- case 'ROMN':
- case 'SURN':
- case '_HEB':
- echo help_link($fact);
- break;
- }
- }
- // tag level
- if ($level > 0) {
- if ($fact === 'TEXT' && $level > 1) {
- echo '<input type="hidden" name="glevels[]" value="', $level - 1, '">';
- echo '<input type="hidden" name="islink[]" value="0">';
- echo '<input type="hidden" name="tag[]" value="DATA">';
- // leave data text[] value empty because the following TEXT line will cause the DATA to be added
- echo '<input type="hidden" name="text[]" value="">';
- }
- echo '<input type="hidden" name="glevels[]" value="', $level, '">';
- echo '<input type="hidden" name="islink[]" value="', $islink, '">';
- echo '<input type="hidden" name="tag[]" value="', $fact, '">';
- }
- echo '</td>';
-
- // value
- echo '<td class="optionbox wrap">';
-
- // retrieve linked NOTE
- if ($fact === 'NOTE' && $islink) {
- $note1 = Note::getInstance($value, $WT_TREE);
- if ($note1) {
- $noterec = $note1->getGedcom();
- preg_match('/' . $value . '/i', $noterec, $notematch);
- $value = $notematch[0];
- }
- }
-
- if (in_array($fact, ConfigData::emptyFacts()) && ($value === '' || $value === 'Y' || $value === 'y')) {
- echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" value="', $value, '">';
- if ($level <= 1) {
- echo '<input type="checkbox" ';
- if ($value) {
- echo 'checked';
- }
- echo ' onclick="if (this.checked) ', $element_id, '.value=\'Y\'; else ', $element_id, '.value=\'\';">';
- echo I18N::translate('yes');
- }
-
- } elseif ($fact === 'TEMP') {
- echo select_edit_control($element_name, GedcomCodeTemp::templeNames(), I18N::translate('No temple - living ordinance'), $value);
- } elseif ($fact === 'ADOP') {
- echo edit_field_adop($element_name, $value, '', $person);
- } elseif ($fact === 'PEDI') {
- echo edit_field_pedi($element_name, $value, '', $person);
- } elseif ($fact === 'STAT') {
- echo select_edit_control($element_name, GedcomCodeStat::statusNames($upperlevel), '', $value);
- } elseif ($fact === 'RELA') {
- echo edit_field_rela($element_name, strtolower($value));
- } elseif ($fact === 'QUAY') {
- echo select_edit_control($element_name, GedcomCodeQuay::getValues(), '', $value);
- } elseif ($fact === '_WT_USER') {
- echo edit_field_username($element_name, $value);
- } elseif ($fact === 'RESN') {
- echo edit_field_resn($element_name, $value);
- } elseif ($fact === '_PRIM') {
- echo '<select id="', $element_id, '" name="', $element_name, '" >';
- echo '<option value=""></option>';
- echo '<option value="Y" ';
- if ($value === 'Y') {
- echo ' selected';
- }
- echo '>', /* I18N: option in list box “always use this image” */ I18N::translate('always'), '</option>';
- echo '<option value="N" ';
- if ($value === 'N') {
- echo 'selected';
- }
- echo '>', /* I18N: option in list box “never use this image” */ I18N::translate('never'), '</option>';
- echo '</select>';
- echo '<p class="small text-muted">', I18N::translate('Use this image for charts and on the individual’s page.'), '</p>';
- } elseif ($fact === 'SEX') {
- echo '<select id="', $element_id, '" name="', $element_name, '"><option value="M" ';
- if ($value === 'M') {
- echo 'selected';
- }
- echo '>', I18N::translate('Male'), '</option><option value="F" ';
- if ($value === 'F') {
- echo 'selected';
- }
- echo '>', I18N::translate('Female'), '</option><option value="U" ';
- if ($value === 'U' || empty($value)) {
- echo 'selected';
- }
- echo '>', I18N::translateContext('unknown gender', 'Unknown'), '</option></select>';
- } elseif ($fact === 'TYPE' && $level === 3) {
- //-- Build the selector for the Media 'TYPE' Fact
- echo '<select name="text[]"><option selected value="" ></option>';
- $selectedValue = strtolower($value);
- if (!array_key_exists($selectedValue, GedcomTag::getFileFormTypes())) {
- echo '<option selected value="', Filter::escapeHtml($value), '" >', Filter::escapeHtml($value), '</option>';
- }
- foreach (GedcomTag::getFileFormTypes() as $typeName => $typeValue) {
- echo '<option value="', $typeName, '" ';
- if ($selectedValue === $typeName) {
- echo 'selected';
- }
- echo '>', $typeValue, '</option>';
- }
- echo '</select>';
- } elseif (($fact === 'NAME' && $upperlevel !== 'REPO') || $fact === '_MARNM') {
- // Populated in javascript from sub-tags
- echo '<input type="hidden" id="', $element_id, '" name="', $element_name, '" onchange="updateTextName(\'', $element_id, '\');" value="', Filter::escapeHtml($value), '" class="', $fact, '">';
- echo '<span id="', $element_id, '_display" dir="auto">', Filter::escapeHtml($value), '</span>';
- echo ' <a href="#edit_name" onclick="convertHidden(\'', $element_id, '\'); return false;" class="icon-edit_indi" title="' . I18N::translate('Edit name') . '"></a>';
- } else {
- // textarea
- if ($fact === 'TEXT' || $fact === 'ADDR' || ($fact === 'NOTE' && !$islink)) {
- echo '<textarea id="', $element_id, '" name="', $element_name, '" dir="auto">', Filter::escapeHtml($value), '</textarea><br>';
- } else {
- // text
- // If using GEDFact-assistant window
- if ($action === 'addnewnote_assisted') {
- echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" style="width:4.1em;" dir="ltr"';
- } else {
- echo '<input type="text" id="', $element_id, '" name="', $element_name, '" value="', Filter::escapeHtml($value), '" dir="ltr"';
- }
- echo ' class="', $fact, '"';
- if (in_array($fact, $subnamefacts)) {
- echo ' onblur="updatewholename();" onkeyup="updatewholename();"';
- }
-
- // Extra markup for specific fact types
- switch ($fact) {
- case 'ALIA':
- case 'ASSO':
- echo ' data-autocomplete-type="INDI"';
- break;
- case '_ASSO':
- echo ' data-autocomplete-type="ASSO"';
- break;
- case 'DATE':
- echo ' onblur="valid_date(this);" onmouseout="valid_date(this);"';
- break;
- case 'GIVN':
- echo ' autofocus data-autocomplete-type="GIVN"';
- break;
- case 'LATI':
- echo ' onblur="valid_lati_long(this, \'N\', \'S\');" onmouseout="valid_lati_long(this, \'N\', \'S\');"';
- break;
- case 'LONG':
- echo ' onblur="valid_lati_long(this, \'E\', \'W\');" onmouseout="valid_lati_long(this, \'E\', \'W\');"';
- break;
- case 'NOTE':
- // Shared notes. Inline notes are handled elsewhere.
- echo ' data-autocomplete-type="NOTE"';
- break;
- case 'OBJE':
- echo ' data-autocomplete-type="OBJE"';
- break;
- case 'PAGE':
- echo ' data-autocomplete-type="PAGE" data-autocomplete-extra="' . $source_element_id . '"';
- break;
- case 'PLAC':
- echo ' data-autocomplete-type="PLAC"';
- break;
- case 'REPO':
- echo ' data-autocomplete-type="REPO"';
- break;
- case 'SOUR':
- $source_element_id = $element_id;
- echo ' data-autocomplete-type="SOUR"';
- break;
- case 'SURN':
- case '_MARNM_SURN':
- echo ' data-autocomplete-type="SURN"';
- break;
- case 'TIME':
- echo ' pattern="([0-1][0-9]|2[0-3]):[0-5][0-9](:[0-5]0-9])?" dir="ltr" placeholder="' . /* I18N: Examples of valid time formats (hours:minutes:seconds) */ I18N::translate('hh:mm or hh:mm:ss') . '"';
- break;
- }
- echo '>';
- }
-
- $tmp_array = array('TYPE', 'TIME', 'NOTE', 'SOUR', 'REPO', 'OBJE', 'ASSO', '_ASSO', 'AGE');
-
- // split PLAC
- if ($fact === 'PLAC') {
- echo '<div id="', $element_id, '_pop" style="display: inline;">';
- echo print_specialchar_link($element_id), ' ', print_findplace_link($element_id);
- echo '<span onclick="jQuery(\'tr[id^=', $upperlevel, '_LATI],tr[id^=', $upperlevel, '_LONG],tr[id^=LATI],tr[id^=LONG]\').toggle(\'fast\'); return false;" class="icon-target" title="', GedcomTag::getLabel('LATI'), ' / ', GedcomTag::getLabel('LONG'), '"></span>';
- echo '</div>';
- if (Module::getModuleByName('places_assistant')) {
- \PlacesAssistantModule::setup_place_subfields($element_id);
- \PlacesAssistantModule::print_place_subfields($element_id);
- }
- } elseif (!in_array($fact, $tmp_array)) {
- echo print_specialchar_link($element_id);
- }
- }
- // MARRiage TYPE : hide text field and show a selection list
- if ($fact === 'TYPE' && $level === 2 && $tags[0] === 'MARR') {
- echo '<script>';
- echo 'document.getElementById(\'', $element_id, '\').style.display=\'none\'';
- echo '</script>';
- echo '<select id="', $element_id, '_sel" onchange="document.getElementById(\'', $element_id, '\').value=this.value;" >';
- foreach (array('Unknown', 'Civil', 'Religious', 'Partners') as $key) {
- if ($key === 'Unknown') {
- echo '<option value="" ';
- } else {
- echo '<option value="', $key, '" ';
- }
- $a = strtolower($key);
- $b = strtolower($value);
- if ($b !== '' && strpos($a, $b) !== false || strpos($b, $a) !== false) {
- echo 'selected';
- }
- echo '>', GedcomTag::getLabel('MARR_' . strtoupper($key)), '</option>';
- }
- echo '</select>';
- } elseif ($fact === 'TYPE' && $level === 0) {
- // NAME TYPE : hide text field and show a selection list
- $onchange = 'onchange="document.getElementById(\'' . $element_id . '\').value=this.value;"';
- echo edit_field_name_type($element_name, $value, $onchange, $person);
- echo '<script>document.getElementById("', $element_id, '").style.display="none";</script>';
- }
-
- // popup links
- switch ($fact) {
- case 'DATE':
- echo print_calendar_popup($element_id);
-
- // Allow the GEDFact_assistant module to show a census-date selector
- if (Module::getModuleByName('GEDFact_assistant')) {
- echo CensusAssistantModule::censusDateSelector($action, $upperlevel, $element_id);
- }
- break;
- case 'FAMC':
- case 'FAMS':
- echo print_findfamily_link($element_id);
- break;
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- echo print_findindi_link($element_id, $element_id . '_description');
- break;
- case 'FILE':
- print_findmedia_link($element_id, '0file');
- break;
- case 'SOUR':
- echo print_findsource_link($element_id, $element_id . '_description'), ' ', print_addnewsource_link($element_id);
- //-- checkboxes to apply '1 SOUR' to BIRT/MARR/DEAT as '2 SOUR'
- if ($level === 1) {
- echo '<br>';
- switch ($WT_TREE->getPreference('PREFER_LEVEL2_SOURCES')) {
- case '2': // records
- $level1_checked = 'checked';
- $level2_checked = '';
- break;
- case '1': // facts
- $level1_checked = '';
- $level2_checked = 'checked';
- break;
- case '0': // none
- default:
- $level1_checked = '';
- $level2_checked = '';
- break;
- }
- if (strpos($bdm, 'B') !== false) {
- echo '&nbsp;<input type="checkbox" name="SOUR_INDI" ', $level1_checked, ' value="1">';
- echo I18N::translate('Individual');
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- if (!in_array($match, explode('|', WT_EVENTS_DEAT))) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '" ', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- if (strpos($bdm, 'D') !== false) {
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- if (in_array($match, explode('|', WT_EVENTS_DEAT))) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- if (strpos($bdm, 'M') !== false) {
- echo '&nbsp;<input type="checkbox" name="SOUR_FAM" ', $level1_checked, ' value="1">';
- echo I18N::translate('Family');
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
- foreach ($matches[1] as $match) {
- echo '&nbsp;<input type="checkbox" name="SOUR_', $match, '"', $level2_checked, ' value="1">';
- echo GedcomTag::getLabel($match);
- }
- }
- }
- }
- break;
- case 'REPO':
- echo print_findrepository_link($element_id), ' ', print_addnewrepository_link($element_id);
- break;
- case 'NOTE':
- // Shared Notes Icons ========================================
- if ($islink) {
- // Print regular Shared Note icons ---------------------------
- echo ' ', print_findnote_link($element_id, $element_id . '_description'), ' ', print_addnewnote_link($element_id);
- if ($value) {
- echo ' ', print_editnote_link($value);
- }
-
- // Allow the GEDFact_assistant module to create a formatted shared note.
- if ($upperlevel === 'CENS' && Module::getModuleByName('GEDFact_assistant')) {
- echo CensusAssistantModule::addNoteWithAssistantLink($element_id, $xref, $action);
- }
- }
- break;
- case 'OBJE':
- echo print_findmedia_link($element_id, '1media');
- if (!$value) {
- echo ' ', print_addnewmedia_link($element_id);
- $value = 'new';
- }
- break;
- }
-
- echo '<div id="' . $element_id . '_description">';
-
- // current value
- if ($fact === 'DATE') {
- $date = new Date($value);
- echo $date->display();
- }
- if (($fact === 'ASSO' || $fact === '_ASSO') && $value === '') {
- if ($level === 1) {
- echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this individual, such as a friend or an employer.') . '</p>';
- } else {
- echo '<p class="small text-muted">' . I18N::translate('An associate is another individual who was involved with this fact or event, such as a witness or a priest.') . '</p>';
- }
- }
-
- if ($value && $value !== 'new' && $islink) {
- switch ($fact) {
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- $tmp = Individual::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'SOUR':
- $tmp = Source::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'NOTE':
- $tmp = Note::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'OBJE':
- $tmp = Media::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- case 'REPO':
- $tmp = Repository::getInstance($value, $WT_TREE);
- if ($tmp) {
- echo ' ', $tmp->getFullname();
- }
- break;
- }
- }
-
- // pastable values
- if ($fact === 'FORM' && $upperlevel === 'OBJE') {
- print_autopaste_link($element_id, ConfigData::fileFormats());
- }
- echo '</div>', $extra, '</td></tr>';
-
- return $element_id;
-}
-
-/**
- * Prints collapsable fields to add ASSO/RELA, SOUR, OBJE, etc.
- *
- * @param string $tag
- * @param int $level
- * @param string $parent_tag
- */
-function print_add_layer($tag, $level = 2, $parent_tag = '') {
- global $WT_TREE;
-
- switch ($tag) {
- case 'SOUR':
- echo '<a href="#" onclick="return expand_layer(\'newsource\');"><i id="newsource_img" class="icon-plus"></i> ', I18N::translate('Add a new source citation'), '</a>';
- echo '<br>';
- echo '<div id="newsource" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 SOUR
- add_simple_tag($level . ' SOUR @');
- // 3 PAGE
- add_simple_tag(($level + 1) . ' PAGE');
- // 3 DATA
- // 4 TEXT
- add_simple_tag(($level + 2) . ' TEXT');
- if ($WT_TREE->getPreference('FULL_SOURCES')) {
- // 4 DATE
- add_simple_tag(($level + 2) . ' DATE', '', GedcomTag::getLabel('DATA:DATE'));
- // 3 QUAY
- add_simple_tag(($level + 1) . ' QUAY');
- }
- // 3 OBJE
- add_simple_tag(($level + 1) . ' OBJE');
- // 3 SHARED_NOTE
- add_simple_tag(($level + 1) . ' SHARED_NOTE');
- echo '</table></div>';
- break;
-
- case 'ASSO':
- case 'ASSO2':
- //-- Add a new ASSOciate
- if ($tag === 'ASSO') {
- echo "<a href=\"#\" onclick=\"return expand_layer('newasso');\"><i id=\"newasso_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
- echo '<br>';
- echo '<div id="newasso" style="display: none;">';
- } else {
- echo "<a href=\"#\" onclick=\"return expand_layer('newasso2');\"><i id=\"newasso2_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new associate'), '</a>';
- echo '<br>';
- echo '<div id="newasso2" style="display: none;">';
- }
- echo '<table class="facts_table">';
- // 2 ASSO
- add_simple_tag($level . ' ASSO @');
- // 3 RELA
- add_simple_tag(($level + 1) . ' RELA');
- // 3 NOTE
- add_simple_tag(($level + 1) . ' NOTE');
- // 3 SHARED_NOTE
- add_simple_tag(($level + 1) . ' SHARED_NOTE');
- echo '</table></div>';
- break;
-
- case 'NOTE':
- //-- Retrieve existing note or add new note to fact
- echo "<a href=\"#\" onclick=\"return expand_layer('newnote');\"><i id=\"newnote_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new note'), '</a>';
- echo '<br>';
- echo '<div id="newnote" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 NOTE
- add_simple_tag($level . ' NOTE');
- echo '</table></div>';
- break;
-
- case 'SHARED_NOTE':
- echo "<a href=\"#\" onclick=\"return expand_layer('newshared_note');\"><i id=\"newshared_note_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new shared note'), '</a>';
- echo '<br>';
- echo '<div id="newshared_note" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 SHARED NOTE
- add_simple_tag($level . ' SHARED_NOTE', $parent_tag);
- echo '</table></div>';
- break;
-
- case 'OBJE':
- if ($WT_TREE->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
- echo "<a href=\"#\" onclick=\"return expand_layer('newobje');\"><i id=\"newobje_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new media object'), '</a>';
- echo '<br>';
- echo '<div id="newobje" style="display: none;">';
- echo '<table class="facts_table">';
- add_simple_tag($level . ' OBJE');
- echo '</table></div>';
- }
- break;
-
- case 'RESN':
- echo "<a href=\"#\" onclick=\"return expand_layer('newresn');\"><i id=\"newresn_img\" class=\"icon-plus\"></i> ", I18N::translate('Add a new restriction'), '</a>';
- echo '<br>';
- echo '<div id="newresn" style="display: none;">';
- echo '<table class="facts_table">';
- // 2 RESN
- add_simple_tag($level . ' RESN');
- echo '</table></div>';
- break;
- }
-}
-
-/**
- * Add some empty tags to create a new fact.
- *
- * @param string $fact
- */
-function addSimpleTags($fact) {
- global $WT_TREE;
-
- // For new individuals, these facts default to "Y"
- if ($fact === 'MARR') {
- add_simple_tag('0 ' . $fact . ' Y');
- } else {
- add_simple_tag('0 ' . $fact);
- }
-
- if (!in_array($fact, ConfigData::nonDateFacts())) {
- add_simple_tag('0 DATE', $fact, GedcomTag::getLabel($fact . ':DATE'));
- }
-
- if (!in_array($fact, ConfigData::nonPlaceFacts())) {
- add_simple_tag('0 PLAC', $fact, GedcomTag::getLabel($fact . ':PLAC'));
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- add_simple_tag('0 ' . $tag, $fact, GedcomTag::getLabel($fact . ':PLAC:' . $tag));
- }
- }
- add_simple_tag('0 MAP', $fact);
- add_simple_tag('0 LATI', $fact);
- add_simple_tag('0 LONG', $fact);
- }
-}
-
-/**
- * Assemble the pieces of a newly created record into gedcom
- *
- * @return string
- */
-function addNewName() {
- global $WT_TREE;
-
- $gedrec = "\n1 NAME " . Filter::post('NAME');
-
- $tags = array('NPFX', 'GIVN', 'SPFX', 'SURN', 'NSFX');
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $match)) {
- $tags = array_merge($tags, $match[1]);
- }
-
- // Paternal and Polish and Lithuanian surname traditions can also create a _MARNM
- $SURNAME_TRADITION = $WT_TREE->getPreference('SURNAME_TRADITION');
- if ($SURNAME_TRADITION === 'paternal' || $SURNAME_TRADITION === 'polish' || $SURNAME_TRADITION === 'lithuanian') {
- $tags[] = '_MARNM';
- }
-
- foreach (array_unique($tags) as $tag) {
- $TAG = Filter::post($tag);
- if ($TAG) {
- $gedrec .= "\n2 {$tag} {$TAG}";
- }
- }
-
- return $gedrec;
-}
-
-/**
- * @return string
- */
-function addNewSex() {
- switch (Filter::post('SEX', '[MF]', 'U')) {
- case 'M':
- return "\n1 SEX M";
- case 'F':
- return "\n1 SEX F";
- default:
- return "\n1 SEX U";
- }
-}
-
-/**
- * @param string $fact
- *
- * @return string
- */
-function addNewFact($fact) {
- global $WT_TREE;
-
- $FACT = Filter::post($fact);
- $DATE = Filter::post($fact . '_DATE');
- $PLAC = Filter::post($fact . '_PLAC');
- if ($DATE || $PLAC || $FACT && $FACT !== 'Y') {
- if ($FACT && $FACT !== 'Y') {
- $gedrec = "\n1 " . $fact . ' ' . $FACT;
- } else {
- $gedrec = "\n1 " . $fact;
- }
- if ($DATE) {
- $gedrec .= "\n2 DATE " . $DATE;
- }
- if ($PLAC) {
- $gedrec .= "\n2 PLAC " . $PLAC;
-
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- $TAG = Filter::post($fact . '_' . $tag);
- if ($TAG) {
- $gedrec .= "\n3 " . $tag . ' ' . $TAG;
- }
- }
- }
- $LATI = Filter::post($fact . '_LATI');
- $LONG = Filter::post($fact . '_LONG');
- if ($LATI || $LONG) {
- $gedrec .= "\n3 MAP\n4 LATI " . $LATI . "\n4 LONG " . $LONG;
- }
- }
- if (Filter::postBool('SOUR_' . $fact)) {
- return updateSOUR($gedrec, 2);
- } else {
- return $gedrec;
- }
- } elseif ($FACT === 'Y') {
- if (Filter::postBool('SOUR_' . $fact)) {
- return updateSOUR("\n1 " . $fact . ' Y', 2);
- } else {
- return "\n1 " . $fact . ' Y';
- }
- } else {
- return '';
- }
-}
-
-/**
- * This function splits the $glevels, $tag, $islink, and $text arrays so that the
- * entries associated with a SOUR record are separate from everything else.
- *
- * Input arrays:
- * - $glevels[] - an array of the gedcom level for each line that was edited
- * - $tag[] - an array of the tags for each gedcom line that was edited
- * - $islink[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $text[] - an array of the text data for each line
- *
- * Output arrays:
- * ** For the SOUR record:
- * - $glevelsSOUR[] - an array of the gedcom level for each line that was edited
- * - $tagSOUR[] - an array of the tags for each gedcom line that was edited
- * - $islinkSOUR[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $textSOUR[] - an array of the text data for each line
- * ** For the remaining records:
- * - $glevelsRest[] - an array of the gedcom level for each line that was edited
- * - $tagRest[] - an array of the tags for each gedcom line that was edited
- * - $islinkRest[] - an array of 1 or 0 values to indicate when the text is a link element
- * - $textRest[] - an array of the text data for each line
- */
-function splitSOUR() {
- global $glevels, $tag, $islink, $text;
- global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
- global $glevelsRest, $tagRest, $islinkRest, $textRest;
-
- $glevelsSOUR = array();
- $tagSOUR = array();
- $islinkSOUR = array();
- $textSOUR = array();
-
- $glevelsRest = array();
- $tagRest = array();
- $islinkRest = array();
- $textRest = array();
-
- $inSOUR = false;
-
- for ($i = 0; $i < count($glevels); $i++) {
- if ($inSOUR) {
- if ($levelSOUR < $glevels[$i]) {
- $dest = 'S';
- } else {
- $inSOUR = false;
- $dest = 'R';
- }
- } else {
- if ($tag[$i] === 'SOUR') {
- $inSOUR = true;
- $levelSOUR = $glevels[$i];
- $dest = 'S';
- } else {
- $dest = 'R';
- }
- }
- if ($dest === 'S') {
- $glevelsSOUR[] = $glevels[$i];
- $tagSOUR[] = $tag[$i];
- $islinkSOUR[] = $islink[$i];
- $textSOUR[] = $text[$i];
- } else {
- $glevelsRest[] = $glevels[$i];
- $tagRest[] = $tag[$i];
- $islinkRest[] = $islink[$i];
- $textRest[] = $text[$i];
- }
- }
-}
-
-/**
- * Add new GEDCOM lines from the $xxxSOUR interface update arrays, which
- * were produced by the splitSOUR() function.
- * See the handle_updates() function for details.
- *
- * @param string $inputRec
- * @param string $levelOverride
- *
- * @return string
- */
-function updateSOUR($inputRec, $levelOverride = 'no') {
- global $glevels, $tag, $islink, $text;
- global $glevelsSOUR, $tagSOUR, $islinkSOUR, $textSOUR;
-
- if (count($tagSOUR) === 0) {
- return $inputRec; // No update required
- }
-
- // Save original interface update arrays before replacing them with the xxxSOUR ones
- $glevelsSave = $glevels;
- $tagSave = $tag;
- $islinkSave = $islink;
- $textSave = $text;
-
- $glevels = $glevelsSOUR;
- $tag = $tagSOUR;
- $islink = $islinkSOUR;
- $text = $textSOUR;
-
- $myRecord = handle_updates($inputRec, $levelOverride); // Now do the update
-
- // Restore the original interface update arrays (just in case ...)
- $glevels = $glevelsSave;
- $tag = $tagSave;
- $islink = $islinkSave;
- $text = $textSave;
-
- return $myRecord;
-}
-
-/**
- * Add new GEDCOM lines from the $xxxRest interface update arrays, which
- * were produced by the splitSOUR() function.
- * See the handle_updates() function for details.
- *
- * @param string $inputRec
- * @param string $levelOverride
- *
- * @return string
- */
-function updateRest($inputRec, $levelOverride = 'no') {
- global $glevels, $tag, $islink, $text;
- global $glevelsRest, $tagRest, $islinkRest, $textRest;
-
- if (count($tagRest) === 0) {
- return $inputRec; // No update required
- }
-
- // Save original interface update arrays before replacing them with the xxxRest ones
- $glevelsSave = $glevels;
- $tagSave = $tag;
- $islinkSave = $islink;
- $textSave = $text;
-
- $glevels = $glevelsRest;
- $tag = $tagRest;
- $islink = $islinkRest;
- $text = $textRest;
-
- $myRecord = handle_updates($inputRec, $levelOverride); // Now do the update
-
- // Restore the original interface update arrays (just in case ...)
- $glevels = $glevelsSave;
- $tag = $tagSave;
- $islink = $islinkSave;
- $text = $textSave;
-
- return $myRecord;
-}
-
-/**
- * Add new gedcom lines from interface update arrays
- * The edit_interface and add_simple_tag function produce the following
- * arrays incoming from the $_POST form
- * - $glevels[] - an array of the gedcom level for each line that was edited
- * - $tag[] - an array of the tags for each gedcom line that was edited
- * - $islink[] - an array of 1 or 0 values to tell whether the text is a link element and should be surrounded by @@
- * - $text[] - an array of the text data for each line
- * With these arrays you can recreate the gedcom lines like this
- * <code>$glevel[0].' '.$tag[0].' '.$text[0]</code>
- * There will be an index in each of these arrays for each line of the gedcom
- * fact that is being edited.
- * If the $text[] array is empty for the given line, then it means that the
- * user removed that line during editing or that the line is supposed to be
- * empty (1 DEAT, 1 BIRT) for example. To know if the line should be removed
- * there is a section of code that looks ahead to the next lines to see if there
- * are sub lines. For example we don't want to remove the 1 DEAT line if it has
- * a 2 PLAC or 2 DATE line following it. If there are no sub lines, then the line
- * can be safely removed.
- *
- * @param string $newged the new gedcom record to add the lines to
- * @param string $levelOverride Override GEDCOM level specified in $glevels[0]
- *
- * @return string The updated gedcom record
- */
-function handle_updates($newged, $levelOverride = 'no') {
- global $glevels, $islink, $tag, $uploaded_files, $text;
-
- if ($levelOverride === 'no' || count($glevels) === 0) {
- $levelAdjust = 0;
- } else {
- $levelAdjust = $levelOverride - $glevels[0];
- }
-
- for ($j = 0; $j < count($glevels); $j++) {
-
- // Look for empty SOUR reference with non-empty sub-records.
- // This can happen when the SOUR entry is deleted but its sub-records
- // were incorrectly left intact.
- // The sub-records should be deleted.
- if ($tag[$j] === 'SOUR' && ($text[$j] === '@@' || $text[$j] === '')) {
- $text[$j] = '';
- $k = $j + 1;
- while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
- $text[$k] = '';
- $k++;
- }
- }
-
- if (trim($text[$j]) !== '') {
- $pass = true;
- } else {
- //-- for facts with empty values they must have sub records
- //-- this section checks if they have subrecords
- $k = $j + 1;
- $pass = false;
- while (($k < count($glevels)) && ($glevels[$k] > $glevels[$j])) {
- if ($text[$k] !== '') {
- if (($tag[$j] !== 'OBJE') || ($tag[$k] === 'FILE')) {
- $pass = true;
- break;
- }
- }
- if (($tag[$k] === 'FILE') && (count($uploaded_files) > 0)) {
- $filename = array_shift($uploaded_files);
- if (!empty($filename)) {
- $text[$k] = $filename;
- $pass = true;
- break;
- }
- }
- $k++;
- }
- }
-
- //-- if the value is not empty or it has sub lines
- //--- then write the line to the gedcom record
- //if ((($text[trim($j)]!='')||($pass==true)) && (strlen($text[$j]) > 0)) {
- //-- we have to let some emtpy text lines pass through... (DEAT, BIRT, etc)
- if ($pass) {
- $newline = $glevels[$j] + $levelAdjust . ' ' . $tag[$j];
- //-- check and translate the incoming dates
- if ($tag[$j] === 'DATE' && $text[$j] !== '') {
- }
- if ($text[$j] !== '') {
- if ($islink[$j]) {
- $newline .= ' @' . $text[$j] . '@';
- } else {
- $newline .= ' ' . $text[$j];
- }
- }
- $newged .= "\n" . str_replace("\n", "\n" . (1 + substr($newline, 0, 1)) . ' CONT ', $newline);
- }
- }
-
- return $newged;
-}
-
-/**
- * builds the form for adding new facts
- *
- * @param string $fact the new fact we are adding
- */
-function create_add_form($fact) {
- global $tags, $WT_TREE;
-
- $tags = array();
-
- // handle MARRiage TYPE
- if (substr($fact, 0, 5) === 'MARR_') {
- $tags[0] = 'MARR';
- add_simple_tag('1 MARR');
- insert_missing_subtags($fact);
- } else {
- $tags[0] = $fact;
- if ($fact === '_UID') {
- $fact .= ' ' . GedcomTag::createUid();
- }
- // These new level 1 tags need to be turned into links
- if (in_array($fact, array('ALIA', 'ASSO'))) {
- $fact .= ' @';
- }
- if (in_array($fact, ConfigData::emptyFacts())) {
- add_simple_tag('1 ' . $fact . ' Y');
- } else {
- add_simple_tag('1 ' . $fact);
- }
- insert_missing_subtags($tags[0]);
- //-- handle the special SOURce case for level 1 sources [ 1759246 ]
- if ($fact === 'SOUR') {
- add_simple_tag('2 PAGE');
- add_simple_tag('3 TEXT');
- if ($WT_TREE->getPreference('FULL_SOURCES')) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('DATA:DATE'));
- add_simple_tag('2 QUAY');
- }
- }
- }
-}
-
-/**
- * Create a form to edit a Fact object.
- *
- * @param GedcomRecord $record
- * @param Fact $fact
- *
- * @return string
- */
-function create_edit_form(GedcomRecord $record, Fact $fact) {
- global $tags, $WT_TREE;
-
- $pid = $record->getXref();
-
- $tags = array();
- $gedlines = explode("\n", $fact->getGedcom());
-
- $linenum = 0;
- $fields = explode(' ', $gedlines[$linenum]);
- $glevel = $fields[0];
- $level = $glevel;
-
- $type = $fact->getTag();
- $parent = $fact->getParent();
- $level0type = $parent::RECORD_TYPE;
- $level1type = $type;
-
- $i = $linenum;
- $inSource = false;
- $levelSource = 0;
- $add_date = true;
- // List of tags we would expect at the next level
- // NB add_missing_subtags() already takes care of the simple cases
- // where a level 1 tag is missing a level 2 tag. Here we only need to
- // handle the more complicated cases.
- $expected_subtags = array(
- 'SOUR' => array('PAGE', 'DATA'),
- 'DATA' => array('TEXT'),
- 'PLAC' => array('MAP'),
- 'MAP' => array('LATI', 'LONG'),
- );
- if ($record->getTree()->getPreference('FULL_SOURCES')) {
- $expected_subtags['SOUR'][] = 'QUAY';
- $expected_subtags['DATA'][] = 'DATE';
- }
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $record->getTree()->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- $expected_subtags['PLAC'] = array_merge($match[1], $expected_subtags['PLAC']);
- }
-
- $stack = array(0 => $level0type);
- // Loop on existing tags :
- while (true) {
- // Keep track of our hierarchy, e.g. 1=>BIRT, 2=>PLAC, 3=>FONE
- $stack[(int) $level] = $type;
- // Merge them together, e.g. BIRT:PLAC:FONE
- $label = implode(':', array_slice($stack, 1, $level));
-
- $text = '';
- for ($j = 2; $j < count($fields); $j++) {
- if ($j > 2) {
- $text .= ' ';
- }
- $text .= $fields[$j];
- }
- $text = rtrim($text);
- while (($i + 1 < count($gedlines)) && (preg_match("/" . ($level + 1) . ' CONT ?(.*)/', $gedlines[$i + 1], $cmatch) > 0)) {
- $text .= "\n" . $cmatch[1];
- $i++;
- }
-
- if ($type === 'SOUR') {
- $inSource = true;
- $levelSource = $level;
- } elseif ($levelSource >= $level) {
- $inSource = false;
- }
-
- if ($type !== 'DATA' && $type !== 'CONT') {
- $tags[] = $type;
- $person = Individual::getInstance($pid, $WT_TREE);
- $subrecord = $level . ' ' . $type . ' ' . $text;
- if ($inSource && $type === 'DATE') {
- add_simple_tag($subrecord, '', GedcomTag::getLabel($label, $person));
- } elseif (!$inSource && $type === 'DATE') {
- add_simple_tag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
- $add_date = false;
- } elseif ($type === 'STAT') {
- add_simple_tag($subrecord, $level1type, GedcomTag::getLabel($label, $person));
- } elseif ($level0type === 'REPO') {
- $repo = Repository::getInstance($pid, $WT_TREE);
- add_simple_tag($subrecord, $level0type, GedcomTag::getLabel($label, $repo));
- } else {
- add_simple_tag($subrecord, $level0type, GedcomTag::getLabel($label, $person));
- }
- }
-
- // Get a list of tags present at the next level
- $subtags = array();
- for ($ii = $i + 1; isset($gedlines[$ii]) && preg_match('/^\s*(\d+)\s+(\S+)/', $gedlines[$ii], $mm) && $mm[1] > $level; ++$ii) {
- if ($mm[1] == $level + 1) {
- $subtags[] = $mm[2];
- }
- }
-
- // Insert missing tags
- if (!empty($expected_subtags[$type])) {
- foreach ($expected_subtags[$type] as $subtag) {
- if (!in_array($subtag, $subtags)) {
- if (!$inSource || $subtag !== 'DATA') {
- add_simple_tag(($level + 1) . ' ' . $subtag, '', GedcomTag::getLabel($label . ':' . $subtag));
- }
- if (!empty($expected_subtags[$subtag])) {
- foreach ($expected_subtags[$subtag] as $subsubtag) {
- add_simple_tag(($level + 2) . ' ' . $subsubtag, '', GedcomTag::getLabel($label . ':' . $subtag . ':' . $subsubtag));
- }
- }
- }
- }
- }
-
- // Awkward special cases
- if ($level == 2 && $type === 'DATE' && in_array($level1type, ConfigData::dateAndTime()) && !in_array('TIME', $subtags)) {
- add_simple_tag('3 TIME'); // TIME is NOT a valid 5.5.1 tag
- }
- if ($level == 2 && $type === 'STAT' && GedcomCodeTemp::isTagLDS($level1type) && !in_array('DATE', $subtags)) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
- }
-
- $i++;
- if (isset($gedlines[$i])) {
- $fields = explode(' ', $gedlines[$i]);
- $level = $fields[0];
- if (isset($fields[1])) {
- $type = trim($fields[1]);
- } else {
- $level = 0;
- }
- } else {
- $level = 0;
- }
- if ($level <= $glevel) {
- break;
- }
- }
-
- if ($level1type !== '_PRIM') {
- insert_missing_subtags($level1type, $add_date);
- }
-
- return $level1type;
-}
-
-/**
- * Populates the global $tags array with any missing sub-tags.
- *
- * @param string $level1tag the type of the level 1 gedcom record
- * @param bool $add_date
- */
-function insert_missing_subtags($level1tag, $add_date = false) {
- global $tags, $WT_TREE;
-
- // handle MARRiage TYPE
- $type_val = '';
- if (substr($level1tag, 0, 5) === 'MARR_') {
- $type_val = substr($level1tag, 5);
- $level1tag = 'MARR';
- }
-
- foreach (ConfigData::levelTwoTags() as $key => $value) {
- if ($key === 'DATE' && in_array($level1tag, ConfigData::nonDateFacts()) || $key === 'PLAC' && in_array($level1tag, ConfigData::nonPlaceFacts())) {
- continue;
- }
- if (in_array($level1tag, $value) && !in_array($key, $tags)) {
- if ($key === 'TYPE') {
- add_simple_tag('2 TYPE ' . $type_val, $level1tag);
- } elseif ($level1tag === '_TODO' && $key === 'DATE') {
- add_simple_tag('2 ' . $key . ' ' . strtoupper(date('d M Y')), $level1tag);
- } elseif ($level1tag === '_TODO' && $key === '_WT_USER') {
- add_simple_tag('2 ' . $key . ' ' . Auth::user()->getUserName(), $level1tag);
- } elseif ($level1tag === 'TITL' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
- add_simple_tag('2 ' . $key, $level1tag);
- } elseif ($level1tag === 'NAME' && strstr($WT_TREE->getPreference('ADVANCED_NAME_FACTS'), $key) !== false) {
- add_simple_tag('2 ' . $key, $level1tag);
- } elseif ($level1tag !== 'TITL' && $level1tag !== 'NAME') {
- add_simple_tag('2 ' . $key, $level1tag);
- }
- // Add level 3/4 tags as appropriate
- switch ($key) {
- case 'PLAC':
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $tag) {
- add_simple_tag('3 ' . $tag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $tag));
- }
- }
- add_simple_tag('3 MAP');
- add_simple_tag('4 LATI');
- add_simple_tag('4 LONG');
- break;
- case 'FILE':
- add_simple_tag('3 FORM');
- break;
- case 'EVEN':
- add_simple_tag('3 DATE');
- add_simple_tag('3 PLAC');
- break;
- case 'STAT':
- if (GedcomCodeTemp::isTagLDS($level1tag)) {
- add_simple_tag('3 DATE', '', GedcomTag::getLabel('STAT:DATE'));
- }
- break;
- case 'DATE':
- // TIME is NOT a valid 5.5.1 tag
- if (in_array($level1tag, ConfigData::dateAndTime())) {
- add_simple_tag('3 TIME');
- }
- break;
- case 'HUSB':
- case 'WIFE':
- add_simple_tag('3 AGE');
- break;
- case 'FAMC':
- if ($level1tag === 'ADOP') {
- add_simple_tag('3 ADOP BOTH');
- }
- break;
- }
- } elseif ($key === 'DATE' && $add_date) {
- add_simple_tag('2 DATE', $level1tag, GedcomTag::getLabel($level1tag . ':DATE'));
- }
- }
- // Do something (anything!) with unrecognized custom tags
- if (substr($level1tag, 0, 1) === '_' && $level1tag !== '_UID' && $level1tag !== '_TODO') {
- foreach (array('DATE', 'PLAC', 'ADDR', 'AGNC', 'TYPE', 'AGE') as $tag) {
- if (!in_array($tag, $tags)) {
- add_simple_tag('2 ' . $tag);
- if ($tag === 'PLAC') {
- if (preg_match_all('/(' . WT_REGEX_TAG . ')/', $WT_TREE->getPreference('ADVANCED_PLAC_FACTS'), $match)) {
- foreach ($match[1] as $ptag) {
- add_simple_tag('3 ' . $ptag, '', GedcomTag::getLabel($level1tag . ':PLAC:' . $ptag));
- }
- }
- add_simple_tag('3 MAP');
- add_simple_tag('4 LATI');
- add_simple_tag('4 LONG');
- }
- }
- }
- }
-}
diff --git a/includes/functions/functions_export.php b/includes/functions/functions_export.php
deleted file mode 100644
index 5aad3fb595..0000000000
--- a/includes/functions/functions_export.php
+++ /dev/null
@@ -1,308 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Tidy up a gedcom record on export, for compatibility/portability.
- *
- * @param string $rec
- *
- * @return string
- */
-function reformat_record_export($rec) {
- global $WT_TREE;
-
- $newrec = '';
- foreach (preg_split('/[\r\n]+/', $rec, -1, PREG_SPLIT_NO_EMPTY) as $line) {
- // Split long lines
- // The total length of a GEDCOM line, including level number, cross-reference number,
- // tag, value, delimiters, and terminator, must not exceed 255 (wide) characters.
- if (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH) {
- list($level, $tag) = explode(' ', $line, 3);
- if ($tag != 'CONT' && $tag != 'CONC') {
- $level++;
- }
- do {
- // Split after $pos chars
- $pos = WT_GEDCOM_LINE_LENGTH;
- if ($WT_TREE->getPreference('WORD_WRAPPED_NOTES')) {
- // Split on a space, and remove it (for compatibility with some desktop apps)
- while ($pos && mb_substr($line, $pos - 1, 1) != ' ') {
- --$pos;
- }
- if ($pos == strpos($line, ' ', 3) + 1) {
- // No spaces in the data! Can’t split it :-(
- break;
- } else {
- $newrec .= mb_substr($line, 0, $pos - 1) . WT_EOL;
- $line = $level . ' CONC ' . mb_substr($line, $pos);
- }
- } else {
- // Split on a non-space (standard gedcom behaviour)
- while ($pos && mb_substr($line, $pos - 1, 1) == ' ') {
- --$pos;
- }
- if ($pos == strpos($line, ' ', 3)) {
- // No non-spaces in the data! Can’t split it :-(
- break;
- }
- $newrec .= mb_substr($line, 0, $pos) . WT_EOL;
- $line = $level . ' CONC ' . mb_substr($line, $pos);
- }
- } while (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH);
- }
- $newrec .= $line . WT_EOL;
- }
-
- return $newrec;
-}
-
-/**
- * Create a header for a (newly-created or already-imported) gedcom file.
- *
- * @param Tree $tree
- *
- * @return string
- */
-function gedcom_header(Tree $tree) {
- // Default values for a new header
- $HEAD = "0 HEAD";
- $SOUR = "\n1 SOUR " . WT_WEBTREES . "\n2 NAME " . WT_WEBTREES . "\n2 VERS " . WT_VERSION;
- $DEST = "\n1 DEST DISKETTE";
- $DATE = "\n1 DATE " . strtoupper(date("d M Y")) . "\n2 TIME " . date("H:i:s");
- $GEDC = "\n1 GEDC\n2 VERS 5.5.1\n2 FORM Lineage-Linked";
- $CHAR = "\n1 CHAR UTF-8";
- $FILE = "\n1 FILE " . $tree->getName();
- $LANG = "";
- $PLAC = "\n1 PLAC\n2 FORM City, County, State/Province, Country";
- $COPR = "";
- $SUBN = "";
- $SUBM = "\n1 SUBM @SUBM@\n0 @SUBM@ SUBM\n1 NAME " . Auth::user()->getUserName(); // The SUBM record is mandatory
-
- // Preserve some values from the original header
- $record = GedcomRecord::getInstance('HEAD', $tree);
- if ($fact = $record->getFirstFact('PLAC')) {
- $PLAC = "\n1 PLAC\n2 FORM " . $fact->getAttribute('FORM');
- }
- if ($fact = $record->getFirstFact('LANG')) {
- $LANG = $fact->getValue();
- }
- if ($fact = $record->getFirstFact('SUBN')) {
- $SUBN = $fact->getValue();
- }
- if ($fact = $record->getFirstFact('COPR')) {
- $COPR = $fact->getValue();
- }
- // Link to actual SUBM/SUBN records, if they exist
- $subn =
- Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
- ->execute(array('SUBN', $tree->getTreeId()))
- ->fetchOne();
- if ($subn) {
- $SUBN = "\n1 SUBN @{$subn}@";
- }
- $subm =
- Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
- ->execute(array('SUBM', $tree->getTreeId()))
- ->fetchOne();
- if ($subm) {
- $SUBM = "\n1 SUBM @{$subm}@";
- }
-
- return $HEAD . $SOUR . $DEST . $DATE . $GEDC . $CHAR . $FILE . $COPR . $LANG . $PLAC . $SUBN . $SUBM . "\n";
-}
-
-/**
- * Prepend the GEDCOM_MEDIA_PATH to media filenames.
- *
- * @param string $rec
- * @param string $path
- *
- * @return string
- */
-function convert_media_path($rec, $path) {
- if ($path && preg_match('/\n1 FILE (.+)/', $rec, $match)) {
- $old_file_name = $match[1];
- // Don’t modify external links
- if (!preg_match('~^(https?|ftp):~', $old_file_name)) {
- // Adding a windows path? Convert the slashes.
- if (strpos($path, '\\') !== false) {
- $new_file_name = preg_replace('~/+~', '\\', $old_file_name);
- } else {
- $new_file_name = $old_file_name;
- }
- // Path not present - add it.
- if (strpos($new_file_name, $path) === false) {
- $new_file_name = $path . $new_file_name;
- }
- $rec = str_replace("\n1 FILE " . $old_file_name, "\n1 FILE " . $new_file_name, $rec);
- }
- }
-
- return $rec;
-}
-
-/**
- * Export the database in GEDCOM format
- *
- * @param Tree $tree Which tree to export
- * @param resource $gedout Handle to a writable stream
- * @param string[] $exportOptions Export options are as follows:
- * 'privatize': which Privacy rules apply? (none, visitor, user, manager)
- * 'toANSI': should the output be produced in ISO-8859-1 instead of UTF-8? (yes, no)
- * 'path': what constant should prefix all media file paths? (eg: media/ or c:\my pictures\my family
- * 'slashes': what folder separators apply to media file paths? (forward, backward)
- */
-function export_gedcom(Tree $tree, $gedout, $exportOptions) {
- switch ($exportOptions['privatize']) {
- case 'gedadmin':
- $access_level = Auth::PRIV_NONE;
- break;
- case 'user':
- $access_level = Auth::PRIV_USER;
- break;
- case 'visitor':
- $access_level = Auth::PRIV_PRIVATE;
- break;
- case 'none':
- $access_level = Auth::PRIV_HIDE;
- break;
- }
-
- $head = gedcom_header($tree);
- if ($exportOptions['toANSI'] == 'yes') {
- $head = str_replace('UTF-8', 'ANSI', $head);
- $head = utf8_decode($head);
- }
- $head = reformat_record_export($head);
- fwrite($gedout, $head);
-
- // Buffer the output. Lots of small fwrite() calls can be very slow when writing large gedcoms.
- $buffer = '';
-
- // Generate the OBJE/SOUR/REPO/NOTE records first, as their privacy calcualations involve
- // database queries, and we wish to avoid large gaps between queries due to MySQL connection timeouts.
- $tmp_gedcom = '';
- $rows = Database::prepare(
- "SELECT m_id AS xref, m_gedcom AS gedcom" .
- " FROM `##media` WHERE m_file = :tree_id ORDER BY m_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Media::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- $rec = convert_media_path($rec, $exportOptions['path']);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom" .
- " FROM `##sources` WHERE s_file = :tree_id ORDER BY s_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Source::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT o_type AS type, o_id AS xref, o_gedcom AS gedcom" .
- " FROM `##other` WHERE o_file = :tree_id AND o_type NOT IN ('HEAD', 'TRLR') ORDER BY o_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- switch ($row->type) {
- case 'NOTE':
- $record = Note::getInstance($row->xref, $tree, $row->gedcom);
- break;
- case 'REPO':
- $record = Repository::getInstance($row->xref, $tree, $row->gedcom);
- break;
- default:
- $record = GedcomRecord::getInstance($row->xref, $tree, $row->gedcom);
- break;
- }
-
- $rec = $record->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $tmp_gedcom .= reformat_record_export($rec);
- }
-
- $rows = Database::prepare(
- "SELECT i_id AS xref, i_gedcom AS gedcom" .
- " FROM `##individuals` WHERE i_file = :tree_id ORDER BY i_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Individual::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $buffer .= reformat_record_export($rec);
- if (strlen($buffer) > 65536) {
- fwrite($gedout, $buffer);
- $buffer = '';
- }
- }
-
- $rows = Database::prepare(
- "SELECT f_id AS xref, f_gedcom AS gedcom" .
- " FROM `##families` WHERE f_file = :tree_id ORDER BY f_id"
- )->execute(array(
- 'tree_id' => $tree->getTreeId(),
- ))->fetchAll();
-
- foreach ($rows as $row) {
- $rec = Family::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
- if ($exportOptions['toANSI'] === 'yes') {
- $rec = utf8_decode($rec);
- }
- $buffer .= reformat_record_export($rec);
- if (strlen($buffer) > 65536) {
- fwrite($gedout, $buffer);
- $buffer = '';
- }
- }
-
- fwrite($gedout, $buffer);
- fwrite($gedout, $tmp_gedcom);
- fwrite($gedout, '0 TRLR' . WT_EOL);
-}
diff --git a/includes/functions/functions_import.php b/includes/functions/functions_import.php
deleted file mode 100644
index 2bb608e5d7..0000000000
--- a/includes/functions/functions_import.php
+++ /dev/null
@@ -1,1153 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-use Fisharebest\Webtrees\Database;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Log;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Soundex;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Tree;
-
-/**
- * Tidy up a gedcom record on import, so that we can access it consistently/efficiently.
- *
- * @param string $rec
- * @param Tree $tree
- *
- * @return string
- */
-function reformat_record_import($rec, Tree $tree) {
- // Strip out UTF8 formatting characters
- $rec = str_replace(array(WT_UTF8_BOM, WT_UTF8_LRM, WT_UTF8_RLM), '', $rec);
-
- // Strip out mac/msdos line endings
- $rec = preg_replace("/[\r\n]+/", "\n", $rec);
-
- // Extract lines from the record; lines consist of: level + optional xref + tag + optional data
- $num_matches = preg_match_all('/^[ \t]*(\d+)[ \t]*(@[^@]*@)?[ \t]*(\w+)[ \t]?(.*)$/m', $rec, $matches, PREG_SET_ORDER);
-
- // Process the record line-by-line
- $newrec = '';
- foreach ($matches as $n => $match) {
- list(, $level, $xref, $tag, $data) = $match;
- $tag = strtoupper($tag); // Tags should always be upper case
- switch ($tag) {
- // Convert PhpGedView tags to WT
- case '_PGVU':
- $tag = '_WT_USER';
- break;
- case '_PGV_OBJS':
- $tag = '_WT_OBJE_SORT';
- break;
- // Convert FTM-style "TAG_FORMAL_NAME" into "TAG".
- case 'ABBREVIATION':
- $tag = 'ABBR';
- break;
- case 'ADDRESS':
- $tag = 'ADDR';
- break;
- case 'ADDRESS1':
- $tag = 'ADR1';
- break;
- case 'ADDRESS2':
- $tag = 'ADR2';
- break;
- case 'ADDRESS3':
- $tag = 'ADR3';
- break;
- case 'ADOPTION':
- $tag = 'ADOP';
- break;
- case 'ADULT_CHRISTENING':
- $tag = 'CHRA';
- break;
- case 'AFN':
- // AFN values are upper case
- $data = strtoupper($data);
- break;
- case 'AGENCY':
- $tag = 'AGNC';
- break;
- case 'ALIAS':
- $tag = 'ALIA';
- break;
- case 'ANCESTORS':
- $tag = 'ANCE';
- break;
- case 'ANCES_INTEREST':
- $tag = 'ANCI';
- break;
- case 'ANNULMENT':
- $tag = 'ANUL';
- break;
- case 'ASSOCIATES':
- $tag = 'ASSO';
- break;
- case 'AUTHOR':
- $tag = 'AUTH';
- break;
- case 'BAPTISM':
- $tag = 'BAPM';
- break;
- case 'BAPTISM_LDS':
- $tag = 'BAPL';
- break;
- case 'BAR_MITZVAH':
- $tag = 'BARM';
- break;
- case 'BAS_MITZVAH':
- $tag = 'BASM';
- break;
- case 'BIRTH':
- $tag = 'BIRT';
- break;
- case 'BLESSING':
- $tag = 'BLES';
- break;
- case 'BURIAL':
- $tag = 'BURI';
- break;
- case 'CALL_NUMBER':
- $tag = 'CALN';
- break;
- case 'CASTE':
- $tag = 'CAST';
- break;
- case 'CAUSE':
- $tag = 'CAUS';
- break;
- case 'CENSUS':
- $tag = 'CENS';
- break;
- case 'CHANGE':
- $tag = 'CHAN';
- break;
- case 'CHARACTER':
- $tag = 'CHAR';
- break;
- case 'CHILD':
- $tag = 'CHIL';
- break;
- case 'CHILDREN_COUNT':
- $tag = 'NCHI';
- break;
- case 'CHRISTENING':
- $tag = 'CHR';
- break;
- case 'CONCATENATION':
- $tag = 'CONC';
- break;
- case 'CONFIRMATION':
- $tag = 'CONF';
- break;
- case 'CONFIRMATION_LDS':
- $tag = 'CONL';
- break;
- case 'CONTINUED':
- $tag = 'CONT';
- break;
- case 'COPYRIGHT':
- $tag = 'COPR';
- break;
- case 'CORPORATE':
- $tag = 'CORP';
- break;
- case 'COUNTRY':
- $tag = 'CTRY';
- break;
- case 'CREMATION':
- $tag = 'CREM';
- break;
- case 'DATE':
- // Preserve text from INT dates
- if (strpos($data, '(') !== false) {
- list($date, $text) = explode('(', $data, 2);
- $text = ' (' . $text;
- } else {
- $date = $data;
- $text = '';
- }
- // Capitals
- $date = strtoupper($date);
- // Temporarily add leading/trailing spaces, to allow efficient matching below
- $date = " {$date} ";
- // Ensure space digits and letters
- $date = preg_replace('/([A-Z])(\d)/', '$1 $2', $date);
- $date = preg_replace('/(\d)([A-Z])/', '$1 $2', $date);
- // Ensure space before/after calendar escapes
- $date = preg_replace('/@#[^@]+@/', ' $0 ', $date);
- // "BET." => "BET"
- $date = preg_replace('/(\w\w)\./', '$1', $date);
- // "CIR" => "ABT"
- $date = str_replace(' CIR ', ' ABT ', $date);
- $date = str_replace(' APX ', ' ABT ', $date);
- // B.C. => BC (temporarily, to allow easier handling of ".")
- $date = str_replace(' B.C. ', ' BC ', $date);
- // "BET X - Y " => "BET X AND Y"
- $date = preg_replace('/^(.* BET .+) - (.+)/', '$1 AND $2', $date);
- $date = preg_replace('/^(.* FROM .+) - (.+)/', '$1 TO $2', $date);
- // "@#ESC@ FROM X TO Y" => "FROM @#ESC@ X TO @#ESC@ Y"
- $date = preg_replace('/^ +(@#[^@]+@) +FROM +(.+) +TO +(.+)/', ' FROM $1 $2 TO $1 $3', $date);
- $date = preg_replace('/^ +(@#[^@]+@) +BET +(.+) +AND +(.+)/', ' BET $1 $2 AND $1 $3', $date);
- // "@#ESC@ AFT X" => "AFT @#ESC@ X"
- $date = preg_replace('/^ +(@#[^@]+@) +(FROM|BET|TO|AND|BEF|AFT|CAL|EST|INT|ABT) +(.+)/', ' $2 $1 $3', $date);
- // Ignore any remaining punctuation, e.g. "14-MAY, 1900" => "14 MAY 1900"
- // (don't change "/" - it is used in NS/OS dates)
- $date = preg_replace('/[.,:;-]/', ' ', $date);
- // BC => B.C.
- $date = str_replace(' BC ', ' B.C. ', $date);
- // Append the "INT" text
- $data = $date . $text;
- break;
- case 'DEATH':
- $tag = 'DEAT';
- break;
- case '_DEATH_OF_SPOUSE':
- $tag = '_DETS';
- break;
- case '_DEGREE':
- $tag = '_DEG';
- break;
- case 'DESCENDANTS':
- $tag = 'DESC';
- break;
- case 'DESCENDANT_INT':
- $tag = 'DESI';
- break;
- case 'DESTINATION':
- $tag = 'DEST';
- break;
- case 'DIVORCE':
- $tag = 'DIV';
- break;
- case 'DIVORCE_FILED':
- $tag = 'DIVF';
- break;
- case 'EDUCATION':
- $tag = 'EDUC';
- break;
- case 'EMIGRATION':
- $tag = 'EMIG';
- break;
- case 'ENDOWMENT':
- $tag = 'ENDL';
- break;
- case 'ENGAGEMENT':
- $tag = 'ENGA';
- break;
- case 'EVENT':
- $tag = 'EVEN';
- break;
- case 'FACSIMILE':
- $tag = 'FAX';
- break;
- case 'FAMILY':
- $tag = 'FAM';
- break;
- case 'FAMILY_CHILD':
- $tag = 'FAMC';
- break;
- case 'FAMILY_FILE':
- $tag = 'FAMF';
- break;
- case 'FAMILY_SPOUSE':
- $tag = 'FAMS';
- break;
- case 'FIRST_COMMUNION':
- $tag = 'FCOM';
- break;
- case '_FILE':
- $tag = 'FILE';
- break;
- case 'FORMAT':
- $tag = 'FORM';
- case 'FORM':
- // Consistent commas
- $data = preg_replace('/ *, */', ', ', $data);
- break;
- case 'GEDCOM':
- $tag = 'GEDC';
- break;
- case 'GIVEN_NAME':
- $tag = 'GIVN';
- break;
- case 'GRADUATION':
- $tag = 'GRAD';
- break;
- case 'HEADER':
- $tag = 'HEAD';
- case 'HEAD':
- // HEAD records don't have an XREF or DATA
- if ($level == '0') {
- $xref = '';
- $data = '';
- }
- break;
- case 'HUSBAND':
- $tag = 'HUSB';
- break;
- case 'IDENT_NUMBER':
- $tag = 'IDNO';
- break;
- case 'IMMIGRATION':
- $tag = 'IMMI';
- break;
- case 'INDIVIDUAL':
- $tag = 'INDI';
- break;
- case 'LANGUAGE':
- $tag = 'LANG';
- break;
- case 'LATITUDE':
- $tag = 'LATI';
- break;
- case 'LONGITUDE':
- $tag = 'LONG';
- break;
- case 'MARRIAGE':
- $tag = 'MARR';
- break;
- case 'MARRIAGE_BANN':
- $tag = 'MARB';
- break;
- case 'MARRIAGE_COUNT':
- $tag = 'NMR';
- break;
- case 'MARRIAGE_CONTRACT':
- $tag = 'MARC';
- break;
- case 'MARRIAGE_LICENSE':
- $tag = 'MARL';
- break;
- case 'MARRIAGE_SETTLEMENT':
- $tag = 'MARS';
- break;
- case 'MEDIA':
- $tag = 'MEDI';
- break;
- case '_MEDICAL':
- $tag = '_MDCL';
- break;
- case '_MILITARY_SERVICE':
- $tag = '_MILT';
- break;
- case 'NAME':
- // Tidy up whitespace
- $data = preg_replace('/ +/', ' ', trim($data));
- break;
- case 'NAME_PREFIX':
- $tag = 'NPFX';
- break;
- case 'NAME_SUFFIX':
- $tag = 'NSFX';
- break;
- case 'NATIONALITY':
- $tag = 'NATI';
- break;
- case 'NATURALIZATION':
- $tag = 'NATU';
- break;
- case 'NICKNAME':
- $tag = 'NICK';
- break;
- case 'OBJECT':
- $tag = 'OBJE';
- break;
- case 'OCCUPATION':
- $tag = 'OCCU';
- break;
- case 'ORDINANCE':
- $tag = 'ORDI';
- break;
- case 'ORDINATION':
- $tag = 'ORDN';
- break;
- case 'PEDIGREE':
- $tag = 'PEDI';
- case 'PEDI':
- // PEDI values are lower case
- $data = strtolower($data);
- break;
- case 'PHONE':
- $tag = 'PHON';
- break;
- case 'PHONETIC':
- $tag = 'FONE';
- break;
- case 'PHY_DESCRIPTION':
- $tag = 'DSCR';
- break;
- case 'PLACE':
- $tag = 'PLAC';
- case 'PLAC':
- // Consistent commas
- $data = preg_replace('/ *(،|,) */', ', ', $data);
- // The Master Genealogist stores LAT/LONG data in the PLAC field, e.g. Pennsylvania, USA, 395945N0751013W
- if (preg_match('/(.*), (\d\d)(\d\d)(\d\d)([NS])(\d\d\d)(\d\d)(\d\d)([EW])$/', $data, $match)) {
- $data =
- $match[1] . "\n" .
- ($level + 1) . " MAP\n" .
- ($level + 2) . " LATI " . ($match[5] . (round($match[2] + ($match[3] / 60) + ($match[4] / 3600), 4))) . "\n" .
- ($level + 2) . " LONG " . ($match[9] . (round($match[6] + ($match[7] / 60) + ($match[8] / 3600), 4)));
- }
- break;
- case 'POSTAL_CODE':
- $tag = 'POST';
- break;
- case 'PROBATE':
- $tag = 'PROB';
- break;
- case 'PROPERTY':
- $tag = 'PROP';
- break;
- case 'PUBLICATION':
- $tag = 'PUBL';
- break;
- case 'QUALITY_OF_DATA':
- $tag = 'QUAL';
- break;
- case 'REC_FILE_NUMBER':
- $tag = 'RFN';
- break;
- case 'REC_ID_NUMBER':
- $tag = 'RIN';
- break;
- case 'REFERENCE':
- $tag = 'REFN';
- break;
- case 'RELATIONSHIP':
- $tag = 'RELA';
- break;
- case 'RELIGION':
- $tag = 'RELI';
- break;
- case 'REPOSITORY':
- $tag = 'REPO';
- break;
- case 'RESIDENCE':
- $tag = 'RESI';
- break;
- case 'RESTRICTION':
- $tag = 'RESN';
- case 'RESN':
- // RESN values are lower case (confidential, privacy, locked, none)
- $data = strtolower($data);
- if ($data == 'invisible') {
- $data = 'confidential'; // From old versions of Legacy.
- }
- break;
- case 'RETIREMENT':
- $tag = 'RETI';
- break;
- case 'ROMANIZED':
- $tag = 'ROMN';
- break;
- case 'SEALING_CHILD':
- $tag = 'SLGC';
- break;
- case 'SEALING_SPOUSE':
- $tag = 'SLGS';
- break;
- case 'SOC_SEC_NUMBER':
- $tag = 'SSN';
- break;
- case 'SEX':
- $data = strtoupper($data);
- break;
- case 'SOURCE':
- $tag = 'SOUR';
- break;
- case 'STATE':
- $tag = 'STAE';
- break;
- case 'STATUS':
- $tag = 'STAT';
- case 'STAT':
- if ($data == 'CANCELLED') {
- // PhpGedView mis-spells this tag - correct it.
- $data = 'CANCELED';
- }
- break;
- case 'SUBMISSION':
- $tag = 'SUBN';
- break;
- case 'SUBMITTER':
- $tag = 'SUBM';
- break;
- case 'SURNAME':
- $tag = 'SURN';
- break;
- case 'SURN_PREFIX':
- $tag = 'SPFX';
- break;
- case 'TEMPLE':
- $tag = 'TEMP';
- case 'TEMP':
- // Temple codes are upper case
- $data = strtoupper($data);
- break;
- case 'TITLE':
- $tag = 'TITL';
- break;
- case 'TRAILER':
- $tag = 'TRLR';
- case 'TRLR':
- // TRLR records don't have an XREF or DATA
- if ($level == '0') {
- $xref = '';
- $data = '';
- }
- break;
- case 'VERSION':
- $tag = 'VERS';
- break;
- case 'WEB':
- $tag = 'WWW';
- break;
- }
- // Suppress "Y", for facts/events with a DATE or PLAC
- if ($data == 'y') {
- $data = 'Y';
- }
- if ($level == '1' && $data == 'Y') {
- for ($i = $n + 1; $i < $num_matches - 1 && $matches[$i][1] != '1'; ++$i) {
- if ($matches[$i][3] == 'DATE' || $matches[$i][3] == 'PLAC') {
- $data = '';
- break;
- }
- }
- }
- // Reassemble components back into a single line
- switch ($tag) {
- default:
- // Remove tabs and multiple/leading/trailing spaces
- if (strpos($data, "\t") !== false) {
- $data = str_replace("\t", ' ', $data);
- }
- if (substr($data, 0, 1) == ' ' || substr($data, -1, 1) == ' ') {
- $data = trim($data);
- }
- while (strpos($data, ' ')) {
- $data = str_replace(' ', ' ', $data);
- }
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'NOTE':
- case 'TEXT':
- case 'DATA':
- case 'CONT':
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'FILE':
- // Strip off the user-defined path prefix
- $GEDCOM_MEDIA_PATH = $tree->getPreference('GEDCOM_MEDIA_PATH');
- if ($GEDCOM_MEDIA_PATH && strpos($data, $GEDCOM_MEDIA_PATH) === 0) {
- $data = substr($data, strlen($GEDCOM_MEDIA_PATH));
- }
- // convert backslashes in filenames to forward slashes
- $data = preg_replace("/\\\/", "/", $data);
-
- $newrec .= ($newrec ? "\n" : '') . $level . ' ' . ($level == '0' && $xref ? $xref . ' ' : '') . $tag . ($data === '' && $tag != "NOTE" ? '' : ' ' . $data);
- break;
- case 'CONC':
- // Merge CONC lines, to simplify access later on.
- $newrec .= ($tree->getPreference('WORD_WRAPPED_NOTES') ? ' ' : '') . $data;
- break;
- }
- }
-
- return $newrec;
-}
-
-/**
- * import record into database
- *
- * this function will parse the given gedcom record and add it to the database
- *
- * @param string $gedrec the raw gedcom record to parse
- * @param Tree $tree import the record into this tree
- * @param bool $update whether or not this is an updated record that has been accepted
- */
-function import_record($gedrec, Tree $tree, $update) {
- $tree_id = $tree->getTreeId();
-
- // Escaped @ signs (only if importing from file)
- if (!$update) {
- $gedrec = str_replace('@@', '@', $gedrec);
- }
-
- // Standardise gedcom format
- $gedrec = reformat_record_import($gedrec, $tree);
-
- // import different types of records
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
- list(, $xref, $type) = $match;
- // check for a _UID, if the record doesn't have one, add one
- if ($tree->getPreference('GENERATE_UIDS') && !strpos($gedrec, "\n1 _UID ")) {
- $gedrec .= "\n1 _UID " . GedcomTag::createUid();
- }
- } elseif (preg_match('/0 (HEAD|TRLR)/', $gedrec, $match)) {
- $type = $match[1];
- $xref = $type; // For HEAD/TRLR, use type as pseudo XREF.
- } else {
- echo I18N::translate('Invalid GEDCOM format'), '<br><pre>', $gedrec, '</pre>';
-
- return;
- }
-
- // If the user has downloaded their GEDCOM data (containing media objects) and edited it
- // using an application which does not support (and deletes) media objects, then add them
- // back in.
- if ($tree->getPreference('keep_media') && $xref) {
- $old_linked_media =
- Database::prepare("SELECT l_to FROM `##link` WHERE l_from=? AND l_file=? AND l_type='OBJE'")
- ->execute(array($xref, $tree_id))
- ->fetchOneColumn();
- foreach ($old_linked_media as $media_id) {
- $gedrec .= "\n1 OBJE @" . $media_id . "@";
- }
- }
-
- switch ($type) {
- case 'INDI':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Individual($xref, $gedrec, null, $tree);
- if ($tree->getPreference('USE_RIN') && preg_match('/\n1 RIN (.+)/', $gedrec, $match)) {
- $rin = $match[1];
- } else {
- $rin = $xref;
- }
- Database::prepare(
- "INSERT INTO `##individuals` (i_id, i_file, i_rin, i_sex, i_gedcom) VALUES (?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $tree_id, $rin, $record->getSex(), $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_places($xref, $tree_id, $gedrec);
- update_dates($xref, $tree_id, $gedrec);
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'FAM':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- if (preg_match('/\n1 HUSB @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
- $husb = $match[1];
- } else {
- $husb = '';
- }
- if (preg_match('/\n1 WIFE @(' . WT_REGEX_XREF . ')@/', $gedrec, $match)) {
- $wife = $match[1];
- } else {
- $wife = '';
- }
- $nchi = preg_match_all('/\n1 CHIL @(' . WT_REGEX_XREF . ')@/', $gedrec, $match);
- if (preg_match('/\n1 NCHI (\d+)/', $gedrec, $match)) {
- $nchi = max($nchi, $match[1]);
- }
- Database::prepare(
- "INSERT INTO `##families` (f_id, f_file, f_husb, f_wife, f_gedcom, f_numchil) VALUES (?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $tree_id, $husb, $wife, $gedrec, $nchi,
- ));
- // Update the cross-reference/index tables.
- update_places($xref, $tree_id, $gedrec);
- update_dates($xref, $tree_id, $gedrec);
- update_links($xref, $tree_id, $gedrec);
- break;
- case 'SOUR':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Source($xref, $gedrec, null, $tree);
- if (preg_match('/\n1 TITL (.+)/', $gedrec, $match)) {
- $name = $match[1];
- } elseif (preg_match('/\n1 ABBR (.+)/', $gedrec, $match)) {
- $name = $match[1];
- } else {
- $name = $xref;
- }
- Database::prepare(
- "INSERT INTO `##sources` (s_id, s_file, s_name, s_gedcom) VALUES (?, ?, LEFT(?, 255), ?)"
- )->execute(array(
- $xref, $tree_id, $name, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'REPO':
- // Convert inline media into media objects
- $gedrec = convert_inline_media($tree, $gedrec);
-
- $record = new Repository($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'REPO', ?)"
- )->execute(array(
- $xref, $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'NOTE':
- $record = new Note($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, 'NOTE', ?)"
- )->execute(array(
- $xref, $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'OBJE':
- $record = new Media($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree_id, $gedrec,
- ));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- case 'HEAD':
- // Force HEAD records to have a creation date.
- if (!strpos($gedrec, "\n1 DATE ")) {
- $gedrec .= "\n1 DATE " . date('j M Y');
- }
- // No break;
- case 'TRLR':
- case 'SUBM':
- case 'SUBN':
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
- )->execute(array($xref, $tree_id, $type, $gedrec));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- break;
- default:
- $record = new GedcomRecord($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##other` (o_id, o_file, o_type, o_gedcom) VALUES (?, ?, ?, ?)"
- )->execute(array($xref, $tree_id, $type, $gedrec));
- // Update the cross-reference/index tables.
- update_links($xref, $tree_id, $gedrec);
- update_names($xref, $tree_id, $record);
- break;
- }
-}
-
-/**
- * extract all places from the given record and insert them into the places table
- *
- * @param string $gid
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_places($gid, $ged_id, $gedrec) {
- global $placecache;
-
- if (!isset($placecache)) {
- $placecache = array();
- }
- $personplace = array();
- // import all place locations, but not control info such as
- // 0 HEAD/1 PLAC or 0 _EVDEF/1 PLAC
- $pt = preg_match_all("/^[2-9] PLAC (.+)/m", $gedrec, $match, PREG_SET_ORDER);
- for ($i = 0; $i < $pt; $i++) {
- $place = trim($match[$i][1]);
- $lowplace = I18N::strtolower($place);
- //-- if we have already visited this place for this person then we don't need to again
- if (isset($personplace[$lowplace])) {
- continue;
- }
- $personplace[$lowplace] = 1;
- $places = explode(',', $place);
- //-- reverse the array to start at the highest level
- $secalp = array_reverse($places);
- $parent_id = 0;
- $search = true;
-
- foreach ($secalp as $place) {
- $place = trim($place);
- $key = strtolower(mb_substr($place, 0, 150) . "_" . $parent_id);
- //-- if this place has already been added then we don't need to add it again
- if (isset($placecache[$key])) {
- $parent_id = $placecache[$key];
- if (!isset($personplace[$key])) {
- $personplace[$key] = 1;
- // Use INSERT IGNORE as a (temporary) fix for https://bugs.launchpad.net/webtrees/+bug/582226
- // It ignores places that utf8_unicode_ci consider to be the same (i.e. accents).
- // For example Québec and Quebec
- // We need a better solution that attaches multiple names to single places
- Database::prepare(
- "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
- )->execute(array(
- $parent_id, $gid, $ged_id,
- ));
- }
- continue;
- }
-
- //-- only search the database while we are finding places in it
- if ($search) {
- //-- check if this place and level has already been added
- $tmp = Database::prepare(
- "SELECT p_id FROM `##places` WHERE p_file = ? AND p_parent_id = ? AND p_place = LEFT(?, 150)"
- )->execute(array(
- $ged_id, $parent_id, $place,
- ))->fetchOne();
- if ($tmp) {
- $p_id = $tmp;
- } else {
- $search = false;
- }
- }
-
- //-- if we are not searching then we have to insert the place into the db
- if (!$search) {
- $std_soundex = Soundex::russell($place);
- $dm_soundex = Soundex::daitchMokotoff($place);
- Database::prepare(
- "INSERT INTO `##places` (p_place, p_parent_id, p_file, p_std_soundex, p_dm_soundex) VALUES (LEFT(?, 150), ?, ?, ?, ?)"
- )->execute(array(
- $place, $parent_id, $ged_id, $std_soundex, $dm_soundex,
- ));
- $p_id = Database::getInstance()->lastInsertId();
- }
-
- Database::prepare(
- "INSERT IGNORE INTO `##placelinks` (pl_p_id, pl_gid, pl_file) VALUES (?, ?, ?)"
- )->execute(array(
- $p_id, $gid, $ged_id,
- ));
- //-- increment the level and assign the parent id for the next place level
- $parent_id = $p_id;
- $placecache[$key] = $p_id;
- $personplace[$key] = 1;
- }
- }
-}
-
-/**
- * Extract all the dates from the given record and insert them into the database.
- *
- * @param string $xref
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_dates($xref, $ged_id, $gedrec) {
- if (strpos($gedrec, '2 DATE ') && preg_match_all("/\n1 (\w+).*(?:\n[2-9].*)*(?:\n2 DATE (.+))(?:\n[2-9].*)*/", $gedrec, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $fact = $match[1];
- if (($fact == 'FACT' || $fact == 'EVEN') && preg_match("/\n2 TYPE ([A-Z]{3,5})/", $match[0], $tmatch)) {
- $fact = $tmatch[1];
- }
- $date = new Date($match[2]);
- Database::prepare(
- "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
- )->execute(array(
- $date->minimumDate()->d,
- $date->minimumDate()->format('%O'),
- $date->minimumDate()->m,
- $date->minimumDate()->y,
- $date->minimumDate()->minJD,
- $date->minimumDate()->maxJD,
- $fact,
- $xref,
- $ged_id,
- $date->minimumDate()->format('%@'),
- ));
- if ($date->minimumDate() !== $date->maximumDate()) {
- Database::prepare(
- "INSERT INTO `##dates` (d_day,d_month,d_mon,d_year,d_julianday1,d_julianday2,d_fact,d_gid,d_file,d_type) VALUES (?,?,?,?,?,?,?,?,?,?)"
- )->execute(array(
- $date->maximumDate()->d,
- $date->maximumDate()->format('%O'),
- $date->maximumDate()->m,
- $date->maximumDate()->y,
- $date->maximumDate()->minJD,
- $date->maximumDate()->maxJD,
- $fact,
- $xref,
- $ged_id,
- $date->maximumDate()->format('%@'),
- ));
- }
- }
- }
-}
-
-/**
- * Extract all the links from the given record and insert them into the database
- *
- * @param string $xref
- * @param int $ged_id
- * @param string $gedrec
- */
-function update_links($xref, $ged_id, $gedrec) {
- if (preg_match_all('/^\d+ (' . WT_REGEX_TAG . ') @(' . WT_REGEX_XREF . ')@/m', $gedrec, $matches, PREG_SET_ORDER)) {
- $data = array();
- foreach ($matches as $match) {
- // Include each link once only.
- if (!in_array($match[1] . $match[2], $data)) {
- $data[] = $match[1] . $match[2];
- // Ignore any errors, which may be caused by "duplicates" that differ on case/collation, e.g. "S1" and "s1"
- try {
- Database::prepare(
- "INSERT INTO `##link` (l_from, l_to, l_type, l_file) VALUES (?, ?, ?, ?)"
- )->execute(array(
- $xref, $match[2], $match[1], $ged_id,
- ));
- } catch (PDOException $e) {
- // We could display a warning here....
- }
- }
- }
- }
-}
-
-/**
- * Extract all the names from the given record and insert them into the database.
- *
- * @param string $xref
- * @param int $ged_id
- * @param GedcomRecord $record
- */
-function update_names($xref, $ged_id, GedcomRecord $record) {
- foreach ($record->getAllNames() as $n => $name) {
- if ($record instanceof Individual) {
- if ($name['givn'] === '@P.N.') {
- $soundex_givn_std = null;
- $soundex_givn_dm = null;
- } else {
- $soundex_givn_std = Soundex::russell($name['givn']);
- $soundex_givn_dm = Soundex::daitchMokotoff($name['givn']);
- }
- if ($name['surn'] === '@N.N.') {
- $soundex_surn_std = null;
- $soundex_surn_dm = null;
- } else {
- $soundex_surn_std = Soundex::russell($name['surname']);
- $soundex_surn_dm = Soundex::daitchMokotoff($name['surname']);
- }
- Database::prepare(
- "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full,n_surname,n_surn,n_givn,n_soundex_givn_std,n_soundex_surn_std,n_soundex_givn_dm,n_soundex_surn_dm) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), LEFT(?, 255), ?, ?, ?, ?, ?)"
- )->execute(array(
- $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'], $name['surname'], $name['surn'], $name['givn'], $soundex_givn_std, $soundex_surn_std, $soundex_givn_dm, $soundex_surn_dm,
- ));
- } else {
- Database::prepare(
- "INSERT INTO `##name` (n_file,n_id,n_num,n_type,n_sort,n_full) VALUES (?, ?, ?, ?, LEFT(?, 255), LEFT(?, 255))"
- )->execute(array(
- $ged_id, $xref, $n, $name['type'], $name['sort'], $name['fullNN'],
- ));
- }
- }
-}
-
-/**
- * Extract inline media data, and convert to media objects.
- *
- * @param Tree $tree
- * @param string $gedrec
- *
- * @return string
- */
-function convert_inline_media(Tree $tree, $gedrec) {
- while (preg_match('/\n1 OBJE(?:\n[2-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(1, $match[0], $tree), $gedrec);
- }
- while (preg_match('/\n2 OBJE(?:\n[3-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(2, $match[0], $tree), $gedrec);
- }
- while (preg_match('/\n3 OBJE(?:\n[4-9].+)+/', $gedrec, $match)) {
- $gedrec = str_replace($match[0], create_media_object(3, $match[0], $tree), $gedrec);
- }
-
- return $gedrec;
-}
-
-/**
- * Create a new media object, from inline media data.
- *
- * @param int $level
- * @param string $gedrec
- * @param Tree $tree
- *
- * @return string
- */
-function create_media_object($level, $gedrec, Tree $tree) {
- if (preg_match('/\n\d FILE (.+)/', $gedrec, $file_match)) {
- $file = $file_match[1];
- } else {
- $file = '';
- }
-
- if (preg_match('/\n\d TITL (.+)/', $gedrec, $file_match)) {
- $titl = $file_match[1];
- } else {
- $titl = $file;
- }
-
- // Have we already created a media object with the same title/filename?
- $xref = Database::prepare(
- "SELECT m_id FROM `##media` WHERE m_filename = ? AND m_titl = ? AND m_file = ?"
- )->execute(array(
- $file, $titl, $tree->getTreeId(),
- ))->fetchOne();
-
- if (!$xref) {
- $xref = $tree->getNewXref('OBJE');
- // renumber the lines
- $gedrec = preg_replace_callback('/\n(\d+)/', function ($m) use ($level) { return "\n" . ($m[1] - $level); }, $gedrec);
- // convert to an object
- $gedrec = str_replace("\n0 OBJE\n", '0 @' . $xref . "@ OBJE\n", $gedrec);
- // Fix Legacy GEDCOMS
- $gedrec = preg_replace('/\n1 FORM (.+)\n1 FILE (.+)\n1 TITL (.+)/', "\n1 FILE $2\n2 FORM $1\n2 TITL $3", $gedrec);
- // Create new record
- $record = new Media($xref, $gedrec, null, $tree);
- Database::prepare(
- "INSERT INTO `##media` (m_id, m_ext, m_type, m_titl, m_filename, m_file, m_gedcom) VALUES (?, ?, ?, ?, ?, ?, ?)"
- )->execute(array(
- $xref, $record->extension(), $record->getMediaType(), $record->getTitle(), $record->getFilename(), $tree->getTreeId(), $gedrec,
- ));
- }
-
- return "\n" . $level . ' OBJE @' . $xref . '@';
-}
-
-/**
- * Accept all pending changes for a specified record.
- *
- * @param string $xref
- * @param int $ged_id
- */
-function accept_all_changes($xref, $ged_id) {
- $changes = Database::prepare(
- "SELECT change_id, gedcom_name, old_gedcom, new_gedcom" .
- " FROM `##change` c" .
- " JOIN `##gedcom` g USING (gedcom_id)" .
- " WHERE c.status='pending' AND xref=? AND gedcom_id=?" .
- " ORDER BY change_id"
- )->execute(array($xref, $ged_id))->fetchAll();
- foreach ($changes as $change) {
- if (empty($change->new_gedcom)) {
- // delete
- update_record($change->old_gedcom, $ged_id, true);
- } else {
- // add/update
- update_record($change->new_gedcom, $ged_id, false);
- }
- Database::prepare(
- "UPDATE `##change`" .
- " SET status='accepted'" .
- " WHERE status='pending' AND xref=? AND gedcom_id=?"
- )->execute(array($xref, $ged_id));
- Log::addEditLog("Accepted change {$change->change_id} for {$xref} / {$change->gedcom_name} into database");
- }
-}
-
-/**
- * Accept all pending changes for a specified record.
- *
- * @param GedcomRecord $record
- */
-function reject_all_changes(GedcomRecord $record) {
- Database::prepare(
- "UPDATE `##change`" .
- " SET status = 'rejected'" .
- " WHERE status = 'pending' AND xref = :xref AND gedcom_id = :tree_id"
- )->execute(array(
- 'xref' => $record->getXref(),
- 'tree_id' => $record->getTree()->getTreeId(),
- ));
-}
-
-/**
- * update a record in the database
- *
- * @param string $gedrec
- * @param int $ged_id
- * @param bool $delete
- */
-function update_record($gedrec, $ged_id, $delete) {
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ (' . WT_REGEX_TAG . ')/', $gedrec, $match)) {
- list(, $gid, $type) = $match;
- } else {
- echo "ERROR: Invalid gedcom record.";
-
- return;
- }
-
- // TODO deleting unlinked places can be done more efficiently in a single query
- $placeids =
- Database::prepare("SELECT pl_p_id FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")
- ->execute(array($gid, $ged_id))
- ->fetchOneColumn();
-
- Database::prepare("DELETE FROM `##placelinks` WHERE pl_gid=? AND pl_file=?")->execute(array($gid, $ged_id));
- Database::prepare("DELETE FROM `##dates` WHERE d_gid =? AND d_file =?")->execute(array($gid, $ged_id));
-
- //-- delete any unlinked places
- foreach ($placeids as $p_id) {
- $num =
- Database::prepare("SELECT count(pl_p_id) FROM `##placelinks` WHERE pl_p_id=? AND pl_file=?")
- ->execute(array($p_id, $ged_id))
- ->fetchOne();
- if ($num == 0) {
- Database::prepare("DELETE FROM `##places` WHERE p_id=? AND p_file=?")->execute(array($p_id, $ged_id));
- }
- }
-
- Database::prepare("DELETE FROM `##name` WHERE n_id=? AND n_file=?")->execute(array($gid, $ged_id));
- Database::prepare("DELETE FROM `##link` WHERE l_from=? AND l_file=?")->execute(array($gid, $ged_id));
-
- switch ($type) {
- case 'INDI':
- Database::prepare("DELETE FROM `##individuals` WHERE i_id=? AND i_file=?")->execute(array($gid, $ged_id));
- break;
- case 'FAM':
- Database::prepare("DELETE FROM `##families` WHERE f_id=? AND f_file=?")->execute(array($gid, $ged_id));
- break;
- case 'SOUR':
- Database::prepare("DELETE FROM `##sources` WHERE s_id=? AND s_file=?")->execute(array($gid, $ged_id));
- break;
- case 'OBJE':
- Database::prepare("DELETE FROM `##media` WHERE m_id=? AND m_file=?")->execute(array($gid, $ged_id));
- break;
- default:
- Database::prepare("DELETE FROM `##other` WHERE o_id=? AND o_file=?")->execute(array($gid, $ged_id));
- break;
- }
-
- if (!$delete) {
- import_record($gedrec, Tree::FindById($ged_id), true);
- }
-}
diff --git a/includes/functions/functions_mediadb.php b/includes/functions/functions_mediadb.php
deleted file mode 100644
index f2e40af6d8..0000000000
--- a/includes/functions/functions_mediadb.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-use Fisharebest\Webtrees\Log;
-
-/**
- * Convert raw values from php.ini file into bytes
- *
- * @param string $val
- *
- * @return int
- */
-function return_bytes($val) {
- if (!$val) {
- // no value was passed in, assume no limit and return -1
- $val = -1;
- }
- switch (substr($val, -1)) {
- case 'g':
- case 'G':
- return (int) $val * 1024 * 1024 * 1024;
- case 'm':
- case 'M':
- return (int) $val * 1024 * 1024;
- case 'k':
- case 'K':
- return (int) $val * 1024;
- default:
- return (int) $val;
- }
-}
-
-/**
- * Determine whether there is enough memory to load a particular image.
- *
- * @param string $serverFilename
- *
- * @return bool
- */
-function hasMemoryForImage($serverFilename) {
- // find out how much total memory this script can access
- $memoryAvailable = return_bytes(ini_get('memory_limit'));
- // if memory is unlimited, it will return -1 and we don’t need to worry about it
- if ($memoryAvailable == -1) {
- return true;
- }
-
- // find out how much memory we are already using
- $memoryUsed = memory_get_usage();
-
- try {
- $imgsize = getimagesize($serverFilename);
- } catch (\ErrorException $ex) {
- // Not an image, or not a valid image?
- $imgsize = false;
- }
-
- // find out how much memory this image needs for processing, probably only works for jpegs
- // from comments on http://www.php.net/imagecreatefromjpeg
- if ($imgsize && isset($imgsize['bits']) && (isset($imgsize['channels']))) {
- $memoryNeeded = round(($imgsize[0] * $imgsize[1] * $imgsize['bits'] * $imgsize['channels'] / 8 + Pow(2, 16)) * 1.65);
- $memorySpare = $memoryAvailable - $memoryUsed - $memoryNeeded;
- if ($memorySpare > 0) {
- // we have enough memory to load this file
- return true;
- } else {
- // not enough memory to load this file
- $image_info = sprintf('%.2fKB, %d × %d %d bits %d channels', filesize($serverFilename) / 1024, $imgsize[0], $imgsize[1], $imgsize['bits'], $imgsize['channels']);
- Log::addMediaLog('Cannot create thumbnail ' . $serverFilename . ' (' . $image_info . ') memory avail: ' . $memoryAvailable . ' used: ' . $memoryUsed . ' needed: ' . $memoryNeeded . ' spare: ' . $memorySpare);
-
- return false;
- }
- } else {
- // assume there is enough memory
- // TODO find out how to check memory needs for gif and png
- return true;
- }
-}
diff --git a/includes/functions/functions_print.php b/includes/functions/functions_print.php
deleted file mode 100644
index 336cf29481..0000000000
--- a/includes/functions/functions_print.php
+++ /dev/null
@@ -1,867 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Controller\SearchController;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeRela;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeStat;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeTemp;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Place;
-use Fisharebest\Webtrees\Session;
-use Fisharebest\Webtrees\Theme;
-use Fisharebest\Webtrees\Tree;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * print the information for an individual chart box
- *
- * find and print a given individuals information for a pedigree chart
- *
- * @param Individual $person The person to print
- * @param int $show_full The style to print the box in, 0 for smaller boxes, 1 for larger boxes
- */
-function print_pedigree_person(Individual $person = null, $show_full = 1) {
-
- switch ($show_full) {
- case 0:
- if ($person) {
- echo Theme::theme()->individualBoxSmall($person);
- } else {
- echo Theme::theme()->individualBoxSmallEmpty();
- }
- break;
- case 1:
- if ($person) {
- echo Theme::theme()->individualBox($person);
- } else {
- echo Theme::theme()->individualBoxEmpty();
- }
- break;
- }
-}
-
-/**
- * print a note record
- *
- * @param string $text
- * @param int $nlevel the level of the note record
- * @param string $nrec the note record to print
- * @param bool $textOnly Don't print the "Note: " introduction
- *
- * @return string
- */
-function print_note_record($text, $nlevel, $nrec, $textOnly = false) {
- global $WT_TREE;
-
- $text .= get_cont($nlevel, $nrec);
-
- // Check if shared note (we have already checked that it exists)
- if (preg_match('/^0 @(' . WT_REGEX_XREF . ')@ NOTE/', $nrec, $match)) {
- $note = Note::getInstance($match[1], $WT_TREE);
- $label = 'SHARED_NOTE';
- // If Census assistant installed, allow it to format the note
- if (Module::getModuleByName('GEDFact_assistant')) {
- $html = CensusAssistantModule::formatCensusNote($note);
- } else {
- $html = Filter::formatText($note->getNote(), $WT_TREE);
- }
- } else {
- $note = null;
- $label = 'NOTE';
- $html = Filter::formatText($text, $WT_TREE);
- }
-
- if ($textOnly) {
- return strip_tags($text);
- }
-
- if (strpos($text, "\n") === false) {
- // A one-line note? strip the block-level tags, so it displays inline
- return GedcomTag::getLabelValue($label, strip_tags($html, '<a><strong><em>'));
- } elseif ($WT_TREE->getPreference('EXPAND_NOTES')) {
- // A multi-line note, and we're expanding notes by default
- return GedcomTag::getLabelValue($label, $html);
- } else {
- // A multi-line note, with an expand/collapse option
- $element_id = Uuid::uuid4();
- // NOTE: class "note-details" is (currently) used only by some third-party themes
- if ($note) {
- $first_line = '<a href="' . $note->getHtmlUrl() . '">' . $note->getFullName() . '</a>';
- } else {
- list($text) = explode("\n", strip_tags($html));
- $first_line = strlen($text) > 100 ? mb_substr($text, 0, 100) . I18N::translate('…') : $text;
- }
-
- return
- '<div class="fact_NOTE"><span class="label">' .
- '<a href="#" onclick="expand_layer(\'' . $element_id . '\'); return false;"><i id="' . $element_id . '_img" class="icon-plus"></i></a> ' . GedcomTag::getLabel($label) . ':</span> ' . '<span id="' . $element_id . '-alt">' . $first_line . '</span>' .
- '</div>' .
- '<div class="note-details" id="' . $element_id . '" style="display:none">' . $html . '</div>';
- }
-}
-
-/**
- * Print all of the notes in this fact record
- *
- * @param string $factrec The factrecord to print the notes from
- * @param int $level The level of the factrecord
- * @param bool $textOnly Don't print the "Note: " introduction
- *
- * @return string HTML
- */
-function print_fact_notes($factrec, $level, $textOnly = false) {
- global $WT_TREE;
-
- $data = '';
- $previous_spos = 0;
- $nlevel = $level + 1;
- $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- $spos1 = strpos($factrec, $match[$j][0], $previous_spos);
- $spos2 = strpos($factrec . "\n$level", "\n$level", $spos1 + 1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $previous_spos = $spos2;
- $nrec = substr($factrec, $spos1, $spos2 - $spos1);
- if (!isset($match[$j][1])) {
- $match[$j][1] = '';
- }
- if (!preg_match('/@(.*)@/', $match[$j][1], $nmatch)) {
- $data .= print_note_record($match[$j][1], $nlevel, $nrec, $textOnly);
- } else {
- $note = Note::getInstance($nmatch[1], $WT_TREE);
- if ($note) {
- if ($note->canShow()) {
- $noterec = $note->getGedcom();
- $nt = preg_match("/0 @$nmatch[1]@ NOTE (.*)/", $noterec, $n1match);
- $data .= print_note_record(($nt > 0) ? $n1match[1] : "", 1, $noterec, $textOnly);
- if (!$textOnly) {
- if (strpos($noterec, '1 SOUR') !== false) {
- $data .= print_fact_sources($noterec, 1);
- }
- }
- }
- } else {
- $data = '<div class="fact_NOTE"><span class="label">' . I18N::translate('Note') . '</span>: <span class="field error">' . $nmatch[1] . '</span></div>';
- }
- }
- if (!$textOnly) {
- if (strpos($factrec, "$nlevel SOUR") !== false) {
- $data .= "<div class=\"indent\">";
- $data .= print_fact_sources($nrec, $nlevel);
- $data .= "</div>";
- }
- }
- }
-
- return $data;
-}
-
-/**
- * Print a link for a popup help window.
- *
- * @param string $help_topic
- * @param string $module
- *
- * @return string
- */
-function help_link($help_topic, $module = '') {
- return '<span class="icon-help" onclick="helpDialog(\'' . $help_topic . '\',\'' . $module . '\'); return false;">&nbsp;</span>';
-}
-
-/**
- * Print an external help link to the wiki site, in a new window
- *
- * @param string $topic
- *
- * @return string
- */
-function wiki_help_link($topic) {
- return '<a class="help icon-wiki" href="' . WT_WEBTREES_WIKI . $topic . '" title="' . I18N::translate('webtrees wiki') . '" target="_blank">&nbsp;</a>';
-}
-
-/**
- * When a user has searched for text, highlight any matches in
- * the displayed string.
- *
- * @param string $string
- *
- * @return string
- */
-function highlight_search_hits($string) {
- global $controller;
-
- if ($controller instanceof SearchController && $controller->query) {
- // TODO: when a search contains multiple words, we search independently.
- // e.g. searching for "FOO BAR" will find records containing both FOO and BAR.
- // However, we only highlight the original search string, not the search terms.
- // The controller needs to provide its "query_terms" array.
- $regex = array();
- foreach (array($controller->query) as $search_term) {
- $regex[] = preg_quote($search_term, '/');
- }
- // Match these strings, provided they do not occur inside HTML tags
- $regex = '(' . implode('|', $regex) . ')(?![^<]*>)';
-
- return preg_replace('/' . $regex . '/i', '<span class="search_hit">$1</span>', $string);
- } else {
- return $string;
- }
-}
-
-/**
- * Print the associations from the associated individuals in $event to the individuals in $record
- *
- * @param Fact $event
- *
- * @return string
- */
-function format_asso_rela_record(Fact $event) {
- $parent = $event->getParent();
- // To whom is this record an assocate?
- if ($parent instanceof Individual) {
- // On an individual page, we just show links to the person
- $associates = array($parent);
- } elseif ($parent instanceof Family) {
- // On a family page, we show links to both spouses
- $associates = $parent->getSpouses();
- } else {
- // On other pages, it does not make sense to show associates
- return '';
- }
-
- preg_match_all('/^1 ASSO @(' . WT_REGEX_XREF . ')@((\n[2-9].*)*)/', $event->getGedcom(), $amatches1, PREG_SET_ORDER);
- preg_match_all('/\n2 _?ASSO @(' . WT_REGEX_XREF . ')@((\n[3-9].*)*)/', $event->getGedcom(), $amatches2, PREG_SET_ORDER);
-
- $html = '';
- // For each ASSO record
- foreach (array_merge($amatches1, $amatches2) as $amatch) {
- $person = Individual::getInstance($amatch[1], $event->getParent()->getTree());
- if ($person && $person->canShowName()) {
- // Is there a "RELA" tag
- if (preg_match('/\n[23] RELA (.+)/', $amatch[2], $rmatch)) {
- // Use the supplied relationship as a label
- $label = GedcomCodeRela::getValue($rmatch[1], $person);
- } else {
- // Use a default label
- $label = GedcomTag::getLabel('ASSO', $person);
- }
-
- $values = array('<a href="' . $person->getHtmlUrl() . '">' . $person->getFullName() . '</a>');
- if (!Auth::isSearchEngine()) {
- foreach ($associates as $associate) {
- $relationship_name = get_associate_relationship_name($associate, $person);
- if (!$relationship_name) {
- $relationship_name = GedcomTag::getLabel('RELA');
- }
-
- if ($parent instanceof Family) {
- // For family ASSO records (e.g. MARR), identify the spouse with a sex icon
- $relationship_name .= $associate->getSexImage();
- }
-
- $values[] = '<a href="relationship.php?pid1=' . $associate->getXref() . '&amp;pid2=' . $person->getXref() . '&amp;ged=' . $associate->getTree()->getNameUrl() . '">' . $relationship_name . '</a>';
- }
- }
- $value = implode(' — ', $values);
-
- // Use same markup as GedcomTag::getLabelValue()
- $asso = I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $label, $value);
- } elseif (!$person && Auth::isEditor($event->getParent()->getTree())) {
- $asso = GedcomTag::getLabelValue('ASSO', '<span class="error">' . $amatch[1] . '</span>');
- } else {
- $asso = '';
- }
- $html .= '<div class="fact_ASSO">' . $asso . '</div>';
- }
-
- return $html;
-}
-
-/**
- * Format age of parents in HTML
- *
- * @param Individual $person child
- * @param Date $birth_date
- *
- * @return string HTML
- */
-function format_parents_age(Individual $person, Date $birth_date) {
- $html = '';
- $families = $person->getChildFamilies();
- // Multiple sets of parents (e.g. adoption) cause complications, so ignore.
- if ($birth_date->isOK() && count($families) == 1) {
- $family = current($families);
- foreach ($family->getSpouses() as $parent) {
- if ($parent->getBirthDate()->isOK()) {
- $sex = $parent->getSexImage();
- $age = Date::getAge($parent->getBirthDate(), $birth_date, 2);
- $deatdate = $parent->getDeathDate();
- switch ($parent->getSex()) {
- case 'F':
- // Highlight mothers who die in childbirth or shortly afterwards
- if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay() + 90) {
- $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
- } else {
- $html .= ' <span title="' . I18N::translate('Mother’s age') . '">' . $sex . $age . '</span>';
- }
- break;
- case 'M':
- // Highlight fathers who die before the birth
- if ($deatdate->isOK() && $deatdate->maximumJulianDay() < $birth_date->minimumJulianDay()) {
- $html .= ' <span title="' . GedcomTag::getLabel('_DEAT_PARE', $parent) . '" class="parentdeath">' . $sex . $age . '</span>';
- } else {
- $html .= ' <span title="' . I18N::translate('Father’s age') . '">' . $sex . $age . '</span>';
- }
- break;
- default:
- $html .= ' <span title="' . I18N::translate('Parent’s age') . '">' . $sex . $age . '</span>';
- break;
- }
- }
- }
- if ($html) {
- $html = '<span class="age">' . $html . '</span>';
- }
- }
-
- return $html;
-}
-
-/**
- * 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
- */
-function format_fact_date(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 && !Auth::isSearchEngine());
- // 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 .= format_parents_age($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') . ' ' . get_age_at_event($age, false) . ')';
- }
- }
- }
- }
- if ($fact != 'DEAT' && Date::compare($date, $death_date) >= 0) {
- // After death, print time since death
- $age = get_age_at_event(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') . ' ' . get_age_at_event($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">' . get_age_at_event($age, false) . '</span>';
- }
- }
-
- return $html;
-}
-
-/**
- * print fact PLACe TEMPle STATus
- *
- * @param Fact $event gedcom fact record
- * @param bool $anchor to print a link to placelist
- * @param bool $sub_records to print place subrecords
- * @param bool $lds to print LDS TEMPle and STATus
- *
- * @return string HTML
- */
-function format_fact_place(Fact $event, $anchor = false, $sub_records = false, $lds = false) {
- if ($anchor) {
- // Show the full place name, for facts/events tab
- $html = '<a href="' . $event->getPlace()->getURL() . '">' . $event->getPlace()->getFullName() . '</a>';
- } else {
- // Abbreviate the place name, for chart boxes
- return ' - ' . $event->getPlace()->getShortName();
- }
-
- if ($sub_records) {
- $placerec = get_sub_record(2, '2 PLAC', $event->getGedcom());
- if (!empty($placerec)) {
- if (preg_match_all('/\n3 (?:_HEB|ROMN) (.+)/', $placerec, $matches)) {
- foreach ($matches[1] as $match) {
- $wt_place = new Place($match, $event->getParent()->getTree());
- $html .= ' - ' . $wt_place->getFullName();
- }
- }
- $map_lati = "";
- $cts = preg_match('/\d LATI (.*)/', $placerec, $match);
- if ($cts > 0) {
- $map_lati = $match[1];
- $html .= '<br><span class="label">' . GedcomTag::getLabel('LATI') . ': </span>' . $map_lati;
- }
- $map_long = '';
- $cts = preg_match('/\d LONG (.*)/', $placerec, $match);
- if ($cts > 0) {
- $map_long = $match[1];
- $html .= ' <span class="label">' . GedcomTag::getLabel('LONG') . ': </span>' . $map_long;
- }
- if ($map_lati && $map_long) {
- $map_lati = trim(strtr($map_lati, "NSEW,�", " - -. ")); // S5,6789 ==> -5.6789
- $map_long = trim(strtr($map_long, "NSEW,�", " - -. ")); // E3.456� ==> 3.456
- $html .= ' <a rel="nofollow" href="https://maps.google.com/maps?q=' . $map_lati . ',' . $map_long . '" class="icon-googlemaps" title="' . I18N::translate('Google Maps™') . '"></a>';
- $html .= ' <a rel="nofollow" href="https://www.bing.com/maps/?lvl=15&cp=' . $map_lati . '~' . $map_long . '" class="icon-bing" title="' . I18N::translate('Bing Maps™') . '"></a>';
- $html .= ' <a rel="nofollow" href="https://www.openstreetmap.org/#map=15/' . $map_lati . '/' . $map_long . '" class="icon-osm" title="' . I18N::translate('OpenStreetMap™') . '"></a>';
- }
- if (preg_match('/\d NOTE (.*)/', $placerec, $match)) {
- $html .= '<br>' . print_fact_notes($placerec, 3);
- }
- }
- }
- if ($lds) {
- if (preg_match('/2 TEMP (.*)/', $event->getGedcom(), $match)) {
- $html .= '<br>' . I18N::translate('LDS temple') . ': ' . GedcomCodeTemp::templeName($match[1]);
- }
- if (preg_match('/2 STAT (.*)/', $event->getGedcom(), $match)) {
- $html .= '<br>' . I18N::translate('Status') . ': ' . GedcomCodeStat::statusName($match[1]);
- if (preg_match('/3 DATE (.*)/', $event->getGedcom(), $match)) {
- $date = new Date($match[1]);
- $html .= ', ' . GedcomTag::getLabel('STAT:DATE') . ': ' . $date->display();
- }
- }
- }
-
- return $html;
-}
-
-/**
- * Check for facts that may exist only once for a certain record type.
- * If the fact already exists in the second array, delete it from the first one.
- *
- * @param string[] $uniquefacts
- * @param Fact[] $recfacts
- * @param string $type
- *
- * @return string[]
- */
-function CheckFactUnique($uniquefacts, $recfacts, $type) {
- foreach ($recfacts as $factarray) {
- $fact = false;
- if (is_object($factarray)) {
- $fact = $factarray->getTag();
- } else {
- if ($type === 'SOUR' || $type === 'REPO') {
- $factrec = $factarray[0];
- }
- if ($type === 'FAM' || $type === 'INDI') {
- $factrec = $factarray[1];
- }
-
- $ft = preg_match("/1 (\w+)(.*)/", $factrec, $match);
- if ($ft > 0) {
- $fact = trim($match[1]);
- }
- }
- if ($fact !== false) {
- $key = array_search($fact, $uniquefacts);
- if ($key !== false) {
- unset($uniquefacts[$key]);
- }
- }
- }
-
- return $uniquefacts;
-}
-
-/**
- * Print a new fact box on details pages
- *
- * @param string $id the id of the person, family, source etc the fact will be added to
- * @param array $usedfacts an array of facts already used in this record
- * @param string $type the type of record INDI, FAM, SOUR etc
- */
-function print_add_new_fact($id, $usedfacts, $type) {
- global $WT_TREE;
-
- // -- Add from clipboard
- if (is_array(Session::get('clipboard'))) {
- $newRow = true;
- foreach (array_reverse(Session::get('clipboard'), true) as $fact_id => $fact) {
- if ($fact["type"] == $type || $fact["type"] == 'all') {
- if ($newRow) {
- $newRow = false;
- echo '<tr><td class="descriptionbox">';
- echo I18N::translate('Add from clipboard'), '</td>';
- echo '<td class="optionbox wrap"><form method="get" name="newFromClipboard" action="?" onsubmit="return false;">';
- echo '<select id="newClipboardFact">';
- }
- echo '<option value="', Filter::escapeHtml($fact_id), '">', GedcomTag::getLabel($fact['fact']);
- // TODO use the event class to store/parse the clipboard events
- if (preg_match('/^2 DATE (.+)/m', $fact['factrec'], $match)) {
- $tmp = new Date($match[1]);
- echo '; ', $tmp->minimumDate()->format('%Y');
- }
- if (preg_match('/^2 PLAC ([^,\n]+)/m', $fact['factrec'], $match)) {
- echo '; ', $match[1];
- }
- echo '</option>';
- }
- }
- if (!$newRow) {
- echo '</select>';
- echo '&nbsp;&nbsp;<input type="button" value="', I18N::translate('Add'), "\" onclick=\"return paste_fact('$id', '#newClipboardFact');\"> ";
- echo '</form></td></tr>', "\n";
- }
- }
-
- // -- Add from pick list
- switch ($type) {
- case "INDI":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('INDI_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "FAM":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('FAM_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "SOUR":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('SOUR_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "NOTE":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('NOTE_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- case "REPO":
- $addfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_ADD'), -1, PREG_SPLIT_NO_EMPTY);
- $uniquefacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_UNIQUE'), -1, PREG_SPLIT_NO_EMPTY);
- $quickfacts = preg_split("/[, ;:]+/", $WT_TREE->getPreference('REPO_FACTS_QUICK'), -1, PREG_SPLIT_NO_EMPTY);
- break;
- default:
- return;
- }
- $addfacts = array_merge(CheckFactUnique($uniquefacts, $usedfacts, $type), $addfacts);
- $quickfacts = array_intersect($quickfacts, $addfacts);
- $translated_addfacts = array();
- foreach ($addfacts as $addfact) {
- $translated_addfacts[$addfact] = GedcomTag::getLabel($addfact);
- }
- uasort($translated_addfacts, function ($x, $y) {
- return I18N::strcasecmp(I18N::translate($x), I18N::translate($y));
- });
- echo '<tr><td class="descriptionbox">';
- echo I18N::translate('Fact or event');
- echo '</td>';
- echo '<td class="optionbox wrap">';
- echo '<form method="get" name="newfactform" action="?" onsubmit="return false;">';
- echo '<select id="newfact" name="newfact">';
- echo '<option value="" disabled selected>' . I18N::translate('&lt;select&gt;') . '</option>';
- foreach ($translated_addfacts as $fact => $fact_name) {
- echo '<option value="', $fact, '">', $fact_name, '</option>';
- }
- if ($type == 'INDI' || $type == 'FAM') {
- echo '<option value="FACT">', I18N::translate('Custom fact'), '</option>';
- echo '<option value="EVEN">', I18N::translate('Custom event'), '</option>';
- }
- echo '</select>';
- echo '<input type="button" value="', I18N::translate('Add'), '" onclick="add_record(\'' . $id . '\', \'newfact\');">';
- echo '<span class="quickfacts">';
- foreach ($quickfacts as $fact) {
- echo '<a href="#" onclick="add_new_record(\'' . $id . '\', \'' . $fact . '\');return false;">', GedcomTag::getLabel($fact), '</a>';
- }
- echo '</span></form>';
- echo '</td></tr>';
-}
-
-/**
- * javascript declaration for calendar popup
- */
-function init_calendar_popup() {
- global $controller;
-
- $controller->addInlineJavascript('
- cal_setMonthNames(
- "' . I18N::translateContext('NOMINATIVE', 'January') . '",
- "' . I18N::translateContext('NOMINATIVE', 'February') . '",
- "' . I18N::translateContext('NOMINATIVE', 'March') . '",
- "' . I18N::translateContext('NOMINATIVE', 'April') . '",
- "' . I18N::translateContext('NOMINATIVE', 'May') . '",
- "' . I18N::translateContext('NOMINATIVE', 'June') . '",
- "' . I18N::translateContext('NOMINATIVE', 'July') . '",
- "' . I18N::translateContext('NOMINATIVE', 'August') . '",
- "' . I18N::translateContext('NOMINATIVE', 'September') . '",
- "' . I18N::translateContext('NOMINATIVE', 'October') . '",
- "' . I18N::translateContext('NOMINATIVE', 'November') . '",
- "' . I18N::translateContext('NOMINATIVE', 'December') . '"
- )
- cal_setDayHeaders(
- "' . I18N::translate('Sun') . '",
- "' . I18N::translate('Mon') . '",
- "' . I18N::translate('Tue') . '",
- "' . I18N::translate('Wed') . '",
- "' . I18N::translate('Thu') . '",
- "' . I18N::translate('Fri') . '",
- "' . I18N::translate('Sat') . '"
- )
- cal_setWeekStart(' . I18N::firstDay() . ');
- ');
-}
-
-/**
- * @param string $element_id
- * @param string $indiname
- * @param Tree $tree
- *
- * @return string
- */
-function print_findindi_link($element_id, $indiname = '', $tree = null) {
- global $WT_TREE;
-
- if ($tree === null) {
- $tree = $WT_TREE;
- }
-
- return '<a href="#" onclick="findIndi(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $indiname . '\'), \'' . $tree->getNameHtml() . '\'); return false;" class="icon-button_indi" title="' . I18N::translate('Find an individual') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findplace_link($element_id) {
- return '<a href="#" onclick="findPlace(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_place" title="' . I18N::translate('Find a place') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findfamily_link($element_id) {
- return '<a href="#" onclick="findFamily(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_family" title="' . I18N::translate('Find a family') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_specialchar_link($element_id) {
- return '<span onclick="findSpecialChar(document.getElementById(\'' . $element_id . '\')); if (window.updatewholename) { updatewholename(); } return false;" class="icon-button_keyboard" title="' . I18N::translate('Find a special character') . '"></span>';
-}
-
-/**
- * @param string $element_id
- * @param string[] $choices
- */
-function print_autopaste_link($element_id, $choices) {
- echo '<small>';
- foreach ($choices as $choice) {
- echo '<span onclick="document.getElementById(\'', $element_id, '\').value=';
- echo '\'', $choice, '\';';
- echo " return false;\">", $choice, '</span> ';
- }
- echo '</small>';
-}
-
-/**
- * @param string $element_id
- * @param string $sourcename
- *
- * @return string
- */
-function print_findsource_link($element_id, $sourcename = '') {
- return '<a href="#" onclick="findSource(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $sourcename . '\'), WT_GEDCOM); return false;" class="icon-button_source" title="' . I18N::translate('Find a source') . '"></a>';
-}
-
-/**
- * @param string $element_id
- * @param string $notename
- *
- * @return string
- */
-function print_findnote_link($element_id, $notename = '') {
- return '<a href="#" onclick="findnote(document.getElementById(\'' . $element_id . '\'), document.getElementById(\'' . $notename . '\'), \'WT_GEDCOM\'); return false;" class="icon-button_find" title="' . I18N::translate('Find a shared note') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findrepository_link($element_id) {
- return '<a href="#" onclick="findRepository(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_repository" title="' . I18N::translate('Find a repository') . '"></a>';
-}
-
-/**
- * @param string $element_id
- * @param string $choose
- *
- * @return string
- */
-function print_findmedia_link($element_id, $choose = '') {
- return '<a href="#" onclick="findMedia(document.getElementById(\'' . $element_id . '\'), \'' . $choose . '\', WT_GEDCOM); return false;" class="icon-button_media" title="' . I18N::translate('Find a media object') . '"></a>';
-}
-
-/**
- * @param string $element_id
- *
- * @return string
- */
-function print_findfact_link($element_id) {
- return '<a href="#" onclick="findFact(document.getElementById(\'' . $element_id . '\'), WT_GEDCOM); return false;" class="icon-button_find_facts" title="' . I18N::translate('Find a fact or event') . '"></a>';
-}
-
-/**
- * Summary of LDS ordinances.
- *
- * @param Individual $individual
- *
- * @return string
- */
-function get_lds_glance(Individual $individual) {
- $BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
- $ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
- $SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
- $SLGS = '_';
-
- foreach ($individual->getSpouseFamilies() as $family) {
- if ($family->getFacts('SLGS')) {
- $SLGS = '';
- }
- }
-
- return $BAPL . $ENDL . $SLGS . $SLGC;
-}
diff --git a/includes/functions/functions_print_facts.php b/includes/functions/functions_print_facts.php
deleted file mode 100644
index 6d66cbe772..0000000000
--- a/includes/functions/functions_print_facts.php
+++ /dev/null
@@ -1,1144 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-use Fisharebest\Webtrees\Auth;
-use Fisharebest\Webtrees\Date;
-use Fisharebest\Webtrees\Fact;
-use Fisharebest\Webtrees\Family;
-use Fisharebest\Webtrees\Filter;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeAdop;
-use Fisharebest\Webtrees\GedcomCode\GedcomCodeQuay;
-use Fisharebest\Webtrees\GedcomRecord;
-use Fisharebest\Webtrees\GedcomTag;
-use Fisharebest\Webtrees\I18N;
-use Fisharebest\Webtrees\Individual;
-use Fisharebest\Webtrees\Media;
-use Fisharebest\Webtrees\Module;
-use Fisharebest\Webtrees\Module\CensusAssistantModule;
-use Fisharebest\Webtrees\Note;
-use Fisharebest\Webtrees\Repository;
-use Fisharebest\Webtrees\Source;
-use Fisharebest\Webtrees\Theme;
-use Fisharebest\Webtrees\User;
-use Rhumsaa\Uuid\Uuid;
-
-/**
- * Print a fact record, for the individual/family/source/repository/etc. pages.
- *
- * Although a Fact has a parent object, we also need to know
- * the GedcomRecord for which we are printing it. For example,
- * we can show the death of X on the page of Y, or the marriage
- * of X+Y on the page of Z. We need to know both records to
- * calculate ages, relationships, etc.
- *
- * @param Fact $fact
- * @param GedcomRecord $record
- */
-function print_fact(Fact $fact, GedcomRecord $record) {
- static $n_chil = 0, $n_gchi = 0;
-
- $parent = $fact->getParent();
-
- // Some facts don't get printed here ...
- switch ($fact->getTag()) {
- case 'NOTE':
- print_main_notes($fact, 1);
-
- return;
- case 'SOUR':
- print_main_sources($fact, 1);
-
- return;
- case 'OBJE':
- print_main_media($fact, 1);
-
- return;
- case 'FAMC':
- case 'FAMS':
- case 'CHIL':
- case 'HUSB':
- case 'WIFE':
- // These are internal links, not facts
- return;
- case '_WT_OBJE_SORT':
- // These links are used internally to record the sort order.
- return;
- default:
- // Hide unrecognized/custom tags?
- if ($fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') && !GedcomTag::isTag($fact->getTag())) {
- return;
- }
- break;
- }
-
- // Who is this fact about? Need it to translate fact label correctly
- if ($parent instanceof Family && $record instanceof Individual) {
- // Family event
- $label_person = $fact->getParent()->getSpouse($record);
- } else {
- // Individual event
- $label_person = $parent;
- }
-
- // New or deleted facts need different styling
- $styleadd = '';
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- }
- if ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- }
-
- // Event of close relative
- if (preg_match('/^_[A-Z_]{3,5}_[A-Z0-9]{4}$/', $fact->getTag())) {
- $styleadd = trim($styleadd . ' rela');
- }
-
- // Event of close associates
- if ($fact->getFactId() == 'asso') {
- $styleadd = trim($styleadd . ' rela');
- }
-
- // historical facts
- if ($fact->getFactId() == 'histo') {
- $styleadd = trim($styleadd . ' histo');
- }
-
- // Does this fact have a type?
- if (preg_match('/\n2 TYPE (.+)/', $fact->getGedcom(), $match)) {
- $type = $match[1];
- } else {
- $type = '';
- }
-
- switch ($fact->getTag()) {
- case 'EVEN':
- case 'FACT':
- if (GedcomTag::isTag($type)) {
- // Some users (just Meliza?) use "1 EVEN/2 TYPE BIRT". Translate the TYPE.
- $label = GedcomTag::getLabel($type, $label_person);
- $type = ''; // Do not print this again
- } elseif ($type) {
- // We don't have a translation for $type - but a custom translation might exist.
- $label = I18N::translate(Filter::escapeHtml($type));
- $type = ''; // Do not print this again
- } else {
- // An unspecified fact/event
- $label = $fact->getLabel();
- }
- break;
- case 'MARR':
- // This is a hack for a proprietory extension. Is it still used/needed?
- $utype = strtoupper($type);
- if ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS') {
- $label = GedcomTag::getLabel('MARR_' . $utype, $label_person);
- $type = ''; // Do not print this again
- } else {
- $label = $fact->getLabel();
- }
- break;
- default:
- // Normal fact/event
- $label = $fact->getLabel();
- break;
- }
-
- echo '<tr class="', $styleadd, '">';
- echo '<td class="descriptionbox width20">';
-
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo Theme::theme()->icon($fact), ' ';
- }
-
- if ($fact->getFactId() != 'histo' && $fact->canEdit()) {
- ?>
- <a
- href="#"
- title="<?php echo I18N::translate('Edit'); ?>"
- onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><?php echo $label; ?></a>
- <div class="editfacts">
- <div class="editlink">
- <a
- href="#"
- title="<?php echo I18N::translate('Edit'); ?>"
- class="editicon"
- onclick="return edit_record('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Edit'); ?></span></a>
- </div>
- <div class="copylink">
- <a
- href="#"
- title="<?php echo I18N::translate('Copy'); ?>"
- class="copyicon"
- onclick="return copy_fact('<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Copy'); ?></span></a>
- </div>
- <div class="deletelink">
- <a
- href="#"
- title="<?php echo I18N::translate('Delete'); ?>"
- class="deleteicon"
- onclick="return delete_fact('<?php echo I18N::translate('Are you sure you want to delete this fact?'); ?>', '<?php echo $parent->getXref(); ?>', '<?php echo $fact->getFactId(); ?>');"
- ><span class="link_text"><?php echo I18N::translate('Delete'); ?></span></a>
- </div>
- </div>
- <?php
- } else {
- echo $label;
- }
-
- switch ($fact->getTag()) {
- case '_BIRT_CHIL':
- echo '<br>', /* I18N: Abbreviation for "number %s" */
- I18N::translate('#%s', ++$n_chil);
- break;
- case '_BIRT_GCHI':
- case '_BIRT_GCH1':
- case '_BIRT_GCH2':
- echo '<br>', I18N::translate('#%s', ++$n_gchi);
- break;
- }
-
- echo '</td><td class="optionbox ', $styleadd, ' wrap">';
-
- // Event from another record?
- if ($parent !== $record) {
- if ($parent instanceof Family) {
- foreach ($parent->getSpouses() as $spouse) {
- if ($record !== $spouse) {
- echo '<a href="', $spouse->getHtmlUrl(), '">', $spouse->getFullName(), '</a> — ';
- }
- }
- echo '<a href="', $parent->getHtmlUrl(), '">', I18N::translate('View family'), '</a><br>';
- } elseif ($parent instanceof Individual) {
- echo '<a href="', $parent->getHtmlUrl(), '">', $parent->getFullName(), '</a><br>';
- }
- }
-
- // Print the value of this fact/event
- switch ($fact->getTag()) {
- case 'ADDR':
- echo $fact->getValue();
- break;
- case 'AFN':
- echo '<div class="field"><a href="https://familysearch.org/search/tree/results#count=20&query=afn:', rawurlencode($fact->getValue()), '" target="new">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'ASSO':
- // we handle this later, in format_asso_rela_record()
- break;
- case 'EMAIL':
- case 'EMAI':
- case '_EMAIL':
- echo '<div class="field"><a href="mailto:', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'FILE':
- if (Auth::isEditor($fact->getParent()->getTree())) {
- echo '<div class="field">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- break;
- case 'RESN':
- echo '<div class="field">';
- switch ($fact->getValue()) {
- case 'none':
- // Note: "1 RESN none" is not valid gedcom.
- // However, webtrees privacy rules will interpret it as "show an otherwise private record to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-class-none"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-confidential-none"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-locked-none"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo Filter::escapeHtml($fact->getValue());
- break;
- }
- echo '</div>';
- break;
- case 'PUBL': // Publication details might contain URLs.
- echo '<div class="field">', Filter::expandUrls($fact->getValue()), '</div>';
- break;
- case 'REPO':
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
- print_repository_record($match[1]);
- } else {
- echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- break;
- case 'URL':
- case '_URL':
- case 'WWW':
- echo '<div class="field"><a href="', Filter::escapeHtml($fact->getValue()), '">', Filter::escapeHtml($fact->getValue()), '</a></div>';
- break;
- case 'TEXT': // 0 SOUR / 1 TEXT
- echo '<div class="field">', nl2br(Filter::escapeHtml($fact->getValue()), false), '</div>';
- break;
- default:
- // Display the value for all other facts/events
- switch ($fact->getValue()) {
- case '':
- // Nothing to display
- break;
- case 'N':
- // Not valid GEDCOM
- echo '<div class="field">', I18N::translate('No'), '</div>';
- break;
- case 'Y':
- // Do not display "Yes".
- break;
- default:
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $fact->getValue(), $match)) {
- $target = GedcomRecord::getInstance($match[1], $fact->getParent()->getTree());
- if ($target) {
- echo '<div><a href="', $target->getHtmlUrl(), '">', $target->getFullName(), '</a></div>';
- } else {
- echo '<div class="error">', Filter::escapeHtml($fact->getValue()), '</div>';
- }
- } else {
- echo '<div class="field"><span dir="auto">', Filter::escapeHtml($fact->getValue()), '</span></div>';
- }
- break;
- }
- break;
- }
-
- // Print the type of this fact/event
- if ($type) {
- $utype = strtoupper($type);
- // Events of close relatives, e.g. _MARR_CHIL
- if (substr($fact->getTag(), 0, 6) == '_MARR_' && ($utype == 'CIVIL' || $utype == 'PARTNERS' || $utype == 'RELIGIOUS')) {
- // Translate MARR/TYPE using the code that supports MARR_CIVIL, etc. tags
- $type = GedcomTag::getLabel('MARR_' . $utype);
- } else {
- // Allow (custom) translations for other types
- $type = I18N::translate($type);
- }
- echo GedcomTag::getLabelValue('TYPE', Filter::escapeHtml($type));
- }
-
- // Print the date of this fact/event
- echo format_fact_date($fact, $record, true, true);
-
- // Print the place of this fact/event
- echo '<div class="place">', format_fact_place($fact, true, true, true), '</div>';
- // A blank line between the primary attributes (value, date, place) and the secondary ones
- echo '<br>';
-
- $addr = $fact->getAttribute('ADDR');
- if ($addr) {
- echo GedcomTag::getLabelValue('ADDR', $addr);
- }
-
- // Print the associates of this fact/event
- echo format_asso_rela_record($fact);
-
- // Print any other "2 XXXX" attributes, in the order in which they appear.
- preg_match_all('/\n2 (' . WT_REGEX_TAG . ') (.+)/', $fact->getGedcom(), $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- switch ($match[1]) {
- case 'DATE':
- case 'TIME':
- case 'AGE':
- case 'PLAC':
- case 'ADDR':
- case 'ALIA':
- case 'ASSO':
- case '_ASSO':
- case 'DESC':
- case 'RELA':
- case 'STAT':
- case 'TEMP':
- case 'TYPE':
- case 'FAMS':
- case 'CONT':
- // These were already shown at the beginning
- break;
- case 'NOTE':
- case 'OBJE':
- case 'SOUR':
- // These will be shown at the end
- break;
- case 'EVEN': // 0 SOUR / 1 DATA / 2 EVEN / 3 DATE / 3 PLAC
- $events = array();
- foreach (preg_split('/ *, */', $match[2]) as $event) {
- $events[] = GedcomTag::getLabel($event);
- }
- if (count($events) == 1) {
- echo GedcomTag::getLabelValue('EVEN', $event);
- } else {
- echo GedcomTag::getLabelValue('EVEN', implode(I18N::$list_separator, $events));
- }
- if (preg_match('/\n3 DATE (.+)/', $fact->getGedcom(), $date_match)) {
- $date = new Date($date_match[1]);
- echo GedcomTag::getLabelValue('DATE', $date->display());
- }
- if (preg_match('/\n3 PLAC (.+)/', $fact->getGedcom(), $plac_match)) {
- echo GedcomTag::getLabelValue('PLAC', $plac_match[1]);
- }
- break;
- case 'FAMC': // 0 INDI / 1 ADOP / 2 FAMC / 3 ADOP
- $family = Family::getInstance(str_replace('@', '', $match[2]), $fact->getParent()->getTree());
- if ($family) {
- echo GedcomTag::getLabelValue('FAM', '<a href="' . $family->getHtmlUrl() . '">' . $family->getFullName() . '</a>');
- if (preg_match('/\n3 ADOP (HUSB|WIFE|BOTH)/', $fact->getGedcom(), $match)) {
- echo GedcomTag::getLabelValue('ADOP', GedcomCodeAdop::getValue($match[1], $label_person));
- }
- } else {
- echo GedcomTag::getLabelValue('FAM', '<span class="error">' . $match[2] . '</span>');
- }
- break;
- case '_WT_USER':
- $user = User::findByIdentifier($match[2]); // may not exist
- if ($user) {
- echo GedcomTag::getLabelValue('_WT_USER', $user->getRealNameHtml());
- } else {
- echo GedcomTag::getLabelValue('_WT_USER', Filter::escapeHtml($match[2]));
- }
- break;
- case 'RESN':
- switch ($match[2]) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-none"></i> ' . I18N::translate('Show to visitors'));
- break;
- case 'privacy':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-privacy"></i> ' . I18N::translate('Show to members'));
- break;
- case 'confidential':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-confidential"></i> ' . I18N::translate('Show to managers'));
- break;
- case 'locked':
- echo GedcomTag::getLabelValue('RESN', '<i class="icon-resn-locked"></i> ' . I18N::translate('Only managers can edit'));
- break;
- default:
- echo GedcomTag::getLabelValue('RESN', Filter::escapeHtml($match[2]));
- break;
- }
- break;
- case 'CALN':
- echo GedcomTag::getLabelValue('CALN', Filter::expandUrls($match[2]));
- break;
- case 'FORM': // 0 OBJE / 1 FILE / 2 FORM / 3 TYPE
- echo GedcomTag::getLabelValue('FORM', $match[2]);
- if (preg_match('/\n3 TYPE (.+)/', $fact->getGedcom(), $type_match)) {
- echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($type_match[1]));
- }
- break;
- case 'URL':
- case '_URL':
- case 'WWW':
- $link = '<a href="' . Filter::escapeHtml($match[2]) . '">' . Filter::escapeHtml($match[2]) . '</a>';
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
- break;
- default:
- if (!$fact->getParent()->getTree()->getPreference('HIDE_GEDCOM_ERRORS') || GedcomTag::isTag($match[1])) {
- if (preg_match('/^@(' . WT_REGEX_XREF . ')@$/', $match[2], $xmatch)) {
- // Links
- $linked_record = GedcomRecord::getInstance($xmatch[1], $fact->getParent()->getTree());
- if ($linked_record) {
- $link = '<a href="' . $linked_record->getHtmlUrl() . '">' . $linked_record->getFullName() . '</a>';
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], $link);
- } else {
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
- }
- } else {
- // Non links
- echo GedcomTag::getLabelValue($fact->getTag() . ':' . $match[1], Filter::escapeHtml($match[2]));
- }
- }
- break;
- }
- }
- echo print_fact_sources($fact->getGedcom(), 2);
- echo print_fact_notes($fact->getGedcom(), 2);
- print_media_links($fact->getGedcom(), 2);
- echo '</td></tr>';
-}
-
-/**
- * print a repository record
- *
- * find and print repository information attached to a source
- *
- * @param string $xref the Gedcom Xref ID of the repository to print
- */
-function print_repository_record($xref) {
- global $WT_TREE;
-
- $repository = Repository::getInstance($xref, $WT_TREE);
- if ($repository && $repository->canShow()) {
- echo '<a class="field" href="', $repository->getHtmlUrl(), '">', $repository->getFullName(), '</a><br>';
- echo '<br>';
- echo print_fact_notes($repository->getGedcom(), 1);
- }
-}
-
-/**
- * print a source linked to a fact (2 SOUR)
- *
- * this function is called by the print_fact function and other functions to
- * print any source information attached to the fact
- *
- * @param string $factrec The fact record to look for sources in
- * @param int $level The level to look for sources at
- *
- * @return string HTML text
- */
-function print_fact_sources($factrec, $level) {
- global $WT_TREE;
-
- $data = '';
- $nlevel = $level + 1;
-
- // -- Systems not using source records [ 1046971 ]
- $ct = preg_match_all("/$level SOUR (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- if (strpos($match[$j][1], '@') === false) {
- $data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">' . Filter::escapeHtml($match[$j][1]) . '</span></div>';
- }
- }
- // -- find source for each fact
- $ct = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
- $spos2 = 0;
- for ($j = 0; $j < $ct; $j++) {
- $sid = $match[$j][1];
- $source = Source::getInstance($sid, $WT_TREE);
- if ($source) {
- if ($source->canShow()) {
- $spos1 = strpos($factrec, "$level SOUR @" . $sid . "@", $spos2);
- $spos2 = strpos($factrec, "\n$level", $spos1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $srec = substr($factrec, $spos1, $spos2 - $spos1);
- $lt = preg_match_all("/$nlevel \w+/", $srec, $matches);
- $data .= '<div class="fact_SOUR">';
- $data .= '<span class="label">';
- $elementID = Uuid::uuid4();
- if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
- $plusminus = 'icon-minus';
- } else {
- $plusminus = 'icon-plus';
- }
- if ($lt > 0) {
- $data .= '<a href="#" onclick="return expand_layer(\'' . $elementID . '\');"><i id="' . $elementID . '_img" class="' . $plusminus . '"></i></a> ';
- }
- $data .= I18N::translate('Source') . ':</span> <span class="field">';
- $data .= '<a href="' . $source->getHtmlUrl() . '">' . $source->getFullName() . '</a>';
- $data .= '</span></div>';
-
- $data .= "<div id=\"$elementID\"";
- if ($WT_TREE->getPreference('EXPAND_SOURCES')) {
- $data .= ' style="display:block"';
- }
- $data .= ' class="source_citations">';
- // PUBL
- $publ = $source->getFirstFact('PUBL');
- if ($publ) {
- $data .= GedcomTag::getLabelValue('PUBL', $publ->getValue());
- }
- $data .= printSourceStructure(getSourceStructure($srec));
- $data .= '<div class="indent">';
- ob_start();
- print_media_links($srec, $nlevel);
- $data .= ob_get_clean();
- $data .= print_fact_notes($srec, $nlevel, false);
- $data .= '</div>';
- $data .= '</div>';
- } else {
- // Here we could show that we do actually have sources for this data,
- // but not the details. For example “Sources: ”.
- // But not by default, based on user feedback.
- // http://webtrees.net/index.php/en/forum/3-help-for-beta-and-svn-versions/27002-source-media-privacy-issue
- }
- } else {
- $data .= GedcomTag::getLabelValue('SOUR', '<span class="error">' . $sid . '</span>');
- }
- }
-
- return $data;
-}
-
-/**
- * Print the links to media objects
- *
- * @param string $factrec
- * @param int $level
- */
-function print_media_links($factrec, $level) {
- global $WT_TREE;
-
- $nlevel = $level + 1;
- if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) == 0) {
- return;
- }
- $objectNum = 0;
- while ($objectNum < count($omatch)) {
- $media_id = $omatch[$objectNum][1];
- $media = Media::getInstance($media_id, $WT_TREE);
- if ($media) {
- if ($media->canShow()) {
- if ($objectNum > 0) {
- echo '<br class="media-separator" style="clear:both;">';
- }
- echo '<div class="media-display"><div class="media-display-image">';
- echo $media->displayImage();
- echo '</div>';
- echo '<div class="media-display-title">';
- echo '<a href="', $media->getHtmlUrl(), '">', $media->getFullName(), '</a>';
- // NOTE: echo the notes of the media
- echo '<p>';
- echo print_fact_notes($media->getGedcom(), 1);
- $ttype = preg_match("/" . ($nlevel + 1) . " TYPE (.*)/", $media->getGedcom(), $match);
- if ($ttype > 0) {
- $mediaType = GedcomTag::getFileFormTypeValue($match[1]);
- echo '<p class="label">', I18N::translate('Type'), ': </span> <span class="field">', $mediaType, '</p>';
- }
- echo '</p>';
- //-- print spouse name for marriage events
- $ct = preg_match("/WT_SPOUSE: (.*)/", $factrec, $match);
- if ($ct > 0) {
- $spouse = Individual::getInstance($match[1], $media->getTree());
- if ($spouse) {
- echo '<a href="', $spouse->getHtmlUrl(), '">';
- echo $spouse->getFullName();
- echo '</a>';
- }
- $ct = preg_match("/WT_FAMILY_ID: (.*)/", $factrec, $match);
- if ($ct > 0) {
- $famid = trim($match[1]);
- $family = Family::getInstance($famid, $spouse->getTree());
- if ($family) {
- if ($spouse) {
- echo " - ";
- }
- echo '<a href="', $family->getHtmlUrl(), '">', I18N::translate('View family'), '</a>';
- }
- }
- }
- echo print_fact_notes($media->getGedcom(), $nlevel);
- echo print_fact_sources($media->getGedcom(), $nlevel);
- echo '</div>'; //close div "media-display-title"
- echo '</div>'; //close div "media-display"
- }
- } elseif (!$WT_TREE->getPreference('HIDE_GEDCOM_ERRORS')) {
- echo '<p class="ui-state-error">', $media_id, '</p>';
- }
- $objectNum++;
- }
-}
-
-/**
- * Print a row for the sources tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_sources(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $fact_id = $fact->getFactId();
- $parent = $fact->getParent();
- $pid = $parent->getXref();
-
- $nlevel = $level + 1;
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- // -- find source for each fact
- $ct = preg_match_all("/($level SOUR (.+))/", $factrec, $match, PREG_SET_ORDER);
- $spos2 = 0;
- for ($j = 0; $j < $ct; $j++) {
- $sid = trim($match[$j][2], '@');
- $spos1 = strpos($factrec, $match[$j][1], $spos2);
- $spos2 = strpos($factrec, "\n$level", $spos1);
- if (!$spos2) {
- $spos2 = strlen($factrec);
- }
- $srec = substr($factrec, $spos1, $spos2 - $spos1);
- $source = Source::getInstance($sid, $fact->getParent()->getTree());
- // Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
- if (!$source || $source->canShow()) {
- if ($level > 1) {
- echo '<tr class="row_sour2">';
- } else {
- echo '<tr>';
- }
- echo '<td class="descriptionbox';
- if ($level > 1) {
- echo ' rela';
- }
- echo ' ', $styleadd, ' width20">';
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = trim($ematch[1]);
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($can_edit) {
- echo "<a onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"", I18N::translate('Edit'), '">';
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- if ($level == 1) {
- echo '<i class="icon-source"></i> ';
- }
- }
- echo GedcomTag::getLabel($factname, $parent), '</a>';
- echo '<div class="editfacts">';
- if (preg_match('/^@.+@$/', $match[$j][2])) {
- // Inline sources can't be edited. Attempting to save one will convert it
- // into a link, and delete it.
- // e.g. "1 SOUR my source" becomes "1 SOUR @my source@" which does not exist.
- echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- }
- echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
- echo '</div>';
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- echo '</td>';
- echo '<td class="optionbox ', $styleadd, ' wrap">';
- if ($source) {
- echo '<a href="', $source->getHtmlUrl(), '">', $source->getFullName(), '</a>';
- // PUBL
- $publ = $source->getFirstFact('PUBL');
- if ($publ) {
- echo GedcomTag::getLabelValue('PUBL', $publ->getValue());
- }
- // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
- if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
- foreach ($rmatches[1] as $rmatch) {
- echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
- switch ($rmatch) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo $rmatch;
- break;
- }
- echo '</span>';
- }
- }
- $cs = preg_match("/$nlevel EVEN (.*)/", $srec, $cmatch);
- if ($cs > 0) {
- echo '<br><span class="label">', GedcomTag::getLabel('EVEN'), ' </span><span class="field">', $cmatch[1], '</span>';
- $cs = preg_match("/" . ($nlevel + 1) . " ROLE (.*)/", $srec, $cmatch);
- if ($cs > 0) {
- echo '<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="label">', GedcomTag::getLabel('ROLE'), ' </span><span class="field">', $cmatch[1], '</span>';
- }
- }
- echo printSourceStructure(getSourceStructure($srec));
- echo '<div class="indent">';
- print_media_links($srec, $nlevel);
- if ($nlevel == 2) {
- print_media_links($source->getGedcom(), 1);
- }
- echo print_fact_notes($srec, $nlevel);
- if ($nlevel == 2) {
- echo print_fact_notes($source->getGedcom(), 1);
- }
- echo '</div>';
- } else {
- echo $sid;
- }
- echo '</td></tr>';
- }
- }
-}
-
-/**
- * Print SOUR structure
- * This function prints the input array of SOUR sub-records built by the
- * getSourceStructure() function.
- *
- * @param string[] $textSOUR
- *
- * @return string
- */
-function printSourceStructure($textSOUR) {
- global $WT_TREE;
- $html = '';
-
- if ($textSOUR['PAGE']) {
- $html .= GedcomTag::getLabelValue('PAGE', Filter::expandUrls($textSOUR['PAGE']));
- }
-
- if ($textSOUR['EVEN']) {
- $html .= GedcomTag::getLabelValue('EVEN', Filter::escapeHtml($textSOUR['EVEN']));
- if ($textSOUR['ROLE']) {
- $html .= GedcomTag::getLabelValue('ROLE', Filter::escapeHtml($textSOUR['ROLE']));
- }
- }
-
- if ($textSOUR['DATE'] || count($textSOUR['TEXT'])) {
- if ($textSOUR['DATE']) {
- $date = new Date($textSOUR['DATE']);
- $html .= GedcomTag::getLabelValue('DATA:DATE', $date->display());
- }
- foreach ($textSOUR['TEXT'] as $text) {
- $html .= GedcomTag::getLabelValue('TEXT', Filter::formatText($text, $WT_TREE));
- }
- }
-
- if ($textSOUR['QUAY'] != '') {
- $html .= GedcomTag::getLabelValue('QUAY', GedcomCodeQuay::getValue($textSOUR['QUAY']));
- }
-
- return '<div class="indent">' . $html . '</div>';
-}
-
-/**
- * Extract SOUR structure from the incoming Source sub-record
- * The output array is defined as follows:
- * $textSOUR['PAGE'] = Source citation
- * $textSOUR['EVEN'] = Event type
- * $textSOUR['ROLE'] = Role in event
- * $textSOUR['DATA'] = place holder (no text in this sub-record)
- * $textSOUR['DATE'] = Entry recording date
- * $textSOUR['TEXT'] = (array) Text from source
- * $textSOUR['QUAY'] = Certainty assessment
- *
- * @param string $srec
- *
- * @return string[]
- */
-function getSourceStructure($srec) {
- // Set up the output array
- $textSOUR = array(
- 'PAGE' => '',
- 'EVEN' => '',
- 'ROLE' => '',
- 'DATA' => '',
- 'DATE' => '',
- 'TEXT' => array(),
- 'QUAY' => '',
- );
-
- if ($srec) {
- $subrecords = explode("\n", $srec);
- for ($i = 0; $i < count($subrecords); $i++) {
- $tag = substr($subrecords[$i], 2, 4);
- $text = substr($subrecords[$i], 7);
- $i++;
- for (; $i < count($subrecords); $i++) {
- $nextTag = substr($subrecords[$i], 2, 4);
- if ($nextTag != 'CONT') {
- $i--;
- break;
- }
- if ($nextTag == 'CONT') {
- $text .= "\n";
- }
- $text .= rtrim(substr($subrecords[$i], 7));
- }
- if ($tag == 'TEXT') {
- $textSOUR[$tag][] = $text;
- } else {
- $textSOUR[$tag] = $text;
- }
- }
- }
-
- return $textSOUR;
-}
-
-/**
- * Print a row for the notes tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_notes(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $fact_id = $fact->getFactId();
- $parent = $fact->getParent();
- $pid = $parent->getXref();
-
- if ($fact->isPendingAddition()) {
- $styleadd = ' new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = ' old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- $ct = preg_match_all("/$level NOTE (.*)/", $factrec, $match, PREG_SET_ORDER);
- for ($j = 0; $j < $ct; $j++) {
- // Note object, or inline note?
- if (preg_match("/$level NOTE @(.*)@/", $match[$j][0], $nmatch)) {
- $note = Note::getInstance($nmatch[1], $fact->getParent()->getTree());
- if ($note && !$note->canShow()) {
- continue;
- }
- } else {
- $note = null;
- }
-
- if ($level >= 2) {
- echo '<tr class="row_note2"><td class="descriptionbox rela ', $styleadd, ' width20">';
- } else {
- echo '<tr><td class="descriptionbox ', $styleadd, ' width20">';
- }
- if ($can_edit) {
- echo '<a onclick="return edit_record(\'', $pid, '\', \'', $fact_id, '\');" href="#" title="', I18N::translate('Edit'), '">';
- if ($level < 2) {
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo '<i class="icon-note"></i> ';
- }
- if ($note) {
- echo GedcomTag::getLabel('SHARED_NOTE');
- } else {
- echo GedcomTag::getLabel('NOTE');
- }
- echo '</a>';
- echo '<div class="editfacts">';
- echo "<div class=\"editlink\"><a class=\"editicon\" onclick=\"return edit_record('$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Edit') . "\"><span class=\"link_text\">" . I18N::translate('Edit') . "</span></a></div>";
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="return copy_fact(\'', $pid, '\', \'', $fact_id, '\');" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- echo "<div class=\"deletelink\"><a class=\"deleteicon\" onclick=\"return delete_fact('" . I18N::translate('Are you sure you want to delete this fact?') . "', '$pid', '$fact_id');\" href=\"#\" title=\"" . I18N::translate('Delete') . "\"><span class=\"link_text\">" . I18N::translate('Delete') . "</span></a></div>";
- if ($note) {
- echo '<a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a>';
- }
- echo '</div>';
- }
- } else {
- if ($level < 2) {
- if ($fact->getParent()->getTree()->getPreference('SHOW_FACT_ICONS')) {
- echo '<i class="icon-note"></i> ';
- }
- if ($note) {
- echo GedcomTag::getLabel('SHARED_NOTE');
- } else {
- echo GedcomTag::getLabel('NOTE');
- }
- }
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 NOTE ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- $parent = GedcomRecord::getInstance($pid, $fact->getParent()->getTree());
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = trim($ematch[1]);
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($factname != 'NOTE') {
- // Note is already printed
- echo GedcomTag::getLabel($factname, $parent);
- if ($note) {
- echo '<div class="editfacts"><a class="icon-note" href="', $note->getHtmlUrl(), '" title="' . I18N::translate('View') . '"><span class="link_text">' . I18N::translate('View') . '</span></a></div>';
-
- }
- }
- }
- echo '</td>';
- if ($note) {
- // Note objects
- if (Module::getModuleByName('GEDFact_assistant')) {
- // If Census assistant installed, allow it to format the note
- $text = CensusAssistantModule::formatCensusNote($note);
- } else {
- $text = Filter::formatText($note->getNote(), $fact->getParent()->getTree());
- }
- } else {
- // Inline notes
- $nrec = get_sub_record($level, "$level NOTE", $factrec, $j + 1);
- $text = $match[$j][1] . get_cont($level + 1, $nrec);
- $text = Filter::formatText($text, $fact->getParent()->getTree());
- }
-
- echo '<td class="optionbox', $styleadd, ' wrap">';
- echo $text;
-
- if (!empty($noterec)) {
- echo print_fact_sources($noterec, 1);
- }
-
- // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
- if (preg_match_all("/\n2 RESN (.+)/", $factrec, $matches)) {
- foreach ($matches[1] as $match) {
- echo '<br><span class="label">', GedcomTag::getLabel('RESN'), ':</span> <span class="field">';
- switch ($match) {
- case 'none':
- // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
- // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
- echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
- break;
- case 'privacy':
- echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
- break;
- case 'confidential':
- echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
- break;
- case 'locked':
- echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
- break;
- default:
- echo $match;
- break;
- }
- echo '</span>';
- }
- }
- echo '</td></tr>';
- }
-}
-
-/**
- * Print a row for the media tab on the individual page.
- *
- * @param Fact $fact
- * @param int $level
- */
-function print_main_media(Fact $fact, $level) {
- $factrec = $fact->getGedcom();
- $parent = $fact->getParent();
-
- if ($fact->isPendingAddition()) {
- $styleadd = 'new';
- $can_edit = $level == 1 && $fact->canEdit();
- } elseif ($fact->isPendingDeletion()) {
- $styleadd = 'old';
- $can_edit = false;
- } else {
- $styleadd = '';
- $can_edit = $level == 1 && $fact->canEdit();
- }
-
- // -- find source for each fact
- preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $factrec, $matches);
- foreach ($matches[1] as $xref) {
- $media = Media::getInstance($xref, $fact->getParent()->getTree());
- // Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
- if (!$media || $media->canShow()) {
- if ($level > 1) {
- echo '<tr class="row_obje2">';
- } else {
- echo '<tr>';
- }
- echo '<td class="descriptionbox';
- if ($level > 1) {
- echo ' rela';
- }
- echo ' ', $styleadd, ' width20">';
- preg_match("/^\d (\w*)/", $factrec, $factname);
- $factlines = explode("\n", $factrec); // 1 BIRT Y\n2 SOUR ...
- $factwords = explode(" ", $factlines[0]); // 1 BIRT Y
- $factname = $factwords[1]; // BIRT
- if ($factname == 'EVEN' || $factname == 'FACT') {
- // Add ' EVEN' to provide sensible output for an event with an empty TYPE record
- $ct = preg_match("/2 TYPE (.*)/", $factrec, $ematch);
- if ($ct > 0) {
- $factname = $ematch[1];
- echo $factname;
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- } elseif ($can_edit) {
- echo '<a onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '">';
- echo GedcomTag::getLabel($factname, $parent), '</a>';
- echo '<div class="editfacts">';
- echo '<div class="editlink"><a class="editicon" onclick="window.open(\'addmedia.php?action=editmedia&amp;pid=', $media->getXref(), '\', \'_blank\', edit_window_specs); return false;" href="#" title="', I18N::translate('Edit'), '"><span class="link_text">', I18N::translate('Edit'), '</span></a></div>';
- echo '<div class="copylink"><a class="copyicon" href="#" onclick="jQuery.post(\'action.php\',{action:\'copy-fact\', type:\'\', factgedcom:\'' . rawurlencode($factrec) . '\'},function(){location.reload();})" title="' . I18N::translate('Copy') . '"><span class="link_text">' . I18N::translate('Copy') . '</span></a></div>';
- echo '<div class="deletelink"><a class="deleteicon" onclick="return delete_fact(\'', I18N::translate('Are you sure you want to delete this fact?'), '\', \'', $parent->getXref(), '\', \'', $fact->getFactId(), '\');" href="#" title="', I18N::translate('Delete'), '"><span class="link_text">', I18N::translate('Delete'), '</span></a></div>';
- echo '</div>';
- } else {
- echo GedcomTag::getLabel($factname, $parent);
- }
- echo '</td>';
- echo '<td class="optionbox ', $styleadd, ' wrap">';
- if ($media) {
- echo '<span class="field">';
- echo $media->displayImage();
- echo '<a href="' . $media->getHtmlUrl() . '">';
- echo '<em>';
- foreach ($media->getAllNames() as $name) {
- if ($name['type'] != 'TITL') {
- echo '<br>';
- }
- echo $name['full'];
- }
- echo '</em>';
- echo '</a>';
- echo '</span>';
-
- echo GedcomTag::getLabelValue('FORM', $media->mimeType());
- $imgsize = $media->getImageAttributes('main');
- if (!empty($imgsize['WxH'])) {
- echo GedcomTag::getLabelValue('__IMAGE_SIZE__', $imgsize['WxH']);
- }
- if ($media->getFilesizeraw() > 0) {
- echo GedcomTag::getLabelValue('__FILE_SIZE__', $media->getFilesize());
- }
- $mediatype = $media->getMediaType();
- if ($mediatype) {
- echo GedcomTag::getLabelValue('TYPE', GedcomTag::getFileFormTypeValue($mediatype));
- }
-
- switch ($media->isPrimary()) {
- case 'Y':
- echo GedcomTag::getLabelValue('_PRIM', I18N::translate('yes'));
- break;
- case 'N':
- echo GedcomTag::getLabelValue('_PRIM', I18N::translate('no'));
- break;
- }
- echo print_fact_notes($media->getGedcom(), 1);
- echo print_fact_sources($media->getGedcom(), 1);
- } else {
- echo $xref;
- }
- echo '</td></tr>';
- }
- }
-}
diff --git a/includes/functions/functions_print_lists.php b/includes/functions/functions_print_lists.php
deleted file mode 100644
index 376e581b6e..0000000000
--- a/includes/functions/functions_print_lists.php
+++ /dev/null
@@ -1,2289 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-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 (a<b) ? -1 : (a>b ? 1 : 0);};
- jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (a<b ? 1 : 0);};
- jQuery("#' . $table_id . '").dataTable( {
- dom: \'<"H"<"filtersH_' . $table_id . '">T<"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 = '
- <div class="loading-image">&nbsp;</div>
- <div class="indi-list">
- <table id="' . $table_id . '">
- <thead>
- <tr>
- <th colspan="24">
- <div class="btn-toolbar">
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="M"
- title="' . I18N::translate('Show only males.') . '"
- type="button"
- >
- ' . Individual::sexImage('M', 'large') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="F"
- title="' . I18N::translate('Show only females.') . '"
- type="button"
- >
- ' . Individual::sexImage('F', 'large') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="20"
- data-filter-value="U"
- title="' . I18N::translate('Show only individuals for whom the gender is not known.') . '"
- type="button"
- >
- ' . Individual::sexImage('U', 'large') . '
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="N"
- title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
- type="button"
- >
- ' . I18N::translate('Alive') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="Y"
- title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '"
- type="button"
- >
- ' . I18N::translate('Dead') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="YES"
- title="' . I18N::translate('Show individuals who died more than 100 years ago.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('DEAT') . '&gt;100
- </button>
- <button
- class="ui-state-default"
- data-filter-column="22"
- data-filter-value="Y100"
- title="' . I18N::translate('Show individuals who died within the last 100 years.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('DEAT') . '&lt;=100
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="21"
- data-filter-value="YES"
- title="' . I18N::translate('Show individuals born more than 100 years ago.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('BIRT') . '&gt;100
- </button>
- <button
- class="ui-state-default"
- data-filter-column="21"
- data-filter-value="Y100"
- title="' . I18N::translate('Show individuals born within the last 100 years.') . '"
- type="button"
- >
- ' . GedcomTag::getLabel('BIRT') . '&lt;=100
- </button>
- </div>
- <div class="btn-group">
- <button
- class="ui-state-default"
- data-filter-column="23"
- data-filter-value="R"
- 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.') . '"
- type="button"
- >
- ' . I18N::translate('Roots') . '
- </button>
- <button
- class="ui-state-default"
- data-filter-column="23"
- data-filter-value="L"
- title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
- type="button"
- >
- ' . I18N::translate('Leaves') . '
- </button>
- </div>
- </div>
- </th>
- </tr>
- <tr>
- <th>' . GedcomTag::getLabel('GIVN') . '</th>
- <th>' . GedcomTag::getLabel('SURN') . '</th>
- <th>GIVN</th>
- <th>SURN</th>
- <th>' . /* I18N: Abbreviation for “Sosa-Stradonitz number”. This is an individual’s surname, so may need transliterating into non-latin alphabets. */ I18N::translate('Sosa') . '</th>
- <th>SOSA</th>
- <th>' . GedcomTag::getLabel('BIRT') . '</th>
- <th>SORT_BIRT</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('DEAT') . '</th>
- <th>SORT_DEAT</th>
- <th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
- <th>' . GedcomTag::getLabel('AGE') . '</th>
- <th>AGE</th>
- <th>' . GedcomTag::getLabel('PLAC') . '</th>
- <th>' . GedcomTag::getLabel('CHAN') . '</th>
- <th>CHAN</th>
- <th>SEX</th>
- <th>BIRT</th>
- <th>DEAT</th>
- <th>TREE</th>
- </tr>
- </thead>
- <tfoot>
- <tr>
- <th colspan="24">
- <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
- $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 .= '<tr' . $class . '>';
- //-- Indi name(s)
- $html .= '<td colspan="2">';
- 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 .= '<a ' . $title . ' href="' . $person->getHtmlUrl() . '"' . $class . '>' . highlight_search_hits($name['full']) . '</a>' . $sex_image . '<br>';
- }
- // Indi parents
- $html .= $person->getPrimaryParentsNames('parents details1', 'none');
- $html .= '</td>';
- // Dummy column to match colspan in header
- $html .= '<td style="display:none;"></td>';
- //-- 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 .= '<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>';
- //-- SOSA
- if ($option == 'sosa') {
- $html .= '<td><a href="relationship.php?pid1=' . $datalist[1] . '&amp;pid2=' . $person->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a></td><td>' . $key . '</td>';
- } else {
- $html .= '<td></td><td>0</td>';
- }
- //-- Birth date
- $html .= '<td>';
- if ($birth_dates = $person->getAllBirthDates()) {
- foreach ($birth_dates as $num => $birth_date) {
- if ($num) {
- $html .= '<br>';
- }
- $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 .= '&nbsp;';
- }
- $birth_dates[0] = new Date('');
- }
- $html .= '</td>';
- //-- Event date (sortable)hidden by datatables code
- $html .= '<td>' . $birth_date->julianDay() . '</td>';
- //-- Birth anniversary
- $html .= '<td>' . Date::getAge($birth_dates[0], null, 2) . '</td>';
- //-- Birth place
- $html .= '<td>';
- foreach ($person->getAllBirthPlaces() as $n => $birth_place) {
- $tmp = new Place($birth_place, $person->getTree());
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
- $html .= highlight_search_hits($tmp->getShortName()) . '</a>';
- }
- $html .= '</td>';
- //-- Number of children
- $nchi = $person->getNumberOfChildren();
- $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>';
- //-- Death date
- $html .= '<td>';
- if ($death_dates = $person->getAllDeathDates()) {
- foreach ($death_dates as $num => $death_date) {
- if ($num) {
- $html .= '<br>';
- }
- $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 .= '&nbsp;';
- }
- $death_dates[0] = new Date('');
- }
- $html .= '</td>';
- //-- Event date (sortable)hidden by datatables code
- $html .= '<td>' . $death_date->julianDay() . '</td>';
- //-- Death anniversary
- $html .= '<td>' . Date::getAge($death_dates[0], null, 2) . '</td>';
- //-- 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 .= '<td>' . Date::getAge($birth_dates[0], $death_dates[0], 2) . '</td><td>' . Date::getAge($birth_dates[0], $death_dates[0], 1) . '</td>';
- //-- Death place
- $html .= '<td>';
- foreach ($person->getAllDeathPlaces() as $n => $death_place) {
- $tmp = new Place($death_place, $person->getTree());
- if ($n) {
- $html .= '<br>';
- }
- $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
- $html .= highlight_search_hits($tmp->getShortName()) . '</a>';
- }
- $html .= '</td>';
- //-- Last change
- $html .= '<td>' . $person->lastChangeTimestamp() . '</td>';
- $html .= '<td>' . $person->lastChangeTimestamp(true) . '</td>';
- //-- Sorting by gender
- $html .= '<td>' . $person->getSex() . '</td>';
- //-- Filtering by birth date
- $html .= '<td>';
- if (!$person->canShow() || Date::compare($birth_date, $d100y) > 0) {
- $html .= 'Y100';
- } else {
- $html .= 'YES';
- }
- $html .= '</td>';
- //-- Filtering by death date
- $html .= '<td>';
- // 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 .= '</td>';
- //-- Roots or Leaves ?
- $html .= '<td>';
- if (!$person->getChildFamilies()) {
- $html .= 'R';
- } // roots
- elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) {
- $html .= 'L';
- } // leaves
- else {
- $html .= '&nbsp;';
- }
- $html .= '</td>';
- $html .= '</tr>';
- $unique_indis[$person->getXref()] = true;
- }
- $html .= '
- </tbody>
- </table>
- <div id="indi_list_table-charts_' . $table_id . '" style="display:none">
- <table class="list-charts">
- <tr>
- <td>
- ' . print_chart_by_decade($birt_by_decade, I18N::translate('Decade of birth')) . '
- </td>
- <td>
- ' . print_chart_by_decade($deat_by_decade, I18N::translate('Decade of death')) . '
- </td>
- </tr>
- <tr>
- <td colspan="2">
- ' . print_chart_by_age($deat_by_age, I18N::translate('Age related to death year')) . '
- </td>
- </tr>
- </table>
- </div>
- </div>';
-
- 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 = '
- <div class="loading-image">&nbsp;</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') . '&gt;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') . '&lt;=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 . '>' . highlight_search_hits($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 . '>' . highlight_search_hits($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 .= '&nbsp;';
- }
- $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 .= highlight_search_hits($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 .= '&nbsp;';
- }
- $html .= '</td>
- </tr>';
- }
- $html .= '
- </tbody>
- </table>
- <div id="fam_list_table-charts_' . $table_id . '" style="display:none">
- <table class="list-charts">
- <tr>
- <td>
- ' . print_chart_by_decade($birt_by_decade, I18N::translate('Decade of birth')) . '
- </td>
- <td>
- ' . print_chart_by_decade($marr_by_decade, I18N::translate('Decade of marriage')) . '
- </td>
- </tr>
- <tr>
- <td colspan="2">
- ' . print_chart_by_age($marr_by_age, I18N::translate('Age in year of marriage')) . '
- </td>
- </tr>
- </table>
- </div>
- </div>';
-
- 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 .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="source-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>TITL</th>';
- $html .= '<th>' . GedcomTag::getLabel('AUTH') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Media objects') . '</th>';
- $html .= '<th>#OBJE</th>';
- $html .= '<th>' . I18N::translate('Shared notes') . '</th>';
- $html .= '<th>#NOTE</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- foreach ($datalist as $source) {
- if (!$source->canShow()) {
- continue;
- }
- if ($source->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($source->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Source name(s)
- $html .= '<td>';
- foreach ($source->getAllNames() as $n => $name) {
- if ($n) {
- $html .= '<br>';
- }
- if ($n == $source->getPrimaryName()) {
- $html .= '<a class="name2" href="' . $source->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- } else {
- $html .= '<a href="' . $source->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- }
- }
- $html .= '</td>';
- // Sortable name
- $html .= '<td>' . strip_tags($source->getFullName()) . '</td>';
- //-- Author
- $auth = $source->getFirstFact('AUTH');
- if ($auth) {
- $author = $auth->getValue();
- } else {
- $author = '';
- }
- $html .= '<td>' . highlight_search_hits($author) . '</td>';
- $key = $source->getXref() . '@' . $source->getTree()->getTreeId();
- //-- Linked INDIs
- $num = array_key_exists($key, $count_individuals) ? $count_individuals[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = array_key_exists($key, $count_families) ? $count_families[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked OBJEcts
- $num = array_key_exists($key, $count_media) ? $count_media[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked NOTEs
- $num = array_key_exists($key, $count_notes) ? $count_notes[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $source->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $source->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_source(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($source->getFullName()))) . "', '" . $source->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- 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 .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="note-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Media objects') . '</th>';
- $html .= '<th>#OBJE</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
- foreach ($datalist as $note) {
- if (!$note->canShow()) {
- continue;
- }
- if ($note->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($note->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Shared Note name
- $html .= '<td><a class="name2" href="' . $note->getHtmlUrl() . '">' . highlight_search_hits($note->getFullName()) . '</a></td>';
- //-- Linked INDIs
- $num = count($note->linkedIndividuals('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = count($note->linkedfamilies('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked OBJEcts
- $num = count($note->linkedMedia('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked SOURs
- $num = count($note->linkedSources('NOTE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $note->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $note->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_note(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($note->getFullName()))) . "', '" . $note->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- 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 .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="repo-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . I18N::translate('Repository name') . '</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '<th></th>'; //delete
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- foreach ($repositories as $repository) {
- if (!$repository->canShow()) {
- continue;
- }
- if ($repository->isPendingAddtion()) {
- $class = ' class="new"';
- } elseif ($repository->isPendingDeletion()) {
- $class = ' class="old"';
- } else {
- $class = '';
- }
- $html .= '<tr' . $class . '>';
- //-- Repository name(s)
- $html .= '<td>';
- foreach ($repository->getAllNames() as $n => $name) {
- if ($n) {
- $html .= '<br>';
- }
- if ($n == $repository->getPrimaryName()) {
- $html .= '<a class="name2" href="' . $repository->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- } else {
- $html .= '<a href="' . $repository->getHtmlUrl() . '">' . highlight_search_hits($name['full']) . '</a>';
- }
- }
- $html .= '</td>';
- $key = $repository->getXref() . '@' . $repository->getTree()->getTreeId();
- //-- Linked SOURces
- $num = array_key_exists($key, $count_sources) ? $count_sources[$key] : 0;
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $repository->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $repository->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Delete
- if (Auth::isManager($WT_TREE)) {
- $html .= '<td><div title="' . I18N::translate('Delete') . '" class="deleteicon" onclick="return delete_repository(\'' . I18N::translate('Are you sure you want to delete “%s”?', Filter::escapeJs(Filter::unescapeHtml($repository->getFullName()))) . "', '" . $repository->getXref() . '\');"><span class="link_text">' . I18N::translate('Delete') . '</span></div></td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- $html .= '</tbody></table></div>';
-
- 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 .= '<div class="loading-image">&nbsp;</div>';
- $html .= '<div class="media-list">';
- //-- table header
- $html .= '<table id="' . $table_id . '"><thead><tr>';
- $html .= '<th>' . I18N::translate('Media') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('TITL') . '</th>';
- $html .= '<th>' . I18N::translate('Individuals') . '</th>';
- $html .= '<th>#INDI</th>';
- $html .= '<th>' . I18N::translate('Families') . '</th>';
- $html .= '<th>#FAM</th>';
- $html .= '<th>' . I18N::translate('Sources') . '</th>';
- $html .= '<th>#SOUR</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? '' : '') . '>CHAN</th>';
- $html .= '</tr></thead>';
- //-- table body
- $html .= '<tbody>';
-
- 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 .= '<tr' . $class . '>';
- //-- Object thumbnail
- $html .= '<td>' . $media_object->displayImage() . '</td>';
- //-- Object name(s)
- $html .= '<td>';
- $html .= '<a href="' . $media_object->getHtmlUrl() . '" class="list_item name2">';
- $html .= highlight_search_hits($name) . '</a>';
- if (Auth::isEditor($media_object->getTree())) {
- $html .= '<br><a href="' . $media_object->getHtmlUrl() . '">' . basename($media_object->getFilename()) . '</a>';
- }
- $html .= '</td>';
-
- //-- Linked INDIs
- $num = count($media_object->linkedIndividuals('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked FAMs
- $num = count($media_object->linkedfamilies('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Linked SOURces
- $num = count($media_object->linkedSources('OBJE'));
- $html .= '<td>' . I18N::number($num) . '</td><td>' . $num . '</td>';
- //-- Last change
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $media_object->LastChangeTimestamp() . '</td>';
- } else {
- $html .= '<td></td>';
- }
- //-- Last change hidden sort column
- if ($WT_TREE->getPreference('SHOW_LAST_CHANGE')) {
- $html .= '<td>' . $media_object->LastChangeTimestamp(true) . '</td>';
- } else {
- $html .= '<td></td>';
- }
- $html .= '</tr>';
- }
- }
- $html .= '</tbody></table></div>';
-
- 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 (a<b) ? -1 : (a>b ? 1 : 0);};
- jQuery.fn.dataTableExt.oSort["num-desc"]=function(a,b) {a=parseFloat(a); b=parseFloat(b); return (a>b) ? -1 : (a<b ? 1 : 0);};
- jQuery(".surname-list").dataTable( {
- dom: \'t\',
- jQueryUI: true,
- autoWidth: false,
- paging: false,
- sorting: [],
- columns: [
- /* 0 name */ { dataSort: 1 },
- /* 1 NAME */ { visible: false },
- /* 2 count */ { dataSort: 3, class: "center" },
- /* 3 COUNT */ { visible: false }
- ],
- });
- ');
-
- if ($script == 'famlist.php') {
- $col_heading = I18N::translate('Spouses');
- } else {
- $col_heading = I18N::translate('Individuals');
- }
-
- $html .= '<table class="surname-list">' .
- '<thead><tr>' .
- '<th>' . GedcomTag::getLabel('SURN') . '</th>' .
- '<th></th>' .
- '<th>' . $col_heading . '</th>' .
- '<th></th>' .
- '</tr></thead>';
-
- $html .= '<tbody>';
- foreach ($surnames as $surn => $surns) {
- // Each surname links back to the indi/fam surname list
- if ($surn) {
- $url = $script . '?surname=' . rawurlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $url = $script . '?alpha=,&amp;ged=' . $tree->getNameUrl();
- }
- // Row counter
- $html .= '<tr>';
- // Surname
- $html .= '<td>';
- // Multiple surname variants, e.g. von Groot, van Groot, van der Groot, etc.
- foreach ($surns as $spfxsurn => $indis) {
- if ($spfxsurn) {
- $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($spfxsurn) . '</a><br>';
- } else {
- // No surname, but a value from "2 SURN"? A common workaround for toponyms, etc.
- $html .= '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml($surn) . '</a><br>';
- }
- }
- $html .= '</td>';
- // Sort column for name
- $html .= '<td>' . $surn . '</td>';
- // Surname count
- $html .= '<td>';
- $subtotal = 0;
- foreach ($surns as $indis) {
- $subtotal += count($indis);
- $html .= I18N::number(count($indis)) . '<br>';
- }
- // More than one surname variant? Show a subtotal
- if (count($surns) > 1) {
- $html .= I18N::number($subtotal);
- }
- $html .= '</td>';
- // add hidden numeric sort column
- $html .= '<td>' . $subtotal . '</td></tr>';
- }
- $html .= '</tbody></table>';
-
- 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 .= '<a style="font-size:' . $size . '%" href="' . $script . '?';
- if ($surn) {
- $html .= 'surname=' . urlencode($surn) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $html .= '?alpha=,&amp;ged=' . $WT_TREE->getNameUrl();
- }
- if ($totals) {
- $html .= I18N::translate('%1$s (%2$s)', '<span dir="auto">' . $spfxsurn . '</span>', I18N::number(count($indis)));
- } else {
- $html .= $spfxsurn;
- }
- $html .= '</a> ';
- }
- }
-
- return '<div class="tag_cloud">' . $html . '</div>';
-}
-
-/**
- * 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) . '&amp;ged=' . $tree->getNameUrl();
- } else {
- $url = $script . '?alpha=,&amp;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 = '<a href="' . $url . '" dir="auto">' . Filter::escapeHtml(implode(I18N::$list_separator, array_keys($surns))) . '</a>';
-
- if ($totals) {
- $subtotal = 0;
- foreach ($surns as $indis) {
- $subtotal += count($indis);
- }
- $subhtml .= '&nbsp;(' . I18N::number($subtotal) . ')';
- }
- $html[] = $subhtml;
-
- }
- switch ($style) {
- case 1:
- return '<ul><li>' . implode('</li><li>', $html) . '</li></ul>';
- 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 = '<table class="list_table"><tr>';
- $html2 .= '<td class="list_value" style="padding: 14px;">';
-
- foreach ($html as $surns) {
- $html2 .= $surns . '<br>';
- $i++;
- if ($i == $newcol && $i < $count) {
- $html2 .= '</td><td class="list_value" style="padding: 14px;">';
- $newcol = $i + ceil($count / $col);
- }
- }
- $html2 .= '</td></tr></table>';
-
- 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 .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item name2">' . $value['record']->getFullName() . '</a>';
- $html .= '<div class="indent" style="margin-bottom: 5px;">';
- if ($value['record'] instanceof Individual) {
- if ($value['record']->getAddName()) {
- $html .= '<a href="' . $value['record']->getHtmlUrl() . '" class="list_item">' . $value['record']->getAddName() . '</a>';
- }
- }
- $html .= /* I18N: [a record was] Changed on <date/time> by <user> */ I18N::translate('Changed on %1$s by %2$s', $value['record']->lastChangeTimestamp(), Filter::escapeHtml($value['record']->lastChangeUser()));
- $html .= '</div>';
- }
-
- 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 .= '<table id="' . $table_id . '" class="width100">';
- $html .= '<thead><tr>';
- $html .= '<th></th>';
- $html .= '<th>' . I18N::translate('Record') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('CHAN') . '</th>';
- $html .= '<th>' . GedcomTag::getLabel('_WT_USER') . '</th>';
- $html .= '<th>DATE</th>'; //hidden by datatables code
- $html .= '<th>SORTNAME</th>'; //hidden by datatables code
- $html .= '</tr></thead><tbody>';
-
- //-- table body
- foreach ($change_ids as $change_id) {
- $record = GedcomRecord::getInstance($change_id, $WT_TREE);
- if (!$record || !$record->canShow()) {
- continue;
- }
- $html .= '<tr><td>';
- switch ($record::RECORD_TYPE) {
- case 'INDI':
- $icon = $record->getSexImage('small');
- break;
- case 'FAM':
- $icon = '<i class="icon-button_family"></i>';
- break;
- case 'OBJE':
- $icon = '<i class="icon-button_media"></i>';
- break;
- case 'NOTE':
- $icon = '<i class="icon-button_note"></i>';
- break;
- case 'SOUR':
- $icon = '<i class="icon-button_source"></i>';
- break;
- case 'REPO':
- $icon = '<i class="icon-button_repository"></i>';
- break;
- default:
- $icon = '&nbsp;';
- break;
- }
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $icon . '</a>';
- $html .= '</td>';
- ++$n;
- //-- Record name(s)
- $name = $record->getFullName();
- $html .= '<td class="wrap">';
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $name . '</a>';
- if ($record instanceof Individual) {
- $addname = $record->getAddName();
- if ($addname) {
- $html .= '<div class="indent"><a href="' . $record->getHtmlUrl() . '">' . $addname . '</a></div>';
- }
- }
- $html .= "</td>";
- //-- Last change date/time
- $html .= '<td class="wrap">' . $record->lastChangeTimestamp() . '</td>';
- //-- Last change user
- $html .= '<td class="wrap">' . Filter::escapeHtml($record->lastChangeUser()) . '</td>';
- //-- change date (sortable) hidden by datatables code
- $html .= '<td>' . $record->lastChangeTimestamp(true) . '</td>';
- //-- names (sortable) hidden by datatables code
- $html .= '<td>' . $record->getSortName() . '</td></tr>';
- }
-
- $html .= '</tbody></table>';
-
- 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 .= '<table id="' . $table_id . '" class="width100">';
- $html .= '<thead><tr>';
- $html .= '<th>' . I18N::translate('Record') . '</th>';
- $html .= '<th>NAME</th>'; //hidden by datatables code
- $html .= '<th>' . GedcomTag::getLabel('DATE') . '</th>';
- $html .= '<th>DATE</th>'; //hidden by datatables code
- $html .= '<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>';
- $html .= '<th>ANNIV</th>';
- $html .= '<th>' . GedcomTag::getLabel('EVEN') . '</th>';
- $html .= '</tr></thead><tbody>';
- }
-
- $filtered_events[] = $fact;
- }
-
- foreach ($filtered_events as $n => $fact) {
- $record = $fact->getParent();
- $html .= '<tr>';
- $html .= '<td>';
- $html .= '<a href="' . $record->getHtmlUrl() . '">' . $record->getFullName() . '</a>';
- if ($record instanceof Individual) {
- $html .= $record->getSexImage();
- }
- $html .= '</td>';
- $html .= '<td>' . $record->getSortName() . '</td>';
- $html .= '<td>' . $fact->getDate()->display(!Auth::isSearchEngine()) . '</td>';
- $html .= '<td>' . $n . '</td>';
- $html .= '<td>' . I18N::number($fact->anniv) . '</td>';
- $html .= '<td>' . $fact->anniv . '</td>';
- $html .= '<td>' . $fact->getLabel() . '</td>';
- $html .= '</tr>';
- }
-
- if ($output != 0) {
- $html .= '</tbody></table>';
- }
-
- // 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 .= '<strong>' . $summary . '</strong>';
- }
-
- 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 .= '<a href="' . $record->getHtmlUrl() . '" class="list_item name2">' . $record->getFullName() . '</a>';
- if ($record instanceof Individual) {
- $html .= $record->getSexImage();
- }
- $html .= '<br><div class="indent">';
- $html .= $fact->getLabel() . ' — ' . $fact->getDate()->display(true);
- if ($fact->anniv) {
- $html .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')';
- }
- if (!$fact->getPlace()->isEmpty()) {
- $html .= ' — <a href="' . $fact->getPlace()->getURL() . '">' . $fact->getPlace()->getFullName() . '</a>';
- }
- $html .= '</div>';
- }
-
- // 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 .= "<b>" . $summary . "</b>";
- }
-
- 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 .= "&amp;chs=725x150"; // size
- $chart_url .= "&amp;chbh=3,2,2"; // bvg : 4,1,2
- $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
- $chart_url .= "&amp;chco=0000FF,FFA0CB,FF0000"; // bar color
- $chart_url .= "&amp;chdl=" . rawurlencode(I18N::translate('Males')) . "|" . rawurlencode(I18N::translate('Females')) . "|" . rawurlencode(I18N::translate('Average age') . ": " . $avg); // legend & average age
- $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
- $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
- $chart_url .= "&amp;chm=V,FF0000,0," . ($avg - 0.3) . ",1"; // average age line marker
- $chart_url .= "&amp;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 .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
- $chart_url .= "&amp;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 = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
-
- 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 .= "&amp;chs=360x150"; // size
- $chart_url .= "&amp;chbh=3,3"; // bvg : 4,1,2
- $chart_url .= "&amp;chf=bg,s,FFFFFF99"; //background color
- $chart_url .= "&amp;chco=0000FF,FFA0CB"; // bar color
- $chart_url .= "&amp;chtt=" . rawurlencode($title); // title
- $chart_url .= "&amp;chxt=x,y,r"; // axis labels specification
- $chart_url .= "&amp;chxl=0:|&lt;|||"; // <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 .= "&amp;chg=100," . round(100 * $step / $vmax, 1) . ",1,5"; // grid
- $chart_url .= "&amp;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 = '<img src="' . $chart_url . '" alt="' . $title . '" title="' . $title . '" class="gchart">';
-
- return $html;
-}
diff --git a/includes/functions/functions_rtl.php b/includes/functions/functions_rtl.php
deleted file mode 100644
index 3c4f7bca8e..0000000000
--- a/includes/functions/functions_rtl.php
+++ /dev/null
@@ -1,1141 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-// RTL Functions for use in the PDF/HTML reports
-
-use Fisharebest\Webtrees\I18N;
-
-$SpecialChar = array(' ', '.', ',', '"', '\'', '/', '\\', '|', ':', ';', '+', '&', '#', '@', '-', '=', '*', '%', '!', '?', '$', '<', '>', "\n");
-$SpecialPar = array('(', ')', '[', ']', '{', '}');
-$SpecialNum = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
-
-$openPar = '([{';
-$closePar = ')]}';
-$numbers = '0123456789';
-$numberPrefix = '+-'; // Treat these like numbers when at beginning or end of numeric strings
-$numberPunctuation = '- ,.:/'; // Treat these like numbers when inside numeric strings
-$punctuation = ',.:;?!';
-
-/**
- * This function strips &lrm; and &rlm; from the input string. It should be used for all
- * text that has been passed through the PrintReady() function before that text is stored
- * in the database. The database should NEVER contain these characters.
- *
- * @param string $inputText The string from which the &lrm; and &rlm; characters should be stripped
- *
- * @return string The input string, with &lrm; and &rlm; stripped
- */
-function stripLRMRLM($inputText) {
- return str_replace(array(WT_UTF8_LRM, WT_UTF8_RLM, WT_UTF8_LRO, WT_UTF8_RLO, WT_UTF8_LRE, WT_UTF8_RLE, WT_UTF8_PDF, "&lrm;", "&rlm;", "&LRM;", "&RLM;"), "", $inputText);
-}
-
-/**
- * This function encapsulates all texts in the input with <span dir='xxx'> and </span>
- * according to the directionality specified.
- *
- * @param string $inputText Raw input
- * @param string $direction Directionality (LTR, BOTH, RTL) default BOTH
- * @param string $class Additional text to insert into output <span dir="xxx"> (such as 'class="yyy"')
- *
- * @return string The string with all texts encapsulated as required
- */
-function spanLTRRTL($inputText, $direction = 'BOTH', $class = '') {
- global $openPar, $closePar, $punctuation;
- global $numbers, $numberPrefix, $numberPunctuation;
- global $previousState, $currentState, $waitingText;
- global $startLTR, $endLTR, $startRTL, $endRTL, $lenStart, $lenEnd;
- static $spanNumber = 0;
-
- if ($inputText == '') {
- // Nothing to do
- return '';
- }
- $spanNumber++;
-
- $workingText = str_replace("\n", '<br>', $inputText);
- $workingText = str_replace(array('<span class="starredname"><br>', '<span<br>class="starredname">'), '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks
- $workingText = stripLRMRLM($workingText); // Get rid of any existing UTF8 control codes
-
- // $nothing = '&zwnj;'; // Zero Width Non-Joiner (not sure whether this is still needed to work around a TCPDF bug)
- $nothing = '';
-
- $startLTR = '<LTR>'; // This will become '<span dir="ltr">' at the end
- $endLTR = '</LTR>'; // This will become '</span>' at the end
- $startRTL = '<RTL>'; // This will become '<span dir="rtl">' at the end
- $endRTL = '</RTL>'; // This will become '</span>' at the end
- $lenStart = strlen($startLTR); // RTL version MUST have same length
- $lenEnd = strlen($endLTR); // RTL version MUST have same length
-
- $previousState = '';
- $currentState = strtoupper(I18N::direction());
- $numberState = false; // Set when we're inside a numeric string
- $result = '';
- $waitingText = '';
- $openParDirection = array();
-
- beginCurrentSpan($result);
-
- while ($workingText != '') {
- $charArray = getChar($workingText, 0); // Get the next ASCII or UTF-8 character
- $currentLetter = $charArray['letter'];
- $currentLen = $charArray['length'];
-
- $openParIndex = strpos($openPar, $currentLetter); // Which opening parenthesis is this?
- $closeParIndex = strpos($closePar, $currentLetter); // Which closing parenthesis is this?
-
- switch ($currentLetter) {
- case '<':
- // Assume this '<' starts an HTML element
- $endPos = strpos($workingText, '>'); // look for the terminating '>'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen += $endPos;
- $element = substr($workingText, 0, $currentLen);
- $temp = strtolower(substr($element, 0, 3));
- if (strlen($element) < 7 && $temp == '<br') {
- if ($numberState) {
- $numberState = false;
- if ($currentState == 'RTL') {
- $waitingText .= WT_UTF8_PDF;
- }
- }
- breakCurrentSpan($result);
- } elseif ($waitingText == '') {
- $result .= $element;
- } else {
- $waitingText .= $element;
- }
- $workingText = substr($workingText, $currentLen);
- break;
- case '&':
- // Assume this '&' starts an HTML entity
- $endPos = strpos($workingText, ';'); // look for the terminating ';'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen += $endPos;
- $entity = substr($workingText, 0, $currentLen);
- if (strtolower($entity) == '&nbsp;') {
- $entity .= '&nbsp;'; // Ensure consistent case for this entity
- }
- if ($waitingText == '') {
- $result .= $entity;
- } else {
- $waitingText .= $entity;
- }
- $workingText = substr($workingText, $currentLen);
- break;
- case '{':
- if (substr($workingText, 1, 1) == '{') {
- // Assume this '{{' starts a TCPDF directive
- $endPos = strpos($workingText, '}}'); // look for the terminating '}}'
- if ($endPos === false) {
- $endPos = 0;
- }
- $currentLen = $endPos + 2;
- $directive = substr($workingText, 0, $currentLen);
- $workingText = substr($workingText, $currentLen);
- $result = $result . $waitingText . $directive;
- $waitingText = '';
- break;
- }
- default:
- // Look for strings of numbers with optional leading or trailing + or -
- // and with optional embedded numeric punctuation
- if ($numberState) {
- // If we're inside a numeric string, look for reasons to end it
- $offset = 0; // Be sure to look at the current character first
- $charArray = getChar($workingText . "\n", $offset);
- if (strpos($numbers, $charArray['letter']) === false) {
- // This is not a digit. Is it numeric punctuation?
- if (substr($workingText . "\n", $offset, 6) == '&nbsp;') {
- $offset += 6; // This could be numeric punctuation
- } elseif (strpos($numberPunctuation, $charArray['letter']) !== false) {
- $offset += $charArray['length']; // This could be numeric punctuation
- }
- // If the next character is a digit, the current character is numeric punctuation
- $charArray = getChar($workingText . "\n", $offset);
- if (strpos($numbers, $charArray['letter']) === false) {
- // This is not a digit. End the run of digits and punctuation.
- $numberState = false;
- if ($currentState == 'RTL') {
- if (strpos($numberPrefix, $currentLetter) === false) {
- $currentLetter = WT_UTF8_PDF . $currentLetter;
- } else {
- $currentLetter = $currentLetter . WT_UTF8_PDF; // Include a trailing + or - in the run
- }
- }
- }
- }
- } else {
- // If we're outside a numeric string, look for reasons to start it
- if (strpos($numberPrefix, $currentLetter) !== false) {
- // This might be a number lead-in
- $offset = $currentLen;
- $nextChar = substr($workingText . "\n", $offset, 1);
- if (strpos($numbers, $nextChar) !== false) {
- $numberState = true; // We found a digit: the lead-in is therefore numeric
- if ($currentState == 'RTL') {
- $currentLetter = WT_UTF8_LRE . $currentLetter;
- }
- }
- } elseif (strpos($numbers, $currentLetter) !== false) {
- $numberState = true; // The current letter is a digit
- if ($currentState == 'RTL') {
- $currentLetter = WT_UTF8_LRE . $currentLetter;
- }
- }
- }
-
- // Determine the directionality of the current UTF-8 character
- $newState = $currentState;
- while (true) {
- if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') {
- if ($currentState == '') {
- $newState = 'RTL';
- break;
- }
-
- if ($currentState == 'RTL') {
- break;
- }
- // Switch to RTL only if this isn't a solitary RTL letter
- $tempText = substr($workingText, $currentLen);
- while ($tempText != '') {
- $nextCharArray = getChar($tempText, 0);
- $nextLetter = $nextCharArray['letter'];
- $nextLen = $nextCharArray['length'];
- $tempText = substr($tempText, $nextLen);
-
- if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') {
- $newState = 'RTL';
- break 2;
- }
-
- if (strpos($punctuation, $nextLetter) !== false || strpos($openPar, $nextLetter) !== false) {
- $newState = 'RTL';
- break 2;
- }
-
- if ($nextLetter === ' ') {
- break;
- }
- $nextLetter .= substr($tempText . "\n", 0, 5);
- if ($nextLetter === '&nbsp;') {
- break;
- }
- }
- // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality
- $currentLetter = WT_UTF8_LRO . $currentLetter . WT_UTF8_PDF;
- $newState = 'LTR';
- break;
- }
- if (($currentLen != 1) || ($currentLetter >= 'A' && $currentLetter <= 'Z') || ($currentLetter >= 'a' && $currentLetter <= 'z')) {
- // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR
- $newState = 'LTR';
- break;
- }
- if ($closeParIndex !== false) {
- // This closing parenthesis has to inherit the matching opening parenthesis' directionality
- if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] != '?') {
- $newState = $openParDirection[$closeParIndex];
- }
- $openParDirection[$closeParIndex] = '';
- break;
- }
- if ($openParIndex !== false) {
- // Opening parentheses always inherit the following directionality
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- while (true) {
- if ($workingText === '') {
- break;
- }
- if (substr($workingText, 0, 1) === ' ') {
- // Spaces following this left parenthesis inherit the following directionality too
- $waitingText .= ' ';
- $workingText = substr($workingText, 1);
- continue;
- }
- if (substr($workingText, 0, 6) === '&nbsp;') {
- // Spaces following this left parenthesis inherit the following directionality too
- $waitingText .= '&nbsp;';
- $workingText = substr($workingText, 6);
- continue;
- }
- break;
- }
- $openParDirection[$openParIndex] = '?';
- break 2; // double break because we're waiting for more information
- }
-
- // We have a digit or a "normal" special character.
- //
- // When this character is not at the start of the input string, it inherits the preceding directionality;
- // at the start of the input string, it assumes the following directionality.
- //
- // Exceptions to this rule will be handled later during final clean-up.
- //
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- if ($currentState != '') {
- $result .= $waitingText;
- $waitingText = '';
- }
- break 2; // double break because we're waiting for more information
- }
- if ($newState != $currentState) {
- // A direction change has occurred
- finishCurrentSpan($result, false);
- $previousState = $currentState;
- $currentState = $newState;
- beginCurrentSpan($result);
- }
- $waitingText .= $currentLetter;
- $workingText = substr($workingText, $currentLen);
- $result .= $waitingText;
- $waitingText = '';
-
- foreach ($openParDirection as $index => $value) {
- // Since we now know the proper direction, remember it for all waiting opening parentheses
- if ($value === '?') {
- $openParDirection[$index] = $currentState;
- }
- }
-
- break;
- }
- }
-
- // We're done. Finish last <span> if necessary
- if ($numberState) {
- if ($waitingText === '') {
- if ($currentState === 'RTL') {
- $result .= WT_UTF8_PDF;
- }
- } else {
- if ($currentState === 'RTL') {
- $waitingText .= WT_UTF8_PDF;
- }
- }
- }
- finishCurrentSpan($result, true);
-
- // Get rid of any waiting text
- if ($waitingText != '') {
- if (I18N::direction() === 'rtl' && $currentState === 'LTR') {
- $result .= $startRTL;
- $result .= $waitingText;
- $result .= $endRTL;
- } else {
- $result .= $startLTR;
- $result .= $waitingText;
- $result .= $endLTR;
- }
- $waitingText = '';
- }
-
- // Lastly, do some more cleanups
-
- // Move leading RTL numeric strings to following LTR text
- // (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text)
- while (substr($result, 0, $lenStart + 3) === $startRTL . WT_UTF8_LRE) {
- $spanEnd = strpos($result, $endRTL . $startLTR);
- if ($spanEnd === false) {
- break;
- }
- $textSpan = stripLRMRLM(substr($result, $lenStart + 3, $spanEnd - $lenStart - 3));
- if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') {
- break;
- }
- $result = $startLTR . substr($result, $lenStart, $spanEnd - $lenStart) . substr($result, $spanEnd + $lenStart + $lenEnd);
- break;
- }
-
- // On RTL pages, put trailing "." in RTL numeric strings into its own RTL span
- if (I18N::direction() === 'rtl') {
- $result = str_replace(WT_UTF8_PDF . '.' . $endRTL, WT_UTF8_PDF . $endRTL . $startRTL . '.' . $endRTL, $result);
- }
-
- // Trim trailing blanks preceding <br> in LTR text
- while ($previousState != 'RTL') {
- if (strpos($result, ' <LTRbr>') !== false) {
- $result = str_replace(' <LTRbr>', '<LTRbr>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<LTRbr>') !== false) {
- $result = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $result);
- continue;
- }
- if (strpos($result, ' <br>') !== false) {
- $result = str_replace(' <br>', '<br>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<br>') !== false) {
- $result = str_replace('&nbsp;<br>', '<br>', $result);
- continue;
- }
- break; // Neither space nor &nbsp; : we're done
- }
-
- // Trim trailing blanks preceding <br> in RTL text
- while (true) {
- if (strpos($result, ' <RTLbr>') !== false) {
- $result = str_replace(' <RTLbr>', '<RTLbr>', $result);
- continue;
- }
- if (strpos($result, '&nbsp;<RTLbr>') !== false) {
- $result = str_replace('&nbsp;<RTLbr>', '<RTLbr>', $result);
- continue;
- }
- break; // Neither space nor &nbsp; : we're done
- }
-
- // Convert '<LTRbr>' and '<RTLbr /'
- $result = str_replace(array('<LTRbr>', '<RTLbr>'), array($endLTR . '<br>' . $startLTR, $endRTL . '<br>' . $startRTL), $result);
-
- // Include leading indeterminate directional text in whatever follows
- if (substr($result . "\n", 0, $lenStart) != $startLTR && substr($result . "\n", 0, $lenStart) != $startRTL && substr($result . "\n", 0, 6) != '<br>') {
- $leadingText = '';
- while (true) {
- if ($result == '') {
- $result = $leadingText;
- break;
- }
- if (substr($result . "\n", 0, $lenStart) != $startLTR && substr($result . "\n", 0, $lenStart) != $startRTL) {
- $leadingText .= substr($result, 0, 1);
- $result = substr($result, 1);
- continue;
- }
- $result = substr($result, 0, $lenStart) . $leadingText . substr($result, $lenStart);
- break;
- }
- }
-
- // Include solitary "-" and "+" in surrounding RTL text
- $result = str_replace(array($endRTL . $startLTR . '-' . $endLTR . $startRTL, $endRTL . $startLTR . '-' . $endLTR . $startRTL), array('-', '+'), $result);
-
- // Remove empty spans
- $result = str_replace(array($startLTR . $endLTR, $startRTL . $endRTL), '', $result);
-
- // Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>'
- switch ($direction) {
- case 'BOTH':
- case 'both':
- // LTR text: <span dir="ltr"> text </span>
- // RTL text: <span dir="rtl"> text </span>
- $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
- $eLTR = $nothing . '</span>';
- $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
- $eRTL = $nothing . '</span>';
- break;
- case 'LTR':
- case 'ltr':
- // LTR text: <span dir="ltr"> text </span>
- // RTL text: text
- $sLTR = '<span dir="ltr" ' . $class . '>' . $nothing;
- $eLTR = $nothing . '</span>';
- $sRTL = '';
- $eRTL = '';
- break;
- case 'RTL':
- case 'rtl':
- default:
- // LTR text: text
- // RTL text: <span dir="rtl"> text </span>
- $sLTR = '';
- $eLTR = '';
- $sRTL = '<span dir="rtl" ' . $class . '>' . $nothing;
- $eRTL = $nothing . '</span>';
- break;
- }
- $result = str_replace(array($startLTR, $endLTR, $startRTL, $endRTL), array($sLTR, $eLTR, $sRTL, $eRTL), $result);
-
- return $result;
-}
-
-/**
- * Wrap words that have an asterisk suffix in <u> and </u> tags.
- * This should underline starred names to show the preferred name.
- *
- * @param string $textSpan
- * @param string $direction
- *
- * @return string
- */
-function starredName($textSpan, $direction) {
- // To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags
- // only when page and span directions are identical.
- if ($direction === strtoupper(I18N::direction())) {
- while (true) {
- $starPos = strpos($textSpan, '*');
- if ($starPos === false) {
- break;
- }
- $trailingText = substr($textSpan, $starPos + 1);
- $textSpan = substr($textSpan, 0, $starPos);
- $wordStart = strrpos($textSpan, ' '); // Find the start of the word
- if ($wordStart !== false) {
- $leadingText = substr($textSpan, 0, $wordStart + 1);
- $wordText = substr($textSpan, $wordStart + 1);
- } else {
- $leadingText = '';
- $wordText = $textSpan;
- }
- $textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText;
- }
- $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan);
- // The &nbsp; is a work-around for a TCPDF bug eating blanks.
- $textSpan = str_replace(array(' <u>', '</u> '), array('&nbsp;<u>', '</u>&nbsp;'), $textSpan);
- } else {
- // Text and page directions differ: remove the <span> and </span>
- $textSpan = preg_replace('~(.*)\*~', '\1', $textSpan);
- $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan);
- }
-
- return $textSpan;
-}
-
-/**
- * Get the next character from an input string
- *
- * @param string $text
- * @param string $offset
- *
- * @return array
- */
-function getChar($text, $offset) {
-
- if ($text == '') {
- return array('letter' => '', 'length' => 0);
- }
-
- $char = substr($text, $offset, 1);
- $length = 1;
- if ((ord($char) & 0xE0) == 0xC0) {
- $length = 2;
- }
- if ((ord($char) & 0xF0) == 0xE0) {
- $length = 3;
- }
- if ((ord($char) & 0xF8) == 0xF0) {
- $length = 4;
- }
- $letter = substr($text, $offset, $length);
-
- return array('letter' => $letter, 'length' => $length);
-}
-
-/**
- * Insert <br> into current span
- *
- * @param string $result
- */
-function breakCurrentSpan(&$result) {
- global $currentState, $waitingText;
-
- // Interrupt the current span, insert that <br>, and then continue the current span
- $result .= $waitingText;
- $waitingText = '';
-
- $breakString = '<' . $currentState . 'br>';
- $result .= $breakString;
-
- return;
-}
-
-/**
- * Begin current span
- *
- * @param string $result
- */
-function beginCurrentSpan(&$result) {
- global $currentState, $startLTR, $startRTL, $posSpanStart;
-
- if ($currentState == 'LTR') {
- $result .= $startLTR;
- }
- if ($currentState == 'RTL') {
- $result .= $startRTL;
- }
-
- $posSpanStart = strlen($result);
-
- return;
-}
-
-/**
- * Finish current span
- *
- * @param string $result
- * @param bool $theEnd
- */
-function finishCurrentSpan(&$result, $theEnd = false) {
- global $previousState, $currentState, $posSpanStart, $waitingText;
- global $startLTR, $endLTR, $startRTL, $endRTL;
- global $numbers, $punctuation;
-
- $textSpan = substr($result, $posSpanStart);
- $result = substr($result, 0, $posSpanStart);
-
- // Get rid of empty spans, so that our check for presence of RTL will work
- $result = str_replace(array($startLTR . $endLTR, $startRTL . $endRTL), '', $result);
-
- // Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers.
- $tempResult = '';
- while ($textSpan != '') {
- $posColon = strpos($textSpan, ':');
- if ($posColon === false) {
- break;
- } // No more possible time strings
- $posLRE = strpos($textSpan, WT_UTF8_LRE);
- if ($posLRE === false) {
- break;
- } // No more numeric strings
- $posPDF = strpos($textSpan, WT_UTF8_PDF, $posLRE);
- if ($posPDF === false) {
- break;
- } // No more numeric strings
-
- $tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string
- $numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string
- $textSpan = substr($textSpan, $posPDF + 3);
- $posColon = strpos($numericString, ':');
- if ($posColon === false) {
- // Nothing that looks like a time here
- $tempResult .= $numericString;
- continue;
- }
- $posBlank = strpos($numericString . ' ', ' ');
- $posNbsp = strpos($numericString . '&nbsp;', '&nbsp;');
- if ($posBlank < $posNbsp) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } else {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- }
- if ($posColon > $posSeparator) {
- // We have a time string preceded by a blank: Exclude that blank from the numeric string
- $tempResult .= substr($numericString, 0, $posSeparator);
- $tempResult .= WT_UTF8_PDF;
- $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
- $tempResult .= WT_UTF8_LRE;
- $numericString = substr($numericString, $posSeparator + $lengthSeparator);
- }
-
- $posBlank = strpos($numericString, ' ');
- $posNbsp = strpos($numericString, '&nbsp;');
- if ($posBlank === false && $posNbsp === false) {
- // The time string isn't followed by a blank
- $textSpan = $numericString . $textSpan;
- continue;
- }
-
- // We have a time string followed by a blank: Exclude that blank from the numeric string
- if ($posBlank === false) {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- } elseif ($posNbsp === false) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } elseif ($posBlank < $posNbsp) {
- $posSeparator = $posBlank;
- $lengthSeparator = 1;
- } else {
- $posSeparator = $posNbsp;
- $lengthSeparator = 6;
- }
- $tempResult .= substr($numericString, 0, $posSeparator);
- $tempResult .= WT_UTF8_PDF;
- $tempResult .= substr($numericString, $posSeparator, $lengthSeparator);
- $posSeparator += $lengthSeparator;
- $numericString = substr($numericString, $posSeparator);
- $textSpan = WT_UTF8_LRE . $numericString . $textSpan;
- }
- $textSpan = $tempResult . $textSpan;
- $trailingBlanks = '';
- $trailingBreaks = '';
-
- /* ****************************** LTR text handling ******************************** */
-
- if ($currentState === 'LTR') {
- // Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too.
- if (I18N::direction() === 'rtl' && $previousState === 'RTL' && !$theEnd) {
- $trailingString = '';
- $savedSpan = $textSpan;
- while ($textSpan !== '') {
- // Look for trailing spaces and tentatively move them
- if (substr($textSpan, -1) === ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -3) !== WT_UTF8_PDF) {
- // There is no trailing numeric string
- $textSpan = $savedSpan;
- break;
- }
-
- // We have a numeric string
- $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
- if ($posStartNumber === false) {
- $posStartNumber = 0;
- }
- $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
- $textSpan = substr($textSpan, 0, $posStartNumber);
-
- // Look for more spaces and move them too
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- $waitingText = $trailingString . $waitingText;
- break;
- }
- }
-
- $savedSpan = $textSpan;
- // Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- while (substr($textSpan, -9) == '<LTRbr>') {
- $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
- $textSpan = substr($textSpan, 0, -9);
- }
- if ($trailingBreaks != '') {
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBreaks = ' ' . $trailingBreaks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBreaks = '&nbsp;' . $trailingBreaks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- $waitingText = $trailingBlanks . $waitingText; // Put those trailing blanks inside the following span
- } else {
- $textSpan = $savedSpan;
- }
-
- $trailingBlanks = '';
- $trailingPunctuation = '';
- $trailingID = '';
- $trailingSeparator = '';
- $leadingSeparator = '';
- while (I18N::direction() === 'rtl') {
- if (strpos($result, $startRTL) !== false) {
- // Remove trailing blanks for inclusion in a separate LTR span
- while ($textSpan != '') {
- if (substr($textSpan, -1) === ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- // Remove trailing punctuation for inclusion in a separate LTR span
- if ($textSpan == '') {
- $trailingChar = "\n";
- } else {
- $trailingChar = substr($textSpan, -1);
- }
- if (strpos($punctuation, $trailingChar) !== false) {
- $trailingPunctuation = $trailingChar;
- $textSpan = substr($textSpan, 0, -1);
- }
- }
-
- // Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span
- while (true) {
- if (substr($textSpan, -1) != ')') {
- break;
- } // There is no trailing ')'
- $posLeftParen = strrpos($textSpan, '(');
- if ($posLeftParen === false) {
- break;
- } // There is no leading '('
- $temp = stripLRMRLM(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes
-
- // If the parenthesized text doesn't look like an ID number,
- // we don't want to touch it.
- // This check won’t work if somebody uses ID numbers with an unusual format.
- $offset = 1;
- $charArray = getchar($temp, $offset); // Get 1st character of parenthesized text
- if (strpos($numbers, $charArray['letter']) !== false) {
- break;
- }
- $offset += $charArray['length']; // Point at 2nd character of parenthesized text
- if (strpos($numbers, substr($temp, $offset, 1)) === false) {
- break;
- }
- // 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too
- if (strpos($numbers, substr($temp, -2, 1)) === false) {
- break;
- }
-
- $trailingID = substr($textSpan, $posLeftParen);
- $textSpan = substr($textSpan, 0, $posLeftParen);
- break;
- }
-
- // Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span
- if ($trailingID != '') {
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingSeparator = ' ' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingSeparator = '&nbsp;' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- if (substr($textSpan, -1) == '-') {
- $trailingSeparator = '-' . $trailingSeparator;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
- }
-
- // Look for " - " preceding the text and remove it for inclusion in a separate LTR span
- $foundSeparator = false;
- $savedSpan = $textSpan;
- while ($textSpan != '') {
- if (substr($textSpan, 0, 1) == ' ') {
- $leadingSeparator = ' ' . $leadingSeparator;
- $textSpan = substr($textSpan, 1);
- continue;
- }
- if (substr($textSpan, 0, 6) == '&nbsp;') {
- $leadingSeparator = '&nbsp;' . $leadingSeparator;
- $textSpan = substr($textSpan, 6);
- continue;
- }
- if (substr($textSpan, 0, 1) == '-') {
- $leadingSeparator = '-' . $leadingSeparator;
- $textSpan = substr($textSpan, 1);
- $foundSeparator = true;
- continue;
- }
- break;
- }
- if (!$foundSeparator) {
- $textSpan = $savedSpan;
- $leadingSeparator = '';
- }
- break;
- }
-
- // We're done: finish the span
- $textSpan = starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags
- while (true) {
- // Remove blanks that precede <LTRbr>
- if (strpos($textSpan, ' <LTRbr>') !== false) {
- $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan);
- continue;
- }
- if (strpos($textSpan, '&nbsp;<LTRbr>') !== false) {
- $textSpan = str_replace('&nbsp;<LTRbr>', '<LTRbr>', $textSpan);
- continue;
- }
- break;
- }
- if ($leadingSeparator != '') {
- $result = $result . $startLTR . $leadingSeparator . $endLTR;
- }
- $result = $result . $textSpan . $endLTR;
- if ($trailingSeparator != '') {
- $result = $result . $startLTR . $trailingSeparator . $endLTR;
- }
- if ($trailingID != '') {
- $result = $result . $startLTR . $trailingID . $endLTR;
- }
- if ($trailingPunctuation != '') {
- $result = $result . $startLTR . $trailingPunctuation . $endLTR;
- }
- if ($trailingBlanks != '') {
- $result = $result . $startLTR . $trailingBlanks . $endLTR;
- }
- }
-
- /* ****************************** RTL text handling ******************************** */
-
- if ($currentState == 'RTL') {
- $savedSpan = $textSpan;
-
- // Move any trailing <br>, optionally followed by blanks, outside this RTL span
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingBlanks = ' ' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr('......' . $textSpan, -6) == '&nbsp;') {
- $trailingBlanks = '&nbsp;' . $trailingBlanks;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
- while (substr($textSpan, -9) == '<RTLbr>') {
- $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span
- $textSpan = substr($textSpan, 0, -9);
- }
- if ($trailingBreaks != '') {
- $waitingText = $trailingBlanks . $waitingText; // Put those trailing blanks inside the following span
- } else {
- $textSpan = $savedSpan;
- }
-
- // Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too.
- if (!$theEnd && I18N::direction() !== 'rtl') {
- $trailingString = '';
- $savedSpan = $textSpan;
- while ($textSpan != '') {
- // Look for trailing spaces and tentatively move them
- if (substr($textSpan, -1) === ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) === '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -3) !== WT_UTF8_PDF) {
- // There is no trailing numeric string
- $textSpan = $savedSpan;
- break;
- }
-
- // We have a numeric string
- $posStartNumber = strrpos($textSpan, WT_UTF8_LRE);
- if ($posStartNumber === false) {
- $posStartNumber = 0;
- }
- $trailingString = substr($textSpan, $posStartNumber, strlen($textSpan) - $posStartNumber) . $trailingString;
- $textSpan = substr($textSpan, 0, $posStartNumber);
-
- // Look for more spaces and move them too
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $trailingString = ' ' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $trailingString = '&nbsp;' . $trailingString;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- break;
- }
-
- $waitingText = $trailingString . $waitingText;
- break;
- }
- }
-
- // Trailing " - " needs to be prefixed to the following span
- if (!$theEnd && substr('...' . $textSpan, -3) == ' - ') {
- $textSpan = substr($textSpan, 0, -3);
- $waitingText = ' - ' . $waitingText;
- }
-
- while (I18N::direction() === 'rtl') {
- // Look for " - " preceding <RTLbr> and relocate it to the front of the string
- $posDashString = strpos($textSpan, ' - <RTLbr>');
- if ($posDashString === false) {
- break;
- }
- $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
- if ($posStringStart === false) {
- $posStringStart = 0;
- } else {
- $posStringStart += 9;
- } // Point to the first char following the last <RTLbr>
-
- $textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3);
- }
-
- // Strip leading spaces from the RTL text
- $countLeadingSpaces = 0;
- while ($textSpan != '') {
- if (substr($textSpan, 0, 1) == ' ') {
- $countLeadingSpaces++;
- $textSpan = substr($textSpan, 1);
- continue;
- }
- if (substr($textSpan, 0, 6) == '&nbsp;') {
- $countLeadingSpaces++;
- $textSpan = substr($textSpan, 6);
- continue;
- }
- break;
- }
-
- // Strip trailing spaces from the RTL text
- $countTrailingSpaces = 0;
- while ($textSpan != '') {
- if (substr($textSpan, -1) == ' ') {
- $countTrailingSpaces++;
- $textSpan = substr($textSpan, 0, -1);
- continue;
- }
- if (substr($textSpan, -6) == '&nbsp;') {
- $countTrailingSpaces++;
- $textSpan = substr($textSpan, 0, -6);
- continue;
- }
- break;
- }
-
- // Look for trailing " -", reverse it, and relocate it to the front of the string
- if (substr($textSpan, -2) === ' -') {
- $posDashString = strlen($textSpan) - 2;
- $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>');
- if ($posStringStart === false) {
- $posStringStart = 0;
- } else {
- $posStringStart += 9;
- } // Point to the first char following the last <RTLbr>
-
- $textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2);
- }
-
- if ($countLeadingSpaces != 0) {
- $newLength = strlen($textSpan) + $countLeadingSpaces;
- $textSpan = str_pad($textSpan, $newLength, ' ', (I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT));
- }
- if ($countTrailingSpaces != 0) {
- if (I18N::direction() === 'ltr') {
- if ($trailingBreaks === '') {
- // Move trailing RTL spaces to front of following LTR span
- $newLength = strlen($waitingText) + $countTrailingSpaces;
- $waitingText = str_pad($waitingText, $newLength, ' ', STR_PAD_LEFT);
- }
- } else {
- $newLength = strlen($textSpan) + $countTrailingSpaces;
- $textSpan = str_pad($textSpan, $newLength, ' ', STR_PAD_RIGHT);
- }
- }
-
- // We're done: finish the span
- $textSpan = starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags
- $result = $result . $textSpan . $endRTL;
- }
-
- if ($currentState != 'LTR' && $currentState != 'RTL') {
- $result = $result . $textSpan;
- }
-
- $result .= $trailingBreaks; // Get rid of any waiting <br>
-
- return;
-}
-
-/**
- * @param string $string
- * @param int $width
- * @param string $sep
- * @param bool $cut
- *
- * @return string
- */
-function utf8_wordwrap($string, $width = 75, $sep = "\n", $cut = false) {
- $out = '';
- while ($string) {
- if (mb_strlen($string) <= $width) {
- // Do not wrap any text that is less than the output area.
- $out .= $string;
- $string = '';
- } else {
- $sub1 = mb_substr($string, 0, $width + 1);
- if (mb_substr($string, mb_strlen($sub1) - 1, 1) == ' ') {
- // include words that end by a space immediately after the area.
- $sub = $sub1;
- } else {
- $sub = mb_substr($string, 0, $width);
- }
- $spacepos = strrpos($sub, ' ');
- if ($spacepos === false) {
- // No space on line?
- if ($cut) {
- $out .= $sub . $sep;
- $string = mb_substr($string, mb_strlen($sub));
- } else {
- $spacepos = strpos($string, ' ');
- if ($spacepos === false) {
- $out .= $string;
- $string = '';
- } else {
- $out .= substr($string, 0, $spacepos) . $sep;
- $string = substr($string, $spacepos + 1);
- }
- }
- } else {
- // Split at space;
- $out .= substr($string, 0, $spacepos) . $sep;
- $string = substr($string, $spacepos + 1);
- }
- }
- }
-
- return $out;
-}
diff --git a/index.php b/index.php
index e502a0043c..1400aed899 100644
--- a/index.php
+++ b/index.php
@@ -25,6 +25,8 @@ global $WT_TREE;
use Fisharebest\Webtrees\Controller\AjaxController;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
define('WT_SCRIPT_NAME', 'index.php');
require './includes/session.php';
@@ -41,15 +43,15 @@ if (Auth::check()) {
// Get the blocks list
if ($ctype === 'user') {
- $blocks = get_user_blocks(Auth::id());
+ $blocks = FunctionsDb::getUserBlocks(Auth::id());
} else {
- $blocks = get_gedcom_blocks($WT_TREE->getTreeId());
+ $blocks = FunctionsDb::getTreeBlocks($WT_TREE->getTreeId());
}
$active_blocks = Module::getActiveBlocks($WT_TREE);
// The latest version is shown on the administration page. This updates it every day.
-fetch_latest_version();
+Functions::fetchLatestVersion();
// We generate individual blocks using AJAX
if ($action === 'ajax') {
diff --git a/index_edit.php b/index_edit.php
index 4637d9bff0..8b58cd8d5d 100644
--- a/index_edit.php
+++ b/index_edit.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
define('WT_SCRIPT_NAME', 'index_edit.php');
require './includes/session.php';
@@ -69,9 +70,9 @@ $action = Filter::get('action');
if ($can_reset && Filter::post('default') === '1') {
if ($user_id) {
- $defaults = get_user_blocks(-1);
+ $defaults = FunctionsDb::getUserBlocks(-1);
} else {
- $defaults = get_gedcom_blocks(-1);
+ $defaults = FunctionsDb::getTreeBlocks(-1);
}
$main = $defaults['main'];
$right = $defaults['side'];
@@ -111,9 +112,9 @@ foreach (Module::getActiveBlocks($WT_TREE) as $name => $block) {
}
if ($user_id) {
- $blocks = get_user_blocks($user_id);
+ $blocks = FunctionsDb::getUserBlocks($user_id);
} else {
- $blocks = get_gedcom_blocks($gedcom_id);
+ $blocks = FunctionsDb::getTreeBlocks($gedcom_id);
}
if ($action === 'update') {
diff --git a/indilist.php b/indilist.php
index a9b0fe1efe..05574dfc2e 100644
--- a/indilist.php
+++ b/indilist.php
@@ -25,6 +25,7 @@ global
$WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\Query\QueryName;
define('WT_SCRIPT_NAME', 'indilist.php');
@@ -191,14 +192,14 @@ if ($show === 'indi' || $show === 'surn') {
// Show the surname list
switch ($WT_TREE->getPreference('SURNAME_LIST_STYLE')) {
case 'style1';
- echo format_surname_list($surns, 3, true, WT_SCRIPT_NAME, $WT_TREE);
+ echo FunctionsPrintLists::surnameList($surns, 3, true, WT_SCRIPT_NAME, $WT_TREE);
break;
case 'style3':
- echo format_surname_tagcloud($surns, WT_SCRIPT_NAME, true, $WT_TREE);
+ echo FunctionsPrintLists::surnameTagCloud($surns, WT_SCRIPT_NAME, true, $WT_TREE);
break;
case 'style2':
default:
- echo format_surname_table($surns, WT_SCRIPT_NAME, $WT_TREE);
+ echo FunctionsPrintLists::surnameTable($surns, WT_SCRIPT_NAME, $WT_TREE);
break;
}
} else {
@@ -254,7 +255,7 @@ if ($show === 'indi' || $show === 'surn') {
}
}
if ($show === 'indi') {
- echo format_indi_table(QueryName::individuals($WT_TREE, $surname, $alpha, $falpha, $show_marnm === 'yes', false));
+ echo FunctionsPrintLists::individualTable(QueryName::individuals($WT_TREE, $surname, $alpha, $falpha, $show_marnm === 'yes', false));
}
}
}
diff --git a/individual.php b/individual.php
index 473b1149b2..6f1ae5735c 100644
--- a/individual.php
+++ b/individual.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\IndividualController;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'individual.php');
require './includes/session.php';
@@ -51,13 +53,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This individual has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -69,13 +71,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This individual has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -179,10 +181,10 @@ if ($controller->record->canShow()) {
echo '<span class="header_age">';
if ($bdate->isOK() && !$controller->record->isDead()) {
// If living display age
- echo GedcomTag::getLabelValue('AGE', get_age_at_event(Date::getAgeGedcom($bdate), true), $controller->record, 'span');
+ echo GedcomTag::getLabelValue('AGE', FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate), true), $controller->record, 'span');
} elseif ($bdate->isOK() && $ddate->isOK()) {
// If dead, show age at death
- echo GedcomTag::getLabelValue('AGE', get_age_at_event(Date::getAgeGedcom($bdate, $ddate), false), $controller->record, 'span');
+ echo GedcomTag::getLabelValue('AGE', FunctionsDate::getAgeAtEvent(Date::getAgeGedcom($bdate, $ddate), false), $controller->record, 'span');
}
echo '</span>';
// Display summary birth/death info.
diff --git a/inverselink.php b/inverselink.php
index d79e368d1d..4e5ba6cd59 100644
--- a/inverselink.php
+++ b/inverselink.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'inverselink.php');
require './includes/session.php';
@@ -82,7 +83,7 @@ if ($linkto == 'manage' && Module::getModuleByName('GEDFact_assistant')) {
}
} else {
echo '<input data-autocomplete-type="OBJE" type="text" name="mediaid" id="mediaid" size="5">';
- echo ' ', print_findmedia_link('mediaid', '1media');
+ echo ' ', FunctionsPrint::printFindMediaLink('mediaid', '1media');
echo '</td></tr>';
}
@@ -96,7 +97,7 @@ if ($linkto == 'manage' && Module::getModuleByName('GEDFact_assistant')) {
echo '<td class="optionbox wrap">';
if ($linktoid == "") {
echo '<input class="pedigree_form" type="text" name="linktoid" id="linktopid" size="3" value="', $linktoid, '"> ';
- echo print_findindi_link('linktopid');
+ echo FunctionsPrint::printFindIndividualLink('linktopid');
} else {
$record = Individual::getInstance($linktoid, $WT_TREE);
echo $record->formatList('span', false, $record->getFullName());
@@ -108,7 +109,7 @@ if ($linkto == 'manage' && Module::getModuleByName('GEDFact_assistant')) {
echo '<td class="optionbox wrap">';
if ($linktoid == "") {
echo '<input class="pedigree_form" type="text" name="linktoid" id="linktofamid" size="3" value="', $linktoid, '"> ';
- echo print_findfamily_link('linktofamid');
+ echo FunctionsPrint::printFindFamilyLink('linktofamid');
} else {
$record = Family::getInstance($linktoid, $WT_TREE);
echo $record->formatList('span', false, $record->getFullName());
@@ -120,7 +121,7 @@ if ($linkto == 'manage' && Module::getModuleByName('GEDFact_assistant')) {
echo '<td class="optionbox wrap">';
if ($linktoid == "") {
echo '<input class="pedigree_form" type="text" name="linktoid" id="linktosid" size="3" value="', $linktoid, '"> ';
- echo print_findsource_link('linktosid');
+ echo FunctionsPrint::printFindSourceLink('linktosid');
} else {
$record = Source::getInstance($linktoid, $WT_TREE);
echo $record->formatList('span', false, $record->getFullName());
diff --git a/lifespan.php b/lifespan.php
index 06bf92e617..dc0e8d957c 100644
--- a/lifespan.php
+++ b/lifespan.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\LifespanController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'lifespan.php');
require './includes/session.php';
@@ -146,7 +147,7 @@ $people = count($controller->people);
<?php echo I18N::translate('Add another individual to the chart'); ?>
<br>
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" size="5" id="newpid" name="newpid">
- <?php print_findindi_link('newpid'); ?>
+ <?php FunctionsPrint::printFindIndividualLink('newpid'); ?>
<br>
<div>
<?php echo I18N::translate('Include the individual’s immediate family?'); ?>
diff --git a/login.php b/login.php
index f27b3611cf..b03910ffcb 100644
--- a/login.php
+++ b/login.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\Functions;
use Rhumsaa\Uuid\Uuid;
/**
@@ -105,7 +106,7 @@ case 'login':
// We're logging in as an administrator
if (Auth::isAdmin()) {
// Check for updates
- $latest_version_txt = fetch_latest_version();
+ $latest_version_txt = Functions::fetchLatestVersion();
if (preg_match('/^[0-9.]+\|[0-9.]+\|/', $latest_version_txt)) {
list($latest_version, $earliest_version, $download_url) = explode('|', $latest_version_txt);
if (version_compare(WT_VERSION, $latest_version) < 0) {
diff --git a/mediafirewall.php b/mediafirewall.php
index 4cc4575b97..c384efc1d0 100644
--- a/mediafirewall.php
+++ b/mediafirewall.php
@@ -23,6 +23,8 @@ namespace Fisharebest\Webtrees;
*/
global $WT_TREE;
+use Fisharebest\Webtrees\Functions\FunctionsMedia;
+
define('WT_SCRIPT_NAME', 'mediafirewall.php');
require './includes/session.php';
@@ -314,7 +316,7 @@ if ($type) {
// determine whether we have enough memory to watermark this image
if ($usewatermark) {
- if (!hasMemoryForImage($serverFilename)) {
+ if (!FunctionsMedia::hasMemoryForImage($serverFilename)) {
// not enough memory to watermark this file
$usewatermark = false;
}
diff --git a/medialist.php b/medialist.php
index 71823f7e33..3de060c52f 100644
--- a/medialist.php
+++ b/medialist.php
@@ -25,6 +25,9 @@ global $WT_TREE;
use Fisharebest\Webtrees\Controller\MediaController;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
use Fisharebest\Webtrees\Query\QueryMedia;
define('WT_SCRIPT_NAME', 'medialist.php');
@@ -89,7 +92,7 @@ $medialist = QueryMedia::mediaList(
</label>
</td>
<td class="optionbox wrap">
- <?php echo select_edit_control('folder', $folders, null, $folder); ?>
+ <?php echo FunctionsEdit::selectEditControl('folder', $folders, null, $folder); ?>
</td>
<?php if (Auth::isEditor($WT_TREE)): ?>
<td class="descriptionbox wrap">
@@ -288,8 +291,8 @@ if ($action === 'submit') {
}
echo '<br>';
echo '<div>';
- echo print_fact_sources($mediaobject->getGedcom(), 1);
- echo print_fact_notes($mediaobject->getGedcom(), 1);
+ echo FunctionsPrintFacts::printFactSources($mediaobject->getGedcom(), 1);
+ echo FunctionsPrint::printFactNotes($mediaobject->getGedcom(), 1);
echo '</div>';
foreach ($mediaobject->linkedIndividuals('OBJE') as $individual) {
echo '<a href="' . $individual->getHtmlUrl() . '">' . I18N::translate('View individual') . ' — ' . $individual->getFullname() . '</a><br>';
diff --git a/mediaviewer.php b/mediaviewer.php
index 21820e914d..771656bc62 100644
--- a/mediaviewer.php
+++ b/mediaviewer.php
@@ -24,6 +24,9 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\MediaController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'mediaviewer.php');
require './includes/session.php';
@@ -41,13 +44,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This media object has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -59,13 +62,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This media object has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -121,7 +124,7 @@ echo '<div id="media-tabs">';
<table class="facts_table">';
$facts = $controller->getFacts();
foreach ($facts as $f => $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
echo '</table>
</td>
@@ -151,27 +154,27 @@ echo '<div id="media-tabs">';
// Individuals linked to this media object
if ($linked_indi) {
- echo '<div id="indi-media">', format_indi_table($linked_indi), '</div>';
+ echo '<div id="indi-media">', FunctionsPrintLists::individualTable($linked_indi), '</div>';
}
// Families linked to this media object
if ($linked_fam) {
- echo '<div id="fam-media">', format_fam_table($linked_fam), '</div>';
+ echo '<div id="fam-media">', FunctionsPrintLists::familyTable($linked_fam), '</div>';
}
// Sources linked to this media object
if ($linked_sour) {
- echo '<div id="sources-media">', format_sour_table($linked_sour), '</div>';
+ echo '<div id="sources-media">', FunctionsPrintLists::sourceTable($linked_sour), '</div>';
}
// Repositories linked to this media object
if ($linked_repo) {
- echo '<div id="repo-media">', format_repo_table($linked_repo), '</div>';
+ echo '<div id="repo-media">', FunctionsPrintLists::repositoryTable($linked_repo), '</div>';
}
// medias linked to this media object
if ($linked_note) {
- echo '<div id="notes-media">', format_note_table($linked_note), '</div>';
+ echo '<div id="notes-media">', FunctionsPrintLists::noteTable($linked_note), '</div>';
}
echo '</div>';
echo '</div>';
diff --git a/modules_v3/GEDFact_assistant/_MEDIA/media_0_inverselink.php b/modules_v3/GEDFact_assistant/_MEDIA/media_0_inverselink.php
index 5ffe17d5ee..d1b1cd3d65 100644
--- a/modules_v3/GEDFact_assistant/_MEDIA/media_0_inverselink.php
+++ b/modules_v3/GEDFact_assistant/_MEDIA/media_0_inverselink.php
@@ -23,6 +23,8 @@ namespace Fisharebest\Webtrees;
*/
global $WT_TREE;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+
$more_links = Filter::get('more_links');
$exist_links = Filter::get('exist_links');
$gid = Filter::get('gid', WT_REGEX_XREF);
@@ -167,9 +169,9 @@ if ($action == 'choose' && $paramok) {
echo '</td><td style="padding-bottom: 2px; vertical-align: middle;">';
echo '&nbsp;';
echo '<img style="border-style:none;" src="', Theme::theme()->parameter('image-add'), '" alt="', I18N::translate('Add'), ' " title="', I18N::translate('Add'), '" align="middle" name="addLink" value="" onclick="blankwin(); return false;">';
- echo ' ', print_findindi_link('gid');
- echo ' ', print_findfamily_link('gid');
- echo ' ', print_findsource_link('gid');
+ echo ' ', FunctionsPrint::printFindIndividualLink('gid');
+ echo ' ', FunctionsPrint::printFindFamilyLink('gid');
+ echo ' ', FunctionsPrint::printFindSourceLink('gid');
echo '</td></tr></table>';
echo "<sub>" . I18N::translate('Enter or search for the ID of the individual, family, or source to which this media item should be linked.') . "</sub>";
echo '<br><br>';
diff --git a/modules_v3/GEDFact_assistant/census/census-edit.php b/modules_v3/GEDFact_assistant/census/census-edit.php
index 1670658780..215fb2244d 100644
--- a/modules_v3/GEDFact_assistant/census/census-edit.php
+++ b/modules_v3/GEDFact_assistant/census/census-edit.php
@@ -16,6 +16,9 @@ namespace Fisharebest\Webtrees;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Controller\SimpleController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsDate;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
/** @global SimpleController $controller */
global $controller;
@@ -40,11 +43,11 @@ echo '<h3>', $controller->getPageTitle(), '&nbsp;&nbsp;';
// When more languages are added to the wiki, we can expand or redesign this
switch (WT_LOCALE) {
case 'fr':
- echo wiki_help_link('/fr/Module_Assistant_Recensement');
+ echo FunctionsPrint::wikiHelpLink('/fr/Module_Assistant_Recensement');
break;
case 'en':
default:
- echo wiki_help_link('/en/Census_Assistant_module');
+ echo FunctionsPrint::wikiHelpLink('/en/Census_Assistant_module');
break;
}
echo '</h3>';
@@ -67,7 +70,7 @@ if (!($person->isDead())) {
$bdate = $person->getBirthDate();
$age = Date::getAgeGedcom($bdate);
if ($age != "") {
- $summary .= "<span class=\"label\">" . I18N::translate('Age') . ":</span><span class=\"field\"> " . get_age_at_event($age, true) . "</span>";
+ $summary .= "<span class=\"label\">" . I18N::translate('Age') . ":</span><span class=\"field\"> " . FunctionsDate::getAgeAtEvent($age, true) . "</span>";
}
}
$summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
@@ -1158,7 +1161,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
<div class="optionbox">
<textarea wrap="off" name="NOTE" id="NOTE"></textarea><br>
<center>
- <?php echo print_specialchar_link('NOTE'); ?>
+ <?php echo FunctionsPrint::printSpecialCharacterLink('NOTE'); ?>
</center>
</div>
</div>
@@ -1266,7 +1269,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censdate);
$submenu = new Menu($parentlinks);
@@ -1345,7 +1348,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censyear);
$submenu = new Menu($parentlinks);
@@ -1430,7 +1433,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $child);
+ $label = Functions::getCloseRelationshipName($person, $child);
$menu = new Menu($label);
print_pedigree_person_nav_cens($child->getXref(), $label, $censyear);
$submenu = new Menu($spouselinks);
@@ -1526,7 +1529,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censyear);
$submenu = new Menu($parentlinks);
@@ -1613,7 +1616,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censyear);
$submenu = new Menu($parentlinks);
@@ -1693,7 +1696,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $child);
+ $label = Functions::getCloseRelationshipName($person, $child);
$menu = new Menu($label);
print_pedigree_person_nav_cens($child->getXref(), $label, $censyear);
$submenu = new Menu($spouselinks);
@@ -1802,7 +1805,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censyear);
$submenu = new Menu($parentlinks);
@@ -1892,7 +1895,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $gparent);
+ $label = Functions::getCloseRelationshipName($person, $gparent);
$menu = new Menu($label);
print_pedigree_person_nav_cens($gparent->getXref(), $label, $censyear);
$submenu = new Menu($parentlinks);
@@ -1988,7 +1991,7 @@ $summary .= $person->formatFirstMajorFact(WT_EVENTS_DEAT, 2);
$fulmn = strip_tags($n['full']);
}
}
- $label = get_close_relationship_name($person, $child);
+ $label = Functions::getCloseRelationshipName($person, $child);
$menu = new Menu($label);
print_pedigree_person_nav_cens($child->getXref(), $label, $censyear);
$submenu = new Menu($spouselinks);
diff --git a/note.php b/note.php
index e066e8024d..bee72e557e 100644
--- a/note.php
+++ b/note.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\NoteController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
use Fisharebest\Webtrees\Module\CensusAssistantModule;
define('WT_SCRIPT_NAME', 'note.php');
@@ -35,13 +38,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This note has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -53,13 +56,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This note has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -161,26 +164,26 @@ if (Module::getModuleByName('GEDFact_assistant')) {
</tr>
<?php
foreach ($facts as $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
if ($controller->record->canEdit()) {
- print_add_new_fact($controller->record->getXref(), $facts, 'NOTE');
+ FunctionsPrint::printAddNewFact($controller->record->getXref(), $facts, 'NOTE');
}
?>
</table>
</div>
<?php
if ($linked_indi) {
- echo '<div id="indi-note">', format_indi_table($linked_indi), '</div>';
+ echo '<div id="indi-note">', FunctionsPrintLists::individualTable($linked_indi), '</div>';
}
if ($linked_fam) {
- echo '<div id="fam-note">', format_fam_table($linked_fam), '</div>';
+ echo '<div id="fam-note">', FunctionsPrintLists::familyTable($linked_fam), '</div>';
}
if ($linked_obje) {
- echo '<div id="media-note">', format_media_table($linked_obje), '</div>';
+ echo '<div id="media-note">', FunctionsPrintLists::mediaTable($linked_obje), '</div>';
}
if ($linked_sour) {
- echo '<div id="source-note">', format_sour_table($linked_sour), '</div>';
+ echo '<div id="source-note">', FunctionsPrintLists::sourceTable($linked_sour), '</div>';
}
?>
</div>
diff --git a/notelist.php b/notelist.php
index cba4ee79ad..1fdc31e331 100644
--- a/notelist.php
+++ b/notelist.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'notelist.php');
require './includes/session.php';
@@ -34,5 +36,5 @@ $controller->pageHeader();
echo '<div id="notelist-page">',
'<h2>', I18N::translate('Shared notes'), '</h2>',
- format_note_table(get_note_list($WT_TREE));
+ FunctionsPrintLists::noteTable(FunctionsDb::getNoteList($WT_TREE));
echo '</div>';
diff --git a/pedigree.php b/pedigree.php
index ab82f3fb79..10e9a015ca 100644
--- a/pedigree.php
+++ b/pedigree.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PedigreeController;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'pedigree.php');
require './includes/session.php';
@@ -173,16 +175,16 @@ $controller
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" id="rootid" name="rootid"
size="3" value="<?php echo $controller->root->getXref(); ?>">
- <?php echo print_findindi_link('rootid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('rootid'); ?>
</td>
<td class="optionbox center">
- <?php echo edit_field_integers('PEDIGREE_GENERATIONS', $controller->generations, 3, $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS')); ?>
+ <?php echo FunctionsEdit::editFieldInteger('PEDIGREE_GENERATIONS', $controller->generations, 3, $WT_TREE->getPreference('MAX_PEDIGREE_GENERATIONS')); ?>
</td>
<td class="optionbox center">
- <?php echo select_edit_control('orientation', array(0 => I18N::translate('Portrait'), 1 => I18N::translate('Landscape'), 2 => I18N::translate('Oldest at top'), 3 => I18N::translate('Oldest at bottom')), null, $controller->orientation); ?>
+ <?php echo FunctionsEdit::selectEditControl('orientation', array(0 => I18N::translate('Portrait'), 1 => I18N::translate('Landscape'), 2 => I18N::translate('Oldest at top'), 3 => I18N::translate('Oldest at bottom')), null, $controller->orientation); ?>
</td>
<td class="optionbox center">
- <?php echo two_state_checkbox("show_full", $controller->showFull()); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox("show_full", $controller->showFull()); ?>
</td>
</tr>
</table>
@@ -212,15 +214,15 @@ foreach ($controller->nodes as $i => $node) {
}
} else {
if (!$i) {
- echo $controller->get_menu();
+ echo $controller->getMenu();
}
}
- print_pedigree_person($controller->nodes[$i]['indi'], $controller->showFull());
+ FunctionsPrint::printPedigreePerson($controller->nodes[$i]['indi'], $controller->showFull());
if ($controller->orientation === $controller::OLDEST_AT_TOP) {
if (!$i) {
- echo $controller->get_menu();
+ echo $controller->getMenu();
}
} else {
if ($i >= $lastgenStart) {
diff --git a/placelist.php b/placelist.php
index 14a9492b98..5b02457573 100644
--- a/placelist.php
+++ b/placelist.php
@@ -24,6 +24,7 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'placelist.php');
require './includes/session.php';
@@ -226,13 +227,13 @@ case 'hierarchy':
}
echo '</ul>';
if ($myindilist) {
- echo '<div id="places-indi">', format_indi_table($myindilist), '</div>';
+ echo '<div id="places-indi">', FunctionsPrintLists::individualTable($myindilist), '</div>';
}
if ($myfamlist) {
- echo '<div id="places-fam">', format_fam_table($myfamlist), '</div>';
+ echo '<div id="places-fam">', FunctionsPrintLists::familyTable($myfamlist), '</div>';
}
if (!$myindilist && !$myfamlist) {
- echo '<div id="places-indi">', format_indi_table(array()), '</div>';
+ echo '<div id="places-indi">', FunctionsPrintLists::individualTable(array()), '</div>';
}
echo '</div>'; // <div id="places-tabs">
}
diff --git a/relationship.php b/relationship.php
index 15bf347fe9..a0c8c41ff6 100644
--- a/relationship.php
+++ b/relationship.php
@@ -24,6 +24,9 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\RelationshipController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'relationship.php');
require './includes/session.php';
@@ -65,11 +68,11 @@ if ($person1 && $person2) {
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="pid1" id="pid1" size="3" value="<?php echo $pid1; ?>">
- <?php echo print_findindi_link('pid1'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('pid1'); ?>
</td>
<td class="optionbox">
<label>
- <?php echo two_state_checkbox('show_full', $show_full); ?>
+ <?php echo FunctionsEdit::twoStateCheckbox('show_full', $show_full); ?>
<?php echo I18N::translate('Show details'); ?>
</label>
</td>
@@ -83,7 +86,7 @@ if ($person1 && $person2) {
</td>
<td class="optionbox">
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" name="pid2" id="pid2" size="3" value="<?php echo $pid2; ?>">
- <?php echo print_findindi_link('pid2'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('pid2'); ?>
<br>
<a href="#" onclick="var x = jQuery('#pid1').val(); jQuery('#pid1').val(jQuery('#pid2').val()); jQuery('#pid2').val(x); return false;"><?php /* I18N: Reverse the order of two individuals */ echo I18N::translate('Swap individuals'); ?></a>
</td>
@@ -125,7 +128,7 @@ if ($person1 && $person2) {
// Cannot see one of the families/individuals, due to privacy;
continue;
}
- echo '<h3>', I18N::translate('Relationship: %s', get_relationship_name_from_path(implode('', $relationships), $person1, $person2)), '</h3>';
+ echo '<h3>', I18N::translate('Relationship: %s', Functions::getRelationshipNameFromPath(implode('', $relationships), $person1, $person2)), '</h3>';
$num_paths++;
// Use a table/grid for layout.
@@ -147,18 +150,18 @@ if ($person1 && $person2) {
case 'bro':
case 'sis':
case 'sib':
- $table[$x + 1][$y] = '<div style="background:url(' . Theme::theme()->parameter('image-hline') . ') repeat-x center; width: 64px; text-align: center"><div style="height: 32px;">' . get_relationship_name_from_path($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px;">' . $horizontal_arrow . '</div></div>';
+ $table[$x + 1][$y] = '<div style="background:url(' . Theme::theme()->parameter('image-hline') . ') repeat-x center; width: 64px; text-align: center"><div style="height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px;">' . $horizontal_arrow . '</div></div>';
$x += 2;
break;
case 'son':
case 'dau':
case 'chi':
if ($n > 2 && preg_match('/fat|mot|par/', $relationships[$n - 2])) {
- $table[$x + 1][$y - 1] = '<div style="background:url(' . $diagonal2 . '); width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: end;">' . get_relationship_name_from_path($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px; text-align: start;">' . $down_arrow . '</div></div>';
+ $table[$x + 1][$y - 1] = '<div style="background:url(' . $diagonal2 . '); width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: end;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px; text-align: start;">' . $down_arrow . '</div></div>';
$x += 2;
} else {
$table[$x][$y - 1] = '<div style="background:url(' . Theme::theme()
- ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align: center;"><div style="display: inline-block; width:50%; line-height: 64px;">' . get_relationship_name_from_path($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="display: inline-block; width:50%; line-height: 64px;">' . $down_arrow . '</div></div>';
+ ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align: center;"><div style="display: inline-block; width:50%; line-height: 64px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="display: inline-block; width:50%; line-height: 64px;">' . $down_arrow . '</div></div>';
}
$y -= 2;
break;
@@ -166,11 +169,11 @@ if ($person1 && $person2) {
case 'mot':
case 'par':
if ($n > 2 && preg_match('/son|dau|chi/', $relationships[$n - 2])) {
- $table[$x + 1][$y + 1] = '<div style="background:url(' . $diagonal1 . '); background-position: top right; width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: start;">' . get_relationship_name_from_path($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px; text-align: end;">' . $up_arrow . '</div></div>';
+ $table[$x + 1][$y + 1] = '<div style="background:url(' . $diagonal1 . '); background-position: top right; width: 64px; height: 64px; text-align: center;"><div style="height: 32px; text-align: start;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="height: 32px; text-align: end;">' . $up_arrow . '</div></div>';
$x += 2;
} else {
$table[$x][$y + 1] = '<div style="background:url(' . Theme::theme()
- ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align:center; "><div style="display: inline-block; width: 50%; line-height: 32px;">' . get_relationship_name_from_path($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="display: inline-block; width: 50%; line-height: 32px">' . $up_arrow . '</div></div>';
+ ->parameter('image-vline') . ') repeat-y center; height: 64px; text-align:center; "><div style="display: inline-block; width: 50%; line-height: 32px;">' . Functions::getRelationshipNameFromPath($relationships[$n], Individual::getInstance($path[$n - 1], $WT_TREE), Individual::getInstance($path[$n + 1], $WT_TREE)) . '</div><div style="display: inline-block; width: 50%; line-height: 32px">' . $up_arrow . '</div></div>';
}
$y += 2;
break;
@@ -181,7 +184,7 @@ if ($person1 && $person2) {
} else {
$individual = Individual::getInstance($xref, $WT_TREE);
ob_start();
- print_pedigree_person($individual, $show_full);
+ FunctionsPrint::printPedigreePerson($individual, $show_full);
$table[$x][$y] = ob_get_clean();
}
}
diff --git a/repo.php b/repo.php
index d8462df3cf..2ed20a7726 100644
--- a/repo.php
+++ b/repo.php
@@ -17,6 +17,9 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\RepositoryController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'repo.php');
require './includes/session.php';
@@ -34,13 +37,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This repository has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -52,13 +55,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This repository has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -120,12 +123,12 @@ echo '<div id="repo-tabs">
// Print the facts
foreach ($facts as $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
// new fact link
if ($controller->record->canEdit()) {
- print_add_new_fact($controller->record->getXref(), $facts, 'REPO');
+ FunctionsPrint::printAddNewFact($controller->record->getXref(), $facts, 'REPO');
}
echo '</table>
</div>';
@@ -133,7 +136,7 @@ echo '<div id="repo-tabs">
// Sources linked to this repository
if ($linked_sour) {
echo '<div id="source-repo">';
- echo format_sour_table($linked_sour);
+ echo FunctionsPrintLists::sourceTable($linked_sour);
echo '</div>';
}
diff --git a/repolist.php b/repolist.php
index 8b0fb3fb89..1d51a802b2 100644
--- a/repolist.php
+++ b/repolist.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'repolist.php');
require './includes/session.php';
@@ -34,5 +36,5 @@ $controller->pageHeader();
echo '<div id="repolist-page">',
'<h2>', I18N::translate('Repositories'), '</h2>';
- echo format_repo_table(get_repo_list($WT_TREE));
+ echo FunctionsPrintLists::repositoryTable(FunctionsDb::getRepositoryList($WT_TREE));
echo '</div>';
diff --git a/reportengine.php b/reportengine.php
index 657838bab1..d7aecb6f80 100644
--- a/reportengine.php
+++ b/reportengine.php
@@ -16,6 +16,7 @@ namespace Fisharebest\Webtrees;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
use Fisharebest\Webtrees\Report\ReportHtml;
use Fisharebest\Webtrees\Report\ReportParserGenerate;
use Fisharebest\Webtrees\Report\ReportParserSetup;
@@ -142,7 +143,7 @@ case 'setup':
->addExternalJavascript(WT_AUTOCOMPLETE_JS_URL)
->addInlineJavascript('autocomplete();');
- init_calendar_popup();
+ FunctionsPrint::initializeCalendarPopup();
echo '<div id="reportengine-page">
<form name="setupreport" method="get" action="reportengine.php" onsubmit="if (this.output[1].checked) {this.target=\'_blank\';}">
@@ -233,13 +234,13 @@ case 'setup':
if (isset($input['lookup'])) {
echo '<input type="hidden" name="type[', Filter::escapeHtml($input['name']), ']" value="', Filter::escapeHtml($input['lookup']), '">';
if ($input['lookup'] == 'INDI') {
- echo print_findindi_link('pid');
+ echo FunctionsPrint::printFindIndividualLink('pid');
} elseif ($input['lookup'] == 'PLAC') {
- echo print_findplace_link($input['name']);
+ echo FunctionsPrint::printFindPlaceLink($input['name']);
} elseif ($input['lookup'] == 'FAM') {
- echo print_findfamily_link('famid');
+ echo FunctionsPrint::printFindFamilyLink('famid');
} elseif ($input['lookup'] == 'SOUR') {
- echo print_findsource_link($input['name']);
+ echo FunctionsPrint::printFindSourceLink($input['name']);
} elseif ($input['lookup'] == 'DATE') {
echo ' <a href="#" onclick="cal_toggleDate(\'div_', Filter::EscapeJs($input['name']), '\', \'', Filter::EscapeJs($input['name']), '\'); return false;" class="icon-button_calendar" title="', I18N::translate('Select a date'), '"></a>';
echo '<div id="div_', Filter::EscapeHtml($input['name']), '" style="position:absolute;visibility:hidden;background-color:white;"></div>';
diff --git a/search.php b/search.php
index 147e41dfbf..044c682fe6 100644
--- a/search.php
+++ b/search.php
@@ -17,6 +17,7 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\SearchController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'search.php');
require './includes/session.php';
@@ -76,7 +77,7 @@ function checknames(frm) {
</div>
<div class="value">
<input id="query" type="text" name="query" value="<?php echo Filter::escapeHtml($controller->query); ?>" size="40" autofocus>
- <?php echo print_specialchar_link('query'); ?>
+ <?php echo FunctionsPrint::printSpecialCharacterLink('query'); ?>
</div>
<div class="label">
<?php echo I18N::translate('Records'); ?>
diff --git a/setup.php b/setup.php
index 45835ee7ae..a057425455 100644
--- a/setup.php
+++ b/setup.php
@@ -16,6 +16,7 @@ namespace Fisharebest\Webtrees;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsEdit;
use PDOException;
error_reporting(E_ALL);
@@ -104,7 +105,7 @@ if (!isset($_POST['lang'])) {
echo
'<p>', I18N::translate('Change language'), ' ',
- select_edit_control('change_lang', $installed_languages, null, WT_LOCALE, 'onchange="window.location=\'' . WT_SCRIPT_NAME . '?lang=\'+this.value;">'),
+ FunctionsEdit::selectEditControl('change_lang', $installed_languages, null, WT_LOCALE, 'onchange="window.location=\'' . WT_SCRIPT_NAME . '?lang=\'+this.value;">'),
'</p>',
'<h2>', I18N::translate('Checking server configuration'), '</h2>';
$warnings = false;
diff --git a/source.php b/source.php
index 8a61e92a95..988938a18c 100644
--- a/source.php
+++ b/source.php
@@ -24,6 +24,9 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\SourceController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
+use Fisharebest\Webtrees\Functions\FunctionsPrintFacts;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'source.php');
require './includes/session.php';
@@ -41,13 +44,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the deletion and then accept or reject it.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This source has been deleted. The deletion will need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
} elseif ($controller->record->isPendingAddtion()) {
@@ -59,13 +62,13 @@ if ($controller->record && $controller->record->canShow()) {
'<a href="#" onclick="accept_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'accept') . '</a>',
'<a href="#" onclick="reject_changes(\'' . $controller->record->getXref() . '\');">' . I18N::translateContext('You should review the changes and then accept or reject them.', 'reject') . '</a>'
),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
} elseif (Auth::isEditor($controller->record->getTree())) {
echo
'<p class="ui-state-highlight">',
I18N::translate('This source has been edited. The changes need to be reviewed by a moderator.'),
- ' ', help_link('pending_changes'),
+ ' ', FunctionsPrint::helpLink('pending_changes'),
'</p>';
}
}
@@ -144,19 +147,19 @@ echo '<div id="source-tabs">
// Print the facts
foreach ($facts as $fact) {
- print_fact($fact, $controller->record);
+ FunctionsPrintFacts::printFact($fact, $controller->record);
}
// new fact link
if ($controller->record->canEdit()) {
- print_add_new_fact($controller->record->getXref(), $facts, 'SOUR');
+ FunctionsPrint::printAddNewFact($controller->record->getXref(), $facts, 'SOUR');
// new media
if ($controller->record->getTree()->getPreference('MEDIA_UPLOAD') >= Auth::accessLevel($WT_TREE)) {
echo '<tr><td class="descriptionbox">';
echo GedcomTag::getLabel('OBJE');
echo '</td><td class="optionbox">';
echo '<a href="#" onclick="window.open(\'addmedia.php?action=showmediaform&amp;linktoid=', $controller->record->getXref(), '\', \'_blank\', edit_window_specs); return false;">', I18N::translate('Add a new media object'), '</a>';
- echo help_link('OBJE');
+ echo FunctionsPrint::helpLink('OBJE');
echo '<br>';
echo '<a href="#" onclick="window.open(\'inverselink.php?linktoid=', $controller->record->getXref(), '&amp;linkto=source\', \'_blank\', find_window_specs); return false;">', I18N::translate('Link to an existing media object'), '</a>';
echo '</td></tr>';
@@ -167,19 +170,19 @@ echo '<div id="source-tabs">
// Individuals linked to this source
if ($linked_indi) {
- echo '<div id="indi-sources">', format_indi_table($linked_indi), '</div>';
+ echo '<div id="indi-sources">', FunctionsPrintLists::individualTable($linked_indi), '</div>';
}
// Families linked to this source
if ($linked_fam) {
- echo '<div id="fam-sources">', format_fam_table($linked_fam), '</div>';
+ echo '<div id="fam-sources">', FunctionsPrintLists::familyTable($linked_fam), '</div>';
}
// Media Items linked to this source
if ($linked_obje) {
- echo '<div id="media-sources">', format_media_table($linked_obje), '</div>';
+ echo '<div id="media-sources">', FunctionsPrintLists::mediaTable($linked_obje), '</div>';
}
// Shared Notes linked to this source
if ($linked_note) {
- echo '<div id="note-sources">', format_note_table($linked_note), '</div>';
+ echo '<div id="note-sources">', FunctionsPrintLists::noteTable($linked_note), '</div>';
}
echo '</div>'; //close div "source-tabs"
echo '</div>'; //close div "source-details"
diff --git a/sourcelist.php b/sourcelist.php
index 78ca2d324c..4139376481 100644
--- a/sourcelist.php
+++ b/sourcelist.php
@@ -24,6 +24,8 @@ namespace Fisharebest\Webtrees;
global $WT_TREE;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsDb;
+use Fisharebest\Webtrees\Functions\FunctionsPrintLists;
define('WT_SCRIPT_NAME', 'sourcelist.php');
require './includes/session.php';
@@ -34,5 +36,5 @@ $controller->pageHeader();
echo '<div id="sourcelist-page">',
'<h2>', I18N::translate('Sources'), '</h2>';
- echo format_sour_table(get_source_list($WT_TREE));
+ echo FunctionsPrintLists::sourceTable(FunctionsDb::getSourceList($WT_TREE));
echo '</div>';
diff --git a/statistics.php b/statistics.php
index 0cfddc201b..c2f89bfc80 100644
--- a/statistics.php
+++ b/statistics.php
@@ -25,6 +25,7 @@ global $WT_TREE;
use Fisharebest\Webtrees\Controller\AjaxController;
use Fisharebest\Webtrees\Controller\PageController;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
define('WT_SCRIPT_NAME', 'statistics.php');
require './includes/session.php';
@@ -587,7 +588,7 @@ if (!$ajax) {
<br>
</div>
<div id="surname_opt" style="display:none;">';
- echo GedcomTag::getLabel('SURN'), help_link('google_chart_surname'), '<br><input data-autocomplete-type="SURN" type="text" name="SURN" size="20">';
+ echo GedcomTag::getLabel('SURN'), FunctionsPrint::helpLink('google_chart_surname'), '<br><input data-autocomplete-type="SURN" type="text" name="SURN" size="20">';
echo '<br>
</div>';
echo I18N::translate('Geographical area');
diff --git a/statisticsplot.php b/statisticsplot.php
index a7681345b6..ec16d090c7 100644
--- a/statisticsplot.php
+++ b/statisticsplot.php
@@ -623,7 +623,7 @@ function my_plot($mytitle, $xdata, $xtitle, $ydata, $ytitle, $legend) {
}
if ($percentage) {
$ypercentmax = 0;
- $yt = array();
+ $yt = array();
for ($i = 0; $i < $stop; $i++) {
if (isset($ydata[$i])) {
$ymax = max($ydata[$i]);
diff --git a/tests/app/Functions/FunctionsChartsTest.php b/tests/app/Functions/FunctionsChartsTest.php
new file mode 100644
index 0000000000..7a73a863a7
--- /dev/null
+++ b/tests/app/Functions/FunctionsChartsTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_charts.php
+ */
+class FunctionsChartsTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsCharts'));
+ }
+}
diff --git a/tests/includes/functions/FunctionsDateTest.php b/tests/app/Functions/FunctionsDateTest.php
index 5ce76a452f..6d1a2f2331 100644
--- a/tests/includes/functions/FunctionsDateTest.php
+++ b/tests/app/Functions/FunctionsDateTest.php
@@ -20,29 +20,9 @@
*/
class FunctionsDateTest extends PHPUnit_Framework_TestCase {
/**
- * Prepare the environment for these tests
+ * Test that the class exists
*/
- public function setUp() {
- }
-
- /**
- * Test that function get_age_at_event() exists in the correct namespace.
- */
- public function testFunctionGetAgeAtEventExists() {
- $this->assertEquals(function_exists('get_age_at_event'), true);
- }
-
- /**
- * Test that function format_timestamp() exists in the correct namespace.
- */
- public function testFunctionFormatTimestampExists() {
- $this->assertEquals(function_exists('format_timestamp'), true);
- }
-
- /**
- * Test that function timestamp_to_gedcom_date() exists in the correct namespace.
- */
- public function testFunctionTimestampToGedcomDateExists() {
- $this->assertEquals(function_exists('timestamp_to_gedcom_date'), true);
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsDate'));
}
}
diff --git a/tests/app/Functions/FunctionsDbTest.php b/tests/app/Functions/FunctionsDbTest.php
new file mode 100644
index 0000000000..568cebb1b1
--- /dev/null
+++ b/tests/app/Functions/FunctionsDbTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_db.php
+ */
+class FunctionsDbTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsDb'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsEditTest.php b/tests/app/Functions/FunctionsEditTest.php
new file mode 100644
index 0000000000..dc0736cb44
--- /dev/null
+++ b/tests/app/Functions/FunctionsEditTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_edit.php
+ */
+class FunctionsEditTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsEdit'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsExportTest.php b/tests/app/Functions/FunctionsExportTest.php
new file mode 100644
index 0000000000..9f45befc0f
--- /dev/null
+++ b/tests/app/Functions/FunctionsExportTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_export.php
+ */
+class FunctionsExportTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsExport'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsImportTest.php b/tests/app/Functions/FunctionsImportTest.php
new file mode 100644
index 0000000000..1809f1d551
--- /dev/null
+++ b/tests/app/Functions/FunctionsImportTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_import.php
+ */
+class FunctionsImportTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsExport'));
+ }
+}
diff --git a/tests/includes/functions/FunctionsMediaDbTest.php b/tests/app/Functions/FunctionsMediaTest.php
index fb4956e5e8..3bdc182783 100644
--- a/tests/includes/functions/FunctionsMediaDbTest.php
+++ b/tests/app/Functions/FunctionsMediaTest.php
@@ -14,38 +14,26 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+use Fisharebest\Webtrees\Functions\FunctionsMedia;
/**
* Unit tests for the global functions in the file includes/functions/functions_mediadb.php
*/
-class FunctionsMediaDbTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
+class FunctionsMediaTest extends PHPUnit_Framework_TestCase {
/**
* Test the function return_bytes().
*/
public function testFunctionReturnBytes() {
- $this->assertSame(-1, return_bytes(''));
- $this->assertSame(-1, return_bytes('-1'));
- $this->assertSame(42, return_bytes('42'));
- $this->assertSame(42, return_bytes('42b'));
- $this->assertSame(42, return_bytes('42B'));
- $this->assertSame(43008, return_bytes('42k'));
- $this->assertSame(43008, return_bytes('42K'));
- $this->assertSame(44040192, return_bytes('42m'));
- $this->assertSame(44040192, return_bytes('42M'));
- $this->assertSame(45097156608, return_bytes('42g'));
- $this->assertSame(45097156608, return_bytes('42G'));
- }
-
- /**
- * Test that function hasMemoryForImage() exists in the correct namespace.
- */
- public function testFunctionHasMemoryForImageExists() {
- $this->assertEquals(function_exists('hasMemoryForImage'), true);
+ $this->assertSame(-1, FunctionsMedia::sizeToBytes(''));
+ $this->assertSame(-1, FunctionsMedia::sizeToBytes('-1'));
+ $this->assertSame(42, FunctionsMedia::sizeToBytes('42'));
+ $this->assertSame(42, FunctionsMedia::sizeToBytes('42b'));
+ $this->assertSame(42, FunctionsMedia::sizeToBytes('42B'));
+ $this->assertSame(43008, FunctionsMedia::sizeToBytes('42k'));
+ $this->assertSame(43008, FunctionsMedia::sizeToBytes('42K'));
+ $this->assertSame(44040192, FunctionsMedia::sizeToBytes('42m'));
+ $this->assertSame(44040192, FunctionsMedia::sizeToBytes('42M'));
+ $this->assertSame(45097156608, FunctionsMedia::sizeToBytes('42g'));
+ $this->assertSame(45097156608, FunctionsMedia::sizeToBytes('42G'));
}
}
diff --git a/tests/app/Functions/FunctionsPrintFactsTest.php b/tests/app/Functions/FunctionsPrintFactsTest.php
new file mode 100644
index 0000000000..0de3f03089
--- /dev/null
+++ b/tests/app/Functions/FunctionsPrintFactsTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_print_facts.php
+ */
+class FunctionsPrintFactsTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsPrintFacts'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsPrintListsTest.php b/tests/app/Functions/FunctionsPrintListsTest.php
new file mode 100644
index 0000000000..8949264312
--- /dev/null
+++ b/tests/app/Functions/FunctionsPrintListsTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_print_lists.php
+ */
+class FunctionsPrintListsTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsPrintLists'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsPrintTest.php b/tests/app/Functions/FunctionsPrintTest.php
new file mode 100644
index 0000000000..4781ccf144
--- /dev/null
+++ b/tests/app/Functions/FunctionsPrintTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_print.php
+ */
+class FunctionsPrintTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsPrint'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsRtlTest.php b/tests/app/Functions/FunctionsRtlTest.php
new file mode 100644
index 0000000000..379334c1e4
--- /dev/null
+++ b/tests/app/Functions/FunctionsRtlTest.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions_rtl.php
+ */
+class FunctionsRtlTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test that the class exists
+ */
+ public function testClassExists() {
+ $this->assertTrue(class_exists('\Fisharebest\Webtrees\Functions\FunctionsRtl'));
+ }
+}
diff --git a/tests/app/Functions/FunctionsTest.php b/tests/app/Functions/FunctionsTest.php
new file mode 100644
index 0000000000..6713483e60
--- /dev/null
+++ b/tests/app/Functions/FunctionsTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2015 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+use Fisharebest\Webtrees\Functions\Functions;
+
+/**
+ * Unit tests for the global functions in the file includes/functions/functions.php
+ */
+class FunctionsTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Tests for function isFileExternal()
+ */
+ public function testFunctionIsFileExternal() {
+ $this->assertEquals(Functions::isFileExternal('http://www.example.com/file.txt'), true);
+ $this->assertEquals(Functions::isFileExternal('file.txt'), false);
+ $this->assertEquals(Functions::isFileExternal('folder/file.txt'), false);
+ $this->assertEquals(Functions::isFileExternal('folder\\file.txt'), false);
+ $this->assertEquals(Functions::isFileExternal('/folder/file.txt'), false);
+ $this->assertEquals(Functions::isFileExternal('\\folder\\file.txt'), false);
+ $this->assertEquals(Functions::isFileExternal('C:\\folder\\file.txt'), false);
+ }
+}
diff --git a/tests/includes/functions/FunctionsChartsTest.php b/tests/includes/functions/FunctionsChartsTest.php
deleted file mode 100644
index 705206654d..0000000000
--- a/tests/includes/functions/FunctionsChartsTest.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_charts.php
- */
-class FunctionsChartsTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function print_sosa_number() exists in the correct namespace.
- */
- public function testFunctionPrintSosaNumberExists() {
- $this->assertEquals(function_exists('print_sosa_number'), true);
- }
-
- /**
- * Test that function print_family_children() exists in the correct namespace.
- */
- public function testFunctionPrintFamilyParentsExists() {
- $this->assertEquals(function_exists('print_family_children'), true);
- }
-
- /**
- * Test that function print_family_children() exists in the correct namespace.
- */
- public function testFunctionPrintFamilyChildrenExists() {
- $this->assertEquals(function_exists('print_family_children'), true);
- }
-
- /**
- * Test that function print_sosa_family() exists in the correct namespace.
- */
- public function testFunctionPrintSosaFamilyExists() {
- $this->assertEquals(function_exists('print_sosa_family'), true);
- }
-
- /**
- * Test that function print_url_arrow() exists in the correct namespace.
- */
- public function testFunctionPrintUrlArrowExists() {
- $this->assertEquals(function_exists('print_url_arrow'), true);
- }
-
- /**
- * Test that function get_sosa_name() exists in the correct namespace.
- */
- public function testFunctionGetSosaNameExists() {
- $this->assertEquals(function_exists('get_sosa_name'), true);
- }
-
- /**
- * Test that function print_cousins() exists in the correct namespace.
- */
- public function testFunctionPrintCousinsExists() {
- $this->assertEquals(function_exists('print_cousins'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsDbTest.php b/tests/includes/functions/FunctionsDbTest.php
deleted file mode 100644
index 2d04e69c52..0000000000
--- a/tests/includes/functions/FunctionsDbTest.php
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_db.php
- */
-class FunctionsDbTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function fetch_all_links() exists in the correct namespace.
- */
- public function testFunctionFetchAllLinksExists() {
- $this->assertEquals(function_exists('fetch_all_links'), true);
- }
-
- /**
- * Test that function get_source_list() exists in the correct namespace.
- */
- public function testFunctionGetSourceListExists() {
- $this->assertEquals(function_exists('get_source_list'), true);
- }
-
- /**
- * Test that function get_repo_list() exists in the correct namespace.
- */
- public function testFunctionGetRepoListExists() {
- $this->assertEquals(function_exists('get_repo_list'), true);
- }
-
- /**
- * Test that function get_note_list() exists in the correct namespace.
- */
- public function testFunctionGetNoteListExists() {
- $this->assertEquals(function_exists('get_note_list'), true);
- }
-
- /**
- * Test that function search_indis() exists in the correct namespace.
- */
- public function testFunctionSearchIndisExists() {
- $this->assertEquals(function_exists('search_indis'), true);
- }
-
- /**
- * Test that function search_indis_names() exists in the correct namespace.
- */
- public function testFunctionSearchIndisNamesExists() {
- $this->assertEquals(function_exists('search_indis_names'), true);
- }
-
- /**
- * Test that function search_indis_soundex() exists in the correct namespace.
- */
- public function testFunctionSearchIndisSoundexExists() {
- $this->assertEquals(function_exists('search_indis_soundex'), true);
- }
-
- /**
- * Test that function get_recent_changes() exists in the correct namespace.
- */
- public function testFunctionGetRecentChangesExists() {
- $this->assertEquals(function_exists('get_recent_changes'), true);
- }
-
- /**
- * Test that function search_fams() exists in the correct namespace.
- */
- public function testFunctionSearchFamsExists() {
- $this->assertEquals(function_exists('search_fams'), true);
- }
-
- /**
- * Test that function search_fams_names() exists in the correct namespace.
- */
- public function testFunctionSearchFamsNamesExists() {
- $this->assertEquals(function_exists('search_fams_names'), true);
- }
-
- /**
- * Test that function search_sources() exists in the correct namespace.
- */
- public function testFunctionSearchSourcesExists() {
- $this->assertEquals(function_exists('search_sources'), true);
- }
-
- /**
- * Test that function search_notes() exists in the correct namespace.
- */
- public function testFunctionSearchNotesExists() {
- $this->assertEquals(function_exists('search_notes'), true);
- }
-
- /**
- * Test that function search_repos() exists in the correct namespace.
- */
- public function testFunctionSearchReposExists() {
- $this->assertEquals(function_exists('search_repos'), true);
- }
-
- /**
- * Test that function find_rin_id() exists in the correct namespace.
- */
- public function testFunctionFindRinIdExists() {
- $this->assertEquals(function_exists('find_rin_id'), true);
- }
-
- /**
- * Test that function get_common_surnames() exists in the correct namespace.
- */
- public function testFunctionGetCommonSurnamesExists() {
- $this->assertEquals(function_exists('get_common_surnames'), true);
- }
-
- /**
- * Test that function get_top_surnames() exists in the correct namespace.
- */
- public function testFunctionGetTopSurnamesExists() {
- $this->assertEquals(function_exists('get_top_surnames'), true);
- }
-
- /**
- * Test that function get_anniversary_events() exists in the correct namespace.
- */
- public function testFunctionGetAnniversaryEventsExists() {
- $this->assertEquals(function_exists('get_anniversary_events'), true);
- }
-
- /**
- * Test that function get_calendar_events() exists in the correct namespace.
- */
- public function testFunctionGetCalendarEventsExists() {
- $this->assertEquals(function_exists('get_calendar_events'), true);
- }
-
- /**
- * Test that function is_media_used_in_other_gedcom() exists in the correct namespace.
- */
- public function testFunctionIsMediaUsedInOtherGedcomExists() {
- $this->assertEquals(function_exists('is_media_used_in_other_gedcom'), true);
- }
-
- /**
- * Test that function get_user_blocks() exists in the correct namespace.
- */
- public function testFunctionGetUserBlocksExists() {
- $this->assertEquals(function_exists('get_user_blocks'), true);
- }
-
- /**
- * Test that function get_gedcom_blocks() exists in the correct namespace.
- */
- public function testFunctionGetGedcomBlocksExists() {
- $this->assertEquals(function_exists('get_gedcom_blocks'), true);
- }
-
- /**
- * Test that function update_favorites() exists in the correct namespace.
- */
- public function testFunctionUpdateFavoritesExists() {
- $this->assertEquals(function_exists('update_favorites'), true);
- }
-
- /**
- * Test that function get_events_list() exists in the correct namespace.
- */
- public function testFunctionGetEventsListExists() {
- $this->assertEquals(function_exists('get_events_list'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsEditTest.php b/tests/includes/functions/FunctionsEditTest.php
deleted file mode 100644
index 73476f9b86..0000000000
--- a/tests/includes/functions/FunctionsEditTest.php
+++ /dev/null
@@ -1,280 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_edit.php
- */
-class FunctionsEditTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- defined('WT_ROOT') || define('WT_ROOT', '');
- }
-
- /**
- * Test that function select_edit_control() exists in the correct namespace.
- */
- public function testFunctionSelectEditControlExists() {
- $this->assertEquals(function_exists('select_edit_control'), true);
- }
-
- /**
- * Test that function radio_buttons() exists in the correct namespace.
- */
- public function testFunctionRadioButtonsExists() {
- $this->assertEquals(function_exists('radio_buttons'), true);
- }
-
- /**
- * Test that function edit_field_yes_no() exists in the correct namespace.
- */
- public function testFunctionEditFieldYesNoExists() {
- $this->assertEquals(function_exists('edit_field_yes_no'), true);
- }
-
- /**
- * Test that function checkbox() exists in the correct namespace.
- */
- public function testFunctionCheckboxExists() {
- $this->assertEquals(function_exists('checkbox'), true);
- }
-
- /**
- * Test that function two_state_checkbox() exists in the correct namespace.
- */
- public function testFunctionTwoStateCheckboxExists() {
- $this->assertEquals(function_exists('two_state_checkbox'), true);
- }
-
- /**
- * Test that function edit_language_checkboxes() exists in the correct namespace.
- */
- public function testFunctionEditLanguageCheckboxesExists() {
- $this->assertEquals(function_exists('edit_language_checkboxes'), true);
- }
-
- /**
- * Test that function edit_field_access_level() exists in the correct namespace.
- */
- public function testFunctionEditFieldAccessLevelExists() {
- $this->assertEquals(function_exists('edit_field_access_level'), true);
- }
-
- /**
- * Test that function edit_field_resn() exists in the correct namespace.
- */
- public function testFunctionEditFieldResnExists() {
- $this->assertEquals(function_exists('edit_field_resn'), true);
- }
-
- /**
- * Test that function edit_field_contact() exists in the correct namespace.
- */
- public function testFunctionEditFieldContactExists() {
- $this->assertEquals(function_exists('edit_field_contact'), true);
- }
-
- /**
- * Test that function edit_field_language() exists in the correct namespace.
- */
- public function testFunctionEditFieldLangaugeExists() {
- $this->assertEquals(function_exists('edit_field_language'), true);
- }
-
- /**
- * Test that function edit_field_integers() exists in the correct namespace.
- */
- public function testFunctionEditFieldIntegersExists() {
- $this->assertEquals(function_exists('edit_field_integers'), true);
- }
-
- /**
- * Test that function edit_field_username() exists in the correct namespace.
- */
- public function testFunctionEditFieldUsernameExists() {
- $this->assertEquals(function_exists('edit_field_username'), true);
- }
-
- /**
- * Test that function edit_field_adop() exists in the correct namespace.
- */
- public function testFunctionEditFieldAdopExists() {
- $this->assertEquals(function_exists('edit_field_adop'), true);
- }
-
- /**
- * Test that function edit_field_pedi() exists in the correct namespace.
- */
- public function testFunctionEditFieldPediExists() {
- $this->assertEquals(function_exists('edit_field_pedi'), true);
- }
-
- /**
- * Test that function edit_field_name_type() exists in the correct namespace.
- */
- public function testFunctionEditFieldNameTypeExists() {
- $this->assertEquals(function_exists('edit_field_name_type'), true);
- }
-
- /**
- * Test that function edit_field_rela() exists in the correct namespace.
- */
- public function testFunctionEditFieldRelaExists() {
- $this->assertEquals(function_exists('edit_field_rela'), true);
- }
-
- /**
- * Test that function remove_links() exists in the correct namespace.
- */
- public function testFunctionRemoveLinksExists() {
- $this->assertEquals(function_exists('remove_links'), true);
- }
-
- /**
- * Test that function print_calendar_popup() exists in the correct namespace.
- */
- public function testFunctionPrintCalendarPopupExists() {
- $this->assertEquals(function_exists('print_calendar_popup'), true);
- }
-
- /**
- * Test that function print_addnewmedia_link() exists in the correct namespace.
- */
- public function testFunctionPrintAddnewmediaLinkExists() {
- $this->assertEquals(function_exists('print_addnewmedia_link'), true);
- }
-
- /**
- * Test that function print_addnewrepository_link() exists in the correct namespace.
- */
- public function testFunctionPrintAddnewrepositoryLinkExists() {
- $this->assertEquals(function_exists('print_addnewrepository_link'), true);
- }
-
- /**
- * Test that function print_addnewnote_link() exists in the correct namespace.
- */
- public function testFunctionPrintAddnewnoteLinkExists() {
- $this->assertEquals(function_exists('print_addnewnote_link'), true);
- }
-
- /**
- * Test that function print_editnote_link() exists in the correct namespace.
- */
- public function testFunctionPrintEditnoteLinkExists() {
- $this->assertEquals(function_exists('print_editnote_link'), true);
- }
-
- /**
- * Test that function print_addnewsource_link() exists in the correct namespace.
- */
- public function testFunctionPrintAddnewsourceLinkExists() {
- $this->assertEquals(function_exists('print_addnewsource_link'), true);
- }
-
- /**
- * Test that function add_simple_tag() exists in the correct namespace.
- */
- public function testFunctionAddSimpleTagExists() {
- $this->assertEquals(function_exists('add_simple_tag'), true);
- }
-
- /**
- * Test that function print_add_layer() exists in the correct namespace.
- */
- public function testFunctionPrintAddLayerExists() {
- $this->assertEquals(function_exists('print_add_layer'), true);
- }
-
- /**
- * Test that function addSimpleTags() exists in the correct namespace.
- */
- public function testFunctionAddSimpleTagsExists() {
- $this->assertEquals(function_exists('addSimpleTags'), true);
- }
-
- /**
- * Test that function addNewName() exists in the correct namespace.
- */
- public function testFunctionAddNewNameExists() {
- $this->assertEquals(function_exists('addNewName'), true);
- }
-
- /**
- * Test that function addNewSex() exists in the correct namespace.
- */
- public function testFunctionAddNewSexExists() {
- $this->assertEquals(function_exists('addNewSex'), true);
- }
-
- /**
- * Test that function addNewFact() exists in the correct namespace.
- */
- public function testFunctionAddNewFactExists() {
- $this->assertEquals(function_exists('addNewFact'), true);
- }
-
- /**
- * Test that function splitSOUR() exists in the correct namespace.
- */
- public function testFunctionSplitSourExists() {
- $this->assertEquals(function_exists('splitSOUR'), true);
- }
-
- /**
- * Test that function updateSOUR() exists in the correct namespace.
- */
- public function testFunctionUpdateSourExists() {
- $this->assertEquals(function_exists('updateSOUR'), true);
- }
-
- /**
- * Test that function updateRest() exists in the correct namespace.
- */
- public function testFunctionUpdateRestExists() {
- $this->assertEquals(function_exists('updateRest'), true);
- }
-
- /**
- * Test that function handle_updates() exists in the correct namespace.
- */
- public function testFunctionHandleUpdatesExists() {
- $this->assertEquals(function_exists('handle_updates'), true);
- }
-
- /**
- * Test that function create_add_form() exists in the correct namespace.
- */
- public function testFunctionCreateAddFromExists() {
- $this->assertEquals(function_exists('create_add_form'), true);
- }
-
- /**
- * Test that function create_edit_form() exists in the correct namespace.
- */
- public function testFunctionCreateEditFromExists() {
- $this->assertEquals(function_exists('create_edit_form'), true);
- }
-
- /**
- * Test that function insert_missing_subtags() exists in the correct namespace.
- */
- public function testFunctionInsertMissingSubtagsExists() {
- $this->assertEquals(function_exists('insert_missing_subtags'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsExportTest.php b/tests/includes/functions/FunctionsExportTest.php
deleted file mode 100644
index 7e802c97b2..0000000000
--- a/tests/includes/functions/FunctionsExportTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_export.php
- */
-class FunctionsExportTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function reformat_record_export() exists in the correct namespace.
- */
- public function testFunctionReformatRecordExportExists() {
- $this->assertEquals(function_exists('reformat_record_export'), true);
- }
-
- /**
- * Test that function gedcom_header() exists in the correct namespace.
- */
- public function testFunctionGedcomHeaderExists() {
- $this->assertEquals(function_exists('gedcom_header'), true);
- }
-
- /**
- * Test that function convert_media_path() exists in the correct namespace.
- */
- public function testFunctionConvertMediaPathExists() {
- $this->assertEquals(function_exists('convert_media_path'), true);
- }
-
- /**
- * Test that function export_gedcom() exists in the correct namespace.
- */
- public function testFunctionExportGedcomExists() {
- $this->assertEquals(function_exists('export_gedcom'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsImportTest.php b/tests/includes/functions/FunctionsImportTest.php
deleted file mode 100644
index da6a5c03a0..0000000000
--- a/tests/includes/functions/FunctionsImportTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_import.php
- */
-class FunctionsImportTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function reformat_record_import() exists in the correct namespace.
- */
- public function testFunctionReformatRecordImportExists() {
- $this->assertEquals(function_exists('reformat_record_import'), true);
- }
-
- /**
- * Test that function update_dates() exists in the correct namespace.
- */
- public function testFunctionUpdateDatesExists() {
- $this->assertEquals(function_exists('update_dates'), true);
- }
-
- /**
- * Test that function update_links() exists in the correct namespace.
- */
- public function testFunctionUpdateLinksExists() {
- $this->assertEquals(function_exists('update_links'), true);
- }
-
- /**
- * Test that function update_names() exists in the correct namespace.
- */
- public function testFunctionUpdateNamesExists() {
- $this->assertEquals(function_exists('update_names'), true);
- }
-
- /**
- * Test that function convert_inline_media() exists in the correct namespace.
- */
- public function testFunctionConvertInlineMediaExists() {
- $this->assertEquals(function_exists('convert_inline_media'), true);
- }
-
- /**
- * Test that function create_media_object() exists in the correct namespace.
- */
- public function testFunctionCreateMediaObjectExists() {
- $this->assertEquals(function_exists('create_media_object'), true);
- }
-
- /**
- * Test that function accept_all_changes() exists in the correct namespace.
- */
- public function testFunctionAcceptAllChangesExists() {
- $this->assertEquals(function_exists('accept_all_changes'), true);
- }
-
- /**
- * Test that function reject_all_changes() exists in the correct namespace.
- */
- public function testFunctionRejectAllChangesExists() {
- $this->assertEquals(function_exists('reject_all_changes'), true);
- }
-
- /**
- * Test that function update_record() exists in the correct namespace.
- */
- public function testFunctionUpdateRecordExists() {
- $this->assertEquals(function_exists('update_record'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsPrintFactsTest.php b/tests/includes/functions/FunctionsPrintFactsTest.php
deleted file mode 100644
index a60cb5441f..0000000000
--- a/tests/includes/functions/FunctionsPrintFactsTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_print_facts.php
- */
-class FunctionsPrintFactsTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function print_fact() exists in the correct namespace.
- */
- public function testFunctionPrintFactExists() {
- $this->assertEquals(function_exists('print_fact'), true);
- }
-
- /**
- * Test that function print_repository_record() exists in the correct namespace.
- */
- public function testFunctionPrintRepositoryRecordExists() {
- $this->assertEquals(function_exists('print_repository_record'), true);
- }
-
- /**
- * Test that function print_fact_sources() exists in the correct namespace.
- */
- public function testFunctionPrintFactSourcesExists() {
- $this->assertEquals(function_exists('print_fact_sources'), true);
- }
-
- /**
- * Test that function print_media_links() exists in the correct namespace.
- */
- public function testFunctionPrintMediaLinksExists() {
- $this->assertEquals(function_exists('print_media_links'), true);
- }
-
- /**
- * Test that function print_main_sources() exists in the correct namespace.
- */
- public function testFunctionPrintMainSourcesExists() {
- $this->assertEquals(function_exists('print_main_sources'), true);
- }
-
- /**
- * Test that function printSourceStructure() exists in the correct namespace.
- */
- public function testFunctionPrintSourceStructureExists() {
- $this->assertEquals(function_exists('printSourceStructure'), true);
- }
-
- /**
- * Test that function getSourceStructure() exists in the correct namespace.
- */
- public function testFunctionGetSourceStructureExists() {
- $this->assertEquals(function_exists('getSourceStructure'), true);
- }
-
- /**
- * Test that function print_main_notes() exists in the correct namespace.
- */
- public function testFunctionPrintMainNotesExists() {
- $this->assertEquals(function_exists('print_main_notes'), true);
- }
-
- /**
- * Test that function print_main_media() exists in the correct namespace.
- */
- public function testFunctionPrintMainMediaExists() {
- $this->assertEquals(function_exists('print_main_media'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsPrintListsTest.php b/tests/includes/functions/FunctionsPrintListsTest.php
deleted file mode 100644
index bef2be0aab..0000000000
--- a/tests/includes/functions/FunctionsPrintListsTest.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_print_lists.php
- */
-class FunctionsPrintListsTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function format_indi_table() exists in the correct namespace.
- */
- public function testFunctionFormatIndiTableExists() {
- $this->assertEquals(function_exists('format_indi_table'), true);
- }
-
- /**
- * Test that function format_fam_table() exists in the correct namespace.
- */
- public function testFunctionFormatFamTableExists() {
- $this->assertEquals(function_exists('format_fam_table'), true);
- }
-
- /**
- * Test that function format_sour_table() exists in the correct namespace.
- */
- public function testFunctionFormatSourTableExists() {
- $this->assertEquals(function_exists('format_sour_table'), true);
- }
-
- /**
- * Test that function format_note_table() exists in the correct namespace.
- */
- public function testFunctionFormatNoteTableExists() {
- $this->assertEquals(function_exists('format_note_table'), true);
- }
-
- /**
- * Test that function format_repo_table() exists in the correct namespace.
- */
- public function testFunctionFormatRepoTableExists() {
- $this->assertEquals(function_exists('format_repo_table'), true);
- }
-
- /**
- * Test that function format_media_table() exists in the correct namespace.
- */
- public function testFunctionFormatMediaTableExists() {
- $this->assertEquals(function_exists('format_media_table'), true);
- }
-
- /**
- * Test that function format_surname_table() exists in the correct namespace.
- */
- public function testFunctionFormatSurnameTableExists() {
- $this->assertEquals(function_exists('format_surname_table'), true);
- }
-
- /**
- * Test that function format_surname_tagcloud() exists in the correct namespace.
- */
- public function testFunctionFormatSurnameTagcloudExists() {
- $this->assertEquals(function_exists('format_surname_tagcloud'), true);
- }
-
- /**
- * Test that function format_surname_list() exists in the correct namespace.
- */
- public function testFunctionFormatSurnameListExists() {
- $this->assertEquals(function_exists('format_surname_list'), true);
- }
-
- /**
- * Test that function print_changes_list() exists in the correct namespace.
- */
- public function testFunctionPrintChangesListExists() {
- $this->assertEquals(function_exists('print_changes_list'), true);
- }
-
- /**
- * Test that function print_changes_table() exists in the correct namespace.
- */
- public function testFunctionPrintChangesTableExists() {
- $this->assertEquals(function_exists('print_changes_table'), true);
- }
-
- /**
- * Test that function print_events_table() exists in the correct namespace.
- */
- public function testFunctionPrintEventsTableExists() {
- $this->assertEquals(function_exists('print_events_table'), true);
- }
-
- /**
- * Test that function print_events_list() exists in the correct namespace.
- */
- public function testFunctionPrintEventsListExists() {
- $this->assertEquals(function_exists('print_events_list'), true);
- }
-
- /**
- * Test that function print_chart_by_age() exists in the correct namespace.
- */
- public function testFunctionPrintChartByAgeExists() {
- $this->assertEquals(function_exists('print_chart_by_age'), true);
- }
-
- /**
- * Test that function print_chart_by_decade() exists in the correct namespace.
- */
- public function testFunctionPrintChartByDecadeExists() {
- $this->assertEquals(function_exists('print_chart_by_decade'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsPrintTest.php b/tests/includes/functions/FunctionsPrintTest.php
deleted file mode 100644
index 688c29ecdb..0000000000
--- a/tests/includes/functions/FunctionsPrintTest.php
+++ /dev/null
@@ -1,196 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_print.php
- */
-class FunctionsPrintTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function print_pedigree_person() exists in the correct namespace.
- */
- public function testFunctionPrintPedigreePersonExists() {
- $this->assertEquals(function_exists('print_pedigree_person'), true);
- }
-
- /**
- /**
- * Test that function print_note_record() exists in the correct namespace.
- */
- public function testFunctionPrintNoteRecordExists() {
- $this->assertEquals(function_exists('print_note_record'), true);
- }
-
- /**
- * Test that function print_fact_notes() exists in the correct namespace.
- */
- public function testFunctionPrintFactNotesExists() {
- $this->assertEquals(function_exists('print_fact_notes'), true);
- }
-
- /**
- * Test that function help_link() exists in the correct namespace.
- */
- public function testFunctionHelpLinkExists() {
- $this->assertEquals(function_exists('help_link'), true);
- }
-
- /**
- * Test that function wiki_help_link() exists in the correct namespace.
- */
- public function testFunctionWikiHelpLinkExists() {
- $this->assertEquals(function_exists('wiki_help_link'), true);
- }
-
- /**
- * Test that function highlight_search_hits() exists in the correct namespace.
- */
- public function testFunctionHighlightSearchHitsExists() {
- $this->assertEquals(function_exists('highlight_search_hits'), true);
- }
-
- /**
- * Test that function format_asso_rela_record() exists in the correct namespace.
- */
- public function testFunctionFormatAssoRelaRecordExists() {
- $this->assertEquals(function_exists('format_asso_rela_record'), true);
- }
-
- /**
- * Test that function format_parents_age() exists in the correct namespace.
- */
- public function testFunctionFormatParentsAgeExists() {
- $this->assertEquals(function_exists('format_parents_age'), true);
- }
-
- /**
- * Test that function format_fact_date() exists in the correct namespace.
- */
- public function testFunctionFormatFactDateExists() {
- $this->assertEquals(function_exists('format_fact_date'), true);
- }
-
- /**
- * Test that function format_fact_place() exists in the correct namespace.
- */
- public function testFunctionFormatFactPlaceExists() {
- $this->assertEquals(function_exists('format_fact_place'), true);
- }
-
- /**
- * Test that function CheckFactUnique() exists in the correct namespace.
- */
- public function testFunctionCheckFactUniqueExists() {
- $this->assertEquals(function_exists('CheckFactUnique'), true);
- }
-
- /**
- * Test that function print_add_new_fact() exists in the correct namespace.
- */
- public function testFunctionPrintAddNewFactExists() {
- $this->assertEquals(function_exists('print_add_new_fact'), true);
- }
-
- /**
- * Test that function init_calendar_popup() exists in the correct namespace.
- */
- public function testFunctionInitCalendarPopupExists() {
- $this->assertEquals(function_exists('init_calendar_popup'), true);
- }
-
- /**
- * Test that function print_findindi_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindindiLinkExists() {
- $this->assertEquals(function_exists('print_findindi_link'), true);
- }
-
- /**
- * Test that function print_findplace_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindplaceLinkExists() {
- $this->assertEquals(function_exists('print_findplace_link'), true);
- }
-
- /**
- * Test that function print_findfamily_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindfamilyLinkExists() {
- $this->assertEquals(function_exists('print_findfamily_link'), true);
- }
-
- /**
- * Test that function print_specialchar_link() exists in the correct namespace.
- */
- public function testFunctionPrintSpecialcharLinkExists() {
- $this->assertEquals(function_exists('print_specialchar_link'), true);
- }
-
- /**
- * Test that function print_autopaste_link() exists in the correct namespace.
- */
- public function testFunctionPrintAutopasteLinkExists() {
- $this->assertEquals(function_exists('print_autopaste_link'), true);
- }
-
- /**
- * Test that function print_findsource_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindsourceLinkExists() {
- $this->assertEquals(function_exists('print_findsource_link'), true);
- }
-
- /**
- * Test that function print_findnote_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindnoteLinkExists() {
- $this->assertEquals(function_exists('print_findnote_link'), true);
- }
-
- /**
- * Test that function print_findrepository_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindrepositoryLinkExists() {
- $this->assertEquals(function_exists('print_findrepository_link'), true);
- }
-
- /**
- * Test that function print_findmedia_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindmediaLinkExists() {
- $this->assertEquals(function_exists('print_findmedia_link'), true);
- }
-
- /**
- * Test that function print_findfact_link() exists in the correct namespace.
- */
- public function testFunctionPrintFindfactLinkExists() {
- $this->assertEquals(function_exists('print_findfact_link'), true);
- }
-
- /**
- * Test that function get_lds_glance() exists in the correct namespace.
- */
- public function testFunctionGetLdsGlanceExists() {
- $this->assertEquals(function_exists('get_lds_glance'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsRtlTest.php b/tests/includes/functions/FunctionsRtlTest.php
deleted file mode 100644
index 75020cd94a..0000000000
--- a/tests/includes/functions/FunctionsRtlTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions_rtl.php
- */
-class FunctionsRtlTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function stripLRMRLM() exists in the correct namespace.
- */
- public function testFunctionStripLRMRLMExists() {
- $this->assertEquals(function_exists('stripLRMRLM'), true);
- }
-
- /**
- * Test that function spanLTRRTL() exists in the correct namespace.
- */
- public function testFunctionSpanLTRRTLExists() {
- $this->assertEquals(function_exists('spanLTRRTL'), true);
- }
-
- /**
- * Test that function starredName() exists in the correct namespace.
- */
- public function testFunctionStarredNameExists() {
- $this->assertEquals(function_exists('starredName'), true);
- }
-
- /**
- * Test that function getChar() exists in the correct namespace.
- */
- public function testFunctionGetCharExists() {
- $this->assertEquals(function_exists('getChar'), true);
- }
-
- /**
- * Test that function breakCurrentSpan() exists in the correct namespace.
- */
- public function testFunctionBreakCurrentSpanExists() {
- $this->assertEquals(function_exists('breakCurrentSpan'), true);
- }
-
- /**
- * Test that function beginCurrentSpan() exists in the correct namespace.
- */
- public function testFunctionBeginCurrentSpanExists() {
- $this->assertEquals(function_exists('beginCurrentSpan'), true);
- }
-
- /**
- * Test that function finishCurrentSpan() exists in the correct namespace.
- */
- public function testFunctionFinishCurrentSpanExists() {
- $this->assertEquals(function_exists('finishCurrentSpan'), true);
- }
-
- /**
- * Test that function utf8_wordwrap() exists in the correct namespace.
- */
- public function testFunctionUtf8WordwrapExists() {
- $this->assertEquals(function_exists('utf8_wordwrap'), true);
- }
-}
diff --git a/tests/includes/functions/FunctionsTest.php b/tests/includes/functions/FunctionsTest.php
deleted file mode 100644
index a411c30003..0000000000
--- a/tests/includes/functions/FunctionsTest.php
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-
-/**
- * webtrees: online genealogy
- * Copyright (C) 2015 webtrees development team
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Unit tests for the global functions in the file includes/functions/functions.php
- */
-class FunctionsTest extends PHPUnit_Framework_TestCase {
- /**
- * Prepare the environment for these tests
- */
- public function setUp() {
- }
-
- /**
- * Test that function fetch_latest_version() exists in the correct namespace.
- */
- public function testFunctionFetchLatestVersionExists() {
- $this->assertEquals(function_exists('fetch_latest_version'), true);
- }
-
- /**
- * Test that function file_upload_error_text() exists in the correct namespace.
- */
- public function testFunctionFileUploadErrorTextExists() {
- $this->assertEquals(function_exists('file_upload_error_text'), true);
- }
-
- /**
- * Test that function get_sub_record() exists in the correct namespace.
- */
- public function testFunctionGetSubRecordExists() {
- $this->assertEquals(function_exists('get_sub_record'), true);
- }
-
- /**
- * Test that function get_cont() exists in the correct namespace.
- */
- public function testFunctionGetContExists() {
- $this->assertEquals(function_exists('get_cont'), true);
- }
-
- /**
- * Test that function event_sort() exists in the correct namespace.
- */
- public function testFunctionEventSortExists() {
- $this->assertEquals(function_exists('event_sort'), true);
- }
-
- /**
- * Test that function event_sort_name() exists in the correct namespace.
- */
- public function testFunctionEventSortNameExists() {
- $this->assertEquals(function_exists('event_sort_name'), true);
- }
-
- /**
- * Test that function sort_facts() exists in the correct namespace.
- */
- public function testFunctionSortFactsExists() {
- $this->assertEquals(function_exists('sort_facts'), true);
- }
-
- /**
- * Test that function get_close_relationship_name() exists in the correct namespace.
- */
- public function testFunctionGetCloseRelationshipNameExists() {
- $this->assertEquals(function_exists('get_close_relationship_name'), true);
- }
-
- /**
- * Test that function get_associate_relationship_name() exists in the correct namespace.
- */
- public function testFunctionGetAssociateRelationshipNameExists() {
- $this->assertEquals(function_exists('get_associate_relationship_name'), true);
- }
-
- /**
- * Test that function get_relationship() exists in the correct namespace.
- */
- public function testFunctionGetRelationshipExists() {
- $this->assertEquals(function_exists('get_relationship'), true);
- }
-
- /**
- * Test that function get_relationship_name() exists in the correct namespace.
- */
- public function testFunctionGetRelationshipNameExists() {
- $this->assertEquals(function_exists('get_relationship_name'), true);
- }
-
- /**
- * Test that function cousin_name() exists in the correct namespace.
- */
- public function testFunctionCousinNameExists() {
- $this->assertEquals(function_exists('cousin_name'), true);
- }
-
- /**
- * Test that function cousin_name2() exists in the correct namespace.
- */
- public function testFunctionCousinName2Exists() {
- $this->assertEquals(function_exists('cousin_name2'), true);
- }
-
- /**
- * Test that function get_relationship_name_from_path() exists in the correct namespace.
- */
- public function testFunctionGetRelationshipNameFromPathExists() {
- $this->assertEquals(function_exists('get_relationship_name_from_path'), true);
- }
-
- /**
- * Test that function get_query_url() exists in the correct namespace.
- */
- public function testFunctionGetQueryUrlExists() {
- $this->assertEquals(function_exists('get_query_url'), true);
- }
-
- /**
- * Test that function isFileExternal() exists in the correct namespace.
- */
- public function testFunctionIsFileExternalExists() {
- $this->assertEquals(function_exists('isFileExternal'), true);
- }
-
- /**
- * Tests for function isFileExternal()
- */
- public function testFunctionIsFileExternal() {
- $this->assertEquals(isFileExternal('http://www.example.com/file.txt'), true);
- $this->assertEquals(isFileExternal('file.txt'), false);
- $this->assertEquals(isFileExternal('folder/file.txt'), false);
- $this->assertEquals(isFileExternal('folder\\file.txt'), false);
- $this->assertEquals(isFileExternal('/folder/file.txt'), false);
- $this->assertEquals(isFileExternal('\\folder\\file.txt'), false);
- $this->assertEquals(isFileExternal('C:\\folder\\file.txt'), false);
- }
-}
diff --git a/timeline.php b/timeline.php
index 70ea012fa5..cd03e41ee1 100644
--- a/timeline.php
+++ b/timeline.php
@@ -17,6 +17,8 @@ namespace Fisharebest\Webtrees;
*/
use Fisharebest\Webtrees\Controller\TimelineController;
+use Fisharebest\Webtrees\Functions\Functions;
+use Fisharebest\Webtrees\Functions\FunctionsPrint;
$basexoffset = 0;
$baseyoffset = 0;
@@ -293,7 +295,7 @@ document.onmouseup = function () {
<td class="list_value" style="padding: 5px;" valign="top">
<?php echo I18N::translate('Add another individual to the chart'), '<br>'; ?>
<input class="pedigree_form" data-autocomplete-type="INDI" type="text" size="5" id="newpid" name="newpid">
- <?php echo print_findindi_link('newpid'); ?>
+ <?php echo FunctionsPrint::printFindIndividualLink('newpid'); ?>
<br>
<br>
@@ -341,7 +343,7 @@ if (count($controller->people) > 0) {
echo "<div id=\"scale{$controller->topyear}\" style=\"position:absolute; " . (I18N::direction() === 'ltr' ? "left: $basexoffset" : "right: $basexoffset") . "px; top:" . ($baseyoffset + (($controller->topyear - $controller->baseyear) * $controller->scale)) . "px; font-size: 7pt; text-align:" . (I18N::direction() === 'ltr' ? 'left' : 'right') . ";\">";
echo $controller->topyear . '—';
echo '</div>';
- sort_facts($controller->indifacts);
+ Functions::sortFacts($controller->indifacts);
$factcount = 0;
foreach ($controller->indifacts as $fact) {
$controller->printTimeFact($fact);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
index 11cd730086..5645a52f0d 100644
--- a/vendor/composer/autoload_files.php
+++ b/vendor/composer/autoload_files.php
@@ -8,16 +8,4 @@ $baseDir = dirname($vendorDir);
return array(
$vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
$vendorDir . '/ircmaxell/password-compat/lib/password.php',
- $baseDir . '/includes/functions/functions.php',
- $baseDir . '/includes/functions/functions_charts.php',
- $baseDir . '/includes/functions/functions_date.php',
- $baseDir . '/includes/functions/functions_db.php',
- $baseDir . '/includes/functions/functions_edit.php',
- $baseDir . '/includes/functions/functions_export.php',
- $baseDir . '/includes/functions/functions_import.php',
- $baseDir . '/includes/functions/functions_mediadb.php',
- $baseDir . '/includes/functions/functions_print.php',
- $baseDir . '/includes/functions/functions_print_facts.php',
- $baseDir . '/includes/functions/functions_print_lists.php',
- $baseDir . '/includes/functions/functions_rtl.php',
);