PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/joomla/table/table.php

http://github.com/joomla/joomla-platform
PHP | 1548 lines | 803 code | 197 blank | 548 comment | 110 complexity | 0ff564def162a1b9dd4a659c9b653454 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Table
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.filesystem.path');
  11. /**
  12. * Abstract Table class
  13. *
  14. * Parent class to all tables.
  15. *
  16. * @package Joomla.Platform
  17. * @subpackage Table
  18. * @link http://docs.joomla.org/JTable
  19. * @since 11.1
  20. * @tutorial Joomla.Platform/jtable.cls
  21. */
  22. abstract class JTable extends JObject
  23. {
  24. /**
  25. * Include paths for searching for JTable classes.
  26. *
  27. * @var array
  28. * @since 12.1
  29. */
  30. private static $_includePaths = array();
  31. /**
  32. * Name of the database table to model.
  33. *
  34. * @var string
  35. * @since 11.1
  36. */
  37. protected $_tbl = '';
  38. /**
  39. * Name of the primary key field in the table.
  40. *
  41. * @var string
  42. * @since 11.1
  43. */
  44. protected $_tbl_key = '';
  45. /**
  46. * Name of the primary key fields in the table.
  47. *
  48. * @var array
  49. * @since 12.2
  50. */
  51. protected $_tbl_keys = array();
  52. /**
  53. * JDatabaseDriver object.
  54. *
  55. * @var JDatabaseDriver
  56. * @since 11.1
  57. */
  58. protected $_db;
  59. /**
  60. * Should rows be tracked as ACL assets?
  61. *
  62. * @var boolean
  63. * @since 11.1
  64. */
  65. protected $_trackAssets = false;
  66. /**
  67. * The rules associated with this record.
  68. *
  69. * @var JAccessRules A JAccessRules object.
  70. * @since 11.1
  71. */
  72. protected $_rules;
  73. /**
  74. * Indicator that the tables have been locked.
  75. *
  76. * @var boolean
  77. * @since 11.1
  78. */
  79. protected $_locked = false;
  80. /**
  81. * Indicates that the primary keys autoincrement.
  82. *
  83. * @var boolean
  84. * @since 12.3
  85. */
  86. protected $_autoincrement = true;
  87. /**
  88. * Object constructor to set table and key fields. In most cases this will
  89. * be overridden by child classes to explicitly set the table and key fields
  90. * for a particular database table.
  91. *
  92. * @param string $table Name of the table to model.
  93. * @param mixed $key Name of the primary key field in the table or array of field names that compose the primary key.
  94. * @param JDatabaseDriver $db JDatabaseDriver object.
  95. *
  96. * @since 11.1
  97. */
  98. public function __construct($table, $key, JDatabaseDriver $db)
  99. {
  100. // Set internal variables.
  101. $this->_tbl = $table;
  102. // Set the key to be an array.
  103. if (is_string($key))
  104. {
  105. $key = array($key);
  106. }
  107. elseif (is_object($key))
  108. {
  109. $key = (array) $key;
  110. }
  111. $this->_tbl_keys = $key;
  112. if (count($key) == 1)
  113. {
  114. $this->_autoincrement = true;
  115. }
  116. else
  117. {
  118. $this->_autoincrement = false;
  119. }
  120. // Set the singular table key for backwards compatibility.
  121. $this->_tbl_key = $this->getKeyName();
  122. $this->_db = $db;
  123. // Initialise the table properties.
  124. $fields = $this->getFields();
  125. if ($fields)
  126. {
  127. foreach ($fields as $name => $v)
  128. {
  129. // Add the field if it is not already present.
  130. if (!property_exists($this, $name))
  131. {
  132. $this->$name = null;
  133. }
  134. }
  135. }
  136. // If we are tracking assets, make sure an access field exists and initially set the default.
  137. if (property_exists($this, 'asset_id'))
  138. {
  139. $this->_trackAssets = true;
  140. }
  141. // If the access property exists, set the default.
  142. if (property_exists($this, 'access'))
  143. {
  144. $this->access = (int) JFactory::getConfig()->get('access');
  145. }
  146. }
  147. /**
  148. * Get the columns from database table.
  149. *
  150. * @return mixed An array of the field names, or false if an error occurs.
  151. *
  152. * @since 11.1
  153. * @throws UnexpectedValueException
  154. */
  155. public function getFields()
  156. {
  157. static $cache = null;
  158. if ($cache === null)
  159. {
  160. // Lookup the fields for this table only once.
  161. $name = $this->_tbl;
  162. $fields = $this->_db->getTableColumns($name, false);
  163. if (empty($fields))
  164. {
  165. throw new UnexpectedValueException(sprintf('No columns found for %s table', $name));
  166. }
  167. $cache = $fields;
  168. }
  169. return $cache;
  170. }
  171. /**
  172. * Static method to get an instance of a JTable class if it can be found in
  173. * the table include paths. To add include paths for searching for JTable
  174. * classes @see JTable::addIncludePath().
  175. *
  176. * @param string $type The type (name) of the JTable class to get an instance of.
  177. * @param string $prefix An optional prefix for the table class name.
  178. * @param array $config An optional array of configuration values for the JTable object.
  179. *
  180. * @return mixed A JTable object if found or boolean false if one could not be found.
  181. *
  182. * @link http://docs.joomla.org/JTable/getInstance
  183. * @since 11.1
  184. */
  185. public static function getInstance($type, $prefix = 'JTable', $config = array())
  186. {
  187. // Sanitize and prepare the table class name.
  188. $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
  189. $tableClass = $prefix . ucfirst($type);
  190. // Only try to load the class if it doesn't already exist.
  191. if (!class_exists($tableClass))
  192. {
  193. // Search for the class file in the JTable include paths.
  194. $path = JPath::find(self::addIncludePath(), strtolower($type) . '.php');
  195. if ($path)
  196. {
  197. // Import the class file.
  198. include_once $path;
  199. // If we were unable to load the proper class, raise a warning and return false.
  200. if (!class_exists($tableClass))
  201. {
  202. JLog::add(JText::sprintf('JLIB_DATABASE_ERROR_CLASS_NOT_FOUND_IN_FILE', $tableClass), JLog::WARNING, 'jerror');
  203. return false;
  204. }
  205. }
  206. else
  207. {
  208. // If we were unable to find the class file in the JTable include paths, raise a warning and return false.
  209. JLog::add(JText::sprintf('JLIB_DATABASE_ERROR_NOT_SUPPORTED_FILE_NOT_FOUND', $type), JLog::WARNING, 'jerror');
  210. return false;
  211. }
  212. }
  213. // If a database object was passed in the configuration array use it, otherwise get the global one from JFactory.
  214. $db = isset($config['dbo']) ? $config['dbo'] : JFactory::getDbo();
  215. // Instantiate a new table class and return it.
  216. return new $tableClass($db);
  217. }
  218. /**
  219. * Add a filesystem path where JTable should search for table class files.
  220. * You may either pass a string or an array of paths.
  221. *
  222. * @param mixed $path A filesystem path or array of filesystem paths to add.
  223. *
  224. * @return array An array of filesystem paths to find JTable classes in.
  225. *
  226. * @link http://docs.joomla.org/JTable/addIncludePath
  227. * @since 11.1
  228. */
  229. public static function addIncludePath($path = null)
  230. {
  231. // If the internal paths have not been initialised, do so with the base table path.
  232. if (empty(self::$_includePaths))
  233. {
  234. self::$_includePaths = array(__DIR__);
  235. }
  236. // Convert the passed path(s) to add to an array.
  237. settype($path, 'array');
  238. // If we have new paths to add, do so.
  239. if (!empty($path))
  240. {
  241. // Check and add each individual new path.
  242. foreach ($path as $dir)
  243. {
  244. // Sanitize path.
  245. $dir = trim($dir);
  246. // Add to the front of the list so that custom paths are searched first.
  247. if (!in_array($dir, self::$_includePaths))
  248. {
  249. array_unshift(self::$_includePaths, $dir);
  250. }
  251. }
  252. }
  253. return self::$_includePaths;
  254. }
  255. /**
  256. * Method to compute the default name of the asset.
  257. * The default name is in the form table_name.id
  258. * where id is the value of the primary key of the table.
  259. *
  260. * @return string
  261. *
  262. * @since 11.1
  263. */
  264. protected function _getAssetName()
  265. {
  266. $keys = array();
  267. foreach ($this->_tbl_keys as $k)
  268. {
  269. $keys[] = (int) $this->$k;
  270. }
  271. return $this->_tbl . '.' . implode('.', $keys);
  272. }
  273. /**
  274. * Method to return the title to use for the asset table. In
  275. * tracking the assets a title is kept for each asset so that there is some
  276. * context available in a unified access manager. Usually this would just
  277. * return $this->title or $this->name or whatever is being used for the
  278. * primary name of the row. If this method is not overridden, the asset name is used.
  279. *
  280. * @return string The string to use as the title in the asset table.
  281. *
  282. * @link http://docs.joomla.org/JTable/getAssetTitle
  283. * @since 11.1
  284. */
  285. protected function _getAssetTitle()
  286. {
  287. return $this->_getAssetName();
  288. }
  289. /**
  290. * Method to get the parent asset under which to register this one.
  291. * By default, all assets are registered to the ROOT node with ID,
  292. * which will default to 1 if none exists.
  293. * The extended class can define a table and id to lookup. If the
  294. * asset does not exist it will be created.
  295. *
  296. * @param JTable $table A JTable object for the asset parent.
  297. * @param integer $id Id to look up
  298. *
  299. * @return integer
  300. *
  301. * @since 11.1
  302. */
  303. protected function _getAssetParentId(JTable $table = null, $id = null)
  304. {
  305. // For simple cases, parent to the asset root.
  306. $assets = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
  307. $rootId = $assets->getRootId();
  308. if (!empty($rootId))
  309. {
  310. return $rootId;
  311. }
  312. return 1;
  313. }
  314. /**
  315. * Method to append the primary keys for this table to a query.
  316. *
  317. * @param JDatabaseQuery $query A query object to append.
  318. * @param mixed $pk Optional primary key parameter.
  319. *
  320. * @return void
  321. *
  322. * @since 12.3
  323. */
  324. public function appendPrimaryKeys($query, $pk = null)
  325. {
  326. if (is_null($pk))
  327. {
  328. foreach ($this->_tbl_keys as $k)
  329. {
  330. $query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($this->$k));
  331. }
  332. }
  333. else
  334. {
  335. if (is_string($pk))
  336. {
  337. $pk = array($this->_tbl_key => $pk);
  338. }
  339. $pk = (object) $pk;
  340. foreach ($this->_tbl_keys AS $k)
  341. {
  342. $query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($pk->$k));
  343. }
  344. }
  345. }
  346. /**
  347. * Method to get the database table name for the class.
  348. *
  349. * @return string The name of the database table being modeled.
  350. *
  351. * @since 11.1
  352. *
  353. * @link http://docs.joomla.org/JTable/getTableName
  354. */
  355. public function getTableName()
  356. {
  357. return $this->_tbl;
  358. }
  359. /**
  360. * Method to get the primary key field name for the table.
  361. *
  362. * @param boolean $multiple True to return all primary keys (as an array) or false to return just the first one (as a string).
  363. *
  364. * @return mixed Array of primary key field names or string containing the first primary key field.
  365. *
  366. * @link http://docs.joomla.org/JTable/getKeyName
  367. * @since 11.1
  368. */
  369. public function getKeyName($multiple = false)
  370. {
  371. // Count the number of keys
  372. if (count($this->_tbl_keys))
  373. {
  374. if ($multiple)
  375. {
  376. // If we want multiple keys, return the raw array.
  377. return $this->_tbl_keys;
  378. }
  379. else
  380. {
  381. // If we want the standard method, just return the first key.
  382. return $this->_tbl_keys[0];
  383. }
  384. }
  385. return '';
  386. }
  387. /**
  388. * Method to get the JDatabaseDriver object.
  389. *
  390. * @return JDatabaseDriver The internal database driver object.
  391. *
  392. * @link http://docs.joomla.org/JTable/getDBO
  393. * @since 11.1
  394. */
  395. public function getDbo()
  396. {
  397. return $this->_db;
  398. }
  399. /**
  400. * Method to set the JDatabaseDriver object.
  401. *
  402. * @param JDatabaseDriver $db A JDatabaseDriver object to be used by the table object.
  403. *
  404. * @return boolean True on success.
  405. *
  406. * @link http://docs.joomla.org/JTable/setDBO
  407. * @since 11.1
  408. */
  409. public function setDBO(JDatabaseDriver $db)
  410. {
  411. $this->_db = $db;
  412. return true;
  413. }
  414. /**
  415. * Method to set rules for the record.
  416. *
  417. * @param mixed $input A JAccessRules object, JSON string, or array.
  418. *
  419. * @return void
  420. *
  421. * @since 11.1
  422. */
  423. public function setRules($input)
  424. {
  425. if ($input instanceof JAccessRules)
  426. {
  427. $this->_rules = $input;
  428. }
  429. else
  430. {
  431. $this->_rules = new JAccessRules($input);
  432. }
  433. }
  434. /**
  435. * Method to get the rules for the record.
  436. *
  437. * @return JAccessRules object
  438. *
  439. * @since 11.1
  440. */
  441. public function getRules()
  442. {
  443. return $this->_rules;
  444. }
  445. /**
  446. * Method to reset class properties to the defaults set in the class
  447. * definition. It will ignore the primary key as well as any private class
  448. * properties (except $_errors).
  449. *
  450. * @return void
  451. *
  452. * @link http://docs.joomla.org/JTable/reset
  453. * @since 11.1
  454. */
  455. public function reset()
  456. {
  457. // Get the default values for the class from the table.
  458. foreach ($this->getFields() as $k => $v)
  459. {
  460. // If the property is not the primary key or private, reset it.
  461. if (!in_array($k, $this->_tbl_keys) && (strpos($k, '_') !== 0))
  462. {
  463. $this->$k = $v->Default;
  464. }
  465. }
  466. // Reset table errors
  467. $this->_errors = array();
  468. }
  469. /**
  470. * Method to bind an associative array or object to the JTable instance.This
  471. * method only binds properties that are publicly accessible and optionally
  472. * takes an array of properties to ignore when binding.
  473. *
  474. * @param mixed $src An associative array or object to bind to the JTable instance.
  475. * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
  476. *
  477. * @return boolean True on success.
  478. *
  479. * @link http://docs.joomla.org/JTable/bind
  480. * @since 11.1
  481. * @throws UnexpectedValueException
  482. */
  483. public function bind($src, $ignore = array())
  484. {
  485. // If the source value is not an array or object return false.
  486. if (!is_object($src) && !is_array($src))
  487. {
  488. throw new InvalidArgumentException(sprintf('%s::bind(*%s*)', get_class($this), gettype($src)));
  489. }
  490. // If the source value is an object, get its accessible properties.
  491. if (is_object($src))
  492. {
  493. $src = get_object_vars($src);
  494. }
  495. // If the ignore value is a string, explode it over spaces.
  496. if (!is_array($ignore))
  497. {
  498. $ignore = explode(' ', $ignore);
  499. }
  500. // Bind the source value, excluding the ignored fields.
  501. foreach ($this->getProperties() as $k => $v)
  502. {
  503. // Only process fields not in the ignore array.
  504. if (!in_array($k, $ignore))
  505. {
  506. if (isset($src[$k]))
  507. {
  508. $this->$k = $src[$k];
  509. }
  510. }
  511. }
  512. return true;
  513. }
  514. /**
  515. * Method to load a row from the database by primary key and bind the fields
  516. * to the JTable instance properties.
  517. *
  518. * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. If not
  519. * set the instance property value is used.
  520. * @param boolean $reset True to reset the default values before loading the new row.
  521. *
  522. * @return boolean True if successful. False if row not found.
  523. *
  524. * @link http://docs.joomla.org/JTable/load
  525. * @since 11.1
  526. * @throws RuntimeException
  527. * @throws UnexpectedValueException
  528. */
  529. public function load($keys = null, $reset = true)
  530. {
  531. if (empty($keys))
  532. {
  533. $empty = true;
  534. $keys = array();
  535. // If empty, use the value of the current key
  536. foreach ($this->_tbl_keys as $key)
  537. {
  538. $empty = $empty && empty($this->$key);
  539. $keys[$key] = $this->$key;
  540. }
  541. // If empty primary key there's is no need to load anything
  542. if ($empty)
  543. {
  544. return true;
  545. }
  546. }
  547. elseif (!is_array($keys))
  548. {
  549. // Load by primary key.
  550. $keyCount = count($this->_tbl_keys);
  551. if ($keyCount)
  552. {
  553. if ($keyCount > 1)
  554. {
  555. throw new InvalidArgumentException('Table has multiple primary keys specified, only one primary key value provided.');
  556. }
  557. $keys = array($this->getKeyName() => $keys);
  558. }
  559. else
  560. {
  561. throw new RuntimeException('No table keys defined.');
  562. }
  563. }
  564. if ($reset)
  565. {
  566. $this->reset();
  567. }
  568. // Initialise the query.
  569. $query = $this->_db->getQuery(true);
  570. $query->select('*');
  571. $query->from($this->_tbl);
  572. $fields = array_keys($this->getProperties());
  573. foreach ($keys as $field => $value)
  574. {
  575. // Check that $field is in the table.
  576. if (!in_array($field, $fields))
  577. {
  578. throw new UnexpectedValueException(sprintf('Missing field in database: %s &#160; %s.', get_class($this), $field));
  579. }
  580. // Add the search tuple to the query.
  581. $query->where($this->_db->quoteName($field) . ' = ' . $this->_db->quote($value));
  582. }
  583. $this->_db->setQuery($query);
  584. $row = $this->_db->loadAssoc();
  585. // Check that we have a result.
  586. if (empty($row))
  587. {
  588. return false;
  589. }
  590. // Bind the object with the row and return.
  591. return $this->bind($row);
  592. }
  593. /**
  594. * Method to perform sanity checks on the JTable instance properties to ensure
  595. * they are safe to store in the database. Child classes should override this
  596. * method to make sure the data they are storing in the database is safe and
  597. * as expected before storage.
  598. *
  599. * @return boolean True if the instance is sane and able to be stored in the database.
  600. *
  601. * @link http://docs.joomla.org/JTable/check
  602. * @since 11.1
  603. */
  604. public function check()
  605. {
  606. return true;
  607. }
  608. /**
  609. * Method to store a row in the database from the JTable instance properties.
  610. * If a primary key value is set the row with that primary key value will be
  611. * updated with the instance property values. If no primary key value is set
  612. * a new row will be inserted into the database with the properties from the
  613. * JTable instance.
  614. *
  615. * @param boolean $updateNulls True to update fields even if they are null.
  616. *
  617. * @return boolean True on success.
  618. *
  619. * @link http://docs.joomla.org/JTable/store
  620. * @since 11.1
  621. */
  622. public function store($updateNulls = false)
  623. {
  624. $k = $this->_tbl_keys;
  625. $currentAssetId = 0;
  626. if (!empty($this->asset_id))
  627. {
  628. $currentAssetId = $this->asset_id;
  629. }
  630. // The asset id field is managed privately by this class.
  631. if ($this->_trackAssets)
  632. {
  633. unset($this->asset_id);
  634. }
  635. // If a primary key exists update the object, otherwise insert it.
  636. if ($this->hasPrimaryKey())
  637. {
  638. $this->_db->updateObject($this->_tbl, $this, $this->_tbl_keys, $updateNulls);
  639. }
  640. else
  641. {
  642. $this->_db->insertObject($this->_tbl, $this, $this->_tbl_keys);
  643. }
  644. // If the table is not set to track assets return true.
  645. if (!$this->_trackAssets)
  646. {
  647. return true;
  648. }
  649. if ($this->_locked)
  650. {
  651. $this->_unlock();
  652. }
  653. /*
  654. * Asset Tracking
  655. */
  656. $parentId = $this->_getAssetParentId();
  657. $name = $this->_getAssetName();
  658. $title = $this->_getAssetTitle();
  659. $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
  660. $asset->loadByName($name);
  661. // Re-inject the asset id.
  662. $this->asset_id = $asset->id;
  663. // Check for an error.
  664. $error = $asset->getError();
  665. if ($error)
  666. {
  667. $this->setError($error);
  668. return false;
  669. }
  670. // Specify how a new or moved node asset is inserted into the tree.
  671. if (empty($this->asset_id) || $asset->parent_id != $parentId)
  672. {
  673. $asset->setLocation($parentId, 'last-child');
  674. }
  675. // Prepare the asset to be stored.
  676. $asset->parent_id = $parentId;
  677. $asset->name = $name;
  678. $asset->title = $title;
  679. if ($this->_rules instanceof JAccessRules)
  680. {
  681. $asset->rules = (string) $this->_rules;
  682. }
  683. if (!$asset->check() || !$asset->store($updateNulls))
  684. {
  685. $this->setError($asset->getError());
  686. return false;
  687. }
  688. // Create an asset_id or heal one that is corrupted.
  689. if (empty($this->asset_id) || ($currentAssetId != $this->asset_id && !empty($this->asset_id)))
  690. {
  691. // Update the asset_id field in this table.
  692. $this->asset_id = (int) $asset->id;
  693. $query = $this->_db->getQuery(true);
  694. $query->update($this->_db->quoteName($this->_tbl));
  695. $query->set('asset_id = ' . (int) $this->asset_id);
  696. $this->appendPrimaryKeys($query);
  697. $this->_db->setQuery($query);
  698. $this->_db->execute();
  699. }
  700. return true;
  701. }
  702. /**
  703. * Method to provide a shortcut to binding, checking and storing a JTable
  704. * instance to the database table. The method will check a row in once the
  705. * data has been stored and if an ordering filter is present will attempt to
  706. * reorder the table rows based on the filter. The ordering filter is an instance
  707. * property name. The rows that will be reordered are those whose value matches
  708. * the JTable instance for the property specified.
  709. *
  710. * @param mixed $src An associative array or object to bind to the JTable instance.
  711. * @param string $orderingFilter Filter for the order updating
  712. * @param mixed $ignore An optional array or space separated list of properties
  713. * to ignore while binding.
  714. *
  715. * @return boolean True on success.
  716. *
  717. * @link http://docs.joomla.org/JTable/save
  718. * @since 11.1
  719. */
  720. public function save($src, $orderingFilter = '', $ignore = '')
  721. {
  722. // Attempt to bind the source to the instance.
  723. if (!$this->bind($src, $ignore))
  724. {
  725. return false;
  726. }
  727. // Run any sanity checks on the instance and verify that it is ready for storage.
  728. if (!$this->check())
  729. {
  730. return false;
  731. }
  732. // Attempt to store the properties to the database table.
  733. if (!$this->store())
  734. {
  735. return false;
  736. }
  737. // Attempt to check the row in, just in case it was checked out.
  738. if (!$this->checkin())
  739. {
  740. return false;
  741. }
  742. // If an ordering filter is set, attempt reorder the rows in the table based on the filter and value.
  743. if ($orderingFilter)
  744. {
  745. $filterValue = $this->$orderingFilter;
  746. $this->reorder($orderingFilter ? $this->_db->quoteName($orderingFilter) . ' = ' . $this->_db->Quote($filterValue) : '');
  747. }
  748. // Set the error to empty and return true.
  749. $this->setError('');
  750. return true;
  751. }
  752. /**
  753. * Method to delete a row from the database table by primary key value.
  754. *
  755. * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
  756. *
  757. * @return boolean True on success.
  758. *
  759. * @link http://docs.joomla.org/JTable/delete
  760. * @since 11.1
  761. * @throws UnexpectedValueException
  762. */
  763. public function delete($pk = null)
  764. {
  765. if (is_null($pk))
  766. {
  767. $pk = array();
  768. foreach ($this->_tbl_keys AS $key)
  769. {
  770. $pk[$key] = $this->$key;
  771. }
  772. }
  773. elseif (!is_array($pk))
  774. {
  775. $pk = array($this->_tbl_key => $pk);
  776. }
  777. foreach ($this->_tbl_keys AS $key)
  778. {
  779. $pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
  780. if ($pk[$key] === null)
  781. {
  782. throw new UnexpectedValueException('Null primary key not allowed.');
  783. }
  784. $this->$key = $pk[$key];
  785. }
  786. // If tracking assets, remove the asset first.
  787. if ($this->_trackAssets)
  788. {
  789. // Get the asset name
  790. $name = $this->_getAssetName();
  791. $asset = self::getInstance('Asset');
  792. if ($asset->loadByName($name))
  793. {
  794. if (!$asset->delete())
  795. {
  796. $this->setError($asset->getError());
  797. return false;
  798. }
  799. }
  800. else
  801. {
  802. $this->setError($asset->getError());
  803. return false;
  804. }
  805. }
  806. // Delete the row by primary key.
  807. $query = $this->_db->getQuery(true);
  808. $query->delete();
  809. $query->from($this->_tbl);
  810. $this->appendPrimaryKeys($query, $pk);
  811. $this->_db->setQuery($query);
  812. // Check for a database error.
  813. $this->_db->execute();
  814. return true;
  815. }
  816. /**
  817. * Method to check a row out if the necessary properties/fields exist. To
  818. * prevent race conditions while editing rows in a database, a row can be
  819. * checked out if the fields 'checked_out' and 'checked_out_time' are available.
  820. * While a row is checked out, any attempt to store the row by a user other
  821. * than the one who checked the row out should be held until the row is checked
  822. * in again.
  823. *
  824. * @param integer $userId The Id of the user checking out the row.
  825. * @param mixed $pk An optional primary key value to check out. If not set
  826. * the instance property value is used.
  827. *
  828. * @return boolean True on success.
  829. *
  830. * @link http://docs.joomla.org/JTable/checkOut
  831. * @since 11.1
  832. */
  833. public function checkOut($userId, $pk = null)
  834. {
  835. // If there is no checked_out or checked_out_time field, just return true.
  836. if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
  837. {
  838. return true;
  839. }
  840. if (is_null($pk))
  841. {
  842. $pk = array();
  843. foreach ($this->_tbl_keys AS $key)
  844. {
  845. $pk[$key] = $this->$key;
  846. }
  847. }
  848. elseif (!is_array($pk))
  849. {
  850. $pk = array($this->_tbl_key => $pk);
  851. }
  852. foreach ($this->_tbl_keys AS $key)
  853. {
  854. $pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
  855. if ($pk[$key] === null)
  856. {
  857. throw new UnexpectedValueException('Null primary key not allowed.');
  858. }
  859. }
  860. // Get the current time in MySQL format.
  861. $time = JFactory::getDate()->toSql();
  862. // Check the row out by primary key.
  863. $query = $this->_db->getQuery(true);
  864. $query->update($this->_tbl);
  865. $query->set($this->_db->quoteName('checked_out') . ' = ' . (int) $userId);
  866. $query->set($this->_db->quoteName('checked_out_time') . ' = ' . $this->_db->quote($time));
  867. $this->appendPrimaryKeys($query, $pk);
  868. $this->_db->setQuery($query);
  869. $this->_db->execute();
  870. // Set table values in the object.
  871. $this->checked_out = (int) $userId;
  872. $this->checked_out_time = $time;
  873. return true;
  874. }
  875. /**
  876. * Method to check a row in if the necessary properties/fields exist. Checking
  877. * a row in will allow other users the ability to edit the row.
  878. *
  879. * @param mixed $pk An optional primary key value to check out. If not set the instance property value is used.
  880. *
  881. * @return boolean True on success.
  882. *
  883. * @link http://docs.joomla.org/JTable/checkIn
  884. * @since 11.1
  885. */
  886. public function checkIn($pk = null)
  887. {
  888. // If there is no checked_out or checked_out_time field, just return true.
  889. if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
  890. {
  891. return true;
  892. }
  893. if (is_null($pk))
  894. {
  895. $pk = array();
  896. foreach ($this->_tbl_keys AS $key)
  897. {
  898. $pk[$this->$key] = $this->$key;
  899. }
  900. }
  901. elseif (!is_array($pk))
  902. {
  903. $pk = array($this->_tbl_key => $pk);
  904. }
  905. foreach ($this->_tbl_keys AS $key)
  906. {
  907. $pk[$key] = empty($pk[$key]) ? $this->$key : $pk[$key];
  908. if ($pk[$key] === null)
  909. {
  910. throw new UnexpectedValueException('Null primary key not allowed.');
  911. }
  912. }
  913. // Check the row in by primary key.
  914. $query = $this->_db->getQuery(true);
  915. $query->update($this->_tbl);
  916. $query->set($this->_db->quoteName('checked_out') . ' = 0');
  917. $query->set($this->_db->quoteName('checked_out_time') . ' = ' . $this->_db->quote($this->_db->getNullDate()));
  918. $this->appendPrimaryKeys($query, $pk);
  919. $this->_db->setQuery($query);
  920. // Check for a database error.
  921. $this->_db->execute();
  922. // Set table values in the object.
  923. $this->checked_out = 0;
  924. $this->checked_out_time = '';
  925. return true;
  926. }
  927. /**
  928. * Validate that the primary key has been set.
  929. *
  930. * @return boolean True if the primary key(s) have been set.
  931. *
  932. * @since 12.3
  933. */
  934. public function hasPrimaryKey()
  935. {
  936. if ($this->_autoincrement)
  937. {
  938. $empty = true;
  939. foreach ($this->_tbl_keys as $key)
  940. {
  941. $empty = $empty && empty($this->$key);
  942. }
  943. }
  944. else
  945. {
  946. $query = $this->_db->getQuery(true);
  947. $query->select('COUNT(*)');
  948. $query->from($this->_tbl);
  949. $this->appendPrimaryKeys($query);
  950. $this->_db->setQuery($query);
  951. $count = $this->_db->loadResult();
  952. if ($count == 1)
  953. {
  954. $empty = false;
  955. }
  956. else
  957. {
  958. $empty = true;
  959. }
  960. }
  961. return !$empty;
  962. }
  963. /**
  964. * Method to increment the hits for a row if the necessary property/field exists.
  965. *
  966. * @param mixed $pk An optional primary key value to increment. If not set the instance property value is used.
  967. *
  968. * @return boolean True on success.
  969. *
  970. * @link http://docs.joomla.org/JTable/hit
  971. * @since 11.1
  972. */
  973. public function hit($pk = null)
  974. {
  975. // If there is no hits field, just return true.
  976. if (!property_exists($this, 'hits'))
  977. {
  978. return true;
  979. }
  980. if (is_null($pk))
  981. {
  982. $pk = array();
  983. foreach ($this->_tbl_keys AS $key)
  984. {
  985. $pk[$key] = $this->$key;
  986. }
  987. }
  988. elseif (!is_array($pk))
  989. {
  990. $pk = array($this->_tbl_key => $pk);
  991. }
  992. foreach ($this->_tbl_keys AS $key)
  993. {
  994. $pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];
  995. if ($pk[$key] === null)
  996. {
  997. throw new UnexpectedValueException('Null primary key not allowed.');
  998. }
  999. }
  1000. // Check the row in by primary key.
  1001. $query = $this->_db->getQuery(true);
  1002. $query->update($this->_tbl);
  1003. $query->set($this->_db->quoteName('hits') . ' = (' . $this->_db->quoteName('hits') . ' + 1)');
  1004. $this->appendPrimaryKeys($query, $pk);
  1005. $this->_db->setQuery($query);
  1006. $this->_db->execute();
  1007. // Set table values in the object.
  1008. $this->hits++;
  1009. return true;
  1010. }
  1011. /**
  1012. * Method to determine if a row is checked out and therefore uneditable by
  1013. * a user. If the row is checked out by the same user, then it is considered
  1014. * not checked out -- as the user can still edit it.
  1015. *
  1016. * @param integer $with The userid to preform the match with, if an item is checked
  1017. * out by this user the function will return false.
  1018. * @param integer $against The userid to perform the match against when the function
  1019. * is used as a static function.
  1020. *
  1021. * @return boolean True if checked out.
  1022. *
  1023. * @link http://docs.joomla.org/JTable/isCheckedOut
  1024. * @since 11.1
  1025. */
  1026. public function isCheckedOut($with = 0, $against = null)
  1027. {
  1028. // Handle the non-static case.
  1029. if (isset($this) && ($this instanceof JTable) && is_null($against))
  1030. {
  1031. $against = $this->get('checked_out');
  1032. }
  1033. // The item is not checked out or is checked out by the same user.
  1034. if (!$against || ($against == $with))
  1035. {
  1036. return false;
  1037. }
  1038. $db = JFactory::getDBO();
  1039. $db->setQuery('SELECT COUNT(userid)' . ' FROM ' . $db->quoteName('#__session') . ' WHERE ' . $db->quoteName('userid') . ' = ' . (int) $against);
  1040. $checkedOut = (boolean) $db->loadResult();
  1041. // If a session exists for the user then it is checked out.
  1042. return $checkedOut;
  1043. }
  1044. /**
  1045. * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause.
  1046. * This is useful for placing a new item last in a group of items in the table.
  1047. *
  1048. * @param string $where WHERE clause to use for selecting the MAX(ordering) for the table.
  1049. *
  1050. * @return mixed Boolean false an failure or the next ordering value as an integer.
  1051. *
  1052. * @link http://docs.joomla.org/JTable/getNextOrder
  1053. * @since 11.1
  1054. */
  1055. public function getNextOrder($where = '')
  1056. {
  1057. // If there is no ordering field set an error and return false.
  1058. if (!property_exists($this, 'ordering'))
  1059. {
  1060. throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
  1061. }
  1062. // Get the largest ordering value for a given where clause.
  1063. $query = $this->_db->getQuery(true);
  1064. $query->select('MAX(ordering)');
  1065. $query->from($this->_tbl);
  1066. if ($where)
  1067. {
  1068. $query->where($where);
  1069. }
  1070. $this->_db->setQuery($query);
  1071. $max = (int) $this->_db->loadResult();
  1072. // Return the largest ordering value + 1.
  1073. return ($max + 1);
  1074. }
  1075. /**
  1076. * Get the primary key values for this table using passed in values as a default.
  1077. *
  1078. * @param array $keys Optional primary key values to use.
  1079. *
  1080. * @return array An array of primary key names and values.
  1081. *
  1082. * @since 12.3
  1083. */
  1084. public function getPrimaryKey(array $keys = array())
  1085. {
  1086. foreach ($this->_tbl_keys as $key)
  1087. {
  1088. if (!isset($keys[$key]))
  1089. {
  1090. if (!empty($this->$key))
  1091. {
  1092. $keys[$key] = $this->$key;
  1093. }
  1094. }
  1095. }
  1096. return $keys;
  1097. }
  1098. /**
  1099. * Method to compact the ordering values of rows in a group of rows
  1100. * defined by an SQL WHERE clause.
  1101. *
  1102. * @param string $where WHERE clause to use for limiting the selection of rows to compact the ordering values.
  1103. *
  1104. * @return mixed Boolean True on success.
  1105. *
  1106. * @link http://docs.joomla.org/JTable/reorder
  1107. * @since 11.1
  1108. */
  1109. public function reorder($where = '')
  1110. {
  1111. // If there is no ordering field set an error and return false.
  1112. if (!property_exists($this, 'ordering'))
  1113. {
  1114. throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
  1115. }
  1116. $k = $this->_tbl_key;
  1117. // Get the primary keys and ordering values for the selection.
  1118. $query = $this->_db->getQuery(true);
  1119. $query->select(implode(',', $this->_tbl_keys) . ', ordering');
  1120. $query->from($this->_tbl);
  1121. $query->where('ordering >= 0');
  1122. $query->order('ordering');
  1123. // Setup the extra where and ordering clause data.
  1124. if ($where)
  1125. {
  1126. $query->where($where);
  1127. }
  1128. $this->_db->setQuery($query);
  1129. $rows = $this->_db->loadObjectList();
  1130. // Compact the ordering values.
  1131. foreach ($rows as $i => $row)
  1132. {
  1133. // Make sure the ordering is a positive integer.
  1134. if ($row->ordering >= 0)
  1135. {
  1136. // Only update rows that are necessary.
  1137. if ($row->ordering != $i + 1)
  1138. {
  1139. // Update the row ordering field.
  1140. $query = $this->_db->getQuery(true);
  1141. $query->update($this->_tbl);
  1142. $query->set('ordering = ' . ($i + 1));
  1143. $this->appendPrimaryKeys($query, $row);
  1144. $this->_db->setQuery($query);
  1145. $this->_db->execute();
  1146. }
  1147. }
  1148. }
  1149. return true;
  1150. }
  1151. /**
  1152. * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
  1153. * Negative numbers move the row up in the sequence and positive numbers move it down.
  1154. *
  1155. * @param integer $delta The direction and magnitude to move the row in the ordering sequence.
  1156. * @param string $where WHERE clause to use for limiting the selection of rows to compact the
  1157. * ordering values.
  1158. *
  1159. * @return mixed Boolean True on success.
  1160. *
  1161. * @link http://docs.joomla.org/JTable/move
  1162. * @since 11.1
  1163. * @throws UnexpectedValueException
  1164. */
  1165. public function move($delta, $where = '')
  1166. {
  1167. // If there is no ordering field set an error and return false.
  1168. if (!property_exists($this, 'ordering'))
  1169. {
  1170. throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
  1171. }
  1172. // If the change is none, do nothing.
  1173. if (empty($delta))
  1174. {
  1175. return true;
  1176. }
  1177. $k = $this->_tbl_key;
  1178. $row = null;
  1179. $query = $this->_db->getQuery(true);
  1180. // Select the primary key and ordering values from the table.
  1181. $query->select(implode(',', $this->_tbl_keys) . ', ordering');
  1182. $query->from($this->_tbl);
  1183. // If the movement delta is negative move the row up.
  1184. if ($delta < 0)
  1185. {
  1186. $query->where('ordering < ' . (int) $this->ordering);
  1187. $query->order('ordering DESC');
  1188. }
  1189. // If the movement delta is positive move the row down.
  1190. elseif ($delta > 0)
  1191. {
  1192. $query->where('ordering > ' . (int) $this->ordering);
  1193. $query->order('ordering ASC');
  1194. }
  1195. // Add the custom WHERE clause if set.
  1196. if ($where)
  1197. {
  1198. $query->where($where);
  1199. }
  1200. // Select the first row with the criteria.
  1201. $this->_db->setQuery($query, 0, 1);
  1202. $row = $this->_db->loadObject();
  1203. // If a row is found, move the item.
  1204. if (!empty($row))
  1205. {
  1206. // Update the ordering field for this instance to the row's ordering value.
  1207. $query = $this->_db->getQuery(true);
  1208. $query->update($this->_tbl);
  1209. $query->set('ordering = ' . (int) $row->ordering);
  1210. $this->appendPrimaryKeys($query);
  1211. $this->_db->setQuery($query);
  1212. $this->_db->execute();
  1213. // Update the ordering field for the row to this instance's ordering value.
  1214. $query = $this->_db->getQuery(true);
  1215. $query->update($this->_tbl);
  1216. $query->set('ordering = ' . (int) $this->ordering);
  1217. $this->appendPrimaryKeys($query, $row);
  1218. $this->_db->setQuery($query);
  1219. $this->_db->execute();
  1220. // Update the instance value.
  1221. $this->ordering = $row->ordering;
  1222. }
  1223. else
  1224. {
  1225. // Update the ordering field for this instance.
  1226. $query = $this->_db->getQuery(true);
  1227. $query->update($this->_tbl);
  1228. $query->set('ordering = ' . (int) $this->ordering);
  1229. $this->appendPrimaryKeys($query);
  1230. $this->_db->setQuery($query);
  1231. $this->_db->execute();
  1232. }
  1233. return true;
  1234. }
  1235. /**
  1236. * Method to set the publishing state for a row or list of rows in the database
  1237. * table. The method respects checked out rows by other users and will attempt
  1238. * to checkin rows that it can after adjustments are made.
  1239. *
  1240. * @param mixed $pks An optional array of primary key values to update.
  1241. * If not set the instance property value is used.
  1242. * @param integer $state The publishing state. eg. [0 = unpublished, 1 = published]
  1243. * @param integer $userId The user id of the user performing the operation.
  1244. *
  1245. * @return boolean True on success; false if $pks is empty.
  1246. *
  1247. * @link http://docs.joomla.org/JTable/publish
  1248. * @since 11.1
  1249. */
  1250. public function publish($pks = null, $state = 1, $userId = 0)
  1251. {
  1252. $k = $this->_tbl_keys;
  1253. if (!is_null($pks))
  1254. {
  1255. foreach ($pks AS $key => $pk)
  1256. {
  1257. if (!is_array($pk))
  1258. {
  1259. $pks[$key] = array($this->_tbl_key => $pk);
  1260. }
  1261. }
  1262. }
  1263. $userId = (int) $userId;
  1264. $state = (int) $state;
  1265. // If there are no primary keys set check to see if the instance key is set.
  1266. if (empty($pks))
  1267. {
  1268. $pk = array();
  1269. foreach ($this->_tbl_keys AS $key)
  1270. {
  1271. if ($this->$key)
  1272. {
  1273. $pk[$this->$key] = $this->$key;
  1274. }
  1275. // We don't have a full primary key - return false
  1276. else
  1277. {
  1278. return false;
  1279. }
  1280. }
  1281. $pks = array($pk);
  1282. }
  1283. foreach ($pks AS $pk)
  1284. {
  1285. // Update the publishing state for rows with the given primary keys.
  1286. $query = $this->_db->getQuery(true);
  1287. $query->update($this->_tbl);
  1288. $query->set('published = ' . (int) $state);
  1289. // Determine if there is checkin support for the table.
  1290. if (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'))
  1291. {
  1292. $query->where('(checked_out = 0 OR checked_out = ' . (int) $userId . ')');
  1293. $checkin = true;
  1294. }
  1295. else
  1296. {
  1297. $checkin = false;
  1298. }
  1299. // Build the WHERE clause for the primary keys.
  1300. $this->appendPrimaryKeys($query, $pk);
  1301. $this->_db->setQuery($query);
  1302. $this->_db->execute();
  1303. // If checkin is supported and all rows were adjusted, check them in.
  1304. if ($checkin && (count($pks) == $this->_db->getAffectedRows()))
  1305. {
  1306. $this->checkin($pk);
  1307. }
  1308. $ours = true;
  1309. foreach ($this->_tbl_keys AS $key)
  1310. {
  1311. if ($this->$key != $pk[$key])
  1312. {
  1313. $ours = false;
  1314. }
  1315. }
  1316. if ($ours)
  1317. {
  1318. $this->published = $state;
  1319. }
  1320. }
  1321. $this->setError('');
  1322. return true;
  1323. }
  1324. /**
  1325. * Method to lock the database table for writing.
  1326. *
  1327. * @return boolean True on success.
  1328. *
  1329. * @since 11.1
  1330. * @throws RuntimeException
  1331. */
  1332. protected function _lock()
  1333. {
  1334. $this->_db->lockTable($this->_tbl);
  1335. $this->_locked = true;
  1336. return true;
  1337. }
  1338. /**
  1339. * Method to unlock the database table for writing.
  1340. *
  1341. * @return boolean True on success.
  1342. *
  1343. * @since 11.1
  1344. */
  1345. protected function _unlock()
  1346. {
  1347. $this->_db->unlockTables();
  1348. $this->_locked = false;
  1349. return true;
  1350. }
  1351. }