Fixing file upload params ($_FILES) normalization. Closes #75
[akelos.git] / lib / AkActiveRecord / AkActsAsList.php
blobbc93d071010252951cf327c1798324a89f6d8f4c
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);
139 function moveLower()
141 $this->_ActiveRecordInstance->transactionStart();
142 if($LowerItem = $this->getLowerItem()){
143 if($LowerItem->list->decrementPosition() && $this->incrementPosition()){
144 $this->_ActiveRecordInstance->transactionComplete();
145 return true;
146 }else{
147 $this->_ActiveRecordInstance->transactionFail();
150 $this->_ActiveRecordInstance->transactionComplete();
151 return false;
154 function moveHigher()
156 $this->_ActiveRecordInstance->transactionStart();
157 if($HigherItem = $this->getHigherItem()){
158 if($HigherItem->list->incrementPosition() && $this->decrementPosition()){
159 $this->_ActiveRecordInstance->transactionComplete();
160 return true;
161 }else{
162 $this->_ActiveRecordInstance->transactionFail();
165 $this->_ActiveRecordInstance->transactionComplete();
166 return false;
170 function moveToBottom()
172 if($this->isInList()){
173 $this->_ActiveRecordInstance->transactionStart();
174 if($this->decrementPositionsOnLowerItems() && $this->assumeBottomPosition()){
175 $this->_ActiveRecordInstance->transactionComplete();
176 return true;
177 }else{
178 $this->_ActiveRecordInstance->transactionFail();
180 $this->_ActiveRecordInstance->transactionComplete();
182 return false;
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));
192 return true;
194 return false;
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();
214 if(isset($except)){
215 $conditions .= " AND id != $except";
217 return $this->_ActiveRecordInstance->find('first', array('conditions' => $conditions, 'order' => "{$this->column} DESC"));
221 function isInList()
223 return !empty($this->_ActiveRecordInstance->{$this->column});
227 function moveToTop()
229 if($this->isInList()){
230 $this->_ActiveRecordInstance->transactionStart();
231 if($this->incrementPositionsOnHigherItems() && $this->assumeTopPosition()){
232 $this->_ActiveRecordInstance->transactionComplete();
233 return true;
234 }else{
235 $this->_ActiveRecordInstance->transactionFail();
237 $this->_ActiveRecordInstance->transactionComplete();
239 return false;
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));
249 return true;
251 return false;
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;
265 return true;
268 return false;
272 function incrementPosition()
274 if($this->isInList()){
275 return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) + 1);
277 return false;
280 function decrementPosition()
282 if($this->isInList()){
283 return $this->_ActiveRecordInstance->updateAttribute($this->column, $this->_ActiveRecordInstance->getAttribute($this->column) - 1);
285 return false;
288 function isFirst()
290 if($this->isInList()){
291 return $this->_ActiveRecordInstance->getAttribute($this->column) == 1;
293 return false;
296 function isLast()
298 if($this->isInList()){
299 return $this->_ActiveRecordInstance->getAttribute($this->column) == $this->getBottomPosition();
301 return false;
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)));
309 return false;
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)));
317 return false;
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;
350 }else{
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);
362 }else{
363 return $column;
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();
406 return false;
409 $this->_ActiveRecordInstance->transactionComplete();
410 return true;