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