File "table.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/libraries/adapter/database/table.php
File size: 22.44 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* @package VikWP - Libraries
* @subpackage adapter.database
* @author E4J s.r.l.
* @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
* @link https://vikwp.com
*/
// No direct access
defined('ABSPATH') or die('No script kiddies please!');
/**
* Generic table class for database objects.
*
* @since 10.1.19
*/
class JTable extends JObject
{
/**
* Include paths for searching for Table classes.
*
* @var array
*/
private static $_includePaths = array();
/**
* Table fields cache to prevent the static changed applied by PHP 8.1.
* @link https://bugs.php.net/bug.php?id=81686
*
* @var array
* @since 10.1.41
*/
protected static $_tableFields = [];
/**
* Name of the database table to model.
*
* @var string
*/
protected $_table = '';
/**
* Name of the primary key fields in the table.
*
* @var array
*/
protected $_tableKeys = array();
/**
* Indicates that the primary keys autoincrement.
*
* @var boolean
*/
protected $_autoincrement = true;
/**
* Array with alias for "special" columns such as ordering, hits etc etc
*
* @var array
* @since 10.1.30
*/
protected $_columnAlias = array();
/**
* Static method to get an instance of a Table class if it
* can be found in the table include paths.
*
* @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 mixed A Table object if found, false otherwise.
*
* @see JTable::addIncludePath() to add include paths for searching for Table classes.
*/
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.
$paths = static::addIncludePath();
$pathIndex = 0;
// iterate until the class is loaded
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))
{
throw new Exception(sprintf('Table [%s] not found', $tableClass), 404);
}
}
// instantiate a new table class and return it
return new $tableClass($type);
}
/**
* Adds a filesystem path where Table should search for table class files.
*
* @param mixed $path A filesystem path or array of filesystem paths to add.
*
* @return array An array of filesystem paths to find Table classes in.
*/
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[] = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'tables';
}
if ($path)
{
// convert the passed path(s) to add to an array.
$path = (array) $path;
// 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;
}
/**
* 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.
*/
public function __construct($table, $key = 'id')
{
// set internal variables
$this->_table = $table;
// set the key to be an array.
if (is_string($key))
{
$key = array($key);
}
else if (is_object($key))
{
$key = (array) $key;
}
$this->_tableKeys = $key;
if (count($key) == 1)
{
$this->_autoincrement = true;
}
else
{
$this->_autoincrement = false;
}
// initialise the table properties
$fields = $this->getFields();
if ($fields)
{
foreach ($fields as $name => $v)
{
if (is_int($name))
{
// use the value in case we have a linear array
$name = $v;
}
// add the field if it is not already present
if (!property_exists($this, $name))
{
$this->{$name} = null;
}
}
}
}
/**
* Gets the columns from database table.
*
* @param boolean $reload Flag to reload cache.
*
* @return mixed An array of the field names, or false if an error occurs.
*
* @throws Exception
*/
public function getFields($reload = false)
{
$key = $this->getTableName();
if (!isset(static::$_tableFields[$key]) || $reload)
{
$dbo = JFactory::getDbo();
// lookup the fields for this table only once
$fields = $dbo->getTableColumns($this->_table, false);
if (empty($fields))
{
throw new Exception(sprintf('No columns found for [%s] table', $this->_table));
}
static::$_tableFields[$key] = $fields;
}
return static::$_tableFields[$key];
}
/**
* Method to get the database table name for the class.
*
* @return string The name of the database table being modeled.
*
* @since 10.1.30
*/
public function getTableName()
{
return $this->_table;
}
/**
* 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 10.1.35
*/
public function getKeyName($multiple = false)
{
// Count the number of keys
if (count($this->_tableKeys))
{
if ($multiple)
{
// If we want multiple keys, return the raw array.
return $this->_tableKeys;
}
else
{
// If we want the standard method, just return the first key.
return $this->_tableKeys[0];
}
}
return '';
}
/**
* Validate that the primary key has been set.
*
* @return boolean True if the primary key(s) have been set.
*
* @since 10.1.30
*/
public function hasPrimaryKey()
{
if ($this->_autoincrement)
{
$empty = true;
foreach ($this->_tableKeys as $key)
{
$empty = $empty && empty($this->$key);
}
}
else
{
$dbo = JFactory::getDbo();
$q = $dbo->getQuery(true)
->select('COUNT(*)')
->from($this->_table);
$this->appendPrimaryKeys($q);
$dbo->setQuery($q);
$count = $dbo->loadResult();
if ($count == 1)
{
$empty = false;
}
else
{
$empty = true;
}
}
return !$empty;
}
/**
* Method to append the primary keys for this table to a query.
*
* @param mixed $query A query object to append.
* @param mixed $pk Optional primary key parameter.
*
* @return void
*
* @since 10.1.30
*/
public function appendPrimaryKeys($query, $pk = null)
{
$dbo = JFactory::getDbo();
if (is_null($pk))
{
foreach ($this->_tableKeys as $k)
{
$query->where($dbo->qn($k) . ' = ' . $dbo->q($this->{$k}));
}
}
else
{
if (is_string($pk))
{
$pk = array($this->_tableKeys[0] => $pk);
}
$pk = (object) $pk;
foreach ($this->_tableKeys as $k)
{
$query->where($dbo->qn($k) . ' = ' . $dbo->q($pk->{$k}));
}
}
}
/**
* 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 10.1.30
*/
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->_tableKeys) && (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 mixed $src An associative array or object to bind to the Table instance.
* @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @since 10.1.30
*
* @throws InvalidArgumentException
*/
public function bind($src, $ignore = array())
{
// 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 %s::bind(), the source must be an array or object but a "%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);
}
$ignore = (array) $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 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 10.1.30
*/
public function check()
{
// inherit in children classes
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 10.1.30
*/
public function store($updateNulls = false)
{
$dbo = JFactory::getDbo();
$event = JEventDispatcher::getInstance();
$k = $this->_tableKeys;
// pre-processing by observers
$event->trigger('onBeforeStore', array($updateNulls, $k));
// If a primary key exists update the object, otherwise insert it.
if ($this->hasPrimaryKey())
{
$result = $dbo->updateObject($this->_table, $this, $this->_tableKeys, $updateNulls);
}
else
{
$result = $dbo->insertObject($this->_table, $this, $this->_tableKeys[0]);
}
/**
* The table is now able to propagate the error message faced during
* the database insert/update execution.
*
* @since 10.1.58
*/
if (!$result)
{
// propagate error message
$this->setError($dbo->getLastError() ?: 'Database query error.');
}
// post-processing by observers
$event->trigger('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 mixed $src An associative array or object to bind to the Table 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.
*
* @since 10.1.30
*/
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;
}
// ff an ordering filter is set, attempt reorder the rows in the table based on the filter and value.
if ($orderingFilter)
{
$dbo = JFactory::getDbo();
$filterValue = $this->{$orderingFilter};
$this->reorder($orderingFilter ? $dbo->qn($orderingFilter) . ' = ' . $dbo->q($filterValue) : '');
}
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 10.1.30
*
* @throws UnexpectedValueException
*/
public function delete($pk = null)
{
if (is_null($pk))
{
$pk = array();
foreach ($this->_tableKeys as $key)
{
$pk[$key] = $this->{$key};
}
}
else if (!is_array($pk))
{
$pk = array($this->_tableKeys[0] => $pk);
}
foreach ($this->_tableKeys 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];
}
$dbo = JFactory::getDbo();
$event = JEventDispatcher::getInstance();
// pre-processing by observers
$event->trigger('onBeforeDelete', array($pk));
// delete the row by primary key
$q = $dbo->getQuery(true)
->delete($this->_table);
$this->appendPrimaryKeys($q, $pk);
$dbo->setQuery($q);
// check for a database error
$dbo->execute();
// post-processing by observers
$event->trigger('onAfterDelete', array($pk));
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 10.1.30
*
* @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)));
}
$dbo = JFactory::getDbo();
// get the largest ordering value for a given where clause
$q = $dbo->getQuery(true)
->select('MAX(' . $dbo->qn($orderingField) . ')')
->from($this->_table);
if ($where)
{
$q->where($where);
}
$dbo->setQuery($q);
$max = (int) $dbo->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.
*
* @since 10.1.30
*
* @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)));
}
$dbo = JFactory::getDbo();
$quotedOrderingField = $dbo->qn($orderingField);
$subquery = $dbo->getQuery(true)
->from($this->_table)
->selectRowNumber($quotedOrderingField, 'new_ordering');
$query = $dbo->getQuery(true)
->update($this->_table)
->set($quotedOrderingField . ' = sq.new_ordering');
$innerOn = array();
// get the primary keys for the selection
foreach ($this->_tableKeys as $i => $k)
{
$subquery->select($dbo->qn($k, 'pk__' . $i));
$innerOn[] = $dbo->qn($k) . ' = sq.' . $dbo->qn('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));
$dbo->setQuery($query);
$dbo->execute();
}
/**
* Method to set the publishing state for a row or list of rows in the database table.
*
* The method respects checked out rows by other users and will attempt to checkin rows that it can after adjustments are made.
*
* @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used.
* @param integer $state The publishing state. eg. [0 = unpublished, 1 = published]
* @param integer $userId The user ID of the user performing the operation.
*
* @return boolean True on success; false if $pks is empty.
*
* @since 10.1.30
*/
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->_tableKeys[0] => $pk);
}
}
}
// if there are no primary keys set check to see if the instance key is set
if (empty($pks))
{
$pk = array();
foreach ($this->_tableKeys as $key)
{
if ($this->{$key})
{
$pk[$key] = $this->{$key};
}
// we don't have a full primary key - return false
else
{
$this->setError('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED');
return false;
}
}
$pks = array($pk);
}
$dbo = JFactory::getDbo();
$publishedField = $this->getColumnAlias('published');
foreach ($pks as $pk)
{
// update the publishing state for rows with the given primary keys
$query = $dbo->getQuery(true)
->update($this->_table)
->set($dbo->qn($publishedField) . ' = ' . (int) $state);
// build the WHERE clause for the primary keys
$this->appendPrimaryKeys($query, $pk);
$dbo->setQuery($query);
try
{
$dbo->execute();
}
catch (RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
// if the Table instance value is in the list of primary keys that were set, set the instance
$ours = true;
foreach ($this->_tableKeys as $key)
{
if ($this->{$key} != $pk[$key])
{
$ours = false;
}
}
if ($ours)
{
$this->{$publishedField} = $state;
}
}
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 10.1.30
*/
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 (i.e. ordering).
* @param string $columnAlias The real column name (i.e. foo_ordering).
*
* @return void
*
* @since 10.1.30
*/
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 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 10.1.35
*/
public function load($keys = null, $reset = true)
{
if (empty($keys))
{
$empty = true;
$keys = array();
// If empty, use the value of the current key
foreach ($this->_tableKeys 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;
}
}
else if (!is_array($keys))
{
// Load by primary key.
$keyCount = count($this->_tableKeys);
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();
}
$dbo = JFactory::getDbo();
// Initialise the query.
$query = $dbo->getQuery(true)
->select('*')
->from($this->_table);
$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($dbo->quoteName($field) . ' = ' . $dbo->quote($value));
}
$dbo->setQuery($query);
$row = $dbo->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);
}
return $result;
}
}