Source: controllers/class-user.php

<?php
/**
 * User controller.
 *
 * @package HivePress\Controllers
 */

namespace HivePress\Controllers;

use HivePress\Helpers as hp;
use HivePress\Models;
use HivePress\Forms;
use HivePress\Menus;
use HivePress\Blocks;
use HivePress\Emails;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Manages users.
 */
final class User extends Controller {

	/**
	 * Class constructor.
	 *
	 * @param array $args Controller arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'routes' => [
					'users_resource'               => [
						'path'   => '/users',
						'method' => 'GET',
						'action' => [ $this, 'get_users' ],
						'rest'   => true,
					],

					/**
					* @OA\Parameter(
					*     name="user_id",
					*     description="User ID.",
					*     in="path",
					*     required=true,
					*     @OA\Schema(type="integer"),
					* ),
					*/
					'user_resource'                => [
						'base' => 'users_resource',
						'path' => '/(?P<user_id>\d+)',
						'rest' => true,
					],

					/**
					 * @OA\Post(
					 *     path="/users",
					 *     summary="Register a user",
					 *     tags={"Users"},
					 *     @OA\RequestBody(
					 *       @OA\JsonContent(
					 *         @OA\Property(property="username", type="string", description="Username."),
					 *         @OA\Property(property="email", type="string", description="Email address."),
					 *         @OA\Property(property="password", type="string", description="Password.")
					 *       ),
					 *     ),
					 *     @OA\Response(response="201", description="")
					 * )
					 */
					'user_register_action'         => [
						'base'   => 'users_resource',
						'method' => 'POST',
						'action' => [ $this, 'register_user' ],
						'rest'   => true,
					],

					/**
					 * @OA\Post(
					 *     path="/users/login",
					 *     summary="Login a user",
					 *     tags={"Users"},
					 *     @OA\RequestBody(
					 *       @OA\JsonContent(
					 *         @OA\Property(property="username_or_email", type="string", description="Username or email address."),
					 *         @OA\Property(property="password", type="string", description="Password.")
					 *       ),
					 *     ),
					 *     @OA\Response(response="200", description="")
					 * )
					 */
					'user_login_action'            => [
						'base'   => 'users_resource',
						'path'   => '/login',
						'method' => 'POST',
						'action' => [ $this, 'login_user' ],
						'rest'   => true,
					],

					/**
					 * @OA\Post(
					 *     path="/users/request-password",
					 *     summary="Request a password reset",
					 *     tags={"Users"},
					 *     @OA\RequestBody(
					 *       @OA\JsonContent(
					 *         @OA\Property(property="username_or_email", type="string", description="Username or email address.")
					 *       ),
					 *     ),
					 *     @OA\Response(response="200", description="")
					 * )
					 */
					'user_password_request_action' => [
						'base'   => 'users_resource',
						'path'   => '/request-password',
						'method' => 'POST',
						'action' => [ $this, 'request_user_password' ],
						'rest'   => true,
					],

					/**
					 * @OA\Post(
					 *     path="/users/reset-password",
					 *     summary="Reset a password",
					 *     tags={"Users"},
					 *     @OA\RequestBody(
					 *       @OA\JsonContent(
					 *         @OA\Property(property="username", type="string", description="Username."),
					 *         @OA\Property(property="password", type="string", description="New password."),
					 *         @OA\Property(property="password_reset_key", type="string", description="Password reset key.")
					 *       ),
					 *     ),
					 *     @OA\Response(response="200", description="")
					 * )
					 */
					'user_password_reset_action'   => [
						'base'   => 'users_resource',
						'path'   => '/reset-password',
						'method' => 'POST',
						'action' => [ $this, 'reset_user_password' ],
						'rest'   => true,
					],

