wCMF  3.6
All Classes Namespaces Files Functions Variables Groups Pages
class.CopyController.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.CopyController.php 1462 2014-02-04 23:52:27Z iherwig $
18  */
19 require_once(BASE."wcmf/application/controller/class.BatchController.php");
20 require_once(BASE."wcmf/lib/persistence/class.PersistenceFacade.php");
21 require_once(BASE."wcmf/lib/model/class.PersistentIterator.php");
22 require_once(BASE."wcmf/lib/model/class.Node.php");
23 require_once(BASE."wcmf/lib/model/class.NodeUtil.php");
24 
25 /**
26  * @class CopyController
27  * @ingroup Controller
28  * @brief CopyController is a controller that copies Nodes.
29  *
30  * <b>Input actions:</b>
31  * - @em move Move the given Node and its children to the given target Node (delete original Node)
32  * - @em copy Copy the given Node and its children to the given target Node (do not delete original Node)
33  *
34  * <b>Output actions:</b>
35  * - @em ok In any case
36  *
37  * @param[in] oid The oid of the Node to copy. The Node and all of its children will be copied
38  * @param[in] targetoid The oid of the parent to attach the copy to (if it does not accept the
39  * Node type an error occurs) (optional, if empty the new Node has no parent)
40  * @param[in] nodes_per_call The number of nodes to process in one call, default: 50
41  * @param[in] target_initparams The name of the configuration section that holds the initparams for the target mappers.
42  * This allows to copy nodes to a different store, optional, does not work for move action
43  * @param[in] recursive True/False wether to copy children too, only applies when copy action, default: true
44  * @param[out] oid The object id of the newly created Node.
45  *
46  * @author ingo herwig <ingo@wemove.com>
47  */
49 {
50  // session name constants
51  var $REQUEST = 'CopyController.request';
52  var $OBJECT_MAP = 'CopyController.objectmap';
53  var $ITERATOR_ID = 'CopyController.iteratorid';
54 
55  var $_targetNode = null;
56  var $_targetMapper = array();
57 
58  // default values, maybe overriden by corresponding request values (see above)
59  var $_NODES_PER_CALL = 50;
60 
61  /**
62  * @see Controller::initialize()
63  */
64  function initialize(&$request, &$response)
65  {
66  parent::initialize($request, $response);
67 
68  // initialize controller
69  if ($request->getAction() != 'continue')
70  {
71  $session = &SessionData::getInstance();
72 
73  // set defaults
74  if (!$request->hasValue('nodes_per_call')) {
75  $request->setValue('nodes_per_call', $this->_NODES_PER_CALL);
76  }
77  if (!$request->hasValue('recursive')) {
78  $request->setValue('recursive', true);
79  }
80 
81  // store request in session
82  $session->set($this->REQUEST, $request, array(BASE."wcmf/lib/presentation/class.ControllerMessage.php"));
83  $map = array();
84  $session->set($this->OBJECT_MAP, $map);
85  }
86  }
87  /**
88  * @see Controller::validate()
89  */
90  function validate()
91  {
92  if ($this->_request->getAction() != 'continue')
93  {
94  // check request values
95  if(strlen($this->_request->getValue('oid')) == 0)
96  {
97  $this->appendErrorMsg("No 'oid' given in data.");
98  return false;
99  }
100  if($this->_request->hasValue('targetoid') &&
101  !PersistenceFacade::isValidOID($this->_request->getValue('targetoid')))
102  {
103  $this->appendErrorMsg("Invalid 'targetoid' parameter given in data.");
104  return false;
105  }
106 
107  // check if the parent accepts this node type (only when not adding to root)
108  $addOk = true;
109  if ($this->_request->hasValue('targetoid'))
110  {
111  $persistenceFacade = &PersistenceFacade::getInstance();
112  $targetOID = $this->_request->getValue('targetoid');
113  $nodeOID = $this->_request->getValue('oid');
114 
115  $targetNode = $this->getTargetNode($targetOID);
116  $nodeType = PersistenceFacade::getOIDParameter($nodeOID, 'type');
117  $targetType = PersistenceFacade::getOIDParameter($targetOID, 'type');
118 
119  $tplNode = &$persistenceFacade->create($targetType, 1);
120  $possibleChildren = NodeUtil::getPossibleChildren($targetNode, $tplNode);
121  if (!in_array($nodeType, array_keys($possibleChildren)))
122  {
123  $this->appendErrorMsg(Message::get("%1% does not accept children of type %2%. The parent type is not compatible.",
124  array($targetOID, $nodeType)));
125  $addOk = false;
126  }
127  else
128  {
129  $template = &$possibleChildren[$nodeType];
130  if (!$template->getProperty('canCreate'))
131  {
132  $this->appendErrorMsg(Message::get("%1% does not accept children of type %2%. The maximum number of children of that type is reached.",
133  array($targetOID, $nodeType)));
134  $addOk = false;
135  }
136  }
137  }
138  if (!$addOk) {
139  return false;
140  }
141  }
142  // do default validation
143  return parent::validate();
144  }
145  /**
146  * @see BatchController::getWorkPackage()
147  */
148  function getWorkPackage($number)
149  {
150  $name = '';
151  if ($this->_request->getAction() == 'move') {
152  $name = Message::get('Moving');
153  }
154  else if ($this->_request->getAction() == 'copy') {
155  $name = Message::get('Copying');
156  }
157  $name .= ': '.$this->_request->getValue('oid');
158 
159  if ($number == 0) {
160  return array('name' => $name, 'size' => 1, 'oids' => array(1), 'callback' => 'startProcess');
161  }
162  else {
163  return null;
164  }
165  }
166  /**
167  * @see LongTaskController::getDisplayText()
168  */
169  function getDisplayText($step)
170  {
171  return $this->_workPackages[$step-1]['name']." ...";
172  }
173  /**
174  * Copy/Move the first node (oids parameter will be ignored)
175  * @param oids The oids to process
176  */
177  function startProcess($oids)
178  {
179  $session = &SessionData::getInstance();
180 
181  // restore the request from session
182  $request = $session->get($this->REQUEST);
183  $action = $request->getAction();
184  $targetOID = $request->getValue('targetoid');
185  $nodeOID = $request->getValue('oid');
186 
187  // do the action
188  if ($action == 'move')
189  {
190  if ($request->hasValue('target_initparams')) {
191  WCMFException::throwEx("Moving nodes to a different store is not supported. Use the 'copy' action instead.", __FILE__, __LINE__);
192  }
193 
194  // with move action, we only need to attach the Node to the new target
195  // the children will not be loaded, they will be moved automatically
196  $nodeCopy = &$persistenceFacade->load($nodeOID, BUILDDEPTH_SINGLE);
197  if ($nodeCopy)
198  {
199  // attach the node to the target node
200  $parentNode = &$this->getTargetNode($targetOID);
201  $parentNode->addChild($nodeCopy);
202 
203  // save changes
204  $this->modify($nodeCopy);
205  $nodeCopy->save();
206 
207  // set the result and finish
208  $this->endProcess($nodeCopy->getOID());
209 
210  if (Log::isInfoEnabled(__CLASS__)) {
211  Log::info("Moved: ".$nodeOID." to ".$parentNode->getOID(), __CLASS__);
212  }
213  }
214  }
215  else if ($action == 'copy')
216  {
217  // with copy action, we need to attach a copy of the Node to the new target,
218  // the children need to be loaded and treated in the same way too
219  $iterator = new PersistentIterator($nodeOID);
220  $iteratorID = $iterator->save();
221  $session->set($this->ITERATOR_ID, $iteratorID);
222 
223  // copy the first node in order to reduce the number of calls for a single copy
224  $nodeCopy = &$this->copyNode($iterator->getCurrentOID());
225  if ($nodeCopy)
226  {
227  // attach the copy to the target node
228  $parentNode = &$this->getTargetNode($targetOID);
229  $parentNode->addChild($nodeCopy);
230 
231  // save changes
232  $this->modify($nodeCopy);
233  $this->saveToTarget($nodeCopy);
234 
235  $iterator->proceed();
236 
237  // proceed if nodes are left
238  if ($request->getBooleanValue('recursive') && !$iterator->isEnd())
239  {
240  $iteratorID = $iterator->save();
241  $session->set($this->ITERATOR_ID, $iteratorID);
242 
243  $name = Message::get('Copying tree: continue with %1%', array($iterator->getCurrentOID()));
244  $this->addWorkPackage($name, 1, array(null), 'copyNodes');
245  }
246  else
247  {
248  // set the result and finish
249  $this->endProcess($nodeCopy->getOID());
250  }
251  }
252  }
253  }
254  /**
255  * Copy nodes provided by the persisted iterator (oids parameter will be ignored)
256  * @param oids The oids to process
257  */
258  function copyNodes($oids)
259  {
260  $session = &SessionData::getInstance();
261 
262  // restore the request from session
263  $request = $session->get($this->REQUEST);
264  $action = $request->getAction();
265  $targetOID = $request->getValue('targetoid');
266  $nodeOID = $request->getValue('oid');
267 
268  // check for iterator in session
269  $iterator = null;
270  $iteratorID = $session->get($this->ITERATOR_ID);
271  if ($iteratorID != null) {
272  $iterator = &PersistentIterator::load($iteratorID);
273  }
274 
275  // no iterator, finish
276  if ($iterator == null)
277  {
278  // set the result and finish
279  $this->endProcess($this->getCopyOID($nodeOID));
280  }
281 
282  // process _NODES_PER_CALL nodes
283  $counter = 0;
284  while (!$iterator->isEnd() && $counter < $request->getValue('nodes_per_call'))
285  {
286  $currentOID = $iterator->getCurrentOID();
287  $this->copyNode($currentOID);
288 
289  $iterator->proceed();
290  $counter++;
291  }
292 
293  // decide what to do next
294  if (!$iterator->isEnd())
295  {
296  // proceed with current iterator
297  $iteratorID = $iterator->save();
298  $session->set($this->ITERATOR_ID, $iteratorID);
299 
300  $name = Message::get('Copying tree: continue with %1%', array($iterator->getCurrentOID()));
301  $this->addWorkPackage($name, 1, array(null), 'copyNodes');
302  }
303  else
304  {
305  // set the result and finish
306  $this->endProcess($this->getCopyOID($nodeOID));
307  }
308  }
309  /**
310  * Finish the process and set the result
311  * @param oid The object id of the newly created Node
312  */
313  function endProcess($oid)
314  {
315  $this->_response->setValue('oid', $oid);
316 
317  $session = &SessionData::getInstance();
318 
319  // clear session variables
320  $tmp = null;
321  $session->set($this->REQUEST, $tmp);
322  $session->set($this->OBJECT_MAP, $tmp);
323  $session->set($this->ITERATOR_ID, $tmp);
324  }
325  /**
326  * Create a copy of the node with the given object id. The returned
327  * node is already persisted.
328  * @param oid The oid of the node to copy
329  * @return A reference to the copied node or null
330  */
331  function &copyNode($oid)
332  {
333  if (Log::isDebugEnabled(__CLASS__)) {
334  Log::debug("Copying node ".$oid, __CLASS__);
335  }
336  $persistenceFacade = &PersistenceFacade::getInstance();
337 
338  // load the original node
339  $node = &$persistenceFacade->load($oid, BUIDLDEPTH_SINGLE);
340  if ($node == null) {
341  WCMFException::throwEx("Can't load node '".$oid."'", __FILE__, __LINE__);
342  }
343 
344  // check if we already have a copy of the node
345  $nodeCopy = &$this->getCopy($node->getOID());
346  if ($nodeCopy == null)
347  {
348  // if not, create a copy
349  $nodeCopy = &$persistenceFacade->create($node->getType(), BUILDDEPTH_SINGLE);
350  $node->copyValues($nodeCopy, array(), false);
351  }
352 
353  // create the connections to already copied parents
354  if (Log::isDebugEnabled(__CLASS__)) {
355  Log::debug("Parents: ".join(', ', $node->getProperty('parentoids')), __CLASS__);
356  }
357  foreach ($node->getProperty('parentoids') as $parentOID)
358  {
359  $copiedParent = &$this->getCopy($parentOID);
360  if ($copiedParent != null) {
361  $copiedParent->addChild($nodeCopy);
362  if (Log::isDebugEnabled(__CLASS__)) {
363  Log::debug("Added ".$nodeCopy->getOID()." to ".$copiedParent->getOID(), __CLASS__);
364  }
365  }
366  }
367 
368  // save copy
369  $this->saveToTarget($nodeCopy);
370  $this->registerCopy($node, $nodeCopy);
371 
372  if (Log::isInfoEnabled(__CLASS__)) {
373  Log::info("Copied: ".$node->getOID()." to ".$nodeCopy->getOID(), __CLASS__);
374  }
375  if (Log::isDebugEnabled(__CLASS__)) {
376  Log::debug($nodeCopy->toString(), __CLASS__);
377  }
378 
379  // create the connections to already copied children
380  // this must be done after saving the node in order to have a correct oid
381  if (Log::isDebugEnabled(__CLASS__)) {
382  Log::debug("Children: ".join(', ', $node->getProperty('childoids')), __CLASS__);
383  }
384  foreach ($node->getProperty('childoids') as $childOID)
385  {
386  $copiedChild = &$this->getCopy($childOID);
387  if ($copiedChild != null) {
388  $nodeCopy->addChild($copiedChild);
389  $this->saveToTarget($copiedChild);
390  if (Log::isDebugEnabled(__CLASS__)) {
391  Log::debug("Added ".$copiedChild->getOID()." to ".$nodeCopy->getOID(), __CLASS__);
392  Log::debug($copiedChild->toString(), __CLASS__);
393  }
394  }
395  }
396 
397  return $nodeCopy;
398  }
399  /**
400  * Get the target node from the request parameter targetoid
401  * @param targetOID The oid of the target node or null
402  * @return A reference to the node, an empty node, if targetoid is null
403  */
404  function &getTargetNode($targetOID)
405  {
406  if ($this->_targetNode == null)
407  {
408  // load parent node or create an empty node if adding to root
409  if ($targetOID == null) {
410  $targetNode = new Node('');
411  }
412  else {
413  $targetNode = &$this->loadFromTarget($targetOID, BUILDDEPTH_SINGLE);
414  }
415  $this->_targetNode = &$targetNode;
416  }
417  return $this->_targetNode;
418  }
419  /**
420  * Register a copied node in the session for later reference
421  * @param origNode A reference to the original node
422  * @param copyNode A reference to the copied node
423  */
424  function registerCopy(&$origNode, &$copyNode)
425  {
426  $session = &SessionData::getInstance();
427  $registry = $session->get($this->OBJECT_MAP);
428  // store oid and corresponding base oid in the registry
429  $registry[$origNode->getOID()] = $copyNode->getOID();
430  $registry[$origNode->getBaseOID()] = $copyNode->getOID();
431  $session->set($this->OBJECT_MAP, $registry);
432  }
433  /**
434  * Get the object id of the copied node for a node id
435  * @param oid The object id of the original node
436  * @return The object id or null, if it does not exist already
437  */
438  function getCopyOID($origOID)
439  {
440  $session = &SessionData::getInstance();
441  $registry = $session->get($this->OBJECT_MAP);
442 
443  $oid = $origOID;
444  $origOIDParts = PersistenceFacade::decomposeOID($oid);
445  $requestedType = $origOIDParts['type'];
446 
447  // check if the oid exists in the registry
448  if (!isset($registry[$origOID])) {
449  // check if the corresponding base oid exists in the registry
450  $persistenceFacade = &PersistenceFacade::getInstance();
451  $origNodeType = &$persistenceFacade->create($requestedType, BUILDDEPTH_SINGLE);
452  $baseOID = PersistenceFacade::composeOID(array('type' => $origNodeType->getBaseType(), 'id' => $origOIDParts['id']));
453  if (!isset($registry[$baseOID]))
454  {
455  if (Log::isDebugEnabled(__CLASS__)) {
456  Log::debug("Copy of ".$oid." not found.", __CLASS__);
457  }
458  return null;
459  }
460  else {
461  $oid = $baseOID;
462  }
463  }
464 
465  $copyOID = $registry[$oid];
466 
467  // make sure to return the oid in the requested role
468  $copyOIDParts = PersistenceFacade::decomposeOID($copyOID);
469  if ($copyOIDParts['type'] != $requestedType) {
470  $copyOID = PersistenceFacade::composeOID(array('type' => $requestedType, 'id' => $copyOIDParts['id']));
471  }
472  return $copyOID;
473  }
474  /**
475  * Get the copied node for a node id
476  * @param oid The object id of the original node
477  * @return A reference to the copied node or null, if it does not exist already
478  */
479  function &getCopy($origOID)
480  {
481  $copyOID = $this->getCopyOID($origOID);
482  if ($copyOID != null)
483  {
484  $nodeCopy = &$this->loadFromTarget($copyOID);
485  return $nodeCopy;
486  }
487  else {
488  return null;
489  }
490  }
491  /**
492  * Save a node to the target store
493  * @param node A reference to the node
494  */
495  function saveToTarget(&$node)
496  {
497  $persistenceFacade = &PersistenceFacade::getInstance();
498  $originalMapper = &$persistenceFacade->getMapper($node->getType());
499 
500  $targetMapper = &$this->getTargetMapper($originalMapper);
501  $persistenceFacade->setMapper($node->getType(), $targetMapper);
502  $node->save();
503 
504  $persistenceFacade->setMapper($node->getType(), $originalMapper);
505  }
506  /**
507  * Load a node from the target store
508  * @param oid The object id of the node
509  * @return A reference to the node
510  */
511  function &loadFromTarget($oid)
512  {
513  $persistenceFacade = &PersistenceFacade::getInstance();
514  $type = PersistenceFacade::getOIDParameter($oid, 'type');
515  $originalMapper = &$persistenceFacade->getMapper($type);
516 
517  $targetMapper = &$this->getTargetMapper($originalMapper);
518  $persistenceFacade->setMapper($type, $targetMapper);
519  $node = &$persistenceFacade->load($oid, BUILDDEPTH_SINGLE);
520 
521  $persistenceFacade->setMapper($node->getType(), $originalMapper);
522 
523  return $node;
524  }
525  /**
526  * Get the target mapper for a source mapper (maybe saving to another store)
527  * @param sourceMapper A reference to the source mapper
528  * @param targetMapper A reference to the target mapper
529  */
530  function &getTargetMapper(&$sourceMapper)
531  {
532  // restore the request from session
533  $session = &SessionData::getInstance();
534  $request = $session->get($this->REQUEST);
535  if ($request->hasValue('target_initparams'))
536  {
537  // get a mapper wih the target initparams
538  $mapperClass = get_class($sourceMapper);
539  if (!isset($this->_targetMapper[$mapperClass]))
540  {
541  $initSection = $request->getValue('target_initparams');
542  $parser = &InifileParser::getInstance();
543  if (($initParams = $parser->getSection($initSection)) === false)
544  {
545  WCMFException::throwEx("No '".$initSection."' section given in configfile.", __FILE__, __LINE__);
546  return null;
547  }
548  $targetMapper = new $mapperClass($initParams);
549  $this->_targetMapper[$mapperClass] = &$targetMapper;
550  }
551  return $this->_targetMapper[$mapperClass];
552  }
553  else {
554  return $sourceMapper;
555  }
556 
557  }
558  /**
559  * Modify the given Node before save action (Called only for the copied root Node, not for its children)
560  * @note subclasses will override this to implement special application requirements.
561  * @param node A reference to the Node to modify.
562  * @return True/False whether the Node was modified [default: false].
563  */
564  function modify(&$node)
565  {
566  return false;
567  }
568 }
569 ?>
570 
initialize(&$request, &$response)
debug($message, $category)
Definition: class.Log.php:39
get($message, $parameters=null, $domain='', $lang='')
Node is the basic component for building trees (although a Node can have one than more parents)...
Definition: class.Node.php:118
info($message, $category)
Definition: class.Log.php:49
registerCopy(&$origNode, &$copyNode)
getPossibleChildren(&$realNode, &$tplNode, $resolveManyToMany=true)
CopyController is a controller that copies Nodes.
throwEx($message, $file='', $line='')
& getTargetNode($targetOID)
& getTargetMapper(&$sourceMapper)
isInfoEnabled($category)
Definition: class.Log.php:99
decomposeOID($oid, $validate=true)
isDebugEnabled($category)
Definition: class.Log.php:89
addWorkPackage($name, $size, $oids, $callback, $args=null)
getOIDParameter($oid, $param, $validate=true)
PersistentIterator is used to iterate over a tree/list build of oids using a Depth-First-Algorithm. To persist its state use the PersistentIterator::save() method, to restore its state use the static PersistentIterator::load() method, which returns the loaded instance. States are identified by an unique id, which is provided after saving. PersistentIterator implements the 'Iterator Pattern'.
const BUILDDEPTH_SINGLE
BatchController allows to define work packages that will be processed in a sequence. It simplifies the usage of LongTaskController functionality for splitting different bigger tasks into many smaller (similar) tasks where the whole number of tasks isn't known at designtime.