Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla4/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla4/Table.tar

Asset.php000064400000011141151155731250006337 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * 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   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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(array('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()
	{
		$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 \JDatabaseQuery object
			$query = $this->_db->getQuery(true)
				->select('1')
				->from($this->_db->quoteName($this->_tbl))
				->where($this->_db->quoteName('id') . ' =
' . $this->parent_id);

			if ($this->_db->setQuery($query, 0, 1)->loadResult())
			{
				return true;
			}

			$this->setError(\JText::_('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 (property_exists($this, '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;
	}
}
Category.php000064400000013433151155731250007043 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\ContentHistory;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\Registry\Registry;

/**
 * Category table
 *
 * @since  1.5
 */
class Category extends Nested
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__categories', 'id', $db);

		Tags::createObserver($this, array('typeAlias' =>
'{extension}.category'));
		ContentHistory::createObserver($this, array('typeAlias' =>
'{extension}.category'));

		$this->access = (int)
\JFactory::getConfig()->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 JTable 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') . ' =
' . $this->parent_id);

			// Get the asset id from the database.
			$this->_db->setQuery($query);

			if ($result = $this->_db->loadResult())
			{
				$assetId = (int) $result;
			}
		}
		// This is a category that needs to parent with the extension.
		elseif ($assetId === null)
		{
			// 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') . ' =
' . $this->_db->quote($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()
	{
		// Check for a title.
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('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 =
\JFactory::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 = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::getUser();

		$this->modified_time = $date->toSql();

		if ($this->id)
		{
			// Existing category
			$this->modified_user_id = $user->get('id');
		}
		else
		{
			// New category. A category created_time and 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->created_time)
			{
				$this->created_time = $date->toSql();
			}

			if (empty($this->created_user_id))
			{
				$this->created_user_id = $user->get('id');
			}
		}

		// Verify that the alias is unique
		$table = Table::getInstance('Category', 'JTable',
array('dbo' => $this->getDbo()));

		if ($table->load(array('alias' => $this->alias,
'parent_id' => (int) $this->parent_id,
'extension' => $this->extension))
			&& ($table->id != $this->id || $this->id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_CATEGORY_UNIQUE_ALIAS'));

			return false;
		}

		return parent::store($updateNulls);
	}
}
Content.php000064400000021427151155731250006702 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\CMS\Table\Observer\ContentHistory as ContentHistoryObserver;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;

/**
 * Content table
 *
 * @since       1.5
 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
 */
class Content extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   1.5
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__content', 'id', $db);

		Tags::createObserver($this, array('typeAlias' =>
'com_content.article'));
		ContentHistoryObserver::createObserver($this, array('typeAlias'
=> 'com_content.article'));

		// 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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;

		// This is an article under a category.
		if ($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') . ' =
' . (int) $this->catid);

			// 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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	public function check()
	{
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('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 =
\JFactory::getDate()->format('Y-m-d-H-i-s');
		}

		if (trim(str_replace('&nbsp;', '',
$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 = '{}';
			}
		}

		// Check the publish down date is not earlier than publish up.
		if ($this->publish_down < $this->publish_up &&
$this->publish_down > $this->_db->getNullDate())
		{
			// 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
			$bad_characters = array("\n", "\r",
"\"", '<', '>');

			// Remove bad characters
			$after_clean = StringHelper::str_ireplace($bad_characters, '',
$this->metakey);

			// Create array using commas as delimiter
			$keys = explode(',', $after_clean);

			$clean_keys = array();

			foreach ($keys as $key)
			{
				if (trim($key))
				{
					// Ignore blank keywords
					$clean_keys[] = trim($key);
				}
			}

			// Put array back together delimited by ", "
			$this->metakey = implode(', ', $clean_keys);
		}

		return true;
	}

	/**
	 * Gets the default asset values for a component.
	 *
	 * @param   string  $component  The component asset name to search for
	 *
	 * @return  Rules  The Rules object for the asset
	 *
	 * @since   3.4
	 * @deprecated  3.4 Class will be removed upon completion of transition to
UCM
	 */
	protected function getDefaultAssetValues($component)
	{
		// Need to find the asset id by the name of the component.
		$db = $this->getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from($db->quoteName('#__assets'))
			->where($db->quoteName('name') . ' = ' .
$db->quote($component));
		$db->setQuery($query);
		$assetId = (int) $db->loadResult();

		return Access::getAssetRules($assetId);
	}

	/**
	 * 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
	 * @deprecated  3.1.4 Class will be removed upon completion of transition
to UCM
	 */
	public function store($updateNulls = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::getUser();

		$this->modified = $date->toSql();

		if ($this->id)
		{
			// Existing item
			$this->modified_by = $user->get('id');
		}
		else
		{
			// New article. An article created and created_by field can be set by
the user,
			// so we don't touch either of these if they are set.
			if (!(int) $this->created)
			{
				$this->created = $date->toSql();
			}

			if (empty($this->created_by))
			{
				$this->created_by = $user->get('id');
			}
		}

		// Verify that the alias is unique
		$table = Table::getInstance('Content', 'JTable',
array('dbo' => $this->getDbo()));

		if ($table->load(array('alias' => $this->alias,
'catid' => $this->catid)) && ($table->id !=
$this->id || $this->id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_ARTICLE_UNIQUE_ALIAS'));

			return false;
		}

		return parent::store($updateNulls);
	}
}
ContentHistory.php000064400000015137151155731250010265 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * 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();

	/**
	 * 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 = array();

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_history', 'version_id',
$db);
		$this->ignoreChanges = array(
			'modified_by',
			'modified_user_id',
			'modified',
			'modified_time',
			'checked_out',
			'checked_out_time',
			'version',
			'hits',
			'path',
		);
		$this->convertToInt  = array('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', array('dbo' => $this->getDbo()));
		$typeTable->load($this->ucm_type_id);

		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', \JFactory::getUser()->id);
			$this->set('save_date', \JFactory::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 = isset($options->ignoreChanges) ?
$options->ignoreChanges : $this->ignoreChanges;
			$this->convertToInt = isset($options->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;
		$query = $db->getQuery(true);
		$query->select('*')
			->from($db->quoteName('#__ucm_history'))
			->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
			->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
			->where($db->quoteName('sha1_hash') . ' = ' .
$db->quote($this->get('sha1_hash')));
		$db->setQuery($query, 0, 1);

		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;
		$query = $db->getQuery(true);
		$query->select($db->quoteName('version_id'))
			->from($db->quoteName('#__ucm_history'))
			->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
			->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
			->where($db->quoteName('keep_forever') . ' !=
1')
			->order($db->quoteName('save_date') . ' DESC
');
		$db->setQuery($query, 0, (int) $maxVersions);
		$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('#__ucm_history'))
				->where($db->quoteName('ucm_item_id') . ' = '
. (int) $this->get('ucm_item_id'))
				->where($db->quoteName('ucm_type_id') . ' = '
. (int) $this->get('ucm_type_id'))
				->where($db->quoteName('version_id') . ' NOT IN
(' . implode(',', $idsToSave) . ')')
				->where($db->quoteName('keep_forever') . ' !=
1');
			$db->setQuery($query);
			$result = (boolean) $db->execute();
		}

		return $result;
	}
}
ContentType.php000064400000007010151155731250007534 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Tags table
 *
 * @since  3.1
 */
class ContentType extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($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()
	{
		// 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',
array('dbo' => $this->getDbo()));

		if ($table->load(array('type_alias' =>
$this->type_alias)) && ($table->type_id != $this->type_id
|| $this->type_id == 0))
		{
			$this->setError(\JText::_('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') . ' = ' .
$db->quote($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 = isset($tableInfo->special->class) ?
$tableInfo->special->class : 'Joomla\\CMS\\Table\\Table';

				if (!class_implements($class,
'Joomla\\CMS\\Table\\TableInterface'))
				{
					// This isn't an instance of TableInterface. Abort.
					throw new \RuntimeException('Class must be an instance of
Joomla\\CMS\\Table\\TableInterface');
				}

				$result = $class::getInstance($tableInfo->special->type,
$tableInfo->special->prefix);
			}
		}

		return $result;
	}
}
CoreContent.php000064400000025110151155731250007504 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

/**
 * Core content table
 *
 * @since  3.1
 */
class CoreContent extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_content',
'core_content_id', $db);
	}

	/**
	 * 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   3.1
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['core_params']) &&
is_array($array['core_params']))
		{
			$registry = new Registry($array['core_params']);
			$array['core_params'] = (string) $registry;
		}

		if (isset($array['core_metadata']) &&
is_array($array['core_metadata']))
		{
			$registry = new Registry($array['core_metadata']);
			$array['core_metadata'] = (string) $registry;
		}

		if (isset($array['core_images']) &&
is_array($array['core_images']))
		{
			$registry = new Registry($array['core_images']);
			$array['core_images'] = (string) $registry;
		}

		if (isset($array['core_urls']) &&
is_array($array['core_urls']))
		{
			$registry = new Registry($array['core_urls']);
			$array['core_urls'] = (string) $registry;
		}

		if (isset($array['core_body']) &&
is_array($array['core_body']))
		{
			$registry = new Registry($array['core_body']);
			$array['core_body'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @see     Table::check()
	 * @since   3.1
	 */
	public function check()
	{
		if (trim($this->core_title) === '')
		{
			$this->setError(\JText::_('JLIB_CMS_WARNING_PROVIDE_VALID_NAME'));

			return false;
		}

		if (trim($this->core_alias) === '')
		{
			$this->core_alias = $this->core_title;
		}

		$this->core_alias =
\JApplicationHelper::stringURLSafe($this->core_alias);

		if (trim(str_replace('-', '', $this->core_alias))
=== '')
		{
			$this->core_alias =
\JFactory::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_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 = array("\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 = array();

			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',
array('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)
	{
		if ($contentItemId === null || ((int) $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') . '
= ' . (int) $contentItemId)
			->where($db->quoteName('core_type_alias') . ' =
' . $db->quote($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 = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::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');
			}

			$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 = false, $isNew = false)
	{
		// Store the ucm_base row
		$db         = $this->getDbo();
		$query      = $db->getQuery(true);
		$languageId = \JHelperContent::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(array($db->quoteName('ucm_id'),
$db->quoteName('ucm_item_id'),
$db->quoteName('ucm_type_id'),
$db->quoteName('ucm_language_id')))
				->values(
					$db->quote($this->core_content_id) . ', '
					. $db->quote($this->core_content_item_id) . ', '
					. $db->quote($this->core_type_id) . ', '
					. $db->quote($languageId)
			);
		}
		else
		{
			$query->update($db->quoteName('#__ucm_base'))
				->set($db->quoteName('ucm_item_id') . ' = ' .
$db->quote($this->core_content_item_id))
				->set($db->quoteName('ucm_type_id') . ' = ' .
$db->quote($this->core_type_id))
				->set($db->quoteName('ucm_language_id') . ' =
' . $db->quote($languageId))
				->where($db->quoteName('ucm_id') . ' = ' .
$db->quote($this->core_content_id));
		}

		$db->setQuery($query);

		return $db->execute();
	}

	/**
	 * 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.
	 *
	 * @since   3.1
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		$k = $this->_tbl_key;

		// Sanitize input.
		$pks    = ArrayHelper::toInteger($pks);
		$userId = (int) $userId;
		$state  = (int) $state;

		// If there are no primary keys set check to see if the instance key is
set.
		if (empty($pks))
		{
			if ($this->$k)
			{
				$pks = array($this->$k);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

				return false;
			}
		}

		$pksImploded = implode(',', $pks);

		// Get the JDatabaseQuery object
		$query = $this->_db->getQuery(true);

		// Update the publishing state for rows with the given primary keys.
		$query->update($this->_db->quoteName($this->_tbl))
			->set($this->_db->quoteName('core_state') . ' =
' . (int) $state)
			->where($this->_db->quoteName($k) . 'IN (' .
$pksImploded . ')');

		// Determine if there is checkin support for the table.
		$checkin = false;

		if (property_exists($this, 'core_checked_out_user_id')
&& property_exists($this, 'core_checked_out_time'))
		{
			$checkin = true;
			$query->where(
				' ('
				. $this->_db->quoteName('core_checked_out_user_id') .
' = 0 OR ' .
$this->_db->quoteName('core_checked_out_user_id') . '
= ' . (int) $userId
				. ')'
			);
		}

		$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())
		{
			// Checkin the rows.
			foreach ($pks as $pk)
			{
				$this->checkin($pk);
			}
		}

		// If the JTable instance value is in the list of primary keys that were
set, set the instance.
		if (in_array($this->$k, $pks))
		{
			$this->core_state = $state;
		}

		$this->setError('');

		return true;
	}
}
Extension.php000064400000011542151155731250007241 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

/**
 * Extension table
 *
 * @since  1.7.0
 */
class Extension extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->element) ==
'')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		if (!$this->extension_id)
		{
			if (!$this->custom_data)
			{
				$this->custom_data = '';
			}

			if (!$this->system_data)
			{
				$this->system_data = '';
			}
		}

		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.7.0
	 */
	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['control']) &&
is_array($array['control']))
		{
			$registry = new Registry($array['control']);
			$array['control'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * 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 = array())
	{
		// Get the \JDatabaseQuery 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();
	}

	/**
	 * 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.
	 *
	 * @since   1.7.0
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		$k = $this->_tbl_key;

		// Sanitize input.
		$pks = ArrayHelper::toInteger($pks);
		$userId = (int) $userId;
		$state = (int) $state;

		// If there are no primary keys set check to see if the instance key is
set.
		if (empty($pks))
		{
			if ($this->$k)
			{
				$pks = array($this->$k);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

				return false;
			}
		}

		// Build the WHERE clause for the primary keys.
		$where = $k . '=' . implode(' OR ' . $k .
'=', $pks);

		// Determine if there is checkin support for the table.
		if (!property_exists($this, 'checked_out') ||
!property_exists($this, 'checked_out_time'))
		{
			$checkin = '';
		}
		else
		{
			$checkin = ' AND (checked_out = 0 OR checked_out = ' . (int)
$userId . ')';
		}

		// 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('enabled') . ' =
' . (int) $state)
			->where('(' . $where . ')' . $checkin);
		$this->_db->setQuery($query);
		$this->_db->execute();

		// If checkin is supported and all rows were adjusted, check them in.
		if ($checkin && (count($pks) ==
$this->_db->getAffectedRows()))
		{
			// Checkin the rows.
			foreach ($pks as $pk)
			{
				$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->enabled = $state;
		}

		$this->setError('');

		return true;
	}
}
Language.php000064400000006410151155731250007006 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Languages table.
 *
 * @since  1.7.0
 */
class Language extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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()
	{
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('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',
array('dbo' => $this->getDbo()));

		// Verify that the language code is unique
		if ($table->load(array('lang_code' =>
$this->lang_code)) && ($table->lang_id != $this->lang_id
|| $this->lang_id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_LANG_CODE'));

			return false;
		}

		// Verify that the sef field is unique
		if ($table->load(array('sef' => $this->sef))
&& ($table->lang_id != $this->lang_id || $this->lang_id ==
0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE'));

			return false;
		}

		// Verify that the image field is unique
		if ($this->image && $table->load(array('image'
=> $this->image)) && ($table->lang_id != $this->lang_id
|| $this->lang_id == 0))
		{
			$this->setError(\JText::_('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 === null ? parent::_getAssetParentId($table, $id) :
$assetId;
	}
}
Menu.php000064400000017654151155731250006203 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Registry\Registry;

/**
 * Menu table
 *
 * @since  1.5
 */
class Menu extends Nested
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__menu', 'id', $db);

		// Set the default access level.
		$this->access = (int)
\JFactory::getConfig()->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(\JText::_('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(\JText::_('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(\JText::_('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()
	{
		// Check for a title.
		if (trim($this->title) === '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MENUITEM'));

			return false;
		}

		// Check for a path.
		if (trim($this->path) === '')
		{
			$this->path = $this->alias;
		}

		// Check for params.
		if (trim($this->params) === '')
		{
			$this->params = '{}';
		}

		// Check for img.
		if (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(\JText::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_COMPONENT'));

			return false;
		}

		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 = false)
	{
		$db = $this->getDbo();

		// Verify that the alias is unique
		$table = Table::getInstance('Menu', 'JTable',
array('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(\JText::_('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_COMPONENT'));

				return false;
			}

			// Verify that a first level menu item alias is not the name of a
folder.
			jimport('joomla.filesystem.folder');

			if (in_array($this->alias, \JFolder::folders(JPATH_ROOT)))
			{
				$this->setError(\JText::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 =
\JFactory::getDate()->format('Y-m-d-H-i-s');
		}
		else
		{
			$itemSearch = array('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,
array('language' => '*'))) && ($table->id
!= $this->id || $this->id == 0))
					|| ($table->load(array_replace($itemSearch,
array('language' => $this->language))) &&
($table->id != $this->id || $this->id == 0))
					|| ($this->language === '*' && $this->id == 0
&& $table->load($itemSearch)))
				{
					$error = true;
				}
				// When editing an item with All language check if there are more menu
items with the same alias in any language.
				elseif ($this->language === '*' && $this->id !=
0)
				{
					$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') . ' != ' .
(int) $this->id)
						->where($db->quoteName('alias') . ' = ' .
$db->quote($this->alias));

					$otherMenuItemId = (int) $db->setQuery($query)->loadResult();

					if ($otherMenuItemId)
					{
						$table->load(array('id' => $otherMenuItemId));
						$error = true;
					}
				}
			}
			// Check if the alias already exists. For monolingual site.
			else
			{
				// 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', array('dbo' => $db));
				$menuTypeTable->load(array('menutype' =>
$table->menutype));
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENU_UNIQUE_ALIAS',
$this->alias, $table->title, $menuTypeTable->title));

				return false;
			}
		}

		if ($this->home == '1')
		{
			// Verify that the home page for this menu is unique.
			if ($table->load(
					array(
					'menutype' => $this->menutype,
					'client_id' => (int) $this->client_id,
					'home' => '1',
					)
				)
				&& ($table->language != $this->language))
			{
				$this->setError(\JText::_('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(array('home' => '1',
'language' => $this->language, 'client_id' =>
(int) $this->client_id)))
			{
				if ($table->checked_out && $table->checked_out !=
$this->checked_out)
				{
					$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_DEFAULT_CHECKIN_USER_MISMATCH'));

					return false;
				}

				$table->home = 0;
				$table->checked_out = 0;
				$table->checked_out_time = $db->getNullDate();
				$table->store();
			}
		}

		if (!parent::store($updateNulls))
		{
			return false;
		}

		// Get the new path in case the node was moved
		$pathNodes = $this->getPath();
		$segments = array();

		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;
	}
}
MenuType.php000064400000017244151155731250007040 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;

/**
 * Menu Types table
 *
 * @since  1.6
 */
class MenuType extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.6
	 */
	public function __construct(\JDatabaseDriver $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()
	{
		$this->menutype =
ApplicationHelper::stringURLSafe($this->menutype);

		if (empty($this->menutype))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_EMPTY'));

			return false;
		}

		// Sanitise data.
		if (trim($this->title) === '')
		{
			$this->title = $this->menutype;
		}

		// Check for unique menutype.
		$query = $this->_db->getQuery(true)
			->select('COUNT(id)')
			->from($this->_db->quoteName('#__menu_types'))
			->where($this->_db->quoteName('menutype') . ' =
' . $this->_db->quote($this->menutype))
			->where($this->_db->quoteName('id') . ' <>
' . (int) $this->id);
		$this->_db->setQuery($query);

		if ($this->_db->loadResult())
		{
			$this->setError(\JText::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 = \JFactory::getUser()->id;

			// Get the old value of the table
			$table = Table::getInstance('Menutype', 'JTable',
array('dbo' => $this->getDbo()));
			$table->load($this->id);

			// Verify that no items are checked out
			$query = $this->_db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype=' .
$this->_db->quote($table->menutype))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(
					\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
get_class($this),
\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
				);

				return false;
			}

			// Verify that no module for this menu are checked out
			$query->clear()
				->select('id')
				->from('#__modules')
				->where('module=' .
$this->_db->quote('mod_menu'))
				->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(
					\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
get_class($this),
\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
				);

				return false;
			}

			// Update the menu items
			$query->clear()
				->update('#__menu')
				->set('menutype=' .
$this->_db->quote($this->menutype))
				->where('menutype=' .
$this->_db->quote($table->menutype));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the module items
			$query->clear()
				->update('#__modules')
				->set(
				'params=REPLACE(params,' .
$this->_db->quote('"menutype":' .
json_encode($table->menutype)) . ',' .
				$this->_db->quote('"menutype":' .
json_encode($this->menutype)) . ')'
			);
			$query->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::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 = \JFactory::getUser()->id;

			// Get the old value of the table
			$table = Table::getInstance('Menutype', 'JTable',
array('dbo' => $this->getDbo()));
			$table->load($pk);

			// Verify that no items are checked out
			$query = $this->_db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype=' .
$this->_db->quote($table->menutype))
				->where('(checked_out NOT IN (0,' . (int) $userId .
') OR home=1 AND language=' .
$this->_db->quote('*') . ')');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED',
get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE')));

				return false;
			}

			// Verify that no module for this menu are checked out
			$query->clear()
				->select('id')
				->from('#__modules')
				->where('module=' .
$this->_db->quote('mod_menu'))
				->where('params LIKE ' .
$this->_db->quote('%"menutype":' .
json_encode($table->menutype) . '%'))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED',
get_class($this), \JText::_('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;
	}
}
Module.php000064400000010506151155731250006511 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Rules;
use Joomla\Registry\Registry;

/**
 * Module table
 *
 * @since  1.5
 */
class Module extends Table
{
	/**
	 * Constructor.
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__modules', 'id', $db);

		$this->access = (int)
\JFactory::getConfig()->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()
	{
		// Check for valid name
		if (trim($this->title) === '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MODULE'));

			return false;
		}

		// Prevent to save too large content > 65535 
		if ((strlen($this->content) > 65535) || (strlen($this->params)
> 65535))
		{
			$this->setError(\JText::_('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 = false)
	{
		// Set publish_up, publish_down and checked_out_time to null date if not
set
		if (!$this->publish_up)
		{
			$this->publish_up = $this->_db->getNullDate();
		}

		if (!$this->publish_down)
		{
			$this->publish_down = $this->_db->getNullDate();
		}

		return parent::store($updateNulls);
	}
}
Nested.php000064400000140530151155731250006507 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * 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 constuct 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 = array();

	/**
	 * 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 = array('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;
			}
		}
		// We are moving the tree to be the last child of the root node
		else
		{
			// 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');
			$this->_db->setQuery($query, 0, 1);
			$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 (property_exists($this, '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 (property_exists($this, '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;

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeDelete', array($pk));

		// Lock the table for writing.
		if (!$this->_lock())
		{
			// Error message set in lock method.
			return false;
		}

		// If tracking assets, remove the asset first.
		if ($this->_trackAssets)
		{
			$name = $this->_getAssetName();
			$asset = Table::getInstance('Asset', 'JTable',
array('dbo' => $this->getDbo()));

			// Lock the table for writing.
			if (!$asset->_lock())
			{
				// Error message set in lock method.
				return false;
			}

			if ($asset->loadByName($name))
			{
				// Delete the node in assets table.
				if (!$asset->delete(null, $children))
				{
					$this->setError($asset->getError());
					$asset->_unlock();

					return false;
				}

				$asset->_unlock();
			}
			else
			{
				$this->setError($asset->getError());
				$asset->_unlock();

				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');
		}
		// Leave the children and move them up a level.
		else
		{
			// 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();

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterDelete', array($pk));

		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()
	{
		$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;

		// Implement \JObservableInterface: Pre-processing by observers
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->update('onBeforeStore',
array($updateNulls, $k));
		}

		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');
					$this->_db->setQuery($query, 0, 1);
					$reference = $this->_db->loadObject();

					if ($this->_debug)
					{
						$this->_logtable(false);
					}
				}
				// We have a real node set as a location reference.
				else
				{
					// 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;
			}
		}
		/*
		 * 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.
		 */
		else
		{
			// 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;
			}
		}

		// Implement \JObservableInterface: We do not want parent::store to
update observers,
		// since tables are locked and we are updating it from this level of
store():

		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$oldCallObservers = $this->_observers->doCallObservers(false);
		}

		$result = parent::store($updateNulls);

		// Implement \JObservableInterface: Restore previous callable observers
state:
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->doCallObservers($oldCallObservers);
		}

		if ($result)
		{
			if ($this->_debug)
			{
				$this->_logtable();
			}
		}

		// Unlock the table for writing.
		$this->_unlock();

		if (property_exists($this, 'published'))
		{
			$this->recursiveUpdatePublishedColumn($this->$k);
		}

		// Implement \JObservableInterface: Post-processing by observers
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->update('onAfterStore',
array(&$result));
		}

		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'));
		$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);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$e = new \UnexpectedValueException(sprintf('%s::publish(%s, %d,
%d) empty.', get_class($this), $pks[0], $state, $userId));
				$this->setError($e);

				return false;
			}
		}

		// Determine if there is checkout support for the table.
		$checkoutSupport = (property_exists($this, 'checked_out') ||
property_exists($this, '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('(checked_out <> 0 AND checked_out <>
' . (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).
				$this->_db->setQuery($query, 0, 1);

				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.
			$query->_unlock($this->_db);
			$this->_locked = false;

			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.
			$orderingField = $this->getColumnAlias('ordering');

			if (property_exists($this, $orderingField))
			{
				$query->order('parent_id, ' .
$this->_db->quoteName($orderingField) . ', 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  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 AS c")
			->innerJoin("($subquery) AS c2 ON c2.newId = c.$key")
			->set("$published = c2.newPublished")
			->where("c.$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);

		$row = $this->_db->setQuery($query, 0, 1)->loadObject();

		// Check for no $row returned
		if (empty($row))
		{
			$e = new \UnexpectedValueException(sprintf('%s::_getNode(%d, %s)
failed.', get_class($this), $id, $key));
			$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 \JDatabaseQuery object.
	 * @param   string  $errorMessage  Unused.
	 *
	 * @return  boolean  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;
		}
	}
}
Observer/AbstractObserver.php000064400000005131151155731250012324
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Table\Table;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  3.1.2
 */
abstract class AbstractObserver implements \JObserverInterface
{
	/**
	 * The observed table
	 *
	 * @var    Table
	 * @since  3.1.2
	 */
	protected $table;

	/**
	 * Constructor: Associates to $table $this observer
	 *
	 * @param   TableInterface  $table  Table to be observed
	 *
	 * @since   3.1.2
	 */
	public function __construct(TableInterface $table)
	{
		$table->attachObserver($this);
		$this->table = $table;
	}

	/**
	 * Pre-processor for $table->load($keys, $reset)
	 *
	 * @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  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeLoad($keys, $reset)
	{
	}

	/**
	 * Post-processor for $table->load($keys, $reset)
	 *
	 * @param   boolean  &$result  The result of the load
	 * @param   array    $row      The loaded (and already binded to
$this->table) row of the database table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterLoad(&$result, $row)
	{
	}

	/**
	 * Pre-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  $updateNulls  The result of the load
	 * @param   string   $tableKey     The key of the table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeStore($updateNulls, $tableKey)
	{
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  &$result  The result of the store
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterStore(&$result)
	{
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not
set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
	}

	/**
	 * Post-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  The deleted primary key value.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterDelete($pk)
	{
	}
}
Observer/ContentHistory.php000064400000007114151155731250012050
0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  3.2
 */
class ContentHistory extends AbstractObserver
{
	/**
	 * Helper object for storing and deleting version history information
associated with this table observer
	 *
	 * @var    \JHelperContenthistory
	 * @since  3.2
	 */
	protected $contenthistoryHelper;

	/**
	 * The pattern for this table's TypeAlias
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $typeAliasPattern = null;

	/**
	 * Not public, so marking private and deprecated, but needed internally in
parseTypeAlias for
	 * PHP < 5.4.0 as it's not passing context $this to closure
function.
	 *
	 * @var         ContentHistory
	 * @since       3.2
	 * @deprecated  Never use this
	 * @private
	 */
	public static $_myTableForPregreplaceOnly;

	/**
	 * Creates the associated observer instance and attaches it to the
$observableObject
	 * Creates the associated content history helper class instance
	 * $typeAlias can be of the form "{variableName}.type",
automatically replacing {variableName} with table-instance variables
variableName
	 *
	 * @param   \JObservableInterface  $observableObject  The subject object
to be observed
	 * @param   array                  $params            (
'typeAlias' => $typeAlias )
	 *
	 * @return  ContentHistory
	 *
	 * @since   3.2
	 */
	public static function createObserver(\JObservableInterface
$observableObject, $params = array())
	{
		$typeAlias = $params['typeAlias'];

		$observer = new self($observableObject);

		$observer->contenthistoryHelper = new
\JHelperContenthistory($typeAlias);
		$observer->typeAliasPattern = $typeAlias;

		return $observer;
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  &$result  The result of the load
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function onAfterStore(&$result)
	{
		if ($result)
		{
			$this->parseTypeAlias();
			$aliasParts = explode('.',
$this->contenthistoryHelper->typeAlias);

			if
(\JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
			{
				$this->contenthistoryHelper->store($this->table);
			}
		}
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not
set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
		$this->parseTypeAlias();
		$aliasParts = explode('.',
$this->contenthistoryHelper->typeAlias);

		if
(\JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
		{
			$this->parseTypeAlias();
			$this->contenthistoryHelper->deleteHistory($this->table);
		}
	}

	/**
	 * Internal method
	 * Parses a TypeAlias of the form "{variableName}.type",
replacing {variableName} with table-instance variables variableName
	 * Storing result into $this->contenthistoryHelper->typeAlias
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function parseTypeAlias()
	{
		// Needed for PHP < 5.4.0 as it's not passing context $this to
closure function
		static::$_myTableForPregreplaceOnly = $this->table;

		$this->contenthistoryHelper->typeAlias =
preg_replace_callback('/{([^}]+)}/',
			function($matches)
			{
				return ContentHistory::$_myTableForPregreplaceOnly->{$matches[1]};
			},
			$this->typeAliasPattern
		);
	}
}
Observer/Tags.php000064400000012001151155731260007742 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

/**
 * Abstract class defining methods that can be
 * implemented by an Observer class of a Table class (which is an
Observable).
 * Attaches $this Observer to the $table in the constructor.
 * The classes extending this class should not be instantiated directly, as
they
 * are automatically instanciated by the \JObserverMapper
 *
 * @since  3.1.2
 */
class Tags extends AbstractObserver
{
	/**
	 * Helper object for managing tags
	 *
	 * @var    \JHelperTags
	 * @since  3.1.2
	 */
	protected $tagsHelper;

	/**
	 * The pattern for this table's TypeAlias
	 *
	 * @var    string
	 * @since  3.1.2
	 */
	protected $typeAliasPattern = null;

	/**
	 * Override for postStoreProcess param newTags, Set by setNewTags, used by
onAfterStore and onBeforeStore
	 *
	 * @var    array
	 * @since  3.1.2
	 */
	protected $newTags = false;

	/**
	 * Override for postStoreProcess param replaceTags. Set by setNewTags,
used by onAfterStore
	 *
	 * @var    boolean
	 * @since  3.1.2
	 */
	protected $replaceTags = true;

	/**
	 * Not public, so marking private and deprecated, but needed internally in
parseTypeAlias for
	 * PHP < 5.4.0 as it's not passing context $this to closure
function.
	 *
	 * @var         Tags
	 * @since       3.1.2
	 * @deprecated  Never use this
	 * @private
	 */
	public static $_myTableForPregreplaceOnly;

	/**
	 * Creates the associated observer instance and attaches it to the
$observableObject
	 * Creates the associated tags helper class instance
	 * $typeAlias can be of the form "{variableName}.type",
automatically replacing {variableName} with table-instance variables
variableName
	 *
	 * @param   \JObservableInterface  $observableObject  The subject object
to be observed
	 * @param   array                  $params            (
'typeAlias' => $typeAlias )
	 *
	 * @return  Tags
	 *
	 * @since   3.1.2
	 */
	public static function createObserver(\JObservableInterface
$observableObject, $params = array())
	{
		$typeAlias = $params['typeAlias'];

		$observer = new self($observableObject);

		$observer->tagsHelper = new \JHelperTags;
		$observer->typeAliasPattern = $typeAlias;

		return $observer;
	}

	/**
	 * Pre-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  $updateNulls  The result of the load
	 * @param   string   $tableKey     The key of the table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeStore($updateNulls, $tableKey)
	{
		$this->parseTypeAlias();

		if (empty($this->table->tagsHelper->tags))
		{
			$this->tagsHelper->preStoreProcess($this->table);
		}
		else
		{
			$this->tagsHelper->preStoreProcess($this->table, (array)
$this->table->tagsHelper->tags);
		}
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 * You can change optional params newTags and replaceTags of tagsHelper
with method setNewTagsToAdd
	 *
	 * @param   boolean  &$result  The result of the load
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterStore(&$result)
	{
		if ($result)
		{
			if (empty($this->table->tagsHelper->tags))
			{
				$result = $this->tagsHelper->postStoreProcess($this->table);
			}
			else
			{
				$result = $this->tagsHelper->postStoreProcess($this->table,
$this->table->tagsHelper->tags);
			}

			// Restore default values for the optional params:
			$this->newTags = array();
			$this->replaceTags = true;
		}
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not
set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
		$this->parseTypeAlias();
		$this->tagsHelper->deleteTagData($this->table, $pk);
	}

	/**
	 * Sets the new tags to be added or to replace existing tags
	 *
	 * @param   array    $newTags      New tags to be added to or replace
current tags for an item
	 * @param   boolean  $replaceTags  Replace tags (true) or add them (false)
	 *
	 * @return  boolean
	 *
	 * @since   3.1.2
	 */
	public function setNewTags($newTags, $replaceTags)
	{
		$this->parseTypeAlias();

		return $this->tagsHelper->postStoreProcess($this->table,
$newTags, $replaceTags);
	}

	/**
	 * Internal method
	 * Parses a TypeAlias of the form "{variableName}.type",
replacing {variableName} with table-instance variables variableName
	 * Storing result into $this->tagsHelper->typeAlias
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	protected function parseTypeAlias()
	{
		// Needed for PHP < 5.4.0 as it's not passing context $this to
closure function
		static::$_myTableForPregreplaceOnly = $this->table;

		$this->tagsHelper->typeAlias =
preg_replace_callback('/{([^}]+)}/',
			function($matches)
			{
				return Tags::$_myTableForPregreplaceOnly->{$matches[1]};
			},
			$this->typeAliasPattern
		);
	}
}
Table.php000064400000125036151155731260006321 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

\JLoader::import('joomla.filesystem.path');

/**
 * Abstract Table class
 *
 * Parent class to all tables.
 *
 * @since  1.7.0
 * @tutorial  Joomla.Platform/jtable.cls
 */
abstract class Table extends \JObject implements \JObservableInterface,
\JTableInterface
{
	/**
	 * Include paths for searching for Table classes.
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	private static $_includePaths = array();

	/**
	 * 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 = array();

	/**
	 * \JDatabaseDriver object.
	 *
	 * @var    \JDatabaseDriver
	 * @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    \JAccessRules  A \JAccessRules 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;

	/**
	 * Generic observers for this Table (Used e.g. for tags Processing)
	 *
	 * @var    \JObserverUpdater
	 * @since  3.1.2
	 */
	protected $_observers;

	/**
	 * Array with alias for "special" columns such as ordering, hits
etc etc
	 *
	 * @var    array
	 * @since  3.4.0
	 */
	protected $_columnAlias = array();

	/**
	 * An array of key names to be json encoded in the bind function
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $_jsonEncode = array();

	/**
	 * 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   \JDatabaseDriver  $db     \JDatabaseDriver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($table, $key, $db)
	{
		// Set internal variables.
		$this->_tbl = $table;

		// Set the key to be an array.
		if (is_string($key))
		{
			$key = array($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 (!property_exists($this, $name))
				{
					$this->$name = null;
				}
			}
		}

		// If we are tracking assets, make sure an access field exists and
initially set the default.
		if (property_exists($this, 'asset_id'))
		{
			$this->_trackAssets = true;
		}

		// If the access property exists, set the default.
		if (property_exists($this, 'access'))
		{
			$this->access = (int)
\JFactory::getConfig()->get('access');
		}

		// Implement \JObservableInterface:
		// Create observer updater and attaches all observers interested by $this
class:
		$this->_observers = new \JObserverUpdater($this);
		\JObserverMapper::attachAllObservers($this);
	}

	/**
	 * Implement \JObservableInterface:
	 * Adds an observer to this instance.
	 * This method will be called fron the constructor of classes implementing
\JObserverInterface
	 * which is instanciated by the constructor of $this with
\JObserverMapper::attachAllObservers($this)
	 *
	 * @param   \JObserverInterface|\JTableObserver  $observer  The observer
object
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function attachObserver(\JObserverInterface $observer)
	{
		$this->_observers->attachObserver($observer);
	}

	/**
	 * Gets the instance of the observer of class $observerClass
	 *
	 * @param   string  $observerClass  The observer class-name to return the
object of
	 *
	 * @return  \JTableObserver|null
	 *
	 * @since   3.1.2
	 */
	public function getObserverOfClass($observerClass)
	{
		return $this->_observers->getObserverOfClass($observerClass);
	}

	/**
	 * 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)
	{
		static $cache = null;

		if ($cache === null || $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));
			}

			$cache = $fields;
		}

		return $cache;
	}

	/**
	 * 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
	 */
	public static function getInstance($type, $prefix = 'JTable',
$config = array())
	{
		// 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.
			jimport('joomla.filesystem.path');

			$paths = self::addIncludePath();
			$pathIndex = 0;

			while (!class_exists($tableClass) && $pathIndex <
count($paths))
			{
				if ($tryThis = \JPath::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 \JFactory.
		$db = isset($config['dbo']) ? $config['dbo'] :
\JFactory::getDbo();

		// 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
	 */
	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 = array(__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 = array();

		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  \JTableAsset  $assets */
		$assets = self::getInstance('Asset', 'JTable',
array('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   \JDatabaseQuery  $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 = array($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 '';
	}

	/**
	 * Method to get the \JDatabaseDriver object.
	 *
	 * @return  \JDatabaseDriver  The internal database driver object.
	 *
	 * @since   1.7.0
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to set the \JDatabaseDriver object.
	 *
	 * @param   \JDatabaseDriver  $db  A \JDatabaseDriver object to be used by
the table object.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setDbo($db)
	{
		$this->_db = $db;

		return true;
	}

	/**
	 * Method to set rules for the record.
	 *
	 * @param   mixed  $input  A \JAccessRules object, JSON string, or array.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setRules($input)
	{
		if ($input instanceof \JAccessRules)
		{
			$this->_rules = $input;
		}
		else
		{
			$this->_rules = new \JAccessRules($input);
		}
	}

	/**
	 * Method to get the rules for the record.
	 *
	 * @return  \JAccessRules 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()
	{
		// 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 = array();
	}

	/**
	 * 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 = array())
	{
		// 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]);
				}
			}
		}

		// 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 source value is an object, get its accessible properties.
		if (is_object($src))
		{
			$src = get_object_vars($src);
		}

		// If the ignore value is a string, explode it over spaces.
		if (!is_array($ignore))
		{
			$ignore = explode(' ', $ignore);
		}

		// 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];
				}
			}
		}

		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)
	{
		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeLoad', array($keys,
$reset));

		if (empty($keys))
		{
			$empty = true;
			$keys  = array();

			// 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 = array($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->_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 &#160; %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);
		}

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterLoad',
array(&$result, $row));

		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()
	{
		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;

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeStore',
array($updateNulls, $k));

		$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);
		}

		// 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]);
		}

		// 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  \JTableAsset  $asset */
			$asset = self::getInstance('Asset', 'JTable',
array('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;
				$asset->title     = $title;

				if ($this->_rules instanceof \JAccessRules)
				{
					$asset->rules = (string) $this->_rules;
				}

				if (!$asset->check() || !$asset->store($updateNulls))
				{
					$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();
					}
				}
			}
		}

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterStore',
array(&$result));

		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 = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($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];
		}

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeDelete', array($pk));

		// If tracking assets, remove the asset first.
		if ($this->_trackAssets)
		{
			// Get the asset name
			$name  = $this->_getAssetName();
			/** @var  \JTableAsset  $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->_tbl);
		$this->appendPrimaryKeys($query, $pk);

		$this->_db->setQuery($query);

		// Check for a database error.
		$this->_db->execute();

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterDelete', array($pk));

		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)
	{
		$checkedOutField = $this->getColumnAlias('checked_out');
		$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');

		// If there is no checked_out or checked_out_time field, just return
true.
		if (!property_exists($this, $checkedOutField) || !property_exists($this,
$checkedOutTimeField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($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 the current time in the database format.
		$time = \JFactory::getDate()->toSql();

		// Check the row out by primary key.
		$query = $this->_db->getQuery(true)
			->update($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;

		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)
	{
		$checkedOutField = $this->getColumnAlias('checked_out');
		$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');

		// If there is no checked_out or checked_out_time field, just return
true.
		if (!property_exists($this, $checkedOutField) || !property_exists($this,
$checkedOutTimeField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$this->$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($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.');
			}
		}

		// Check the row in by primary key.
		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($this->_db->quoteName($checkedOutField) . ' =
0')
			->set($this->_db->quoteName($checkedOutTimeField) . ' =
' . $this->_db->quote($this->_db->getNullDate()));
		$this->appendPrimaryKeys($query, $pk);
		$this->_db->setQuery($query);

		// Check for a database error.
		$this->_db->execute();

		// Set table values in the object.
		$this->$checkedOutField      = 0;
		$this->$checkedOutTimeField = '';

		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onAfterCheckin',
array($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->_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)
	{
		$hitsField = $this->getColumnAlias('hits');

		// If there is no hits field, just return true.
		if (!property_exists($this, $hitsField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($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.');
			}
		}

		// Check the row in by primary key.
		$query = $this->_db->getQuery(true)
			->update($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++;

		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 preform 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;
		}

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('COUNT(userid)')
			->from($db->quoteName('#__session'))
			->where($db->quoteName('userid') . ' = ' .
(int) $against);
		$db->setQuery($query);
		$checkedOut = (boolean) $db->loadResult();

		// If a session exists for the user then it is checked out.
		return $checkedOut;
	}

	/**
	 * 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
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			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($orderingField) . ')')
			->from($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 = array())
	{
		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  $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
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			throw new \UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
		}

		$quotedOrderingField = $this->_db->quoteName($orderingField);

		$subquery = $this->_db->getQuery(true)
			->from($this->_tbl)
			->selectRowNumber($quotedOrderingField, 'new_ordering');

		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($quotedOrderingField . ' = sq.new_ordering');

		$innerOn = array();

		// 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
ON ' . implode(' AND ', $innerOn));

		$this->_db->setQuery($query);
		$this->_db->execute();

		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   $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
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			throw new \UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
		}

		$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->_tbl);

		// If the movement delta is negative move the row up.
		if ($delta < 0)
		{
			$query->where($quotedOrderingField . ' < ' . (int)
$this->$orderingField)
				->order($quotedOrderingField . ' DESC');
		}
		// If the movement delta is positive move the row down.
		elseif ($delta > 0)
		{
			$query->where($quotedOrderingField . ' > ' . (int)
$this->$orderingField)
				->order($quotedOrderingField . ' ASC');
		}

		// Add the custom WHERE clause if set.
		if ($where)
		{
			$query->where($where);
		}

		// Select the first row with the criteria.
		$this->_db->setQuery($query, 0, 1);
		$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->_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->_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->_tbl)
				->set($quotedOrderingField . ' = ' . (int)
$this->$orderingField);
			$this->appendPrimaryKeys($query);
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		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;

		if (!is_null($pks))
		{
			if (!is_array($pks))
			{
				$pks = array($pks);
			}

			foreach ($pks as $key => $pk)
			{
				if (!is_array($pk))
				{
					$pks[$key] = array($this->_tbl_key => $pk);
				}
			}
		}

		// If there are no primary keys set check to see if the instance key is
set.
		if (empty($pks))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				if ($this->$key)
				{
					$pk[$key] = $this->$key;
				}
				// We don't have a full primary key - return false
				else
				{
					$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

					return false;
				}
			}

			$pks = array($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->_tbl)
				->set($this->_db->quoteName($publishedField) . ' = '
. (int) $state);

			// If publishing, set published date/time if not previously set
			if ($state && property_exists($this, 'publish_up')
&& (int) $this->publish_up == 0)
			{
				$nowDate = $this->_db->quote(\JFactory::getDate()->toSql());
				$query->set($this->_db->quoteName($this->getColumnAlias('publish_up'))
. ' = ' . $nowDate);
			}

			// Determine if there is checkin support for the table.
			if (property_exists($this, 'checked_out') ||
property_exists($this, '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('');

		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)
	{
		// Santize 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()
	{
		$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.php000064400000006655151155731260010147 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * 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 = array());

	/**
	 * 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 \JDatabaseDriver object.
	 *
	 * @return  \JDatabaseDriver  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);
}
Ucm.php000064400000001064151155731260006010 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * UCM map table
 *
 * @since  3.1
 */
class Ucm extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_base', 'ucm_id', $db);
	}
}
Update.php000064400000004625151155731260006514 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Update table
 * Stores updates temporarily
 *
 * @since  1.7.0
 */
class Update extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->element) ==
'')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		if (!$this->update_id && !$this->data)
		{
			$this->data = '';
		}

		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.7.0
	 */
	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['control']) &&
is_array($array['control']))
		{
			$registry = new Registry($array['control']);
			$array['control'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * 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 = array())
	{
		$where = array();

		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();
	}
}
UpdateSite.php000064400000001763151155731260007341 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Update site table
 * Stores the update sites for extensions
 *
 * @since  3.4
 */
class UpdateSite extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   3.4
	 */
	public function __construct($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()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->location) ==
'')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		return true;
	}
}
User.php000064400000031620151155731260006203 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

/**
 * Users table
 *
 * @since  1.7.0
 */
class User extends Table
{
	/**
	 * Associative array of group ids => group ids for the user
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $groups;

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since  1.7.0
	 */
	public function __construct($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();

		// Load the user data.
		$query = $this->_db->getQuery(true)
			->select('*')
			->from($this->_db->quoteName('#__users'))
			->where($this->_db->quoteName('id') . ' = '
. (int) $userId);
		$this->_db->setQuery($query);
		$data = (array) $this->_db->loadAssoc();

		if (!count($data))
		{
			return false;
		}

		// Convert email from punycode
		$data['email'] =
\JStringPunycode::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') .
' AS g')
				->join('INNER',
$this->_db->quoteName('#__user_usergroup_map') . ' AS
m ON m.group_id = g.id')
				->where($this->_db->quoteName('m.user_id') . '
= ' . (int) $userId);
			$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'))
				->where($this->_db->quoteName('id') . ' =
' . implode(' OR ' .
$this->_db->quoteName('id') . ' = ',
$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()
	{
		// Set user id to null istead of 0, if needed
		if ($this->id === 0)
		{
			$this->id = null;
		}

		$filterInput = \JFilterInput::getInstance();

		// Validate user information
		if ($filterInput->clean($this->name, 'TRIM') ==
'')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_YOUR_NAME'));

			return false;
		}

		if ($filterInput->clean($this->username, 'TRIM') ==
'')
		{
			$this->setError(\JText::_('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(\JText::sprintf('JLIB_DATABASE_ERROR_VALID_AZ09',
2));

			return false;
		}

		if (($filterInput->clean($this->email, 'TRIM') ==
'') || !\JMailHelper::isEmailAddress($this->email)
			|| StringHelper::strlen($this->email) > 100)
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));

			return false;
		}

		// Convert email to punycode for storage
		$this->email = \JStringPunycode::emailToPunycode($this->email);

		// Set the registration timestamp
		if (empty($this->registerDate) || $this->registerDate ==
$this->_db->getNullDate())
		{
			$this->registerDate = \JFactory::getDate()->toSql();
		}

		// Set the lastvisitDate timestamp
		if (empty($this->lastvisitDate))
		{
			$this->lastvisitDate = $this->_db->getNullDate();
		}

		// Set the lastResetTime timestamp
		if (empty($this->lastResetTime))
		{
			$this->lastResetTime = $this->_db->getNullDate();
		}

		// Check for existing username
		$query = $this->_db->getQuery(true)
			->select($this->_db->quoteName('id'))
			->from($this->_db->quoteName('#__users'))
			->where($this->_db->quoteName('username') . ' =
' . $this->_db->quote($this->username))
			->where($this->_db->quoteName('id') . ' !=
' . (int) $this->id);
		$this->_db->setQuery($query);

		$xid = (int) $this->_db->loadResult();

		if ($xid && $xid != (int) $this->id)
		{
			$this->setError(\JText::_('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(' .
$this->_db->quote($this->email) . ')')
			->where($this->_db->quoteName('id') . ' !=
' . (int) $this->id);
		$this->_db->setQuery($query);
		$xid = (int) $this->_db->loadResult();

		if ($xid && $xid != (int) $this->id)
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));

			return false;
		}

		// Check for root_user != username
		$config = \JFactory::getConfig();
		$rootUser = $config->get('root_user');

		if (!is_numeric($rootUser))
		{
			$query->clear()
				->select($this->_db->quoteName('id'))
				->from($this->_db->quoteName('#__users'))
				->where($this->_db->quoteName('username') . ' =
' . $this->_db->quote($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(\JText::_('JLIB_DATABASE_ERROR_USERNAME_CANNOT_CHANGE'));

				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.7.0
	 */
	public function store($updateNulls = false)
	{
		// 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))
		{
			// 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') . ' =
' . (int) $this->id);

			$this->_db->setQuery($query);
			$result = $this->_db->loadObjectList();

			// Loop through them and check if database contains something
$this->groups does not
			if (count($result))
			{
				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
					{
						// It should be removed
						$query -> clear()
							->
delete($this->_db->quoteName('#__user_usergroup_map'))
							-> where($this->_db->quoteName('user_id') .
' = ' . (int) $this->id)
							-> where($this->_db->quoteName('group_id') .
' = ' . (int) $map->group_id);

						$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(array($this->_db->quoteName('user_id'),
$this->_db->quoteName('group_id')));

				// Have to break this up into individual queries for cross-database
support.
				foreach ($groups as $group)
				{
					$query->clear('values')
						->values($this->id . ', ' . $group);
					$this->_db->setQuery($query);
					$this->_db->execute();
				}
			}

			unset($groups);
		}

		// If a user is blocked, delete the cookie login rows
		if ($this->block == (int) 1)
		{
			$query->clear()
				->delete($this->_db->quoteName('#__user_keys'))
				->where($this->_db->quoteName('user_id') . ' =
' . $this->_db->quote($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;
		}

		// Delete the user.
		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName($this->_tbl_key) . ' =
' . (int) $this->$k);
		$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') . ' =
' . (int) $this->$k);
		$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') . ' =
' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		$query->clear()
			->delete($this->_db->quoteName('#__messages'))
			->where($this->_db->quoteName('user_id_to') . '
= ' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		$query->clear()
			->delete($this->_db->quoteName('#__user_keys'))
			->where($this->_db->quoteName('user_id') . ' =
' . $this->_db->quote($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, than current time is
used.
		$date = \JFactory::getDate($timeStamp);

		// Update the database row for the user.
		$db = $this->_db;
		$query = $db->getQuery(true)
			->update($db->quoteName($this->_tbl))
			->set($db->quoteName('lastvisitDate') . '=' .
$db->quote($date->toSql()))
			->where($db->quoteName('id') . '=' . (int)
$userId);
		$db->setQuery($query);
		$db->execute();

		return true;
	}
}
Usergroup.php000064400000016475151155731260007273 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Usergroup table class.
 *
 * @since  1.7.0
 */
class Usergroup extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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()
	{
		// Validate the title.
		if ((trim($this->title)) == '')
		{
			$this->setError(\JText::_('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(\JText::_('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;
		$query = $db->getQuery(true)
			->select('COUNT(title)')
			->from($this->_tbl)
			->where('title = ' . $db->quote(trim($this->title)))
			->where('parent_id = ' . (int) $this->parent_id)
			->where('id <> ' . (int) $this->id);
		$db->setQuery($query);

		if ($db->loadResult() > 0)
		{
			$this->setError(\JText::_('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',
array('dbo' => $this->getDbo()));

			$table->load($this->id);

			if ((!$table->parent_id && $this->parent_id) ||
($table->parent_id && !$this->parent_id))
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_PARENT_ID_NOT_VALID'));

				return false;
			}
		}
		// New entry should always be greater 0
		elseif (!$this->parent_id)
		{
			$this->setError(\JText::_('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',
array('dbo' => $this->getDbo()));
			$table->load($this->parent_id);

			if ($table->id != $this->parent_id)
			{
				$this->setError(\JText::_('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;

		// Get all children of this node
		$db->setQuery('SELECT id FROM ' . $this->_tbl . '
WHERE parent_id=' . (int) $parentId . ' ORDER BY parent_id,
title');
		$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;
			}
		}

		// We've got the left value, and now that we've processed
		// the children of this node we also know the right value
		$db->setQuery('UPDATE ' . $this->_tbl . ' SET
lft=' . (int) $left . ', rgt=' . (int) $right . ' WHERE
id=' . (int) $parentId);

		// If there is an update failure, return false to break out of the
recursion
		try
		{
			$db->execute();
		}
		catch (\JDatabaseExceptionExecuting $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;

		// Select the usergroup ID and its children
		$query = $db->getQuery(true)
			->select($db->quoteName('c.id'))
			->from($db->quoteName($this->_tbl) . 'AS c')
			->where($db->quoteName('c.lft') . ' >= ' .
(int) $this->lft)
			->where($db->quoteName('c.rgt') . ' <= ' .
(int) $this->rgt);
		$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))
			->where($db->quoteName('id') . ' IN (' .
implode(',', $ids) . ')');
		$db->setQuery($query);
		$db->execute();

		// Rebuild the nested set tree.
		$this->rebuild();

		// Delete the usergroup in view levels
		$replace = array();

		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('id, rules')
			->from('#__viewlevels');
		$db->setQuery($query);
		$rules = $db->loadObjectList();

		$match_ids = array();

		foreach ($rules as $rule)
		{
			foreach ($ids as $id)
			{
				if (strstr($rule->rules, '[' . $id) ||
strstr($rule->rules, ',' . $id) || strstr($rule->rules, $id
. ']'))
				{
					$match_ids[] = $rule->id;
				}
			}
		}

		if (!empty($match_ids))
		{
			$query->clear()
				->set('rules=' . str_repeat('replace(', 4 *
count($ids)) . 'rules' . implode('', $replace))
				->update('#__viewlevels')
				->where('id IN (' . implode(',', $match_ids) .
')');
			$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'))
			->where($db->quoteName('group_id') . ' IN (' .
implode(',', $ids) . ')');
		$db->setQuery($query);
		$db->execute();

		return true;
	}
}
ViewLevel.php000064400000003532151155731260007170 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license    GNU General Public License version 2 or later; see
LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Viewlevels table class.
 *
 * @since  1.7.0
 */
class ViewLevel extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($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()
	{
		// Validate the title.
		if ((trim($this->title)) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_VIEWLEVEL'));

			return false;
		}

		// Check for a duplicate title.
		$db = $this->_db;
		$query = $db->getQuery(true)
			->select('COUNT(title)')
			->from($db->quoteName('#__viewlevels'))
			->where($db->quoteName('title') . ' = ' .
$db->quote($this->title))
			->where($db->quoteName('id') . ' != ' . (int)
$this->id);
		$db->setQuery($query);

		if ($db->loadResult() > 0)
		{
			$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_USERLEVEL_NAME_EXISTS',
$this->title));

			return false;
		}

		return true;
	}
}