init(); } /** * Start the engine running * * @return void */ private function init() { add_filter( 'export_wp_filename', [ $this, 'filter_xml_export_filename' ], 10, 3 ); add_action( 'admin_init', [ $this, 'export_settings' ] ); add_action( 'wp_ajax_adminoptim_import_settings', [ $this, 'import_settings' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } /** * Give Admin Optimizer XML exports a descriptive filename. * * @param string $filename Default WordPress export filename. * @param string $sitename Sanitized site name. * @param string $date Export date. * * @return string */ public function filter_xml_export_filename( $filename, $sitename, $date ) { $content = isset( $_GET['content'] ) && is_scalar( $_GET['content'] ) ? sanitize_key( wp_unslash( $_GET['content'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $types = [ 'adminoptim-form' => 'admin-optimizer-form-builder-forms', 'ao_custom_code' => 'admin-optimizer-custom-code', ]; if ( ! isset( $types[ $content ] ) ) { return $filename; } return sanitize_file_name( $sitename . '-' . $types[ $content ] . '-' . $date . '.xml' ); } /** * List of Settings fields * * @return array */ private function get_settings_fields() { $fields = [ [ 'type' => 'callback', 'title' => __( 'Import Settings', 'admin-optimizer' ), 'id' => 'import-settings', 'name' => 'import_settings', 'callback' => [ $this, 'render_import_field' ], 'desc' => __( 'Import settings from a JSON file. This will overwrite all current settings. Please make sure to backup your current settings before importing a new file.', 'admin-optimizer' ), ], [ 'type' => 'callback', 'title' => __( 'Export Settings', 'admin-optimizer' ), 'id' => 'export-settings', 'name' => 'export_settings', 'callback' => [ $this, 'render_export_field' ], 'desc' => __( 'Export all your settings to a JSON file.', 'admin-optimizer' ), ], ]; if ( $this->is_module_activated( 'enable_form_builder' ) ) { $fields[] = [ 'type' => 'callback', 'title' => __( 'Export Form Builder Forms', 'admin-optimizer' ), 'id' => 'export-form-builder-forms', 'name' => 'export_form_builder_forms', 'callback' => [ $this, 'render_form_builder_export_field' ], 'desc' => __( 'Export your Form Builder forms to an XML file.', 'admin-optimizer' ), ]; $fields[] = [ 'type' => 'callback', 'title' => __( 'Import Form Builder Forms', 'admin-optimizer' ), 'id' => 'import-form-builder-forms', 'name' => 'import_form_builder_forms', 'callback' => [ $this, 'render_form_builder_import_field' ], 'desc' => __( 'Import your Form Builder forms with the WordPress importer.', 'admin-optimizer' ), ]; } if ( $this->is_module_activated( 'enable_custom_code' ) ) { $fields[] = [ 'type' => 'callback', 'title' => __( 'Export Custom Code', 'admin-optimizer' ), 'id' => 'export-custom-code', 'name' => 'export_custom_code', 'callback' => [ $this, 'render_custom_code_export_field' ], 'desc' => __( 'Export your Custom Code snippets to an XML file.', 'admin-optimizer' ), ]; $fields[] = [ 'type' => 'callback', 'title' => __( 'Import Custom Code', 'admin-optimizer' ), 'id' => 'import-custom-code', 'name' => 'import_custom_code', 'callback' => [ $this, 'render_custom_code_import_field' ], 'desc' => __( 'Import your Custom Code snippets with the WordPress importer.', 'admin-optimizer' ), ]; } return $fields; } /** * Render settings page */ public function render_settings_page() { $fields = $this->get_settings_fields(); ?>

'true', 'content' => 'adminoptim-form', ], admin_url( 'export.php' ) ), 'export' ); ?>

'true', 'content' => 'ao_custom_code', ], admin_url( 'export.php' ) ), 'export' ); ?>

