PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/database/table.php

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