<?php
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 6.2.3 - Licence Number L3138846
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2026 MH Sub I, LLC dba vBulletin. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

use Krombox\OAuth2\Client\Provider\WordPress;
use Krombox\OAuth2\Client\Provider\WordPressResourceOwner;

class wordpresslogin_Library_ExternalLogin extends vB_Library_ExternalLogin_OAuth2Client
{
	protected $productid = 'wordpresslogin';

	//customize some errors in the oath2 library to be specific to wordpress.
	protected $errors = [
		'noexternaluser' => 'wordpresslogin_error_check_auth_popup',
		'notloggedin' => 'wordpresslogin_error_not_loggedin',
		'no_oauth_user_found_register_x' => 'wordpresslogin_no_oauth_user_found_register_x',
	];

	public function getCallbackUrl() : string
	{
		$options = vB::getDatastore()->getValue('options');
		return $options['frontendurl'] . '/oauth2/callback?provider=wordpresslogin';
	}

	protected function __construct()
	{
		parent::__construct();

		$options = vB::getDatastore()->getValue('options');

		//whe might want to set enabled to false if the clientid/secret aren't set
		$clientid = $options['wordpresslogin_client_id'] ?? '';
		$secret = $options['wordpresslogin_client_secret'] ?? '';

		// we may want to allow relative URLs for the endpoints as well...
		$wordpressSiteUrl = rtrim($options['wordpresslogin_site_url'], '/');
		$authEndpoint = $options['wordpresslogin_auth_endpoint'];
		$tokenUrl = $options['wordpresslogin_token_endpoint'];
		$resourceOwnerUrl = $options['wordpresslogin_profile_endpoint'];


		$this->setEnabled(!empty($options['wordpresslogin_enabled']), !empty($options['wordpresslogin_register_enabled']), ($clientid AND $secret));
		if ($this->enabled)
		{
			//hook up the composer autoload
			vB::includeLibraryFile('vendor/autoload.php');

			try
			{
				//setting the redirect url is a little dubious but there isn't a good way to pass stuff to library constructors
				//and we can't set this after the object is constructed.
				$this->provider = new WordPress([
					'clientId'     => $clientid,
					'clientSecret' => $secret,
					'redirectUri'  => $this->getCallbackUrl(),
					'urlAuthorize' => $authEndpoint,
					'urlAccessToken' => $tokenUrl,
					'urlResourceOwnerDetails' => $resourceOwnerUrl,
					// This is not used if the admin explicitly provided the 3 endpoints above. If the endpoints are left empty,
					// the wrapper will use the default endpoints generated by the "OAuth Server" plugin.
					'domain' => $wordpressSiteUrl,
				]);
			}
			catch (Exception $e)
			{
				//reset the enabled state based on the config not being set properly.
				$this->setEnabled(!empty($options['wordpresslogin_enabled']), !empty($options['wordpresslogin_register_enabled']), false);

				$this->provider = null;
			}
		}
	}

	public function getRegistrationData()
	{
		try
		{
			// Warning: The default fallback for this wrapper uses the wp-json/wp/v2/users/me endpoint, which is
			// the CURRENTLY LOGGED IN user, not the access-token user. In most cases, this distinction probably
			// does not matter, but technically they could be different, especially in async cases!


			/** @var WordPressResourceOwner */
			$user = $this->getOauthUserFromSession();

			// Depending on the plugin and endpoints set, we may get different user objects:
			//  [ID] => 90
			//  [user_login] => adams.alfredo
			//  [user_nicename] => adams-alfredo
			//  [user_email] => emmanuel15@example.com
			//  [user_registered] => 2025-03-21 23:19:46
			//  [user_status] => 0
			//  [display_name] => adams.alfredo
			//  ...
			// OR
			//  [id] => 90
			//  [ID] => 90
			//  [email] => emmanuel15@example.com
			//  [username] => adams.alfredo
			//  [first_name] => Emerald
			//  [last_name] => Runte
			//  [nickname] => adams.alfredo
			//  [display_name] => adams.alfredo
			//  ...
			// OR
			//  [id] => 90
			//  [username] => adams.alfredo
			//  [name] => adams.alfredo
			//  [first_name] => Emerald
			//  [last_name] => Runte
			//  [email] => emmanuel15@example.com
			//  [url] =>
			//  ...
			$userArray = $user->toArray();

			return [
				'found' => true,
				'external_userid' => $userArray['id'] ?? $userArray['ID'] ?? null,
				'username' => $userArray['username'] ?? $userArray['user_login'] ?? null,
				'email' => $userArray['email'] ?? $userArray['user_email'] ?? null,
				'picture' => $this->getProfileUrlFromAnOAuthUser($user),
			];
		}
		//if we don't have a user we throw an error.  In this case we anticipate that we might not have a user here.
		catch(vB_Exception_Api $e)
		{
			return [
				'found' => false,
				'external_userid' => '',
				'username' => '',
				'email' => '',
				'picture' => '',
			];
		}
	}

