wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
class.XMLExportController.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.XMLExportController.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/util/class.InifileParser.php");
23 require_once(BASE."wcmf/lib/util/class.FileUtil.php");
24 
25 /**
26  * @class XMLExportController
27  * @ingroup Controller
28  * @brief XMLExportController exports the content tree into an XML file.
29  *
30  * <b>Input actions:</b>
31  * - @em continue Continue export
32  * - unspecified: Initiate the export
33  *
34  * <b>Output actions:</b>
35  * - see BatchController
36  *
37  * @param[in] docfile The name of the file to write to (path relative to script main location), default: 'export.xml'
38  * @param[in] doctype The document type (will be written into XML header), default: ''
39  * @param[in] dtd The dtd (will be written into XML header), default: ''
40  * @param[in] docrootelement The root element of the document (use this to enclose different root types if necessary), default: 'Root'
41  * @param[in] doclinebreak The linebreak character(s) to use, default: '\n'
42  * @param[in] docindent The indent character(s) to use, default: ' '
43  * @param[in] nodes_per_call The number of nodes to process in one call, default: 10
44  *
45  * @author ingo herwig <ingo@wemove.com>
46  */
48 {
49  // session name constants
50  var $ROOT_OIDS = 'XMLExportController.rootoids';
51  var $ITERATOR_ID = 'XMLExportController.iteratorid';
52 
53  // documentInfo passes the current document info/status from one call to the next:
54  // An assoziative array with keys 'docFile', 'doctype', 'dtd', 'docLinebreak', 'docIndent', 'nodesPerCall',
55  // 'lastIndent' and 'tagsToClose' where the latter is an array of assoziative arrays with keys 'indent', 'name'
56  var $DOCUMENT_INFO = 'XMLExportController.documentinfo';
57 
58  // default values, maybe overriden by corresponding request values (see above)
59  var $_DOCFILE = "export.xml";
60  var $_DOCTYPE = "";
61  var $_DTD = "";
62  var $_DOCROOTELEMENT = "Root";
63  var $_DOCLINEBREAK = "\n";
64  var $_DOCINDENT = " ";
65  var $_NODES_PER_CALL = 10;
66 
67  /**
68  * @see Controller::initialize()
69  */
70  function initialize(&$request, &$response)
71  {
72  parent::initialize($request, $response);
73 
74  // construct initial document info
75  if ($request->getAction() != 'continue')
76  {
77  $session = &SessionData::getInstance();
78 
79  $docFile = $this->_request->hasValue('docfile') ? $this->_request->getValue('docfile') : $this->_DOCFILE;
80  $docType = $this->_request->hasValue('doctype') ? $this->_request->getValue('doctype') : $this->_DOCTYPE;
81  $dtd = $this->_request->hasValue('dtd') ? $this->_request->getValue('dtd') : $this->_DTD;
82  $docRootElement = $this->_request->hasValue('docrootelement') ? $this->_request->getValue('docrootelement') : $this->_DOCROOTELEMENT;
83  $docLinebreak = $this->_request->hasValue('doclinebreak') ? $this->_request->getValue('doclinebreak') : $this->_DOCLINEBREAK;
84  $docIndent = $this->_request->hasValue('docindent') ? $this->_request->getValue('docindent') : $this->_DOCINDENT;
85  $nodesPerCall = $this->_request->hasValue('nodes_per_call') ? $this->_request->getValue('nodes_per_call') : $this->_NODES_PER_CALL;
86 
87  $documentInfo = array('docFile' => $docFile, 'docType' => $docType, 'dtd' => $dtd, 'docRootElement' => $docRootElement,
88  'docLinebreak' => $docLinebreak, 'docIndent' => $docIndent, 'nodesPerCall' => $nodesPerCall,
89  'lastIndent' => 0, 'tagsToClose' => array());
90 
91  // store document info in session
92  $session->set($this->DOCUMENT_INFO, $documentInfo);
93  }
94  }
95  /**
96  * @see BatchController::getWorkPackage()
97  */
98  function getWorkPackage($number)
99  {
100  if ($number == 0)
101  return array('name' => Message::get('Initialization'), 'size' => 1, 'oids' => array(1), 'callback' => 'initExport');
102  else
103  return null;
104  }
105  /**
106  * @see LongTaskController::getDisplayText()
107  */
108  function getDisplayText($step)
109  {
110  return $this->_workPackages[$step-1]['name']." ...";
111  }
112  /**
113  * Initialize the XML export (oids parameter will be ignored)
114  * @param oids The oids to process
115  * @note This is a callback method called on a matching work package, see BatchController::addWorkPackage()
116  */
117  function initExport($oids)
118  {
119  $session = &SessionData::getInstance();
120  // restore document state from session
121  $documentInfo = $session->get($this->DOCUMENT_INFO);
122 
123  // delete export file
124  unlink($documentInfo['docFile']);
125 
126  // start document
127  $fileHandle = fopen($documentInfo['docFile'], "a");
128  FileUtil::fputsUnicode($fileHandle, '<?xml version="1.0" encoding="UTF-8"?>'.$documentInfo['docLinebreak']);
129  if ($documentInfo['docType'] != "")
130  FileUtil::fputsUnicode($fileHandle, '<!DOCTYPE '.$documentInfo['docType'].' SYSTEM "'.$documentInfo['dtd'].'">'.$documentInfo['docLinebreak']);
131  FileUtil::fputsUnicode($fileHandle, '<'.$documentInfo['docRootElement'].'>'.$documentInfo['docLinebreak']);
132  fclose($fileHandle);
133 
134  // get root types from ini file
135  $rootOIDs = array();
136  $parser = &InifileParser::getInstance();
137  $rootTypes = $parser->getValue('rootTypes', 'cms');
138 
139  if (is_array($rootTypes))
140  {
141  $persistenceFacade = &PersistenceFacade::getInstance();
142  foreach($rootTypes as $rootType)
143  $rootOIDs = array_merge($rootOIDs, $persistenceFacade->getOIDs($rootType));
144  }
145 
146  // store root object ids in session
147  $nextOID = array_shift($rootOIDs);
148  $session->set($this->ROOT_OIDS, $rootOIDs);
149 
150  // create work package for first root node
151  $this->addWorkPackage(Message::get('Exporting tree: start with %1%', array($nextOID)), 1, array($nextOID), 'exportNodes');
152  }
153  /**
154  * Serialize all Nodes with given oids to XML
155  * @param oids The oids to process
156  * @note This is a callback method called on a matching work package, see BatchController::addWorkPackage()
157  */
158  function exportNodes($oids)
159  {
160  // Export starts from root oids and iterates over all children.
161  // On every call we have to decide what to do:
162  // - If there is an iterator stored in the session we are inside a tree and continue iterating (_NODES_PER_CALL nodes)
163  // until the iterator finishes
164  // - If the oids array holds one value!=null this is assumed to be an root oid and a new iterator is constructed
165  // - If there is no iterator and no oid given, we return
166 
167  $session = &SessionData::getInstance();
168  // restore document state from session
169  $documentInfo = $session->get($this->DOCUMENT_INFO);
170 
171  // check for iterator in session
172  $iterator = null;
173  $iteratorID = $session->get($this->ITERATOR_ID);
174  if ($iteratorID != null)
175  $iterator = &PersistentIterator::load($iteratorID);
176 
177  // no iterator but oid given, start with new root oid
178  if ($iterator == null && $oids[0] != null)
179  $iterator = new PersistentIterator($oids[0]);
180 
181  // no iterator, no oid, finish
182  if ($iterator == null)
183  {
184  $this->addWorkPackage(Message::get('Finish'), 1, array(null), 'finishExport');
185  return;
186  }
187 
188  // process _NODES_PER_CALL nodes
189  $fileHandle = fopen($documentInfo['docFile'], "a");
190  $counter = 0;
191  $documentInfo['endTag'] = false;
192  while (!$iterator->isEnd() && $counter < $documentInfo['nodesPerCall'])
193  {
194  // write node
195  $documentInfo = $this->writeNode($fileHandle, $iterator->getCurrentOID(), $iterator->getCurrentDepth()+1, $documentInfo);
196  $iterator->proceed();
197  $counter++;
198  }
199  fclose($fileHandle);
200 
201  // save document state to session
202  $session->set($this->DOCUMENT_INFO, $documentInfo);
203 
204  // decide what to do next
205  $rootOIDs = $session->get($this->ROOT_OIDS);
206  if ($iterator->isEnd() && sizeof($rootOIDs) > 0)
207  {
208  // if the current iterator is finished, set iterator null and proceed with the next root oid
209  $nextOID = array_shift($rootOIDs);
210  $iterator = null;
211  // store remaining root oids in session
212  $session->set($this->ROOT_OIDS, $rootOIDs);
213  // unset iterator id to start with new root oid
214  $tmp = null;
215  $session->set($this->ITERATOR_ID, $tmp);
216 
217  $name = Message::get('Exporting tree: start with %1%', array($nextOID));
218  $this->addWorkPackage($name, 1, array($nextOID), 'exportNodes');
219  }
220  elseif (!$iterator->isEnd())
221  {
222  // proceed with current iterator
223  $iteratorID = $iterator->save();
224  $session->set($this->ITERATOR_ID, $iteratorID);
225 
226  $name = Message::get('Exporting tree: continue with %1%', array($iterator->getCurrentOID()));
227  $this->addWorkPackage($name, 1, array(null), 'exportNodes');
228  }
229  else
230  {
231  // finish
232  $this->addWorkPackage(Message::get('Finish'), 1, array(null), 'finishExport');
233  }
234  }
235  /**
236  * Finish the XML export (oids parameter will be ignored)
237  * @param oids The oids to process
238  * @note This is a callback method called on a matching work package, see BatchController::addWorkPackage()
239  */
240  function finishExport($oids)
241  {
242  $session = &SessionData::getInstance();
243  // restore document state from session
244  $documentInfo = $session->get($this->DOCUMENT_INFO);
245 
246  // end document
247  $fileHandle = fopen($documentInfo['docFile'], "a");
248  $this->endTags($fileHandle, 1, $documentInfo);
249  FileUtil::fputsUnicode($fileHandle, '</'.$documentInfo['docRootElement'].'>'.$documentInfo['docLinebreak']);
250  fclose($fileHandle);
251 
252  // clear session variables
253  $tmp = null;
254  $session->set($this->ROOT_OIDS, $tmp);
255  $session->set($this->ITERATOR_ID, $tmp);
256  $session->set($this->DOCUMENT_INFO, $tmp);
257  }
258  /**
259  * Ends all tags up to $curIndent level
260  * @param fileHandle The file handle to write to
261  * @param curIndent The depth of the node in the tree
262  * @param documentInfo An assoziative array (see DOCUMENT_INFO)
263  */
264  function endTags($fileHandle, $curIndent, $documentInfo)
265  {
266  $lastIndent = $documentInfo['lastIndent'];
267 
268  // write last opened and not closed tags
269  if ($curIndent < $lastIndent)
270  {
271  for ($i=$lastIndent-$curIndent;$i>0;$i--)
272  {
273  $closeTag = array_shift($documentInfo['tagsToClose']);
274  FileUtil::fputsUnicode($fileHandle, str_repeat($documentInfo['docIndent'], $closeTag["indent"]).'</'.$closeTag["name"].'>'.$documentInfo['docLinebreak']);
275  }
276  }
277  }
278  /**
279  * Serialize a Node to XML
280  * @param fileHandle The file handle to write to
281  * @param oid The oid of the node
282  * @param depth The depth of the node in the tree
283  * @param documentInfo An assoziative array (see DOCUMENT_INFO)
284  * @return The updated document state
285  */
286  function writeNode($fileHandle, $oid, $depth, $documentInfo)
287  {
288  $persistenceFacade = &PersistenceFacade::getInstance();
289  $node = &$persistenceFacade->load($oid, BUILDDEPTH_SINGLE);
290  $numChildren = sizeof($node->getProperty('childoids'));
291 
292  $lastIndent = $documentInfo['lastIndent'];
293  $curIndent = $depth;
294  $this->endTags($fileHandle, $curIndent, $documentInfo);
295 
296  if (!$documentInfo['endTag'])
297  {
298  if ($numChildren > 0)
299  {
300  $closeTag = array("name" => $node->getType(), "indent" => $curIndent);
301  array_unshift($documentInfo['tagsToClose'], $closeTag);
302  $documentInfo['endTag'] = true;
303  }
304  }
305 
306  // write object's content
307  // open tag
308  FileUtil::fputsUnicode($fileHandle, str_repeat($documentInfo['docIndent'], $curIndent).'<'.$node->getType());
309  // write object id
310  FileUtil::fputsUnicode($fileHandle, ' id="'.$node->getOID().'"');
311  // write object attributes
312  $attributeNames = $node->getValueNames(DATATYPE_ATTRIBUTE);
313  foreach ($attributeNames as $curAttribute)
314  if ($node->getValue($curAttribute) != '')
315  FileUtil::fputsUnicode($fileHandle, ' '.$curAttribute.'="'.$this->formatValue($node->getValue($curAttribute, DATATYPE_ATTRIBUTE)).'"');
316  // close tag
317  FileUtil::fputsUnicode($fileHandle, '>');
318  if ($numChildren > 0)
319  FileUtil::fputsUnicode($fileHandle, $documentInfo['docLinebreak']);
320  // write object element
321  $elementNames = $node->getValueNames(DATATYPE_ELEMENT);
322  foreach ($elementNames as $curElement)
323  {
324  if ($node->getValue($curElement) != '')
325  FileUtil::fputsUnicode($fileHandle, $this->formatValue($node->getValue($curElement, DATATYPE_ELEMENT)));
326  }
327 
328  // remember open tag if not closed
329  if ($numChildren > 0)
330  {
331  $closeTag = array("name" => $node->getType(), "indent" => $curIndent);
332  array_unshift($documentInfo['tagsToClose'], $closeTag);
333  }
334  else
335  FileUtil::fputsUnicode($fileHandle, '</'.$node->getType().'>'.$documentInfo['docLinebreak']);
336  // remember current indent
337  $documentInfo['lastIndent'] = $curIndent;
338 
339  // return the updated document info
340  return $documentInfo;
341  }
342  /**
343  * Format a value for XML output
344  * @param value The value to format
345  * @return The formatted value
346  * @note Subclasses may overrite this for special application requirements
347  */
348  function formatValue($value)
349  {
350  return htmlentities(str_replace(array("\r", "\n"), array("", ""), nl2br($value)), ENT_QUOTES);
351  }
352 }
353 ?>
writeNode($fileHandle, $oid, $depth, $documentInfo)
get($message, $parameters=null, $domain='', $lang='')
initialize(&$request, &$response)
const DATATYPE_ATTRIBUTE
const DATATYPE_ELEMENT
XMLExportController exports the content tree into an XML file.
endTags($fileHandle, $curIndent, $documentInfo)
addWorkPackage($name, $size, $oids, $callback, $args=null)
fputsUnicode($fp, $str)
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.