PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/table/table.php

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