Ensuring tests pass after bringing back support for db DSN connection. Rel. [501...
[akelos.git] / lib / AkActiveRecord / AkActsAsBehaviours / AkActsAsList.php
blob9b1a61193c1fc5001b02c58a21dcee78faed66ec
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 /**
12 * @package ActiveRecord
13 * @subpackage Behaviours
14 * @author Bermi Ferrer <bermi a.t akelos c.om>
15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
19 require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkObserver.php');
21 /**
22 * This act provides the capabilities for sorting and reordering a number of objects in list.
23 * The class that has this specified needs to have a "position" column defined as an integer on
24 * the mapped database table.
26 * Todo list example:
27 * <code>
28 * class TodoList extends ActiveRecord
29 * {
30 * var $has_many = array('todo_items', array('order' => "position"));
31 * }
33 * class TodoItem extends ActiveRecord
34 * {
35 * var $belongs_to = 'todo_list';
36 * var $acts_as = array('list' => array('scope' => 'todo_list'));
37 * }
39 * $TodoList =& new TodoList();
41 * $TodoList->list->moveToBottom();
42 * </code>
44 class AkActsAsList extends AkObserver
46 var $column = 'position';
47 var $scope = '';
48 var $scope_condition;
49 /**
50 * Configuration options are:
52 * * +column+ - specifies the column name to use for keeping the position integer (default: position)
53 * * +scope+ - restricts what is to be considered a list.
54 * Example:
56 * class TodoTask extends ActiveRecord
57 * {
58 * var $acts_as = array('list'=> array('scope'=> array('todo_list_id','completed = 0')));
59 * var $belongs_to = 'todo_list';
60 * }
62 var $_ActiveRecordInstance;
63 function AkActsAsList(&$ActiveRecordInstance)
65 $this->_ActiveRecordInstance =& $ActiveRecordInstance;
68 function init($options = array())
70 $this->column = !empty($options['column']) ? $options['column'] : $this->column;
71 $this->scope = !empty($options['scope']) ? $options['scope'] : $this->scope;
72 return $this->_ensureIsActiveRecordInstance($this->_ActiveRecordInstance);
75 function _ensureIsActiveRecordInstance(&$ActiveRecordInstance)
77 if(is_object($ActiveRecordInstance) && method_exists($ActiveRecordInstance,'actsLike')){
78 $this->_ActiveRecordInstance =& $ActiveRecordInstance;
79 if(!$this->_ActiveRecordInstance->hasColumn($this->column)){
80 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);
81 unset($this->_ActiveRecordInstance->list);
82 return false;
83 }else {
84 $this->observe(&$ActiveRecordInstance);
86 }else{
87 trigger_error(Ak::t('You are trying to set an object that is not an active record.'), E_USER_ERROR);
88 return false;
90 return true;
93 function reloadActiveRecordInstance(&$listObject)
95 AK_PHP5 ? null : $listObject->list->setActiveRecordInstance(&$listObject);
98 function getType()
100 return 'list';
103 function beforeDestroy(&$object)
105 $object->list->_ActiveRecordInstance->reload();
106 return true;
109 function afterSave(&$object)
111 $object->list->_ActiveRecordInstance->reload();
112 return true;
115 function afterDestroy(&$object)
117 return $object->list->removeFromList();
120 function beforeCreate(&$object)
122 $object->list->_addToBottom();
123 return true;
127 * All the methods available to a record that has had <tt>acts_as list</tt> specified. Each method works
128 * by assuming the object to be the item in the list, so <tt>$Chapter->list->moveLower()</tt> would move that chapter
129 * lower in the list of all chapters. Likewise, <tt>$Chapter->list->isFirst()</tt> would return true if that chapter is
130 * the first in the list of all chapters.
134 function insertAt($position = 1)
136 return $this->insertAtPosition($position);
140 * This function saves the object using save() before inserting it into the list
142 function insertAtPosition($position)
144 $this->_ActiveRecordInstance->transactionStart();
145 if($this->_ActiveRecordInstance->isNewRecord()){
146 $this->_ActiveRecordInstance->save();
148 $this->removeFromList();
149 $this->incrementPositionsOnLowerItems($position);
151 $this->_ActiveRecordInstance->updateAttribute($this->column, $position);
152 if($this->_ActiveRecordInstance->transactionHasFailed()){
153 $this->_ActiveRecordInstance->transactionComplete();
154 return false;
157 $this->_ActiveRecordInstance->transactionComplete();
158 return true;
161 function moveLower()
163 $this->_ActiveRecordInstance->transactionStart();
164 if($LowerItem = $this->getLowerItem()){
165 if($LowerItem->list->decrementPosition() && $this->incrementPosition()){
166 $this->_ActiveRecordInstance->transactionComplete();
167 return true;
168 }else{
169 $this->_ActiveRecordInstance->transactionFail();
172 $this->_ActiveRecordInstance->transactionComplete();
173 return false;
176 function moveHigher()
178 $this->_ActiveRecordInstance->transactionStart();
179 if($HigherItem = $this->getHigherItem()){
180 if($HigherItem->list->incrementPosition() && $this->decrementPosition()){
181 $this->_ActiveRecordInstance->transactionComplete();
182 return true;
183 }else{
184 $this->_ActiveRecordInstance->transactionFail();
187 $this->_ActiveRecordInstance->transactionComplete();
188 return false;
191 function moveToBottom()
193 if($this->isInList()){
194 $this->_ActiveRecordInstance->transactionStart();
195 if($this->decrementPositionsOnLowerItems() && $this->assumeBottomPosition()){
196 $this->_ActiveRecordInstance->transactionComplete();
197 return true;
198 }else{
199 $this->_ActiveRecordInstance->transactionFail();
201 $this->_ActiveRecordInstance->transactionComplete();
203 return false;
206 function moveToTop()
208 if($this->isInList()){
209 $this->_ActiveRecordInstance->transactionStart();
210 if($this->incrementPositionsOnHigherItems() && $this->assumeTopPosition()){
211 $this->_ActiveRecordInstance->transactionComplete();
212 return true;
213 }else{
214 $this->_ActiveRecordInstance->transactionFail();
216 $this->_ActiveRecordInstance->transactionComplete();
218 return false;
221 function assumeBottomPosition()
223 return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->getBottomPosition($this->_ActiveRecordInstance->getId()) + 1);
226 function assumeTopPosition()
228 return $this->_ActiveRecordInstance->updateAttribute($this->column, 1);
231 function getBottomPosition($except = null)
233 return ($item = $this->getBottomItem($except)) ? $item->getAttribute($this->column) : 0;
237 * Returns an instance of the item that's on the very bottom of the list. Returns false if there's none
239 function getBottomItem($except = null)
241 $conditions = $this->getScopeCondition();
243 if(isset($except)){
244 $conditions .= " AND id != $except";
246 return $this->_ActiveRecordInstance->find('first', array('conditions' => $conditions, 'order' => "{$this->column} DESC"));
249 function isInList()
251 return !empty($this->_ActiveRecordInstance->{$this->column});
255 * This has the effect of moving all the higher items up one.
257 function decrementPositionsOnHigherItems($position)
259 return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} <= $position");
263 * This has the effect of moving all the lower items up one.
265 function decrementPositionsOnLowerItems()
267 if($this->isInList()){
268 $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} > ".$this->_ActiveRecordInstance->getAttribute($this->column));
269 return true;
271 return false;
275 * This has the effect of moving all the higher items down one.
277 function incrementPositionsOnHigherItems()
279 if($this->isInList()){
280 $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} < ".$this->_ActiveRecordInstance->getAttribute($this->column));
281 return true;
283 return false;
287 * This has the effect of moving all the lower items down one.
289 function incrementPositionsOnLowerItems($position)
291 return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} >= $position");
294 function incrementPositionsOnAllItems()
296 return $this->_ActiveRecordInstance->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition());
299 function removeFromList()
301 if($this->isInList()){
302 if($this->decrementPositionsOnLowerItems()){
303 $this->_ActiveRecordInstance->{$this->column} = null;
304 return true;
307 return false;
310 function incrementPosition()
312 if($this->isInList()){
313 return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) + 1);
315 return false;
318 function decrementPosition()
320 if($this->isInList()){
321 return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) - 1);
323 return false;
326 function isFirst()
328 if($this->isInList()){
329 return $this->_ActiveRecordInstance->getAttribute($this->column) == 1;
331 return false;
334 function isLast()
336 if($this->isInList()){
337 return $this->_ActiveRecordInstance->getAttribute($this->column) == $this->getBottomPosition();
339 return false;
342 function getHigherItem()
344 if($this->isInList()){
345 return $this->_ActiveRecordInstance->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance->getAttribute($this->column) - 1)));
347 return false;
350 function getLowerItem()
352 if($this->isInList()){
353 return $this->_ActiveRecordInstance->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance->getAttribute($this->column) + 1)));
355 return false;
358 function addToListTop()
360 $this->incrementPositionsOnAllItems();
363 function _addToBottom()
365 $this->_ActiveRecordInstance->{$this->column} = $this->getBottomPosition() + 1;
368 function getScopeCondition()
370 if (!empty($this->variable_scope_condition)){
371 return $this->_ActiveRecordInstance->_getVariableSqlCondition($this->variable_scope_condition);
373 // True condition in case we don't have a scope
374 }elseif(empty($this->scope_condition) && empty($this->scope)){
375 $this->scope_condition = ($this->_ActiveRecordInstance->_db->type() == 'postgre') ? 'true' : '1';
376 }elseif (!empty($this->scope)){
377 $this->setScopeCondition(join(' AND ',array_map(array(&$this,'getScopedColumn'),(array)$this->scope)));
379 return $this->scope_condition;
382 function setScopeCondition($scope_condition)
384 if(!is_array($scope_condition) && strstr($scope_condition, '?')){
385 $this->variable_scope_condition = $scope_condition;
386 }else{
387 $this->scope_condition = $scope_condition;
391 function getScopedColumn($column)
393 if($this->_ActiveRecordInstance->hasColumn($column)){
394 $value = $this->_ActiveRecordInstance->get($column);
395 $condition = $this->_ActiveRecordInstance->getAttributeCondition($value);
396 $value = $this->_ActiveRecordInstance->castAttributeForDatabase($column, $value);
397 return $column.' '.str_replace('?', $value, $condition);
398 }else{
399 return $column;