PluginProbe ʕ •ᴥ•ʔ
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets / 2.13
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets v2.13
3.9.5 3.9.6 4.0.0 4.0.1 4.1.0 trunk 2.12 2.13 2.14 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.7.0 3.7.1 3.7.2 3.7.3 3.7.3-beta-1.0 3.7.4 3.7.4-beta-1.0 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4
wp-all-import / controllers / admin / import.php
wp-all-import / controllers / admin Last commit date
help.php 13 years ago home.php 13 years ago import.php 13 years ago manage.php 13 years ago settings.php 13 years ago
import.php
1306 lines
1 <?php
2 /**
3 * Import configuration wizard
4 *
5 * @author Pavel Kulbakin <p.kulbakin@gmail.com>
6 */
7
8 class PMXI_Admin_Import extends PMXI_Controller_Admin {
9 protected $isWizard = true; // indicates whether controller is in wizard mode (otherwize it called to be deligated an edit action)
10 protected $isTemplateEdit = false; // indicates whether controlled is deligated by manage imports controller
11 protected $csv_mimes = array('text/comma-separated-values','text/csv','application/csv','application/excel','application/vnd.ms-excel','application/vnd.msexcel','text/anytext');
12
13 protected function init() {
14 parent::init();
15
16 // enable sessions
17 if ( ! session_id()) session_start();
18
19 if ('PMXI_Admin_Manage' == PMXI_Plugin::getInstance()->getAdminCurrentScreen()->base) { // prereqisites are not checked when flow control is deligated
20 $id = $this->input->get('id');
21 $this->data['import'] = $import = new PMXI_Import_Record();
22 if ( ! $id or $import->getById($id)->isEmpty()) { // specified import is not found
23 wp_redirect(add_query_arg('page', 'pmxi-admin-manage', admin_url('admin.php'))); die();
24 }
25 $this->isWizard = false;
26
27 } else {
28 $action = PMXI_Plugin::getInstance()->getAdminCurrentScreen()->action;
29 $this->_step_ready($action);
30 $this->isInline = 'process' == $action;
31 }
32
33 XmlImportConfig::getInstance()->setCacheDirectory(sys_get_temp_dir());
34
35 // preserve id parameter as part of baseUrl
36 $id = $this->input->get('id') and $this->baseUrl = add_query_arg('id', $id, $this->baseUrl);
37 }
38
39 public function set($var, $val)
40 {
41 $this->{$var} = $val;
42 }
43 public function get($var)
44 {
45 return $this->{$var};
46 }
47
48 /**
49 * Checks whether corresponding step of wizard is complete
50 * @param string $action
51 */
52 protected function _step_ready($action) {
53 // step #1: xml selction - has no prerequisites
54 if ('index' == $action) return true;
55
56 // step #2: element selection
57 $this->data['dom'] = $dom = new DOMDocument();
58 $this->data['update_previous'] = $update_previous = new PMXI_Import_Record();
59 $old = libxml_use_internal_errors(true);
60 if (empty($_SESSION['pmxi_import'])
61 or ! $dom->loadXML(preg_replace('%xmlns\s*=\s*([\'"]).*\1%sU', '', $_SESSION['pmxi_import']['xml'])) // FIX: libxml xpath doesn't handle default namespace properly, so remove it upon XML load
62 or empty($_SESSION['pmxi_import']['source'])
63 or ! empty($_SESSION['pmxi_import']['update_previous']) and $update_previous->getById($_SESSION['pmxi_import']['update_previous'])->isEmpty()
64 ) {
65 wp_redirect_or_javascript($this->baseUrl); die();
66 }
67 libxml_use_internal_errors($old);
68 if ('element' == $action) return true;
69 if ('evaluate' == $action) return true;
70
71 // step #3: template
72 $xpath = new DOMXPath($dom);
73 if (empty($_SESSION['pmxi_import']['xpath']) or ! ($this->data['elements'] = $elements = $xpath->query($_SESSION['pmxi_import']['xpath'])) or ! $elements->length) {
74 wp_redirect_or_javascript(add_query_arg('action', 'element', $this->baseUrl)); die();
75 }
76 if ('template' == $action or 'preview' == $action or 'tag' == $action) return true;
77
78 // step #4: options
79 if (empty($_SESSION['pmxi_import']['template']) or empty($_SESSION['pmxi_import']['template']['title']) or empty($_SESSION['pmxi_import']['template']['title'])) {
80 wp_redirect_or_javascript(add_query_arg('action', 'template', $this->baseUrl)); die();
81 }
82 if ('options' == $action) return true;
83
84 if (empty($_SESSION['pmxi_import']['options'])) {
85 wp_redirect(add_query_arg('action', 'options', $this->baseUrl)); die();
86 }
87 }
88
89 /**
90 * Step #1: Choose File
91 */
92 public function index() {
93
94 $import = new PMXI_Import_Record();
95 $this->data['id'] = $id = $this->input->get('id');
96 if ($id and $import->getById($id)->isEmpty()) { // update requested but corresponding import is not found
97 wp_redirect(remove_query_arg('id', $this->baseUrl)); die();
98 }
99
100 $this->data['post'] = $post = $this->input->post(array(
101 'type' => 'upload',
102 'url' => 'http://',
103 'ftp' => array('url' => 'ftp://'),
104 'file' => '',
105 'reimport' => '',
106 'is_update_previous' => $id ? 1 : 0,
107 'update_previous' => $id,
108 ));
109
110 $this->data['imports'] = $imports = new PMXI_Import_List();
111 $imports->setColumns('id', 'name', 'registered_on', 'path')->getBy(NULL, 'name ASC, registered_on DESC');
112
113 $this->data['history'] = $history = new PMXI_File_List();
114 $history->setColumns('id', 'name', 'registered_on', 'path')->getBy(NULL, 'id DESC');
115
116 if ($this->input->post('is_submitted_continue')) {
117 if ( ! empty($_SESSION['pmxi_import']['xml'])) {
118 wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
119 }
120 } elseif ('upload' == $this->input->post('type')) {
121 if (empty($_FILES['upload']) or empty($_FILES['upload']['name'])) {
122 $this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
123 } elseif (empty($_FILES['upload']['size'])) {
124 $this->errors->add('form-validation', __('Uploaded file is empty', 'pmxi_plugin'));
125 } elseif ( ! preg_match('%\W(xml|gzip|csv)$%i', trim($_FILES['upload']['name'])) and !$this->detect_csv($_FILES['upload']['type'])) {
126 $this->errors->add('form-validation', __('Uploaded file must be XML, CSV or GZIP', 'pmxi_plugin'));
127 } elseif ( preg_match('%\W(csv)$%i', trim($_FILES['upload']['name'])) or $this->detect_csv($_FILES['upload']['type'])) {
128 $uploads = wp_upload_dir();
129 if($uploads['error']){
130 $this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
131 }
132 // copy file in temporary folder
133 $fdata = file_get_contents($_FILES['upload']['tmp_name']);
134 $fdata = utf8_encode($fdata);
135 file_put_contents($uploads['path'] . '/' . trim(basename($_FILES['upload']['name'])), $fdata);
136 chmod($uploads['path'] . '/'. trim(basename($_FILES['upload']['name'])), "0777");
137 // end file convertion
138 $xml = $this->csv_to_xml($uploads['path'] . '/' . trim(basename($_FILES['upload']['name'])));
139 if( is_array($xml) && isset($xml['error'])){
140 $this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
141 }
142 else {
143 // delete file in temporary folder
144 unlink( $uploads['path'] .'/'. trim(basename($_FILES['upload']['name']) ));
145 $filename = $_FILES['upload']['tmp_name'];
146
147 // Let's make sure the file exists and is writable first.
148 if (is_writable($filename)) {
149
150 if (!$handle = fopen($filename, 'w')) {
151 $this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
152 }
153
154 // Write $somecontent to our opened file.
155 if (fwrite($handle, $xml) === FALSE) {
156 $this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
157 }
158
159 fclose($handle);
160
161 } else {
162 $this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
163 }
164 $filePath = $_FILES['upload']['tmp_name'];
165 $source = array(
166 'name' => $_FILES['upload']['name'],
167 'type' => 'upload',
168 'path' => '',
169 );
170 }
171 } else {
172 $filePath = $_FILES['upload']['tmp_name'];
173 $source = array(
174 'name' => $_FILES['upload']['name'],
175 'type' => 'upload',
176 'path' => '',
177 );
178 }
179 } elseif ('url' == $this->input->post('type')) {
180 if (empty($post['url'])) {
181 $this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
182 } elseif ( ! preg_match('%^https?://%i', $post['url'])) {
183 $this->errors->add('form-validation', __('Specified URL has wrong format'), 'pmxi_plugin');
184 } elseif ( preg_match('%\W(csv)$%i', trim($post['url'])) or strpos(file_get_contents($post['url']), '<?xml') === false) {
185 $uploads = wp_upload_dir();
186 $fdata = file_get_contents($post['url']);
187 $fdata = utf8_encode($fdata);
188 $tmpname = md5(time()).'.csv';
189 file_put_contents($uploads['path'] .'/'. $tmpname, $fdata);
190 $xml = $this->csv_to_xml($uploads['path'] .'/'. $tmpname);
191 if( is_array($xml) && isset($xml['error'])){
192 $this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
193 }
194 else {
195 $filename = tempnam(XmlImportConfig::getInstance()->getCacheDirectory(), 'xim');
196
197 // Let's make sure the file exists and is writable first.
198 if (is_writable($filename)) {
199
200 if (!$handle = fopen($filename, 'w')) {
201 $this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
202 }
203
204 // Write $somecontent to our opened file.
205 if (fwrite($handle, $xml) === FALSE) {
206 $this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
207 }
208
209 fclose($handle);
210
211 } else {
212 $this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
213 }
214 $filePath = $filename;
215 $source = array(
216 'name' => basename(parse_url($post['url'], PHP_URL_PATH)),
217 'type' => 'url',
218 'path' => $post['url'],
219 );
220 }
221 }else {
222 $filePath = $post['url'];
223 $source = array(
224 'name' => basename(parse_url($filePath, PHP_URL_PATH)),
225 'type' => 'url',
226 'path' => $filePath,
227 );
228
229 }
230 } elseif ('ftp' == $this->input->post('type')) {
231 if (empty($post['ftp']['url'])) {
232 $this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
233 } elseif ( ! preg_match('%^ftps?://%i', $post['ftp']['url'])) {
234 $this->errors->add('form-validation', __('Specified FTP resource has wrong format'), 'pmxi_plugin');
235 } elseif ( preg_match('%\W(csv)$%i', trim($post['ftp']['url']))) {
236 // path to remote file
237 $remote_file = $post['ftp']['url'];
238 $local_file = tempnam(XmlImportConfig::getInstance()->getCacheDirectory(), 'xim');
239
240 // open some file to write to
241 $handle = fopen($local_file, 'w');
242
243 // set up basic connection
244 $ftp_url = $post['ftp']['url'];
245 $parsed_url = parse_url($ftp_url);
246 $ftp_server = $parsed_url['host'] ;
247 $conn_id = ftp_connect( $ftp_server );
248 $is_ftp_ok = TRUE;
249
250 // login with username and password
251 $ftp_user_name = $post['ftp']['user'];
252 $ftp_user_pass = $post['ftp']['pass'];
253
254 // hide warning message
255 echo '<span style="display:none">';
256 if ( !ftp_login($conn_id, $ftp_user_name, $ftp_user_pass) ){
257 $this->errors->add('form-validation', __('Login authentication failed', 'pmxi_plugin'));
258 $is_ftp_ok = false;
259 }
260 echo '</span>';
261
262
263 if ( $is_ftp_ok ){
264 // try to download $remote_file and save it to $handle
265 if (!ftp_fget($conn_id, $handle, $parsed_url['path'], FTP_ASCII, 0)) {
266 $this->errors->add('form-validation', __('There was a problem while downloading' . $remote_file . 'to' . $local_file, 'pmxi_plugin'));
267 }
268
269 // close the connection and the file handler
270 ftp_close($conn_id);
271 fclose($handle);
272
273 // copy file in temporary folder
274 $uploads = wp_upload_dir();
275 if($uploads['error']){
276 $this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
277 }
278 copy( $local_file, $uploads['path'] . basename($local_file));
279 $url = $uploads['url'] . basename($local_file);
280 // convert file to utf8
281 chmod($uploads['path'] . basename($local_file), '0755');
282 $fdata = file_get_contents($url);
283 $fdata = utf8_encode($fdata);
284 file_put_contents($uploads['path'] . basename($local_file), $fdata);
285 // end file convertion
286 $xml = $this->csv_to_xml($uploads['path'] . basename($local_file));
287 if( is_array($xml) && isset($xml['error'])){
288 $this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
289 }
290 else {
291 unlink( $uploads['path'] . basename($local_file) );
292 $filename = $local_file;
293
294 // Let's make sure the file exists and is writable first.
295 if (is_writable($filename)) {
296
297 if (!$handle = fopen($filename, 'w')) {
298 $this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
299 }
300
301 // Write $somecontent to our opened file.
302 if (fwrite($handle, $xml) === FALSE) {
303 $this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
304 }
305
306 fclose($handle);
307
308 } else {
309 $this->errors->add('form-validation', __('The file' . $filename . 'is not writable', 'pmxi_plugin'));
310 }
311 $filePath = $local_file;
312 $source = array(
313 'name' => basename($local_file),
314 'type' => 'ftp',
315 'path' => $filePath,
316 );
317 }
318 }
319 } else {
320 $filePath = $post['ftp']['url'];
321 if (isset($post['ftp']['user']) and $post['ftp']['user'] !== '') {
322 $filePath = preg_replace('%://([^@/]*@)?%', '://' . urlencode($post['ftp']['user']) . ':' . urlencode($post['ftp']['pass']) . '@', $filePath, 1);
323 }
324 $source = array(
325 'name' => basename(parse_url($filePath, PHP_URL_PATH)),
326 'type' => 'ftp',
327 'path' => $filePath,
328 );
329 }
330 } elseif ('file' == $this->input->post('type')) {
331 if (empty($post['file'])) {
332 $this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
333 } elseif (preg_match('%\W(csv)$%i', trim($post['file']))) {
334 $uploads = PMXI_Plugin::ROOT_DIR . '/upload/';
335 $wp_uploads = wp_upload_dir();
336 if($wp_uploads['error']){
337 $this->errors->add('form-validation', __('Can not create upload folder. Permision denied', 'pmxi_plugin'));
338 }
339 // copy file in temporary folder
340 // hide warning message
341 echo '<span style="display:none">';
342 copy( $uploads . $post['file'], $wp_uploads['path'] . basename($post['file']));
343 echo '</span>';
344 $url = $wp_uploads['url'] . basename($post['file']);
345 // convert file to utf8
346 chmod($wp_uploads['path'] . basename($post['file']), '0755');
347 $fdata = file_get_contents($url);
348 $fdata = utf8_encode($fdata);
349 file_put_contents($wp_uploads['path'] . basename($post['file']), $fdata);
350 // end file convertion
351 $xml = $this->csv_to_xml($wp_uploads['path'] . basename($post['file']));
352 if( is_array($xml) && isset($xml['error'])){
353 $this->errors->add('form-validation', __($xml['error'], 'pmxi_plugin'));
354 }
355 else {
356 $filename = $wp_uploads['path'] . basename($post['file']);
357
358 // Let's make sure the file exists and is writable first.
359 if (is_writable($filename)) {
360
361 if (!$handle = fopen($filename, 'w')) {
362 $this->errors->add('form-validation', __('Cannot open file ' . $filename, 'pmxi_plugin'));
363 }
364
365 // Write $somecontent to our opened file.
366 if (fwrite($handle, $xml) === FALSE) {
367 $this->errors->add('form-validation', __('Cannot write to file ' . $filename, 'pmxi_plugin'));
368 }
369
370 fclose($handle);
371
372 } else {
373 $this->errors->add('form-validation', __('The file ' . $filename . ' is not writable or you use wildcard for CSV file', 'pmxi_plugin'));
374 }
375 $filePath = $wp_uploads['path'] . basename($post['file']);
376 $source = array(
377 'name' => basename(parse_url($filePath, PHP_URL_PATH)),
378 'type' => 'file',
379 'path' => $filePath,
380 );
381 }
382 }
383 else {
384 $filePath = PMXI_Plugin::ROOT_DIR . '/upload/' . $post['file'];
385 $source = array(
386 'name' => basename(parse_url($filePath, PHP_URL_PATH)),
387 'type' => 'file',
388 'path' => $filePath,
389 );
390 }
391 } elseif ('reimport' == $this->input->post('type')) {
392 if (empty($post['reimport'])) {
393 $this->errors->add('form-validation', __('XML/CSV file must be specified', 'pmxi_plugin'));
394 }
395 }
396
397 if ($post['is_update_previous'] and empty($post['update_previous'])) {
398 $this->errors->add('form-validation', __('Previous import for update must be selected or uncheck `Update Previous Import` option to proceed with a new one', 'pmxi_plugin'));
399 }
400
401 if ($this->input->post('is_submitted') and ! $this->errors->get_error_codes()) {
402
403 check_admin_referer('choose-file', '_wpnonce_choose-file');
404
405 if ('reimport' == $this->input->post('type')) { // get file content from database
406 preg_match('%^#(\d+):%', $post['reimport'], $mtch) and $reimport_id = $mtch[1] or $reimport_id = 0;
407 $file = new PMXI_File_Record();
408 if ( ! $reimport_id or $file->getById($reimport_id)->isEmpty()) {
409 $xml = FALSE;
410 } else {
411 $xml = $file->contents;
412 $source = array(
413 'name' => $file->name,
414 'type' => 'reimport',
415 'path' => $file->path,
416 );
417 }
418 } else {
419 if (in_array($this->input->post('type'), array('ftp', 'file'))) { // file may be specified by pattern
420 $file_path_array = @PMXI_Helper::safe_glob($filePath, PMXI_Helper::GLOB_NODIR | PMXI_Helper::GLOB_PATH);
421 if ($file_path_array) {
422 $filePath = array_shift($file_path_array); // take only 1st matching one
423 } else {
424 $filePath = FALSE;
425 }
426 }
427
428 ob_start();
429 $filePath && @readgzfile($filePath);
430
431 $xml = ob_get_clean();
432
433 }
434
435 if (PMXI_Import_Record::validateXml($xml, $this->errors)) {
436 // xml is valid
437
438 $_SESSION['pmxi_import'] = array(
439 'xml' => $xml,
440 'source' => $source,
441 );
442
443 $update_previous = new PMXI_Import_Record();
444 if ($post['is_update_previous'] and ! $update_previous->getById($post['update_previous'])->isEmpty()) {
445 $_SESSION['pmxi_import'] += array(
446 'update_previous' => $update_previous->id,
447 'xpath' => $update_previous->xpath,
448 'template' => $update_previous->template,
449 'options' => $update_previous->options,
450 );
451 } else {
452 $_SESSION['pmxi_import']['update_previous'] = '';
453 }
454
455 wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
456 }
457 }
458
459 $this->render();
460 }
461
462 /**
463 * Step #2: Choose elements
464 */
465 public function element()
466 {
467
468
469 $xpath = new DOMXPath($this->data['dom']);
470 $post = $this->input->post(array('xpath' => ''));
471 $this->data['post'] =& $post;
472
473 if ($this->input->post('is_submitted')) {
474 check_admin_referer('choose-elements', '_wpnonce_choose-elements');
475 if ('' == $post['xpath']) {
476 $this->errors->add('form-validation', __('No elements selected', 'pmxi_plugin'));
477 } else {
478 $node_list = @ $xpath->query($post['xpath']); // make sure only element selection is allowed; prevent parsing warning to be displayed
479
480 if (FALSE === $node_list) {
481 $this->errors->add('form-validation', __('Invalid XPath expression', 'pmxi_plugin'));
482 } elseif ( ! $node_list->length) {
483 $this->errors->add('form-validation', __('No matching elements found for XPath expression specified', 'pmxi_plugin'));
484 } else {
485 foreach ($node_list as $el) {
486 if ( ! $el instanceof DOMElement) {
487 $this->errors->add('form-validation', __('XPath must match only elements', 'pmxi_plugin'));
488 break;
489 };
490 }
491 }
492 }
493 if ( ! $this->errors->get_error_codes()) {
494 $_SESSION['pmxi_import']['xpath'] = $post['xpath'];
495 wp_redirect(add_query_arg('action', 'template', $this->baseUrl)); die();
496 }
497 } else {
498
499 $this->shrink_xml_element($this->data['dom']->documentElement);
500
501 if (isset($_SESSION['pmxi_import']['xpath'])) {
502 $post['xpath'] = $_SESSION['pmxi_import']['xpath'];
503 if ( ! $xpath->query($post['xpath'])->length and ! empty($_SESSION['pmxi_import']['update_previous'])) {
504 $_GET['pmxi_nt'] = __('<b>Warning</b>: No matching elements found for XPath expression from the import being updated. It probably means that new XML file has different format. Though you can update XPath, procceed only if you sure about update operation being valid.', 'pmxi_plugin');
505 }
506 } else {
507 // suggest 1st repeating element as default selection
508 $post['xpath'] = $this->xml_find_repeating($this->data['dom']->documentElement);
509 }
510 }
511
512 // workaround to prevent rendered XML representation to eat memory since it has to be stored in momory when output is bufferred
513 $this->render();
514 add_action('pmxi_action_after', array($this, 'element_after'));
515 }
516 public function element_after()
517 {
518 $this->render();
519 }
520
521 /**
522 * Helper to evaluate xpath and return matching elements as direct paths for javascript side to highlight them
523 */
524 public function evaluate()
525 {
526 if ( ! PMXI_Plugin::getInstance()->getAdminCurrentScreen()->is_ajax) { // call is only valid when send with ajax
527 wp_redirect(add_query_arg('action', 'element', $this->baseUrl)); die();
528 }
529
530 $xpath = new DOMXPath($this->data['dom']);
531 $post = $this->input->post(array('xpath' => ''));
532 if ('' == $post['xpath']) {
533 $this->errors->add('form-validation', __('No elements selected', 'pmxi_plugin'));
534 } else {
535 $node_list = @ $xpath->query($post['xpath']); // prevent parsing warning to be displayed
536 $this->data['node_list_count'] = $node_list->length;
537 if (FALSE === $node_list) {
538 $this->errors->add('form-validation', __('Invalid XPath expression', 'pmxi_plugin'));
539 } elseif ( ! $node_list->length) {
540 $this->errors->add('form-validation', __('No matching elements found for XPath expression specified', 'pmxi_plugin'));
541 } else {
542 foreach ($node_list as $el) {
543 if ( ! $el instanceof DOMElement) {
544 $this->errors->add('form-validation', __('XPath must match only elements', 'pmxi_plugin'));
545 break;
546 };
547 }
548 }
549 }
550 if ( ! $this->errors->get_error_codes()) {
551 $this->shrink_xml_element($this->data['dom']->documentElement);
552 $xpath = new DOMXPath($this->data['dom']);
553 $this->data['node_list'] = $node_list = @ $xpath->query($post['xpath']); // prevent parsing warning to be displayed
554
555 $paths = array(); $this->data['paths'] =& $paths;
556 if (PMXI_Plugin::getInstance()->getOption('highlight_limit') and $node_list->length <= PMXI_Plugin::getInstance()->getOption('highlight_limit')) {
557 foreach ($node_list as $el) {
558 if ( ! $el instanceof DOMElement) continue;
559
560 $p = $this->get_xml_path($el, $xpath) and $paths[] = $p;
561 }
562 }
563 $this->render();
564 } else {
565 $this->error();
566 }
567 }
568
569 /**
570 * Step #3: Choose template
571 */
572 public function template()
573 {
574
575 $template = new PMXI_Template_Record();
576 $default = array(
577 'title' => '',
578 'content' => '',
579 'name' => '',
580 'is_keep_linebreaks' => 0,
581 );
582 if ($this->isWizard) {
583 $this->data['post'] = $post = $this->input->post(
584 (isset($_SESSION['pmxi_import']['template']) ? $_SESSION['pmxi_import']['template'] : array())
585 + $default
586 );
587 } else {
588 $this->data['post'] = $post = $this->input->post(
589 $this->data['import']->template
590 + $default
591 );
592 }
593
594 if (($load_template = $this->input->post('load_template'))) { // init form with template selected
595 if ( ! $template->getById($load_template)->isEmpty()) {
596 $this->data['post'] = array(
597 'title' => $template->title,
598 'content' => $template->content,
599 'is_keep_linebreaks' => $template->is_keep_linebreaks,
600 'name' => '', // template is always empty
601 );
602 $_SESSION['pmxi_import']['is_loaded_template'] = $load_template;
603 }
604
605 } elseif ($this->input->post('is_submitted')) { // save template submission
606 check_admin_referer('template', '_wpnonce_template');
607
608 if (empty($post['title'])) {
609 $this->errors->add('form-validation', __('Post title is empty', 'pmxi_plugin'));
610 } else {
611 $this->_validate_template($post['title'], 'Post title');
612 }
613 if (empty($post['content'])) {
614 $this->errors->add('form-validation', __('Post content is empty', 'pmxi_plugin'));
615 } else {
616 $this->_validate_template($post['content'], 'Post content');
617 }
618
619
620 if ( ! $this->errors->get_error_codes()) {
621 if ( ! empty($post['name'])) { // save template in database
622 $template->getByName($post['name'])->set($post)->save();
623 $_SESSION['pmxi_import']['saved_template'] = $template->id;
624 }
625 if ($this->isWizard) {
626 $_SESSION['pmxi_import']['template'] = $post;
627 wp_redirect(add_query_arg('action', 'options', $this->baseUrl)); die();
628 } else {
629 $this->data['import']->set('template', $post)->save();
630 wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode(__('Template updated', 'pmxi_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php'))); die();
631 }
632 }
633 }
634
635 if (user_can_richedit()) {
636 wp_enqueue_script('editor');
637 }
638 wp_enqueue_script('word-count');
639 add_thickbox();
640 wp_enqueue_script('media-upload');
641 add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
642 wp_enqueue_script('quicktags');
643 $this->render();
644 }
645 protected function _validate_template($text, $field_title)
646 {
647 try {
648 $scanner = new XmlImportTemplateScanner();
649 $tokens = $scanner->scan(new XmlImportStringReader($text));
650 $parser = new XmlImportTemplateParser($tokens);
651 $tree = $parser->parse();
652 } catch (XmlImportException $e) {
653 $this->errors->add('form-validation', sprintf(__('%s template is invalid: %s', 'pmxi_plugin'), $field_title, $e->getMessage()));
654 }
655 }
656
657 /**
658 * Preview selected xml tag (called with ajax from `template` step)
659 */
660 public function tag()
661 {
662 if (empty($this->data['elements']))
663 {
664
665 $update_previous = new PMXI_Import_Record();
666 if ($update_previous->getById($this->input->get('id'))) {
667 $_SESSION['pmxi_import'] = array(
668 'update_previous' => $update_previous->id,
669 'xpath' => $update_previous->xpath,
670 'template' => $update_previous->template,
671 'options' => $update_previous->options,
672 );
673 $history_file = new PMXI_File_Record();
674 $history_file->getBy('import_id', $update_previous->id);
675 $history_file->__get('contents');
676 $_SESSION['pmxi_import']['xml'] = $history_file->contents;
677 } else {
678 $_SESSION['pmxi_import']['update_previous'] = '';
679 }
680 if (!empty($_SESSION['pmxi_import']['xml']))
681 {
682 $dom = new DOMDocument();
683 $dom->loadXML(preg_replace('%xmlns\s*=\s*([\'"]).*\1%sU', '', $_SESSION['pmxi_import']['xml']));
684 $xpath = new DOMXPath($dom);
685
686 $this->data['elements'] = $elements = $xpath->query($_SESSION['pmxi_import']['xpath']);
687 }
688 }
689
690 $this->data['tagno'] = min(max(intval($this->input->getpost('tagno', 1)), 1), $this->data['elements']->length);
691 $this->render();
692 }
693
694 /**
695 * Preview future post based on current template and tag (called with ajax from `template` step)
696 */
697 public function preview()
698 {
699 $post = $this->input->post(array(
700 'title' => '',
701 'content' => '',
702 'is_keep_linebreaks' => 0,
703 ));
704 $tagno = min(max(intval($this->input->getpost('tagno', 1)), 1), $this->data['elements']->length);
705 $xpath = "(" . $_SESSION['pmxi_import']['xpath'] . ")[$tagno]";
706 // validate
707 try {
708
709 if (empty($post['title'])) {
710 $this->errors->add('form-validation', __('Post title is empty', 'pmxi_plugin'));
711 } else {
712 list($this->data['title']) = XmlImportParser::factory($_SESSION['pmxi_import']['xml'], $xpath, $post['title'], $file)->parse(); unlink($file);
713 if ( ! isset($this->data['title']) or '' == strval(trim(strip_tags($this->data['title'], '<img><input><textarea><iframe><object><embed>')))) {
714 $this->errors->add('xml-parsing', __('<strong>Warning</strong>: resulting post title is empty', 'pmxi_plugin'));
715 }
716 }
717 } catch (XmlImportException $e) {
718 $this->errors->add('form-validation', sprintf(__('Error parsing title: %s', 'pmxi_plugin'), $e->getMessage()));
719 }
720 try {
721 if (empty($post['content'])) {
722 $this->errors->add('form-validation', __('Post content is empty', 'pmxi_plugin'));
723 } else {
724 list($this->data['content']) = XmlImportParser::factory($post['is_keep_linebreaks'] ? $_SESSION['pmxi_import']['xml'] : preg_replace('%\r\n?|\n%', ' ', $_SESSION['pmxi_import']['xml']), $xpath, $post['content'], $file)->parse(); unlink($file);
725 if ( ! isset($this->data['content']) or '' == strval(trim(strip_tags($this->data['content'], '<img><input><textarea><iframe><object><embed>')))) {
726 $this->errors->add('xml-parsing', __('<strong>Warning</strong>: resulting post content is empty', 'pmxi_plugin'));
727 }
728 }
729 } catch (XmlImportException $e) {
730 $this->errors->add('form-validation', sprintf(__('Error parsing content: %s', 'pmxi_plugin'), $e->getMessage()));
731 }
732
733 $this->render();
734 }
735
736 /**
737 * Step #4: Options
738 */
739 public function options()
740 {
741 include_once(PMXI_Plugin::ROOT_DIR.'/libraries/XmlImportCsvParse.php');
742
743 $default = PMXI_Plugin::get_default_import_options();
744
745 if ($this->isWizard) {
746 $this->data['source_type'] = $_SESSION['pmxi_import']['source']['type'];
747 $default['unique_key'] = $_SESSION['pmxi_import']['template']['title'];
748 $post = $this->input->post(
749 (isset($_SESSION['pmxi_import']['options']) ? $_SESSION['pmxi_import']['options'] : array())
750 + $default
751 );
752
753 $scheduled = $this->input->post(array(
754 'is_scheduled' => ! empty($post['scheduled']),
755 'scheduled_period' => ! empty($post['scheduled']) ? $post['scheduled'] : '0 0 * * *', // daily by default
756 ));
757
758 } else {
759 $this->data['source_type'] = $this->data['import']->type;
760 $post = $this->input->post(
761 $this->data['import']->options
762 + $default
763 );
764 $scheduled = $this->input->post(array(
765 'is_scheduled' => ! empty($this->data['import']->scheduled),
766 'scheduled_period' => ! empty($this->data['import']->scheduled) ? $this->data['import']->scheduled : '0 0 * * *', // daily by default
767 ));
768 }
769 $this->data['post'] =& $post;
770 $this->data['scheduled'] =& $scheduled;
771 $this->data['is_loaded_template'] = $_SESSION['pmxi_import']['is_loaded_template'];
772
773 if (($load_options = $this->input->post('load_options'))) { // init form with template selected
774 $this->data['load_options'] = true;
775 $template = new PMXI_Template_Record();
776 if ( ! $template->getById($this->data['is_loaded_template'])->isEmpty()) {
777 $post = $template->options + $default;
778 $scheduled = array(
779 'is_scheduled' => ! empty($template->scheduled),
780 'scheduled_period' => ! empty($template->scheduled) ? $template->scheduled : '0 0 * * *', // daily by default
781 );
782 }
783
784 } elseif (($reset_options = $this->input->post('reset_options'))){
785 $post = $default;
786 $scheduled = $this->input->post(array(
787 'is_scheduled' => ! empty($post['scheduled']),
788 'scheduled_period' => ! empty($post['scheduled']) ? $post['scheduled'] : '0 0 * * *', // daily by default
789 ));
790 } elseif ($this->input->post('is_submitted')) {
791 check_admin_referer('options', '_wpnonce_options');
792 // remove entires where both custom_name and custom_value are empty
793 $not_empty = array_flip(array_values(array_merge(array_keys(array_filter($post['custom_name'])), array_keys(array_filter($post['custom_value'])))));
794 $post['custom_name'] = array_intersect_key($post['custom_name'], $not_empty);
795 $post['custom_value'] = array_intersect_key($post['custom_value'], $not_empty);
796 // validate
797 if (array_keys(array_filter($post['custom_name'])) != array_keys(array_filter($post['custom_value']))) {
798 $this->errors->add('form-validation', __('Both name and value must be set for all custom parameters', 'pmxi_plugin'));
799 } else {
800 foreach ($post['custom_name'] as $custom_name) {
801 $this->_validate_template($custom_name, __('Custom Field Name', 'pmxi_plugin'));
802 }
803 foreach ($post['custom_value'] as $custom_value) {
804 $this->_validate_template($custom_value, __('Custom Field Value', 'pmxi_plugin'));
805 }
806 }
807 if ('page' == $post['type'] and ! preg_match('%^(-?\d+)?$%', $post['order'])) {
808 $this->errors->add('form-validation', __('Order must be an integer number', 'pmxi_plugin'));
809 }
810 if ('post' == $post['type']) {
811 '' == $post['categories'] or $this->_validate_template($post['categories'], __('Categories', 'pmxi_plugin'));
812 '' == $post['tags'] or $this->_validate_template($post['tags'], __('Tags', 'pmxi_plugin'));
813 }
814 if ('specific' == $post['date_type']) {
815 '' == $post['date'] or $this->_validate_template($post['date'], __('Date', 'pmxi_plugin'));
816 } else {
817 '' == $post['date_start'] or $this->_validate_template($post['date_start'], __('Start Date', 'pmxi_plugin'));
818 '' == $post['date_end'] or $this->_validate_template($post['date_end'], __('Start Date', 'pmxi_plugin'));
819 }
820 if ('' == $post['categories_delim']) {
821 $this->errors->add('form-validation', __('Category list delimiter cannot be empty', 'pmxi_plugin'));
822 }
823 if ('' == $post['tags_delim']) {
824 $this->errors->add('form-validation', __('Tag list delimiter must cannot be empty', 'pmxi_plugin'));
825 }
826 if ($post['is_import_specified']) {
827 if (empty($post['import_specified'])) {
828 $this->errors->add('form-validation', __('Records to import must be specified or uncheck `Import only specified records` option to process all records', 'pmxi_plugin'));
829 } else {
830 $chanks = preg_split('% *, *%', $post['import_specified']);
831 foreach ($chanks as $chank) {
832 if ( ! preg_match('%^([1-9]\d*)( *- *([1-9]\d*))?$%', $chank, $mtch)) {
833 $this->errors->add('form-validation', __('Wrong format of `Import only specified records` value', 'pmxi_plugin'));
834 break;
835 }
836 }
837 }
838 }
839 if ('' == $post['unique_key']) {
840 $this->errors->add('form-validation', __('Expression for `Post Unique Key` must be set, use the same expression as specified for post title if you are not sure what to put there', 'pmxi_plugin'));
841 } else {
842 $this->_validate_template($post['unique_key'], __('Post Unique Key', 'pmxi_plugin'));
843 }
844
845 if ( ! $this->errors->get_error_codes()) { // no validation errors found
846 // assign some defaults
847 '' !== $post['date'] or $post['date'] = 'now';
848 '' !== $post['date_start'] or $post['date_start'] = 'now';
849 '' !== $post['date_end'] or $post['date_end'] = 'now';
850
851 if ($this->isWizard) {
852 $_SESSION['pmxi_import']['options'] = $post;
853 $_SESSION['pmxi_import']['scheduled'] = $scheduled['is_scheduled'] ? $scheduled['scheduled_period'] : '';
854
855 // Update template options
856 if (!empty($_SESSION['pmxi_import']['saved_template'])) {
857 $template = new PMXI_Template_Record();
858 $template->getById($_SESSION['pmxi_import']['saved_template'])->set(array(
859 'options' => $_SESSION['pmxi_import']['options'],
860 'scheduled' => $_SESSION['pmxi_import']['scheduled']))->update();
861 }
862 elseif (!empty($_SESSION['pmxi_import']['is_loaded_template']))
863 {
864 $template = new PMXI_Template_Record();
865 $template->getById($_SESSION['pmxi_import']['is_loaded_template'])->set(array(
866 'options' => $_SESSION['pmxi_import']['options'],
867 'scheduled' => $_SESSION['pmxi_import']['scheduled']))->update();
868 }
869
870 if ( ! $this->input->post('save_only')) {
871 $this->process();die();
872 } else {
873 $import = $this->data['update_previous'];
874 $is_update = ! $import->isEmpty();
875 $import->set(
876 $_SESSION['pmxi_import']['source']
877 + array(
878 'xpath' => $_SESSION['pmxi_import']['xpath'],
879 'template' => $_SESSION['pmxi_import']['template'],
880 'options' => $_SESSION['pmxi_import']['options'],
881 'scheduled' => $_SESSION['pmxi_import']['scheduled'],
882 )
883 )->save();
884
885 $history_file = new PMXI_File_Record();
886 $history_file->set(array(
887 'name' => $import->name,
888 'import_id' => $import->id,
889 'path' => $import->path,
890 'contents' => $_SESSION['pmxi_import']['xml'],
891 'registered_on' => date('Y-m-d H:i:s'),
892 ))->save();
893 unset($_SESSION['pmxi_import']); // clear session data
894 wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode($is_update ? __('Import updated', 'pmxi_plugin') : __('Import created', 'pmxi_plugin'))), admin_url('admin.php'))); die();
895 }
896 } else {
897 $this->data['import']->set('options', $post)->set('scheduled', $scheduled['is_scheduled'] ? $scheduled['scheduled_period'] : '')->save();
898 wp_redirect(add_query_arg(array('page' => 'pmxi-admin-manage', 'pmlc_nt' => urlencode(__('Options updated', 'pmxi_plugin'))) + array_intersect_key($_GET, array_flip($this->baseUrlParamNames)), admin_url('admin.php'))); die();
899 }
900 }
901 }
902
903 ! empty($post['custom_name']) or $post['custom_name'] = array('') and $post['custom_value'] = array('');
904
905 $this->render();
906 }
907
908 /**
909 * Import processing step (status console)
910 */
911 public function process()
912 {
913 wp_ob_end_flush_all(); flush();
914
915 // store import info in database
916 $import = $this->data['update_previous'];
917
918 if ($_SESSION['pmxi_import']['options']['is_first_chank'] or !$_SESSION['pmxi_import']['options']['is_update_previous'])
919 {
920 $_SESSION['pmxi_import']['step'] = 0;
921 $import->set(
922 $_SESSION['pmxi_import']['source']
923 + array(
924 'xpath' => $_SESSION['pmxi_import']['xpath'],
925 'template' => $_SESSION['pmxi_import']['template'],
926 'options' => $_SESSION['pmxi_import']['options'],
927 'scheduled' => $_SESSION['pmxi_import']['scheduled']
928 )
929 )->save();
930
931 $history_file = new PMXI_File_Record();
932 $history_file->set(array(
933 'name' => $import->name,
934 'import_id' => $import->id,
935 'path' => $import->path,
936 'contents' => $_SESSION['pmxi_import']['xml'],
937 'registered_on' => date('Y-m-d H:i:s'),
938 ))->save();
939
940 $_SESSION['pmxi_import']['import_start'] = date('H:i:s');
941 }
942 else
943 {
944 $_SESSION['pmxi_import']['step']++;
945 $import = new PMXI_Import_Record();
946 $import->getById($_SESSION['pmxi_import']['options']['is_update_previous']);
947 }
948
949 $logger = create_function('$m', 'echo "<div class=\\"progress-msg\\">$m</div>\\n"; flush();');
950 if (in_array($import->type, array('ftp', 'file'))) { // process files by patten
951 $import->execute($logger);
952 } else { // directly process XML
953 set_time_limit(0);
954 $xml = new SimpleXMLElement($_SESSION['pmxi_import']['xml']);
955 $rootNodes = $xml->xpath($_SESSION['pmxi_import']['xpath']);
956 $this->removeNode($xml, $_SESSION['pmxi_import']['xpath'].'[position() >= 10]');
957
958 if (count($xml->xpath($_SESSION['pmxi_import']['xpath'])))
959 {
960 ob_start();
961 $import->process($xml->asXML(), $logger, ((count($rootNodes) < 10) ? true : false));
962
963 if ((count($rootNodes) < 10))
964 {
965 // [indicate in header process is complete]
966 $msg = addcslashes(__('Complete', 'pmxi_plugin'), "'\n\r");
967 echo <<<COMPLETE
968 <script type="text/javascript">
969 //<![CDATA[
970 (function($){
971 $('#status').html('$msg');
972 window.onbeforeunload = false;
973 })(jQuery);
974 //]]>
975 </script>
976 COMPLETE;
977 // [/indicate in header process is complete]
978 }
979 $log = ob_get_clean();
980 }
981 unset($xml);
982
983 $xml_chank = new SimpleXMLElement($_SESSION['pmxi_import']['xml']);
984 $this->removeNode($xml_chank, $_SESSION['pmxi_import']['xpath'].'[position() < 10]');
985
986 $_SESSION['pmxi_import']['xml'] = (count($rootNodes) >= 10) ? $xml_chank->asXML() : NULL;
987 unset($xml_chank);
988 }
989
990 if (!empty($_SESSION['pmxi_import']['xml']))
991 exit(json_encode(array('status' => 'process','update_previous' => $import->__get('id'), 'count' => count($rootNodes), 'is_last_chank' => ((count($rootNodes) < 10) ? true : false), 'is_first_chank' => $_SESSION['pmxi_import']['options']['is_first_chank'], 'log' => $log)));
992 else {
993 $start_time = $_SESSION['pmxi_import']['import_start'];
994 $end_time = date('H:i:s');
995 unset($_SESSION['pmxi_import']); // clear session data (prevent from reimporting the same data on page refresh)
996 exit(json_encode(array('status' => 'stop', 'log' => $log, 'start_time' => $start_time, 'end_time' => $end_time, 'import_time' => date('H:i:s', strtotime($end_time) - strtotime($start_time)))));
997 };
998
999
1000 }
1001 /**
1002 * Remove xml document nodes by xpath expression
1003 */
1004 function removeNode($xml, $path)
1005 {
1006 $result = $xml->xpath($path);
1007
1008 if (empty($result)) return false;
1009 $errlevel = error_reporting(E_ALL & ~E_WARNING);
1010 foreach ($result as $r) unset ($r[0]);
1011 error_reporting($errlevel);
1012
1013 return true;
1014 }
1015
1016 /*
1017 *
1018 * Get SimpleXML object by xpath extension
1019 *
1020 */
1021 function get_chank(& $xml, $path){
1022
1023 $result = $xml->xpath($path);
1024
1025 $array = array();
1026 foreach ($result as $el) {
1027 array_push($array, $this->simplexml2array($el));
1028 }
1029
1030 if (empty($array)) return false;
1031
1032 return new SimpleXMLElement(ArrayToXML::toXml($array));
1033 }
1034
1035 /*
1036 *
1037 * Convert SimpleXML object to array
1038 *
1039 */
1040 function simplexml2array($xml) {
1041 if (@get_class($xml) == 'SimpleXMLElement') {
1042 $attributes = $xml->attributes();
1043 foreach($attributes as $k=>$v) {
1044 if ($v) $a[$k] = (string) $v;
1045 }
1046 $x = $xml;
1047 $xml = get_object_vars($xml);
1048 }
1049 if (is_array($xml)) {
1050 if (count($xml) == 0) return (string) $x; // for CDATA
1051 foreach($xml as $key=>$value) {
1052 $r[$key] = $this->simplexml2array($value);
1053 }
1054 if (isset($a)) $r['@attributes'] = $a; // Attributes
1055 return $r;
1056 }
1057 return (string) $xml;
1058 }
1059
1060 protected $_sibling_limit = 20;
1061 protected function get_xml_path(DOMElement $el, DOMXPath $xpath)
1062 {
1063 for($p = '', $doc = $el; $doc and ! $doc instanceof DOMDocument; $doc = $doc->parentNode) {
1064 if (($ind = $xpath->query('preceding-sibling::' . $doc->nodeName, $doc)->length)) {
1065 $p = '[' . ++$ind . ']' . $p;
1066 } elseif ( ! $doc->parentNode instanceof DOMDocument) {
1067 $p = '[' . ($ind = 1) . ']' . $p;
1068 }
1069 $p = '/' . $doc->nodeName . $p;
1070 }
1071 return $p;
1072 }
1073
1074 protected function shrink_xml_element(DOMElement $el)
1075 {
1076 $prev = null; $sub_ind = null;
1077 for ($i = 0; $i < $el->childNodes->length; $i++) {
1078 $child = $el->childNodes->item($i);
1079 if ($child instanceof DOMText) {
1080 if ('' == trim($child->wholeText)) {
1081 $el->removeChild($child);
1082 $i--;
1083 continue;
1084 }
1085 }
1086 if ($child instanceof DOMComment) {
1087 continue;
1088 }
1089 if ($prev instanceof $child and $prev->nodeName == $child->nodeName) {
1090 $sub_ind++;
1091 } else {
1092 if ($sub_ind > $this->_sibling_limit) {
1093 $el->insertBefore(new DOMComment('[pmxi_more:' . ($sub_ind - $this->_sibling_limit) . ']'), $child);
1094 $i++;
1095 }
1096 $sub_ind = 1;
1097 $prev = null;
1098 }
1099 if ($child instanceof DOMElement) {
1100 $prev = $child;
1101 if ($sub_ind <= $this->_sibling_limit) {
1102 $this->shrink_xml_element($child);
1103 } else {
1104 $el->removeChild($child);
1105 $i--;
1106 }
1107 }
1108 }
1109 if ($sub_ind > $this->_sibling_limit) {
1110 $el->appendChild(new DOMComment('[pmxi_more:' . ($sub_ind - $this->_sibling_limit) . ']'));
1111 }
1112 return $el;
1113 }
1114 protected function render_xml_element(DOMElement $el, $shorten = false, $path = '/', $ind = 1, $lvl = 0)
1115 {
1116 $path .= $el->nodeName;
1117 if ( ! $el->parentNode instanceof DOMDocument and $ind > 0) {
1118 $path .= "[$ind]";
1119 }
1120
1121 echo '<div class="xml-element lvl-' . $lvl . ' lvl-mod4-' . ($lvl % 4) . '" title="' . $path . '">';
1122 if ($el->hasChildNodes()) {
1123 $is_render_collapsed = $ind > 1;
1124 if ($el->childNodes->length > 1 or ! $el->childNodes->item(0) instanceof DOMText or strlen(trim($el->childNodes->item(0)->wholeText)) > 40) {
1125 echo '<div class="xml-expander">' . ($is_render_collapsed ? '+' : '-') . '</div>';
1126 }
1127 echo '<div class="xml-tag opening">&lt;<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el, $path . '/'); echo '&gt;</div>';
1128 if (1 == $el->childNodes->length and $el->childNodes->item(0) instanceof DOMText) {
1129 $this->render_xml_text(trim($el->childNodes->item(0)->wholeText), $shorten, $is_render_collapsed);
1130 } else {
1131 echo '<div class="xml-content' . ($is_render_collapsed ? ' collapsed' : '') . '">';
1132 $indexes = array();
1133 foreach ($el->childNodes as $child) {
1134 if ($child instanceof DOMElement) {
1135 empty($indexes[$child->nodeName]) and $indexes[$child->nodeName] = 0; $indexes[$child->nodeName]++;
1136 $this->render_xml_element($child, $shorten, $path . '/', $indexes[$child->nodeName], $lvl + 1);
1137 } elseif ($child instanceof DOMText) {
1138 $this->render_xml_text(trim($child->wholeText), $shorten);
1139 } elseif ($child instanceof DOMComment) {
1140 if (preg_match('%\[pmxi_more:(\d+)\]%', $child->nodeValue, $mtch)) {
1141 $no = intval($mtch[1]);
1142 echo '<div class="xml-more">[ &dArr; ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' &dArr; ]</div>';
1143 }
1144 }
1145 }
1146 echo '</div>';
1147 }
1148 echo '<div class="xml-tag closing">&lt;/<span class="xml-tag-name">' . $el->nodeName . '</span>&gt;</div>';
1149 } else {
1150 echo '<div class="xml-tag opening empty">&lt;<span class="xml-tag-name">' . $el->nodeName . '</span>'; $this->render_xml_attributes($el); echo '/&gt;</div>';
1151 }
1152 echo '</div>';
1153 }
1154 protected function render_xml_text($text, $shorten = false, $is_render_collapsed = false)
1155 {
1156 if (empty($text)) {
1157 return; // do not display empty text nodes
1158 }
1159 if (preg_match('%\[more:(\d+)\]%', $text, $mtch)) {
1160 $no = intval($mtch[1]);
1161 echo '<div class="xml-more">[ &dArr; ' . sprintf(__('<strong>%s</strong> %s more', 'pmxi_plugin'), $no, _n('element', 'elements', $no, 'pmxi_plugin')) . ' &dArr; ]</div>';
1162 return;
1163 }
1164 $more = '';
1165 if ($shorten and preg_match('%^(.*?\s+){20}(?=\S)%', $text, $mtch)) {
1166 $text = $mtch[0];
1167 $more = '<span class="xml-more">[' . __('more', 'pmxi_plugin') . ']</span>';
1168 }
1169 $is_short = strlen($text) <= 40;
1170 $text = esc_html($text);
1171 $text = preg_replace('%(?<!\s)\b(?!\s|\W[\w\s])|\w{20}%', '$0&#8203;', $text); // put explicit breaks for xml content to wrap
1172 echo '<div class="xml-content textonly' . ($is_short ? ' short' : '') . ($is_render_collapsed ? ' collapsed' : '') . '">' . $text . $more . '</div>';
1173 }
1174 protected function render_xml_attributes(DOMElement $el, $path = '/')
1175 {
1176 foreach ($el->attributes as $attr) {
1177 echo ' <span class="xml-attr" title="' . $path . '@' . $attr->nodeName . '"><span class="xml-attr-name">' . $attr->nodeName . '</span>=<span class="xml-attr-value">"' . esc_attr($attr->value) . '"</span></span>';
1178 }
1179 }
1180
1181 protected function render_xml_element_table(DOMElement $el, $shorten = false, $path = '/', $ind = 0, $lvl = 0)
1182 {
1183 $path .= $el->nodeName;
1184 if ($ind > 0) {
1185 $path .= "[$ind]";
1186 }
1187
1188 $is_render_collapsed = $ind > 1;
1189 echo '<tr class="xml-element lvl-' . $lvl . ($is_render_collapsed ? ' collapsed' : '') . '" title="' . $path . '">';
1190 echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px">';
1191 $is_inline = true;
1192 if ( ! (0 == $el->attributes->length and 1 == $el->childNodes->length and $el->childNodes->item(0) instanceof DOMText and strlen($el->childNodes->item(0)->wholeText) <= 40)) {
1193 $is_inline = false;
1194 echo '<div class="xml-expander">' . ($is_render_collapsed ? '+' : '-') . '</div>';
1195 }
1196 echo '<div class="xml-tag opening"><span class="xml-tag-name">' . $el->nodeName . '</span></div>';
1197 echo '</td>';
1198 echo '<td>';
1199 $is_inline and $this->render_xml_text_table(trim($el->childNodes->item(0)->wholeText), $shorten, NULL, NULL, $is_inline = true);
1200 echo '</td>';
1201 echo '</tr>';
1202 if ( ! $is_inline) {
1203 echo '<tr class="xml-content' . ($is_render_collapsed ? ' collapsed' : '') . '">';
1204 echo '<td colspan="2">';
1205 echo '<table>';
1206 $this->render_xml_attributes_table($el, $path . '/', $lvl + 1);
1207 $indexes = array();
1208 foreach ($el->childNodes as $child) {
1209 if ($child instanceof DOMElement) {
1210 empty($indexes[$child->nodeName]) and $indexes[$child->nodeName] = 1;
1211 $this->render_xml_element_table($child, $shorten, $path . '/', $indexes[$child->nodeName]++, $lvl + 1);
1212 } elseif ($child instanceof DOMText) {
1213 $this->render_xml_text_table(trim($child->wholeText), $shorten, $path . '/', $lvl + 1);
1214 }
1215 }
1216 echo '</table>';
1217 echo '</td>';
1218 echo '</tr>';
1219 }
1220 }
1221 protected function render_xml_text_table($text, $shorten = false, $path = '/', $lvl = 0, $is_inline = false)
1222 {
1223 if (empty($text)) {
1224 return; // do not display empty text nodes
1225 }
1226 $more = '';
1227 if ($shorten and preg_match('%^(.*?\s+){20}(?=\S)%', $text, $mtch)) {
1228 $text = $mtch[0];
1229 $more = '<span class="xml-more">[' . __('more', 'pmxi_plugin') . ']</span>';
1230 }
1231 $is_short = strlen($text) <= 40;
1232 $text = esc_html($text);
1233 $text = preg_replace('%(?<!\s)\b(?!\s|\W[\w\s])|\w{20}%', '$0&#8203;', $text); // put explicit breaks for xml content to wrap
1234 if ($is_inline) {
1235 echo $text . $more;
1236 } else {
1237 echo '<tr class="xml-content-tr textonly lvl-' . $lvl . ($is_short ? ' short' : '') . '" title="' . $path . 'text()">';
1238 echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px"><span class="xml-attr-name">text</span></td>';
1239 echo '<td>' . $text . $more . '</td>';
1240 echo '</tr>';
1241 }
1242 }
1243 protected function render_xml_attributes_table(DOMElement $el, $path = '/', $lvl = 0)
1244 {
1245 foreach ($el->attributes as $attr) {
1246 echo '<tr class="xml-attr lvl-' . $lvl . '" title="' . $path . '@' . $attr->nodeName . '">';
1247 echo '<td style="padding-left:' . ($lvl + 1) * 15 . 'px"><span class="xml-attr-name">@' . $attr->nodeName . '</span></td>';
1248 echo '<td><span class="xml-attr-value">' . esc_attr($attr->value) . '</span></td>';
1249 echo '</tr>';
1250 }
1251 }
1252
1253 protected function xml_find_repeating(DOMElement $el, $path = '/')
1254 {
1255 $path .= $el->nodeName;
1256 if ( ! $el->parentNode instanceof DOMDocument) {
1257 $path .= '[1]';
1258 }
1259 $children = array();
1260 foreach ($el->childNodes as $child) {
1261 if ($child instanceof DOMElement) {
1262 if ( ! empty($children[$child->nodeName])) {
1263 return $path . '/' . $child->nodeName;
1264 } else {
1265 $children[$child->nodeName] = true;
1266 }
1267 }
1268 }
1269 // reaching this point means we didn't find anything among current element children, so recursively ask children to find something in them
1270 foreach ($el->childNodes as $child) {
1271 if ($child instanceof DOMElement) {
1272 $result = $this->xml_find_repeating($child, $path . '/');
1273 if ($result) {
1274 return $result;
1275 }
1276 }
1277 }
1278 // reaching this point means we didn't find anything, so return element itself if the function was called for it
1279 if ('/' . $el->nodeName == $path) {
1280 return $path;
1281 }
1282
1283 return NULL;
1284 }
1285 /*
1286 * Convert csv to xml using yahoo API
1287 */
1288 protected function csv_to_xml($csv_url){
1289
1290 include_once(PMXI_Plugin::ROOT_DIR.'/libraries/XmlImportCsvParse.php');
1291
1292 $csv = new PMXI_CsvParser($csv_url);
1293
1294 return $csv->toXML();
1295
1296 }
1297 /*
1298 *
1299 * Detect CSV file
1300 *
1301 */
1302 protected function detect_csv($type){
1303 return in_array($type, $this->csv_mimes);
1304 }
1305 }
1306