VvebOIDC/system/import/xml.php

311 lines
7.8 KiB
PHP

<?php
/**
* Vvveb
*
* Copyright (C) 2022 Ziadin Givan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Vvveb\System\Import;
#[\AllowDynamicProperties]
class Xml {
private $db;
private $dom;
private $xpath;
private $sqlPath;
private $prefix;
private $idsMap;
private $importXMLOptions = LIBXML_NOBLANKS |
LIBXML_COMPACT |
LIBXML_NOCDATA |
LIBXML_PARSEHUGE |
LIBXML_NOWARNING |
LIBXML_BIGLINES;
private $tableOrder = [];
private $cDataColumns = ['content', 'excerpt', 'name', 'title', 'description', 'meta_title', 'meta_description', 'slug'];
function __construct($driver = DB_ENGINE, $host = DB_HOST, $dbname = DB_NAME, $user = DB_USER, $pass = DB_PASS, $port = DB_PORT, $prefix = DB_PREFIX) {
$this->sqlPath = DIR_ROOT . "install/sql/$driver/";
$engine = '\Vvveb\System\Db\\' . ucfirst($driver);
$this->prefix = $prefix;
$this->db = new $engine($host, $dbname, $user, $pass, $port, $prefix);
$this->tableOrder = include 'table-order.php';
}
/*
* Get all table names for the database
*/
function getTableNames($db = DB_NAME) {
return $this->db->getTableNames();
}
function export($tables = []) {
$this->dom = new \DOMDocument('1.0', 'utf-8');
$this->dom->formatOutput = true;
$this->dom->preserveWhiteSpace = false;
if (empty($tables)) {
$tables = $this->getTableNames();
}
$root = $this->dom->createElement('root');
$database = $this->dom->createElement('database');
foreach ($tables as $tableName) {
$table = $this->dom->createElement($tableName);
$q = '`';
if (DB_ENGINE == 'pgsql') {
$q = '"';
}
$stmt = $this->db->query("SELECT * FROM $q$tableName$q");
//$result = $stmt->get_result();
while ($row = $this->db->fetchArray($stmt)) {
$item = $this->dom->createElement('item');
foreach ($row as $key => $value) {
$isString = is_string($value);
if ($value === NULL) {
$value = 'NULL';
}
if ($value === 0) {
$value = '0';
}
if (! $value) {
$value = '';
}
if (in_array($key,$this->cDataColumns) || $isString) {
//$element = $this->dom->createElement($key, $value);
$cdata = $this->dom->createCDataSection($value);
$element = $this->dom->createElement($key);
$element->appendChild($cdata);
} else {
$element = $this->dom->createElement($key, $value);
}
$item->appendChild($element);
}
$table->appendChild($item);
}
if ($table->hasChildNodes()) {
$database->appendChild($table);
}
}
$root->appendChild($database);
$this->dom->appendChild($root);
return $this->dom->saveXML();
}
function display_xml_error($error, $xml) {
$return = $xml[$error->line - 1] . "\n";
$return .= str_repeat('-', $error->column) . "^\n";
switch ($error->level) {
case LIBXML_ERR_WARNING:
$return .= "Warning $error->code: ";
break;
case LIBXML_ERR_ERROR:
$return .= "Error $error->code: ";
break;
case LIBXML_ERR_FATAL:
$return .= "Fatal Error $error->code: ";
break;
}
$return .= trim($error->message) .
"\n Line: $error->line" .
"\n Column: $error->column";
if ($error->file) {
$return .= "\n File: $error->file";
}
return "$return\n\n--------------------------------------------\n\n";
}
function import($xml, $tables = []) {
if ($tables) {
//sort tables by table order
$tableOrder = array_flip($this->tableOrder);
usort($tables, function ($left, $right) use ($tableOrder) {
return $tableOrder[$left] <=> $tableOrder[$right];
});
} else {
$tables = $this->tableOrder;
}
$this->dom = new \DOMDocument('1.0', 'utf-8');
$this->dom->formatOutput = false;
$this->dom->preserveWhiteSpace = false;
$this->dom->strictErrorChecking = false;
libxml_use_internal_errors(true);
$result = $this->dom->loadXML($xml, $this->importXMLOptions);
if (! $result) {
$error = libxml_get_last_error();
$message = $this->display_xml_error($error, explode("\n", $xml));
throw new \Exception(sprintf("Unable to load XML content, check if valid XML!\n\t%s", $message), 1);
return;
}
$this->dom->normalize();
$this->xpath = new \DOMXpath($this->dom);
$domTables = $this->xpath->query('//root/database/*');
//map ids
foreach ($domTables as $table) {
$tableName = $table->nodeName;
//continue;
$rows = $table->childNodes;
if (! is_null($rows)) {
foreach ($rows as $row) {
$columns = $row->childNodes;
if ($columns) {
foreach ($columns as $column) {
//if ($column->nodeName == '#text') continue;
$columnName = $column->nodeName;
$columnValue = $column->nodeValue;
$data[$columnName] = $columnValue;
$idColumn = $tableName . '_id';
if ($columnName == $idColumn) {
//$this->idsMap[$idColumn][$columnValue] = 0;
$this->idsMap[$idColumn][$columnValue] = $columnValue;
}
}
}
}
}
}
if (DB_ENGINE == 'mysqli') {
$stmt = $this->db->execute('SET sql_mode = "";');
}
$q = '`';
if (DB_ENGINE == 'pgsql') {
$q = '"';
}
foreach ($tables as $tableName) {
$rows = $this->xpath->query("//root/database/$tableName/item");
if (! is_null($rows)) {
foreach ($rows as $row) {
$columns = $row->childNodes;
$update = '';
$data = [];
$idColumn = $tableName . '_id';
foreach ($columns as $column) {
//if ($column->nodeName == '#text') continue;
$columnName = $column->nodeName;
$columnValue = $column->nodeValue;
if ($columnName == 'parent_id' && $columnValue > 0) {
//self refrence table id
$oldId = $columnValue;
$columnValue = $this->idsMap[$idColumn][$columnValue];
}
if ($columnName == $idColumn) {
$idValue = $columnValue;
$this->idsMap[$idColumn][$idValue] = $idValue;
} else {
if (isset($this->idsMap[$columnName]) && $columnValue > 0) {
$oldId = $columnValue;
if (isset($this->idsMap[$columnName][$columnValue])) {
$columnValue = $this->idsMap[$columnName][$columnValue];
} else {
error_log("$tableName - $columnName with value `$columnValue` does not map, probably missing data");
}
}
$data[$columnName] = $columnValue;
if ($update) {
$update .= ',';
}
$update .= "$q$columnName$q = :$columnName";
}
}
$cols = implode("$q, $q",array_keys($data));
$values = implode(', :',array_keys($data));
$duplicate = "ON DUPLICATE KEY UPDATE $update\n\n";
if (DB_ENGINE == 'sqlite' || DB_ENGINE == 'pgsql') {
//$duplicate = "ON CONFLICT($q$cols$q) DO UPDATE SET $update\n\n";
$duplicate = '';
}
$sql = "INSERT INTO $q$tableName$q ($q$cols$q) VALUES (:$values)" . $duplicate;
$stmt = $this->db->execute($sql, $data);
$lastId = $this->db->insert_id;
if ($idColumn != 'site_id' && $idColumn != 'language_id') {
$this->idsMap[$idColumn][$idValue] = $lastId;
}
//$result = $stmt->get_result();
}
}
}
return true;
}
}