PluginProbe
Solace Extra / trunk
Solace Extra vtrunk
1.7.1 1.7.0 1.6.2 1.6.1 1.6.0 1.5.3 trunk 1.0.8 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.7 1.1.8 1.1.9 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.5.0 1.5.1 All 26 releases
solace-extra / admin / import.php

import.php in Solace Extra trunk, at admin/import.php

3,207 lines 113.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined( 'ABSPATH' ) || exit;
3
4 /**
5 * Backend submenu Import
6 *
7 * @link https://solacewp.com
8 * @since 1.0.0
9 *
10 * @package Solace_Extra
11 * @subpackage Solace_Extra/admin
12 */
13
14 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress importer uses core/legacy hook names for compatibility.
15
16 /**
17 * The admin-specific functionality of the plugin (import).
18 *
19 * @package Solace_Extra
20 * @subpackage Solace_Extra/import
21 * @author Solace <solacewp@gmail.com>
22 */
23 require_once plugin_dir_path(__FILE__) . 'wp-background-processing.php';
24
25 /** Display verbose errors */
26 if ( ! defined( 'IMPORT_DEBUG' ) ) {
27 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress Importer compatibility constant.
28 define( 'IMPORT_DEBUG', WP_DEBUG );
29 }
30
31 if ( ! function_exists( 'post_exists' ) ) {
32 require_once ABSPATH . 'wp-admin/includes/post.php';
33 }
34
35 /** WordPress Import Administration API */
36 require_once ABSPATH . 'wp-admin/includes/import.php';
37
38 if ( ! class_exists( 'WP_Importer' ) ) {
39 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress Importer API compatibility
40 $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
41 if ( file_exists( $class_wp_importer ) ) {
42 require $class_wp_importer;
43 }
44 }
45
46 if ( ! function_exists( 'solace_extra_deactivate_atomic_editor' ) ) {
47 /**
48 * Keep Elementor's Atomic Editor (Editor V4) experiments inactive while the
49 * Solace Extra plugin is active.
50 *
51 * Solace demo kits are built with v3 Elementor elements, so importing with
52 * the Atomic Editor active is unnecessary: v4 element types written during
53 * an import (or by editing pages in the Atomic Editor) render blank once the
54 * Atomic Editor is turned off. Deactivating these experiments on every
55 * request keeps every imported/edited template independent of the Atomic
56 * Editor. The user may re-enable it from the Elementor dashboard — the next
57 * request simply deactivates it again while this plugin is active.
58 *
59 * Mirrors Elementor's own `opt_out_v4()` list.
60 *
61 * @return void
62 */
63 function solace_extra_deactivate_atomic_editor() {
64 $features = array(
65 'e_opt_in_v4',
66 'e_atomic_elements',
67 'e_global_classes',
68 'e_variables',
69 'e_components',
70 );
71
72 foreach ( $features as $feature ) {
73 update_option( 'elementor_experiment-' . $feature, 'inactive' );
74 }
75 }
76 }
77
78 // Keep the Atomic Editor deactivated on every request while this plugin runs.
79 add_action( 'init', 'solace_extra_deactivate_atomic_editor', 1 );
80
81
82 /** Functions missing in older WordPress versions. */
83 require_once __DIR__ . '/export-import/compat.php';
84
85 /** Solace_Extra_WXR_Parser class */
86 require_once __DIR__ . '/export-import/parsers/class-wxr-parser.php';
87
88 /** Solace_Extra_WXR_Parser_SimpleXML class */
89 require_once __DIR__ . '/export-import/parsers/class-wxr-parser-simplexml.php';
90
91 /** Solace_Extra_WXR_Parser_XML class */
92 require_once __DIR__ . '/export-import/parsers/class-wxr-parser-xml.php';
93
94 /** Solace_Extra_WXR_Parser_Regex class */
95 require_once __DIR__ . '/export-import/parsers/class-wxr-parser-regex.php';
96
97 /** Solace_Extra_WP_Import class */
98 require_once __DIR__ . '/export-import/class-wp-import.php';
99
100 class Solace_Import_Elementor_Process extends XWP_Background_Process {
101 protected $action = 'import_posts_process';
102 private $demo_name;
103
104 protected function task($item) {
105 if (isset($item['action']) && $item['action'] === 'demo_name' && isset($item['demo_name'])) {
106 $this->import_elementor_kit($item['demo_name']);
107 }
108 return false;
109 }
110
111 protected function import_elementor_kit($demo_name) {
112 // Set elementor settings
113 $demo = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $demo_name . '/wp-json/solace/v1/elementor';
114 $this->demo_name = $demo_name;
115
116 // Remote get data
117 $response = wp_remote_get($demo);
118 $datas = wp_remote_retrieve_body($response);
119 $data = json_decode($datas);
120 update_option( 'elementor_disable_color_schemes', 'yes' );
121 update_option( 'elementor_disable_typography_schemes', 'yes' );
122 update_option( 'elementor_experiment-nested-elements', sanitize_key( $data->nested_elements ) );
123
124 $zip_url = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . "wp-content/uploads/demolist/" . $demo_name . "/" . $demo_name . ".zip";
125 \Elementor\Plugin::$instance->uploads_manager->enable_unfiltered_files_upload();
126 \Elementor\Plugin::$instance->uploads_manager->set_elementor_upload_state(true);
127 $upload_dir = wp_upload_dir();
128 $response = wp_remote_get($zip_url);
129
130
131
132 $zip_data = wp_upload_bits(basename($zip_url), null, wp_remote_retrieve_body(wp_remote_get($zip_url)));
133 if (!$zip_data['error']) {
134 if (class_exists('Elementor\Plugin')) {
135 $zip_file_path = $zip_data['file'];
136 $zip = new ZipArchive();
137 if ($zip->open($zip_file_path, ZipArchive::CREATE) === true) {
138
139 \Elementor\Plugin::$instance->uploads_manager->set_elementor_upload_state(true);
140 \Elementor\Plugin::$instance->uploads_manager->enable_unfiltered_files_upload();
141
142 add_filter('elementor/template_library/import_images/new_attachment', array($this, 'handle_images_import'));
143
144 add_action('wp_import_insert_post', array($this, 'handle_import_post'), 10, 4);
145
146 add_action('wp_import_set_post_terms', array($this, 'handle_import_post_terms'), 10, 5);
147
148 add_filter('elementor/document/save/data', array($this, 'handle_pages_import'), 10, 2);
149
150 $import_export = \Elementor\Plugin::$instance->app->get_component('import-export');
151
152 // In non-admin contexts (WP-Cron / REST) Elementor skips registering
153 // the 'import-export' component — App::__construct() gates it behind
154 // current_user_can('manage_options'). When that happens, build the
155 // module directly so the kit still imports.
156 if (!$import_export && class_exists('\Elementor\App\Modules\ImportExport\Module')) {
157 $import_export = new \Elementor\App\Modules\ImportExport\Module();
158 }
159
160 if (!$import_export) {
161 throw new \RuntimeException('Elementor import-export component is unavailable; cannot import kit.');
162 }
163
164 // The Atomic Editor is already kept inactive by the global
165 // `init` hook, so no import-specific disable is needed.
166 $import_export->import_kit($zip_file_path, array('include', 'selected_plugins', 'selected_cpt', 'selected_override_conditions'));
167
168 $zip->close();
169
170 // $this->set_latest_cart_page_as_default();
171 $this->set_latest_page_as_default('CART');
172 $this->set_latest_page_as_default('CHECKOUT');
173 $this->set_latest_page_as_default('MY ACCOUNT');
174
175 } else {
176 esc_html_e('Failed opening file ZIP.', 'solace-extra');
177 }
178 } else {
179 esc_html_e('Plugin Elementor Not Activated.', 'solace-extra');
180 }
181 } else {
182 esc_html_e('Failed downloading file ZIP.', 'solace-extra');
183 }
184
185 if ( class_exists( 'Elementor\Plugin' ) ) {
186 $elementor_active_kit = get_option( 'elementor_active_kit' );
187 update_post_meta( $elementor_active_kit, 'solace_import_elementor_kit', true );
188 }
189 }
190
191 protected function complete() {
192 parent::complete();
193 update_option('solace_extra_import_zip_complete', true);
194 set_theme_mod('solace_extra_import_zip_complete', true);
195 }
196
197 /**
198 * Run a demo's Elementor kit import synchronously, in the current PHP process.
199 *
200 * The REST/automation bridge (Solace_Extra_REST_Demo) uses this instead of the
201 * async loopback dispatch, which fails under Application-Password auth: the
202 * loopback request carries no login cookie, so admin-ajax.php sees it as an
203 * unauthenticated user and check_ajax_referer() rejects the nonce — the queue
204 * is never processed. Running the import inline (from a WP-Cron event that does
205 * NOT require the admin nonce/cookie) sidesteps that entirely.
206 *
207 * @param string $demo_name The demo slug to import.
208 * @return void
209 */
210 public function run_import_now($demo_name) {
211 $this->import_elementor_kit($demo_name);
212 }
213
214 /**
215 * Drain the queued batch in this PHP process instead of via the async loopback.
216 *
217 * import_zip() ends with save()->dispatch(). Under Application-Password auth the
218 * dispatch is a dead end (cookieless loopback -> rejected nonce), so nothing ever
219 * processes the batch. Calling handle() here runs the queued task, then complete()
220 * flips solace_extra_import_zip_complete — exactly what the admin flow ends up doing.
221 *
222 * @return void
223 */
224 public function run_queue_now() {
225 $this->handle();
226 }
227
228 public function set_latest_page_as_default($page_title) {
229 $pages = get_posts([
230 'post_type' => 'page',
231 'post_status' => 'publish',
232 'title' => $page_title,
233 'numberposts' => -1,
234 'fields' => 'ids',
235 ]);
236
237 if (!empty($pages)) {
238 $latest_page_id = max($pages);
239
240 if ($page_title === 'CART') {
241 update_option('woocommerce_cart_page_id', $latest_page_id);
242 } elseif ($page_title === 'CHECKOUT') {
243 update_option('woocommerce_checkout_page_id', $latest_page_id);
244 } elseif ($page_title === 'MY ACCOUNT') {
245 update_option('woocommerce_myaccount_page_id', $latest_page_id);
246 }
247 }
248 }
249
250 /**
251 * Handles the import of pages.
252 *
253 * @param mixed $data The data being processed during import.
254 * @param WP_Document $document The document being imported.
255 *
256 * @return mixed The modified or unmodified data after handling the pages import.
257 */
258 public function handle_pages_import($data, $document)
259 {
260 // Get the post object from the document
261 $post_object = $document->get_post();
262
263 // Ensure the post object is an instance of WP_Post
264 if ($post_object instanceof WP_Post) {
265 // Check if the post type is 'page'
266 if ($post_object->post_type === 'page') {
267 // Get the post ID
268 $post_id = $post_object->ID;
269
270 // Ensure the post ID is available before updating the meta
271 if ($post_id) {
272 // Get demo_name.
273 $imported_demo_name = 'solace_extra_' . $this->demo_name;
274
275 // Update the 'solace_import_pages' meta to true to mark the page as imported
276 update_post_meta($post_id, 'solace_import_pages', true);
277 update_post_meta($post_id, $imported_demo_name, true);
278 }
279 }
280
281 // Check if the post type is 'solace-sitebuilder'
282 if ($post_object->post_type === 'solace-sitebuilder') {
283 // Get the post ID
284 $post_id = $post_object->ID;
285
286 // Ensure the post ID is available before updating the meta
287 if ($post_id) {
288 // Get demo_name.
289 $imported_demo_name = 'solace_extra_' . $this->demo_name;
290
291 // Update the 'solace_import_site_builder' meta to true to mark the page as imported
292 update_post_meta($post_id, 'solace_import_site_builder', true);
293 update_post_meta($post_id, $imported_demo_name, true);
294 }
295 }
296 }
297
298 // Return the unmodified data
299 return $data;
300 }
301
302 /**
303 * Handles the import of posts and updates the post meta 'solace_import_posts'.
304 *
305 * @param int $post_id The ID of the imported post.
306 * @param int $original_post_id The ID of the original post.
307 * @param object $postdata The post data.
308 * @param object $post The post object.
309 */
310 public function handle_import_post($post_id, $original_post_id, $postdata, $post)
311 {
312
313 // Get demo_name.
314 $imported_demo_name = 'solace_extra_' . $this->demo_name;
315
316 update_post_meta($post_id, $imported_demo_name, true);
317
318 update_post_meta($post_id, 'solace_import_posts', true);
319
320 if ($post['post_type'] === 'product') {
321 update_post_meta($post_id, 'solace_import_products', true);
322 }
323 }
324 /**
325 * Handles the import of images and updates the post meta 'solace_import_images'.
326 *
327 * @param int $post_id The ID of the post associated with the imported image.
328 */
329 public function handle_images_import($post_id)
330 {
331 // Update the post meta 'solace_import_images' with the value 'true'
332 update_post_meta($post_id, 'solace_import_images', true);
333 return $post_id;
334 }
335
336 /**
337 * Handle the import of terms for a post.
338 *
339 * This function processes the imported terms for a post, updating term metadata
340 * for categories and post tags with the 'solace_imported_term' flag.
341 *
342 * @param array $tt_ids Term taxonomy IDs.
343 * @param array $ids Term IDs.
344 * @param string $tax Taxonomy name.
345 * @param int $post_id Post ID.
346 * @param WP_Post $post The post object.
347 *
348 * @return void
349 */
350 public function handle_import_post_terms($tt_ids, $ids, $tax, $post_id, $post)
351 {
352
353 foreach ($post['terms'] as $data) {
354 if ($data['domain'] === 'category') {
355
356 $category = get_term_by('slug', $data['slug'], 'category');
357 if ($category) {
358 if ($data['slug'] !== 'uncategorized') {
359 $category_id = $category->term_id;
360 update_term_meta($category_id, 'solace_imported_term_category', true);
361 }
362 }
363 }
364
365 if ($data['domain'] === 'post_tag') {
366 $tag = get_term_by('slug', $data['slug'], 'post_tag');
367 if ($tag) {
368 $tag_id = $tag->term_id;
369 update_term_meta($tag_id, 'solace_imported_term_post_tag', true);
370 }
371 }
372
373 // Products cat
374 if ($data['domain'] === 'product_cat') {
375 $product_cat = get_term_by('slug', $data['slug'], 'product_cat');
376 if ($product_cat) {
377 if ($data['slug'] !== 'uncategorized') {
378 $product_cat_id = $product_cat->term_id;
379 update_term_meta($product_cat_id, 'solace_imported_term_product_cat', true);
380 }
381 }
382 }
383
384 // Product tag
385 if ($data['domain'] === 'product_tag') {
386 $tag = get_term_by('slug', $data['slug'], 'product_tag');
387 if ($tag) {
388 $tag_id = $tag->term_id;
389 update_term_meta($tag_id, 'solace_imported_term_product_tag', true);
390 }
391 }
392 }
393 }
394 }
395
396 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- WordPress Importer compatibility class name.
397 class Thumbnail_Generator extends XWP_Background_Process {
398
399 protected $action = 'generate_thumbnails';
400
401 /**
402 * Proses setiap item
403 *
404 * @param mixed $item Data attachment ID.
405 * @return mixed
406 */
407 protected function task($item) {
408 if (!wp_attachment_is_image($item)) {
409 return false;
410 }
411
412 $file_path = get_attached_file($item);
413
414 if (!$file_path || !file_exists($file_path)) {
415 return false;
416 }
417
418 $sizes = get_intermediate_image_sizes();
419
420 $metadata = wp_generate_attachment_metadata($item, $file_path);
421
422 foreach ($sizes as $size) {
423 if (!isset($metadata['sizes'][$size])) {
424 $editor = wp_get_image_editor($file_path);
425 if (!is_wp_error($editor)) {
426 $editor->resize(
427 get_option("{$size}_size_w"),
428 get_option("{$size}_size_h"),
429 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- WordPress core image size crop filter
430 apply_filters("{$size}_crop", false)
431 );
432 $editor->save();
433 }
434 }
435 }
436
437 wp_update_attachment_metadata($item, $metadata);
438
439 return false;
440 }
441
442 /**
443 * Selesai proses
444 *
445 * @return void
446 */
447 protected function complete() {
448 parent::complete();
449 }
450
451 }
452
453
454 class Solace_Extra_Import {
455
456 /**
457 * The ID of this plugin.
458 *
459 * @since 1.0.0
460 * @access private
461 * @var string $plugin_name The ID of this plugin.
462 */
463 private $plugin_name;
464 private $import_process;
465
466 /**
467 * The demo_name of this plugin.
468 *
469 * @since 1.0.0
470 * @access private
471 * @var string $demo_name The current demo_name of this plugin.
472 */
473 private $demo_name;
474
475 /**
476 * The version of this plugin.
477 *
478 * @since 1.0.0
479 * @access private
480 * @var string $version The current version of this plugin.
481 */
482 private $version;
483
484 /**
485 * Initialize the class and set its properties.
486 *
487 * @since 1.0.0
488 * @param string $plugin_name The name of this plugin.
489 * @param string $version The version of this plugin.
490 */
491 public function __construct($plugin_name, $version)
492 {
493
494 $this->plugin_name = $plugin_name;
495 $this->version = $version;
496 $this->import_process = new Solace_Import_Elementor_Process();
497
498 }
499
500 /**
501 * Checks to see whether a string is an image url or not.
502 *
503 * @since 0.1
504 * @access private
505 * @param string $string The string to check.
506 * @return bool Whether the string is an image url or not.
507 */
508 static private function _is_image_url($string = '')
509 {
510 if (is_string($string)) {
511
512 if (preg_match('/\.(jpg|jpeg|png|gif)/i', $string)) {
513 return true;
514 }
515 }
516
517 return false;
518 }
519
520
521
522 /**
523 * Handles AJAX request for importing the site builder.
524 *
525 * This method verifies the nonce and checks if the current user has the
526 * 'manage_options' capability. If either check fails, it returns an
527 * appropriate JSON error response with a relevant HTTP status code.
528 *
529 * @return void
530 */
531 public function call_ajax_import_site_builder() {
532 // Verify nonce.
533 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'ajax-nonce' ) ) {
534 wp_send_json_error( array( 'error' => 'Invalid nonce!' ), 403 ); // 403 Forbidden
535 }
536
537 // Check current user capability.
538 if ( ! current_user_can( 'manage_options' ) ) {
539 wp_send_json_error( array( 'error' => 'Unauthorized' ), 401 ); // 401 Unauthorized
540 }
541
542 // Get and sanitize the demo name from POST data
543 $get_demo_name = ! empty( $_POST['getDemo'] ) ? sanitize_title( wp_unslash( $_POST['getDemo'] ) ) : '';
544
545 // If the demo name is empty after sanitization, reject the request
546 if ( empty( $get_demo_name ) ) {
547 wp_send_json_error( array( 'error' => 'Demo name is required.' ), 400 ); // Bad Request
548 }
549
550 // Construct the remote URL to the XML file
551 $demo_url = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . "wp-content/uploads/demolist/" . $get_demo_name . "/" . $get_demo_name . ".xml";
552
553 // Use wp_remote_head() to check if the remote file exists
554 $response = wp_remote_head( $demo_url );
555 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
556 wp_send_json_error( array( 'error' => 'Demo XML not found.' ), 404 ); // Not Found
557 }
558
559 // Instantiate your custom importer and run the import using the remote URL
560 $importer = new Solace_Extra_WP_Import();
561 $importer->fetch_attachments = true;
562 $file = $demo_url;
563 $importer->import( $file );
564
565 wp_send_json_success(
566 array(
567 'message' => 'Import completed successfully.',
568 'file' => $file,
569 )
570 );
571
572 wp_die();
573 }
574
575 /**
576 * Solace Ajax Import
577 */
578 public function call_ajax_import_customizer()
579 {
580 // Verify nonce
581 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
582 wp_send_json_error( array( 'error' => 'Invalid nonce!' ) );
583 }
584
585 // Check current user
586 if ( ! current_user_can( 'manage_options' ) ) {
587 wp_send_json_error( array( 'error' => 'Unauthorized' ) );
588 }
589
590 // Delete import page site builder.
591 $this->delete_imported_site_builder();
592
593 // Get menus data from the API
594 $get_demo_name = ! empty( $_POST['getDemo'] ) ? sanitize_title( wp_unslash( $_POST['getDemo'] ) ) : '';
595
596 $demo = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo_name . "/wp-json/solace/v1/customizer-setting";
597
598 // Remote and local API URLs
599 $url_customizer = $demo;
600 $url_local_customizer = plugin_dir_url(__FILE__) . 'demo/customizer/casanova/casanova.json';
601
602 // Make remote request using wp_remote_get
603 $response_customizer = wp_remote_get($url_customizer);
604
605 // Check for errors
606 if (is_wp_error($response_customizer)) {
607 echo esc_html('Error: ' . $response_customizer->get_error_message());
608 wp_die();
609 }
610
611 // Get the HTTP response code
612 $http_code_customizer = wp_remote_retrieve_response_code($response_customizer);
613
614 // Check for errors based on HTTP response code
615 if ($http_code_customizer >= 400) {
616 // The response code is 400 or greater, switch to the backup URL
617 $response_customizer = wp_remote_get($url_local_customizer);
618 }
619
620 // Decode the response body
621 $data = json_decode(wp_remote_retrieve_body($response_customizer), true);
622
623 // Data checks.
624 if (!is_array($data)) {
625 esc_html_e('An error occurred while importing the customizer! The imported data is not valid.', 'solace-extra');
626 wp_die();
627 }
628
629 // If wp_css is set then import it.
630 if (function_exists('wp_update_custom_css_post') && isset($data['wp_css']) && '' !== $data['wp_css']) {
631 wp_update_custom_css_post($data['wp_css']);
632 }
633
634 // Loop through the mods.
635 foreach ($data as $key => $val) {
636 if ($key === 'solace_menu_locations') {
637 continue;
638 }
639
640 // Save the mod.
641 set_theme_mod($key, $val);
642 }
643
644 // Set menu locations
645 if (isset($data['solace_menu_locations'])) {
646 foreach ($data['solace_menu_locations'] as $menuLocation) {
647 if ($menuLocation['location'] && $menuLocation['menu_slug']) {
648 $get_slug_menu = $menuLocation['menu_slug'];
649 $menu = wp_get_nav_menu_object($get_slug_menu);
650 if ($menu && $menu->slug == $get_slug_menu) {
651 $locations = get_nav_menu_locations();
652 $locations[$menuLocation['location']] = $menu->term_id;
653 set_theme_mod('nav_menu_locations', $locations);
654 }
655 }
656 }
657 }
658
659 $api_url = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo_name . '/wp-json/solace/v1/customizer-setting?timestamp=' . time();
660 $response = wp_remote_get($api_url);
661
662 if (!is_wp_error($response)) {
663 $data = json_decode(wp_remote_retrieve_body($response), true);
664 if ($data) {
665 $remote_logo_header_url = $data['logourl'];
666 $this->download_and_update_logo_logo_from_live_url($remote_logo_header_url);
667
668 $remote_logo_footer_url = $data['logofooterurl'];
669 $this->download_and_update_logo_footer_logo_from_live_url($remote_logo_footer_url);
670 }
671 }
672
673 // Define the remote URL to the XML file.
674 $demo_url = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . "wp-content/uploads/demolist/" . $get_demo_name . "/" . $get_demo_name . ".xml";
675
676 // Make a GET request to download the file.
677 $response = wp_remote_get( $demo_url );
678
679 // Check if the request was successful and the response code is 200 (OK).
680 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) == 200 ) {
681
682 // Get the body/content of the response.
683 $body = wp_remote_retrieve_body( $response );
684
685 // Get the upload directory path.
686 $upload_dir = wp_upload_dir();
687
688 // Set the local file path to save the downloaded XML.
689 $local_path = trailingslashit( $upload_dir['path'] ) . $get_demo_name . '.xml';
690
691 // Save the file to the server.
692 file_put_contents( $local_path, $body );
693
694 // Instantiate your custom importer and import from the local file.
695 $importer = new Solace_Extra_WP_Import();
696 $importer->fetch_attachments = true;
697 $importer->import( $local_path );
698 }
699
700 // Remote and local API URLs
701 $url_setting_options = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo_name . '/wp-json/solace/v1/setting-options';
702
703 // Make remote request using wp_remote_get
704 $response_setting_options = wp_remote_get($url_setting_options);
705
706 // Check for errors
707 if ( is_wp_error( $response_setting_options ) ) {
708 echo esc_html( 'Error setting options: ' . $response_setting_options->get_error_message() );
709 wp_die();
710 }
711
712 // Decode the response body
713 $data_setting_options = json_decode( wp_remote_retrieve_body( $response_setting_options ), true );
714
715 // Data checks.
716 if ( ! is_array( $data_setting_options ) ) {
717 esc_html_e( 'An error occurred while importing the setting options!', 'solace-extra' );
718 wp_die();
719 }
720
721 // Fix products sale shortcode.
722 if ( class_exists( 'WooCommerce' ) ) {
723 global $wpdb;
724
725 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
726 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_posts'");
727
728 // looping products
729 foreach ( $post_ids as $id ) {
730 $product = wc_get_product( $id );
731 if ( $product && !is_wp_error( $product ) ) {
732
733 $sale_price = $product->get_sale_price();
734
735 if ( $sale_price ) {
736 $new_regular_price = $product->get_regular_price();
737 $product->set_regular_price( $new_regular_price + 1 );
738
739 $product->save();
740
741 $product->set_regular_price( $new_regular_price - 1 );
742
743 $product->save();
744 }
745 }
746 }
747 }
748
749 // Ensure the 'plugin.php' file is loaded to use WordPress plugin functions.
750 require_once ABSPATH . 'wp-admin/includes/plugin.php';
751
752 // Define the plugin path for LiteSpeed Cache.
753 $plugin = 'litespeed-cache/litespeed-cache.php';
754 $status_plugin_litespeed = get_option( 'solace_extra_deactivate_litespeed', false );
755
756 // Check status plugin litespeed.
757 if ( $status_plugin_litespeed ) {
758 // Check if the plugin is already active.
759 if ( ! is_plugin_active( $plugin ) ) {
760 // Attempt to activate the plugin.
761 $result = activate_plugin( $plugin );
762
763 // Check if the activation was successful or if an error occurred.
764 if ( is_wp_error( $result ) ) {
765 // Output the error message if activation failed.
766 // esc_html_e( 'Failed to activate the plugin: ', 'solace-extra' ) . $result->get_error_message();
767 } else {
768 // Output a success message if activation was successful.
769 // esc_html_e( 'LiteSpeed Cache plugin has been successfully activated.', 'solace-extra' );
770 }
771 } else {
772 // Output a message if the plugin is already active.
773 // esc_html_e( 'LiteSpeed Cache plugin is already active.', 'solace-extra' );
774 }
775 }
776
777 esc_html_e('Success! Import Customizer...', 'solace-extra');
778
779 wp_die();
780 }
781
782 public function download_and_update_logo_logo_from_live_url($remote_url) {
783 $remote_url = esc_url_raw($remote_url);
784
785 if (!filter_var($remote_url, FILTER_VALIDATE_URL)) {
786 esc_html_e('Invalid URL.', 'solace-extra');
787 return;
788 }
789
790 $allowed_domains = ['solacewp.com', 'www.solacewp.com'];
791 $parsed_url = wp_parse_url($remote_url);
792 if (empty($parsed_url['host']) || !in_array($parsed_url['host'], $allowed_domains, true)) {
793 esc_html_e('Domain not allowed.', 'solace-extra');
794 return;
795 }
796
797 $response = wp_remote_get($remote_url, ['timeout' => 10]);
798 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
799 esc_html_e('Failed to download file.', 'solace-extra');
800 return;
801 }
802
803 $file_content = wp_remote_retrieve_body($response);
804 $upload_dir = wp_upload_dir();
805 $file_name = sanitize_file_name(basename($remote_url));
806 $file_path = $upload_dir['path'] . '/' . $file_name;
807
808 if (!function_exists('wp_filesystem')) {
809 esc_html_e('Failed to initialize WP_Filesystem.', 'solace-extra');
810 return;
811 }
812
813 global $wp_filesystem;
814 WP_Filesystem();
815 $file_saved = $wp_filesystem->put_contents($file_path, $file_content, FS_CHMOD_FILE);
816
817 if ($file_saved === false) {
818 esc_html_e('Failed to save file.', 'solace-extra');
819 return;
820 }
821
822 $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
823 $file_type = wp_check_filetype($file_name);
824 if (!in_array($file_type['type'], $allowed_types, true)) {
825 esc_html_e('File type not allowed.', 'solace-extra');
826 wp_delete_file($file_path);
827 return;
828 }
829
830 $attachment = [
831 'post_title' => pathinfo($file_name, PATHINFO_FILENAME),
832 'post_mime_type' => $file_type['type'],
833 'post_status' => 'inherit',
834 ];
835
836 $attachment_id = wp_insert_attachment($attachment, $file_path);
837 if (!$attachment_id) {
838 wp_delete_file($file_path);
839 esc_html_e('Failed to save attachment.', 'solace-extra');
840 return;
841 }
842
843 require_once ABSPATH . 'wp-admin/includes/image.php';
844 $attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path);
845 wp_update_attachment_metadata($attachment_id, $attachment_data);
846
847 set_theme_mod('logo_logo', json_encode(['light' => $attachment_id, 'dark' => $attachment_id, 'same' => true]));
848
849 echo esc_html__('Media successfully downloaded and saved.', 'solace-extra');
850 }
851
852
853 public function download_and_update_logo_footer_logo_from_live_url($remote_url) {
854 $remote_url = esc_url_raw($remote_url);
855
856 if (!filter_var($remote_url, FILTER_VALIDATE_URL)) {
857 esc_html_e('Invalid URL.', 'solace-extra');
858 return;
859 }
860
861 $allowed_domains = ['solacewp.com', 'www.solacewp.com'];
862 $parsed_url = wp_parse_url($remote_url);
863 if (empty($parsed_url['host']) || !in_array($parsed_url['host'], $allowed_domains, true)) {
864 esc_html_e('Domain not allowed.', 'solace-extra');
865 return;
866 }
867
868 $response = wp_remote_get($remote_url, ['timeout' => 10]);
869 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
870 esc_html_e('Failed to download file.', 'solace-extra');
871 return;
872 }
873
874 $file_content = wp_remote_retrieve_body($response);
875 $upload_dir = wp_upload_dir();
876 $file_name = sanitize_file_name(basename($remote_url));
877 $file_path = $upload_dir['path'] . '/' . $file_name;
878
879 if (!function_exists('wp_filesystem')) {
880 esc_html_e('Failed to initialize WP_Filesystem.', 'solace-extra');
881 return;
882 }
883
884 global $wp_filesystem;
885 WP_Filesystem();
886 $file_saved = $wp_filesystem->put_contents($file_path, $file_content, FS_CHMOD_FILE);
887
888 if ($file_saved === false) {
889 esc_html_e('Failed to save file.', 'solace-extra');
890 return;
891 }
892
893 $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
894 $file_type = wp_check_filetype($file_name);
895 if (!in_array($file_type['type'], $allowed_types, true)) {
896 esc_html_e('File type not allowed.', 'solace-extra');
897 wp_delete_file($file_path);
898 return;
899 }
900
901 $attachment = [
902 'post_title' => pathinfo($file_name, PATHINFO_FILENAME),
903 'post_mime_type' => $file_type['type'],
904 'post_status' => 'inherit',
905 ];
906
907 $attachment_id = wp_insert_attachment($attachment, $file_path);
908 if (!$attachment_id) {
909 wp_delete_file($file_path);
910 esc_html_e('Failed to save attachment.', 'solace-extra');
911 return;
912 }
913
914 require_once ABSPATH . 'wp-admin/includes/image.php';
915 $attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path);
916 wp_update_attachment_metadata($attachment_id, $attachment_data);
917
918 $logo_logo_data = json_encode(['light' => $attachment_id, 'dark' => $attachment_id, 'same' => true]);
919
920 if ($attachment_id) {
921 update_post_meta($attachment_id, 'solace_import_images', true);
922 }
923
924 set_theme_mod('logo-footer_logo', $logo_logo_data);
925
926 echo esc_html__('Media successfully downloaded and saved.', 'solace-extra');
927 }
928
929
930
931 /**
932 * Available widgets
933 *
934 * Gather site's widgets into array with ID base, name, etc.
935 * Used by export and import functions.
936 *
937 * @since 0.4
938 * @global array $wp_registered_widget_updates
939 * @return array Widget information
940 */
941 public function available_widgets()
942 {
943 global $wp_registered_widget_controls;
944
945 $widget_controls = $wp_registered_widget_controls;
946
947 $available_widgets = array();
948
949 foreach ($widget_controls as $widget) {
950 // No duplicates.
951 if (!empty($widget['id_base']) && !isset($available_widgets[$widget['id_base']])) {
952 $available_widgets[$widget['id_base']]['id_base'] = $widget['id_base'];
953 $available_widgets[$widget['id_base']]['name'] = $widget['name'];
954 }
955 }
956
957 return apply_filters('available_widgets', $available_widgets);
958 }
959
960 /**
961 * Solace Ajax Import Widget
962 */
963 public function call_ajax_import_widget()
964 {
965 // Verify nonce
966 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
967 $response = array('error' => 'Invalid nonce!');
968 echo wp_json_encode($response);
969 wp_die();
970 }
971
972 // Check current user
973 if ( ! current_user_can( 'manage_options' ) ) {
974 wp_send_json_error( array( 'error' => 'Unauthorized' ) );
975 }
976
977 // Get menus data from the API
978 $get_demo_name = ! empty( $_POST['getDemo'] ) ? sanitize_title( wp_unslash( $_POST['getDemo'] ) ) : '';
979
980 $demo = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo_name . "/wp-json/solace/v1/widgets";
981
982 // Remote API URL
983 $url_widget = $demo;
984
985 // Make remote request using wp_remote_get
986 $response_widget = wp_remote_get($url_widget);
987
988 // Check for errors
989 if (is_wp_error($response_widget)) {
990 echo esc_html( 'Error: ' . $response_widget->get_error_message() );
991 wp_die();
992 }
993
994 // Get the HTTP response code
995 $http_code_widget = wp_remote_retrieve_response_code($response_widget);
996
997 // Check for errors based on HTTP response code
998 if ($http_code_widget >= 400) {
999 // The response code is 400 or greater, handle accordingly
1000 // For example, switch to a backup URL
1001 // $url_local_widget = plugin_dir_url(__FILE__) . 'demo/customizer/casanova/casanova.json';
1002 // $response_widget = wp_remote_get($url_local_widget);
1003 }
1004
1005 // Decode the response body
1006 $data = json_decode(wp_remote_retrieve_body($response_widget), true);
1007 $data = (object) $data;
1008
1009 // Filter Widget menu
1010 foreach ($data as $widget_area => $widgets) {
1011 foreach ($widgets as $widget_id => $item) {
1012 // Check if the 'nav_menu_slug' index exists in the $item array
1013 if (isset($item['nav_menu_slug'])) {
1014 // Get the menu slug from the data
1015 $menu_slug = $item['nav_menu_slug'];
1016
1017 // Get the menu object based on the menu slug
1018 $menu = get_term_by('slug', $menu_slug, 'nav_menu');
1019
1020 // Check if the menu is found before updating the values
1021 if ($menu) {
1022 // Update 'nav_menu' and 'nav_menu_id' with the term_id of the menu
1023 $data->$widget_area[$widget_id]['nav_menu'] = $menu->term_id;
1024 $data->$widget_area[$widget_id]['nav_menu_id'] = $menu->term_id;
1025 }
1026 }
1027 }
1028 }
1029
1030 $this->import_widgets($data);
1031
1032 esc_html_e('Success! Import Widgets...', 'solace-extra');
1033
1034 wp_die();
1035 }
1036
1037 /**
1038 * Import widget JSON data
1039 *
1040 * @since 0.4
1041 * @global array $wp_registered_sidebars
1042 * @param object $data JSON widget data from .wie file.
1043 * @return array Results array
1044 */
1045 function import_widgets($data)
1046 {
1047 // Verify nonce
1048 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
1049 $response = array('error' => 'Invalid nonce!');
1050 echo wp_json_encode($response);
1051 wp_die();
1052 }
1053
1054 global $wp_registered_sidebars;
1055
1056 // Have valid data?
1057 // If no data or could not decode.
1058 if (empty($data) || !is_object($data)) {
1059 wp_die(
1060 esc_html__('Import data is invalid.', 'solace-extra'),
1061 '',
1062 array(
1063 'back_link' => true,
1064 )
1065 );
1066 }
1067
1068 // Hook before import.
1069 do_action('before_import');
1070 $data = apply_filters('import_widgets', $data);
1071
1072 // Get all available widgets site supports.
1073 $available_widgets = $this->available_widgets();
1074
1075 // Get all existing widget instances.
1076 $widget_instances = array();
1077 foreach ($available_widgets as $widget_data) {
1078 $widget_instances[$widget_data['id_base']] = get_option('widget_' . $widget_data['id_base']);
1079 }
1080
1081 // Begin results.
1082 $results = array();
1083
1084 // Loop import data's sidebars.
1085 foreach ($data as $sidebar_id => $widgets) {
1086 // Skip inactive widgets (should not be in export file).
1087 if ('wp_inactive_widgets' === $sidebar_id) {
1088 continue;
1089 }
1090
1091 // Check if sidebar is available on this site.
1092 // Otherwise add widgets to inactive, and say so.
1093 if (isset($wp_registered_sidebars[$sidebar_id])) {
1094 $sidebar_available = true;
1095 $use_sidebar_id = $sidebar_id;
1096 $sidebar_message_type = 'success';
1097 $sidebar_message = '';
1098 } else {
1099 $sidebar_available = false;
1100 $use_sidebar_id = 'wp_inactive_widgets'; // Add to inactive if sidebar does not exist in theme.
1101 $sidebar_message_type = 'error';
1102 $sidebar_message = esc_html__('Widget area does not exist in theme (using Inactive)', 'solace-extra');
1103 }
1104
1105 // Result for sidebar
1106 // Sidebar name if theme supports it; otherwise ID.
1107 $results[$sidebar_id]['name'] = !empty($wp_registered_sidebars[$sidebar_id]['name']) ? $wp_registered_sidebars[$sidebar_id]['name'] : $sidebar_id;
1108 $results[$sidebar_id]['message_type'] = $sidebar_message_type;
1109 $results[$sidebar_id]['message'] = $sidebar_message;
1110 $results[$sidebar_id]['widgets'] = array();
1111
1112 // Loop widgets.
1113 foreach ($widgets as $widget_instance_id => $widget) {
1114 $fail = false;
1115
1116 // Get id_base (remove -# from end) and instance ID number.
1117 $id_base = preg_replace('/-[0-9]+$/', '', $widget_instance_id);
1118 $instance_id_number = str_replace($id_base . '-', '', $widget_instance_id);
1119
1120 // Does site support this widget?
1121 if (!$fail && !isset($available_widgets[$id_base])) {
1122 $fail = true;
1123 $widget_message_type = 'error';
1124 $widget_message = esc_html__('Site does not support widget', 'solace-extra'); // Explain why widget not imported.
1125 }
1126
1127 // Filter to modify settings object before conversion to array and import
1128 // Leave this filter here for backwards compatibility with manipulating objects (before conversion to array below)
1129 // Ideally the newer wie_widget_settings_array below will be used instead of this.
1130 $widget = apply_filters('wie_widget_settings', $widget);
1131
1132 // Convert multidimensional objects to multidimensional arrays
1133 // Some plugins like Jetpack Widget Visibility store settings as multidimensional arrays
1134 // Without this, they are imported as objects and cause fatal error on Widgets page
1135 // If this creates problems for plugins that do actually intend settings in objects then may need to consider other approach: https://wordpress.org/support/topic/problem-with-array-of-arrays
1136 // It is probably much more likely that arrays are used than objects, however.
1137 $widget = json_decode(wp_json_encode($widget), true);
1138
1139 // Filter to modify settings array
1140 // This is preferred over the older wie_widget_settings filter above
1141 // Do before identical check because changes may make it identical to end result (such as URL replacements).
1142 $widget = apply_filters('wie_widget_settings_array', $widget);
1143
1144 // Does widget with identical settings already exist in same sidebar?
1145 if (!$fail && isset($widget_instances[$id_base])) {
1146 // Get existing widgets in this sidebar.
1147 $sidebars_widgets = get_option('sidebars_widgets');
1148 $sidebar_widgets = isset($sidebars_widgets[$use_sidebar_id]) ? $sidebars_widgets[$use_sidebar_id] : array(); // Check Inactive if that's where will go.
1149
1150 // Loop widgets with ID base.
1151 $single_widget_instances = !empty($widget_instances[$id_base]) ? $widget_instances[$id_base] : array();
1152 foreach ($single_widget_instances as $check_id => $check_widget) {
1153 // Is widget in same sidebar and has identical settings?
1154 if (in_array("$id_base-$check_id", $sidebar_widgets, true) && (array) $widget === $check_widget) {
1155 $fail = true;
1156 $widget_message_type = 'warning';
1157
1158 // Explain why widget not imported.
1159 $widget_message = esc_html__('Widget already exists', 'solace-extra');
1160
1161 break;
1162 }
1163 }
1164 }
1165
1166 // No failure.
1167 if (!$fail) {
1168 // Add widget instance
1169 $single_widget_instances = get_option('widget_' . $id_base); // All instances for that widget ID base, get fresh every time.
1170 $single_widget_instances = !empty($single_widget_instances) ? $single_widget_instances : array(
1171 '_multiwidget' => 1, // Start fresh if have to.
1172 );
1173 $single_widget_instances[] = $widget; // Add it.
1174
1175 // Get the key it was given.
1176 end($single_widget_instances);
1177 $new_instance_id_number = key($single_widget_instances);
1178
1179 // If key is 0, make it 1
1180 // When 0, an issue can occur where adding a widget causes data from other widget to load,
1181 // and the widget doesn't stick (reload wipes it).
1182 if ('0' === strval($new_instance_id_number)) {
1183 $new_instance_id_number = 1;
1184 $single_widget_instances[$new_instance_id_number] = $single_widget_instances[0];
1185 unset($single_widget_instances[0]);
1186 }
1187
1188 // Move _multiwidget to end of array for uniformity.
1189 if (isset($single_widget_instances['_multiwidget'])) {
1190 $multiwidget = $single_widget_instances['_multiwidget'];
1191 unset($single_widget_instances['_multiwidget']);
1192 $single_widget_instances['_multiwidget'] = $multiwidget;
1193 }
1194
1195 // Update option with new widget.
1196 update_option('widget_' . $id_base, $single_widget_instances);
1197
1198 // Assign widget instance to sidebar.
1199 // Which sidebars have which widgets, get fresh every time.
1200 $sidebars_widgets = get_option('sidebars_widgets');
1201
1202 // Avoid rarely fatal error when the option is an empty string
1203 // https://github.com/churchthemes/widget-importer-exporter/pull/11.
1204 if (!$sidebars_widgets) {
1205 $sidebars_widgets = array();
1206 }
1207
1208 // Use ID number from new widget instance.
1209 $new_instance_id = $id_base . '-' . $new_instance_id_number;
1210
1211 // Add new instance to sidebar.
1212 $sidebars_widgets[$use_sidebar_id][] = $new_instance_id;
1213
1214 // Save the amended data.
1215 update_option('sidebars_widgets', $sidebars_widgets);
1216
1217 // After widget import action.
1218 $after_widget_import = array(
1219 'sidebar' => $use_sidebar_id,
1220 'sidebar_old' => $sidebar_id,
1221 'widget' => $widget,
1222 'widget_type' => $id_base,
1223 'widget_id' => $new_instance_id,
1224 'widget_id_old' => $widget_instance_id,
1225 'widget_id_num' => $new_instance_id_number,
1226 'widget_id_num_old' => $instance_id_number,
1227 );
1228 do_action('after_widget_import', $after_widget_import);
1229
1230 // Success message.
1231 if ($sidebar_available) {
1232 $widget_message_type = 'success';
1233 $widget_message = esc_html__('Imported', 'solace-extra');
1234 } else {
1235 $widget_message_type = 'warning';
1236 $widget_message = esc_html__('Imported to Inactive', 'solace-extra');
1237 }
1238 }
1239
1240 // Result for widget instance
1241 $results[$sidebar_id]['widgets'][$widget_instance_id]['name'] = isset($available_widgets[$id_base]['name']) ? $available_widgets[$id_base]['name'] : $id_base; // Widget name or ID if name not available (not supported by site).
1242 $results[$sidebar_id]['widgets'][$widget_instance_id]['title'] = !empty($widget['title']) ? $widget['title'] : esc_html__('No Title', 'solace-extra'); // Show "No Title" if widget instance is untitled.
1243 $results[$sidebar_id]['widgets'][$widget_instance_id]['message_type'] = $widget_message_type;
1244 $results[$sidebar_id]['widgets'][$widget_instance_id]['message'] = $widget_message;
1245 }
1246 }
1247
1248 // Hook after import.
1249 do_action('after_import');
1250 }
1251
1252 /**
1253 * Deletes attachments imported during the Solace import process.
1254 */
1255 public function delete_sidebars_widgets()
1256 {
1257 update_option('sidebars_widgets', array());
1258 }
1259
1260 /**
1261 * Deletes customizers imported during the Solace import process.
1262 */
1263 public function delete_customizers()
1264 {
1265 // Remove all them mods
1266 remove_theme_mods();
1267 }
1268
1269 /**
1270 * Deletes attachments imported during the Solace import process.
1271 */
1272 public function delete_imported_attachments()
1273 {
1274 global $wpdb;
1275
1276 $meta_key = 'solace_import_images';
1277
1278 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1279 $post_ids = $wpdb->get_results(
1280 $wpdb->prepare(
1281 "SELECT *
1282 FROM {$wpdb->prefix}postmeta
1283 WHERE CONVERT(meta_key USING utf8) LIKE %s",
1284 '%' . $wpdb->esc_like($meta_key) . '%'
1285 )
1286 );
1287
1288 // Remove attachment
1289 if (!empty($post_ids)) {
1290 foreach ($post_ids as $id) {
1291 wp_delete_attachment($id->post_id, true);
1292 }
1293 }
1294 }
1295
1296 /**
1297 * Deletes imported terms from the 'category' taxonomy based on a specific term meta key.
1298 */
1299 public function delete_imported_terms_category()
1300 {
1301 global $wpdb;
1302
1303 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1304 $term_ids = $wpdb->get_col("SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key='solace_imported_term_category'");
1305
1306 foreach ($term_ids as $id) {
1307 wp_delete_term($id, 'category');
1308 }
1309 }
1310
1311 /**
1312 * Deletes imported terms from the 'product_cat' taxonomy based on a specific term meta key.
1313 */
1314 public function delete_imported_terms_product_cat()
1315 {
1316 global $wpdb;
1317
1318 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1319 $term_ids = $wpdb->get_col("SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key='solace_imported_term_product_cat'");
1320
1321 foreach ($term_ids as $id) {
1322 wp_delete_term($id, 'product_cat');
1323 }
1324 }
1325
1326 /**
1327 * Deletes imported terms from the 'product_tag' taxonomy based on a specific term meta key.
1328 */
1329 public function delete_imported_terms_product_tag()
1330 {
1331 global $wpdb;
1332
1333 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1334 $term_ids = $wpdb->get_col("SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key='solace_imported_term_product_tag'");
1335
1336 foreach ($term_ids as $id) {
1337 wp_delete_term($id, 'product_tag');
1338 }
1339 }
1340
1341 /**
1342 * Deletes imported terms from the 'post_tag' taxonomy based on a specific term meta key.
1343 */
1344 public function delete_imported_terms_post_tag()
1345 {
1346 global $wpdb;
1347
1348 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1349 $term_ids = $wpdb->get_col("SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key='solace_imported_term_post_tag'");
1350
1351 foreach ($term_ids as $id) {
1352 wp_delete_term($id, 'post_tag');
1353 }
1354 }
1355
1356 /**
1357 * Deletes posts imported during the Solace import process.
1358 */
1359 public function delete_imported_posts()
1360 {
1361 global $wpdb;
1362
1363 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1364 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_posts'");
1365
1366 // Remove posts
1367 foreach ($post_ids as $id) {
1368 $thumbnail_id = get_post_thumbnail_id($id);
1369 wp_delete_attachment($thumbnail_id, true);
1370 wp_delete_post($id, true);
1371 }
1372 }
1373
1374 /**
1375 * Deletes menu items imported during the Solace import process.
1376 */
1377 public function delete_imported_menu_items()
1378 {
1379 global $wpdb;
1380
1381 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1382 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_menu_item'");
1383
1384 // Remove menu items.
1385 foreach ($post_ids as $id) {
1386 wp_delete_post($id, true);
1387 }
1388 }
1389
1390 /**
1391 * Deletes products imported during the Solace import process.
1392 */
1393 public function delete_imported_products()
1394 {
1395 global $wpdb;
1396
1397 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1398 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_products'");
1399
1400 // Remove products
1401 foreach ($post_ids as $id) {
1402 $thumbnail_id = get_post_thumbnail_id($id);
1403 wp_delete_attachment($thumbnail_id, true);
1404 wp_delete_post($id, true);
1405 }
1406 }
1407
1408 /**
1409 * Deletes pages imported during the Solace import process.
1410 */
1411 public function delete_imported_pages()
1412 {
1413 global $wpdb;
1414
1415 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1416 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_pages'");
1417
1418 // Remove pages
1419 foreach ($post_ids as $id) {
1420 $thumbnail_id = get_post_thumbnail_id($id);
1421 wp_delete_attachment($thumbnail_id, true);
1422 wp_delete_post($id, true);
1423 }
1424 }
1425
1426 /**
1427 * Deletes pages imported during the Solace import process.
1428 */
1429 public function delete_imported_site_builder()
1430 {
1431 global $wpdb;
1432
1433 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
1434 $post_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_site_builder'");
1435
1436 // Remove pages
1437 foreach ($post_ids as $id) {
1438 $thumbnail_id = get_post_thumbnail_id($id);
1439 wp_delete_attachment($thumbnail_id, true);
1440 wp_delete_post($id, true);
1441 }
1442 }
1443
1444 /**
1445 * Delete all posts of the custom post type 'solace-sitebuilder'
1446 * that have the meta key 'solace_wp_importer_site_builder'.
1447 *
1448 * This function uses WordPress functions instead of direct SQL queries
1449 * to maintain compatibility with coding standards and caching.
1450 *
1451 * @return void
1452 */
1453 function delete_wp_importer_post_type_sitebuilder() {
1454 // Query posts using get_posts() with meta_query
1455 $posts = get_posts(array(
1456 'post_type' => 'solace-sitebuilder',
1457 'posts_per_page' => -1, // Get all matching posts
1458 'fields' => 'ids', // Return only IDs
1459 'post_status' => 'any',
1460 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
1461 'meta_query' => array(
1462 array(
1463 'key' => 'solace_wp_importer_site_builder',
1464 'compare' => 'EXISTS',
1465 ),
1466 ),
1467 ));
1468
1469 // Delete each post permanently.
1470 foreach ($posts as $post_id) {
1471 wp_delete_post($post_id, true); // true = force delete (skip Trash).
1472 }
1473 }
1474
1475 /**
1476 * Deletes all Elementor templates.
1477 *
1478 * This function retrieves all Elementor template IDs from the WordPress database
1479 * and deletes each template using the wp_delete_post function.
1480 *
1481 * @return void
1482 */
1483 function delete_all_elementor_templates() {
1484 global $wpdb;
1485
1486 remove_all_actions( 'wp_trash_post' );
1487 remove_all_actions( 'before_delete_post' );
1488
1489 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all elementor templates.
1490 $template_ids = $wpdb->get_col("
1491 SELECT {$wpdb->posts}.ID
1492 FROM {$wpdb->posts}
1493 INNER JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
1494 WHERE {$wpdb->posts}.post_type = 'elementor_library'
1495 AND {$wpdb->postmeta}.meta_key = 'solace_import_elementor_kit'
1496 ");
1497 // $template_ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='solace_import_elementor_kit'");
1498
1499 // Loop through each template ID and delete the template.
1500 foreach ( $template_ids as $id ) {
1501 wp_delete_post( $id, true );
1502 }
1503 }
1504
1505 /**
1506 * Deletes previously imported attachments and posts as part of the cleanup process.
1507 */
1508 public function delete_previously_imported()
1509 {
1510 if ( ! current_user_can( 'manage_options' ) ) {
1511 wp_send_json_error( array( 'error' => 'Unauthorized!' ), 403 );
1512 }
1513
1514 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce(
1515 sanitize_text_field( wp_unslash( $_POST['nonce'] ) ),
1516 'ajax-nonce'
1517 ) ) {
1518 wp_send_json_error( array( 'error' => 'Invalid nonce!' ), 403 );
1519 }
1520
1521 $this->delete_wp_importer_post_type_sitebuilder();
1522 $this->delete_imported_terms_category();
1523 $this->delete_imported_terms_post_tag();
1524 $this->delete_imported_attachments();
1525 $this->delete_imported_posts();
1526 $this->delete_imported_pages();
1527
1528 $this->delete_imported_terms_product_cat();
1529 $this->delete_imported_terms_product_tag();
1530 $this->delete_imported_products();
1531
1532 $this->delete_elementor_current_setting();
1533
1534
1535 wp_die();
1536 }
1537
1538 /**
1539 * Run the "delete previously imported" cleanup and return, without any
1540 * AJAX/nonce/wp_die plumbing.
1541 *
1542 * delete_previously_imported() above is an admin-ajax handler: it does its
1543 * own capability + 'ajax-nonce' check and ends with wp_die(), so it can't be
1544 * reused from other contexts. This variant performs the exact same deletion
1545 * sequence but is safe to call from the REST bridge, which authorizes the
1546 * request itself (manage_options) and needs a normal return value.
1547 *
1548 * Only content tagged with the importer's own meta keys is removed
1549 * (solace_import_pages / _posts / _products / _images, solace_imported_term_*,
1550 * and the solace-sitebuilder CPT) — hand-authored content is left untouched.
1551 *
1552 * @return void
1553 */
1554 public function delete_previously_imported_data() {
1555 $this->delete_wp_importer_post_type_sitebuilder();
1556 $this->delete_imported_terms_category();
1557 $this->delete_imported_terms_post_tag();
1558 $this->delete_imported_attachments();
1559 $this->delete_imported_posts();
1560 $this->delete_imported_pages();
1561
1562 $this->delete_imported_terms_product_cat();
1563 $this->delete_imported_terms_product_tag();
1564 $this->delete_imported_products();
1565
1566 $this->delete_elementor_current_setting();
1567 }
1568
1569 function delete_elementor_current_setting(){
1570 $custom_typography = [
1571 [
1572 '_id' => 'primary',
1573 'title' => 'Smaller',
1574 'typography_typography' => 'custom',
1575 'typography_font_family' => '',
1576 'typography_font_size' => [
1577 'unit' => '',
1578 'size' => '',
1579 'sizes' => []
1580 ],
1581 'typography_font_weight' => '',
1582 'typography_text_transform' => '',
1583 'typography_line_height' => [
1584 'unit' => '',
1585 'size' => '',
1586 'sizes' => []
1587 ],
1588 'typography_letter_spacing' => [
1589 'unit' => '',
1590 'size' => '',
1591 'sizes' => []
1592 ],
1593 'typography_font_size_tablet' => [
1594 'unit' => '',
1595 'size' => '',
1596 'sizes' => []
1597 ],
1598 'typography_font_size_mobile' => [
1599 'unit' => '',
1600 'size' => '',
1601 'sizes' => []
1602 ],
1603 'typography_line_height_tablet' => [
1604 'unit' => '',
1605 'size' => '',
1606 'sizes' => []
1607 ],
1608 'typography_line_height_mobile' => [
1609 'unit' => '',
1610 'size' => '',
1611 'sizes' => []
1612 ],
1613 'typography_letter_spacing_tablet' => [
1614 'unit' => '',
1615 'size' => '',
1616 'sizes' => []
1617 ],
1618 'typography_letter_spacing_mobile' => [
1619 'unit' => '',
1620 'size' => '',
1621 'sizes' => []
1622 ]
1623 ],
1624 [
1625 '_id' => 'secondary',
1626 'title' => 'Logo Title / Subtitle',
1627 'typography_typography' => 'custom',
1628 'typography_font_family' => '',
1629 'typography_font_size' => [
1630 'unit' => '',
1631 'size' => '',
1632 'sizes' => []
1633 ],
1634 'typography_font_weight' => '',
1635 'typography_text_transform' => '',
1636 'typography_line_height' => [
1637 'unit' => '',
1638 'size' => '',
1639 'sizes' => []
1640 ],
1641 'typography_letter_spacing' => [
1642 'unit' => '',
1643 'size' => '',
1644 'sizes' => []
1645 ],
1646 'typography_font_size_tablet' => [
1647 'unit' => '',
1648 'size' => '',
1649 'sizes' => []
1650 ],
1651 'typography_font_size_mobile' => [
1652 'unit' => '',
1653 'size' => '',
1654 'sizes' => []
1655 ],
1656 'typography_line_height_tablet' => [
1657 'unit' => '',
1658 'size' => '',
1659 'sizes' => []
1660 ],
1661 'typography_line_height_mobile' => [
1662 'unit' => '',
1663 'size' => '',
1664 'sizes' => []
1665 ],
1666 'typography_letter_spacing_tablet' => [
1667 'unit' => '',
1668 'size' => '',
1669 'sizes' => []
1670 ],
1671 'typography_letter_spacing_mobile' => [
1672 'unit' => '',
1673 'size' => '',
1674 'sizes' => []
1675 ]
1676 ],
1677 [
1678 '_id' => 'text',
1679 'title' => 'Solace Base',
1680 'typography_typography' => 'custom',
1681 'typography_font_family' => '',
1682 'typography_font_size' => [
1683 'unit' => '',
1684 'size' => '',
1685 'sizes' => []
1686 ],
1687 'typography_font_weight' => '',
1688 'typography_text_transform' => '',
1689 'typography_line_height' => [
1690 'unit' => '',
1691 'size' => '',
1692 'sizes' => []
1693 ],
1694 'typography_letter_spacing' => [
1695 'unit' => '',
1696 'size' => '',
1697 'sizes' => []
1698 ],
1699 'typography_font_size_tablet' => [
1700 'unit' => '',
1701 'size' => '',
1702 'sizes' => []
1703 ],
1704 'typography_font_size_mobile' => [
1705 'unit' => '',
1706 'size' => '',
1707 'sizes' => []
1708 ],
1709 'typography_line_height_tablet' => [
1710 'unit' => '',
1711 'size' => '',
1712 'sizes' => []
1713 ],
1714 'typography_line_height_mobile' => [
1715 'unit' => '',
1716 'size' => '',
1717 'sizes' => []
1718 ],
1719 'typography_letter_spacing_tablet' => [
1720 'unit' => '',
1721 'size' => '',
1722 'sizes' => []
1723 ],
1724 'typography_letter_spacing_mobile' => [
1725 'unit' => '',
1726 'size' => '',
1727 'sizes' => []
1728 ]
1729 ],
1730 [
1731 '_id' => 'accent',
1732 'title' => 'Button',
1733 'typography_typography' => 'custom',
1734 'typography_font_family' => '',
1735 'typography_font_size' => [
1736 'unit' => '',
1737 'size' => '',
1738 'sizes' => []
1739 ],
1740 'typography_font_weight' => '',
1741 'typography_text_transform' => '',
1742 'typography_line_height' => [
1743 'unit' => '',
1744 'size' => '',
1745 'sizes' => []
1746 ],
1747 'typography_letter_spacing' => [
1748 'unit' => '',
1749 'size' => '',
1750 'sizes' => []
1751 ],
1752 'typography_font_size_tablet' => [
1753 'unit' => '',
1754 'size' => '',
1755 'sizes' => []
1756 ],
1757 'typography_font_size_mobile' => [
1758 'unit' => '',
1759 'size' => '',
1760 'sizes' => []
1761 ],
1762 'typography_line_height_tablet' => [
1763 'unit' => '',
1764 'size' => '',
1765 'sizes' => []
1766 ],
1767 'typography_line_height_mobile' => [
1768 'unit' => '',
1769 'size' => '',
1770 'sizes' => []
1771 ],
1772 'typography_letter_spacing_tablet' => [
1773 'unit' => '',
1774 'size' => '',
1775 'sizes' => []
1776 ],
1777 'typography_letter_spacing_mobile' => [
1778 'unit' => '',
1779 'size' => '',
1780 'sizes' => []
1781 ]
1782 ],
1783 [
1784 '_id' => 'solace_body_font_family',
1785 'title' => 'Solace Base',
1786 'typography_typography' => 'custom',
1787 'typography_font_family' => '',
1788 'typography_font_size' => [
1789 'unit' => '',
1790 'size' => '',
1791 'sizes' => []
1792 ],
1793 'typography_font_weight' => '',
1794 'typography_text_transform' => '',
1795 'typography_line_height' => [
1796 'unit' => '',
1797 'size' => '',
1798 'sizes' => []
1799 ],
1800 'typography_letter_spacing' => [
1801 'unit' => '',
1802 'size' => '',
1803 'sizes' => []
1804 ],
1805 'typography_font_size_tablet' => [
1806 'unit' => '',
1807 'size' => '',
1808 'sizes' => []
1809 ],
1810 'typography_font_size_mobile' => [
1811 'unit' => '',
1812 'size' => '',
1813 'sizes' => []
1814 ],
1815 'typography_line_height_tablet' => [
1816 'unit' => '',
1817 'size' => '',
1818 'sizes' => []
1819 ],
1820 'typography_line_height_mobile' => [
1821 'unit' => '',
1822 'size' => '',
1823 'sizes' => []
1824 ],
1825 'typography_letter_spacing_tablet' => [
1826 'unit' => '',
1827 'size' => '',
1828 'sizes' => []
1829 ],
1830 'typography_letter_spacing_mobile' => [
1831 'unit' => '',
1832 'size' => '',
1833 'sizes' => []
1834 ]
1835 ],
1836 [
1837 '_id' => 'solace_h1_font_family_general',
1838 'title' => 'Solace H1',
1839 'typography_typography' => 'custom',
1840 'typography_font_family' => '',
1841 'typography_font_size' => [
1842 'unit' => '',
1843 'size' => '',
1844 'sizes' => []
1845 ],
1846 'typography_font_weight' => '',
1847 'typography_text_transform' => '',
1848 'typography_line_height' => [
1849 'unit' => '',
1850 'size' => '',
1851 'sizes' => []
1852 ],
1853 'typography_letter_spacing' => [
1854 'unit' => '',
1855 'size' => '',
1856 'sizes' => []
1857 ],
1858 'typography_font_size_tablet' => [
1859 'unit' => '',
1860 'size' => '',
1861 'sizes' => []
1862 ],
1863 'typography_font_size_mobile' => [
1864 'unit' => '',
1865 'size' => '',
1866 'sizes' => []
1867 ],
1868 'typography_line_height_tablet' => [
1869 'unit' => '',
1870 'size' => '',
1871 'sizes' => []
1872 ],
1873 'typography_line_height_mobile' => [
1874 'unit' => '',
1875 'size' => '',
1876 'sizes' => []
1877 ],
1878 'typography_letter_spacing_tablet' => [
1879 'unit' => '',
1880 'size' => '',
1881 'sizes' => []
1882 ],
1883 'typography_letter_spacing_mobile' => [
1884 'unit' => '',
1885 'size' => '',
1886 'sizes' => []
1887 ]
1888 ],
1889 [
1890 '_id' => 'solace_h2_font_family_general',
1891 'title' => 'Solace H2',
1892 'typography_typography' => 'custom',
1893 'typography_font_family' => '',
1894 'typography_font_size' => [
1895 'unit' => '',
1896 'size' => '',
1897 'sizes' => []
1898 ],
1899 'typography_font_weight' => '',
1900 'typography_text_transform' => '',
1901 'typography_line_height' => [
1902 'unit' => '',
1903 'size' => '',
1904 'sizes' => []
1905 ],
1906 'typography_letter_spacing' => [
1907 'unit' => '',
1908 'size' => '',
1909 'sizes' => []
1910 ],
1911 'typography_font_size_tablet' => [
1912 'unit' => '',
1913 'size' => '',
1914 'sizes' => []
1915 ],
1916 'typography_font_size_mobile' => [
1917 'unit' => '',
1918 'size' => '',
1919 'sizes' => []
1920 ],
1921 'typography_line_height_tablet' => [
1922 'unit' => '',
1923 'size' => '',
1924 'sizes' => []
1925 ],
1926 'typography_line_height_mobile' => [
1927 'unit' => '',
1928 'size' => '',
1929 'sizes' => []
1930 ],
1931 'typography_letter_spacing_tablet' => [
1932 'unit' => '',
1933 'size' => '',
1934 'sizes' => []
1935 ],
1936 'typography_letter_spacing_mobile' => [
1937 'unit' => '',
1938 'size' => '',
1939 'sizes' => []
1940 ]
1941 ],
1942 [
1943 '_id' => 'solace_h3_font_family_general',
1944 'title' => 'Solace H3',
1945 'typography_typography' => 'custom',
1946 'typography_font_family' => '',
1947 'typography_font_size' => [
1948 'unit' => '',
1949 'size' => '',
1950 'sizes' => []
1951 ],
1952 'typography_font_weight' => '',
1953 'typography_text_transform' => '',
1954 'typography_line_height' => [
1955 'unit' => '',
1956 'size' => '',
1957 'sizes' => []
1958 ],
1959 'typography_letter_spacing' => [
1960 'unit' => '',
1961 'size' => '',
1962 'sizes' => []
1963 ],
1964 'typography_font_size_tablet' => [
1965 'unit' => '',
1966 'size' => '',
1967 'sizes' => []
1968 ],
1969 'typography_font_size_mobile' => [
1970 'unit' => '',
1971 'size' => '',
1972 'sizes' => []
1973 ],
1974 'typography_line_height_tablet' => [
1975 'unit' => '',
1976 'size' => '',
1977 'sizes' => []
1978 ],
1979 'typography_line_height_mobile' => [
1980 'unit' => '',
1981 'size' => '',
1982 'sizes' => []
1983 ],
1984 'typography_letter_spacing_tablet' => [
1985 'unit' => '',
1986 'size' => '',
1987 'sizes' => []
1988 ],
1989 'typography_letter_spacing_mobile' => [
1990 'unit' => '',
1991 'size' => '',
1992 'sizes' => []
1993 ]
1994 ],
1995 [
1996 '_id' => 'solace_h4_font_family_general',
1997 'title' => 'Solace H4',
1998 'typography_typography' => 'custom',
1999 'typography_font_family' => '',
2000 'typography_font_size' => [
2001 'unit' => '',
2002 'size' => '',
2003 'sizes' => []
2004 ],
2005 'typography_font_weight' => '',
2006 'typography_text_transform' => '',
2007 'typography_line_height' => [
2008 'unit' => '',
2009 'size' => '',
2010 'sizes' => []
2011 ],
2012 'typography_letter_spacing' => [
2013 'unit' => '',
2014 'size' => '',
2015 'sizes' => []
2016 ],
2017 'typography_font_size_tablet' => [
2018 'unit' => '',
2019 'size' => '',
2020 'sizes' => []
2021 ],
2022 'typography_font_size_mobile' => [
2023 'unit' => '',
2024 'size' => '',
2025 'sizes' => []
2026 ],
2027 'typography_line_height_tablet' => [
2028 'unit' => '',
2029 'size' => '',
2030 'sizes' => []
2031 ],
2032 'typography_line_height_mobile' => [
2033 'unit' => '',
2034 'size' => '',
2035 'sizes' => []
2036 ],
2037 'typography_letter_spacing_tablet' => [
2038 'unit' => '',
2039 'size' => '',
2040 'sizes' => []
2041 ],
2042 'typography_letter_spacing_mobile' => [
2043 'unit' => '',
2044 'size' => '',
2045 'sizes' => []
2046 ]
2047 ],
2048 [
2049 '_id' => 'solace_h5_font_family_general',
2050 'title' => 'Solace H5',
2051 'typography_typography' => 'custom',
2052 'typography_font_family' => '',
2053 'typography_font_size' => [
2054 'unit' => '',
2055 'size' => '',
2056 'sizes' => []
2057 ],
2058 'typography_font_weight' => '',
2059 'typography_text_transform' => '',
2060 'typography_line_height' => [
2061 'unit' => '',
2062 'size' => '',
2063 'sizes' => []
2064 ],
2065 'typography_letter_spacing' => [
2066 'unit' => '',
2067 'size' => '',
2068 'sizes' => []
2069 ],
2070 'typography_font_size_tablet' => [
2071 'unit' => '',
2072 'size' => '',
2073 'sizes' => []
2074 ],
2075 'typography_font_size_mobile' => [
2076 'unit' => '',
2077 'size' => '',
2078 'sizes' => []
2079 ],
2080 'typography_line_height_tablet' => [
2081 'unit' => '',
2082 'size' => '',
2083 'sizes' => []
2084 ],
2085 'typography_line_height_mobile' => [
2086 'unit' => '',
2087 'size' => '',
2088 'sizes' => []
2089 ],
2090 'typography_letter_spacing_tablet' => [
2091 'unit' => 'px',
2092 'size' => '',
2093 'sizes' => []
2094 ],
2095 'typography_letter_spacing_mobile' => [
2096 'unit' => '',
2097 'size' => '',
2098 'sizes' => []
2099 ]
2100 ],
2101 [
2102 '_id' => 'solace_h6_font_family_general',
2103 'title' => 'Solace H6',
2104 'typography_typography' => 'custom',
2105 'typography_font_family' => '',
2106 'typography_font_size' => [
2107 'unit' => '',
2108 'size' => '',
2109 'sizes' => []
2110 ],
2111 'typography_font_weight' => '',
2112 'typography_text_transform' => '',
2113 'typography_line_height' => [
2114 'unit' => '',
2115 'size' => '',
2116 'sizes' => []
2117 ],
2118 'typography_letter_spacing' => [
2119 'unit' => '',
2120 'size' => '',
2121 'sizes' => []
2122 ],
2123 'typography_font_size_tablet' => [
2124 'unit' => '',
2125 'size' => '',
2126 'sizes' => []
2127 ],
2128 'typography_font_size_mobile' => [
2129 'unit' => '',
2130 'size' => '',
2131 'sizes' => []
2132 ],
2133 'typography_line_height_tablet' => [
2134 'unit' => '',
2135 'size' => '',
2136 'sizes' => []
2137 ],
2138 'typography_line_height_mobile' => [
2139 'unit' => '',
2140 'size' => '',
2141 'sizes' => []
2142 ],
2143 'typography_letter_spacing_tablet' => [
2144 'unit' => '',
2145 'size' => '',
2146 'sizes' => []
2147 ],
2148 'typography_letter_spacing_mobile' => [
2149 'unit' => '',
2150 'size' => '',
2151 'sizes' => []
2152 ]
2153 ],
2154 [
2155 '_id' => 'solace_smaller_font_family',
2156 'title' => 'Smaller',
2157 'typography_typography' => 'custom',
2158 'typography_font_family' => '',
2159 'typography_font_size' => [
2160 'unit' => '',
2161 'size' => '',
2162 'sizes' => []
2163 ],
2164 'typography_font_weight' => '',
2165 'typography_text_transform' => '',
2166 'typography_line_height' => [
2167 'unit' => '',
2168 'size' => '',
2169 'sizes' => []
2170 ],
2171 'typography_letter_spacing' => [
2172 'unit' => '',
2173 'size' => '',
2174 'sizes' => []
2175 ],
2176 'typography_font_size_tablet' => [
2177 'unit' => '',
2178 'size' => '',
2179 'sizes' => []
2180 ],
2181 'typography_font_size_mobile' => [
2182 'unit' => '',
2183 'size' => '',
2184 'sizes' => []
2185 ],
2186 'typography_line_height_tablet' => [
2187 'unit' => '',
2188 'size' => '',
2189 'sizes' => []
2190 ],
2191 'typography_line_height_mobile' => [
2192 'unit' => '',
2193 'size' => '',
2194 'sizes' => []
2195 ],
2196 'typography_letter_spacing_tablet' => [
2197 'unit' => '',
2198 'size' => '',
2199 'sizes' => []
2200 ],
2201 'typography_letter_spacing_mobile' => [
2202 'unit' => '',
2203 'size' => '',
2204 'sizes' => []
2205 ]
2206 ],
2207 [
2208 '_id' => 'solace_logotitle_font_family',
2209 'title' => 'Logo Title / Subtitle',
2210 'typography_typography' => 'custom',
2211 'typography_font_family' => '',
2212 'typography_font_size' => [
2213 'unit' => '',
2214 'size' => '',
2215 'sizes' => []
2216 ],
2217 'typography_font_weight' => '',
2218 'typography_text_transform' => '',
2219 'typography_line_height' => [
2220 'unit' => '',
2221 'size' => '',
2222 'sizes' => []
2223 ],
2224 'typography_letter_spacing' => [
2225 'unit' => '',
2226 'size' => '',
2227 'sizes' => []
2228 ],
2229 'typography_font_size_tablet' => [
2230 'unit' => '',
2231 'size' => '',
2232 'sizes' => []
2233 ],
2234 'typography_font_size_mobile' => [
2235 'unit' => '',
2236 'size' => '',
2237 'sizes' => []
2238 ],
2239 'typography_line_height_tablet' => [
2240 'unit' => '',
2241 'size' => '',
2242 'sizes' => []
2243 ],
2244 'typography_line_height_mobile' => [
2245 'unit' => '',
2246 'size' => '',
2247 'sizes' => []
2248 ],
2249 'typography_letter_spacing_tablet' => [
2250 'unit' => '',
2251 'size' => '',
2252 'sizes' => []
2253 ],
2254 'typography_letter_spacing_mobile' => [
2255 'unit' => '',
2256 'size' => '',
2257 'sizes' => []
2258 ]
2259 ],
2260 [
2261 '_id' => 'solace_button_font_family',
2262 'title' => 'Button',
2263 'typography_typography' => 'custom',
2264 'typography_font_family' => '',
2265 'typography_font_size' => [
2266 'unit' => '',
2267 'size' => '',
2268 'sizes' => []
2269 ],
2270 'typography_font_weight' => '',
2271 'typography_text_transform' => '',
2272 'typography_line_height' => [
2273 'unit' => '',
2274 'size' => '',
2275 'sizes' => []
2276 ],
2277 'typography_letter_spacing' => [
2278 'unit' => '',
2279 'size' => '',
2280 'sizes' => []
2281 ],
2282 'typography_font_size_tablet' => [
2283 'unit' => '',
2284 'size' => '',
2285 'sizes' => []
2286 ],
2287 'typography_font_size_mobile' => [
2288 'unit' => '',
2289 'size' => '',
2290 'sizes' => []
2291 ],
2292 'typography_line_height_tablet' => [
2293 'unit' => '',
2294 'size' => '',
2295 'sizes' => []
2296 ],
2297 'typography_line_height_mobile' => [
2298 'unit' => '',
2299 'size' => '',
2300 'sizes' => []
2301 ],
2302 'typography_letter_spacing_tablet' => [
2303 'unit' => '',
2304 'size' => '',
2305 'sizes' => []
2306 ],
2307 'typography_letter_spacing_mobile' => [
2308 'unit' => '',
2309 'size' => '',
2310 'sizes' => []
2311 ]
2312 ]
2313 ];
2314
2315 if (class_exists('Elementor\Plugin')) {
2316 $elementor_active_kit = get_option( 'elementor_active_kit' );
2317
2318 // Retrieve the Elementor page settings meta data.
2319 $meta = get_post_meta( $elementor_active_kit, '_elementor_page_settings', true );
2320 if ( $meta ) {
2321 \Elementor\Plugin::$instance->kits_manager->update_kit_settings_based_on_option( 'system_typography', $custom_typography );
2322 }
2323 }
2324 }
2325
2326 function install_and_activate_theme() {
2327 // Verify nonce
2328 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
2329 $response = array('error' => 'Invalid nonce!');
2330 echo wp_json_encode($response);
2331 wp_die();
2332 }
2333
2334 // Capability check — only administrators may install/activate themes.
2335 if ( ! current_user_can( 'manage_options' ) ) {
2336 $response = array( 'error' => 'Forbidden' );
2337 echo wp_json_encode( $response );
2338 wp_die();
2339 }
2340
2341 solace_disable_image_processing();
2342
2343 // Set import zip complete option to false
2344 update_option('solace_extra_import_zip_complete', false);
2345 set_theme_mod('solace_extra_import_zip_complete', false);
2346
2347 $theme_slug = 'solace';
2348
2349 if (!(get_option('template') === $theme_slug || get_option('stylesheet') === $theme_slug)) {
2350
2351 if (!(file_exists(trailingslashit(WP_CONTENT_DIR) . 'themes/' . $theme_slug))) {
2352 $api = themes_api('theme_information', array('slug' => $theme_slug));
2353
2354 if (is_wp_error($api)) {
2355 esc_html_e('Failed to retrieve theme information from WordPress.org.', 'solace-extra');
2356 return;
2357 }
2358
2359 $theme_zip = download_url($api->download_link);
2360
2361 if (!is_wp_error($theme_zip)) {
2362 $theme_dir = trailingslashit(WP_CONTENT_DIR) . 'themes';
2363 $zip = new ZipArchive;
2364
2365 if ($zip->open($theme_zip) === true) {
2366 $zip->extractTo($theme_dir);
2367 $zip->close();
2368 wp_delete_file($theme_zip);
2369 } else {
2370 esc_html_e('Failed to open ZIP file.', 'solace-extra');
2371 }
2372 }
2373 }
2374
2375 switch_theme($theme_slug);
2376
2377 if (!(get_option('template') === $theme_slug || get_option('stylesheet') === $theme_slug)) {
2378 esc_html_e('Theme Installed and Activated', 'solace-extra');
2379 } else {
2380 esc_html_e('Failed to activate theme.', 'solace-extra');
2381 }
2382 } else {
2383 esc_html_e('Theme is already installed and active.', 'solace-extra');
2384 }
2385 }
2386
2387
2388 function install_and_activate_plugins()
2389 {
2390 // Verify user permissions and nonce
2391 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'ajax-nonce' ) ) {
2392 wp_send_json_error( [ 'error' => 'Invalid nonce!' ] );
2393 }
2394 if ( ! current_user_can( 'install_plugins' ) ) {
2395 wp_send_json_error( [ 'error' => 'Unauthorized action!' ] );
2396 }
2397
2398 update_option( 'elementor_onboarded', true );
2399
2400 $demo_name = ! empty( $_POST['getDemo'] ) ? sanitize_text_field( wp_unslash( $_POST['getDemo'] ) ) : '';
2401
2402 // Remote and local API URLs
2403 $url = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL . $demo_name ) . 'wp-json/solace/v1/required-plugin';
2404
2405 // Make remote request using wp_remote_get
2406 $response = wp_remote_get($url);
2407 if ( is_wp_error( $response ) ) {
2408 wp_send_json_error( [ 'error' => 'Failed to fetch plugins data.' ] );
2409 }
2410
2411 $decoded_data = json_decode( wp_remote_retrieve_body( $response ), true );
2412 $plugins_to_install = null;
2413 if ( $decoded_data['page_builder'] && $decoded_data['ecommerce'] ) {
2414 $plugins_to_install = array(
2415 'elementor',
2416 'woocommerce',
2417 );
2418 } else if ( $decoded_data['page_builder'] && ! $decoded_data['ecommerce'] ) {
2419 $plugins_to_install = array(
2420 'elementor',
2421 );
2422 } else {
2423 $plugins_to_install = array(
2424 'elementor',
2425 );
2426 }
2427
2428 if ( ! function_exists( 'plugins_api' ) ) {
2429 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
2430 }
2431 if ( ! class_exists( 'WP_Upgrader' ) ) {
2432 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
2433 }
2434
2435 foreach ( $plugins_to_install as $plugin_slug ) {
2436 if ( ! is_plugin_active( "$plugin_slug/$plugin_slug.php" ) && ! file_exists( WP_PLUGIN_DIR . "/$plugin_slug/$plugin_slug.php" ) ) {
2437 $api = plugins_api( 'plugin_information', [ 'slug' => $plugin_slug ] );
2438 if ( isset( $api->download_link ) ) {
2439 $plugin_zip = download_url( $api->download_link );
2440 if ( ! is_wp_error( $plugin_zip ) ) {
2441 $zip = new ZipArchive;
2442 if ( $zip->open( $plugin_zip ) === true ) {
2443 $zip->extractTo( WP_PLUGIN_DIR );
2444 $zip->close();
2445 wp_delete_file( $plugin_zip );
2446 }
2447 }
2448 }
2449 }
2450 }
2451
2452 foreach ( $plugins_to_install as $plugin_slug ) {
2453 activate_plugin( "$plugin_slug/$plugin_slug.php" );
2454 }
2455
2456 update_option( 'permalink_structure', '/%postname%/' );
2457 flush_rewrite_rules();
2458
2459 $url = admin_url( 'admin.php?page=elementor-app#onboarding' );
2460
2461 $response = wp_remote_get( $url );
2462
2463 if ( is_wp_error( $response ) ) {
2464 esc_html_e( 'Onboarding True', 'solace-extra');
2465 } else {
2466 esc_html_e( 'Onboarding False', 'solace-extra');
2467 }
2468
2469 // Check if the LiteSpeed Cache plugin is active by looking in the 'active_plugins' option.
2470 $active_plugins = get_option( 'active_plugins' );
2471 if ( in_array( 'litespeed-cache/litespeed-cache.php', $active_plugins ) ) {
2472 update_option( 'solace_extra_deactivate_litespeed', true );
2473
2474 // Ensure the 'plugin.php' file is loaded to use WordPress plugin functions.
2475 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2476
2477 // Define the plugin path for LiteSpeed Cache.
2478 $plugin = 'litespeed-cache/litespeed-cache.php';
2479
2480 // Check if the plugin is active.
2481 if ( is_plugin_active( $plugin ) ) {
2482 // Deactivate the plugin.
2483 deactivate_plugins( $plugin );
2484
2485 // Check if the plugin was successfully deactivated.
2486 if ( ! is_plugin_active( $plugin ) ) {
2487 // Output a success message.
2488 // esc_html_e( 'LiteSpeed Cache plugin has been successfully deactivated.', 'solace-extra');
2489 } else {
2490 // Output an error message if the plugin is still active.
2491 // esc_html_e( 'Failed to deactivate the LiteSpeed Cache plugin.', 'solace-extra');
2492 }
2493 } else {
2494 // Output a message if the plugin is not active.
2495 // esc_html_e( 'LiteSpeed Cache plugin is already inactive.', 'solace-extra');
2496 }
2497
2498 } else {
2499 update_option( 'solace_extra_deactivate_litespeed', false );
2500 }
2501 }
2502
2503
2504 public function import_zip()
2505 {
2506
2507 // Verify nonce
2508 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
2509 $response = array('error' => 'Invalid nonce!');
2510 echo wp_json_encode($response);
2511 wp_die();
2512 }
2513
2514 // Capability check — only administrators may import/destructive-reset.
2515 if ( ! current_user_can( 'manage_options' ) ) {
2516 $response = array( 'error' => 'Forbidden' );
2517 echo wp_json_encode( $response );
2518 wp_die();
2519 }
2520
2521 set_theme_mod('solace_extra_import_zip_complete', false); // Menyimpan status impor sebagai false
2522
2523 // Delete menu items.
2524 $this->delete_imported_menu_items();
2525
2526 // Delete menu previously.
2527 $prevDemo = ! empty( $_POST['prevDemo'] ) ? sanitize_text_field( wp_unslash ( $_POST['prevDemo'] ) ) : '';
2528 if (!empty($prevDemo) && $prevDemo !== 'blank') {
2529
2530 // Split the string into an array using commas as separators
2531 $arrayPrevDemo = explode(',', $prevDemo);
2532
2533 // Remove leading and trailing whitespaces from each element in the array
2534 $arrayPrevDemo = array_map('trim', $arrayPrevDemo);
2535
2536 // Remove duplicate values from the array, keeping only unique values
2537 $arrayPrevDemo = array_unique($arrayPrevDemo);
2538
2539
2540 foreach ($arrayPrevDemo as $get_demo) {
2541 $demo = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo . "/wp-json/solace/v1/menus";
2542 $response = wp_remote_get($demo);
2543 $menus_data = wp_remote_retrieve_body($response);
2544 $menus = json_decode($menus_data);
2545
2546 if (empty($get_demo)) {
2547 esc_html_e('Erorr remove menu', 'solace-extra');
2548 die;
2549 }
2550
2551 // Loop through each menu
2552 if (!empty($menus)) {
2553 $menus_local = wp_get_nav_menus();
2554 foreach ($menus as $menu) {
2555 wp_delete_nav_menu($menu->slug);
2556
2557 // Loop through each menu to be deleted
2558 foreach ($menus_local as $menu_local) {
2559 // Check if there is a duplicate by appending 'duplicate' at the end of slug
2560 if (strpos($menu_local->slug, $menu->slug . '-duplicate') !== false) {
2561 // If found, delete the menu
2562 wp_delete_nav_menu($menu_local->slug);
2563 }
2564 }
2565 }
2566 }
2567 }
2568 }
2569
2570 if ( class_exists( 'Elementor\Plugin' ) ) {
2571 // Delete Elementor templates kit.
2572 $this->delete_all_elementor_templates();
2573
2574 // Delete widgets.
2575 $this->delete_sidebars_widgets();
2576
2577 // Delete customizers.
2578 $this->delete_customizers();
2579 }
2580
2581 // $this->install_and_activate_elementor();
2582 $demo_name = isset($_POST['getDemoName']) ? sanitize_text_field(wp_unslash($_POST['getDemoName'])) : '';
2583 $this->import_process->push_to_queue(array(
2584 'action' => 'demo_name',
2585 'demo_name' => $demo_name
2586 ));
2587
2588 $this->import_process->save()->dispatch();
2589
2590 }
2591
2592
2593
2594 /**
2595 * Get unique posts by title or slug, filtering by a specific post meta.
2596 *
2597 * @param string $title The title or slug to search for.
2598 * @return array|null An array of posts with the specified title/slug and meta, or null if none found.
2599 */
2600 public function get_unique_posts_by_title( $title, $demo_name ) {
2601
2602 global $wpdb;
2603
2604 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need this to get all the terms and taxonomy. Traditional WP_Query would have been expensive here.
2605 $post_ids = $wpdb->get_col(
2606 $wpdb->prepare(
2607 "
2608 SELECT p.ID
2609 FROM {$wpdb->posts} p
2610 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
2611 WHERE pm.meta_key = %s
2612 AND p.post_title = %s
2613 ",
2614 $demo_name,
2615 $title
2616 )
2617 );
2618
2619 // Check if the array has more than one element.
2620 if (count($post_ids) > 1) {
2621 // Get the last element of the array.
2622 $last_post_id = end($post_ids);
2623 } else {
2624 // If there's only one element or the array is empty
2625 $last_post_id = !empty($post_ids) ? $post_ids[0] : null;
2626 }
2627
2628 return $last_post_id;
2629 }
2630
2631 // Function to update the WordPress menu
2632 public function call_ajax_import_menu()
2633 {
2634
2635 // Verify nonce
2636 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash ( $_POST['nonce'] ) ), 'ajax-nonce' )) {
2637 $response = array('error' => 'Invalid nonce!');
2638 echo wp_json_encode($response);
2639 wp_die();
2640 }
2641
2642 // Check current user
2643 if ( ! current_user_can( 'manage_options' ) ) {
2644 wp_send_json_error( array( 'error' => 'Unauthorized' ) );
2645 }
2646
2647 // Get menus data from the API
2648 $get_demo_url = ! empty( $_POST['getDemo'] ) ? sanitize_title( wp_unslash( $_POST['getDemo'] ) ) : '';
2649 $demo_name = ! empty( $_POST['getDemo'] ) ? sanitize_title( 'solace_extra_' . wp_unslash( $_POST['getDemo'] ) ) : '';
2650
2651 $demo = trailingslashit( SOLACE_EXTRA_DEMO_IMPORT_URL ) . $get_demo_url . "/wp-json/solace/v1/menus";
2652 $response = wp_remote_get($demo);
2653 $menus_data = wp_remote_retrieve_body($response);
2654 $menus = json_decode($menus_data);
2655
2656 if (empty($get_demo_url)) {
2657 esc_html_e('Error menu demo url', 'solace-extra');
2658 die;
2659 }
2660
2661 // Get the list of menus
2662 $menu_local = wp_get_nav_menus();
2663 // Check if there are any menus available
2664 if ( ! empty( $menu_local ) ) {
2665 // Loop through the menus and check if any slug contains "-duplicate"
2666 foreach ( $menu_local as $menu ) {
2667
2668 // Check if the slug contains '-duplicate'
2669 if ( strpos( $menu->slug, '-duplicate' ) !== false ) {
2670 // Delete the menu that has '-duplicate' in its slug
2671 wp_delete_nav_menu( $menu->slug );
2672 }
2673 }
2674 }
2675
2676 // Set Shop page.
2677 update_option( 'woocommerce_shop_page_id', null );
2678
2679 // Loop through each menu
2680 if (!empty($menus)) {
2681 foreach ($menus as $menu) {
2682 // Skip if the menu name is not 'test_mymenu'
2683 // if ('test_mymenu' !== $menu->name) {
2684 // continue;
2685 // }
2686
2687 // Get the internal menu by slug
2688 $internal_menu = get_term_by('slug', $menu->slug, 'nav_menu');
2689
2690 // Return if internal menu is not found
2691 if ($internal_menu === false) {
2692 return;
2693 }
2694
2695 // Remove existing menu items
2696 $menu_items = wp_get_nav_menu_items($internal_menu->term_id);
2697 foreach ($menu_items as $menu_item) {
2698 wp_delete_post($menu_item->ID, true);
2699 }
2700
2701 // if ( isset( $internal_menu->term_id ) ) {
2702 // $new_slug = 'solace-extra-' . $internal_menu->slug;
2703 // wp_update_term( $internal_menu->term_id, 'nav_menu', array(
2704 // 'slug' => $new_slug,
2705 // ));
2706 // }
2707
2708 // Build arguments for new menu items
2709 $args = [];
2710 $index = 0;
2711 $index_menu_item = 0;
2712
2713 if (!empty($menu->terms)) {
2714 foreach ($menu->terms as $menu_item) {
2715 // Skip if the post status is 'draft'
2716 if ('draft' === $menu_item->post_status) {
2717 continue;
2718 }
2719
2720 // Process menu item based on its type
2721 $args_menu_item = $this->processMenuItem($menu_item, $demo_name, $index_menu_item);
2722
2723 // Update nav menu item and get the updated ID
2724 $nav_menu_item_update = wp_update_nav_menu_item($internal_menu->term_id, 0, $args_menu_item);
2725
2726 update_post_meta($nav_menu_item_update, 'solace_import_menu_item', true);
2727
2728 // Update the object ID in the arguments
2729 $args_menu_item['menu-item-object-id'] = $nav_menu_item_update;
2730
2731 // Output for debugging
2732 // echo '<pre style="background: yellow;">';
2733 // $this->outputDebuggingInfo($args_menu_item);
2734 // echo "</pre>";
2735
2736 $args[$index++] = $args_menu_item;
2737
2738 $index_menu_item++;
2739 }
2740 }
2741
2742 // Update parent ID for menu items
2743 $this->updateParentID($internal_menu->term_id, $args);
2744
2745 // Output for debugging
2746 // echo '<pre style="background: lightgreen;">';
2747 // outputDebuggingInfo($args);
2748 // echo "</pre>";
2749 }
2750 }
2751
2752 wp_die();
2753 }
2754
2755 /**
2756 * Normalize a menu item URL from remote demo domain to local site.
2757 *
2758 * For custom-link menu items imported from a remote demo, the URL contains
2759 * the remote domain and a demo-slug prefix (/clawnest/...). This method
2760 * replaces the remote domain with the local site URL and strips the
2761 * demo-slug prefix so the link works on the user's site.
2762 *
2763 * The allowed domain is extracted from SOLACE_EXTRA_DEMO_IMPORT_URL,
2764 * so it adapts automatically when the demo server URL changes.
2765 *
2766 * @since 1.6.0
2767 * @param string $url The original menu item URL from the demo data.
2768 * @return string Normalized URL pointing to the local site.
2769 */
2770 private function normalize_menu_url($url)
2771 {
2772 // Keep fragment-only or empty links as-is.
2773 if (empty($url) || '#' === $url || 0 === strpos($url, '#')) {
2774 return $url;
2775 }
2776
2777 $parsed = wp_parse_url($url);
2778
2779 // Relative path — keep as-is, WP will resolve it.
2780 if (empty($parsed['host'])) {
2781 return $url;
2782 }
2783
2784 // Build allowed domains from SOLACE_EXTRA_DEMO_IMPORT_URL.
2785 $demo_host = wp_parse_url(SOLACE_EXTRA_DEMO_IMPORT_URL, PHP_URL_HOST);
2786 $allowed = array($demo_host);
2787 if ($demo_host && 'www.' !== substr($demo_host, 0, 4)) {
2788 $allowed[] = 'www.' . $demo_host;
2789 }
2790
2791 if (!in_array($parsed['host'], $allowed, true)) {
2792 return $url;
2793 }
2794
2795 $path = isset($parsed['path']) ? $parsed['path'] : '/';
2796
2797 // Strip the first path segment (the demo slug, e.g. /clawnest/… → /…).
2798 $segments = explode('/', trim($path, '/'));
2799 if (count($segments) > 1) {
2800 array_shift($segments); // remove demo slug
2801 $path = '/' . implode('/', $segments);
2802 } elseif (count($segments) === 1) {
2803 // URL is just the demo slug root — point to site root.
2804 $path = '/';
2805 }
2806
2807 // Preserve query string if present.
2808 $query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
2809
2810 return home_url($path . $query);
2811 }
2812
2813 // Function to process menu item based on its type
2814 // Function to process menu item based on its type
2815 public function processMenuItem($menu_item, $demo_name, $index_menu_item)
2816 {
2817 $args = array();
2818
2819 // Common properties for all types (default assume original data).
2820 $common_args = array(
2821 'menu-item-title' => $menu_item->title,
2822 'menu-item-object' => $menu_item->object,
2823 'menu-item-object-id' => 0,
2824 'menu-item-position' => $menu_item->menu_order,
2825 'menu-item-type' => $menu_item->type,
2826 'menu-item-url' => $this->normalize_menu_url($menu_item->url),
2827 'menu-item-description' => $menu_item->description,
2828 'menu-item-attr-title' => $menu_item->attr_title,
2829 'menu-item-target' => $menu_item->target,
2830 'menu-item-xfn' => $menu_item->xfn,
2831 'menu-item-status' => $menu_item->post_status,
2832 'menu-item-parent-id' => 0,
2833 'menu-item-parent-title' => $menu_item->menu_item_parent_title,
2834 'menu-item-parent-type' => $menu_item->menu_item_parent_type,
2835 );
2836
2837 // Helper: fallback to custom link when target object is not found.
2838 $fallback_to_custom = function () use ( $menu_item, $common_args ) {
2839 return array_merge(
2840 $common_args,
2841 array(
2842 'menu-item-type' => 'custom',
2843 'menu-item-object' => 'custom',
2844 'menu-item-object-id' => 0,
2845 'menu-item-url' => $this->normalize_menu_url($menu_item->url),
2846 )
2847 );
2848 };
2849
2850 if ($menu_item->type === 'taxonomy') {
2851 // Process taxonomy type menu item.
2852 $url = $menu_item->url;
2853 $path = wp_parse_url($url, PHP_URL_PATH);
2854 $slug = basename($path);
2855
2856 // Use the original taxonomy from the menu item when possible.
2857 $taxonomy = ! empty( $menu_item->object ) && taxonomy_exists( $menu_item->object )
2858 ? $menu_item->object
2859 : 'category';
2860
2861 $term = get_term_by('slug', $slug, $taxonomy);
2862 if ($term && is_object($term)) {
2863 $args = array_merge($common_args, array(
2864 'menu-item-object-id' => $term->term_id,
2865 ));
2866 } else {
2867 // If the term does not exist locally, store as a custom link
2868 // so the item is not left in "Pending" state.
2869 $args = $fallback_to_custom();
2870 }
2871 } elseif ($menu_item->type === 'post_type') {
2872 // Process post_type menu item.
2873 $menu_item_url = $menu_item->url;
2874 $path = wp_parse_url($menu_item_url, PHP_URL_PATH);
2875 $slug = basename($path);
2876
2877 // ==== SPECIAL CASE: SHOP MENU ITEM ====
2878 $is_shop_title = ( stripos( $menu_item->title, 'shop' ) !== false );
2879 $is_shop_slug = ( strtolower( $slug ) === 'shop' );
2880
2881 if ( $is_shop_title && $is_shop_slug ) {
2882 $local_shop_id = 0;
2883
2884 // 1) Coba cari page lokal dengan slug 'shop' terlebih dahulu.
2885 if ( ! $local_shop_id ) {
2886 $shop_page = get_page_by_path( 'shop', OBJECT, 'page' );
2887 if ( $shop_page && ! is_wp_error( $shop_page ) && $shop_page->ID ) {
2888 // Pastikan memang slug-nya 'shop', bukan page lain seperti 'hello-world'.
2889 if ( isset( $shop_page->post_name ) && strtolower( $shop_page->post_name ) === 'shop' ) {
2890 $local_shop_id = (int) $shop_page->ID;
2891 }
2892 }
2893 }
2894
2895 // 2) Jika belum ada, coba dari WooCommerce shop page setting
2896 // tapi validasi bahwa judul/slug memang berhubungan dengan "shop".
2897 if ( ! $local_shop_id && function_exists( 'wc_get_page_id' ) ) {
2898 $candidate_id = absint( wc_get_page_id( 'shop' ) );
2899 if ( $candidate_id ) {
2900 $candidate_post = get_post( $candidate_id );
2901 if ( $candidate_post && ! is_wp_error( $candidate_post ) ) {
2902 $title_ok = ( stripos( $candidate_post->post_title, 'shop' ) !== false );
2903 $slug_ok = ( isset( $candidate_post->post_name ) && strtolower( $candidate_post->post_name ) === 'shop' );
2904 if ( $title_ok || $slug_ok ) {
2905 $local_shop_id = $candidate_id;
2906 }
2907 }
2908 }
2909 }
2910
2911 // 3) Kalau ada page lokal, pakai sebagai post_type page.
2912 if ( $local_shop_id ) {
2913 $local_url = get_permalink( $local_shop_id );
2914 if ( ! $local_url || is_wp_error( $local_url ) ) {
2915 $local_url = $menu_item->url; // fallback ke URL dari JSON.
2916 }
2917
2918 $shop_args = $common_args;
2919 $shop_args['menu-item-object'] = 'page';
2920 $shop_args['menu-item-type'] = 'post_type';
2921 $shop_args['menu-item-object-id'] = $local_shop_id;
2922 $shop_args['menu-item-url'] = $local_url;
2923
2924 return $shop_args;
2925 }
2926
2927 return $fallback_to_custom();
2928 }
2929 // ==== END SPECIAL CASE SHOP ====
2930
2931 // ==== SPECIAL CASE: HOME / FRONT PAGE MENU ITEM ====
2932 $home_path_normalized = trim( (string) $path, '/' );
2933 $home_demo_slug = str_replace( 'solace_extra_', '', $demo_name );
2934 $home_path_parts = ( '' === $home_path_normalized )
2935 ? array()
2936 : explode( '/', $home_path_normalized );
2937
2938 // Strip the demo slug prefix (e.g. "/nuvion/").
2939 if ( ! empty( $home_path_parts ) && $home_path_parts[0] === $home_demo_slug ) {
2940 array_shift( $home_path_parts );
2941 }
2942
2943 $is_home_url = empty( $home_path_parts );
2944 $is_page_like = ! empty( $menu_item->object ) && 'page' === $menu_item->object;
2945
2946 if ( $is_home_url && $is_page_like ) {
2947 $local_home_id = 0;
2948
2949 // 1) Prefer a page imported from this demo that matches the
2950 // menu item title (e.g. "Home").
2951 $local_home_id = absint( $this->get_unique_posts_by_title( $menu_item->title, $demo_name ) );
2952
2953 // 2) Fallback to the local page with slug "home".
2954 if ( ! $local_home_id ) {
2955 $home_page = get_page_by_path( 'home', OBJECT, 'page' );
2956 if ( $home_page && ! is_wp_error( $home_page ) && isset( $home_page->ID ) ) {
2957 $local_home_id = (int) $home_page->ID;
2958 }
2959 }
2960
2961 // 3) Fallback to WordPress' configured front page.
2962 if ( ! $local_home_id ) {
2963 $front_id = absint( get_option( 'page_on_front', 0 ) );
2964 if ( $front_id ) {
2965 $local_home_id = $front_id;
2966 }
2967 }
2968
2969 if ( $local_home_id ) {
2970 $local_url = get_permalink( $local_home_id );
2971 if ( ! $local_url || is_wp_error( $local_url ) ) {
2972 $local_url = home_url( '/' );
2973 }
2974
2975 $home_args = $common_args;
2976 $home_args['menu-item-object'] = 'page';
2977 $home_args['menu-item-type'] = 'post_type';
2978 $home_args['menu-item-object-id'] = $local_home_id;
2979 $home_args['menu-item-url'] = $local_url;
2980
2981 return $home_args;
2982 }
2983 }
2984 // ==== END SPECIAL CASE HOME ====
2985
2986 // Determine the post type from menu item object
2987 $post_type = ! empty( $menu_item->object ) && post_type_exists( $menu_item->object )
2988 ? $menu_item->object
2989 : 'post';
2990
2991 // Define arguments for querying a post based on the slug.
2992 $query_args = array(
2993 'name' => $slug,
2994 'post_type' => $post_type,
2995 'post_status' => 'publish',
2996 'posts_per_page' => -1,
2997 );
2998
2999 // Retrieve the post based on the arguments.
3000 $post = get_posts($query_args);
3001
3002 // Track resolved object ID.
3003 $resolved_object_id = 0;
3004
3005 // Check if a post with the specified slug exists.
3006 if ($post && $post[0]->post_title) {
3007 $resolved_object_id = (int) $post[0]->ID;
3008 } else {
3009 // Second approach if the first one doesn't yield results.
3010 // Check if a post with the menu item title exists.
3011 $post_exists = post_exists($menu_item->title);
3012
3013 // Parse the URL and get the path
3014 $path = wp_parse_url($menu_item->url, PHP_URL_PATH);
3015
3016 // Get the last segment from the path
3017 $path_segments = explode('/', trim($path, '/'));
3018 $last_segment = end($path_segments);
3019
3020 // Remove dashes and slashes, convert to desired format
3021 $clean_slug = str_replace('-', ' ', $last_segment);
3022
3023 // Remove the prefix 'solace_extra_'
3024 $get_demo_name = str_replace( 'solace_extra_', '', $demo_name );
3025
3026 // Set default variable $unique_posts (post ID or false).
3027 $unique_posts = false;
3028
3029 if ( $get_demo_name === $clean_slug ) {
3030 // Check property demo name.
3031 $unique_posts = absint( $this->get_unique_posts_by_title( $menu_item->title, $demo_name ) );
3032 } else {
3033 // Check property demo name.
3034 $unique_posts = absint( $this->get_unique_posts_by_title( $clean_slug, $demo_name ) );
3035 }
3036
3037 // Check if the word "shop" exists in the text
3038 $page_shop = stripos( $menu_item->title, 'shop' ) !== false;
3039
3040 if ( $page_shop ) {
3041
3042 // Check dan ganti "pages" dengan "page"
3043 $clean_slug = str_replace( 'pages', 'page', $clean_slug );
3044
3045 $unique_posts = absint( $this->get_unique_posts_by_title( $clean_slug, $demo_name ) );
3046
3047 }
3048
3049 // Fix page blog.
3050 if ( 'Blog' === $menu_item->title || 'blog' === $menu_item->title ) {
3051 if ( 'post_type' === $menu_item->type && $menu_item->is_posts_page ) {
3052 $post = get_post( $unique_posts );
3053 if ( ! $post ) {
3054 $new_page = array(
3055 'post_title' => 'Blog',
3056 'post_status' => 'publish',
3057 'post_type' => 'page'
3058 );
3059
3060 // Insert new page.
3061 $page_id = wp_insert_post( $new_page );
3062
3063 $unique_posts = $page_id;
3064 }
3065 }
3066 }
3067
3068 // Check if any unique posts were found and display their details.
3069 if ($unique_posts) {
3070
3071 $post_exists = $unique_posts;
3072
3073 if ( $menu_item->is_posts_page ) {
3074 $page_for_posts = $post_exists;
3075 update_option( 'page_for_posts', $page_for_posts );
3076 }
3077
3078 if ( $menu_item->is_shop_page && $menu_item->woocommerce_shop_page_id ) {
3079 // Set Shop page.
3080 update_option( 'woocommerce_shop_page_id', $post_exists );
3081 }
3082
3083 }
3084
3085 if ($post_exists) {
3086 $resolved_object_id = (int) $post_exists;
3087 } else {
3088 // Try to find by post type from menu item object first
3089 $found_by_type = false;
3090 if ( ! empty( $menu_item->object ) && post_type_exists( $menu_item->object ) ) {
3091 $post_by_type = get_page_by_path($slug, OBJECT, $menu_item->object);
3092 if ($post_by_type && $post_by_type->ID) {
3093 $resolved_object_id = (int) $post_by_type->ID;
3094 $found_by_type = true;
3095 }
3096 }
3097
3098 // Fallback to common post types if not found by specific type
3099 if ( ! $found_by_type ) {
3100 $page = get_page_by_path($slug, OBJECT, 'page');
3101 $post = get_page_by_path($slug, OBJECT, 'post');
3102 $singular = get_page_by_path($slug, OBJECT, 'any');
3103 if ($page && $page->ID) {
3104 $resolved_object_id = (int) $page->ID;
3105 } elseif ($post && $post->ID) {
3106 $resolved_object_id = (int) $post->ID;
3107 } elseif ($singular && $singular->ID) {
3108 $resolved_object_id = (int) $singular->ID;
3109 }
3110 }
3111 }
3112 }
3113
3114 // Extra handling for Shop page:
3115 // In some cases the imported Shop page may have a different slug or title,
3116 // so we try a more forgiving lookup when nothing has been resolved yet.
3117 if ( ! $resolved_object_id && stripos( $menu_item->title, 'shop' ) !== false ) {
3118 // 1) Use WooCommerce shop page if already configured.
3119 $wc_shop_id = 0;
3120 if ( function_exists( 'wc_get_page_id' ) ) {
3121 $wc_shop_id = absint( wc_get_page_id( 'shop' ) );
3122 } else {
3123 $wc_shop_id = absint( get_option( 'woocommerce_shop_page_id', 0 ) );
3124 }
3125
3126 if ( $wc_shop_id ) {
3127 $resolved_object_id = $wc_shop_id;
3128 }
3129 }
3130
3131 // If still not resolved and looks like "Shop", try to find it by path / title.
3132 if ( ! $resolved_object_id && stripos( $menu_item->title, 'shop' ) !== false ) {
3133 // 2) Try by canonical path "shop".
3134 $shop_page = get_page_by_path( 'shop', OBJECT, 'page' );
3135 if ( $shop_page && ! is_wp_error( $shop_page ) && $shop_page->ID ) {
3136 $resolved_object_id = (int) $shop_page->ID;
3137 }
3138 }
3139
3140 if ( ! $resolved_object_id && stripos( $menu_item->title, 'shop' ) !== false ) {
3141 // 3) Fallback: search any published page that matches the title loosely.
3142 $shop_candidates = get_posts(
3143 array(
3144 'post_type' => 'page',
3145 'post_status' => 'publish',
3146 's' => $menu_item->title,
3147 'posts_per_page' => 1,
3148 'fields' => 'ids',
3149 )
3150 );
3151 if ( ! empty( $shop_candidates ) ) {
3152 $resolved_object_id = (int) $shop_candidates[0];
3153 }
3154
3155 }
3156
3157 if ( $resolved_object_id ) {
3158 // Target content found secara umum (bukan kasus khusus Shop).
3159 $args = array_merge(
3160 $common_args,
3161 array(
3162 'menu-item-object-id' => $resolved_object_id,
3163 )
3164 );
3165 } else {
3166 // Nothing resolved -> turn into custom link to avoid "Pending" state.
3167 $args = $fallback_to_custom();
3168 }
3169 } else {
3170 // Process other types of menu item
3171 $args = array_merge($common_args, array(
3172 'menu-item-object-id' => $menu_item->object_id,
3173 ));
3174 }
3175
3176 return $args;
3177 }
3178
3179 // Function to update parent ID for menu items
3180 public function updateParentID($menu_id, &$args)
3181 {
3182 $self_items = wp_get_nav_menu_items($menu_id);
3183 $index = 0;
3184
3185 foreach ($args as &$args_menu_item) {
3186 foreach ($self_items as $self_menu_item) {
3187 // Update parent ID if the titles match
3188 if (array_key_exists('menu-item-parent-title', $args_menu_item)) {
3189 if ($args_menu_item['menu-item-parent-title'] === $self_menu_item->title) {
3190 $args_menu_item['menu-item-parent-id'] = $self_menu_item->ID;
3191 // wp_update_nav_menu_item($menu_id, $args_menu_item['menu-item-object-id'], $args_menu_item);
3192 update_post_meta($args_menu_item['menu-item-object-id'], '_menu_item_menu_item_parent', $self_menu_item->ID);
3193 }
3194 }
3195 }
3196 $index++;
3197 }
3198 }
3199
3200 // Function to output debugging information
3201 public function outputDebuggingInfo($data)
3202 {
3203 // Output debugging information
3204 // print_r($data);
3205 }
3206 }
3207