import-export.class.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2020 Johannes Keßler
  7. *
  8. * Development starting from 2011: Johannes Keßler
  9. * https://www.bananas-playground.net/projekt/insipid/
  10. *
  11. * creator:
  12. * Luke Reeves <luke@neuro-tech.net>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License as published by
  16. * the Free Software Foundation, either version 3 of the License, or
  17. * (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU General Public License
  25. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  26. *
  27. */
  28. /**
  29. * Simple Class ImportExport used to create and process a xml file
  30. * Different from the complete mysql dump
  31. */
  32. class ImportExport {
  33. /**
  34. * @var String The current memory xmlwriter
  35. */
  36. private $_currentXW;
  37. private $_xmlImportXSD = 'lib/xmlimport.xsd';
  38. /**
  39. * @var
  40. */
  41. private $_uploadedData;
  42. public function __construct() {
  43. }
  44. /**
  45. * create a xml file for a given single link
  46. * expects the array from Link->load
  47. * @param array $data
  48. * @return string XML string from xmlwriter
  49. */
  50. public function createSingleLinkExportXML($data) {
  51. $this->_currentXW = xmlwriter_open_memory();
  52. xmlwriter_set_indent($this->_currentXW, 1);
  53. xmlwriter_set_indent_string($this->_currentXW, ' ');
  54. xmlwriter_start_document($this->_currentXW, '1.0', 'UTF-8');
  55. xmlwriter_start_element($this->_currentXW, 'root');
  56. xmlwriter_start_element($this->_currentXW, 'insipidlink');
  57. xmlwriter_start_attribute($this->_currentXW, 'id');
  58. xmlwriter_text($this->_currentXW, $data['id']);
  59. xmlwriter_end_attribute($this->_currentXW);
  60. xmlwriter_start_element($this->_currentXW, 'link');
  61. xmlwriter_start_cdata($this->_currentXW);
  62. xmlwriter_text($this->_currentXW, $data['link']);
  63. xmlwriter_end_cdata($this->_currentXW);
  64. xmlwriter_end_element($this->_currentXW);
  65. xmlwriter_start_element($this->_currentXW, 'description');
  66. xmlwriter_start_cdata($this->_currentXW);
  67. xmlwriter_text($this->_currentXW, $data['description']);
  68. xmlwriter_end_cdata($this->_currentXW);
  69. xmlwriter_end_element($this->_currentXW);
  70. xmlwriter_start_element($this->_currentXW, 'title');
  71. xmlwriter_start_cdata($this->_currentXW);
  72. xmlwriter_text($this->_currentXW, $data['title']);
  73. xmlwriter_end_cdata($this->_currentXW);
  74. xmlwriter_end_element($this->_currentXW);
  75. xmlwriter_start_element($this->_currentXW, 'hash');
  76. xmlwriter_start_cdata($this->_currentXW);
  77. xmlwriter_text($this->_currentXW, $data['hash']);
  78. xmlwriter_end_cdata($this->_currentXW);
  79. xmlwriter_end_element($this->_currentXW);
  80. xmlwriter_start_element($this->_currentXW, 'image');
  81. xmlwriter_start_cdata($this->_currentXW);
  82. xmlwriter_text($this->_currentXW, $data['image']);
  83. xmlwriter_end_cdata($this->_currentXW);
  84. xmlwriter_end_element($this->_currentXW);
  85. if(!empty($data['tags'])) {
  86. xmlwriter_start_element($this->_currentXW, 'tags');
  87. foreach($data['tags'] as $k=>$v) {
  88. $this->_elementFromKeyValue('tag',$k,$v);
  89. }
  90. xmlwriter_end_element($this->_currentXW);
  91. }
  92. if(!empty($data['categories'])) {
  93. xmlwriter_start_element($this->_currentXW, 'categories');
  94. foreach($data['categories'] as $k=>$v) {
  95. $this->_elementFromKeyValue('category',$k,$v);
  96. }
  97. xmlwriter_end_element($this->_currentXW);
  98. }
  99. xmlwriter_start_element($this->_currentXW, 'created');
  100. xmlwriter_start_cdata($this->_currentXW);
  101. xmlwriter_text($this->_currentXW, $data['created']);
  102. xmlwriter_end_cdata($this->_currentXW);
  103. xmlwriter_end_element($this->_currentXW);
  104. xmlwriter_start_element($this->_currentXW, 'updated');
  105. xmlwriter_start_cdata($this->_currentXW);
  106. xmlwriter_text($this->_currentXW, $data['updated']);
  107. xmlwriter_end_cdata($this->_currentXW);
  108. xmlwriter_end_element($this->_currentXW);
  109. xmlwriter_start_element($this->_currentXW, 'exportcreated');
  110. xmlwriter_start_cdata($this->_currentXW);
  111. xmlwriter_text($this->_currentXW, date('Y-m-d H:i:s'));
  112. xmlwriter_end_cdata($this->_currentXW);
  113. xmlwriter_end_element($this->_currentXW);
  114. xmlwriter_start_element($this->_currentXW, 'status');
  115. xmlwriter_start_cdata($this->_currentXW);
  116. xmlwriter_text($this->_currentXW, $data['status']);
  117. xmlwriter_end_cdata($this->_currentXW);
  118. xmlwriter_end_element($this->_currentXW);
  119. xmlwriter_end_element($this->_currentXW); // insipidlink
  120. xmlwriter_end_element($this->_currentXW); // root
  121. xmlwriter_end_document($this->_currentXW); // document
  122. return xmlwriter_output_memory($this->_currentXW);
  123. }
  124. /**
  125. * @param $file array $_FILES array. Just check if everything is there
  126. * and put it into _uploadedData
  127. * @throws Exception
  128. */
  129. public function loadImportFile($file) {
  130. if(!isset($file['name'])
  131. || !isset($file['type'])
  132. || !isset($file['size'])
  133. || !isset($file['tmp_name'])
  134. || !isset($file['error'])
  135. ) {
  136. throw new Exception('Invalid Upload');
  137. }
  138. $workWith = $file['tmp_name'];
  139. if(!empty($workWith)) {
  140. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  141. $mime = finfo_file($finfo, $workWith);
  142. finfo_close($finfo);
  143. if($mime != 'text/xml') {
  144. throw new Exception('Invalid mime type');
  145. }
  146. } else {
  147. throw new Exception('Invalid file upload information');
  148. }
  149. // now validate the xml file
  150. $this->_uploadedData = file_get_contents($file['tmp_name']);
  151. if(!empty($this->_uploadedData)) {
  152. $_valid = $this->_validateXMLImport();
  153. if($_valid !== true) {
  154. $this->_uploadedData = '';
  155. throw new Exception('Invalid xml format: '.$_valid);
  156. }
  157. }
  158. else {
  159. $this->_uploadedData = '';
  160. throw new Exception('Empty upload file?');
  161. }
  162. }
  163. /**
  164. * parse the data from _uploadedData and create an array we can use
  165. * @return array
  166. * @throws Exception
  167. */
  168. public function parseImportFile() {
  169. $ret = array();
  170. if(!empty($this->_uploadedData)) {
  171. $xml = simplexml_load_string($this->_uploadedData, "SimpleXMLElement", LIBXML_NOCDATA);
  172. if(!empty($xml->insipidlink)) {
  173. foreach($xml->insipidlink as $linkEntry) {
  174. $_id = (string)$linkEntry->attributes()->id;
  175. $ret[$_id]['id'] = $_id;
  176. $ret[$_id]['link'] = (string)$linkEntry->link;
  177. $ret[$_id]['description'] = (string)$linkEntry->description;
  178. $ret[$_id]['title'] = (string)$linkEntry->title;
  179. $ret[$_id]['hash'] = (string)$linkEntry->hash;
  180. $ret[$_id]['created'] = (string)$linkEntry->created;
  181. $ret[$_id]['updated'] = (string)$linkEntry->updated;
  182. $ret[$_id]['private'] = (string)$linkEntry->status;
  183. $ret[$_id]['image'] = (string)$linkEntry->image;
  184. if($linkEntry->categories->count() > 0) {
  185. $ret[$_id]['category'] = '';
  186. foreach ($linkEntry->categories->category as $cat) {
  187. $_cname = (string)$cat;
  188. $ret[$_id]['category'] .= $_cname.",";
  189. }
  190. }
  191. if($linkEntry->tags->count() > 0) {
  192. $ret[$_id]['tag'] = '';
  193. foreach ($linkEntry->tags->tag as $tag) {
  194. $_tname = (string)$tag;
  195. $ret[$_id]['tag'] .= $_tname.",";
  196. }
  197. }
  198. }
  199. }
  200. }
  201. else {
  202. throw new Exception('Empty xml data. LoadImportFile needs to be called first.');
  203. }
  204. return $ret;
  205. }
  206. /**
  207. * Create a single xml element for the current loaded xmlwriter
  208. * @param String $name
  209. * @param String $key
  210. * @param String $value
  211. */
  212. private function _elementFromKeyValue($name, $key, $value) {
  213. if(!empty($key) && !empty($value) && !empty($name)) {
  214. xmlwriter_start_element($this->_currentXW, $name);
  215. xmlwriter_start_attribute($this->_currentXW, 'id');
  216. xmlwriter_text($this->_currentXW, $key);
  217. xmlwriter_end_attribute($this->_currentXW);
  218. xmlwriter_start_cdata($this->_currentXW);
  219. xmlwriter_text($this->_currentXW, $value);
  220. xmlwriter_end_cdata($this->_currentXW);
  221. xmlwriter_end_element($this->_currentXW);
  222. }
  223. }
  224. /**
  225. * validate an import of a export xml with the
  226. * saved xsd file _xmlImportXSD
  227. * @return bool|string
  228. */
  229. private function _validateXMLImport() {
  230. $ret = false;
  231. $xmlReader = new XMLReader();
  232. $xmlReader->XML($this->_uploadedData);
  233. if(!empty($xmlReader)) {
  234. $xmlReader->setSchema($this->_xmlImportXSD);
  235. libxml_use_internal_errors(true);
  236. while($xmlReader->read()) {
  237. if (!$xmlReader->isValid()) {
  238. $ret = $this->_xmlErrors();
  239. break;
  240. } else {
  241. $ret = true;
  242. break;
  243. }
  244. }
  245. }
  246. return $ret;
  247. }
  248. /**
  249. * Reads libxml_get_errors and creates a simple string with all
  250. * the info we need.
  251. * @return string
  252. */
  253. private function _xmlErrors() {
  254. $errors = libxml_get_errors();
  255. $result = array();
  256. foreach ($errors as $error) {
  257. $errorString = "Error $error->code in $error->file (Line:{$error->line}):";
  258. $errorString .= trim($error->message);
  259. $result[] = $errorString;
  260. }
  261. libxml_clear_errors();
  262. return implode("\n",$result);
  263. }
  264. }