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

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • 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

  • ArrayData
  • CalendarWidget
  • DataObjectManagerAction
  • LiveCalendarWidget
  • MonthNavigator
  • Requirements
  • Requirements_Backend
  • SSViewer
  • SSViewer_Cached_PartialParser
  • SSViewer_FromString
  • SSViewer_PartialParser
  • ViewableData
  • ViewableData_Customised
  • ViewableData_Debugger
  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             '/{\\$([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) ?>',
459             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>',
460             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>',
461             '/{\\$([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) ?>',
462             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>',
463             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>',
464             '/{\\$([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) ?>',
465             '/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>',
466             '/{\\$([A-Za-z_][A-Za-z0-9_]*)}/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
467 
468             '/\\$([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',
469             '/\\$([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',
470 
471             '/\\$([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',
472             '/\\$([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',
473             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>\\4',
474             '/\\$([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',
475             '/\\$([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',
476             '/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>\\3',
477             '/\\$([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',
478             '/\\$([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',
479             '/\\$([A-Za-z_][A-Za-z0-9_]*)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2',
480         );
481         
482         $content = preg_replace(array_keys($replacements), array_values($replacements), $content);
483         $content = str_replace('{dlr}','$',$content);
484 
485         // Cache block
486         $content = SSViewer_PartialParser::process($template, $content);
487 
488         // legacy
489         $content = ereg_replace('<!-- +pc +([A-Za-z0-9_(),]+) +-->', '<' . '% control \\1 %' . '>', $content);
490         $content = ereg_replace('<!-- +pc_end +-->', '<' . '% end_control %' . '>', $content);
491         
492         // < % select Foo % >
493         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1")) { ?>', $content);
494         // < % select Foo(Bar) % >
495         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1",array("\\2"))) { ?>', $content);
496         // < % select Foo.Bar % >
497         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1")->hasValue("\\2")) {  ?>', $content);
498         // < % select Foo.Bar(Baz) % >
499         $content = ereg_replace('<' . '% +select +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? array_push($itemStack, $item); if ($item = $item->obj("\\1")->hasValue("\\2",array("\\3"))) { ?>', $content);
500         $content = ereg_replace('<' . '% +end_select +%' . '>', '<? } $item = array_pop($itemStack); ?>', $content);
501 
502         // < % control Foo % >
503         $content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+) +%' . '>', '<? array_push($itemStack, $item); if($loop = $item->obj("\\1")) foreach($loop as $key => $item) { ?>', $content);
504         // < % control Foo.Bar % >
505         $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);
506         // < % control Foo.Bar(Baz) % >
507         $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);
508         // < % control Foo.Bar(Baz, Buz) % >
509         $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);
510         // < % control Foo(Bar) % >
511         $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);
512         // < % control Foo(Bar, Baz) % >
513         $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);
514         // < % control Foo(Bar, Baz, Buz) % >
515         $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);
516         $content = ereg_replace('<' . '% +end_control +%' . '>', '<? } $item = array_pop($itemStack); ?>', $content);
517 
518         // < % debug % >
519         $content = ereg_replace('<' . '% +debug +%' . '>', '<? Debug::show($item) ?>', $content);
520         // < % debug Foo % >
521         $content = ereg_replace('<' . '% +debug +([A-Za-z0-9_]+) +%' . '>', '<? Debug::show($item->cachedCall("\\1")) ?>', $content);
522 
523         // < % if val1.property % >
524         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>', '<? if($item->obj("\\1",null,true)->hasValue("\\2")) {  ?>', $content);
525         
526         // < % if val1(parameter) % >
527         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\) +%' . '>', '<? if($item->hasValue("\\1",array("\\2"))) {  ?>', $content);
528         // < % if val1(par1, par2) % >
529         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+), *([^),]+)\\) +%' . '>', '<? if($item->hasValue("\\1",array("\\2","\\3"))) {  ?>', $content);
530         // < % if val1(parameter).property % >
531         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\)\\.([A-Za-z0-9_]+) +%' . '>', '<? if ($item->obj("\\1",array("\\2"),true)->hasValue("\\3")) {  ?>', $content);
532 
533         // < % if val1 % >
534         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1")) {  ?>', $content);
535         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1")) {  ?>', $content);
536 
537         // < % if val1 || val2 % >
538         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
539         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>', $content);
540 
541         // < % if val1 && val2 % >
542         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
543         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>', '<? } else if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>', $content);
544 
545         // < % if val1.property && val2 % >
546         $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);
547         $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);
548 
549         // < % if val1 == val2 % >
550         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) == "\\2") {  ?>', $content);
551         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) == "\\2") {  ?>', $content);
552 
553                 // < % if val1 > val2 % >
554         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
555         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *> *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
556 
557                 // < % if val1 < val2 % >
558         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
559         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *< *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
560 
561                 // < % if val1 >= val2 % >
562         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *>= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) >= "\\2") {  ?>', $content);
563         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *>= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) > "\\2") {  ?>', $content);
564 
565                 // < % if val1 <= val2 % >
566         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *<= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) <= "\\2") {  ?>', $content);
567         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *<= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) < "\\2") {  ?>', $content);
568 
569                 // <% if val1.property == val2 % >
570         $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);
571         $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);
572 
573                 // <% if val1.property > val2 % >
574         $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);
575         $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);
576 
577                 // <% if val1.property < val2 % >
578         $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);
579         $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);
580 
581         // <% if val1(parametr) == val2 % >
582         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+)\\(([^),]+)\\) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",array("\\2"),true) == "\\3") {  ?>', $content);
583         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+)\\(([^),]+)\\) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",array("\\2"),true) == "\\3") {  ?>', $content);
584 
585                 // <% if val1(parametr).property == val2 % >
586         $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);
587         $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);
588         
589         // < % if val1 != val2 % >
590         $content = ereg_replace('<' . '% +if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? if($item->XML_val("\\1",null,true) != "\\2") {  ?>', $content);
591         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>', '<? } else if($item->XML_val("\\1",null,true) != "\\2") {  ?>', $content);
592 
593         $content = ereg_replace('<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>', '<? } else if(($test = $item->cachedCall("\\1")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) {  ?>', $content);
594 
595         $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);
596         $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);
597 
598         $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);
599         $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);
600         
601         $content = ereg_replace('<' . '% +else +%' . '>', '<? } else { ?>', $content);
602         $content = ereg_replace('<' . '% +end_if +%' . '>', '<? }  ?>', $content);
603 
604         // i18n - get filename of currently parsed template
605         // CAUTION: No spaces allowed between arguments for all i18n calls!
606         ereg('.*[\/](.*)',$template,$path);
607         
608         // i18n _t(...) - with entity only (no dots in namespace), 
609         // meaning the current template filename will be added as a namespace. 
610         // This applies only to "root" templates, not includes which should always have their namespace set already.
611         // See getTemplateContent() for more information.
612         $content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\''. $path[1] . '.\\2\\3\'\\4) ?>', $content);
613         // i18n _t(...)
614         $content = ereg_replace('<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\'\\2\\3\'\\4) ?>', $content);
615 
616         // i18n sprintf(_t(...),$argument) with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
617         $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\), *\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\''. $path[1] . '.\\2\\3\'\\4),\\6) ?>', $content);
618         // i18n sprintf(_t(...),$argument)
619         $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\), *\<\?= +([^\?]*) +\?\>) +%' . '>', '<?= sprintf(_t(\'\\2\\3\'\\4),\\6) ?>', $content);
620 
621         // </base> isnt valid html? !? 
622         $content = ereg_replace('<' . '% +base_tag +%' . '>', '<?= SSViewer::get_base_tag($val); ?>', $content);
623 
624         $content = ereg_replace('<' . '% +current_page +%' . '>', '<?= @$_SERVER["REQUEST_URI"] ?>', $content);
625         
626         // change < % require x() % > calls to corresponding Requirement::x() ones, including 0, 1 or 2 options
627         $content = preg_replace('/<% +require +([a-zA-Z]+)(?:\(([^),]+)\))? +%>/', '<? Requirements::\\1("\\2"); ?>', $content);
628         $content = preg_replace('/<% +require +([a-zA-Z]+)\(([^),]+), *([^),]+)\) +%>/', '<? Requirements::\\1("\\2", "\\3"); ?>', $content);
629         
630         // legacy
631         $content = ereg_replace('<!-- +if +([A-Za-z0-9_]+) +-->', '<? if($item->cachedCall("\\1")) { ?>', $content);
632         $content = ereg_replace('<!-- +else +-->', '<? } else { ?>', $content);
633         $content = ereg_replace('<!-- +if_end +-->', '<? }  ?>', $content);
634             
635         // Fix link stuff
636         $content = ereg_replace('href *= *"#', 'href="<?= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "" ?>#', $content);
637     
638         // Protect xml header
639         $content = ereg_replace('<\?xml([^>]+)\?' . '>', '<##xml\\1##>', $content);
640 
641         // Turn PHP file into string definition
642         $content = str_replace('<?=',"\nSSVIEWER;\n\$val .= ", $content);
643         $content = str_replace('<?',"\nSSVIEWER;\n", $content);
644         $content = str_replace('?>',";\n \$val .= <<<SSVIEWER\n", $content);
645         
646         $output  = "<?php\n";
647         $output .= '$val .= <<<SSVIEWER' . "\n" . $content . "\nSSVIEWER;\n"; 
648         
649         // Protect xml header @sam why is this run twice ?
650         $output = ereg_replace('<##xml([^>]+)##>', '<' . '?xml\\1?' . '>', $output);
651     
652         return $output;
653     }
654 
655     /**
656      * Returns the filenames of the template that will be rendered.  It is a map that may contain
657      * 'Content' & 'Layout', and will have to contain 'main'
658      */
659     public function templates() {
660         return $this->chosenTemplates;
661     }
662     
663     /**
664      * @param string $type "Layout" or "main"
665      * @param string $file Full system path to the template file
666      */
667     public function setTemplateFile($type, $file) {
668         $this->chosenTemplates[$type] = $file;
669     }
670     
671     /**
672      * Return an appropriate base tag for the given template.
673      * It will be closed on an XHTML document, and unclosed on an HTML document.
674      * 
675      * @param $contentGeneratedSoFar The content of the template generated so far; it should contain
676      * the DOCTYPE declaration.
677      */
678     static function get_base_tag($contentGeneratedSoFar) {
679         $base = Director::absoluteBaseURL();
680         
681         // Is the document XHTML?
682         if(preg_match('/<!DOCTYPE[^>]+xhtml/i', $contentGeneratedSoFar)) {
683             return "<base href=\"$base\"></base>";
684         } else {
685             return "<base href=\"$base\"><!--[if lte IE 6]></base><![endif]-->";
686         }
687     }
688 }
689 
690 /**
691  * Special SSViewer that will process a template passed as a string, rather than a filename.
692  * @package sapphire
693  * @subpackage view
694  */
695 class SSViewer_FromString extends SSViewer {
696     protected $content;
697     
698     public function __construct($content) {
699         $this->content = $content;
700     }
701     
702     public function process($item, $cache = null) {
703         $template = SSViewer::parseTemplateContent($this->content, "string sha1=".sha1($this->content));
704 
705         $tmpFile = tempnam(TEMP_FOLDER,"");
706         $fh = fopen($tmpFile, 'w');
707         fwrite($fh, $template);
708         fclose($fh);
709 
710         if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
711             $lines = file($tmpFile);
712             echo "<h2>Template: $tmpFile</h2>";
713             echo "<pre>";
714             foreach($lines as $num => $line) {
715                 echo str_pad($num+1,5) . htmlentities($line);
716             }
717             echo "</pre>";
718         }
719 
720         $itemStack = array();
721         $val = "";
722         $valStack = array();
723         
724         $cache = SS_Cache::factory('cacheblock');
725         
726         include($tmpFile);
727         unlink($tmpFile);
728         
729 
730         return $val;
731     }
732 }
733 
734 /**
735  * Handle the parsing for cacheblock tags.
736  * 
737  * Needs to be handled differently from the other tags, because cacheblock can take any number of arguments
738  * 
739  * This shouldn't be used as an example of how to add functionality to SSViewer - the eventual plan is to re-write
740  * SSViewer using a proper parser (probably http://github.com/hafriedlander/php-peg), so that extra functionality
741  * can be added without relying on ad-hoc parsers like this.
742  * 
743  * @package sapphire
744  * @subpackage view
745  */
746 class SSViewer_PartialParser {
747     
748     static $tag = '/< % [ \t]+ (cached|cacheblock|uncached|end_cached|end_cacheblock|end_uncached) [ \t]+ ([^%]+ [ \t]+)? % >/xS';
749     
750     static $argument_splitter = '/^\s*
751         # The argument itself
752         (
753             (?P<conditional> if | unless ) |                     # The if or unless keybreak
754             (?P<property> (?P<identifier> \w+) \s*               # A property lookup or a function call
755                 ( \( (?P<arguments> [^\)]*) \) )?
756             ) |
757             (?P<sqstring> \' (\\\'|[^\'])+ \' ) |                # A string surrounded by \'
758             (?P<dqstring> " (\\"|[^"])+ " )                      # A string surrounded by "
759         )
760         # Some seperator after the argument
761         (
762             \s*(?P<comma>,)\s* |                                 # A comma (maybe with whitespace before or after)
763             (?P<fullstop>\.)                                     # A period (no whitespace before)
764         )?
765     /xS';
766     
767     static function process($template, $content) {
768         $parser = new SSViewer_PartialParser($template, $content, 0);
769         $parser->parse();
770         return $parser->generate();
771     }
772     
773     function __construct($template, $content, $offset) {
774         $this->template = $template;
775         $this->content = $content;
776         $this->offset = $offset;
777 
778         $this->blocks = array();
779     }
780 
781     function controlcheck($text) {
782         // NOP - hook for Cached_PartialParser
783     }
784 
785     function parse() {
786         $current_tag_offset = 0;
787         
788         while (preg_match(self::$tag, $this->content, $matches, PREG_OFFSET_CAPTURE, $this->offset)) {
789             $tag = $matches[1][0];
790 
791             $startpos = $matches[0][1];
792             $endpos = $matches[0][1] + strlen($matches[0][0]);
793 
794             switch($tag) {
795                 case 'cached':
796                 case 'uncached':
797                 case 'cacheblock':
798 
799                     $pretext = substr($this->content, $this->offset, $startpos - $this->offset);
800                     $this->controlcheck($pretext);
801                     $this->blocks[] = $pretext;
802 
803                     if ($tag == 'cached' || $tag == 'cacheblock') {
804                         list($keyparts, $conditional, $condition) = $this->parseargs(@$matches[2][0]);
805                         $parser = new SSViewer_Cached_PartialParser($this->template, $this->content, $endpos, $keyparts, $conditional, $condition);
806                     }
807                     else {
808                         $parser = new SSViewer_PartialParser($this->template, $this->content, $endpos);
809                     }
810 
811                     $parser->parse();
812                     $this->blocks[] = $parser;
813                     $this->offset = $parser->offset;
814                     break;
815 
816                 case 'end_cached':
817                 case 'end_cacheblock':
818                 case 'end_uncached':
819                     $this->blocks[] = substr($this->content, $this->offset, $startpos - $this->offset);
820                     $this->content = null;
821 
822                     $this->offset = $endpos;
823                     return $this;
824             }
825         }
826 
827         $this->blocks[] = substr($this->content, $this->offset);
828         $this->content = null;
829     }
830 
831     function parseargs($string) {
832         preg_match_all(self::$argument_splitter, $string, $matches, PREG_SET_ORDER);
833 
834         $parts = array();
835         $conditional = null; $condition = null;
836 
837         $current = '$item->';
838 
839         while (strlen($string) && preg_match(self::$argument_splitter, $string, $match)) {
840 
841             $string = substr($string, strlen($match[0]));
842 
843             // If this is a conditional keyword, break, and the next loop will grab the conditional
844             if (@$match['conditional']) {
845                 $conditional = $match['conditional'];
846                 continue;
847             }
848 
849             // If it's a property lookup or a function call
850             if (@$match['property']) {
851                 // Get the property
852                 $what = $match['identifier'];
853                 $args = array();
854 
855                 // Extract any arguments passed to the function call
856                 if (@$match['arguments']) {
857                     foreach (explode(',', $match['arguments']) as $arg) {
858                         $args[] = is_numeric($arg) ? (string)$arg : '"'.$arg.'"';
859                     }
860                 }
861 
862                 $args = empty($args) ? 'null' : 'array('.implode(',',$args).')';
863 
864                 // If this fragment ended with '.', then there's another lookup coming, so return an obj for that lookup
865                 if (@$match['fullstop']) {
866                     $current .= "obj('$what', $args, true)->";
867                 }
868                 // 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
869                 else {
870                     $accessor = $current . "XML_val('$what', $args, true)"; $current = '$item->';
871 
872                     // If we've hit a conditional already, this is the condition. Set it and be done.
873                     if ($conditional) {
874                         $condition = $accessor;
875                         break;
876                     }
877                     // Otherwise we're another key component. Add it to array.
878                     else $parts[] = $accessor;
879                 }
880             }
881 
882             // Else it's a quoted string of some kind
883             else if (@$match['sqstring']) $parts[] = $match['sqstring'];
884             else if (@$match['dqstring']) $parts[] = $match['dqstring'];
885         }
886 
887         if ($conditional && !$condition) {
888             throw new Exception("You need to have a condition after the conditional $conditional in your cache block");
889         }
890 
891         return array($parts, $conditional, $condition);
892     }
893 
894     function generate() {
895         $res = array();
896 
897         foreach ($this->blocks as $i => $block) {
898             if ($block instanceof SSViewer_PartialParser)
899                 $res[] = $block->generate();
900             else {
901                 $res[] = $block;
902             }
903         }
904 
905         return implode('', $res);
906     }
907 }
908 
909 /**
910  * @package sapphire
911  * @subpackage view
912  */
913 class SSViewer_Cached_PartialParser extends SSViewer_PartialParser {
914 
915     function __construct($template, $content, $offset, $keyparts, $conditional, $condition) {
916         $this->keyparts = $keyparts;
917         $this->conditional = $conditional;
918         $this->condition = $condition;
919 
920         parent::__construct($template, $content, $offset);
921     }
922 
923     function controlcheck($text) {
924         $ifs = preg_match_all('/<'.'% +if +/', $text, $matches);
925         $end_ifs = preg_match_all('/<'.'% +end_if +/', $text, $matches);
926 
927         if ($ifs != $end_ifs) throw new Exception('You can\'t have cached or uncached blocks within condition structures');
928 
929         $controls = preg_match_all('/<'.'% +control +/', $text, $matches);
930         $end_controls = preg_match_all('/<'.'% +end_control +/', $text, $matches);
931 
932         if ($controls != $end_controls) throw new Exception('You can\'t have cached or uncached blocks within control structures');
933     }
934 
935     function key() {
936         if (empty($this->keyparts)) return "''";
937         return 'sha1(' . implode(".'_'.", $this->keyparts) . ')';
938     }
939 
940     function generate() {
941         $res = array();
942         $key = $this->key();
943 
944         $condition = "";
945 
946         switch ($this->conditional) {
947             case 'if':
948                 $condition = "{$this->condition} && ";
949                 break;
950             case 'unless':
951                 $condition = "!({$this->condition}) && ";
952                 break;
953         }
954 
955         /* Output this set of blocks */
956 
957         foreach ($this->blocks as $i => $block) {
958             if ($block instanceof SSViewer_PartialParser)
959                 $res[] = $block->generate();
960             else {
961                 // Include the template name and this cache block's current contents as a sha hash, so we get auto-seperation
962                 // of cache blocks, and invalidation of the cache when the template changes
963                 $partialkey = "'".sha1($this->template . $block)."_'.$key.'_$i'";
964 
965                 // Try to load from cache
966                 $res[] = "<?\n".'if ('.$condition.' ($partial = $cache->load('.$partialkey.'))) $val .= $partial;'."\n";
967 
968                 // Cache miss - regenerate
969                 $res[] = "else {\n";
970                 $res[] = '$oldval = $val; $val = "";'."\n";
971                 $res[] = "\n?>" . $block . "<?\n";
972                 $res[] = $condition . ' $cache->save($val); $val = $oldval . $val ;'."\n";
973                 $res[] = "}\n?>";
974             }
975         }
976 
977         return implode('', $res);
978     }
979 }
980 
981 function supressOutput() {
982     return "";
983 }
984 
985 ?>
[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.1 API Docs API documentation generated by ApiGen 2.8.0