|
eZ Publish
[4.0]
|
00001 <?php 00002 // 00003 // Definition of eZXMLOutputHandler class 00004 // 00005 // Created on: <06-Nov-2002 15:10:02 wy> 00006 // 00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00008 // SOFTWARE NAME: eZ Publish 00009 // SOFTWARE RELEASE: 4.0.x 00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS 00011 // SOFTWARE LICENSE: GNU General Public License v2.0 00012 // NOTICE: > 00013 // This program is free software; you can redistribute it and/or 00014 // modify it under the terms of version 2.0 of the GNU General 00015 // Public License as published by the Free Software Foundation. 00016 // 00017 // This program is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 // GNU General Public License for more details. 00021 // 00022 // You should have received a copy of version 2.0 of the GNU General 00023 // Public License along with this program; if not, write to the Free 00024 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00025 // MA 02110-1301, USA. 00026 // 00027 // 00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00029 // 00030 00031 /*! \file ezxmloutputhandler.php 00032 */ 00033 00034 /*! 00035 \class eZXMLOutputHandler ezxmloutputhandler 00036 \ingroup eZDatatype 00037 \brief The class eZXMLOutputHandler does 00038 00039 */ 00040 00041 //include_once( 'kernel/classes/datatypes/ezurl/ezurl.php' ); 00042 //include_once( 'lib/eztemplate/classes/eztemplateincludefunction.php' ); 00043 00044 // if ( !class_exists( 'eZXMLSchema' ) ) 00045 //include_once( 'kernel/classes/datatypes/ezxmltext/ezxmlschema.php' ); 00046 00047 class eZXMLOutputHandler 00048 { 00049 /*! 00050 Constructor 00051 */ 00052 function eZXMLOutputHandler( $xmlData, $aliasedType, $contentObjectAttribute = null ) 00053 { 00054 $this->XMLData = $xmlData; 00055 $this->AliasedType = $aliasedType; 00056 $this->AliasedHandler = null; 00057 00058 if ( is_object( $contentObjectAttribute ) ) 00059 { 00060 $this->ContentObjectAttribute = $contentObjectAttribute; 00061 $this->ObjectAttributeID = $contentObjectAttribute->attribute( 'id' ); 00062 } 00063 00064 $ini = eZINI::instance( 'ezxml.ini' ); 00065 if ( $ini->hasVariable( 'InputSettings', 'AllowMultipleSpaces' ) ) 00066 { 00067 $allowMultipleSpaces = $ini->variable( 'InputSettings', 'AllowMultipleSpaces' ); 00068 $this->AllowMultipleSpaces = $allowMultipleSpaces == 'true' ? true : false; 00069 } 00070 if ( $ini->hasVariable( 'InputSettings', 'AllowNumericEntities' ) ) 00071 { 00072 $allowNumericEntities = $ini->variable( 'InputSettings', 'AllowNumericEntities' ); 00073 $this->AllowNumericEntities = $allowNumericEntities == 'true' ? true : false; 00074 } 00075 } 00076 00077 /*! 00078 \return an array with attribute names. 00079 */ 00080 function attributes() 00081 { 00082 return array( 'output_text', 00083 'aliased_type', 00084 'aliased_handler', 00085 'view_template_name' ); 00086 } 00087 00088 /*! 00089 \return true if the attribute \a $name exists. 00090 */ 00091 function hasAttribute( $name ) 00092 { 00093 return in_array( $name, $this->attributes() ); 00094 } 00095 00096 /*! 00097 \return the value of the attribute \a $name if it exists, if not returns \c null. 00098 */ 00099 function attribute( $name ) 00100 { 00101 switch ( $name ) 00102 { 00103 case 'output_text': 00104 { 00105 return $this->outputText(); 00106 } break; 00107 case 'aliased_type': 00108 { 00109 return $this->AliasedType; 00110 } break; 00111 case 'view_template_name': 00112 { 00113 return $this->viewTemplateName(); 00114 } break; 00115 case 'aliased_handler': 00116 { 00117 if ( $this->AliasedType !== false and 00118 $this->AliasHandler === null ) 00119 { 00120 $this->AliasedHandler = eZXMLText::inputHandler( $this->XMLData, 00121 $this->AliasedType, 00122 false, 00123 $this->ContentObjectAttribute ); 00124 } 00125 return $this->AliasedHandler; 00126 } break; 00127 default: 00128 { 00129 eZDebug::writeError( "Attribute '$name' does not exist", 'eZXMLOutputHandler::attribute' ); 00130 return null; 00131 } break; 00132 } 00133 } 00134 00135 /*! 00136 \return the template name for this input handler, includes the edit suffix if any. 00137 */ 00138 function &viewTemplateName() 00139 { 00140 $name = 'ezxmltext'; 00141 $suffix = $this->viewTemplateSuffix(); 00142 if ( $suffix !== false ) 00143 { 00144 $name .= '_' . $suffix; 00145 } 00146 return $name; 00147 } 00148 00149 /*! 00150 \virtual 00151 \return true if the output handler is considered valid, if not the handler will not be used. 00152 \note Default returns true 00153 */ 00154 function isValid() 00155 { 00156 return true; 00157 } 00158 00159 /*! 00160 \pure 00161 \return the suffix for the attribute template, if false it is ignored. 00162 */ 00163 function &viewTemplateSuffix( &$contentobjectAttribute ) 00164 { 00165 $suffix = false; 00166 return $suffix; 00167 } 00168 00169 /*! 00170 \return the xml data as text. 00171 */ 00172 function xmlData() 00173 { 00174 return $this->XMLData; 00175 } 00176 00177 /*! 00178 Returns the output text representation of the XML structure 00179 Default implementation uses default mechanism of rules and tag handlers to render tags. 00180 */ 00181 function &outputText() 00182 { 00183 if ( !$this->XMLData ) 00184 { 00185 $output = ''; 00186 return $output; 00187 } 00188 00189 $this->Tpl = templateInit(); 00190 $this->Res = eZTemplateDesignResource::instance(); 00191 if ( $this->ContentObjectAttribute ) 00192 { 00193 $this->Res->setKeys( array( array( 'attribute_identifier', $this->ContentObjectAttribute->attribute( 'contentclass_attribute_identifier' ) ) ) ); 00194 } 00195 00196 $this->Document = new DOMDocument( '1.0', 'utf-8' ); 00197 $this->Document->preserveWhiteSpace = false; 00198 $success = $this->Document->loadXML( $this->XMLData ); 00199 00200 if ( !$success ) 00201 { 00202 $this->Output = ''; 00203 return $this->Output; 00204 } 00205 00206 $this->prefetch(); 00207 00208 $this->XMLSchema = eZXMLSchema::instance(); 00209 00210 // Add missing elements to the OutputTags array 00211 foreach( $this->XMLSchema->availableElements() as $element ) 00212 { 00213 if ( !isset( $this->OutputTags[$element] ) ) 00214 { 00215 $this->OutputTags[$element] = array(); 00216 } 00217 } 00218 00219 $this->NestingLevel = 0; 00220 $params = array(); 00221 00222 $output = $this->outputTag( $this->Document->documentElement, $params ); 00223 $this->Output = $output[1]; 00224 00225 unset( $this->Document ); 00226 00227 $this->Res->removeKey( 'attribute_identifier' ); 00228 return $this->Output; 00229 } 00230 00231 // Prefetch objects, nodes and urls for further rendering 00232 function prefetch() 00233 { 00234 $relatedObjectIDArray = array(); 00235 $nodeIDArray = array(); 00236 00237 // Fetch all links and cache urls 00238 $linkIDArray = $this->getAttributeValueArray( 'link', 'url_id' ); 00239 if ( count( $linkIDArray ) > 0 ) 00240 { 00241 $inIDSQL = implode( ', ', $linkIDArray ); 00242 00243 $db = eZDB::instance(); 00244 $linkArray = $db->arrayQuery( "SELECT * FROM ezurl WHERE id IN ( $inIDSQL ) " ); 00245 00246 foreach ( $linkArray as $linkRow ) 00247 { 00248 $url = str_replace( '&', '&', $linkRow['url'] ); 00249 $this->LinkArray[$linkRow['id']] = $url; 00250 } 00251 } 00252 00253 $linkRelatedObjectIDArray = $this->getAttributeValueArray( 'link', 'object_id' ); 00254 $linkNodeIDArray = $this->getAttributeValueArray( 'link', 'node_id' ); 00255 00256 // Fetch all embeded objects and cache by ID 00257 $objectRelatedObjectIDArray = $this->getAttributeValueArray( 'object', 'id' ); 00258 00259 $embedRelatedObjectIDArray = $this->getAttributeValueArray( 'embed', 'object_id' ); 00260 $embedInlineRelatedObjectIDArray = $this->getAttributeValueArray( 'embed-inline', 'object_id' ); 00261 00262 $embedNodeIDArray = $this->getAttributeValueArray( 'embed', 'node_id' ); 00263 $embedInlineNodeIDArray = $this->getAttributeValueArray( 'embed-inline', 'node_id' ); 00264 00265 $relatedObjectIDArray = array_merge( 00266 $linkRelatedObjectIDArray, 00267 $objectRelatedObjectIDArray, 00268 $embedRelatedObjectIDArray, 00269 $embedInlineRelatedObjectIDArray ); 00270 $relatedObjectIDArray = array_unique( $relatedObjectIDArray ); 00271 00272 if ( count( $relatedObjectIDArray ) > 0 ) 00273 { 00274 $this->ObjectArray = eZContentObject::fetchIDArray( $relatedObjectIDArray ); 00275 } 00276 00277 $nodeIDArray = array_merge( 00278 $linkNodeIDArray, 00279 $embedNodeIDArray, 00280 $embedInlineNodeIDArray 00281 ); 00282 $nodeIDArray = array_unique( $nodeIDArray ); 00283 00284 if ( count( $nodeIDArray ) > 0 ) 00285 { 00286 $nodes = eZContentObjectTreeNode::fetch( $nodeIDArray ); 00287 00288 if ( is_array( $nodes ) ) 00289 { 00290 foreach( $nodes as $node ) 00291 { 00292 $nodeID = $node->attribute( 'node_id' ); 00293 $this->NodeArray["$nodeID"] = $node; 00294 } 00295 } 00296 elseif ( $nodes ) 00297 { 00298 $node = $nodes; 00299 $nodeID = $node->attribute( 'node_id' ); 00300 $this->NodeArray["$nodeID"] = $node; 00301 } 00302 } 00303 } 00304 00305 function getAttributeValueArray( $tagName, $attributeName ) 00306 { 00307 $attributeValueArray = array(); 00308 $elements = $this->Document->getElementsByTagName( $tagName ); 00309 foreach ( $elements as $element ) 00310 { 00311 $attributeValue = $element->getAttribute( $attributeName ); 00312 if ( $attributeValue ) 00313 { 00314 $attributeValueArray[] = $attributeValue; 00315 } 00316 } 00317 return $attributeValueArray; 00318 } 00319 00320 // Main recursive functions for rendering tags 00321 // $element - current element 00322 // $siblingParams - array of parameters that are passed by reference to all the children of the 00323 // current tag to provide a way to "communicate" between their handlers. 00324 // This array is empty for the first child. 00325 // $parentParams - parameter passed to this tag handler by the parent tag's handler. 00326 // This array is passed with no reference. Can by modified in tag's handler 00327 // for subordinate tags. 00328 00329 function outputTag( $element, &$siblingParams, $parentParams = array() ) 00330 { 00331 $tagName = $element->localName; 00332 if ( isset( $this->OutputTags[$tagName] ) ) 00333 { 00334 $currentTag = $this->OutputTags[$tagName]; 00335 } 00336 else 00337 { 00338 $currentTag = null; 00339 } 00340 00341 // Prepare attributes array 00342 $attributes = array(); 00343 if ( $element->hasAttributes() ) 00344 { 00345 $attributeNodes = $element->attributes; 00346 00347 foreach ( $attributeNodes as $attrNode ) 00348 { 00349 if ( $attrNode->prefix && $attrNode->prefix != 'custom' ) 00350 { 00351 $attrName = $attrNode->prefix . ':' . $attrNode->localName; 00352 } 00353 else 00354 { 00355 $attrName = $attrNode->nodeName; 00356 } 00357 00358 // classes check 00359 if ( $attrName == 'class' ) 00360 { 00361 $classesList = $this->XMLSchema->getClassesList( $tagName ); 00362 if ( !in_array( $attrNode->value, $classesList ) ) 00363 { 00364 eZDebug::writeWarning( "Using tag '$tagName' with class '$attrNode->value' is not allowed.", 'XML output handler' ); 00365 return array( true, '' ); 00366 } 00367 } 00368 00369 $attributes[$attrName] = $attrNode->value; 00370 } 00371 } 00372 00373 // Set default attribute values if not present in the input 00374 $attrDefaults = $this->XMLSchema->attrDefaultValues( $tagName ); 00375 foreach ( $attrDefaults as $name=>$value ) 00376 { 00377 if ( !isset( $attributes[$name] ) ) 00378 { 00379 $attributes[$name] = $value; 00380 } 00381 } 00382 00383 // Init handler returns an array that may contain the following items: 00384 // 00385 // 'no_render' (boolean) : 00386 // If false tag will not be rendered, only it's children (if any). 00387 // 'design_keys' array( 'design_key_name_1' => 'value_1', 'design_key_name_2'=>'value_2', ... ) : 00388 // An array of additional design keys. 00389 // 'tpl_vars' array( 'var_name_1' => 'value_1', 'var_name_2' => 'value_2', ... ) : 00390 // An array of additional template variables. 00391 // 'template_name' (string) : 00392 // Overrides tag template name. 00393 00394 $result = $this->callTagInitHandler( 'initHandler', $element, $attributes, $siblingParams, $parentParams ); 00395 00396 // Process children 00397 $childrenOutput = array(); 00398 if ( $element->hasChildNodes() ) 00399 { 00400 // Initialize sibiling parameters array for the next level children 00401 // Parent parameters for the children may be modified in the current tag handler. 00402 $nextSibilingParams = array(); 00403 00404 $this->NestingLevel++; 00405 foreach( $element->childNodes as $child ) 00406 { 00407 $childOutput = $this->outputTag( $child, $nextSibilingParams, $parentParams ); 00408 00409 if ( is_array( $childOutput[0] ) ) 00410 { 00411 $childrenOutput = array_merge( $childrenOutput, $childOutput ); 00412 } 00413 else 00414 { 00415 $childrenOutput[] = $childOutput; 00416 } 00417 } 00418 $this->NestingLevel--; 00419 } 00420 else 00421 { 00422 $childrenOutput = array( array( true, '' ) ); 00423 } 00424 00425 if ( isset( $result['no_render'] ) && $result['no_render'] ) 00426 { 00427 return $childrenOutput; 00428 } 00429 00430 // Set tpl variables by attributes and rename rules 00431 $vars = array(); 00432 00433 if ( !isset( $currentTag['quickRender'] ) && isset( $currentTag['attrNamesTemplate'] ) ) 00434 { 00435 $attrRenameRules = $currentTag['attrNamesTemplate']; 00436 } 00437 elseif ( isset( $currentTag['quickRender'] ) && isset( $currentTag['attrNamesQuick'] ) ) 00438 { 00439 $attrRenameRules = $currentTag['attrNamesQuick']; 00440 } 00441 else 00442 { 00443 $attrRenameRules = array(); 00444 } 00445 00446 foreach( $attributes as $name=>$value ) 00447 { 00448 if ( isset( $attrRenameRules[$name] ) ) 00449 { 00450 $vars[$attrRenameRules[$name]] = $value; 00451 continue; 00452 } 00453 00454 if ( strpos( $name, 'custom:' ) === 0 ) 00455 { 00456 $name = substr( $name, 7 ); 00457 } 00458 00459 $vars[$name] = $value; 00460 } 00461 00462 // set missing variables that have rename rules defined 00463 // but were not present in the element 00464 foreach( $attrRenameRules as $attrName=>$varName ) 00465 { 00466 if ( !isset( $attributes[$attrName] ) ) 00467 { 00468 $vars[$varName] = ''; 00469 } 00470 } 00471 00472 $this->TemplateUri = ''; 00473 00474 // In quick render mode we does not use templates and 00475 // render template variables as tag attributes 00476 if ( !isset( $currentTag['quickRender'] ) ) 00477 { 00478 // Set additional variables passed by tag handler 00479 if ( isset( $result['tpl_vars'] ) ) 00480 { 00481 $vars = array_merge( $vars, $result['tpl_vars'] ); 00482 } 00483 00484 foreach( $vars as $name=>$value ) 00485 { 00486 $this->Tpl->setVariable( $name, $value, 'xmltagns' ); 00487 } 00488 00489 // Create design keys array (including the ones with no value so they still overwrite values of parent tag) 00490 $designKeys = array(); 00491 if ( isset( $currentTag['attrDesignKeys'] ) ) 00492 { 00493 foreach( $currentTag['attrDesignKeys'] as $attrName=>$keyName ) 00494 { 00495 if ( isset( $attributes[$attrName] ) ) 00496 { 00497 $designKeys[$keyName] = $attributes[$attrName]; 00498 } 00499 } 00500 } 00501 // Set additional design keys passed by tag handler 00502 if ( isset( $result['design_keys'] ) ) 00503 { 00504 $designKeys = array_merge( $designKeys, $result['design_keys'] ); 00505 } 00506 00507 $existingKeys = $this->Res->keys(); 00508 $savedKeys = array(); 00509 00510 // Save old keys values and set new design keys 00511 foreach( $designKeys as $key=>$value ) 00512 { 00513 if ( isset( $existingKeys[$key] ) ) 00514 { 00515 $savedKeys[$key] = $existingKeys[$key]; 00516 } 00517 $this->Res->setKeys( array( array( $key, $value ) ) ); 00518 } 00519 00520 // Template name 00521 if ( isset( $result['template_name'] ) ) 00522 { 00523 $templateName = $result['template_name']; 00524 } 00525 else 00526 { 00527 $templateName = $element->nodeName; 00528 } 00529 $this->TemplateUri = $this->TemplatesPath . $templateName . '.tpl'; 00530 } 00531 00532 $output = $this->callTagRenderHandler( 'renderHandler', $element, $childrenOutput, $vars ); 00533 00534 if ( !isset( $currentTag['quickRender'] ) ) 00535 { 00536 // Restore saved template override keys and remove others 00537 foreach( $designKeys as $key => $value ) 00538 { 00539 if ( isset( $savedKeys[$key] ) ) 00540 { 00541 $this->Res->setKeys( array( array( $key, $savedKeys[$key] ) ) ); 00542 } 00543 else 00544 { 00545 $this->Res->removeKey( $key ); 00546 } 00547 } 00548 00549 // Unset variables 00550 foreach ( $vars as $name=>$value ) 00551 { 00552 if ( $this->Tpl->hasVariable( $name, 'xmltagns' ) ) 00553 { 00554 $this->Tpl->unsetVariable( $name, 'xmltagns' ); 00555 } 00556 } 00557 } 00558 00559 return $output; 00560 } 00561 00562 function renderTag( $element, $content, $vars ) 00563 { 00564 $currentTag = $this->OutputTags[$element->nodeName]; 00565 if ( $currentTag && isset( $currentTag['quickRender'] ) ) 00566 { 00567 $renderedTag = ''; 00568 $attrString = ''; 00569 foreach( $vars as $name => $value ) 00570 { 00571 if ( $value != '' ) 00572 { 00573 $attrString .= " $name=\"$value\""; 00574 } 00575 } 00576 00577 if ( isset( $currentTag['quickRender'][0] ) && $currentTag['quickRender'][0] ) 00578 { 00579 $renderedTag = '<' . $currentTag['quickRender'][0] . "$attrString>" . $content . '</' . $currentTag['quickRender'][0] . '>'; 00580 } 00581 else 00582 { 00583 $renderedTag = $content; 00584 } 00585 00586 if ( isset( $currentTag['quickRender'][1] ) && $currentTag['quickRender'][1] ) 00587 { 00588 $renderedTag .= $currentTag['quickRender'][1]; 00589 } 00590 } 00591 else 00592 { 00593 if ( isset( $currentTag['contentVarName'] ) ) 00594 { 00595 $contentVarName = $currentTag['contentVarName']; 00596 } 00597 else 00598 { 00599 $contentVarName = 'content'; 00600 } 00601 00602 $this->Tpl->setVariable( $contentVarName, $content, 'xmltagns' ); 00603 eZTemplateIncludeFunction::handleInclude( $textElements, $this->TemplateUri, $this->Tpl, 'foo', 'xmltagns' ); 00604 $renderedTag = is_array( $textElements ) ? implode( '', $textElements ) : ''; 00605 } 00606 return $renderedTag; 00607 } 00608 00609 // Default render handler 00610 // Renders all the content of children tags inside the current tag 00611 function renderAll( $element, $childrenOutput, $vars ) 00612 { 00613 $tagText = ''; 00614 foreach( $childrenOutput as $childOutput ) 00615 { 00616 $tagText .= $childOutput[1]; 00617 } 00618 $tagText = $this->renderTag( $element, $tagText, $vars ); 00619 return array( false, $tagText ); 00620 } 00621 00622 function callTagInitHandler( $handlerName, $element, &$attributes, &$siblingParams, &$parentParams ) 00623 { 00624 $result = array(); 00625 $thisOutputTag = $this->OutputTags[$element->nodeName]; 00626 if ( isset( $thisOutputTag[$handlerName] ) ) 00627 { 00628 if ( is_callable( array( $this, $thisOutputTag[$handlerName] ) ) ) 00629 { 00630 $result = call_user_func_array( array( $this, $thisOutputTag[$handlerName] ), 00631 array( $element, &$attributes, &$siblingParams, &$parentParams ) ); 00632 } 00633 } 00634 return $result; 00635 } 00636 00637 function callTagRenderHandler( $handlerName, $element, $childrenOutput, $vars ) 00638 { 00639 $result = array(); 00640 $thisOutputTag = $this->OutputTags[$element->nodeName]; 00641 if ( isset( $thisOutputTag[$handlerName] ) ) 00642 { 00643 $handlerFunction = $thisOutputTag[$handlerName]; 00644 } 00645 else 00646 { 00647 $handlerFunction = 'renderAll'; 00648 } 00649 00650 if ( is_callable( array( $this, $handlerFunction ) ) ) 00651 { 00652 $result = call_user_func_array( array( $this, $handlerFunction ), 00653 array( $element, $childrenOutput, $vars ) ); 00654 } 00655 else 00656 { 00657 eZDebug::writeWarning( "'$handlerName' render handler for tag <$element->nodeName> doesn't exist: '" . $thisOutputTag[$handlerName] . "'.", 'eZXML converter' ); 00658 } 00659 return $result; 00660 } 00661 00662 // This array should be overriden in derived class with the set of rules 00663 // for outputting tags. 00664 public $OutputTags = array(); 00665 00666 // Path to tags' templates 00667 public $TemplatesPath = 'design:content/datatype/view/ezxmltags/'; 00668 00669 /// Contains the XML data as text 00670 public $XMLData; 00671 public $Document; 00672 00673 public $XMLSchema; 00674 00675 public $AliasedType; 00676 public $AliasedHandler; 00677 00678 public $Output = ''; 00679 public $Tpl; 00680 public $TemplateURI = ''; 00681 public $Res; 00682 00683 public $AllowMultipleSpaces = false; 00684 public $AllowNumericEntities = false; 00685 00686 public $ContentObjectAttribute; 00687 public $ObjectAttributeID; 00688 00689 /// Contains the URL's for <link> tags hashed by ID 00690 public $LinkArray = array(); 00691 /// Contains the Objects hashed by ID 00692 public $ObjectArray = array(); 00693 /// Contains the Nodes hashed by ID 00694 public $NodeArray = array(); 00695 00696 public $NestingLevel = 0; 00697 } 00698 00699 ?>