Webylon 3.2 API Docs
  • Package
  • Function
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • Address
  • AddSubsiteTask
  • Announcement
  • AnnouncementDecorator
  • AnnouncementHolder
  • AssociatedFolderDecorator
  • AttachedFiles
  • AudioPhpCaptcha
  • AutoCompleteField
  • Base1CExchanger
  • BookingOrder_StatusLog
  • CalendarUtil
  • CalendarWidgetExtention
  • CatalogCatalogLinkedDecorator
  • CatalogOldFieldsDecorator
  • CatalogProductLinkedDecorator
  • CheckoutStep_ShippingMethod
  • ChequePayment
  • ClearHistoryTask
  • ClientEmailOrderNotification_SiteConfig
  • ClientVKOrderNotification_SiteConfig
  • CommentsSiteConfig
  • ContactsPage
  • CookieExtention
  • CostTableShippingRate
  • CustomMenu
  • CustomMenuAdmin
  • CustomMenuDecorator
  • CustomMenuHolder
  • CustomMenuItem
  • CustomPayment
  • DataObjectLoggerExtension
  • DataObjectSubsites
  • DefaultCMSActionDecorator
  • DocPage
  • DocumentDirection
  • DocumentHaving
  • DocumentSearchForm
  • DocumentSiteConfig
  • DocumentsPage
  • DocumentType
  • DOMUtil
  • ExtendPageAnnouncement
  • ExtendPagePoll
  • FavoriteExtention
  • FavoritePage
  • FavoriteProductMemberExtention
  • FavoriteProducts
  • FileOversizeDecorator
  • FindCyrillicSiteConfig
  • FolderRenameTask
  • GDMergeAlpha
  • HomePage
  • ImageAutoResize
  • ImageResizeTask
  • ImportCatalog1C_Importer
  • ImportCatalogSiteConfig
  • ImportSiteConfig
  • LoggerAdmin
  • LogItem
  • ManagerEmailOrderNotification_SiteConfig
  • MapObject
  • MapObjectGroup
  • MapPage
  • MediawebForm
  • MemberLoggerExtension
  • MonumentsSiteConfig
  • MultiUploadConfig
  • NearestEventExtention
  • NewDocumentsAdmin
  • NewsArchive
  • NewsEntry
  • NewsHolder
  • NewsSiteConfig
  • Order_StatusLog
  • Orders1CExchange_Exporter
  • PageHideFieldsExtension
  • PageInformerRates
  • PageInformerWeather
  • Payment_Failure
  • Payment_Processing
  • Payment_Result
  • Payment_Success
  • PaymentMethod
  • PaymentSiteConfig
  • PayPalPayment
  • PayPalPayment_Handler
  • PhoneField
  • PhotoAlbumExtentions
  • PhpCaptcha
  • PhpCaptchaColour
  • PhpCaptchaField
  • phpMorphy
  • phpMorphy_AncodesResolver_AsIs
  • phpMorphy_AncodesResolver_Proxy
  • phpMorphy_AncodesResolver_ToDialingAncodes
  • phpMorphy_AncodesResolver_ToText
  • phpMorphy_AnnotDecoder_Base
  • phpMorphy_AnnotDecoder_Common
  • phpMorphy_AnnotDecoder_Factory
  • phpMorphy_AnnotDecoder_Predict
  • phpMorphy_FilesBundle
  • phpMorphy_Fsa
  • phpMorphy_Fsa_Decorator
  • phpMorphy_Fsa_Proxy
  • phpMorphy_Fsa_Sparse_File
  • phpMorphy_Fsa_Sparse_Mem
  • phpMorphy_Fsa_Sparse_Shm
  • phpMorphy_Fsa_Tree_File
  • phpMorphy_Fsa_Tree_Mem
  • phpMorphy_Fsa_Tree_Shm
  • phpMorphy_Fsa_WordsCollector
  • phpMorphy_GramInfo
  • phpMorphy_GramInfo_AncodeCache
  • phpMorphy_GramInfo_Decorator
  • phpMorphy_Graminfo_File
  • phpMorphy_Graminfo_Mem
  • phpMorphy_GramInfo_Proxy
  • phpMorphy_GramInfo_Proxy_WithHeader
  • phpMorphy_GramInfo_RuntimeCaching
  • phpMorphy_Graminfo_Shm
  • phpMorphy_GrammemsProvider_Base
  • phpMorphy_GrammemsProvider_Decorator
  • phpMorphy_GrammemsProvider_Empty
  • phpMorphy_GrammemsProvider_Factory
  • phpMorphy_GrammemsProvider_ForFactory
  • phpMorphy_GrammemsProvider_ru_RU
  • phpMorphy_GramTab
  • phpMorphy_GramTab_Empty
  • phpMorphy_GramTab_Proxy
  • phpMorphy_Link
  • phpMorphy_Link_Annot
  • phpMorphy_Link_Base
  • phpMorphy_Morphier_Base
  • phpMorphy_Morphier_Bulk
  • phpMorphy_Morphier_Common
  • phpMorphy_Morphier_Empty
  • phpMorphy_Morphier_Finder_Base
  • phpMorphy_Morphier_Finder_Common
  • phpMorphy_Morphier_Finder_Predict_Databse
  • phpMorphy_Morphier_Finder_Predict_Suffix
  • phpMorphy_Morphier_Helper
  • phpMorphy_Morphier_Predict_Database
  • phpMorphy_Morphier_Predict_Suffix
  • phpMorphy_Morphier_PredictCollector
  • phpMorphy_Semaphore
  • phpMorphy_Semaphore_Empty
  • phpMorphy_Semaphore_Nix
  • phpMorphy_Semaphore_Win
  • phpMorphy_Shm_Cache
  • phpMorphy_Shm_Cache_FileDescriptor
  • phpMorphy_Shm_Header
  • phpMorphy_Source_Dba
  • phpMorphy_Source_Fsa
  • phpMorphy_State
  • phpMorphy_Storage
  • phpMorphy_Storage_Factory
  • phpMorphy_Storage_File
  • phpMorphy_Storage_Mem
  • phpMorphy_Storage_Proxy
  • phpMorphy_Storage_Shm
  • phpMorphy_UnicodeHelper
  • phpMorphy_UnicodeHelper_Base
  • phpMorphy_UnicodeHelper_MultiByteFixed
  • phpMorphy_UnicodeHelper_singlebyte
  • phpMorphy_UnicodeHelper_ucs_2be
  • phpMorphy_UnicodeHelper_ucs_2le
  • phpMorphy_UnicodeHelper_ucs_4be
  • phpMorphy_UnicodeHelper_ucs_4le
  • phpMorphy_UnicodeHelper_utf_16_Base
  • phpMorphy_UnicodeHelper_utf_16be
  • phpMorphy_UnicodeHelper_utf_16le
  • phpMorphy_UnicodeHelper_utf_32_Base
  • phpMorphy_UnicodeHelper_utf_32be
  • phpMorphy_UnicodeHelper_utf_32le
  • phpMorphy_UnicodeHelper_utf_8
  • phpMorphy_WordDescriptor
  • phpMorphy_WordDescriptor_Collection
  • phpMorphy_WordDescriptor_Collection_Serializer
  • phpMorphy_WordForm
  • ProductCatalogImportTask
  • ProductOldFieldsDecorator
  • ProductParamValue_BoolValueField
  • ProductParamValue_MultiValueField
  • ProductParamValue_MultiValueSetField
  • ProductParamValue_ValueField
  • ProductProductLinkedDecorator
  • PublHolder
  • Publication
  • PublicationSiteTree
  • RatingDataObject
  • RatingExtension
  • RealtySiteConfigDecorator
  • RecentComments
  • RecentFiles
  • RecentPages
  • RelatedPageLink
  • RoomRate_PriceField
  • RussianUpLower
  • SberbankPayment
  • SberbankPayment_Handler
  • SetMainSiteHomePageTypeTask
  • ShippingEstimator
  • ShippingPackage
  • ShowUserFromExtension
  • ShowViewedProductExtention
  • SiteConfigDecorator
  • SiteConfigSubsites
  • SiteTreeSubsites
  • SMSCOrderNotification_SiteConfig
  • SMSOrderNotification_SiteConfig
  • Socle
  • SocleSize
  • SortCMSActionDecorator
  • SS_Report_FakeQuery
  • SSMorphy
  • SSNController
  • SteppedCheckout_PageMessages
  • SubpageListField
  • SubscribeFormAllPagesExtension
  • SubsiteDropdownField
  • SubsiteReportWrapper
  • TableShippingRate
  • UnitellerPayment
  • UnitellerPayment_Handler
  • UnmoderatedComments
  • VAT
  • VideoManager
  • VideoSiteConfig
  • ViewedProductExtention
  • WatermarkImage
  • WatermarkSiteConfig
  • WeightTableShippingRate
  • XMLValidate
  • YaMoneyPayment
  • YaMoneyPayment_Handler
  • YMLExporter
  • YMLSiteConfig

