HEX
Server: LiteSpeed
System: Linux premium263.web-hosting.com 4.18.0-553.50.1.lve.el8.x86_64 #1 SMP Thu Apr 17 19:10:24 UTC 2025 x86_64
User: eastcjee (525)
PHP: 8.2.31
Disabled: NONE
Upload Files
File: /home/eastcjee/londonribeyesteak.co.uk/wp-content/plugins/members/admin/class-role-import.php
<?php
/**
 * Handles role import.
 *
 * @package    Members
 * @subpackage Admin
 * @author     The MemberPress Team
 * @copyright  Copyright (c) 2009 - 2018, The MemberPress Team
 * @link       https://members-plugin.com/
 * @license    http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */
namespace Members\Admin;

defined('ABSPATH') || exit;

/**
 * Class that handles importing roles and settings from a JSON file.
 * Uses a two-step flow: upload/preview, then confirm/import.
 *
 * @since  3.3.0
 * @access public
 */
final class Role_Import {

	/**
	 * Highest import schema version this class understands.
	 *
	 * @since 3.4.0
	 */
	const SUPPORTED_SCHEMA_VERSION = 1;

	/**
	 * Holds the instances of this class.
	 *
	 * @since  3.3.0
	 * @access private
	 * @var    object
	 */
	private static $instance;

	/**
	 * Sets up initial actions.
	 *
	 * @since  3.3.0
	 * @access public
	 * @return void
	 */
	public function __construct() {

		add_action( 'admin_post_members_import_roles', array( $this, 'handle_upload' ) );
		add_action( 'admin_post_members_import_roles_confirm', array( $this, 'handle_confirm' ) );
	}

