File manager - Edit - /home/opticamezl/www/newok/Table.tar
Back
MapTable.php 0000644 00000003445 15172645147 0006764 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\Table; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Table\Nested; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Map table class for the Finder package. * * @since 2.5 */ class MapTable extends Nested { /** * Constructor * * @param DatabaseDriver $db Database Driver connector object. * * @since 2.5 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__finder_taxonomy', 'id', $db); $this->setColumnAlias('published', 'state'); $this->access = (int) Factory::getApplication()->get('access'); } /** * Override check function * * @return boolean * * @see Table::check() * @since 4.0.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for a title. if (trim($this->title) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_CATEGORY')); return false; } $this->alias = ApplicationHelper::stringURLSafe($this->title, $this->language); if (trim($this->alias) == '') { $this->alias = md5(serialize($this->getProperties())); } return true; } } LinkTable.php 0000644 00000002573 15172645147 0007145 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\Table; use Joomla\CMS\Table\Table; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Link table class for the Finder package. * * @since 2.5 */ class LinkTable extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Constructor * * @param DatabaseDriver $db Database Driver connector object. * * @since 2.5 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__finder_links', 'link_id', $db); } /** * Overloaded store function * * @param boolean $updateNulls True to update fields even if they are null. * * @return mixed False on failure, positive integer on success. * * @see Table::store() * @since 4.0.0 */ public function store($updateNulls = true) { return parent::store($updateNulls); } } FilterTable.php 0000644 00000011130 15172645147 0007462 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\Table; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Table\Table; use Joomla\Database\DatabaseDriver; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Filter table class for the Finder package. * * @since 2.5 */ class FilterTable extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Ensure the params are json encoded in the bind method * * @var array * @since 4.0.0 */ protected $_jsonEncode = ['params']; /** * Constructor * * @param DatabaseDriver $db Database Driver connector object. * * @since 2.5 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__finder_filters', 'filter_id', $db); $this->setColumnAlias('published', 'state'); } /** * Method to perform sanity checks on the \JTable instance properties to ensure * they are safe to store in the database. Child classes should override this * method to make sure the data they are storing in the database is safe and * as expected before storage. * * @return boolean True if the instance is sane and able to be stored in the database. * * @since 2.5 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (trim($this->alias) === '') { $this->alias = $this->title; } $this->alias = ApplicationHelper::stringURLSafe($this->alias); if (trim(str_replace('-', '', $this->alias)) === '') { $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); } $params = new Registry($this->params); $d1 = $params->get('d1', ''); $d2 = $params->get('d2', ''); // Check the end date is not earlier than the start date. if (!empty($d1) && !empty($d2) && $d2 < $d1) { // Swap the dates. $params->set('d1', $d2); $params->set('d2', $d1); $this->params = (string) $params; } return true; } /** * Method to store a row in the database from the \JTable instance properties. * If a primary key value is set the row with that primary key value will be * updated with the instance property values. If no primary key value is set * a new row will be inserted into the database with the properties from the * \JTable instance. * * @param boolean $updateNulls True to update fields even if they are null. [optional] * * @return boolean True on success. * * @since 2.5 */ public function store($updateNulls = true) { $date = Factory::getDate()->toSql(); $userId = Factory::getUser()->id; // Set created date if not set. if (!(int) $this->created) { $this->created = $date; } if ($this->filter_id) { // Existing item $this->modified_by = $userId; $this->modified = $date; } else { if (empty($this->created_by)) { $this->created_by = $userId; } if (!(int) $this->modified) { $this->modified = $this->created; } if (empty($this->modified_by)) { $this->modified_by = $this->created_by; } } if (is_array($this->data)) { $this->map_count = count($this->data); $this->data = implode(',', $this->data); } else { $this->map_count = 0; $this->data = implode(',', []); } // Verify that the alias is unique $table = new static($this->getDbo()); if ($table->load(['alias' => $this->alias]) && ($table->filter_id != $this->filter_id || $this->filter_id == 0)) { $this->setError(Text::_('COM_FINDER_FILTER_ERROR_UNIQUE_ALIAS')); return false; } return parent::store($updateNulls); } } ViewLevel.php 0000644 00000005105 15172723307 0007167 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Viewlevels table class. * * @since 1.7.0 */ class ViewLevel extends Table { /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__viewlevels', 'id', $db); } /** * Method to bind the data. * * @param array $array The data to bind. * @param mixed $ignore An array or space separated list of fields to ignore. * * @return boolean True on success, false on failure. * * @since 1.7.0 */ public function bind($array, $ignore = '') { // Bind the rules as appropriate. if (isset($array['rules'])) { if (\is_array($array['rules'])) { $array['rules'] = json_encode($array['rules']); } } return parent::bind($array, $ignore); } /** * Method to check the current record to save * * @return boolean True on success * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Validate the title. if ((trim($this->title)) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_VIEWLEVEL')); return false; } $id = (int) $this->id; // Check for a duplicate title. $db = $this->_db; $query = $db->getQuery(true) ->select('COUNT(' . $db->quoteName('title') . ')') ->from($db->quoteName('#__viewlevels')) ->where($db->quoteName('title') . ' = :title') ->where($db->quoteName('id') . ' != :id') ->bind(':title', $this->title) ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); if ($db->loadResult() > 0) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_USERLEVEL_NAME_EXISTS', $this->title)); return false; } return true; } } Language.php 0000644 00000007657 15172723307 0007026 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Languages table. * * @since 1.7.0 */ class Language extends Table { /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__languages', 'lang_id', $db); } /** * Overloaded check method to ensure data integrity * * @return boolean True on success * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (trim($this->title) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_LANGUAGE_NO_TITLE')); return false; } return true; } /** * Overrides Table::store to check unique fields. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 2.5.0 */ public function store($updateNulls = false) { $table = Table::getInstance('Language', 'JTable', ['dbo' => $this->getDbo()]); // Verify that the language code is unique if ($table->load(['lang_code' => $this->lang_code]) && ($table->lang_id != $this->lang_id || $this->lang_id == 0)) { $this->setError(Text::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_LANG_CODE')); return false; } // Verify that the sef field is unique if ($table->load(['sef' => $this->sef]) && ($table->lang_id != $this->lang_id || $this->lang_id == 0)) { $this->setError(Text::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE')); return false; } // Verify that the image field is unique if ($this->image && $table->load(['image' => $this->image]) && ($table->lang_id != $this->lang_id || $this->lang_id == 0)) { $this->setError(Text::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE')); return false; } return parent::store($updateNulls); } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 3.8.0 */ protected function _getAssetName() { return 'com_languages.language.' . $this->lang_id; } /** * Method to return the title to use for the asset table. * * @return string * * @since 3.8.0 */ protected function _getAssetTitle() { return $this->title; } /** * Method to get the parent asset under which to register this one. * By default, all assets are registered to the ROOT node with ID, * which will default to 1 if none exists. * The extended class can define a table and id to lookup. If the * asset does not exist it will be created. * * @param Table $table A Table object for the asset parent. * @param integer $id Id to look up * * @return integer * * @since 3.8.0 */ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; $asset = Table::getInstance('asset'); if ($asset->loadByName('com_languages')) { $assetId = $asset->id; } return $assetId ?? parent::_getAssetParentId($table, $id); } } Content.php 0000644 00000025210 15172723307 0006676 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Access\Rules; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Tag\TaggableTableInterface; use Joomla\CMS\Tag\TaggableTableTrait; use Joomla\CMS\Versioning\VersionableTableInterface; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Content table * * @since 1.5 */ class Content extends Table implements VersionableTableInterface, TaggableTableInterface { use TaggableTableTrait; /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Constructor * * @param DatabaseDriver $db A database connector object * * @since 1.5 */ public function __construct(DatabaseDriver $db) { $this->typeAlias = 'com_content.article'; parent::__construct('#__content', 'id', $db); // Set the alias since the column is called state $this->setColumnAlias('published', 'state'); } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 1.6 */ protected function _getAssetName() { $k = $this->_tbl_key; return 'com_content.article.' . (int) $this->$k; } /** * Method to return the title to use for the asset table. * * @return string * * @since 1.6 */ protected function _getAssetTitle() { return $this->title; } /** * Method to get the parent asset id for the record * * @param Table $table A Table object (optional) for the asset parent * @param integer $id The id (optional) of the content. * * @return integer * * @since 1.6 */ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; // This is an article under a category. if ($this->catid) { $catId = (int) $this->catid; // Build the query to get the asset id for the parent category. $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('asset_id')) ->from($this->_db->quoteName('#__categories')) ->where($this->_db->quoteName('id') . ' = :catid') ->bind(':catid', $catId, ParameterType::INTEGER); // Get the asset id from the database. $this->_db->setQuery($query); if ($result = $this->_db->loadResult()) { $assetId = (int) $result; } } // Return the asset id. if ($assetId) { return $assetId; } else { return parent::_getAssetParentId($table, $id); } } /** * Overloaded bind function * * @param array $array Named array * @param mixed $ignore An optional array or space separated list of properties * to ignore while binding. * * @return mixed Null if operation was satisfactory, otherwise returns an error string * * @see Table::bind() * @since 1.6 */ public function bind($array, $ignore = '') { // Search for the {readmore} tag and split the text up accordingly. if (isset($array['articletext'])) { $pattern = '#<hr\s+id=("|\')system-readmore("|\')\s*\/*>#i'; $tagPos = preg_match($pattern, $array['articletext']); if ($tagPos == 0) { $this->introtext = $array['articletext']; $this->fulltext = ''; } else { list($this->introtext, $this->fulltext) = preg_split($pattern, $array['articletext'], 2); } } if (isset($array['attribs']) && \is_array($array['attribs'])) { $registry = new Registry($array['attribs']); $array['attribs'] = (string) $registry; } if (isset($array['metadata']) && \is_array($array['metadata'])) { $registry = new Registry($array['metadata']); $array['metadata'] = (string) $registry; } // Bind the rules. if (isset($array['rules']) && \is_array($array['rules'])) { $rules = new Rules($array['rules']); $this->setRules($rules); } return parent::bind($array, $ignore); } /** * Overloaded check function * * @return boolean True on success, false on failure * * @see Table::check() * @since 1.5 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (trim($this->title) == '') { $this->setError(Text::_('COM_CONTENT_WARNING_PROVIDE_VALID_NAME')); return false; } if (trim($this->alias) == '') { $this->alias = $this->title; } $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language); if (trim(str_replace('-', '', $this->alias)) == '') { $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); } // Check for a valid category. if (!$this->catid = (int) $this->catid) { $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED')); return false; } if (trim(str_replace(' ', '', $this->fulltext)) == '') { $this->fulltext = ''; } /** * Ensure any new items have compulsory fields set. This is needed for things like * frontend editing where we don't show all the fields or using some kind of API */ if (!$this->id) { // Images can be an empty json string if (!isset($this->images)) { $this->images = '{}'; } // URLs can be an empty json string if (!isset($this->urls)) { $this->urls = '{}'; } // Attributes (article params) can be an empty json string if (!isset($this->attribs)) { $this->attribs = '{}'; } // Metadata can be an empty json string if (!isset($this->metadata)) { $this->metadata = '{}'; } // Hits must be zero on a new item $this->hits = 0; } // Set publish_up to null if not set if (!$this->publish_up) { $this->publish_up = null; } // Set publish_down to null if not set if (!$this->publish_down) { $this->publish_down = null; } // Check the publish down date is not earlier than publish up. if (!is_null($this->publish_up) && !is_null($this->publish_down) && $this->publish_down < $this->publish_up) { // Swap the dates. $temp = $this->publish_up; $this->publish_up = $this->publish_down; $this->publish_down = $temp; } // Clean up keywords -- eliminate extra spaces between phrases // and cr (\r) and lf (\n) characters from string if (!empty($this->metakey)) { // Only process if not empty // Array of characters to remove $badCharacters = ["\n", "\r", "\"", '<', '>']; // Remove bad characters $afterClean = StringHelper::str_ireplace($badCharacters, '', $this->metakey); // Create array using commas as delimiter $keys = explode(',', $afterClean); $cleanKeys = []; foreach ($keys as $key) { if (trim($key)) { // Ignore blank keywords $cleanKeys[] = trim($key); } } // Put array back together delimited by ", " $this->metakey = implode(', ', $cleanKeys); } else { $this->metakey = ''; } if ($this->metadesc === null) { $this->metadesc = ''; } return true; } /** * Overrides Table::store to set modified data and user id. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 1.6 */ public function store($updateNulls = true) { $date = Factory::getDate()->toSql(); $user = Factory::getUser(); // Set created date if not set. if (!(int) $this->created) { $this->created = $date; } if ($this->id) { // Existing item $this->modified_by = $user->get('id'); $this->modified = $date; } else { // Field created_by can be set by the user, so we don't touch it if it's set. if (empty($this->created_by)) { $this->created_by = $user->get('id'); } // Set modified to created date if not set if (!(int) $this->modified) { $this->modified = $this->created; } // Set modified_by to created_by user if not set if (empty($this->modified_by)) { $this->modified_by = $this->created_by; } } // Verify that the alias is unique $table = Table::getInstance('Content', 'JTable', ['dbo' => $this->getDbo()]); if ($table->load(['alias' => $this->alias, 'catid' => $this->catid]) && ($table->id != $this->id || $this->id == 0)) { // Is the existing article trashed? $this->setError(Text::_('COM_CONTENT_ERROR_UNIQUE_ALIAS')); if ($table->state === -2) { $this->setError(Text::_('COM_CONTENT_ERROR_UNIQUE_ALIAS_TRASHED')); } return false; } return parent::store($updateNulls); } /** * Get the type alias for UCM features * * @return string The alias as described above * * @since 4.0.0 */ public function getTypeAlias() { return $this->typeAlias; } } Nested.php 0000644 00000163665 15172723307 0006527 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Event\AbstractEvent; use Joomla\Event\Dispatcher; use Joomla\Event\Event; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Table class supporting modified pre-order tree traversal behavior. * * @since 1.7.0 */ class Nested extends Table { /** * Object property holding the primary key of the parent node. Provides adjacency list data for nodes. * * @var integer * @since 1.7.0 */ public $parent_id; /** * Object property holding the depth level of the node in the tree. * * @var integer * @since 1.7.0 */ public $level; /** * Object property holding the left value of the node for managing its placement in the nested sets tree. * * @var integer * @since 1.7.0 */ public $lft; /** * Object property holding the right value of the node for managing its placement in the nested sets tree. * * @var integer * @since 1.7.0 */ public $rgt; /** * Object property holding the alias of this node used to construct the full text path, forward-slash delimited. * * @var string * @since 1.7.0 */ public $alias; /** * Object property to hold the location type to use when storing the row. * * @var string * @since 1.7.0 * @see Nested::$_validLocations */ protected $_location; /** * Object property to hold the primary key of the location reference node to use when storing the row. * * A combination of location type and reference node describes where to store the current node in the tree. * * @var integer * @since 1.7.0 */ protected $_location_id; /** * An array to cache values in recursive processes. * * @var array * @since 1.7.0 */ protected $_cache = []; /** * Debug level * * @var integer * @since 1.7.0 */ protected $_debug = 0; /** * Cache for the root ID * * @var integer * @since 3.3 */ protected static $root_id = 0; /** * Array declaring the valid location values for moving a node * * @var array * @since 3.7.0 */ private $_validLocations = ['before', 'after', 'first-child', 'last-child']; /** * Sets the debug level on or off * * @param integer $level 0 = off, 1 = on * * @return void * * @since 1.7.0 */ public function debug($level) { $this->_debug = (int) $level; } /** * Method to get an array of nodes from a given node to its root. * * @param integer $pk Primary key of the node for which to get the path. * @param boolean $diagnostic Only select diagnostic data for the nested sets. * * @return mixed An array of node objects including the start node. * * @since 1.7.0 * @throws \RuntimeException on database error */ public function getPath($pk = null, $diagnostic = false) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Get the path from the node to the root. $select = ($diagnostic) ? 'p.' . $k . ', p.parent_id, p.level, p.lft, p.rgt' : 'p.*'; $query = $this->_db->getQuery(true) ->select($select) ->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p') ->where('n.lft BETWEEN p.lft AND p.rgt') ->where('n.' . $k . ' = ' . (int) $pk) ->order('p.lft'); $this->_db->setQuery($query); return $this->_db->loadObjectList(); } /** * Method to get a node and all its child nodes. * * @param integer $pk Primary key of the node for which to get the tree. * @param boolean $diagnostic Only select diagnostic data for the nested sets. * * @return mixed Boolean false on failure or array of node objects on success. * * @since 1.7.0 * @throws \RuntimeException on database error. */ public function getTree($pk = null, $diagnostic = false) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Get the node and children as a tree. $select = ($diagnostic) ? 'n.' . $k . ', n.parent_id, n.level, n.lft, n.rgt' : 'n.*'; $query = $this->_db->getQuery(true) ->select($select) ->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p') ->where('n.lft BETWEEN p.lft AND p.rgt') ->where('p.' . $k . ' = ' . (int) $pk) ->order('n.lft'); return $this->_db->setQuery($query)->loadObjectList(); } /** * Method to determine if a node is a leaf node in the tree (has no children). * * @param integer $pk Primary key of the node to check. * * @return boolean True if a leaf node, false if not or null if the node does not exist. * * @note Since 3.0.0 this method returns null if the node does not exist. * @since 1.7.0 * @throws \RuntimeException on database error. */ public function isLeaf($pk = null) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; $node = $this->_getNode($pk); // Get the node by primary key. if (empty($node)) { // Error message set in getNode method. return; } // The node is a leaf node. return ($node->rgt - $node->lft) == 1; } /** * Method to set the location of a node in the tree object. This method does not * save the new location to the database, but will set it in the object so * that when the node is stored it will be stored in the new location. * * @param integer $referenceId The primary key of the node to reference new location by. * @param string $position Location type string. * * @return void * * @note Since 3.0.0 this method returns void and throws an \InvalidArgumentException when an invalid position is passed. * @see Nested::$_validLocations * @since 1.7.0 * @throws \InvalidArgumentException */ public function setLocation($referenceId, $position = 'after') { // Make sure the location is valid. if (!\in_array($position, $this->_validLocations)) { throw new \InvalidArgumentException( sprintf('Invalid location "%1$s" given, valid values are %2$s', $position, implode(', ', $this->_validLocations)) ); } // Set the location properties. $this->_location = $position; $this->_location_id = $referenceId; } /** * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause. * Negative numbers move the row up in the sequence and positive numbers move it down. * * @param integer $delta The direction and magnitude to move the row in the ordering sequence. * @param string $where WHERE clause to use for limiting the selection of rows to compact the * ordering values. * * @return mixed Boolean true on success. * * @since 1.7.0 */ public function move($delta, $where = '') { $k = $this->_tbl_key; $pk = $this->$k; $query = $this->_db->getQuery(true) ->select($k) ->from($this->_tbl) ->where('parent_id = ' . $this->parent_id); if ($where) { $query->where($where); } if ($delta > 0) { $query->where('rgt > ' . $this->rgt) ->order('rgt ASC'); $position = 'after'; } else { $query->where('lft < ' . $this->lft) ->order('lft DESC'); $position = 'before'; } $this->_db->setQuery($query); $referenceId = $this->_db->loadResult(); if ($referenceId) { return $this->moveByReference($referenceId, $position, $pk); } else { return false; } } /** * Method to move a node and its children to a new location in the tree. * * @param integer $referenceId The primary key of the node to reference new location by. * @param string $position Location type string. ['before', 'after', 'first-child', 'last-child'] * @param integer $pk The primary key of the node to move. * @param boolean $recursiveUpdate Flag indicate that method recursiveUpdatePublishedColumn should be call. * * @return boolean True on success. * * @since 1.7.0 * @throws \RuntimeException on database error. */ public function moveByReference($referenceId, $position = 'after', $pk = null, $recursiveUpdate = true) { if ($this->_debug) { echo "\nMoving ReferenceId:$referenceId, Position:$position, PK:$pk"; } $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Get the node by id. if (!$node = $this->_getNode($pk)) { // Error message set in getNode method. return false; } // Get the ids of child nodes. $query = $this->_db->getQuery(true) ->select($k) ->from($this->_tbl) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $children = $this->_db->setQuery($query)->loadColumn(); if ($this->_debug) { $this->_logtable(false); } // Cannot move the node to be a child of itself. if (\in_array($referenceId, $children)) { $this->setError( new \UnexpectedValueException( sprintf('%1$s::moveByReference() is trying to make record ID %2$d a child of itself.', \get_class($this), $pk) ) ); return false; } // Lock the table for writing. if (!$this->_lock()) { return false; } /* * Move the sub-tree out of the nested sets by negating its left and right values. */ $query->clear() ->update($this->_tbl) ->set('lft = lft * (-1), rgt = rgt * (-1)') ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); /* * Close the hole in the tree that was opened by removing the sub-tree from the nested sets. */ // Compress the left values. $query->clear() ->update($this->_tbl) ->set('lft = lft - ' . (int) $node->width) ->where('lft > ' . (int) $node->rgt); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); // Compress the right values. $query->clear() ->update($this->_tbl) ->set('rgt = rgt - ' . (int) $node->width) ->where('rgt > ' . (int) $node->rgt); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); // We are moving the tree relative to a reference node. if ($referenceId) { // Get the reference node by primary key. if (!$reference = $this->_getNode($referenceId)) { // Error message set in getNode method. $this->_unlock(); return false; } // Get the reposition data for shifting the tree and re-inserting the node. if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, $position)) { // Error message set in getNode method. $this->_unlock(); return false; } } else { // We are moving the tree to be the last child of the root node // Get the last root node as the reference node. $query->clear() ->select($this->_tbl_key . ', parent_id, level, lft, rgt') ->from($this->_tbl) ->where('parent_id = 0') ->order('lft DESC'); $query->setLimit(1); $this->_db->setQuery($query); $reference = $this->_db->loadObject(); if ($this->_debug) { $this->_logtable(false); } // Get the reposition data for re-inserting the node after the found root. if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, 'last-child')) { // Error message set in getNode method. $this->_unlock(); return false; } } /* * Create space in the nested sets at the new location for the moved sub-tree. */ // Shift left values. $query->clear() ->update($this->_tbl) ->set('lft = lft + ' . (int) $node->width) ->where($repositionData->left_where); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); // Shift right values. $query->clear() ->update($this->_tbl) ->set('rgt = rgt + ' . (int) $node->width) ->where($repositionData->right_where); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); /* * Calculate the offset between where the node used to be in the tree and * where it needs to be in the tree for left ids (also works for right ids). */ $offset = $repositionData->new_lft - $node->lft; $levelOffset = $repositionData->new_level - $node->level; // Move the nodes back into position in the tree using the calculated offsets. $query->clear() ->update($this->_tbl) ->set('rgt = ' . (int) $offset . ' - rgt') ->set('lft = ' . (int) $offset . ' - lft') ->set('level = level + ' . (int) $levelOffset) ->where('lft < 0'); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); // Set the correct parent id for the moved node if required. if ($node->parent_id != $repositionData->new_parent_id) { $query = $this->_db->getQuery(true) ->update($this->_tbl); // Update the title and alias fields if they exist for the table. $fields = $this->getFields(); if ($this->hasField('title') && $this->title !== null) { $query->set('title = ' . $this->_db->quote($this->title)); } if (\array_key_exists('alias', $fields) && $this->alias !== null) { $query->set('alias = ' . $this->_db->quote($this->alias)); } $query->set('parent_id = ' . (int) $repositionData->new_parent_id) ->where($this->_tbl_key . ' = ' . (int) $node->$k); $this->_db->setQuery($query); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED'); } // Unlock the table for writing. $this->_unlock(); if ($this->hasField('published') && $recursiveUpdate) { $this->recursiveUpdatePublishedColumn($node->$k); } // Set the object values. $this->parent_id = $repositionData->new_parent_id; $this->level = $repositionData->new_level; $this->lft = $repositionData->new_lft; $this->rgt = $repositionData->new_rgt; return true; } /** * Method to delete a node and, optionally, its child nodes from the table. * * @param integer $pk The primary key of the node to delete. * @param boolean $children True to delete child nodes, false to move them up a level. * * @return boolean True on success. * * @since 1.7.0 */ public function delete($pk = null, $children = true) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Pre-processing by observers $event = new Event( 'onBeforeDelete', [ 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onBeforeDelete', $event); // If tracking assets, remove the asset first. if ($this->_trackAssets) { $name = $this->_getAssetName(); /** @var Asset $asset */ $asset = Table::getInstance('Asset', 'JTable', ['dbo' => $this->getDbo()]); if ($asset->loadByName($name)) { // Delete the node in assets table. if (!$asset->delete(null, $children)) { $this->setError($asset->getError()); return false; } } else { $this->setError($asset->getError()); return false; } } // Lock the table for writing. if (!$this->_lock()) { // Error message set in lock method. return false; } // Get the node by id. $node = $this->_getNode($pk); if (empty($node)) { // Error message set in getNode method. $this->_unlock(); return false; } $query = $this->_db->getQuery(true); // Should we delete all children along with the node? if ($children) { // Delete the node and all of its children. $query->clear() ->delete($this->_tbl) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Compress the left values. $query->clear() ->update($this->_tbl) ->set('lft = lft - ' . (int) $node->width) ->where('lft > ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Compress the right values. $query->clear() ->update($this->_tbl) ->set('rgt = rgt - ' . (int) $node->width) ->where('rgt > ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); } else { // Leave the children and move them up a level. // Delete the node. $query->clear() ->delete($this->_tbl) ->where('lft = ' . (int) $node->lft); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Shift all node's children up a level. $query->clear() ->update($this->_tbl) ->set('lft = lft - 1') ->set('rgt = rgt - 1') ->set('level = level - 1') ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Adjust all the parent values for direct children of the deleted node. $query->clear() ->update($this->_tbl) ->set('parent_id = ' . (int) $node->parent_id) ->where('parent_id = ' . (int) $node->$k); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Shift all of the left values that are right of the node. $query->clear() ->update($this->_tbl) ->set('lft = lft - 2') ->where('lft > ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); // Shift all of the right values that are right of the node. $query->clear() ->update($this->_tbl) ->set('rgt = rgt - 2') ->where('rgt > ' . (int) $node->rgt); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED'); } // Unlock the table for writing. $this->_unlock(); // Post-processing by observers $event = new Event( 'onAfterDelete', [ 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onAfterDelete', $event); return true; } /** * Checks that the object is valid and able to be stored. * * This method checks that the parent_id is non-zero and exists in the database. * Note that the root node (parent_id = 0) cannot be manipulated with this class. * * @return boolean True if all checks pass. * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } $this->parent_id = (int) $this->parent_id; // Set up a mini exception handler. try { // Check that the parent_id field is valid. if ($this->parent_id == 0) { throw new \UnexpectedValueException(sprintf('Invalid `parent_id` [%1$d] in %2$s::check()', $this->parent_id, \get_class($this))); } $query = $this->_db->getQuery(true) ->select('1') ->from($this->_tbl) ->where($this->_tbl_key . ' = ' . $this->parent_id); if (!$this->_db->setQuery($query)->loadResult()) { throw new \UnexpectedValueException(sprintf('Invalid `parent_id` [%1$d] in %2$s::check()', $this->parent_id, \get_class($this))); } } catch (\UnexpectedValueException $e) { // Validation error - record it and return false. $this->setError($e); return false; } return true; } /** * Method to store a node in the database table. * * @param boolean $updateNulls True to update null values as well. * * @return boolean True on success. * * @since 1.7.0 */ public function store($updateNulls = false) { $k = $this->_tbl_key; // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeStore', [ 'subject' => $this, 'updateNulls' => $updateNulls, 'k' => $k, ] ); $this->getDispatcher()->dispatch('onTableBeforeStore', $event); if ($this->_debug) { echo "\n" . \get_class($this) . "::store\n"; $this->_logtable(true, false); } /* * If the primary key is empty, then we assume we are inserting a new node into the * tree. From this point we would need to determine where in the tree to insert it. */ if (empty($this->$k)) { /* * We are inserting a node somewhere in the tree with a known reference * node. We have to make room for the new node and set the left and right * values before we insert the row. */ if ($this->_location_id >= 0) { // Lock the table for writing. if (!$this->_lock()) { // Error message set in lock method. return false; } // We are inserting a node relative to the last root node. if ($this->_location_id == 0) { // Get the last root node as the reference node. $query = $this->_db->getQuery(true) ->select($this->_tbl_key . ', parent_id, level, lft, rgt') ->from($this->_tbl) ->where('parent_id = 0') ->order('lft DESC'); $query->setLimit(1); $this->_db->setQuery($query); $reference = $this->_db->loadObject(); if ($this->_debug) { $this->_logtable(false); } } else { // We have a real node set as a location reference. // Get the reference node by primary key. if (!$reference = $this->_getNode($this->_location_id)) { // Error message set in getNode method. $this->_unlock(); return false; } } // Get the reposition data for shifting the tree and re-inserting the node. if (!($repositionData = $this->_getTreeRepositionData($reference, 2, $this->_location))) { // Error message set in getNode method. $this->_unlock(); return false; } // Create space in the tree at the new location for the new node in left ids. $query = $this->_db->getQuery(true) ->update($this->_tbl) ->set('lft = lft + 2') ->where($repositionData->left_where); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED'); // Create space in the tree at the new location for the new node in right ids. $query->clear() ->update($this->_tbl) ->set('rgt = rgt + 2') ->where($repositionData->right_where); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED'); // Set the object values. $this->parent_id = $repositionData->new_parent_id; $this->level = $repositionData->new_level; $this->lft = $repositionData->new_lft; $this->rgt = $repositionData->new_rgt; } else { // Negative parent ids are invalid $e = new \UnexpectedValueException(sprintf('%s::store() used a negative _location_id', \get_class($this))); $this->setError($e); return false; } } else { /** * If we have a given primary key then we assume we are simply updating this * node in the tree. We should assess whether or not we are moving the node * or just updating its data fields. */ // If the location has been set, move the node to its new location. if ($this->_location_id > 0) { // Skip recursiveUpdatePublishedColumn method, it will be called later. if (!$this->moveByReference($this->_location_id, $this->_location, $this->$k, false)) { // Error message set in move method. return false; } } // Lock the table for writing. if (!$this->_lock()) { // Error message set in lock method. return false; } } // We do not want parent::store to update observers since tables are locked and we are updating it from this // level of store(): $oldDispatcher = clone $this->getDispatcher(); $blankDispatcher = new Dispatcher(); $this->setDispatcher($blankDispatcher); $result = parent::store($updateNulls); // Restore previous callable dispatcher state: $this->setDispatcher($oldDispatcher); if ($result) { if ($this->_debug) { $this->_logtable(); } } // Unlock the table for writing. $this->_unlock(); if ($result && $this->hasField('published')) { $this->recursiveUpdatePublishedColumn($this->$k); } // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterStore', [ 'subject' => $this, 'result' => &$result, ] ); $this->getDispatcher()->dispatch('onTableAfterStore', $event); return $result; } /** * Method to set the publishing state for a node or list of nodes in the database * table. The method respects rows checked out by other users and will attempt * to checkin rows that it can after adjustments are made. The method will not * allow you to set a publishing state higher than any ancestor node and will * not allow you to set a publishing state on a node with a checked out child. * * @param mixed $pks An optional array of primary key values to update. If not * set the instance property value is used. * @param integer $state The publishing state. eg. [0 = unpublished, 1 = published] * @param integer $userId The user id of the user performing the operation. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function publish($pks = null, $state = 1, $userId = 0) { $k = $this->_tbl_key; $query = $this->_db->getQuery(true); $table = $this->_db->quoteName($this->_tbl); $published = $this->_db->quoteName($this->getColumnAlias('published')); $checkedOut = $this->_db->quoteName($this->getColumnAlias('checked_out')); $key = $this->_db->quoteName($k); // Sanitize input. $pks = ArrayHelper::toInteger($pks); $userId = (int) $userId; $state = (int) $state; // If $state > 1, then we allow state changes even if an ancestor has lower state // (for example, can change a child state to Archived (2) if an ancestor is Published (1) $compareState = ($state > 1) ? 1 : $state; // If there are no primary keys set check to see if the instance key is set. if (empty($pks)) { if ($this->$k) { $pks = explode(',', $this->$k); } else { // Nothing to set publishing state on, return false. $e = new \UnexpectedValueException(sprintf('%s::publish(%s, %d, %d) empty.', \get_class($this), implode(',', $pks), $state, $userId)); $this->setError($e); return false; } } // Determine if there is checkout support for the table. $checkoutSupport = ($this->hasField('checked_out') || $this->hasField('checked_out_time')); // Iterate over the primary keys to execute the publish action if possible. foreach ($pks as $pk) { // Get the node by primary key. if (!$node = $this->_getNode($pk)) { // Error message set in getNode method. return false; } // If the table has checkout support, verify no children are checked out. if ($checkoutSupport) { // Ensure that children are not checked out. $query->clear() ->select('COUNT(' . $k . ')') ->from($this->_tbl) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt) ->where('(' . $checkedOut . ' <> 0 AND ' . $checkedOut . ' <> ' . (int) $userId . ')'); $this->_db->setQuery($query); // Check for checked out children. if ($this->_db->loadResult()) { // @todo Convert to a conflict exception when available. $e = new \RuntimeException(sprintf('%s::publish(%s, %d, %d) checked-out conflict.', \get_class($this), $pks[0], $state, $userId)); $this->setError($e); return false; } } // If any parent nodes have lower published state values, we cannot continue. if ($node->parent_id) { // Get any ancestor nodes that have a lower publishing state. $query->clear() ->select('1') ->from($table) ->where('lft < ' . (int) $node->lft) ->where('rgt > ' . (int) $node->rgt) ->where('parent_id > 0') ->where($published . ' < ' . (int) $compareState); // Just fetch one row (one is one too many). $query->setLimit(1); $this->_db->setQuery($query); if ($this->_db->loadResult()) { $e = new \UnexpectedValueException( sprintf('%s::publish(%s, %d, %d) ancestors have lower state.', \get_class($this), $pks[0], $state, $userId) ); $this->setError($e); return false; } } $this->recursiveUpdatePublishedColumn($pk, $state); // If checkout support exists for the object, check the row in. if ($checkoutSupport) { $this->checkIn($pk); } } // If the Table instance value is in the list of primary keys that were set, set the instance. if (\in_array($this->$k, $pks)) { $this->published = $state; } $this->setError(''); return true; } /** * Method to move a node one position to the left in the same level. * * @param integer $pk Primary key of the node to move. * * @return boolean True on success. * * @since 1.7.0 * @throws \RuntimeException on database error. */ public function orderUp($pk) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Lock the table for writing. if (!$this->_lock()) { // Error message set in lock method. return false; } // Get the node by primary key. $node = $this->_getNode($pk); if (empty($node)) { // Error message set in getNode method. $this->_unlock(); return false; } // Get the left sibling node. $sibling = $this->_getNode($node->lft - 1, 'right'); if (empty($sibling)) { // Error message set in getNode method. $this->_unlock(); return false; } try { // Get the primary keys of child nodes. $query = $this->_db->getQuery(true) ->select($this->_tbl_key) ->from($this->_tbl) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $children = $this->_db->setQuery($query)->loadColumn(); // Shift left and right values for the node and its children. $query->clear() ->update($this->_tbl) ->set('lft = lft - ' . (int) $sibling->width) ->set('rgt = rgt - ' . (int) $sibling->width) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_db->setQuery($query)->execute(); // Shift left and right values for the sibling and its children. $query->clear() ->update($this->_tbl) ->set('lft = lft + ' . (int) $node->width) ->set('rgt = rgt + ' . (int) $node->width) ->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt) ->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')'); $this->_db->setQuery($query)->execute(); } catch (\RuntimeException $e) { $this->_unlock(); throw $e; } // Unlock the table for writing. $this->_unlock(); return true; } /** * Method to move a node one position to the right in the same level. * * @param integer $pk Primary key of the node to move. * * @return boolean True on success. * * @since 1.7.0 * @throws \RuntimeException on database error. */ public function orderDown($pk) { $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Lock the table for writing. if (!$this->_lock()) { // Error message set in lock method. return false; } // Get the node by primary key. $node = $this->_getNode($pk); if (empty($node)) { // Error message set in getNode method. $this->_unlock(); return false; } $query = $this->_db->getQuery(true); // Get the right sibling node. $sibling = $this->_getNode($node->rgt + 1, 'left'); if (empty($sibling)) { // Error message set in getNode method. $this->_unlock(); return false; } try { // Get the primary keys of child nodes. $query->clear() ->select($this->_tbl_key) ->from($this->_tbl) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_db->setQuery($query); $children = $this->_db->loadColumn(); // Shift left and right values for the node and its children. $query->clear() ->update($this->_tbl) ->set('lft = lft + ' . (int) $sibling->width) ->set('rgt = rgt + ' . (int) $sibling->width) ->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt); $this->_db->setQuery($query)->execute(); // Shift left and right values for the sibling and its children. $query->clear() ->update($this->_tbl) ->set('lft = lft - ' . (int) $node->width) ->set('rgt = rgt - ' . (int) $node->width) ->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt) ->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')'); $this->_db->setQuery($query)->execute(); } catch (\RuntimeException $e) { $this->_unlock(); throw $e; } // Unlock the table for writing. $this->_unlock(); return true; } /** * Gets the ID of the root item in the tree * * @return mixed The primary id of the root row, or false if not found and the internal error is set. * * @since 1.7.0 */ public function getRootId() { if ((int) self::$root_id > 0) { return self::$root_id; } // Get the root item. $k = $this->_tbl_key; // Test for a unique record with parent_id = 0 $query = $this->_db->getQuery(true) ->select($k) ->from($this->_tbl) ->where('parent_id = 0'); $result = $this->_db->setQuery($query)->loadColumn(); if (\count($result) == 1) { self::$root_id = $result[0]; return self::$root_id; } // Test for a unique record with lft = 0 $query->clear() ->select($k) ->from($this->_tbl) ->where('lft = 0'); $result = $this->_db->setQuery($query)->loadColumn(); if (\count($result) == 1) { self::$root_id = $result[0]; return self::$root_id; } $fields = $this->getFields(); if (\array_key_exists('alias', $fields)) { // Test for a unique record alias = root $query->clear() ->select($k) ->from($this->_tbl) ->where('alias = ' . $this->_db->quote('root')); $result = $this->_db->setQuery($query)->loadColumn(); if (\count($result) == 1) { self::$root_id = $result[0]; return self::$root_id; } } $e = new \UnexpectedValueException(sprintf('%s::getRootId', \get_class($this))); $this->setError($e); self::$root_id = false; return false; } /** * Method to recursively rebuild the whole nested set tree. * * @param integer $parentId The root of the tree to rebuild. * @param integer $leftId The left id to start with in building the tree. * @param integer $level The level to assign to the current nodes. * @param string $path The path to the current nodes. * * @return integer 1 + value of root rgt on success, false on failure * * @since 1.7.0 * @throws \RuntimeException on database error. */ public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '') { // If no parent is provided, try to find it. if ($parentId === null) { // Get the root item. $parentId = $this->getRootId(); if ($parentId === false) { return false; } } $query = $this->_db->getQuery(true); // Build the structure of the recursive query. if (!isset($this->_cache['rebuild.sql'])) { $query->clear() ->select($this->_tbl_key . ', alias') ->from($this->_tbl) ->where('parent_id = %d'); // If the table has an ordering field, use that for ordering. if ($this->hasField('ordering')) { $query->order('parent_id, ' . $this->_db->quoteName($this->getColumnAlias('ordering')) . ', lft'); } else { $query->order('parent_id, lft'); } $this->_cache['rebuild.sql'] = (string) $query; } // Make a shortcut to database object. // Assemble the query to find all children of this node. $this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId)); $children = $this->_db->loadObjectList(); // The right value of this node is the left value + 1 $rightId = $leftId + 1; // Execute this function recursively over all children foreach ($children as $node) { /* * $rightId is the current right value, which is incremented on recursion return. * Increment the level for the children. * Add this item's alias to the path (but avoid a leading /) */ $rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1, $path . (empty($path) ? '' : '/') . $node->alias); // If there is an update failure, return false to break out of the recursion. if ($rightId === false) { return false; } } // We've got the left value, and now that we've processed // the children of this node we also know the right value. $query->clear() ->update($this->_tbl) ->set('lft = ' . (int) $leftId) ->set('rgt = ' . (int) $rightId) ->set('level = ' . (int) $level) ->set('path = ' . $this->_db->quote($path)) ->where($this->_tbl_key . ' = ' . (int) $parentId); $this->_db->setQuery($query)->execute(); // Return the right value of this node + 1. return $rightId + 1; } /** * Method to rebuild the node's path field from the alias values of the nodes from the current node to the root node of the tree. * * @param integer $pk Primary key of the node for which to get the path. * * @return boolean True on success. * * @since 1.7.0 */ public function rebuildPath($pk = null) { $fields = $this->getFields(); // If there is no alias or path field, just return true. if (!\array_key_exists('alias', $fields) || !\array_key_exists('path', $fields)) { return true; } $k = $this->_tbl_key; $pk = (\is_null($pk)) ? $this->$k : $pk; // Get the aliases for the path from the node to the root node. $query = $this->_db->getQuery(true) ->select('p.alias') ->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p') ->where('n.lft BETWEEN p.lft AND p.rgt') ->where('n.' . $this->_tbl_key . ' = ' . (int) $pk) ->order('p.lft'); $this->_db->setQuery($query); $segments = $this->_db->loadColumn(); // Make sure to remove the root path if it exists in the list. if ($segments[0] === 'root') { array_shift($segments); } // Build the path. $path = trim(implode('/', $segments), ' /\\'); // Update the path field for the node. $query->clear() ->update($this->_tbl) ->set('path = ' . $this->_db->quote($path)) ->where($this->_tbl_key . ' = ' . (int) $pk); $this->_db->setQuery($query)->execute(); // Update the current record's path to the new one: $this->path = $path; return true; } /** * Method to reset class properties to the defaults set in the class * definition. It will ignore the primary key as well as any private class * properties (except $_errors). * * @return void * * @since 3.2.1 */ public function reset() { parent::reset(); // Reset the location properties. $this->setLocation(0); } /** * Method to update order of table rows * * @param array $idArray id numbers of rows to be reordered. * @param array $lftArray lft values of rows to be reordered. * * @return integer|boolean 1 + value of root rgt on success, false on failure. * * @since 1.7.0 * @throws \Exception on database error. */ public function saveorder($idArray = null, $lftArray = null) { try { $query = $this->_db->getQuery(true); // Validate arguments if (\is_array($idArray) && \is_array($lftArray) && \count($idArray) == \count($lftArray)) { for ($i = 0, $count = \count($idArray); $i < $count; $i++) { // Do an update to change the lft values in the table for each id $query->clear() ->update($this->_tbl) ->where($this->_tbl_key . ' = ' . (int) $idArray[$i]) ->set('lft = ' . (int) $lftArray[$i]); $this->_db->setQuery($query)->execute(); if ($this->_debug) { $this->_logtable(); } } return $this->rebuild(); } else { return false; } } catch (\Exception $e) { $this->_unlock(); throw $e; } } /** * Method to recursive update published column for children rows. * * @param integer $pk Id number of row which published column was changed. * @param integer $newState An optional value for published column of row identified by $pk. * * @return boolean True on success. * * @since 3.7.0 * @throws \RuntimeException on database error. */ protected function recursiveUpdatePublishedColumn($pk, $newState = null) { $query = $this->_db->getQuery(true); $table = $this->_db->quoteName($this->_tbl); $key = $this->_db->quoteName($this->_tbl_key); $published = $this->_db->quoteName($this->getColumnAlias('published')); if ($newState !== null) { // Use a new published state in changed row. $newState = "(CASE WHEN p2.$key = " . (int) $pk . " THEN " . (int) $newState . " ELSE p2.$published END)"; } else { $newState = "p2.$published"; } /** * We have to calculate the correct value for c2.published * based on p2.published and own c2.published column, * where (p2) is parent category is and (c2) current category * * p2.published <= c2.published AND p2.published > 0 THEN c2.published * 2 <= 2 THEN 2 (If archived in archived then archived) * 1 <= 2 THEN 2 (If archived in published then archived) * 1 <= 1 THEN 1 (If published in published then published) * * p2.published > c2.published AND c2.published > 0 THEN p2.published * 2 > 1 THEN 2 (If published in archived then archived) * * p2.published > c2.published THEN c2.published ELSE p2.published * 2 > -2 THEN -2 (If trashed in archived then trashed) * 2 > 0 THEN 0 (If unpublished in archived then unpublished) * 1 > 0 THEN 0 (If unpublished in published then unpublished) * 0 > -2 THEN -2 (If trashed in unpublished then trashed) * ELSE * 0 <= 2 THEN 0 (If archived in unpublished then unpublished) * 0 <= 1 THEN 0 (If published in unpublished then unpublished) * 0 <= 0 THEN 0 (If unpublished in unpublished then unpublished) * -2 <= -2 THEN -2 (If trashed in trashed then trashed) * -2 <= 0 THEN -2 (If unpublished in trashed then trashed) * -2 <= 1 THEN -2 (If published in trashed then trashed) * -2 <= 2 THEN -2 (If archived in trashed then trashed) */ // Find node and all children keys $query->select("c.$key") ->from("$table AS node") ->leftJoin("$table AS c ON node.lft <= c.lft AND c.rgt <= node.rgt") ->where("node.$key = " . (int) $pk); $pks = $this->_db->setQuery($query)->loadColumn(); // Prepare a list of correct published states. $subquery = (string) $query->clear() ->select("c2.$key AS newId") ->select("CASE WHEN MIN($newState) > 0 THEN MAX($newState) ELSE MIN($newState) END AS newPublished") ->from("$table AS c2") ->innerJoin("$table AS p2 ON p2.lft <= c2.lft AND c2.rgt <= p2.rgt") ->where("c2.$key IN (" . implode(',', $pks) . ")") ->group("c2.$key"); // Update and cascade the publishing state. $query->clear() ->update($table) ->innerJoin("($subquery) AS c2") ->set("$published = " . $this->_db->quoteName("c2.newpublished")) ->where("$key = c2.newId") ->where("$key IN (" . implode(',', $pks) . ")"); $this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED'); return true; } /** * Method to get nested set properties for a node in the tree. * * @param integer $id Value to look up the node by. * @param string $key An optional key to look up the node by (parent | left | right). * If omitted, the primary key of the table is used. * * @return mixed Boolean false on failure or node object on success. * * @since 1.7.0 * @throws \RuntimeException on database error. */ protected function _getNode($id, $key = null) { // Determine which key to get the node base on. switch ($key) { case 'parent': $k = 'parent_id'; break; case 'left': $k = 'lft'; break; case 'right': $k = 'rgt'; break; default: $k = $this->_tbl_key; break; } // Get the node data. $query = $this->_db->getQuery(true) ->select($this->_tbl_key . ', parent_id, level, lft, rgt') ->from($this->_tbl) ->where($k . ' = ' . (int) $id); $query->setLimit(1); $row = $this->_db->setQuery($query)->loadObject(); // Check for no $row returned if (empty($row)) { $e = new \UnexpectedValueException(sprintf('%s::_getNode(%d, %s) failed.', \get_class($this), $id, $k)); $this->setError($e); return false; } // Do some simple calculations. $row->numChildren = (int) ($row->rgt - $row->lft - 1) / 2; $row->width = (int) $row->rgt - $row->lft + 1; return $row; } /** * Method to get various data necessary to make room in the tree at a location * for a node and its children. The returned data object includes conditions * for SQL WHERE clauses for updating left and right id values to make room for * the node as well as the new left and right ids for the node. * * @param object $referenceNode A node object with at least a 'lft' and 'rgt' with * which to make room in the tree around for a new node. * @param integer $nodeWidth The width of the node for which to make room in the tree. * @param string $position The position relative to the reference node where the room * should be made. * * @return mixed Boolean false on failure or data object on success. * * @since 1.7.0 */ protected function _getTreeRepositionData($referenceNode, $nodeWidth, $position = 'before') { // Make sure the reference an object with a left and right id. if (!\is_object($referenceNode) || !(isset($referenceNode->lft) && isset($referenceNode->rgt))) { return false; } // A valid node cannot have a width less than 2. if ($nodeWidth < 2) { return false; } $k = $this->_tbl_key; $data = new \stdClass(); // Run the calculations and build the data object by reference position. switch ($position) { case 'first-child': $data->left_where = 'lft > ' . $referenceNode->lft; $data->right_where = 'rgt >= ' . $referenceNode->lft; $data->new_lft = $referenceNode->lft + 1; $data->new_rgt = $referenceNode->lft + $nodeWidth; $data->new_parent_id = $referenceNode->$k; $data->new_level = $referenceNode->level + 1; break; case 'last-child': $data->left_where = 'lft > ' . ($referenceNode->rgt); $data->right_where = 'rgt >= ' . ($referenceNode->rgt); $data->new_lft = $referenceNode->rgt; $data->new_rgt = $referenceNode->rgt + $nodeWidth - 1; $data->new_parent_id = $referenceNode->$k; $data->new_level = $referenceNode->level + 1; break; case 'before': $data->left_where = 'lft >= ' . $referenceNode->lft; $data->right_where = 'rgt >= ' . $referenceNode->lft; $data->new_lft = $referenceNode->lft; $data->new_rgt = $referenceNode->lft + $nodeWidth - 1; $data->new_parent_id = $referenceNode->parent_id; $data->new_level = $referenceNode->level; break; default: case 'after': $data->left_where = 'lft > ' . $referenceNode->rgt; $data->right_where = 'rgt > ' . $referenceNode->rgt; $data->new_lft = $referenceNode->rgt + 1; $data->new_rgt = $referenceNode->rgt + $nodeWidth; $data->new_parent_id = $referenceNode->parent_id; $data->new_level = $referenceNode->level; break; } if ($this->_debug) { echo "\nRepositioning Data for $position\n-----------------------------------\nLeft Where: $data->left_where" . "\nRight Where: $data->right_where\nNew Lft: $data->new_lft\nNew Rgt: $data->new_rgt" . "\nNew Parent ID: $data->new_parent_id\nNew Level: $data->new_level\n"; } return $data; } /** * Method to create a log table in the buffer optionally showing the query and/or data. * * @param boolean $showData True to show data * @param boolean $showQuery True to show query * * @return void * * @codeCoverageIgnore * @since 1.7.0 */ protected function _logtable($showData = true, $showQuery = true) { $sep = "\n" . str_pad('', 40, '-'); $buffer = ''; if ($showQuery) { $buffer .= "\n" . htmlspecialchars($this->_db->getQuery(), ENT_QUOTES, 'UTF-8') . $sep; } if ($showData) { $query = $this->_db->getQuery(true) ->select($this->_tbl_key . ', parent_id, lft, rgt, level') ->from($this->_tbl) ->order($this->_tbl_key); $this->_db->setQuery($query); $rows = $this->_db->loadRowList(); $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $this->_tbl_key, 'par', 'lft', 'rgt'); $buffer .= $sep; foreach ($rows as $row) { $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $row[0], $row[1], $row[2], $row[3]); } $buffer .= $sep; } echo $buffer; } /** * Runs a query and unlocks the database on an error. * * @param mixed $query A string or DatabaseQuery object. * @param string $errorMessage Unused. * * @return void * * @note Since 3.0.0 this method returns void and will rethrow the database exception. * @since 1.7.0 * @throws \Exception on database error. */ protected function _runQuery($query, $errorMessage) { // Prepare to catch an exception. try { $this->_db->setQuery($query)->execute(); if ($this->_debug) { $this->_logtable(); } } catch (\Exception $e) { // Unlock the tables and rethrow. $this->_unlock(); throw $e; } } } Ucm.php 0000644 00000001253 15172723307 0006011 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * UCM map table * * @since 3.1 */ class Ucm extends Table { /** * Constructor * * @param \Joomla\Database\DatabaseDriver $db A database connector object * * @since 3.1 */ public function __construct($db) { parent::__construct('#__ucm_base', 'ucm_id', $db); } } UpdateSite.php 0000644 00000002624 15172723307 0007337 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Update site table * Stores the update sites for extensions * * @since 3.4 */ class UpdateSite extends Table { /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 3.4 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__update_sites', 'update_site_id', $db); } /** * Overloaded check function * * @return boolean True if the object is ok * * @see Table::check() * @since 3.4 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for valid name if (trim($this->name) == '' || trim($this->location) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION')); return false; } return true; } } Category.php 0000644 00000020310 15172723307 0007035 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Access\Rules; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Tag\TaggableTableInterface; use Joomla\CMS\Tag\TaggableTableTrait; use Joomla\CMS\Versioning\VersionableTableInterface; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Category table * * @since 1.5 */ class Category extends Nested implements VersionableTableInterface, TaggableTableInterface { use TaggableTableTrait; /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.5 */ public function __construct(DatabaseDriver $db) { /** * @deprecated 4.0 will be removed in 6.0 * This format was used by tags and versioning before 4.0 before * the introduction of the getTypeAlias function. */ $this->typeAlias = '{extension}.category'; parent::__construct('#__categories', 'id', $db); $this->access = (int) Factory::getApplication()->get('access'); } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 1.6 */ protected function _getAssetName() { $k = $this->_tbl_key; return $this->extension . '.category.' . (int) $this->$k; } /** * Method to return the title to use for the asset table. * * @return string * * @since 1.6 */ protected function _getAssetTitle() { return $this->title; } /** * Get the parent asset id for the record * * @param Table $table A Table object for the asset parent. * @param integer $id The id for the asset * * @return integer The id of the asset's parent * * @since 1.6 */ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; // This is a category under a category. if ($this->parent_id > 1) { // Build the query to get the asset id for the parent category. $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('asset_id')) ->from($this->_db->quoteName('#__categories')) ->where($this->_db->quoteName('id') . ' = :parentId') ->bind(':parentId', $this->parent_id, ParameterType::INTEGER); // Get the asset id from the database. $this->_db->setQuery($query); if ($result = $this->_db->loadResult()) { $assetId = (int) $result; } } elseif ($assetId === null) { // This is a category that needs to parent with the extension. // Build the query to get the asset id for the parent category. $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__assets')) ->where($this->_db->quoteName('name') . ' = :extension') ->bind(':extension', $this->extension); // Get the asset id from the database. $this->_db->setQuery($query); if ($result = $this->_db->loadResult()) { $assetId = (int) $result; } } // Return the asset id. if ($assetId) { return $assetId; } else { return parent::_getAssetParentId($table, $id); } } /** * Override check function * * @return boolean * * @see Table::check() * @since 1.5 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for a title. if (trim($this->title) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_CATEGORY')); return false; } $this->alias = trim($this->alias); if (empty($this->alias)) { $this->alias = $this->title; } $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language); if (trim(str_replace('-', '', $this->alias)) == '') { $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); } return true; } /** * Overloaded bind function. * * @param array $array named array * @param string $ignore An optional array or space separated list of properties * to ignore while binding. * * @return mixed Null if operation was satisfactory, otherwise returns an error * * @see Table::bind() * @since 1.6 */ public function bind($array, $ignore = '') { if (isset($array['params']) && \is_array($array['params'])) { $registry = new Registry($array['params']); $array['params'] = (string) $registry; } if (isset($array['metadata']) && \is_array($array['metadata'])) { $registry = new Registry($array['metadata']); $array['metadata'] = (string) $registry; } // Bind the rules. if (isset($array['rules']) && \is_array($array['rules'])) { $rules = new Rules($array['rules']); $this->setRules($rules); } return parent::bind($array, $ignore); } /** * Overridden Table::store to set created/modified and user id. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 1.6 */ public function store($updateNulls = true) { $date = Factory::getDate()->toSql(); $user = Factory::getUser(); // Set created date if not set. if (!(int) $this->created_time) { $this->created_time = $date; } if ($this->id) { // Existing category $this->modified_user_id = $user->get('id'); $this->modified_time = $date; } else { if (!(int) ($this->modified_time)) { $this->modified_time = $this->created_time; } // Field created_user_id can be set by the user, so we don't touch it if it's set. if (empty($this->created_user_id)) { $this->created_user_id = $user->get('id'); } if (empty($this->modified_user_id)) { $this->modified_user_id = $this->created_user_id; } } // Verify that the alias is unique $table = Table::getInstance('Category', 'JTable', ['dbo' => $this->getDbo()]); if ( $table->load(['alias' => $this->alias, 'parent_id' => (int) $this->parent_id, 'extension' => $this->extension]) && ($table->id != $this->id || $this->id == 0) ) { // Is the existing category trashed? $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_UNIQUE_ALIAS')); if ($table->published === -2) { $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_UNIQUE_ALIAS_TRASHED')); } return false; } return parent::store($updateNulls); } /** * Get the type alias for the history table * * @return string The alias as described above * * @since 4.0.0 */ public function getTypeAlias() { return $this->extension . '.category'; } } Module.php 0000644 00000013141 15172723307 0006511 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Access\Rules; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Module table * * @since 1.5 */ class Module extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Constructor. * * @param DatabaseDriver $db Database driver object. * * @since 1.5 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__modules', 'id', $db); $this->access = (int) Factory::getApplication()->get('access'); } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 3.2 */ protected function _getAssetName() { $k = $this->_tbl_key; return 'com_modules.module.' . (int) $this->$k; } /** * Method to return the title to use for the asset table. * * @return string * * @since 3.2 */ protected function _getAssetTitle() { return $this->title; } /** * Method to get the parent asset id for the record * * @param Table $table A Table object (optional) for the asset parent * @param integer $id The id (optional) of the content. * * @return integer * * @since 3.2 */ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; // This is a module that needs to parent with the extension. if ($assetId === null) { // Build the query to get the asset id of the parent component. $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__assets')) ->where($this->_db->quoteName('name') . ' = ' . $this->_db->quote('com_modules')); // Get the asset id from the database. $this->_db->setQuery($query); if ($result = $this->_db->loadResult()) { $assetId = (int) $result; } } // Return the asset id. if ($assetId) { return $assetId; } else { return parent::_getAssetParentId($table, $id); } } /** * Overloaded check function. * * @return boolean True if the instance is sane and able to be stored in the database. * * @see Table::check() * @since 1.5 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for valid name if (trim($this->title) === '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MODULE')); return false; } // Set publish_up, publish_down to null if not set if (!$this->publish_up) { $this->publish_up = null; } if (!$this->publish_down) { $this->publish_down = null; } // Prevent to save too large content > 65535 if ((\strlen($this->content) > 65535) || (\strlen($this->params) > 65535)) { $this->setError(Text::_('COM_MODULES_FIELD_CONTENT_TOO_LARGE')); return false; } // Check the publish down date is not earlier than publish up. if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up) { // Swap the dates. $temp = $this->publish_up; $this->publish_up = $this->publish_down; $this->publish_down = $temp; } return true; } /** * Overloaded bind function. * * @param array $array Named array. * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. * * @return mixed Null if operation was satisfactory, otherwise returns an error * * @see Table::bind() * @since 1.5 */ public function bind($array, $ignore = '') { if (isset($array['params']) && \is_array($array['params'])) { $registry = new Registry($array['params']); $array['params'] = (string) $registry; } // Bind the rules. if (isset($array['rules']) && \is_array($array['rules'])) { $rules = new Rules($array['rules']); $this->setRules($rules); } return parent::bind($array, $ignore); } /** * Stores a module. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success, false on failure. * * @since 3.7.0 */ public function store($updateNulls = true) { if (!$this->ordering) { $this->ordering = $this->getNextOrder($this->_db->quoteName('position') . ' = ' . $this->_db->quote($this->position)); } return parent::store($updateNulls); } } Menu.php 0000644 00000025404 15172723307 0006175 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu table * * @since 1.5 */ class Menu extends Nested { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.5 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__menu', 'id', $db); // Set the default access level. $this->access = (int) Factory::getApplication()->get('access'); } /** * Overloaded bind function * * @param array $array Named array * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. * * @return mixed Null if operation was satisfactory, otherwise returns an error * * @see Table::bind() * @since 1.5 */ public function bind($array, $ignore = '') { // Verify that the default home menu is not unset if ($this->home == '1' && $this->language === '*' && $array['home'] == '0') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT_DEFAULT')); return false; } // Verify that the default home menu set to "all" languages" is not unset if ($this->home == '1' && $this->language === '*' && $array['language'] !== '*') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT')); return false; } // Verify that the default home menu is not unpublished if ($this->home == '1' && $this->language === '*' && $array['published'] != '1') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME')); return false; } if (isset($array['params']) && \is_array($array['params'])) { $registry = new Registry($array['params']); $array['params'] = (string) $registry; } return parent::bind($array, $ignore); } /** * Overloaded check function * * @return boolean True on success * * @see Table::check() * @since 1.5 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for a title. if ($this->title === null || trim($this->title) === '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MENUITEM')); return false; } // Check for a path. if ($this->path === null || trim($this->path) === '') { $this->path = $this->alias; } // Check for params. if ($this->params === null || trim($this->params) === '') { $this->params = '{}'; } // Check for img. if ($this->img === null || trim($this->img) === '') { $this->img = ' '; } // Cast the home property to an int for checking. $this->home = (int) $this->home; // Verify that the home item is a component. if ($this->home && $this->type !== 'component') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_COMPONENT')); return false; } // Set publish_up, publish_down to null if not set if (!$this->publish_up) { $this->publish_up = null; } if (!$this->publish_down) { $this->publish_down = null; } return true; } /** * Overloaded store function * * @param boolean $updateNulls True to update fields even if they are null. * * @return mixed False on failure, positive integer on success. * * @see Table::store() * @since 1.6 */ public function store($updateNulls = true) { $db = $this->getDbo(); // Verify that the alias is unique $table = Table::getInstance('Menu', 'JTable', ['dbo' => $db]); $originalAlias = trim($this->alias); $this->alias = !$originalAlias ? $this->title : $originalAlias; $this->alias = ApplicationHelper::stringURLSafe(trim($this->alias), $this->language); if ($this->parent_id == 1 && $this->client_id == 0) { // Verify that a first level menu item alias is not 'component'. if ($this->alias === 'component') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_COMPONENT')); return false; } // Verify that a first level menu item alias is not the name of a folder. if (\in_array($this->alias, Folder::folders(JPATH_ROOT))) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_FOLDER', $this->alias, $this->alias)); return false; } } // If alias still empty (for instance, new menu item with chinese characters with no unicode alias setting). if (empty($this->alias)) { $this->alias = Factory::getDate()->format('Y-m-d-H-i-s'); } else { $itemSearch = ['alias' => $this->alias, 'parent_id' => $this->parent_id, 'client_id' => (int) $this->client_id]; $error = false; // Check if the alias already exists. For multilingual site. if (Multilanguage::isEnabled() && (int) $this->client_id == 0) { // If there is a menu item at the same level with the same alias (in the All or the same language). if ( ($table->load(array_replace($itemSearch, ['language' => '*'])) && ($table->id != $this->id || $this->id == 0)) || ($table->load(array_replace($itemSearch, ['language' => $this->language])) && ($table->id != $this->id || $this->id == 0)) || ($this->language === '*' && $this->id == 0 && $table->load($itemSearch)) ) { $error = true; } elseif ($this->language === '*' && $this->id != 0) { // When editing an item with All language check if there are more menu items with the same alias in any language. $id = (int) $this->id; $query = $db->getQuery(true) ->select('id') ->from($db->quoteName('#__menu')) ->where($db->quoteName('parent_id') . ' = 1') ->where($db->quoteName('client_id') . ' = 0') ->where($db->quoteName('id') . ' != :id') ->where($db->quoteName('alias') . ' = :alias') ->bind(':id', $id, ParameterType::INTEGER) ->bind(':alias', $this->alias); $otherMenuItemId = (int) $db->setQuery($query)->loadResult(); if ($otherMenuItemId) { $table->load(['id' => $otherMenuItemId]); $error = true; } } } else { // Check if the alias already exists. For monolingual site. // If there is a menu item at the same level with the same alias (in any language). if ($table->load($itemSearch) && ($table->id != $this->id || $this->id == 0)) { $error = true; } } // The alias already exists. Enqueue an error message. if ($error) { $menuTypeTable = Table::getInstance('MenuType', 'JTable', ['dbo' => $db]); $menuTypeTable->load(['menutype' => $table->menutype]); $url = Route::_('index.php?option=com_menus&task=item.edit&id=' . (int) $table->id); // Is the existing menu item trashed? $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_MENU_UNIQUE_ALIAS', $this->alias, $table->title, $menuTypeTable->title, $url)); if ($table->published === -2) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_MENU_UNIQUE_ALIAS_TRASHED', $this->alias, $table->title, $menuTypeTable->title, $url)); } return false; } } if ($this->home == '1') { // Verify that the home page for this menu is unique. if ( $table->load( [ 'menutype' => $this->menutype, 'client_id' => (int) $this->client_id, 'home' => '1', ] ) && ($table->language != $this->language) ) { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_UNIQUE_IN_MENU')); return false; } // Verify that the home page for this language is unique per client id if ($table->load(['home' => '1', 'language' => $this->language, 'client_id' => (int) $this->client_id])) { if ($table->checked_out && $table->checked_out != $this->checked_out) { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENU_DEFAULT_CHECKIN_USER_MISMATCH')); return false; } $table->home = 0; $table->checked_out = null; $table->checked_out_time = null; $table->store(); } } if (!parent::store($updateNulls)) { return false; } // Get the new path in case the node was moved $pathNodes = $this->getPath(); $segments = []; foreach ($pathNodes as $node) { // Don't include root in path if ($node->alias !== 'root') { $segments[] = $node->alias; } } $newPath = trim(implode('/', $segments), ' /\\'); // Use new path for partial rebuild of table // Rebuild will return positive integer on success, false on failure return $this->rebuild($this->{$this->_tbl_key}, $this->lft, $this->level, $newPath) > 0; } } Table.php 0000644 00000164524 15172723307 0006327 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Access\Rules; use Joomla\CMS\Event\AbstractEvent; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Language\Text; use Joomla\CMS\Object\CMSObject; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseQuery; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Event\DispatcherInterface; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Abstract Table class * * Parent class to all tables. * * @since 1.7.0 */ #[\AllowDynamicProperties] abstract class Table extends CMSObject implements TableInterface, DispatcherAwareInterface { use DispatcherAwareTrait; /** * Include paths for searching for Table classes. * * @var array * @since 3.0.0 */ private static $_includePaths = []; /** * Table fields cache * * @var array * @since 3.10.4 */ private static $tableFields; /** * Name of the database table to model. * * @var string * @since 1.7.0 */ protected $_tbl = ''; /** * Name of the primary key field in the table. * * @var string * @since 1.7.0 */ protected $_tbl_key = ''; /** * Name of the primary key fields in the table. * * @var array * @since 3.0.1 */ protected $_tbl_keys = []; /** * DatabaseDriver object. * * @var DatabaseDriver * @since 1.7.0 */ protected $_db; /** * Should rows be tracked as ACL assets? * * @var boolean * @since 1.7.0 */ protected $_trackAssets = false; /** * The rules associated with this record. * * @var Rules A Rules object. * @since 1.7.0 */ protected $_rules; /** * Indicator that the tables have been locked. * * @var boolean * @since 1.7.0 */ protected $_locked = false; /** * Indicates that the primary keys autoincrement. * * @var boolean * @since 3.1.4 */ protected $_autoincrement = true; /** * Array with alias for "special" columns such as ordering, hits etc etc * * @var array * @since 3.4.0 */ protected $_columnAlias = []; /** * An array of key names to be json encoded in the bind function * * @var array * @since 3.3 */ protected $_jsonEncode = []; /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 3.10.0 */ protected $_supportNullValue = false; /** * The UCM type alias. Used for tags, content versioning etc. Leave blank to effectively disable these features. * * @var string * @since 4.0.0 */ public $typeAlias = null; /** * Object constructor to set table and key fields. In most cases this will * be overridden by child classes to explicitly set the table and key fields * for a particular database table. * * @param string $table Name of the table to model. * @param mixed $key Name of the primary key field in the table or array of field names that compose the primary key. * @param DatabaseDriver $db DatabaseDriver object. * @param DispatcherInterface $dispatcher Event dispatcher for this table * * @since 1.7.0 */ public function __construct($table, $key, DatabaseDriver $db, DispatcherInterface $dispatcher = null) { parent::__construct(); // Set internal variables. $this->_tbl = $table; // Set the key to be an array. if (\is_string($key)) { $key = [$key]; } elseif (\is_object($key)) { $key = (array) $key; } $this->_tbl_keys = $key; if (\count($key) == 1) { $this->_autoincrement = true; } else { $this->_autoincrement = false; } // Set the singular table key for backwards compatibility. $this->_tbl_key = $this->getKeyName(); $this->_db = $db; // Initialise the table properties. $fields = $this->getFields(); if ($fields) { foreach ($fields as $name => $v) { // Add the field if it is not already present. if (!$this->hasField($name)) { $this->$name = null; } } } // If we are tracking assets, make sure an access field exists and initially set the default. if ($this->hasField('asset_id')) { $this->_trackAssets = true; } // If the access property exists, set the default. if ($this->hasField('access')) { $this->access = (int) Factory::getApplication()->get('access'); } // Create or set a Dispatcher if (!\is_object($dispatcher) || !($dispatcher instanceof DispatcherInterface)) { // @todo Maybe we should use a dedicated "behaviour" dispatcher for performance reasons and to prevent system plugins from butting in? $dispatcher = Factory::getApplication()->getDispatcher(); } $this->setDispatcher($dispatcher); $event = AbstractEvent::create( 'onTableObjectCreate', [ 'subject' => $this, ] ); $this->getDispatcher()->dispatch('onTableObjectCreate', $event); } /** * Get the columns from database table. * * @param bool $reload flag to reload cache * * @return mixed An array of the field names, or false if an error occurs. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function getFields($reload = false) { $key = $this->_db->getServerType() . ':' . $this->_db->getName() . ':' . $this->_tbl; if (!isset(self::$tableFields[$key]) || $reload) { // Lookup the fields for this table only once. $name = $this->_tbl; $fields = $this->_db->getTableColumns($name, false); if (empty($fields)) { throw new \UnexpectedValueException(sprintf('No columns found for %s table', $name)); } self::$tableFields[$key] = $fields; } return self::$tableFields[$key]; } /** * Static method to get an instance of a Table class if it can be found in the table include paths. * * To add include paths for searching for Table classes see Table::addIncludePath(). * * @param string $type The type (name) of the Table class to get an instance of. * @param string $prefix An optional prefix for the table class name. * @param array $config An optional array of configuration values for the Table object. * * @return Table|boolean A Table object if found or boolean false on failure. * * @since 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Use the MvcFactory instead * Example: Factory::getApplication()->bootComponent('...')->getMVCFactory()->createTable($name, $prefix, $config); */ public static function getInstance($type, $prefix = 'JTable', $config = []) { // Sanitize and prepare the table class name. $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type); $tableClass = $prefix . ucfirst($type); // Only try to load the class if it doesn't already exist. if (!class_exists($tableClass)) { // Search for the class file in the JTable include paths. $paths = self::addIncludePath(); $pathIndex = 0; while (!class_exists($tableClass) && $pathIndex < \count($paths)) { if ($tryThis = Path::find($paths[$pathIndex++], strtolower($type) . '.php')) { // Import the class file. include_once $tryThis; } } if (!class_exists($tableClass)) { /* * If unable to find the class file in the Table include paths. Return false. * The warning JLIB_DATABASE_ERROR_NOT_SUPPORTED_FILE_NOT_FOUND has been removed in 3.6.3. * In 4.0 an Exception (type to be determined) will be thrown. * For more info see https://github.com/joomla/joomla-cms/issues/11570 */ return false; } } // If a database object was passed in the configuration array use it, otherwise get the global one from Factory. $db = $config['dbo'] ?? Factory::getDbo(); // Check for a possible service from the container otherwise manually instantiate the class if (Factory::getContainer()->has($tableClass)) { return Factory::getContainer()->get($tableClass); } // Instantiate a new table class and return it. return new $tableClass($db); } /** * Add a filesystem path where Table should search for table class files. * * @param array|string $path A filesystem path or array of filesystem paths to add. * * @return array An array of filesystem paths to find Table classes in. * * @since 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Should not be used anymore as tables are loaded through the MvcFactory */ public static function addIncludePath($path = null) { // If the internal paths have not been initialised, do so with the base table path. if (empty(self::$_includePaths)) { self::$_includePaths = [__DIR__]; } // Convert the passed path(s) to add to an array. settype($path, 'array'); // If we have new paths to add, do so. if (!empty($path)) { // Check and add each individual new path. foreach ($path as $dir) { // Sanitize path. $dir = trim($dir); // Add to the front of the list so that custom paths are searched first. if (!\in_array($dir, self::$_includePaths)) { array_unshift(self::$_includePaths, $dir); } } } return self::$_includePaths; } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 1.7.0 */ protected function _getAssetName() { $keys = []; foreach ($this->_tbl_keys as $k) { $keys[] = (int) $this->$k; } return $this->_tbl . '.' . implode('.', $keys); } /** * Method to return the title to use for the asset table. * * In tracking the assets a title is kept for each asset so that there is some context available in a unified access manager. * Usually this would just return $this->title or $this->name or whatever is being used for the primary name of the row. * If this method is not overridden, the asset name is used. * * @return string The string to use as the title in the asset table. * * @since 1.7.0 */ protected function _getAssetTitle() { return $this->_getAssetName(); } /** * Method to get the parent asset under which to register this one. * * By default, all assets are registered to the ROOT node with ID, which will default to 1 if none exists. * An extended class can define a table and ID to lookup. If the asset does not exist it will be created. * * @param Table $table A Table object for the asset parent. * @param integer $id Id to look up * * @return integer * * @since 1.7.0 */ protected function _getAssetParentId(Table $table = null, $id = null) { // For simple cases, parent to the asset root. /** @var Asset $assets */ $assets = self::getInstance('Asset', 'JTable', ['dbo' => $this->getDbo()]); $rootId = $assets->getRootId(); if (!empty($rootId)) { return $rootId; } return 1; } /** * Method to append the primary keys for this table to a query. * * @param DatabaseQuery $query A query object to append. * @param mixed $pk Optional primary key parameter. * * @return void * * @since 3.1.4 */ public function appendPrimaryKeys($query, $pk = null) { if (\is_null($pk)) { foreach ($this->_tbl_keys as $k) { $query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($this->$k)); } } else { if (\is_string($pk)) { $pk = [$this->_tbl_key => $pk]; } $pk = (object) $pk; foreach ($this->_tbl_keys as $k) { $query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($pk->$k)); } } } /** * Method to get the database table name for the class. * * @return string The name of the database table being modeled. * * @since 1.7.0 */ public function getTableName() { return $this->_tbl; } /** * Method to get the primary key field name for the table. * * @param boolean $multiple True to return all primary keys (as an array) or false to return just the first one (as a string). * * @return mixed Array of primary key field names or string containing the first primary key field. * * @since 1.7.0 */ public function getKeyName($multiple = false) { // Count the number of keys if (\count($this->_tbl_keys)) { if ($multiple) { // If we want multiple keys, return the raw array. return $this->_tbl_keys; } else { // If we want the standard method, just return the first key. return $this->_tbl_keys[0]; } } return ''; } /** * Returns the identity (primary key) value of this record * * @return mixed * * @since 4.0.0 */ public function getId() { $key = $this->getKeyName(); return $this->$key; } /** * Method to get the DatabaseDriver object. * * @return DatabaseDriver The internal database driver object. * * @since 1.7.0 */ public function getDbo() { return $this->_db; } /** * Method to set the DatabaseDriver object. * * @param DatabaseDriver $db A DatabaseDriver object to be used by the table object. * * @return boolean True on success. * * @since 1.7.0 */ public function setDbo(DatabaseDriver $db) { $this->_db = $db; return true; } /** * Method to set rules for the record. * * @param mixed $input A Rules object, JSON string, or array. * * @return void * * @since 1.7.0 */ public function setRules($input) { if ($input instanceof Rules) { $this->_rules = $input; } else { $this->_rules = new Rules($input); } } /** * Method to get the rules for the record. * * @return Rules object * * @since 1.7.0 */ public function getRules() { return $this->_rules; } /** * Method to reset class properties to the defaults set in the class * definition. It will ignore the primary key as well as any private class * properties (except $_errors). * * @return void * * @since 1.7.0 */ public function reset() { $event = AbstractEvent::create( 'onTableBeforeReset', [ 'subject' => $this, ] ); $this->getDispatcher()->dispatch('onTableBeforeReset', $event); // Get the default values for the class from the table. foreach ($this->getFields() as $k => $v) { // If the property is not the primary key or private, reset it. if (!\in_array($k, $this->_tbl_keys) && (strpos($k, '_') !== 0)) { $this->$k = $v->Default; } } // Reset table errors $this->_errors = []; $event = AbstractEvent::create( 'onTableAfterReset', [ 'subject' => $this, ] ); $this->getDispatcher()->dispatch('onTableAfterReset', $event); } /** * Method to bind an associative array or object to the Table instance.This * method only binds properties that are publicly accessible and optionally * takes an array of properties to ignore when binding. * * @param array|object $src An associative array or object to bind to the Table instance. * @param array|string $ignore An optional array or space separated list of properties to ignore while binding. * * @return boolean True on success. * * @since 1.7.0 * @throws \InvalidArgumentException */ public function bind($src, $ignore = []) { // Check if the source value is an array or object if (!\is_object($src) && !\is_array($src)) { throw new \InvalidArgumentException( sprintf( 'Could not bind the data source in %1$s::bind(), the source must be an array or object but a "%2$s" was given.', \get_class($this), \gettype($src) ) ); } // If the ignore value is a string, explode it over spaces. if (!\is_array($ignore)) { $ignore = explode(' ', $ignore); } $event = AbstractEvent::create( 'onTableBeforeBind', [ 'subject' => $this, 'src' => $src, 'ignore' => $ignore, ] ); $this->getDispatcher()->dispatch('onTableBeforeBind', $event); // If the source value is an object, get its accessible properties. if (\is_object($src)) { $src = get_object_vars($src); } // JSON encode any fields required if (!empty($this->_jsonEncode)) { foreach ($this->_jsonEncode as $field) { if (isset($src[$field]) && \is_array($src[$field])) { $src[$field] = json_encode($src[$field]); } } } // Bind the source value, excluding the ignored fields. foreach ($this->getProperties() as $k => $v) { // Only process fields not in the ignore array. if (!\in_array($k, $ignore)) { if (isset($src[$k])) { $this->$k = $src[$k]; } } } $event = AbstractEvent::create( 'onTableAfterBind', [ 'subject' => $this, 'src' => $src, 'ignore' => $ignore, ] ); $this->getDispatcher()->dispatch('onTableAfterBind', $event); return true; } /** * Method to load a row from the database by primary key and bind the fields to the Table instance properties. * * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. * If not set the instance property value is used. * @param boolean $reset True to reset the default values before loading the new row. * * @return boolean True if successful. False if row not found. * * @since 1.7.0 * @throws \InvalidArgumentException * @throws \RuntimeException * @throws \UnexpectedValueException */ public function load($keys = null, $reset = true) { // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeLoad', [ 'subject' => $this, 'keys' => $keys, 'reset' => $reset, ] ); $this->getDispatcher()->dispatch('onTableBeforeLoad', $event); if (empty($keys)) { $empty = true; $keys = []; // If empty, use the value of the current key foreach ($this->_tbl_keys as $key) { $empty = $empty && empty($this->$key); $keys[$key] = $this->$key; } // If empty primary key there's is no need to load anything if ($empty) { return true; } } elseif (!\is_array($keys)) { // Load by primary key. $keyCount = \count($this->_tbl_keys); if ($keyCount) { if ($keyCount > 1) { throw new \InvalidArgumentException('Table has multiple primary keys specified, only one primary key value provided.'); } $keys = [$this->getKeyName() => $keys]; } else { throw new \RuntimeException('No table keys defined.'); } } if ($reset) { $this->reset(); } // Initialise the query. $query = $this->_db->getQuery(true) ->select('*') ->from($this->_db->quoteName($this->_tbl)); $fields = array_keys($this->getProperties()); foreach ($keys as $field => $value) { // Check that $field is in the table. if (!\in_array($field, $fields)) { throw new \UnexpectedValueException(sprintf('Missing field in database: %s   %s.', \get_class($this), $field)); } // Add the search tuple to the query. $query->where($this->_db->quoteName($field) . ' = ' . $this->_db->quote($value)); } $this->_db->setQuery($query); $row = $this->_db->loadAssoc(); // Check that we have a result. if (empty($row)) { $result = false; } else { // Bind the object with the row and return. $result = $this->bind($row); } // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterLoad', [ 'subject' => $this, 'result' => &$result, 'row' => $row, ] ); $this->getDispatcher()->dispatch('onTableAfterLoad', $event); return $result; } /** * Method to perform sanity checks on the Table instance properties to ensure they are safe to store in the database. * * Child classes should override this method to make sure the data they are storing in the database is safe and as expected before storage. * * @return boolean True if the instance is sane and able to be stored in the database. * * @since 1.7.0 */ public function check() { // Post-processing by observers $event = AbstractEvent::create( 'onTableCheck', [ 'subject' => $this, ] ); $this->getDispatcher()->dispatch('onTableCheck', $event); return true; } /** * Method to store a row in the database from the Table instance properties. * * If a primary key value is set the row with that primary key value will be updated with the instance property values. * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 1.7.0 */ public function store($updateNulls = false) { $result = true; $k = $this->_tbl_keys; // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeStore', [ 'subject' => $this, 'updateNulls' => $updateNulls, 'k' => $k, ] ); $this->getDispatcher()->dispatch('onTableBeforeStore', $event); $currentAssetId = 0; if (!empty($this->asset_id)) { $currentAssetId = $this->asset_id; } // The asset id field is managed privately by this class. if ($this->_trackAssets) { unset($this->asset_id); } // We have to unset typeAlias since updateObject / insertObject will try to insert / update all public variables... $typeAlias = $this->typeAlias; unset($this->typeAlias); try { // If a primary key exists update the object, otherwise insert it. if ($this->hasPrimaryKey()) { $this->_db->updateObject($this->_tbl, $this, $this->_tbl_keys, $updateNulls); } else { $this->_db->insertObject($this->_tbl, $this, $this->_tbl_keys[0]); } } catch (\Exception $e) { $this->setError($e->getMessage()); $result = false; } $this->typeAlias = $typeAlias; // If the table is not set to track assets return true. if ($this->_trackAssets) { if ($this->_locked) { $this->_unlock(); } /* * Asset Tracking */ $parentId = $this->_getAssetParentId(); $name = $this->_getAssetName(); $title = $this->_getAssetTitle(); /** @var Asset $asset */ $asset = self::getInstance('Asset', 'JTable', ['dbo' => $this->getDbo()]); $asset->loadByName($name); // Re-inject the asset id. $this->asset_id = $asset->id; // Check for an error. $error = $asset->getError(); if ($error) { $this->setError($error); return false; } else { // Specify how a new or moved node asset is inserted into the tree. if (empty($this->asset_id) || $asset->parent_id != $parentId) { $asset->setLocation($parentId, 'last-child'); } // Prepare the asset to be stored. $asset->parent_id = $parentId; $asset->name = $name; // Respect the table field limits $asset->title = StringHelper::substr($title, 0, 100); if ($this->_rules instanceof Rules) { $asset->rules = (string) $this->_rules; } if (!$asset->check() || !$asset->store()) { $this->setError($asset->getError()); return false; } else { // Create an asset_id or heal one that is corrupted. if (empty($this->asset_id) || ($currentAssetId != $this->asset_id && !empty($this->asset_id))) { // Update the asset_id field in this table. $this->asset_id = (int) $asset->id; $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set('asset_id = ' . (int) $this->asset_id); $this->appendPrimaryKeys($query); $this->_db->setQuery($query)->execute(); } } } } // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterStore', [ 'subject' => $this, 'result' => &$result, ] ); $this->getDispatcher()->dispatch('onTableAfterStore', $event); return $result; } /** * Method to provide a shortcut to binding, checking and storing a Table instance to the database table. * * The method will check a row in once the data has been stored and if an ordering filter is present will attempt to reorder * the table rows based on the filter. The ordering filter is an instance property name. The rows that will be reordered * are those whose value matches the Table instance for the property specified. * * @param array|object $src An associative array or object to bind to the Table instance. * @param string $orderingFilter Filter for the order updating * @param array|string $ignore An optional array or space separated list of properties to ignore while binding. * * @return boolean True on success. * * @since 1.7.0 */ public function save($src, $orderingFilter = '', $ignore = '') { // Attempt to bind the source to the instance. if (!$this->bind($src, $ignore)) { return false; } // Run any sanity checks on the instance and verify that it is ready for storage. if (!$this->check()) { return false; } // Attempt to store the properties to the database table. if (!$this->store()) { return false; } // Attempt to check the row in, just in case it was checked out. if (!$this->checkIn()) { return false; } // If an ordering filter is set, attempt reorder the rows in the table based on the filter and value. if ($orderingFilter) { $filterValue = $this->$orderingFilter; $this->reorder($orderingFilter ? $this->_db->quoteName($orderingFilter) . ' = ' . $this->_db->quote($filterValue) : ''); } // Set the error to empty and return true. $this->setError(''); return true; } /** * Method to delete a row from the database table by primary key value. * * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function delete($pk = null) { if (\is_null($pk)) { $pk = []; foreach ($this->_tbl_keys as $key) { $pk[$key] = $this->$key; } } elseif (!\is_array($pk)) { $pk = [$this->_tbl_key => $pk]; } foreach ($this->_tbl_keys as $key) { $pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key]; if ($pk[$key] === null) { throw new \UnexpectedValueException('Null primary key not allowed.'); } $this->$key = $pk[$key]; } // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeDelete', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableBeforeDelete', $event); // If tracking assets, remove the asset first. if ($this->_trackAssets) { // Get the asset name $name = $this->_getAssetName(); /** @var Asset $asset */ $asset = self::getInstance('Asset'); if ($asset->loadByName($name)) { if (!$asset->delete()) { $this->setError($asset->getError()); return false; } } } // Delete the row by primary key. $query = $this->_db->getQuery(true) ->delete($this->_db->quoteName($this->_tbl)); $this->appendPrimaryKeys($query, $pk); $this->_db->setQuery($query); // Check for a database error. $this->_db->execute(); // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterDelete', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableAfterDelete', $event); return true; } /** * Method to check a row out if the necessary properties/fields exist. * * To prevent race conditions while editing rows in a database, a row can be checked out if the fields 'checked_out' and 'checked_out_time' * are available. While a row is checked out, any attempt to store the row by a user other than the one who checked the row out should be * held until the row is checked in again. * * @param integer $userId The Id of the user checking out the row. * @param mixed $pk An optional primary key value to check out. If not set the instance property value is used. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function checkOut($userId, $pk = null) { // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeCheckout', [ 'subject' => $this, 'userId' => $userId, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableBeforeCheckout', $event); // If there is no checked_out or checked_out_time field, just return true. if (!$this->hasField('checked_out') || !$this->hasField('checked_out_time')) { return true; } if (\is_null($pk)) { $pk = []; foreach ($this->_tbl_keys as $key) { $pk[$key] = $this->$key; } } elseif (!\is_array($pk)) { $pk = [$this->_tbl_key => $pk]; } foreach ($this->_tbl_keys as $key) { $pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key]; if ($pk[$key] === null) { throw new \UnexpectedValueException('Null primary key not allowed.'); } } // Get column names. $checkedOutField = $this->getColumnAlias('checked_out'); $checkedOutTimeField = $this->getColumnAlias('checked_out_time'); // Get the current time in the database format. $time = Factory::getDate()->toSql(); // Check the row out by primary key. $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set($this->_db->quoteName($checkedOutField) . ' = ' . (int) $userId) ->set($this->_db->quoteName($checkedOutTimeField) . ' = ' . $this->_db->quote($time)); $this->appendPrimaryKeys($query, $pk); $this->_db->setQuery($query); $this->_db->execute(); // Set table values in the object. $this->$checkedOutField = (int) $userId; $this->$checkedOutTimeField = $time; // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterCheckout', [ 'subject' => $this, 'userId' => $userId, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableAfterCheckout', $event); return true; } /** * Method to check a row in if the necessary properties/fields exist. * * Checking a row in will allow other users the ability to edit the row. * * @param mixed $pk An optional primary key value to check out. If not set the instance property value is used. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function checkIn($pk = null) { // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeCheckin', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableBeforeCheckin', $event); // If there is no checked_out or checked_out_time field, just return true. if (!$this->hasField('checked_out') || !$this->hasField('checked_out_time')) { return true; } if (\is_null($pk)) { $pk = []; foreach ($this->_tbl_keys as $key) { $pk[$this->$key] = $this->$key; } } elseif (!\is_array($pk)) { $pk = [$this->_tbl_key => $pk]; } foreach ($this->_tbl_keys as $key) { $pk[$key] = empty($pk[$key]) ? $this->$key : $pk[$key]; if ($pk[$key] === null) { throw new \UnexpectedValueException('Null primary key not allowed.'); } } // Get column names. $checkedOutField = $this->getColumnAlias('checked_out'); $checkedOutTimeField = $this->getColumnAlias('checked_out_time'); $nullDate = $this->_supportNullValue ? 'NULL' : $this->_db->quote($this->_db->getNullDate()); $nullID = $this->_supportNullValue ? 'NULL' : '0'; // Check the row in by primary key. $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set($this->_db->quoteName($checkedOutField) . ' = ' . $nullID) ->set($this->_db->quoteName($checkedOutTimeField) . ' = ' . $nullDate); $this->appendPrimaryKeys($query, $pk); $this->_db->setQuery($query); // Check for a database error. $this->_db->execute(); // Set table values in the object. $this->$checkedOutField = $this->_supportNullValue ? null : 0; $this->$checkedOutTimeField = $this->_supportNullValue ? null : ''; // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterCheckin', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableAfterCheckin', $event); Factory::getApplication()->triggerEvent('onAfterCheckin', [$this->_tbl]); return true; } /** * Validate that the primary key has been set. * * @return boolean True if the primary key(s) have been set. * * @since 3.1.4 */ public function hasPrimaryKey() { if ($this->_autoincrement) { $empty = true; foreach ($this->_tbl_keys as $key) { $empty = $empty && empty($this->$key); } } else { $query = $this->_db->getQuery(true) ->select('COUNT(*)') ->from($this->_db->quoteName($this->_tbl)); $this->appendPrimaryKeys($query); $this->_db->setQuery($query); $count = $this->_db->loadResult(); if ($count == 1) { $empty = false; } else { $empty = true; } } return !$empty; } /** * Method to increment the hits for a row if the necessary property/field exists. * * @param mixed $pk An optional primary key value to increment. If not set the instance property value is used. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function hit($pk = null) { // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeHit', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableBeforeHit', $event); // If there is no hits field, just return true. if (!$this->hasField('hits')) { return true; } if (\is_null($pk)) { $pk = []; foreach ($this->_tbl_keys as $key) { $pk[$key] = $this->$key; } } elseif (!\is_array($pk)) { $pk = [$this->_tbl_key => $pk]; } foreach ($this->_tbl_keys as $key) { $pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key]; if ($pk[$key] === null) { throw new \UnexpectedValueException('Null primary key not allowed.'); } } // Get column name. $hitsField = $this->getColumnAlias('hits'); // Check the row in by primary key. $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set($this->_db->quoteName($hitsField) . ' = (' . $this->_db->quoteName($hitsField) . ' + 1)'); $this->appendPrimaryKeys($query, $pk); $this->_db->setQuery($query); $this->_db->execute(); // Set table values in the object. $this->hits++; // Pre-processing by observers $event = AbstractEvent::create( 'onTableAfterHit', [ 'subject' => $this, 'pk' => $pk, ] ); $this->getDispatcher()->dispatch('onTableAfterHit', $event); return true; } /** * Method to determine if a row is checked out and therefore uneditable by a user. * * If the row is checked out by the same user, then it is considered not checked out -- as the user can still edit it. * * @param integer $with The user ID to perform the match with, if an item is checked out by this user the function will return false. * @param integer $against The user ID to perform the match against when the function is used as a static function. * * @return boolean True if checked out. * * @since 1.7.0 */ public function isCheckedOut($with = 0, $against = null) { // Handle the non-static case. if (isset($this) && ($this instanceof Table) && \is_null($against)) { $checkedOutField = $this->getColumnAlias('checked_out'); $against = $this->get($checkedOutField); } // The item is not checked out or is checked out by the same user. if (!$against || ($against == $with)) { return false; } // This last check can only be relied on if tracking session metadata if (Factory::getApplication()->get('session_metadata', true)) { $db = Factory::getDbo(); $query = $db->getQuery(true) ->select('COUNT(userid)') ->from($db->quoteName('#__session')) ->where($db->quoteName('userid') . ' = ' . (int) $against); $db->setQuery($query); $checkedOut = (bool) $db->loadResult(); // If a session exists for the user then it is checked out. return $checkedOut; } // Assume if we got here that there is a value in the checked out column but it doesn't match the given user return true; } /** * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause. * * This is useful for placing a new item last in a group of items in the table. * * @param string $where WHERE clause to use for selecting the MAX(ordering) for the table. * * @return integer The next ordering value. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function getNextOrder($where = '') { // Check if there is an ordering field set if (!$this->hasField('ordering')) { throw new \UnexpectedValueException(sprintf('%s does not support ordering.', \get_class($this))); } // Get the largest ordering value for a given where clause. $query = $this->_db->getQuery(true) ->select('MAX(' . $this->_db->quoteName($this->getColumnAlias('ordering')) . ')') ->from($this->_db->quoteName($this->_tbl)); if ($where) { $query->where($where); } $this->_db->setQuery($query); $max = (int) $this->_db->loadResult(); // Return the largest ordering value + 1. return $max + 1; } /** * Get the primary key values for this table using passed in values as a default. * * @param array $keys Optional primary key values to use. * * @return array An array of primary key names and values. * * @since 3.1.4 */ public function getPrimaryKey(array $keys = []) { foreach ($this->_tbl_keys as $key) { if (!isset($keys[$key])) { if (!empty($this->$key)) { $keys[$key] = $this->$key; } } } return $keys; } /** * Method to compact the ordering values of rows in a group of rows defined by an SQL WHERE clause. * * @param string|string[] $where WHERE clause to use for limiting the selection of rows to compact the ordering values. * * @return mixed Boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function reorder($where = '') { // Check if there is an ordering field set if (!$this->hasField('ordering')) { throw new \UnexpectedValueException(sprintf('%s does not support ordering.', \get_class($this))); } $quotedOrderingField = $this->_db->quoteName($this->getColumnAlias('ordering')); $subquery = $this->_db->getQuery(true) ->from($this->_db->quoteName($this->_tbl)) ->selectRowNumber($quotedOrderingField, 'new_ordering'); $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set($quotedOrderingField . ' = sq.new_ordering'); $innerOn = []; // Get the primary keys for the selection. foreach ($this->_tbl_keys as $i => $k) { $subquery->select($this->_db->quoteName($k, 'pk__' . $i)); $innerOn[] = $this->_db->quoteName($k) . ' = sq.' . $this->_db->quoteName('pk__' . $i); } // Setup the extra where and ordering clause data. if ($where) { $subquery->where($where); $query->where($where); } $subquery->where($quotedOrderingField . ' >= 0'); $query->where($quotedOrderingField . ' >= 0'); $query->innerJoin('(' . (string) $subquery . ') AS sq '); foreach ($innerOn as $key) { $query->where($key); } // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeReorder', [ 'subject' => $this, 'query' => $query, 'where' => $where, ] ); $this->getDispatcher()->dispatch('onTableBeforeReorder', $event); $this->_db->setQuery($query); $this->_db->execute(); // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterReorder', [ 'subject' => $this, 'where' => $where, ] ); $this->getDispatcher()->dispatch('onTableAfterReorder', $event); return true; } /** * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause. * * Negative numbers move the row up in the sequence and positive numbers move it down. * * @param integer $delta The direction and magnitude to move the row in the ordering sequence. * @param string|string[] $where WHERE clause to use for limiting the selection of rows to compact the ordering values. * * @return boolean True on success. * * @since 1.7.0 * @throws \UnexpectedValueException */ public function move($delta, $where = '') { // Check if there is an ordering field set if (!$this->hasField('ordering')) { throw new \UnexpectedValueException(sprintf('%s does not support ordering.', \get_class($this))); } $orderingField = $this->getColumnAlias('ordering'); $quotedOrderingField = $this->_db->quoteName($orderingField); // If the change is none, do nothing. if (empty($delta)) { return true; } $row = null; $query = $this->_db->getQuery(true); // Select the primary key and ordering values from the table. $query->select(implode(',', $this->_tbl_keys) . ', ' . $quotedOrderingField) ->from($this->_db->quoteName($this->_tbl)); // If the movement delta is negative move the row up. if ($delta < 0) { $query->where($quotedOrderingField . ' < ' . (int) $this->$orderingField) ->order($quotedOrderingField . ' DESC'); } elseif ($delta > 0) { // If the movement delta is positive move the row down. $query->where($quotedOrderingField . ' > ' . (int) $this->$orderingField) ->order($quotedOrderingField . ' ASC'); } // Add the custom WHERE clause if set. if ($where) { $query->where($where); } // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforeMove', [ 'subject' => $this, 'query' => $query, 'delta' => $delta, 'where' => $where, ] ); $this->getDispatcher()->dispatch('onTableBeforeMove', $event); // Select the first row with the criteria. $query->setLimit(1); $this->_db->setQuery($query); $row = $this->_db->loadObject(); // If a row is found, move the item. if (!empty($row)) { // Update the ordering field for this instance to the row's ordering value. $query->clear() ->update($this->_db->quoteName($this->_tbl)) ->set($quotedOrderingField . ' = ' . (int) $row->$orderingField); $this->appendPrimaryKeys($query); $this->_db->setQuery($query); $this->_db->execute(); // Update the ordering field for the row to this instance's ordering value. $query->clear() ->update($this->_db->quoteName($this->_tbl)) ->set($quotedOrderingField . ' = ' . (int) $this->$orderingField); $this->appendPrimaryKeys($query, $row); $this->_db->setQuery($query); $this->_db->execute(); // Update the instance value. $this->$orderingField = $row->$orderingField; } else { // Update the ordering field for this instance. $query->clear() ->update($this->_db->quoteName($this->_tbl)) ->set($quotedOrderingField . ' = ' . (int) $this->$orderingField); $this->appendPrimaryKeys($query); $this->_db->setQuery($query); $this->_db->execute(); } // Post-processing by observers $event = AbstractEvent::create( 'onTableAfterMove', [ 'subject' => $this, 'row' => $row, 'delta' => $delta, 'where' => $where, ] ); $this->getDispatcher()->dispatch('onTableAfterMove', $event); return true; } /** * Method to set the publishing state for a row or list of rows in the database table. * * The method respects checked out rows by other users and will attempt to checkin rows that it can after adjustments are made. * * @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used. * @param integer $state The publishing state. eg. [0 = unpublished, 1 = published] * @param integer $userId The user ID of the user performing the operation. * * @return boolean True on success; false if $pks is empty. * * @since 1.7.0 */ public function publish($pks = null, $state = 1, $userId = 0) { // Sanitize input $userId = (int) $userId; $state = (int) $state; // Pre-processing by observers $event = AbstractEvent::create( 'onTableBeforePublish', [ 'subject' => $this, 'pks' => $pks, 'state' => $state, 'userId' => $userId, ] ); $this->getDispatcher()->dispatch('onTableBeforePublish', $event); if (!\is_null($pks)) { if (!\is_array($pks)) { $pks = [$pks]; } foreach ($pks as $key => $pk) { if (!\is_array($pk)) { $pks[$key] = [$this->_tbl_key => $pk]; } } } // If there are no primary keys set check to see if the instance key is set. if (empty($pks)) { $pk = []; foreach ($this->_tbl_keys as $key) { if ($this->$key) { $pk[$key] = $this->$key; } else { // We don't have a full primary key - return false $this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED')); return false; } } $pks = [$pk]; } $publishedField = $this->getColumnAlias('published'); $checkedOutField = $this->getColumnAlias('checked_out'); foreach ($pks as $pk) { // Update the publishing state for rows with the given primary keys. $query = $this->_db->getQuery(true) ->update($this->_db->quoteName($this->_tbl)) ->set($this->_db->quoteName($publishedField) . ' = ' . (int) $state); // If publishing, set published date/time if not previously set if ($state && $this->hasField('publish_up') && (int) $this->publish_up == 0) { $nowDate = $this->_db->quote(Factory::getDate()->toSql()); $query->set($this->_db->quoteName($this->getColumnAlias('publish_up')) . ' = ' . $nowDate); } // Determine if there is checkin support for the table. if ($this->hasField('checked_out') || $this->hasField('checked_out_time')) { $query->where( '(' . $this->_db->quoteName($checkedOutField) . ' = 0' . ' OR ' . $this->_db->quoteName($checkedOutField) . ' = ' . (int) $userId . ' OR ' . $this->_db->quoteName($checkedOutField) . ' IS NULL' . ')' ); $checkin = true; } else { $checkin = false; } // Build the WHERE clause for the primary keys. $this->appendPrimaryKeys($query, $pk); $this->_db->setQuery($query); try { $this->_db->execute(); } catch (\RuntimeException $e) { $this->setError($e->getMessage()); return false; } // If checkin is supported and all rows were adjusted, check them in. if ($checkin && (\count($pks) == $this->_db->getAffectedRows())) { $this->checkIn($pk); } // If the Table instance value is in the list of primary keys that were set, set the instance. $ours = true; foreach ($this->_tbl_keys as $key) { if ($this->$key != $pk[$key]) { $ours = false; } } if ($ours) { $this->$publishedField = $state; } } $this->setError(''); // Pre-processing by observers $event = AbstractEvent::create( 'onTableAfterPublish', [ 'subject' => $this, 'pks' => $pks, 'state' => $state, 'userId' => $userId, ] ); $this->getDispatcher()->dispatch('onTableAfterPublish', $event); return true; } /** * Method to lock the database table for writing. * * @return boolean True on success. * * @since 1.7.0 * @throws \RuntimeException */ protected function _lock() { $this->_db->lockTable($this->_tbl); $this->_locked = true; return true; } /** * Method to return the real name of a "special" column such as ordering, hits, published * etc etc. In this way you are free to follow your db naming convention and use the * built in \Joomla functions. * * @param string $column Name of the "special" column (ie ordering, hits) * * @return string The string that identify the special * * @since 3.4 */ public function getColumnAlias($column) { // Get the column data if set if (isset($this->_columnAlias[$column])) { $return = $this->_columnAlias[$column]; } else { $return = $column; } // Sanitize the name $return = preg_replace('#[^A-Z0-9_]#i', '', $return); return $return; } /** * Method to register a column alias for a "special" column. * * @param string $column The "special" column (ie ordering) * @param string $columnAlias The real column name (ie foo_ordering) * * @return void * * @since 3.4 */ public function setColumnAlias($column, $columnAlias) { // Sanitize the column name alias $column = strtolower($column); $column = preg_replace('#[^A-Z0-9_]#i', '', $column); // Set the column alias internally $this->_columnAlias[$column] = $columnAlias; } /** * Method to unlock the database table for writing. * * @return boolean True on success. * * @since 1.7.0 */ protected function _unlock() { if ($this->_locked) { $this->_db->unlockTables(); $this->_locked = false; } return true; } /** * Check if the record has a property (applying a column alias if it exists) * * @param string $key key to be checked * * @return boolean * * @since 3.9.11 */ public function hasField($key) { $key = $this->getColumnAlias($key); return property_exists($this, $key); } } TableInterface.php 0000644 00000010267 15172723307 0010142 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Table class interface. * * @since 3.2 */ interface TableInterface { /** * Method to bind an associative array or object to the TableInterface instance. * * This method only binds properties that are publicly accessible and optionally takes an array of properties to ignore when binding. * * @param mixed $src An associative array or object to bind to the TableInterface instance. * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. * * @return boolean True on success. * * @since 3.2 * @throws \UnexpectedValueException */ public function bind($src, $ignore = []); /** * Method to perform sanity checks on the TableInterface instance properties to ensure they are safe to store in the database. * * Implementations of this interface should use this method to make sure the data they are storing in the database is safe and * as expected before storage. * * @return boolean True if the instance is sane and able to be stored in the database. * * @since 3.2 */ public function check(); /** * Method to delete a record. * * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used. * * @return boolean True on success. * * @since 3.2 * @throws \UnexpectedValueException */ public function delete($pk = null); /** * Method to get the DatabaseDriver object. * * @return DatabaseDriver The internal database driver object. * * @since 3.2 */ public function getDbo(); /** * Method to get the primary key field name for the table. * * @return string The name of the primary key for the table. * * @since 3.2 */ public function getKeyName(); /** * Method to load a row from the database by primary key and bind the fields to the TableInterface instance properties. * * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. If not * set the instance property value is used. * @param boolean $reset True to reset the default values before loading the new row. * * @return boolean True if successful. False if row not found. * * @since 3.2 * @throws \RuntimeException * @throws \UnexpectedValueException */ public function load($keys = null, $reset = true); /** * Method to reset class properties to the defaults set in the class definition. * * It will ignore the primary key as well as any private class properties. * * @return void * * @since 3.2 */ public function reset(); /** * Method to store a row in the database from the TableInterface instance properties. * * If a primary key value is set the row with that primary key value will be updated with the instance property values. * If no primary key value is set a new row will be inserted into the database with the properties from the TableInterface instance. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 3.2 */ public function store($updateNulls = false); /** * Returns the identity (primary key) value of this record * * @return mixed * * @since 4.0.0 */ public function getId(); /** * Check if the record has a property (applying a column alias if it exists) * * @param string $key key to be checked * * @return boolean * * @since 4.0.0 */ public function hasField($key); } Asset.php 0000644 00000013300 15172723307 0006340 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Table class supporting modified pre-order tree traversal behavior. * * @since 1.7.0 */ class Asset extends Nested { /** * The primary key of the asset. * * @var integer * @since 1.7.0 */ public $id = null; /** * The unique name of the asset. * * @var string * @since 1.7.0 */ public $name = null; /** * The human readable title of the asset. * * @var string * @since 1.7.0 */ public $title = null; /** * The rules for the asset stored in a JSON string * * @var string * @since 1.7.0 */ public $rules = null; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__assets', 'id', $db); } /** * Method to load an asset by its name. * * @param string $name The name of the asset. * * @return integer * * @since 1.7.0 */ public function loadByName($name) { return $this->load(['name' => $name]); } /** * Assert that the nested set data is valid. * * @return boolean True if the instance is sane and able to be stored in the database. * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } $this->parent_id = (int) $this->parent_id; if (empty($this->rules)) { $this->rules = '{}'; } // Nested does not allow parent_id = 0, override this. if ($this->parent_id > 0) { // Get the DatabaseQuery object $query = $this->_db->getQuery(true) ->select('1') ->from($this->_db->quoteName($this->_tbl)) ->where($this->_db->quoteName('id') . ' = ' . $this->parent_id); $query->setLimit(1); if ($this->_db->setQuery($query)->loadResult()) { return true; } $this->setError(Text::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID')); return false; } return true; } /** * Method to recursively rebuild the whole nested set tree. * * @param integer $parentId The root of the tree to rebuild. * @param integer $leftId The left id to start with in building the tree. * @param integer $level The level to assign to the current nodes. * @param string $path The path to the current nodes. * * @return integer 1 + value of root rgt on success, false on failure * * @since 3.5 * @throws \RuntimeException on database error. */ public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = null) { // If no parent is provided, try to find it. if ($parentId === null) { // Get the root item. $parentId = $this->getRootId(); if ($parentId === false) { return false; } } $query = $this->_db->getQuery(true); // Build the structure of the recursive query. if (!isset($this->_cache['rebuild.sql'])) { $query->clear() ->select($this->_tbl_key) ->from($this->_tbl) ->where('parent_id = %d'); // If the table has an ordering field, use that for ordering. if ($this->hasField('ordering')) { $query->order('parent_id, ordering, lft'); } else { $query->order('parent_id, lft'); } $this->_cache['rebuild.sql'] = (string) $query; } // Make a shortcut to database object. // Assemble the query to find all children of this node. $this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId)); $children = $this->_db->loadObjectList(); // The right value of this node is the left value + 1 $rightId = $leftId + 1; // Execute this function recursively over all children foreach ($children as $node) { /* * $rightId is the current right value, which is incremented on recursion return. * Increment the level for the children. * Add this item's alias to the path (but avoid a leading /) */ $rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1); // If there is an update failure, return false to break out of the recursion. if ($rightId === false) { return false; } } // We've got the left value, and now that we've processed // the children of this node we also know the right value. $query->clear() ->update($this->_tbl) ->set('lft = ' . (int) $leftId) ->set('rgt = ' . (int) $rightId) ->set('level = ' . (int) $level) ->where($this->_tbl_key . ' = ' . (int) $parentId); $this->_db->setQuery($query)->execute(); // Return the right value of this node + 1. return $rightId + 1; } } User.php 0000644 00000042410 15172723307 0006203 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Factory; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Language\Text; use Joomla\CMS\Mail\MailHelper; use Joomla\CMS\String\PunycodeHelper; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\Registry\Registry; use Joomla\String\StringHelper; use Joomla\Utilities\ArrayHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Users table * * @since 1.7.0 */ class User extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Associative array of group ids => group ids for the user * * @var array * @since 1.7.0 */ public $groups; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__users', 'id', $db); // Initialise. $this->id = 0; $this->sendEmail = 0; } /** * Method to load a user, user groups, and any other necessary data * from the database so that it can be bound to the user object. * * @param integer $userId An optional user id. * @param boolean $reset False if row not found or on error * (internal error state set in that case). * * @return boolean True on success, false on failure. * * @since 1.7.0 */ public function load($userId = null, $reset = true) { // Get the id to load. if ($userId !== null) { $this->id = $userId; } else { $userId = $this->id; } // Check for a valid id to load. if ($userId === null) { return false; } // Reset the table. $this->reset(); $userId = (int) $userId; // Load the user data. $query = $this->_db->getQuery(true) ->select('*') ->from($this->_db->quoteName('#__users')) ->where($this->_db->quoteName('id') . ' = :userid') ->bind(':userid', $userId, ParameterType::INTEGER); $this->_db->setQuery($query); $data = (array) $this->_db->loadAssoc(); if (!\count($data)) { return false; } // Convert email from punycode $data['email'] = PunycodeHelper::emailToUTF8($data['email']); // Bind the data to the table. $return = $this->bind($data); if ($return !== false) { // Load the user groups. $query->clear() ->select($this->_db->quoteName('g.id')) ->select($this->_db->quoteName('g.title')) ->from($this->_db->quoteName('#__usergroups', 'g')) ->join( 'INNER', $this->_db->quoteName('#__user_usergroup_map', 'm'), $this->_db->quoteName('m.group_id') . ' = ' . $this->_db->quoteName('g.id') ) ->where($this->_db->quoteName('m.user_id') . ' = :muserid') ->bind(':muserid', $userId, ParameterType::INTEGER); $this->_db->setQuery($query); // Add the groups to the user data. $this->groups = $this->_db->loadAssocList('id', 'id'); } return $return; } /** * Method to bind the user, user groups, and any other necessary data. * * @param array $array The data to bind. * @param mixed $ignore An array or space separated list of fields to ignore. * * @return boolean True on success, false on failure. * * @since 1.7.0 */ public function bind($array, $ignore = '') { if (\array_key_exists('params', $array) && \is_array($array['params'])) { $registry = new Registry($array['params']); $array['params'] = (string) $registry; } // Attempt to bind the data. $return = parent::bind($array, $ignore); // Load the real group data based on the bound ids. if ($return && !empty($this->groups)) { // Set the group ids. $this->groups = ArrayHelper::toInteger($this->groups); // Get the titles for the user groups. $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->select($this->_db->quoteName('title')) ->from($this->_db->quoteName('#__usergroups')) ->whereIn($this->_db->quoteName('id'), array_values($this->groups)); $this->_db->setQuery($query); // Set the titles for the user groups. $this->groups = $this->_db->loadAssocList('id', 'id'); } return $return; } /** * Validation and filtering * * @return boolean True if satisfactory * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Set user id to null instead of 0, if needed if ($this->id === 0) { $this->id = null; } $filterInput = InputFilter::getInstance(); // Validate user information if ($filterInput->clean($this->name, 'TRIM') == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_YOUR_NAME')); return false; } if ($filterInput->clean($this->username, 'TRIM') == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_A_USER_NAME')); return false; } if ( preg_match('#[<>"\'%;()&\\\\]|\\.\\./#', $this->username) || StringHelper::strlen($this->username) < 2 || $filterInput->clean($this->username, 'TRIM') !== $this->username || StringHelper::strlen($this->username) > 150 ) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_VALID_AZ09', 2)); return false; } if ( ($filterInput->clean($this->email, 'TRIM') == '') || !MailHelper::isEmailAddress($this->email) || StringHelper::strlen($this->email) > 100 ) { $this->setError(Text::_('JLIB_DATABASE_ERROR_VALID_MAIL')); return false; } // Convert email to punycode for storage $this->email = PunycodeHelper::emailToPunycode($this->email); // Set the registration timestamp if (empty($this->registerDate)) { $this->registerDate = Factory::getDate()->toSql(); } // Set the lastvisitDate timestamp if (empty($this->lastvisitDate)) { $this->lastvisitDate = null; } // Set the lastResetTime timestamp if (empty($this->lastResetTime)) { $this->lastResetTime = null; } $uid = (int) $this->id; // Check for existing username $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__users')) ->where($this->_db->quoteName('username') . ' = :username') ->where($this->_db->quoteName('id') . ' != :userid') ->bind(':username', $this->username) ->bind(':userid', $uid, ParameterType::INTEGER); $this->_db->setQuery($query); $xid = (int) $this->_db->loadResult(); if ($xid && $xid != (int) $this->id) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERNAME_INUSE')); return false; } // Check for existing email $query->clear() ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__users')) ->where('LOWER(' . $this->_db->quoteName('email') . ') = LOWER(:mail)') ->where($this->_db->quoteName('id') . ' != :muserid') ->bind(':mail', $this->email) ->bind(':muserid', $uid, ParameterType::INTEGER); $this->_db->setQuery($query); $xid = (int) $this->_db->loadResult(); if ($xid && $xid != (int) $this->id) { $this->setError(Text::_('JLIB_DATABASE_ERROR_EMAIL_INUSE')); return false; } // Check for root_user != username $rootUser = Factory::getApplication()->get('root_user'); if (!is_numeric($rootUser)) { $query->clear() ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__users')) ->where($this->_db->quoteName('username') . ' = :username') ->bind(':username', $rootUser); $this->_db->setQuery($query); $xid = (int) $this->_db->loadResult(); if ( $rootUser == $this->username && (!$xid || $xid && $xid != (int) $this->id) || $xid && $xid == (int) $this->id && $rootUser != $this->username ) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERNAME_CANNOT_CHANGE')); return false; } } // Set an empty string value to the legacy otpKey and otep columns if empty $this->otpKey = $this->otpKey ?: ''; $this->otep = $this->otep ?: ''; return true; } /** * Method to store a row in the database from the Table instance properties. * * If a primary key value is set the row with that primary key value will be updated with the instance property values. * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 1.7.0 */ public function store($updateNulls = true) { // Get the table key and key value. $k = $this->_tbl_key; $key = $this->$k; // @todo: This is a dumb way to handle the groups. // Store groups locally so as to not update directly. $groups = $this->groups; unset($this->groups); // Insert or update the object based on presence of a key value. if ($key) { // Already have a table key, update the row. $this->_db->updateObject($this->_tbl, $this, $this->_tbl_key, $updateNulls); } else { // Don't have a table key, insert the row. $this->_db->insertObject($this->_tbl, $this, $this->_tbl_key); } // Reset groups to the local object. $this->groups = $groups; $query = $this->_db->getQuery(true); // Store the group data if the user data was saved. if (\is_array($this->groups) && \count($this->groups)) { $uid = (int) $this->id; // Grab all usergroup entries for the user $query->clear() ->select($this->_db->quoteName('group_id')) ->from($this->_db->quoteName('#__user_usergroup_map')) ->where($this->_db->quoteName('user_id') . ' = :userid') ->bind(':userid', $uid, ParameterType::INTEGER); $this->_db->setQuery($query); $result = $this->_db->loadObjectList(); // Loop through them and check if database contains something $this->groups does not if (\count($result)) { $mapGroupId = []; foreach ($result as $map) { if (\array_key_exists($map->group_id, $this->groups)) { // It already exists, no action required unset($groups[$map->group_id]); } else { $mapGroupId[] = (int) $map->group_id; } } if (\count($mapGroupId)) { $query->clear() ->delete($this->_db->quoteName('#__user_usergroup_map')) ->where($this->_db->quoteName('user_id') . ' = :uid') ->whereIn($this->_db->quoteName('group_id'), $mapGroupId) ->bind(':uid', $uid, ParameterType::INTEGER); $this->_db->setQuery($query); $this->_db->execute(); } } // If there is anything left in this->groups it needs to be inserted if (\count($groups)) { // Set the new user group maps. $query->clear() ->insert($this->_db->quoteName('#__user_usergroup_map')) ->columns([$this->_db->quoteName('user_id'), $this->_db->quoteName('group_id')]); foreach ($groups as $group) { $query->values( implode( ',', $query->bindArray( [$this->id , $group], [ParameterType::INTEGER, ParameterType::INTEGER] ) ) ); } $this->_db->setQuery($query); $this->_db->execute(); } unset($groups); } // If a user is blocked, delete the cookie login rows if ($this->block == 1) { $query->clear() ->delete($this->_db->quoteName('#__user_keys')) ->where($this->_db->quoteName('user_id') . ' = :user_id') ->bind(':user_id', $this->username); $this->_db->setQuery($query); $this->_db->execute(); } return true; } /** * Method to delete a user, user groups, and any other necessary data from the database. * * @param integer $userId An optional user id. * * @return boolean True on success, false on failure. * * @since 1.7.0 */ public function delete($userId = null) { // Set the primary key to delete. $k = $this->_tbl_key; if ($userId) { $this->$k = (int) $userId; } $key = (int) $this->$k; // Delete the user. $query = $this->_db->getQuery(true) ->delete($this->_db->quoteName($this->_tbl)) ->where($this->_db->quoteName($this->_tbl_key) . ' = :key') ->bind(':key', $key, ParameterType::INTEGER); $this->_db->setQuery($query); $this->_db->execute(); // Delete the user group maps. $query->clear() ->delete($this->_db->quoteName('#__user_usergroup_map')) ->where($this->_db->quoteName('user_id') . ' = :key') ->bind(':key', $key, ParameterType::INTEGER); $this->_db->setQuery($query); $this->_db->execute(); /* * Clean Up Related Data. */ $query->clear() ->delete($this->_db->quoteName('#__messages_cfg')) ->where($this->_db->quoteName('user_id') . ' = :key') ->bind(':key', $key, ParameterType::INTEGER); $this->_db->setQuery($query); $this->_db->execute(); $query->clear() ->delete($this->_db->quoteName('#__messages')) ->where($this->_db->quoteName('user_id_to') . ' = :key') ->bind(':key', $key, ParameterType::INTEGER); $this->_db->setQuery($query); $this->_db->execute(); $query->clear() ->delete($this->_db->quoteName('#__user_keys')) ->where($this->_db->quoteName('user_id') . ' = :username') ->bind(':username', $this->username); $this->_db->setQuery($query); $this->_db->execute(); return true; } /** * Updates last visit time of user * * @param integer $timeStamp The timestamp, defaults to 'now'. * @param integer $userId The user id (optional). * * @return boolean False if an error occurs * * @since 1.7.0 */ public function setLastVisit($timeStamp = null, $userId = null) { // Check for User ID if (\is_null($userId)) { if (isset($this)) { $userId = $this->id; } else { jexit('No userid in setLastVisit'); } } // If no timestamp value is passed to function, then current time is used. if ($timeStamp === null) { $timeStamp = 'now'; } $date = Factory::getDate($timeStamp); $userId = (int) $userId; $lastVisit = $date->toSql(); // Update the database row for the user. $db = $this->_db; $query = $db->getQuery(true) ->update($db->quoteName($this->_tbl)) ->set($db->quoteName('lastvisitDate') . ' = :lastvisitDate') ->where($db->quoteName('id') . ' = :id') ->bind(':lastvisitDate', $lastVisit) ->bind(':id', $userId, ParameterType::INTEGER); $db->setQuery($query); $db->execute(); return true; } } Usergroup.php 0000644 00000024134 15172723307 0007263 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; use Joomla\Database\Exception\ExecutionFailureException; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Usergroup table class. * * @since 1.7.0 */ class Usergroup extends Table { /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__usergroups', 'id', $db); } /** * Method to check the current record to save * * @return boolean True on success * * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Validate the title. if ((trim($this->title)) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE')); return false; } // The parent_id can not be equal to the current id if ($this->id === (int) $this->parent_id) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID')); return false; } // Check for a duplicate parent_id, title. // There is a unique index on the (parent_id, title) field in the table. $db = $this->_db; $parentId = (int) $this->parent_id; $title = trim($this->title); $id = (int) $this->id; $query = $db->getQuery(true) ->select('COUNT(title)') ->from($this->_tbl) ->where($db->quoteName('title') . ' = :title') ->where($db->quoteName('parent_id') . ' = :parentid') ->where($db->quoteName('id') . ' <> :id') ->bind(':title', $title) ->bind(':parentid', $parentId, ParameterType::INTEGER) ->bind(':id', $id, ParameterType::INTEGER); $db->setQuery($query); if ($db->loadResult() > 0) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE_EXISTS')); return false; } // We do not allow to move non public to root and public to non-root if (!empty($this->id)) { $table = self::getInstance('Usergroup', 'JTable', ['dbo' => $this->getDbo()]); $table->load($this->id); if ((!$table->parent_id && $this->parent_id) || ($table->parent_id && !$this->parent_id)) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID')); return false; } } elseif (!$this->parent_id) { // New entry should always be greater 0 $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID')); return false; } // The new parent_id has to be a valid group if ($this->parent_id) { $table = self::getInstance('Usergroup', 'JTable', ['dbo' => $this->getDbo()]); $table->load($this->parent_id); if ($table->id != $this->parent_id) { $this->setError(Text::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID')); return false; } } return true; } /** * Method to recursively rebuild the nested set tree. * * @param integer $parentId The root of the tree to rebuild. * @param integer $left The left id to start with in building the tree. * * @return boolean True on success * * @since 1.7.0 */ public function rebuild($parentId = 0, $left = 0) { // Get the database object $db = $this->_db; $query = $db->getQuery(true); $parentId = (int) $parentId; // Get all children of this node $query->clear() ->select($db->quoteName('id')) ->from($db->quoteName($this->_tbl)) ->where($db->quoteName('parent_id') . ' = :parentid') ->bind(':parentid', $parentId, ParameterType::INTEGER) ->order([$db->quoteName('parent_id'), $db->quoteName('title')]); $db->setQuery($query); $children = $db->loadColumn(); // The right value of this node is the left value + 1 $right = $left + 1; // Execute this function recursively over all children for ($i = 0, $n = \count($children); $i < $n; $i++) { // $right is the current right value, which is incremented on recursion return $right = $this->rebuild($children[$i], $right); // If there is an update failure, return false to break out of the recursion if ($right === false) { return false; } } $left = (int) $left; $right = (int) $right; // We've got the left value, and now that we've processed // the children of this node we also know the right value $query->clear() ->update($db->quoteName($this->_tbl)) ->set($db->quoteName('lft') . ' = :lft') ->set($db->quoteName('rgt') . ' = :rgt') ->where($db->quoteName('id') . ' = :id') ->bind(':lft', $left, ParameterType::INTEGER) ->bind(':rgt', $right, ParameterType::INTEGER) ->bind(':id', $parentId, ParameterType::INTEGER); $db->setQuery($query); // If there is an update failure, return false to break out of the recursion try { $db->execute(); } catch (ExecutionFailureException $e) { return false; } // Return the right value of this node + 1 return $right + 1; } /** * Inserts a new row if id is zero or updates an existing row in the database table * * @param boolean $updateNulls If false, null object variables are not updated * * @return boolean True if successful, false otherwise and an internal error message is set * * @since 1.7.0 */ public function store($updateNulls = false) { if ($result = parent::store($updateNulls)) { // Rebuild the nested set tree. $this->rebuild(); } return $result; } /** * Delete this object and its dependencies * * @param integer $oid The primary key of the user group to delete. * * @return mixed Boolean or Exception. * * @since 1.7.0 * @throws \RuntimeException on database error. * @throws \UnexpectedValueException on data error. */ public function delete($oid = null) { if ($oid) { $this->load($oid); } if ($this->id == 0) { throw new \UnexpectedValueException('Usergroup not found'); } if ($this->parent_id == 0) { throw new \UnexpectedValueException('Root usergroup cannot be deleted.'); } if ($this->lft == 0 || $this->rgt == 0) { throw new \UnexpectedValueException('Left-Right data inconsistency. Cannot delete usergroup.'); } $db = $this->_db; $lft = (int) $this->lft; $rgt = (int) $this->rgt; // Select the usergroup ID and its children $query = $db->getQuery(true) ->select($db->quoteName('c.id')) ->from($db->quoteName($this->_tbl, 'c')) ->where($db->quoteName('c.lft') . ' >= :lft') ->where($db->quoteName('c.rgt') . ' <= :rgt') ->bind(':lft', $lft, ParameterType::INTEGER) ->bind(':rgt', $rgt, ParameterType::INTEGER); $db->setQuery($query); $ids = $db->loadColumn(); if (empty($ids)) { throw new \UnexpectedValueException('Left-Right data inconsistency. Cannot delete usergroup.'); } // Delete the usergroup and its children $query->clear() ->delete($db->quoteName($this->_tbl)) ->whereIn($db->quoteName('id'), $ids); $db->setQuery($query); $db->execute(); // Rebuild the nested set tree. $this->rebuild(); // Delete the usergroup in view levels $replace = []; foreach ($ids as $id) { $replace[] = ',' . $db->quote("[$id,") . ',' . $db->quote('['); $replace[] = ',' . $db->quote(",$id,") . ',' . $db->quote(','); $replace[] = ',' . $db->quote(",$id]") . ',' . $db->quote(']'); $replace[] = ',' . $db->quote("[$id]") . ',' . $db->quote('[]'); } $query->clear() ->select( [ $db->quoteName('id'), $db->quoteName('rules'), ] ) ->from($db->quoteName('#__viewlevels')); $db->setQuery($query); $rules = $db->loadObjectList(); $matchIds = []; foreach ($rules as $rule) { foreach ($ids as $id) { if (strstr($rule->rules, '[' . $id) || strstr($rule->rules, ',' . $id) || strstr($rule->rules, $id . ']')) { $matchIds[] = $rule->id; } } } if (!empty($matchIds)) { $query->clear() ->update($db->quoteName('#__viewlevels')) ->set($db->quoteName('rules') . ' = ' . str_repeat('REPLACE(', 4 * \count($ids)) . $db->quoteName('rules') . implode(')', $replace) . ')') ->whereIn($db->quoteName('id'), $matchIds); $db->setQuery($query); $db->execute(); } // Delete the user to usergroup mappings for the group(s) from the database. $query->clear() ->delete($db->quoteName('#__user_usergroup_map')) ->whereIn($db->quoteName('group_id'), $ids); $db->setQuery($query); $db->execute(); return true; } } MenuType.php 0000644 00000025462 15172723307 0007043 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Table; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu Types table * * @since 1.6 */ class MenuType extends Table { /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.6 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__menu_types', 'id', $db); } /** * Overloaded check function * * @return boolean True on success, false on failure * * @see Table::check() * @since 1.6 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } $this->menutype = ApplicationHelper::stringURLSafe($this->menutype); if (empty($this->menutype)) { $this->setError(Text::_('JLIB_DATABASE_ERROR_MENUTYPE_EMPTY')); return false; } // Sanitise data. if (trim($this->title) === '') { $this->title = $this->menutype; } $id = (int) $this->id; // Check for unique menutype. $query = $this->_db->getQuery(true) ->select('COUNT(id)') ->from($this->_db->quoteName('#__menu_types')) ->where($this->_db->quoteName('menutype') . ' = :menutype') ->where($this->_db->quoteName('id') . ' <> :id') ->bind(':menutype', $this->menutype) ->bind(':id', $id, ParameterType::INTEGER); $this->_db->setQuery($query); if ($this->_db->loadResult()) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_MENUTYPE_EXISTS', $this->menutype)); return false; } return true; } /** * Method to store a row in the database from the Table instance properties. * * If a primary key value is set the row with that primary key value will be updated with the instance property values. * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 1.6 */ public function store($updateNulls = false) { if ($this->id) { // Get the user id $userId = (int) Factory::getUser()->id; $notIn = [0, $userId]; // Get the old value of the table $table = Table::getInstance('Menutype', 'JTable', ['dbo' => $this->getDbo()]); $table->load($this->id); // Verify that no items are checked out $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__menu')) ->where($this->_db->quoteName('menutype') . ' = :menutype') ->whereNotIn($this->_db->quoteName('checked_out'), $notIn) ->bind(':menutype', $table->menutype); $this->_db->setQuery($query); if ($this->_db->loadRowList()) { $this->setError( Text::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', \get_class($this), Text::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT')) ); return false; } // Verify that no module for this menu are checked out $searchParams = '%"menutype":' . json_encode($table->menutype) . '%'; $query->clear() ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__modules')) ->where($this->_db->quoteName('module') . ' = ' . $this->_db->quote('mod_menu')) ->where($this->_db->quoteName('params') . ' LIKE :params') ->whereNotIn($this->_db->quoteName('checked_out'), $notIn) ->bind(':params', $searchParams); $this->_db->setQuery($query); if ($this->_db->loadRowList()) { $this->setError( Text::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', \get_class($this), Text::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT')) ); return false; } // Update the menu items $query->clear() ->update($this->_db->quoteName('#__menu')) ->set($this->_db->quoteName('menutype') . ' = :setmenutype') ->where($this->_db->quoteName('menutype') . ' = :menutype') ->bind(':setmenutype', $this->menutype) ->bind(':menutype', $table->menutype); $this->_db->setQuery($query); $this->_db->execute(); // Update the module items $whereParams = '%"menutype":' . json_encode($table->menutype) . '%'; $searchParams = '"menutype":' . json_encode($table->menutype); $replaceParams = '"menutype":' . json_encode($this->menutype); $query->clear() ->update($this->_db->quoteName('#__modules')) ->set( $this->_db->quoteName('params') . ' = REPLACE(' . $this->_db->quoteName('params') . ', :search, :value)' ); $query->where($this->_db->quoteName('module') . ' = ' . $this->_db->quote('mod_menu')) ->where($this->_db->quoteName('params') . ' LIKE :whereparams') ->bind(':search', $searchParams) ->bind(':value', $replaceParams) ->bind(':whereparams', $whereParams); $this->_db->setQuery($query); $this->_db->execute(); } return parent::store($updateNulls); } /** * Method to delete a row from the database table by primary key value. * * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used. * * @return boolean True on success. * * @since 1.6 */ public function delete($pk = null) { $k = $this->_tbl_key; $pk = $pk === null ? $this->$k : $pk; // If no primary key is given, return false. if ($pk !== null) { // Get the user id $userId = (int) Factory::getUser()->id; $notIn = [0, $userId]; $star = '*'; // Get the old value of the table $table = Table::getInstance('Menutype', 'JTable', ['dbo' => $this->getDbo()]); $table->load($pk); // Verify that no items are checked out $query = $this->_db->getQuery(true) ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__menu')) ->where($this->_db->quoteName('menutype') . ' = :menutype') ->where('(' . $this->_db->quoteName('checked_out') . ' NOT IN (NULL, :id)' . ' OR ' . $this->_db->quoteName('home') . ' = 1' . ' AND ' . $this->_db->quoteName('language') . ' = :star' . ')') ->bind(':menutype', $table->menutype) ->bind(':id', $userId, ParameterType::INTEGER) ->bind(':star', $star); $this->_db->setQuery($query); if ($this->_db->loadRowList()) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED', \get_class($this), Text::_('JLIB_DATABASE_ERROR_MENUTYPE'))); return false; } // Verify that no module for this menu are checked out $searchParams = '%"menutype":' . json_encode($table->menutype) . '%'; $query->clear() ->select($this->_db->quoteName('id')) ->from($this->_db->quoteName('#__modules')) ->where($this->_db->quoteName('module') . ' = ' . $this->_db->quote('mod_menu')) ->where($this->_db->quoteName('params') . ' LIKE :menutype') ->whereNotIn($this->_db->quoteName('checked_out'), $notIn) ->bind(':menutype', $searchParams); $this->_db->setQuery($query); if ($this->_db->loadRowList()) { $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED', \get_class($this), Text::_('JLIB_DATABASE_ERROR_MENUTYPE'))); return false; } // Delete the menu items $query->clear() ->delete('#__menu') ->where('menutype=' . $this->_db->quote($table->menutype)); $this->_db->setQuery($query); $this->_db->execute(); // Update the module items $query->clear() ->delete('#__modules') ->where('module=' . $this->_db->quote('mod_menu')) ->where('params LIKE ' . $this->_db->quote('%"menutype":' . json_encode($table->menutype) . '%')); $this->_db->setQuery($query); $this->_db->execute(); } return parent::delete($pk); } /** * Method to compute the default name of the asset. * The default name is in the form table_name.id * where id is the value of the primary key of the table. * * @return string * * @since 3.6 */ protected function _getAssetName() { return 'com_menus.menu.' . $this->id; } /** * Method to return the title to use for the asset table. * * @return string * * @since 3.6 */ protected function _getAssetTitle() { return $this->title; } /** * Method to get the parent asset under which to register this one. * By default, all assets are registered to the ROOT node with ID, * which will default to 1 if none exists. * The extended class can define a table and id to lookup. If the * asset does not exist it will be created. * * @param Table $table A Table object for the asset parent. * @param integer $id Id to look up * * @return integer * * @since 3.6 */ protected function _getAssetParentId(Table $table = null, $id = null) { $assetId = null; $asset = Table::getInstance('asset'); if ($asset->loadByName('com_menus')) { $assetId = $asset->id; } return $assetId === null ? parent::_getAssetParentId($table, $id) : $assetId; } } CoreContent.php 0000644 00000024253 15172723307 0007515 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Application\ApplicationHelper; use Joomla\CMS\Factory; use Joomla\CMS\Helper\ContentHelper; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; use Joomla\String\StringHelper; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Core content table * * @since 3.1 */ class CoreContent extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Encode necessary fields to JSON in the bind method * * @var array * @since 4.0.0 */ protected $_jsonEncode = ['core_params', 'core_metadata', 'core_images', 'core_urls', 'core_body']; /** * Constructor * * @param DatabaseDriver $db A database connector object * * @since 3.1 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__ucm_content', 'core_content_id', $db); $this->setColumnAlias('published', 'core_state'); $this->setColumnAlias('checked_out', 'core_checked_out_user_id'); $this->setColumnAlias('checked_out_time', 'core_checked_out_time'); $this->_trackAssets = false; } /** * Overloaded check function * * @return boolean True on success, false on failure * * @see Table::check() * @since 3.1 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } if (trim($this->core_title) === '') { $this->setError(Text::_('JLIB_CMS_WARNING_PROVIDE_VALID_NAME')); return false; } if (trim($this->core_alias) === '') { $this->core_alias = $this->core_title; } $this->core_alias = ApplicationHelper::stringURLSafe($this->core_alias); if (trim(str_replace('-', '', $this->core_alias)) === '') { $this->core_alias = Factory::getDate()->format('Y-m-d-H-i-s'); } // Not Null sanity check if (empty($this->core_images)) { $this->core_images = '{}'; } if (empty($this->core_urls)) { $this->core_urls = '{}'; } // Check the publish down date is not earlier than publish up. if ( $this->core_publish_up !== null && $this->core_publish_down !== null && $this->core_publish_down < $this->core_publish_up && $this->core_publish_down > $this->_db->getNullDate() ) { // Swap the dates. $temp = $this->core_publish_up; $this->core_publish_up = $this->core_publish_down; $this->core_publish_down = $temp; } // Clean up keywords -- eliminate extra spaces between phrases // and cr (\r) and lf (\n) characters from string if (!empty($this->core_metakey)) { // Only process if not empty // Array of characters to remove $bad_characters = ["\n", "\r", "\"", '<', '>']; // Remove bad characters $after_clean = StringHelper::str_ireplace($bad_characters, '', $this->core_metakey); // Create array using commas as delimiter $keys = explode(',', $after_clean); $clean_keys = []; foreach ($keys as $key) { if (trim($key)) { // Ignore blank keywords $clean_keys[] = trim($key); } } // Put array back together delimited by ", " $this->core_metakey = implode(', ', $clean_keys); } return true; } /** * Override JTable delete method to include deleting corresponding row from #__ucm_base. * * @param integer $pk primary key value to delete. Must be set or throws an exception. * * @return boolean True on success. * * @since 3.1 * @throws \UnexpectedValueException */ public function delete($pk = null) { $baseTable = Table::getInstance('Ucm', 'JTable', ['dbo' => $this->getDbo()]); return parent::delete($pk) && $baseTable->delete($pk); } /** * Method to delete a row from the #__ucm_content table by content_item_id. * * @param integer $contentItemId value of the core_content_item_id to delete. Corresponds to the primary key of the content table. * @param string $typeAlias Alias for the content type * * @return boolean True on success. * * @since 3.1 * @throws \UnexpectedValueException */ public function deleteByContentId($contentItemId = null, $typeAlias = null) { $contentItemId = (int) $contentItemId; if ($contentItemId === 0) { throw new \UnexpectedValueException('Null content item key not allowed.'); } if ($typeAlias === null) { throw new \UnexpectedValueException('Null type alias not allowed.'); } $db = $this->getDbo(); $query = $db->getQuery(true); $query->select($db->quoteName('core_content_id')) ->from($db->quoteName('#__ucm_content')) ->where( [ $db->quoteName('core_content_item_id') . ' = :contentItemId', $db->quoteName('core_type_alias') . ' = :typeAlias', ] ) ->bind(':contentItemId', $contentItemId, ParameterType::INTEGER) ->bind(':typeAlias', $typeAlias); $db->setQuery($query); if ($ucmId = $db->loadResult()) { return $this->delete($ucmId); } else { return true; } } /** * Overrides Table::store to set modified data and user id. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 3.1 */ public function store($updateNulls = true) { $date = Factory::getDate(); $user = Factory::getUser(); if ($this->core_content_id) { // Existing item $this->core_modified_time = $date->toSql(); $this->core_modified_user_id = $user->get('id'); $isNew = false; } else { // New content item. A content item core_created_time and core_created_user_id field can be set by the user, // so we don't touch either of these if they are set. if (!(int) $this->core_created_time) { $this->core_created_time = $date->toSql(); } if (empty($this->core_created_user_id)) { $this->core_created_user_id = $user->get('id'); } if (!(int) $this->core_modified_time) { $this->core_modified_time = $this->core_created_time; } if (empty($this->core_modified_user_id)) { $this->core_modified_user_id = $this->core_created_user_id; } $isNew = true; } $oldRules = $this->getRules(); if (empty($oldRules)) { $this->setRules('{}'); } $result = parent::store($updateNulls); return $result && $this->storeUcmBase($updateNulls, $isNew); } /** * Insert or update row in ucm_base table * * @param boolean $updateNulls True to update fields even if they are null. * @param boolean $isNew if true, need to insert. Otherwise update. * * @return boolean True on success. * * @since 3.1 */ protected function storeUcmBase($updateNulls = true, $isNew = false) { // Store the ucm_base row $db = $this->getDbo(); $query = $db->getQuery(true); $languageId = ContentHelper::getLanguageId($this->core_language); // Selecting "all languages" doesn't give a language id - we can't store a blank string in non mysql databases, so save 0 (the default value) if (!$languageId) { $languageId = 0; } if ($isNew) { $query->insert($db->quoteName('#__ucm_base')) ->columns( [ $db->quoteName('ucm_id'), $db->quoteName('ucm_item_id'), $db->quoteName('ucm_type_id'), $db->quoteName('ucm_language_id'), ] ) ->values( implode( ',', $query->bindArray( [ $this->core_content_id, $this->core_content_item_id, $this->core_type_id, $languageId, ] ) ) ); } else { $query->update($db->quoteName('#__ucm_base')) ->set( [ $db->quoteName('ucm_item_id') . ' = :coreContentItemId', $db->quoteName('ucm_type_id') . ' = :typeId', $db->quoteName('ucm_language_id') . ' = :languageId', ] ) ->where($db->quoteName('ucm_id') . ' = :coreContentId') ->bind(':coreContentItemId', $this->core_content_item_id, ParameterType::INTEGER) ->bind(':typeId', $this->core_type_id, ParameterType::INTEGER) ->bind(':languageId', $languageId, ParameterType::INTEGER) ->bind(':coreContentId', $this->core_content_id, ParameterType::INTEGER); } $db->setQuery($query); return $db->execute(); } } Update.php 0000644 00000004713 15172723307 0006513 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Update table * Stores updates temporarily * * @since 1.7.0 */ class Update extends Table { /** * Ensure the params in json encoded in the bind method * * @var array * @since 4.0.0 */ protected $_jsonEncode = ['params']; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__updates', 'update_id', $db); } /** * Overloaded check function * * @return boolean True if the object is ok * * @see Table::check() * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for valid name if (trim($this->name) == '' || trim($this->element) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION')); return false; } if (!$this->update_id && !$this->data) { $this->data = ''; } // While column is not nullable, make sure we have a value. if ($this->description === null) { $this->description = ''; } return true; } /** * Method to create and execute a SELECT WHERE query. * * @param array $options Array of options * * @return string Results of query * * @since 1.7.0 */ public function find($options = []) { $where = []; foreach ($options as $col => $val) { $where[] = $col . ' = ' . $this->_db->quote($val); } $query = $this->_db->getQuery(true) ->select($this->_db->quoteName($this->_tbl_key)) ->from($this->_db->quoteName($this->_tbl)) ->where(implode(' AND ', $where)); $this->_db->setQuery($query); return $this->_db->loadResult(); } } Extension.php 0000644 00000005174 15172723307 0007247 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Extension table * * @since 1.7.0 */ class Extension extends Table { /** * Indicates that columns fully support the NULL value in the database * * @var boolean * @since 4.0.0 */ protected $_supportNullValue = true; /** * Ensure the params in json encoded in the bind method * * @var array * @since 4.0.0 */ protected $_jsonEncode = ['params']; /** * Custom data can be used by extension developers * * @var string * @since 4.0.0 */ public $custom_data = ''; /** * Constructor * * @param DatabaseDriver $db Database driver object. * * @since 1.7.0 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__extensions', 'extension_id', $db); // Set the alias since the column is called enabled $this->setColumnAlias('published', 'enabled'); } /** * Overloaded check function * * @return boolean True if the object is ok * * @see Table::check() * @since 1.7.0 */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for valid name if (trim($this->name) == '' || trim($this->element) == '') { $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION')); return false; } return true; } /** * Method to create and execute a SELECT WHERE query. * * @param array $options Array of options * * @return string The database query result * * @since 1.7.0 */ public function find($options = []) { // Get the DatabaseQuery object $query = $this->_db->getQuery(true); foreach ($options as $col => $val) { $query->where($col . ' = ' . $this->_db->quote($val)); } $query->select($this->_db->quoteName('extension_id')) ->from($this->_db->quoteName('#__extensions')); $this->_db->setQuery($query); return $this->_db->loadResult(); } } ContentType.php 0000644 00000010434 15172723307 0007542 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Language\Text; use Joomla\Database\DatabaseDriver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Tags table * * @since 3.1 */ class ContentType extends Table { /** * Constructor * * @param DatabaseDriver $db A database connector object * * @since 3.1 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__content_types', 'type_id', $db); } /** * Overloaded check method to ensure data integrity. * * @return boolean True on success. * * @since 3.1 * @throws \UnexpectedValueException */ public function check() { try { parent::check(); } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } // Check for valid name. if (trim($this->type_title) === '') { throw new \UnexpectedValueException(sprintf('The title is empty')); } $this->type_title = ucfirst($this->type_title); if (empty($this->type_alias)) { throw new \UnexpectedValueException(sprintf('The type_alias is empty')); } return true; } /** * Overridden Table::store. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 3.1 */ public function store($updateNulls = false) { // Verify that the alias is unique $table = Table::getInstance('Contenttype', 'JTable', ['dbo' => $this->getDbo()]); if ($table->load(['type_alias' => $this->type_alias]) && ($table->type_id != $this->type_id || $this->type_id == 0)) { $this->setError(Text::_('COM_TAGS_ERROR_UNIQUE_ALIAS')); return false; } return parent::store($updateNulls); } /** * Method to expand the field mapping * * @param boolean $assoc True to return an associative array. * * @return mixed Array or object with field mappings. Defaults to object. * * @since 3.1 */ public function fieldmapExpand($assoc = true) { return $this->fieldmap = json_decode($this->fieldmappings, $assoc); } /** * Method to get the id given the type alias * * @param string $typeAlias Content type alias (for example, 'com_content.article'). * * @return mixed type_id for this alias if successful, otherwise null. * * @since 3.2 */ public function getTypeId($typeAlias) { $db = $this->_db; $query = $db->getQuery(true); $query->select($db->quoteName('type_id')) ->from($db->quoteName($this->_tbl)) ->where($db->quoteName('type_alias') . ' = :type_alias') ->bind(':type_alias', $typeAlias); $db->setQuery($query); return $db->loadResult(); } /** * Method to get the Table object for the content type from the table object. * * @return mixed Table object on success, otherwise false. * * @since 3.2 * * @throws \RuntimeException */ public function getContentTable() { $result = false; $tableInfo = json_decode($this->table); if (\is_object($tableInfo) && isset($tableInfo->special)) { if (\is_object($tableInfo->special) && isset($tableInfo->special->type) && isset($tableInfo->special->prefix)) { $class = $tableInfo->special->class ?? 'Joomla\\CMS\\Table\\Table'; if (!class_implements($class, 'Joomla\\CMS\\Table\\TableInterface')) { // This isn't an instance of TableInterface. Stop. throw new \RuntimeException('Class must be an instance of Joomla\\CMS\\Table\\TableInterface'); } $result = $class::getInstance($tableInfo->special->type, $tableInfo->special->prefix); } } return $result; } } ContentHistory.php 0000644 00000017270 15172723307 0010267 0 ustar 00 <?php /** * Joomla! Content Management System * * @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\CMS\Table; use Joomla\CMS\Factory; use Joomla\Database\DatabaseDriver; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Content History table. * * @since 3.2 */ class ContentHistory extends Table { /** * Array of object fields to unset from the data object before calculating SHA1 hash. This allows us to detect a meaningful change * in the database row using the hash. This can be read from the #__content_types content_history_options column. * * @var array * @since 3.2 */ public $ignoreChanges = []; /** * Array of object fields to convert to integers before calculating SHA1 hash. Some values are stored differently * when an item is created than when the item is changed and saved. This works around that issue. * This can be read from the #__content_types content_history_options column. * * @var array * @since 3.2 */ public $convertToInt = []; /** * Constructor * * @param DatabaseDriver $db A database connector object * * @since 3.1 */ public function __construct(DatabaseDriver $db) { parent::__construct('#__history', 'version_id', $db); $this->ignoreChanges = [ 'modified_by', 'modified_user_id', 'modified', 'modified_time', 'checked_out', 'checked_out_time', 'version', 'hits', 'path', ]; $this->convertToInt = ['publish_up', 'publish_down', 'ordering', 'featured']; } /** * Overrides Table::store to set modified hash, user id, and save date. * * @param boolean $updateNulls True to update fields even if they are null. * * @return boolean True on success. * * @since 3.2 */ public function store($updateNulls = false) { $this->set('character_count', \strlen($this->get('version_data'))); $typeTable = Table::getInstance('ContentType', 'JTable', ['dbo' => $this->getDbo()]); $typeAlias = explode('.', $this->item_id); array_pop($typeAlias); $typeTable->load(['type_alias' => implode('.', $typeAlias)]); if (!isset($this->sha1_hash)) { $this->set('sha1_hash', $this->getSha1($this->get('version_data'), $typeTable)); } // Modify author and date only when not toggling Keep Forever if ($this->get('keep_forever') === null) { $this->set('editor_user_id', Factory::getUser()->id); $this->set('save_date', Factory::getDate()->toSql()); } return parent::store($updateNulls); } /** * Utility method to get the hash after removing selected values. This lets us detect changes other than * modified date (which will change on every save). * * @param mixed $jsonData Either an object or a string with json-encoded data * @param ContentType $typeTable Table object with data for this content type * * @return string SHA1 hash on success. Empty string on failure. * * @since 3.2 */ public function getSha1($jsonData, ContentType $typeTable) { $object = \is_object($jsonData) ? $jsonData : json_decode($jsonData); if (isset($typeTable->content_history_options) && \is_object(json_decode($typeTable->content_history_options))) { $options = json_decode($typeTable->content_history_options); $this->ignoreChanges = $options->ignoreChanges ?? $this->ignoreChanges; $this->convertToInt = $options->convertToInt ?? $this->convertToInt; } foreach ($this->ignoreChanges as $remove) { if (property_exists($object, $remove)) { unset($object->$remove); } } // Convert integers, booleans, and nulls to strings to get a consistent hash value foreach ($object as $name => $value) { if (\is_object($value)) { // Go one level down for JSON column values foreach ($value as $subName => $subValue) { $object->$subName = \is_int($subValue) || \is_bool($subValue) || $subValue === null ? (string) $subValue : $subValue; } } else { $object->$name = \is_int($value) || \is_bool($value) || $value === null ? (string) $value : $value; } } // Work around empty values foreach ($this->convertToInt as $convert) { if (isset($object->$convert)) { $object->$convert = (int) $object->$convert; } } if (isset($object->review_time)) { $object->review_time = (int) $object->review_time; } return sha1(json_encode($object)); } /** * Utility method to get a matching row based on the hash value and id columns. * This lets us check to make sure we don't save duplicate versions. * * @return string SHA1 hash on success. Empty string on failure. * * @since 3.2 */ public function getHashMatch() { $db = $this->_db; $itemId = $this->get('item_id'); $sha1Hash = $this->get('sha1_hash'); $query = $db->getQuery(true); $query->select('*') ->from($db->quoteName('#__history')) ->where($db->quoteName('item_id') . ' = :item_id') ->where($db->quoteName('sha1_hash') . ' = :sha1_hash') ->bind(':item_id', $itemId, ParameterType::STRING) ->bind(':sha1_hash', $sha1Hash); $query->setLimit(1); $db->setQuery($query); return $db->loadObject(); } /** * Utility method to remove the oldest versions of an item, saving only the most recent versions. * * @param integer $maxVersions The maximum number of versions to save. All others will be deleted. * * @return boolean true on success, false on failure. * * @since 3.2 */ public function deleteOldVersions($maxVersions) { $result = true; // Get the list of version_id values we want to save $db = $this->_db; $itemId = $this->get('item_id'); $query = $db->getQuery(true); $query->select($db->quoteName('version_id')) ->from($db->quoteName('#__history')) ->where($db->quoteName('item_id') . ' = :item_id') ->where($db->quoteName('keep_forever') . ' != 1') ->bind(':item_id', $itemId, ParameterType::STRING) ->order($db->quoteName('save_date') . ' DESC '); $query->setLimit((int) $maxVersions); $db->setQuery($query); $idsToSave = $db->loadColumn(0); // Don't process delete query unless we have at least the maximum allowed versions if (\count($idsToSave) === (int) $maxVersions) { // Delete any rows not in our list and and not flagged to keep forever. $query = $db->getQuery(true); $query->delete($db->quoteName('#__history')) ->where($db->quoteName('item_id') . ' = :item_id') ->whereNotIn($db->quoteName('version_id'), $idsToSave) ->where($db->quoteName('keep_forever') . ' != 1') ->bind(':item_id', $itemId, ParameterType::STRING); $db->setQuery($query); $result = (bool) $db->execute(); } return $result; } } MenuTable.php 0000644 00000005061 15172774314 0007146 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\Table; use Joomla\CMS\Language\Text; use Joomla\CMS\Table\Menu; use Joomla\Database\ParameterType; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu table * * @since 1.6 */ class MenuTable extends Menu { /** * Method to delete a node and, optionally, its child nodes from the table. * * @param integer $pk The primary key of the node to delete. * @param boolean $children True to delete child nodes, false to move them up a level. * * @return boolean True on success. * * @since 2.5 */ public function delete($pk = null, $children = false) { $return = parent::delete($pk, $children); if ($return) { // Delete key from the #__modules_menu table $db = $this->getDbo(); $query = $db->getQuery(true) ->delete($db->quoteName('#__modules_menu')) ->where($db->quoteName('menuid') . ' = :pk') ->bind(':pk', $pk, ParameterType::INTEGER); $db->setQuery($query); $db->execute(); } return $return; } /** * Overloaded check function * * @return boolean True on success, false on failure * * @see JTable::check * @since 4.0.0 */ public function check() { $return = parent::check(); if ($return) { // Set publish_up to null date if not set if (!$this->publish_up) { $this->publish_up = null; } // Set publish_down to null date if not set if (!$this->publish_down) { $this->publish_down = null; } // Check the publish down date is not earlier than publish up. if (!is_null($this->publish_down) && !is_null($this->publish_up) && $this->publish_down < $this->publish_up) { $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH')); return false; } if ((int) $this->home) { // Set the publish down/up always for home. $this->publish_up = null; $this->publish_down = null; } } return $return; } } MenuTypeTable.php 0000644 00000000762 15172774314 0010013 0 ustar 00 <?php /** * @package Joomla.Administrator * @subpackage com_menus * * @copyright (C) 2017 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\Table; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Menu table * * @since 1.6 */ class MenuTypeTable extends \Joomla\CMS\Table\MenuType { }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings