<?php
/**
 * Permissions class.
 *
 * @since 1.9.11
 *
 * @package Envira_Gallery
 * @author  Envira Gallery Team <support@enviragallery.com>
 */

namespace Envira\Admin;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Permissions class.
 *
 * @since 1.9.11
 *
 * @package Envira_Gallery
 * @author  Envira Gallery Team <support@enviragallery.com>
 */
class Permissions {

	/**
	 * Permissions constructor.
	 *
	 * @since 1.9.11
	 */
	public function __construct() {
		// Actions.
		add_action( 'envira_permissions_update', [ $this, 'envira_permissions_update' ], 10, 1 );
		// Set default permissions.
		$this->set_default_permissions();
	}

	/**
	 * Localize the permissions.
	 *
	 * @since 1.9.11
	 */
	public function envira_permissions_localize() {
		$permissions = $this->get_fields();
		$map         = [];
		foreach ( $permissions as $permission ) {
			switch ( $permission ) {
				case 'envira_permissions_edit_others':
					$map[ $permission ] = [ 'envira_permissions_edit_others' ];
					break;
				case 'envira_permissions_delete':
					$map[ $permission ] = [ 'envira_permissions_create', 'envira_permissions_edit', 'envira_permissions_delete' ];
					break;
				case 'envira_permissions_edit':
					$map[ $permission ] = [ 'envira_permissions_create', 'envira_permissions_edit' ];
					break;
				default:
					$map[ $permission ] = [ 'envira_permissions_create' ];
			}
		}

		$permissions_labels = [
			'envira_permissions_create'      => esc_html__( 'Create Galleries' ),
			'envira_permissions_edit'        => esc_html__( 'Edit Galleries' ),
			'envira_permissions_delete'      => esc_html__( 'Delete Galleries' ),
			'envira_permissions_edit_others' => esc_html__( 'Edit Others Galleries' ),
		];

		$map                = apply_filters( 'envira_permissions_fields_mapping', $map );
		$permissions_labels = apply_filters( 'envira_permissions_labels', $permissions_labels );

		wp_localize_script( ENVIRA_SLUG . '-admin-script', 'enviraPermissions', $map );
		wp_localize_script( ENVIRA_SLUG . '-admin-script', 'enviraPermissionsLabels', $permissions_labels );
	}

	/**
	 * Map the permissions and capabilities for user roles.
	 *
	 * @since 1.9.11
	 * @return array
	 */
	public function map_permissions(): array {
		// Map the permissions and capabilities and user roles.

		$permissions = [
			'envira_permissions_create'      => [ 'create_envira_galleries', 'read_envira_gallery', 'publish_envira_galleries' ],
			'envira_permissions_edit'        => [ 'edit_envira_gallery', 'edit_envira_galleries', 'edit_published_envira_galleries' ],
			'envira_permissions_delete'      => [ 'delete_envira_galleries', 'delete_envira_gallery', 'delete_published_envira_galleries' ],
			'envira_permissions_edit_others' => [
				'edit_other_envira_galleries',
				'edit_others_envira_galleries',
				'edit_private_envira_galleries',
				'read_private_envira_galleries',
			],
		];

		$permissions = apply_filters( 'envira_permissions_map', $permissions );

		return $permissions;
	}

	/**
	 * Update the permissions and capabilities for user roles.
	 * Also used to set permissions on plugin activation of addons.
	 *
	 * @since 1.9.11
	 *
	 * @param array $permissions The permissions.
	 */
	public function envira_permissions_update( $permissions ) {
		if ( empty( $permissions ) ) {
			return;
		}

		foreach ( $permissions as $permission => $capabilities ) {
			$roles = get_option( $permission, [] );
			$roles = maybe_unserialize( $roles );
			$this->add_capabilities_by_permissions( $permission, $roles, $permissions );
		}
	}

	/**
	 * Set the default permissions for user roles.
	 *
	 * @since 1.9.11
	 */
	public function set_default_permissions() {

		$has_default_permissions = get_option( 'envira_permissions_default', false );

		if ( $has_default_permissions ) {
			return;
		}

		$all    = [ 'administrator', 'editor', 'author' ];
		$editor = [ 'administrator', 'editor' ];

		$permissions = [
			'envira_permissions_create'      => $all,
			'envira_permissions_edit'        => $all,
			'envira_permissions_delete'      => $all,
			'envira_permissions_edit_others' => $editor,
		];

		$permissions = apply_filters( 'envira_permissions_default_roles', $permissions );

		foreach ( $permissions as $permission => $roles ) {
			update_option( $permission, maybe_serialize( $roles ) );
			$this->add_capabilities_by_permissions( $permission, $roles );
		}

		update_option( 'envira_permissions_default', 'yes' );
	}

