Spade
Mini Shell
Asset.php000064400000011141151170250210006324 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.php000064400000013433151170250210007030 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.php000064400000021427151170250210006667 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(' ', '',
$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.php000064400000015137151170250210010252 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.php000064400000007010151170250210007521 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.php000064400000025110151170250210007471 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.php000064400000011542151170250210007226 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.php000064400000006410151170250210006773 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.php000064400000017654151170250210006170 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.php000064400000017244151170250210007025 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.php000064400000010506151170250210006476 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.php000064400000140530151170250210006474 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.php000064400000005131151170250210012311
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.php000064400000007114151170250210012035
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.php000064400000012001151170250210007726 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.php000064400000125036151170250210006305 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   %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.php000064400000006655151170250210010133 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.php000064400000001064151170250210005774 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.php000064400000004625151170250210006500 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.php000064400000001763151170250210007325 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.php000064400000031620151170250210006167 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.php000064400000016475151170250210007257 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.php000064400000003532151170250210007154 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;
}
}