<?php
/**
 * @package     Joomla.Plugin
 * @subpackage  System.Webauthn
 *
 * @copyright   (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Plugin\System\Webauthn\PluginTraits;

// Protect from unauthorized access
\defined('_JEXEC') or die();

use Exception;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\Plugin\System\Webauthn\CredentialRepository;
use Joomla\Plugin\System\Webauthn\Helper\Joomla;
use Joomla\Registry\Registry;

/**
 * Add extra fields in the User Profile page.
 *
 * This class only injects the custom form fields. The actual interface is rendered through
 * JFormFieldWebauthn.
 *
 * @see     JFormFieldWebauthn::getInput()
 *
 * @since   4.0.0
 */
trait UserProfileFields
{
	/**
	 * User object derived from the displayed user profile data.
	 *
	 * This is required to display the number and names of authenticators already registered when
	 * the user displays the profile view page.
	 *
	 * @var   User|null
	 * @since 4.0.0
	 */
	private static $userFromFormData = null;

	/**
	 * HTMLHelper method to render the WebAuthn user profile field in the profile view page.
	 *
	 * Instead of showing a nonsensical "Website default" label next to the field, this method
	 * displays the number and names of authenticators already registered by the user.
	 *
	 * This static method is set up for use in the onContentPrepareData method of this plugin.
	 *
	 * @param   mixed  $value  Ignored. The WebAuthn profile field is virtual, it doesn't have a
	 *                         stored value. We only use it as a proxy to render a sub-form.
	 *
	 * @return  string
	 */
	public static function renderWebauthnProfileField($value): string
	{
		if (\is_null(self::$userFromFormData))
		{
			return '';
		}

		$credentialRepository = new CredentialRepository;
		$credentials          = $credentialRepository->getAll(self::$userFromFormData->id);
		$authenticators       = array_map(
			function (array $credential) {
				return $credential['label'];
			},
			$credentials
		);

		return Text::plural('PLG_SYSTEM_WEBAUTHN_FIELD_N_AUTHENTICATORS_REGISTERED', \count($authenticators), implode(', ', $authenticators));
	}

	/**
	 * Adds additional fields to the user editing form
	 *
	 * @param   Form   $form  The form to be altered.
	 * @param   mixed  $data  The associated data for the form.
	 *
	 * @return  boolean
	 *
	 * @throws  Exception
	 *
	 * @since   4.0.0
	 */
	public function onContentPrepareForm(Form $form, $data)
	{
		// This feature only applies to HTTPS sites.
		if (!Uri::getInstance()->isSsl())
		{
			return true;
		}

		$name = $form->getName();

		$allowedForms = [
			'com_users.user', 'com_users.profile', 'com_users.registration',
		];

		if (!\in_array($name, $allowedForms))
		{
			return true;
		}

		// Get the user object
		$user = $this->getUserFromData($data);

		// Make sure the loaded user is the correct one
		if (\is_null($user))
		{
			return true;
		}

		// Make sure I am either editing myself OR I am a Super User
		if (!Joomla::canEditUser($user))
		{
			return true;
		}

		// Add the fields to the form.
		Joomla::log(
			'system',
			'Injecting WebAuthn Passwordless Login fields in user profile edit page'
		);
		Form::addFormPath(JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name . '/forms');
		$this->loadLanguage();
		$form->loadFile('webauthn', false);

		return true;
	}

	/**
	 * Get the user object based on the ID found in the provided user form data
	 *
	 * @param   array|object|null  $data  The user form data
	 *
	 * @return  User|null  A user object or null if no match is found
	 *
	 * @throws  Exception
	 * @since   4.0.0
	 */
	private function getUserFromData($data): ?User
	{
		$id = null;

		if (\is_array($data))
		{
			$id = isset($data['id']) ? $data['id'] : null;
		}
		elseif (\is_object($data) && ($data instanceof Registry))
		{
			$id = $data->get('id');
		}
		elseif (\is_object($data))
		{
			$id = isset($data->id) ? $data->id : null;
		}

		$user = empty($id) ? Factory::getApplication()->getIdentity() : Factory::getContainer()
			->get(UserFactoryInterface::class)
			->loadUserById($id);

		// Make sure the loaded user is the correct one
		if ($user->id != $id)
		{
			return null;
		}

		return $user;
	}

	/**
	 * @param   string|null        $context  The context for the data
	 * @param   array|object|null  $data     An object or array containing the data for the form.
	 *
	 * @return  bool
	 *
	 * @since   4.0.0
	 */
	public function onContentPrepareData(?string $context, $data): bool
	{
		if (!\in_array($context, ['com_users.profile', 'com_users.user']))
		{
			return true;
		}

		self::$userFromFormData = $this->getUserFromData($data);

		if (!HTMLHelper::isRegistered('users.webauthnWebauthn'))
		{
			HTMLHelper::register('users.webauthn', [__CLASS__, 'renderWebauthnProfileField']);
		}

		return true;
	}
}