load_option_names(); if ( ! empty( $_REQUEST['nonce'] ) ) { if ( wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['nonce'] ) ), 'adminoptim-export-settings' ) ) { $export_options = apply_filters( 'adminoptim_export_options', $this->get_export_arr() ); header( 'Content-Description: File Transfer' ); header( 'Content-Type: application/json' ); header( 'Content-Disposition: attachment; filename="admin-optimizer-settings-' . wp_date( 'Ymd' ) . '.json"' ); header( 'Expires: 0' ); header( 'Cache-Control: must-revalidate' ); header( 'Pragma: public' ); echo wp_json_encode( $export_options ); die(); } } } } /** * Get export array * * @return array */ private function get_export_arr() { $export_arr = []; foreach ( $this->get_option_names() as $option_name ) { if ( self::UAM_ROLES_BACKUP_KEY === $option_name ) { continue; } $option = get_option( $option_name ); if ( ! empty( $option ) && is_array( $option ) ) { if ( 'adminoptim_smtp' === $option_name ) { // Passwords (and the references to their secrets) never leave the site. unset( $option['smtp_password'] ); foreach ( (array) ( $option['additional_profiles'] ?? [] ) as $profile_id => $profile ) { unset( $option['additional_profiles'][ $profile_id ]['password'] ); } } $export_arr[ $option_name ] = $option; } } $uam_roles = get_option( $this->get_user_roles_option_name() ); if ( ! empty( $uam_roles ) && is_array( $uam_roles ) ) { $export_arr[ self::UAM_ROLES_BACKUP_KEY ] = $uam_roles; } return $export_arr; } /** * Get the current site's native WordPress roles option name. * * @return string */ private function get_user_roles_option_name() { $roles = wp_roles(); if ( is_object( $roles ) && ! empty( $roles->role_key ) ) { return (string) $roles->role_key; } global $wpdb; return $wpdb->get_blog_prefix() . 'user_roles'; } /** * Sanitize role definitions restored from a UAM backup. * * @param array $roles Raw role definitions. * * @return array */ private function sanitize_user_access_manager_roles( array $roles ) { $sanitized = []; foreach ( $roles as $role_key => $details ) { $role_key = sanitize_key( (string) $role_key ); if ( '' === $role_key || ! is_array( $details ) ) { continue; } $capabilities = []; foreach ( isset( $details['capabilities'] ) && is_array( $details['capabilities'] ) ? $details['capabilities'] : [] as $capability => $granted ) { $capability = sanitize_key( (string) $capability ); if ( '' !== $capability ) { $capabilities[ $capability ] = (bool) $granted; } } $sanitized[ $role_key ] = [ 'name' => sanitize_text_field( isset( $details['name'] ) && is_scalar( $details['name'] ) ? (string) $details['name'] : $role_key ), 'capabilities' => $capabilities, ]; } return $sanitized; } /** * Restore User Access Manager role/capability customizations from an * imported backup. * * Merges onto the site's current live roles rather than replacing the * option wholesale, so a role the import doesn't mention is never * discarded. Any capability listed in self::UAM_PROTECTED_CAPABILITIES * always keeps its current site value, regardless of what the imported * file specifies, so this path can never be used to grant or revoke one * of those capabilities. * * @param array $raw_roles Decoded UAM_ROLES_BACKUP_KEY payload from the import file. * * @return void */ private function import_user_access_manager_roles( array $raw_roles ) { $sanitized = $this->sanitize_user_access_manager_roles( $raw_roles ); if ( empty( $sanitized ) ) { return; } $current_roles = wp_roles(); $merged = ( is_object( $current_roles ) && is_array( $current_roles->roles ) ) ? $current_roles->roles : []; foreach ( $sanitized as $role_key => $details ) { $existing_capabilities = isset( $merged[ $role_key ]['capabilities'] ) && is_array( $merged[ $role_key ]['capabilities'] ) ? $merged[ $role_key ]['capabilities'] : []; $incoming_capabilities = $details['capabilities']; foreach ( self::UAM_PROTECTED_CAPABILITIES as $protected ) { if ( array_key_exists( $protected, $existing_capabilities ) ) { $incoming_capabilities[ $protected ] = $existing_capabilities[ $protected ]; } else { unset( $incoming_capabilities[ $protected ] ); } } $merged[ $role_key ] = [ 'name' => $details['name'], 'capabilities' => array_merge( $existing_capabilities, $incoming_capabilities ), ]; } $has_manage_options_role = false; foreach ( $merged as $role ) { if ( ! empty( $role['capabilities']['manage_options'] ) ) { $has_manage_options_role = true; break; } } if ( ! $has_manage_options_role ) { return; } update_option( $this->get_user_roles_option_name(), $merged, false ); } /** * Load option name helpers. * * @return void */ private function load_option_names() { include_once self::SECTION_PATH . 'includes/option-names.php'; } /** * Get allowed option names for export/import. * * @return string[] */ private function get_option_names() { $this->load_option_names(); $option_names = function_exists( __NAMESPACE__ . '\ExportImport\ao_get_option_names' ) ? ao_get_option_names() : []; $option_names = apply_filters( 'adminoptim_import_options_name', $option_names ); $option_names = apply_filters( 'adminoptim_export_import_option_names', $option_names ); $option_names = array_filter( array_unique( is_array( $option_names ) ? $option_names : [] ), static function ( $option_name ) { return is_string( $option_name ) && '' !== $option_name; } ); return array_values( $option_names ); } /** * Import Settings */ public function import_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( new \WP_Error( 'adminoptim-unauthorized-user', ao_get_admin_notice( __( 'Unauthorized action.', 'admin-optimizer' ), [ 'type' => 'error', ] ) ) ); } check_ajax_referer( 'adminoptim-import-settings', 'nonce' ); $this->load_option_names(); $file = isset( $_FILES['file']['name'] ) ? sanitize_file_name( $_FILES['file']['name'] ) : ''; $extension = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) ); if ( 'json' !== $extension ) { wp_send_json_error( [ 'message' => __( 'Please upload a valid .json file', 'admin-optimizer' ) ] ); } $import_file = isset( $_FILES['file']['tmp_name'] ) ? $_FILES['file']['tmp_name'] : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( empty( $import_file ) ) { wp_send_json_error( [ 'message' => __( 'Please upload a file to import', 'admin-optimizer' ) ] ); } // Retrieve the settings from the file and convert the json object to an array. $raw_contents = file_get_contents( $import_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( false === $raw_contents ) { wp_send_json_error( [ 'message' => __( 'Could not read the uploaded file.', 'admin-optimizer' ) ] ); } $settings = json_decode( $raw_contents, true ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $settings ) ) { wp_send_json_error( [ 'message' => __( 'The uploaded JSON file could not be parsed.', 'admin-optimizer' ) ] ); } if ( ! empty( $settings ) && is_array( $settings ) ) { if ( isset( $settings[ self::UAM_ROLES_BACKUP_KEY ] ) && is_array( $settings[ self::UAM_ROLES_BACKUP_KEY ] ) ) { $this->import_user_access_manager_roles( $settings[ self::UAM_ROLES_BACKUP_KEY ] ); } $option_names = $this->get_option_names(); $smtp_imported = false; foreach ( $settings as $key => $setting ) { if ( self::UAM_ROLES_BACKUP_KEY === $key ) { continue; // Handled separately above, never through the generic path. } if ( in_array( $key, $option_names, true ) && is_array( $setting ) ) { array_walk_recursive( $setting, 'sanitize_text_field' ); if ( 'adminoptim_smtp' === $key ) { $smtp_imported = true; $existing_smtp = get_option( 'adminoptim_smtp' ); $setting['smtp_password'] = ( is_array( $existing_smtp ) && isset( $existing_smtp['smtp_password'] ) ) ? $existing_smtp['smtp_password'] : ''; // A file can never supply a profile's password (or a reference to another secret); keep this site's own. foreach ( (array) ( $setting['additional_profiles'] ?? [] ) as $profile_id => $profile ) { if ( ! is_array( $profile ) ) { continue; } $setting['additional_profiles'][ $profile_id ]['password'] = (string) ( $existing_smtp['additional_profiles'][ $profile_id ]['password'] ?? '' ); } } // Lets the module that owns an option validate an imported value // (sanitize, merge into what is stored, drop unknown keys) before // it is stored. No callback, no change. $setting = apply_filters( 'adminoptim_import_option_value', $setting, $key ); if ( ! is_array( $setting ) ) { continue; } update_option( $key, $setting, false ); } } } $message = __( 'Settings imported', 'admin-optimizer' ); if ( ! empty( $smtp_imported ) ) { $message .= ' ' . __( 'SMTP credentials are not included in backups for security reasons. Re-enter your SMTP password to restore email delivery.', 'admin-optimizer' ); } wp_send_json_success( [ 'status' => 'success', 'message' => $message, ] ); } /** * Enqueue scripts * * @param string $hook_suffix Page hook. * * @return void */ public function enqueue_scripts( $hook_suffix ) { if ( 'toplevel_page_' . ADMINOPTIMIZER_MODULES_MENU_SLUG === $hook_suffix ) { wp_enqueue_script( 'adminoptim-export-import', self::SECTION_URL . 'assets/js/export-import.min.js', [ 'jquery' ], ADMINOPTIMIZER_VERSION, true ); wp_enqueue_style( 'adminoptim-export-import', self::SECTION_URL . 'assets/css/export-import.min.css', [], ADMINOPTIMIZER_VERSION ); } } }