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

namespace Joomla\Component\Installer\Administrator\Model;

\defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\Database\DatabaseQuery;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;

/**
 * Installer Discover Model
 *
 * @since  1.6
 */
class DiscoverModel extends InstallerModel
{
	/**
	 * Constructor.
	 *
	 * @param   array                $config   An optional associative array of configuration settings.
	 * @param   MVCFactoryInterface  $factory  The factory.
	 *
	 * @see     \Joomla\CMS\MVC\Model\ListModel
	 * @since   1.6
	 */
	public function __construct($config = array(), MVCFactoryInterface $factory = null)
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
				'name',
				'client_id',
				'client', 'client_translated',
				'type', 'type_translated',
				'folder', 'folder_translated',
				'extension_id',
			);
		}

		parent::__construct($config, $factory);
	}

	/**
	 * Method to auto-populate the model state.
	 *
	 * Note. Calling getState in this method will result in recursion.
	 *
	 * @param   string  $ordering   An optional ordering field.
	 * @param   string  $direction  An optional direction (asc|desc).
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function populateState($ordering = 'name', $direction = 'asc')
	{
		$app = Factory::getApplication();

		// Load the filter state.
		$this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
		$this->setState('filter.client_id', $this->getUserStateFromRequest($this->context . '.filter.client_id', 'filter_client_id', null, 'int'));
		$this->setState('filter.type', $this->getUserStateFromRequest($this->context . '.filter.type', 'filter_type', '', 'string'));
		$this->setState('filter.folder', $this->getUserStateFromRequest($this->context . '.filter.folder', 'filter_folder', '', 'string'));

		$this->setState('message', $app->getUserState('com_installer.message'));
		$this->setState('extension_message', $app->getUserState('com_installer.extension_message'));

		$app->setUserState('com_installer.message', '');
		$app->setUserState('com_installer.extension_message', '');

		parent::populateState($ordering, $direction);
	}

	/**
	 * Method to get the database query.
	 *
	 * @return  DatabaseQuery  The database query
	 *
	 * @since   3.1
	 */
	protected function getListQuery()
	{
		$db = $this->getDbo();
		$query = $db->getQuery(true)
			->select('*')
			->from($db->quoteName('#__extensions'))
			->where($db->quoteName('state') . ' = -1');

		// Process select filters.
		$type     = $this->getState('filter.type');
		$clientId = $this->getState('filter.client_id');
		$folder   = $this->getState('filter.folder');

		if ($type)
		{
			$query->where($db->quoteName('type') . ' = :type')
				->bind(':type', $type);
		}

		if ($clientId != '')
		{
			$clientId = (int) $clientId;
			$query->where($db->quoteName('client_id') . ' = :clientid')
				->bind(':clientid', $clientId, ParameterType::INTEGER);
		}

		if ($folder != '' && in_array($type, array('plugin', 'library', '')))
		{
			$folder = $folder === '*' ? '' : $folder;
			$query->where($db->quoteName('folder') . ' = :folder')
				->bind(':folder', $folder);
		}

		// Process search filter.
		$search = $this->getState('filter.search');

		if (!empty($search))
		{
			if (stripos($search, 'id:') === 0)
			{
				$ids = (int) substr($search, 3);
				$query->where($db->quoteName('extension_id') . ' = :eid')
					->bind(':eid', $ids, ParameterType::INTEGER);
			}
		}

		// Note: The search for name, ordering and pagination are processed by the parent InstallerModel class (in extension.php).

		return $query;
	}

	/**
	 * Discover extensions.
	 *
	 * Finds uninstalled extensions
	 *
	 * @return  int  The count of discovered extensions
	 *
	 * @since   1.6
	 */
	public function discover()
	{
		// Purge the list of discovered extensions and fetch them again.
		$this->purge();
		$results = Installer::getInstance()->discover();

		// Get all templates, including discovered ones
		$db = $this->getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName(['extension_id', 'element', 'folder', 'client_id', 'type']))
			->from($db->quoteName('#__extensions'));
		$db->setQuery($query);
		$installedtmp = $db->loadObjectList();

		$extensions = array();

		foreach ($installedtmp as $install)
		{
			$key = implode(':',
				[
					$install->type,
					str_replace('\\', '/', $install->element),
					$install->folder,
					$install->client_id
				]
			);
			$extensions[$key] = $install;
		}

		$count = 0;

		foreach ($results as $result)
		{
			// Check if we have a match on the element
			$key = implode(':',
				[
					$result->type,
					str_replace('\\', '/', $result->element),
					$result->folder,
					$result->client_id
				]
			);

			if (!array_key_exists($key, $extensions))
			{
				// Put it into the table
				$result->check();
				$result->store();
				$count++;
			}
		}

		return $count;
	}

	/**
	 * Installs a discovered extension.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function discover_install()
	{
		$app   = Factory::getApplication();
		$input = $app->input;
		$eid   = $input->get('cid', 0, 'array');

		if (is_array($eid) || $eid)
		{
			if (!is_array($eid))
			{
				$eid = array($eid);
			}

			$eid = ArrayHelper::toInteger($eid);
			$failed = false;

			foreach ($eid as $id)
			{
				$installer = new Installer;

				$result = $installer->discover_install($id);

				if (!$result)
				{
					$failed = true;
					$app->enqueueMessage(Text::_('COM_INSTALLER_MSG_DISCOVER_INSTALLFAILED') . ': ' . $id);
				}
			}

			// TODO - We are only receiving the message for the last Installer instance
			$this->setState('action', 'remove');
			$this->setState('name', $installer->get('name'));
			$app->setUserState('com_installer.message', $installer->message);
			$app->setUserState('com_installer.extension_message', $installer->get('extension_message'));

			if (!$failed)
			{
				$app->enqueueMessage(Text::_('COM_INSTALLER_MSG_DISCOVER_INSTALLSUCCESSFUL'));
			}
		}
		else
		{
			$app->enqueueMessage(Text::_('COM_INSTALLER_MSG_DISCOVER_NOEXTENSIONSELECTED'));
		}
	}

	/**
	 * Cleans out the list of discovered extensions.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.6
	 */
	public function purge()
	{
		$db = $this->getDbo();
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__extensions'))
			->where($db->quoteName('state') . ' = -1');
		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (ExecutionFailureException $e)
		{
			$this->_message = Text::_('COM_INSTALLER_MSG_DISCOVER_FAILEDTOPURGEEXTENSIONS');

			return false;
		}

		$this->_message = Text::_('COM_INSTALLER_MSG_DISCOVER_PURGEDDISCOVEREDEXTENSIONS');

		return true;
	}

	/**
	 * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
	 *
	 * @return DatabaseQuery
	 *
	 * @since 4.0.0
	 */
	protected function getEmptyStateQuery()
	{
		$query = parent::getEmptyStateQuery();

		$query->where($this->_db->quoteName('state') . ' = -1');

		return $query;
	}
}