	/**
	 * Add capabilities by permissions.
	 * This is used to add capabilities to user roles based on the permissions.
	 * Addons can use this to add capabilities to user roles on activation.
	 *
	 * @param string $new_permission The new permission to be added.
	 * @param array  $role_names        The role names.
	 * @param array  $permissions   The permissions array that contains mapping.
	 *
	 * @return void
	 */
	public function add_capabilities_by_permissions( string $new_permission, array $role_names, array $permissions = [] ) {

		if ( empty( $permissions ) ) {
			$permissions = $this->map_permissions();
		}

		// remove administrator role from the roles array.
		$role_names = array_diff( $role_names, [ 'administrator' ] );

		// Roles to remove permissions.
		$all_roles = array_keys( $this->get_user_roles() );
		// remove administrator role from the roles array.
		$all_roles = array_diff( $all_roles, [ 'administrator' ] );

		$roles_to_remove = [];
		foreach ( $all_roles as $role_name ) {
			if ( ! in_array( $role_name, $role_names, true ) ) {
				$roles_to_remove[] = $role_name;
			}
		}

		// Add the capabilities to the user roles.

		foreach ( $role_names as $role_name ) {
			$role = get_role( $role_name );
			// Skip if role is not found or if it's an administrator role.
			if ( ! is_object( $role ) ) {
				continue;
			}
			// Get the capabilities for the new permission.
			$cap = $permissions[ $new_permission ];

			foreach ( $cap as $capability ) {
				$check = $role->has_cap( $capability );
				if ( empty( $check ) ) {
					$role->add_cap( $capability );
				}
			}
		}

		// Remove the capabilities from the user roles if they are not in the new permission.

		foreach ( $roles_to_remove as $role_name ) {
			$role = get_role( $role_name );
			// Skip if role is not found or if it's an administrator role.
			if ( ! is_object( $role ) ) {
				continue;
			}
			// Get the capabilities for the new permission.
			$cap = $permissions[ $new_permission ];

			foreach ( $cap as $capability ) {
				$check = $role->has_cap( $capability );
				if ( ! empty( $check ) ) {
					$role->remove_cap( $capability );
				}
			}
		}
	}

	/**
	 * Get the user roles.
	 * This is used to get the user roles to display in the permissions settings.
	 * Only roles with edit_posts capability are shown.
	 *
	 * @since 1.9.11
	 * @return array
	 */
	public function get_user_roles(): array {
		global $wp_roles;
		$roles_array  = [];
		$envira_roles = $wp_roles;
		if ( ! is_object( $envira_roles ) ) {
			// Don't assign this to the global because otherwise WordPress won't override it.
			$envira_roles = new \WP_Roles();
		}

		$role_names = $envira_roles->get_names();
		asort( $role_names );
		foreach ( $role_names as $role_name => $role_label ) {
			$role = get_role( $role_name );
			if ( ! is_object( $role ) ) {
				continue;
			}

			// Only show roles that can edit posts. To avoid showing roles that can't edit.
			if ( ! $role->has_cap( 'edit_posts' ) ) {
				continue;
			}
			$roles_array[ $role_name ] = $role_label;
		}
		return $roles_array;
	}

	/**
	 * Sanitize the field data.
	 *
	 * @since 1.9.11
	 *
	 * @param array $field_value The field value.
	 *
	 * @return array
	 */
	public function sanitize_field_data( array $field_value ): array {
		return array_map( 'sanitize_text_field', array_values( $field_value ) );
	}

	/**
	 * Get the fields.
	 *
	 * @since 1.9.11
	 *
	 * @return array
	 */
	public function get_fields(): array {
		$fields = [ 'envira_permissions_create', 'envira_permissions_edit', 'envira_permissions_delete', 'envira_permissions_edit_others' ];
		$fields = apply_filters( 'envira_permissions_fields', $fields );
		return $fields;
	}

	/**
	 * Render the permissions field.
	 *
	 * @since 1.9.11
	 *
	 * @param string $field The field name.
	 *
	 * @return void
	 */
	public function render_permissions_field( string $field ) {

		$allowed_html = [
			'select' => [
				'name'     => [],
				'id'       => [],
				'class'    => [],
				'multiple' => 'multiple',
			],
			'option' => [
				'value'    => [],
				'selected' => [],
			],
			'div'    => [
				'class' => [],
			],
		];

		$field_value = get_option( $field, [] );
		$field_value = maybe_unserialize( $field_value );
		$data        = '<div class="envira-permissions-field">';
		$data       .= '<select name="' . esc_attr( $field ) . '[]" id="' . esc_attr( $field ) . '" multiple class="envira-permissions-select-field">';
		$selected    = '';
		$role_names  = $this->get_user_roles();
		foreach ( $role_names as $role_name => $role_label ) {
			if ( isset( $field_value ) && is_array( $field_value ) ) {
				$selected = in_array( $role_name, $field_value, true ) ? 'selected' : '';
			}
			$data .= '<option value="' . esc_attr( $role_name ) . '" ' . esc_attr( $selected ) . '>' . esc_html( $role_label ) . '</option>';
		}
		$data .= '</select>';
		$data .= '</div>';
		echo wp_kses( $data, $allowed_html );
	}
}
