.
*/
namespace Fisharebest\Webtrees;
use Fisharebest\Webtrees\Controller\AjaxController;
use Fisharebest\Webtrees\Functions\FunctionsImport;
use PDOException;
define('WT_SCRIPT_NAME', 'import.php');
require './includes/session.php';
// Don't use ged=XX as we want to be able to run without changing the current gedcom.
// This will let us load several gedcoms together, or to edit one while loading another.
$gedcom_id = Filter::getInteger('gedcom_id');
$tree = Tree::findById($gedcom_id);
if (!$tree || !Auth::isManager($tree, Auth::user())) {
http_response_code(403);
return;
}
$controller = new AjaxController;
$controller
->pageHeader();
// Don't allow the user to cancel the request. We do not want to be left
// with an incomplete transaction.
ignore_user_abort(true);
// Run in a transaction
Database::beginTransaction();
// Only allow one process to import each gedcom at a time
Database::prepare("SELECT * FROM `##gedcom_chunk` WHERE gedcom_id=? FOR UPDATE")->execute(array($gedcom_id));
// What is the current import status?
$row = Database::prepare(
"SELECT" .
" SUM(IF(imported, LENGTH(chunk_data), 0)) AS import_offset," .
" SUM(LENGTH(chunk_data)) AS import_total" .
" FROM `##gedcom_chunk` WHERE gedcom_id=?"
)->execute(array($gedcom_id))->fetchOneRow();
if ($row->import_offset == $row->import_total) {
Tree::findById($gedcom_id)->setPreference('imported', '1');
// Finished? Show the maintenance links, similar to admin_trees_manage.php
Database::commit();
$controller->addInlineJavascript(
'jQuery("#import' . $gedcom_id . '").addClass("hidden");' .
'jQuery("#actions' . $gedcom_id . '").removeClass("hidden");'
);
return;
}
// Calculate progress so far
$progress = $row->import_offset / $row->import_total;
?>
import_offset == 0);
// Run for one second. This keeps the resource requirements low.
for ($end_time = microtime(true) + 1.0; microtime(true) < $end_time;) {
$data = Database::prepare(
"SELECT gedcom_chunk_id, REPLACE(chunk_data, '\r', '\n') AS chunk_data" .
" FROM `##gedcom_chunk`" .
" WHERE gedcom_id=? AND NOT imported" .
" ORDER BY gedcom_chunk_id" .
" LIMIT 1"
)->execute(array($gedcom_id))->fetchOneRow();
// If we are loading the first (header) record, make sure the encoding is UTF-8.
if ($first_time) {
// Remove any byte-order-mark
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=TRIM(LEADING ? FROM chunk_data)" .
" WHERE gedcom_chunk_id=?"
)->execute(array(WT_UTF8_BOM, $data->gedcom_chunk_id));
// Re-fetch the data, now that we have removed the BOM
$data = Database::prepare(
"SELECT gedcom_chunk_id, REPLACE(chunk_data, '\r', '\n') AS chunk_data" .
" FROM `##gedcom_chunk`" .
" WHERE gedcom_chunk_id=?"
)->execute(array($data->gedcom_chunk_id))->fetchOneRow();
if (substr($data->chunk_data, 0, 6) != '0 HEAD') {
Database::rollBack();
echo I18N::translate('Invalid GEDCOM file - no header record found.');
$controller->addInlineJavascript('jQuery("#actions' . $gedcom_id . '").removeClass("hidden");');
return;
}
// What character set is this? Need to convert it to UTF8
if (preg_match('/[\r\n][ \t]*1 CHAR(?:ACTER)? (.+)/', $data->chunk_data, $match)) {
$charset = trim(strtoupper($match[1]));
} else {
$charset = 'ASCII';
}
// MySQL supports a wide range of collation conversions. These are ones that
// have been encountered "in the wild".
switch ($charset) {
case 'ASCII':
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=CONVERT(CONVERT(chunk_data USING ascii) USING utf8)" .
" WHERE gedcom_id=?"
)->execute(array($gedcom_id));
break;
case 'IBMPC': // IBMPC, IBM WINDOWS and MS-DOS could be anything. Mostly it means CP850.
case 'IBM WINDOWS':
case 'MS-DOS':
case 'CP437':
case 'CP850':
// CP850 has extra letters with diacritics to replace box-drawing chars in CP437.
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=CONVERT(CONVERT(chunk_data USING cp850) USING utf8)" .
" WHERE gedcom_id=?"
)->execute(array($gedcom_id));
break;
case 'ANSI': // ANSI could be anything. Most applications seem to treat it as latin1.
$controller->addInlineJavascript(
'jQuery("#import' . $gedcom_id . '").parent().prepend("' . /* I18N: %1$s and %2$s are the names of character encodings, such as ISO-8859-1 or ASCII */
I18N::translate('This GEDCOM file is encoded using %1$s. Assume this to mean %2$s.', $charset, 'ISO-8859-1') . '
");'
);
// no break;
case 'WINDOWS':
case 'CP1252':
case 'ISO8859-1':
case 'ISO-8859-1':
case 'LATIN1':
case 'LATIN-1':
// Convert from ISO-8859-1 (western european) to UTF8.
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=CONVERT(CONVERT(chunk_data USING latin1) USING utf8)" .
" WHERE gedcom_id=?"
)->execute(array($gedcom_id));
break;
case 'CP1250':
case 'ISO8859-2':
case 'ISO-8859-2':
case 'LATIN2':
case 'LATIN-2':
// Convert from ISO-8859-2 (eastern european) to UTF8.
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=CONVERT(CONVERT(chunk_data USING latin2) USING utf8)" .
" WHERE gedcom_id=?"
)->execute(array($gedcom_id));
break;
case 'MACINTOSH':
// Convert from MAC Roman to UTF8.
Database::prepare(
"UPDATE `##gedcom_chunk`" .
" SET chunk_data=CONVERT(CONVERT(chunk_data USING macroman) USING utf8)" .
" WHERE gedcom_id=?"
)->execute(array($gedcom_id));
break;
case 'UTF8':
case 'UTF-8':
// Already UTF-8 so nothing to do!
break;
case 'ANSEL':
default:
Database::rollBack();
echo '', I18N::translate('Error: converting GEDCOM files from %s encoding to UTF-8 encoding not currently supported.', $charset), '';
$controller->addInlineJavascript('jQuery("#actions' . $gedcom_id . '").removeClass("hidden");');
return;
}
$first_time = false;
// Re-fetch the data, now that we have performed character set conversion.
$data = Database::prepare(
"SELECT gedcom_chunk_id, REPLACE(chunk_data, '\r', '\n') AS chunk_data" .
" FROM `##gedcom_chunk`" .
" WHERE gedcom_chunk_id=?"
)->execute(array($data->gedcom_chunk_id))->fetchOneRow();
}
if (!$data) {
break;
}
try {
// Import all the records in this chunk of data
foreach (preg_split('/\n+(?=0)/', $data->chunk_data) as $rec) {
FunctionsImport::importRecord($rec, $tree, false);
}
// Mark the chunk as imported
Database::prepare(
"UPDATE `##gedcom_chunk` SET imported=TRUE WHERE gedcom_chunk_id=?"
)->execute(array($data->gedcom_chunk_id));
} catch (PDOException $ex) {
Database::rollBack();
if ($ex->getCode() === '40001') {
// "SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction"
// The documentation says that if you get this error, wait and try again.....
$controller->addInlineJavascript('jQuery("#import' . $gedcom_id . '").load("import.php?gedcom_id=' . $gedcom_id . '&u=' . uniqid() . '");');
} else {
// A fatal error. Nothing we can do?
echo '', $ex->getMessage(), '';
$controller->addInlineJavascript('jQuery("#actions' . $gedcom_id . '").removeClass("hidden");');
}
return;
}
}
Database::commit();
// Reload.....
// Use uniqid() to prevent jQuery caching the previous response.
$controller->addInlineJavascript('jQuery("#import' . $gedcom_id . '").load("import.php?gedcom_id=' . $gedcom_id . '&u=' . uniqid() . '");');