Interfaces

  • PaymentObjectInterface
  • phpMorphy_AncodesResolver_Interface
  • phpMorphy_AnnotDecoder_Interface
  • phpMorphy_Fsa_Interface
  • phpMorphy_GramInfo_Interace
  • phpMorphy_GrammemsProvider_Interface
  • phpMorphy_GramTab_Interface
  • phpMorphy_Morphier_Finder_Interface
  • phpMorphy_Morphier_Interface
  • phpMorphy_Shm_Cache_Interface
  • phpMorphy_Source_Interface

Exceptions

  • phpMorphy_Exception

Functions

  • column_sort
  • column_sort_callback_basic
  • encodeFileForEmail
  • encodeMultipart
  • getMimeType
  • htmlEmail
  • loadMimeTypes
  • phpmorphy_overload_mb_funcs
  • plaintextEmail
  • processHeaders
  • QuotedPrintable_encode
  • supressOutput
  • validEmailAddr
  • wrapImagesInline
  • wrapImagesInline_rewriter
   1 <?php
   2 /**
   3  * The SSViewer executes a .ss template file.
   4  * The SSViewer class handles rendering of .ss templates.  In addition to a full template in
   5  * the views folder, a template in views/Content or views/Layout will be rendered into $Content and
   6  * $Layout, respectively.
   7  *
   8  * Compiled templates are cached.  If you put ?flush=1 on your URL, it will force the template to be recompiled.  This
   9  * is a hack; the system should really detect when a page needs re-fetching.
  10  * 
  11  * Works with the global $_TEMPLATE_MANIFEST which is compiled by {@link ManifestBuilder->getTemplateManifest()}.
  12  * This associative array lists all template filepaths by "identifier", meaning the name
  13  * of the template without its path or extension.
  14  * 
  15  * Example:
  16  * <code>
  17  * array(
  18  *  'LeftAndMain' => 
  19  *  array (
  20  *  'main' => '/my/system/path/cms/templates/LeftAndMain.ss',
  21  *  ),
  22  * 'CMSMain_left' => 
  23  *   array (
  24  *     'Includes' => '/my/system/path/cms/templates/Includes/CMSMain_left.ss',
  25  *   ),
  26  * 'Page' => 
  27  *   array (
  28  *     'themes' => 
  29  *     array (
  30  *       'blackcandy' => 
  31  *       array (
  32  *         'Layout' => '/my/system/path/themes/blackcandy/templates/Layout/Page.ss',
  33  *         'main' => '/my/system/path/themes/blackcandy/templates/Page.ss',
  34  *       ),
  35  *       'blue' => 
  36  *       array (
  37  *         'Layout' => '/my/system/path/themes/mysite/templates/Layout/Page.ss',
  38  *         'main' => '/my/system/path/themes/mysite/templates/Page.ss',
  39  *       ),
  40  *     ),
  41  *   ),
  42  *   // ...
  43  * )
  44  * </code>
  45  *
  46  * @todo Fix the broken caching.
  47  * @package sapphire
  48  * @subpackage view
  49  */
  50 class SSViewer {
  51     
  52     /**
  53      * @var boolean $source_file_comments
  54      */
  55     protected static $source_file_comments = false;
  56     
  57     /**
  58      * Set whether HTML comments indicating the source .SS file used to render this page should be
  59      * included in the output.  This is enabled by default
  60      *
  61      * @param boolean $val
  62      */
  63     static function set_source_file_comments($val) {
  64         self::$source_file_comments = $val;
  65     }
  66     
  67     /**
  68      * @return boolean
  69      */
  70     static function get_source_file_comments() {
  71         return self::$source_file_comments;
  72     }
  73     
  74     /**
  75      * @var array $chosenTemplates Associative array for the different
  76      * template containers: "main" and "Layout".
  77      */
  78     private $chosenTemplates = array();
  79     
  80     /**
  81      * @var boolean
  82      */
  83     protected $rewriteHashlinks = true;
  84     
  85     /**
  86      * @var string
  87      */
  88     protected static $current_theme = null;
  89     
  90     /**
  91      * @var string
  92      */
  93     protected static $current_custom_theme = null;
  94     
  95     /**
  96      * Create a template from a string instead of a .ss file
  97      * 
  98      * @return SSViewer
  99      */
 100     static function fromString($content) {
 101         return new SSViewer_FromString($content);
 102     }
 103     
 104     /**
 105      * @param string $theme
 106      */
 107     static function set_theme($theme) {
 108         self::$current_theme = $theme;
 109         //Static publishing needs to have a theme set, otherwise it defaults to the content controller theme
 110         if(!is_null($theme))
 111             self::$current_custom_theme=$theme;
 112     }
 113     
 114     /**
 115      * @return string 
 116      */
 117     static function current_theme() {
 118         return self::$current_theme;
 119     }
 120     
 121     /**
 122      * @return string
 123      */
 124     static function current_custom_theme(){
 125         return self::$current_custom_theme;
 126     }
 127     
 128     /**
 129      * Pass the SilverStripe template to be used.
 130      * 
 131      * @param string|array $templateList
 132      *   If passed as a string with .ss extension, used as the "main" template
 133      */
 134     public function __construct($templateList) {
 135         global $_TEMPLATE_MANIFEST;
 136         
 137         // flush template manifest cache if requested
 138         if (isset($_GET['flush']) && $_GET['flush'] == 'all') {
 139             if(Director::isDev() || Director::is_cli() || Permission::check('ADMIN')) {
 140                 self::flush_template_cache();
 141             } else {
 142                 return Security::permissionFailure(null, 'Please log in as an administrator to flush the template cache.');
 143             }
 144         }
 145         
 146         if(is_string($templateList) && substr((string) $templateList,-3) == '.ss') {
 147             $this->chosenTemplates['main'] = $templateList;
 148         } else {
 149             if(!is_array($templateList)) $templateList = array($templateList);
 150             
 151             if(isset($_GET['debug_request'])) Debug::message("Selecting templates from the following list: " . implode(", ", $templateList));
 152 
 153             foreach($templateList as $template) {
 154                 // if passed as a partial directory (e.g. "Layout/Page"), split into folder and template components
 155                 if(strpos($template,'/') !== false) list($templateFolder, $template) = explode('/', $template, 2);
 156                 else $templateFolder = null;
 157 
 158                 // Use the theme template if available
 159                 if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()])) {
 160                     $this->chosenTemplates = array_merge(
 161                         $_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()], 
 162                         $this->chosenTemplates
 163                     );
 164                     
 165                     if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main theme '" . self::current_theme() . "': " . var_export($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()], true));
 166                 }
 167                 
 168                 // Fall back to unthemed base templates
 169                 if(isset($_TEMPLATE_MANIFEST[$template]) && (array_keys($_TEMPLATE_MANIFEST[$template]) != array('themes'))) {
 170                     $this->chosenTemplates = array_merge(
 171                         $_TEMPLATE_MANIFEST[$template], 
 172                         $this->chosenTemplates
 173                     );
 174                     
 175                     if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main template archive, containing the following items: " . var_export($_TEMPLATE_MANIFEST[$template], true));
 176                     
 177                     unset($this->chosenTemplates['themes']);
 178                 }
 179 
 180                 if($templateFolder) {
 181                     $this->chosenTemplates['main'] = $this->chosenTemplates[$templateFolder];
 182                     unset($this->chosenTemplates[$templateFolder]);
 183                 }
 184             }
 185 
 186             if(isset($_GET['debug_request'])) Debug::message("Final template selections made: " . var_export($this->chosenTemplates, true));
 187 
 188         }
 189 
 190         if(!$this->chosenTemplates) user_error("None of these templates can be found in theme '"
 191             . self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING);
 192     }
 193     
 194     /**
 195      * Returns true if at least one of the listed templates exists
 196      */
 197     static function hasTemplate($templateList) {
 198         if(!is_array($templateList)) $templateList = array($templateList);
 199     
 200         global $_TEMPLATE_MANIFEST;
 201         foreach($templateList as $template) {
 202             if(strpos($template,'/') !== false) list($templateFolder, $template) = explode('/', $template, 2);
 203             if(isset($_TEMPLATE_MANIFEST[$template])) return true;
 204         }
 205         
 206         return false;
 207     }
 208     
 209     /**
 210      * Set a global rendering option.
 211      * The following options are available:
 212      *  - rewriteHashlinks: If true (the default), <a href="#..."> will be rewritten to contain the 
 213      *    current URL.  This lets it play nicely with our <base> tag.
 214      *  - If rewriteHashlinks = 'php' then, a piece of PHP script will be inserted before the hash 
 215      *    links: "<?php echo $_SERVER['REQUEST_URI']; ?>".  This is useful if you're generating a 
 216      *    page that will be saved to a .php file and may be accessed from different URLs.
 217      */
 218     public static function setOption($optionName, $optionVal) {
 219         SSViewer::$options[$optionName] = $optionVal;
 220     }
 221     protected static $options = array(
 222         'rewriteHashlinks' => true,
 223     );
 224     
 225     protected static $topLevel = array();
 226     public static function topLevel() {
 227         if(SSViewer::$topLevel) {
 228             return SSViewer::$topLevel[sizeof(SSViewer::$topLevel)-1];
 229         }
 230     }
 231     
 232     /**
 233      * Call this to disable rewriting of <a href="#xxx"> links.  This is useful in Ajax applications.
 234      * It returns the SSViewer objects, so that you can call new SSViewer("X")->dontRewriteHashlinks()->process();
 235      */
 236     public function dontRewriteHashlinks() {
 237         $this->rewriteHashlinks = false;
 238         self::$options['rewriteHashlinks'] = false;
 239         return $this;
 240     }
 241     
 242     public function exists() {
 243         return $this->chosenTemplates;
 244     }
 245     
 246     /**
 247      * Searches for a template name in the current theme:
 248      * - themes/mytheme/templates
 249      * - themes/mytheme/templates/Includes
 250      * Falls back to unthemed template files.
 251      * 
 252      * Caution: Doesn't search in any /Layout folders.
 253      * 
 254      * @param string $identifier A template name without '.ss' extension or path.
 255      * @return string Full system path to a template file
 256      */
 257     public static function getTemplateFile($identifier) {
 258         global $_TEMPLATE_MANIFEST;
 259         
 260         $includeTemplateFile = self::getTemplateFileByType($identifier, 'Includes');
 261         if($includeTemplateFile) return $includeTemplateFile;
 262         
 263         $mainTemplateFile = self::getTemplateFileByType($identifier, 'main');
 264         if($mainTemplateFile) return $mainTemplateFile;
 265         
 266         return false;
 267     }
 268     
 269     /**
 270      * @param string $identifier A template name without '.ss' extension or path
 271      * @param string $type The template type, either "main", "Includes" or "Layout"
 272      * @return string Full system path to a template file
 273      */
 274     public static function getTemplateFileByType($identifier, $type) {
 275         global $_TEMPLATE_MANIFEST;
 276         if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type])) {
 277             return $_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type];
 278         } else if(isset($_TEMPLATE_MANIFEST[$identifier][$type])){
 279             return $_TEMPLATE_MANIFEST[$identifier][$type];
 280         } else {
 281             return false;
 282         }
 283     }
 284     
 285     /**
 286      * Used by <% include Identifier %> statements to get the full
 287      * unparsed content of a template file.
 288      * 
 289      * @uses getTemplateFile()
 290      * @param string $identifier A template name without '.ss' extension or path.
 291      * @return string content of template
 292      */
 293     public static function getTemplateContent($identifier) {
 294         if(!SSViewer::getTemplateFile($identifier)) {
 295             return null;
 296         }
 297         
 298         $content = file_get_contents(SSViewer::getTemplateFile($identifier));
 299 
 300         // $content = "<!-- getTemplateContent() :: identifier: $identifier -->". $content; 
 301         // Adds an i18n namespace to all <% _t(...) %> calls without an existing one
 302         // to avoid confusion when using the include in different contexts.
 303         // Entities without a namespace are deprecated, but widely used.
 304         $content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\''. $identifier . '.ss' . '.\\2\\3\'\\4) ?>', $content);
 305         $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\), \$([a-zA-Z][a-zA-Z0-9_]*)\) +%' . '>', '<?= sprintf(_t(\'' . $identifier . '.ss.\\2\\3\'\\4),  {dlr}item->XML_val("\\6",null,true)) ?>', $content);
 306 
 307         // Remove UTF-8 byte order mark
 308         // This is only necessary if you don't have zend-multibyte enabled.
 309         if(substr($content, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
 310             $content = substr($content, 3);
 311         }
 312 
 313         return $content;
 314     }
 315     
 316     /**
 317      * @ignore
 318      */
 319     static private $flushed = false;
 320     
 321     /**
 322      * Clears all parsed template files in the cache folder.
 323      *
 324      * Can only be called once per request (there may be multiple SSViewer instances).
 325      */
 326     static function flush_template_cache() {
 327         if (!self::$flushed) {
 328             $dir = dir(TEMP_FOLDER);
 329             while (false !== ($file = $dir->read())) {
 330                 if (strstr($file, '.cache')) { unlink(TEMP_FOLDER.'/'.$file); }
 331             }
 332             self::$flushed = true;
 333         }
 334     }
 335     
 336     /**
 337      * The process() method handles the "meat" of the template processing.
 338      */
 339     public function process($item, $cache = null) {
 340         SSViewer::$topLevel[] = $item;
 341         
 342         if (!$cache) $cache = SS_Cache::factory('cacheblock');
 343         
 344         if(isset($this->chosenTemplates['main'])) {
 345             $template = $this->chosenTemplates['main'];
 346         } else {
 347             $template = $this->chosenTemplates[ reset($dummy = array_keys($this->chosenTemplates)) ];
 348         }
 349         
 350         if(isset($_GET['debug_profile'])) Profiler::mark("SSViewer::process", " for $template");
 351 //      $cacheFile = TEMP_FOLDER . "/.cache" . str_replace(array("\\", '/', ':'), '.', realpath($template));
 352         $cacheFile = TEMP_FOLDER . "/.cache" . str_replace(array("\\", '/', ':'), '.', str_replace(BASE_PATH, '', $template));
 353 
 354         $lastEdited = filemtime($template);
 355 
 356         if(!file_exists($cacheFile) 
 357             || (Director::isDev() && filemtime($cacheFile) < $lastEdited) 
 358             || (isset($_GET['flush']) && (Director::isDev() || Director::is_cli() || Permission::check('ADMIN')))
 359         ) {
 360             if(isset($_GET['debug_profile'])) Profiler::mark("SSViewer::process - compile", " for $template");
 361             
 362             $content = file_get_contents($template);
 363             $content = SSViewer::parseTemplateContent($content, $template);
 364             
 365             $fh = fopen($cacheFile,'w');
 366             fwrite($fh, $content);
 367             fclose($fh);
 368 
 369             if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process - compile", " for $template");
 370         }
 371     
 372         
 373         if(isset($_GET['showtemplate']) && !Director::isLive()) {
 374             $lines = file($cacheFile);
 375             echo "<h2>Template: $cacheFile</h2>";
 376             echo "<pre>";
 377             foreach($lines as $num => $line) {
 378                 echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
 379             }
 380             echo "</pre>";
 381         }
 382         
 383         
 384         foreach(array('Content', 'Layout') as $subtemplate) {
 385             if(isset($this->chosenTemplates[$subtemplate])) {
 386                 $subtemplateViewer = new SSViewer($this->chosenTemplates[$subtemplate]);
 387                 $item = $item->customise(array(
 388                     $subtemplate => $subtemplateViewer->process($item, $cache)
 389                 ));
 390             }
 391         }
 392         
 393         $itemStack = array();
 394         $val = "";
 395         $valStack = array();
 396         
 397         include($cacheFile);
 398 
 399         $output = $val;
 400         $output = Requirements::includeInHTML($template, $output);
 401         
 402         array_pop(SSViewer::$topLevel);
 403 
 404         if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process", " for $template");
 405         
 406         // If we have our crazy base tag, then fix # links referencing the current page.
 407         if($this->rewriteHashlinks && self::$options['rewriteHashlinks']) {
 408             if(strpos($output, '<base') !== false) {
 409                 if(SSViewer::$options['rewriteHashlinks'] === 'php') { 
 410                     $thisURLRelativeToBase = "<?php echo \$_SERVER['REQUEST_URI']; ?>"; 
 411                 } else { 
 412                     $thisURLRelativeToBase = Director::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI'])); 
 413                 }
 414                 $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
 415             }
 416         }
 417 
 418         return $output;
 419     }
 420 
 421     static function parseTemplateContent($content, $template="") {
 422         // Remove UTF-8 byte order mark:
 423         // This is only necessary if you don't have zend-multibyte enabled.
 424         if(substr($content, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
 425             $content = substr($content, 3);
 426         }
 427 
 428         // Add template filename comments on dev sites
 429         if(Director::isDev() && self::$source_file_comments && $template && stripos($content, "<?xml") === false) {
 430             // If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
 431             if(stripos($content, "<html") !== false) {
 432                 $content = preg_replace('/(<html[^>]*>)/i', "\\1<!-- template $template -->", $content);
 433                 $content = preg_replace('/(<\/html[^>]*>)/i', "\\1<!-- end template $template -->", $content);
 434             } else {
 435                 $content = "<!-- template $template -->\n" . $content . "\n<!-- end template $template -->";
 436             }
 437         }
 438         
 439         while(true) {
 440             $oldContent = $content;
 441             
 442             // Add include filename comments on dev sites
 443             if(Director::isDev() && self::$source_file_comments) $replacementCode = 'return "<!-- include " . SSViewer::getTemplateFile($matches[1]) . " -->\n" 
 444                 . SSViewer::getTemplateContent($matches[1]) 
 445                 . "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . " -->";';
 446             else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);';
 447             
 448             $content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(
 449                 '$matches', $replacementCode
 450                 ), $content);
 451             if($oldContent == $content) break;
 452         }
 453         
 454         // $val, $val.property, $val(param), etc.
 455         $replacements = array(
 456             '/<%--.*--%>/U' =>  '',
 457             '/\$Iteration/' =>  '<?= {dlr}key ?>',
 458             // {$funct(arg1, arg2, arg3)}
 459             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+), *([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3","\\4"),true) ?>',
 460             // {$funct(arg1, arg2).prop.subporp}
 461             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->obj("\\4",null,true)->XML_val("\\5",null,true) ?>',
 462             // {$funct(arg1, arg2).prop}
 463             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>',
 464             // {$funct(arg1, arg2)}
 465             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>',
 466             // {$funct(arg1).prop.subporp}
 467             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->obj("\\3",null,true)->XML_val("\\4",null,true) ?>',
 468             // {$funct(arg1).prop}
 469             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>',
 470             // {$funct(arg1)}
 471             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>',
 472             // {$obj.funct($arg).prop}
 473             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->obj("\\2",array("\\3"),true)->XML_val("\\4",null,true) ?>',
 474             // {$obj.funct($arg)}
 475             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",array("\\3"),true) ?>',
 476             // {$obj.prop.subporp}
 477             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->obj("\\2",null,true)->XML_val("\\3",null,true) ?>',
 478             // {$obj.prop}
 479             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>',
 480             // {$obj}
 481             '/{\\$([A-Za-z_][A-Za-z0-9_]*)}/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
 482 
 483             // $funct(arg1, arg2, arg3)
 484             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+), *([^),]+)\\)/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3","\\4"),true) ?>',
 485             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1")->XML_val("\\2",array("\\3"),true) ?>\\4',
 486             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1")->XML_val("\\2",array("\\3", "\\4"),true) ?>\\5',
 487 
 488             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->obj("\\4",null,true)->XML_val("\\5",null,true) ?>\\6',
 489             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>\\5',
 490             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>\\4',
 491             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->obj("\\3",null,true)->XML_val("\\4",null,true) ?>\\5',
 492             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>\\4',
 493             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>\\3',
 494             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",null,true)->obj("\\2",null,true)->XML_val("\\3",null,true) ?>\\4',
 495             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>\\3',
 496             '/\\$([A-Za-z_][A-Za-z0-9_]*)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
 497         );
 498         
 499         $content = preg_replace(array_keys($replacements), array_values($replacements), $content);
 500         $content = str_replace('{dlr}','$',$content);
 501 
 502         // Cache block
 503         $content = SSViewer_PartialParser::process($template, $content);
 504 
 505         // legacy
 506         $content = ereg_replace('<!-- +pc +([A-Za-z0-9_(),]+) +-->', '<' . '% control \\1 %' . '>', $content);
 507         $content = ereg_replace('<!-- +pc_end +-->', '<' . '% end_control %' . '>', $content);
 508         
 509         // < % select Foo % >
 510         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1")) { ?>', $content);
 511         // < % select Foo(Bar) % >
 512         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1", array("\\2"))) { ?>', $content);
 513         // < % select Foo.Bar % >
 514         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1")->hasValue("\\2")) {  ?>', $content);
 515         // < % select Foo.Bar(Baz) % >
 516         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if (($item = $item->obj("\\1")) && ($item = $item->obj("\\2", array("\\3")))) { ?>', $content);
 517         $content = ereg_replace('<' . '% +end_select +%' . '>', '<? } $item = array_pop($itemStack); ?>', $content);
 518 
 519         // < % control Foo % >
 520         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1")) foreach($loop as $key => $item) { ?>', $content);
 521         // < % control Foo.Bar % >
 522         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if(($loop = $item->obj("\\1")) && ($loop = $loop->obj("\\2"))) foreach($loop as $key => $item) { ?>', $content);
 523         // < % control Foo.Bar(Baz) % >
 524         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if(($loop = $item->obj("\\1")) && ($loop = $loop->obj("\\2", array("\\3")))) foreach($loop as $key => $item) { ?>', $content);
 525         // < % control Foo.Bar(Baz, Buz) % >
 526         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if(($loop = $item->obj("\\1")) && ($loop = $loop->obj("\\2", array("\\3", "\\4")))) foreach($loop as $key => $item) { ?>', $content);
 527         // < % control Foo(Bar) % >
 528         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2"))) foreach($loop as $key => $item) { ?>', $content);
 529         // < % control Foo(Bar, Baz) % >
 530         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2","\\3"))) foreach($loop as $key => $item) { ?>', $content);
 531         // < % control Foo(Bar, Baz, Buz) % >
 532         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+), *([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2", "\\3", "\\4"))) foreach($loop as $key => $item) { ?>', $content);
 533         $content = ereg_replace('<' . '% +end_control +%' . '>', '<? } $item = array_pop($itemStack); ?>', $content);
 534 
 535         // < % debug % >
 536         $content = ereg_replace('<' . '% +debug +%' . '>', '<? Debug::show($item) ?>', $content);
 537         // < % debug Foo % >
 538         $content = ereg_replace('<' . '% +debug +([A-Za-z0-9_]+) +%' . '>', '<? Debug::show($item->cachedCall("\\1")) ?>', $content);
 539 
 540         // < % if val1.property % >
 541         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? if($item->obj("\\1",null,true)->hasValue("\\2")) {  ?>', $content);
 542         
 543         // < % if val1(parameter) % >
 544         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? if($item->hasValue("\\1",array("\\2"))) {  ?>', $content);
 545         // < % if val1(par1, par2) % >
 546         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\) +%' . '>', '<? if($item->hasValue("\\1",array("\\2","\\3"))) {  ?>', $content);
 547         // < % if val1(parameter).property % >
 548         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\)\\.([A-Za-z0-9_]+) +%' . '>', '<? if ($item->obj("\\1",array("\\2"),true)->hasValue("\\3")) {  ?>', $content);
 549 
 550         // < % if val1 % >
 551         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1")) {  ?>', $content);
 552         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1")) {  ?>', $content);
 553 
 554         // < % if val1 || val2 % >
 555         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
 556         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
 557 
 558         // < % if val1 && val2 % >
 559         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
 560         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
 561 
 562         // < % if val1.property || val2 % >
 563         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->obj("\\1",null,true)->hasValue("\\2") || $item->hasValue("\\3")) {  ?>', $content);
 564         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? else if($item->obj("\\1",null,true)->hasValue("\\2") || $item->hasValue("\\3")) {  ?>', $content);
 565 
 566         // < % if val1.property && val2 % >
 567         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->obj("\\1",null,true)->hasValue("\\2") && $item->hasValue("\\3")) {  ?>', $content);
 568         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? else if($item->obj("\\1",null,true)->hasValue("\\2") && $item->hasValue("\\3")) {  ?>', $content);
 569 
 570         // < % if val1 == val2 % >
 571         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) == "\\2") {  ?>', $content);
 572         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) == "\\2") {  ?>', $content);
 573 
 574         // < % if val1 > val2 % >
 575         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
 576         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
 577 
 578         // < % if val1 < val2 % >
 579         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
 580         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
 581 
 582         // < % if val1 >= val2 % >
 583         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *>= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) >= "\\2") {  ?>', $content);
 584         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *>= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
 585 
 586         // < % if val1 <= val2 % >
 587         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *<= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) <= "\\2") {  ?>', $content);
 588         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *<= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
 589 
 590         // <% if val1.property == val2 % >
 591         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->obj("\\1",null,true)->XML_val("\\2") == "\\3") {  ?>', $content);
 592         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->obj("\\1",null,true)->XML_val("\\2") == "\\3") {  ?>', $content);
 593 
 594         // <% if val1.property > val2 % >
 595         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->obj("\\1",null,true)->XML_val("\\2") > "\\3") {  ?>', $content);
 596         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->obj("\\1",null,true)->XML_val("\\2") > "\\3") {  ?>', $content);
 597 
 598         // <% if val1.property < val2 % >
 599         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->obj("\\1",null,true)->XML_val("\\2") < "\\3") {  ?>', $content);
 600         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->obj("\\1",null,true)->XML_val("\\2") < "\\3") {  ?>', $content);
 601 
 602         // <% if val1(parametr) == val2 % >
 603         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",array("\\2"),true) == "\\3") {  ?>', $content);
 604         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\(([^),]+)\\) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",array("\\2"),true) == "\\3") {  ?>', $content);
 605 
 606         // <% if val1(parametr).property == val2 % >
 607         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\)\\.([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->obj("\\1",array("\\2"),true)->XML_val("\\3") == "\\4") {  ?>', $content);
 608         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\(([^),]+)\\)\\.([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->obj("\\1",array("\\2"),true)->XML_val("\\3") == "\\4") {  ?>', $content);
 609         
 610         // < % if val1 != val2 % >
 611         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) != "\\2") {  ?>', $content);
 612         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) != "\\2") {  ?>', $content);
 613 
 614         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->cachedCall("\\1")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) {  ?>', $content);
 615 
 616         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? $test = $item->obj("\\1",null,true)->cachedCall("\\2"); if((!is_object($test) && $test) || ($test && $test->exists())) {  ?>', $content);
 617         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->obj("\\1",null,true)->cachedCall("\\2")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) {  ?>', $content);
 618 
 619         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? $test = $item->obj("\\1",null,true)->obj("\\2",null,true)->cachedCall("\\3"); if((!is_object($test) && $test) || ($test && $test->exists())) {  ?>', $content);
 620         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->obj("\\1",null,true)->obj("\\2",null,true)->cachedCall("\\3")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) {  ?>', $content);
 621         
 622         $content = ereg_replace('<' . '% +else +%' . '>', '<? } else { ?>', $content);
 623         $content = ereg_replace('<' . '% +end_if +%' . '>', '<? }  ?>', $content);
 624 
 625         // i18n - get filename of currently parsed template
 626         // CAUTION: No spaces allowed between arguments for all i18n calls!
 627         ereg('.*[\/](.*)',$template,$path);
 628         
 629         // i18n _t(...) - with entity only (no dots in namespace), 
 630         // meaning the current template filename will be added as a namespace. 
 631         // This applies only to "root" templates, not includes which should always have their namespace set already.
 632         // See getTemplateContent() for more information.
 633         $content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\''. $path[1] . '.\\2\\3\'\\4) ?>', $content);
 634         // i18n _t(...)
 635         $content = ereg_replace('<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\'\\2\\3\'\\4) ?>', $content);
 636 
 637         // i18n sprintf(_t(...),$argument) with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
 638         $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\), *\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\''. $path[1] . '.\\2\\3\'\\4),\\6) ?>', $content);
 639         // i18n sprintf(_t(...),$argument)
 640         $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\), *\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\'\\2\\3\'\\4),\\6) ?>', $content);
 641 
 642         // </base> isnt valid html? !? 
 643         $content = ereg_replace('<' . '% +base_tag +%' . '>', '<?= SSViewer::get_base_tag($val); ?>', $content);
 644 
 645         $content = ereg_replace('<' . '% +current_page +%' . '>', '<?= @$_SERVER["REQUEST_URI"] ?>', $content);
 646         
 647         // change < % require x() % > calls to corresponding Requirement::x() ones, including 0, 1 or 2 options
 648         $content = preg_replace('/<% +require +([a-zA-Z]+)(?:\(([^),]+)\))? +%>/', '<? Requirements::\\1("\\2"); ?>', $content);
 649         $content = preg_replace('/<% +require +([a-zA-Z]+)\(([^),]+), *([^),]+)\) +%>/', '<? Requirements::\\1("\\2", "\\3"); ?>', $content);
 650         
 651         // legacy
 652         $content = ereg_replace('<!-- +if +([A-Za-z0-9_]+) +-->', '<? if($item->cachedCall("\\1")) { ?>', $content);
 653         $content = ereg_replace('<!-- +else +-->', '<? } else { ?>', $content);
 654         $content = ereg_replace('<!-- +if_end +-->', '<? }  ?>', $content);
 655             
 656         // Fix link stuff
 657         $content = ereg_replace('href *= *"#', 'href="<?= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "" ?>#', $content);
 658     
 659         // Protect xml header
 660         $content = ereg_replace('<\?xml([^>]+)\?' . '>', '<##xml\\1##>', $content);
 661 
 662         // Turn PHP file into string definition
 663         $content = str_replace('<?=',"\nSSVIEWER;\n\$val .= ", $content);
 664         $content = str_replace('<?',"\nSSVIEWER;\n", $content);
 665         $content = str_replace('?>',";\n \$val .= <<<SSVIEWER\n", $content);
 666         
 667         $output  = "<?php\n";
 668         $output .= '$val .= <<<SSVIEWER' . "\n" . $content . "\nSSVIEWER;\n"; 
 669         
 670         // Protect xml header @sam why is this run twice ?
 671         $output = ereg_replace('<##xml([^>]+)##>', '<' . '?xml\\1?' . '>', $output);
 672     
 673         return $output;
 674     }
 675 
 676     /**
 677      * Returns the filenames of the template that will be rendered.  It is a map that may contain
 678      * 'Content' & 'Layout', and will have to contain 'main'
 679      */
 680     public function templates() {
 681         return $this->chosenTemplates;
 682     }
 683     
 684     /**
 685      * @param string $type "Layout" or "main"
 686      * @param string $file Full system path to the template file
 687      */
 688     public function setTemplateFile($type, $file) {
 689         $this->chosenTemplates[$type] = $file;
 690     }
 691     
 692     /**
 693      * Return an appropriate base tag for the given template.
 694      * It will be closed on an XHTML document, and unclosed on an HTML document.
 695      * 
 696      * @param $contentGeneratedSoFar The content of the template generated so far; it should contain
 697      * the DOCTYPE declaration.
 698      */
 699     static function get_base_tag($contentGeneratedSoFar) {
 700         $base = Director::absoluteBaseURL();
 701         
 702         // Is the document XHTML?
 703         if(preg_match('/<!DOCTYPE[^>]+xhtml/i', $contentGeneratedSoFar)) {
 704             return "<base href=\"$base\"></base>";
 705         } else {
 706             return "<base href=\"$base\"><!--[if lte IE 6]></base><![endif]-->";
 707         }
 708     }
 709 }
 710 
 711 /**
 712  * Special SSViewer that will process a template passed as a string, rather than a filename.
 713  * @package sapphire
 714  * @subpackage view
 715  */
 716 class SSViewer_FromString extends SSViewer {
 717     protected $content;
 718     
 719     public function __construct($content) {
 720         $this->content = $content;
 721     }
 722     
 723     public function process($item, $cache = null) {
 724         $template = SSViewer::parseTemplateContent($this->content, "string sha1=".sha1($this->content));
 725 
 726         $tmpFile = tempnam(TEMP_FOLDER,"");
 727         $fh = fopen($tmpFile, 'w');
 728         fwrite($fh, $template);
 729         fclose($fh);
 730 
 731         if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
 732             $lines = file($tmpFile);
 733             echo "<h2>Template: $tmpFile</h2>";
 734             echo "<pre>";
 735             foreach($lines as $num => $line) {
 736                 echo str_pad($num+1,5) . htmlentities($line);
 737             }
 738             echo "</pre>";
 739         }
 740 
 741         $itemStack = array();
 742         $val = "";
 743         $valStack = array();
 744         
 745         $cache = SS_Cache::factory('cacheblock');
 746         
 747         include($tmpFile);
 748         unlink($tmpFile);
 749         
 750 
 751         return $val;
 752     }
 753 }
 754 
 755 /**
 756  * Handle the parsing for cacheblock tags.
 757  * 
 758  * Needs to be handled differently from the other tags, because cacheblock can take any number of arguments
 759  * 
 760  * This shouldn't be used as an example of how to add functionality to SSViewer - the eventual plan is to re-write
 761  * SSViewer using a proper parser (probably http://github.com/hafriedlander/php-peg), so that extra functionality
 762  * can be added without relying on ad-hoc parsers like this.
 763  * 
 764  * @package sapphire
 765  * @subpackage view
 766  */
 767 class SSViewer_PartialParser {
 768     
 769     static $tag = '/< % [ \t]+ (cached|cacheblock|uncached|end_cached|end_cacheblock|end_uncached) [ \t]+ ([^%]+ [ \t]+)? % >/xS';
 770     
 771     static $argument_splitter = '/^\s*
 772         # The argument itself
 773         (
 774             (?P<conditional> if | unless ) |                     # The if or unless keybreak
 775             (?P<property> (?P<identifier> \w+) \s*               # A property lookup or a function call
 776                 ( \( (?P<arguments> [^\)]*) \) )?
 777             ) |
 778             (?P<sqstring> \' (\\\'|[^\'])+ \' ) |                # A string surrounded by \'
 779             (?P<dqstring> " (\\"|[^"])+ " )                      # A string surrounded by "
 780         )
 781         # Some seperator after the argument
 782         (
 783             \s*(?P<comma>,)\s* |                                 # A comma (maybe with whitespace before or after)
 784             (?P<fullstop>\.)                                     # A period (no whitespace before)
 785         )?
 786     /xS';
 787     
 788     static function process($template, $content) {
 789         $parser = new SSViewer_PartialParser($template, $content, 0);
 790         $parser->parse();
 791         return $parser->generate();
 792     }
 793     
 794     function __construct($template, $content, $offset) {
 795         $this->template = $template;
 796         $this->content = $content;
 797         $this->offset = $offset;
 798 
 799         $this->blocks = array();
 800     }
 801 
 802     function controlcheck($text) {
 803         // NOP - hook for Cached_PartialParser
 804     }
 805 
 806     function parse() {
 807         $current_tag_offset = 0;
 808         
 809         while (preg_match(self::$tag, $this->content, $matches, PREG_OFFSET_CAPTURE, $this->offset)) {
 810             $tag = $matches[1][0];
 811 
 812             $startpos = $matches[0][1];
 813             $endpos = $matches[0][1] + strlen($matches[0][0]);
 814 
 815             switch($tag) {
 816                 case 'cached':
 817                 case 'uncached':
 818                 case 'cacheblock':
 819 
 820                     $pretext = substr($this->content, $this->offset, $startpos - $this->offset);
 821                     $this->controlcheck($pretext);
 822                     $this->blocks[] = $pretext;
 823 
 824                     if ($tag == 'cached' || $tag == 'cacheblock') {
 825                         list($keyparts, $conditional, $condition) = $this->parseargs(@$matches[2][0]);
 826                         $parser = new SSViewer_Cached_PartialParser($this->template, $this->content, $endpos, $keyparts, $conditional, $condition);
 827                     }
 828                     else {
 829                         $parser = new SSViewer_PartialParser($this->template, $this->content, $endpos);
 830                     }
 831 
 832                     $parser->parse();
 833                     $this->blocks[] = $parser;
 834                     $this->offset = $parser->offset;
 835                     break;
 836 
 837                 case 'end_cached':
 838                 case 'end_cacheblock':
 839                 case 'end_uncached':
 840                     $this->blocks[] = substr($this->content, $this->offset, $startpos - $this->offset);
 841                     $this->content = null;
 842 
 843                     $this->offset = $endpos;
 844                     return $this;
 845             }
 846         }
 847 
 848         $this->blocks[] = substr($this->content, $this->offset);
 849         $this->content = null;
 850     }
 851 
 852     function parseargs($string) {
 853         preg_match_all(self::$argument_splitter, $string, $matches, PREG_SET_ORDER);
 854 
 855         $parts = array();
 856         $conditional = null; $condition = null;
 857 
 858         $current = '$item->';
 859 
 860         while (strlen($string) && preg_match(self::$argument_splitter, $string, $match)) {
 861 
 862             $string = substr($string, strlen($match[0]));
 863 
 864             // If this is a conditional keyword, break, and the next loop will grab the conditional
 865             if (@$match['conditional']) {
 866                 $conditional = $match['conditional'];
 867                 continue;
 868             }
 869 
 870             // If it's a property lookup or a function call
 871             if (@$match['property']) {
 872                 // Get the property
 873                 $what = $match['identifier'];
 874                 $args = array();
 875 
 876                 // Extract any arguments passed to the function call
 877                 if (@$match['arguments']) {
 878                     foreach (explode(',', $match['arguments']) as $arg) {
 879                         $args[] = is_numeric($arg) ? (string)$arg : '"'.$arg.'"';
 880                     }
 881                 }
 882 
 883                 $args = empty($args) ? 'null' : 'array('.implode(',',$args).')';
 884 
 885                 // If this fragment ended with '.', then there's another lookup coming, so return an obj for that lookup
 886                 if (@$match['fullstop']) {
 887                     $current .= "obj('$what', $args, true)->";
 888                 }
 889                 // Otherwise this is the end of the lookup chain, so add the resultant value to the key array and reset the key-get php fragement
 890                 else {
 891                     $accessor = $current . "XML_val('$what', $args, true)"; $current = '$item->';
 892 
 893                     // If we've hit a conditional already, this is the condition. Set it and be done.
 894                     if ($conditional) {
 895                         $condition = $accessor;
 896                         break;
 897                     }
 898                     // Otherwise we're another key component. Add it to array.
 899                     else $parts[] = $accessor;
 900                 }
 901             }
 902 
 903             // Else it's a quoted string of some kind
 904             else if (@$match['sqstring']) $parts[] = $match['sqstring'];
 905             else if (@$match['dqstring']) $parts[] = $match['dqstring'];
 906         }
 907 
 908         if ($conditional && !$condition) {
 909             throw new Exception("You need to have a condition after the conditional $conditional in your cache block");
 910         }
 911 
 912         return array($parts, $conditional, $condition);
 913     }
 914 
 915     function generate() {
 916         $res = array();
 917 
 918         foreach ($this->blocks as $i => $block) {
 919             if ($block instanceof SSViewer_PartialParser)
 920                 $res[] = $block->generate();
 921             else {
 922                 $res[] = $block;
 923             }
 924         }
 925 
 926         return implode('', $res);
 927     }
 928 }
 929 
 930 /**
 931  * @package sapphire
 932  * @subpackage view
 933  */
 934 class SSViewer_Cached_PartialParser extends SSViewer_PartialParser {
 935 
 936     function __construct($template, $content, $offset, $keyparts, $conditional, $condition) {
 937         $this->keyparts = $keyparts;
 938         $this->conditional = $conditional;
 939         $this->condition = $condition;
 940 
 941         parent::__construct($template, $content, $offset);
 942     }
 943 
 944     function controlcheck($text) {
 945         $ifs = preg_match_all('/<'.'% +if +/', $text, $matches);
 946         $end_ifs = preg_match_all('/<'.'% +end_if +/', $text, $matches);
 947 
 948         if ($ifs != $end_ifs) throw new Exception('You can\'t have cached or uncached blocks within condition structures');
 949 
 950         $controls = preg_match_all('/<'.'% +control +/', $text, $matches);
 951         $end_controls = preg_match_all('/<'.'% +end_control +/', $text, $matches);
 952 
 953         if ($controls != $end_controls) throw new Exception('You can\'t have cached or uncached blocks within control structures');
 954     }
 955 
 956     function key() {
 957         if (empty($this->keyparts)) return "''";
 958         return 'sha1(' . implode(".'_'.", $this->keyparts) . ')';
 959     }
 960 
 961     function generate() {
 962         $res = array();
 963         $key = $this->key();
 964 
 965         $condition = "";
 966 
 967         switch ($this->conditional) {
 968             case 'if':
 969                 $condition = "{$this->condition} && ";
 970                 break;
 971             case 'unless':
 972                 $condition = "!({$this->condition}) && ";
 973                 break;
 974         }
 975 
 976         /* Output this set of blocks */
 977 
 978         foreach ($this->blocks as $i => $block) {
 979             if ($block instanceof SSViewer_PartialParser)
 980                 $res[] = $block->generate();
 981             else {
 982                 // Include the template name and this cache block's current contents as a sha hash, so we get auto-seperation
 983                 // of cache blocks, and invalidation of the cache when the template changes
 984                 $partialkey = "'".sha1($this->template . $block)."_'.$key.'_$i'";
 985 
 986                 // Try to load from cache
 987                 $res[] = "<?\n".'if ('.$condition.' ($partial = $cache->load('.$partialkey.'))) $val .= $partial;'."\n";
 988 
 989                 // Cache miss - regenerate
 990                 $res[] = "else {\n";
 991                 $res[] = '$oldval = $val; $val = "";'."\n";
 992                 $res[] = "\n?>" . $block . "<?\n";
 993                 $res[] = $condition . ' $cache->save($val); $val = $oldval . $val ;'."\n";
 994                 $res[] = "}\n?>";
 995             }
 996         }
 997 
 998         return implode('', $res);
 999     }
1000 }
1001 
1002 function supressOutput() {
1003     return "";
1004 }
1005 
1006 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0