PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.7.5
Import WP – CSV & XML Import Export for WordPress v2.7.5
2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 2.9.1 All 144 releases
jc-importer / class / Common / Importer / File / XMLFile.php

XMLFile.php in Import WP – CSV & XML Import Export for WordPress 2.7.5, at class/Common/Importer/File/XMLFile.php

439 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImportWP\Common\Importer\File;
4
5 use ImportWP\Common\Importer\FileInterface;
6
7 class XMLFile extends AbstractIndexedFile implements FileInterface
8 {
9
10 private $base_path;
11 private $base_path_segments = array();
12 private $record_offset = 0;
13 private $chunk_size = 8192;
14 private $depth = 0;
15 private $open_nodes = array();
16 private $node_list = array();
17 private $chunk;
18 private $chunk_offset = 0;
19
20 private $record_counter = 0;
21 private $record_start_index = 0;
22 private $record_opened = false;
23 private $last_record_incomplete = false;
24 private $record_patch_cache_key = '';
25
26 /**
27 * Set base path for records
28 *
29 * @param string $base_path
30 */
31 public function setRecordPath($base_path = '/')
32 {
33 // strip slash from start
34 if (strpos($base_path, '/') === 0) {
35 $base_path = substr($base_path, 1);
36 }
37
38 // split basepath in segments
39 $this->base_path_segments = explode('/', $base_path);
40 array_filter($this->base_path_segments, function ($value) {
41 return $value !== "";
42 });
43
44 if (!empty($this->base_path_segments)) {
45 $this->record_patch_cache_key = implode('_', $this->base_path_segments);
46 $base_path = array_pop($this->base_path_segments);
47 }
48
49 $this->base_path = $base_path;
50 }
51
52 /**
53 * Get record
54 *
55 * @param int $index
56 *
57 * @return string
58 */
59 public function getRecord($index = 0)
60 {
61 $record = parent::getRecord($index);
62
63 if ($index === $this->getRecordCount() - 1 && true === $this->last_record_incomplete) {
64 $record = $this->fixRecord($record);
65 }
66
67 return $this->wrapWithRecordTag($record);
68 }
69
70 /**
71 * Fix a broken xml segment
72 *
73 * @param $record
74 *
75 * @return bool|string
76 */
77 private function fixRecord($record)
78 {
79 $open = "/<[^\/][^>]+[^\/]>/";
80 $close = "/<\/[^>]+[^\/]>/";
81 $depth = 0;
82 $started = false;
83 $open_nodes = array();
84
85 $chunk = $record;
86
87 while (preg_match(
88 "/<[^?|!\-\-][^>]*>/",
89 $chunk,
90 $matches,
91 PREG_OFFSET_CAPTURE
92 ) !== 0) {
93 if (isset($matches[0]) && isset($matches[0][0])) {
94 list($captured, $offset) = $matches[0];
95
96 if (preg_match($open, $captured) === 1) {
97 $started = true;
98 $depth++;
99 $node_name = $this->getNodeName($captured);
100 $open_nodes[] = $node_name;
101 } else if (preg_match($close, $captured) === 1) {
102 $depth--;
103 array_pop($open_nodes);
104 }
105
106 $string_offset = $offset + strlen($captured);
107 $chunk = substr($chunk, $string_offset);
108 }
109
110 if ($started && 0 === $depth) {
111 // We should be at the end of the xml
112 return substr($record, 0, strlen($chunk));
113 }
114 }
115
116 if (!empty($open_nodes)) {
117 // Add closing tags for open nodes
118 foreach ($open_nodes as $node) {
119 $record .= "</{$node}>";
120 }
121 }
122
123 return $record;
124 }
125
126 /**
127 * Get node name from xml tag
128 *
129 * @param $node
130 *
131 * @return bool|string
132 */
133 private function getNodeName($node)
134 {
135
136 preg_match("/<\/?([a-zA-Z0-9\-_\.:]+)[ ]*[^>]*>/", $node, $matches);
137 if (isset($matches[0]) && isset($matches[1])) {
138 $name = $matches[1];
139 } else {
140 $name = substr($node, 1, strlen($node) - 2);
141 }
142
143 return $name;
144 }
145
146 /**
147 * Generate file position index.
148 *
149 * Read through entire xml file generating record start and end positions
150 *
151 * @return mixed
152 */
153 protected function generateIndex()
154 {
155 $this->record_counter = 0;
156 $this->record_start_index = 0;
157
158 rewind($this->getFileHandle());
159 while (!feof($this->getFileHandle())) {
160
161 $this->chunk .= $this->getChunk();
162 $this->readChunkXmlNodes();
163
164 // only read the first 1mb of file
165 if ($this->is_processing && ($this->record_counter > 0 || ftell($this->getFileHandle()) > $this->process_max_size)) {
166 break;
167 }
168 }
169
170 // if file is incomplete do we try and fix the record, or skip it?
171 if (!$this->is_processing && true === $this->record_opened) {
172 $this->record_opened = false;
173 $this->last_record_incomplete = true;
174 $this->setIndex(
175 $this->record_counter,
176 $this->record_start_index,
177 $this->chunk_offset
178 );
179 }
180 }
181
182 /**
183 * Read chunk from file
184 *
185 * @return bool|string
186 */
187 private function getChunk()
188 {
189 return fread($this->getFileHandle(), $this->chunk_size);
190 }
191
192 /**
193 * Read all xml nodes in chunk
194 */
195 private function readChunkXmlNodes()
196 {
197 $tags = [
198 ["<?", "?>", true, 0],
199 ["<!--", "-->", true, 0],
200 ["<![CDATA[", "]]>", true, 0],
201 ["<!", ">", true, 0],
202 ["</", ">", false, -1],
203 ["<", "/>", false, 0],
204 ["<", ">", false, 1],
205 ];
206
207 while (preg_match("/<[^>]+>/", $this->chunk, $matches, PREG_OFFSET_CAPTURE) !== 0) {
208 list($captured, $offset) = $matches[0];
209
210 $this->record_offset = $offset;
211
212 foreach ($tags as $tag) {
213
214 list($open_tag, $close_tag, $search_for_close, $depth_delta) = $tag;
215
216 if (strpos($captured, $open_tag) === 0) {
217
218 if (strlen($captured) - strlen($close_tag) === strpos($captured, $close_tag)) {
219 // Tag Found
220 if ($search_for_close === false) {
221 $this->record_node($captured, $depth_delta);
222 }
223 break;
224 } elseif ($search_for_close === true) {
225 $close_tag_position = strpos($this->chunk, $close_tag, $offset);
226 if ($close_tag_position !== false) {
227 // Close tag is found in current chunk
228 $captured = substr($this->chunk, $offset, $close_tag_position + strlen($close_tag) - $offset);
229 if ($search_for_close === false) {
230 $this->record_node($captured, $depth_delta);
231 }
232 } else {
233 // We need to load next chunk to find
234 return;
235 }
236 break;
237 }
238 }
239 }
240
241 $string_offset = $offset + strlen($captured);
242 $this->chunk_offset += $string_offset;
243 $this->chunk = substr($this->chunk, $string_offset);
244 }
245 return;
246
247 // TODO: Match xmlns: <[^>]+xmlns:(.*?)="(.*?)"[^>]+>
248 /*$open = "/<[^\/][^>]*[^\/]>/s";
249 $close = "/<\/[^>]*[^\/]>/";
250 $openclose = "/<[^\/][^>]+\/>/s";
251
252 while (preg_match("/<[^?|!\-\-][^>]*>/s", $this->chunk, $matches,
253 PREG_OFFSET_CAPTURE) !== 0) {
254 if (isset($matches[0]) && isset($matches[0][0])) {
255 list($captured, $offset) = $matches[0];
256
257 $this->record_offset = $offset;
258
259 if (preg_match($close, $captured) === 1) {
260 $this->depth--;
261
262 $node = array_pop($this->open_nodes);
263
264 if ($this->isRecordEnd($node)) {
265 $this->record_opened = false;
266 $temp = $this->chunk_offset + $this->record_offset + strlen($captured);
267 $this->setIndex($this->record_counter,
268 $this->record_start_index, $temp);
269 $this->record_counter++;
270 }
271 }elseif (preg_match($open, $captured) === 1) {
272 $this->depth++;
273
274 $node_name = $this->getNodeName($captured);
275
276 if ($this->isRecordStart($node_name)) {
277 $this->record_opened = true;
278 $temp = $this->chunk_offset + $this->record_offset;
279 $this->record_start_index = $temp;
280 }
281
282 $this->open_nodes[] = $node_name;
283 $this->record_open_nodes();
284
285 }
286 $string_offset = $offset + strlen($captured);
287 $this->chunk_offset += $string_offset;
288 $this->chunk = substr($this->chunk, $string_offset);
289 }
290 }*/
291 }
292
293 private function record_node($node, $depth_delta)
294 {
295
296 $this->depth += $depth_delta;
297 $node_name = $this->getNodeName($node);
298
299 if ($depth_delta > 0) {
300
301 if ($this->isRecordStart($node_name)) {
302 $this->record_opened = true;
303 $this->record_start_index = $this->chunk_offset + $this->record_offset;
304 }
305
306 $this->open_nodes[] = $node_name;
307 $this->record_open_nodes();
308 } elseif ($depth_delta < 0) {
309
310 array_pop($this->open_nodes);
311 if ($this->isRecordEnd($node_name)) {
312 $this->record_opened = false;
313
314 $this->setIndex(
315 $this->record_counter,
316 $this->record_start_index,
317 $this->chunk_offset + $this->record_offset + strlen($node)
318 );
319 $this->record_counter++;
320 }
321 }
322
323 // echo sprintf("%s %s - %d\n", str_repeat("\t", $this->depth), $node_name, $depth_delta);
324 }
325
326 /**
327 * Check for record start node
328 *
329 * @param $node_name
330 *
331 * @return bool
332 */
333 private function isRecordStart($node_name)
334 {
335
336 if ($this->base_path_segments === $this->open_nodes && $node_name === $this->base_path) {
337 return true;
338 }
339
340 return false;
341 }
342
343 /**
344 * Check for record end node
345 *
346 * @param $node_name
347 *
348 * @return bool
349 */
350 private function isRecordEnd($node_name)
351 {
352 if ($this->record_opened === true && $node_name === $this->base_path) {
353
354 // check to see if we are a nested duplicate node name /Events/Event/Instance and we are closing Event
355 if (in_array($node_name, $this->open_nodes)) {
356 $array_counts = array_count_values($this->open_nodes);
357 if (isset($array_counts[$node_name]) && $array_counts[$node_name] > 0) {
358 return false;
359 }
360 }
361
362 return true;
363 }
364
365 return false;
366 }
367
368 public function wrapWithRecordTag($record)
369 {
370 $namespace_attrs = '';
371 $unique_matches = array();
372
373 // clear sub namespaces
374 $record = preg_replace('/xmlns[^=]*="[^"]*"/i', '', $record);
375
376 $temp = $record;
377
378 // $new_regex = '/<([^:!\/]+):[^ ]+[^>]*>/';
379 // $new_regex = '/<([^!\/>]+):[^>]+>/'; // breaking due to attr="http://", would match the :
380 $new_regex = '/<([^!\/">]+):[^>]+>/';
381
382 while (preg_match($new_regex, $temp, $matches, PREG_OFFSET_CAPTURE) !== 0) {
383 if (isset($matches[0]) && isset($matches[0][0]) && isset($matches[1]) && isset($matches[1][0])) {
384 list($match, $offset) = $matches[0];
385 $namespace = trim($matches[1][0]);
386
387 // TODO: update regex to not need this
388 $namespace_parts = explode(' ', $namespace);
389 $namespace = $namespace_parts[count($namespace_parts) - 1];
390
391 if (!in_array($namespace, $unique_matches)) {
392 $unique_matches[] = $namespace;
393 $namespace_attrs .= ' xmlns:' . $namespace . '="false"';
394 }
395
396 $temp = substr($temp, $offset + strlen($match));
397 }
398 }
399
400 $open_tag = '<record' . $namespace_attrs . '>';
401 $close_tag = '</record>';
402 return $open_tag . $record . $close_tag;
403 }
404
405 private function record_open_nodes()
406 {
407 $key = '/' . implode('/', $this->open_nodes);
408 if (!in_array($key, $this->node_list, true)) {
409 array_push($this->node_list, $key);
410 }
411 }
412
413 public function get_node_list()
414 {
415
416 $this->node_list = $this->config->get('node_list');
417 if (!is_array($this->node_list)) {
418 $this->node_list = array();
419 }
420
421 $loaded = $this->loadIndex();
422 if (empty($this->node_list) || !$loaded) {
423 $this->generateIndex();
424 $this->storeIndexes();
425
426 $this->config->set('node_list', $this->node_list);
427 }
428
429 return $this->node_list;
430 }
431
432 protected final function getFileIndexKey()
433 {
434 $key = sprintf('file_index-%s', $this->record_patch_cache_key);
435
436 return $key;
437 }
438 }
439