PluginProbe
Listdom: AI-powered Business Directory with Classifieds Ads Listings / trunk
Listdom: AI-powered Business Directory with Classifieds Ads Listings vtrunk
6.0.0 5.9.0 5.8.1 5.8.0 5.7.0 5.6.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.2.0 1.2.1 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.8.0 1.9.0 All 75 releases
listdom / app / includes / ix.php

ix.php in Listdom: AI-powered Business Directory with Classifieds Ads Listings trunk, at app/includes/ix.php

1,348 lines 45.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class LSD_IX extends LSD_Base
4 {
5 protected $db;
6
7 protected static $last_result = null;
8
9 public function __construct()
10 {
11 // DB Library
12 $this->db = new LSD_db();
13 }
14
15 public static function import_types(): array
16 {
17 $types = [
18 'listings' => ['label' => esc_html__('Listings', 'listdom')],
19 'categories' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_CATEGORY, 'plural')), 'taxonomy' => LSD_Base::TAX_CATEGORY],
20 'locations' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_LOCATION, 'plural')), 'taxonomy' => LSD_Base::TAX_LOCATION],
21 'tags' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_TAG, 'plural')), 'taxonomy' => LSD_Base::TAX_TAG],
22 'features' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_FEATURE, 'plural')), 'taxonomy' => LSD_Base::TAX_FEATURE],
23 'labels' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_LABEL, 'plural')), 'taxonomy' => LSD_Base::TAX_LABEL],
24 'attributes' => ['label' => esc_html(lsd_t_label(LSD_Base::TAX_ATTRIBUTE, 'plural')), 'taxonomy' => LSD_Base::TAX_ATTRIBUTE],
25 ];
26
27 /**
28 * Allow third parties to extend the available JSON import / export types.
29 */
30 return apply_filters('lsd_ix_json_types', $types);
31 }
32
33 public static function import_type_labels(): array
34 {
35 $labels = [];
36 foreach (self::import_types() as $key => $type) $labels[$key] = $type['label'];
37
38 return $labels;
39 }
40
41 public static function normalize_import_type($type): string
42 {
43 $type = is_string($type) ? strtolower(sanitize_key($type)) : '';
44 $types = self::import_types();
45
46 return isset($types[$type]) ? $type : 'listings';
47 }
48
49 public static function import_type_label($type): string
50 {
51 $types = self::import_types();
52 $type = self::normalize_import_type($type);
53
54 return $types[$type]['label'] ?? '';
55 }
56
57 protected function filename_type(string $type): string
58 {
59 $type = self::normalize_import_type($type);
60
61 return $type === 'attributes' ? 'custom-fields' : $type;
62 }
63
64 public static function taxonomy_for_type($type): ?string
65 {
66 $types = self::import_types();
67 $type = self::normalize_import_type($type);
68
69 return $types[$type]['taxonomy'] ?? null;
70 }
71
72 public static function set_last_result($result): void
73 {
74 self::$last_result = $result;
75 }
76
77 /**
78 * @return WP_Error|array|null
79 */
80 public static function get_last_result()
81 {
82 return self::$last_result;
83 }
84
85 protected function get_data(string $type = 'listings'): array
86 {
87 $type = self::normalize_import_type($type);
88 if ($taxonomy = self::taxonomy_for_type($type)) return $this->taxonomy_data($taxonomy);
89
90 return $this->listings_data();
91 }
92
93 protected function listings_data(): array
94 {
95 $listings = get_posts([
96 'post_type' => LSD_Base::PTYPE_LISTING,
97 'post_status' => ['publish', 'pending', 'draft', 'future', 'private'],
98 'posts_per_page' => '-1',
99 ]);
100
101 $data = [];
102 foreach ($listings as $listing)
103 {
104 // Force to Array
105 $listing = (array) $listing;
106
107 // ID
108 $listing_id = $listing['ID'];
109
110 // Remove Useless Keys
111 foreach ([
112 'ID', 'comment_count', 'comment_status', 'filter', 'guid',
113 'menu_order', 'ping_status', 'pinged', 'to_ping', 'post_content_filtered',
114 'post_parent', 'post_mime_type',
115 ] as $key) if (isset($listing[$key])) unset($listing[$key]);
116
117 $metas = $this->get_post_meta($listing_id);
118
119 $listing['excerpt'] = $listing['post_excerpt'] ?? '';
120
121 // Remove Useless Keys
122 foreach ($metas as $key => $value)
123 {
124 if (in_array($key, [
125 '_edit_last', '_edit_lock', '_thumbnail_id',
126 'lsd_attributes', 'lsd_gallery', 'lsd_embeds', 'lsd_faqs',
127 ]) || strpos($key, 'lsd_attribute_') !== false) unset($metas[$key]);
128 }
129
130 // Meta Values
131 $listing['meta'] = $metas;
132
133 // Taxonomies
134 $listing['taxonomies'] = $this->get_taxonomies($listing_id);
135
136 // Gallery
137 $listing['gallery'] = $this->get_gallery($listing_id);
138
139 // Featured Image
140 $listing['image'] = get_the_post_thumbnail_url($listing_id, 'full');
141
142 // Attributes
143 $listing['attributes'] = $this->get_attributes($listing_id);
144
145 // FAQs
146 $faqs = get_post_meta($listing_id, 'lsd_faqs', true);
147 $listing['faqs'] = is_array($faqs) ? $faqs : [];
148
149 if (LSD_Base::isPro())
150 {
151 // Embed Codes
152 $embeds = get_post_meta($listing_id, 'lsd_embeds', true);
153 $listing['embeds'] = is_array($embeds) ? $embeds : [];
154 }
155
156 $data[] = $listing;
157 }
158
159 return $data;
160 }
161
162 protected function normalize_faqs($faqs): array
163 {
164 if (is_string($faqs))
165 {
166 $decoded = json_decode($faqs, true);
167 if (json_last_error() === JSON_ERROR_NONE) $faqs = $decoded;
168 }
169
170 if (!is_array($faqs)) return [];
171
172 if (array_key_exists('question', $faqs) || array_key_exists('answer', $faqs))
173 {
174 $faqs = [$faqs];
175 }
176
177 $normalized = [];
178 foreach ($faqs as $faq)
179 {
180 if (!is_array($faq)) continue;
181 if (!array_key_exists('question', $faq) && !array_key_exists('answer', $faq)) continue;
182
183 $normalized[] = $faq;
184 }
185
186 return $normalized;
187 }
188
189 protected function normalize_embeds($embeds): array
190 {
191 if (is_string($embeds))
192 {
193 $decoded = json_decode($embeds, true);
194 if (json_last_error() === JSON_ERROR_NONE) $embeds = $decoded;
195 }
196
197 if (!is_array($embeds)) return [];
198
199 if (array_key_exists('name', $embeds) || array_key_exists('code', $embeds))
200 {
201 $embeds = [$embeds];
202 }
203
204 $normalized = [];
205 foreach ($embeds as $embed)
206 {
207 if (!is_array($embed)) continue;
208
209 $code = isset($embed['code']) && is_scalar($embed['code']) ? trim((string) $embed['code']) : '';
210 if ($code === '') continue;
211
212 $featured = isset($embed['featured']) && is_scalar($embed['featured'])
213 ? strtolower(trim((string) $embed['featured']))
214 : '';
215
216 $normalized[] = [
217 'name' => isset($embed['name']) && is_scalar($embed['name']) ? trim((string) $embed['name']) : '',
218 'code' => $code,
219 'featured' => in_array($featured, ['1', 'yes', 'y', 'true'], true) ? '1' : '0',
220 ];
221 }
222
223 return $normalized;
224 }
225
226 protected function taxonomy_data(string $taxonomy): array
227 {
228 $terms = get_terms([
229 'taxonomy' => $taxonomy,
230 'hide_empty' => false,
231 ]);
232
233 if (!is_array($terms) || !count($terms)) return [];
234
235 $data = [];
236 foreach ($terms as $term)
237 {
238 $term = (array) $term;
239
240 $term_id = (int) ($term['term_id'] ?? 0);
241 $parent = (int) ($term['parent'] ?? 0);
242
243 foreach ([
244 'term_id', 'count', 'filter',
245 'term_taxonomy_id', 'parent',
246 ] as $key) if (isset($term[$key])) unset($term[$key]);
247
248 $metas = $this->get_term_meta($term_id);
249 if (isset($metas['lsd_image']))
250 {
251 $term['image'] = wp_get_attachment_url($metas['lsd_image']);
252 unset($metas['lsd_image']);
253 }
254
255 $term['meta'] = $metas;
256
257 if ($parent)
258 {
259 $parent_term = get_term($parent, $taxonomy);
260 if ($parent_term && !is_wp_error($parent_term))
261 {
262 $term['parent_slug'] = $parent_term->slug;
263 }
264 }
265
266 $data[] = $term;
267 }
268
269 return $data;
270 }
271
272 protected function taxonomy_data_chunk(string $taxonomy, int $offset, int $limit): array
273 {
274 $terms = get_terms([
275 'taxonomy' => $taxonomy,
276 'hide_empty' => false,
277 'offset' => $offset,
278 'number' => $limit,
279 ]);
280
281 if (!is_array($terms) || !count($terms)) return [];
282
283 $data = [];
284 foreach ($terms as $term)
285 {
286 $term = (array) $term;
287
288 $term_id = (int) ($term['term_id'] ?? 0);
289 $parent = (int) ($term['parent'] ?? 0);
290
291 foreach ([
292 'term_id', 'count', 'filter',
293 'term_taxonomy_id', 'parent',
294 ] as $key) if (isset($term[$key])) unset($term[$key]);
295
296 $metas = $this->get_term_meta($term_id);
297 if (isset($metas['lsd_image']))
298 {
299 $term['image'] = wp_get_attachment_url($metas['lsd_image']);
300 unset($metas['lsd_image']);
301 }
302
303 $term['meta'] = $metas;
304
305 if ($parent)
306 {
307 $parent_term = get_term($parent, $taxonomy);
308 if ($parent_term && !is_wp_error($parent_term))
309 {
310 $term['parent_slug'] = $parent_term->slug;
311 }
312 }
313
314 $data[] = $term;
315 }
316
317 return $data;
318 }
319
320 public function import($file, $type = 'listings')
321 {
322 // Content to Import
323 $content = LSD_File::read($file);
324
325 $ex = explode('.', $file);
326 $extension = strtolower(end($ex));
327
328 switch ($extension)
329 {
330 case 'json':
331
332 $result = $this->import_json($content, $type);
333 self::set_last_result($result);
334
335 return $result;
336
337 default:
338 $error = new WP_Error('lsd_import_invalid_extension', esc_html__('Invalid file extension! Only JSON files are allowed.', 'listdom'));
339 self::set_last_result($error);
340
341 return $error;
342 }
343 }
344
345 public function import_json($JSON, $type = 'listings')
346 {
347 $decoded = json_decode($JSON, true);
348 if (json_last_error() !== JSON_ERROR_NONE)
349 {
350 return new WP_Error('lsd_import_invalid_json', esc_html__('The uploaded file does not contain valid JSON data.', 'listdom'));
351 }
352
353 $file_type = 'listings';
354 $items = [];
355
356 if (is_array($decoded))
357 {
358 if (isset($decoded['type']))
359 {
360 $file_type = self::normalize_import_type($decoded['type']);
361 $items = isset($decoded['items']) && is_array($decoded['items']) ? $decoded['items'] : [];
362 }
363 else
364 {
365 $items = $decoded;
366 }
367 }
368
369 $type = self::normalize_import_type($type);
370 if ($file_type !== $type)
371 {
372 return new WP_Error('lsd_import_type_mismatch', '', ['file_type' => $file_type]);
373 }
374
375 if ($taxonomy = self::taxonomy_for_type($type))
376 {
377 return $this->import_terms(is_array($items) ? $items : [], $taxonomy);
378 }
379
380 return $this->collection(is_array($items) ? $items : []);
381 }
382
383 public function collection($listings): array
384 {
385 $ids = [];
386 foreach ($listings as $listing)
387 {
388 $ids[] = $this->save($listing);
389 }
390
391 do_action('lsd_import_finished');
392
393 return $ids;
394 }
395
396 public function import_term_collection(array $terms, string $taxonomy, array $options = []): array
397 {
398 return $this->import_terms($terms, $taxonomy, $options);
399 }
400
401 protected function import_terms(array $terms, string $taxonomy, array $options = []): array
402 {
403 $ids = [];
404 $pending_storage_key = 'lsd_pending_term_parents_' . $taxonomy;
405
406 $pending_parents_option = get_option($pending_storage_key, []);
407 $pending_parents = [];
408
409 if (is_array($pending_parents_option))
410 {
411 foreach ($pending_parents_option as $pending)
412 {
413 if (!is_array($pending)) continue;
414
415 $term_id = isset($pending['term_id']) ? (int) $pending['term_id'] : 0;
416 if (!$term_id) continue;
417
418 $pending_parents[$term_id] = [
419 'term_id' => $term_id,
420 'parent_slug' => isset($pending['parent_slug']) ? (string) $pending['parent_slug'] : '',
421 'parent' => array_key_exists('parent', $pending) ? (int) $pending['parent'] : null,
422 ];
423 }
424 }
425
426 foreach ($terms as $index => $term)
427 {
428 if (!is_array($term)) continue;
429
430 if ($this->should_import_hierarchy_path($term, $taxonomy, $options))
431 {
432 $path = $this->normalize_hierarchy_path($term['path']);
433 if (!count($path)) continue;
434
435 $term_id = $this->upsert_term_by_path($path, $taxonomy, $term);
436 if (!$term_id) continue;
437
438 $meta = isset($term['meta']) && is_array($term['meta']) ? $term['meta'] : [];
439 foreach ($meta as $key => $value) update_term_meta($term_id, $key, $value);
440
441 if (!empty($term['image']))
442 {
443 $attachment_id = $this->attach(trim((string) $term['image']));
444 if ($attachment_id) update_term_meta($term_id, 'lsd_image', $attachment_id);
445 }
446
447 $ids[$index] = $term_id;
448 continue;
449 }
450
451 $name = isset($term['name']) ? trim((string) $term['name']) : '';
452 if (!$name) continue;
453
454 $slug = isset($term['slug']) ? sanitize_title((string) $term['slug']) : '';
455 $exists = $slug ? term_exists($slug, $taxonomy) : term_exists($name, $taxonomy);
456
457 $args = [
458 'description' => $term['description'] ?? '',
459 ];
460 if ($slug) $args['slug'] = $slug;
461
462 $parent_slug = isset($term['parent_slug']) ? sanitize_title((string) $term['parent_slug']) : '';
463 $has_parent = ($parent_slug !== '') || array_key_exists('parent', $term);
464
465 unset($term_id);
466
467 if (is_array($exists) && isset($exists['term_id']))
468 {
469 $term_id = (int) $exists['term_id'];
470 $update_args = ['name' => $name, 'description' => $args['description']];
471 if (isset($args['slug'])) $update_args['slug'] = $args['slug'];
472
473 wp_update_term($term_id, $taxonomy, $update_args);
474 }
475 else if ($exists)
476 {
477 $term_id = (int) $exists;
478 $update_args = ['name' => $name, 'description' => $args['description']];
479 if (isset($args['slug'])) $update_args['slug'] = $args['slug'];
480
481 wp_update_term($term_id, $taxonomy, $update_args);
482 }
483 else
484 {
485 $insert = wp_insert_term($name, $taxonomy, $args);
486 if (is_wp_error($insert)) continue;
487
488 $term_id = (int) $insert['term_id'];
489 }
490
491 if (!isset($term_id) || !$term_id) continue;
492
493 $meta = isset($term['meta']) && is_array($term['meta']) ? $term['meta'] : [];
494 foreach ($meta as $key => $value) update_term_meta($term_id, $key, $value);
495
496 if (!empty($term['image']))
497 {
498 $attachment_id = $this->attach(trim((string) $term['image']));
499 if ($attachment_id) update_term_meta($term_id, 'lsd_image', $attachment_id);
500 }
501
502 $ids[$index] = $term_id;
503
504 if ($has_parent)
505 {
506 $pending_parents[$term_id] = [
507 'term_id' => $term_id,
508 'parent_slug' => $parent_slug,
509 'parent' => array_key_exists('parent', $term) ? (int) $term['parent'] : null,
510 ];
511 }
512 }
513
514 if (count($pending_parents))
515 {
516 $remaining_pending_parents = $this->assign_term_parents($pending_parents, $taxonomy);
517
518 if (count($remaining_pending_parents)) update_option($pending_storage_key, $remaining_pending_parents, false);
519 else delete_option($pending_storage_key);
520 }
521
522 return $ids;
523 }
524
525 protected function assign_term_parents(array $pending_parents, string $taxonomy): array
526 {
527 $remaining = [];
528
529 foreach ($pending_parents as $pending)
530 {
531 $term_id = isset($pending['term_id']) ? (int) $pending['term_id'] : 0;
532 if (!$term_id) continue;
533
534 $parent_id = $this->find_term_parent($pending, $taxonomy);
535 if ($parent_id === null)
536 {
537 $remaining[$term_id] = [
538 'term_id' => $term_id,
539 'parent_slug' => isset($pending['parent_slug']) ? (string) $pending['parent_slug'] : '',
540 'parent' => array_key_exists('parent', $pending) ? (int) $pending['parent'] : null,
541 ];
542
543 continue;
544 }
545
546 wp_update_term($term_id, $taxonomy, ['parent' => $parent_id]);
547 }
548
549 return $remaining;
550 }
551
552 protected function find_term_parent(array $pending, string $taxonomy): ?int
553 {
554 $parent_slug = isset($pending['parent_slug']) ? (string) $pending['parent_slug'] : '';
555 if ($parent_slug !== '')
556 {
557 $parent_term = get_term_by('slug', $parent_slug, $taxonomy);
558 if ($parent_term && !is_wp_error($parent_term)) return $parent_term->term_id;
559 }
560
561 if (array_key_exists('parent', $pending))
562 {
563 $parent = (int) $pending['parent'];
564 if ($parent > 0)
565 {
566 $maybe_parent = get_term($parent, $taxonomy);
567 if ($maybe_parent && !is_wp_error($maybe_parent)) return $maybe_parent->term_id;
568 }
569 else if ($parent === 0)
570 {
571 return 0;
572 }
573 }
574
575 return null;
576 }
577
578 protected function should_import_hierarchy_path(array $term, string $taxonomy, array $options): bool
579 {
580 return !empty($options['hierarchical_terms'])
581 && is_taxonomy_hierarchical($taxonomy)
582 && in_array($taxonomy, [LSD_Base::TAX_CATEGORY, LSD_Base::TAX_LOCATION], true)
583 && isset($term['path'])
584 && is_array($term['path'])
585 && count($term['path']);
586 }
587
588 protected function normalize_hierarchy_path(array $path): array
589 {
590 $normalized = [];
591 foreach ($path as $item)
592 {
593 if (!is_string($item) && !is_numeric($item)) continue;
594
595 $item = trim((string) $item);
596 if ($item === '') continue;
597
598 $normalized[] = $item;
599 }
600
601 return $normalized;
602 }
603
604 protected function upsert_term_by_path(array $path, string $taxonomy, array $term = []): int
605 {
606 $term_ids = $this->upsert_term_path($path, $taxonomy, $term);
607 if (!count($term_ids)) return 0;
608
609 return (int) end($term_ids);
610 }
611
612 protected function upsert_term_path(array $path, string $taxonomy, array $term = []): array
613 {
614 $path = $this->normalize_hierarchy_path($path);
615 if (!count($path)) return [];
616
617 $parent_id = 0;
618 $total = count($path);
619 $term_ids = [];
620
621 foreach ($path as $index => $segment)
622 {
623 $is_leaf = $index === ($total - 1);
624 $args = [];
625
626 if ($is_leaf)
627 {
628 if (array_key_exists('description', $term))
629 {
630 $args['description'] = (string) $term['description'];
631 }
632
633 $slug = isset($term['slug']) ? sanitize_title((string) $term['slug']) : '';
634 if ($slug !== '') $args['slug'] = $slug;
635 }
636
637 $term_id = $this->upsert_term_under_parent($segment, $taxonomy, $parent_id, $args);
638 if (!$term_id) return [];
639
640 $term_ids[] = $term_id;
641 $parent_id = $term_id;
642 }
643
644 return $term_ids;
645 }
646
647 protected function upsert_term_under_parent(string $name, string $taxonomy, int $parent_id = 0, array $args = []): int
648 {
649 $name = trim($name);
650 if ($name === '') return 0;
651
652 $slug = isset($args['slug']) ? sanitize_title((string) $args['slug']) : '';
653 $has_description = array_key_exists('description', $args);
654
655 $term_id = $slug !== '' ? $this->find_term_id_by_parent_and_slug($taxonomy, $parent_id, $slug) : 0;
656 if (!$term_id) $term_id = $this->find_term_id_by_parent_and_name($taxonomy, $parent_id, $name);
657
658 if ($term_id)
659 {
660 $update_args = ['name' => $name];
661 if ($has_description) $update_args['description'] = (string) $args['description'];
662
663 if ($slug !== '') $update_args['slug'] = $slug;
664
665 wp_update_term($term_id, $taxonomy, $update_args);
666 return $term_id;
667 }
668
669 $insert_args = ['parent' => $parent_id];
670 if ($has_description) $insert_args['description'] = (string) $args['description'];
671 if ($slug !== '') $insert_args['slug'] = $slug;
672
673 $insert = wp_insert_term($name, $taxonomy, $insert_args);
674 if (!is_array($insert) || !isset($insert['term_id'])) return 0;
675
676 return (int) $insert['term_id'];
677 }
678
679 protected function find_term_id_by_parent_and_slug(string $taxonomy, int $parent_id, string $slug): int
680 {
681 $term = get_term_by('slug', $slug, $taxonomy);
682 if (!$term || is_wp_error($term)) return 0;
683
684 return (int) $term->parent === $parent_id ? (int) $term->term_id : 0;
685 }
686
687 protected function find_term_id_by_parent_and_name(string $taxonomy, int $parent_id, string $name): int
688 {
689 $terms = get_terms([
690 'taxonomy' => $taxonomy,
691 'hide_empty' => false,
692 'name' => $name,
693 'parent' => $parent_id,
694 'number' => 1,
695 ]);
696
697 if (!is_array($terms) || !count($terms)) return 0;
698
699 $term = $terms[0];
700 return isset($term->term_id) ? (int) $term->term_id : 0;
701 }
702
703 public function save($listing)
704 {
705 $post = [
706 'post_title' => $listing['post_title'],
707 'post_name' => isset($listing['post_name']) && trim($listing['post_name']) ? $listing['post_name'] : $listing['post_title'],
708 'post_content' => $listing['post_content'] ?? '',
709 'post_excerpt' => $listing['post_excerpt'] ?? ($listing['excerpt'] ?? ''),
710 'post_type' => LSD_Base::PTYPE_LISTING,
711 'post_status' => $listing['post_status'] ?? 'publish',
712 'post_date' => isset($listing['post_date']) ? lsd_date('Y-m-d', strtotime($listing['post_date'])) : null,
713 'post_password' => $listing['post_password'] ?? '',
714 ];
715
716 // Don't Duplicate the Listing by Unique ID
717 if (isset($listing['unique_id']) && $listing['unique_id'])
718 {
719 $db = new LSD_db();
720 $exists = $db->select("SELECT `post_id` FROM `#__postmeta` WHERE `meta_value`='" . esc_sql($listing['unique_id']) . "' AND `meta_key`='lsd_sys_unique_id'", 'loadResult');
721
722 // Perform Duplicate Detection with ID or Not.
723 $id_duplicate_detection = apply_filters('lsd_ix_id_duplicate_detection', false);
724
725 // Detect Duplicate using ID
726 if (!$exists && $id_duplicate_detection)
727 {
728 $p = get_post($listing['unique_id']);
729 if ($p && is_object($p) && isset($p->ID) && $p->post_type === LSD_Base::PTYPE_LISTING) $exists = $p->ID;
730 }
731 }
732 // Don't Duplicate the Listing by Title and Content
733 else
734 {
735 $exists = post_exists($listing['post_title'], $listing['post_content'] ?? '', '', LSD_Base::PTYPE_LISTING);
736 }
737
738 $mode = 'add';
739 if ($exists)
740 {
741 $post['ID'] = $exists;
742 $mode = 'edit';
743 }
744
745 // Insert User
746 if (isset($listing['post_author']) && is_email($listing['post_author']))
747 {
748 $post['post_author'] = LSD_User::create($listing['post_author']);
749 }
750
751 // Add-ons and Third Party Applications
752 $post = apply_filters('lsd_ix_before_upsert', $post, $listing);
753
754 // Insert / Update Post
755 $post_id = wp_insert_post($post);
756
757 // Import Taxonomies
758 $taxonomies = isset($listing['taxonomies']) && is_array($listing['taxonomies']) ? $listing['taxonomies'] : [];
759 $primary_category_slug = '';
760 foreach ($taxonomies as $taxonomy => $terms)
761 {
762 if (!is_array($terms) || !count($terms)) continue;
763
764 $t = [];
765 foreach ($terms as $term)
766 {
767 if (is_string($term)) $term = ['name' => $term];
768 if (!is_array($term)) continue;
769
770 if (isset($term['path']) && is_array($term['path']) && is_taxonomy_hierarchical($taxonomy))
771 {
772 $path = $this->normalize_hierarchy_path($term['path']);
773 if (!count($path)) continue;
774
775 $path_ids = $this->upsert_term_path($path, $taxonomy, $term);
776 if (!count($path_ids)) continue;
777
778 $assign_full_path = apply_filters('lsd_ix_assign_full_hierarchy_path_terms', true, $taxonomy, $term, $listing);
779 $assigned_ids = $assign_full_path ? $path_ids : [(int) end($path_ids)];
780
781 foreach ($assigned_ids as $assigned_id)
782 {
783 if ($assigned_id) $t[] = (int) $assigned_id;
784 }
785
786 if ($taxonomy === LSD_Base::TAX_CATEGORY)
787 {
788 $leaf_category_id = (int) end($path_ids);
789 if ($leaf_category_id)
790 {
791 $primary_category = get_term($leaf_category_id, LSD_Base::TAX_CATEGORY);
792 if ($primary_category && !is_wp_error($primary_category) && isset($primary_category->slug))
793 {
794 $primary_category_slug = $primary_category->slug;
795 }
796 }
797 }
798
799 continue;
800 }
801
802 $term_name = isset($term['name']) ? trim($term['name']) : '';
803 $term_slug = isset($term['slug']) ? sanitize_title($term['slug']) : '';
804 $term_id = 0;
805
806 if ($term_slug)
807 {
808 $exists = term_exists($term_slug, $taxonomy);
809 if (!$term_name) $term_name = $term_slug;
810 }
811 else if ($term_name) $exists = term_exists($term_name, $taxonomy);
812 else continue;
813
814 if (is_array($exists) && isset($exists['term_id']))
815 {
816 $term_id = (int) $exists['term_id'];
817 }
818 else if ($exists)
819 {
820 $term_id = (int) $exists;
821 }
822 else
823 {
824 $term_args = [
825 'description' => $term['description'] ?? '',
826 'parent' => isset($term['parent']) ? (int) $term['parent'] : 0,
827 ];
828
829 if ($term_slug) $term_args['slug'] = $term_slug;
830
831 // Create Term
832 $wpt = wp_insert_term($term_name, $taxonomy, $term_args);
833
834 // An Error Occurred
835 if (is_wp_error($wpt))
836 {
837 if ($wpt->get_error_code() === 'term_exists')
838 {
839 $existing_term_id = (int) $wpt->get_error_data('term_exists');
840 if (!$existing_term_id) $existing_term_id = (int) $wpt->get_error_data();
841
842 if ($existing_term_id) $term_id = $existing_term_id;
843 }
844
845 if (!$term_id) continue;
846 }
847 else if (!is_array($wpt) || !isset($wpt['term_id']))
848 {
849 continue;
850 }
851 else
852 {
853 // Term ID
854 $term_id = (int) $wpt['term_id'];
855
856 // Import Term Meta
857 if (isset($term['meta']) && is_array($term['meta']) && count($term['meta']))
858 {
859 foreach ($term['meta'] as $key => $value) update_term_meta($term_id, $key, $value);
860 }
861
862 // Import Image
863 if (!empty($term['image']))
864 {
865 $attachment_id = $this->attach($term['image']);
866 if ($attachment_id) update_term_meta($term_id, 'lsd_image', $attachment_id);
867 }
868 }
869 }
870
871 if ($term_id)
872 {
873 $t[] = $term_id;
874
875 if ($taxonomy === LSD_Base::TAX_CATEGORY)
876 {
877 $primary_category = get_term($term_id, LSD_Base::TAX_CATEGORY);
878 if ($primary_category && !is_wp_error($primary_category) && isset($primary_category->slug))
879 {
880 $primary_category_slug = $primary_category->slug;
881 }
882 }
883 }
884 }
885
886 $t = array_values(array_unique(array_filter(array_map('intval', $t))));
887 if (count($t)) wp_set_post_terms($post_id, $t, $taxonomy);
888 }
889
890 if ($primary_category_slug !== '') update_post_meta($post_id, 'lsd_primary_category', $primary_category_slug);
891
892 // Import Image
893 if (isset($listing['image']) && trim($listing['image']))
894 {
895 $attachment_id = $this->attach(trim($listing['image']));
896 if ($attachment_id) set_post_thumbnail($post_id, $attachment_id);
897 }
898
899 // Import Gallery
900 $gallery = [];
901 if (isset($listing['gallery']) && is_array($listing['gallery']) && count($listing['gallery']))
902 {
903 foreach ($listing['gallery'] as $image)
904 {
905 $attachment_id = $this->attach(trim($image));
906 if ($attachment_id) $gallery[] = $attachment_id;
907 }
908 }
909
910 // Import Attributes
911 $attributes = [];
912 if (isset($listing['attributes']) && is_array($listing['attributes']) && count($listing['attributes']))
913 {
914 foreach ($listing['attributes'] as $attribute)
915 {
916 $term = $attribute['term'] ?? [];
917 if (!is_array($term) || !count($term)) continue;
918
919 $term_name = isset($term['name']) ? trim((string) $term['name']) : '';
920 $slug = isset($term['slug']) ? sanitize_title((string) $term['slug']) : '';
921
922 if (!$term_name && $slug) $term_name = $slug;
923 if (!$term_name) continue;
924
925 $exists = $slug ? term_exists($slug, LSD_Base::TAX_ATTRIBUTE) : term_exists($term_name, LSD_Base::TAX_ATTRIBUTE);
926
927 if (is_array($exists) && isset($exists['term_id'])) $term_id = (int) $exists['term_id'];
928 else
929 {
930 // Create Term
931 $wpt = wp_insert_term($term_name, LSD_Base::TAX_ATTRIBUTE, [
932 'description' => $term['description'] ?? '',
933 'parent' => isset($term['parent']) ? (int) $term['parent'] : 0,
934 'slug' => $slug,
935 ]);
936
937 // An Error Occurred
938 if (!is_array($wpt)) continue;
939
940 // Term ID
941 $term_id = (int) $wpt['term_id'];
942
943 // Import Term Meta for newly created attributes only.
944 if (isset($term['meta']) && is_array($term['meta']) && count($term['meta']))
945 {
946 foreach ($term['meta'] as $key => $value) update_term_meta($term_id, $key, $value);
947 }
948 }
949
950 $term_obj = get_term($term_id, LSD_Base::TAX_ATTRIBUTE);
951 if (!$term_obj || is_wp_error($term_obj)) continue;
952
953 $field_type = get_term_meta($term_obj->term_id, 'lsd_field_type', true);
954 $value = $this->normalize_imported_attribute_value($attribute['value'] ?? '', $field_type);
955 $this->sync_attribute_values($term_obj->term_id, $value);
956
957 // Add to Attributes
958 $attributes[$term_obj->slug] = $value;
959 }
960 }
961
962 // Metas
963 $metas = isset($listing['meta']) && is_array($listing['meta']) ? $listing['meta'] : [];
964
965 $faqs = [];
966 if (isset($listing['faqs'])) $faqs = $this->normalize_faqs($listing['faqs']);
967 else if (isset($metas['lsd_faqs'])) $faqs = $this->normalize_faqs($metas['lsd_faqs']);
968
969 $embeds = [];
970 $has_embeds = isset($listing['embeds']) || isset($metas['lsd_embeds']);
971 if (isset($listing['embeds'])) $embeds = $this->normalize_embeds($listing['embeds']);
972 else if (isset($metas['lsd_embeds'])) $embeds = $this->normalize_embeds($metas['lsd_embeds']);
973
974 // Prepare Data
975 $data = [
976 'listing_category' => null,
977 'object_type' => $metas['lsd_object_type'] ?? 'marker',
978 'zoomlevel' => $metas['lsd_zoomlevel'] ?? 6,
979 'latitude' => $metas['lsd_latitude'] ?? null,
980 'longitude' => $metas['lsd_longitude'] ?? null,
981 'address' => $metas['lsd_address'] ?? '',
982 'shape_type' => $metas['lsd_shape_type'] ?? '',
983 'shape_paths' => $metas['lsd_shape_paths'] ?? '',
984 'shape_radius' => $metas['lsd_shape_radius'] ?? '',
985 'attributes' => $attributes,
986 'link' => $metas['lsd_link'] ?? '',
987 'price' => $metas['lsd_price'] ?? 0,
988 'price_max' => $metas['lsd_price_max'] ?? 0,
989 'price_after' => $metas['lsd_price_after'] ?? '',
990 'currency' => $metas['lsd_currency'] ?? 'USD',
991 'ava' => $metas['lsd_ava'] ?? [],
992 'email' => $metas['lsd_email'] ?? '',
993 'phone' => $metas['lsd_phone'] ?? '',
994 'website' => $metas['lsd_website'] ?? '',
995 'contact_address' => $metas['lsd_contact_address'] ?? '',
996 'remark' => $metas['lsd_remark'] ?? '',
997 'displ' => $metas['lsd_displ'] ?? [],
998 'gallery' => $gallery,
999 'faqs' => $faqs,
1000 'sc' => [], // Social Networks
1001 ];
1002
1003 if (LSD_Base::isPro() && $has_embeds) $data['embeds'] = $embeds;
1004
1005 // Social Networks
1006 $SN = new LSD_Socials();
1007
1008 $networks = LSD_Options::socials();
1009 foreach ($networks as $network => $values)
1010 {
1011 $obj = $SN->get($network, $values);
1012
1013 // Social Network is Disabled or Data Not Available
1014 if (!$obj || !isset($metas['lsd_' . $obj->key()])) continue;
1015
1016 // Save Social Network Data
1017 $data['sc'][$obj->key()] = $metas['lsd_' . $obj->key()];
1018 }
1019
1020 if (isset($metas['lsd_guest_email']))
1021 {
1022 $data['guest_email'] = sanitize_email($metas['lsd_guest_email']);
1023 $data['guest_fullname'] = isset($metas['lsd_guest_fullname']) ? sanitize_email($metas['lsd_guest_fullname']) : '';
1024 $data['guest_message'] = $metas['lsd_guest_message'] ?? '';
1025 }
1026
1027 $entity = new LSD_Entity_Listing($post_id);
1028 $entity->save($data, false);
1029
1030 // Save the Unique ID
1031 if (isset($listing['unique_id']) && $listing['unique_id']) update_post_meta($post_id, 'lsd_sys_unique_id', $listing['unique_id']);
1032
1033 // New Listing Imported
1034 do_action('lsd_listing_imported', $post_id, $listing, $mode);
1035
1036 return $post_id;
1037 }
1038
1039 protected function sync_attribute_values(int $term_id, $value): void
1040 {
1041 $field_type = get_term_meta($term_id, 'lsd_field_type', true);
1042 if (!in_array($field_type, ['dropdown', 'radio', 'checkbox'], true)) return;
1043
1044 $values_raw = (string) get_term_meta($term_id, 'lsd_values', true);
1045 $existing_values = array_filter(array_map('trim', explode(',', $values_raw)));
1046
1047 if (is_array($value)) $incoming_values = $value;
1048 else if (is_string($value)) $incoming_values = explode(',', $value);
1049 else if (is_numeric($value)) $incoming_values = [(string) $value];
1050 else $incoming_values = [];
1051
1052 $added = false;
1053
1054 foreach ($incoming_values as $incoming_value)
1055 {
1056 $incoming_value = trim((string) $incoming_value);
1057 if ($incoming_value === '') continue;
1058
1059 if (!in_array($incoming_value, $existing_values, true))
1060 {
1061 $existing_values[] = $incoming_value;
1062 $added = true;
1063 }
1064 }
1065
1066 if ($added) update_term_meta($term_id, 'lsd_values', implode(',', $existing_values));
1067 }
1068
1069 protected function strict_datetime_from_format(string $format, string $value): ?DateTime
1070 {
1071 $dt = DateTime::createFromFormat($format, $value);
1072 if (!$dt instanceof DateTime) return null;
1073
1074 $errors = DateTime::getLastErrors();
1075 if (is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0)) return null;
1076
1077 return $dt;
1078 }
1079
1080 protected function normalize_imported_attribute_value($value, string $field_type)
1081 {
1082 if (is_object($value)) return '';
1083
1084 if (is_array($value))
1085 {
1086 $value = array_values(array_filter(array_map(static function ($item)
1087 {
1088 return is_scalar($item) ? sanitize_text_field((string) $item) : '';
1089 }, $value), static function (string $item): bool
1090 {
1091 return $item !== '';
1092 }));
1093
1094 // Preserve imported multi-value selections only for fields that store arrays.
1095 if ($field_type === 'checkbox') return $value;
1096
1097 $value = reset($value);
1098 if ($value === false) return '';
1099 }
1100
1101 if ($field_type === 'date')
1102 {
1103 $value = trim((string) $value);
1104 if ($value === '') return '';
1105
1106 $dt = $this->strict_datetime_from_format('Y-m-d', $value);
1107 if ($dt instanceof DateTime) return sanitize_text_field($dt->format('Y-m-d'));
1108 if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) return $value;
1109
1110 $timestamp = strtotime($value);
1111 return $timestamp !== false ? sanitize_text_field(lsd_date('Y-m-d', $timestamp)) : $value;
1112 }
1113
1114 if ($field_type === 'time')
1115 {
1116 $value = trim((string) $value);
1117 if ($value === '') return '';
1118
1119 foreach (['H:i', 'H:i:s'] as $format)
1120 {
1121 $dt = $this->strict_datetime_from_format($format, $value);
1122 if ($dt instanceof DateTime) return sanitize_text_field($dt->format($format));
1123 }
1124 if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $value)) return $value;
1125
1126 $timestamp = strtotime($value);
1127 return $timestamp !== false ? sanitize_text_field(lsd_date('H:i', $timestamp)) : $value;
1128 }
1129
1130 if ($field_type === 'datetime')
1131 {
1132 $value = trim((string) $value);
1133 if ($value === '') return '';
1134
1135 $normalized = str_replace(' ', 'T', $value);
1136
1137 foreach (['Y-m-d\TH:i', 'Y-m-d\TH:i:s', 'Y-m-d H:i', 'Y-m-d H:i:s'] as $format)
1138 {
1139 $dt = $this->strict_datetime_from_format($format, $normalized);
1140 if ($dt instanceof DateTime) return sanitize_text_field($dt->format('Y-m-d\TH:i'));
1141 }
1142 if (preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?$/', $value)) return $value;
1143
1144 $timestamp = strtotime(str_replace('T', ' ', $normalized));
1145 return $timestamp !== false ? sanitize_text_field(lsd_date('Y-m-d\TH:i', $timestamp)) : $value;
1146 }
1147
1148 if ($field_type !== 'file') return is_scalar($value) ? sanitize_text_field((string) $value) : '';
1149
1150 $value = trim((string) $value);
1151 if ($value === '') return '';
1152
1153 if (filter_var($value, FILTER_VALIDATE_URL))
1154 {
1155 $attachment_id = $this->attach($value);
1156 if ($attachment_id) return (string) $attachment_id;
1157
1158 return esc_url_raw($value);
1159 }
1160
1161 return '';
1162 }
1163
1164 protected function normalize_export_attribute_value($value, string $field_type)
1165 {
1166 if ($field_type !== 'file') return $value;
1167
1168 $url = $this->attribute_file_url($value);
1169 if ($url !== '') return $url;
1170
1171 if (is_array($value) || is_object($value)) return '';
1172
1173 return sanitize_text_field((string) $value);
1174 }
1175
1176 protected function attribute_file_url($value): string
1177 {
1178 if (is_array($value) || is_object($value)) return '';
1179
1180 $value = trim((string) $value);
1181 if ($value === '') return '';
1182
1183 if (is_numeric($value))
1184 {
1185 $attachment_url = wp_get_attachment_url((int) $value);
1186 return $attachment_url ? esc_url_raw($attachment_url) : '';
1187 }
1188
1189 if (filter_var($value, FILTER_VALIDATE_URL))
1190 {
1191 return esc_url_raw($value);
1192 }
1193
1194 return '';
1195 }
1196
1197 public function get_attributes($post_id): array
1198 {
1199 $attributes = [];
1200
1201 $values = get_post_meta($post_id, 'lsd_attributes', true);
1202 if (!is_array($values)) $values = [];
1203
1204 $attribute_context = LSD_Taxonomies_Attribute::context([
1205 'post_id' => (int) $post_id,
1206 ]);
1207
1208 foreach ($values as $slug => $value)
1209 {
1210 $term = (array) get_term_by('slug', $slug, LSD_Base::TAX_ATTRIBUTE);
1211
1212 // Term ID
1213 $term_id = $term['term_id'] ?? '';
1214 if (!$term_id || !LSD_Taxonomies_Attribute::applies((int) $term_id, $attribute_context)) continue;
1215
1216 // Remove Useless Keys
1217 foreach ([
1218 'count', 'filter', 'term_group',
1219 'term_id', 'term_taxonomy_id',
1220 ] as $key) if (isset($term[$key])) unset($term[$key]);
1221
1222 // Meta Values
1223 $term['meta'] = $this->get_term_meta($term_id);
1224
1225 $field_type = get_term_meta($term_id, 'lsd_field_type', true);
1226 $value = $this->normalize_export_attribute_value($value, $field_type);
1227
1228 $attributes[] = [
1229 'value' => $value,
1230 'term' => $term,
1231 ];
1232 }
1233
1234 return $attributes;
1235 }
1236
1237 public function get_taxonomies($post_id): array
1238 {
1239 $taxonomies = [];
1240
1241 foreach ([
1242 LSD_Base::TAX_CATEGORY,
1243 LSD_Base::TAX_LABEL,
1244 LSD_Base::TAX_LOCATION,
1245 LSD_Base::TAX_FEATURE,
1246 LSD_Base::TAX_TAG,
1247 ] as $taxonomy)
1248 {
1249 $terms = get_the_terms($post_id, $taxonomy);
1250 if ($terms && !is_wp_error($terms))
1251 {
1252 $t = [];
1253 foreach ($terms as $term)
1254 {
1255 // Force to Array
1256 $term = (array) $term;
1257
1258 // Term ID
1259 $term_id = $term['term_id'];
1260
1261 // Remove Useless Keys
1262 foreach ([
1263 'count', 'filter',
1264 'term_group', 'term_taxonomy_id',
1265 ] as $key) if (isset($term[$key])) unset($term[$key]);
1266
1267 // Metas
1268 $metas = $this->get_term_meta($term_id);
1269
1270 // Image
1271 if (isset($metas['lsd_image']))
1272 {
1273 $term['image'] = wp_get_attachment_url($metas['lsd_image']);
1274 unset($metas['lsd_image']);
1275 }
1276
1277 $term['meta'] = $metas;
1278
1279 $t[] = $term;
1280 }
1281
1282 $taxonomies[$taxonomy] = $t;
1283 }
1284 }
1285
1286 return $taxonomies;
1287 }
1288
1289 public function get_gallery($post_id): array
1290 {
1291 $value = get_post_meta($post_id, 'lsd_gallery', true);
1292 if (!is_array($value)) $value = [];
1293
1294 $gallery = [];
1295 foreach ($value as $attachment_id)
1296 {
1297 $image = wp_get_attachment_url($attachment_id);
1298 if (!$image) continue;
1299
1300 $gallery[] = $image;
1301 }
1302
1303 return $gallery;
1304 }
1305
1306 public function attach($image)
1307 {
1308 // Already imported
1309 if ($attachment_id = LSD_Main::get_post_id_by_meta('lsd_imported_from', $image)) return $attachment_id;
1310
1311 // Image is already exist in website
1312 if ($attachment_id = attachment_url_to_postid($image)) return $attachment_id;
1313
1314 $buffer = LSD_File::download($image);
1315 if (!$buffer) return false;
1316
1317 return $this->attach_by_buffer($buffer, basename($image), $image);
1318 }
1319
1320 public function attach_by_buffer($buffer, $name, $url = null)
1321 {
1322 // Media Libraries
1323 require_once ABSPATH . 'wp-admin/includes/image.php';
1324 require_once ABSPATH . 'wp-admin/includes/file.php';
1325 require_once ABSPATH . 'wp-admin/includes/media.php';
1326
1327 $upload = wp_upload_bits($name, null, $buffer);
1328
1329 $file = $upload['file'];
1330 $wp_filetype = wp_check_filetype($file);
1331 $attachment = [
1332 'post_mime_type' => $wp_filetype['type'],
1333 'post_title' => basename($file),
1334 'post_content' => '',
1335 'post_status' => 'inherit',
1336 ];
1337
1338 $attachment_id = wp_insert_attachment($attachment, $file);
1339 $attach_data = wp_generate_attachment_metadata($attachment_id, $file);
1340 wp_update_attachment_metadata($attachment_id, $attach_data);
1341
1342 // Flag the attachment imported from URL
1343 update_post_meta($attachment_id, 'lsd_imported_from', $url);
1344
1345 return $attachment_id;
1346 }
1347 }
1348