eZ Publish  [trunk]
ezxmloutputhandler.php
Go to the documentation of this file.
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( '&', '&amp;', $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 ?>