					/**
					 * @OA\Post(
					 *     path="/users/{user_id}",
					 *     summary="Update a user",
					 *     description="In addition to the default user fields, you can also update custom fields added via the vendor attributes or HivePress extensions.",
					 *     tags={"Users"},
					 *     @OA\Parameter(ref="#/components/parameters/user_id"),
					 *     @OA\RequestBody(@OA\JsonContent(ref="#/components/schemas/User")),
					 *     @OA\Response(response="200", description="")
					 * )
					 */
					'user_update_action'           => [
						'base'   => 'user_resource',
						'method' => 'POST',
						'action' => [ $this, 'update_user' ],
						'rest'   => true,
					],

					/**
					 * @OA\Delete(
					 *     path="/users/{user_id}",
					 *     summary="Delete a user",
					 *     tags={"Users"},
					 *     @OA\Parameter(ref="#/components/parameters/user_id"),
					 *     @OA\Response(response="204", description="")
					 * )
					 */
					'user_delete_action'           => [
						'base'   => 'user_resource',
						'method' => 'DELETE',
						'action' => [ $this, 'delete_user' ],
						'rest'   => true,
					],

					'user_account_page'            => [
						'path'     => '/account',
						'redirect' => [ $this, 'redirect_user_account_page' ],
					],

					'user_login_page'              => [
						'title'    => esc_html__( 'Sign In', 'hivepress' ),
						'base'     => 'user_account_page',
						'path'     => '/login',
						'redirect' => [ $this, 'redirect_user_login_page' ],
						'action'   => [ $this, 'render_user_login_page' ],
					],

					'user_logout_page'             => [
						'base'     => 'user_account_page',
						'path'     => '/logout',
						'redirect' => [ $this, 'redirect_user_logout_page' ],
					],

					'user_password_reset_page'     => [
						'title'    => esc_html__( 'Reset Password', 'hivepress' ),
						'base'     => 'user_account_page',
						'path'     => '/reset-password',
						'redirect' => [ $this, 'redirect_user_password_reset_page' ],
						'action'   => [ $this, 'render_user_password_reset_page' ],
					],

					'user_email_verify_page'       => [
						'title'    => esc_html__( 'Email Verified', 'hivepress' ),
						'base'     => 'user_account_page',
						'path'     => '/verify-email',
						'redirect' => [ $this, 'redirect_user_email_verify_page' ],
						'action'   => [ $this, 'render_user_email_verify_page' ],
					],

					'user_edit_settings_page'      => [
						'title'    => hivepress()->translator->get_string( 'settings' ),
						'base'     => 'user_account_page',
						'path'     => '/settings',
						'redirect' => [ $this, 'redirect_user_edit_settings_page' ],
						'action'   => [ $this, 'render_user_edit_settings_page' ],
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}

