* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Installer\Administrator\Model; 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; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * 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->getDatabase(); $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->getDatabase(); $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(); $installer->setDatabase($this->getDatabase()); $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->getDatabase(); $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->getDatabase()->quoteName('state') . ' = -1'); return $query; } /** * Checks for not installed extensions in extensions table. * * @return boolean True if there are discovered extensions in the database. * * @since 4.2.0 */ public function checkExtensions() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__extensions')) ->where($db->quoteName('state') . ' = -1'); $db->setQuery($query); $discoveredExtensions = $db->loadObjectList(); return count($discoveredExtensions) > 0; } }