Spade
Mini Shell
home/lmsyaran/public_html/htaccess.back/src/Table/Table.php000064400000125036151171627770017723
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);
}
}
home/lmsyaran/public_html/joomla5/libraries/src/Table/Table.php000064400000166224151176056630020551
0ustar00<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2005 Open Source Matters, Inc.
<https://www.joomla.org>
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Table;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Event\Checkin\AfterCheckinEvent as GlobalAfterCheckinEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\DatabaseQuery;
use Joomla\Event\DispatcherAwareInterface;
use Joomla\Event\DispatcherAwareTrait;
use Joomla\Event\DispatcherInterface;
use Joomla\Filesystem\Path;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Abstract Table class
*
* Parent class to all tables.
*
* @since 1.7.0
*/
abstract class Table extends \stdClass implements TableInterface,
DispatcherAwareInterface
{
use DispatcherAwareTrait;
use LegacyErrorHandlingTrait;
use LegacyPropertyManagementTrait;
/**
* Include paths for searching for Table classes.
*
* @var array
* @since 3.0.0
*/
private static $_includePaths = [];
/**
* Table fields cache
*
* @var array
* @since 3.10.4
*/
private static $tableFields;
/**
* Name of the database table to model.
*
* @var string
* @since 1.7.0
*/
protected $_tbl = '';
/**
* Name of the primary key field in the table.
*
* @var string
* @since 1.7.0
*/
protected $_tbl_key = '';
/**
* Name of the primary key fields in the table.
*
* @var array
* @since 3.0.1
*/
protected $_tbl_keys = [];
/**
* DatabaseDriver object.
*
* @var DatabaseDriver
* @since 1.7.0
*/
protected $_db;
/**
* Should rows be tracked as ACL assets?
*
* @var boolean
* @since 1.7.0
*/
protected $_trackAssets = false;
/**
* The rules associated with this record.
*
* @var Rules A Rules object.
* @since 1.7.0
*/
protected $_rules;
/**
* Indicator that the tables have been locked.
*
* @var boolean
* @since 1.7.0
*/
protected $_locked = false;
/**
* Indicates that the primary keys autoincrement.
*
* @var boolean
* @since 3.1.4
*/
protected $_autoincrement = true;
/**
* Array with alias for "special" columns such as ordering,
hits etc etc
*
* @var array
* @since 3.4.0
*/
protected $_columnAlias = [];
/**
* An array of key names to be json encoded in the bind function
*
* @var array
* @since 3.3
*/
protected $_jsonEncode = [];
/**
* Indicates that columns fully support the NULL value in the database
*
* @var boolean
* @since 3.10.0
*/
protected $_supportNullValue = false;
/**
* The UCM type alias. Used for tags, content versioning etc. Leave
blank to effectively disable these features.
*
* @var string
* @since 4.0.0
*/
public $typeAlias = null;
/**
* Object constructor to set table and key fields. In most cases this
will
* be overridden by child classes to explicitly set the table and key
fields
* for a particular database table.
*
* @param string $table Name of the table to
model.
* @param mixed $key Name of the primary key
field in the table or array of field names that compose the primary key.
* @param DatabaseDriver $db DatabaseDriver object.
* @param DispatcherInterface $dispatcher Event dispatcher for this
table
*
* @since 1.7.0
*/
public function __construct($table, $key, DatabaseDriver $db,
DispatcherInterface $dispatcher = null)
{
// Set internal variables.
$this->_tbl = $table;
// Set the key to be an array.
if (\is_string($key)) {
$key = [$key];
} elseif (\is_object($key)) {
$key = (array) $key;
}
$this->_tbl_keys = $key;
if (\count($key) == 1) {
$this->_autoincrement = true;
} else {
$this->_autoincrement = false;
}
// Set the singular table key for backwards compatibility.
$this->_tbl_key = $this->getKeyName();
$this->_db = $db;
// Initialise the table properties.
$fields = $this->getFields();
if ($fields) {
foreach ($fields as $name => $v) {
// Add the field if it is not already present.
if (!$this->hasField($name)) {
$this->$name = null;
}
}
}
// If we are tracking assets, make sure an access field exists and
initially set the default.
if ($this->hasField('asset_id')) {
$this->_trackAssets = true;
}
// If the access property exists, set the default.
if ($this->hasField('access')) {
$this->access = (int)
Factory::getApplication()->get('access');
}
// Create or set a Dispatcher
if (!\is_object($dispatcher) || !($dispatcher instanceof
DispatcherInterface)) {
// @todo Maybe we should use a dedicated "behaviour"
dispatcher for performance reasons and to prevent system plugins from
butting in?
$dispatcher = Factory::getApplication()->getDispatcher();
}
$this->setDispatcher($dispatcher);
$event = AbstractEvent::create(
'onTableObjectCreate',
[
'subject' => $this,
]
);
$this->getDispatcher()->dispatch('onTableObjectCreate',
$event);
}
/**
* Get the columns from database table.
*
* @param bool $reload flag to reload cache
*
* @return mixed An array of the field names, or false if an error
occurs.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function getFields($reload = false)
{
$key = $this->_db->getServerType() . ':' .
$this->_db->getName() . ':' . $this->_tbl;
if (!isset(self::$tableFields[$key]) || $reload) {
// Lookup the fields for this table only once.
$name = $this->_tbl;
$fields = $this->_db->getTableColumns($name, false);
if (empty($fields)) {
throw new \UnexpectedValueException(sprintf('No
columns found for %s table', $name));
}
self::$tableFields[$key] = $fields;
}
return self::$tableFields[$key];
}
/**
* Static method to get an instance of a Table class if it can be found
in the table include paths.
*
* To add include paths for searching for Table classes see
Table::addIncludePath().
*
* @param string $type The type (name) of the Table class to get
an instance of.
* @param string $prefix An optional prefix for the table class
name.
* @param array $config An optional array of configuration values
for the Table object.
*
* @return Table|boolean A Table object if found or boolean false on
failure.
*
* @since 1.7.0
*
* @deprecated 4.3 will be removed in 6.0
* Use the MvcFactory instead
* Example:
Factory::getApplication()->bootComponent('...')->getMVCFactory()->createTable($name,
$prefix, $config);
*/
public static function getInstance($type, $prefix = 'JTable',
$config = [])
{
/**
* For B/C reasons we don't change the $prefix to
\\Joomla\\CMS\\Table\\ since extensions which
* use JTable as table prefix instead of an own prefix and not
adding 'JTable' as prefix will
* fail to load the table. We can't detect this situation.
* Example:
* class JTableMytable {}
* JTable::getInstance('Mytable');
* This will fail when we change the function default $prefix from
JTable to \\Joomla\\CMS\\Table\\
*
* In case of $prefix is 'JTable' we make an additional
check for '\\Joomla\\CMS\\Table\\' $type
*
*/
// Sanitize and prepare the table class name.
$type = preg_replace('/[^A-Z0-9_\.-]/i',
'', $type);
$tableClass = $prefix . ucfirst($type);
$tableClassLegacy = $tableClass;
if ($prefix === 'JTable') {
$tableClass = '\\Joomla\\CMS\\Table\\' .
ucfirst($type);
}
// Only try to load the class if it doesn't already exist.
if (!class_exists($tableClass) &&
!class_exists($tableClassLegacy)) {
// Search for the class file in the JTable include paths.
$paths = self::addIncludePath();
$pathIndex = 0;
while (!class_exists($tableClass) &&
!class_exists($tableClassLegacy) && $pathIndex < \count($paths))
{
if ($tryThis = Path::find($paths[$pathIndex++],
strtolower($type) . '.php')) {
// Import the class file.
include_once $tryThis;
}
}
}
if (!class_exists($tableClass) &&
class_exists($tableClassLegacy)) {
$tableClass = $tableClassLegacy;
}
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 6.0 an Exception (type to be determined) will be thrown.
* For more info see
https://github.com/joomla/joomla-cms/issues/11570
*/
return false;
}
// If a database object was passed in the configuration array use
it, otherwise get the global one from Factory.
$db = $config['dbo'] ??
Factory::getContainer()->get(DatabaseInterface::class);
// Check for a possible service from the container otherwise
manually instantiate the class
if (Factory::getContainer()->has($tableClass)) {
return Factory::getContainer()->get($tableClass);
}
// Instantiate a new table class and return it.
return new $tableClass($db);
}
/**
* Add a filesystem path where Table should search for table class
files.
*
* @param array|string $path A filesystem path or array of
filesystem paths to add.
*
* @return array An array of filesystem paths to find Table classes
in.
*
* @since 1.7.0
*
* @deprecated 4.3 will be removed in 6.0
* Should not be used anymore as tables are loaded through
the MvcFactory
*/
public static function addIncludePath($path = null)
{
// If the internal paths have not been initialised, do so with the
base table path.
if (empty(self::$_includePaths)) {
self::$_includePaths = [__DIR__];
}
// Convert the passed path(s) to add to an array.
settype($path, 'array');
// If we have new paths to add, do so.
if (!empty($path)) {
// Check and add each individual new path.
foreach ($path as $dir) {
// Sanitize path.
$dir = trim($dir);
// Add to the front of the list so that custom paths are
searched first.
if (!\in_array($dir, self::$_includePaths)) {
array_unshift(self::$_includePaths, $dir);
}
}
}
return self::$_includePaths;
}
/**
* Method to compute the default name of the asset.
* The default name is in the form table_name.id
* where id is the value of the primary key of the table.
*
* @return string
*
* @since 1.7.0
*/
protected function _getAssetName()
{
$keys = [];
foreach ($this->_tbl_keys as $k) {
$keys[] = (int) $this->$k;
}
return $this->_tbl . '.' . implode('.',
$keys);
}
/**
* Method to return the title to use for the asset table.
*
* In tracking the assets a title is kept for each asset so that there
is some context available in a unified access manager.
* Usually this would just return $this->title or $this->name or
whatever is being used for the primary name of the row.
* If this method is not overridden, the asset name is used.
*
* @return string The string to use as the title in the asset table.
*
* @since 1.7.0
*/
protected function _getAssetTitle()
{
return $this->_getAssetName();
}
/**
* Method to get the parent asset under which to register this one.
*
* By default, all assets are registered to the ROOT node with ID,
which will default to 1 if none exists.
* An extended class can define a table and ID to lookup. If the asset
does not exist it will be created.
*
* @param Table $table A Table object for the asset parent.
* @param integer $id Id to look up
*
* @return integer
*
* @since 1.7.0
*/
protected function _getAssetParentId(Table $table = null, $id = null)
{
// For simple cases, parent to the asset root.
$assets = new Asset($this->getDbo(), $this->getDispatcher());
$rootId = $assets->getRootId();
if (!empty($rootId)) {
return $rootId;
}
return 1;
}
/**
* Method to append the primary keys for this table to a query.
*
* @param DatabaseQuery $query A query object to append.
* @param mixed $pk Optional primary key parameter.
*
* @return void
*
* @since 3.1.4
*/
public function appendPrimaryKeys($query, $pk = null)
{
if (\is_null($pk)) {
foreach ($this->_tbl_keys as $k) {
$query->where($this->_db->quoteName($k) . ' =
' . $this->_db->quote($this->$k));
}
} else {
if (\is_string($pk)) {
$pk = [$this->_tbl_key => $pk];
}
$pk = (object) $pk;
foreach ($this->_tbl_keys as $k) {
$query->where($this->_db->quoteName($k) . ' =
' . $this->_db->quote($pk->$k));
}
}
}
/**
* Method to get the database table name for the class.
*
* @return string The name of the database table being modeled.
*
* @since 1.7.0
*/
public function getTableName()
{
return $this->_tbl;
}
/**
* Method to get the primary key field name for the table.
*
* @param boolean $multiple True to return all primary keys (as an
array) or false to return just the first one (as a string).
*
* @return mixed Array of primary key field names or string
containing the first primary key field.
*
* @since 1.7.0
*/
public function getKeyName($multiple = false)
{
// Count the number of keys
if (\count($this->_tbl_keys)) {
if ($multiple) {
// If we want multiple keys, return the raw array.
return $this->_tbl_keys;
}
// If we want the standard method, just return the first key.
return $this->_tbl_keys[0];
}
return '';
}
/**
* Returns the identity (primary key) value of this record
*
* @return mixed
*
* @since 4.0.0
*/
public function getId()
{
$key = $this->getKeyName();
return $this->$key;
}
/**
* Method to get the DatabaseDriver object.
*
* @return DatabaseDriver The internal database driver object.
*
* @since 1.7.0
*/
public function getDbo()
{
return $this->_db;
}
/**
* Method to set the DatabaseDriver object.
*
* @param DatabaseDriver $db A DatabaseDriver object to be used by
the table object.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function setDbo(DatabaseDriver $db)
{
$this->_db = $db;
return true;
}
/**
* Method to set rules for the record.
*
* @param mixed $input A Rules object, JSON string, or array.
*
* @return void
*
* @since 1.7.0
*/
public function setRules($input)
{
if ($input instanceof Rules) {
$this->_rules = $input;
} else {
$this->_rules = new Rules($input);
}
}
/**
* Method to get the rules for the record.
*
* @return Rules object
*
* @since 1.7.0
*/
public function getRules()
{
return $this->_rules;
}
/**
* Method to reset class properties to the defaults set in the class
* definition. It will ignore the primary key as well as any private
class
* properties (except $_errors).
*
* @return void
*
* @since 1.7.0
*/
public function reset()
{
$event = AbstractEvent::create(
'onTableBeforeReset',
[
'subject' => $this,
]
);
$this->getDispatcher()->dispatch('onTableBeforeReset',
$event);
// Get the default values for the class from the table.
foreach ($this->getFields() as $k => $v) {
// If the property is not the primary key or private, reset it.
if (!\in_array($k, $this->_tbl_keys) && (strpos($k,
'_') !== 0)) {
$this->$k = $v->Default;
}
}
// Reset table errors
$this->_errors = [];
$event = AbstractEvent::create(
'onTableAfterReset',
[
'subject' => $this,
]
);
$this->getDispatcher()->dispatch('onTableAfterReset',
$event);
}
/**
* Method to bind an associative array or object to the Table
instance.This
* method only binds properties that are publicly accessible and
optionally
* takes an array of properties to ignore when binding.
*
* @param array|object $src An associative array or object to
bind to the Table instance.
* @param array|string $ignore An optional array or space separated
list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \InvalidArgumentException
*/
public function bind($src, $ignore = [])
{
// Check if the source value is an array or object
if (!\is_object($src) && !\is_array($src)) {
throw new \InvalidArgumentException(
sprintf(
'Could not bind the data source in %1$s::bind(),
the source must be an array or object but a "%2$s" was
given.',
\get_class($this),
\gettype($src)
)
);
}
// If the ignore value is a string, explode it over spaces.
if (!\is_array($ignore)) {
$ignore = explode(' ', $ignore);
}
$event = AbstractEvent::create(
'onTableBeforeBind',
[
'subject' => $this,
'src' => $src,
'ignore' => $ignore,
]
);
$this->getDispatcher()->dispatch('onTableBeforeBind',
$event);
// If the source value is an object, get its accessible properties.
if (\is_object($src)) {
$src = get_object_vars($src);
}
// JSON encode any fields required
if (!empty($this->_jsonEncode)) {
foreach ($this->_jsonEncode as $field) {
if (isset($src[$field]) && \is_array($src[$field]))
{
$src[$field] = json_encode($src[$field]);
}
}
}
// Bind the source value, excluding the ignored fields.
foreach ($this->getProperties() as $k => $v) {
// Only process fields not in the ignore array.
if (!\in_array($k, $ignore)) {
if (isset($src[$k])) {
$this->$k = $src[$k];
}
}
}
$event = AbstractEvent::create(
'onTableAfterBind',
[
'subject' => $this,
'src' => $src,
'ignore' => $ignore,
]
);
$this->getDispatcher()->dispatch('onTableAfterBind',
$event);
return true;
}
/**
* Method to load a row from the database by primary key and bind the
fields to the Table instance properties.
*
* @param mixed $keys An optional primary key value to load the
row by, or an array of fields to match.
* If not set the instance property value is
used.
* @param boolean $reset True to reset the default values before
loading the new row.
*
* @return boolean True if successful. False if row not found.
*
* @since 1.7.0
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/
public function load($keys = null, $reset = true)
{
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeLoad',
[
'subject' => $this,
'keys' => $keys,
'reset' => $reset,
]
);
$this->getDispatcher()->dispatch('onTableBeforeLoad',
$event);
if (empty($keys)) {
$empty = true;
$keys = [];
// If empty, use the value of the current key
foreach ($this->_tbl_keys as $key) {
$empty = $empty && empty($this->$key);
$keys[$key] = $this->$key;
}
// If empty primary key there's is no need to load
anything
if ($empty) {
return true;
}
} elseif (!\is_array($keys)) {
// Load by primary key.
$keyCount = \count($this->_tbl_keys);
if ($keyCount) {
if ($keyCount > 1) {
throw new \InvalidArgumentException('Table has
multiple primary keys specified, only one primary key value
provided.');
}
$keys = [$this->getKeyName() => $keys];
} else {
throw new \RuntimeException('No table keys
defined.');
}
}
if ($reset) {
$this->reset();
}
// Initialise the query.
$query = $this->_db->getQuery(true)
->select('*')
->from($this->_db->quoteName($this->_tbl));
$fields = array_keys($this->getProperties());
foreach ($keys as $field => $value) {
// Check that $field is in the table.
if (!\in_array($field, $fields)) {
throw new \UnexpectedValueException(sprintf('Missing
field in database: %s   %s.', \get_class($this), $field));
}
// Add the search tuple to the query.
$query->where($this->_db->quoteName($field) . ' =
' . $this->_db->quote($value));
}
$this->_db->setQuery($query);
$row = $this->_db->loadAssoc();
// Check that we have a result.
if (empty($row)) {
$result = false;
} else {
// Bind the object with the row and return.
$result = $this->bind($row);
}
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterLoad',
[
'subject' => $this,
'result' => &$result,
'row' => $row,
]
);
$this->getDispatcher()->dispatch('onTableAfterLoad',
$event);
return $result;
}
/**
* Method to perform sanity checks on the Table instance properties to
ensure they are safe to store in the database.
*
* Child classes should override this method to make sure the data they
are storing in the database is safe and as expected before storage.
*
* @return boolean True if the instance is sane and able to be stored
in the database.
*
* @since 1.7.0
*/
public function check()
{
// Post-processing by observers
$event = AbstractEvent::create(
'onTableCheck',
[
'subject' => $this,
]
);
$this->getDispatcher()->dispatch('onTableCheck',
$event);
return true;
}
/**
* Method to store a row in the database from the Table instance
properties.
*
* If a primary key value is set the row with that primary key value
will be updated with the instance property values.
* If no primary key value is set a new row will be inserted into the
database with the properties from the Table instance.
*
* @param boolean $updateNulls True to update fields even if they
are null.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function store($updateNulls = false)
{
$result = true;
$k = $this->_tbl_keys;
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeStore',
[
'subject' => $this,
'updateNulls' => $updateNulls,
'k' => $k,
]
);
$this->getDispatcher()->dispatch('onTableBeforeStore',
$event);
$currentAssetId = 0;
if (!empty($this->asset_id)) {
$currentAssetId = $this->asset_id;
}
// The asset id field is managed privately by this class.
if ($this->_trackAssets) {
unset($this->asset_id);
}
// We have to unset typeAlias since updateObject / insertObject
will try to insert / update all public variables...
$typeAlias = $this->typeAlias;
unset($this->typeAlias);
try {
// If a primary key exists update the object, otherwise insert
it.
if ($this->hasPrimaryKey()) {
$this->_db->updateObject($this->_tbl, $this,
$this->_tbl_keys, $updateNulls);
} else {
$this->_db->insertObject($this->_tbl, $this,
$this->_tbl_keys[0]);
}
} catch (\Exception $e) {
$this->setError($e->getMessage());
$result = false;
}
$this->typeAlias = $typeAlias;
// If the table is not set to track assets return true.
if ($this->_trackAssets) {
if ($this->_locked) {
$this->_unlock();
}
/*
* Asset Tracking
*/
$parentId = $this->_getAssetParentId();
$name = $this->_getAssetName();
$title = $this->_getAssetTitle();
$asset = new Asset($this->getDbo(),
$this->getDispatcher());
$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;
}
// Specify how a new or moved node asset is inserted into the
tree.
if (empty($this->asset_id) || $asset->parent_id !=
$parentId) {
$asset->setLocation($parentId, 'last-child');
}
// Prepare the asset to be stored.
$asset->parent_id = $parentId;
$asset->name = $name;
// Respect the table field limits
$asset->title = StringHelper::substr($title, 0, 100);
if ($this->_rules instanceof Rules) {
$asset->rules = (string) $this->_rules;
}
if (!$asset->check() || !$asset->store()) {
$this->setError($asset->getError());
return false;
}
// Create an asset_id or heal one that is corrupted.
if (empty($this->asset_id) || ($currentAssetId !=
$this->asset_id && !empty($this->asset_id))) {
// Update the asset_id field in this table.
$this->asset_id = (int) $asset->id;
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set('asset_id = ' . (int)
$this->asset_id);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query)->execute();
}
}
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterStore',
[
'subject' => $this,
'result' => &$result,
]
);
$this->getDispatcher()->dispatch('onTableAfterStore',
$event);
return $result;
}
/**
* Method to provide a shortcut to binding, checking and storing a
Table instance to the database table.
*
* The method will check a row in once the data has been stored and if
an ordering filter is present will attempt to reorder
* the table rows based on the filter. The ordering filter is an
instance property name. The rows that will be reordered
* are those whose value matches the Table instance for the property
specified.
*
* @param array|object $src An associative array or
object to bind to the Table instance.
* @param string $orderingFilter Filter for the order
updating
* @param array|string $ignore An optional array or space
separated list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
public function save($src, $orderingFilter = '', $ignore =
'')
{
// Attempt to bind the source to the instance.
if (!$this->bind($src, $ignore)) {
return false;
}
// Run any sanity checks on the instance and verify that it is
ready for storage.
if (!$this->check()) {
return false;
}
// Attempt to store the properties to the database table.
if (!$this->store()) {
return false;
}
// Attempt to check the row in, just in case it was checked out.
if (!$this->checkIn()) {
return false;
}
// If an ordering filter is set, attempt reorder the rows in the
table based on the filter and value.
if ($orderingFilter) {
$filterValue = $this->$orderingFilter;
$this->reorder($orderingFilter ?
$this->_db->quoteName($orderingFilter) . ' = ' .
$this->_db->quote($filterValue) : '');
}
// Set the error to empty and return true.
$this->setError('');
return true;
}
/**
* Method to delete a row from the database table by primary key value.
*
* @param mixed $pk An optional primary key value to delete. If
not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function delete($pk = null)
{
if (\is_null($pk)) {
$pk = [];
foreach ($this->_tbl_keys as $key) {
$pk[$key] = $this->$key;
}
} elseif (!\is_array($pk)) {
$pk = [$this->_tbl_key => $pk];
}
foreach ($this->_tbl_keys as $key) {
$pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null) {
throw new \UnexpectedValueException('Null primary key
not allowed.');
}
$this->$key = $pk[$key];
}
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeDelete',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableBeforeDelete',
$event);
// If tracking assets, remove the asset first.
if ($this->_trackAssets) {
// Get the asset name
$name = $this->_getAssetName();
$asset = new Asset($this->getDbo(),
$this->getDispatcher());
if ($asset->loadByName($name)) {
if (!$asset->delete()) {
$this->setError($asset->getError());
return false;
}
}
}
// Delete the row by primary key.
$query = $this->_db->getQuery(true)
->delete($this->_db->quoteName($this->_tbl));
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
// Check for a database error.
$this->_db->execute();
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterDelete',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableAfterDelete',
$event);
return true;
}
/**
* Method to check a row out if the necessary properties/fields exist.
*
* To prevent race conditions while editing rows in a database, a row
can be checked out if the fields 'checked_out' and
'checked_out_time'
* are available. While a row is checked out, any attempt to store the
row by a user other than the one who checked the row out should be
* held until the row is checked in again.
*
* @param integer $userId The Id of the user checking out the row.
* @param mixed $pk An optional primary key value to check
out. If not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function checkOut($userId, $pk = null)
{
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeCheckout',
[
'subject' => $this,
'userId' => $userId,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableBeforeCheckout',
$event);
// If there is no checked_out or checked_out_time field, just
return true.
if (!$this->hasField('checked_out') ||
!$this->hasField('checked_out_time')) {
return true;
}
if (\is_null($pk)) {
$pk = [];
foreach ($this->_tbl_keys as $key) {
$pk[$key] = $this->$key;
}
} elseif (!\is_array($pk)) {
$pk = [$this->_tbl_key => $pk];
}
foreach ($this->_tbl_keys as $key) {
$pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null) {
throw new \UnexpectedValueException('Null primary key
not allowed.');
}
}
// Get column names.
$checkedOutField =
$this->getColumnAlias('checked_out');
$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');
// Get the current time in the database format.
$time = Factory::getDate()->toSql();
// Check the row out by primary key.
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName($checkedOutField) . '
= ' . (int) $userId)
->set($this->_db->quoteName($checkedOutTimeField) .
' = ' . $this->_db->quote($time));
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
$this->_db->execute();
// Set table values in the object.
$this->$checkedOutField = (int) $userId;
$this->$checkedOutTimeField = $time;
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterCheckout',
[
'subject' => $this,
'userId' => $userId,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableAfterCheckout',
$event);
return true;
}
/**
* Method to check a row in if the necessary properties/fields exist.
*
* Checking a row in will allow other users the ability to edit the
row.
*
* @param mixed $pk An optional primary key value to check out. If
not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function checkIn($pk = null)
{
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeCheckin',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableBeforeCheckin',
$event);
// If there is no checked_out or checked_out_time field, just
return true.
if (!$this->hasField('checked_out') ||
!$this->hasField('checked_out_time')) {
return true;
}
if (\is_null($pk)) {
$pk = [];
foreach ($this->_tbl_keys as $key) {
$pk[$this->$key] = $this->$key;
}
} elseif (!\is_array($pk)) {
$pk = [$this->_tbl_key => $pk];
}
foreach ($this->_tbl_keys as $key) {
$pk[$key] = empty($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null) {
throw new \UnexpectedValueException('Null primary key
not allowed.');
}
}
// Get column names.
$checkedOutField =
$this->getColumnAlias('checked_out');
$checkedOutTimeField =
$this->getColumnAlias('checked_out_time');
$nullDate = $this->_supportNullValue ? 'NULL' :
$this->_db->quote($this->_db->getNullDate());
$nullID = $this->_supportNullValue ? 'NULL' :
'0';
// Check the row in by primary key.
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName($checkedOutField) . '
= ' . $nullID)
->set($this->_db->quoteName($checkedOutTimeField) .
' = ' . $nullDate);
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
// Check for a database error.
$this->_db->execute();
// Set table values in the object.
$this->$checkedOutField = $this->_supportNullValue ? null
: 0;
$this->$checkedOutTimeField = $this->_supportNullValue ? null
: '';
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterCheckin',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableAfterCheckin',
$event);
$this->getDispatcher()->dispatch('onAfterCheckin',
new GlobalAfterCheckinEvent('onAfterCheckin', [
'subject' => $this->_tbl,
]));
return true;
}
/**
* Validate that the primary key has been set.
*
* @return boolean True if the primary key(s) have been set.
*
* @since 3.1.4
*/
public function hasPrimaryKey()
{
if ($this->_autoincrement) {
$empty = true;
foreach ($this->_tbl_keys as $key) {
$empty = $empty && empty($this->$key);
}
} else {
$query = $this->_db->getQuery(true)
->select('COUNT(*)')
->from($this->_db->quoteName($this->_tbl));
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$count = $this->_db->loadResult();
if ($count == 1) {
$empty = false;
} else {
$empty = true;
}
}
return !$empty;
}
/**
* Method to increment the hits for a row if the necessary
property/field exists.
*
* @param mixed $pk An optional primary key value to increment. If
not set the instance property value is used.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function hit($pk = null)
{
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeHit',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableBeforeHit',
$event);
// If there is no hits field, just return true.
if (!$this->hasField('hits')) {
return true;
}
if (\is_null($pk)) {
$pk = [];
foreach ($this->_tbl_keys as $key) {
$pk[$key] = $this->$key;
}
} elseif (!\is_array($pk)) {
$pk = [$this->_tbl_key => $pk];
}
foreach ($this->_tbl_keys as $key) {
$pk[$key] = \is_null($pk[$key]) ? $this->$key : $pk[$key];
if ($pk[$key] === null) {
throw new \UnexpectedValueException('Null primary key
not allowed.');
}
}
// Get column name.
$hitsField = $this->getColumnAlias('hits');
// Check the row in by primary key.
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName($hitsField) . ' =
(' . $this->_db->quoteName($hitsField) . ' + 1)');
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
$this->_db->execute();
// Set table values in the object.
$this->hits++;
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableAfterHit',
[
'subject' => $this,
'pk' => $pk,
]
);
$this->getDispatcher()->dispatch('onTableAfterHit',
$event);
return true;
}
/**
* Method to determine if a row is checked out and therefore uneditable
by a user.
*
* If the row is checked out by the same user, then it is considered
not checked out -- as the user can still edit it.
*
* @param integer $with The user ID to perform the match with,
if an item is checked out by this user the function will return false.
* @param integer $against The user ID to perform the match against
when the function is used as a static function.
*
* @return boolean True if checked out.
*
* @since 1.7.0
*/
public function isCheckedOut($with = 0, $against = null)
{
// Handle the non-static case.
if (isset($this) && ($this instanceof Table) &&
\is_null($against)) {
$checkedOutField =
$this->getColumnAlias('checked_out');
$against = $this->get($checkedOutField);
}
// The item is not checked out or is checked out by the same user.
if (!$against || ($against == $with)) {
return false;
}
// This last check can only be relied on if tracking session
metadata
if (Factory::getApplication()->get('session_metadata',
true)) {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('COUNT(userid)')
->from($db->quoteName('#__session'))
->where($db->quoteName('userid') . ' =
' . (int) $against);
$db->setQuery($query);
$checkedOut = (bool) $db->loadResult();
// If a session exists for the user then it is checked out.
return $checkedOut;
}
// Assume if we got here that there is a value in the checked out
column but it doesn't match the given user
return true;
}
/**
* Method to get the next ordering value for a group of rows defined by
an SQL WHERE clause.
*
* This is useful for placing a new item last in a group of items in
the table.
*
* @param string $where WHERE clause to use for selecting the
MAX(ordering) for the table.
*
* @return integer The next ordering value.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function getNextOrder($where = '')
{
// Check if there is an ordering field set
if (!$this->hasField('ordering')) {
throw new \UnexpectedValueException(sprintf('%s does not
support ordering.', \get_class($this)));
}
// Get the largest ordering value for a given where clause.
$query = $this->_db->getQuery(true)
->select('MAX(' .
$this->_db->quoteName($this->getColumnAlias('ordering'))
. ')')
->from($this->_db->quoteName($this->_tbl));
if ($where) {
$query->where($where);
}
$this->_db->setQuery($query);
$max = (int) $this->_db->loadResult();
// Return the largest ordering value + 1.
return $max + 1;
}
/**
* Get the primary key values for this table using passed in values as
a default.
*
* @param array $keys Optional primary key values to use.
*
* @return array An array of primary key names and values.
*
* @since 3.1.4
*/
public function getPrimaryKey(array $keys = [])
{
foreach ($this->_tbl_keys as $key) {
if (!isset($keys[$key])) {
if (!empty($this->$key)) {
$keys[$key] = $this->$key;
}
}
}
return $keys;
}
/**
* Method to compact the ordering values of rows in a group of rows
defined by an SQL WHERE clause.
*
* @param string|string[] $where WHERE clause to use for limiting
the selection of rows to compact the ordering values.
*
* @return mixed Boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function reorder($where = '')
{
// Check if there is an ordering field set
if (!$this->hasField('ordering')) {
throw new \UnexpectedValueException(sprintf('%s does not
support ordering.', \get_class($this)));
}
$quotedOrderingField =
$this->_db->quoteName($this->getColumnAlias('ordering'));
$subquery = $this->_db->getQuery(true)
->from($this->_db->quoteName($this->_tbl))
->selectRowNumber($quotedOrderingField,
'new_ordering');
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($quotedOrderingField . ' =
sq.new_ordering');
$innerOn = [];
// Get the primary keys for the selection.
foreach ($this->_tbl_keys as $i => $k) {
$subquery->select($this->_db->quoteName($k,
'pk__' . $i));
$innerOn[] = $this->_db->quoteName($k) . ' =
sq.' . $this->_db->quoteName('pk__' . $i);
}
// Setup the extra where and ordering clause data.
if ($where) {
$subquery->where($where);
$query->where($where);
}
$subquery->where($quotedOrderingField . ' >= 0');
$query->where($quotedOrderingField . ' >= 0');
$query->innerJoin('(' . (string) $subquery . ')
AS sq ');
foreach ($innerOn as $key) {
$query->where($key);
}
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeReorder',
[
'subject' => $this,
'query' => $query,
'where' => $where,
]
);
$this->getDispatcher()->dispatch('onTableBeforeReorder',
$event);
$this->_db->setQuery($query);
$this->_db->execute();
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterReorder',
[
'subject' => $this,
'where' => $where,
]
);
$this->getDispatcher()->dispatch('onTableAfterReorder',
$event);
return true;
}
/**
* Method to move a row in the ordering sequence of a group of rows
defined by an SQL WHERE clause.
*
* Negative numbers move the row up in the sequence and positive
numbers move it down.
*
* @param integer $delta The direction and magnitude to
move the row in the ordering sequence.
* @param string|string[] $where WHERE clause to use for limiting
the selection of rows to compact the ordering values.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \UnexpectedValueException
*/
public function move($delta, $where = '')
{
// Check if there is an ordering field set
if (!$this->hasField('ordering')) {
throw new \UnexpectedValueException(sprintf('%s does not
support ordering.', \get_class($this)));
}
$orderingField =
$this->getColumnAlias('ordering');
$quotedOrderingField = $this->_db->quoteName($orderingField);
// If the change is none, do nothing.
if (empty($delta)) {
return true;
}
$row = null;
$query = $this->_db->getQuery(true);
// Select the primary key and ordering values from the table.
$query->select(implode(',', $this->_tbl_keys) .
', ' . $quotedOrderingField)
->from($this->_db->quoteName($this->_tbl));
// If the movement delta is negative move the row up.
if ($delta < 0) {
$query->where($quotedOrderingField . ' < ' .
(int) $this->$orderingField)
->order($quotedOrderingField . ' DESC');
} elseif ($delta > 0) {
// If the movement delta is positive move the row down.
$query->where($quotedOrderingField . ' > ' .
(int) $this->$orderingField)
->order($quotedOrderingField . ' ASC');
}
// Add the custom WHERE clause if set.
if ($where) {
$query->where($where);
}
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforeMove',
[
'subject' => $this,
'query' => $query,
'delta' => $delta,
'where' => $where,
]
);
$this->getDispatcher()->dispatch('onTableBeforeMove',
$event);
// Select the first row with the criteria.
$query->setLimit(1);
$this->_db->setQuery($query);
$row = $this->_db->loadObject();
// If a row is found, move the item.
if (!empty($row)) {
// Update the ordering field for this instance to the
row's ordering value.
$query->clear()
->update($this->_db->quoteName($this->_tbl))
->set($quotedOrderingField . ' = ' . (int)
$row->$orderingField);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$this->_db->execute();
// Update the ordering field for the row to this
instance's ordering value.
$query->clear()
->update($this->_db->quoteName($this->_tbl))
->set($quotedOrderingField . ' = ' . (int)
$this->$orderingField);
$this->appendPrimaryKeys($query, $row);
$this->_db->setQuery($query);
$this->_db->execute();
// Update the instance value.
$this->$orderingField = $row->$orderingField;
} else {
// Update the ordering field for this instance.
$query->clear()
->update($this->_db->quoteName($this->_tbl))
->set($quotedOrderingField . ' = ' . (int)
$this->$orderingField);
$this->appendPrimaryKeys($query);
$this->_db->setQuery($query);
$this->_db->execute();
}
// Post-processing by observers
$event = AbstractEvent::create(
'onTableAfterMove',
[
'subject' => $this,
'row' => $row,
'delta' => $delta,
'where' => $where,
]
);
$this->getDispatcher()->dispatch('onTableAfterMove',
$event);
return true;
}
/**
* Method to set the publishing state for a row or list of rows in the
database table.
*
* The method respects checked out rows by other users and will attempt
to checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values
to update. If not set the instance property value is used.
* @param integer $state The publishing state. eg. [0 =
unpublished, 1 = published]
* @param integer $userId The user ID of the user performing the
operation.
*
* @return boolean True on success; false if $pks is empty.
*
* @since 1.7.0
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
// Sanitize input
$userId = (int) $userId;
$state = (int) $state;
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableBeforePublish',
[
'subject' => $this,
'pks' => $pks,
'state' => $state,
'userId' => $userId,
]
);
$this->getDispatcher()->dispatch('onTableBeforePublish',
$event);
if (!\is_null($pks)) {
if (!\is_array($pks)) {
$pks = [$pks];
}
foreach ($pks as $key => $pk) {
if (!\is_array($pk)) {
$pks[$key] = [$this->_tbl_key => $pk];
}
}
}
// If there are no primary keys set check to see if the instance
key is set.
if (empty($pks)) {
$pk = [];
foreach ($this->_tbl_keys as $key) {
if ($this->$key) {
$pk[$key] = $this->$key;
} else {
// We don't have a full primary key - return false
$this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
return false;
}
}
$pks = [$pk];
}
$publishedField = $this->getColumnAlias('published');
$checkedOutField =
$this->getColumnAlias('checked_out');
foreach ($pks as $pk) {
// Update the publishing state for rows with the given primary
keys.
$query = $this->_db->getQuery(true)
->update($this->_db->quoteName($this->_tbl))
->set($this->_db->quoteName($publishedField) .
' = ' . (int) $state);
// If publishing, set published date/time if not previously set
if ($state &&
$this->hasField('publish_up') && (int)
$this->publish_up == 0) {
$nowDate =
$this->_db->quote(Factory::getDate()->toSql());
$query->set($this->_db->quoteName($this->getColumnAlias('publish_up'))
. ' = ' . $nowDate);
}
// Determine if there is checkin support for the table.
if ($this->hasField('checked_out') ||
$this->hasField('checked_out_time')) {
$query->where(
'('
. $this->_db->quoteName($checkedOutField) .
' = 0'
. ' OR ' .
$this->_db->quoteName($checkedOutField) . ' = ' . (int)
$userId
. ' OR ' .
$this->_db->quoteName($checkedOutField) . ' IS NULL'
. ')'
);
$checkin = true;
} else {
$checkin = false;
}
// Build the WHERE clause for the primary keys.
$this->appendPrimaryKeys($query, $pk);
$this->_db->setQuery($query);
try {
$this->_db->execute();
} catch (\RuntimeException $e) {
$this->setError($e->getMessage());
return false;
}
// If checkin is supported and all rows were adjusted, check
them in.
if ($checkin && (\count($pks) ==
$this->_db->getAffectedRows())) {
$this->checkIn($pk);
}
// If the Table instance value is in the list of primary keys
that were set, set the instance.
$ours = true;
foreach ($this->_tbl_keys as $key) {
if ($this->$key != $pk[$key]) {
$ours = false;
}
}
if ($ours) {
$this->$publishedField = $state;
}
}
$this->setError('');
// Pre-processing by observers
$event = AbstractEvent::create(
'onTableAfterPublish',
[
'subject' => $this,
'pks' => $pks,
'state' => $state,
'userId' => $userId,
]
);
$this->getDispatcher()->dispatch('onTableAfterPublish',
$event);
return true;
}
/**
* Method to lock the database table for writing.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws \RuntimeException
*/
protected function _lock()
{
$this->_db->lockTable($this->_tbl);
$this->_locked = true;
return true;
}
/**
* Method to return the real name of a "special" column such
as ordering, hits, published
* etc etc. In this way you are free to follow your db naming
convention and use the
* built in \Joomla functions.
*
* @param string $column Name of the "special" column (ie
ordering, hits)
*
* @return string The string that identify the special
*
* @since 3.4
*/
public function getColumnAlias($column)
{
// Get the column data if set
$return = $this->_columnAlias[$column] ?? $column;
// Sanitize the name
$return = preg_replace('#[^A-Z0-9_]#i', '',
$return);
return $return;
}
/**
* Method to register a column alias for a "special" column.
*
* @param string $column The "special" column (ie
ordering)
* @param string $columnAlias The real column name (ie
foo_ordering)
*
* @return void
*
* @since 3.4
*/
public function setColumnAlias($column, $columnAlias)
{
// Sanitize the column name alias
$column = strtolower($column);
$column = preg_replace('#[^A-Z0-9_]#i', '',
$column);
// Set the column alias internally
$this->_columnAlias[$column] = $columnAlias;
}
/**
* Method to unlock the database table for writing.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
protected function _unlock()
{
if ($this->_locked) {
$this->_db->unlockTables();
$this->_locked = false;
}
return true;
}
/**
* Check if the record has a property (applying a column alias if it
exists)
*
* @param string $key key to be checked
*
* @return boolean
*
* @since 3.9.11
*/
public function hasField($key)
{
$key = $this->getColumnAlias($key);
return property_exists($this, $key);
}
}