	/**
	 * Gets users.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function get_users( $request ) {

		// Check authentication.
		if ( ! is_user_logged_in() ) {
			return hp\rest_error( 401 );
		}

		// Check permissions.
		if ( ! current_user_can( 'edit_others_posts' ) ) {
			return hp\rest_error( 403 );
		}

		// Get search query.
		$query = sanitize_text_field( $request->get_param( 'search' ) );

		if ( strlen( $query ) < 3 ) {
			return hp\rest_error( 400 );
		}

		// Get users.
		$users = Models\User::query()->search( $query )
		->limit( 20 )
		->get();

		// Get results.
		$results = [];

		if ( $request->get_param( 'context' ) === 'list' ) {
			foreach ( $users as $user ) {
				$results[] = [
					'id'   => $user->get_id(),
					'text' => $user->get_username(),
				];
			}
		}

		return hp\rest_response( 200, $results );
	}

	/**
	 * Registers user.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function register_user( $request ) {

		// Check permissions.
		if ( ! get_option( 'hp_user_enable_registration', true ) ) {
			return hp\rest_error( 403 );
		}

		if ( is_user_logged_in() && ! current_user_can( 'create_users' ) ) {
			return hp\rest_error( 403 );
		}

		// Validate form.
		$form = ( new Forms\User_Register() )->set_values( $request->get_params() );

		if ( ! $form->validate() ) {
			return hp\rest_error( 400, $form->get_errors() );
		}

		// Check username.
		if ( $form->get_value( 'username' ) ) {
			if ( sanitize_user( $form->get_value( 'username' ), true ) !== $form->get_value( 'username' ) ) {
				return hp\rest_error( 400, esc_html__( 'Username contains invalid characters.', 'hivepress' ) );
			} elseif ( username_exists( $form->get_value( 'username' ) ) ) {
				return hp\rest_error( 400, esc_html__( 'This username is already in use.', 'hivepress' ) );
			}
		}

		// Check email.
		if ( email_exists( $form->get_value( 'email' ) ) ) {
			return hp\rest_error( 400, esc_html__( 'This email is already registered.', 'hivepress' ) );
		}

		// Get username.
		$username = hp\get_first_array_value( explode( '@', $form->get_value( 'email' ) ) );

		if ( $form->get_value( 'username' ) ) {
			$username = $form->get_value( 'username' );
		} else {
			$username = sanitize_user( $username, true );

			if ( empty( $username ) ) {
				$username = 'user';
			}

			while ( username_exists( $username ) ) {
				$username .= wp_rand( 1, 9 );
			}
		}

		// Register user.
		$user = ( new Models\User() )->fill(
			array_merge(
				$form->get_values(),
				[
					'username' => $username,
				]
			)
		);

		if ( ! $user->save() ) {
			return hp\rest_error( 400, $user->_get_errors() );
		}

		/**
		 * Fires when a new user is registered.
		 *
		 * @hook hivepress/v1/models/user/register
		 * @param {int} $user_id User ID.
		 * @param {array} $values Form values.
		 */
		do_action( 'hivepress/v1/models/user/register', $user->get_id(), $form->get_values() );

		if ( get_option( 'hp_user_verify_email' ) ) {

			// Set email key.
			$email_key = md5( $user->get_email() . time() . wp_rand() );

			update_user_meta( $user->get_id(), 'hp_email_verify_key', $email_key );

			// Set email redirect.
			$email_redirect = wp_validate_redirect( $form->get_value( '_redirect' ) );

			if ( $email_redirect ) {
				update_user_meta( $user->get_id(), 'hp_email_verify_redirect', $email_redirect );
			}

			// Send email.
			( new Emails\User_Email_Verify(
				[
					'recipient' => $user->get_email(),

					'tokens'    => [
						'user'             => $user,
						'user_name'        => $user->get_username(),
						'email_verify_url' => hivepress()->router->get_url(
							'user_email_verify_page',
							[
								'username'         => $user->get_username(),
								'email_verify_key' => $email_key,
							]
						),
					],
				]
			) )->send();
		} elseif ( ! is_user_logged_in() ) {

			// Authenticate user.
			do_action( 'hivepress/v1/models/user/login' );

			wp_signon(
				[
					'user_login'    => $user->get_username(),
					'user_password' => $form->get_value( 'password' ),
					'remember'      => true,
				]
			);
		}

