1346 lines
35 KiB
PHP
1346 lines
35 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Platform
|
|
* @subpackage Table
|
|
*
|
|
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE
|
|
*/
|
|
|
|
defined('JPATH_PLATFORM') or die;
|
|
|
|
jimport('joomla.filesystem.path');
|
|
|
|
/**
|
|
* Abstract Table class
|
|
*
|
|
* Parent class to all tables.
|
|
*
|
|
* @package Joomla.Platform
|
|
* @subpackage Table
|
|
* @link http://docs.joomla.org/JTable
|
|
* @since 11.1
|
|
* @tutorial Joomla.Platform/jtable.cls
|
|
*/
|
|
abstract class JTable extends JObject implements JObservableInterface
|
|
{
|
|
/**
|
|
* Include paths for searching for JTable classes.
|
|
*
|
|
* @var array
|
|
* @since 12.1
|
|
*/
|
|
private static $_includePaths = array();
|
|
|
|
/**
|
|
* Name of the database table to model.
|
|
*
|
|
* @var string
|
|
* @since 11.1
|
|
*/
|
|
protected $_tbl = '';
|
|
|
|
/**
|
|
* Name of the primary key field in the table.
|
|
*
|
|
* @var string
|
|
* @since 11.1
|
|
*/
|
|
protected $_tbl_key = '';
|
|
|
|
/**
|
|
* JDatabaseDriver object.
|
|
*
|
|
* @var JDatabaseDriver
|
|
* @since 11.1
|
|
*/
|
|
protected $_db;
|
|
|
|
/**
|
|
* Should rows be tracked as ACL assets?
|
|
*
|
|
* @var boolean
|
|
* @since 11.1
|
|
*/
|
|
protected $_trackAssets = false;
|
|
|
|
/**
|
|
* The rules associated with this record.
|
|
*
|
|
* @var JAccessRules A JAccessRules object.
|
|
* @since 11.1
|
|
*/
|
|
protected $_rules;
|
|
|
|
/**
|
|
* Indicator that the tables have been locked.
|
|
*
|
|
* @var boolean
|
|
* @since 11.1
|
|
*/
|
|
protected $_locked = false;
|
|
|
|
/**
|
|
* Generic observers for this JTable (Used e.g. for tags Processing)
|
|
*
|
|
* @var JObserverUpdater
|
|
* @since 3.1.2
|
|
*/
|
|
protected $_observers;
|
|
|
|
/**
|
|
* 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 string $key Name of the primary key field in the table.
|
|
* @param JDatabaseDriver $db JDatabaseDriver object.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function __construct($table, $key, $db)
|
|
{
|
|
// Set internal variables.
|
|
$this->_tbl = $table;
|
|
$this->_tbl_key = $key;
|
|
$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.
|
|
*
|
|
* @return mixed An array of the field names, or false if an error occurs.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function getFields()
|
|
{
|
|
static $cache = null;
|
|
|
|
if ($cache === null)
|
|
{
|
|
// 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 JTable class if it can be found in
|
|
* the table include paths. To add include paths for searching for JTable
|
|
* classes @see JTable::addIncludePath().
|
|
*
|
|
* @param string $type The type (name) of the JTable 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 JTable object.
|
|
*
|
|
* @return mixed A JTable object if found or boolean false if one could not be found.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getInstance
|
|
* @since 11.1
|
|
*/
|
|
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.
|
|
$path = JPath::find(self::addIncludePath(), strtolower($type) . '.php');
|
|
if ($path)
|
|
{
|
|
// Import the class file.
|
|
include_once $path;
|
|
|
|
// If we were unable to load the proper class, raise a warning and return false.
|
|
if (!class_exists($tableClass))
|
|
{
|
|
JLog::add(JText::sprintf('JLIB_DATABASE_ERROR_CLASS_NOT_FOUND_IN_FILE', $tableClass), JLog::WARNING, 'jerror');
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we were unable to find the class file in the JTable include paths, raise a warning and return false.
|
|
JLog::add(JText::sprintf('JLIB_DATABASE_ERROR_NOT_SUPPORTED_FILE_NOT_FOUND', $type), JLog::WARNING, 'jerror');
|
|
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 JTable should search for table class files.
|
|
* You may either pass a string or an array of paths.
|
|
*
|
|
* @param mixed $path A filesystem path or array of filesystem paths to add.
|
|
*
|
|
* @return array An array of filesystem paths to find JTable classes in.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/addIncludePath
|
|
* @since 11.1
|
|
*/
|
|
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) && !in_array($path, self::$_includePaths))
|
|
{
|
|
// 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.
|
|
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 11.1
|
|
*/
|
|
protected function _getAssetName()
|
|
{
|
|
$k = $this->_tbl_key;
|
|
return $this->_tbl . '.' . (int) $this->$k;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getAssetTitle
|
|
* @since 11.1
|
|
*/
|
|
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.
|
|
* The extended class can define a table and id to lookup. If the
|
|
* asset does not exist it will be created.
|
|
*
|
|
* @param JTable $table A JTable object for the asset parent.
|
|
* @param integer $id Id to look up
|
|
*
|
|
* @return integer
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function _getAssetParentId($table = null, $id = null)
|
|
{
|
|
// For simple cases, parent to the asset root.
|
|
$assets = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
|
|
$rootId = $assets->getRootId();
|
|
if (!empty($rootId))
|
|
{
|
|
return $rootId;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Method to get the database table name for the class.
|
|
*
|
|
* @return string The name of the database table being modeled.
|
|
*
|
|
* @since 11.1
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getTableName
|
|
*/
|
|
public function getTableName()
|
|
{
|
|
return $this->_tbl;
|
|
}
|
|
|
|
/**
|
|
* Method to get the primary key field name for the table.
|
|
*
|
|
* @return string The name of the primary key for the table.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getKeyName
|
|
* @since 11.1
|
|
*/
|
|
public function getKeyName()
|
|
{
|
|
return $this->_tbl_key;
|
|
}
|
|
|
|
/**
|
|
* Method to get the JDatabaseDriver object.
|
|
*
|
|
* @return JDatabaseDriver The internal database driver object.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getDBO
|
|
* @since 11.1
|
|
*/
|
|
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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/setDBO
|
|
* @since 11.1
|
|
*/
|
|
public function setDBO(JDatabaseDriver $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 11.1
|
|
*/
|
|
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 11.1
|
|
*/
|
|
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.
|
|
*
|
|
* @return void
|
|
*
|
|
* @link http://docs.joomla.org/JTable/reset
|
|
* @since 11.1
|
|
*/
|
|
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 ($k != $this->_tbl_key && (strpos($k, '_') !== 0))
|
|
{
|
|
$this->$k = $v->Default;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to bind an associative array or object to the JTable 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 JTable instance.
|
|
* @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/bind
|
|
* @since 11.1
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function bind($src, $ignore = array())
|
|
{
|
|
// If the source value is not an array or object return false.
|
|
if (!is_object($src) && !is_array($src))
|
|
{
|
|
throw new InvalidArgumentException(sprintf('%s::bind(*%s*)', 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 JTable 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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/load
|
|
* @since 11.1
|
|
* @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))
|
|
{
|
|
// If empty, use the value of the current key
|
|
$keyName = $this->_tbl_key;
|
|
$keyValue = $this->$keyName;
|
|
|
|
// If empty primary key there's is no need to load anything
|
|
if (empty($keyValue))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$keys = array($keyName => $keyValue);
|
|
}
|
|
elseif (!is_array($keys))
|
|
{
|
|
// Load by primary key.
|
|
$keys = array($this->_tbl_key => $keys);
|
|
}
|
|
|
|
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 JTable instance properties to ensure
|
|
* they are safe to store in the database. Child classes should override this
|
|
* method to make sure the data they are storing in the database is safe and
|
|
* as expected before storage.
|
|
*
|
|
* @return boolean True if the instance is sane and able to be stored in the database.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/check
|
|
* @since 11.1
|
|
*/
|
|
public function check()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to store a row in the database from the JTable instance properties.
|
|
* If a primary key value is set the row with that primary key value will be
|
|
* updated with the instance property values. If no primary key value is set
|
|
* a new row will be inserted into the database with the properties from the
|
|
* JTable instance.
|
|
*
|
|
* @param boolean $updateNulls True to update fields even if they are null.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/store
|
|
* @since 11.1
|
|
*/
|
|
public function store($updateNulls = false)
|
|
{
|
|
$k = $this->_tbl_key;
|
|
|
|
// Implement JObservableInterface: Pre-processing by observers
|
|
$this->_observers->update('onBeforeStore', array($updateNulls, $k));
|
|
|
|
if (!empty($this->asset_id))
|
|
{
|
|
$currentAssetId = $this->asset_id;
|
|
}
|
|
|
|
if (0 == $this->$k)
|
|
{
|
|
$this->$k = null;
|
|
}
|
|
|
|
// 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->$k)
|
|
{
|
|
$result = $this->_db->updateObject($this->_tbl, $this, $this->_tbl_key, $updateNulls);
|
|
}
|
|
else
|
|
{
|
|
$result = $this->_db->insertObject($this->_tbl, $this, $this->_tbl_key);
|
|
}
|
|
|
|
// 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 = 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);
|
|
$result = 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());
|
|
$result = 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)
|
|
->where($this->_db->quoteName($k) . ' = ' . (int) $this->$k);
|
|
$this->_db->setQuery($query);
|
|
|
|
$this->_db->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 JTable
|
|
* 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 JTable instance for the property specified.
|
|
*
|
|
* @param mixed $src An associative array or object to bind to the JTable instance.
|
|
* @param string $orderingFilter Filter for the order updating
|
|
* @param mixed $ignore An optional array or space separated list of properties
|
|
* to ignore while binding.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/save
|
|
* @since 11.1
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Deletes this row in database (or if provided, the row of key $pk)
|
|
*
|
|
* @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/delete
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function delete($pk = null)
|
|
{
|
|
$k = $this->_tbl_key;
|
|
|
|
// Implement JObservableInterface: Pre-processing by observers
|
|
$this->_observers->update('onBeforeDelete', array($pk, $k));
|
|
|
|
$pk = (is_null($pk)) ? $this->$k : $pk;
|
|
|
|
// If no primary key is given, return false.
|
|
if ($pk === null)
|
|
{
|
|
throw new UnexpectedValueException('Null primary key not allowed.');
|
|
}
|
|
|
|
// If tracking assets, remove the asset first.
|
|
if ($this->_trackAssets)
|
|
{
|
|
// Get and the asset name.
|
|
$savedK = $this->$k;
|
|
|
|
$this->$k = $pk;
|
|
$name = $this->_getAssetName();
|
|
$asset = self::getInstance('Asset');
|
|
|
|
if ($asset->loadByName($name))
|
|
{
|
|
if (!$asset->delete())
|
|
{
|
|
$this->setError($asset->getError());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->setError($asset->getError());
|
|
return false;
|
|
}
|
|
|
|
$this->$k = $savedK;
|
|
}
|
|
|
|
// Delete the row by primary key.
|
|
$query = $this->_db->getQuery(true)
|
|
->delete($this->_tbl)
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/checkOut
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function checkOut($userId, $pk = null)
|
|
{
|
|
// If there is no checked_out or checked_out_time field, just return true.
|
|
if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$k = $this->_tbl_key;
|
|
$pk = (is_null($pk)) ? $this->$k : $pk;
|
|
|
|
// If no primary key is given, return false.
|
|
if ($pk === null)
|
|
{
|
|
throw new UnexpectedValueException('Null primary key not allowed.');
|
|
}
|
|
|
|
// Get the current time in MySQL format.
|
|
$time = JFactory::getDate()->toSql();
|
|
|
|
// Check the row out by primary key.
|
|
$query = $this->_db->getQuery(true)
|
|
->update($this->_tbl)
|
|
->set($this->_db->quoteName('checked_out') . ' = ' . (int) $userId)
|
|
->set($this->_db->quoteName('checked_out_time') . ' = ' . $this->_db->quote($time))
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($pk));
|
|
$this->_db->setQuery($query);
|
|
$this->_db->execute();
|
|
|
|
// Set table values in the object.
|
|
$this->checked_out = (int) $userId;
|
|
$this->checked_out_time = $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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/checkIn
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function checkIn($pk = null)
|
|
{
|
|
// If there is no checked_out or checked_out_time field, just return true.
|
|
if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$k = $this->_tbl_key;
|
|
$pk = (is_null($pk)) ? $this->$k : $pk;
|
|
|
|
// If no primary key is given, return false.
|
|
if ($pk === 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('checked_out') . ' = 0')
|
|
->set($this->_db->quoteName('checked_out_time') . ' = ' . $this->_db->quote($this->_db->getNullDate()))
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($pk));
|
|
$this->_db->setQuery($query);
|
|
|
|
// Check for a database error.
|
|
$this->_db->execute();
|
|
|
|
// Set table values in the object.
|
|
$this->checked_out = 0;
|
|
$this->checked_out_time = '';
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/hit
|
|
* @since 11.1
|
|
*/
|
|
public function hit($pk = null)
|
|
{
|
|
// If there is no hits field, just return true.
|
|
if (!property_exists($this, 'hits'))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$k = $this->_tbl_key;
|
|
$pk = (is_null($pk)) ? $this->$k : $pk;
|
|
|
|
// If no primary key is given, return false.
|
|
if ($pk === null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check the row in by primary key.
|
|
$query = $this->_db->getQuery(true)
|
|
->update($this->_tbl)
|
|
->set($this->_db->quoteName('hits') . ' = (' . $this->_db->quoteName('hits') . ' + 1)')
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($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 userid to preform the match with, if an item is checked
|
|
* out by this user the function will return false.
|
|
* @param integer $against The userid to perform the match against when the function
|
|
* is used as a static function.
|
|
*
|
|
* @return boolean True if checked out.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/isCheckedOut
|
|
* @since 11.1
|
|
*/
|
|
public function isCheckedOut($with = 0, $against = null)
|
|
{
|
|
// Handle the non-static case.
|
|
if (isset($this) && ($this instanceof JTable) && is_null($against))
|
|
{
|
|
$against = $this->get('checked_out');
|
|
}
|
|
|
|
// The item is not checked out or is checked out by the same user.
|
|
if (!$against || ($against == $with))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$db = JFactory::getDbo();
|
|
$db->setQuery('SELECT COUNT(userid) FROM ' . $db->quoteName('#__session') . ' WHERE ' . $db->quoteName('userid') . ' = ' . (int) $against);
|
|
$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 mixed Boolean false an failure or the next ordering value as an integer.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/getNextOrder
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function getNextOrder($where = '')
|
|
{
|
|
// If there is no ordering field set an error and return false.
|
|
if (!property_exists($this, '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(ordering)')
|
|
->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);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/reorder
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function reorder($where = '')
|
|
{
|
|
// If there is no ordering field set an error and return false.
|
|
if (!property_exists($this, 'ordering'))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
|
|
}
|
|
|
|
$k = $this->_tbl_key;
|
|
|
|
// Get the primary keys and ordering values for the selection.
|
|
$query = $this->_db->getQuery(true)
|
|
->select($this->_tbl_key . ', ordering')
|
|
->from($this->_tbl)
|
|
->where('ordering >= 0')
|
|
->order('ordering');
|
|
|
|
// Setup the extra where and ordering clause data.
|
|
if ($where)
|
|
{
|
|
$query->where($where);
|
|
}
|
|
|
|
$this->_db->setQuery($query);
|
|
$rows = $this->_db->loadObjectList();
|
|
|
|
// Compact the ordering values.
|
|
foreach ($rows as $i => $row)
|
|
{
|
|
// Make sure the ordering is a positive integer.
|
|
if ($row->ordering >= 0)
|
|
{
|
|
// Only update rows that are necessary.
|
|
if ($row->ordering != $i + 1)
|
|
{
|
|
// Update the row ordering field.
|
|
$query->clear()
|
|
->update($this->_tbl)
|
|
->set('ordering = ' . ($i + 1))
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($row->$k));
|
|
$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 mixed Boolean True on success.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/move
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function move($delta, $where = '')
|
|
{
|
|
// If there is no ordering field set an error and return false.
|
|
if (!property_exists($this, 'ordering'))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
|
|
}
|
|
|
|
// If the change is none, do nothing.
|
|
if (empty($delta))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$k = $this->_tbl_key;
|
|
$row = null;
|
|
$query = $this->_db->getQuery(true);
|
|
|
|
// Select the primary key and ordering values from the table.
|
|
$query->select($this->_tbl_key . ', ordering')
|
|
->from($this->_tbl);
|
|
|
|
// If the movement delta is negative move the row up.
|
|
if ($delta < 0)
|
|
{
|
|
$query->where('ordering < ' . (int) $this->ordering)
|
|
->order('ordering DESC');
|
|
}
|
|
// If the movement delta is positive move the row down.
|
|
elseif ($delta > 0)
|
|
{
|
|
$query->where('ordering > ' . (int) $this->ordering)
|
|
->order('ordering 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('ordering = ' . (int) $row->ordering)
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($this->$k));
|
|
$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('ordering = ' . (int) $this->ordering)
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($row->$k));
|
|
$this->_db->setQuery($query);
|
|
$this->_db->execute();
|
|
|
|
// Update the instance value.
|
|
$this->ordering = $row->ordering;
|
|
}
|
|
else
|
|
{
|
|
// Update the ordering field for this instance.
|
|
$query->clear()
|
|
->update($this->_tbl)
|
|
->set('ordering = ' . (int) $this->ordering)
|
|
->where($this->_tbl_key . ' = ' . $this->_db->quote($this->$k));
|
|
$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.
|
|
*
|
|
* @link http://docs.joomla.org/JTable/publish
|
|
* @since 11.1
|
|
*/
|
|
public function publish($pks = null, $state = 1, $userId = 0)
|
|
{
|
|
$k = $this->_tbl_key;
|
|
|
|
// Sanitize input.
|
|
JArrayHelper::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
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Update the publishing state for rows with the given primary keys.
|
|
$query = $this->_db->getQuery(true)
|
|
->update($this->_tbl)
|
|
->set('published = ' . (int) $state);
|
|
|
|
// Determine if there is checkin support for the table.
|
|
if (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'))
|
|
{
|
|
$query->where('(checked_out = 0 OR checked_out = ' . (int) $userId . ')');
|
|
$checkin = true;
|
|
}
|
|
else
|
|
{
|
|
$checkin = false;
|
|
}
|
|
|
|
// Build the WHERE clause for the primary keys.
|
|
$query->where($k . ' = ' . implode(' OR ' . $k . ' = ', $pks));
|
|
|
|
$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 JTable 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 lock the database table for writing.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws RuntimeException
|
|
*/
|
|
protected function _lock()
|
|
{
|
|
$this->_db->lockTable($this->_tbl);
|
|
$this->_locked = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to unlock the database table for writing.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function _unlock()
|
|
{
|
|
$this->_db->unlockTables();
|
|
$this->_locked = false;
|
|
|
|
return true;
|
|
}
|
|
}
|