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