diff --git a/inc/admin-pages/class-settings-admin-page.php b/inc/admin-pages/class-settings-admin-page.php index e00b55d7..e3734123 100644 --- a/inc/admin-pages/class-settings-admin-page.php +++ b/inc/admin-pages/class-settings-admin-page.php @@ -9,6 +9,8 @@ namespace WP_Ultimo\Admin_Pages; +use SebastianBergmann\Template\RuntimeException; +use WP_Ultimo\Exception\Runtime_Exception; use WP_Ultimo\Settings; use WP_Ultimo\UI\Form; use WP_Ultimo\UI\Field; @@ -611,4 +613,269 @@ public function default_view(): void { $form->render(); } + + /** + * Overrides parent page_loaded to handle export/import functionality. + * + * @since 2.0.0 + * @return void + */ + public function page_loaded() { + + $this->handle_export(); + $this->handle_import_redirect(); + $this->register_forms(); + + parent::page_loaded(); + } + + /** + * Handle settings export request. + * + * @since 2.0.0 + * @return void + */ + protected function handle_export() { + + if ( ! isset($_GET['wu_export_settings'])) { + return; + } + check_admin_referer('wu_export_settings'); + + // Check permissions + if ( ! current_user_can('wu_edit_settings')) { + wp_die(esc_html__('You do not have permission to export settings.', 'ultimate-multisite')); + } + + $this->export_settings(); + $settings = wu_get_all_settings(); + + $export_data = [ + 'version' => \WP_Ultimo::VERSION, + 'plugin' => 'ultimate-multisite', + 'timestamp' => time(), + 'site_url' => get_site_url(), + 'wp_version' => get_bloginfo('version'), + 'settings' => $settings, + ]; + + $filename = sprintf( + 'ultimate-multisite-settings-export-%s-%s.json', + gmdate('Y-m-d'), + get_current_site()->cookie_domain, + ); + nocache_headers(); + + header('Content-Disposition: attachment; filename=' . $filename); + header('Pragma: no-cache'); + header('Expires: 0'); + wp_send_json($export_data, null, JSON_PRETTY_PRINT); + + exit; + } + + /** + * Register import form. + * + * @since 2.0.0 + * @return void + */ + public function register_forms() { + + wu_register_form( + 'import_settings', + [ + 'render' => [$this, 'render_import_settings_modal'], + 'handler' => [$this, 'handle_import_settings_modal'], + 'capability' => 'wu_edit_settings', + ] + ); + } + + /** + * Render the import settings modal. + * + * @since 2.0.0 + * @return void + */ + public function render_import_settings_modal() { + + $fields = [ + 'import_file_header' => [ + 'type' => 'header', + 'title' => __('Upload Settings File', 'ultimate-multisite'), + 'desc' => __('Select a JSON file previously exported from Ultimate Multisite.', 'ultimate-multisite'), + ], + 'import_file' => [ + 'type' => 'html', + 'content' => '', + ], + 'confirm' => [ + 'type' => 'toggle', + 'title' => __('I understand this will replace all current settings', 'ultimate-multisite'), + 'desc' => __('This action cannot be undone. Make sure you have a backup of your current settings.', 'ultimate-multisite'), + 'value' => false, + 'html_attr' => [ + 'v-model' => 'confirm', + ], + ], + 'submit_button' => [ + 'type' => 'submit', + 'title' => __('Import Settings', 'ultimate-multisite'), + 'value' => 'save', + 'classes' => 'button button-primary wu-w-full', + 'wrapper_classes' => 'wu-items-end', + 'html_attr' => [ + 'v-bind:disabled' => '!confirm', + ], + ], + ]; + + $form = new Form( + 'import_settings', + $fields, + [ + 'views' => 'admin-pages/fields', + 'classes' => 'wu-modal-form wu-widget-list wu-striped wu-m-0', + 'field_wrapper_classes' => 'wu-w-full wu-box-border wu-items-center wu-flex wu-justify-between wu-p-4 wu-m-0 wu-border-t wu-border-l-0 wu-border-r-0 wu-border-b-0 wu-border-gray-300 wu-border-solid', + 'html_attr' => [ + 'data-wu-app' => 'import_settings_modal', + 'data-state' => wp_json_encode(['confirm' => false]), + 'enctype' => 'multipart/form-data', + ], + ] + ); + + $form->render(); + } + + /** + * Handle import settings form submission. + * + * @since 2.0.0 + * @return void + * @throws Runtime_Exception When an error is found in the file. + */ + public function handle_import_settings_modal() { + + try { + // Validate file upload + if ( ! isset($_FILES['import_file']) || empty($_FILES['import_file']['tmp_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + throw new Runtime_Exception('no_file'); + } + + // Validate and parse the file + $file = $_FILES['import_file']; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + // Check for upload errors + if (UPLOAD_ERR_OK !== $file['error']) { + throw new Runtime_Exception('upload_error'); + } + + // Check file extension + $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + + if ('json' !== $file_ext) { + throw new Runtime_Exception('invalid_file_type'); + } + + // Read and decode JSON + $json_content = file_get_contents($file['tmp_name']); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + + if (false === $json_content) { + throw new Runtime_Exception('read_error'); + } + + $data = json_decode($json_content, true); + + if (null === $data) { + throw new Runtime_Exception('invalid_json'); + } + + // Validate structure + if ( ! isset($data['plugin']) || 'ultimate-multisite' !== $data['plugin']) { + throw new Runtime_Exception('invalid_format'); + } + + if ( ! isset($data['settings']) || ! is_array($data['settings'])) { + throw new Runtime_Exception('invalid_structure'); + } + } catch ( Runtime_Exception $e ) { + wp_send_json_error(new \WP_Error($e->getMessage(), __('Something is wrong with the uploaded file.', 'ultimate-multisite'))); + } + + WP_Ultimo()->settings->save_settings($data['settings']); + + do_action('wu_settings_imported', $data['settings'], $data); + + // Success + wp_send_json_success( + [ + 'redirect_url' => add_query_arg( + [ + 'tab' => 'import-export', + 'updated' => 1, + ], + wu_network_admin_url('wp-ultimo-settings') + ), + ] + ); + } + + /** + * Display a success message after import redirect. + * + * @since 2.0.0 + * @return void + */ + protected function handle_import_redirect() { + + if ( ! isset($_GET['updated']) || 'import-export' !== wu_request('tab')) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return; + } + + add_action( + 'wu_page_wizard_after_title', + function () { + ?> +
+

+
+ \WP_Ultimo::VERSION, + 'plugin' => 'ultimate-multisite', + 'timestamp' => time(), + 'site_url' => get_site_url(), + 'wp_version' => get_bloginfo('version'), + 'settings' => $settings, + ]; + + $filename = sprintf( + 'ultimate-multisite-settings-export-%s-%s.json', + gmdate('Y-m-d-His'), + get_current_site()->cookie_domain, + ); + + return [ + 'success' => true, + 'data' => $export_data, + 'filename' => $filename, + ]; + } } diff --git a/inc/class-settings.php b/inc/class-settings.php index 663f2dd8..3bd22b42 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -1514,6 +1514,116 @@ public function default_sections(): void { do_action('wu_settings_integrations'); + /* + * Import/Export + * This section holds the Import/Export settings of the Ultimate Multisite Plugin. + */ + + $this->add_section( + 'import-export', + [ + 'title' => __('Import/Export', 'ultimate-multisite'), + 'desc' => __('Export your settings to a JSON file or import settings from a previously exported file.', 'ultimate-multisite'), + 'icon' => 'dashicons-wu-download', + 'order' => 995, + ] + ); + + // Export Settings Header + $this->add_field( + 'import-export', + 'export_header', + [ + 'title' => __('Export Settings', 'ultimate-multisite'), + 'desc' => __('Download all your Ultimate Multisite settings as a JSON file for backup or migration purposes.', 'ultimate-multisite'), + 'type' => 'header', + ], + 10 + ); + + // Export Description + $this->add_field( + 'import-export', + 'export_description', + [ + 'type' => 'note', + 'desc' => __('The exported file will contain all ultimate multisite settings defined on this page. This includes general settings, payment gateway configurations, email settings, domain mapping settings, and all other plugin configurations. It does not include products, sites, domains, customers and other entities.', 'ultimate-multisite'), + 'classes' => 'wu-text-gray-600 wu-text-sm', + ], + 20 + ); + + // Export Button + $this->add_field( + 'import-export', + 'export_settings_button', + [ + 'type' => 'submit', + 'title' => __('Export Settings', 'ultimate-multisite'), + 'classes' => 'button button-primary', + 'wrapper_classes' => 'wu-items-start', + 'html_attr' => [ + 'onclick' => 'window.location.href="' . wp_nonce_url( + add_query_arg(['wu_export_settings' => '1'], wu_get_current_url()), + 'wu_export_settings' + ) . '"; return false;', + ], + ], + 30 + ); + + // Import Settings Header + $this->add_field( + 'import-export', + 'import_header', + [ + 'title' => __('Import Settings', 'ultimate-multisite'), + 'desc' => __('Upload a previously exported JSON file to restore settings.', 'ultimate-multisite'), + 'type' => 'header', + ], + 40 + ); + + // Import Button + $this->add_field( + 'import-export', + 'import_settings_button', + [ + 'type' => 'link', + 'display_value' => __('Import Settings', 'ultimate-multisite'), + 'title' => __('Import and Replace All Settings', 'ultimate-multisite'), + 'classes' => 'button button-secondary wu-ml-0 wubox', + 'wrapper_classes' => 'wu-items-start', + 'html_attr' => [ + 'href' => wu_get_form_url( + 'import_settings', + [ + 'width' => 600, + ] + ), + ], + ], + 55 + ); + + // Import Warning + $this->add_field( + 'import-export', + 'import_warning', + [ + 'type' => 'note', + 'desc' => sprintf( + '%s %s', + __('Warning:', 'ultimate-multisite'), + __('Importing settings will replace ALL current settings with the values from the uploaded file. This action cannot be undone. We recommend exporting your current settings as a backup before importing.', 'ultimate-multisite') + ), + 'classes' => 'wu-bg-red-50 wu-border-l-4 wu-border-red-500 wu-p-4', + ], + 60 + ); + + do_action('wu_settings_import_export'); + /* * Other Options * This section holds the Other Options settings of the Ultimate Multisite Plugin.