<?php

// Prevent direct file access.
defined( 'ABSPATH' ) || die;

if ( ! class_exists( 'Mai_User_Role_Base' ) ):
/**
 * The User Role Base class.
 *
 * @version 0.2.0
 *
 * @link https://gist.github.com/JiveDig/5501764b97a967d53af0edb78ff15268
 * @link https://wordpress.stackexchange.com/questions/330812/changing-author-slug-for-a-custom-role-without-using-plugin
 *
 * base: The url base, for `/homies/{user_nicename}`.
 * redirect: The status to use when redirecting the existing `/author/{user_nicename}` urls.
 * priority: The priority of this role vs other roles with custom bases. Higher priorities will take precedence.
 *
 * Example usage:
 *	new Mai_User_Role_Base(
 *		[
 *			'customrole' =>  [
 *				'base'     => 'homies',
 *				'redirect' => 301,
 *				'priority' => 0,
 *			],
 *			'editor' =>  [
 *				'base'     => 'ninjas',
 *				'redirect' => 301,
 *				'priority' => 10,
 *			],
 *		],
 *	);
 */
class Mai_User_Role_Base {
	protected $config;

	/**
	 * Construct the class.
	 *
	 * @param array $config The configuration. An array of arrays of role, base, and status.
	 *
	 * @since 0.1.0
	 */
	function __construct( $config = [] ) {
		// Set the data.
		$this->config = $this->sanitize_config( $config );

		// Run hooks.
		$this->hooks();
	}

	/**
	 * Add hooks.
	 *
	 * @since 0.1.0
	 *
	 * @return void
	 */
	function hooks() {
		add_action( 'init',              [ $this, 'add_permastruct' ] );
		add_action( 'template_redirect', [ $this, 'add_redirect' ] );
		add_filter( 'author_link',       [ $this, 'author_link' ], 10, 3 );
	}

	/**
	 * Add permastruct.
	 *
	 * @since 0.1.0
	 *
	 * @return void
	 */
	function add_permastruct() {
		// Loop through config.
		foreach ( $this->config as $role => $values ) {
			$base = $values['base'];

			// Add the permastruct.
			add_permastruct( "%{$role}_{$base}%", "{$base}/%author%", [
				'ep_mask' => EP_AUTHORS,
			] );
		}
	}

	/**
	 * Add redirect.
	 *
	 * @since 0.1.0
	 *
	 * @return void
	 */
	function add_redirect() {
		// Bail if not an author archive.
		if ( ! is_author() ) {
			return;
		}

		// Get author.
		$author      = get_queried_object();
		$author_id   = $author->ID;
		$author_slug = $author->user_nicename;
		$role        = $this->get_user_role( $author_id );

		// Get the server request.
		$request = trailingslashit( $_SERVER['REQUEST_URI'] );

		// If valid role.
		if ( $role ) {
			// Get the base and redirect.
			$base   = $this->config[ $role ]['base'];
			$status = $this->config[ $role ]['redirect'];

			// Build strings to compare.
			$compare = "/{$base}/" . $author_slug . '/';

			// Bail if already the correct base.
			if ( $compare === $request ) {
				return;
			}

			// Redirect to the correct author url.
			wp_safe_redirect( user_trailingslashit( home_url( "/{$base}/" . $author_slug ) ), $status );
			exit;
		}

		// Get author posts url path.
		$url  = esc_url( get_author_posts_url( $author_id, $author_slug ) );
		$path = wp_parse_url( $url, PHP_URL_PATH );

		// Bail if already the correct path.
		if ( $path === $request ) {
			return;
		}

		// Redirect to the correct author url.
		wp_safe_redirect( user_trailingslashit( $url ), 301 );
		exit;
	}

	/**
	 * Change the author link url.
	 *
	 * @since 0.1.0
	 *
	 * @param string $link            The author link.
	 * @param int    $author_id       The author ID.
	 * @param string $author_nicename The author nicename.
	 *
	 * @return string
	 */
	function author_link( $link, $author_id, $author_nicename ) {
		$role = $this->get_user_role( $author_id );

		// Bail if not a valid role.
		if ( ! $role ) {
			return $link;
		}

		// Get the new link.
		$base = $this->config[ $role ]['base'];
		$link = "/{$base}/" . $author_nicename;
		$link = home_url( user_trailingslashit( $link ) );

		return esc_url( $link );
	}

	/**
	 * Gets the highest priority valid user role.
	 *
	 * @since 0.1.0
	 *
	 * @param int $user_id The user ID.
	 *
	 * @return bool
	 */
	function get_user_role( $user_id ) {
		// Set cache.
		static $cache = [];

		// If we have cache, return it.
		if ( isset( $cache[ $user_id ] ) ) {
			return $cache[ $user_id ];
		}

		// Get and set.
		$roles             = $this->get_valid_roles( $user_id );
		$cache[ $user_id ] = reset( $roles );

		return $cache[ $user_id ];
	}

	/**
	 * Check if the user has a role we need to handle.
	 *
	 * @since 0.1.0
	 *
	 * @param int $user_id The user ID.
	 *
	 * @return bool
	 */
	function get_valid_roles( $user_id ) {
		// Set cache.
		static $cache = [];

		// If we have cache, return it.
		if ( isset( $cache[ $user_id ] ) ) {
			return $cache[ $user_id ];
		}

		// Get and set.
		$user              = get_userdata( $user_id );
		$cache[ $user_id ] = $user ? array_intersect( array_keys( $this->config ), (array) $user->roles ) : [];

		return $cache[ $user_id ];
	}

	/**
	 * Sanitize the config.
	 *
	 * @since 0.1.0
	 *
	 * @param array $config The config.
	 *
	 * @return array
	 */
	function sanitize_config( $config ) {
		$sanitized = [];
		$roles     = wp_roles()->roles;

		// Loop through config.
		foreach ( $config as $role => $values ) {
			// Set data defaults.
			$values = wp_parse_args( $values, [
				'base'     => '',
				'redirect' => 301,
				'priority' => 0,
			] );

			// Skip if not a valid role or missing base.
			if ( ! ( isset( $roles[ $role ] ) && $values['base'] ) ) {
				continue;
			}

			// Set the role, base, and status.
			foreach ( $values as $key => $value ) {
				switch ( $key ) {
					case 'redirect':
						$values[ $key ] = $value ? absint( $value ) : 301;
						break;
					case 'priority':
						$values[ $key ] = $value ? absint( $value ) : 0;
						break;
					default:
						$values[ $key ] = sanitize_key( $value );
						break;
				}
			}

			// Add to sanitized array.
			$sanitized[ $role ] = $values;
		}

		// Sort the original array by priority (descending order for highest first).
		uasort( $sanitized, function( $a, $b ) {
			return $b['priority'] <=> $a['priority'];
		});

		return $sanitized;
	}
}
endif;