PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/AkActiveRecord/AkActsAsList.php

http://akelosframework.googlecode.com/
PHP | 411 lines | 276 code | 58 blank | 77 comment | 35 complexity | a13a7f30237e042226413cecffc86fce MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. // +----------------------------------------------------------------------+
  4. // | Akelos Framework - http://www.akelos.org |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
  7. // | Released under the GNU Lesser General Public License, see LICENSE.txt|
  8. // +----------------------------------------------------------------------+
  9. /**
  10. * @package AkelosFramework
  11. * @subpackage AkActiveRecord
  12. * @author Bermi Ferrer <bermi a.t akelos c.om>
  13. * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
  14. * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
  15. */
  16. require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkObserver.php');
  17. /**
  18. * This act provides the capabilities for sorting and reordering a number of objects in list.
  19. * The class that has this specified needs to have a "position" column defined as an integer on
  20. * the mapped database table.
  21. *
  22. * Todo list example:
  23. *
  24. * class TodoList extends AkActiveRecord
  25. * {
  26. * var $has_many = array('todo_items', array('order' => "position"));
  27. * }
  28. *
  29. * class TodoItem extends AkActiveRecord
  30. * {
  31. * var $belongs_to = 'todo_list';
  32. * var $acts_as = array('list' => array('scope' => 'todo_list'));
  33. * }
  34. *
  35. * $TodoList =& new TodoList();
  36. *
  37. * $TodoList->list->.moveToBottom
  38. * todo_list.last.moveHigher
  39. */
  40. class AkActsAsList extends AkObserver
  41. {
  42. var $column = 'position';
  43. var $scope = '';
  44. var $scope_condition;
  45. /**
  46. * Configuration options are:
  47. *
  48. * * +column+ - specifies the column name to use for keeping the position integer (default: position)
  49. * * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
  50. * (if that hasn't been already) and use that as the foreign key restriction. It's also possible
  51. * to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
  52. * Example:
  53. *
  54. * class TodoTask extends ActiveRecord
  55. * {
  56. * var $acts_as = array('list'=> array('scope'=> array('todo_list_id','completed = 0')));
  57. * var $belongs_to = 'todo_list';
  58. * }
  59. */
  60. var $_ActiveRecordInstance;
  61. function AkActsAsList(&$ActiveRecordInstance)
  62. {
  63. $this->_ActiveRecordInstance =& $ActiveRecordInstance;
  64. }
  65. function init($options = array())
  66. {
  67. $this->column = !empty($options['column']) ? $options['column'] : $this->column;
  68. $this->scope = !empty($options['scope']) ? $options['scope'] : $this->scope;
  69. return $this->_ensureIsActiveRecordInstance($this->_ActiveRecordInstance);
  70. }
  71. function _ensureIsActiveRecordInstance(&$ActiveRecordInstance)
  72. {
  73. if(is_object($ActiveRecordInstance) && method_exists($ActiveRecordInstance,'actsLike')){
  74. $this->_ActiveRecordInstance =& $ActiveRecordInstance;
  75. if(!$this->_ActiveRecordInstance->hasColumn($this->column)){
  76. trigger_error(Ak::t('Could not find the column "%column" into the table "%table". This column is needed in order to make "%model" act as a list.',array('%column'=>$this->column,'%table'=>$this->_ActiveRecordInstance->getTableName(),'%model'=>$this->_ActiveRecordInstance->getModelName())),E_USER_ERROR);
  77. unset($this->_ActiveRecordInstance->list);
  78. return false;
  79. }else {
  80. $this->observe(&$ActiveRecordInstance);
  81. }
  82. }else{
  83. trigger_error(Ak::t('You are trying to set an object that is not an active record.'), E_USER_ERROR);
  84. return false;
  85. }
  86. return true;
  87. }
  88. function reloadActiveRecordInstance(&$listObject)
  89. {
  90. AK_PHP5 ? null : $listObject->list->setActiveRecordInstance(&$listObject);
  91. }
  92. function getType()
  93. {
  94. return 'list';
  95. }
  96. function beforeDestroy(&$object)
  97. {
  98. $object->list->_ActiveRecordInstance->reload();
  99. return true;
  100. }
  101. function afterSave(&$object)
  102. {
  103. $object->list->_ActiveRecordInstance->reload();
  104. return true;
  105. }
  106. function afterDestroy(&$object)
  107. {
  108. return $object->list->removeFromList();
  109. }
  110. function beforeCreate(&$object)
  111. {
  112. $object->list->_addToBottom();
  113. return true;
  114. }
  115. /**
  116. * All the methods available to a record that has had <tt>acts_as list</tt> specified. Each method works
  117. * by assuming the object to be the item in the list, so <tt>$Chapter->list->moveLower()</tt> would move that chapter
  118. * lower in the list of all chapters. Likewise, <tt>$Chapter->list->isFirst()</tt> would return true if that chapter is
  119. * the first in the list of all chapters.
  120. */
  121. function insertAt($position = 1)
  122. {
  123. return $this->insertAtPosition($position);
  124. }
  125. function moveLower()
  126. {
  127. $this->_ActiveRecordInstance->transactionStart();
  128. if($LowerItem = $this->getLowerItem()){
  129. if($LowerItem->list->decrementPosition() && $this->incrementPosition()){
  130. $this->_ActiveRecordInstance->transactionComplete();
  131. return true;
  132. }else{
  133. $this->_ActiveRecordInstance->transactionFail();
  134. }
  135. }
  136. $this->_ActiveRecordInstance->transactionComplete();
  137. return false;
  138. }
  139. function moveHigher()
  140. {
  141. $this->_ActiveRecordInstance->transactionStart();
  142. if($HigherItem = $this->getHigherItem()){
  143. if($HigherItem->list->incrementPosition() && $this->decrementPosition()){
  144. $this->_ActiveRecordInstance->transactionComplete();
  145. return true;
  146. }else{
  147. $this->_ActiveRecordInstance->transactionFail();
  148. }
  149. }
  150. $this->_ActiveRecordInstance->transactionComplete();
  151. return false;
  152. }
  153. function moveToBottom()
  154. {
  155. if($this->isInList()){
  156. $this->_ActiveRecordInstance->transactionStart();
  157. if($this->decrementPositionsOnLowerItems() && $this->assumeBottomPosition()){
  158. $this->_ActiveRecordInstance->transactionComplete();
  159. return true;
  160. }else{
  161. $this->_ActiveRecordInstance->transactionFail();
  162. }
  163. $this->_ActiveRecordInstance->transactionComplete();
  164. }
  165. return false;
  166. }
  167. /**
  168. * This has the effect of moving all the lower items up one.
  169. */
  170. function decrementPositionsOnLowerItems()
  171. {
  172. if($this->isInList()){
  173. $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} > ".$this->_ActiveRecordInstance->getAttribute($this->column));
  174. return true;
  175. }
  176. return false;
  177. }
  178. function assumeBottomPosition()
  179. {
  180. return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->getBottomPosition($this->_ActiveRecordInstance->getId()) + 1);
  181. }
  182. function getBottomPosition($except = null)
  183. {
  184. return ($item = $this->getBottomItem($except)) ? $item->getAttribute($this->column) : 0;
  185. }
  186. /**
  187. * Returns an instance of the item that's on the very bottom of the list. Returns false if there's none
  188. */
  189. function getBottomItem($except = null)
  190. {
  191. $conditions = $this->getScopeCondition();
  192. if(isset($except)){
  193. $conditions .= " AND id != $except";
  194. }
  195. return $this->_ActiveRecordInstance->find('first', array('conditions' => $conditions, 'order' => "{$this->column} DESC"));
  196. }
  197. function isInList()
  198. {
  199. return !empty($this->_ActiveRecordInstance->{$this->column});
  200. }
  201. function moveToTop()
  202. {
  203. if($this->isInList()){
  204. $this->_ActiveRecordInstance->transactionStart();
  205. if($this->incrementPositionsOnHigherItems() && $this->assumeTopPosition()){
  206. $this->_ActiveRecordInstance->transactionComplete();
  207. return true;
  208. }else{
  209. $this->_ActiveRecordInstance->transactionFail();
  210. }
  211. $this->_ActiveRecordInstance->transactionComplete();
  212. }
  213. return false;
  214. }
  215. /**
  216. * This has the effect of moving all the higher items down one.
  217. */
  218. function incrementPositionsOnHigherItems()
  219. {
  220. if($this->isInList()){
  221. $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} < ".$this->_ActiveRecordInstance->getAttribute($this->column));
  222. return true;
  223. }
  224. return false;
  225. }
  226. function assumeTopPosition()
  227. {
  228. return $this->_ActiveRecordInstance->updateAttribute($this->column, 1);
  229. }
  230. function removeFromList()
  231. {
  232. if($this->isInList()){
  233. if($this->decrementPositionsOnLowerItems()){
  234. $this->_ActiveRecordInstance->{$this->column} = null;
  235. return true;
  236. }
  237. }
  238. return false;
  239. }
  240. function incrementPosition()
  241. {
  242. if($this->isInList()){
  243. return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) + 1);
  244. }
  245. return false;
  246. }
  247. function decrementPosition()
  248. {
  249. if($this->isInList()){
  250. return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) - 1);
  251. }
  252. return false;
  253. }
  254. function isFirst()
  255. {
  256. if($this->isInList()){
  257. return $this->_ActiveRecordInstance->getAttribute($this->column) == 1;
  258. }
  259. return false;
  260. }
  261. function isLast()
  262. {
  263. if($this->isInList()){
  264. return $this->_ActiveRecordInstance->getAttribute($this->column) == $this->getBottomPosition();
  265. }
  266. return false;
  267. }
  268. function getHigherItem()
  269. {
  270. if($this->isInList()){
  271. return $this->_ActiveRecordInstance->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance->getAttribute($this->column) - 1)));
  272. }
  273. return false;
  274. }
  275. function getLowerItem()
  276. {
  277. if($this->isInList()){
  278. return $this->_ActiveRecordInstance->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance->getAttribute($this->column) + 1)));
  279. }
  280. return false;
  281. }
  282. function addToListTop()
  283. {
  284. $this->incrementPositionsOnAllItems();
  285. }
  286. function _addToBottom()
  287. {
  288. $this->_ActiveRecordInstance->{$this->column} = $this->getBottomPosition() + 1;
  289. }
  290. function getScopeCondition()
  291. {
  292. // An allways true condition in case no scope has been specified
  293. if(empty($this->scope_condition) && empty($this->scope)){
  294. $this->scope_condition = (substr($this->_ActiveRecordInstance->_db->databaseType,0,4) == 'post') ? 'true' : '1';
  295. }elseif (!empty($this->scope)){
  296. $this->setScopeCondition(join(' AND ',array_map(array(&$this,'getScopedColumn'),(array)$this->scope)));
  297. }
  298. return $this->scope_condition;
  299. }
  300. function setScopeCondition($scope_condition)
  301. {
  302. $this->scope_condition = $scope_condition;
  303. }
  304. function getScopedColumn($column)
  305. {
  306. if($this->_ActiveRecordInstance->hasColumn($column)){
  307. $value = $this->_ActiveRecordInstance->get($column);
  308. $condition = $this->_ActiveRecordInstance->getAttributeCondition($value);
  309. $value = $this->_ActiveRecordInstance->castAttributeForDatabase($column, $value);
  310. return $column.' '.str_replace('?', $value, $condition);
  311. }else{
  312. return $column;
  313. }
  314. }
  315. /**
  316. * This has the effect of moving all the higher items up one.
  317. */
  318. function decrementPositionsOnHigherItems($position)
  319. {
  320. return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} <= $position");
  321. }
  322. /**
  323. * This has the effect of moving all the lower items down one.
  324. */
  325. function incrementPositionsOnLowerItems($position)
  326. {
  327. return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} >= $position");
  328. }
  329. function incrementPositionsOnAllItems()
  330. {
  331. return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition());
  332. }
  333. /**
  334. * This function saves the object using save() before inserting it into the list
  335. */
  336. function insertAtPosition($position)
  337. {
  338. $this->_ActiveRecordInstance->transactionStart();
  339. if($this->_ActiveRecordInstance->isNewRecord()){
  340. $this->_ActiveRecordInstance->save();
  341. }
  342. $this->removeFromList();
  343. $this->incrementPositionsOnLowerItems($position);
  344. $this->_ActiveRecordInstance->updateAttribute($this->column, $position);
  345. if($this->_ActiveRecordInstance->transactionHasFailed()){
  346. $this->_ActiveRecordInstance->transactionComplete();
  347. return false;
  348. }
  349. $this->_ActiveRecordInstance->transactionComplete();
  350. return true;
  351. }
  352. }
  353. ?>