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);
141 $this->_ActiveRecordInstance
->transactionStart();
142 if($LowerItem = $this->getLowerItem()){
143 if($LowerItem->list->decrementPosition() && $this->incrementPosition()){
144 $this->_ActiveRecordInstance
->transactionComplete();
147 $this->_ActiveRecordInstance
->transactionFail();
150 $this->_ActiveRecordInstance
->transactionComplete();
154 function moveHigher()
156 $this->_ActiveRecordInstance
->transactionStart();
157 if($HigherItem = $this->getHigherItem()){
158 if($HigherItem->list->incrementPosition() && $this->decrementPosition()){
159 $this->_ActiveRecordInstance
->transactionComplete();
162 $this->_ActiveRecordInstance
->transactionFail();
165 $this->_ActiveRecordInstance
->transactionComplete();
170 function moveToBottom()
172 if($this->isInList()){
173 $this->_ActiveRecordInstance
->transactionStart();
174 if($this->decrementPositionsOnLowerItems() && $this->assumeBottomPosition()){
175 $this->_ActiveRecordInstance
->transactionComplete();
178 $this->_ActiveRecordInstance
->transactionFail();
180 $this->_ActiveRecordInstance
->transactionComplete();
186 * This has the effect of moving all the lower items up one.
188 function decrementPositionsOnLowerItems()
190 if($this->isInList()){
191 $this->_ActiveRecordInstance
->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} > ".$this->_ActiveRecordInstance
->getAttribute($this->column
));
197 function assumeBottomPosition()
199 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, $this->getBottomPosition($this->_ActiveRecordInstance
->getId()) +
1);
202 function getBottomPosition($except = null)
204 return ($item = $this->getBottomItem($except)) ?
$item->getAttribute($this->column
) : 0;
208 * Returns an instance of the item that's on the very bottom of the list. Returns false if there's none
210 function getBottomItem($except = null)
212 $conditions = $this->getScopeCondition();
215 $conditions .= " AND id != $except";
217 return $this->_ActiveRecordInstance
->find('first', array('conditions' => $conditions, 'order' => "{$this->column} DESC"));
223 return !empty($this->_ActiveRecordInstance
->{$this->column
});
229 if($this->isInList()){
230 $this->_ActiveRecordInstance
->transactionStart();
231 if($this->incrementPositionsOnHigherItems() && $this->assumeTopPosition()){
232 $this->_ActiveRecordInstance
->transactionComplete();
235 $this->_ActiveRecordInstance
->transactionFail();
237 $this->_ActiveRecordInstance
->transactionComplete();
243 * This has the effect of moving all the higher items down one.
245 function incrementPositionsOnHigherItems()
247 if($this->isInList()){
248 $this->_ActiveRecordInstance
->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} < ".$this->_ActiveRecordInstance
->getAttribute($this->column
));
254 function assumeTopPosition()
256 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, 1);
260 function removeFromList()
262 if($this->isInList()){
263 if($this->decrementPositionsOnLowerItems()){
264 $this->_ActiveRecordInstance
->{$this->column
} = null;
272 function incrementPosition()
274 if($this->isInList()){
275 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, $this->_ActiveRecordInstance
->getAttribute($this->column
) +
1);
280 function decrementPosition()
282 if($this->isInList()){
283 return $this->_ActiveRecordInstance
->updateAttribute($this->column
, $this->_ActiveRecordInstance
->getAttribute($this->column
) - 1);
290 if($this->isInList()){
291 return $this->_ActiveRecordInstance
->getAttribute($this->column
) == 1;
298 if($this->isInList()){
299 return $this->_ActiveRecordInstance
->getAttribute($this->column
) == $this->getBottomPosition();
304 function getHigherItem()
306 if($this->isInList()){
307 return $this->_ActiveRecordInstance
->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance
->getAttribute($this->column
) - 1)));
312 function getLowerItem()
314 if($this->isInList()){
315 return $this->_ActiveRecordInstance
->find('first', array('conditions' => $this->getScopeCondition()." AND {$this->column} = ".($this->_ActiveRecordInstance
->getAttribute($this->column
) +
1)));
321 function addToListTop()
323 $this->incrementPositionsOnAllItems();
326 function _addToBottom()
328 $this->_ActiveRecordInstance
->{$this->column
} = $this->getBottomPosition() +
1;
331 function getScopeCondition()
333 if (!empty($this->variable_scope_condition
)){
334 return $this->_ActiveRecordInstance
->_getVariableSqlCondition($this->variable_scope_condition
);
336 // True condition in case we don't have a scope
337 }elseif(empty($this->scope_condition
) && empty($this->scope
)){
338 $this->scope_condition
= (substr($this->_ActiveRecordInstance
->_db
->databaseType
,0,4) == 'post') ?
'true' : '1';
339 }elseif (!empty($this->scope
)){
340 $this->setScopeCondition(join(' AND ',array_map(array(&$this,'getScopedColumn'),(array)$this->scope
)));
342 return $this->scope_condition
;
346 function setScopeCondition($scope_condition)
348 if(!is_array($scope_condition) && strstr($scope_condition, '?')){
349 $this->variable_scope_condition
= $scope_condition;
351 $this->scope_condition
= $scope_condition;
355 function getScopedColumn($column)
357 if($this->_ActiveRecordInstance
->hasColumn($column)){
358 $value = $this->_ActiveRecordInstance
->get($column);
359 $condition = $this->_ActiveRecordInstance
->getAttributeCondition($value);
360 $value = $this->_ActiveRecordInstance
->castAttributeForDatabase($column, $value);
361 return $column.' '.str_replace('?', $value, $condition);
369 * This has the effect of moving all the higher items up one.
371 function decrementPositionsOnHigherItems($position)
373 return $this->_ActiveRecordInstance
->updateAll("{$this->column} = ({$this->column} - 1)", $this->getScopeCondition()." AND {$this->column} <= $position");
377 * This has the effect of moving all the lower items down one.
379 function incrementPositionsOnLowerItems($position)
381 return $this->_ActiveRecordInstance
->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition()." AND {$this->column} >= $position");
384 function incrementPositionsOnAllItems()
386 return $this->_ActiveRecordInstance
->updateAll("{$this->column} = ({$this->column} + 1)", $this->getScopeCondition());
392 * This function saves the object using save() before inserting it into the list
394 function insertAtPosition($position)
396 $this->_ActiveRecordInstance
->transactionStart();
397 if($this->_ActiveRecordInstance
->isNewRecord()){
398 $this->_ActiveRecordInstance
->save();
400 $this->removeFromList();
401 $this->incrementPositionsOnLowerItems($position);
403 $this->_ActiveRecordInstance
->updateAttribute($this->column
, $position);
404 if($this->_ActiveRecordInstance
->transactionHasFailed()){
405 $this->_ActiveRecordInstance
->transactionComplete();
409 $this->_ActiveRecordInstance
->transactionComplete();