Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
vikbooking
/
libraries
/
adapter
/
database
:
table.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?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; } }