Spade
Mini Shell
<?php
/**
* @package Joomla.Platform
* @subpackage Application
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Basic Web application router class for the Joomla Platform.
*
* @since 3.0
* @deprecated 4.0 Use the `joomla/router` package via Composer instead
*/
class JApplicationWebRouterBase extends JApplicationWebRouter
{
/**
* @var array An array of rules, each rule being an associative
array('regex'=> $regex, 'vars' => $vars,
'controller' => $controller)
* for routing the request.
* @since 3.0
*/
protected $maps = array();
/**
* Add a route map to the router. If the pattern already exists it will
be overwritten.
*
* @param string $pattern The route pattern to use for matching.
* @param string $controller The controller name to map to the given
pattern.
*
* @return JApplicationWebRouter This object for method chaining.
*
* @since 3.0
*/
public function addMap($pattern, $controller)
{
// Sanitize and explode the pattern.
$pattern = explode('/', trim(parse_url((string) $pattern,
PHP_URL_PATH), ' /'));
// Prepare the route variables
$vars = array();
// Initialize regular expression
$regex = array();
// Loop on each segment
foreach ($pattern as $segment)
{
// Match a splat with no variable.
if ($segment == '*')
{
$regex[] = '.*';
}
// Match a splat and capture the data to a named variable.
elseif ($segment[0] == '*')
{
$vars[] = substr($segment, 1);
$regex[] = '(.*)';
}
// Match an escaped splat segment.
elseif ($segment[0] == '\\' && $segment[1] ==
'*')
{
$regex[] = '\*' . preg_quote(substr($segment, 2));
}
// Match an unnamed variable without capture.
elseif ($segment == ':')
{
$regex[] = '[^/]*';
}
// Match a named variable and capture the data.
elseif ($segment[0] == ':')
{
$vars[] = substr($segment, 1);
$regex[] = '([^/]*)';
}
// Match a segment with an escaped variable character prefix.
elseif ($segment[0] == '\\' && $segment[1] ==
':')
{
$regex[] = preg_quote(substr($segment, 1));
}
// Match the standard segment.
else
{
$regex[] = preg_quote($segment);
}
}
$this->maps[] = array(
'regex' => chr(1) . '^' . implode('/',
$regex) . '$' . chr(1),
'vars' => $vars,
'controller' => (string) $controller,
);
return $this;
}
/**
* Add a route map to the router. If the pattern already exists it will
be overwritten.
*
* @param array $maps A list of route maps to add to the router as
$pattern => $controller.
*
* @return JApplicationWebRouter This object for method chaining.
*
* @since 3.0
*/
public function addMaps($maps)
{
foreach ($maps as $pattern => $controller)
{
$this->addMap($pattern, $controller);
}
return $this;
}
/**
* Parse the given route and return the name of a controller mapped to the
given route.
*
* @param string $route The route string for which to find and execute
a controller.
*
* @return string The controller name for the given route excluding
prefix.
*
* @since 3.0
* @throws InvalidArgumentException
*/
protected function parseRoute($route)
{
$controller = false;
// Trim the query string off.
$route = preg_replace('/([^?]*).*/u', '\1', $route);
// Sanitize and explode the route.
$route = trim(parse_url($route, PHP_URL_PATH), ' /');
// If the route is empty then simply return the default route. No
parsing necessary.
if ($route == '')
{
return $this->default;
}
// Iterate through all of the known route maps looking for a match.
foreach ($this->maps as $rule)
{
if (preg_match($rule['regex'], $route, $matches))
{
// If we have gotten this far then we have a positive match.
$controller = $rule['controller'];
// Time to set the input variables.
// We are only going to set them if they don't already exist to
avoid overwriting things.
foreach ($rule['vars'] as $i => $var)
{
$this->input->def($var, $matches[$i + 1]);
// Don't forget to do an explicit set on the GET superglobal.
$this->input->get->def($var, $matches[$i + 1]);
}
$this->input->def('_rawRoute', $route);
break;
}
}
// We were unable to find a route match for the request. Panic.
if (!$controller)
{
throw new InvalidArgumentException(sprintf('Unable to handle
request for route `%s`.', $route), 404);
}
return $controller;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Application
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* RESTful Web application router class for the Joomla Platform.
*
* @since 3.0
* @deprecated 4.0 Use the `joomla/router` package via Composer instead
*/
class JApplicationWebRouterRest extends JApplicationWebRouterBase
{
/**
* @var boolean A boolean allowing to pass _method as parameter in
POST requests
* @since 3.0
*/
protected $methodInPostRequest = false;
/**
* @var array An array of HTTP Method => controller suffix pairs
for routing the request.
* @since 3.0
*/
protected $suffixMap = array(
'GET' => 'Get',
'POST' => 'Create',
'PUT' => 'Update',
'PATCH' => 'Update',
'DELETE' => 'Delete',
'HEAD' => 'Head',
'OPTIONS' => 'Options',
);
/**
* Find and execute the appropriate controller based on a given route.
*
* @param string $route The route string for which to find and execute
a controller.
*
* @return void
*
* @since 3.0
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function execute($route)
{
// Get the controller name based on the route patterns and requested
route.
$name = $this->parseRoute($route);
// Append the HTTP method based suffix.
$name .= $this->fetchControllerSuffix();
// Get the controller object by name.
$controller = $this->fetchController($name);
// Execute the controller.
$controller->execute();
}
/**
* Set a controller class suffix for a given HTTP method.
*
* @param string $method The HTTP method for which to set the class
suffix.
* @param string $suffix The class suffix to use when fetching the
controller name for a given request.
*
* @return JApplicationWebRouter This object for method chaining.
*
* @since 3.0
*/
public function setHttpMethodSuffix($method, $suffix)
{
$this->suffixMap[strtoupper((string) $method)] = (string) $suffix;
return $this;
}
/**
* Set to allow or not method in POST request
*
* @param boolean $value A boolean to allow or not method in POST
request
*
* @return void
*
* @since 3.0
*/
public function setMethodInPostRequest($value)
{
$this->methodInPostRequest = $value;
}
/**
* Get the property to allow or not method in POST request
*
* @return boolean
*
* @since 3.0
*/
public function isMethodInPostRequest()
{
return $this->methodInPostRequest;
}
/**
* Get the controller class suffix string.
*
* @return string
*
* @since 3.0
* @throws RuntimeException
*/
protected function fetchControllerSuffix()
{
// Validate that we have a map to handle the given HTTP method.
if (!isset($this->suffixMap[$this->input->getMethod()]))
{
throw new RuntimeException(sprintf('Unable to support the HTTP
method `%s`.', $this->input->getMethod()), 404);
}
// Check if request method is POST
if ($this->methodInPostRequest == true &&
strcmp(strtoupper($this->input->server->getMethod()),
'POST') === 0)
{
// Get the method from input
$postMethod = $this->input->get->getWord('_method');
// Validate that we have a map to handle the given HTTP method from
input
if ($postMethod &&
isset($this->suffixMap[strtoupper($postMethod)]))
{
return ucfirst($this->suffixMap[strtoupper($postMethod)]);
}
}
return ucfirst($this->suffixMap[$this->input->getMethod()]);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Application
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Class to define an abstract Web application router.
*
* @since 3.0
* @deprecated 4.0 Use the `joomla/router` package via Composer instead
*/
abstract class JApplicationWebRouter
{
/**
* @var JApplicationWeb The web application on whose behalf we are
routing the request.
* @since 3.0
*/
protected $app;
/**
* @var string The default page controller name for an empty route.
* @since 3.0
*/
protected $default;
/**
* @var string Controller class name prefix for creating controller
objects by name.
* @since 3.0
*/
protected $controllerPrefix;
/**
* @var JInput An input object from which to derive the route.
* @since 3.0
*/
protected $input;
/**
* Constructor.
*
* @param JApplicationWeb $app The web application on whose behalf
we are routing the request.
* @param JInput $input An optional input object from which
to derive the route. If none
* is given than the input from the
application object will be used.
*
* @since 3.0
*/
public function __construct(JApplicationWeb $app, JInput $input = null)
{
$this->app = $app;
$this->input = ($input === null) ? $this->app->input : $input;
}
/**
* Find and execute the appropriate controller based on a given route.
*
* @param string $route The route string for which to find and execute
a controller.
*
* @return mixed The return value of the controller executed
*
* @since 3.0
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function execute($route)
{
// Get the controller name based on the route patterns and requested
route.
$name = $this->parseRoute($route);
// Get the controller object by name.
$controller = $this->fetchController($name);
// Execute the controller.
return $controller->execute();
}
/**
* Set the controller name prefix.
*
* @param string $prefix Controller class name prefix for creating
controller objects by name.
*
* @return JApplicationWebRouter This object for method chaining.
*
* @since 3.0
*/
public function setControllerPrefix($prefix)
{
$this->controllerPrefix = (string) $prefix;
return $this;
}
/**
* Set the default controller name.
*
* @param string $name The default page controller name for an empty
route.
*
* @return JApplicationWebRouter This object for method chaining.
*
* @since 3.0
*/
public function setDefaultController($name)
{
$this->default = (string) $name;
return $this;
}
/**
* Parse the given route and return the name of a controller mapped to the
given route.
*
* @param string $route The route string for which to find and execute
a controller.
*
* @return string The controller name for the given route excluding
prefix.
*
* @since 3.0
* @throws InvalidArgumentException
*/
abstract protected function parseRoute($route);
/**
* Get a JController object for a given name.
*
* @param string $name The controller name (excluding prefix) for
which to fetch and instance.
*
* @return JController
*
* @since 3.0
* @throws RuntimeException
*/
protected function fetchController($name)
{
// Derive the controller class name.
$class = $this->controllerPrefix . ucfirst($name);
// If the controller class does not exist panic.
if (!class_exists($class) || !is_subclass_of($class,
'JController'))
{
throw new RuntimeException(sprintf('Unable to locate controller
`%s`.', $class), 404);
}
// Instantiate the controller.
$controller = new $class($this->input, $this->app);
return $controller;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 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.file');
jimport('joomla.filesystem.folder');
use Joomla\Archive\Archive;
/**
* An Archive handling class
*
* @since 1.5
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
class JArchive
{
/**
* The array of instantiated archive adapters.
*
* @var JArchiveExtractable[]
* @since 3.0.0
*/
protected static $adapters = array();
/**
* Extract an archive file to a directory.
*
* @param string $archivename The name of the archive file
* @param string $extractdir Directory to unpack into
*
* @return boolean True for success
*
* @since 1.5
* @throws InvalidArgumentException
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
public static function extract($archivename, $extractdir)
{
// The archive instance
$archive = new Archive(array('tmp_path' =>
JFactory::getConfig()->get('tmp_path')));
// Extract the archive
return $archive->extract($archivename, $extractdir);
}
/**
* Get a file compression adapter.
*
* @param string $type The type of adapter (bzip2|gzip|tar|zip).
*
* @return JArchiveExtractable Adapter for the requested type
*
* @since 1.5
* @throws UnexpectedValueException
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
public static function getAdapter($type)
{
if (!isset(self::$adapters[$type]))
{
// Try to load the adapter object
$class = 'JArchive' . ucfirst($type);
if (!class_exists($class))
{
throw new UnexpectedValueException('Unable to load archive',
500);
}
self::$adapters[$type] = new $class;
}
return self::$adapters[$type];
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 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.file');
jimport('joomla.filesystem.stream');
/**
* Bzip2 format adapter for the JArchive class
*
* @since 1.5
* @deprecated 4.0 use the Joomla\Archive\Bzip2 class instead
*/
class JArchiveBzip2 implements JArchiveExtractable
{
/**
* Bzip2 file data buffer
*
* @var string
* @since 1.5
*/
private $_data = null;
/**
* Extract a Bzip2 compressed file to a given path
*
* @param string $archive Path to Bzip2 archive to extract
* @param string $destination Path to extract archive to
* @param array $options Extraction options [unused]
*
* @return boolean True if successful
*
* @since 1.5
* @throws RuntimeException
*/
public function extract($archive, $destination, array $options = array())
{
$this->_data = null;
if (!extension_loaded('bz2'))
{
$this->raiseWarning(100, 'The bz2 extension is not
available.');
}
if (isset($options['use_streams']) &&
$options['use_streams'] != false)
{
return $this->extractStream($archive, $destination, $options);
}
// Old style: read the whole file and then parse it
$this->_data = file_get_contents($archive);
if (!$this->_data)
{
return $this->raiseWarning(100, 'Unable to read archive');
}
$buffer = bzdecompress($this->_data);
unset($this->_data);
if (empty($buffer))
{
return $this->raiseWarning(100, 'Unable to decompress
data');
}
if (JFile::write($destination, $buffer) === false)
{
return $this->raiseWarning(100, 'Unable to write archive');
}
return true;
}
/**
* Method to extract archive using stream objects
*
* @param string $archive Path to Bzip2 archive to extract
* @param string $destination Path to extract archive to
* @param array $options Extraction options [unused]
*
* @return boolean True if successful
*
* @since 3.6.0
*/
protected function extractStream($archive, $destination, $options =
array())
{
// New style! streams!
$input = JFactory::getStream();
// Use bzip
$input->set('processingmethod', 'bz');
if (!$input->open($archive))
{
return $this->raiseWarning(100, 'Unable to read archive
(bz2)');
}
$output = JFactory::getStream();
if (!$output->open($destination, 'w'))
{
$input->close();
return $this->raiseWarning(100, 'Unable to write archive
(bz2)');
}
do
{
$this->_data = $input->read($input->get('chunksize',
8196));
if ($this->_data && !$output->write($this->_data))
{
$input->close();
return $this->raiseWarning(100, 'Unable to write archive
(bz2)');
}
}
while ($this->_data);
$output->close();
$input->close();
return true;
}
/**
* Temporary private method to isolate JError from the extract method
* This code should be removed when JError is removed.
*
* @param int $code The application-internal error code for this
error
* @param string $msg The error message, which may also be shown the
user if need be.
*
* @return JException JException instance if JError class exists
*
* @since 3.6.0
* @throws RuntimeException if JError class does not exist
*/
private function raiseWarning($code, $msg)
{
if (class_exists('JError'))
{
return JError::raiseWarning($code, $msg);
}
throw new RuntimeException($msg);
}
/**
* Tests whether this adapter can unpack files on this computer.
*
* @return boolean True if supported
*
* @since 2.5.0
*/
public static function isSupported()
{
return extension_loaded('bz2');
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Archieve class interface
*
* @since 3.0.0
* @deprecated 4.0 use the Joomla\Archive\ExtractableInterface interface
instead
*/
interface JArchiveExtractable
{
/**
* Extract a compressed file to a given path
*
* @param string $archive Path to archive to extract
* @param string $destination Path to extract archive to
* @param array $options Extraction options [may be unused]
*
* @return boolean True if successful
*
* @since 3.0.0
*/
public function extract($archive, $destination, array $options = array());
/**
* Tests whether this adapter can unpack files on this computer.
*
* @return boolean True if supported
*
* @since 3.0.0
*/
public static function isSupported();
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 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.file');
/**
* Gzip format adapter for the JArchive class
*
* This class is inspired from and draws heavily in code and concept from
the Compress package of
* The Horde Project <https://www.horde.org>
*
* @contributor Michael Slusarz <slusarz@horde.org>
* @contributor Michael Cochrane <mike@graftonhall.co.nz>
*
* @since 1.5
* @deprecated 4.0 use the Joomla\Archive\Gzip class instead
*/
class JArchiveGzip implements JArchiveExtractable
{
/**
* Gzip file flags.
*
* @var array
* @since 1.5
*/
private $_flags = array('FTEXT' => 0x01, 'FHCRC'
=> 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08,
'FCOMMENT' => 0x10);
/**
* Gzip file data buffer
*
* @var string
* @since 1.5
*/
private $_data = null;
/**
* Extract a Gzip compressed file to a given path
*
* @param string $archive Path to ZIP archive to extract
* @param string $destination Path to extract archive to
* @param array $options Extraction options [unused]
*
* @return boolean True if successful
*
* @since 1.5
* @throws RuntimeException
*/
public function extract($archive, $destination, array $options = array())
{
$this->_data = null;
if (!extension_loaded('zlib'))
{
return $this->raiseWarning(100, 'The zlib extension is not
available.');
}
if (isset($options['use_streams']) &&
$options['use_streams'] != false)
{
return $this->extractStream($archive, $destination, $options);
}
$this->_data = file_get_contents($archive);
if (!$this->_data)
{
return $this->raiseWarning(100, 'Unable to read archive');
}
$position = $this->_getFilePosition();
$buffer = gzinflate(substr($this->_data, $position,
strlen($this->_data) - $position));
if (empty($buffer))
{
return $this->raiseWarning(100, 'Unable to decompress
data');
}
if (JFile::write($destination, $buffer) === false)
{
return $this->raiseWarning(100, 'Unable to write archive');
}
return true;
}
/**
* Method to extract archive using stream objects
*
* @param string $archive Path to ZIP archive to extract
* @param string $destination Path to extract archive to
* @param array $options Extraction options [unused]
*
* @return boolean True if successful
*
* @since 3.6.0
*/
protected function extractStream($archive, $destination, $options =
array())
{
// New style! streams!
$input = JFactory::getStream();
// Use gz
$input->set('processingmethod', 'gz');
if (!$input->open($archive))
{
return $this->raiseWarning(100, 'Unable to read archive
(gz)');
}
$output = JFactory::getStream();
if (!$output->open($destination, 'w'))
{
$input->close();
return $this->raiseWarning(100, 'Unable to write archive
(gz)');
}
do
{
$this->_data = $input->read($input->get('chunksize',
8196));
if ($this->_data && !$output->write($this->_data))
{
$input->close();
return $this->raiseWarning(100, 'Unable to write file
(gz)');
}
}
while ($this->_data);
$output->close();
$input->close();
return true;
}
/**
* Temporary private method to isolate JError from the extract method
* This code should be removed when JError is removed.
*
* @param int $code The application-internal error code for this
error
* @param string $msg The error message, which may also be shown the
user if need be.
*
* @return JException JException instance if JError class exists
*
* @since 3.6.0
* @throws RuntimeException if JError class does not exist
*/
private function raiseWarning($code, $msg)
{
if (class_exists('JError'))
{
return JError::raiseWarning($code, $msg);
}
throw new RuntimeException($msg);
}
/**
* Tests whether this adapter can unpack files on this computer.
*
* @return boolean True if supported
*
* @since 2.5.0
*/
public static function isSupported()
{
return extension_loaded('zlib');
}
/**
* Get file data offset for archive
*
* @return integer Data position marker for archive
*
* @since 1.5
* @throws RuntimeException
*/
public function _getFilePosition()
{
// Gzipped file... unpack it first
$position = 0;
$info = @ unpack('CCM/CFLG/VTime/CXFL/COS',
substr($this->_data, $position + 2));
if (!$info)
{
return $this->raiseWarning(100, 'Unable to decompress
data.');
}
$position += 10;
if ($info['FLG'] & $this->_flags['FEXTRA'])
{
$XLEN = unpack('vLength', substr($this->_data, $position +
0, 2));
$XLEN = $XLEN['Length'];
$position += $XLEN + 2;
}
if ($info['FLG'] & $this->_flags['FNAME'])
{
$filenamePos = strpos($this->_data, "\x0", $position);
$position = $filenamePos + 1;
}
if ($info['FLG'] & $this->_flags['FCOMMENT'])
{
$commentPos = strpos($this->_data, "\x0", $position);
$position = $commentPos + 1;
}
if ($info['FLG'] & $this->_flags['FHCRC'])
{
$hcrc = unpack('vCRC', substr($this->_data, $position + 0,
2));
$hcrc = $hcrc['CRC'];
$position += 2;
}
return $position;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 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.file');
jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.path');
/**
* Tar format adapter for the JArchive class
*
* This class is inspired from and draws heavily in code and concept from
the Compress package of
* The Horde Project <https://www.horde.org>
*
* @contributor Michael Slusarz <slusarz@horde.org>
* @contributor Michael Cochrane <mike@graftonhall.co.nz>
*
* @since 1.5
* @deprecated 4.0 use the Joomla\Archive\Tar class instead
*/
class JArchiveTar implements JArchiveExtractable
{
/**
* Tar file types.
*
* @var array
* @since 1.5
*/
private $_types = array(
0x0 => 'Unix file',
0x30 => 'File',
0x31 => 'Link',
0x32 => 'Symbolic link',
0x33 => 'Character special file',
0x34 => 'Block special file',
0x35 => 'Directory',
0x36 => 'FIFO special file',
0x37 => 'Contiguous file',
);
/**
* Tar file data buffer
*
* @var string
* @since 1.5
*/
private $_data = null;
/**
* Tar file metadata array
*
* @var array
* @since 1.5
*/
private $_metadata = null;
/**
* Extract a ZIP compressed file to a given path
*
* @param string $archive Path to ZIP archive to extract
* @param string $destination Path to extract archive into
* @param array $options Extraction options [unused]
*
* @return boolean|JException True on success, JException instance on
failure if JError class exists
*
* @since 1.5
* @throws RuntimeException if JError class does not exist
*/
public function extract($archive, $destination, array $options = array())
{
$this->_data = null;
$this->_metadata = null;
$this->_data = file_get_contents($archive);
if (!$this->_data)
{
if (class_exists('JError'))
{
return JError::raiseWarning(100, 'Unable to read archive');
}
else
{
throw new RuntimeException('Unable to read archive');
}
}
$this->_getTarInfo($this->_data);
for ($i = 0, $n = count($this->_metadata); $i < $n; $i++)
{
$type = strtolower($this->_metadata[$i]['type']);
if ($type == 'file' || $type == 'unix file')
{
$buffer = $this->_metadata[$i]['data'];
$path = JPath::clean($destination . '/' .
$this->_metadata[$i]['name']);
// Make sure the destination folder exists
if (!JFolder::create(dirname($path)))
{
if (class_exists('JError'))
{
return JError::raiseWarning(100, 'Unable to create
destination');
}
else
{
throw new RuntimeException('Unable to create destination');
}
}
if (JFile::write($path, $buffer) === false)
{
if (class_exists('JError'))
{
return JError::raiseWarning(100, 'Unable to write entry');
}
else
{
throw new RuntimeException('Unable to write entry');
}
}
}
}
return true;
}
/**
* Tests whether this adapter can unpack files on this computer.
*
* @return boolean True if supported
*
* @since 2.5.0
*/
public static function isSupported()
{
return true;
}
/**
* Get the list of files/data from a Tar archive buffer.
*
* @param string &$data The Tar archive buffer.
*
* @return boolean|JException True on success, JException instance on
failure if JError class exists
*
* @since 1.5
* @throws RuntimeException if JError class does not exist
*/
protected function _getTarInfo(& $data)
{
$position = 0;
$return_array = array();
while ($position < strlen($data))
{
if (version_compare(PHP_VERSION, '5.5', '>='))
{
$info = @unpack(
'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Ctypeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor',
substr($data, $position)
);
}
else
{
$info = @unpack(
'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor',
substr($data, $position)
);
}
/**
* This variable has been set in the previous loop,
* meaning that the filename was present in the previous block
* to allow more than 100 characters - see below
*/
if (isset($longlinkfilename))
{
$info['filename'] = $longlinkfilename;
unset($longlinkfilename);
}
if (!$info)
{
if (class_exists('JError'))
{
return JError::raiseWarning(100, 'Unable to decompress
data');
}
else
{
throw new RuntimeException('Unable to decompress data');
}
}
$position += 512;
$contents = substr($data, $position, octdec($info['size']));
$position += ceil(octdec($info['size']) / 512) * 512;
if ($info['filename'])
{
$file = array(
'attr' => null,
'data' => null,
'date' => octdec($info['mtime']),
'name' => trim($info['filename']),
'size' => octdec($info['size']),
'type' =>
isset($this->_types[$info['typeflag']]) ?
$this->_types[$info['typeflag']] : null,
);
if (($info['typeflag'] == 0) || ($info['typeflag']
== 0x30) || ($info['typeflag'] == 0x35))
{
// File or folder.
$file['data'] = $contents;
$mode = hexdec(substr($info['mode'], 4, 3));
$file['attr'] = (($info['typeflag'] == 0x35) ?
'd' : '-') . (($mode & 0x400) ? 'r' :
'-') . (($mode & 0x200) ? 'w' : '-') .
(($mode & 0x100) ? 'x' : '-') . (($mode &
0x040) ? 'r' : '-') . (($mode & 0x020) ?
'w' : '-') . (($mode & 0x010) ? 'x' :
'-') .
(($mode & 0x004) ? 'r' : '-') . (($mode &
0x002) ? 'w' : '-') . (($mode & 0x001) ?
'x' : '-');
}
elseif (chr($info['typeflag']) == 'L' &&
$info['filename'] == '././@LongLink')
{
// GNU tar ././@LongLink support - the filename is actually in the
contents,
// setting a variable here so we can test in the next loop
$longlinkfilename = $contents;
// And the file contents are in the next block so we'll need to
skip this
continue;
}
else
{
// Some other type.
}
$return_array[] = $file;
}
}
$this->_metadata = $return_array;
return true;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Wrapper class for JArchive
*
* @package Joomla.Platform
* @subpackage Archive
* @since 3.4
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
class JArchiveWrapperArchive
{
/**
* Helper wrapper method for extract
*
* @param string $archivename The name of the archive file
* @param string $extractdir Directory to unpack into
*
* @return boolean True for success
*
* @see JArchive::extract()
* @since 3.4
* @throws InvalidArgumentException
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
public function extract($archivename, $extractdir)
{
return JArchive::extract($archivename, $extractdir);
}
/**
* Helper wrapper method for getAdapter
*
* @param string $type The type of adapter (bzip2|gzip|tar|zip).
*
* @return JArchiveExtractable Adapter for the requested type
*
* @see JUserHelper::getAdapter()
* @since 3.4
* @deprecated 4.0 use the Joomla\Archive\Archive class instead
*/
public function getAdapter($type)
{
return JArchive::getAdapter($type);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Archive
*
* @copyright Copyright (C) 2005 - 2020 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.file');
jimport('joomla.filesystem.folder');
/**
* ZIP format adapter for the JArchive class
*
* The ZIP compression code is partially based on code from:
* Eric Mueller <eric@themepark.com>
* http://www.zend.com/codex.php?id=535&single=1
*
* Deins125 <webmaster@atlant.ru>
* http://www.zend.com/codex.php?id=470&single=1
*
* The ZIP compression date code is partially based on code from
* Peter Listiak <mlady@users.sourceforge.net>
*
* This class is inspired from and draws heavily in code and concept from
the Compress package of
* The Horde Project <https://www.horde.org>
*
* @contributor Chuck Hagenbuch <chuck@horde.org>
* @contributor Michael Slusarz <slusarz@horde.org>
* @contributor Michael Cochrane <mike@graftonhall.co.nz>
*
* @since 1.5
* @deprecated 4.0 use the Joomla\Archive\Zip class instead
*/
class JArchiveZip implements JArchiveExtractable
{
/**
* ZIP compression methods.
*
* @var array
* @since 1.5
*/
private $_methods = array(
0x0 => 'None',
0x1 => 'Shrunk',
0x2 => 'Super Fast',
0x3 => 'Fast',
0x4 => 'Normal',
0x5 => 'Maximum',
0x6 => 'Imploded',
0x8 => 'Deflated',
);
/**
* Beginning of central directory record.
*
* @var string
* @since 1.5
*/
private $_ctrlDirHeader = "\x50\x4b\x01\x02";
/**
* End of central directory record.
*
* @var string
* @since 1.5
*/
private $_ctrlDirEnd = "\x50\x4b\x05\x06\x00\x00\x00\x00";
/**
* Beginning of file contents.
*
* @var string
* @since 1.5
*/
private $_fileHeader = "\x50\x4b\x03\x04";
/**
* ZIP file data buffer
*
* @var string
* @since 1.5
*/
private $_data = null;
/**
* ZIP file metadata array
*
* @var array
* @since 1.5
*/
private $_metadata = null;
/**
* Create a ZIP compressed file from an array of file data.
*
* @param string $archive Path to save archive.
* @param array $files Array of files to add to archive.
*
* @return boolean True if successful.
*
* @since 1.5
*
* @todo Finish Implementation
*/
public function create($archive, $files)
{
$contents = array();
$ctrldir = array();
foreach ($files as $file)
{
$this->_addToZIPFile($file, $contents, $ctrldir);
}
return $this->_createZIPFile($contents, $ctrldir, $archive);
}
/**
* Extract a ZIP compressed file to a given path
*
* @param string $archive Path to ZIP archive to extract
* @param string $destination Path to extract archive into
* @param array $options Extraction options [unused]
*
* @return boolean True if successful
*
* @since 1.5
* @throws RuntimeException
*/
public function extract($archive, $destination, array $options = array())
{
if (!is_file($archive))
{
return $this->raiseWarning(100, 'Archive does not exist');
}
if ($this->hasNativeSupport())
{
return $this->extractNative($archive, $destination);
}
return $this->extractCustom($archive, $destination);
}
/**
* Temporary private method to isolate JError from the extract method
* This code should be removed when JError is removed.
*
* @param int $code The application-internal error code for this
error
* @param string $msg The error message, which may also be shown the
user if need be.
*
* @return JException JException instance if JError class exists
*
* @since 3.6.0
* @throws RuntimeException if JError class does not exist
*/
private function raiseWarning($code, $msg)
{
if (class_exists('JError'))
{
return JError::raiseWarning($code, $msg);
}
throw new RuntimeException($msg);
}
/**
* Tests whether this adapter can unpack files on this computer.
*
* @return boolean True if supported
*
* @since 2.5.0
*/
public static function isSupported()
{
return self::hasNativeSupport() || extension_loaded('zlib');
}
/**
* Method to determine if the server has native zip support for faster
handling
*
* @return boolean True if php has native ZIP support
*
* @since 1.5
*/
public static function hasNativeSupport()
{
return extension_loaded('zip');
}
/**
* Checks to see if the data is a valid ZIP file.
*
* @param string &$data ZIP archive data buffer.
*
* @return boolean True if valid, false if invalid.
*
* @since 1.5
*/
public function checkZipData(&$data)
{
if (strpos($data, $this->_fileHeader) === false)
{
return false;
}
return true;
}
/**
* Extract a ZIP compressed file to a given path using a php based
algorithm that only requires zlib support
*
* @param string $archive Path to ZIP archive to extract.
* @param string $destination Path to extract archive into.
*
* @return mixed True if successful
*
* @since 3.0
* @throws RuntimeException
*/
protected function extractCustom($archive, $destination)
{
$this->_data = null;
$this->_metadata = null;
if (!extension_loaded('zlib'))
{
return $this->raiseWarning(100, 'Zlib not supported');
}
$this->_data = file_get_contents($archive);
if (!$this->_data)
{
return $this->raiseWarning(100, 'Unable to read archive
(zip)');
}
if (!$this->_readZipInfo($this->_data))
{
return $this->raiseWarning(100, 'Get ZIP Information
failed');
}
for ($i = 0, $n = count($this->_metadata); $i < $n; $i++)
{
$lastPathCharacter = substr($this->_metadata[$i]['name'],
-1, 1);
if ($lastPathCharacter !== '/' && $lastPathCharacter
!== '\\')
{
$buffer = $this->_getFileData($i);
$path = JPath::clean($destination . '/' .
$this->_metadata[$i]['name']);
// Make sure the destination folder exists
if (!JFolder::create(dirname($path)))
{
return $this->raiseWarning(100, 'Unable to create
destination');
}
if (JFile::write($path, $buffer) === false)
{
return $this->raiseWarning(100, 'Unable to write entry');
}
}
}
return true;
}
/**
* Extract a ZIP compressed file to a given path using native php api
calls for speed
*
* @param string $archive Path to ZIP archive to extract
* @param string $destination Path to extract archive into
*
* @return boolean True on success
*
* @since 3.0
* @throws RuntimeException
*/
protected function extractNative($archive, $destination)
{
$zip = new \ZipArchive;
if ($zip->open($archive) !== true)
{
return $this->raiseWarning(100, 'Unable to open archive');
}
// Make sure the destination folder exists
if (!JFolder::create($destination))
{
return $this->raiseWarning(100, 'Unable to create
destination');
}
// Read files in the archive
for ($index = 0; $index < $zip->numFiles; $index++)
{
$file = $zip->getNameIndex($index);
if (substr($file, -1) === '/')
{
continue;
}
$buffer = $zip->getFromIndex($index);
if ($buffer === false)
{
return $this->raiseWarning(100, 'Unable to read entry');
}
if (JFile::write($destination . '/' . $file, $buffer) ===
false)
{
return $this->raiseWarning(100, 'Unable to write entry');
}
}
$zip->close();
return true;
}
/**
* Get the list of files/data from a ZIP archive buffer.
*
* <pre>
* KEY: Position in zipfile
* VALUES: 'attr' -- File attributes
* 'crc' -- CRC checksum
* 'csize' -- Compressed file size
* 'date' -- File modification time
* 'name' -- Filename
* 'method'-- Compression method
* 'size' -- Original file size
* 'type' -- File type
* </pre>
*
* @param string &$data The ZIP archive buffer.
*
* @return boolean True on success
*
* @since 2.5.0
* @throws RuntimeException
*/
private function _readZipInfo(&$data)
{
$entries = array();
// Find the last central directory header entry
$fhLast = strpos($data, $this->_ctrlDirEnd);
do
{
$last = $fhLast;
}
while (($fhLast = strpos($data, $this->_ctrlDirEnd, $fhLast + 1)) !==
false);
// Find the central directory offset
$offset = 0;
if ($last)
{
$endOfCentralDirectory = unpack(
'vNumberOfDisk/vNoOfDiskWithStartOfCentralDirectory/vNoOfCentralDirectoryEntriesOnDisk/'
.
'vTotalCentralDirectoryEntries/VSizeOfCentralDirectory/VCentralDirectoryOffset/vCommentLength',
substr($data, $last + 4)
);
$offset = $endOfCentralDirectory['CentralDirectoryOffset'];
}
// Get details from central directory structure.
$fhStart = strpos($data, $this->_ctrlDirHeader, $offset);
$dataLength = strlen($data);
do
{
if ($dataLength < $fhStart + 31)
{
return $this->raiseWarning(100, 'Invalid Zip Data');
}
$info =
unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength',
substr($data, $fhStart + 10, 20));
$name = substr($data, $fhStart + 46, $info['Length']);
$entries[$name] = array(
'attr' => null,
'crc' => sprintf('%08s',
dechex($info['CRC32'])),
'csize' => $info['Compressed'],
'date' => null,
'_dataStart' => null,
'name' => $name,
'method' => $this->_methods[$info['Method']],
'_method' => $info['Method'],
'size' => $info['Uncompressed'],
'type' => null,
);
$entries[$name]['date'] = mktime(
(($info['Time'] >> 11) & 0x1f),
(($info['Time'] >> 5) & 0x3f),
(($info['Time'] << 1) & 0x3e),
(($info['Time'] >> 21) & 0x07),
(($info['Time'] >> 16) & 0x1f),
((($info['Time'] >> 25) & 0x7f) + 1980)
);
if ($dataLength < $fhStart + 43)
{
return $this->raiseWarning(100, 'Invalid ZIP data');
}
$info = unpack('vInternal/VExternal/VOffset', substr($data,
$fhStart + 36, 10));
$entries[$name]['type'] = ($info['Internal'] &
0x01) ? 'text' : 'binary';
$entries[$name]['attr'] = (($info['External'] &
0x10) ? 'D' : '-') . (($info['External']
& 0x20) ? 'A' : '-')
. (($info['External'] & 0x03) ? 'S' :
'-') . (($info['External'] & 0x02) ? 'H'
: '-') . (($info['External'] & 0x01) ?
'R' : '-');
$entries[$name]['offset'] = $info['Offset'];
// Get details from local file header since we have the offset
$lfhStart = strpos($data, $this->_fileHeader,
$entries[$name]['offset']);
if ($dataLength < $lfhStart + 34)
{
return $this->raiseWarning(100, 'Invalid Zip Data');
}
$info =
unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength',
substr($data, $lfhStart + 8, 25));
$name = substr($data, $lfhStart + 30, $info['Length']);
$entries[$name]['_dataStart'] = $lfhStart + 30 +
$info['Length'] + $info['ExtraLength'];
// Bump the max execution time because not using the built in php zip
libs makes this process slow.
@set_time_limit(ini_get('max_execution_time'));
}
while ((($fhStart = strpos($data, $this->_ctrlDirHeader, $fhStart +
46)) !== false));
$this->_metadata = array_values($entries);
return true;
}
/**
* Returns the file data for a file by offset in the ZIP archive
*
* @param integer $key The position of the file in the archive.
*
* @return string Uncompressed file data buffer.
*
* @since 1.5
*/
private function _getFileData($key)
{
$method = $this->_metadata[$key]['_method'];
if ($method == 0x12 && !extension_loaded('bz2'))
{
return '';
}
switch ($method)
{
case 0x8:
return gzinflate(substr($this->_data,
$this->_metadata[$key]['_dataStart'],
$this->_metadata[$key]['csize']));
case 0x0:
// Files that aren't compressed.
return substr($this->_data,
$this->_metadata[$key]['_dataStart'],
$this->_metadata[$key]['csize']);
case 0x12:
return bzdecompress(substr($this->_data,
$this->_metadata[$key]['_dataStart'],
$this->_metadata[$key]['csize']));
}
return '';
}
/**
* Converts a UNIX timestamp to a 4-byte DOS date and time format
* (date in high 2-bytes, time in low 2-bytes allowing magnitude
* comparison).
*
* @param int $unixtime The current UNIX timestamp.
*
* @return int The current date in a 4-byte DOS format.
*
* @since 1.5
*/
protected function _unix2DOSTime($unixtime = null)
{
$timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
if ($timearray['year'] < 1980)
{
$timearray['year'] = 1980;
$timearray['mon'] = 1;
$timearray['mday'] = 1;
$timearray['hours'] = 0;
$timearray['minutes'] = 0;
$timearray['seconds'] = 0;
}
return (($timearray['year'] - 1980) << 25) |
($timearray['mon'] << 21) | ($timearray['mday']
<< 16) | ($timearray['hours'] << 11) |
($timearray['minutes'] << 5) |
($timearray['seconds'] >> 1);
}
/**
* Adds a "file" to the ZIP archive.
*
* @param array &$file File data array to add
* @param array &$contents An array of existing zipped files.
* @param array &$ctrldir An array of central directory
information.
*
* @return void
*
* @since 1.5
*
* @todo Review and finish implementation
*/
private function _addToZIPFile(array &$file, array &$contents,
array &$ctrldir)
{
$data = &$file['data'];
$name = str_replace('\\', '/',
$file['name']);
/* See if time/date information has been provided. */
$ftime = null;
if (isset($file['time']))
{
$ftime = $file['time'];
}
// Get the hex time.
$dtime = dechex($this->_unix2DosTime($ftime));
$hexdtime = chr(hexdec($dtime[6] . $dtime[7])) . chr(hexdec($dtime[4] .
$dtime[5])) . chr(hexdec($dtime[2] . $dtime[3]))
. chr(hexdec($dtime[0] . $dtime[1]));
/* Begin creating the ZIP data. */
$fr = $this->_fileHeader;
/* Version needed to extract. */
$fr .= "\x14\x00";
/* General purpose bit flag. */
$fr .= "\x00\x00";
/* Compression method. */
$fr .= "\x08\x00";
/* Last modification time/date. */
$fr .= $hexdtime;
/* "Local file header" segment. */
$unc_len = strlen($data);
$crc = crc32($data);
$zdata = gzcompress($data);
$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
$c_len = strlen($zdata);
/* CRC 32 information. */
$fr .= pack('V', $crc);
/* Compressed filesize. */
$fr .= pack('V', $c_len);
/* Uncompressed filesize. */
$fr .= pack('V', $unc_len);
/* Length of filename. */
$fr .= pack('v', strlen($name));
/* Extra field length. */
$fr .= pack('v', 0);
/* File name. */
$fr .= $name;
/* "File data" segment. */
$fr .= $zdata;
/* Add this entry to array. */
$old_offset = strlen(implode('', $contents));
$contents[] = &$fr;
/* Add to central directory record. */
$cdrec = $this->_ctrlDirHeader;
/* Version made by. */
$cdrec .= "\x00\x00";
/* Version needed to extract */
$cdrec .= "\x14\x00";
/* General purpose bit flag */
$cdrec .= "\x00\x00";
/* Compression method */
$cdrec .= "\x08\x00";
/* Last mod time/date. */
$cdrec .= $hexdtime;
/* CRC 32 information. */
$cdrec .= pack('V', $crc);
/* Compressed filesize. */
$cdrec .= pack('V', $c_len);
/* Uncompressed filesize. */
$cdrec .= pack('V', $unc_len);
/* Length of filename. */
$cdrec .= pack('v', strlen($name));
/* Extra field length. */
$cdrec .= pack('v', 0);
/* File comment length. */
$cdrec .= pack('v', 0);
/* Disk number start. */
$cdrec .= pack('v', 0);
/* Internal file attributes. */
$cdrec .= pack('v', 0);
/* External file attributes -'archive' bit set. */
$cdrec .= pack('V', 32);
/* Relative offset of local header. */
$cdrec .= pack('V', $old_offset);
/* File name. */
$cdrec .= $name;
/* Optional extra field, file comment goes here. */
/* Save to central directory array. */
$ctrldir[] = &$cdrec;
}
/**
* Creates the ZIP file.
*
* Official ZIP file format:
https://support.pkware.com/display/PKZIP/APPNOTE
*
* @param array &$contents An array of existing zipped files.
* @param array &$ctrlDir An array of central directory
information.
* @param string $path The path to store the archive.
*
* @return boolean True if successful
*
* @since 1.5
*
* @todo Review and finish implementation
*/
private function _createZIPFile(array &$contents, array &$ctrlDir,
$path)
{
$data = implode('', $contents);
$dir = implode('', $ctrlDir);
$buffer = $data . $dir . $this->_ctrlDirEnd . /* Total # of entries
"on this disk". */
pack('v', count($ctrlDir)) . /* Total # of entries overall. */
pack('v', count($ctrlDir)) . /* Size of central directory. */
pack('V', strlen($dir)) . /* Offset to start of central dir. */
pack('V', strlen($data)) . /* ZIP file comment length. */
"\x00\x00";
if (JFile::write($path, $buffer) === false)
{
return false;
}
return true;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Base
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Adapter Class
* Retains common adapter pattern functions
* Class harvested from joomla.installer.installer
*
* @since 1.6
* @deprecated 5.0 Will be removed without replacement
*/
class JAdapter extends JObject
{
/**
* Associative array of adapters
*
* @var JAdapterInstance[]
* @since 1.6
*/
protected $_adapters = array();
/**
* Adapter Folder
*
* @var string
* @since 1.6
*/
protected $_adapterfolder = 'adapters';
/**
* Adapter Class Prefix
*
* @var string
* @since 1.6
*/
protected $_classprefix = 'J';
/**
* Base Path for the adapter instance
*
* @var string
* @since 1.6
*/
protected $_basepath = null;
/**
* Database Connector Object
*
* @var JDatabaseDriver
* @since 1.6
*/
protected $_db;
/**
* Constructor
*
* @param string $basepath Base Path of the adapters
* @param string $classprefix Class prefix of adapters
* @param string $adapterfolder Name of folder to append to base path
*
* @since 1.6
*/
public function __construct($basepath, $classprefix = null, $adapterfolder
= null)
{
$this->_basepath = $basepath;
$this->_classprefix = $classprefix ? $classprefix : 'J';
$this->_adapterfolder = $adapterfolder ? $adapterfolder :
'adapters';
$this->_db = JFactory::getDbo();
}
/**
* Get the database connector object
*
* @return JDatabaseDriver Database connector object
*
* @since 1.6
*/
public function getDbo()
{
return $this->_db;
}
/**
* Return an adapter.
*
* @param string $name Name of adapter to return
* @param array $options Adapter options
*
* @return JAdapterInstance|boolean Adapter of type 'name' or
false
*
* @since 1.6
*/
public function getAdapter($name, $options = array())
{
if (array_key_exists($name, $this->_adapters))
{
return $this->_adapters[$name];
}
if ($this->setAdapter($name, $options))
{
return $this->_adapters[$name];
}
return false;
}
/**
* Set an adapter by name
*
* @param string $name Adapter name
* @param object &$adapter Adapter object
* @param array $options Adapter options
*
* @return boolean True if successful
*
* @since 1.6
*/
public function setAdapter($name, &$adapter = null, $options =
array())
{
if (is_object($adapter))
{
$this->_adapters[$name] = &$adapter;
return true;
}
$class = rtrim($this->_classprefix, '\\') . '\\' .
ucfirst($name);
if (class_exists($class))
{
$this->_adapters[$name] = new $class($this, $this->_db, $options);
return true;
}
$class = rtrim($this->_classprefix, '\\') . '\\' .
ucfirst($name) . 'Adapter';
if (class_exists($class))
{
$this->_adapters[$name] = new $class($this, $this->_db, $options);
return true;
}
$fullpath = $this->_basepath . '/' .
$this->_adapterfolder . '/' . strtolower($name) .
'.php';
if (!file_exists($fullpath))
{
return false;
}
// Try to load the adapter object
$class = $this->_classprefix . ucfirst($name);
JLoader::register($class, $fullpath);
if (!class_exists($class))
{
return false;
}
$this->_adapters[$name] = new $class($this, $this->_db, $options);
return true;
}
/**
* Loads all adapters.
*
* @param array $options Adapter options
*
* @return void
*
* @since 1.6
*/
public function loadAllAdapters($options = array())
{
$files = new DirectoryIterator($this->_basepath . '/' .
$this->_adapterfolder);
/* @type $file DirectoryIterator */
foreach ($files as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() != 'php')
{
continue;
}
// Try to load the adapter object
require_once $this->_basepath . '/' .
$this->_adapterfolder . '/' . $fileName;
// Derive the class name from the filename.
$name = str_ireplace('.php', '',
ucfirst(trim($fileName)));
$class = $this->_classprefix . ucfirst($name);
if (!class_exists($class))
{
// Skip to next one
continue;
}
$adapter = new $class($this, $this->_db, $options);
$this->_adapters[$name] = clone $adapter;
}
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Base
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Adapter Instance Class
*
* @since 1.6
* @deprecated 5.0 Will be removed without replacement
*/
class JAdapterInstance extends JObject
{
/**
* Parent
*
* @var JAdapter
* @since 1.6
*/
protected $parent = null;
/**
* Database
*
* @var JDatabaseDriver
* @since 1.6
*/
protected $db = null;
/**
* Constructor
*
* @param JAdapter $parent Parent object
* @param JDatabaseDriver $db Database object
* @param array $options Configuration Options
*
* @since 1.6
*/
public function __construct(JAdapter $parent, JDatabaseDriver $db, array
$options = array())
{
// Set the properties from the options array that is passed in
$this->setProperties($options);
// Set the parent and db in case $options for some reason overrides it.
$this->parent = $parent;
// Pull in the global dbo in case something happened to it.
$this->db = $db ?: JFactory::getDbo();
}
/**
* Retrieves the parent object
*
* @return JAdapter
*
* @since 1.6
*/
public function getParent()
{
return $this->parent;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Controller
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
use Joomla\Application\AbstractApplication;
/**
* Joomla Platform Base Controller Class
*
* @since 3.0.0
* @deprecated 4.0 Use the default MVC library
*/
abstract class JControllerBase implements JController
{
/**
* The application object.
*
* @var AbstractApplication
* @since 3.0.0
*/
protected $app;
/**
* The input object.
*
* @var JInput
* @since 3.0.0
*/
protected $input;
/**
* Instantiate the controller.
*
* @param JInput $input The input object.
* @param AbstractApplication $app The application object.
*
* @since 3.0.0
*/
public function __construct(JInput $input = null, AbstractApplication $app
= null)
{
// Setup dependencies.
$this->app = isset($app) ? $app : $this->loadApplication();
$this->input = isset($input) ? $input : $this->loadInput();
}
/**
* Get the application object.
*
* @return AbstractApplication The application object.
*
* @since 3.0.0
*/
public function getApplication()
{
return $this->app;
}
/**
* Get the input object.
*
* @return JInput The input object.
*
* @since 3.0.0
*/
public function getInput()
{
return $this->input;
}
/**
* Serialize the controller.
*
* @return string The serialized controller.
*
* @since 3.0.0
*/
public function serialize()
{
return serialize($this->input);
}
/**
* Unserialize the controller.
*
* @param string $input The serialized controller.
*
* @return JController Supports chaining.
*
* @since 3.0.0
* @throws UnexpectedValueException if input is not the right class.
*/
public function unserialize($input)
{
// Setup dependencies.
$this->app = $this->loadApplication();
// Unserialize the input.
$this->input = unserialize($input);
if (!($this->input instanceof JInput))
{
throw new UnexpectedValueException(sprintf('%s::unserialize would
not accept a `%s`.', get_class($this), gettype($this->input)));
}
return $this;
}
/**
* Load the application object.
*
* @return AbstractApplication The application object.
*
* @since 3.0.0
*/
protected function loadApplication()
{
return JFactory::getApplication();
}
/**
* Load the input object.
*
* @return JInput The input object.
*
* @since 3.0.0
*/
protected function loadInput()
{
return $this->app->input;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Controller
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
use Joomla\Application\AbstractApplication;
/**
* Joomla Platform Controller Interface
*
* @since 3.0.0
* @deprecated 4.0 Use the default MVC library
*/
interface JController extends Serializable
{
/**
* Execute the controller.
*
* @return boolean True if controller finished execution, false if the
controller did not
* finish execution. A controller might return false if
some precondition for
* the controller to run has not been satisfied.
*
* @since 3.0.0
* @throws LogicException
* @throws RuntimeException
*/
public function execute();
/**
* Get the application object.
*
* @return AbstractApplication The application object.
*
* @since 3.0.0
*/
public function getApplication();
/**
* Get the input object.
*
* @return JInput The input object.
*
* @since 3.0.0
*/
public function getInput();
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Database connector class.
*
* @since 1.7.0
* @deprecated 4.0
*/
abstract class JDatabase
{
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 1.7.0
* @throws RuntimeException
* @deprecated 4.0
*/
public function query()
{
JLog::add('JDatabase::query() is deprecated, use
JDatabaseDriver::execute() instead.', JLog::WARNING,
'deprecated');
return $this->execute();
}
/**
* Get a list of available database connectors. The list will only be
populated with connectors that both
* the class exists and the static test method returns true. This gives
us the ability to have a multitude
* of connector classes that are self-aware as to whether or not they are
able to be used on a given system.
*
* @return array An array of available database connectors.
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function getConnectors()
{
JLog::add('JDatabase::getConnectors() is deprecated, use
JDatabaseDriver::getConnectors() instead.', JLog::WARNING,
'deprecated');
return JDatabaseDriver::getConnectors();
}
/**
* Gets the error message from the database connection.
*
* @param boolean $escaped True to escape the message string for use
in JavaScript.
*
* @return string The error message for the most recent query.
*
* @deprecated 4.0
* @since 1.7.0
*/
public function getErrorMsg($escaped = false)
{
JLog::add('JDatabase::getErrorMsg() is deprecated, use exception
handling instead.', JLog::WARNING, 'deprecated');
if ($escaped)
{
return addslashes($this->errorMsg);
}
else
{
return $this->errorMsg;
}
}
/**
* Gets the error number from the database connection.
*
* @return integer The error number for the most recent query.
*
* @since 1.7.0
* @deprecated 4.0
*/
public function getErrorNum()
{
JLog::add('JDatabase::getErrorNum() is deprecated, use exception
handling instead.', JLog::WARNING, 'deprecated');
return $this->errorNum;
}
/**
* Method to return a JDatabaseDriver instance based on the given options.
There are three global options and then
* the rest are specific to the database driver. The 'driver'
option defines which JDatabaseDriver class is
* used for the connection -- the default is 'mysqli'. The
'database' option determines which database is to
* be used for the connection. The 'select' option determines
whether the connector should automatically select
* the chosen database.
*
* Instances are unique to the given options and new objects are only
created when a unique options array is
* passed into the method. This ensures that we don't end up with
unnecessary database connection resources.
*
* @param array $options Parameters to be passed to the database
driver.
*
* @return JDatabaseDriver A database object.
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function getInstance($options = array())
{
JLog::add('JDatabase::getInstance() is deprecated, use
JDatabaseDriver::getInstance() instead.', JLog::WARNING,
'deprecated');
return JDatabaseDriver::getInstance($options);
}
/**
* Splits a string of multiple queries into an array of individual
queries.
*
* @param string $query Input SQL string with which to split into
individual queries.
*
* @return array The queries from the input string separated into an
array.
*
* @since 1.7.0
* @deprecated 4.0
*/
public static function splitSql($query)
{
JLog::add('JDatabase::splitSql() is deprecated, use
JDatabaseDriver::splitSql() instead.', JLog::WARNING,
'deprecated');
return JDatabaseDriver::splitSql($query);
}
/**
* Return the most recent error message for the database connector.
*
* @param boolean $showSQL True to display the SQL statement sent to
the database as well as the error.
*
* @return string The error message for the most recent query.
*
* @since 1.7.0
* @deprecated 4.0
*/
public function stderr($showSQL = false)
{
JLog::add('JDatabase::stderr() is deprecated.', JLog::WARNING,
'deprecated');
if ($this->errorNum != 0)
{
return JText::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED',
$this->errorNum, $this->errorMsg)
. ($showSQL ? "<br />SQL =
<pre>$this->sql</pre>" : '');
}
else
{
return JText::_('JLIB_DATABASE_FUNCTION_NOERROR');
}
}
/**
* Test to see if the connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 1.7.0
* @deprecated 4.0 - Use JDatabaseDriver::isSupported() instead.
*/
public static function test()
{
JLog::add('JDatabase::test() is deprecated. Use
JDatabaseDriver::isSupported() instead.', JLog::WARNING,
'deprecated');
return static::isSupported();
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL database driver
*
* @link https://dev.mysql.com/doc/
* @since 3.0.0
* @deprecated 4.0 Use MySQLi or PDO MySQL instead
*/
class JDatabaseDriverMysql extends JDatabaseDriverMysqli
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'mysql';
/**
* Constructor.
*
* @param array $options Array of database options with keys: host,
user, password, database, select.
*
* @since 3.0.0
*/
public function __construct($options)
{
// PHP's `mysql` extension is not present in PHP 7, block
instantiation in this environment
if (PHP_MAJOR_VERSION >= 7)
{
throw new JDatabaseExceptionUnsupported(
'This driver is unsupported in PHP 7, please use the MySQLi or PDO
MySQL driver instead.'
);
}
// Get some basic values from the options.
$options['host'] = (isset($options['host'])) ?
$options['host'] : 'localhost';
$options['user'] = (isset($options['user'])) ?
$options['user'] : '';
$options['password'] = (isset($options['password']))
? $options['password'] : '';
$options['database'] = (isset($options['database']))
? $options['database'] : '';
$options['select'] = (isset($options['select'])) ?
(bool) $options['select'] : true;
// Finalize initialisation.
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
// Make sure the MySQL extension for PHP is installed and enabled.
if (!self::isSupported())
{
throw new JDatabaseExceptionUnsupported('Make sure the MySQL
extension for PHP is installed and enabled.');
}
// Attempt to connect to the server.
if (!($this->connection = @
mysql_connect($this->options['host'],
$this->options['user'],
$this->options['password'], true)))
{
throw new JDatabaseExceptionConnecting('Could not connect to MySQL
server.');
}
// Set sql_mode to non_strict mode
mysql_query("SET @@SESSION.sql_mode = '';",
$this->connection);
// If auto-select is enabled select the given database.
if ($this->options['select'] &&
!empty($this->options['database']))
{
$this->select($this->options['database']);
}
// Pre-populate the UTF-8 Multibyte compatibility flag based on server
version
$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();
// Set the character set (needed for MySQL 4.1.2+).
$this->utf = $this->setUtf();
// Disable query cache and turn profiling ON in debug mode.
if ($this->debug)
{
mysql_query('SET query_cache_type = 0;',
$this->connection);
if ($this->hasProfiling())
{
mysql_query('SET profiling_history_size = 100, profiling =
1;', $this->connection);
}
}
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
// Close the connection.
if (is_resource($this->connection))
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
mysql_close($this->connection);
}
$this->connection = null;
}
/**
* Method to escape a string for usage in an SQL statement.
*
* @param string $text The string to be escaped.
* @param boolean $extra Optional parameter to provide extra escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$this->connect();
$result = mysql_real_escape_string($text, $this->getConnection());
if ($extra)
{
$result = addcslashes($result, '%_');
}
return $result;
}
/**
* Test to see if the MySQL connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return PHP_MAJOR_VERSION < 7 &&
function_exists('mysql_connect');
}
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 3.0.0
*/
public function connected()
{
if (is_resource($this->connection))
{
return @mysql_ping($this->connection);
}
return false;
}
/**
* Get the number of affected rows by the last INSERT, UPDATE, REPLACE or
DELETE for the previous executed SQL statement.
*
* @return integer The number of affected rows.
*
* @since 3.0.0
*/
public function getAffectedRows()
{
$this->connect();
return mysql_affected_rows($this->connection);
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
* This command is only valid for statements like SELECT or SHOW that
return an actual result set.
* To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE
or DELETE query, use getAffectedRows().
*
* @param resource $cursor An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 3.0.0
*/
public function getNumRows($cursor = null)
{
$this->connect();
return mysql_num_rows($cursor ? $cursor : $this->cursor);
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
return mysql_get_server_info($this->connection);
}
/**
* Method to get the auto-incremented value from the last INSERT
statement.
*
* @return integer The value of the auto-increment field from the last
inserted row.
*
* @since 3.0.0
*/
public function insertid()
{
$this->connect();
return mysql_insert_id($this->connection);
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and
cause issues later
$query = $this->replacePrefix((string) $this->sql);
if (!($this->sql instanceof JDatabaseQuery) &&
($this->limit > 0 || $this->offset > 0))
{
$query .= ' LIMIT ' . $this->offset . ', ' .
$this->limit;
}
if (!is_resource($this->connection))
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
// Increment the query counter.
$this->count++;
// Reset the error values.
$this->errorNum = 0;
$this->errorMsg = '';
// If debugging is enabled then let's log the query.
if ($this->debug)
{
// Add the query to the object queue.
$this->log[] = $query;
JLog::add($query, JLog::DEBUG, 'databasequery');
$this->timings[] = microtime(true);
}
// Execute the query. Error suppression is used here to prevent
warnings/notices that the connection has been lost.
$this->cursor = @mysql_query($query, $this->connection);
if ($this->debug)
{
$this->timings[] = microtime(true);
if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
else
{
$this->callStacks[] = debug_backtrace();
}
}
// If an error occurred handle it.
if (!$this->cursor)
{
// Get the error number and message before we execute any more queries.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Check if the server was disconnected.
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
// If connect fails, ignore that exception and throw the normal
exception.
catch (RuntimeException $e)
{
// Get the error number and message.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum, $e);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
// The server was not disconnected.
else
{
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
}
return $this->cursor;
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
if (!$database)
{
return false;
}
if (!mysql_select_db($database, $this->connection))
{
throw new JDatabaseExceptionConnecting('Could not connect to MySQL
database.');
}
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
// If UTF is not supported return false immediately
if (!$this->utf)
{
return false;
}
// Make sure we're connected to the server
$this->connect();
// Which charset should I use, plain utf8 or multibyte utf8mb4?
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
$result = @mysql_set_charset($charset, $this->connection);
/**
* If I could not set the utf8mb4 charset then the server doesn't
support utf8mb4 despite claiming otherwise.
* This happens on old MySQL server versions (less than 5.5.3) using the
mysqlnd PHP driver. Since mysqlnd
* masks the server version and reports only its own we can not be sure
if the server actually does support
* UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the
utf8mb4 charset is undefined in this case we
* catch the error and determine that utf8mb4 is not supported!
*/
if (!$result && $this->utf8mb4)
{
$this->utf8mb4 = false;
$result = @mysql_set_charset('utf8', $this->connection);
}
return $result;
}
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchArray($cursor = null)
{
return mysql_fetch_row($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchAssoc($cursor = null)
{
return mysql_fetch_assoc($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class The class name to use for the returned row
object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject($cursor = null, $class =
'stdClass')
{
return mysql_fetch_object($cursor ? $cursor : $this->cursor, $class);
}
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult($cursor = null)
{
mysql_free_result($cursor ? $cursor : $this->cursor);
}
/**
* Internal function to check if profiling is available
*
* @return boolean
*
* @since 3.1.3
*/
private function hasProfiling()
{
try
{
$res = mysql_query("SHOW VARIABLES LIKE
'have_profiling'", $this->connection);
$row = mysql_fetch_assoc($res);
return isset($row);
}
catch (Exception $e)
{
return false;
}
}
/**
* Does the database server claim to have support for UTF-8 Multibyte
(utf8mb4) collation?
*
* libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL
server). mysqlnd supports utf8mb4 since 5.0.9.
*
* @return boolean
*
* @since CMS 3.5.0
*/
private function serverClaimsUtf8mb4Support()
{
$client_version = mysql_get_client_info();
$server_version = $this->getVersion();
if (version_compare($server_version, '5.5.3',
'<'))
{
return false;
}
else
{
if (strpos($client_version, 'mysqlnd') !== false)
{
$client_version = preg_replace('/^\D+([\d.]+).*/',
'$1', $client_version);
return version_compare($client_version, '5.0.9',
'>=');
}
else
{
return version_compare($client_version, '5.5.3',
'>=');
}
}
}
/**
* Return the actual SQL Error number
*
* @return integer The SQL Error number
*
* @since 3.4.6
*/
protected function getErrorNumber()
{
return (int) mysql_errno($this->connection);
}
/**
* Return the actual SQL Error message
*
* @return string The SQL Error message
*
* @since 3.4.6
*/
protected function getErrorMessage()
{
$errorMessage = (string) mysql_error($this->connection);
// Replace the Databaseprefix with `#__` if we are not in Debug
if (!$this->debug)
{
$errorMessage = str_replace($this->tablePrefix, '#__',
$errorMessage);
}
return $errorMessage;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQLi database driver
*
* @link https://www.php.net/manual/en/book.mysqli.php
* @since 3.0.0
*/
class JDatabaseDriverMysqli extends JDatabaseDriver
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'mysqli';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'mysql';
/**
* @var mysqli The database connection resource.
* @since 1.7.0
*/
protected $connection;
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.0.1
*/
protected $nameQuote = '`';
/**
* The null or zero representation of a timestamp for the database driver.
This should be
* defined in child classes to hold the appropriate value for the engine.
*
* @var string
* @since 3.0.1
*/
protected $nullDate = '0000-00-00 00:00:00';
/**
* @var string The minimum supported database version.
* @since 3.0.1
*/
protected static $dbMinimum = '5.0.4';
/**
* Constructor.
*
* @param array $options List of options used to configure the
connection
*
* @since 3.0.0
*/
public function __construct($options)
{
// Get some basic values from the options.
$options['host'] = (isset($options['host'])) ?
$options['host'] : 'localhost';
$options['user'] = (isset($options['user'])) ?
$options['user'] : '';
$options['password'] = (isset($options['password']))
? $options['password'] : '';
$options['database'] = (isset($options['database']))
? $options['database'] : '';
$options['select'] = (isset($options['select'])) ?
(bool) $options['select'] : true;
$options['port'] = (isset($options['port'])) ?
(int) $options['port'] : null;
$options['socket'] = (isset($options['socket'])) ?
$options['socket'] : null;
// Finalize initialisation.
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
/*
* Unlike mysql_connect(), mysqli_connect() takes the port and socket as
separate arguments. Therefore, we
* have to extract them from the host string.
*/
$port = isset($this->options['port']) ?
$this->options['port'] : 3306;
$regex =
'/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/';
if (preg_match($regex, $this->options['host'], $matches))
{
// It's an IPv4 address with or without port
$this->options['host'] = $matches['host'];
if (!empty($matches['port']))
{
$port = $matches['port'];
}
}
elseif
(preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/',
$this->options['host'], $matches))
{
// We assume square-bracketed IPv6 address with or without port, e.g.
[fe80:102::2%eth1]:3306
$this->options['host'] = $matches['host'];
if (!empty($matches['port']))
{
$port = $matches['port'];
}
}
elseif
(preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i',
$this->options['host'], $matches))
{
// Named host (e.g example.com or localhost) with or without port
$this->options['host'] = $matches['host'];
if (!empty($matches['port']))
{
$port = $matches['port'];
}
}
elseif (preg_match('/^:(?P<port>[^:]+)$/',
$this->options['host'], $matches))
{
// Empty host, just port, e.g. ':3306'
$this->options['host'] = 'localhost';
$port = $matches['port'];
}
// ... else we assume normal (naked) IPv6 address, so host and port stay
as they are or default
// Get the port number or socket name
if (is_numeric($port))
{
$this->options['port'] = (int) $port;
}
else
{
$this->options['socket'] = $port;
}
// Make sure the MySQLi extension for PHP is installed and enabled.
if (!self::isSupported())
{
throw new JDatabaseExceptionUnsupported('The MySQLi extension for
PHP is not installed or enabled.');
}
$this->connection = @mysqli_connect(
$this->options['host'],
$this->options['user'],
$this->options['password'], null,
$this->options['port'], $this->options['socket']
);
// Attempt to connect to the server.
if (!$this->connection)
{
throw new JDatabaseExceptionConnecting('Could not connect to MySQL
server.');
}
// Set sql_mode to non_strict mode
mysqli_query($this->connection, "SET @@SESSION.sql_mode =
'';");
// If auto-select is enabled select the given database.
if ($this->options['select'] &&
!empty($this->options['database']))
{
$this->select($this->options['database']);
}
// Pre-populate the UTF-8 Multibyte compatibility flag based on server
version
$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();
// Set the character set (needed for MySQL 4.1.2+).
$this->utf = $this->setUtf();
// Disable query cache and turn profiling ON in debug mode.
if ($this->debug)
{
mysqli_query($this->connection, 'SET query_cache_type =
0;');
if ($this->hasProfiling())
{
mysqli_query($this->connection, 'SET profiling_history_size =
100, profiling = 1;');
}
}
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
// Close the connection.
if ($this->connection instanceof mysqli &&
$this->connection->stat() !== false)
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
mysqli_close($this->connection);
}
$this->connection = null;
}
/**
* Method to escape a string for usage in an SQL statement.
*
* @param string $text The string to be escaped.
* @param boolean $extra Optional parameter to provide extra escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$this->connect();
$result = mysqli_real_escape_string($this->getConnection(), $text);
if ($extra)
{
$result = addcslashes($result, '%_');
}
return $result;
}
/**
* Test to see if the MySQL connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('mysqli_connect');
}
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 3.0.0
*/
public function connected()
{
if (is_object($this->connection))
{
return mysqli_ping($this->connection);
}
return false;
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriverMysqli Returns this object to support
chaining.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$query = $this->getQuery(true);
$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS
' : '') . $query->quoteName($tableName));
$this->execute();
return $this;
}
/**
* Get the number of affected rows by the last INSERT, UPDATE, REPLACE or
DELETE for the previous executed SQL statement.
*
* @return integer The number of affected rows.
*
* @since 3.0.0
*/
public function getAffectedRows()
{
$this->connect();
return mysqli_affected_rows($this->connection);
}
/**
* Method to get the database collation.
*
* @return mixed The collation in use by the database (string) or
boolean false if not supported.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function getCollation()
{
$this->connect();
// Attempt to get the database collation by accessing the server system
variable.
$this->setQuery('SHOW VARIABLES LIKE
"collation_database"');
$result = $this->loadObject();
if (property_exists($result, 'Value'))
{
return $result->Value;
}
else
{
return false;
}
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
$this->connect();
// Attempt to get the database collation by accessing the server system
variable.
$this->setQuery('SHOW VARIABLES LIKE
"collation_connection"');
$result = $this->loadObject();
if (property_exists($result, 'Value'))
{
return $result->Value;
}
else
{
return false;
}
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
* This command is only valid for statements like SELECT or SHOW that
return an actual result set.
* To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE
or DELETE query, use getAffectedRows().
*
* @param resource $cursor An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 3.0.0
*/
public function getNumRows($cursor = null)
{
return mysqli_num_rows($cursor ? $cursor : $this->cursor);
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableCreate($tables)
{
$this->connect();
$result = array();
// Sanitize input to an array and iterate over the list.
settype($tables, 'array');
foreach ($tables as $table)
{
// Set the query to get the table CREATE statement.
$this->setQuery('SHOW CREATE table ' .
$this->quoteName($this->escape($table)));
$row = $this->loadRow();
// Populate the result array based on the create statements.
$result[$table] = $row[1];
}
return $result;
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$result = array();
// Set the query to get the table fields statement.
$this->setQuery('SHOW FULL COLUMNS FROM ' .
$this->quoteName($this->escape($table)));
$fields = $this->loadObjectList();
// If we only want the type as the value add just that to the list.
if ($typeOnly)
{
foreach ($fields as $field)
{
$result[$field->Field] = preg_replace('/[(0-9)]/',
'', $field->Type);
}
}
// If we want the whole field data object add that to the list.
else
{
foreach ($fields as $field)
{
$result[$field->Field] = $field;
}
}
return $result;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
// Get the details columns information.
$this->setQuery('SHOW KEYS FROM ' .
$this->quoteName($table));
$keys = $this->loadObjectList();
return $keys;
}
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function getTableList()
{
$this->connect();
// Set the query to get the tables statement.
$this->setQuery('SHOW TABLES');
$tables = $this->loadColumn();
return $tables;
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
return mysqli_get_server_info($this->connection);
}
/**
* Method to get the auto-incremented value from the last INSERT
statement.
*
* @return mixed The value of the auto-increment field from the last
inserted row.
* If the value is greater than maximal int value, it will
return a string.
*
* @since 3.0.0
*/
public function insertid()
{
$this->connect();
return mysqli_insert_id($this->connection);
}
/**
* Locks a table in the database.
*
* @param string $table The name of the table to unlock.
*
* @return JDatabaseDriverMysqli Returns this object to support
chaining.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function lockTable($table)
{
$this->setQuery('LOCK TABLES ' . $this->quoteName($table)
. ' WRITE')->execute();
return $this;
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and
cause issues later
$query = $this->replacePrefix((string) $this->sql);
if (!($this->sql instanceof JDatabaseQuery) &&
($this->limit > 0 || $this->offset > 0))
{
$query .= ' LIMIT ' . $this->offset . ', ' .
$this->limit;
}
if (!is_object($this->connection))
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
// Increment the query counter.
$this->count++;
// Reset the error values.
$this->errorNum = 0;
$this->errorMsg = '';
$memoryBefore = null;
// If debugging is enabled then let's log the query.
if ($this->debug)
{
// Add the query to the object queue.
$this->log[] = $query;
JLog::add($query, JLog::DEBUG, 'databasequery');
$this->timings[] = microtime(true);
if (is_object($this->cursor))
{
// Avoid warning if result already freed by third-party library
@$this->freeResult();
}
$memoryBefore = memory_get_usage();
}
// Execute the query. Error suppression is used here to prevent
warnings/notices that the connection has been lost.
$this->cursor = @mysqli_query($this->connection, $query);
if ($this->debug)
{
$this->timings[] = microtime(true);
if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
else
{
$this->callStacks[] = debug_backtrace();
}
$this->callStacks[count($this->callStacks) -
1][0]['memory'] = array(
$memoryBefore,
memory_get_usage(),
is_object($this->cursor) ? $this->getNumRows() : null,
);
}
// If an error occurred handle it.
if (!$this->cursor)
{
// Get the error number and message before we execute any more queries.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Check if the server was disconnected.
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
// If connect fails, ignore that exception and throw the normal
exception.
catch (RuntimeException $e)
{
// Get the error number and message.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum, $e);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
// The server was not disconnected.
else
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
}
return $this->cursor;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by MySQL.
* @param string $prefix Not used by MySQL.
*
* @return JDatabaseDriverMysqli Returns this object to support
chaining.
*
* @since 3.0.1
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->setQuery('RENAME TABLE ' . $oldTable . ' TO
' . $newTable)->execute();
return $this;
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
if (!$database)
{
return false;
}
if (!mysqli_select_db($this->connection, $database))
{
throw new JDatabaseExceptionConnecting('Could not connect to MySQL
database.');
}
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
// If UTF is not supported return false immediately
if (!$this->utf)
{
return false;
}
// Make sure we're connected to the server
$this->connect();
// Which charset should I use, plain utf8 or multibyte utf8mb4?
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
$result = @$this->connection->set_charset($charset);
/**
* If I could not set the utf8mb4 charset then the server doesn't
support utf8mb4 despite claiming otherwise.
* This happens on old MySQL server versions (less than 5.5.3) using the
mysqlnd PHP driver. Since mysqlnd
* masks the server version and reports only its own we can not be sure
if the server actually does support
* UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the
utf8mb4 charset is undefined in this case we
* catch the error and determine that utf8mb4 is not supported!
*/
if (!$result && $this->utf8mb4)
{
$this->utf8mb4 = false;
$result = @$this->connection->set_charset('utf8');
}
return $result;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.0.1
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('COMMIT')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.0.1
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('ROLLBACK')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.0.1
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
if ($this->setQuery('START TRANSACTION')->execute())
{
$this->transactionDepth = 1;
}
return;
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchArray($cursor = null)
{
return mysqli_fetch_row($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchAssoc($cursor = null)
{
return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class The class name to use for the returned row
object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject($cursor = null, $class =
'stdClass')
{
return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class);
}
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult($cursor = null)
{
mysqli_free_result($cursor ? $cursor : $this->cursor);
if ((! $cursor) || ($cursor === $this->cursor))
{
$this->cursor = null;
}
}
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriverMysqli Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function unlockTables()
{
$this->setQuery('UNLOCK TABLES')->execute();
return $this;
}
/**
* Internal function to check if profiling is available
*
* @return boolean
*
* @since 3.1.3
*/
private function hasProfiling()
{
try
{
$res = mysqli_query($this->connection, "SHOW VARIABLES LIKE
'have_profiling'");
$row = mysqli_fetch_assoc($res);
return isset($row);
}
catch (Exception $e)
{
return false;
}
}
/**
* Does the database server claim to have support for UTF-8 Multibyte
(utf8mb4) collation?
*
* libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL
server). mysqlnd supports utf8mb4 since 5.0.9.
*
* @return boolean
*
* @since CMS 3.5.0
*/
private function serverClaimsUtf8mb4Support()
{
$client_version = mysqli_get_client_info();
$server_version = $this->getVersion();
if (version_compare($server_version, '5.5.3',
'<'))
{
return false;
}
else
{
if (strpos($client_version, 'mysqlnd') !== false)
{
$client_version = preg_replace('/^\D+([\d.]+).*/',
'$1', $client_version);
return version_compare($client_version, '5.0.9',
'>=');
}
else
{
return version_compare($client_version, '5.5.3',
'>=');
}
}
}
/**
* Return the actual SQL Error number
*
* @return integer The SQL Error number
*
* @since 3.4.6
*/
protected function getErrorNumber()
{
return (int) mysqli_errno($this->connection);
}
/**
* Return the actual SQL Error message
*
* @return string The SQL Error message
*
* @since 3.4.6
*/
protected function getErrorMessage()
{
$errorMessage = (string) mysqli_error($this->connection);
// Replace the Databaseprefix with `#__` if we are not in Debug
if (!$this->debug)
{
$errorMessage = str_replace($this->tablePrefix, '#__',
$errorMessage);
}
return $errorMessage;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Oracle database driver
*
* @link https://www.php.net/pdo
* @since 3.0.0
*/
class JDatabaseDriverOracle extends JDatabaseDriverPdo
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'oracle';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'oracle';
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.0.0
*/
protected $nameQuote = '"';
/**
* Returns the current dateformat
*
* @var string
* @since 3.0.0
*/
protected $dateformat;
/**
* Returns the current character set
*
* @var string
* @since 3.0.0
*/
protected $charset;
/**
* Constructor.
*
* @param array $options List of options used to configure the
connection
*
* @since 3.0.0
*/
public function __construct($options)
{
$options['driver'] = 'oci';
$options['charset'] = (isset($options['charset']))
? $options['charset'] : 'AL32UTF8';
$options['dateformat'] =
(isset($options['dateformat'])) ?
$options['dateformat'] : 'RRRR-MM-DD HH24:MI:SS';
$this->charset = $options['charset'];
$this->dateformat = $options['dateformat'];
// Finalize initialisation
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
parent::connect();
if (isset($this->options['schema']))
{
$this->setQuery('ALTER SESSION SET CURRENT_SCHEMA = ' .
$this->quoteName($this->options['schema']))->execute();
}
$this->setDateFormat($this->dateformat);
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
// Close the connection.
$this->freeResult();
$this->connection = null;
}
/**
* Drops a table from the database.
*
* Note: The IF EXISTS flag is unused in the Oracle driver.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriverOracle Returns this object to support
chaining.
*
* @since 3.0.0
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$query = $this->getQuery(true)
->setQuery('DROP TABLE :tableName');
$query->bind(':tableName', $tableName);
$this->setQuery($query);
$this->execute();
return $this;
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 3.0.0
*/
public function getCollation()
{
return $this->charset;
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
return $this->charset;
}
/**
* Get a query to run and verify the database is operational.
*
* @return string The query to check the health of the DB.
*
* @since 3.0.1
*/
public function getConnectedQuery()
{
return 'SELECT 1 FROM dual';
}
/**
* Returns the current date format
* This method should be useful in the case that
* somebody actually wants to use a different
* date format and needs to check what the current
* one is to see if it needs to be changed.
*
* @return string The current date format
*
* @since 3.0.0
*/
public function getDateFormat()
{
return $this->dateformat;
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* Note: You must have the correct privileges before this method
* will return usable results!
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableCreate($tables)
{
$this->connect();
$result = array();
$query = $this->getQuery(true)
->select('dbms_metadata.get_ddl(:type, :tableName)')
->from('dual')
->bind(':type', 'TABLE');
// Sanitize input to an array and iterate over the list.
settype($tables, 'array');
foreach ($tables as $table)
{
$query->bind(':tableName', $table);
$this->setQuery($query);
$statement = (string) $this->loadResult();
$result[$table] = $statement;
}
return $result;
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$columns = array();
$query = $this->getQuery(true);
$fieldCasing = $this->getOption(PDO::ATTR_CASE);
$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
$table = strtoupper($table);
$query->select('*');
$query->from('ALL_TAB_COLUMNS');
$query->where('table_name = :tableName');
$prefixedTable = str_replace('#__',
strtoupper($this->tablePrefix), $table);
$query->bind(':tableName', $prefixedTable);
$this->setQuery($query);
$fields = $this->loadObjectList();
if ($typeOnly)
{
foreach ($fields as $field)
{
$columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
}
}
else
{
foreach ($fields as $field)
{
$columns[$field->COLUMN_NAME] = $field;
$columns[$field->COLUMN_NAME]->Default = null;
}
}
$this->setOption(PDO::ATTR_CASE, $fieldCasing);
return $columns;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
$query = $this->getQuery(true);
$fieldCasing = $this->getOption(PDO::ATTR_CASE);
$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
$table = strtoupper($table);
$query->select('*')
->from('ALL_CONSTRAINTS')
->where('table_name = :tableName')
->bind(':tableName', $table);
$this->setQuery($query);
$keys = $this->loadObjectList();
$this->setOption(PDO::ATTR_CASE, $fieldCasing);
return $keys;
}
/**
* Method to get an array of all tables in the database (schema).
*
* @param string $databaseName The database (schema) name
* @param boolean $includeDatabaseName Whether to include the schema
name in the results
*
* @return array An array of all the tables in the database.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableList($databaseName = null, $includeDatabaseName =
false)
{
$this->connect();
$query = $this->getQuery(true);
if ($includeDatabaseName)
{
$query->select('owner, table_name');
}
else
{
$query->select('table_name');
}
$query->from('all_tables');
if ($databaseName)
{
$query->where('owner = :database')
->bind(':database', $databaseName);
}
$query->order('table_name');
$this->setQuery($query);
if ($includeDatabaseName)
{
$tables = $this->loadAssocList();
}
else
{
$tables = $this->loadColumn();
}
return $tables;
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
$this->setQuery("select value from nls_database_parameters where
parameter = 'NLS_RDBMS_VERSION'");
return $this->loadResult();
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
return true;
}
/**
* Sets the Oracle Date Format for the session
* Default date format for Oracle is = DD-MON-RR
* The default date format for this driver is:
* 'RRRR-MM-DD HH24:MI:SS' since it is the format
* that matches the MySQL one used within most Joomla
* tables.
*
* @param string $dateFormat Oracle Date Format String
*
* @return boolean
*
* @since 3.0.0
*/
public function setDateFormat($dateFormat = 'DD-MON-RR')
{
$this->connect();
$this->setQuery("ALTER SESSION SET NLS_DATE_FORMAT =
'$dateFormat'");
if (!$this->execute())
{
return false;
}
$this->setQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT =
'$dateFormat'");
if (!$this->execute())
{
return false;
}
$this->dateformat = $dateFormat;
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* Returns false automatically for the Oracle driver since
* you can only set the character set when the connection
* is created.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
return false;
}
/**
* Locks a table in the database.
*
* @param string $table The name of the table to unlock.
*
* @return JDatabaseDriverOracle Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function lockTable($table)
{
$this->setQuery('LOCK TABLE ' . $this->quoteName($table)
. ' IN EXCLUSIVE MODE')->execute();
return $this;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by Oracle.
* @param string $prefix Not used by Oracle.
*
* @return JDatabaseDriverOracle Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->setQuery('RENAME ' . $oldTable . ' TO ' .
$newTable)->execute();
return $this;
}
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriverOracle Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function unlockTables()
{
$this->setQuery('COMMIT')->execute();
return $this;
}
/**
* Test to see if the PDO ODBC connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return class_exists('PDO') && in_array('oci',
PDO::getAvailableDrivers());
}
/**
* This function replaces a string identifier
<var>$prefix</var> with the string held is the
* <var>tablePrefix</var> class variable.
*
* @param string $query The SQL statement to prepare.
* @param string $prefix The common table prefix.
*
* @return string The processed SQL statement.
*
* @since 1.7.0
*/
public function replacePrefix($query, $prefix = '#__')
{
$startPos = 0;
$quoteChar = "'";
$literal = '';
$query = trim($query);
$n = strlen($query);
while ($startPos < $n)
{
$ip = strpos($query, $prefix, $startPos);
if ($ip === false)
{
break;
}
$j = strpos($query, "'", $startPos);
if ($j === false)
{
$j = $n;
}
$literal .= str_replace($prefix, $this->tablePrefix, substr($query,
$startPos, $j - $startPos));
$startPos = $j;
$j = $startPos + 1;
if ($j >= $n)
{
break;
}
// Quote comes first, find end of quote
while (true)
{
$k = strpos($query, $quoteChar, $j);
$escaped = false;
if ($k === false)
{
break;
}
$l = $k - 1;
while ($l >= 0 && $query[$l] == '\\')
{
$l--;
$escaped = !$escaped;
}
if ($escaped)
{
$j = $k + 1;
continue;
}
break;
}
if ($k === false)
{
// Error in the query - no end quote; ignore it
break;
}
$literal .= substr($query, $startPos, $k - $startPos + 1);
$startPos = $k + 1;
}
if ($startPos < $n)
{
$literal .= substr($query, $startPos, $n - $startPos);
}
return $literal;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionCommit($toSavepoint);
}
else
{
$this->transactionDepth--;
}
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionRollback($toSavepoint);
}
else
{
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
}
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
return parent::transactionStart($asSavepoint);
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Get the query strings to alter the character set and collation of a
table.
*
* @param string $tableName The name of the table
*
* @return string[] The queries required to alter the table's
character set and collation
*
* @since CMS 3.5.0
*/
public function getAlterTableCharacterSet($tableName)
{
return array();
}
/**
* Return the query string to create new Database.
* Each database driver, other than MySQL, need to override this member to
return correct string.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
*/
protected function getCreateDatabaseQuery($options, $utf)
{
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform PDO Database Driver Class
*
* @link https://www.php.net/pdo
* @since 3.0.0
*/
abstract class JDatabaseDriverPdo extends JDatabaseDriver
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'pdo';
/**
* @var PDO The database connection resource.
* @since 3.0.0
*/
protected $connection;
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.0.0
*/
protected $nameQuote = "'";
/**
* The null or zero representation of a timestamp for the database driver.
This should be
* defined in child classes to hold the appropriate value for the engine.
*
* @var string
* @since 3.0.0
*/
protected $nullDate = '0000-00-00 00:00:00';
/**
* @var resource The prepared statement.
* @since 3.0.0
*/
protected $prepared;
/**
* Contains the current query execution status
*
* @var array
* @since 3.0.0
*/
protected $executed = false;
/**
* Constructor.
*
* @param array $options List of options used to configure the
connection
*
* @since 3.0.0
*/
public function __construct($options)
{
// Get some basic values from the options.
$options['driver'] = (isset($options['driver'])) ?
$options['driver'] : 'odbc';
$options['dsn'] = (isset($options['dsn'])) ?
$options['dsn'] : '';
$options['host'] = (isset($options['host'])) ?
$options['host'] : 'localhost';
$options['database'] = (isset($options['database']))
? $options['database'] : '';
$options['user'] = (isset($options['user'])) ?
$options['user'] : '';
$options['password'] = (isset($options['password']))
? $options['password'] : '';
$options['driverOptions'] =
(isset($options['driverOptions'])) ?
$options['driverOptions'] : array();
$hostParts = explode(':', $options['host']);
if (!empty($hostParts[1]))
{
$options['host'] = $hostParts[0];
$options['port'] = $hostParts[1];
}
// Finalize initialisation
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
// Make sure the PDO extension for PHP is installed and enabled.
if (!self::isSupported())
{
throw new JDatabaseExceptionUnsupported('PDO Extension is not
available.', 1);
}
$replace = array();
$with = array();
// Find the correct PDO DSN Format to use:
switch ($this->options['driver'])
{
case 'cubrid':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 33000;
$format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
break;
case 'dblib':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 1433;
$format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
break;
case 'firebird':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 3050;
$format = 'firebird:dbname=#DBNAME#';
$replace = array('#DBNAME#');
$with = array($this->options['database']);
break;
case 'ibm':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 56789;
if (!empty($this->options['dsn']))
{
$format = 'ibm:DSN=#DSN#';
$replace = array('#DSN#');
$with = array($this->options['dsn']);
}
else
{
$format =
'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
}
break;
case 'informix':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 1526;
$this->options['protocol'] =
(isset($this->options['protocol'])) ?
$this->options['protocol'] : 'onsoctcp';
if (!empty($this->options['dsn']))
{
$format = 'informix:DSN=#DSN#';
$replace = array('#DSN#');
$with = array($this->options['dsn']);
}
else
{
$format =
'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#', '#SERVER#', '#PROTOCOL#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database'],
$this->options['server'],
$this->options['protocol']);
}
break;
case 'mssql':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 1433;
$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
break;
// The pdomysql case is a special case within the CMS environment
case 'pdomysql':
case 'mysql':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 3306;
$format =
'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#', '#CHARSET#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database'],
$this->options['charset']);
break;
case 'oci':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 1521;
$this->options['charset'] =
(isset($this->options['charset'])) ?
$this->options['charset'] : 'AL32UTF8';
if (!empty($this->options['dsn']))
{
$format = 'oci:dbname=#DSN#';
$replace = array('#DSN#');
$with = array($this->options['dsn']);
}
else
{
$format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
}
$format .= ';charset=' .
$this->options['charset'];
break;
case 'odbc':
$format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#';
$replace = array('#DSN#', '#USER#',
'#PASSWORD#');
$with = array($this->options['dsn'],
$this->options['user'],
$this->options['password']);
break;
case 'pgsql':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 5432;
$format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
break;
case 'sqlite':
if (isset($this->options['version']) &&
$this->options['version'] == 2)
{
$format = 'sqlite2:#DBNAME#';
}
else
{
$format = 'sqlite:#DBNAME#';
}
$replace = array('#DBNAME#');
$with = array($this->options['database']);
break;
case 'sybase':
$this->options['port'] =
(isset($this->options['port'])) ?
$this->options['port'] : 1433;
$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = array('#HOST#', '#PORT#',
'#DBNAME#');
$with = array($this->options['host'],
$this->options['port'],
$this->options['database']);
break;
}
// Create the connection string:
$connectionString = str_replace($replace, $with, $format);
try
{
$this->connection = new PDO(
$connectionString,
$this->options['user'],
$this->options['password'],
$this->options['driverOptions']
);
}
catch (PDOException $e)
{
throw new JDatabaseExceptionConnecting('Could not connect to PDO:
' . $e->getMessage(), 2, $e);
}
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
$this->freeResult();
$this->connection = null;
}
/**
* Method to escape a string for usage in an SQL statement.
*
* Oracle escaping reference:
*
http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
*
* SQLite escaping notes:
* http://www.sqlite.org/faq.html#q14
*
* Method body is as implemented by the Zend Framework
*
* Note: Using query objects with bound variables is
* preferable to the below.
*
* @param string $text The string to be escaped.
* @param boolean $extra Unused optional parameter to provide extra
escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$text = str_replace("'", "''", $text);
return addcslashes($text, "\000\n\r\\\032");
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
* @throws Exception
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and
cause issues later
$query = $this->replacePrefix((string) $this->sql);
if (!is_object($this->connection))
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
// Increment the query counter.
$this->count++;
// Reset the error values.
$this->errorNum = 0;
$this->errorMsg = '';
// If debugging is enabled then let's log the query.
if ($this->debug)
{
// Add the query to the object queue.
$this->log[] = $query;
JLog::add($query, JLog::DEBUG, 'databasequery');
$this->timings[] = microtime(true);
}
// Execute the query.
$this->executed = false;
if ($this->prepared instanceof PDOStatement)
{
// Bind the variables:
if ($this->sql instanceof JDatabaseQueryPreparable)
{
$bounded = $this->sql->getBounded();
foreach ($bounded as $key => $obj)
{
$this->prepared->bindParam($key, $obj->value,
$obj->dataType, $obj->length, $obj->driverOptions);
}
}
$this->executed = $this->prepared->execute();
}
if ($this->debug)
{
$this->timings[] = microtime(true);
if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
else
{
$this->callStacks[] = debug_backtrace();
}
}
// If an error occurred handle it.
if (!$this->executed)
{
// Get the error number and message before we execute any more queries.
$errorNum = $this->getErrorNumber();
$errorMsg = $this->getErrorMessage();
// Check if the server was disconnected.
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
// If connect fails, ignore that exception and throw the normal
exception.
catch (RuntimeException $e)
{
// Get the error number and message.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum, $e);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
// The server was not disconnected.
else
{
// Get the error number and message from before we tried to reconnect.
$this->errorNum = $errorNum;
$this->errorMsg = $errorMsg;
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
}
return $this->prepared;
}
/**
* Retrieve a PDO database connection attribute
*
* Usage: $db->getOption(PDO::ATTR_CASE);
*
* @param mixed $key One of the PDO::ATTR_* Constants
*
* @return mixed
*
* @link https://www.php.net/manual/en/pdo.getattribute.php
* @since 3.0.0
*/
public function getOption($key)
{
$this->connect();
return $this->connection->getAttribute($key);
}
/**
* Get a query to run and verify the database is operational.
*
* @return string The query to check the health of the DB.
*
* @since 3.0.1
*/
public function getConnectedQuery()
{
return 'SELECT 1';
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.9.0
*/
public function getVersion()
{
$this->connect();
return $this->getOption(PDO::ATTR_SERVER_VERSION);
}
/**
* Sets an attribute on the PDO database handle.
*
* Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
*
* @param integer $key One of the PDO::ATTR_* Constants
* @param mixed $value One of the associated PDO Constants related
to the particular attribute key.
*
* @return boolean
*
* @link https://www.php.net/manual/en/pdo.setattribute.php
* @since 3.0.0
*/
public function setOption($key, $value)
{
$this->connect();
return $this->connection->setAttribute($key, $value);
}
/**
* Test to see if the PDO extension is available.
* Override as needed to check for specific PDO Drivers.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return defined('PDO::ATTR_DRIVER_NAME');
}
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 3.0.0
*/
public function connected()
{
// Flag to prevent recursion into this function.
static $checkingConnected = false;
if ($checkingConnected)
{
// Reset this flag and throw an exception.
$checkingConnected = true;
die('Recursion trying to check if connected.');
}
// Backup the query state.
$query = $this->sql;
$limit = $this->limit;
$offset = $this->offset;
$prepared = $this->prepared;
try
{
// Set the checking connection flag.
$checkingConnected = true;
// Run a simple query to check the connection.
$this->setQuery($this->getConnectedQuery());
$status = (bool) $this->loadResult();
}
// If we catch an exception here, we must not be connected.
catch (Exception $e)
{
$status = false;
}
// Restore the query state.
$this->sql = $query;
$this->limit = $limit;
$this->offset = $offset;
$this->prepared = $prepared;
$checkingConnected = false;
return $status;
}
/**
* Get the number of affected rows for the previous executed SQL
statement.
* Only applicable for DELETE, INSERT, or UPDATE statements.
*
* @return integer The number of affected rows.
*
* @since 3.0.0
*/
public function getAffectedRows()
{
$this->connect();
if ($this->prepared instanceof PDOStatement)
{
return $this->prepared->rowCount();
}
else
{
return 0;
}
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
* Only applicable for DELETE, INSERT, or UPDATE statements.
*
* @param resource $cursor An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 3.0.0
*/
public function getNumRows($cursor = null)
{
$this->connect();
if ($cursor instanceof PDOStatement)
{
return $cursor->rowCount();
}
elseif ($this->prepared instanceof PDOStatement)
{
return $this->prepared->rowCount();
}
else
{
return 0;
}
}
/**
* Method to get the auto-incremented value from the last INSERT
statement.
*
* @return string The value of the auto-increment field from the last
inserted row.
*
* @since 3.0.0
*/
public function insertid()
{
$this->connect();
// Error suppress this to prevent PDO warning us that the driver
doesn't support this operation.
return @$this->connection->lastInsertId();
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
return true;
}
/**
* Sets the SQL statement string for later execution.
*
* @param mixed $query The SQL statement to set either as a
JDatabaseQuery object or a string.
* @param integer $offset The affected row offset to set.
* @param integer $limit The maximum affected rows to set.
* @param array $driverOptions The optional PDO driver options.
*
* @return JDatabaseDriver This object to support method chaining.
*
* @since 3.0.0
*/
public function setQuery($query, $offset = null, $limit = null,
$driverOptions = array())
{
$this->connect();
$this->freeResult();
if (is_string($query))
{
// Allows taking advantage of bound variables in a direct query:
$query = $this->getQuery(true)->setQuery($query);
}
if ($query instanceof JDatabaseQueryLimitable &&
!is_null($offset) && !is_null($limit))
{
$query = $query->processLimit($query, $limit, $offset);
}
// Create a stringified version of the query (with prefixes replaced):
$sql = $this->replacePrefix((string) $query);
// Use the stringified version in the prepare call:
$this->prepared = $this->connection->prepare($sql,
$driverOptions);
// Store reference to the original JDatabaseQuery instance within the
class.
// This is important since binding variables depends on it within
execute():
parent::setQuery($query, $offset, $limit);
return $this;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
return false;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth == 1)
{
$this->connection->commit();
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth == 1)
{
$this->connection->rollBack();
}
$this->transactionDepth--;
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
$this->connection->beginTransaction();
}
$this->transactionDepth++;
}
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchArray($cursor = null)
{
if (!empty($cursor) && $cursor instanceof PDOStatement)
{
return $cursor->fetch(PDO::FETCH_NUM);
}
if ($this->prepared instanceof PDOStatement)
{
return $this->prepared->fetch(PDO::FETCH_NUM);
}
}
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchAssoc($cursor = null)
{
if (!empty($cursor) && $cursor instanceof PDOStatement)
{
return $cursor->fetch(PDO::FETCH_ASSOC);
}
if ($this->prepared instanceof PDOStatement)
{
return $this->prepared->fetch(PDO::FETCH_ASSOC);
}
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class Unused, only necessary so method signature
will be the same as parent.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject($cursor = null, $class =
'stdClass')
{
if (!empty($cursor) && $cursor instanceof PDOStatement)
{
return $cursor->fetchObject($class);
}
if ($this->prepared instanceof PDOStatement)
{
return $this->prepared->fetchObject($class);
}
}
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult($cursor = null)
{
$this->executed = false;
if ($cursor instanceof PDOStatement)
{
$cursor->closeCursor();
$cursor = null;
}
if ($this->prepared instanceof PDOStatement)
{
$this->prepared->closeCursor();
$this->prepared = null;
}
}
/**
* Method to get the next row in the result set from the database query as
an object.
*
* @param string $class The class name to use for the returned row
object.
*
* @return mixed The result of the query as an array, false if there
are no more rows.
*
* @since 3.0.0
* @throws RuntimeException
* @deprecated 4.0 (CMS) Use getIterator() instead
*/
public function loadNextObject($class = 'stdClass')
{
JLog::add(__METHOD__ . '() is deprecated. Use
JDatabaseDriver::getIterator() instead.', JLog::WARNING,
'deprecated');
$this->connect();
// Execute the query and get the result set cursor.
if (!$this->executed)
{
if (!($this->execute()))
{
return $this->errorNum ? null : false;
}
}
// Get the next row from the result set as an object of type $class.
if ($row = $this->fetchObject(null, $class))
{
return $row;
}
// Free up system resources and return.
$this->freeResult();
return false;
}
/**
* Method to get the next row in the result set from the database query as
an array.
*
* @return mixed The result of the query as an array, false if there are
no more rows.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function loadNextAssoc()
{
$this->connect();
// Execute the query and get the result set cursor.
if (!$this->executed)
{
if (!($this->execute()))
{
return $this->errorNum ? null : false;
}
}
// Get the next row from the result set as an object of type $class.
if ($row = $this->fetchAssoc())
{
return $row;
}
// Free up system resources and return.
$this->freeResult();
return false;
}
/**
* Method to get the next row in the result set from the database query as
an array.
*
* @return mixed The result of the query as an array, false if there are
no more rows.
*
* @since 3.0.0
* @throws RuntimeException
* @deprecated 4.0 (CMS) Use getIterator() instead
*/
public function loadNextRow()
{
JLog::add(__METHOD__ . '() is deprecated. Use
JDatabaseDriver::getIterator() instead.', JLog::WARNING,
'deprecated');
$this->connect();
// Execute the query and get the result set cursor.
if (!$this->executed)
{
if (!($this->execute()))
{
return $this->errorNum ? null : false;
}
}
// Get the next row from the result set as an object of type $class.
if ($row = $this->fetchArray())
{
return $row;
}
// Free up system resources and return.
$this->freeResult();
return false;
}
/**
* PDO does not support serialize
*
* @return array
*
* @since 3.1.4
*/
public function __sleep()
{
$serializedProperties = array();
$reflect = new ReflectionClass($this);
// Get properties of the current class
$properties = $reflect->getProperties();
foreach ($properties as $property)
{
// Do not serialize properties that are PDO
if ($property->isStatic() == false &&
!($this->{$property->name} instanceof PDO))
{
$serializedProperties[] = $property->name;
}
}
return $serializedProperties;
}
/**
* Wake up after serialization
*
* @return array
*
* @since 3.1.4
*/
public function __wakeup()
{
// Get connection back
$this->__construct($this->options);
}
/**
* Return the actual SQL Error number
*
* @return integer The SQL Error number
*
* @since 3.4.6
*/
protected function getErrorNumber()
{
return (int) $this->connection->errorCode();
}
/**
* Return the actual SQL Error message
*
* @return string The SQL Error message
*
* @since 3.4.6
*/
protected function getErrorMessage()
{
// The SQL Error Information
$errorInfo = implode(', ',
$this->connection->errorInfo());
// Replace the Databaseprefix with `#__` if we are not in Debug
if (!$this->debug)
{
$errorInfo = str_replace($this->tablePrefix, '#__',
$errorInfo);
}
return $errorInfo;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL database driver supporting PDO based connections
*
* @link https://www.php.net/manual/en/ref.pdo-mysql.php
* @since 3.4
*/
class JDatabaseDriverPdomysql extends JDatabaseDriverPdo
{
/**
* The name of the database driver.
*
* @var string
* @since 3.4
*/
public $name = 'pdomysql';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'mysql';
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.4
*/
protected $nameQuote = '`';
/**
* The null or zero representation of a timestamp for the database driver.
This should be
* defined in child classes to hold the appropriate value for the engine.
*
* @var string
* @since 3.4
*/
protected $nullDate = '0000-00-00 00:00:00';
/**
* The minimum supported database version.
*
* @var string
* @since 3.4
*/
protected static $dbMinimum = '5.0.4';
/**
* Constructor.
*
* @param array $options Array of database options with keys: host,
user, password, database, select.
*
* @since 3.4
*/
public function __construct($options)
{
// Get some basic values from the options.
$options['driver'] = 'mysql';
if (!isset($options['charset']) ||
$options['charset'] == 'utf8')
{
$options['charset'] = 'utf8mb4';
}
/**
* Pre-populate the UTF-8 Multibyte compatibility flag. Unfortunately PDO
won't report the server version
* unless we're connected to it, and we cannot connect to it unless
we know if it supports utf8mb4, which requires
* us knowing the server version. Because of this chicken and egg issue,
we _assume_ it's supported and we'll just
* catch any problems at connection time.
*/
$this->utf8mb4 = ($options['charset'] ==
'utf8mb4');
// Finalize initialisation.
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.4
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
try
{
// Try to connect to MySQL
parent::connect();
}
catch (\RuntimeException $e)
{
// If the connection failed, but not because of the wrong character set,
then bubble up the exception.
if (!$this->utf8mb4)
{
throw $e;
}
/*
* Otherwise, try connecting again without using
* utf8mb4 and see if maybe that was the problem. If the
* connection succeeds, then we will have learned that the
* client end of the connection does not support utf8mb4.
*/
$this->utf8mb4 = false;
$this->options['charset'] = 'utf8';
parent::connect();
}
if ($this->utf8mb4)
{
/*
* At this point we know the client supports utf8mb4. Now
* we must check if the server supports utf8mb4 as well.
*/
$serverVersion =
$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
$this->utf8mb4 = version_compare($serverVersion, '5.5.3',
'>=');
if (!$this->utf8mb4)
{
// Reconnect with the utf8 character set.
parent::disconnect();
$this->options['charset'] = 'utf8';
parent::connect();
}
}
$this->connection->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
// Set sql_mode to non_strict mode
$this->connection->query("SET @@SESSION.sql_mode =
'';");
// Disable query cache and turn profiling ON in debug mode.
if ($this->debug)
{
$this->connection->query('SET query_cache_type = 0;');
if ($this->hasProfiling())
{
$this->connection->query('SET profiling_history_size = 100,
profiling = 1;');
}
}
}
/**
* Test to see if the MySQL connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.4
*/
public static function isSupported()
{
return class_exists('PDO') &&
in_array('mysql', PDO::getAvailableDrivers());
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriverPdomysql Returns this object to support
chaining.
*
* @since 3.4
* @throws RuntimeException
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$query = $this->getQuery(true);
$query->setQuery('DROP TABLE ' . ($ifExists ? 'IF
EXISTS ' : '') . $this->quoteName($tableName));
$this->setQuery($query);
$this->execute();
return $this;
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.4
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
$this->setQuery('USE ' . $this->quoteName($database));
$this->execute();
return $this;
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database (string) or
boolean false if not supported.
*
* @since 3.4
* @throws RuntimeException
*/
public function getCollation()
{
$this->connect();
// Attempt to get the database collation by accessing the server system
variable.
$this->setQuery('SHOW VARIABLES LIKE
"collation_database"');
$result = $this->loadObject();
if (property_exists($result, 'Value'))
{
return $result->Value;
}
else
{
return false;
}
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
$this->connect();
// Attempt to get the database collation by accessing the server system
variable.
$this->setQuery('SHOW VARIABLES LIKE
"collation_connection"');
$result = $this->loadObject();
if (property_exists($result, 'Value'))
{
return $result->Value;
}
else
{
return false;
}
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 3.4
* @throws RuntimeException
*/
public function getTableCreate($tables)
{
$this->connect();
// Initialise variables.
$result = array();
// Sanitize input to an array and iterate over the list.
settype($tables, 'array');
foreach ($tables as $table)
{
$this->setQuery('SHOW CREATE TABLE ' .
$this->quoteName($table));
$row = $this->loadRow();
// Populate the result array based on the create statements.
$result[$table] = $row[1];
}
return $result;
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.4
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$result = array();
// Set the query to get the table fields statement.
$this->setQuery('SHOW FULL COLUMNS FROM ' .
$this->quoteName($table));
$fields = $this->loadObjectList();
// If we only want the type as the value add just that to the list.
if ($typeOnly)
{
foreach ($fields as $field)
{
$result[$field->Field] = preg_replace('/[(0-9)]/',
'', $field->Type);
}
}
// If we want the whole field data object add that to the list.
else
{
foreach ($fields as $field)
{
$result[$field->Field] = $field;
}
}
return $result;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.4
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
// Get the details columns information.
$this->setQuery('SHOW KEYS FROM ' .
$this->quoteName($table));
$keys = $this->loadObjectList();
return $keys;
}
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 3.4
* @throws RuntimeException
*/
public function getTableList()
{
$this->connect();
// Set the query to get the tables statement.
$this->setQuery('SHOW TABLES');
$tables = $this->loadColumn();
return $tables;
}
/**
* Locks a table in the database.
*
* @param string $table The name of the table to unlock.
*
* @return JDatabaseDriverPdomysql Returns this object to support
chaining.
*
* @since 3.4
* @throws RuntimeException
*/
public function lockTable($table)
{
$this->setQuery('LOCK TABLES ' . $this->quoteName($table)
. ' WRITE')->execute();
return $this;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by MySQL.
* @param string $prefix Not used by MySQL.
*
* @return JDatabaseDriverPdomysql Returns this object to support
chaining.
*
* @since 3.4
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->setQuery('RENAME TABLE ' .
$this->quoteName($oldTable) . ' TO ' .
$this->quoteName($newTable));
$this->execute();
return $this;
}
/**
* Method to escape a string for usage in an SQL statement.
*
* Oracle escaping reference:
*
http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
*
* SQLite escaping notes:
* http://www.sqlite.org/faq.html#q14
*
* Method body is as implemented by the Zend Framework
*
* Note: Using query objects with bound variables is
* preferable to the below.
*
* @param string $text The string to be escaped.
* @param boolean $extra Unused optional parameter to provide extra
escaping.
*
* @return string The escaped string.
*
* @since 3.4
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$this->connect();
$result = substr($this->connection->quote($text), 1, -1);
if ($extra)
{
$result = addcslashes($result, '%_');
}
return $result;
}
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriverPdomysql Returns this object to support
chaining.
*
* @since 3.4
* @throws RuntimeException
*/
public function unlockTables()
{
$this->setQuery('UNLOCK TABLES')->execute();
return $this;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.4
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionCommit($toSavepoint);
}
else
{
$this->transactionDepth--;
}
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.4
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionRollback($toSavepoint);
}
else
{
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
}
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.4
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
parent::transactionStart($asSavepoint);
}
else
{
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
}
/**
* Internal function to check if profiling is available.
*
* @return boolean
*
* @since 3.9.1
*/
private function hasProfiling()
{
$result = $this->setQuery("SHOW VARIABLES LIKE
'have_profiling'")->loadAssoc();
return isset($result);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL PDO Database Driver
*
* @link https://www.php.net/manual/en/ref.pdo-mysql.php
* @since 3.9.0
*/
class JDatabaseDriverPgsql extends JDatabaseDriverPdo
{
/**
* The database driver name
*
* @var string
* @since 3.9.0
*/
public $name = 'pgsql';
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.9.0
*/
protected $nameQuote = '"';
/**
* The null or zero representation of a timestamp for the database driver.
This should be
* defined in child classes to hold the appropriate value for the engine.
*
* @var string
* @since 3.9.0
*/
protected $nullDate = '1970-01-01 00:00:00';
/**
* The minimum supported database version.
*
* @var string
* @since 3.9.0
*/
protected static $dbMinimum = '8.3.18';
/**
* Operator used for concatenation
*
* @var string
* @since 3.9.0
*/
protected $concat_operator = '||';
/**
* Database object constructor
*
* @param array $options List of options used to configure the
connection
*
* @since 3.9.0
*/
public function __construct($options)
{
$options['driver'] = 'pgsql';
$options['host'] = isset($options['host']) ?
$options['host'] : 'localhost';
$options['user'] = isset($options['user']) ?
$options['user'] : '';
$options['password'] = isset($options['password']) ?
$options['password'] : '';
$options['database'] = isset($options['database']) ?
$options['database'] : '';
$options['port'] = isset($options['port']) ?
$options['port'] : null;
// Finalize initialization
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function connect()
{
if ($this->getConnection())
{
return;
}
parent::connect();
$this->setQuery('SET standard_conforming_strings =
off')->execute();
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return boolean true
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function dropTable($tableName, $ifExists = true)
{
$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS
' : '') . $this->quoteName($tableName))->execute();
return true;
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getCollation()
{
$this->setQuery('SHOW LC_COLLATE');
$array = $this->loadAssocList();
return $array[0]['lc_collate'];
}
/**
* Method to get the database connection collation in use by sampling a
text field of a table in the database.
*
* @return mixed The collation in use by the database connection
(string) or boolean false if not supported.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getConnectionCollation()
{
$this->setQuery('SHOW LC_COLLATE');
$array = $this->loadAssocList();
return $array[0]['lc_collate'];
}
/**
* Internal function to get the name of the default schema for the current
PostgreSQL connection.
* That is the schema where tables are created by Joomla.
*
* @return string
*
* @since 3.9.24
*/
private function getDefaultSchema()
{
// Supported since PostgreSQL 7.3
$this->setQuery('SELECT (current_schemas(false))[1]');
return $this->loadResult();
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* This is unsupported by PostgreSQL.
*
* @param mixed $tables A table name or a list of table names.
*
* @return string An empty string because this function is not supported
by PostgreSQL.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getTableCreate($tables)
{
return '';
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$result = array();
$tableSub = $this->replacePrefix($table);
$defaultSchema = $this->getDefaultSchema();
$this->setQuery('
SELECT a.attname AS "column_name",
pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
CASE WHEN a.attnotnull IS TRUE
THEN \'NO\'
ELSE \'YES\'
END AS "null",
CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT
NULL
THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
END as "Default",
CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
THEN \'\'
ELSE pg_catalog.col_description(a.attrelid, a.attnum)
END AS "comments"
FROM pg_catalog.pg_attribute a
LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND
a.attnum=adef.adnum
LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
WHERE a.attrelid =
(SELECT oid FROM pg_catalog.pg_class WHERE relname=' .
$this->quote($tableSub) . '
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
nspname = ' . $this->quote($defaultSchema) . ')
)
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum'
);
$fields = $this->loadObjectList();
if ($typeOnly)
{
foreach ($fields as $field)
{
$result[$field->column_name] = preg_replace('/[(0-9)]/',
'', $field->type);
}
}
else
{
foreach ($fields as $field)
{
if (stristr(strtolower($field->type), 'character
varying'))
{
$field->Default = '';
}
if (stristr(strtolower($field->type), 'text'))
{
$field->Default = '';
}
// Do some dirty translation to MySQL output.
// @todo: Come up with and implement a standard across databases.
$result[$field->column_name] = (object) array(
'column_name' => $field->column_name,
'type' => $field->type,
'null' => $field->null,
'Default' => $field->Default,
'comments' => '',
'Field' => $field->column_name,
'Type' => $field->type,
'Null' => $field->null,
// @todo: Improve query above to return primary key info as well
// 'Key' => ($field->PK == '1' ?
'PRI' : '')
);
}
}
// Change Postgresql's NULL::* type with PHP's null one
foreach ($fields as $field)
{
if (preg_match('/^NULL::*/', $field->Default))
{
$field->Default = null;
}
}
return $result;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
if (in_array($table, $tableList, true))
{
// Get the details columns information.
$this->setQuery('
SELECT indexname AS "idxName", indisprimary AS
"isPrimary", indisunique AS "isUnique",
CASE WHEN indisprimary = true THEN
( SELECT \'ALTER TABLE \' || tablename || \' ADD
\' || pg_catalog.pg_get_constraintdef(const.oid, true)
FROM pg_constraint AS const WHERE const.conname=
pgClassFirst.relname )
ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
END AS "Query"
FROM pg_indexes
LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
WHERE tablename=' . $this->quote($table) . ' ORDER BY
indkey'
);
return $this->loadObjectList();
}
return false;
}
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getTableList()
{
$query = $this->getQuery(true)
->select('table_name')
->from('information_schema.tables')
->where('table_type = ' . $this->quote('BASE
TABLE'))
->where('table_schema NOT IN (' .
$this->quote('pg_catalog') . ', ' .
$this->quote('information_schema') . ')')
->order('table_name ASC');
$this->setQuery($query);
return $this->loadColumn();
}
/**
* Get the details list of sequences for a table.
*
* @param string $table The name of the table.
*
* @return array An array of sequences specification for the table.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function getTableSequences($table)
{
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
if (in_array($table, $tableList, true))
{
$name = array(
's.relname', 'n.nspname', 't.relname',
'a.attname', 'info.data_type',
'info.minimum_value', 'info.maximum_value',
'info.increment', 'info.cycle_option'
);
$as = array('sequence', 'schema', 'table',
'column', 'data_type', 'minimum_value',
'maximum_value', 'increment',
'cycle_option');
if (version_compare($this->getVersion(), '9.1.0') >= 0)
{
$name[] .= 'info.start_value';
$as[] .= 'start_value';
}
// Get the details columns information.
$query = $this->getQuery(true)
->select($this->quoteName($name, $as))
->from('pg_class AS s')
->leftJoin("pg_depend d ON d.objid = s.oid AND d.classid =
'pg_class'::regclass AND d.refclassid =
'pg_class'::regclass")
->leftJoin('pg_class t ON t.oid = d.refobjid')
->leftJoin('pg_namespace n ON n.oid = t.relnamespace')
->leftJoin('pg_attribute a ON a.attrelid = t.oid AND a.attnum =
d.refobjsubid')
->leftJoin('information_schema.sequences AS info ON
info.sequence_name = s.relname')
->where('s.relkind = ' . $this->quote('S') .
' AND d.deptype = ' . $this->quote('a') . ' AND
t.relname = ' . $this->quote($table));
$this->setQuery($query);
return $this->loadObjectList();
}
return false;
}
/**
* Locks a table in the database.
*
* @param string $tableName The name of the table to unlock.
*
* @return JDatabaseDriverPgsql Returns this object to support chaining.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function lockTable($tableName)
{
$this->transactionStart();
$this->setQuery('LOCK TABLE ' .
$this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE
MODE')->execute();
return $this;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by PostgreSQL.
* @param string $prefix Not used by PostgreSQL.
*
* @return JDatabaseDriverPgsql Returns this object to support chaining.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->connect();
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
// Origin Table does not exist
if (!in_array($oldTable, $tableList, true))
{
// Origin Table not found
throw new \RuntimeException('Table not found in Postgresql
database.');
}
// Rename indexes
$subQuery = $this->getQuery(true)
->select('indexrelid')
->from('pg_index, pg_class')
->where('pg_class.relname = ' . $this->quote($oldTable))
->where('pg_class.oid = pg_index.indrelid');
$this->setQuery(
$this->getQuery(true)
->select('relname')
->from('pg_class')
->where('oid IN (' . (string) $subQuery . ')')
);
$oldIndexes = $this->loadColumn();
foreach ($oldIndexes as $oldIndex)
{
$changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
$this->setQuery('ALTER INDEX ' .
$this->escape($oldIndex) . ' RENAME TO ' .
$this->escape($changedIdxName))->execute();
}
// Rename sequences
$subQuery = $this->getQuery(true)
->select('oid')
->from('pg_namespace')
->where('nspname NOT LIKE ' .
$this->quote('pg_%'))
->where('nspname != ' .
$this->quote('information_schema'));
$this->setQuery(
$this->getQuery(true)
->select('relname')
->from('pg_class')
->where('relkind = ' . $this->quote('S'))
->where('relnamespace IN (' . (string) $subQuery .
')')
->where('relname LIKE ' .
$this->quote("%$oldTable%"))
);
$oldSequences = $this->loadColumn();
foreach ($oldSequences as $oldSequence)
{
$changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
$this->setQuery('ALTER SEQUENCE ' .
$this->escape($oldSequence) . ' RENAME TO ' .
$this->escape($changedSequenceName))->execute();
}
// Rename table
$this->setQuery('ALTER TABLE ' . $this->escape($oldTable)
. ' RENAME TO ' . $this->escape($newTable))->execute();
return true;
}
/**
* This function return a field value as a prepared string to be used in a
SQL statement.
*
* @param array $columns Array of table's column returned by
::getTableColumns.
* @param string $fieldName The table field's name.
* @param string $fieldValue The variable value to quote and return.
*
* @return string The quoted string.
*
* @since 3.9.0
*/
public function sqlValue($columns, $fieldName, $fieldValue)
{
switch ($columns[$fieldName])
{
case 'boolean':
$val = 'NULL';
if ($fieldValue === 't' || $fieldValue === true ||
$fieldValue === 1 || $fieldValue === '1')
{
$val = 'TRUE';
}
elseif ($fieldValue === 'f' || $fieldValue === false ||
$fieldValue === 0 || $fieldValue === '0')
{
$val = 'FALSE';
}
break;
case 'bigint':
case 'bigserial':
case 'integer':
case 'money':
case 'numeric':
case 'real':
case 'smallint':
case 'serial':
case 'numeric,':
$val = $fieldValue === '' ? 'NULL' : $fieldValue;
break;
case 'date':
case 'timestamp without time zone':
if (empty($fieldValue))
{
$fieldValue = $this->getNullDate();
}
$val = $this->quote($fieldValue);
break;
default:
$val = $this->quote($fieldValue);
break;
}
return $val;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('COMMIT')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('ROLLBACK')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
$this->setQuery('RELEASE SAVEPOINT ' .
$this->quoteName($savepoint))->execute();
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
if ($this->setQuery('START TRANSACTION')->execute())
{
$this->transactionDepth = 1;
}
return;
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Inserts a row into a table based on an object's properties.
*
* @param string $table The name of the database table to insert
into.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param string $key The name of the primary key. If provided the
object property is updated.
*
* @return boolean True on success.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function insertObject($table, &$object, $key = null)
{
$columns = $this->getTableColumns($table);
$fields = array();
$values = array();
// Iterate over the object variables to build the query fields and
values.
foreach (get_object_vars($object) as $k => $v)
{
// Skip columns that don't exist in the table.
if (!array_key_exists($k, $columns))
{
continue;
}
// Only process non-null scalars.
if (is_array($v) || is_object($v) || $v === null)
{
continue;
}
// Ignore any internal fields or primary keys with value 0.
if (($k[0] === '_') || ($k == $key && (($v === 0) ||
($v === '0'))))
{
continue;
}
// Prepare and sanitize the fields and values for the database query.
$fields[] = $this->quoteName($k);
$values[] = $this->sqlValue($columns, $k, $v);
}
// Create the base insert statement.
$query = $this->getQuery(true);
$query->insert($this->quoteName($table))
->columns($fields)
->values(implode(',', $values));
$retVal = false;
if ($key)
{
$query->returning($key);
// Set the query and execute the insert.
$this->setQuery($query);
$id = $this->loadResult();
if ($id)
{
$object->$key = $id;
$retVal = true;
}
}
else
{
// Set the query and execute the insert.
$this->setQuery($query);
if ($this->execute())
{
$retVal = true;
}
}
return $retVal;
}
/**
* Test to see if the PostgreSQL connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.9.0
*/
public static function isSupported()
{
return class_exists('PDO') &&
in_array('pgsql', PDO::getAvailableDrivers(), true);
}
/**
* Returns an array containing database's table list.
*
* @return array The database's table list.
*
* @since 3.9.0
*/
public function showTables()
{
$query = $this->getQuery(true)
->select('table_name')
->from('information_schema.tables')
->where('table_type=' . $this->quote('BASE
TABLE'))
->where('table_schema NOT IN (' .
$this->quote('pg_catalog') . ', ' .
$this->quote('information_schema') . ' )');
$this->setQuery($query);
return $this->loadColumn();
}
/**
* Get the substring position inside a string
*
* @param string $substring The string being sought
* @param string $string The string/column being searched
*
* @return integer The position of $substring in $string
*
* @since 3.9.0
*/
public function getStringPositionSql($substring, $string)
{
$this->setQuery("SELECT POSITION($substring IN $string)");
$position = $this->loadRow();
return $position['position'];
}
/**
* Generate a random value
*
* @return float The random generated number
*
* @since 3.9.0
*/
public function getRandom()
{
$this->setQuery('SELECT RANDOM()');
$random = $this->loadAssoc();
return $random['random'];
}
/**
* Get the query string to alter the database character set.
*
* @param string $dbName The database name
*
* @return string The query that alter the database query string
*
* @since 3.9.0
*/
public function getAlterDbCharacterSet($dbName)
{
return 'ALTER DATABASE ' . $this->quoteName($dbName) .
' SET CLIENT_ENCODING TO ' . $this->quote('UTF8');
}
/**
* Get the query string to create new Database in correct PostgreSQL
syntax.
*
* @param object $options object coming from "initialise"
function to pass user and database name to database driver.
* @param boolean $utf True if the database supports the UTF-8
character set, not used in PostgreSQL "CREATE DATABASE" query.
*
* @return string The query that creates database, owned by
$options['user']
*
* @since 3.9.0
*/
public function getCreateDbQuery($options, $utf)
{
$query = 'CREATE DATABASE ' .
$this->quoteName($options->db_name) . ' OWNER ' .
$this->quoteName($options->db_user);
if ($utf)
{
$query .= ' ENCODING ' . $this->quote('UTF-8');
}
return $query;
}
/**
* This function replaces a string identifier
<var>$prefix</var> with the string held is the
<var>tablePrefix</var> class variable.
*
* @param string $sql The SQL statement to prepare.
* @param string $prefix The common table prefix.
*
* @return string The processed SQL statement.
*
* @since 3.9.0
*/
public function replacePrefix($sql, $prefix = '#__')
{
$sql = trim($sql);
if (strpos($sql, '\''))
{
// Sequence name quoted with ' ' but need to be replaced
if (strpos($sql, 'currval'))
{
$sql = explode('currval', $sql);
for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax;
$nIndex += 2)
{
$sql[$nIndex] = str_replace($prefix, $this->tablePrefix,
$sql[$nIndex]);
}
$sql = implode('currval', $sql);
}
// Sequence name quoted with ' ' but need to be replaced
if (strpos($sql, 'nextval'))
{
$sql = explode('nextval', $sql);
for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax;
$nIndex += 2)
{
$sql[$nIndex] = str_replace($prefix, $this->tablePrefix,
$sql[$nIndex]);
}
$sql = implode('nextval', $sql);
}
// Sequence name quoted with ' ' but need to be replaced
if (strpos($sql, 'setval'))
{
$sql = explode('setval', $sql);
for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax;
$nIndex += 2)
{
$sql[$nIndex] = str_replace($prefix, $this->tablePrefix,
$sql[$nIndex]);
}
$sql = implode('setval', $sql);
}
$explodedQuery = explode('\'', $sql);
for ($nIndex = 0, $nIndexMax = count($explodedQuery); $nIndex <
$nIndexMax; $nIndex += 2)
{
if (strpos($explodedQuery[$nIndex], $prefix))
{
$explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix,
$explodedQuery[$nIndex]);
}
}
$replacedQuery = implode('\'', $explodedQuery);
}
else
{
$replacedQuery = str_replace($prefix, $this->tablePrefix, $sql);
}
return $replacedQuery;
}
/**
* Unlocks tables in the database, this command does not exist in
PostgreSQL, it is automatically done on commit or rollback.
*
* @return JDatabaseDriverPgsql Returns this object to support chaining.
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function unlockTables()
{
$this->transactionCommit();
return $this;
}
/**
* Updates a row in a table based on an object's properties.
*
* @param string $table The name of the database table to update.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param array $key The name of the primary key.
* @param boolean $nulls True to update null fields or false to
ignore them.
*
* @return boolean
*
* @since 3.9.0
* @throws \RuntimeException
*/
public function updateObject($table, &$object, $key, $nulls = false)
{
$columns = $this->getTableColumns($table);
$fields = array();
$where = array();
if (is_string($key))
{
$key = array($key);
}
if (is_object($key))
{
$key = (array) $key;
}
// Create the base update statement.
$statement = 'UPDATE ' . $this->quoteName($table) . '
SET %s WHERE %s';
// Iterate over the object variables to build the query fields/value
pairs.
foreach (get_object_vars($object) as $k => $v)
{
// Skip columns that don't exist in the table.
if (! array_key_exists($k, $columns))
{
continue;
}
// Only process scalars that are not internal fields.
if (is_array($v) || is_object($v) || $k[0] === '_')
{
continue;
}
// Set the primary key to the WHERE clause instead of a field to update.
if (in_array($k, $key, true))
{
$key_val = $this->sqlValue($columns, $k, $v);
$where[] = $this->quoteName($k) . '=' . $key_val;
continue;
}
// Prepare and sanitize the fields and values for the database query.
if ($v === null)
{
// If the value is null and we do not want to update nulls then ignore
this field.
if (!$nulls)
{
continue;
}
// If the value is null and we want to update nulls then set it.
$val = 'NULL';
}
else
// The field is not null so we prep it for update.
{
$val = $this->sqlValue($columns, $k, $v);
}
// Add the field to be updated.
$fields[] = $this->quoteName($k) . '=' . $val;
}
// We don't have any fields to update.
if (empty($fields))
{
return true;
}
// Set the query and execute the update.
$this->setQuery(sprintf($statement, implode(',', $fields),
implode(' AND ', $where)));
return $this->execute();
}
/**
* Quotes a binary string to database requirements for use in database
queries.
*
* @param mixed $data A binary string to quote.
*
* @return string The binary quoted input string.
*
* @since 3.9.12
*/
public function quoteBinary($data)
{
return "decode('" . bin2hex($data) . "',
'hex')";
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL database driver
*
* @since 3.0.0
* @deprecated 4.0 Use PDO PostgreSQL instead
*/
class JDatabaseDriverPostgresql extends JDatabaseDriver
{
/**
* The database driver name
*
* @var string
* @since 3.0.0
*/
public $name = 'postgresql';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'postgresql';
/**
* Quote for named objects
*
* @var string
* @since 3.0.0
*/
protected $nameQuote = '"';
/**
* The null/zero date string
*
* @var string
* @since 3.0.0
*/
protected $nullDate = '1970-01-01 00:00:00';
/**
* The minimum supported database version.
*
* @var string
* @since 3.0.0
*/
protected static $dbMinimum = '8.3.18';
/**
* Operator used for concatenation
*
* @var string
* @since 3.0.0
*/
protected $concat_operator = '||';
/**
* JDatabaseDriverPostgresqlQuery object returned by getQuery
*
* @var JDatabaseQueryPostgresql
* @since 3.0.0
*/
protected $queryObject = null;
/**
* Database object constructor
*
* @param array $options List of options used to configure the
connection
*
* @since 3.0.0
*/
public function __construct($options)
{
$options['host'] = (isset($options['host'])) ?
$options['host'] : 'localhost';
$options['user'] = (isset($options['user'])) ?
$options['user'] : '';
$options['password'] = (isset($options['password']))
? $options['password'] : '';
$options['database'] = (isset($options['database']))
? $options['database'] : '';
$options['port'] = (isset($options['port'])) ?
$options['port'] : null;
// Finalize initialization
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
// Make sure the postgresql extension for PHP is installed and enabled.
if (!self::isSupported())
{
throw new JDatabaseExceptionUnsupported('The pgsql extension for
PHP is not installed or enabled.');
}
/*
* pg_connect() takes the port as separate argument. Therefore, we
* have to extract it from the host string (if provided).
*/
// Check for empty port
if (!$this->options['port'])
{
// Port is empty or not set via options, check for port annotation (:)
in the host string
$tmp = substr(strstr($this->options['host'],
':'), 1);
if (!empty($tmp))
{
// Get the port number
if (is_numeric($tmp))
{
$this->options['port'] = $tmp;
}
// Extract the host name
$this->options['host'] =
substr($this->options['host'], 0,
strlen($this->options['host']) - (strlen($tmp) + 1));
// This will take care of the following notation: ":5432"
if ($this->options['host'] === '')
{
$this->options['host'] = 'localhost';
}
}
// No port annotation (:) found, setting port to default PostgreSQL port
5432
else
{
$this->options['port'] = '5432';
}
}
// Build the DSN for the connection.
$dsn = '';
if (!empty($this->options['host']))
{
$dsn .= "host={$this->options['host']}
port={$this->options['port']} ";
}
$dsn .= "dbname={$this->options['database']}
user={$this->options['user']}
password={$this->options['password']}";
// Attempt to connect to the server.
if (!($this->connection = @pg_connect($dsn)))
{
throw new JDatabaseExceptionConnecting('Error connecting to PGSQL
database.');
}
pg_set_error_verbosity($this->connection, PGSQL_ERRORS_DEFAULT);
pg_query($this->connection, 'SET
standard_conforming_strings=off');
pg_query($this->connection, 'SET
escape_string_warning=off');
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
// Close the connection.
if (is_resource($this->connection))
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
pg_close($this->connection);
}
$this->connection = null;
}
/**
* Method to escape a string for usage in an SQL statement.
*
* @param string $text The string to be escaped.
* @param boolean $extra Optional parameter to provide extra escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$this->connect();
$result = pg_escape_string($this->connection, $text);
if ($extra)
{
$result = addcslashes($result, '%_');
}
return $result;
}
/**
* Test to see if the PostgreSQL connector is available
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function test()
{
return function_exists('pg_connect');
}
/**
* Determines if the connection to the server is active.
*
* @return boolean
*
* @since 3.0.0
*/
public function connected()
{
$this->connect();
if (is_resource($this->connection))
{
return pg_ping($this->connection);
}
return false;
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return boolean
*
* @since 3.0.0
* @throws RuntimeException
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS
' : '') . $this->quoteName($tableName));
$this->execute();
return true;
}
/**
* Get the number of affected rows by the last INSERT, UPDATE, REPLACE or
DELETE for the previous executed SQL statement.
*
* @return integer The number of affected rows in the previous operation
*
* @since 3.0.0
*/
public function getAffectedRows()
{
$this->connect();
return pg_affected_rows($this->cursor);
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getCollation()
{
$this->connect();
$this->setQuery('SHOW LC_COLLATE');
$array = $this->loadAssocList();
return $array[0]['lc_collate'];
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
return pg_client_encoding($this->connection);
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
* This command is only valid for statements like SELECT or SHOW that
return an actual result set.
* To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE
or DELETE query, use getAffectedRows().
*
* @param resource $cur An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 3.0.0
*/
public function getNumRows($cur = null)
{
$this->connect();
return pg_num_rows((int) $cur ? $cur : $this->cursor);
}
/**
* Get the current or query, or new JDatabaseQuery object.
*
* @param boolean $new False to return the last query set, True to
return a new JDatabaseQuery object.
* @param boolean $asObj False to return last query as string, true to
get JDatabaseQueryPostgresql object.
*
* @return JDatabaseQuery The current query object or a new object
extending the JDatabaseQuery class.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getQuery($new = false, $asObj = false)
{
if ($new)
{
$this->queryObject = new JDatabaseQueryPostgresql($this);
return $this->queryObject;
}
else
{
if ($asObj)
{
return $this->queryObject;
}
else
{
return $this->sql;
}
}
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* This is unsupported by PostgreSQL.
*
* @param mixed $tables A table name or a list of table names.
*
* @return string An empty char because this function is not supported
by PostgreSQL.
*
* @since 3.0.0
*/
public function getTableCreate($tables)
{
return '';
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table. For
PostgreSQL may start with a schema.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$result = array();
$tableSub = $this->replacePrefix($table);
$fn = explode('.', $tableSub);
if (count($fn) === 2)
{
$schema = $fn[0];
$tableSub = $fn[1];
}
else
{
$schema = $this->getDefaultSchema();
}
$this->setQuery('
SELECT a.attname AS "column_name",
pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
CASE WHEN a.attnotnull IS TRUE
THEN \'NO\'
ELSE \'YES\'
END AS "null",
CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT
NULL
THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
END as "Default",
CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
THEN \'\'
ELSE pg_catalog.col_description(a.attrelid, a.attnum)
END AS "comments"
FROM pg_catalog.pg_attribute a
LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND
a.attnum=adef.adnum
LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
WHERE a.attrelid =
(SELECT oid FROM pg_catalog.pg_class WHERE relname=' .
$this->quote($tableSub) . '
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
nspname = ' . $this->quote($schema) . ')
)
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum'
);
$fields = $this->loadObjectList();
if ($typeOnly)
{
foreach ($fields as $field)
{
$result[$field->column_name] = preg_replace('/[(0-9)]/',
'', $field->type);
}
}
else
{
foreach ($fields as $field)
{
if (stristr(strtolower($field->type), 'character
varying'))
{
$field->Default = '';
}
if (stristr(strtolower($field->type), 'text'))
{
$field->Default = '';
}
// Do some dirty translation to MySQL output.
// TODO: Come up with and implement a standard across databases.
$result[$field->column_name] = (object) array(
'column_name' => $field->column_name,
'type' => $field->type,
'null' => $field->null,
'Default' => $field->Default,
'comments' => '',
'Field' => $field->column_name,
'Type' => $field->type,
'Null' => $field->null,
// TODO: Improve query above to return primary key info as well
// 'Key' => ($field->PK == '1' ?
'PRI' : '')
);
}
}
/* Change Postgresql's NULL::* type with PHP's null one */
foreach ($fields as $field)
{
if (preg_match('/^NULL::*/', $field->Default))
{
$field->Default = null;
}
}
return $result;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
if (in_array($table, $tableList))
{
// Get the details columns information.
$this->setQuery('
SELECT indexname AS "idxName", indisprimary AS
"isPrimary", indisunique AS "isUnique",
CASE WHEN indisprimary = true THEN
( SELECT \'ALTER TABLE \' || tablename || \' ADD
\' || pg_catalog.pg_get_constraintdef(const.oid, true)
FROM pg_constraint AS const WHERE const.conname=
pgClassFirst.relname )
ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
END AS "Query"
FROM pg_indexes
LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
WHERE tablename=' . $this->quote($table) . ' ORDER BY
indkey'
);
$keys = $this->loadObjectList();
return $keys;
}
return false;
}
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableList()
{
$this->connect();
$query = $this->getQuery(true)
->select('table_name')
->from('information_schema.tables')
->where('table_type=' . $this->quote('BASE
TABLE'))
->where('table_schema NOT IN (' .
$this->quote('pg_catalog') . ', ' .
$this->quote('information_schema') . ')')
->order('table_name ASC');
$this->setQuery($query);
$tables = $this->loadColumn();
return $tables;
}
/**
* Get the details list of sequences for a table.
*
* @param string $table The name of the table.
*
* @return array An array of sequences specification for the table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableSequences($table)
{
$this->connect();
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
if (in_array($table, $tableList))
{
$name = array(
's.relname', 'n.nspname', 't.relname',
'a.attname', 'info.data_type',
'info.minimum_value', 'info.maximum_value',
'info.increment', 'info.cycle_option',
);
$as = array('sequence', 'schema', 'table',
'column', 'data_type', 'minimum_value',
'maximum_value', 'increment',
'cycle_option');
if (version_compare($this->getVersion(), '9.1.0') >= 0)
{
$name[] .= 'info.start_value';
$as[] .= 'start_value';
}
// Get the details columns information.
$query = $this->getQuery(true)
->select($this->quoteName($name, $as))
->from('pg_class AS s')
->join('LEFT', "pg_depend d ON d.objid=s.oid AND
d.classid='pg_class'::regclass AND
d.refclassid='pg_class'::regclass")
->join('LEFT', 'pg_class t ON t.oid=d.refobjid')
->join('LEFT', 'pg_namespace n ON
n.oid=t.relnamespace')
->join('LEFT', 'pg_attribute a ON a.attrelid=t.oid
AND a.attnum=d.refobjsubid')
->join('LEFT', 'information_schema.sequences AS info
ON info.sequence_name=s.relname')
->where("s.relkind='S' AND d.deptype='a'
AND t.relname=" . $this->quote($table));
$this->setQuery($query);
$seq = $this->loadObjectList();
return $seq;
}
return false;
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
$version = pg_version($this->connection);
return $version['server'];
}
/**
* Method to get the auto-incremented value from the last INSERT
statement.
* To be called after the INSERT statement, it's MANDATORY to have a
sequence on
* every primary key table.
*
* To get the auto incremented value it's possible to call this
function after
* INSERT INTO query, or use INSERT INTO with RETURNING clause.
*
* @example with insertid() call:
* $query = $this->getQuery(true)
* ->insert('jos_dbtest')
* ->columns('title,start_date,description')
* ->values("'testTitle2nd','1971-01-01','testDescription2nd'");
* $this->setQuery($query);
* $this->execute();
* $id = $this->insertid();
*
* @example with RETURNING clause:
* $query = $this->getQuery(true)
* ->insert('jos_dbtest')
* ->columns('title,start_date,description')
* ->values("'testTitle2nd','1971-01-01','testDescription2nd'")
* ->returning('id');
* $this->setQuery($query);
* $id = $this->loadResult();
*
* @return integer The value of the auto-increment field from the last
inserted row.
*
* @since 3.0.0
*/
public function insertid()
{
$this->connect();
$insertQuery = $this->getQuery(false, true);
$table = $insertQuery->__get('insert')->getElements();
/* find sequence column name */
$colNameQuery = $this->getQuery(true);
$colNameQuery->select('column_default')
->from('information_schema.columns')
->where('table_name=' .
$this->quote($this->replacePrefix(str_replace('"',
'', $table[0]))), 'AND')
->where("column_default LIKE '%nextval%'");
$this->setQuery($colNameQuery);
$colName = $this->loadRow();
$changedColName = str_replace('nextval', 'currval',
$colName);
$insertidQuery = $this->getQuery(true);
$insertidQuery->select($changedColName);
$this->setQuery($insertidQuery);
$insertVal = $this->loadRow();
return $insertVal[0];
}
/**
* Locks a table in the database.
*
* @param string $tableName The name of the table to unlock.
*
* @return JDatabaseDriverPostgresql Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function lockTable($tableName)
{
$this->transactionStart();
$this->setQuery('LOCK TABLE ' .
$this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE
MODE')->execute();
return $this;
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and
cause issues later
$query = $this->replacePrefix((string) $this->sql);
if (!($this->sql instanceof JDatabaseQuery) &&
($this->limit > 0 || $this->offset > 0))
{
$query .= ' LIMIT ' . $this->limit . ' OFFSET ' .
$this->offset;
}
if (!is_resource($this->connection))
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
// Increment the query counter.
$this->count++;
// Reset the error values.
$this->errorNum = 0;
$this->errorMsg = '';
// If debugging is enabled then let's log the query.
if ($this->debug)
{
// Add the query to the object queue.
$this->log[] = $query;
JLog::add($query, JLog::DEBUG, 'databasequery');
$this->timings[] = microtime(true);
if (is_object($this->cursor))
{
// Avoid warning if result already freed by third-party library
@$this->freeResult();
}
$memoryBefore = memory_get_usage();
}
// Execute the query. Error suppression is used here to prevent
warnings/notices that the connection has been lost.
$this->cursor = @pg_query($this->connection, $query);
if ($this->debug)
{
$this->timings[] = microtime(true);
if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
else
{
$this->callStacks[] = debug_backtrace();
}
$this->callStacks[count($this->callStacks) -
1][0]['memory'] = array(
$memoryBefore,
memory_get_usage(),
is_resource($this->cursor) ? $this->getNumRows($this->cursor)
: null,
);
}
// If an error occurred handle it.
if (!$this->cursor)
{
// Get the error number and message before we execute any more queries.
$errorNum = $this->getErrorNumber();
$errorMsg = $this->getErrorMessage();
// Check if the server was disconnected.
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
// If connect fails, ignore that exception and throw the normal
exception.
catch (RuntimeException $e)
{
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
null, $e);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
// The server was not disconnected.
else
{
// Get the error number and message from before we tried to reconnect.
$this->errorNum = $errorNum;
$this->errorMsg = $errorMsg;
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg);
}
}
return $this->cursor;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by PostgreSQL.
* @param string $prefix Not used by PostgreSQL.
*
* @return JDatabaseDriverPostgresql Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->connect();
// To check if table exists and prevent SQL injection
$tableList = $this->getTableList();
// Origin Table does not exist
if (!in_array($oldTable, $tableList))
{
// Origin Table not found
throw new RuntimeException('Table not found in Postgresql
database.');
}
else
{
/* Rename indexes */
$this->setQuery(
'SELECT relname
FROM pg_class
WHERE oid IN (
SELECT indexrelid
FROM pg_index, pg_class
WHERE pg_class.relname=' . $this->quote($oldTable, true) .
'
AND pg_class.oid=pg_index.indrelid );'
);
$oldIndexes = $this->loadColumn();
foreach ($oldIndexes as $oldIndex)
{
$changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
$this->setQuery('ALTER INDEX ' .
$this->escape($oldIndex) . ' RENAME TO ' .
$this->escape($changedIdxName));
$this->execute();
}
/* Rename sequence */
$this->setQuery(
'SELECT relname
FROM pg_class
WHERE relkind = \'S\'
AND relnamespace IN (
SELECT oid
FROM pg_namespace
WHERE nspname NOT LIKE \'pg_%\'
AND nspname != \'information_schema\'
)
AND relname LIKE \'%' . $oldTable . '%\' ;'
);
$oldSequences = $this->loadColumn();
foreach ($oldSequences as $oldSequence)
{
$changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
$this->setQuery('ALTER SEQUENCE ' .
$this->escape($oldSequence) . ' RENAME TO ' .
$this->escape($changedSequenceName));
$this->execute();
}
/* Rename table */
$this->setQuery('ALTER TABLE ' .
$this->escape($oldTable) . ' RENAME TO ' .
$this->escape($newTable));
$this->execute();
}
return true;
}
/**
* Selects the database, but redundant for PostgreSQL
*
* @param string $database Database name to select.
*
* @return boolean Always true
*
* @since 3.0.0
*/
public function select($database)
{
return true;
}
/**
* Custom settings for UTF support
*
* @return integer Zero on success, -1 on failure
*
* @since 3.0.0
*/
public function setUtf()
{
$this->connect();
if (!function_exists('pg_set_client_encoding'))
{
return -1;
}
return pg_set_client_encoding($this->connection, 'UTF8');
}
/**
* This function return a field value as a prepared string to be used in a
SQL statement.
*
* @param array $columns Array of table's column returned by
::getTableColumns.
* @param string $fieldName The table field's name.
* @param string $fieldValue The variable value to quote and return.
*
* @return string The quoted string.
*
* @since 3.0.0
*/
public function sqlValue($columns, $fieldName, $fieldValue)
{
switch ($columns[$fieldName])
{
case 'boolean':
$val = 'NULL';
if ($fieldValue == 't')
{
$val = 'TRUE';
}
elseif ($fieldValue == 'f')
{
$val = 'FALSE';
}
break;
case 'bigint':
case 'bigserial':
case 'integer':
case 'money':
case 'numeric':
case 'real':
case 'smallint':
case 'serial':
case 'numeric,':
$val = strlen($fieldValue) == 0 ? 'NULL' : $fieldValue;
break;
case 'date':
case 'timestamp without time zone':
if (empty($fieldValue))
{
$fieldValue = $this->getNullDate();
}
$val = $this->quote($fieldValue);
break;
default:
$val = $this->quote($fieldValue);
break;
}
return $val;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('COMMIT')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('ROLLBACK')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
$this->setQuery('RELEASE SAVEPOINT ' .
$this->quoteName($savepoint))->execute();
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
if ($this->setQuery('START TRANSACTION')->execute())
{
$this->transactionDepth = 1;
}
return;
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchArray($cursor = null)
{
return pg_fetch_row($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchAssoc($cursor = null)
{
return pg_fetch_assoc($cursor ? $cursor : $this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class The class name to use for the returned row
object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject($cursor = null, $class =
'stdClass')
{
return pg_fetch_object(is_null($cursor) ? $this->cursor : $cursor,
null, $class);
}
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult($cursor = null)
{
pg_free_result($cursor ? $cursor : $this->cursor);
}
/**
* Inserts a row into a table based on an object's properties.
*
* @param string $table The name of the database table to insert
into.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param string $key The name of the primary key. If provided the
object property is updated.
*
* @return boolean True on success.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function insertObject($table, &$object, $key = null)
{
$columns = $this->getTableColumns($table);
$fields = array();
$values = array();
// Iterate over the object variables to build the query fields and
values.
foreach (get_object_vars($object) as $k => $v)
{
// Only process non-null scalars.
if (is_array($v) or is_object($v) or $v === null)
{
continue;
}
// Ignore any internal fields or primary keys with value 0.
if (($k[0] == '_') || ($k == $key && (($v === 0) ||
($v === '0'))))
{
continue;
}
// Prepare and sanitize the fields and values for the database query.
$fields[] = $this->quoteName($k);
$values[] = $this->sqlValue($columns, $k, $v);
}
// Create the base insert statement.
$query = $this->getQuery(true)
->insert($this->quoteName($table))
->columns($fields)
->values(implode(',', $values));
$retVal = false;
if ($key)
{
$query->returning($key);
// Set the query and execute the insert.
$this->setQuery($query);
$id = $this->loadResult();
if ($id)
{
$object->$key = $id;
$retVal = true;
}
}
else
{
// Set the query and execute the insert.
$this->setQuery($query);
if ($this->execute())
{
$retVal = true;
}
}
return $retVal;
}
/**
* Test to see if the PostgreSQL connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('pg_connect');
}
/**
* Returns an array containing database's table list.
*
* @return array The database's table list.
*
* @since 3.0.0
*/
public function showTables()
{
$this->connect();
$query = $this->getQuery(true)
->select('table_name')
->from('information_schema.tables')
->where('table_type = ' . $this->quote('BASE
TABLE'))
->where('table_schema NOT IN (' .
$this->quote('pg_catalog') . ', ' .
$this->quote('information_schema') . ' )');
$this->setQuery($query);
$tableList = $this->loadColumn();
return $tableList;
}
/**
* Get the substring position inside a string
*
* @param string $substring The string being sought
* @param string $string The string/column being searched
*
* @return integer The position of $substring in $string
*
* @since 3.0.0
*/
public function getStringPositionSql($substring, $string)
{
$this->connect();
$query = "SELECT POSITION( $substring IN $string )";
$this->setQuery($query);
$position = $this->loadRow();
return $position['position'];
}
/**
* Generate a random value
*
* @return float The random generated number
*
* @since 3.0.0
*/
public function getRandom()
{
$this->connect();
$this->setQuery('SELECT RANDOM()');
$random = $this->loadAssoc();
return $random['random'];
}
/**
* Get the query string to alter the database character set.
*
* @param string $dbName The database name
*
* @return string The query that alter the database query string
*
* @since 3.0.0
*/
public function getAlterDbCharacterSet($dbName)
{
$query = 'ALTER DATABASE ' . $this->quoteName($dbName) .
' SET CLIENT_ENCODING TO ' . $this->quote('UTF8');
return $query;
}
/**
* Get the query string to create new Database in correct PostgreSQL
syntax.
*
* @param object $options object coming from "initialise"
function to pass user and database name to database driver.
* @param boolean $utf True if the database supports the UTF-8
character set, not used in PostgreSQL "CREATE DATABASE" query.
*
* @return string The query that creates database, owned by
$options['user']
*
* @since 3.0.0
*/
public function getCreateDbQuery($options, $utf)
{
$query = 'CREATE DATABASE ' .
$this->quoteName($options->db_name) . ' OWNER ' .
$this->quoteName($options->db_user);
if ($utf)
{
$query .= ' ENCODING ' . $this->quote('UTF-8');
}
return $query;
}
/**
* This function replaces a string identifier
<var>$prefix</var> with the string held is the
* <var>tablePrefix</var> class variable.
*
* @param string $query The SQL statement to prepare.
* @param string $prefix The common table prefix.
*
* @return string The processed SQL statement.
*
* @since 3.0.0
*/
public function replacePrefix($query, $prefix = '#__')
{
$query = trim($query);
if (strpos($query, '\''))
{
// Sequence name quoted with ' ' but need to be replaced
if (strpos($query, 'currval'))
{
$query = explode('currval', $query);
for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax;
$nIndex += 2)
{
$query[$nIndex] = str_replace($prefix, $this->tablePrefix,
$query[$nIndex]);
}
$query = implode('currval', $query);
}
// Sequence name quoted with ' ' but need to be replaced
if (strpos($query, 'nextval'))
{
$query = explode('nextval', $query);
for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax;
$nIndex += 2)
{
$query[$nIndex] = str_replace($prefix, $this->tablePrefix,
$query[$nIndex]);
}
$query = implode('nextval', $query);
}
// Sequence name quoted with ' ' but need to be replaced
if (strpos($query, 'setval'))
{
$query = explode('setval', $query);
for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax;
$nIndex += 2)
{
$query[$nIndex] = str_replace($prefix, $this->tablePrefix,
$query[$nIndex]);
}
$query = implode('setval', $query);
}
$explodedQuery = explode('\'', $query);
for ($nIndex = 0, $nIndexMax = count($explodedQuery); $nIndex <
$nIndexMax; $nIndex += 2)
{
if (strpos($explodedQuery[$nIndex], $prefix))
{
$explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix,
$explodedQuery[$nIndex]);
}
}
$replacedQuery = implode('\'', $explodedQuery);
}
else
{
$replacedQuery = str_replace($prefix, $this->tablePrefix, $query);
}
return $replacedQuery;
}
/**
* Method to release a savepoint.
*
* @param string $savepointName Savepoint's name to release
*
* @return void
*
* @since 3.0.0
*/
public function releaseTransactionSavepoint($savepointName)
{
$this->connect();
$this->setQuery('RELEASE SAVEPOINT ' .
$this->quoteName($this->escape($savepointName)));
$this->execute();
}
/**
* Method to create a savepoint.
*
* @param string $savepointName Savepoint's name to create
*
* @return void
*
* @since 3.0.0
*/
public function transactionSavepoint($savepointName)
{
$this->connect();
$this->setQuery('SAVEPOINT ' .
$this->quoteName($this->escape($savepointName)));
$this->execute();
}
/**
* Unlocks tables in the database, this command does not exist in
PostgreSQL,
* it is automatically done on commit or rollback.
*
* @return JDatabaseDriverPostgresql Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function unlockTables()
{
$this->transactionCommit();
return $this;
}
/**
* Updates a row in a table based on an object's properties.
*
* @param string $table The name of the database table to update.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param array $key The name of the primary key.
* @param boolean $nulls True to update null fields or false to
ignore them.
*
* @return boolean True on success.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function updateObject($table, &$object, $key, $nulls = false)
{
$columns = $this->getTableColumns($table);
$fields = array();
$where = array();
if (is_string($key))
{
$key = array($key);
}
if (is_object($key))
{
$key = (array) $key;
}
// Create the base update statement.
$statement = 'UPDATE ' . $this->quoteName($table) . '
SET %s WHERE %s';
// Iterate over the object variables to build the query fields/value
pairs.
foreach (get_object_vars($object) as $k => $v)
{
// Only process scalars that are not internal fields.
if (is_array($v) or is_object($v) or $k[0] == '_')
{
continue;
}
// Set the primary key to the WHERE clause instead of a field to update.
if (in_array($k, $key))
{
$key_val = $this->sqlValue($columns, $k, $v);
$where[] = $this->quoteName($k) . '=' . $key_val;
continue;
}
// Prepare and sanitize the fields and values for the database query.
if ($v === null)
{
// If the value is null and we want to update nulls then set it.
if ($nulls)
{
$val = 'NULL';
}
// If the value is null and we do not want to update nulls then ignore
this field.
else
{
continue;
}
}
// The field is not null so we prep it for update.
else
{
$val = $this->sqlValue($columns, $k, $v);
}
// Add the field to be updated.
$fields[] = $this->quoteName($k) . '=' . $val;
}
// We don't have any fields to update.
if (empty($fields))
{
return true;
}
// Set the query and execute the update.
$this->setQuery(sprintf($statement, implode(',', $fields),
implode(' AND ', $where)));
return $this->execute();
}
/**
* Return the actual SQL Error number
*
* @return integer The SQL Error number
*
* @since 3.4.6
*
* @throws \JDatabaseExceptionExecuting Thrown if the global cursor is
false indicating a query failed
*/
protected function getErrorNumber()
{
if ($this->cursor === false)
{
$this->errorMsg = pg_last_error($this->connection);
throw new JDatabaseExceptionExecuting($this->sql,
$this->errorMsg);
}
return (int) pg_result_error_field($this->cursor, PGSQL_DIAG_SQLSTATE)
. ' ';
}
/**
* Return the actual SQL Error message
*
* @return string The SQL Error message
*
* @since 3.4.6
*/
protected function getErrorMessage()
{
$errorMessage = (string) pg_last_error($this->connection);
// Replace the Databaseprefix with `#__` if we are not in Debug
if (!$this->debug)
{
$errorMessage = str_replace($this->tablePrefix, '#__',
$errorMessage);
}
return $errorMessage;
}
/**
* Get the query strings to alter the character set and collation of a
table.
*
* @param string $tableName The name of the table
*
* @return string[] The queries required to alter the table's
character set and collation
*
* @since CMS 3.5.0
*/
public function getAlterTableCharacterSet($tableName)
{
return array();
}
/**
* Return the query string to create new Database.
* Each database driver, other than MySQL, need to override this member to
return correct string.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
*/
protected function getCreateDatabaseQuery($options, $utf)
{
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name);
}
/**
* Quotes a binary string to database requirements for use in database
queries.
*
* @param mixed $data A binary string to quote.
*
* @return string The binary quoted input string.
*
* @since 3.9.12
*/
public function quoteBinary($data)
{
return "decode('" . bin2hex($data) . "',
'hex')";
}
/**
* Internal function to get the name of the default schema for the current
PostgreSQL connection.
* That is the schema where tables are created by Joomla.
*
* @return string
*
* @since 3.9.24
*/
private function getDefaultSchema()
{
// Supported since PostgreSQL 7.3
$this->setQuery('SELECT (current_schemas(false))[1]');
return $this->loadResult();
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQL Server database driver
*
* @link
https://azure.microsoft.com/en-us/documentation/services/sql-database/
* @since 3.0.0
*/
class JDatabaseDriverSqlazure extends JDatabaseDriverSqlsrv
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'sqlazure';
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQLite database driver
*
* @link https://www.php.net/pdo
* @since 3.0.0
*/
class JDatabaseDriverSqlite extends JDatabaseDriverPdo
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'sqlite';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'sqlite';
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.0.0
*/
protected $nameQuote = '`';
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
parent::connect();
$this->connection->sqliteCreateFunction(
'ROW_NUMBER',
function($init = null)
{
static $rownum, $partition;
if ($init !== null)
{
$rownum = $init;
$partition = null;
return $rownum;
}
$args = func_get_args();
array_shift($args);
$partitionBy = $args ? implode(',', $args) : null;
if ($partitionBy === null || $partitionBy === $partition)
{
$rownum++;
}
else
{
$rownum = 1;
$partition = $partitionBy;
}
return $rownum;
}
);
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
$this->freeResult();
$this->connection = null;
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriverSqlite Returns this object to support
chaining.
*
* @since 3.0.0
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$query = $this->getQuery(true);
$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS
' : '') . $query->quoteName($tableName));
$this->execute();
return $this;
}
/**
* Method to escape a string for usage in an SQLite statement.
*
* Note: Using query objects with bound variables is
* preferable to the below.
*
* @param string $text The string to be escaped.
* @param boolean $extra Unused optional parameter to provide extra
escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
return SQLite3::escapeString($text);
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 3.0.0
*/
public function getCollation()
{
return $this->charset;
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
return $this->charset;
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* Note: Doesn't appear to have support in SQLite
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableCreate($tables)
{
$this->connect();
// Sanitize input to an array and iterate over the list.
settype($tables, 'array');
return $tables;
}
/**
* Retrieves field information about a given table.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields for the database table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$this->connect();
$columns = array();
$query = $this->getQuery(true);
$fieldCasing = $this->getOption(PDO::ATTR_CASE);
$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
$table = strtoupper($table);
$query->setQuery('pragma table_info(' . $table .
')');
$this->setQuery($query);
$fields = $this->loadObjectList();
if ($typeOnly)
{
foreach ($fields as $field)
{
$columns[$field->NAME] = $field->TYPE;
}
}
else
{
foreach ($fields as $field)
{
// Do some dirty translation to MySQL output.
// TODO: Come up with and implement a standard across databases.
$columns[$field->NAME] = (object) array(
'Field' => $field->NAME,
'Type' => $field->TYPE,
'Null' => ($field->NOTNULL == '1' ?
'NO' : 'YES'),
'Default' => $field->DFLT_VALUE,
'Key' => ($field->PK != '0' ?
'PRI' : ''),
);
}
}
$this->setOption(PDO::ATTR_CASE, $fieldCasing);
return $columns;
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
$keys = array();
$query = $this->getQuery(true);
$fieldCasing = $this->getOption(PDO::ATTR_CASE);
$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
$table = strtoupper($table);
$query->setQuery('pragma table_info( ' . $table .
')');
// $query->bind(':tableName', $table);
$this->setQuery($query);
$rows = $this->loadObjectList();
foreach ($rows as $column)
{
if ($column->PK == 1)
{
$keys[$column->NAME] = $column;
}
}
$this->setOption(PDO::ATTR_CASE, $fieldCasing);
return $keys;
}
/**
* Method to get an array of all tables in the database (schema).
*
* @return array An array of all the tables in the database.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableList()
{
$this->connect();
$type = 'table';
$query = $this->getQuery(true)
->select('name')
->from('sqlite_master')
->where('type = :type')
->bind(':type', $type)
->order('name');
$this->setQuery($query);
$tables = $this->loadColumn();
return $tables;
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
$this->setQuery('SELECT sqlite_version()');
return $this->loadResult();
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* Returns false automatically for the Oracle driver since
* you can only set the character set when the connection
* is created.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
$this->connect();
return false;
}
/**
* Locks a table in the database.
*
* @param string $table The name of the table to unlock.
*
* @return JDatabaseDriverSqlite Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function lockTable($table)
{
return $this;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Not used by Sqlite.
* @param string $prefix Not used by Sqlite.
*
* @return JDatabaseDriverSqlite Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME
TO ' . $newTable)->execute();
return $this;
}
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriverSqlite Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function unlockTables()
{
return $this;
}
/**
* Test to see if the PDO ODBC connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return class_exists('PDO') &&
in_array('sqlite', PDO::getAvailableDrivers());
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionCommit($toSavepoint);
}
else
{
$this->transactionDepth--;
}
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
parent::transactionRollback($toSavepoint);
}
else
{
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TO ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
}
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.1.4
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
parent::transactionStart($asSavepoint);
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('SAVEPOINT ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Get the query strings to alter the character set and collation of a
table.
*
* @param string $tableName The name of the table
*
* @return string[] The queries required to alter the table's
character set and collation
*
* @since CMS 3.5.0
*/
public function getAlterTableCharacterSet($tableName)
{
return array();
}
/**
* Return the query string to create new Database.
* Each database driver, other than MySQL, need to override this member to
return correct string.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
*/
protected function getCreateDatabaseQuery($options, $utf)
{
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQL Server database driver
*
* @link https://msdn.microsoft.com/en-us/library/cc296152(SQL.90).aspx
* @since 3.0.0
*/
class JDatabaseDriverSqlsrv extends JDatabaseDriver
{
/**
* The name of the database driver.
*
* @var string
* @since 3.0.0
*/
public $name = 'sqlsrv';
/**
* The type of the database server family supported by this driver.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType = 'mssql';
/**
* The character(s) used to quote SQL statement names such as table names
or field names,
* etc. The child classes should define this as necessary. If a single
character string the
* same character is used for both sides of the quoted name, else the
first character will be
* used for the opening quote and the second for the closing quote.
*
* @var string
* @since 3.0.0
*/
protected $nameQuote = '[]';
/**
* The null or zero representation of a timestamp for the database driver.
This should be
* defined in child classes to hold the appropriate value for the engine.
*
* @var string
* @since 3.0.0
*/
protected $nullDate = '1900-01-01 00:00:00';
/**
* @var string The minimum supported database version.
* @since 3.0.0
*/
protected static $dbMinimum = '10.50.1600.1';
/**
* Test to see if the SQLSRV connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 3.0.0
*/
public static function isSupported()
{
return function_exists('sqlsrv_connect');
}
/**
* Constructor.
*
* @param array $options List of options used to configure the
connection
*
* @since 3.0.0
*/
public function __construct($options)
{
// Get some basic values from the options.
$options['host'] = (isset($options['host'])) ?
$options['host'] : 'localhost';
$options['user'] = (isset($options['user'])) ?
$options['user'] : '';
$options['password'] = (isset($options['password']))
? $options['password'] : '';
$options['database'] = (isset($options['database']))
? $options['database'] : '';
$options['select'] = (isset($options['select'])) ?
(bool) $options['select'] : true;
// Finalize initialisation
parent::__construct($options);
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
// Build the connection configuration array.
$config = array(
'Database' => $this->options['database'],
'uid' => $this->options['user'],
'pwd' => $this->options['password'],
'CharacterSet' => 'UTF-8',
'ReturnDatesAsStrings' => true,
);
// Make sure the SQLSRV extension for PHP is installed and enabled.
if (!self::isSupported())
{
throw new JDatabaseExceptionUnsupported('The sqlsrv extension for
PHP is not installed or enabled..');
}
// Attempt to connect to the server.
if (!($this->connection = @
sqlsrv_connect($this->options['host'], $config)))
{
throw new JDatabaseExceptionConnecting('Database sqlsrv_connect
failed, ' . print_r(sqlsrv_errors(), true));
}
// Make sure that DB warnings are not returned as errors.
sqlsrv_configure('WarningsReturnAsErrors', 0);
// If auto-select is enabled select the given database.
if ($this->options['select'] &&
!empty($this->options['database']))
{
$this->select($this->options['database']);
}
// Set charactersets.
$this->utf = $this->setUtf();
// Set QUOTED_IDENTIFIER always ON
sqlsrv_query($this->connection, 'SET QUOTED_IDENTIFIER ON');
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
public function disconnect()
{
// Close the connection.
if (is_resource($this->connection))
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
sqlsrv_close($this->connection);
}
$this->connection = null;
}
/**
* Get table constraints
*
* @param string $tableName The name of the database table.
*
* @return array Any constraints available for the table.
*
* @since 3.0.0
*/
protected function getTableConstraints($tableName)
{
$this->connect();
$query = $this->getQuery(true);
$this->setQuery(
'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_NAME = ' . $query->quote($tableName)
);
return $this->loadColumn();
}
/**
* Rename constraints.
*
* @param array $constraints Array(strings) of table constraints
* @param string $prefix A string
* @param string $backup A string
*
* @return void
*
* @since 3.0.0
*/
protected function renameConstraints($constraints = array(), $prefix =
null, $backup = null)
{
$this->connect();
foreach ($constraints as $constraint)
{
$this->setQuery('sp_rename ' . $constraint . ','
. str_replace($prefix, $backup, $constraint));
$this->execute();
}
}
/**
* Method to escape a string for usage in an SQL statement.
*
* The escaping for MSSQL isn't handled in the driver though that
would be nice. Because of this we need
* to handle the escaping ourselves.
*
* @param string $text The string to be escaped.
* @param boolean $extra Optional parameter to provide extra escaping.
*
* @return string The escaped string.
*
* @since 3.0.0
*/
public function escape($text, $extra = false)
{
if (is_int($text))
{
return $text;
}
if (is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', $text);
}
$result = str_replace("'", "''",
$text);
// SQL Server does not accept NULL byte in query string
$result = str_replace("\0", "' + CHAR(0) +
N'", $result);
// Fix for SQL Server escape sequence, see
https://support.microsoft.com/en-us/kb/164291
$result = str_replace(
array("\\\n", "\\\r",
"\\\\\r\r\n"),
array("\\\\\n\n", "\\\\\r\r",
"\\\\\r\n\r\n"),
$result
);
if ($extra)
{
// Escape special chars
$result = str_replace(
array('[', '_', '%'),
array('[[]', '[_]', '[%]'),
$result
);
}
return $result;
}
/**
* Quotes and optionally escapes a string to database requirements for use
in database queries.
*
* @param mixed $text A string or an array of strings to quote.
* @param boolean $escape True (default) to escape the string, false
to leave it unchanged.
*
* @return string The quoted input string.
*
* @note Accepting an array of strings was added in 3.1.4.
* @since 1.7.0
*/
public function quote($text, $escape = true)
{
if (is_array($text))
{
return parent::quote($text, $escape);
}
// To support unicode on MSSQL we have to add prefix N
return 'N\'' . ($escape ? $this->escape($text) : $text)
. '\'';
}
/**
* Quotes a binary string to database requirements for use in database
queries.
*
* @param mixed $data A binary string to quote.
*
* @return string The binary quoted input string.
*
* @since 3.9.12
*/
public function quoteBinary($data)
{
// ODBC syntax for hexadecimal literals
return '0x' . bin2hex($data);
}
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 3.0.0
*/
public function connected()
{
// TODO: Run a blank query here
return true;
}
/**
* Drops a table from the database.
*
* @param string $tableName The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriverSqlsrv Returns this object to support
chaining.
*
* @since 3.0.0
*/
public function dropTable($tableName, $ifExists = true)
{
$this->connect();
$query = $this->getQuery(true);
if ($ifExists)
{
$this->setQuery(
'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE
' . $tableName
);
}
else
{
$this->setQuery('DROP TABLE ' . $tableName);
}
$this->execute();
return $this;
}
/**
* Get the number of affected rows for the previous executed SQL
statement.
*
* @return integer The number of affected rows.
*
* @since 3.0.0
*/
public function getAffectedRows()
{
$this->connect();
return sqlsrv_rows_affected($this->cursor);
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 3.0.0
*/
public function getCollation()
{
// TODO: Not fake this
return 'MSSQL UTF-8 (UCS2)';
}
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
// TODO: Not fake this
return 'MSSQL UTF-8 (UCS2)';
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
*
* @param resource $cursor An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 3.0.0
*/
public function getNumRows($cursor = null)
{
$this->connect();
return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
}
/**
* Retrieves field information about the given tables.
*
* @param mixed $table A table name
* @param boolean $typeOnly True to only return field types.
*
* @return array An array of fields.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableColumns($table, $typeOnly = true)
{
$result = array();
$table_temp = $this->replacePrefix((string) $table);
// Set the query to get the table fields statement.
$this->setQuery(
'SELECT column_name as Field, data_type as Type, is_nullable as
\'Null\', column_default as \'Default\'' .
' FROM information_schema.columns WHERE table_name = ' .
$this->quote($table_temp)
);
$fields = $this->loadObjectList();
// If we only want the type as the value add just that to the list.
if ($typeOnly)
{
foreach ($fields as $field)
{
$result[$field->Field] = preg_replace('/[(0-9)]/',
'', $field->Type);
}
}
// If we want the whole field data object add that to the list.
else
{
foreach ($fields as $field)
{
$field->Default =
preg_replace("/(^(\(\(|\('|\(N'|\()|(('\)|(?<!\()\)\)|\))$))/i",
'', $field->Default);
$result[$field->Field] = $field;
}
}
return $result;
}
/**
* Shows the table CREATE statement that creates the given tables.
*
* This is unsupported by MSSQL.
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableCreate($tables)
{
$this->connect();
return '';
}
/**
* Get the details list of keys for a table.
*
* @param string $table The name of the table.
*
* @return array An array of the column specification for the table.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableKeys($table)
{
$this->connect();
// TODO To implement.
return array();
}
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getTableList()
{
$this->connect();
// Set the query to get the tables statement.
$this->setQuery('SELECT name FROM ' .
$this->getDatabase() . '.sys.Tables WHERE type =
\'U\';');
$tables = $this->loadColumn();
return $tables;
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 3.0.0
*/
public function getVersion()
{
$this->connect();
$version = sqlsrv_server_info($this->connection);
return $version['SQLServerVersion'];
}
/**
* Inserts a row into a table based on an object's properties.
*
* @param string $table The name of the database table to insert
into.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param string $key The name of the primary key. If provided the
object property is updated.
*
* @return boolean True on success.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function insertObject($table, &$object, $key = null)
{
$fields = array();
$values = array();
$statement = 'INSERT INTO ' . $this->quoteName($table) .
' (%s) VALUES (%s)';
foreach (get_object_vars($object) as $k => $v)
{
// Only process non-null scalars.
if (is_array($v) or is_object($v) or $v === null)
{
continue;
}
if (!$this->checkFieldExists($table, $k))
{
continue;
}
if ($k[0] == '_')
{
// Internal field
continue;
}
if ($k == $key && $key == 0)
{
continue;
}
$fields[] = $this->quoteName($k);
$values[] = $this->Quote($v);
}
// Set the query and execute the insert.
$this->setQuery(sprintf($statement, implode(',', $fields),
implode(',', $values)));
if (!$this->execute())
{
return false;
}
$id = $this->insertid();
if ($key && $id)
{
$object->$key = $id;
}
return true;
}
/**
* Method to get the auto-incremented value from the last INSERT
statement.
*
* @return integer The value of the auto-increment field from the last
inserted row.
*
* @since 3.0.0
*/
public function insertid()
{
$this->connect();
// TODO: SELECT IDENTITY
$this->setQuery('SELECT @@IDENTITY');
return (int) $this->loadResult();
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
* @throws Exception
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and
cause issues later
$query = $this->replacePrefix((string) $this->sql);
if (!($this->sql instanceof JDatabaseQuery) &&
($this->limit > 0 || $this->offset > 0))
{
$query = $this->limit($query, $this->limit, $this->offset);
}
if (!is_resource($this->connection))
{
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
// Increment the query counter.
$this->count++;
// Reset the error values.
$this->errorNum = 0;
$this->errorMsg = '';
// If debugging is enabled then let's log the query.
if ($this->debug)
{
// Add the query to the object queue.
$this->log[] = $query;
JLog::add($query, JLog::DEBUG, 'databasequery');
$this->timings[] = microtime(true);
}
// SQLSrv_num_rows requires a static or keyset cursor.
if (strncmp(ltrim(strtoupper($query)), 'SELECT',
strlen('SELECT')) == 0)
{
$array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
}
else
{
$array = array();
}
// Execute the query. Error suppression is used here to prevent
warnings/notices that the connection has been lost.
$this->cursor = @sqlsrv_query($this->connection, $query, array(),
$array);
if ($this->debug)
{
$this->timings[] = microtime(true);
if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
else
{
$this->callStacks[] = debug_backtrace();
}
}
// If an error occurred handle it.
if (!$this->cursor)
{
// Get the error number and message before we execute any more queries.
$errorNum = $this->getErrorNumber();
$errorMsg = $this->getErrorMessage();
// Check if the server was disconnected.
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
// If connect fails, ignore that exception and throw the normal
exception.
catch (RuntimeException $e)
{
// Get the error number and message.
$this->errorNum = $this->getErrorNumber();
$this->errorMsg = $this->getErrorMessage();
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum, $e);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
// The server was not disconnected.
else
{
// Get the error number and message from before we tried to reconnect.
$this->errorNum = $errorNum;
$this->errorMsg = $errorMsg;
// Throw the normal query exception.
JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED',
$this->errorNum, $this->errorMsg), JLog::ERROR,
'database-error');
throw new JDatabaseExceptionExecuting($query, $this->errorMsg,
$this->errorNum);
}
}
return $this->cursor;
}
/**
* This function replaces a string identifier
<var>$prefix</var> with the string held is the
* <var>tablePrefix</var> class variable.
*
* @param string $query The SQL statement to prepare.
* @param string $prefix The common table prefix.
*
* @return string The processed SQL statement.
*
* @since 3.0.0
*/
public function replacePrefix($query, $prefix = '#__')
{
$query = trim($query);
if (strpos($query, "'"))
{
$parts = explode("'", $query);
for ($nIndex = 0, $size = count($parts); $nIndex < $size; $nIndex =
$nIndex + 2)
{
if (strpos($parts[$nIndex], $prefix) !== false)
{
$parts[$nIndex] = str_replace($prefix, $this->tablePrefix,
$parts[$nIndex]);
}
}
$query = implode("'", $parts);
}
else
{
$query = str_replace($prefix, $this->tablePrefix, $query);
}
return $query;
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function select($database)
{
$this->connect();
if (!$database)
{
return false;
}
if (!sqlsrv_query($this->connection, 'USE ' . $database,
null, array('scrollable' => SQLSRV_CURSOR_STATIC)))
{
throw new JDatabaseExceptionConnecting('Could not connect to SQL
Server database.');
}
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 3.0.0
*/
public function setUtf()
{
return false;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('COMMIT TRANSACTION')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth <= 1)
{
if ($this->setQuery('ROLLBACK TRANSACTION')->execute())
{
$this->transactionDepth = 0;
}
return;
}
$savepoint = 'SP_' . ($this->transactionDepth - 1);
$this->setQuery('ROLLBACK TRANSACTION ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth--;
}
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 3.0.0
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
if ($this->setQuery('BEGIN TRANSACTION')->execute())
{
$this->transactionDepth = 1;
}
return;
}
$savepoint = 'SP_' . $this->transactionDepth;
$this->setQuery('BEGIN TRANSACTION ' .
$this->quoteName($savepoint));
if ($this->execute())
{
$this->transactionDepth++;
}
}
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchArray($cursor = null)
{
return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor,
SQLSRV_FETCH_NUMERIC);
}
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchAssoc($cursor = null)
{
return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor,
SQLSRV_FETCH_ASSOC);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class The class name to use for the returned row
object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject($cursor = null, $class =
'stdClass')
{
// Class has to be loaded for sqlsrv on windows platform
class_exists($class);
return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
}
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult($cursor = null)
{
sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
}
/**
* Method to check and see if a field exists in a table.
*
* @param string $table The table in which to verify the field.
* @param string $field The field to verify.
*
* @return boolean True if the field exists in the table.
*
* @since 3.0.0
*/
protected function checkFieldExists($table, $field)
{
$this->connect();
$table = $this->replacePrefix((string) $table);
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_NAME = '$table' AND COLUMN_NAME = '$field' ORDER
BY ORDINAL_POSITION";
$this->setQuery($query);
if ($this->loadResult())
{
return true;
}
else
{
return false;
}
}
/**
* Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior
for scrolling through a result set.
*
* @param string $query The SQL statement to process.
* @param integer $limit The maximum affected rows to set.
* @param integer $offset The affected row offset to set.
*
* @return string The processed SQL statement.
*
* @since 3.0.0
*/
protected function limit($query, $limit, $offset)
{
if ($limit)
{
$total = $offset + $limit;
$position = stripos($query, 'SELECT');
$distinct = stripos($query, 'SELECT DISTINCT');
if ($position === $distinct)
{
$query = substr_replace($query, 'SELECT DISTINCT TOP ' .
(int) $total, $position, 15);
}
else
{
$query = substr_replace($query, 'SELECT TOP ' . (int) $total,
$position, 6);
}
}
if (!$offset)
{
return $query;
}
return PHP_EOL
. 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0))
AS RowNumber FROM ('
. $query
. PHP_EOL . ') AS A) AS A WHERE RowNumber > ' . (int)
$offset;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Table prefix
* @param string $prefix For the table - used to rename constraints
in non-mysql databases
*
* @return JDatabaseDriverSqlsrv Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function renameTable($oldTable, $newTable, $backup = null, $prefix
= null)
{
$constraints = array();
if (!is_null($prefix) && !is_null($backup))
{
$constraints = $this->getTableConstraints($oldTable);
}
if (!empty($constraints))
{
$this->renameConstraints($constraints, $prefix, $backup);
}
$this->setQuery("sp_rename '" . $oldTable .
"', '" . $newTable . "'");
return $this->execute();
}
/**
* Locks a table in the database.
*
* @param string $tableName The name of the table to lock.
*
* @return JDatabaseDriverSqlsrv Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function lockTable($tableName)
{
return $this;
}
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriverSqlsrv Returns this object to support
chaining.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function unlockTables()
{
return $this;
}
/**
* Return the actual SQL Error number
*
* @return integer The SQL Error number
*
* @since 3.4.6
*/
protected function getErrorNumber()
{
$errors = sqlsrv_errors();
return $errors[0]['code'];
}
/**
* Return the actual SQL Error message
*
* @return string The SQL Error message
*
* @since 3.4.6
*/
protected function getErrorMessage()
{
$errors = sqlsrv_errors();
$errorMessage = (string) $errors[0]['message'];
// Replace the Databaseprefix with `#__` if we are not in Debug
if (!$this->debug)
{
$errorMessage = str_replace($this->tablePrefix, '#__',
$errorMessage);
}
return $errorMessage;
}
/**
* Get the query strings to alter the character set and collation of a
table.
*
* @param string $tableName The name of the table
*
* @return string[] The queries required to alter the table's
character set and collation
*
* @since CMS 3.5.0
*/
public function getAlterTableCharacterSet($tableName)
{
return array();
}
/**
* Return the query string to create new Database.
* Each database driver, other than MySQL, need to override this member to
return correct string.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
*/
protected function getCreateDatabaseQuery($options, $utf)
{
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Driver Class
*
* @since 3.0.0
*
* @method string|array q() q($text, $escape = true) Alias for quote
method
* @method string|array qn() qn($name, $as = null) Alias for
quoteName method
*/
abstract class JDatabaseDriver extends JDatabase implements
JDatabaseInterface
{
/**
* The name of the database.
*
* @var string
* @since 2.5.0
*/
private $_database;
/**
* The name of the database driver.
*
* @var string
* @since 1.7.0
*/
public $name;
/**
* The type of the database server family supported by this driver.
Examples: mysql, oracle, postgresql, mssql,
* sqlite.
*
* @var string
* @since CMS 3.5.0
*/
public $serverType;
/**
* @var resource The database connection resource.
* @since 1.7.0
*/
protected $connection;
/**
* @var integer The number of SQL statements executed by the database
driver.
* @since 1.7.0
*/
protected $count = 0;
/**
* @var resource The database connection cursor from the last query.
* @since 1.7.0
*/
protected $cursor;
/**
* @var boolean The database driver debugging state.
* @since 1.7.0
*/
protected $debug = false;
/**
* @var integer The affected row limit for the current SQL statement.
* @since 1.7.0
*/
protected $limit = 0;
/**
* @var array The log of executed SQL statements by the database
driver.
* @since 1.7.0
*/
protected $log = array();
/**
* @var array The log of executed SQL statements timings (start and
stop microtimes) by the database driver.
* @since CMS 3.1.2
*/
protected $timings = array();
/**
* @var array The log of executed SQL statements timings (start and
stop microtimes) by the database driver.
* @since CMS 3.1.2
*/
protected $callStacks = array();
/**
* @var string The character(s) used to quote SQL statement names such
as table names or field names,
* etc. The child classes should define this as
necessary. If a single character string the
* same character is used for both sides of the quoted
name, else the first character will be
* used for the opening quote and the second for the
closing quote.
* @since 1.7.0
*/
protected $nameQuote;
/**
* @var string The null or zero representation of a timestamp for the
database driver. This should be
* defined in child classes to hold the appropriate value
for the engine.
* @since 1.7.0
*/
protected $nullDate;
/**
* @var integer The affected row offset to apply for the current SQL
statement.
* @since 1.7.0
*/
protected $offset = 0;
/**
* @var array Passed in upon instantiation and saved.
* @since 1.7.0
*/
protected $options;
/**
* @var JDatabaseQuery|string The current SQL statement to execute.
* @since 1.7.0
*/
protected $sql;
/**
* @var string The common database table prefix.
* @since 1.7.0
*/
protected $tablePrefix;
/**
* @var boolean True if the database engine supports UTF-8 character
encoding.
* @since 1.7.0
*/
protected $utf = true;
/**
* @var boolean True if the database engine supports UTF-8 Multibyte
(utf8mb4) character encoding.
* @since CMS 3.5.0
*/
protected $utf8mb4 = false;
/**
* @var integer The database error number
* @since 1.7.0
* @deprecated 3.0.0
*/
protected $errorNum = 0;
/**
* @var string The database error message
* @since 1.7.0
* @deprecated 3.0.0
*/
protected $errorMsg;
/**
* @var array JDatabaseDriver instances container.
* @since 1.7.0
*/
protected static $instances = array();
/**
* @var string The minimum supported database version.
* @since 3.0.0
*/
protected static $dbMinimum;
/**
* @var integer The depth of the current transaction.
* @since 3.1.4
*/
protected $transactionDepth = 0;
/**
* @var callable[] List of callables to call just before disconnecting
database
* @since CMS 3.1.2
*/
protected $disconnectHandlers = array();
/**
* Get a list of available database connectors. The list will only be
populated with connectors that both
* the class exists and the static test method returns true. This gives
us the ability to have a multitude
* of connector classes that are self-aware as to whether or not they are
able to be used on a given system.
*
* @return array An array of available database connectors.
*
* @since 1.7.0
*/
public static function getConnectors()
{
$connectors = array();
// Get an iterator and loop trough the driver classes.
$iterator = new DirectoryIterator(__DIR__ . '/driver');
/* @type $file DirectoryIterator */
foreach ($iterator as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() != 'php')
{
continue;
}
// Derive the class name from the type.
$class = str_ireplace('.php', '',
'JDatabaseDriver' . ucfirst(trim($fileName)));
// If the class doesn't exist we have nothing left to do but look
at the next type. We did our best.
if (!class_exists($class))
{
continue;
}
// Sweet! Our class exists, so now we just need to know if it passes
its test method.
if ($class::isSupported())
{
// Connector names should not have file extensions.
$connectors[] = str_ireplace('.php', '',
$fileName);
}
}
return $connectors;
}
/**
* Method to return a JDatabaseDriver instance based on the given options.
There are three global options and then
* the rest are specific to the database driver. The 'driver'
option defines which JDatabaseDriver class is
* used for the connection -- the default is 'mysqli'. The
'database' option determines which database is to
* be used for the connection. The 'select' option determines
whether the connector should automatically select
* the chosen database.
*
* Instances are unique to the given options and new objects are only
created when a unique options array is
* passed into the method. This ensures that we don't end up with
unnecessary database connection resources.
*
* @param array $options Parameters to be passed to the database
driver.
*
* @return JDatabaseDriver A database object.
*
* @since 1.7.0
* @throws RuntimeException
*/
public static function getInstance($options = array())
{
// Sanitize the database connector options.
$options['driver'] = (isset($options['driver'])) ?
preg_replace('/[^A-Z0-9_\.-]/i', '',
$options['driver']) : 'mysqli';
$options['database'] = (isset($options['database']))
? $options['database'] : null;
$options['select'] = (isset($options['select'])) ?
$options['select'] : true;
// If the selected driver is `mysql` and we are on PHP 7 or greater,
switch to the `mysqli` driver.
if ($options['driver'] === 'mysql' &&
PHP_MAJOR_VERSION >= 7)
{
// Check if we have support for the other MySQL drivers
$mysqliSupported = JDatabaseDriverMysqli::isSupported();
$pdoMysqlSupported = JDatabaseDriverPdomysql::isSupported();
// If neither is supported, then the user cannot use MySQL; throw an
exception
if (!$mysqliSupported && !$pdoMysqlSupported)
{
throw new JDatabaseExceptionUnsupported(
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use
the `mysql` driver.'
. ' Also, this system does not support MySQLi or PDO MySQL.
Cannot instantiate database driver.'
);
}
// Prefer MySQLi as it is a closer replacement for the removed MySQL
driver, otherwise use the PDO driver
if ($mysqliSupported)
{
JLog::add(
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use
the `mysql` driver. Trying `mysqli` instead.',
JLog::WARNING,
'deprecated'
);
$options['driver'] = 'mysqli';
}
else
{
JLog::add(
'The PHP `ext/mysql` extension is removed in PHP 7, cannot use
the `mysql` driver. Trying `pdomysql` instead.',
JLog::WARNING,
'deprecated'
);
$options['driver'] = 'pdomysql';
}
}
// Get the options signature for the database connector.
$signature = md5(serialize($options));
// If we already have a database connector instance for these options
then just use that.
if (empty(self::$instances[$signature]))
{
// Derive the class name from the driver.
$class = 'JDatabaseDriver' .
ucfirst(strtolower($options['driver']));
// If the class still doesn't exist we have nothing left to do but
throw an exception. We did our best.
if (!class_exists($class))
{
throw new JDatabaseExceptionUnsupported(sprintf('Unable to load
Database Driver: %s', $options['driver']));
}
// Create our new JDatabaseDriver connector based on the options given.
try
{
$instance = new $class($options);
}
catch (RuntimeException $e)
{
throw new JDatabaseExceptionConnecting(sprintf('Unable to connect
to the Database: %s', $e->getMessage()), $e->getCode(), $e);
}
// Set the new connector to the global instances based on signature.
self::$instances[$signature] = $instance;
}
return self::$instances[$signature];
}
/**
* Splits a string of multiple queries into an array of individual
queries.
* Single line or line end comments and multi line comments are stripped
off.
*
* @param string $sql Input SQL string with which to split into
individual queries.
*
* @return array The queries from the input string separated into an
array.
*
* @since 1.7.0
*/
public static function splitSql($sql)
{
$start = 0;
$open = false;
$comment = false;
$endString = '';
$end = strlen($sql);
$queries = array();
$query = '';
for ($i = 0; $i < $end; $i++)
{
$current = substr($sql, $i, 1);
$current2 = substr($sql, $i, 2);
$current3 = substr($sql, $i, 3);
$lenEndString = strlen($endString);
$testEnd = substr($sql, $i, $lenEndString);
if ($current == '"' || $current == "'" ||
$current2 == '--'
|| ($current2 == '/*' && $current3 != '/*!'
&& $current3 != '/*+')
|| ($current == '#' && $current3 != '#__')
|| ($comment && $testEnd == $endString))
{
// Check if quoted with previous backslash
$n = 2;
while (substr($sql, $i - $n + 1, 1) == '\\' && $n
< $i)
{
$n++;
}
// Not quoted
if ($n % 2 == 0)
{
if ($open)
{
if ($testEnd == $endString)
{
if ($comment)
{
$comment = false;
if ($lenEndString > 1)
{
$i += ($lenEndString - 1);
$current = substr($sql, $i, 1);
}
$start = $i + 1;
}
$open = false;
$endString = '';
}
}
else
{
$open = true;
if ($current2 == '--')
{
$endString = "\n";
$comment = true;
}
elseif ($current2 == '/*')
{
$endString = '*/';
$comment = true;
}
elseif ($current == '#')
{
$endString = "\n";
$comment = true;
}
else
{
$endString = $current;
}
if ($comment && $start < $i)
{
$query = $query . substr($sql, $start, ($i - $start));
}
}
}
}
if ($comment)
{
$start = $i + 1;
}
if (($current == ';' && !$open) || $i == $end - 1)
{
if ($start <= $i)
{
$query = $query . substr($sql, $start, ($i - $start + 1));
}
$query = trim($query);
if ($query)
{
if (($i == $end - 1) && ($current != ';'))
{
$query = $query . ';';
}
$queries[] = $query;
}
$query = '';
$start = $i + 1;
}
}
return $queries;
}
/**
* Magic method to provide method alias support for quote() and
quoteName().
*
* @param string $method The called method.
* @param array $args The array of arguments passed to the method.
*
* @return mixed The aliased method's return value or null.
*
* @since 1.7.0
*/
public function __call($method, $args)
{
if (empty($args))
{
return;
}
switch ($method)
{
case 'q':
return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
break;
case 'qn':
return $this->quoteName($args[0], isset($args[1]) ? $args[1] :
null);
break;
}
}
/**
* Constructor.
*
* @param array $options List of options used to configure the
connection
*
* @since 1.7.0
*/
public function __construct($options)
{
// Initialise object variables.
$this->_database = (isset($options['database'])) ?
$options['database'] : '';
$this->tablePrefix = (isset($options['prefix'])) ?
$options['prefix'] : 'jos_';
$this->count = 0;
$this->errorNum = 0;
$this->log = array();
// Set class options.
$this->options = $options;
}
/**
* Alter database's character set, obtaining query string from
protected member.
*
* @param string $dbName The database name that will be altered
*
* @return string The query that alter the database query string
*
* @since 3.0.1
* @throws RuntimeException
*/
public function alterDbCharacterSet($dbName)
{
if (is_null($dbName))
{
throw new RuntimeException('Database name must not be null.');
}
$this->setQuery($this->getAlterDbCharacterSet($dbName));
return $this->execute();
}
/**
* Alter a table's character set, obtaining an array of queries to do
so from a protected method. The conversion is
* wrapped in a transaction, if supported by the database driver.
Otherwise the table will be locked before the
* conversion. This prevents data corruption.
*
* @param string $tableName The name of the table to alter
* @param boolean $rethrow True to rethrow database exceptions.
Default: false (exceptions are suppressed)
*
* @return boolean True if successful
*
* @since CMS 3.5.0
* @throws RuntimeException If the table name is empty
* @throws Exception Relayed from the database layer if a database error
occurs and $rethrow == true
*/
public function alterTableCharacterSet($tableName, $rethrow = false)
{
if (is_null($tableName))
{
throw new RuntimeException('Table name must not be null.');
}
$queries = $this->getAlterTableCharacterSet($tableName);
if (empty($queries))
{
return false;
}
$hasTransaction = true;
try
{
$this->transactionStart();
}
catch (Exception $e)
{
$hasTransaction = false;
$this->lockTable($tableName);
}
foreach ($queries as $query)
{
try
{
$this->setQuery($query)->execute();
}
catch (Exception $e)
{
if ($hasTransaction)
{
$this->transactionRollback();
}
else
{
$this->unlockTables();
}
if ($rethrow)
{
throw $e;
}
return false;
}
}
if ($hasTransaction)
{
try
{
$this->transactionCommit();
}
catch (Exception $e)
{
$this->transactionRollback();
if ($rethrow)
{
throw $e;
}
return false;
}
}
else
{
$this->unlockTables();
}
return true;
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 3.0.0
* @throws RuntimeException
*/
abstract public function connect();
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 1.7.0
*/
abstract public function connected();
/**
* Create a new database using information from $options object, obtaining
query string
* from protected member.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
* @throws RuntimeException
*/
public function createDatabase($options, $utf = true)
{
if (is_null($options))
{
throw new RuntimeException('$options object must not be
null.');
}
elseif (empty($options->db_name))
{
throw new RuntimeException('$options object must have db_name
set.');
}
elseif (empty($options->db_user))
{
throw new RuntimeException('$options object must have db_user
set.');
}
$this->setQuery($this->getCreateDatabaseQuery($options, $utf));
return $this->execute();
}
/**
* Destructor.
*
* @since 3.8.0
*/
public function __destruct()
{
$this->disconnect();
}
/**
* Disconnects the database.
*
* @return void
*
* @since 3.0.0
*/
abstract public function disconnect();
/**
* Adds a function callable just before disconnecting the database.
Parameter of the callable is $this JDatabaseDriver
*
* @param callable $callable Function to call in disconnect() method
just before disconnecting from database
*
* @return void
*
* @since CMS 3.1.2
*/
public function addDisconnectHandler($callable)
{
$this->disconnectHandlers[] = $callable;
}
/**
* Drops a table from the database.
*
* @param string $table The name of the database table to drop.
* @param boolean $ifExists Optionally specify that the table must
exist before it is dropped.
*
* @return JDatabaseDriver Returns this object to support chaining.
*
* @since 2.5.0
* @throws RuntimeException
*/
abstract public function dropTable($table, $ifExists = true);
/**
* Escapes a string for usage in an SQL statement.
*
* @param string $text The string to be escaped.
* @param boolean $extra Optional parameter to provide extra escaping.
*
* @return string The escaped string.
*
* @since 1.7.0
*/
abstract public function escape($text, $extra = false);
/**
* Method to fetch a row from the result set cursor as an array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 1.7.0
*/
abstract protected function fetchArray($cursor = null);
/**
* Method to fetch a row from the result set cursor as an associative
array.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 1.7.0
*/
abstract protected function fetchAssoc($cursor = null);
/**
* Method to fetch a row from the result set cursor as an object.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
* @param string $class The class name to use for the returned row
object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 1.7.0
*/
abstract protected function fetchObject($cursor = null, $class =
'stdClass');
/**
* Method to free up the memory used for the result set.
*
* @param mixed $cursor The optional result set cursor from which to
fetch the row.
*
* @return void
*
* @since 1.7.0
*/
abstract protected function freeResult($cursor = null);
/**
* Get the number of affected rows for the previous executed SQL
statement.
*
* @return integer The number of affected rows.
*
* @since 1.7.0
*/
abstract public function getAffectedRows();
/**
* Return the query string to alter the database character set.
*
* @param string $dbName The database name
*
* @return string The query that alter the database query string
*
* @since 3.0.1
*/
public function getAlterDbCharacterSet($dbName)
{
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
return 'ALTER DATABASE ' . $this->quoteName($dbName) .
' CHARACTER SET `' . $charset . '`';
}
/**
* Get the query strings to alter the character set and collation of a
table.
*
* @param string $tableName The name of the table
*
* @return string[] The queries required to alter the table's
character set and collation
*
* @since CMS 3.5.0
*/
public function getAlterTableCharacterSet($tableName)
{
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
$collation = $charset . '_unicode_ci';
$quotedTableName = $this->quoteName($tableName);
$queries = array();
$queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET
$charset COLLATE $collation";
/**
* We also need to convert each text column, modifying their character
set and collation. This allows us to
* change, for example, a utf8_bin collated column to a utf8mb4_bin
collated column.
*/
$sql = "SHOW FULL COLUMNS FROM $quotedTableName";
$this->setQuery($sql);
$columns = $this->loadAssocList();
$columnMods = array();
if (is_array($columns))
{
foreach ($columns as $column)
{
// Make sure we are redefining only columns which do support a
collation
$col = (object) $column;
if (empty($col->Collation))
{
continue;
}
// Default new collation: utf8_unicode_ci or utf8mb4_unicode_ci
$newCollation = $charset . '_unicode_ci';
$collationParts = explode('_', $col->Collation);
/**
* If the collation is in the form charset_collationType_ci or
charset_collationType we have to change
* the charset but leave the collationType intact (e.g. utf8_bin must
become utf8mb4_bin, NOT
* utf8mb4_general_ci).
*/
if (count($collationParts) >= 2)
{
$ci = array_pop($collationParts);
$collationType = array_pop($collationParts);
$newCollation = $charset . '_' . $collationType .
'_' . $ci;
/**
* When the last part of the old collation is not _ci we have a
charset_collationType format,
* something like utf8_bin. Therefore the new collation only has *two*
parts.
*/
if ($ci != 'ci')
{
$newCollation = $charset . '_' . $ci;
}
}
// If the old and new collation is the same we don't have to
change the collation type
if (strtolower($newCollation) == strtolower($col->Collation))
{
continue;
}
$null = $col->Null == 'YES' ? 'NULL' : 'NOT
NULL';
$default = is_null($col->Default) ? '' : "DEFAULT
'" . $this->q($col->Default) . "'";
$columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type}
CHARACTER SET $charset COLLATE $newCollation $null $default";
}
}
if (count($columnMods))
{
$queries[] = "ALTER TABLE $quotedTableName " .
implode(',', $columnMods) .
" CHARACTER SET $charset COLLATE $collation";
}
return $queries;
}
/**
* Automatically downgrade a CREATE TABLE or ALTER TABLE query from
utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
* when the server doesn't support UTF-8 Multibyte.
*
* @param string $query The query to convert
*
* @return string The converted query
*/
public function convertUtf8mb4QueryToUtf8($query)
{
if ($this->hasUTF8mb4Support())
{
return $query;
}
// If it's not an ALTER TABLE or CREATE TABLE command there's
nothing to convert
if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query))
{
return $query;
}
// Don't do preg replacement if string does not exist
if (stripos($query, 'utf8mb4') === false)
{
return $query;
}
// Replace utf8mb4 with utf8 if not within single or double quotes or
name quotes
return
preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i',
'utf8', $query);
}
/**
* Return the query string to create new Database.
* Each database driver, other than MySQL, need to override this member to
return correct string.
*
* @param stdClass $options Object used to pass user and database name
to database driver.
* This object must have "db_name" and
"db_user" set.
* @param boolean $utf True if the database supports the UTF-8
character set.
*
* @return string The query that creates database
*
* @since 3.0.1
*/
protected function getCreateDatabaseQuery($options, $utf)
{
if ($utf)
{
$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
$collation = $charset . '_unicode_ci';
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name) . ' CHARACTER SET `' .
$charset . '` COLLATE `' . $collation . '`';
}
return 'CREATE DATABASE ' .
$this->quoteName($options->db_name);
}
/**
* Method to get the database collation in use by sampling a text field of
a table in the database.
*
* @return mixed The collation in use by the database or boolean false
if not supported.
*
* @since 1.7.0
*/
abstract public function getCollation();
/**
* Method to get the database connection collation, as reported by the
driver. If the connector doesn't support
* reporting this value please return an empty string.
*
* @return string
*/
public function getConnectionCollation()
{
return '';
}
/**
* Method that provides access to the underlying database connection.
Useful for when you need to call a
* proprietary method such as postgresql's lo_* methods.
*
* @return resource The underlying database connection resource.
*
* @since 1.7.0
*/
public function getConnection()
{
return $this->connection;
}
/**
* Get the total number of SQL statements executed by the database driver.
*
* @return integer
*
* @since 1.7.0
*/
public function getCount()
{
return $this->count;
}
/**
* Gets the name of the database used by this connection.
*
* @return string
*
* @since 2.5.0
*/
protected function getDatabase()
{
return $this->_database;
}
/**
* Returns a PHP date() function compliant date format for the database
driver.
*
* @return string The format string.
*
* @since 1.7.0
*/
public function getDateFormat()
{
return 'Y-m-d H:i:s';
}
/**
* Get the database driver SQL statement log.
*
* @return array SQL statements executed by the database driver.
*
* @since 1.7.0
*/
public function getLog()
{
return $this->log;
}
/**
* Get the database driver SQL statement log.
*
* @return array SQL statements executed by the database driver.
*
* @since CMS 3.1.2
*/
public function getTimings()
{
return $this->timings;
}
/**
* Get the database driver SQL statement log.
*
* @return array SQL statements executed by the database driver.
*
* @since CMS 3.1.2
*/
public function getCallStacks()
{
return $this->callStacks;
}
/**
* Get the minimum supported database version.
*
* @return string The minimum version number for the database driver.
*
* @since 3.0.0
*/
public function getMinimum()
{
return static::$dbMinimum;
}
/**
* Get the null or zero representation of a timestamp for the database
driver.
*
* @return string Null or zero representation of a timestamp.
*
* @since 1.7.0
*/
public function getNullDate()
{
return $this->nullDate;
}
/**
* Get the number of returned rows for the previous executed SQL
statement.
*
* @param resource $cursor An optional database cursor resource to
extract the row count from.
*
* @return integer The number of returned rows.
*
* @since 1.7.0
*/
abstract public function getNumRows($cursor = null);
/**
* Get the common table prefix for the database driver.
*
* @return string The common database table prefix.
*
* @since 1.7.0
*/
public function getPrefix()
{
return $this->tablePrefix;
}
/**
* Gets an exporter class object.
*
* @return JDatabaseExporter An exporter object.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getExporter()
{
// Derive the class name from the driver.
$class = 'JDatabaseExporter' . ucfirst($this->name);
// Make sure we have an exporter class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database Exporter not
found.');
}
$o = new $class;
$o->setDbo($this);
return $o;
}
/**
* Gets an importer class object.
*
* @return JDatabaseImporter An importer object.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getImporter()
{
// Derive the class name from the driver.
$class = 'JDatabaseImporter' . ucfirst($this->name);
// Make sure we have an importer class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database Importer not
found');
}
$o = new $class;
$o->setDbo($this);
return $o;
}
/**
* Get the name of the database driver. If $this->name is not set it
will try guessing the driver name from the
* class name.
*
* @return string
*
* @since CMS 3.5.0
*/
public function getName()
{
if (empty($this->name))
{
$className = get_class($this);
$className = str_replace('JDatabaseDriver', '',
$className);
$this->name = strtolower($className);
}
return $this->name;
}
/**
* Get the server family type, e.g. mysql, postgresql, oracle, sqlite,
mssql. If $this->serverType is not set it
* will attempt guessing the server family type from the driver name. If
this is not possible the driver name will
* be returned instead.
*
* @return string
*
* @since CMS 3.5.0
*/
public function getServerType()
{
if (empty($this->serverType))
{
$name = $this->getName();
if (stristr($name, 'mysql') !== false)
{
$this->serverType = 'mysql';
}
elseif (stristr($name, 'postgre') !== false)
{
$this->serverType = 'postgresql';
}
elseif (stristr($name, 'pgsql') !== false)
{
$this->serverType = 'postgresql';
}
elseif (stristr($name, 'oracle') !== false)
{
$this->serverType = 'oracle';
}
elseif (stristr($name, 'sqlite') !== false)
{
$this->serverType = 'sqlite';
}
elseif (stristr($name, 'sqlsrv') !== false)
{
$this->serverType = 'mssql';
}
elseif (stristr($name, 'mssql') !== false)
{
$this->serverType = 'mssql';
}
else
{
$this->serverType = $name;
}
}
return $this->serverType;
}
/**
* Get the current query object or a new JDatabaseQuery object.
*
* @param boolean $new False to return the current query object, True
to return a new JDatabaseQuery object.
*
* @return JDatabaseQuery The current query object or a new object
extending the JDatabaseQuery class.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function getQuery($new = false)
{
if ($new)
{
// Derive the class name from the driver.
$class = 'JDatabaseQuery' . ucfirst($this->name);
// Make sure we have a query class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database Query Class not
found.');
}
return new $class($this);
}
else
{
return $this->sql;
}
}
/**
* Get a new iterator on the current query.
*
* @param string $column An option column to use as the iterator key.
* @param string $class The class of object that is returned.
*
* @return JDatabaseIterator A new database iterator.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getIterator($column = null, $class = 'stdClass')
{
// Derive the class name from the driver.
$iteratorClass = 'JDatabaseIterator' . ucfirst($this->name);
// Make sure we have an iterator class for this driver.
if (!class_exists($iteratorClass))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not
defined', $iteratorClass));
}
// Return a new iterator
return new $iteratorClass($this->execute(), $column, $class);
}
/**
* Retrieves field information about the given tables.
*
* @param string $table The name of the database table.
* @param boolean $typeOnly True (default) to only return field types.
*
* @return array An array of fields by table.
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function getTableColumns($table, $typeOnly = true);
/**
* Shows the table CREATE statement that creates the given tables.
*
* @param mixed $tables A table name or a list of table names.
*
* @return array A list of the create SQL for the tables.
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function getTableCreate($tables);
/**
* Retrieves keys information about the given table.
*
* @param string $table The name of the table.
*
* @return array An array of keys for the table.
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function getTableKeys($table);
/**
* Method to get an array of all tables in the database.
*
* @return array An array of all the tables in the database.
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function getTableList();
/**
* Determine whether or not the database engine supports UTF-8 character
encoding.
*
* @return boolean True if the database engine supports UTF-8 character
encoding.
*
* @since 1.7.0
* @deprecated 4.0 - Use hasUTFSupport() instead
*/
public function getUTFSupport()
{
JLog::add('JDatabaseDriver::getUTFSupport() is deprecated. Use
JDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING,
'deprecated');
return $this->hasUTFSupport();
}
/**
* Determine whether or not the database engine supports UTF-8 character
encoding.
*
* @return boolean True if the database engine supports UTF-8 character
encoding.
*
* @since 3.0.0
*/
public function hasUTFSupport()
{
return $this->utf;
}
/**
* Determine whether the database engine support the UTF-8 Multibyte
(utf8mb4) character encoding. This applies to
* MySQL databases.
*
* @return boolean True if the database engine supports UTF-8 Multibyte.
*
* @since CMS 3.5.0
*/
public function hasUTF8mb4Support()
{
return $this->utf8mb4;
}
/**
* Get the version of the database connector
*
* @return string The database connector version.
*
* @since 1.7.0
*/
abstract public function getVersion();
/**
* Method to get the auto-incremented value from the last INSERT
statement.
*
* @return mixed The value of the auto-increment field from the last
inserted row.
*
* @since 1.7.0
*/
abstract public function insertid();
/**
* Inserts a row into a table based on an object's properties.
*
* @param string $table The name of the database table to insert
into.
* @param object &$object A reference to an object whose public
properties match the table fields.
* @param string $key The name of the primary key. If provided the
object property is updated.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function insertObject($table, &$object, $key = null)
{
$fields = array();
$values = array();
// Iterate over the object variables to build the query fields and
values.
foreach (get_object_vars($object) as $k => $v)
{
// Only process non-null scalars.
if (is_array($v) or is_object($v) or $v === null)
{
continue;
}
// Ignore any internal fields.
if ($k[0] == '_')
{
continue;
}
// Prepare and sanitize the fields and values for the database query.
$fields[] = $this->quoteName($k);
$values[] = $this->quote($v);
}
// Create the base insert statement.
$query = $this->getQuery(true)
->insert($this->quoteName($table))
->columns($fields)
->values(implode(',', $values));
// Set the query and execute the insert.
$this->setQuery($query);
if (!$this->execute())
{
return false;
}
// Update the primary key if it exists.
$id = $this->insertid();
if ($key && $id && is_string($key))
{
$object->$key = $id;
}
return true;
}
/**
* Method to check whether the installed database version is supported by
the database driver
*
* @return boolean True if the database version is supported
*
* @since 3.0.0
*/
public function isMinimumVersion()
{
return version_compare($this->getVersion(), static::$dbMinimum) >=
0;
}
/**
* Method to get the first row of the result set from the database query
as an associative array
* of ['field_name' => 'row_value'].
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadAssoc()
{
$this->connect();
$ret = null;
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get the first row from the result set as an associative array.
if ($array = $this->fetchAssoc($cursor))
{
$ret = $array;
}
// Free up system resources and return.
$this->freeResult($cursor);
return $ret;
}
/**
* Method to get an array of the result set rows from the database query
where each row is an associative array
* of ['field_name' => 'row_value']. The array of
rows can optionally be keyed by a field name, but defaults to
* a sequential numeric array.
*
* NOTE: Choosing to key the result array by a non-unique field name can
result in unwanted
* behavior and should be avoided.
*
* @param string $key The name of a field on which to key the
result array.
* @param string $column An optional column name. Instead of the whole
row, only this column value will be in
* the result array.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadAssocList($key = null, $column = null)
{
$this->connect();
$array = array();
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get all of the rows from the result set.
while ($row = $this->fetchAssoc($cursor))
{
$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) :
$row;
if ($key)
{
$array[$row[$key]] = $value;
}
else
{
$array[] = $value;
}
}
// Free up system resources and return.
$this->freeResult($cursor);
return $array;
}
/**
* Method to get an array of values from the
<var>$offset</var> field in each row of the result set from
* the database query.
*
* @param integer $offset The row offset to use to build the result
array.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadColumn($offset = 0)
{
$this->connect();
$array = array();
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get all of the rows from the result set as arrays.
while ($row = $this->fetchArray($cursor))
{
$array[] = $row[$offset];
}
// Free up system resources and return.
$this->freeResult($cursor);
return $array;
}
/**
* Method to get the next row in the result set from the database query as
an object.
*
* @param string $class The class name to use for the returned row
object.
*
* @return mixed The result of the query as an array, false if there
are no more rows.
*
* @since 1.7.0
* @throws RuntimeException
* @deprecated 4.0 - Use getIterator() instead
*/
public function loadNextObject($class = 'stdClass')
{
JLog::add(__METHOD__ . '() is deprecated. Use
JDatabaseDriver::getIterator() instead.', JLog::WARNING,
'deprecated');
$this->connect();
static $cursor = null;
// Execute the query and get the result set cursor.
if (is_null($cursor))
{
if (!($cursor = $this->execute()))
{
return $this->errorNum ? null : false;
}
}
// Get the next row from the result set as an object of type $class.
if ($row = $this->fetchObject($cursor, $class))
{
return $row;
}
// Free up system resources and return.
$this->freeResult($cursor);
$cursor = null;
return false;
}
/**
* Method to get the next row in the result set from the database query as
an array.
*
* @return mixed The result of the query as an array, false if there are
no more rows.
*
* @since 1.7.0
* @throws RuntimeException
* @deprecated 4.0 (CMS) Use JDatabaseDriver::getIterator() instead
*/
public function loadNextRow()
{
JLog::add(__METHOD__ . '() is deprecated. Use
JDatabaseDriver::getIterator() instead.', JLog::WARNING,
'deprecated');
$this->connect();
static $cursor = null;
// Execute the query and get the result set cursor.
if (is_null($cursor))
{
if (!($cursor = $this->execute()))
{
return $this->errorNum ? null : false;
}
}
// Get the next row from the result set as an object of type $class.
if ($row = $this->fetchArray($cursor))
{
return $row;
}
// Free up system resources and return.
$this->freeResult($cursor);
$cursor = null;
return false;
}
/**
* Method to get the first row of the result set from the database query
as an object.
*
* @param string $class The class name to use for the returned row
object.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadObject($class = 'stdClass')
{
$this->connect();
$ret = null;
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get the first row from the result set as an object of type $class.
if ($object = $this->fetchObject($cursor, $class))
{
$ret = $object;
}
// Free up system resources and return.
$this->freeResult($cursor);
return $ret;
}
/**
* Method to get an array of the result set rows from the database query
where each row is an object. The array
* of objects can optionally be keyed by a field name, but defaults to a
sequential numeric array.
*
* NOTE: Choosing to key the result array by a non-unique field name can
result in unwanted
* behavior and should be avoided.
*
* @param string $key The name of a field on which to key the result
array.
* @param string $class The class name to use for the returned row
objects.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadObjectList($key = '', $class =
'stdClass')
{
$this->connect();
$array = array();
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get all of the rows from the result set as objects of type $class.
while ($row = $this->fetchObject($cursor, $class))
{
if ($key)
{
$array[$row->$key] = $row;
}
else
{
$array[] = $row;
}
}
// Free up system resources and return.
$this->freeResult($cursor);
return $array;
}
/**
* Method to get the first field of the first row of the result set from
the database query.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadResult()
{
$this->connect();
$ret = null;
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get the first row from the result set as an array.
if ($row = $this->fetchArray($cursor))
{
$ret = $row[0];
}
// Free up system resources and return.
$this->freeResult($cursor);
return $ret;
}
/**
* Method to get the first row of the result set from the database query
as an array. Columns are indexed
* numerically so the first column in the result set would be accessible
via <var>$row[0]</var>, etc.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadRow()
{
$this->connect();
$ret = null;
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get the first row from the result set as an array.
if ($row = $this->fetchArray($cursor))
{
$ret = $row;
}
// Free up system resources and return.
$this->freeResult($cursor);
return $ret;
}
/**
* Method to get an array of the result set rows from the database query
where each row is an array. The array
* of objects can optionally be keyed by a field offset, but defaults to a
sequential numeric array.
*
* NOTE: Choosing to key the result array by a non-unique field can result
in unwanted
* behavior and should be avoided.
*
* @param integer $index The index of a field on which to key the
result array.
*
* @return mixed The return value or null if the query failed.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function loadRowList($index = null)
{
$this->connect();
$array = array();
// Execute the query and get the result set cursor.
if (!($cursor = $this->execute()))
{
return;
}
// Get all of the rows from the result set as arrays.
while ($row = $this->fetchArray($cursor))
{
if ($index !== null)
{
$array[$row[$index]] = $row;
}
else
{
$array[] = $row;
}
}
// Free up system resources and return.
$this->freeResult($cursor);
return $array;
}
/**
* Locks a table in the database.
*
* @param string $tableName The name of the table to unlock.
*
* @return JDatabaseDriver Returns this object to support chaining.
*
* @since 2.5.0
* @throws RuntimeException
*/
abstract public function lockTable($tableName);
/**
* Quotes and optionally escapes a string to database requirements for use
in database queries.
*
* @param mixed $text A string or an array of strings to quote.
* @param boolean $escape True (default) to escape the string, false
to leave it unchanged.
*
* @return string|array The quoted input.
*
* @note Accepting an array of strings was added in 12.3.
* @since 1.7.0
*/
public function quote($text, $escape = true)
{
if (is_array($text))
{
foreach ($text as $k => $v)
{
$text[$k] = $this->quote($v, $escape);
}
return $text;
}
else
{
return '\'' . ($escape ? $this->escape($text) : $text)
. '\'';
}
}
/**
* Quotes a binary string to database requirements for use in database
queries.
*
* @param mixed $data A binary string to quote.
*
* @return string The binary quoted input string.
*
* @since 3.9.12
*/
public function quoteBinary($data)
{
// SQL standard syntax for hexadecimal literals
return "X'" . bin2hex($data) . "'";
}
/**
* Wrap an SQL statement identifier name such as column, table or database
names in quotes to prevent injection
* risks and reserved word conflicts.
*
* @param mixed $name The identifier name to wrap in quotes, or an
array of identifier names to wrap in quotes.
* Each type supports dot-notation name.
* @param mixed $as The AS query part associated to $name. It can be
string or array, in latter case it has to be
* same length of $name; if is null there will not
be any AS part for string or array element.
*
* @return mixed The quote wrapped name, same type of $name.
*
* @since 1.7.0
*/
public function quoteName($name, $as = null)
{
if (is_string($name))
{
$quotedName = $this->quoteNameStr(explode('.', $name));
$quotedAs = '';
if (!is_null($as))
{
settype($as, 'array');
$quotedAs .= ' AS ' . $this->quoteNameStr($as);
}
return $quotedName . $quotedAs;
}
else
{
$fin = array();
if (is_null($as))
{
foreach ($name as $str)
{
$fin[] = $this->quoteName($str);
}
}
elseif (is_array($name) && (count($name) == count($as)))
{
$count = count($name);
for ($i = 0; $i < $count; $i++)
{
$fin[] = $this->quoteName($name[$i], $as[$i]);
}
}
return $fin;
}
}
/**
* Quote strings coming from quoteName call.
*
* @param array $strArr Array of strings coming from quoteName
dot-explosion.
*
* @return string Dot-imploded string of quoted parts.
*
* @since 1.7.3
*/
protected function quoteNameStr($strArr)
{
$parts = array();
$q = $this->nameQuote;
foreach ($strArr as $part)
{
if (is_null($part))
{
continue;
}
if (strlen($q) == 1)
{
$parts[] = $q . str_replace($q, $q . $q, $part) . $q;
}
else
{
$parts[] = $q[0] . str_replace($q[1], $q[1] . $q[1], $part) . $q[1];
}
}
return implode('.', $parts);
}
/**
* This function replaces a string identifier
<var>$prefix</var> with the string held is the
* <var>tablePrefix</var> class variable.
*
* @param string $sql The SQL statement to prepare.
* @param string $prefix The common table prefix.
*
* @return string The processed SQL statement.
*
* @since 1.7.0
*/
public function replacePrefix($sql, $prefix = '#__')
{
$startPos = 0;
$literal = '';
$sql = trim($sql);
$n = strlen($sql);
while ($startPos < $n)
{
$ip = strpos($sql, $prefix, $startPos);
if ($ip === false)
{
break;
}
$j = strpos($sql, "'", $startPos);
$k = strpos($sql, '"', $startPos);
if (($k !== false) && (($k < $j) || ($j === false)))
{
$quoteChar = '"';
$j = $k;
}
else
{
$quoteChar = "'";
}
if ($j === false)
{
$j = $n;
}
$literal .= str_replace($prefix, $this->tablePrefix, substr($sql,
$startPos, $j - $startPos));
$startPos = $j;
$j = $startPos + 1;
if ($j >= $n)
{
break;
}
// Quote comes first, find end of quote
while (true)
{
$k = strpos($sql, $quoteChar, $j);
$escaped = false;
if ($k === false)
{
break;
}
$l = $k - 1;
while ($l >= 0 && $sql[$l] == '\\')
{
$l--;
$escaped = !$escaped;
}
if ($escaped)
{
$j = $k + 1;
continue;
}
break;
}
if ($k === false)
{
// Error in the query - no end quote; ignore it
break;
}
$literal .= substr($sql, $startPos, $k - $startPos + 1);
$startPos = $k + 1;
}
if ($startPos < $n)
{
$literal .= substr($sql, $startPos, $n - $startPos);
}
return $literal;
}
/**
* Renames a table in the database.
*
* @param string $oldTable The name of the table to be renamed
* @param string $newTable The new name for the table.
* @param string $backup Table prefix
* @param string $prefix For the table - used to rename constraints
in non-mysql databases
*
* @return JDatabaseDriver Returns this object to support chaining.
*
* @since 2.5.0
* @throws RuntimeException
*/
abstract public function renameTable($oldTable, $newTable, $backup = null,
$prefix = null);
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function select($database);
/**
* Sets the database debugging state for the driver.
*
* @param boolean $level True to enable debugging.
*
* @return boolean The old debugging level.
*
* @since 1.7.0
* @deprecated 4.0 This will be removed in Joomla 4 without replacement
*/
public function setDebug($level)
{
$previous = $this->debug;
$this->debug = (bool) $level;
return $previous;
}
/**
* Sets the SQL statement string for later execution.
*
* @param mixed $query The SQL statement to set either as a
JDatabaseQuery object or a string.
* @param integer $offset The affected row offset to set.
* @param integer $limit The maximum affected rows to set.
*
* @return JDatabaseDriver This object to support method chaining.
*
* @since 1.7.0
*/
public function setQuery($query, $offset = 0, $limit = 0)
{
$this->sql = $query;
if ($query instanceof JDatabaseQueryLimitable)
{
if (!$limit && $query->limit)
{
$limit = $query->limit;
}
if (!$offset && $query->offset)
{
$offset = $query->offset;
}
$query->setLimit($limit, $offset);
}
else
{
$this->limit = (int) max(0, $limit);
$this->offset = (int) max(0, $offset);
}
return $this;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 1.7.0
*/
abstract public function setUtf();
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function transactionCommit($toSavepoint = false);
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last
savepoint.
*
* @return void
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function transactionRollback($toSavepoint = false);
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already
active, a savepoint will be created.
*
* @return void
*
* @since 1.7.0
* @throws RuntimeException
*/
abstract public function transactionStart($asSavepoint = false);
/**
* Method to truncate a table.
*
* @param string $table The table to truncate
*
* @return void
*
* @since 1.7.3
* @throws RuntimeException
*/
public function truncateTable($table)
{
$this->setQuery('TRUNCATE TABLE ' .
$this->quoteName($table));
$this->execute();
}
/**
* Updates a row in a table based on an object's properties.
*
* @param string $table The name of the database table to
update.
* @param object &$object A reference to an object whose
public properties match the table fields.
* @param array|string $key The name of the primary key.
* @param boolean $nulls True to update null fields or false to
ignore them.
*
* @return boolean True on success.
*
* @since 1.7.0
* @throws RuntimeException
*/
public function updateObject($table, &$object, $key, $nulls = false)
{
$fields = array();
$where = array();
if (is_string($key))
{
$key = array($key);
}
if (is_object($key))
{
$key = (array) $key;
}
// Create the base update statement.
$statement = 'UPDATE ' . $this->quoteName($table) . '
SET %s WHERE %s';
// Iterate over the object variables to build the query fields/value
pairs.
foreach (get_object_vars($object) as $k => $v)
{
// Only process scalars that are not internal fields.
if (is_array($v) || is_object($v) || $k[0] === '_')
{
continue;
}
// Set the primary key to the WHERE clause instead of a field to update.
if (in_array($k, $key))
{
$where[] = $this->quoteName($k) . ($v === null ? ' IS
NULL' : ' = ' . $this->quote($v));
continue;
}
// Prepare and sanitize the fields and values for the database query.
if ($v === null)
{
// If the value is null and we want to update nulls then set it.
if ($nulls)
{
$val = 'NULL';
}
// If the value is null and we do not want to update nulls then ignore
this field.
else
{
continue;
}
}
// The field is not null so we prep it for update.
else
{
$val = $this->quote($v);
}
// Add the field to be updated.
$fields[] = $this->quoteName($k) . '=' . $val;
}
// We don't have any fields to update.
if (empty($fields))
{
return true;
}
// Set the query and execute the update.
$this->setQuery(sprintf($statement, implode(',', $fields),
implode(' AND ', $where)));
return $this->execute();
}
/**
* Execute the SQL statement.
*
* @return mixed A database cursor resource on success, boolean false on
failure.
*
* @since 3.0.0
* @throws RuntimeException
*/
abstract public function execute();
/**
* Unlocks tables in the database.
*
* @return JDatabaseDriver Returns this object to support chaining.
*
* @since 2.5.0
* @throws RuntimeException
*/
abstract public function unlockTables();
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an error connecting to the database platform
*
* @since 3.6
*/
class JDatabaseExceptionConnecting extends RuntimeException
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an error executing a statement
*
* @since 3.6
*/
class JDatabaseExceptionExecuting extends RuntimeException
{
/**
* The SQL statement that was executed.
*
* @var string
* @since 3.6
*/
private $query;
/**
* Construct the exception
*
* @param string $query The SQL statement that was executed.
* @param string $message The Exception message to throw.
[optional]
* @param integer $code The Exception code. [optional]
* @param Exception $previous The previous exception used for the
exception chaining. [optional]
*
* @since 3.6
*/
public function __construct($query, $message = '', $code = 0,
Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->query = $query;
}
/**
* Get the SQL statement that was executed
*
* @return string
*
* @since 3.6
*/
public function getQuery()
{
return $this->query;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Exception class defining an unsupported database object
*
* @since 3.6
*/
class JDatabaseExceptionUnsupported extends RuntimeException
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL export driver.
*
* @since 1.7.0
* @deprecated 4.0 Use MySQLi or PDO MySQL instead
*/
class JDatabaseExporterMysql extends JDatabaseExporterMysqli
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseExporterMysql Method supports chaining.
*
* @since 1.7.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverMysql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQLi export driver.
*
* @since 1.7.0
*/
class JDatabaseExporterMysqli extends JDatabaseExporter
{
/**
* Builds the XML data for the tables to export.
*
* @return string An XML string
*
* @since 1.7.0
* @throws Exception if an error occurs.
*/
protected function buildXml()
{
$buffer = array();
$buffer[] = '<?xml version="1.0"?>';
$buffer[] = '<mysqldump
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
$buffer[] = ' <database name="">';
$buffer = array_merge($buffer, $this->buildXmlStructure());
$buffer[] = ' </database>';
$buffer[] = '</mysqldump>';
return implode("\n", $buffer);
}
/**
* Builds the XML structure to export.
*
* @return array An array of XML lines (strings).
*
* @since 1.7.0
* @throws Exception if an error occurs.
*/
protected function buildXmlStructure()
{
$buffer = array();
foreach ($this->from as $table)
{
// Replace the magic prefix if found.
$table = $this->getGenericTableName($table);
// Get the details columns information.
$fields = $this->db->getTableColumns($table, false);
$keys = $this->db->getTableKeys($table);
$buffer[] = ' <table_structure name="' . $table .
'">';
foreach ($fields as $field)
{
$buffer[] = ' <field Field="' . $field->Field .
'"' . ' Type="' . $field->Type .
'"' . ' Null="' . $field->Null .
'"' . ' Key="' .
$field->Key . '"' . (isset($field->Default) ?
' Default="' . $field->Default . '"' :
'') . ' Extra="' . $field->Extra .
'"' .
' />';
}
foreach ($keys as $key)
{
$buffer[] = ' <key Table="' . $table .
'"' . ' Non_unique="' . $key->Non_unique .
'"' . ' Key_name="' . $key->Key_name .
'"' .
' Seq_in_index="' . $key->Seq_in_index .
'"' . ' Column_name="' . $key->Column_name
. '"' . ' Collation="' . $key->Collation .
'"' .
' Null="' . $key->Null . '"' . '
Index_type="' . $key->Index_type . '"' .
' Comment="' . htmlspecialchars($key->Comment,
ENT_COMPAT, 'UTF-8') . '"' . ' />';
}
$buffer[] = ' </table_structure>';
}
return $buffer;
}
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseExporterMysqli Method supports chaining.
*
* @since 1.7.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverMysqli))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL export driver for the PDO based MySQL database driver.
*
* @package Joomla.Platform
* @subpackage Database
* @since 3.4
*/
class JDatabaseExporterPdomysql extends JDatabaseExporter
{
/**
* Builds the XML data for the tables to export.
*
* @return string An XML string
*
* @since 3.4
* @throws Exception if an error occurs.
*/
protected function buildXml()
{
$buffer = array();
$buffer[] = '<?xml version="1.0"?>';
$buffer[] = '<mysqldump
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
$buffer[] = ' <database name="">';
$buffer = array_merge($buffer, $this->buildXmlStructure());
$buffer[] = ' </database>';
$buffer[] = '</mysqldump>';
return implode("\n", $buffer);
}
/**
* Builds the XML structure to export.
*
* @return array An array of XML lines (strings).
*
* @since 3.4
* @throws Exception if an error occurs.
*/
protected function buildXmlStructure()
{
$buffer = array();
foreach ($this->from as $table)
{
// Replace the magic prefix if found.
$table = $this->getGenericTableName($table);
// Get the details columns information.
$fields = $this->db->getTableColumns($table, false);
$keys = $this->db->getTableKeys($table);
$buffer[] = ' <table_structure name="' . $table .
'">';
foreach ($fields as $field)
{
$buffer[] = ' <field Field="' . $field->Field .
'"' . ' Type="' . $field->Type .
'"' . ' Null="' . $field->Null .
'"' . ' Key="' .
$field->Key . '"' . (isset($field->Default) ?
' Default="' . $field->Default . '"' :
'') . ' Extra="' . $field->Extra .
'"' .
' />';
}
foreach ($keys as $key)
{
$buffer[] = ' <key Table="' . $table .
'"' . ' Non_unique="' . $key->Non_unique .
'"' . ' Key_name="' . $key->Key_name .
'"' .
' Seq_in_index="' . $key->Seq_in_index .
'"' . ' Column_name="' . $key->Column_name
. '"' . ' Collation="' . $key->Collation .
'"' .
' Null="' . $key->Null . '"' . '
Index_type="' . $key->Index_type . '"' .
' Comment="' . htmlspecialchars($key->Comment,
ENT_COMPAT, 'UTF-8') . '"' . ' />';
}
$buffer[] = ' </table_structure>';
}
return $buffer;
}
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseExporterPdomysql Method supports chaining.
*
* @since 3.4
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPdomysql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PDO PostgreSQL Database Exporter.
*
* @since 3.9.0
*/
class JDatabaseExporterPgsql extends JDatabaseExporterPostgresql
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseExporterPgsql Method supports chaining.
*
* @since 3.9.0
* @throws \Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPgsql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL export driver.
*
* @since 3.0.0
* @deprecated 4.0 Use PDO PostgreSQL instead
*
* @property-read JDatabaseDriverPostgresql $db The database connector
to use for exporting structure and/or data.
*/
class JDatabaseExporterPostgresql extends JDatabaseExporter
{
/**
* Builds the XML data for the tables to export.
*
* @return string An XML string
*
* @since 3.0.0
* @throws Exception if an error occurs.
*/
protected function buildXml()
{
$buffer = array();
$buffer[] = '<?xml version="1.0"?>';
$buffer[] = '<postgresqldump
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
$buffer[] = ' <database name="">';
$buffer = array_merge($buffer, $this->buildXmlStructure());
$buffer[] = ' </database>';
$buffer[] = '</postgresqldump>';
return implode("\n", $buffer);
}
/**
* Builds the XML structure to export.
*
* @return array An array of XML lines (strings).
*
* @since 3.0.0
* @throws Exception if an error occurs.
*/
protected function buildXmlStructure()
{
$buffer = array();
foreach ($this->from as $table)
{
// Replace the magic prefix if found.
$table = $this->getGenericTableName($table);
// Get the details columns information.
$fields = $this->db->getTableColumns($table, false);
$keys = $this->db->getTableKeys($table);
$sequences = $this->db->getTableSequences($table);
$buffer[] = ' <table_structure name="' . $table .
'">';
foreach ($sequences as $sequence)
{
if (version_compare($this->db->getVersion(), '9.1.0')
< 0)
{
$sequence->start_value = null;
}
$buffer[] = ' <sequence Name="' .
$sequence->sequence . '"' . ' Schema="' .
$sequence->schema . '"' .
' Table="' . $sequence->table . '"' .
' Column="' . $sequence->column . '"' .
' Type="' . $sequence->data_type . '"' .
' Start_Value="' . $sequence->start_value .
'"' . ' Min_Value="' .
$sequence->minimum_value . '"' .
' Max_Value="' . $sequence->maximum_value .
'"' . ' Increment="' .
$sequence->increment . '"' .
' Cycle_option="' . $sequence->cycle_option .
'"' .
' />';
}
foreach ($fields as $field)
{
$buffer[] = ' <field Field="' .
$field->column_name . '"' . ' Type="' .
$field->type . '"' . ' Null="' .
$field->null . '"' .
(isset($field->default) ? ' Default="' .
$field->default . '"' : '') . '
Comments="' . $field->comments . '"' .
' />';
}
foreach ($keys as $key)
{
$buffer[] = ' <key Index="' . $key->idxName .
'"' . ' is_primary="' . $key->isPrimary .
'"' . ' is_unique="' . $key->isUnique .
'"' .
' Query="' . $key->Query . '" />';
}
$buffer[] = ' </table_structure>';
}
return $buffer;
}
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseExporterPostgresql Method supports chaining.
*
* @since 3.0.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPostgresql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Exporter Class
*
* @since 3.0.0
*/
abstract class JDatabaseExporter
{
/**
* The type of output format (xml).
*
* @var string
* @since 3.2.0
*/
protected $asFormat = 'xml';
/**
* An array of cached data.
*
* @var array
* @since 3.2.0
*/
protected $cache = array();
/**
* The database connector to use for exporting structure and/or data.
*
* @var JDatabaseDriver
* @since 3.2.0
*/
protected $db = null;
/**
* An array input sources (table names).
*
* @var array
* @since 3.2.0
*/
protected $from = array();
/**
* An array of options for the exporter.
*
* @var object
* @since 3.2.0
*/
protected $options = null;
/**
* Constructor.
*
* Sets up the default options for the exporter.
*
* @since 3.2.0
*/
public function __construct()
{
$this->options = new stdClass;
$this->cache = array('columns' => array(),
'keys' => array());
// Set up the class defaults:
// Export with only structure
$this->withStructure();
// Export as xml.
$this->asXml();
// Default destination is a string using $output = (string) $exporter;
}
/**
* Magic function to exports the data to a string.
*
* @return string
*
* @since 3.2.0
* @throws Exception if an error is encountered.
*/
public function __toString()
{
// Check everything is ok to run first.
$this->check();
// Get the format.
switch ($this->asFormat)
{
case 'xml':
default:
$buffer = $this->buildXml();
break;
}
return $buffer;
}
/**
* Set the output option for the exporter to XML format.
*
* @return JDatabaseExporter Method supports chaining.
*
* @since 3.2.0
*/
public function asXml()
{
$this->asFormat = 'xml';
return $this;
}
/**
* Builds the XML data for the tables to export.
*
* @return string An XML string
*
* @since 3.2.0
* @throws Exception if an error occurs.
*/
abstract protected function buildXml();
/**
* Builds the XML structure to export.
*
* @return array An array of XML lines (strings).
*
* @since 3.2.0
* @throws Exception if an error occurs.
*/
abstract protected function buildXmlStructure();
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseDriver Method supports chaining.
*
* @since 3.2.0
* @throws Exception if an error is encountered.
*/
abstract public function check();
/**
* Specifies a list of table names to export.
*
* @param mixed $from The name of a single table, or an array of the
table names to export.
*
* @return JDatabaseExporter Method supports chaining.
*
* @since 3.2.0
* @throws Exception if input is not a string or array.
*/
public function from($from)
{
if (is_string($from))
{
$this->from = array($from);
}
elseif (is_array($from))
{
$this->from = $from;
}
else
{
throw new
Exception('JPLATFORM_ERROR_INPUT_REQUIRES_STRING_OR_ARRAY');
}
return $this;
}
/**
* Get the generic name of the table, converting the database prefix to
the wildcard string.
*
* @param string $table The name of the table.
*
* @return string The name of the table with the database prefix
replaced with #__.
*
* @since 3.2.0
*/
protected function getGenericTableName($table)
{
$prefix = $this->db->getPrefix();
// Replace the magic prefix if found.
$table = preg_replace("|^$prefix|", '#__', $table);
return $table;
}
/**
* Sets the database connector to use for exporting structure and/or data
from MySQL.
*
* @param JDatabaseDriver $db The database connector.
*
* @return JDatabaseExporter Method supports chaining.
*
* @since 3.2.0
*/
public function setDbo(JDatabaseDriver $db)
{
$this->db = $db;
return $this;
}
/**
* Sets an internal option to export the structure of the input table(s).
*
* @param boolean $setting True to export the structure, false to not.
*
* @return JDatabaseExporter Method supports chaining.
*
* @since 3.2.0
*/
public function withStructure($setting = true)
{
$this->options->withStructure = (boolean) $setting;
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Factory class
*
* @since 3.0.0
*/
class JDatabaseFactory
{
/**
* Contains the current JDatabaseFactory instance
*
* @var JDatabaseFactory
* @since 3.0.0
*/
private static $_instance = null;
/**
* Method to return a JDatabaseDriver instance based on the given options.
There are three global options and then
* the rest are specific to the database driver. The 'database'
option determines which database is to
* be used for the connection. The 'select' option determines
whether the connector should automatically select
* the chosen database.
*
* Instances are unique to the given options and new objects are only
created when a unique options array is
* passed into the method. This ensures that we don't end up with
unnecessary database connection resources.
*
* @param string $name Name of the database driver you'd like
to instantiate
* @param array $options Parameters to be passed to the database
driver.
*
* @return JDatabaseDriver A database driver object.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getDriver($name = 'mysqli', $options = array())
{
// Sanitize the database connector options.
$options['driver'] =
preg_replace('/[^A-Z0-9_\.-]/i', '', $name);
$options['database'] = (isset($options['database']))
? $options['database'] : null;
$options['select'] = (isset($options['select'])) ?
$options['select'] : true;
// Derive the class name from the driver.
$class = 'JDatabaseDriver' .
ucfirst(strtolower($options['driver']));
// If the class still doesn't exist we have nothing left to do but
throw an exception. We did our best.
if (!class_exists($class))
{
throw new JDatabaseExceptionUnsupported(sprintf('Unable to load
Database Driver: %s', $options['driver']));
}
// Create our new JDatabaseDriver connector based on the options given.
try
{
$instance = new $class($options);
}
catch (RuntimeException $e)
{
throw new JDatabaseExceptionConnecting(sprintf('Unable to connect
to the Database: %s', $e->getMessage()), $e->getCode(), $e);
}
return $instance;
}
/**
* Gets an exporter class object.
*
* @param string $name Name of the driver you want an
exporter for.
* @param JDatabaseDriver $db Optional JDatabaseDriver instance
*
* @return JDatabaseExporter An exporter object.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getExporter($name, JDatabaseDriver $db = null)
{
// Derive the class name from the driver.
$class = 'JDatabaseExporter' . ucfirst(strtolower($name));
// Make sure we have an exporter class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database Exporter not
found.');
}
$o = new $class;
if ($db instanceof JDatabaseDriver)
{
$o->setDbo($db);
}
return $o;
}
/**
* Gets an importer class object.
*
* @param string $name Name of the driver you want an
importer for.
* @param JDatabaseDriver $db Optional JDatabaseDriver instance
*
* @return JDatabaseImporter An importer object.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getImporter($name, JDatabaseDriver $db = null)
{
// Derive the class name from the driver.
$class = 'JDatabaseImporter' . ucfirst(strtolower($name));
// Make sure we have an importer class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database importer not
found.');
}
$o = new $class;
if ($db instanceof JDatabaseDriver)
{
$o->setDbo($db);
}
return $o;
}
/**
* Gets an instance of the factory object.
*
* @return JDatabaseFactory
*
* @since 3.0.0
*/
public static function getInstance()
{
return self::$_instance ? self::$_instance : new JDatabaseFactory;
}
/**
* Get the current query object or a new JDatabaseQuery object.
*
* @param string $name Name of the driver you want an query
object for.
* @param JDatabaseDriver $db Optional JDatabaseDriver instance
*
* @return JDatabaseQuery The current query object or a new object
extending the JDatabaseQuery class.
*
* @since 3.0.0
* @throws RuntimeException
*/
public function getQuery($name, JDatabaseDriver $db = null)
{
// Derive the class name from the driver.
$class = 'JDatabaseQuery' . ucfirst(strtolower($name));
// Make sure we have a query class for this driver.
if (!class_exists($class))
{
// If it doesn't exist we are at an impasse so throw an exception.
throw new JDatabaseExceptionUnsupported('Database Query class not
found');
}
return new $class($db);
}
/**
* Gets an instance of a factory object to return on subsequent calls of
getInstance.
*
* @param JDatabaseFactory $instance A JDatabaseFactory object.
*
* @return void
*
* @since 3.0.0
*/
public static function setInstance(JDatabaseFactory $instance = null)
{
self::$_instance = $instance;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL import driver.
*
* @since 1.7.0
* @deprecated 4.0 Use MySQLi or PDO MySQL instead
*/
class JDatabaseImporterMysql extends JDatabaseImporterMysqli
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporterMysql Method supports chaining.
*
* @since 1.7.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverMysql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQLi import driver.
*
* @since 1.7.0
*/
class JDatabaseImporterMysqli extends JDatabaseImporter
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporterMysqli Method supports chaining.
*
* @since 1.7.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverMysqli))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
/**
* Get the SQL syntax to add a table.
*
* @param SimpleXMLElement $table The table information.
*
* @return string
*
* @since 1.7.0
* @throws RuntimeException
*/
protected function xmlToCreate(SimpleXMLElement $table)
{
$existingTables = $this->db->getTableList();
$tableName = (string) $table['name'];
if (in_array($tableName, $existingTables))
{
throw new RuntimeException('The table you are trying to create
already exists');
}
$createTableStatement = 'CREATE TABLE ' .
$this->db->quoteName($tableName) . ' (';
foreach ($table->xpath('field') as $field)
{
$createTableStatement .= $this->getColumnSQL($field) . ',
';
}
$newLookup = $this->getKeyLookup($table->xpath('key'));
// Loop through each key in the new structure.
foreach ($newLookup as $key)
{
$createTableStatement .= $this->getKeySQL($key) . ', ';
}
// Remove the comma after the last key
$createTableStatement = rtrim($createTableStatement, ', ');
$createTableStatement .= ')';
return $createTableStatement;
}
/**
* Get the SQL syntax to add a column.
*
* @param string $table The table name.
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 1.7.0
*/
protected function getAddColumnSql($table, SimpleXMLElement $field)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' ADD COLUMN ' . $this->getColumnSql($field);
}
/**
* Get the SQL syntax to add a key.
*
* @param string $table The table name.
* @param array $keys An array of the fields pertaining to this key.
*
* @return string
*
* @since 1.7.0
*/
protected function getAddKeySql($table, $keys)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' ADD ' . $this->getKeySql($keys);
}
/**
* Get alters for table if there is a difference.
*
* @param SimpleXMLElement $structure The XML structure of the table.
*
* @return array
*
* @since 1.7.0
*/
protected function getAlterTableSql(SimpleXMLElement $structure)
{
$table = $this->getRealTableName($structure['name']);
$oldFields = $this->db->getTableColumns($table, false);
$oldKeys = $this->db->getTableKeys($table);
$alters = array();
// Get the fields and keys from the XML that we are aiming for.
$newFields = $structure->xpath('field');
$newKeys = $structure->xpath('key');
// Loop through each field in the new structure.
foreach ($newFields as $field)
{
$fName = (string) $field['Field'];
if (isset($oldFields[$fName]))
{
// The field exists, check it's the same.
$column = $oldFields[$fName];
// Test whether there is a change.
$change = ((string) $field['Type'] != $column->Type) ||
((string) $field['Null'] != $column->Null)
|| ((string) $field['Default'] != $column->Default) ||
((string) $field['Extra'] != $column->Extra);
if ($change)
{
$alters[] = $this->getChangeColumnSql($table, $field);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldFields[$fName]);
}
else
{
// The field is new.
$alters[] = $this->getAddColumnSql($table, $field);
}
}
// Any columns left are orphans
foreach ($oldFields as $name => $column)
{
// Delete the column.
$alters[] = $this->getDropColumnSql($table, $name);
}
// Get the lookups for the old and new keys.
$oldLookup = $this->getKeyLookup($oldKeys);
$newLookup = $this->getKeyLookup($newKeys);
// Loop through each key in the new structure.
foreach ($newLookup as $name => $keys)
{
// Check if there are keys on this field in the existing table.
if (isset($oldLookup[$name]))
{
$same = true;
$newCount = count($newLookup[$name]);
$oldCount = count($oldLookup[$name]);
// There is a key on this field in the old and new tables. Are they the
same?
if ($newCount == $oldCount)
{
// Need to loop through each key and do a fine grained check.
for ($i = 0; $i < $newCount; $i++)
{
$same = (((string) $newLookup[$name][$i]['Non_unique'] ==
$oldLookup[$name][$i]->Non_unique)
&& ((string) $newLookup[$name][$i]['Column_name']
== $oldLookup[$name][$i]->Column_name)
&& ((string) $newLookup[$name][$i]['Seq_in_index']
== $oldLookup[$name][$i]->Seq_in_index)
&& ((string) $newLookup[$name][$i]['Collation'] ==
$oldLookup[$name][$i]->Collation)
&& ((string) $newLookup[$name][$i]['Index_type']
== $oldLookup[$name][$i]->Index_type));
/*
Debug.
echo '<pre>';
echo '<br />Non_unique: '.
((string) $newLookup[$name][$i]['Non_unique'] ==
$oldLookup[$name][$i]->Non_unique ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Non_unique'].' vs
'.$oldLookup[$name][$i]->Non_unique;
echo '<br />Column_name: '.
((string) $newLookup[$name][$i]['Column_name'] ==
$oldLookup[$name][$i]->Column_name ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Column_name'].' vs
'.$oldLookup[$name][$i]->Column_name;
echo '<br />Seq_in_index: '.
((string) $newLookup[$name][$i]['Seq_in_index'] ==
$oldLookup[$name][$i]->Seq_in_index ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Seq_in_index'].' vs
'.$oldLookup[$name][$i]->Seq_in_index;
echo '<br />Collation: '.
((string) $newLookup[$name][$i]['Collation'] ==
$oldLookup[$name][$i]->Collation ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Collation'].' vs
'.$oldLookup[$name][$i]->Collation;
echo '<br />Index_type: '.
((string) $newLookup[$name][$i]['Index_type'] ==
$oldLookup[$name][$i]->Index_type ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Index_type'].' vs
'.$oldLookup[$name][$i]->Index_type;
echo '<br />Same = '.($same ? 'true' :
'false');
echo '</pre>';
*/
if (!$same)
{
// Break out of the loop. No need to check further.
break;
}
}
}
else
{
// Count is different, just drop and add.
$same = false;
}
if (!$same)
{
$alters[] = $this->getDropKeySql($table, $name);
$alters[] = $this->getAddKeySql($table, $keys);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldLookup[$name]);
}
else
{
// This is a new key.
$alters[] = $this->getAddKeySql($table, $keys);
}
}
// Any keys left are orphans.
foreach ($oldLookup as $name => $keys)
{
if (strtoupper($name) == 'PRIMARY')
{
$alters[] = $this->getDropPrimaryKeySql($table);
}
else
{
$alters[] = $this->getDropKeySql($table, $name);
}
}
return $alters;
}
/**
* Get the syntax to alter a column.
*
* @param string $table The name of the database table to
alter.
* @param SimpleXMLElement $field The XML definition for the field.
*
* @return string
*
* @since 1.7.0
*/
protected function getChangeColumnSql($table, SimpleXMLElement $field)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' CHANGE COLUMN ' . $this->db->quoteName((string)
$field['Field']) . ' '
. $this->getColumnSql($field);
}
/**
* Get the SQL syntax for a single column that would be included in a
table create or alter statement.
*
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 1.7.0
*/
protected function getColumnSql(SimpleXMLElement $field)
{
// TODO Incorporate into parent class and use $this.
$blobs = array('text', 'smalltext',
'mediumtext', 'largetext');
$fName = (string) $field['Field'];
$fType = (string) $field['Type'];
$fNull = (string) $field['Null'];
$fDefault = isset($field['Default']) ? (string)
$field['Default'] : null;
$fExtra = (string) $field['Extra'];
$query = $this->db->quoteName($fName) . ' ' . $fType;
if ($fNull == 'NO')
{
if (in_array($fType, $blobs) || $fDefault === null)
{
$query .= ' NOT NULL';
}
else
{
// TODO Don't quote numeric values.
$query .= ' NOT NULL DEFAULT ' .
$this->db->quote($fDefault);
}
}
else
{
if ($fDefault === null)
{
$query .= ' DEFAULT NULL';
}
else
{
// TODO Don't quote numeric values.
$query .= ' DEFAULT ' . $this->db->quote($fDefault);
}
}
if ($fExtra)
{
$query .= ' ' . strtoupper($fExtra);
}
return $query;
}
/**
* Get the SQL syntax to drop a key.
*
* @param string $table The table name.
* @param string $name The name of the key to drop.
*
* @return string
*
* @since 1.7.0
*/
protected function getDropKeySql($table, $name)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP KEY ' . $this->db->quoteName($name);
}
/**
* Get the SQL syntax to drop a key.
*
* @param string $table The table name.
*
* @return string
*
* @since 1.7.0
*/
protected function getDropPrimaryKeySql($table)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP PRIMARY KEY';
}
/**
* Get the details list of keys for a table.
*
* @param array $keys An array of objects that comprise the keys for
the table.
*
* @return array The lookup array. array({key name} => array(object,
...))
*
* @since 1.7.0
* @throws Exception
*/
protected function getKeyLookup($keys)
{
// First pass, create a lookup of the keys.
$lookup = array();
foreach ($keys as $key)
{
if ($key instanceof SimpleXMLElement)
{
$kName = (string) $key['Key_name'];
}
else
{
$kName = $key->Key_name;
}
if (empty($lookup[$kName]))
{
$lookup[$kName] = array();
}
$lookup[$kName][] = $key;
}
return $lookup;
}
/**
* Get the SQL syntax for a key.
*
* @param array $columns An array of SimpleXMLElement objects
comprising the key.
*
* @return string
*
* @since 1.7.0
*/
protected function getKeySql($columns)
{
// TODO Error checking on array and element types.
$kNonUnique = (string) $columns[0]['Non_unique'];
$kName = (string) $columns[0]['Key_name'];
$kColumn = (string) $columns[0]['Column_name'];
$prefix = '';
if ($kName == 'PRIMARY')
{
$prefix = 'PRIMARY ';
}
elseif ($kNonUnique == 0)
{
$prefix = 'UNIQUE ';
}
$nColumns = count($columns);
$kColumns = array();
if ($nColumns == 1)
{
$kColumns[] = $this->db->quoteName($kColumn);
}
else
{
foreach ($columns as $column)
{
$kColumns[] = (string) $column['Column_name'];
}
}
$query = $prefix . 'KEY ' . ($kName != 'PRIMARY' ?
$this->db->quoteName($kName) : '') . ' (' .
implode(',', $kColumns) . ')';
return $query;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL import driver for the PDO based MySQL database driver.
*
* @package Joomla.Platform
* @subpackage Database
* @since 3.4
*/
class JDatabaseImporterPdomysql extends JDatabaseImporter
{
/**
* Get the SQL syntax to add a column.
*
* @param string $table The table name.
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 3.4
*/
protected function getAddColumnSql($table, SimpleXMLElement $field)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' ADD COLUMN ' . $this->getColumnSql($field);
return $sql;
}
/**
* Get the SQL syntax to add a key.
*
* @param string $table The table name.
* @param array $keys An array of the fields pertaining to this key.
*
* @return string
*
* @since 3.4
*/
protected function getAddKeySql($table, $keys)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' ADD ' . $this->getKeySql($keys);
return $sql;
}
/**
* Get alters for table if there is a difference.
*
* @param SimpleXMLElement $structure The XML structure of the table.
*
* @return array
*
* @since 3.4
*/
protected function getAlterTableSql(SimpleXMLElement $structure)
{
// Initialise variables.
$table = $this->getRealTableName($structure['name']);
$oldFields = $this->db->getTableColumns($table);
$oldKeys = $this->db->getTableKeys($table);
$alters = array();
// Get the fields and keys from the XML that we are aiming for.
$newFields = $structure->xpath('field');
$newKeys = $structure->xpath('key');
// Loop through each field in the new structure.
foreach ($newFields as $field)
{
$fName = (string) $field['Field'];
if (isset($oldFields[$fName]))
{
// The field exists, check it's the same.
$column = $oldFields[$fName];
// Test whether there is a change.
$change = ((string) $field['Type'] != $column->Type) ||
((string) $field['Null'] != $column->Null)
|| ((string) $field['Default'] != $column->Default) ||
((string) $field['Extra'] != $column->Extra);
if ($change)
{
$alters[] = $this->getChangeColumnSql($table, $field);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldFields[$fName]);
}
else
{
// The field is new.
$alters[] = $this->getAddColumnSql($table, $field);
}
}
// Any columns left are orphans
foreach ($oldFields as $name => $column)
{
// Delete the column.
$alters[] = $this->getDropColumnSql($table, $name);
}
// Get the lookups for the old and new keys.
$oldLookup = $this->getKeyLookup($oldKeys);
$newLookup = $this->getKeyLookup($newKeys);
// Loop through each key in the new structure.
foreach ($newLookup as $name => $keys)
{
// Check if there are keys on this field in the existing table.
if (isset($oldLookup[$name]))
{
$same = true;
$newCount = count($newLookup[$name]);
$oldCount = count($oldLookup[$name]);
// There is a key on this field in the old and new tables. Are they the
same?
if ($newCount == $oldCount)
{
// Need to loop through each key and do a fine grained check.
for ($i = 0; $i < $newCount; $i++)
{
$same = (((string) $newLookup[$name][$i]['Non_unique'] ==
$oldLookup[$name][$i]->Non_unique)
&& ((string) $newLookup[$name][$i]['Column_name']
== $oldLookup[$name][$i]->Column_name)
&& ((string) $newLookup[$name][$i]['Seq_in_index']
== $oldLookup[$name][$i]->Seq_in_index)
&& ((string) $newLookup[$name][$i]['Collation'] ==
$oldLookup[$name][$i]->Collation)
&& ((string) $newLookup[$name][$i]['Index_type']
== $oldLookup[$name][$i]->Index_type));
/*
Debug.
echo '<pre>';
echo '<br />Non_unique: '.
((string) $newLookup[$name][$i]['Non_unique'] ==
$oldLookup[$name][$i]->Non_unique ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Non_unique'].' vs
'.$oldLookup[$name][$i]->Non_unique;
echo '<br />Column_name: '.
((string) $newLookup[$name][$i]['Column_name'] ==
$oldLookup[$name][$i]->Column_name ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Column_name'].' vs
'.$oldLookup[$name][$i]->Column_name;
echo '<br />Seq_in_index: '.
((string) $newLookup[$name][$i]['Seq_in_index'] ==
$oldLookup[$name][$i]->Seq_in_index ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Seq_in_index'].' vs
'.$oldLookup[$name][$i]->Seq_in_index;
echo '<br />Collation: '.
((string) $newLookup[$name][$i]['Collation'] ==
$oldLookup[$name][$i]->Collation ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Collation'].' vs
'.$oldLookup[$name][$i]->Collation;
echo '<br />Index_type: '.
((string) $newLookup[$name][$i]['Index_type'] ==
$oldLookup[$name][$i]->Index_type ? 'Pass' :
'Fail').' '.
(string) $newLookup[$name][$i]['Index_type'].' vs
'.$oldLookup[$name][$i]->Index_type;
echo '<br />Same = '.($same ? 'true' :
'false');
echo '</pre>';
*/
if (!$same)
{
// Break out of the loop. No need to check further.
break;
}
}
}
else
{
// Count is different, just drop and add.
$same = false;
}
if (!$same)
{
$alters[] = $this->getDropKeySql($table, $name);
$alters[] = $this->getAddKeySql($table, $keys);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldLookup[$name]);
}
else
{
// This is a new key.
$alters[] = $this->getAddKeySql($table, $keys);
}
}
// Any keys left are orphans.
foreach ($oldLookup as $name => $keys)
{
if (strtoupper($name) == 'PRIMARY')
{
$alters[] = $this->getDropPrimaryKeySql($table);
}
else
{
$alters[] = $this->getDropKeySql($table, $name);
}
}
return $alters;
}
/**
* Get the syntax to alter a column.
*
* @param string $table The name of the database table to
alter.
* @param SimpleXMLElement $field The XML definition for the field.
*
* @return string
*
* @since 3.4
*/
protected function getChangeColumnSql($table, SimpleXMLElement $field)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' CHANGE COLUMN ' . $this->db->quoteName((string)
$field['Field']) . ' '
. $this->getColumnSql($field);
return $sql;
}
/**
* Get the SQL syntax for a single column that would be included in a
table create or alter statement.
*
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 3.4
*/
protected function getColumnSql(SimpleXMLElement $field)
{
// Initialise variables.
// TODO Incorporate into parent class and use $this.
$blobs = array('text', 'smalltext',
'mediumtext', 'largetext');
$fName = (string) $field['Field'];
$fType = (string) $field['Type'];
$fNull = (string) $field['Null'];
$fDefault = isset($field['Default']) ? (string)
$field['Default'] : null;
$fExtra = (string) $field['Extra'];
$sql = $this->db->quoteName($fName) . ' ' . $fType;
if ($fNull == 'NO')
{
if (in_array($fType, $blobs) || $fDefault === null)
{
$sql .= ' NOT NULL';
}
else
{
// TODO Don't quote numeric values.
$sql .= ' NOT NULL DEFAULT ' .
$this->db->quote($fDefault);
}
}
else
{
if ($fDefault === null)
{
$sql .= ' DEFAULT NULL';
}
else
{
// TODO Don't quote numeric values.
$sql .= ' DEFAULT ' . $this->db->quote($fDefault);
}
}
if ($fExtra)
{
$sql .= ' ' . strtoupper($fExtra);
}
return $sql;
}
/**
* Get the SQL syntax to drop a column.
*
* @param string $table The table name.
* @param string $name The name of the field to drop.
*
* @return string
*
* @since 3.4
*/
protected function getDropColumnSql($table, $name)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP COLUMN ' . $this->db->quoteName($name);
return $sql;
}
/**
* Get the SQL syntax to drop a key.
*
* @param string $table The table name.
* @param string $name The name of the key to drop.
*
* @return string
*
* @since 3.4
*/
protected function getDropKeySql($table, $name)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP KEY ' . $this->db->quoteName($name);
return $sql;
}
/**
* Get the SQL syntax to drop a key.
*
* @param string $table The table name.
*
* @return string
*
* @since 3.4
*/
protected function getDropPrimaryKeySql($table)
{
$sql = 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP PRIMARY KEY';
return $sql;
}
/**
* Get the details list of keys for a table.
*
* @param array $keys An array of objects that comprise the keys for
the table.
*
* @return array The lookup array. array({key name} => array(object,
...))
*
* @since 3.4
* @throws Exception
*/
protected function getKeyLookup($keys)
{
// First pass, create a lookup of the keys.
$lookup = array();
foreach ($keys as $key)
{
if ($key instanceof SimpleXMLElement)
{
$kName = (string) $key['Key_name'];
}
else
{
$kName = $key->Key_name;
}
if (empty($lookup[$kName]))
{
$lookup[$kName] = array();
}
$lookup[$kName][] = $key;
}
return $lookup;
}
/**
* Get the SQL syntax for a key.
*
* @param array $columns An array of SimpleXMLElement objects
comprising the key.
*
* @return string
*
* @since 3.4
*/
protected function getKeySql($columns)
{
// TODO Error checking on array and element types.
$kNonUnique = (string) $columns[0]['Non_unique'];
$kName = (string) $columns[0]['Key_name'];
$kColumn = (string) $columns[0]['Column_name'];
$prefix = '';
if ($kName == 'PRIMARY')
{
$prefix = 'PRIMARY ';
}
elseif ($kNonUnique == 0)
{
$prefix = 'UNIQUE ';
}
$nColumns = count($columns);
$kColumns = array();
if ($nColumns == 1)
{
$kColumns[] = $this->db->quoteName($kColumn);
}
else
{
foreach ($columns as $column)
{
$kColumns[] = (string) $column['Column_name'];
}
}
$sql = $prefix . 'KEY ' . ($kName != 'PRIMARY' ?
$this->db->quoteName($kName) : '') . ' (' .
implode(',', $kColumns) . ')';
return $sql;
}
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporterPdomysql Method supports chaining.
*
* @since 3.4
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPdomysql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PDO PostgreSQL Database Importer.
*
* @since 3.9.0
*/
class JDatabaseImporterPgsql extends JDatabaseImporterPostgresql
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporterPgsql Method supports chaining.
*
* @since 3.9.0
* @throws \Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPgsql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL import driver.
*
* @since 3.0.0
* @deprecated 4.0 Use PDO PostgreSQL instead
*/
class JDatabaseImporterPostgresql extends JDatabaseImporter
{
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporterPostgresql Method supports chaining.
*
* @since 3.0.0
* @throws Exception if an error is encountered.
*/
public function check()
{
// Check if the db connector has been set.
if (!($this->db instanceof JDatabaseDriverPostgresql))
{
throw new
Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
}
// Check if the tables have been specified.
if (empty($this->from))
{
throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
}
return $this;
}
/**
* Get the SQL syntax to add a column.
*
* @param string $table The table name.
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 3.0.0
*/
protected function getAddColumnSql($table, SimpleXMLElement $field)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' ADD COLUMN ' . $this->getColumnSql($field);
}
/**
* Get the SQL syntax to add an index.
*
* @param SimpleXMLElement $field The XML index definition.
*
* @return string
*
* @since 3.0.0
*/
protected function getAddIndexSql(SimpleXMLElement $field)
{
return (string) $field['Query'];
}
/**
* Get alters for table if there is a difference.
*
* @param SimpleXMLElement $structure The XML structure of the table.
*
* @return array
*
* @since 3.0.0
*/
protected function getAlterTableSql(SimpleXMLElement $structure)
{
$table = $this->getRealTableName($structure['name']);
$oldFields = $this->db->getTableColumns($table);
$oldKeys = $this->db->getTableKeys($table);
$oldSequence = $this->db->getTableSequences($table);
$alters = array();
// Get the fields and keys from the XML that we are aiming for.
$newFields = $structure->xpath('field');
$newKeys = $structure->xpath('key');
$newSequence = $structure->xpath('sequence');
/* Sequence section */
$oldSeq = $this->getSeqLookup($oldSequence);
$newSequenceLook = $this->getSeqLookup($newSequence);
foreach ($newSequenceLook as $kSeqName => $vSeq)
{
if (isset($oldSeq[$kSeqName]))
{
// The field exists, check it's the same.
$column = $oldSeq[$kSeqName][0];
/* For older database version that doesn't support these fields
use default values */
if (version_compare($this->db->getVersion(), '9.1.0')
< 0)
{
$column->Min_Value = '1';
$column->Max_Value = '9223372036854775807';
$column->Increment = '1';
$column->Cycle_option = 'NO';
$column->Start_Value = '1';
}
// Test whether there is a change.
$change = ((string) $vSeq[0]['Type'] != $column->Type) ||
((string) $vSeq[0]['Start_Value'] != $column->Start_Value)
|| ((string) $vSeq[0]['Min_Value'] != $column->Min_Value)
|| ((string) $vSeq[0]['Max_Value'] != $column->Max_Value)
|| ((string) $vSeq[0]['Increment'] != $column->Increment)
|| ((string) $vSeq[0]['Cycle_option'] !=
$column->Cycle_option)
|| ((string) $vSeq[0]['Table'] != $column->Table) ||
((string) $vSeq[0]['Column'] != $column->Column)
|| ((string) $vSeq[0]['Schema'] != $column->Schema) ||
((string) $vSeq[0]['Name'] != $column->Name);
if ($change)
{
$alters[] = $this->getChangeSequenceSql($kSeqName, $vSeq);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldSeq[$kSeqName]);
}
else
{
// The sequence is new
$alters[] =
$this->getAddSequenceSql($newSequenceLook[$kSeqName][0]);
}
}
// Any sequences left are orphans
foreach ($oldSeq as $name => $column)
{
// Delete the sequence.
$alters[] = $this->getDropSequenceSql($name);
}
/* Field section */
// Loop through each field in the new structure.
foreach ($newFields as $field)
{
$fName = (string) $field['Field'];
if (isset($oldFields[$fName]))
{
// The field exists, check it's the same.
$column = $oldFields[$fName];
// Test whether there is a change.
$change = ((string) $field['Type'] != $column->Type) ||
((string) $field['Null'] != $column->Null)
|| ((string) $field['Default'] != $column->Default);
if ($change)
{
$alters[] = $this->getChangeColumnSql($table, $field);
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldFields[$fName]);
}
else
{
// The field is new.
$alters[] = $this->getAddColumnSql($table, $field);
}
}
// Any columns left are orphans
foreach ($oldFields as $name => $column)
{
// Delete the column.
$alters[] = $this->getDropColumnSql($table, $name);
}
/* Index section */
// Get the lookups for the old and new keys
$oldLookup = $this->getIdxLookup($oldKeys);
$newLookup = $this->getIdxLookup($newKeys);
// Loop through each key in the new structure.
foreach ($newLookup as $name => $keys)
{
// Check if there are keys on this field in the existing table.
if (isset($oldLookup[$name]))
{
$same = true;
$newCount = count($newLookup[$name]);
$oldCount = count($oldLookup[$name]);
// There is a key on this field in the old and new tables. Are they the
same?
if ($newCount == $oldCount)
{
for ($i = 0; $i < $newCount; $i++)
{
// Check only query field -> different query means different index
$same = ((string) $newLookup[$name][$i]['Query'] ==
$oldLookup[$name][$i]->Query);
if (!$same)
{
// Break out of the loop. No need to check further.
break;
}
}
}
else
{
// Count is different, just drop and add.
$same = false;
}
if (!$same)
{
$alters[] = $this->getDropIndexSql($name);
$alters[] = (string) $newLookup[$name][0]['Query'];
}
// Unset this field so that what we have left are fields that need to
be removed.
unset($oldLookup[$name]);
}
else
{
// This is a new key.
$alters[] = (string) $newLookup[$name][0]['Query'];
}
}
// Any keys left are orphans.
foreach ($oldLookup as $name => $keys)
{
if ($oldLookup[$name][0]->is_primary == 'TRUE')
{
$alters[] = $this->getDropPrimaryKeySql($table,
$oldLookup[$name][0]->Index);
}
else
{
$alters[] = $this->getDropIndexSql($name);
}
}
return $alters;
}
/**
* Get the SQL syntax to drop a sequence.
*
* @param string $name The name of the sequence to drop.
*
* @return string
*
* @since 3.0.0
*/
protected function getDropSequenceSql($name)
{
return 'DROP SEQUENCE ' . $this->db->quoteName($name);
}
/**
* Get the syntax to add a sequence.
*
* @param SimpleXMLElement $field The XML definition for the sequence.
*
* @return string
*
* @since 3.0.0
*/
protected function getAddSequenceSql($field)
{
/* For older database version that doesn't support these fields use
default values */
if (version_compare($this->db->getVersion(), '9.1.0')
< 0)
{
$field['Min_Value'] = '1';
$field['Max_Value'] = '9223372036854775807';
$field['Increment'] = '1';
$field['Cycle_option'] = 'NO';
$field['Start_Value'] = '1';
}
return 'CREATE SEQUENCE ' . (string) $field['Name'] .
' INCREMENT BY ' . (string) $field['Increment'] .
' MINVALUE ' . $field['Min_Value'] .
' MAXVALUE ' . (string) $field['Max_Value'] . '
START ' . (string) $field['Start_Value'] .
(((string) $field['Cycle_option'] == 'NO') ? '
NO' : '') . ' CYCLE' .
' OWNED BY ' . $this->db->quoteName((string)
$field['Schema'] . '.' . (string)
$field['Table'] . '.' . (string)
$field['Column']);
}
/**
* Get the syntax to alter a sequence.
*
* @param SimpleXMLElement $field The XML definition for the sequence.
*
* @return string
*
* @since 3.0.0
*/
protected function getChangeSequenceSql($field)
{
/* For older database version that doesn't support these fields use
default values */
if (version_compare($this->db->getVersion(), '9.1.0')
< 0)
{
$field['Min_Value'] = '1';
$field['Max_Value'] = '9223372036854775807';
$field['Increment'] = '1';
$field['Cycle_option'] = 'NO';
$field['Start_Value'] = '1';
}
return 'ALTER SEQUENCE ' . (string) $field['Name'] .
' INCREMENT BY ' . (string) $field['Increment'] .
' MINVALUE ' . (string) $field['Min_Value'] .
' MAXVALUE ' . (string) $field['Max_Value'] . '
START ' . (string) $field['Start_Value'] .
' OWNED BY ' . $this->db->quoteName((string)
$field['Schema'] . '.' . (string)
$field['Table'] . '.' . (string)
$field['Column']);
}
/**
* Get the syntax to alter a column.
*
* @param string $table The name of the database table to
alter.
* @param SimpleXMLElement $field The XML definition for the field.
*
* @return string
*
* @since 3.0.0
*/
protected function getChangeColumnSql($table, SimpleXMLElement $field)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' ALTER COLUMN ' . $this->db->quoteName((string)
$field['Field']) . ' '
. $this->getAlterColumnSql($table, $field);
}
/**
* Get the SQL syntax for a single column that would be included in a
table create statement.
*
* @param string $table The name of the database table to
alter.
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 3.0.0
*/
protected function getAlterColumnSql($table, $field)
{
// TODO Incorporate into parent class and use $this.
$blobs = array('text', 'smalltext',
'mediumtext', 'largetext');
$fName = (string) $field['Field'];
$fType = (string) $field['Type'];
$fNull = (string) $field['Null'];
$fDefault = (isset($field['Default']) &&
$field['Default'] != 'NULL') ?
preg_match('/^[0-9]$/', $field['Default']) ?
$field['Default'] : $this->db->quote((string)
$field['Default'])
: null;
$query = ' TYPE ' . $fType;
if ($fNull == 'NO')
{
if (in_array($fType, $blobs) || $fDefault === null)
{
$query .= ",\nALTER COLUMN " .
$this->db->quoteName($fName) . ' SET NOT NULL' .
",\nALTER COLUMN " . $this->db->quoteName($fName) .
' DROP DEFAULT';
}
else
{
$query .= ",\nALTER COLUMN " .
$this->db->quoteName($fName) . ' SET NOT NULL' .
",\nALTER COLUMN " . $this->db->quoteName($fName) .
' SET DEFAULT ' . $fDefault;
}
}
else
{
if ($fDefault !== null)
{
$query .= ",\nALTER COLUMN " .
$this->db->quoteName($fName) . ' DROP NOT NULL' .
",\nALTER COLUMN " . $this->db->quoteName($fName) .
' SET DEFAULT ' . $fDefault;
}
}
/* sequence was created in other function, here is associated a default
value but not yet owner */
if (strpos($fDefault, 'nextval') !== false)
{
$query .= ";\nALTER SEQUENCE " .
$this->db->quoteName($table . '_' . $fName .
'_seq') . ' OWNED BY ' .
$this->db->quoteName($table . '.' . $fName);
}
return $query;
}
/**
* Get the SQL syntax for a single column that would be included in a
table create statement.
*
* @param SimpleXMLElement $field The XML field definition.
*
* @return string
*
* @since 3.0.0
*/
protected function getColumnSql(SimpleXMLElement $field)
{
// TODO Incorporate into parent class and use $this.
$blobs = array('text', 'smalltext',
'mediumtext', 'largetext');
$fName = (string) $field['Field'];
$fType = (string) $field['Type'];
$fNull = (string) $field['Null'];
$fDefault = (isset($field['Default']) &&
$field['Default'] != 'NULL') ?
preg_match('/^[0-9]$/', $field['Default']) ?
$field['Default'] : $this->db->quote((string)
$field['Default'])
: null;
/* nextval() as default value means that type field is serial */
if (strpos($fDefault, 'nextval') !== false)
{
$query = $this->db->quoteName($fName) . ' SERIAL';
}
else
{
$query = $this->db->quoteName($fName) . ' ' . $fType;
if ($fNull == 'NO')
{
if (in_array($fType, $blobs) || $fDefault === null)
{
$query .= ' NOT NULL';
}
else
{
$query .= ' NOT NULL DEFAULT ' . $fDefault;
}
}
else
{
if ($fDefault !== null)
{
$query .= ' DEFAULT ' . $fDefault;
}
}
}
return $query;
}
/**
* Get the SQL syntax to drop an index.
*
* @param string $name The name of the key to drop.
*
* @return string
*
* @since 3.0.0
*/
protected function getDropIndexSql($name)
{
return 'DROP INDEX ' . $this->db->quoteName($name);
}
/**
* Get the SQL syntax to drop a key.
*
* @param string $table The table name.
* @param string $name The constraint name.
*
* @return string
*
* @since 3.0.0
*/
protected function getDropPrimaryKeySql($table, $name)
{
return 'ALTER TABLE ONLY ' . $this->db->quoteName($table)
. ' DROP CONSTRAINT ' . $this->db->quoteName($name);
}
/**
* Get the details list of keys for a table.
*
* @param array $keys An array of objects that comprise the keys for
the table.
*
* @return array The lookup array. array({key name} => array(object,
...))
*
* @since 3.0.0
* @throws Exception
*/
protected function getIdxLookup($keys)
{
// First pass, create a lookup of the keys.
$lookup = array();
foreach ($keys as $key)
{
if ($key instanceof SimpleXMLElement)
{
$kName = (string) $key['Index'];
}
else
{
$kName = $key->Index;
}
if (empty($lookup[$kName]))
{
$lookup[$kName] = array();
}
$lookup[$kName][] = $key;
}
return $lookup;
}
/**
* Get the details list of sequences for a table.
*
* @param array $sequences An array of objects that comprise the
sequences for the table.
*
* @return array The lookup array. array({key name} => array(object,
...))
*
* @since 3.0.0
* @throws Exception
*/
protected function getSeqLookup($sequences)
{
// First pass, create a lookup of the keys.
$lookup = array();
foreach ($sequences as $seq)
{
if ($seq instanceof SimpleXMLElement)
{
$sName = (string) $seq['Name'];
}
else
{
$sName = $seq->Name;
}
if (empty($lookup[$sName]))
{
$lookup[$sName] = array();
}
$lookup[$sName][] = $seq;
}
return $lookup;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Importer Class
*
* @since 3.0.0
*/
abstract class JDatabaseImporter
{
/**
* @var array An array of cached data.
* @since 3.2.0
*/
protected $cache = array();
/**
* The database connector to use for exporting structure and/or data.
*
* @var JDatabaseDriver
* @since 3.2.0
*/
protected $db = null;
/**
* The input source.
*
* @var mixed
* @since 3.2.0
*/
protected $from = array();
/**
* The type of input format (XML).
*
* @var string
* @since 3.2.0
*/
protected $asFormat = 'xml';
/**
* An array of options for the exporter.
*
* @var object
* @since 3.2.0
*/
protected $options = null;
/**
* Constructor.
*
* Sets up the default options for the exporter.
*
* @since 3.2.0
*/
public function __construct()
{
$this->options = new stdClass;
$this->cache = array('columns' => array(),
'keys' => array());
// Set up the class defaults:
// Import with only structure
$this->withStructure();
// Export as XML.
$this->asXml();
// Default destination is a string using $output = (string) $exporter;
}
/**
* Set the output option for the exporter to XML format.
*
* @return JDatabaseImporter Method supports chaining.
*
* @since 3.2.0
*/
public function asXml()
{
$this->asFormat = 'xml';
return $this;
}
/**
* Checks if all data and options are in order prior to exporting.
*
* @return JDatabaseImporter Method supports chaining.
*
* @since 3.2.0
* @throws Exception if an error is encountered.
*/
abstract public function check();
/**
* Specifies the data source to import.
*
* @param mixed $from The data source to import.
*
* @return JDatabaseImporter Method supports chaining.
*
* @since 3.2.0
*/
public function from($from)
{
$this->from = $from;
return $this;
}
/**
* Get the SQL syntax to drop a column.
*
* @param string $table The table name.
* @param string $name The name of the field to drop.
*
* @return string
*
* @since 3.2.0
*/
protected function getDropColumnSql($table, $name)
{
return 'ALTER TABLE ' . $this->db->quoteName($table) .
' DROP COLUMN ' . $this->db->quoteName($name);
}
/**
* Get the real name of the table, converting the prefix wildcard string
if present.
*
* @param string $table The name of the table.
*
* @return string The real name of the table.
*
* @since 3.2.0
*/
protected function getRealTableName($table)
{
$prefix = $this->db->getPrefix();
// Replace the magic prefix if found.
$table = preg_replace('|^#__|', $prefix, $table);
return $table;
}
/**
* Merges the incoming structure definition with the existing structure.
*
* @return void
*
* @note Currently only supports XML format.
* @since 3.2.0
* @throws RuntimeException on error.
*/
public function mergeStructure()
{
$prefix = $this->db->getPrefix();
$tables = $this->db->getTableList();
if ($this->from instanceof SimpleXMLElement)
{
$xml = $this->from;
}
else
{
$xml = new SimpleXMLElement($this->from);
}
// Get all the table definitions.
$xmlTables = $xml->xpath('database/table_structure');
foreach ($xmlTables as $table)
{
// Convert the magic prefix into the real table name.
$tableName = (string) $table['name'];
$tableName = preg_replace('|^#__|', $prefix, $tableName);
if (in_array($tableName, $tables))
{
// The table already exists. Now check if there is any difference.
if ($queries = $this->getAlterTableSql($table))
{
// Run the queries to upgrade the data structure.
foreach ($queries as $query)
{
$this->db->setQuery((string) $query);
$this->db->execute();
}
}
}
else
{
// This is a new table.
$sql = $this->xmlToCreate($table);
$this->db->setQuery((string) $sql);
$this->db->execute();
}
}
}
/**
* Sets the database connector to use for exporting structure and/or data.
*
* @param JDatabaseDriver $db The database connector.
*
* @return JDatabaseImporter Method supports chaining.
*
* @since 3.2.0
*/
public function setDbo(JDatabaseDriver $db)
{
$this->db = $db;
return $this;
}
/**
* Sets an internal option to merge the structure based on the input data.
*
* @param boolean $setting True to export the structure, false to not.
*
* @return JDatabaseImporter Method supports chaining.
*
* @since 3.2.0
*/
public function withStructure($setting = true)
{
$this->options->withStructure = (boolean) $setting;
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Interface
*
* @since 1.7.0
*/
interface JDatabaseInterface
{
/**
* Test to see if the connector is available.
*
* @return boolean True on success, false otherwise.
*
* @since 1.7.0
*/
public static function isSupported();
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL database iterator.
*
* @link http://dev.mysql.com/doc/
* @since 3.0.0
* @deprecated 4.0 Use MySQLi or PDO MySQL instead
*/
class JDatabaseIteratorMysql extends JDatabaseIterator
{
/**
* Get the number of rows in the result set for the executed SQL given by
the cursor.
*
* @return integer The number of rows in the result set.
*
* @since 3.0.0
* @see Countable::count()
*/
public function count()
{
return mysql_num_rows($this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject()
{
return mysql_fetch_object($this->cursor, $this->class);
}
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult()
{
mysql_free_result($this->cursor);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQLi database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorMysqli extends JDatabaseIterator
{
/**
* Get the number of rows in the result set for the executed SQL given by
the cursor.
*
* @return integer The number of rows in the result set.
*
* @since 3.0.0
* @see Countable::count()
*/
public function count()
{
return mysqli_num_rows($this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject()
{
return mysqli_fetch_object($this->cursor, $this->class);
}
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult()
{
mysqli_free_result($this->cursor);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Oracle database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorOracle extends JDatabaseIteratorPdo
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PDO database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorPdo extends JDatabaseIterator
{
/**
* Get the number of rows in the result set for the executed SQL given by
the cursor.
*
* @return integer The number of rows in the result set.
*
* @since 3.0.0
* @see Countable::count()
*/
public function count()
{
if (!empty($this->cursor) && $this->cursor instanceof
PDOStatement)
{
return $this->cursor->rowCount();
}
else
{
return 0;
}
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject()
{
if (!empty($this->cursor) && $this->cursor instanceof
PDOStatement)
{
return $this->cursor->fetchObject($this->class);
}
else
{
return false;
}
}
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult()
{
if (!empty($this->cursor) && $this->cursor instanceof
PDOStatement)
{
$this->cursor->closeCursor();
}
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* MySQL database iterator for the PDO based MySQL database driver.
*
* @package Joomla.Platform
* @subpackage Database
* @link https://dev.mysql.com/doc/
* @since 3.4
*/
class JDatabaseIteratorPdomysql extends JDatabaseIteratorPdo
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL database iterator for the PDO based PostgreSQL database
driver.
*
* @since 3.9.0
*/
class JDatabaseIteratorPgsql extends JDatabaseIteratorPdo
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PostgreSQL database iterator.
*
* @since 3.2.0
* @deprecated 4.0 Use PDO PostgreSQL instead
*/
class JDatabaseIteratorPostgresql extends JDatabaseIterator
{
/**
* Get the number of rows in the result set for the executed SQL given by
the cursor.
*
* @return integer The number of rows in the result set.
*
* @since 3.2.0
* @see Countable::count()
*/
public function count()
{
return pg_num_rows($this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.2.0
*/
protected function fetchObject()
{
return pg_fetch_object($this->cursor, null, $this->class);
}
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.2.0
*/
protected function freeResult()
{
pg_free_result($this->cursor);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQL azure database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorSqlazure extends JDatabaseIteratorSqlsrv
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQLite database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorSqlite extends JDatabaseIteratorPdo
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* SQL server database iterator.
*
* @since 3.0.0
*/
class JDatabaseIteratorSqlsrv extends JDatabaseIterator
{
/**
* Get the number of rows in the result set for the executed SQL given by
the cursor.
*
* @return integer The number of rows in the result set.
*
* @since 3.0.0
* @see Countable::count()
*/
public function count()
{
return sqlsrv_num_rows($this->cursor);
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
protected function fetchObject()
{
return sqlsrv_fetch_object($this->cursor, $this->class);
}
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.0.0
*/
protected function freeResult()
{
sqlsrv_free_stmt($this->cursor);
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Platform Database Driver Class
*
* @since 3.0.0
*/
abstract class JDatabaseIterator implements Countable, Iterator
{
/**
* The database cursor.
*
* @var mixed
* @since 3.0.0
*/
protected $cursor;
/**
* The class of object to create.
*
* @var string
* @since 3.0.0
*/
protected $class;
/**
* The name of the column to use for the key of the database record.
*
* @var mixed
* @since 3.0.0
*/
private $_column;
/**
* The current database record.
*
* @var mixed
* @since 3.0.0
*/
private $_current;
/**
* A numeric or string key for the current database record.
*
* @var int|string
* @since 3.0.0
*/
private $_key;
/**
* The number of fetched records.
*
* @var integer
* @since 3.0.0
*/
private $_fetched = 0;
/**
* Database iterator constructor.
*
* @param mixed $cursor The database cursor.
* @param string $column An option column to use as the iterator key.
* @param string $class The class of object that is returned.
*
* @throws InvalidArgumentException
*/
public function __construct($cursor, $column = null, $class =
'stdClass')
{
if (!class_exists($class))
{
throw new InvalidArgumentException(sprintf('new %s(*%s*,
cursor)', get_class($this), gettype($class)));
}
$this->cursor = $cursor;
$this->class = $class;
$this->_column = $column;
$this->_fetched = 0;
$this->next();
}
/**
* Database iterator destructor.
*
* @since 3.0.0
*/
public function __destruct()
{
if ($this->cursor)
{
$this->freeResult($this->cursor);
}
}
/**
* The current element in the iterator.
*
* @return object
*
* @see Iterator::current()
* @since 3.0.0
*/
public function current()
{
return $this->_current;
}
/**
* The key of the current element in the iterator.
*
* @return int|string
*
* @see Iterator::key()
* @since 3.0.0
*/
public function key()
{
return $this->_key;
}
/**
* Moves forward to the next result from the SQL query.
*
* @return void
*
* @see Iterator::next()
* @since 3.0.0
*/
public function next()
{
// Set the default key as being the number of fetched object
$this->_key = $this->_fetched;
// Try to get an object
$this->_current = $this->fetchObject();
// If an object has been found
if ($this->_current)
{
// Set the key as being the indexed column (if it exists)
if (isset($this->_current->{$this->_column}))
{
$this->_key = $this->_current->{$this->_column};
}
// Update the number of fetched object
$this->_fetched++;
}
}
/**
* Rewinds the iterator.
*
* This iterator cannot be rewound.
*
* @return void
*
* @see Iterator::rewind()
* @since 3.0.0
*/
public function rewind()
{
}
/**
* Checks if the current position of the iterator is valid.
*
* @return boolean
*
* @see Iterator::valid()
* @since 3.0.0
*/
public function valid()
{
return (boolean) $this->_current;
}
/**
* Method to fetch a row from the result set cursor as an object.
*
* @return mixed Either the next row from the result set or false if
there are no more rows.
*
* @since 3.0.0
*/
abstract protected function fetchObject();
/**
* Method to free up the memory used for the result set.
*
* @return void
*
* @since 3.0.0
*/
abstract protected function freeResult();
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Query Element Class.
*
* @property-read string $name The name of the element.
* @property-read array $elements An array of elements.
* @property-read string $glue Glue piece.
*
* @since 1.7.0
*/
class JDatabaseQueryElement
{
/**
* @var string The name of the element.
* @since 1.7.0
*/
protected $name = null;
/**
* @var array An array of elements.
* @since 1.7.0
*/
protected $elements = null;
/**
* @var string Glue piece.
* @since 1.7.0
*/
protected $glue = null;
/**
* Constructor.
*
* @param string $name The name of the element.
* @param mixed $elements String or array.
* @param string $glue The glue for elements.
*
* @since 1.7.0
*/
public function __construct($name, $elements, $glue = ',')
{
$this->elements = array();
$this->name = $name;
$this->glue = $glue;
$this->append($elements);
}
/**
* Magic function to convert the query element to a string.
*
* @return string
*
* @since 1.7.0
*/
public function __toString()
{
if (substr($this->name, -2) == '()')
{
return PHP_EOL . substr($this->name, 0, -2) . '(' .
implode($this->glue, $this->elements) . ')';
}
else
{
return PHP_EOL . $this->name . ' ' .
implode($this->glue, $this->elements);
}
}
/**
* Appends element parts to the internal list.
*
* @param mixed $elements String or array.
*
* @return void
*
* @since 1.7.0
*/
public function append($elements)
{
if (is_array($elements))
{
$this->elements = array_merge($this->elements, $elements);
}
else
{
$this->elements[] = $elements;
}
}
/**
* Gets the elements of this element.
*
* @return array
*
* @since 1.7.0
*/
public function getElements()
{
return $this->elements;
}
/**
* Sets the name of this element.
*
* @param string $name Name of the element.
*
* @return JDatabaseQueryElement Returns this object to allow chaining.
*
* @since 3.6
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Method to provide deep copy support to nested objects and arrays
* when cloning.
*
* @return void
*
* @since 1.7.3
*/
public function __clone()
{
foreach ($this as $k => $v)
{
if (is_object($v) || is_array($v))
{
$this->{$k} = unserialize(serialize($v));
}
}
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Joomla Database Query Limitable Interface.
* Adds bind/unbind methods as well as a getBounded() method
* to retrieve the stored bounded variables on demand prior to
* query execution.
*
* @since 3.0.0
*/
interface JDatabaseQueryLimitable
{
/**
* Method to modify a query already in string format with the needed
* additions to make the query limited to a particular number of
* results, or start at a particular offset. This method is used
* automatically by the __toString() method if it detects that the
* query implements the JDatabaseQueryLimitable interface.
*
* @param string $query The query in string format
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return string
*
* @since 3.0.0
*/
public function processLimit($query, $limit, $offset = 0);
/**
* Sets the offset and limit for the result set, if the database driver
supports it.
*
* Usage:
* $query->setLimit(100, 0); (retrieve 100 rows, starting at first
record)
* $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th
record)
*
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return JDatabaseQuery Returns this object to allow chaining.
*
* @since 3.0.0
*/
public function setLimit($limit = 0, $offset = 0);
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Query Building Class.
*
* @since 1.7.0
* @deprecated 4.0 Use MySQLi or PDO MySQL instead
*/
class JDatabaseQueryMysql extends JDatabaseQueryMysqli
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Query Building Class.
*
* @since 1.7.0
*/
class JDatabaseQueryMysqli extends JDatabaseQuery implements
JDatabaseQueryLimitable
{
/**
* @var integer The offset for the result set.
* @since 3.0.0
*/
protected $offset;
/**
* @var integer The limit for the result set.
* @since 3.0.0
*/
protected $limit;
/**
* Magic function to convert the query to a string.
*
* @return string The completed query.
*
* @since 1.7.0
*/
public function __toString()
{
switch ($this->type)
{
case 'select':
if ($this->selectRowNumber)
{
$orderBy = $this->selectRowNumber['orderBy'];
$tmpOffset = $this->offset;
$tmpLimit = $this->limit;
$this->offset = 0;
$this->limit = 0;
$tmpOrder = $this->order;
$this->order = null;
$query = parent::__toString();
$this->order = $tmpOrder;
$this->offset = $tmpOffset;
$this->limit = $tmpLimit;
// Add support for second order by, offset and limit
$query = PHP_EOL . 'SELECT * FROM (' . $query . PHP_EOL .
"ORDER BY $orderBy" . PHP_EOL . ') w';
if ($this->order)
{
$query .= (string) $this->order;
}
return $this->processLimit($query, $this->limit,
$this->offset);
}
}
return parent::__toString();
}
/**
* Method to modify a query already in string format with the needed
* additions to make the query limited to a particular number of
* results, or start at a particular offset.
*
* @param string $query The query in string format
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return string
*
* @since 3.0.0
*/
public function processLimit($query, $limit, $offset = 0)
{
if ($limit > 0 && $offset > 0)
{
$query .= ' LIMIT ' . $offset . ', ' . $limit;
}
elseif ($limit > 0)
{
$query .= ' LIMIT ' . $limit;
}
return $query;
}
/**
* Concatenates an array of column names or values.
*
* @param array $values An array of values to concatenate.
* @param string $separator As separator to place between each value.
*
* @return string The concatenated values.
*
* @since 1.7.0
*/
public function concatenate($values, $separator = null)
{
if ($separator)
{
$concat_string = 'CONCAT_WS(' . $this->quote($separator);
foreach ($values as $value)
{
$concat_string .= ', ' . $value;
}
return $concat_string . ')';
}
else
{
return 'CONCAT(' . implode(',', $values) .
')';
}
}
/**
* Sets the offset and limit for the result set, if the database driver
supports it.
*
* Usage:
* $query->setLimit(100, 0); (retrieve 100 rows, starting at first
record)
* $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th
record)
*
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return JDatabaseQuery Returns this object to allow chaining.
*
* @since 3.0.0
*/
public function setLimit($limit = 0, $offset = 0)
{
$this->limit = (int) $limit;
$this->offset = (int) $offset;
return $this;
}
/**
* Return correct regexp operator for mysqli.
*
* Ensure that the regexp operator is mysqli compatible.
*
* Usage:
* $query->where('field ' . $query->regexp($search));
*
* @param string $value The regex pattern.
*
* @return string Returns the regex operator.
*
* @since 1.7.3
*/
public function regexp($value)
{
return ' REGEXP ' . $value;
}
/**
* Return correct rand() function for Mysql.
*
* Ensure that the rand() function is Mysql compatible.
*
* Usage:
* $query->Rand();
*
* @return string The correct rand function.
*
* @since 3.5
*/
public function Rand()
{
return ' RAND() ';
}
/**
* Return the number of the current row.
*
* @param string $orderBy An expression of ordering for
window function.
* @param string $orderColumnAlias An alias for new ordering column.
*
* @return JDatabaseQuery Returns this object to allow chaining.
*
* @since 3.7.0
* @throws RuntimeException
*/
public function selectRowNumber($orderBy, $orderColumnAlias)
{
$this->validateRowNumber($orderBy, $orderColumnAlias);
$this->select("(SELECT @rownum := @rownum + 1 FROM (SELECT
@rownum := 0) AS r) AS $orderColumnAlias");
return $this;
}
/**
* Casts a value to a char.
*
* Ensure that the value is properly quoted before passing to the method.
*
* Usage:
* $query->select($query->castAsChar('a'));
* $query->select($query->castAsChar('a', 40));
*
* @param string $value The value to cast as a char.
*
* @param string $len The length of the char.
*
* @return string Returns the cast value.
*
* @since 3.7.0
*/
public function castAsChar($value, $len = null)
{
if (!$len)
{
return $value;
}
else
{
return ' CAST(' . $value . ' AS CHAR(' . $len .
'))';
}
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Oracle Query Building Class.
*
* @since 3.0.0
*/
class JDatabaseQueryOracle extends JDatabaseQueryPdo implements
JDatabaseQueryPreparable, JDatabaseQueryLimitable
{
/**
* @var integer The offset for the result set.
* @since 3.0.0
*/
protected $offset;
/**
* @var integer The limit for the result set.
* @since 3.0.0
*/
protected $limit;
/**
* @var array Bounded object array
* @since 3.0.0
*/
protected $bounded = array();
/**
* Method to add a variable to an internal array that will be bound to a
prepared SQL statement before query execution. Also
* removes a variable that has been bounded from the internal bounded
array when the passed in value is null.
*
* @param string|integer $key The key that will be used in
your SQL query to reference the value. Usually of
* the form ':key', but
can also be an integer.
* @param mixed &$value The value that will be
bound. The value is passed by reference to support output
* parameters such as those
possible with stored procedures.
* @param integer $dataType Constant corresponding to a
SQL datatype.
* @param integer $length The length of the variable.
Usually required for OUTPUT parameters.
* @param array $driverOptions Optional driver options to be
used.
*
* @return JDatabaseQueryOracle
*
* @since 3.0.0
*/
public function bind($key = null, &$value = null, $dataType =
PDO::PARAM_STR, $length = 0, $driverOptions = array())
{
// Case 1: Empty Key (reset $bounded array)
if (empty($key))
{
$this->bounded = array();
return $this;
}
// Case 2: Key Provided, null value (unset key from $bounded array)
if (is_null($value))
{
if (isset($this->bounded[$key]))
{
unset($this->bounded[$key]);
}
return $this;
}
$obj = new stdClass;
$obj->value = &$value;
$obj->dataType = $dataType;
$obj->length = $length;
$obj->driverOptions = $driverOptions;
// Case 3: Simply add the Key/Value into the bounded array
$this->bounded[$key] = $obj;
return $this;
}
/**
* Retrieves the bound parameters array when key is null and returns it by
reference. If a key is provided then that item is
* returned.
*
* @param mixed $key The bounded variable key to retrieve.
*
* @return mixed
*
* @since 3.0.0
*/
public function &getBounded($key = null)
{
if (empty($key))
{
return $this->bounded;
}
else
{
if (isset($this->bounded[$key]))
{
return $this->bounded[$key];
}
}
}
/**
* Clear data from the query or a specific clause of the query.
*
* @param string $clause Optionally, the name of the clause to clear,
or nothing to clear the whole query.
*
* @return JDatabaseQueryOracle Returns this object to allow chaining.
*
* @since 3.0.0
*/
public function clear($clause = null)
{
switch ($clause)
{
case null:
$this->bounded = array();
break;
}
parent::clear($clause);
return $this;
}
/**
* Method to modify a query already in string format with the needed
* additions to make the query limited to a particular number of
* results, or start at a particular offset. This method is used
* automatically by the __toString() method if it detects that the
* query implements the JDatabaseQueryLimitable interface.
*
* @param string $query The query in string format
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return string
*
* @since 3.0.0
*/
public function processLimit($query, $limit, $offset = 0)
{
// Check if we need to mangle the query.
if ($limit || $offset)
{
$query = 'SELECT joomla2.*
FROM (
SELECT joomla1.*, ROWNUM AS joomla_db_rownum
FROM (
' . $query . '
) joomla1
) joomla2';
// Check if the limit value is greater than zero.
if ($limit > 0)
{
$query .= ' WHERE joomla2.joomla_db_rownum BETWEEN ' .
($offset + 1) . ' AND ' . ($offset + $limit);
}
else
{
// Check if there is an offset and then use this.
if ($offset)
{
$query .= ' WHERE joomla2.joomla_db_rownum > ' . ($offset
+ 1);
}
}
}
return $query;
}
/**
* Sets the offset and limit for the result set, if the database driver
supports it.
*
* Usage:
* $query->setLimit(100, 0); (retrieve 100 rows, starting at first
record)
* $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th
record)
*
* @param integer $limit The limit for the result set
* @param integer $offset The offset for the result set
*
* @return JDatabaseQueryOracle Returns this object to allow chaining.
*
* @since 3.0.0
*/
public function setLimit($limit = 0, $offset = 0)
{
$this->limit = (int) $limit;
$this->offset = (int) $offset;
return $this;
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PDO Query Building Class.
*
* @since 3.0.0
*/
class JDatabaseQueryPdo extends JDatabaseQuery
{
/**
* Casts a value to a char.
*
* Ensure that the value is properly quoted before passing to the method.
*
* Usage:
* $query->select($query->castAsChar('a'));
* $query->select($query->castAsChar('a', 40));
*
* @param string $value The value to cast as a char.
*
* @param string $len The length of the char.
*
* @return string Returns the cast value.
*
* @since 1.7.0
*/
public function castAsChar($value, $len = null)
{
if (!$len)
{
return $value;
}
else
{
return ' CAST(' . $value . ' AS CHAR(' . $len .
'))';
}
}
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* Query Building Class.
*
* @package Joomla.Platform
* @subpackage Database
* @since 3.4
*/
class JDatabaseQueryPdomysql extends JDatabaseQueryMysqli
{
}
<?php
/**
* @package Joomla.Platform
* @subpackage Database
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
defined('JPATH_PLATFORM') or die;
/**
* PDO PostgreSQL Query Building Class.
*
* @since 3.9.0
*/
class JDatabaseQueryPgsql extends JDatabaseQueryPostgresql implements
JDatabaseQueryPreparable
{
/**
* Holds key / value pair of bound objects.
*
* @var mixed
* @since 3.9.0
*/
protected $bounded = array();
/**
* Method to add a variable to an internal array that will be bound to a
prepared SQL statement before query execution. Also
* removes a variable that has been bounded from the internal bounded
array when the passed in value is null.
*
* @param string|integer $key The key that will be used in
your SQL query to reference the value. Usually of
* the form ':key', but
can also be an integer.
* @param mixed &$value The value that will be
bound. The value is passed by reference to support output
* parameters such as those
possible with stored procedures.
* @param integer $dataType Constant corresponding to a
SQL datatype.
* @param integer $length The length of the variable.
Usually required for OUTPUT parameters.
* @param array $driverOptions Optional driver options to be
used.
*
* @return JDatabaseQueryPgsql
*
* @since 3.9.0
*/
public function bind($key = null, &$value = null, $dataType =
PDO::PARAM_STR, $length = 0, $driverOptions = array())
{
// Case 1: Empty Key (reset $bounded array)
if (empty($key))
{
$this->bounded = array();
return $this;
}
// Case 2: Key Provided, null value (unset key from $bounded array)
if (is_null($value))
{
if (isset($this->bounded[$key]))
{
unset($this->bounded[$key]);
}
return $this;
}
$obj = new stdClass;
$obj->value = &$value;
$obj->dataType = $dataType;
$obj->length = $length;
$obj->driverOptions = $driverOptions;
// Case 3: Simply add the Key/Value into the bounded array
$this->bounded[$key] = $obj;
return $this;
}
/**
* Retrieves the bound parameters array when key is null and returns it by
reference. If a key is provided then that item is
* returned.
*
* @param mixed $key The bounded variable key to retrieve.
*
* @return mixed
*
* @since 3.9.0
*/
public function &getBounded($key = null)
{
if (empty($key))
{
return $this->bounded;
}
if (isset($this->bounded[$key]))
{
return $this->bounded[$key];
}
}
/**
* Clear data from the query or a specific clause of the query.
*
* @param string $clause Optionally, the name of the clause to clear,
or nothing to clear the whole query.
*
* @return JDatabaseQueryPgsql Returns this object to allow chaining.
*
* @since 3.9.0
*/
public function clear($clause = null)
{
switch ($clause)
{
case null:
$this->bounded = array();
break;
}
return parent::clear($clause);
}
}