mirror of
https://github.com/YunoHost-Apps/webtrees_ynh.git
synced 2024-09-03 18:26:37 +02:00
285 lines
9.9 KiB
PHP
285 lines
9.9 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* webtrees: online genealogy
|
||
|
* Copyright (C) 2016 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/>.
|
||
|
*/
|
||
|
namespace Fisharebest\Webtrees;
|
||
|
|
||
|
/**
|
||
|
* Defined in session.php
|
||
|
*
|
||
|
* @global Tree $WT_TREE
|
||
|
*/
|
||
|
global $WT_TREE;
|
||
|
|
||
|
use Fisharebest\Webtrees\Controller\PageController;
|
||
|
|
||
|
define('WT_SCRIPT_NAME', 'admin_trees_check.php');
|
||
|
require './includes/session.php';
|
||
|
|
||
|
$controller = new PageController;
|
||
|
$controller
|
||
|
->restrictAccess(Auth::isManager($WT_TREE))
|
||
|
->setPageTitle(I18N::translate('Check for errors') . ' — ' . $WT_TREE->getTitleHtml())
|
||
|
->pageHeader();
|
||
|
|
||
|
// We need to work with raw GEDCOM data, as we are looking for errors
|
||
|
// which may prevent the GedcomRecord objects from working.
|
||
|
|
||
|
$rows = Database::prepare(
|
||
|
"SELECT i_id AS xref, 'INDI' AS type, i_gedcom AS gedrec FROM `##individuals` WHERE i_file=?" .
|
||
|
" UNION " .
|
||
|
"SELECT f_id AS xref, 'FAM' AS type, f_gedcom AS gedrec FROM `##families` WHERE f_file=?" .
|
||
|
" UNION " .
|
||
|
"SELECT s_id AS xref, 'SOUR' AS type, s_gedcom AS gedrec FROM `##sources` WHERE s_file=?" .
|
||
|
" UNION " .
|
||
|
"SELECT m_id AS xref, 'OBJE' AS type, m_gedcom AS gedrec FROM `##media` WHERE m_file=?" .
|
||
|
" UNION " .
|
||
|
"SELECT o_id AS xref, o_type AS type, o_gedcom AS gedrec FROM `##other` WHERE o_file=? AND o_type NOT IN ('HEAD', 'TRLR')"
|
||
|
)->execute(array($WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId(), $WT_TREE->getTreeId()))->fetchAll();
|
||
|
|
||
|
$records = array();
|
||
|
foreach ($rows as $row) {
|
||
|
$records[$row->xref] = $row;
|
||
|
}
|
||
|
|
||
|
// Need to merge pending new/changed/deleted records
|
||
|
|
||
|
$rows = Database::prepare(
|
||
|
"SELECT xref, SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(CASE WHEN old_gedcom='' THEN new_gedcom ELSE old_gedcom END, '\n', 1), ' ', 3), ' ', -1) AS type, new_gedcom AS gedrec" .
|
||
|
" FROM (" .
|
||
|
" SELECT MAX(change_id) AS change_id" .
|
||
|
" FROM `##change`" .
|
||
|
" WHERE gedcom_id=? AND status='pending'" .
|
||
|
" GROUP BY xref" .
|
||
|
" ) AS t1" .
|
||
|
" JOIN `##change` t2 USING (change_id)"
|
||
|
)->execute(array($WT_TREE->getTreeId()))->fetchAll();
|
||
|
|
||
|
foreach ($rows as $row) {
|
||
|
if ($row->gedrec) {
|
||
|
// new/updated record
|
||
|
$records[$row->xref] = $row;
|
||
|
} else {
|
||
|
// deleted record
|
||
|
unset($records[$row->xref]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Keep a list of upper case XREFs, to detect mismatches.
|
||
|
$ukeys = array();
|
||
|
foreach (array_keys($records) as $key) {
|
||
|
$ukeys[strtoupper($key)] = $key;
|
||
|
}
|
||
|
|
||
|
// LOOK FOR BROKEN LINKS
|
||
|
$XREF_LINKS = array(
|
||
|
'NOTE' => 'NOTE',
|
||
|
'SOUR' => 'SOUR',
|
||
|
'REPO' => 'REPO',
|
||
|
'OBJE' => 'OBJE',
|
||
|
'SUBM' => 'SUBM',
|
||
|
'FAMC' => 'FAM',
|
||
|
'FAMS' => 'FAM',
|
||
|
//'ADOP'=>'FAM', // Need to handle this case specially. We may have both ADOP and FAMC links to the same FAM, but only store one.
|
||
|
'HUSB' => 'INDI',
|
||
|
'WIFE' => 'INDI',
|
||
|
'CHIL' => 'INDI',
|
||
|
'ASSO' => 'INDI',
|
||
|
'_ASSO' => 'INDI', // A webtrees extension
|
||
|
'ALIA' => 'INDI',
|
||
|
'AUTH' => 'INDI', // A webtrees extension
|
||
|
'ANCI' => 'SUBM',
|
||
|
'DESI' => 'SUBM',
|
||
|
'_WT_OBJE_SORT' => 'OBJE',
|
||
|
'_LOC' => '_LOC',
|
||
|
);
|
||
|
|
||
|
$RECORD_LINKS = array(
|
||
|
'INDI' => array('NOTE', 'OBJE', 'SOUR', 'SUBM', 'ASSO', '_ASSO', 'FAMC', 'FAMS', 'ALIA', '_WT_OBJE_SORT', '_LOC'),
|
||
|
'FAM' => array('NOTE', 'OBJE', 'SOUR', 'SUBM', 'ASSO', '_ASSO', 'HUSB', 'WIFE', 'CHIL', '_LOC'),
|
||
|
'SOUR' => array('NOTE', 'OBJE', 'REPO', 'AUTH'),
|
||
|
'REPO' => array('NOTE'),
|
||
|
'OBJE' => array('NOTE'), // The spec also allows SOUR, but we treat this as a warning
|
||
|
'NOTE' => array(), // The spec also allows SOUR, but we treat this as a warning
|
||
|
'SUBM' => array('NOTE', 'OBJE'),
|
||
|
'SUBN' => array('SUBM'),
|
||
|
'_LOC' => array('SOUR', 'OBJE', '_LOC'),
|
||
|
);
|
||
|
|
||
|
$errors = false;
|
||
|
|
||
|
?>
|
||
|
<ol class="breadcrumb small">
|
||
|
<li><a href="admin.php"><?php echo I18N::translate('Control panel'); ?></a></li>
|
||
|
<li><a href="admin_trees_manage.php"><?php echo I18N::translate('Manage family trees'); ?></a></li>
|
||
|
<li class="active"><?php echo $controller->getPageTitle(); ?></li>
|
||
|
</ol>
|
||
|
|
||
|
<h1><?php echo $controller->getPageTitle(); ?></h1>
|
||
|
|
||
|
<ul class="list-group">
|
||
|
<li class="list-group-item"><strong><?php echo I18N::translate('Types of error'); ?></strong></li>
|
||
|
<li class="list-group-item list-group-item-danger"><?php echo I18N::translate('This may cause a problem for webtrees.'); ?></li>
|
||
|
<li class="list-group-item list-group-item-warning"><?php echo I18N::translate('This may cause a problem for other applications.'); ?></li>
|
||
|
<li class="list-group-item list-group-item-info"><?php echo I18N::translate('This may be a mistake in your data.'); ?></li>
|
||
|
</ul>
|
||
|
|
||
|
<ul class="list-group">
|
||
|
<li class="list-group-item"><strong><?php echo I18N::translate('GEDCOM errors'); ?></strong></li>
|
||
|
|
||
|
<?php
|
||
|
|
||
|
// Generate lists of all links
|
||
|
$all_links = array();
|
||
|
$upper_links = array();
|
||
|
foreach ($records as $record) {
|
||
|
$all_links[$record->xref] = array();
|
||
|
$upper_links[strtoupper($record->xref)] = $record->xref;
|
||
|
preg_match_all('/\n\d (' . WT_REGEX_TAG . ') @([^#@\n][^\n@]*)@/', $record->gedrec, $matches, PREG_SET_ORDER);
|
||
|
foreach ($matches as $match) {
|
||
|
$all_links[$record->xref][$match[2]] = $match[1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach ($all_links as $xref1 => $links) {
|
||
|
$type1 = $records[$xref1]->type;
|
||
|
foreach ($links as $xref2 => $type2) {
|
||
|
$type3 = isset($records[$xref2]) ? $records[$xref2]->type : '';
|
||
|
if (!array_key_exists($xref2, $all_links)) {
|
||
|
if (array_key_exists(strtoupper($xref2), $upper_links)) {
|
||
|
echo warning(
|
||
|
link_message($type1, $xref1, $type2, $xref2) . ' ' .
|
||
|
/* I18N: placeholders are GEDCOM XREFs, such as R123 */
|
||
|
I18N::translate('%1$s does not exist. Did you mean %2$s?', format_link($xref2), format_link($upper_links[strtoupper($xref2)]))
|
||
|
);
|
||
|
} else {
|
||
|
echo error(
|
||
|
link_message(
|
||
|
$type1, $xref1, $type2, $xref2) . ' ' .
|
||
|
/* I18N: placeholders are GEDCOM XREFs, such as R123 */
|
||
|
I18N::translate('%1$s does not exist.', format_link($xref2))
|
||
|
);
|
||
|
}
|
||
|
} elseif ($type2 === 'SOUR' && $type1 === 'NOTE') {
|
||
|
// Notes are intended to add explanations and comments to other records. They should not have their own sources.
|
||
|
} elseif ($type2 === 'SOUR' && $type1 === 'OBJE') {
|
||
|
// Media objects are intended to illustrate other records, facts, and source/citations. They should not have their own sources.
|
||
|
} elseif ($type2 === 'OBJE' && $type1 === 'REPO') {
|
||
|
echo warning(
|
||
|
link_message($type1, $xref1, $type2, $xref2) . ' ' . I18N::translate('This type of link is not allowed here.')
|
||
|
);
|
||
|
} elseif (!array_key_exists($type1, $RECORD_LINKS) || !in_array($type2, $RECORD_LINKS[$type1]) || !array_key_exists($type2, $XREF_LINKS)) {
|
||
|
echo error(
|
||
|
link_message($type1, $xref1, $type2, $xref2) . ' ' .
|
||
|
I18N::translate('This type of link is not allowed here.')
|
||
|
);
|
||
|
} elseif ($XREF_LINKS[$type2] !== $type3) {
|
||
|
// Target XREF does exist - but is invalid
|
||
|
echo error(
|
||
|
link_message($type1, $xref1, $type2, $xref2) . ' ' .
|
||
|
/* I18N: %1$s is an internal ID number such as R123. %2$s and %3$s are record types, such as INDI or SOUR */
|
||
|
I18N::translate('%1$s is a %2$s but a %3$s is expected.', format_link($xref2), format_type($type3), format_type($type2))
|
||
|
);
|
||
|
} elseif (
|
||
|
$type2 === 'FAMC' && (!array_key_exists($xref1, $all_links[$xref2]) || $all_links[$xref2][$xref1] !== 'CHIL') ||
|
||
|
$type2 === 'FAMS' && (!array_key_exists($xref1, $all_links[$xref2]) || $all_links[$xref2][$xref1] !== 'HUSB' && $all_links[$xref2][$xref1] !== 'WIFE') ||
|
||
|
$type2 === 'CHIL' && (!array_key_exists($xref1, $all_links[$xref2]) || $all_links[$xref2][$xref1] !== 'FAMC') ||
|
||
|
$type2 === 'HUSB' && (!array_key_exists($xref1, $all_links[$xref2]) || $all_links[$xref2][$xref1] !== 'FAMS') ||
|
||
|
$type2 === 'WIFE' && (!array_key_exists($xref1, $all_links[$xref2]) || $all_links[$xref2][$xref1] !== 'FAMS')
|
||
|
) {
|
||
|
echo error(
|
||
|
link_message($type1, $xref1, $type2, $xref2) . ' ' .
|
||
|
/* I18N: %1$s and %2$s are internal ID numbers such as R123 */
|
||
|
I18N::translate('%1$s does not have a link back to %2$s.', format_link($xref2), format_link($xref1))
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$errors) {
|
||
|
echo '<li class="list-group-item">', I18N::translate('No errors have been found.'), '</li>';
|
||
|
}
|
||
|
|
||
|
echo '</ul>';
|
||
|
|
||
|
/**
|
||
|
* Create a message linking one record to another.
|
||
|
*
|
||
|
* @param string $type1
|
||
|
* @param string $xref1
|
||
|
* @param string $type2
|
||
|
* @param string $xref2
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function link_message($type1, $xref1, $type2, $xref2) {
|
||
|
return /* I18N: The placeholders are GEDCOM XREFs and tags. e.g. “INDI I123 contains a FAMC link to F234.” */ I18N::translate(
|
||
|
'%1$s %2$s has a %3$s link to %4$s.',
|
||
|
format_type($type1),
|
||
|
format_link($xref1),
|
||
|
format_type($type2),
|
||
|
format_link($xref2)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format a link to a record.
|
||
|
*
|
||
|
* @param string $xref
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function format_link($xref) {
|
||
|
return '<b><a href="gedrecord.php?pid=' . $xref . '">' . $xref . '</a></b>';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format a record type.
|
||
|
*
|
||
|
* @param string $type
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function format_type($type) {
|
||
|
return '<b title="' . strip_tags(GedcomTag::getLabel($type)) . '">' . $type . '</b>';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format an error message.
|
||
|
*
|
||
|
* @param string $message
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function error($message) {
|
||
|
global $errors;
|
||
|
$errors = true;
|
||
|
|
||
|
return '<li class="list-group-item list-group-item-danger">' . $message . '</li>';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format a warning message.
|
||
|
*
|
||
|
* @param string $message
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
function warning($message) {
|
||
|
global $errors;
|
||
|
$errors = true;
|
||
|
|
||
|
return '<li class="list-group-item list-group-item-warning">' . $message . '</li>';
|
||
|
}
|