	private function getProfileUrlFromAnOAuthUser(Krombox\OAuth2\Client\Provider\WordPressResourceOwner $oauthuser) : mixed
	{
		$userArray = $oauthuser->toArray();
		$avatarUrls = $userArray['avatar_urls'] ?? null;
		// size default is copied from WordPressResourceOwner
		$size = 96;
		if ($avatarUrls)
		{
			return $avatarUrls[$size] ?? null;
		}

		// the json endpoint may have above, but the actual resource endpoints provided by the OAuth server & miniOrange OAuth 2
		// plugins do not seem to have any. We might be able to leverage some other wp core json endpoint to fetch it in that
		// case, but not sure if that's worth while. AFAIK this is only used for showing an avatar in the registration form
		// button.

		return null;
	}

	private function getIdFromOAuthUser() : mixed
	{
		/** @var WordPressResourceOwner */
		$user = $this->getOauthUserFromSession();
		$userArray = $user->toArray();

		// Depending on the plugin and endpoints set, we may get different user objects:
		//  [ID] => 90
		//  ...
		// OR
		//  [id] => 90
		//  [ID] => 90
		//  ...
		// OR
		//  [id] => 90
		//  ...

		return $userArray['id'] ?? $userArray['ID'] ?? null;
	}

	// override to add a 2nd param to an error message... and also because the id field might be different depending on the
	// plugin used on wordpress's end
	public function getExternalUseridForLinking()
	{
		$external_userid = $this->getIdFromOAuthUser();
		if (!$external_userid)
		{
			$wordpressSitename = vB::getDatastore()->getOption('wordpresslogin_site_name');
			throw new vB_Exception_Api($this->errors['noexternaluser'], [$wordpressSitename]);
		}

		if (!$this->checkExternalUseridAvailability($external_userid))
		{
			throw new vB_Exception_Api('externallogin_extid_notunique');
		}

		return $external_userid;
	}

	// override to add a 2nd param to an error message... and also because the id field might be different depending on the
	// plugin used on wordpress's end
	public function verifyAuthAndLogin($url = '', $userid = null)
	{
		$external_userid = $this->getIdFromOAuthUser();
		$userAuth = $this->getUserAuthRecord($external_userid);

		if ($userAuth)
		{
			//if we are trying to restore a specific user, let's make sure the mapped account matches
			//before we try to authenticate and don't.
			if($userid AND $userAuth['userid'] != $userid)
			{
				throw new vB_Exception_Api('error_external_wrong_vb_user', $this->productid);
			}

			// log in using userid
			$result = $this->loginUser($userAuth['userid']);
			$result['success'] = true;
			return $result;
		}
		else
		{
			$extra = [];
			if ($url)
			{
				if(vB::getUrlLoader()->isSiteUrl($url))
				{
					$extra['urlpath'] = $url;
				}
			}

			$registrationUrl = vB5_Route::buildUrl('register|fullurl', [], $extra);
			// Customize the message with the site name, because it's probably not just "Wordpress"...
			// This is meant for an error message like "... ensure you have already linked your Super Cool Site account with your
			// vBulletin account..." but this might end up not working as well as I hope depending on the site's name
			$wordpressSitename = vB::getDatastore()->getOption('wordpresslogin_site_name');
			throw new vB_Exception_Api($this->errors['no_oauth_user_found_register_x'], [$registrationUrl, $wordpressSitename]);
		}
	}
}

/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 13:27, Sun Aug 30th 2026
|| # CVS: $RCSfile$ - $Revision: 117113 $
|| #######################################################################
\*=========================================================================*/
