PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/kernel/classes/datatypes/ezxmltext/ezxmloutputhandler.php

https://github.com/granitegreg/ezpublish
PHP | 694 lines | 495 code | 85 blank | 114 comment | 60 complexity | f4ffee4a898487224ac7ac0d9edca64c MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. //
  3. // Definition of eZXMLOutputHandler class
  4. //
  5. // Created on: <06-Nov-2002 15:10:02 wy>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2011 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*! \file
  31. */
  32. /*!
  33. \class eZXMLOutputHandler ezxmloutputhandler
  34. \ingroup eZDatatype
  35. \brief The class eZXMLOutputHandler does
  36. */
  37. // if ( !class_exists( 'eZXMLSchema' ) )
  38. class eZXMLOutputHandler
  39. {
  40. /*!
  41. Constructor
  42. */
  43. function eZXMLOutputHandler( $xmlData, $aliasedType, $contentObjectAttribute = null )
  44. {
  45. $this->XMLData = $xmlData;
  46. $this->AliasedHandler = null;
  47. $this->AliasedType = $aliasedType;
  48. if ( is_object( $contentObjectAttribute ) )
  49. {
  50. $this->ContentObjectAttribute = $contentObjectAttribute;
  51. $this->ObjectAttributeID = $contentObjectAttribute->attribute( 'id' );
  52. }
  53. $ini = eZINI::instance( 'ezxml.ini' );
  54. if ( $ini->hasVariable( 'InputSettings', 'AllowMultipleSpaces' ) )
  55. {
  56. $allowMultipleSpaces = $ini->variable( 'InputSettings', 'AllowMultipleSpaces' );
  57. $this->AllowMultipleSpaces = $allowMultipleSpaces == 'true' ? true : false;
  58. }
  59. if ( $ini->hasVariable( 'InputSettings', 'AllowNumericEntities' ) )
  60. {
  61. $allowNumericEntities = $ini->variable( 'InputSettings', 'AllowNumericEntities' );
  62. $this->AllowNumericEntities = $allowNumericEntities == 'true' ? true : false;
  63. }
  64. }
  65. /*!
  66. \return an array with attribute names.
  67. */
  68. function attributes()
  69. {
  70. return array( 'output_text',
  71. 'aliased_type',
  72. 'aliased_handler',
  73. 'view_template_name' );
  74. }
  75. /*!
  76. \return true if the attribute \a $name exists.
  77. */
  78. function hasAttribute( $name )
  79. {
  80. return in_array( $name, $this->attributes() );
  81. }
  82. /*!
  83. \return the value of the attribute \a $name if it exists, if not returns \c null.
  84. */
  85. function attribute( $name )
  86. {
  87. switch ( $name )
  88. {
  89. case 'output_text':
  90. {
  91. return $this->outputText();
  92. } break;
  93. case 'aliased_type':
  94. {
  95. return $this->AliasedType;
  96. } break;
  97. case 'view_template_name':
  98. {
  99. return $this->viewTemplateName();
  100. } break;
  101. case 'aliased_handler':
  102. {
  103. if ( $this->AliasHandler === null )
  104. {
  105. $this->AliasedHandler = eZXMLText::inputHandler( $this->XMLData,
  106. $this->AliasedType,
  107. false,
  108. $this->ContentObjectAttribute );
  109. }
  110. return $this->AliasedHandler;
  111. } break;
  112. default:
  113. {
  114. eZDebug::writeError( "Attribute '$name' does not exist", __METHOD__ );
  115. return null;
  116. } break;
  117. }
  118. }
  119. /*!
  120. \return the template name for this input handler, includes the edit suffix if any.
  121. */
  122. function &viewTemplateName()
  123. {
  124. $name = 'ezxmltext';
  125. $suffix = $this->viewTemplateSuffix();
  126. if ( $suffix !== false )
  127. {
  128. $name .= '_' . $suffix;
  129. }
  130. return $name;
  131. }
  132. /*!
  133. \virtual
  134. \return true if the output handler is considered valid, if not the handler will not be used.
  135. \note Default returns true
  136. */
  137. function isValid()
  138. {
  139. return true;
  140. }
  141. /*!
  142. \pure
  143. \return the suffix for the attribute template, if false it is ignored.
  144. */
  145. function &viewTemplateSuffix( &$contentobjectAttribute )
  146. {
  147. $suffix = false;
  148. return $suffix;
  149. }
  150. /*!
  151. \return the xml data as text.
  152. */
  153. function xmlData()
  154. {
  155. return $this->XMLData;
  156. }
  157. /*!
  158. Returns the output text representation of the XML structure
  159. Default implementation uses default mechanism of rules and tag handlers to render tags.
  160. */
  161. function &outputText()
  162. {
  163. if ( !$this->XMLData )
  164. {
  165. $output = '';
  166. return $output;
  167. }
  168. $this->Tpl = eZTemplate::factory();
  169. $this->Res = eZTemplateDesignResource::instance();
  170. if ( $this->ContentObjectAttribute )
  171. {
  172. $this->Res->setKeys( array( array( 'attribute_identifier', $this->ContentObjectAttribute->attribute( 'contentclass_attribute_identifier' ) ) ) );
  173. }
  174. $this->Document = new DOMDocument( '1.0', 'utf-8' );
  175. $success = $this->Document->loadXML( $this->XMLData );
  176. if ( !$success )
  177. {
  178. $this->Output = '';
  179. return $this->Output;
  180. }
  181. $this->prefetch();
  182. $this->XMLSchema = eZXMLSchema::instance();
  183. // Add missing elements to the OutputTags array
  184. foreach( $this->XMLSchema->availableElements() as $element )
  185. {
  186. if ( !isset( $this->OutputTags[$element] ) )
  187. {
  188. $this->OutputTags[$element] = array();
  189. }
  190. }
  191. $this->NestingLevel = 0;
  192. $params = array();
  193. $output = $this->outputTag( $this->Document->documentElement, $params );
  194. $this->Output = $output[1];
  195. unset( $this->Document );
  196. $this->Res->removeKey( 'attribute_identifier' );
  197. return $this->Output;
  198. }
  199. // Prefetch objects, nodes and urls for further rendering
  200. function prefetch()
  201. {
  202. $relatedObjectIDArray = array();
  203. $nodeIDArray = array();
  204. // Fetch all links and cache urls
  205. $linkIDArray = $this->getAttributeValueArray( 'link', 'url_id' );
  206. if ( count( $linkIDArray ) > 0 )
  207. {
  208. $inIDSQL = implode( ', ', $linkIDArray );
  209. $db = eZDB::instance();
  210. $linkArray = $db->arrayQuery( "SELECT * FROM ezurl WHERE id IN ( $inIDSQL ) " );
  211. foreach ( $linkArray as $linkRow )
  212. {
  213. $url = str_replace( '&', '&amp;', $linkRow['url'] );
  214. $this->LinkArray[$linkRow['id']] = $url;
  215. }
  216. }
  217. $linkRelatedObjectIDArray = $this->getAttributeValueArray( 'link', 'object_id' );
  218. $linkNodeIDArray = $this->getAttributeValueArray( 'link', 'node_id' );
  219. // Fetch all embeded objects and cache by ID
  220. $objectRelatedObjectIDArray = $this->getAttributeValueArray( 'object', 'id' );
  221. $embedRelatedObjectIDArray = $this->getAttributeValueArray( 'embed', 'object_id' );
  222. $embedInlineRelatedObjectIDArray = $this->getAttributeValueArray( 'embed-inline', 'object_id' );
  223. $embedNodeIDArray = $this->getAttributeValueArray( 'embed', 'node_id' );
  224. $embedInlineNodeIDArray = $this->getAttributeValueArray( 'embed-inline', 'node_id' );
  225. $relatedObjectIDArray = array_merge(
  226. $linkRelatedObjectIDArray,
  227. $objectRelatedObjectIDArray,
  228. $embedRelatedObjectIDArray,
  229. $embedInlineRelatedObjectIDArray );
  230. $relatedObjectIDArray = array_unique( $relatedObjectIDArray );
  231. if ( count( $relatedObjectIDArray ) > 0 )
  232. {
  233. $this->ObjectArray = eZContentObject::fetchIDArray( $relatedObjectIDArray );
  234. }
  235. $nodeIDArray = array_merge(
  236. $linkNodeIDArray,
  237. $embedNodeIDArray,
  238. $embedInlineNodeIDArray
  239. );
  240. $nodeIDArray = array_unique( $nodeIDArray );
  241. if ( count( $nodeIDArray ) > 0 )
  242. {
  243. $nodes = eZContentObjectTreeNode::fetch( $nodeIDArray );
  244. if ( is_array( $nodes ) )
  245. {
  246. foreach( $nodes as $node )
  247. {
  248. $nodeID = $node->attribute( 'node_id' );
  249. $this->NodeArray["$nodeID"] = $node;
  250. }
  251. }
  252. elseif ( $nodes )
  253. {
  254. $node = $nodes;
  255. $nodeID = $node->attribute( 'node_id' );
  256. $this->NodeArray["$nodeID"] = $node;
  257. }
  258. }
  259. }
  260. function getAttributeValueArray( $tagName, $attributeName )
  261. {
  262. $attributeValueArray = array();
  263. $elements = $this->Document->getElementsByTagName( $tagName );
  264. foreach ( $elements as $element )
  265. {
  266. $attributeValue = $element->getAttribute( $attributeName );
  267. if ( $attributeValue )
  268. {
  269. $attributeValueArray[] = $attributeValue;
  270. }
  271. }
  272. return $attributeValueArray;
  273. }
  274. // Main recursive functions for rendering tags
  275. // $element - current element
  276. // $siblingParams - array of parameters that are passed by reference to all the children of the
  277. // current tag to provide a way to "communicate" between their handlers.
  278. // This array is empty for the first child.
  279. // $parentParams - parameter passed to this tag handler by the parent tag's handler.
  280. // This array is passed with no reference. Can by modified in tag's handler
  281. // for subordinate tags.
  282. function outputTag( $element, &$siblingParams, $parentParams = array() )
  283. {
  284. $tagName = $element->localName;
  285. if ( isset( $this->OutputTags[$tagName] ) )
  286. {
  287. $currentTag = $this->OutputTags[$tagName];
  288. }
  289. else
  290. {
  291. $currentTag = null;
  292. }
  293. // Prepare attributes array
  294. $attributes = array();
  295. if ( $element->hasAttributes() )
  296. {
  297. $attributeNodes = $element->attributes;
  298. foreach ( $attributeNodes as $attrNode )
  299. {
  300. if ( $attrNode->prefix && $attrNode->prefix != 'custom' )
  301. {
  302. $attrName = $attrNode->prefix . ':' . $attrNode->localName;
  303. }
  304. else
  305. {
  306. $attrName = $attrNode->nodeName;
  307. }
  308. // classes check
  309. if ( $attrName == 'class' )
  310. {
  311. $classesList = $this->XMLSchema->getClassesList( $tagName );
  312. if ( !in_array( $attrNode->value, $classesList ) )
  313. {
  314. eZDebug::writeWarning( "Using tag '$tagName' with class '$attrNode->value' is not allowed.", 'XML output handler' );
  315. return array( true, '' );
  316. }
  317. }
  318. $attributes[$attrName] = $attrNode->value;
  319. }
  320. }
  321. // Set default attribute values if not present in the input
  322. $attrDefaults = $this->XMLSchema->attrDefaultValues( $tagName );
  323. foreach ( $attrDefaults as $name=>$value )
  324. {
  325. if ( !isset( $attributes[$name] ) )
  326. {
  327. $attributes[$name] = $value;
  328. }
  329. }
  330. // Init handler returns an array that may contain the following items:
  331. //
  332. // 'no_render' (boolean) :
  333. // If false tag will not be rendered, only it's children (if any).
  334. // 'design_keys' array( 'design_key_name_1' => 'value_1', 'design_key_name_2'=>'value_2', ... ) :
  335. // An array of additional design keys.
  336. // 'tpl_vars' array( 'var_name_1' => 'value_1', 'var_name_2' => 'value_2', ... ) :
  337. // An array of additional template variables.
  338. // 'template_name' (string) :
  339. // Overrides tag template name.
  340. $result = $this->callTagInitHandler( 'initHandler', $element, $attributes, $siblingParams, $parentParams );
  341. // Process children
  342. $childrenOutput = array();
  343. if ( $element->hasChildNodes() )
  344. {
  345. // Initialize sibiling parameters array for the next level children
  346. // Parent parameters for the children may be modified in the current tag handler.
  347. $nextSibilingParams = array();
  348. $this->NestingLevel++;
  349. foreach( $element->childNodes as $child )
  350. {
  351. $childOutput = $this->outputTag( $child, $nextSibilingParams, $parentParams );
  352. if ( is_array( $childOutput[0] ) )
  353. {
  354. $childrenOutput = array_merge( $childrenOutput, $childOutput );
  355. }
  356. else
  357. {
  358. $childrenOutput[] = $childOutput;
  359. }
  360. }
  361. $this->NestingLevel--;
  362. }
  363. else
  364. {
  365. $childrenOutput = array( array( true, '' ) );
  366. }
  367. if ( isset( $result['no_render'] ) && $result['no_render'] )
  368. {
  369. return $childrenOutput;
  370. }
  371. // Set tpl variables by attributes and rename rules
  372. $vars = array();
  373. if ( !isset( $currentTag['quickRender'] ) && isset( $currentTag['attrNamesTemplate'] ) )
  374. {
  375. $attrRenameRules = $currentTag['attrNamesTemplate'];
  376. }
  377. elseif ( isset( $currentTag['quickRender'] ) && isset( $currentTag['attrNamesQuick'] ) )
  378. {
  379. $attrRenameRules = $currentTag['attrNamesQuick'];
  380. }
  381. else
  382. {
  383. $attrRenameRules = array();
  384. }
  385. foreach( $attributes as $name=>$value )
  386. {
  387. if ( isset( $attrRenameRules[$name] ) )
  388. {
  389. $vars[$attrRenameRules[$name]] = $value;
  390. continue;
  391. }
  392. if ( strpos( $name, 'custom:' ) === 0 )
  393. {
  394. $name = substr( $name, 7 );
  395. }
  396. $vars[$name] = $value;
  397. }
  398. // set missing variables that have rename rules defined
  399. // but were not present in the element
  400. foreach( $attrRenameRules as $attrName=>$varName )
  401. {
  402. if ( !isset( $attributes[$attrName] ) )
  403. {
  404. $vars[$varName] = '';
  405. }
  406. }
  407. $this->TemplateUri = '';
  408. // In quick render mode we does not use templates and
  409. // render template variables as tag attributes
  410. if ( !isset( $currentTag['quickRender'] ) )
  411. {
  412. // Set additional variables passed by tag handler
  413. if ( isset( $result['tpl_vars'] ) )
  414. {
  415. $vars = array_merge( $vars, $result['tpl_vars'] );
  416. }
  417. foreach( $vars as $name=>$value )
  418. {
  419. $this->Tpl->setVariable( $name, $value, 'xmltagns' );
  420. }
  421. // Create design keys array (including the ones with no value so they still overwrite values of parent tag)
  422. $designKeys = array();
  423. if ( isset( $currentTag['attrDesignKeys'] ) )
  424. {
  425. foreach( $currentTag['attrDesignKeys'] as $attrName=>$keyName )
  426. {
  427. if ( isset( $attributes[$attrName] ) )
  428. {
  429. $designKeys[$keyName] = $attributes[$attrName];
  430. }
  431. }
  432. }
  433. // Set additional design keys passed by tag handler
  434. if ( isset( $result['design_keys'] ) )
  435. {
  436. $designKeys = array_merge( $designKeys, $result['design_keys'] );
  437. }
  438. $existingKeys = $this->Res->keys();
  439. $savedKeys = array();
  440. // Save old keys values and set new design keys
  441. foreach( $designKeys as $key=>$value )
  442. {
  443. if ( isset( $existingKeys[$key] ) )
  444. {
  445. $savedKeys[$key] = $existingKeys[$key];
  446. }
  447. $this->Res->setKeys( array( array( $key, $value ) ) );
  448. }
  449. // Template name
  450. if ( isset( $result['template_name'] ) )
  451. {
  452. $templateName = $result['template_name'];
  453. }
  454. else
  455. {
  456. $templateName = $element->nodeName;
  457. }
  458. $this->TemplateUri = $this->TemplatesPath . $templateName . '.tpl';
  459. }
  460. $output = $this->callTagRenderHandler( 'renderHandler', $element, $childrenOutput, $vars );
  461. if ( !isset( $currentTag['quickRender'] ) )
  462. {
  463. // Restore saved template override keys and remove others
  464. foreach( $designKeys as $key => $value )
  465. {
  466. if ( isset( $savedKeys[$key] ) )
  467. {
  468. $this->Res->setKeys( array( array( $key, $savedKeys[$key] ) ) );
  469. }
  470. else
  471. {
  472. $this->Res->removeKey( $key );
  473. }
  474. }
  475. // Unset variables
  476. foreach ( $vars as $name=>$value )
  477. {
  478. if ( $this->Tpl->hasVariable( $name, 'xmltagns' ) )
  479. {
  480. $this->Tpl->unsetVariable( $name, 'xmltagns' );
  481. }
  482. }
  483. }
  484. $this->callTagInitHandler( 'leavingHandler', $element, $attributes, $siblingParams, $parentParams );
  485. return $output;
  486. }
  487. function renderTag( $element, $content, $vars )
  488. {
  489. $currentTag = $this->OutputTags[$element->nodeName];
  490. if ( $currentTag && isset( $currentTag['quickRender'] ) )
  491. {
  492. $renderedTag = '';
  493. $attrString = '';
  494. foreach( $vars as $name => $value )
  495. {
  496. if ( $value != '' )
  497. {
  498. $attrString .= " $name=\"$value\"";
  499. }
  500. }
  501. if ( isset( $currentTag['quickRender'][0] ) && $currentTag['quickRender'][0] )
  502. {
  503. $renderedTag = '<' . $currentTag['quickRender'][0] . "$attrString>" . $content . '</' . $currentTag['quickRender'][0] . '>';
  504. }
  505. else
  506. {
  507. $renderedTag = $content;
  508. }
  509. if ( isset( $currentTag['quickRender'][1] ) && $currentTag['quickRender'][1] )
  510. {
  511. $renderedTag .= $currentTag['quickRender'][1];
  512. }
  513. }
  514. else
  515. {
  516. if ( isset( $currentTag['contentVarName'] ) )
  517. {
  518. $contentVarName = $currentTag['contentVarName'];
  519. }
  520. else
  521. {
  522. $contentVarName = 'content';
  523. }
  524. $this->Tpl->setVariable( $contentVarName, $content, 'xmltagns' );
  525. eZTemplateIncludeFunction::handleInclude( $textElements, $this->TemplateUri, $this->Tpl, 'foo', 'xmltagns' );
  526. $renderedTag = is_array( $textElements ) ? implode( '', $textElements ) : '';
  527. }
  528. return $renderedTag;
  529. }
  530. // Default render handler
  531. // Renders all the content of children tags inside the current tag
  532. function renderAll( $element, $childrenOutput, $vars )
  533. {
  534. $tagText = '';
  535. foreach( $childrenOutput as $childOutput )
  536. {
  537. $tagText .= $childOutput[1];
  538. }
  539. $tagText = $this->renderTag( $element, $tagText, $vars );
  540. return array( false, $tagText );
  541. }
  542. function callTagInitHandler( $handlerName, $element, &$attributes, &$siblingParams, &$parentParams )
  543. {
  544. $result = array();
  545. $thisOutputTag = $this->OutputTags[$element->nodeName];
  546. if ( isset( $thisOutputTag[$handlerName] ) )
  547. {
  548. if ( is_callable( array( $this, $thisOutputTag[$handlerName] ) ) )
  549. {
  550. $result = call_user_func_array( array( $this, $thisOutputTag[$handlerName] ),
  551. array( $element, &$attributes, &$siblingParams, &$parentParams ) );
  552. }
  553. }
  554. return $result;
  555. }
  556. function callTagRenderHandler( $handlerName, $element, $childrenOutput, $vars )
  557. {
  558. $result = array();
  559. $thisOutputTag = $this->OutputTags[$element->nodeName];
  560. if ( isset( $thisOutputTag[$handlerName] ) )
  561. {
  562. $handlerFunction = $thisOutputTag[$handlerName];
  563. }
  564. else
  565. {
  566. $handlerFunction = 'renderAll';
  567. }
  568. if ( is_callable( array( $this, $handlerFunction ) ) )
  569. {
  570. $result = call_user_func_array( array( $this, $handlerFunction ),
  571. array( $element, $childrenOutput, $vars ) );
  572. }
  573. else
  574. {
  575. eZDebug::writeWarning( "'$handlerName' render handler for tag <$element->nodeName> doesn't exist: '" . $thisOutputTag[$handlerName] . "'.", 'eZXML converter' );
  576. }
  577. return $result;
  578. }
  579. // This array should be overriden in derived class with the set of rules
  580. // for outputting tags.
  581. public $OutputTags = array();
  582. // Path to tags' templates
  583. public $TemplatesPath = 'design:content/datatype/view/ezxmltags/';
  584. /// Contains the XML data as text
  585. public $XMLData;
  586. public $Document;
  587. public $XMLSchema;
  588. public $AliasedType;
  589. public $AliasedHandler;
  590. public $Output = '';
  591. public $Tpl;
  592. public $TemplateURI = '';
  593. public $Res;
  594. public $AllowMultipleSpaces = false;
  595. public $AllowNumericEntities = false;
  596. public $ContentObjectAttribute;
  597. public $ObjectAttributeID;
  598. /// Contains the URL's for <link> tags hashed by ID
  599. public $LinkArray = array();
  600. /// Contains the Objects hashed by ID
  601. public $ObjectArray = array();
  602. /// Contains the Nodes hashed by ID
  603. public $NodeArray = array();
  604. public $NestingLevel = 0;
  605. }
  606. ?>