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 );
}
}
}