PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/database/table.php

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