import-export.class.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2022 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 XMLWriter The current memory xmlwriter
  35. */
  36. private XMLWriter $_currentXW;
  37. /**
  38. * @var string
  39. */
  40. private string $_xmlImportXSD = 'lib/xmlimport.xsd';
  41. /**
  42. * @var string
  43. */
  44. private string $_uploadedData;
  45. public function __construct() {
  46. }
  47. /**
  48. * create a xml file for a given single link
  49. * expects the array from Link->load
  50. *
  51. * @param array $data
  52. * @return string XML string from xmlwriter
  53. */
  54. public function createSingleLinkExportXML(array $data): string {
  55. $this->_currentXW = xmlwriter_open_memory();
  56. xmlwriter_set_indent($this->_currentXW, 1);
  57. xmlwriter_set_indent_string($this->_currentXW, ' ');
  58. xmlwriter_start_document($this->_currentXW, '1.0', 'UTF-8');
  59. xmlwriter_start_element($this->_currentXW, 'root');
  60. xmlwriter_start_element($this->_currentXW, 'insipidlink');
  61. xmlwriter_start_attribute($this->_currentXW, 'id');
  62. xmlwriter_text($this->_currentXW, $data['id']);
  63. xmlwriter_end_attribute($this->_currentXW);
  64. xmlwriter_start_element($this->_currentXW, 'link');
  65. xmlwriter_start_cdata($this->_currentXW);
  66. xmlwriter_text($this->_currentXW, $data['link']);
  67. xmlwriter_end_cdata($this->_currentXW);
  68. xmlwriter_end_element($this->_currentXW);
  69. xmlwriter_start_element($this->_currentXW, 'description');
  70. xmlwriter_start_cdata($this->_currentXW);
  71. xmlwriter_text($this->_currentXW, $data['description']);
  72. xmlwriter_end_cdata($this->_currentXW);
  73. xmlwriter_end_element($this->_currentXW);
  74. xmlwriter_start_element($this->_currentXW, 'title');
  75. xmlwriter_start_cdata($this->_currentXW);
  76. xmlwriter_text($this->_currentXW, $data['title']);
  77. xmlwriter_end_cdata($this->_currentXW);
  78. xmlwriter_end_element($this->_currentXW);
  79. xmlwriter_start_element($this->_currentXW, 'hash');
  80. xmlwriter_start_cdata($this->_currentXW);
  81. xmlwriter_text($this->_currentXW, $data['hash']);
  82. xmlwriter_end_cdata($this->_currentXW);
  83. xmlwriter_end_element($this->_currentXW);
  84. xmlwriter_start_element($this->_currentXW, 'image');
  85. xmlwriter_start_cdata($this->_currentXW);
  86. xmlwriter_text($this->_currentXW, $data['image']);
  87. xmlwriter_end_cdata($this->_currentXW);
  88. xmlwriter_end_element($this->_currentXW);
  89. if(!empty($data['tags'])) {
  90. xmlwriter_start_element($this->_currentXW, 'tags');
  91. foreach($data['tags'] as $k=>$v) {
  92. $this->_elementFromKeyValue('tag',$k,$v);
  93. }
  94. xmlwriter_end_element($this->_currentXW);
  95. }
  96. if(!empty($data['categories'])) {
  97. xmlwriter_start_element($this->_currentXW, 'categories');
  98. foreach($data['categories'] as $k=>$v) {
  99. $this->_elementFromKeyValue('category',$k,$v);
  100. }
  101. xmlwriter_end_element($this->_currentXW);
  102. }
  103. xmlwriter_start_element($this->_currentXW, 'created');
  104. xmlwriter_start_cdata($this->_currentXW);
  105. xmlwriter_text($this->_currentXW, $data['created']);
  106. xmlwriter_end_cdata($this->_currentXW);
  107. xmlwriter_end_element($this->_currentXW);
  108. xmlwriter_start_element($this->_currentXW, 'updated');
  109. xmlwriter_start_cdata($this->_currentXW);
  110. xmlwriter_text($this->_currentXW, $data['updated']);
  111. xmlwriter_end_cdata($this->_currentXW);
  112. xmlwriter_end_element($this->_currentXW);
  113. xmlwriter_start_element($this->_currentXW, 'exportcreated');
  114. xmlwriter_start_cdata($this->_currentXW);
  115. xmlwriter_text($this->_currentXW, date('Y-m-d H:i:s'));
  116. xmlwriter_end_cdata($this->_currentXW);
  117. xmlwriter_end_element($this->_currentXW);
  118. xmlwriter_start_element($this->_currentXW, 'status');
  119. xmlwriter_start_cdata($this->_currentXW);
  120. xmlwriter_text($this->_currentXW, $data['status']);
  121. xmlwriter_end_cdata($this->_currentXW);
  122. xmlwriter_end_element($this->_currentXW);
  123. xmlwriter_end_element($this->_currentXW); // insipidlink
  124. xmlwriter_end_element($this->_currentXW); // root
  125. xmlwriter_end_document($this->_currentXW); // document
  126. return xmlwriter_output_memory($this->_currentXW);
  127. }
  128. /**
  129. * Just check if everything is there and put it into _uploadedData
  130. *
  131. * @param array $file $_FILES array.
  132. * @throws Exception
  133. * @return void
  134. */
  135. public function loadImportFile(array $file): void {
  136. if(!isset($file['name'])
  137. || !isset($file['type'])
  138. || !isset($file['size'])
  139. || !isset($file['tmp_name'])
  140. || !isset($file['error'])
  141. ) {
  142. throw new Exception('Invalid Upload');
  143. }
  144. $workWith = $file['tmp_name'];
  145. if(!empty($workWith)) {
  146. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  147. $mime = finfo_file($finfo, $workWith);
  148. finfo_close($finfo);
  149. if($mime != 'text/xml') {
  150. throw new Exception('Invalid mime type');
  151. }
  152. } else {
  153. throw new Exception('Invalid file upload information');
  154. }
  155. // now validate the xml file
  156. $this->_uploadedData = file_get_contents($file['tmp_name']);
  157. if(!empty($this->_uploadedData)) {
  158. $_valid = $this->_validateXMLImport();
  159. if(!empty($_valid)) {
  160. $this->_uploadedData = '';
  161. throw new Exception('Invalid xml format: '.$_valid);
  162. }
  163. }
  164. else {
  165. $this->_uploadedData = '';
  166. throw new Exception('Empty upload file?');
  167. }
  168. }
  169. /**
  170. * parse the data from _uploadedData and create an array we can use
  171. *
  172. * @return array
  173. * @throws Exception
  174. */
  175. public function parseImportFile(): array {
  176. $ret = array();
  177. if(!empty($this->_uploadedData)) {
  178. $xml = simplexml_load_string($this->_uploadedData, "SimpleXMLElement", LIBXML_NOCDATA);
  179. if(!empty($xml->insipidlink)) {
  180. foreach($xml->insipidlink as $linkEntry) {
  181. $_id = (string)$linkEntry->attributes()->id;
  182. $ret[$_id]['id'] = $_id;
  183. $ret[$_id]['link'] = (string)$linkEntry->link;
  184. $ret[$_id]['description'] = (string)$linkEntry->description;
  185. $ret[$_id]['title'] = (string)$linkEntry->title;
  186. $ret[$_id]['hash'] = (string)$linkEntry->hash;
  187. $ret[$_id]['created'] = (string)$linkEntry->created;
  188. $ret[$_id]['updated'] = (string)$linkEntry->updated;
  189. $ret[$_id]['private'] = (string)$linkEntry->status;
  190. $ret[$_id]['image'] = (string)$linkEntry->image;
  191. if($linkEntry->categories->count() > 0) {
  192. $ret[$_id]['category'] = '';
  193. foreach ($linkEntry->categories->category as $cat) {
  194. $_cname = (string)$cat;
  195. $ret[$_id]['category'] .= $_cname.",";
  196. }
  197. }
  198. if($linkEntry->tags->count() > 0) {
  199. $ret[$_id]['tag'] = '';
  200. foreach ($linkEntry->tags->tag as $tag) {
  201. $_tname = (string)$tag;
  202. $ret[$_id]['tag'] .= $_tname.",";
  203. }
  204. }
  205. }
  206. }
  207. }
  208. else {
  209. throw new Exception('Empty xml data. LoadImportFile needs to be called first.');
  210. }
  211. return $ret;
  212. }
  213. /**
  214. * Create a single xml element for the current loaded xmlwriter
  215. *
  216. * @param String $name
  217. * @param String $key
  218. * @param String $value
  219. * @return void
  220. */
  221. private function _elementFromKeyValue(string $name, string $key, string $value): void {
  222. if(!empty($key) && !empty($value) && !empty($name)) {
  223. xmlwriter_start_element($this->_currentXW, $name);
  224. xmlwriter_start_attribute($this->_currentXW, 'id');
  225. xmlwriter_text($this->_currentXW, $key);
  226. xmlwriter_end_attribute($this->_currentXW);
  227. xmlwriter_start_cdata($this->_currentXW);
  228. xmlwriter_text($this->_currentXW, $value);
  229. xmlwriter_end_cdata($this->_currentXW);
  230. xmlwriter_end_element($this->_currentXW);
  231. }
  232. }
  233. /**
  234. * validate an import of a export xml with the
  235. * saved xsd file _xmlImportXSD
  236. *
  237. * @return string
  238. */
  239. private function _validateXMLImport(): string {
  240. $ret = '';
  241. $xmlReader = new XMLReader();
  242. $xmlReader->XML($this->_uploadedData);
  243. if(!empty($xmlReader)) {
  244. $xmlReader->setSchema($this->_xmlImportXSD);
  245. libxml_use_internal_errors(true);
  246. while($xmlReader->read()) {
  247. if (!$xmlReader->isValid()) {
  248. $ret = $this->_xmlErrors();
  249. break;
  250. } else {
  251. break;
  252. }
  253. }
  254. }
  255. return $ret;
  256. }
  257. /**
  258. * Reads libxml_get_errors and creates a simple string with all
  259. * the info we need.
  260. *
  261. * @return string
  262. */
  263. private function _xmlErrors(): string {
  264. $errors = libxml_get_errors();
  265. $result = array();
  266. foreach ($errors as $error) {
  267. $errorString = "Error $error->code in $error->file (Line:{$error->line}):";
  268. $errorString .= trim($error->message);
  269. $result[] = $errorString;
  270. }
  271. libxml_clear_errors();
  272. return implode("\n",$result);
  273. }
  274. }