	/**
	 * Handles the upload step. Validates the file, parses JSON, detects conflicts,
	 * stores data in a transient, and redirects to the preview page.
	 *
	 * @since  3.4.0
	 * @access public
	 * @return void
	 */
	public function handle_upload() {

		check_admin_referer( 'members_import_roles', 'members_import_roles_nonce' );

		if ( ! current_user_can( 'edit_roles' ) ) {
			wp_die( esc_html__( 'You do not have permission to import roles.', 'members' ) );
		}

		// Validate file upload.
		if ( empty( $_FILES['members_import_file']['tmp_name'] ) || $_FILES['members_import_file']['error'] !== UPLOAD_ERR_OK ) {
			$this->redirect_with_error( 'no_file', __( 'Please select a valid JSON file to import.', 'members' ) );
			return;
		}

		$file_ext = pathinfo( $_FILES['members_import_file']['name'], PATHINFO_EXTENSION );

		if ( 'json' !== strtolower( $file_ext ) ) {
			$this->redirect_with_error( 'invalid_file', __( 'The import file must be a .json file.', 'members' ) );
			return;
		}

		// Check file size (2MB limit).
		$max_size = 2 * MB_IN_BYTES;

		if ( $_FILES['members_import_file']['size'] > $max_size ) {
			$this->redirect_with_error( 'file_too_large', __( 'The import file is too large. Maximum file size is 2MB.', 'members' ) );
			return;
		}

		// Parse JSON.
		if ( ! is_uploaded_file( $_FILES['members_import_file']['tmp_name'] ) ) {
			$this->redirect_with_error( 'no_file', __( 'Invalid file upload.', 'members' ) );
			return;
		}

		$json = file_get_contents( $_FILES['members_import_file']['tmp_name'] );

		if ( false === $json ) {
			$this->redirect_with_error( 'read_error', __( 'The import file could not be read.', 'members' ) );
			return;
		}

		$data = json_decode( $json, true );

		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $data ) ) {
			$this->redirect_with_error( 'invalid_json', __( 'The import file contains invalid JSON.', 'members' ) );
			return;
		}

		// Validate structure.
		if ( empty( $data['meta']['plugin'] ) || 'members' !== $data['meta']['plugin'] || empty( $data['roles'] ) || ! is_array( $data['roles'] ) ) {
			$this->redirect_with_error( 'invalid_format', __( 'The import file is not a valid Members export file.', 'members' ) );
			return;
		}

		if ( isset( $data['meta']['schema_version'] ) && (int) $data['meta']['schema_version'] > self::SUPPORTED_SCHEMA_VERSION ) {
			$this->redirect_with_error(
				'unsupported_version',
				__( 'This export was created by a newer version of Members. Please update the Members plugin to import it.', 'members' )
			);
			return;
		}

		// Check whether the user wants to import settings.
		$import_settings = ! empty( $_POST['members_import_settings'] );

		// Build preview data with conflict detection.
		$preview_roles   = array();
		$duplicate_slugs = 0;

		foreach ( $data['roles'] as $role_slug => $role_data ) {

			$sanitized_slug = members_sanitize_role( $role_slug );

			if ( ! $sanitized_slug ) {
				continue;
			}

			// Skip duplicate slugs caused by sanitization (e.g. "My-Role" and "my_role" both become "my_role").
			if ( isset( $preview_roles[ $sanitized_slug ] ) ) {
				$duplicate_slugs++;
				continue;
			}

			$label    = ! empty( $role_data['label'] ) && is_string( $role_data['label'] ) ? wp_strip_all_tags( $role_data['label'] ) : $sanitized_slug;
			$caps     = isset( $role_data['capabilities'] ) && is_array( $role_data['capabilities'] ) ? $role_data['capabilities'] : array();
			$conflict = members_role_exists( $sanitized_slug ) || (bool) get_role( $sanitized_slug );

			$preview_roles[ $sanitized_slug ] = array(
				'slug'     => $sanitized_slug,
				'label'    => $label,
				'caps'     => $caps,
				'conflict' => $conflict,
			);
		}

		// If no valid roles remain after sanitization, redirect with error.
		if ( empty( $preview_roles ) ) {
			$this->redirect_with_error( 'no_valid_roles', __( 'The import file contains no valid roles. Role slugs may contain only lowercase letters, numbers, and underscores.', 'members' ) );
			return;
		}

		// Store in a transient keyed by user ID.
		$transient_key = 'members_import_preview_' . get_current_user_id();

		// Only keep settings that match known default keys to limit what's stored in the transient.
		$filtered_settings = array();

		if ( isset( $data['settings'] ) && is_array( $data['settings'] ) ) {
			$allowed_keys = array_keys( members_get_default_settings() );

			foreach ( $allowed_keys as $key ) {
				if ( isset( $data['settings'][ $key ] ) ) {
					$filtered_settings[ $key ] = $data['settings'][ $key ];
				}
			}
		}

		$transient_data = array(
			'roles'           => $preview_roles,
			'settings'        => $filtered_settings,
			'import_settings' => $import_settings,
			'meta'            => array(
				'plugin'      => isset( $data['meta']['plugin'] ) ? sanitize_text_field( $data['meta']['plugin'] ) : '',
				'version'     => isset( $data['meta']['version'] ) ? sanitize_text_field( $data['meta']['version'] ) : '',
				'export_date' => isset( $data['meta']['export_date'] ) ? sanitize_text_field( $data['meta']['export_date'] ) : '',
				'site_url'    => isset( $data['meta']['site_url'] ) ? esc_url_raw( $data['meta']['site_url'] ) : '',
				'wp_version'  => isset( $data['meta']['wp_version'] ) ? sanitize_text_field( $data['meta']['wp_version'] ) : '',
			),
			'duplicate_slugs' => $duplicate_slugs,
		);

		set_transient( $transient_key, $transient_data, 5 * MINUTE_IN_SECONDS );

		// Redirect to the preview page.
		wp_safe_redirect( add_query_arg(
			array( 'page' => 'roles', 'members_import_preview' => '1' ),
			admin_url( 'admin.php' )
		) );
		exit;
	}

	/**
	 * Handles the confirmation step. Reads the transient, processes per-role
	 * decisions, and performs the actual import.
	 *
	 * @since  3.4.0
	 * @access public
	 * @return void
	 */
	public function handle_confirm() {

		check_admin_referer( 'members_import_roles_confirm', 'members_import_roles_confirm_nonce' );

		if ( ! current_user_can( 'edit_roles' ) ) {
			wp_die( esc_html__( 'You do not have permission to import roles.', 'members' ) );
		}

		$transient_key = 'members_import_preview_' . get_current_user_id();
		$preview_data  = get_transient( $transient_key );

		if ( ! $preview_data || ! is_array( $preview_data ) || empty( $preview_data['roles'] ) || ! is_array( $preview_data['roles'] ) ) {
			$this->redirect_with_error( 'expired', __( 'Your import session has expired. Please upload the file again.', 'members' ) );
			return;
		}

		// Ensure all expected keys exist with safe defaults.
		$preview_data = wp_parse_args( $preview_data, array(
			'roles'           => array(),
			'settings'        => array(),
			'import_settings' => false,
			'meta'            => array(),
			'duplicate_slugs' => 0,
		) );

		// Clean up the transient.
		delete_transient( $transient_key );

		$actions = isset( $_POST['members_import_action'] ) && is_array( $_POST['members_import_action'] ) ? $_POST['members_import_action'] : array();
		$renames = isset( $_POST['members_import_rename'] ) && is_array( $_POST['members_import_rename'] ) ? $_POST['members_import_rename'] : array();

		$imported      = 0;
		$skipped       = 0;
		$updated       = 0;
		$renamed       = 0;
		$rename_failed = 0;
		$dropped_caps  = 0;

		$current_user_roles = wp_get_current_user()->roles;
		$default_role       = get_option( 'default_role' );

		foreach ( $preview_data['roles'] as $original_slug => $role_data ) {

			$action_for_role = isset( $actions[ $original_slug ] ) ? sanitize_key( $actions[ $original_slug ] ) : 'skip';
			$is_protected    = 'administrator' === $original_slug || in_array( $original_slug, $current_user_roles, true ) || $original_slug === $default_role || ! members_is_role_editable( $original_slug );

			if ( $is_protected && 'skip' !== $action_for_role && 'import' !== $action_for_role ) {
				$skipped++;
				continue;
			}

			if ( 'skip' === $action_for_role ) {
				$skipped++;
				continue;
			}

			if ( in_array( $action_for_role, array( 'import', 'rename' ), true ) && ! current_user_can( 'create_roles' ) ) {
				$skipped++;
				continue;
			}

			$label = isset( $role_data['label'] ) ? $role_data['label'] : $original_slug;
			$caps  = isset( $role_data['caps'] ) && is_array( $role_data['caps'] ) ? $role_data['caps'] : array();

			// Sanitize capabilities.
			$sanitized_caps = array();

			foreach ( $caps as $cap => $grant ) {

				$sanitized = members_sanitize_cap( $cap );

				if ( $sanitized ) {
					$sanitized_caps[ $sanitized ] = ( $grant === true || $grant === 1 || $grant === '1' );
				} else {
					$dropped_caps++;
				}
			}

			if ( 'rename' === $action_for_role ) {

				$new_slug = isset( $renames[ $original_slug ] ) ? members_sanitize_role( $renames[ $original_slug ] ) : '';

				if ( ! $new_slug || members_role_exists( $new_slug ) || get_role( $new_slug ) ) {
					$rename_failed++;
					continue;
				}

				add_role( $new_slug, $label, $sanitized_caps );

				members_register_role( $new_slug, array(
					'label' => $label,
					'caps'  => $sanitized_caps,
				) );

				members_track_created_role( $new_slug );

				do_action( 'members_role_added', $new_slug );
				$renamed++;

			} else if ( 'overwrite' === $action_for_role ) {

				remove_role( $original_slug );
				add_role( $original_slug, $label, $sanitized_caps );

				members_unregister_role( $original_slug );
				members_register_role( $original_slug, array(
					'label' => $label,
					'caps'  => $sanitized_caps,
				) );

				do_action( 'members_role_updated', $original_slug );
				$updated++;

			} else if ( 'import' === $action_for_role ) {

				if ( members_role_exists( $original_slug ) || get_role( $original_slug ) ) {
					$skipped++;
					continue;
				}

				add_role( $original_slug, $label, $sanitized_caps );

				members_register_role( $original_slug, array(
					'label' => $label,
					'caps'  => $sanitized_caps,
				) );

				members_track_created_role( $original_slug );

				do_action( 'members_role_added', $original_slug );
				$imported++;

			} else {
				// Unrecognized action — treat as skip for safety.
				$skipped++;
			}
		}

		// Optionally import settings.
		$settings_imported = false;

		if ( ! empty( $preview_data['import_settings'] ) && ! empty( $preview_data['settings'] ) && is_array( $preview_data['settings'] ) ) {

			$defaults     = members_get_default_settings();
			$existing     = get_option( 'members_settings', array() );
			$new_settings = is_array( $existing ) ? $existing : array();

			foreach ( $defaults as $key => $default_value ) {

				if ( isset( $preview_data['settings'][ $key ] ) ) {
					$value = $preview_data['settings'][ $key ];

					// Cast to the same type as the default.
					if ( is_bool( $default_value ) ) {
						$new_settings[ $key ] = (bool) $value;
					} elseif ( is_int( $default_value ) ) {
						$new_settings[ $key ] = (int) $value;
					} elseif ( is_string( $default_value ) ) {
						$new_settings[ $key ] = is_scalar( $value ) ? sanitize_text_field( (string) $value ) : $default_value;
					} else {
						$new_settings[ $key ] = map_deep( $value, 'sanitize_text_field' );
					}
				}
			}

			update_option( 'members_settings', $new_settings );
			$settings_imported = true;
		}

		// Build result message.
		$messages = array();

		if ( $imported > 0 )
			$messages[] = sprintf( _n( '%s role imported.', '%s roles imported.', $imported, 'members' ), number_format_i18n( $imported ) );

		if ( $updated > 0 )
			$messages[] = sprintf( _n( '%s role updated.', '%s roles updated.', $updated, 'members' ), number_format_i18n( $updated ) );

		if ( $renamed > 0 )
			$messages[] = sprintf( _n( '%s role imported as renamed copy.', '%s roles imported as renamed copies.', $renamed, 'members' ), number_format_i18n( $renamed ) );

		if ( $skipped > 0 )
			$messages[] = sprintf( _n( '%s role skipped.', '%s roles skipped.', $skipped, 'members' ), number_format_i18n( $skipped ) );

		if ( $rename_failed > 0 )
			$messages[] = sprintf( _n( '%s role rename failed (slug already exists).', '%s role renames failed (slug already exists).', $rename_failed, 'members' ), number_format_i18n( $rename_failed ) );

		if ( $dropped_caps > 0 )
			$messages[] = sprintf( _n( '%s invalid capability was skipped.', '%s invalid capabilities were skipped.', $dropped_caps, 'members' ), number_format_i18n( $dropped_caps ) );

		if ( $settings_imported )
			$messages[] = __( 'Plugin settings imported.', 'members' );

		if ( empty( $messages ) )
			$messages[] = __( 'No roles were imported.', 'members' );

		set_transient( 'members_import_message_' . get_current_user_id(), implode( ' ', $messages ), 30 );

		wp_safe_redirect( add_query_arg( 'members_imported', '1', members_get_edit_roles_url() ) );
		exit;
	}

	/**
	 * Redirects back to the roles page with an error message stored in a transient.
	 *
	 * @since  3.3.0
	 * @access private
	 * @param  string  $code
	 * @param  string  $message
	 * @return void
	 */
	private function redirect_with_error( $code, $message ) {

		set_transient( 'members_import_error_' . get_current_user_id(), $message, 30 );

		wp_safe_redirect( add_query_arg( 'members_import_error', $code, members_get_edit_roles_url() ) );
		exit;
	}

	/**
	 * Returns the instance.
	 *
	 * @since  3.3.0
	 * @access public
	 * @return object
	 */
	public static function get_instance() {

		if ( ! self::$instance )
			self::$instance = new self;

		return self::$instance;
	}
}

Role_Import::get_instance();