2007-07-19 12:40:28 +02:00
< ? php
2011-02-18 05:06:11 +01:00
/**
* This tracks the current scope for an SSViewer instance . It has three goals :
* - Handle entering & leaving sub - scopes in loops and withs
* - Track Up and Top
* - ( As a side effect ) Inject data that needs to be available globally ( used to live in ViewableData )
*
* In order to handle up , rather than tracking it using a tree , which would involve constructing new objects
* for each step , we use indexes into the itemStack ( which already has to exist ) .
*
* Each item has three indexes associated with it
*
* - Pop . Which item should become the scope once the current scope is popped out of
* - Up . Which item is up from this item
* - Current . Which item is the first time this object has appeared in the stack
*
* We also keep the index of the current starting point for lookups . A lookup is a sequence of obj calls -
* when in a loop or with tag the end result becomes the new scope , but for injections , we throw away the lookup
* and revert back to the original scope once we 've got the value we' re after
*
*/
2011-02-21 05:44:46 +01:00
class SSViewer_Scope {
2011-02-18 05:06:11 +01:00
// The stack of previous "global" items
// And array of item, itemIterator, pop_index, up_index, current_index
private $itemStack = array ();
private $item ; // The current "global" item (the one any lookup starts from)
private $itemIterator ; // If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
private $popIndex ; // A pointer into the item stack for which item should be scope on the next pop call
private $upIndex ; // A pointer into the item stack for which item is "up" from this one
private $currentIndex ; // A pointer into the item stack for which item is this one (or null if not in stack yet)
private $localIndex ;
function __construct ( $item ){
$this -> item = $item ;
$this -> localIndex = 0 ;
$this -> itemStack [] = array ( $this -> item , null , null , null , 0 );
}
function getItem (){
return $this -> itemIterator ? $this -> itemIterator -> current () : $this -> item ;
}
function resetLocalScope (){
list ( $this -> item , $this -> itemIterator , $this -> popIndex , $this -> upIndex , $this -> currentIndex ) = $this -> itemStack [ $this -> localIndex ];
array_splice ( $this -> itemStack , $this -> localIndex + 1 );
}
function obj ( $name ){
switch ( $name ) {
case 'Up' :
list ( $this -> item , $this -> itemIterator , $unused2 , $this -> upIndex , $this -> currentIndex ) = $this -> itemStack [ $this -> upIndex ];
break ;
case 'Top' :
list ( $this -> item , $this -> itemIterator , $unused2 , $this -> upIndex , $this -> currentIndex ) = $this -> itemStack [ 0 ];
break ;
default :
$on = $this -> itemIterator ? $this -> itemIterator -> current () : $this -> item ;
2011-03-10 23:52:51 +01:00
$arguments = func_get_args ();
$this -> item = call_user_func_array ( array ( $on , 'obj' ), $arguments );
2011-02-18 05:06:11 +01:00
$this -> itemIterator = null ;
$this -> upIndex = $this -> currentIndex ? $this -> currentIndex : count ( $this -> itemStack ) - 1 ;
$this -> currentIndex = count ( $this -> itemStack );
break ;
}
$this -> itemStack [] = array ( $this -> item , $this -> itemIterator , null , $this -> upIndex , $this -> currentIndex );
return $this ;
}
function pushScope (){
$newLocalIndex = count ( $this -> itemStack ) - 1 ;
$this -> popIndex = $this -> itemStack [ $newLocalIndex ][ 2 ] = $this -> localIndex ;
$this -> localIndex = $newLocalIndex ;
// We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
// once we enter a new global scope, we need to make sure we use a new one
$this -> itemIterator = $this -> itemStack [ $newLocalIndex ][ 1 ] = null ;
return $this ;
}
function popScope (){
$this -> localIndex = $this -> popIndex ;
$this -> resetLocalScope ();
return $this ;
}
function next (){
if ( ! $this -> item ) return false ;
if ( ! $this -> itemIterator ) {
if ( is_array ( $this -> item )) $this -> itemIterator = new ArrayIterator ( $this -> item );
else $this -> itemIterator = $this -> item -> getIterator ();
$this -> itemStack [ $this -> localIndex ][ 1 ] = $this -> itemIterator ;
$this -> itemIterator -> rewind ();
}
else {
$this -> itemIterator -> next ();
}
$this -> resetLocalScope ();
if ( ! $this -> itemIterator -> valid ()) return false ;
return $this -> itemIterator -> key ();
}
function __call ( $name , $arguments ) {
$on = $this -> itemIterator ? $this -> itemIterator -> current () : $this -> item ;
$retval = call_user_func_array ( array ( $on , $name ), $arguments );
$this -> resetLocalScope ();
return $retval ;
}
}
2011-02-21 05:44:46 +01:00
/**
* This extends SSViewer_Scope to mix in data on top of what the item provides . This can be " global "
* data that is scope - independant ( like BaseURL ), or type - specific data that is layered on top cross - cut like
* ( like $FirstLast etc ) .
*
* It ' s separate from SSViewer_Scope to keep that fairly complex code as clean as possible .
*/
class SSViewer_DataPresenter extends SSViewer_Scope {
private $extras ;
function __construct ( $item , $extras = null ){
parent :: __construct ( $item );
$this -> extras = $extras ;
}
function __call ( $name , $arguments ) {
$property = $arguments [ 0 ];
if ( $this -> extras && array_key_exists ( $property , $this -> extras )) {
$this -> resetLocalScope ();
$value = $this -> extras [ $arguments [ 0 ]];
switch ( $name ) {
case 'hasValue' :
return ( bool ) $value ;
default :
return $value ;
}
}
return parent :: __call ( $name , $arguments );
}
}
2007-07-19 12:40:28 +02:00
/**
2010-10-15 03:21:50 +02:00
* Parses a template file with an *. ss file extension .
*
* In addition to a full template in the templates / folder , a template in
* templates / Content or templates / Layout will be rendered into $Content and
2007-07-19 12:40:28 +02:00
* $Layout , respectively .
2010-10-15 02:28:24 +02:00
*
2010-10-15 03:21:50 +02:00
* A single template can be parsed by multiple nested { @ link SSViewer } instances
* through $Layout / $Content placeholders , as well as <% include MyTemplateFile %> template commands .
*
* < b > Themes </ b >
*
* See http :// doc . silverstripe . org / themes and http :// doc . silverstripe . org / themes : developing
*
2010-10-15 02:28:24 +02:00
* < b > Caching </ b >
2007-07-19 12:40:28 +02:00
*
2010-10-15 03:21:50 +02:00
* Compiled templates are cached via { @ link SS_Cache }, usually on the filesystem .
* If you put ? flush = all on your URL , it will force the template to be recompiled .
2008-10-06 21:25:45 +02:00
*
2010-10-15 02:28:24 +02:00
* < b > Manifest File and Structure </ b >
*
2008-10-06 21:25:45 +02:00
* Works with the global $_TEMPLATE_MANIFEST which is compiled by { @ link ManifestBuilder -> getTemplateManifest ()} .
* This associative array lists all template filepaths by " identifier " , meaning the name
* of the template without its path or extension .
*
* Example :
* < code >
* array (
* 'LeftAndMain' =>
* array (
* 'main' => '/my/system/path/cms/templates/LeftAndMain.ss' ,
* ),
* 'CMSMain_left' =>
* array (
* 'Includes' => '/my/system/path/cms/templates/Includes/CMSMain_left.ss' ,
* ),
* 'Page' =>
* array (
* 'themes' =>
* array (
* 'blackcandy' =>
* array (
* 'Layout' => '/my/system/path/themes/blackcandy/templates/Layout/Page.ss' ,
* 'main' => '/my/system/path/themes/blackcandy/templates/Page.ss' ,
* ),
* 'blue' =>
* array (
* 'Layout' => '/my/system/path/themes/mysite/templates/Layout/Page.ss' ,
* 'main' => '/my/system/path/themes/mysite/templates/Page.ss' ,
* ),
* ),
* ),
* // ...
* )
* </ code >
2008-08-28 12:58:52 +02:00
*
2010-10-15 02:28:24 +02:00
* @ see http :// doc . silverstripe . org / themes
* @ see http :// doc . silverstripe . org / themes : developing
*
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ subpackage view
2007-07-19 12:40:28 +02:00
*/
2009-10-13 00:28:47 +02:00
class SSViewer {
2009-02-02 00:49:53 +01:00
/**
* @ var boolean $source_file_comments
*/
2009-09-04 03:31:40 +02:00
protected static $source_file_comments = false ;
2008-12-04 23:38:32 +01:00
/**
* Set whether HTML comments indicating the source . SS file used to render this page should be
* included in the output . This is enabled by default
2009-02-02 00:49:53 +01:00
*
* @ param boolean $val
2008-12-04 23:38:32 +01:00
*/
2009-02-02 00:49:53 +01:00
static function set_source_file_comments ( $val ) {
2008-12-04 23:38:32 +01:00
self :: $source_file_comments = $val ;
}
2008-10-06 21:25:45 +02:00
2009-02-02 00:49:53 +01:00
/**
* @ return boolean
*/
static function get_source_file_comments () {
return self :: $source_file_comments ;
}
2008-10-06 21:25:45 +02:00
/**
* @ var array $chosenTemplates Associative array for the different
2010-10-15 03:21:50 +02:00
* template containers : " main " and " Layout " . Values are absolute file paths to *. ss files .
2008-10-06 21:25:45 +02:00
*/
private $chosenTemplates = array ();
/**
* @ var boolean
*/
2007-07-19 12:40:28 +02:00
protected $rewriteHashlinks = true ;
2008-10-06 21:25:45 +02:00
/**
* @ var string
*/
2007-07-19 12:40:28 +02:00
protected static $current_theme = null ;
2010-10-13 05:40:05 +02:00
/**
* @ var string
*/
protected static $current_custom_theme = null ;
2007-07-19 12:40:28 +02:00
/**
* Create a template from a string instead of a . ss file
2008-11-10 02:01:55 +01:00
*
* @ return SSViewer
2007-07-19 12:40:28 +02:00
*/
static function fromString ( $content ) {
return new SSViewer_FromString ( $content );
}
2008-10-06 21:25:45 +02:00
/**
2010-10-15 02:28:24 +02:00
* @ param string $theme The " base theme " name ( without underscores ) .
2008-10-06 21:25:45 +02:00
*/
2007-07-19 12:40:28 +02:00
static function set_theme ( $theme ) {
self :: $current_theme = $theme ;
2010-10-13 05:40:05 +02:00
//Static publishing needs to have a theme set, otherwise it defaults to the content controller theme
if ( ! is_null ( $theme ))
self :: $current_custom_theme = $theme ;
2007-07-19 12:40:28 +02:00
}
2008-10-06 21:25:45 +02:00
/**
* @ return string
*/
2010-04-13 05:18:10 +02:00
static function current_theme () {
return self :: $current_theme ;
2007-07-19 12:40:28 +02:00
}
2010-12-11 02:34:47 +01:00
/**
* Returns the path to the theme folder
*
* @ return String
*/
static function get_theme_folder () {
return self :: current_theme () ? THEMES_DIR . " / " . self :: current_theme () : project ();
}
2010-10-13 05:40:05 +02:00
/**
* @ return string
*/
static function current_custom_theme (){
return self :: $current_custom_theme ;
}
2007-07-19 12:40:28 +02:00
/**
2010-10-15 03:21:50 +02:00
* @ param string | array $templateList If passed as a string with . ss extension , used as the " main " template .
* If passed as an array , it can be used for template inheritance ( first found template " wins " ) .
* Usually the array values are PHP class names , which directly correlate to template names .
* < code >
* array ( 'MySpecificPage' , 'MyPage' , 'Page' )
* </ code >
2007-07-19 12:40:28 +02:00
*/
public function __construct ( $templateList ) {
2008-10-06 21:25:45 +02:00
global $_TEMPLATE_MANIFEST ;
2010-10-15 03:21:50 +02:00
2008-10-06 21:25:45 +02:00
// flush template manifest cache if requested
2008-08-28 12:58:52 +02:00
if ( isset ( $_GET [ 'flush' ]) && $_GET [ 'flush' ] == 'all' ) {
2010-04-12 01:47:17 +02:00
if ( Director :: isDev () || Director :: is_cli () || Permission :: check ( 'ADMIN' )) {
2009-03-10 23:08:52 +01:00
self :: flush_template_cache ();
} else {
2009-09-10 04:00:42 +02:00
return Security :: permissionFailure ( null , 'Please log in as an administrator to flush the template cache.' );
2009-03-10 23:08:52 +01:00
}
2008-08-28 12:58:52 +02:00
}
2008-10-06 21:25:45 +02:00
2007-07-19 12:40:28 +02:00
if ( substr (( string ) $templateList , - 3 ) == '.ss' ) {
$this -> chosenTemplates [ 'main' ] = $templateList ;
} else {
if ( ! is_array ( $templateList )) $templateList = array ( $templateList );
2008-10-06 21:25:45 +02:00
2008-08-14 05:35:13 +02:00
if ( isset ( $_GET [ 'debug_request' ])) Debug :: message ( " Selecting templates from the following list: " . implode ( " , " , $templateList ));
2007-07-19 12:40:28 +02:00
foreach ( $templateList as $template ) {
2008-10-06 21:25:45 +02:00
// if passed as a partial directory (e.g. "Layout/Page"), split into folder and template components
2007-07-19 12:40:28 +02:00
if ( strpos ( $template , '/' ) !== false ) list ( $templateFolder , $template ) = explode ( '/' , $template , 2 );
else $templateFolder = null ;
2008-08-14 05:57:46 +02:00
// Use the theme template if available
2010-04-13 04:13:12 +02:00
if ( self :: current_theme () && isset ( $_TEMPLATE_MANIFEST [ $template ][ 'themes' ][ self :: current_theme ()])) {
2008-10-06 21:25:45 +02:00
$this -> chosenTemplates = array_merge (
2010-04-13 04:13:12 +02:00
$_TEMPLATE_MANIFEST [ $template ][ 'themes' ][ self :: current_theme ()],
2008-10-06 21:25:45 +02:00
$this -> chosenTemplates
);
2010-04-13 04:13:12 +02:00
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 ));
2008-08-14 05:57:46 +02:00
}
2007-07-19 12:40:28 +02:00
2008-10-06 21:25:45 +02:00
// Fall back to unthemed base templates
2008-08-14 05:35:13 +02:00
if ( isset ( $_TEMPLATE_MANIFEST [ $template ]) && ( array_keys ( $_TEMPLATE_MANIFEST [ $template ]) != array ( 'themes' ))) {
2008-10-06 21:25:45 +02:00
$this -> chosenTemplates = array_merge (
$_TEMPLATE_MANIFEST [ $template ],
$this -> chosenTemplates
);
2008-08-14 05:35:13 +02:00
if ( isset ( $_GET [ 'debug_request' ])) Debug :: message ( " Found template ' $template ' from main template archive, containing the following items: " . var_export ( $_TEMPLATE_MANIFEST [ $template ], true ));
2008-10-06 21:25:45 +02:00
2008-08-11 00:49:59 +02:00
unset ( $this -> chosenTemplates [ 'themes' ]);
}
2008-08-14 05:35:13 +02:00
2007-07-19 12:40:28 +02:00
if ( $templateFolder ) {
$this -> chosenTemplates [ 'main' ] = $this -> chosenTemplates [ $templateFolder ];
unset ( $this -> chosenTemplates [ $templateFolder ]);
}
}
2008-08-14 05:35:13 +02:00
if ( isset ( $_GET [ 'debug_request' ])) Debug :: message ( " Final template selections made: " . var_export ( $this -> chosenTemplates , true ));
2007-07-19 12:40:28 +02:00
}
2010-04-13 05:18:10 +02:00
if ( ! $this -> chosenTemplates ) user_error ( " None of these templates can be found in theme ' "
. self :: current_theme () . " ': " . implode ( " .ss, " , $templateList ) . " .ss " , E_USER_WARNING );
2010-10-15 03:21:50 +02:00
2007-07-19 12:40:28 +02:00
}
/**
* Returns true if at least one of the listed templates exists
*/
static function hasTemplate ( $templateList ) {
if ( ! is_array ( $templateList )) $templateList = array ( $templateList );
global $_TEMPLATE_MANIFEST ;
foreach ( $templateList as $template ) {
if ( strpos ( $template , '/' ) !== false ) list ( $templateFolder , $template ) = explode ( '/' , $template , 2 );
if ( isset ( $_TEMPLATE_MANIFEST [ $template ])) return true ;
}
return false ;
}
/**
* Set a global rendering option .
* The following options are available :
* - rewriteHashlinks : If true ( the default ), < a href = " #... " > will be rewritten to contain the
* current URL . This lets it play nicely with our < base > tag .
2009-11-05 02:07:00 +01:00
* - If rewriteHashlinks = 'php' then , a piece of PHP script will be inserted before the hash
* links : " <?php echo $_SERVER['REQUEST_URI'] ; ?> " . This is useful if you ' re generating a
* page that will be saved to a . php file and may be accessed from different URLs .
2007-07-19 12:40:28 +02:00
*/
public static function setOption ( $optionName , $optionVal ) {
SSViewer :: $options [ $optionName ] = $optionVal ;
}
protected static $options = array (
'rewriteHashlinks' => true ,
);
2008-08-09 09:03:24 +02:00
protected static $topLevel = array ();
public static function topLevel () {
if ( SSViewer :: $topLevel ) {
return SSViewer :: $topLevel [ sizeof ( SSViewer :: $topLevel ) - 1 ];
}
}
2007-07-19 12:40:28 +02:00
/**
* Call this to disable rewriting of < a href = " #xxx " > links . This is useful in Ajax applications .
* It returns the SSViewer objects , so that you can call new SSViewer ( " X " ) -> dontRewriteHashlinks () -> process ();
*/
public function dontRewriteHashlinks () {
$this -> rewriteHashlinks = false ;
2008-08-09 06:38:44 +02:00
self :: $options [ 'rewriteHashlinks' ] = false ;
2007-07-19 12:40:28 +02:00
return $this ;
}
public function exists () {
return $this -> chosenTemplates ;
}
2008-10-06 21:25:45 +02:00
/**
* Searches for a template name in the current theme :
* - themes / mytheme / templates
* - themes / mytheme / templates / Includes
* Falls back to unthemed template files .
*
* Caution : Doesn ' t search in any / Layout folders .
*
* @ param string $identifier A template name without '.ss' extension or path .
* @ return string Full system path to a template file
*/
2007-07-19 12:40:28 +02:00
public static function getTemplateFile ( $identifier ) {
global $_TEMPLATE_MANIFEST ;
2008-10-06 21:25:45 +02:00
$includeTemplateFile = self :: getTemplateFileByType ( $identifier , 'Includes' );
if ( $includeTemplateFile ) return $includeTemplateFile ;
$mainTemplateFile = self :: getTemplateFileByType ( $identifier , 'main' );
if ( $mainTemplateFile ) return $mainTemplateFile ;
return false ;
}
/**
* @ param string $identifier A template name without '.ss' extension or path
* @ param string $type The template type , either " main " , " Includes " or " Layout "
* @ return string Full system path to a template file
*/
public static function getTemplateFileByType ( $identifier , $type ) {
global $_TEMPLATE_MANIFEST ;
2010-04-13 04:13:12 +02:00
if ( self :: current_theme () && isset ( $_TEMPLATE_MANIFEST [ $identifier ][ 'themes' ][ self :: current_theme ()][ $type ])) {
return $_TEMPLATE_MANIFEST [ $identifier ][ 'themes' ][ self :: current_theme ()][ $type ];
2008-10-06 21:25:45 +02:00
} else if ( isset ( $_TEMPLATE_MANIFEST [ $identifier ][ $type ])){
return $_TEMPLATE_MANIFEST [ $identifier ][ $type ];
2007-07-20 01:34:42 +02:00
} else {
2008-10-06 21:25:45 +02:00
return false ;
2007-07-20 01:34:42 +02:00
}
2007-07-19 12:40:28 +02:00
}
2008-08-28 12:58:52 +02:00
/**
2008-10-06 21:25:45 +02:00
* Used by <% include Identifier %> statements to get the full
* unparsed content of a template file .
*
* @ uses getTemplateFile ()
* @ param string $identifier A template name without '.ss' extension or path .
2008-08-28 12:58:52 +02:00
* @ return string content of template
*/
2007-07-19 12:40:28 +02:00
public static function getTemplateContent ( $identifier ) {
2009-04-09 01:36:05 +02:00
if ( ! SSViewer :: getTemplateFile ( $identifier )) {
return null ;
}
2009-10-23 04:38:48 +02:00
$content = file_get_contents ( SSViewer :: getTemplateFile ( $identifier ));
2010-04-13 01:39:15 +02:00
// $content = "<!-- getTemplateContent() :: identifier: $identifier -->". $content;
2010-10-19 03:36:15 +02:00
// Adds an i18n namespace to all _t(...) calls without an existing one
2010-04-13 01:39:15 +02:00
// to avoid confusion when using the include in different contexts.
// Entities without a namespace are deprecated, but widely used.
$content = ereg_replace ( '<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>' , '<?= _t(\'' . $identifier . '.ss' . '.\\2\\3\'\\4) ?>' , $content );
2009-10-23 04:38:48 +02:00
// Remove UTF-8 byte order mark
// This is only necessary if you don't have zend-multibyte enabled.
if ( substr ( $content , 0 , 3 ) == pack ( " CCC " , 0xef , 0xbb , 0xbf )) {
$content = substr ( $content , 3 );
}
return $content ;
2007-07-19 12:40:28 +02:00
}
2008-08-28 12:58:52 +02:00
/**
* @ ignore
*/
static private $flushed = false ;
/**
* Clears all parsed template files in the cache folder .
*
* Can only be called once per request ( there may be multiple SSViewer instances ) .
*/
2008-10-16 21:48:12 +02:00
static function flush_template_cache () {
2008-08-28 12:58:52 +02:00
if ( ! self :: $flushed ) {
$dir = dir ( TEMP_FOLDER );
while ( false !== ( $file = $dir -> read ())) {
if ( strstr ( $file , '.cache' )) { unlink ( TEMP_FOLDER . '/' . $file ); }
}
self :: $flushed = true ;
}
}
2007-07-19 12:40:28 +02:00
/**
* The process () method handles the " meat " of the template processing .
2010-10-15 03:21:50 +02:00
* It takes care of caching the output ( via { @ link SS_Cache }),
* as well as replacing the special " $Content " and " $Layout "
* placeholders with their respective subtemplates .
* The method injects extra HTML in the header via { @ link Requirements :: includeInHTML ()} .
*
* Note : You can call this method indirectly by { @ link ViewableData -> renderWith ()} .
*
* @ param ViewableData $item
* @ param SS_Cache $cache Optional cache backend
* @ return String Parsed template output .
2007-07-19 12:40:28 +02:00
*/
2010-04-12 23:09:54 +02:00
public function process ( $item , $cache = null ) {
2008-08-09 09:03:24 +02:00
SSViewer :: $topLevel [] = $item ;
2010-04-12 23:09:54 +02:00
2010-04-13 03:48:06 +02:00
if ( ! $cache ) $cache = SS_Cache :: factory ( 'cacheblock' );
2010-04-12 23:09:54 +02:00
2007-07-19 12:40:28 +02:00
if ( isset ( $this -> chosenTemplates [ 'main' ])) {
$template = $this -> chosenTemplates [ 'main' ];
} else {
$template = $this -> chosenTemplates [ reset ( $dummy = array_keys ( $this -> chosenTemplates )) ];
}
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " SSViewer::process " , " for $template " );
2010-10-19 05:44:37 +02:00
$cacheFile = TEMP_FOLDER . " /.cache " . str_replace ( array ( '\\' , '/' , ':' ), '.' , Director :: makeRelative ( realpath ( $template )));
2007-07-19 12:40:28 +02:00
$lastEdited = filemtime ( $template );
if ( ! file_exists ( $cacheFile ) || filemtime ( $cacheFile ) < $lastEdited || isset ( $_GET [ 'flush' ])) {
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " SSViewer::process - compile " , " for $template " );
$content = file_get_contents ( $template );
2007-09-15 22:06:42 +02:00
$content = SSViewer :: parseTemplateContent ( $content , $template );
2007-07-19 12:40:28 +02:00
$fh = fopen ( $cacheFile , 'w' );
fwrite ( $fh , $content );
fclose ( $fh );
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " SSViewer::process - compile " , " for $template " );
2008-08-28 12:58:52 +02:00
}
2007-07-19 12:40:28 +02:00
if ( isset ( $_GET [ 'showtemplate' ]) && ! Director :: isLive ()) {
$lines = file ( $cacheFile );
echo " <h2>Template: $cacheFile </h2> " ;
echo " <pre> " ;
foreach ( $lines as $num => $line ) {
2010-12-08 00:53:42 +01:00
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line , ENT_COMPAT , 'UTF-8' );
2007-07-19 12:40:28 +02:00
}
echo " </pre> " ;
}
2010-10-15 03:21:28 +02:00
// Makes the rendered sub-templates available on the parent item,
// through $Content and $Layout placeholders.
2007-07-19 12:40:28 +02:00
foreach ( array ( 'Content' , 'Layout' ) as $subtemplate ) {
if ( isset ( $this -> chosenTemplates [ $subtemplate ])) {
$subtemplateViewer = new SSViewer ( $this -> chosenTemplates [ $subtemplate ]);
$item = $item -> customise ( array (
2010-04-12 23:09:54 +02:00
$subtemplate => $subtemplateViewer -> process ( $item , $cache )
2007-07-19 12:40:28 +02:00
));
}
}
2011-02-21 05:44:46 +01:00
$scope = new SSViewer_DataPresenter ( $item , array ( 'I18NNamespace' => basename ( $template )));
2011-03-10 05:37:46 +01:00
$val = " " ;
2010-04-12 23:09:54 +02:00
2007-07-19 12:40:28 +02:00
include ( $cacheFile );
2011-02-18 05:06:11 +01:00
$output = Requirements :: includeInHTML ( $template , $val );
2007-07-19 12:40:28 +02:00
2008-08-09 09:03:24 +02:00
array_pop ( SSViewer :: $topLevel );
2007-07-19 12:40:28 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " SSViewer::process " , " for $template " );
2008-03-11 04:29:30 +01:00
// If we have our crazy base tag, then fix # links referencing the current page.
2009-05-11 05:52:16 +02:00
if ( $this -> rewriteHashlinks && self :: $options [ 'rewriteHashlinks' ]) {
if ( strpos ( $output , '<base' ) !== false ) {
2009-11-05 02:07:00 +01:00
if ( SSViewer :: $options [ 'rewriteHashlinks' ] === 'php' ) {
$thisURLRelativeToBase = " <?php echo \$ _SERVER['REQUEST_URI']; ?> " ;
} else {
$thisURLRelativeToBase = Director :: makeRelative ( Director :: absoluteURL ( $_SERVER [ 'REQUEST_URI' ]));
}
2009-11-21 02:43:00 +01:00
$output = preg_replace ( '/(<a[^>]+href *= *)"#/i' , '\\1"' . $thisURLRelativeToBase . '#' , $output );
2009-05-11 05:52:16 +02:00
}
2008-03-11 04:29:30 +01:00
}
2007-07-19 12:40:28 +02:00
return $output ;
}
2010-03-12 04:08:59 +01:00
/**
* Execute the given template , passing it the given data .
* Used by the <% include %> template tag to process templates .
*/
static function execute_template ( $template , $data ) {
$v = new SSViewer ( $template );
return $v -> process ( $data );
}
2007-09-15 22:06:42 +02:00
static function parseTemplateContent ( $content , $template = " " ) {
2011-02-10 05:39:31 +01:00
return SSTemplateParser :: compileString ( $content , $template , Director :: isDev () && self :: $source_file_comments );
2007-07-19 12:40:28 +02:00
}
/**
* Returns the filenames of the template that will be rendered . It is a map that may contain
* 'Content' & 'Layout' , and will have to contain 'main'
*/
public function templates () {
return $this -> chosenTemplates ;
}
2008-10-06 21:25:45 +02:00
/**
* @ param string $type " Layout " or " main "
* @ param string $file Full system path to the template file
*/
public function setTemplateFile ( $type , $file ) {
$this -> chosenTemplates [ $type ] = $file ;
}
2009-10-31 01:16:54 +01:00
/**
* Return an appropriate base tag for the given template .
* It will be closed on an XHTML document , and unclosed on an HTML document .
*
* @ param $contentGeneratedSoFar The content of the template generated so far ; it should contain
* the DOCTYPE declaration .
*/
static function get_base_tag ( $contentGeneratedSoFar ) {
$base = Director :: absoluteBaseURL ();
// Is the document XHTML?
if ( preg_match ( '/<!DOCTYPE[^>]+xhtml/i' , $contentGeneratedSoFar )) {
2010-10-19 03:06:25 +02:00
return " <base href= \" $base\ " /> " ;
2009-10-31 01:16:54 +01:00
} else {
return " <base href= \" $base\ " ><!-- [ if lte IE 6 ] ></ base ><! [ endif ] --> " ;
}
}
2007-07-19 12:40:28 +02:00
}
2008-02-25 03:10:37 +01:00
/**
* Special SSViewer that will process a template passed as a string , rather than a filename .
* @ package sapphire
* @ subpackage view
*/
2007-07-19 12:40:28 +02:00
class SSViewer_FromString extends SSViewer {
protected $content ;
public function __construct ( $content ) {
$this -> content = $content ;
}
2010-04-13 05:18:10 +02:00
public function process ( $item , $cache = null ) {
2010-04-12 23:09:54 +02:00
$template = SSViewer :: parseTemplateContent ( $this -> content , " string sha1= " . sha1 ( $this -> content ));
2007-07-19 12:40:28 +02:00
$tmpFile = tempnam ( TEMP_FOLDER , " " );
$fh = fopen ( $tmpFile , 'w' );
fwrite ( $fh , $template );
fclose ( $fh );
2007-09-15 23:04:33 +02:00
if ( isset ( $_GET [ 'showtemplate' ]) && $_GET [ 'showtemplate' ]) {
2007-07-19 12:40:28 +02:00
$lines = file ( $tmpFile );
echo " <h2>Template: $tmpFile </h2> " ;
echo " <pre> " ;
foreach ( $lines as $num => $line ) {
2010-12-08 00:53:42 +01:00
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line , ENT_COMPAT , 'UTF-8' );
2007-07-19 12:40:28 +02:00
}
echo " </pre> " ;
}
2011-02-18 05:06:11 +01:00
$scope = new SSViewer_DataPresenter ( $item );
2007-07-19 12:40:28 +02:00
$val = " " ;
2010-04-12 23:09:54 +02:00
$valStack = array ();
2010-04-13 03:48:06 +02:00
$cache = SS_Cache :: factory ( 'cacheblock' );
2010-04-12 23:09:54 +02:00
2007-07-19 12:40:28 +02:00
include ( $tmpFile );
unlink ( $tmpFile );
return $val ;
}
}