wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
class.SortController.php
Go to the documentation of this file.
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2014 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of any of the following licenses
7  * at your choice:
8  *
9  * - GNU Lesser General Public License (LGPL)
10  * http://www.gnu.org/licenses/lgpl.html
11  * - Eclipse Public License (EPL)
12  * http://www.eclipse.org/org/documents/epl-v10.php
13  *
14  * See the license.txt file distributed with this work for
15  * additional information.
16  *
17  * $Id: class.SortController.php 1462 2014-02-04 23:52:27Z iherwig $
18  */
19 require_once(BASE."wcmf/lib/presentation/class.Controller.php");
20 require_once(BASE."wcmf/lib/persistence/class.PersistenceFacade.php");
21 require_once(BASE."wcmf/lib/model/class.Node.php");
22 require_once(BASE."wcmf/lib/model/class.NodeUtil.php");
23 require_once(BASE."wcmf/lib/model/class.NodeIterator.php");
24 require_once(BASE."wcmf/lib/visitor/class.CommitVisitor.php");
25 require_once(BASE."wcmf/lib/util/class.Obfuscator.php");
26 
27 /**
28  * @class SortController
29  * @ingroup Controller
30  * @brief SortController is a controller that sorts Nodes of same type.
31  *
32  * This Controller sorts the Nodes by responding to the 'sortup', 'sortdown'
33  * action names. It requires a parameter 'sortoid' which tells to which Node
34  * the action should be applied. According to the given action the Nodes sortkey
35  * attribute will be changed the way that it either swaps with its predecessor
36  * ('prevoid') or with its successor ('nextoid') or move a given distance ('dist')
37  * in a list of nodes that is either defined by the 'filter' or the 'poid' parameter.
38  * If none of the both parameters is given, the list contains all entities that
39  * have the same type as the type defined by 'sortoid'.
40  *
41  * After using the NodeUtil::setSortProperties method on a list of Nodes
42  * you could write the following Smarty template code to support sorting:
43  *
44  * @code
45  * {foreach from=$nodeList item=curNode}
46  * {if $curNode->getValue('hasSortUp')}
47  * <a href="javascript:setVariable('sortoid', '{$curNode->getOID()}'); setVariable('prevoid', '{$curNode->getValue('prevoid')}'); submitAction('sortup');">{translate text="up"}</a>
48  * {else}
49  * <span>{translate text="up"}</span>
50  * {/if}
51  * {if $curNode->getValue('hasSortDown')}
52  * <a href="javascript:setVariable('sortoid', '{$curNode->getOID()}'); setVariable('nextoid', '{$curNode->getValue('nextoid')}'); submitAction('sortdown');">{translate text="down"}</a>
53  * {else}
54  * <span>{translate text="down"}</span>
55  * {/if}
56  * {/foreach}
57  * @endcode
58  *
59  * <b>Input actions:</b>
60  * - @em sortup Move the given Node up in the list
61  * - @em sortdown Move the given Node down in the list
62  *
63  * <b>Output actions:</b>
64  * - @em ok In any case
65  *
66  * @param[in] sortoid The oid of the Node to change its sortkey. The Controller assumes
67  * that the given Node has a sortkey attribute (DATATYPE_IGNORE).
68  * @param[in] prevoid The oid of the Node to swap with on sortup action.
69  * If not given, the Node with previous sortkey is taken.
70  * @param[in] nextoid The oid of the Node to swap with on sortdown action.
71  * If not given, the Node with next sortkey is taken.
72  * @param[in] dist The distance to move the Node up or down. 'prevoid', 'nextoid' will
73  * be ignored.
74  * @param[in] filter A filter string to be used to filter the entities in the list,
75  * when moving by dist (see StringQuery), if no filter is defined, all
76  * entities of the type defined in sortoid are contained in the list to sort.
77  * @param[in] poid As alternative to the filter parameter the entity list maybe defined
78  * by a parent oid, which means that all child nodes of that parent are
79  * contained in the list to sort.
80  * @param[in] sortcol The name of the column to use for sorting (DATATYPE_IGNORE).
81  * If not given it defaults to 'sortkey'.
82  * @param[out] oid The oid of the Node that changed its sortkey (= sortoid).
83  *
84  * @author ingo herwig <ingo@wemove.com>
85  */
87 {
88  /**
89  * @see Controller::hasView()
90  */
91  function hasView()
92  {
93  return false;
94  }
95  /**
96  * @see Controller::validate()
97  */
98  function validate()
99  {
100  if(!$this->_request->hasValue('sortoid'))
101  {
102  $this->setErrorMsg("No 'sortoid' given in data.");
103  return false;
104  }
105  return true;
106  }
107  /**
108  * Sort Nodes.
109  * @return Array of given context and action 'ok' in every case.
110  * @see Controller::executeKernel()
111  */
112  function executeKernel()
113  {
114  $persistenceFacade = &PersistenceFacade::getInstance();
115 
116  // do actions
117  if ($this->_request->getAction() == 'sortdown' && PersistenceFacade::isValidOID($this->_request->getValue('nextoid')))
118  {
119  // if action is sortdown and nextoid is given, we have to swap the Nodes oids
120  $node1 = &$persistenceFacade->load($this->_request->getValue('sortoid'), BUILDDEPTH_SINGLE);
121  $node2 = &$persistenceFacade->load($this->_request->getValue('nextoid'), BUILDDEPTH_SINGLE);
122  $this->swapNodes($node1, $node2, true);
123  }
124  else if ($this->_request->getAction() == 'sortup' && PersistenceFacade::isValidOID($this->_request->getValue('prevoid')))
125  {
126  // if action is sortup and prevoid is given, we have to swap the Nodes oids
127  $node1 = &$persistenceFacade->load($this->_request->getValue('sortoid'), BUILDDEPTH_SINGLE);
128  $node2 = &$persistenceFacade->load($this->_request->getValue('prevoid'), BUILDDEPTH_SINGLE);
129  $this->swapNodes($node1, $node2, true);
130  }
131  else
132  {
133  // if prevoid/nextoid are not given, we have to load all Nodes and sort...
134  $this->sortAll();
135  }
136 
137  $this->_response->setValue('oid', $this->_request->getValue('sortoid'));
138  $this->_response->setAction('ok');
139  return true;
140  }
141  /**
142  * Get the name of the column to use for sorting.
143  * @return The name.
144  */
145  function getSortColumn()
146  {
147  // determine the sort column
148  $sortCol = 'sortkey';
149  if (strlen($this->_request->getValue('sortcol')) > 0) {
150  $sortCol = $this->_request->getValue('sortcol');
151  }
152  return $sortCol;
153  }
154  /**
155  * Swap sortkey of two given Nodes.
156  * @param node1 first Node
157  * @param node2 second Node
158  * @param doSave True/False wether to save the Nodes or not
159  */
160  function swapNodes(&$node1, &$node2, $doSave)
161  {
162  $sortCol = $this->getSortColumn();
163 
164  $sortkey1 = $node2->getValue($sortCol, DATATYPE_IGNORE);
165  $sortkey2 = $node1->getValue($sortCol, DATATYPE_IGNORE);
166 
167  // fallback sortkeys have never been set
168  if (!$sortkey1 || !$sortkey2)
169  {
170  $this->sortAll();
171  return;
172  }
173 
174  // fallback if sortkeys are identical
175  if ($sortkey1 == $sortkey2) {
176  $sortkey1++;
177  }
178  // actually swap sortkeys
179  $node1->setValue($sortCol, $sortkey1, DATATYPE_IGNORE);
180  $node2->setValue($sortCol, $sortkey2, DATATYPE_IGNORE);
181 
182  if ($doSave)
183  {
184  $node1->save();
185  $node2->save();
186  }
187  }
188  /**
189  * Sort all Nodes.
190  */
191  function sortAll()
192  {
193  $sortCol = $this->getSortColumn();
194  $type = PersistenceFacade::getOIDParameter($this->_request->getValue('sortoid'), 'type');
195  $persistenceFacade = PersistenceFacade::getInstance();
196  $mapper = &$persistenceFacade->getMapper($type);
197  $nodes = array();
198 
199  // load all children of poid if the poid parameter is given
200  if ($this->_request->hasValue('poid'))
201  {
202  $poid = $this->_request->getValue('poid');
204  {
205  $persistenceFacade = &PersistenceFacade::getInstance();
206  $parent = &$persistenceFacade->load($poid, 1);
207  if ($parent) {
208  $parent->sortChildren('sortkey');
209  $nodes = $parent->getChildren();
210  }
211  }
212  else {
213  $this->setErrorMsg("The 'poid' parameter is invalid.");
214  }
215  }
216  // load all nodes defined by filter if the filter parameter is given
217  else if ($this->_request->hasValue('filter'))
218  {
219  // unveil the filter value if it is obfuscated
220  $filter = $this->_request->getValue('filter');
221  $unveiled = Obfuscator::unveil($filter);
222  if (strlen($filter) > 0 && strlen($unveiled) > 0) {
223  $filter = $unveiled;
224  $nodes = ObjectQuery::executeString($type, $filter, BUILDDEPTH_SINGLE, array($type.'.'.$sortCol));
225  }
226  else {
227  $this->setErrorMsg("The 'poid' parameter is invalid.");
228  }
229  }
230  // load all nodes defined by 'sortoid' type in any other case
231  else
232  {
233  $filter = NodeUtil::getNodeQuery($type);
234  $nodes = ObjectQuery::executeString($type, $filter, BUILDDEPTH_SINGLE, array($type.'.'.$sortCol));
235  }
236 
237  // get sort directory
238  $dir = $this->_request->getAction() == 'sortdown' ? 1 : -1;
239  // if the default sorting is descending, change the direction
240  $orderBy = $mapper->getOrderBy();
241  if (sizeof($orderBy) > 0) {
242  $firstAttribute = $orderBy[0];
243  if (preg_match('/'.$sortCol.' DESC/', $firstAttribute)) {
244  $dir *= -1;
245  }
246  }
247 
248  // load all nodes and set their sortkeys ascending
249  $rootNode = new Node('');
250  $sortkey = 1;
251  for ($i=0; $i<sizeof($nodes); $i++)
252  {
253  $nodes[$i]->setValue($sortCol, $sortkey, DATATYPE_IGNORE);
254  $rootNode->addChild($nodes[$i]);
255  $sortkey++;
256  }
257 
258  $dist = 1;
259  if (strlen($this->_request->getValue('dist')) > 0) {
260  $dist = $this->_request->getValue('dist');
261  }
262  // swap nodes
263  $nodes = $rootNode->getChildren();
264  $numNodes = sizeof($nodes);
265  for ($j=0; $j<$dist; $j++)
266  {
267  for ($i=0; $i<$numNodes; $i++)
268  {
269  if ($nodes[$i]->getOID() == $this->_request->getValue('sortoid'))
270  {
271  if ($dir == 1 && $i<$numNodes-1) {
272  $this->swapNodes($nodes[$i], $nodes[$i+1], false);
273  }
274  elseif ($dir == -1 && $i>0) {
275  $this->swapNodes($nodes[$i], $nodes[$i-1], false);
276  }
277  }
278  }
279  Node::sort($nodes, $sortCol);
280  }
281 
282  // don't save the root node
283  $rootNode->setState(STATE_CLEAN, false);
284 
285  // commit changes
286  $nIter = new NodeIterator($rootNode);
287  $cv = new CommitVisitor();
288  $cv->startIterator($nIter);
289  }
290 }
291 ?>
executeString($type, $query, $buildDepth, $orderby=null, &$pagingInfo)
The CommitVisitor is used to commit the object's changes to the object storage. The objects must impl...
Node is the basic component for building trees (although a Node can have one than more parents)...
Definition: class.Node.php:118
sort(&$nodeList, $criteria, $recursive=false, $changeSortkey=false, $sortFunction='')
Definition: class.Node.php:356
NodeIterator is used to iterate over a tree/list build of objects using a Depth-First-Algorithm. Classes used with the NodeIterator must implement the getChildren() and getOID() methods. NodeIterator implements the 'Iterator Pattern'. The base class NodeIterator defines the interface for all specialized Iterator classes.
const STATE_CLEAN
getNodeQuery($nodeType)
Controller is the base class of all controllers. If a Controller has a view it is expected to reside ...
const DATATYPE_IGNORE
SortController is a controller that sorts Nodes of same type.
getOIDParameter($oid, $param, $validate=true)
swapNodes(&$node1, &$node2, $doSave)
const BUILDDEPTH_SINGLE