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

namespace Joomla\CMS\Installation\Model;

\defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Installation\Helper\DatabaseHelper;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Text;
use Joomla\Utilities\ArrayHelper;

/**
 * Setup model for the Joomla Core Installer.
 *
 * @since  3.1
 */
class SetupModel extends BaseInstallationModel
{
	/**
	 * Get the current setup options from the session.
	 *
	 * @return  array  An array of options from the session.
	 *
	 * @since   3.1
	 */
	public function getOptions()
	{
		if (!empty(Factory::getSession()->get('setup.options', array())))
		{
			return Factory::getSession()->get('setup.options', array());
		}
	}

	/**
	 * Store the current setup options in the session.
	 *
	 * @param   array  $options  The installation options.
	 *
	 * @return  array  An array of options from the session.
	 *
	 * @since   3.1
	 */
	public function storeOptions($options)
	{
		// Get the current setup options from the session.
		$old = (array) $this->getOptions();

		// Ensure that we have language
		if (!isset($options['language']) || empty($options['language']))
		{
			$options['language'] = Factory::getLanguage()->getTag();
		}

		// Store passwords as a separate key that is not used in the forms
		foreach (array('admin_password', 'db_pass') as $passwordField)
		{
			if (isset($options[$passwordField]))
			{
				$plainTextKey = $passwordField . '_plain';

				$options[$plainTextKey] = $options[$passwordField];

				unset($options[$passwordField]);
			}
		}

		// Get the session
		$session = Factory::getSession();
		$options['helpurl'] = $session->get('setup.helpurl', null);

		// Merge the new setup options into the current ones and store in the session.
		$options = array_merge($old, (array) $options);
		$session->set('setup.options', $options);

		return $options;
	}

	/**
	 * Method to get the form.
	 *
	 * @param   string|null  $view  The view being processed.
	 *
	 * @return  Form|boolean  JForm object on success, false on failure.
	 *
	 * @since   3.1
	 */
	public function getForm($view = null)
	{
		if (!$view)
		{
			$view = Factory::getApplication()->input->getWord('view', 'setup');
		}

		// Get the form.
		Form::addFormPath(JPATH_COMPONENT . '/forms');

		try
		{
			$form = Form::getInstance('jform', $view, array('control' => 'jform'));
		}
		catch (\Exception $e)
		{
			Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');

			return false;
		}

		// Check the session for previously entered form data.
		$data = (array) $this->getOptions();

		// Bind the form data if present.
		if (!empty($data))
		{
			$form->bind($data);
		}

		return $form;
	}

	/**
	 * Method to check the form data.
	 *
	 * @param   string  $page  The view being checked.
	 *
	 * @return  array|boolean  Array with the validated form data or boolean false on a validation failure.
	 *
	 * @since   3.1
	 */
	public function checkForm($page = 'setup')
	{
		// Get the posted values from the request and validate them.
		$data   = Factory::getApplication()->input->post->get('jform', array(), 'array');
		$return = $this->validate($data, $page);

		// Attempt to save the data before validation.
		$form = $this->getForm();
		$data = $form->filter($data);

		$this->storeOptions($data);

		// Check for validation errors.
		if ($return === false)
		{
			return false;
		}

		// Store the options in the session.
		return $this->storeOptions($return);
	}

	/**
	 * Generate a panel of language choices for the user to select their language.
	 *
	 * @return  array
	 *
	 * @since   3.1
	 */
	public function getLanguages()
	{
		// Detect the native language.
		$native = LanguageHelper::detectLanguage();

		if (empty($native))
		{
			$native = 'en-GB';
		}

		// Get a forced language if it exists.
		$forced = Factory::getApplication()->getLocalise();

		if (!empty($forced['language']))
		{
			$native = $forced['language'];
		}

		// Get the list of available languages.
		$list = LanguageHelper::createLanguageList($native);

		if (!$list || $list instanceof \Exception)
		{
			$list = array();
		}

		return $list;
	}

	/**
	 * Method to validate the form data.
	 *
	 * @param   array        $data  The form data.
	 * @param   string|null  $view  The view.
	 *
	 * @return  array|boolean  Array of filtered data if valid, false otherwise.
	 *
	 * @since   3.1
	 */
	public function validate($data, $view = null)
	{
		// Get the form.
		$form = $this->getForm($view);

		// Check for an error.
		if ($form === false)
		{
			return false;
		}

		// Filter and validate the form data.
		$data   = $form->filter($data);
		$return = $form->validate($data);

		// Check for an error.
		if ($return instanceof \Exception)
		{
			Factory::getApplication()->enqueueMessage($return->getMessage(), 'warning');

			return false;
		}

		// Check the validation results.
		if ($return === false)
		{
			// Get the validation messages from the form.
			$messages = array_reverse($form->getErrors());

			foreach ($messages as $message)
			{
				if ($message instanceof \Exception)
				{
					Factory::getApplication()->enqueueMessage($message->getMessage(), 'warning');
				}
				else
				{
					Factory::getApplication()->enqueueMessage($message, 'warning');
				}
			}

			return false;
		}

		return $data;
	}

	/**
	 * Method to validate the db connection properties.
	 *
	 * @return  boolean
	 *
	 * @since   4.0.0
	 * @throws  \Exception
	 */
	public function validateDbConnection()
	{
		$options = $this->getOptions();

		// Get the options as an object for easier handling.
		$options = ArrayHelper::toObject($options);

		// Load the backend language files so that the DB error messages work.
		$lang = Factory::getLanguage();
		$currentLang = $lang->getTag();

		// Load the selected language
		if (LanguageHelper::exists($currentLang, JPATH_ADMINISTRATOR))
		{
			$lang->load('joomla', JPATH_ADMINISTRATOR, $currentLang, true);
		}
		// Pre-load en-GB in case the chosen language files do not exist.
		else
		{
			$lang->load('joomla', JPATH_ADMINISTRATOR, 'en-GB', true);
		}

		// Validate and clean up connection parameters
		$paramsCheck = DatabaseHelper::validateConnectionParameters($options);

		if ($paramsCheck)
		{
			// Validation error: Enqueue the error message
			Factory::getApplication()->enqueueMessage($paramsCheck, 'error');

			return false;
		}

		// Security check for remote db hosts
		if (!DatabaseHelper::checkRemoteDbHost($options))
		{
			// Messages have been enqueued in the called function.
			return false;
		}

		// Get a database object.
		try
		{
			$db = DatabaseHelper::getDbo(
				$options->db_type,
				$options->db_host,
				$options->db_user,
				$options->db_pass_plain,
				$options->db_name,
				$options->db_prefix,
				false,
				DatabaseHelper::getEncryptionSettings($options)
			);

			$db->connect();
		}
		catch (\RuntimeException $e)
		{
			if ($options->db_type === 'mysql' && strpos($e->getMessage(), '[1049] Unknown database') === 42
				|| $options->db_type === 'pgsql' && strpos($e->getMessage(), 'database "' . $options->db_name . '" does not exist'))
			{
				// Database doesn't exist: Skip the below checks, they will be done later at database creation
				return true;
			}

			Factory::getApplication()->enqueueMessage(Text::sprintf('INSTL_DATABASE_COULD_NOT_CONNECT', $e->getMessage()), 'error');

			return false;
		}

		// Check database server parameters
		$dbServerCheck = DatabaseHelper::checkDbServerParameters($db, $options);

		if ($dbServerCheck)
		{
			// Some server parameter is not ok: Enqueue the error message
			Factory::getApplication()->enqueueMessage($dbServerCheck, 'error');

			return false;
		}

		return true;
	}
}