		return hp\rest_response(
			201,
			[
				'id' => $user->get_id(),
			]
		);
	}

	/**
	 * Logins user.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function login_user( $request ) {

		// Check permissions.
		if ( is_user_logged_in() && ! current_user_can( 'edit_users' ) ) {
			return hp\rest_error( 403 );
		}

		// Validate form.
		$form = ( new Forms\User_Login() )->set_values( $request->get_params() );

		if ( ! $form->validate() ) {
			return hp\rest_error( 400, $form->get_errors() );
		}

		// Get user.
		$user_object = null;

		if ( is_email( $form->get_value( 'username_or_email' ) ) ) {
			$user_object = get_user_by( 'email', $form->get_value( 'username_or_email' ) );
		} else {
			$user_object = get_user_by( 'login', $form->get_value( 'username_or_email' ) );
		}

		if ( empty( $user_object ) ) {
			return hp\rest_error( 401, esc_html__( 'Username or password is incorrect.', 'hivepress' ) );
		}

		$user = Models\User::query()->get_by_id( $user_object );

		// Check password.
		if ( ! wp_check_password( $form->get_value( 'password' ), $user->get_password(), $user->get_id() ) ) {
			return hp\rest_error( 401, esc_html__( 'Username or password is incorrect.', 'hivepress' ) );
		}

		// Check email key.
		if ( get_option( 'hp_user_verify_email' ) && $user_object->hp_email_verify_key ) {
			return hp\rest_error( 401, esc_html__( 'Please check your email to activate your account.', 'hivepress' ) );
		}

		// Authenticate user.
		if ( ! is_user_logged_in() ) {

			/**
			 * Fires when a user is being authenticated.
			 *
			 * @hook hivepress/v1/models/user/login
			 */
			do_action( 'hivepress/v1/models/user/login' );

			wp_signon(
				[
					'user_login'    => $user->get_username(),
					'user_password' => $form->get_value( 'password' ),
					'remember'      => true,
				]
			);
		}

		return hp\rest_response(
			200,
			[
				'id' => $user->get_id(),
			]
		);
	}

	/**
	 * Requests user password.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function request_user_password( $request ) {

		// Check permissions.
		if ( is_user_logged_in() && ! current_user_can( 'edit_users' ) ) {
			return hp\rest_error( 403 );
		}

		// Validate form.
		$form = ( new Forms\User_Password_Request() )->set_values( $request->get_params() );

		if ( ! $form->validate() ) {
			return hp\rest_error( 400, $form->get_errors() );
		}

		// Get user.
		$user_object = null;

		if ( is_email( $form->get_value( 'username_or_email' ) ) ) {
			$user_object = get_user_by( 'email', $form->get_value( 'username_or_email' ) );
		} else {
			$user_object = get_user_by( 'login', $form->get_value( 'username_or_email' ) );
		}

		if ( empty( $user_object ) ) {
			if ( is_email( $form->get_value( 'username_or_email' ) ) ) {
				return hp\rest_error( 404, esc_html__( 'User with this email doesn\'t exist.', 'hivepress' ) );
			} else {
				return hp\rest_error( 404, esc_html__( 'User with this username doesn\'t exist.', 'hivepress' ) );
			}
		}

		$user = Models\User::query()->get_by_id( $user_object );

		// Check email key.
		if ( get_option( 'hp_user_verify_email' ) && $user_object->hp_email_verify_key ) {
			return hp\rest_error( 401, esc_html__( 'Please check your email to activate your account.', 'hivepress' ) );
		}

		// Send email.
		( new Emails\User_Password_Request(
			[
				'recipient' => $user->get_email(),

				'tokens'    => [
					'user'               => $user,
					'user_name'          => $user->get_display_name(),
					'password_reset_url' => hivepress()->router->get_url(
						'user_password_reset_page',
						[
							'username'           => $user->get_username(),
							'password_reset_key' => get_password_reset_key( $user_object ),
						]
					),
				],
			]
		) )->send();

		return hp\rest_response(
			200,
			[
				'id' => $user->get_id(),
			]
		);
	}

	/**
	 * Resets user password.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function reset_user_password( $request ) {

		// Check permissions.
		if ( is_user_logged_in() && ! current_user_can( 'edit_users' ) ) {
			return hp\rest_error( 403 );
		}

		// Validate form.
		$form = ( new Forms\User_Password_Reset() )->set_values( $request->get_params() );

		if ( ! $form->validate() ) {
			return hp\rest_error( 400, $form->get_errors() );
		}

		// Get user.
		$user_object = check_password_reset_key( $form->get_value( 'password_reset_key' ), $form->get_value( 'username' ) );

		if ( is_wp_error( $user_object ) ) {
			return hp\rest_error( 401, esc_html__( 'Password reset key is expired or invalid.', 'hivepress' ) );
		}

		$user = Models\User::query()->get_by_id( $user_object );

		// Reset password.
		reset_password( $user_object, $form->get_value( 'password' ) );

		// Authenticate user.
		if ( ! is_user_logged_in() ) {
			do_action( 'hivepress/v1/models/user/login' );

			wp_signon(
				[
					'user_login'    => $user->get_username(),
					'user_password' => $form->get_value( 'password' ),
					'remember'      => true,
				]
			);
		}

		return hp\rest_response(
			200,
			[
				'id' => $user->get_id(),
			]
		);
	}

	/**
	 * Updates user.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function update_user( $request ) {

		// Check authentication.
		if ( ! is_user_logged_in() ) {
			return hp\rest_error( 401 );
		}

		// Get user.
		$user = Models\User::query()->get_by_id( $request->get_param( 'user_id' ) );

		if ( empty( $user ) ) {
			return hp\rest_error( 404 );
		}

		// Check permissions.
		if ( ! current_user_can( 'edit_users' ) && get_current_user_id() !== $user->get_id() ) {
			return hp\rest_error( 403 );
		}

		// Validate form.
		$form = null;

		if ( $request->get_param( '_form' ) === 'user_update_profile' ) {
			$form = new Forms\User_Update_Profile( [ 'model' => $user ] );
		} else {
			$form = new Forms\User_Update( [ 'model' => $user ] );
		}

		$form->set_values( $request->get_params() );

		if ( ! $form->validate() ) {
			return hp\rest_error( 400, $form->get_errors() );
		}

		// Check password.
		if ( get_current_user_id() === $user->get_id() && ( $form->get_value( 'email' ) !== $user->get_email() || $form->get_value( 'password' ) ) ) {
			if ( ! $form->get_value( 'current_password' ) ) {
				return hp\rest_error( 400, esc_html__( 'Current password is required.', 'hivepress' ) );
			}

			if ( ! wp_check_password( $form->get_value( 'current_password' ), $user->get_password(), $user->get_id() ) ) {
				return hp\rest_error( 401, esc_html__( 'Current password is incorrect.', 'hivepress' ) );
			}
		}

		// Update user.
		$user->fill( $form->get_values() );

		if ( ! $user->save() ) {
			return hp\rest_error( 400, $user->_get_errors() );
		}

		return hp\rest_response(
			200,
			[
				'id' => $user->get_id(),
			]
		);
	}

	/**
	 * Deletes user.
	 *
	 * @param WP_REST_Request $request API request.
	 * @return WP_Rest_Response
	 */
	public function delete_user( $request ) {

		// Check authentication.
		if ( ! is_user_logged_in() ) {
			return hp\rest_error( 401 );
		}

		// Get user.
		$user = Models\User::query()->get_by_id( $request->get_param( 'user_id' ) );

		if ( empty( $user ) ) {
			return hp\rest_error( 404 );
		}

		// Check permissions.
		if ( ! current_user_can( 'delete_users' ) && get_current_user_id() !== $user->get_id() ) {
			return hp\rest_error( 403 );
		}

		// Check password.
		if ( get_current_user_id() === $user->get_id() ) {
			$form = ( new Forms\User_Delete() )->set_values( $request->get_params() );

			if ( ! $form->validate() ) {
				return hp\rest_error( 400, $form->get_errors() );
			}

			if ( ! wp_check_password( $form->get_value( 'password' ), $user->get_password(), $user->get_id() ) ) {
				return hp\rest_error( 401, esc_html__( 'Password is incorrect.', 'hivepress' ) );
			}

			// Clear authentication.
			wp_clear_auth_cookie();
		}

		// Delete user.
		if ( ! $user->delete() ) {
			return hp\rest_error( 400 );
		}

		return hp\rest_response( 204 );
	}

	/**
	 * Redirects user account page.
	 *
	 * @return mixed
	 */
	public function redirect_user_account_page() {

		// Check authentication.
		if ( ! is_user_logged_in() ) {
			return hivepress()->router->get_return_url( 'user_login_page' );
		}

		// Get menu items.
		$menu_items = ( new Menus\User_Account() )->get_items();

		if ( $menu_items ) {
			return hp\get_array_value( hp\get_first_array_value( $menu_items ), 'url' );
		}

		return true;
	}

	/**
	 * Redirects user login page.
	 *
	 * @return mixed
	 */
	public function redirect_user_login_page() {
		if ( is_user_logged_in() ) {
			if ( hivepress()->router->get_redirect_url() ) {
				return hivepress()->router->get_redirect_url();
			} else {
				return hivepress()->router->get_url( 'user_account_page' );
			}
		}

		return false;
	}

	/**
	 * Renders user login page.
	 *
	 * @return string
	 */
	public function render_user_login_page() {
		return ( new Blocks\Template(
			[
				'template' => 'user_login_page',
			]
		) )->render();
	}

	/**
	 * Redirects user logout page.
	 *
	 * @return mixed
	 */
	public function redirect_user_logout_page() {
		if ( is_user_logged_in() ) {
			wp_logout();
		}

		return true;
	}

	/**
	 * Redirects user password reset page.
	 *
	 * @return mixed
	 */
	public function redirect_user_password_reset_page() {
		if ( is_user_logged_in() ) {
			if ( hivepress()->router->get_redirect_url() ) {
				return hivepress()->router->get_redirect_url();
			} else {
				return hivepress()->router->get_url( 'user_account_page' );
			}
		}

		return false;
	}

	/**
	 * Renders user password reset page.
	 *
	 * @return string
	 */
	public function render_user_password_reset_page() {
		return ( new Blocks\Template(
			[
				'template' => 'user_password_reset_page',
			]
		) )->render();
	}

	/**
	 * Redirects user email verify page.
	 *
	 * @return mixed
	 */
	public function redirect_user_email_verify_page() {

		// Check permissions.
		if ( ! get_option( 'hp_user_verify_email' ) ) {
			return true;
		}

		// Get username and email key.
		$username  = sanitize_user( hp\get_array_value( $_GET, 'username' ) );
		$email_key = sanitize_key( hp\get_array_value( $_GET, 'email_verify_key' ) );

		if ( ! $username || ! $email_key ) {
			return true;
		}

		// Get user.
		$user = get_user_by( 'login', $username );

		// Check email key.
		if ( ! $user || $user->hp_email_verify_key !== $email_key ) {
			return true;
		}

		// Delete email key.
		delete_user_meta( $user->ID, 'hp_email_verify_key' );

		// Check authentication.
		if ( is_user_logged_in() ) {
			return hivepress()->router->get_url( 'user_account_page' );
		}

		// Authenticate user.
		do_action( 'hivepress/v1/models/user/login' );

		wp_set_auth_cookie( $user->ID, true );

		do_action( 'wp_login', $user->user_login, $user );

		// Redirect user.
		$redirect = $user->hp_email_verify_redirect;

		if ( $redirect ) {
			delete_user_meta( $user->ID, 'hp_email_verify_redirect' );

			return $redirect;
		}

		return false;
	}

	/**
	 * Renders user email verify page.
	 *
	 * @return string
	 */
	public function render_user_email_verify_page() {
		return ( new Blocks\Template(
			[
				'template' => 'user_email_verify_page',
			]
		) )->render();
	}

	/**
	 * Redirects user edit settings page.
	 *
	 * @return mixed
	 */
	public function redirect_user_edit_settings_page() {
		if ( ! is_user_logged_in() ) {
			return hivepress()->router->get_return_url( 'user_login_page' );
		}

		return false;
	}

	/**
	 * Renders user edit settings page.
	 *
	 * @return string
	 */
	public function render_user_edit_settings_page() {
		return ( new Blocks\Template(
			[
				'template' => 'user_edit_settings_page',

				'context'  => [
					'user' => hivepress()->request->get_user(),
				],
			]
		) )->render();
	}
}