From 90ff013ecfa21105e10647d9ca92af40399ef012 Mon Sep 17 00:00:00 2001 From: Banana Date: Mon, 10 Feb 2020 17:58:46 +0100 Subject: [PATCH] importing the exported xml file with validation and stuff. Not complete yet --- ChangeLog | 2 + TODO | 2 + documentation/requirements.txt | 10 +- webroot/lib/import-export.class.php | 161 ++++++++++++++++++++++++++++ webroot/lib/link.class.php | 3 +- webroot/lib/management.class.php | 61 +++++++++++ webroot/lib/summoner.class.php | 44 ++++++++ webroot/lib/xmlimport.xsd | 54 ++++++++++ webroot/view/stats.inc.php | 20 ++++ webroot/view/stats.php | 31 ++++++ 10 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 webroot/lib/xmlimport.xsd diff --git a/ChangeLog b/ChangeLog index cc014f3..421a4ea 100755 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,8 @@ version x.x - Seven Portals (tba) + #2 Protection if the email-import.php file if it needs to be in a web accessible folder + Fixed the search for words. See update instructions how to correct your data + + Removed JS and replaced it with plain old working JS + + Dropped IE support. Edge still working. version 2.3 - Guardian of Steel (2019-12-30) diff --git a/TODO b/TODO index 2fcf6c4..682152c 100755 --- a/TODO +++ b/TODO @@ -2,6 +2,8 @@ TODO / Feature list + Export and import of your data + snapshots + bookmark js snippet ++ translation support ++ stats cleanup. Management functions should be standalone + theme support + more "secure" user authentication ++ multiple user accounts and stuff diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 1b08bc8..d8702ad 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,4 +1,12 @@ Apache (2.4 and up) with PHP extension enabled -PHP (7 and up) with MySQL extension -> mysql & mysqli; curl; pdo; (+imap +ssl if you us the email importer) +PHP (7 and up) +- mysql & mysqli +- curl +- pdo +- imap +ssl if you us the email importer +- xmlread +- xmlwriter MySQL server or access to database 5.6.x and up - DB user rights has to include create, alter a view + +Latest browser for accessing the client. IE (not Edge) is not supported anymore. \ No newline at end of file diff --git a/webroot/lib/import-export.class.php b/webroot/lib/import-export.class.php index 848d684..99bf51b 100644 --- a/webroot/lib/import-export.class.php +++ b/webroot/lib/import-export.class.php @@ -32,8 +32,19 @@ */ class ImportExport { + /** + * @var String The current memory xmlwriter + */ private $_currentXW; + private $_xmlImportXSD = 'lib/xmlimport.xsd'; + + + /** + * @var + */ + private $_uploadedData; + public function __construct() { } @@ -49,6 +60,7 @@ class ImportExport { xmlwriter_set_indent($this->_currentXW, 1); xmlwriter_set_indent_string($this->_currentXW, ' '); xmlwriter_start_document($this->_currentXW, '1.0', 'UTF-8'); + xmlwriter_start_element($this->_currentXW, 'root'); xmlwriter_start_element($this->_currentXW, 'insipidlink'); @@ -80,6 +92,12 @@ class ImportExport { xmlwriter_end_cdata($this->_currentXW); xmlwriter_end_element($this->_currentXW); + xmlwriter_start_element($this->_currentXW, 'image'); + xmlwriter_start_cdata($this->_currentXW); + xmlwriter_text($this->_currentXW, $data['image']); + xmlwriter_end_cdata($this->_currentXW); + xmlwriter_end_element($this->_currentXW); + if(!empty($data['tags'])) { xmlwriter_start_element($this->_currentXW, 'tags'); foreach($data['tags'] as $k=>$v) { @@ -114,13 +132,113 @@ class ImportExport { xmlwriter_end_cdata($this->_currentXW); xmlwriter_end_element($this->_currentXW); + xmlwriter_start_element($this->_currentXW, 'status'); + xmlwriter_start_cdata($this->_currentXW); + xmlwriter_text($this->_currentXW, $data['status']); + xmlwriter_end_cdata($this->_currentXW); + xmlwriter_end_element($this->_currentXW); + xmlwriter_end_element($this->_currentXW); // insipidlink + + xmlwriter_end_element($this->_currentXW); // root xmlwriter_end_document($this->_currentXW); // document return xmlwriter_output_memory($this->_currentXW); } + /** + * @param $file array $_FILES array. Just check if everything is there + * and put it into _uploadedData + * @throws Exception + */ + public function loadImportFile($file) { + + if(!isset($file['name']) + || !isset($file['type']) + || !isset($file['size']) + || !isset($file['tmp_name']) + || !isset($file['error']) + ) { + throw new Exception('Invalid Upload'); + } + + $workWith = $file['tmp_name']; + if(!empty($workWith)) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_file($finfo, $workWith); + finfo_close($finfo); + if($mime != 'text/xml') { + throw new Exception('Invalid mime type'); + } + } else { + throw new Exception('Invalid file upload information'); + } + + // now validate the xml file + $this->_uploadedData = file_get_contents($file['tmp_name']); + + if(!empty($this->_uploadedData)) { + $_valid = $this->_validateXMLImport(); + if($_valid !== true) { + $this->_uploadedData = ''; + throw new Exception('Invalid xml format: '.$_valid); + } + } + else { + $this->_uploadedData = ''; + throw new Exception('Empty upload file?'); + } + } + + /** + * parse the data from _uploadedData and create an array we can use + * @return array + * @throws Exception + */ + public function parseImportFile() { + $ret = array(); + + if(!empty($this->_uploadedData)) { + $xml = simplexml_load_string($this->_uploadedData, "SimpleXMLElement", LIBXML_NOCDATA); + if(!empty($xml->insipidlink)) { + foreach($xml->insipidlink as $linkEntry) { + $_id = (string)$linkEntry->attributes()->id; + $ret[$_id]['id'] = $_id; + $ret[$_id]['link'] = (string)$linkEntry->link; + $ret[$_id]['description'] = (string)$linkEntry->description; + $ret[$_id]['title'] = (string)$linkEntry->title; + $ret[$_id]['hash'] = (string)$linkEntry->hash; + $ret[$_id]['created'] = (string)$linkEntry->created; + $ret[$_id]['updated'] = (string)$linkEntry->updated; + $ret[$_id]['private'] = (string)$linkEntry->status; + $ret[$_id]['image'] = (string)$linkEntry->image; + + if($linkEntry->categories->count() > 0) { + $ret[$_id]['category'] = ''; + foreach ($linkEntry->categories->category as $cat) { + $_cname = (string)$cat; + $ret[$_id]['category'] .= $_cname.","; + } + } + + if($linkEntry->tags->count() > 0) { + $ret[$_id]['tag'] = ''; + foreach ($linkEntry->tags->tag as $tag) { + $_tname = (string)$tag; + $ret[$_id]['tag'] .= $_tname.","; + } + } + } + } + } + else { + throw new Exception('Empty xml data. LoadImportFile needs to be called first.'); + } + + return $ret; + } + /** * Create a single xml element for the current loaded xmlwriter * @param String $name @@ -143,4 +261,47 @@ class ImportExport { xmlwriter_end_element($this->_currentXW); } } + + /** + * validate an import of a export xml with the + * saved xsd file _xmlImportXSD + * @return bool|string + */ + private function _validateXMLImport() { + $ret = false; + $xmlReader = new XMLReader(); + $xmlReader->XML($this->_uploadedData); + if(!empty($xmlReader)) { + $xmlReader->setSchema($this->_xmlImportXSD); + libxml_use_internal_errors(true); + while($xmlReader->read()) { + if (!$xmlReader->isValid()) { + $ret = $this->_xmlErrors(); + break; + } else { + $ret = true; + break; + } + } + } + + return $ret; + } + + /** + * Reads libxml_get_errors and creates a simple string with all + * the info we need. + * @return string + */ + private function _xmlErrors() { + $errors = libxml_get_errors(); + $result = array(); + foreach ($errors as $error) { + $errorString = "Error $error->code in $error->file (Line:{$error->line}):"; + $errorString .= trim($error->message); + $result[] = $errorString; + } + libxml_clear_errors(); + return implode("\n",$result); + } } diff --git a/webroot/lib/link.class.php b/webroot/lib/link.class.php index df4709a..e29c908 100644 --- a/webroot/lib/link.class.php +++ b/webroot/lib/link.class.php @@ -175,7 +175,7 @@ class Link { $ret = false; - if (isset($data['title']) && !empty($data['title'])) { + if (isset($data['title']) && !empty($data['title']) && !empty($this->_data)) { # categories and tag stuff $catArr = Summoner::prepareTagOrCategoryStr($data['category']); @@ -203,7 +203,6 @@ class Link { `image` = '" . $this->DB->real_escape_string($data['image']) . "', `search` = '" . $this->DB->real_escape_string($search) . "' WHERE `hash` = '" . $this->DB->real_escape_string($this->_data['hash']) . "'"; - $query = $this->DB->query($queryStr); if ($query !== false) { diff --git a/webroot/lib/management.class.php b/webroot/lib/management.class.php index 71a8008..effd485 100644 --- a/webroot/lib/management.class.php +++ b/webroot/lib/management.class.php @@ -718,6 +718,51 @@ class Management { return $ret; } + + public function processImportFile($file, $options) { + $ret = array( + 'status' => 'error', + 'message' => 'Processing error' + ); + + $links = array(); + require_once 'lib/import-export.class.php'; + $ImEx = new ImportExport(); + try { + $ImEx->loadImportFile($file); + $links = $ImEx->parseImportFile(); + } + catch (Exception $e) { + $ret['message'] = $e->getMessage(); + } + + $_existing = 0; + $_new = 0; + if(!empty($links)) { + $_amount = count($links); + foreach($links as $linkToImport) { + if($this->_linkExistsById($linkToImport['id'])) { + $linkObj = new Link($this->DB); + $linkObj->load($linkToImport['hash']); + $do = $linkObj->update($linkToImport); + $_existing++; + } + else { + $_new++; + var_dump('new one'); + } + //var_dump($linkToImport); + } + $ret = array( + 'status' => 'success', + 'message' => "Found $_amount link(s) to import. $_existing existing and $_new new one(s)." + ); + + } + + return $ret; + } + /** * Return the query string for the correct status type * @return string @@ -736,5 +781,21 @@ class Management { } return $ret; } + + private function _linkExistsById($id) { + $ret = false; + + if(!empty($id)) { + $queryStr = "SELECT `id` + FROM `" . DB_PREFIX . "_link` + WHERE `id` = '" . $this->DB->real_escape_string($id) . "'"; + $query = $this->DB->query($queryStr); + if(!empty($query) && $query->num_rows > 0) { + $ret = true; + } + } + + return $ret; + } } diff --git a/webroot/lib/summoner.class.php b/webroot/lib/summoner.class.php index c988a36..ece2021 100644 --- a/webroot/lib/summoner.class.php +++ b/webroot/lib/summoner.class.php @@ -589,4 +589,48 @@ class Summoner { return $ret; } + + /** + * Simple helper to detect the $_FILES upload status + * Expects the error value from $_FILES['error'] + * @param $error + * @return array + */ + static function checkFileUploadStatus($error) { + $message = "Unknown upload error"; + $status = false; + + switch ($error) { + case UPLOAD_ERR_OK: + $message = "There is no error, the file uploaded with success."; + $status = true; + break; + case UPLOAD_ERR_INI_SIZE: + $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini"; + break; + case UPLOAD_ERR_FORM_SIZE: + $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"; + break; + case UPLOAD_ERR_PARTIAL: + $message = "The uploaded file was only partially uploaded"; + break; + case UPLOAD_ERR_NO_FILE: + $message = "No file was uploaded"; + break; + case UPLOAD_ERR_NO_TMP_DIR: + $message = "Missing a temporary folder"; + break; + case UPLOAD_ERR_CANT_WRITE: + $message = "Failed to write file to disk"; + break; + case UPLOAD_ERR_EXTENSION: + $message = "File upload stopped by extension"; + break; + } + + return array( + 'message' => $message, + 'status' => $status + ); + } } diff --git a/webroot/lib/xmlimport.xsd b/webroot/lib/xmlimport.xsd new file mode 100644 index 0000000..e8ffb60 --- /dev/null +++ b/webroot/lib/xmlimport.xsd @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webroot/view/stats.inc.php b/webroot/view/stats.inc.php index 27c8304..3c9f6cf 100644 --- a/webroot/view/stats.inc.php +++ b/webroot/view/stats.inc.php @@ -78,6 +78,26 @@ if(isset($_POST['statsCreateDBBackup'])) { exit(); } +if(isset($_POST['statsImportXML'])) { + $_options = array(); + + if(isset($_FILES['importxmlfile']) && !empty($_FILES['importxmlfile'])) { + $do = $Management->processImportFile($_FILES['importxmlfile'], $_options); + if(isset($do['status']) && $do['status'] === 'success') { + $submitFeedback['status'] = 'success'; + $submitFeedback['message'] = $do['message']; + } + else { + $submitFeedback['message'] = $do['message']; + $submitFeedback['status'] = 'error'; + } + } + else { + $submitFeedback['message'] = 'Please provide a import file'; + $submitFeedback['status'] = 'error'; + } +} + if(isset($_POST['statsUpdateSearchIndex'])) { if($Management->updateSearchIndex() === true) { diff --git a/webroot/view/stats.php b/webroot/view/stats.php index 8aaf432..21719cb 100644 --- a/webroot/view/stats.php +++ b/webroot/view/stats.php @@ -27,6 +27,9 @@ */ ?>
+ + +

@@ -93,6 +96,34 @@

+
+
+

Import XML

+

Single or multiple

+
+
+ +
+
+ +
+
+ +
+
-- 2.39.5