+ #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)
+ 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
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
*/
class ImportExport {
+ /**
+ * @var String The current memory xmlwriter
+ */
private $_currentXW;
+ private $_xmlImportXSD = 'lib/xmlimport.xsd';
+
+
+ /**
+ * @var
+ */
+ private $_uploadedData;
+
public function __construct() {
}
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');
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) {
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
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);
+ }
}
$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']);
`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) {
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
}
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;
+ }
}
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
+ );
+ }
}
--- /dev/null
+<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="root">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="insipidlink" maxOccurs="unbounded" minOccurs="1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element type="xs:string" name="link"/>
+ <xs:element type="xs:string" name="description"/>
+ <xs:element type="xs:string" name="title"/>
+ <xs:element type="xs:string" name="hash"/>
+ <xs:element type="xs:string" name="image"/>
+ <xs:element name="tags">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="tag" maxOccurs="unbounded" minOccurs="0">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute type="xs:byte" name="id" use="optional"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="categories">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="category" maxOccurs="unbounded" minOccurs="0">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute type="xs:byte" name="id" use="optional"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element type="xs:string" name="created"/>
+ <xs:element type="xs:string" name="updated"/>
+ <xs:element type="xs:string" name="exportcreated"/>
+ <xs:element type="xs:string" name="status"/>
+ </xs:sequence>
+ <xs:attribute type="xs:byte" name="id" use="optional"/>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>
\ No newline at end of file
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) {
*/
?>
<section class="section">
+
+ <?php require('_displaySubmitStatus.inc.php'); ?>
+
<div class="columns">
<div class="column">
<p class="has-text-right">
<form method="post">
<input type="submit" class="button is-info is-small" value="Update index" name="statsUpdateSearchIndex">
</form>
+ </div>
+ <div class="column is-one-quarter">
+ <h4 class="is-size-4">Import XML</h4>
+ <p>Single or multiple</p>
+ <form method="post" enctype="multipart/form-data">
+ <div class="file">
+ <label class="file-label">
+ <input class="file-input" type="file" name="importxmlfile">
+ <span class="file-cta">
+ <span class="file-icon">
+ <i class="ion-md-cloud-upload"></i>
+ </span>
+ <span class="file-label">
+ Choose a fileā¦
+ </span>
+ </span>
+ </label>
+ </div>
+ <div class="field">
+ <label class="checkbox">
+ <input type="checkbox" value="overwrite" name="importOverwrite">
+ Overwrite existing
+ </label>
+ </div>
+ <div class="field">
+ <input type="submit" class="button is-info is-small" value="Import" name="statsImportXML">
+ </div>
+ </form>
</div>
<?php } ?>
</div>