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 // +----------------------------------------------------------------------+
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');
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.
28 * class TodoList extends ActiveRecord
30 * var $has_many = array('todo_items', array('order' => "position"));
33 * class TodoItem extends ActiveRecord
35 * var $belongs_to = 'todo_list';
36 * var $acts_as = array('list' => array('scope' => 'todo_list'));
39 * $TodoList =& new TodoList();
41 * $TodoList->list->moveToBottom();
44 class AkActsAsList
extends AkObserver
46 var $column = 'position';
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.
56 * class TodoTask extends ActiveRecord
58 * var $acts_as = array('list'=> array('scope'=> array('todo_list_id','completed = 0')));
59 * var $belongs_to = 'todo_list';
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);
84 $this->observe(&$ActiveRecordInstance);
87 trigger_error(Ak
::t('You are trying to set an object that is not an active record.'), E_USER_ERROR
);
93 function reloadActiveRecordInstance(&$listObject)
95 AK_PHP5 ?
null : $listObject->list->setActiveRecordInstance(&$listObject);
103 function beforeDestroy(&$object)
105 $object->list->_ActiveRecordInstance
->reload();
109 function afterSave(&$object)
111 $object->list->_ActiveRecordInstance
->reload();
115 function afterDestroy(&$object)
117 return $object->list->removeFromList();
120 function beforeCreate(&$object)
122 $object->list->_addToBottom();
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();
157 $this->_ActiveRecordInstance
->transactionComplete();
163 $this->_ActiveRecordInstance
->transactionStart();
164 if($LowerItem = $this->getLowerItem()){
165 if($LowerItem->list->decrementPosition() && $this->incrementPosition()){
166 $this->_ActiveRecordInstance
->transactionComplete();
169 $this->_ActiveRecordInstance
->transactionFail();
172 $this->_ActiveRecordInstance
->transactionComplete();
176 function moveHigher()
178 $this->_ActiveRecordInstance
->transactionStart();
179 if($HigherItem = $this->getHigherItem()){
180 if($HigherItem->list->incrementPosition() && $this->decrementPosition()){
181 $this->_ActiveRecordInstance
->transactionComplete();
184 $this->_ActiveRecordInstance
->transactionFail();
187 $this->_ActiveRecordInstance
->transactionComplete();
191 function moveToBottom()
193 if($this->isInList()){
194 $this->_ActiveRecordInstance
->transactionStart();
195 if($this->decrementPositionsOnLowerItems() && $this->assumeBottomPosition()){
196 $this->_ActiveRecordInstance
->transactionComplete();
199 $this->_ActiveRecordInstance
->transactionFail();
201 $this->_ActiveRecordInstance
->transactionComplete();
208 if($this->isInList()){
209 $this->_ActiveRecordInstance
->transactionStart();
210 if($this->incrementPositionsOnHigherItems() && $this->assumeTopPosition()){
211 $this->_ActiveRecordInstance
->transactionComplete();
214 $this->_ActiveRecordInstance
->transactionFail();
216 $this->_ActiveRecordInstance
->transactionComplete();
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();
244 $conditions .= " AND id != $except";
246 return $this->_ActiveRecordInstance
->find('first', array('conditions' => $conditions, 'order' => "{$this->column} DESC"));
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
));
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
));
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;
310 function incrementPosition()
312 if($this->isInList()){
313 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, $this->_ActiveRecordInstance
->getAttribute($this->column
) +
1);
318 function decrementPosition()
320 if($this->isInList()){
321 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, $this->_ActiveRecordInstance
->getAttribute($this->column
) - 1);
328 if($this->isInList()){
329 return $this->_ActiveRecordInstance
->getAttribute($this->column
) == 1;
336 if($this->isInList()){
337 return $this->_ActiveRecordInstance
->getAttribute($this->column
) == $this->getBottomPosition();
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)));
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)));
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;
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);