2007-07-19 12:40:28 +02:00
< ? php
/**
* The SSViewer executes a . ss template file .
* The SSViewer class handles rendering of . ss templates . In addition to a full template in
* the views folder , a template in views / Content or views / Layout will be rendered into $Content and
* $Layout , respectively .
*
* Compiled templates are cached . If you put ? flush = 1 on your URL , it will force the template to be recompiled . This
* is a hack ; the system should really detect when a page needs re - fetching .
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
*
2007-07-19 12:40:28 +02:00
* @ todo Fix the broken caching .
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
* template containers : " main " and " Layout " .
*/
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 ;
/**
* 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
/**
* @ param string $theme
*/
2007-07-19 12:40:28 +02:00
static function set_theme ( $theme ) {
self :: $current_theme = $theme ;
}
2008-10-06 21:25:45 +02:00
/**
* @ return string
*/
2007-07-19 12:40:28 +02:00
static function current_theme () {
return self :: $current_theme ;
}
/**
2008-10-06 21:25:45 +02:00
* Pass the SilverStripe template to be used .
*
* @ param string | array $templateList
* If passed as a string with . ss extension , used as the " main " template
2007-07-19 12:40:28 +02:00
*/
public function __construct ( $templateList ) {
2008-10-06 21:25:45 +02:00
global $_TEMPLATE_MANIFEST ;
// 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
if ( self :: $current_theme && isset ( $_TEMPLATE_MANIFEST [ $template ][ 'themes' ][ self :: $current_theme ])) {
2008-10-06 21:25:45 +02:00
$this -> chosenTemplates = array_merge (
$_TEMPLATE_MANIFEST [ $template ][ 'themes' ][ self :: $current_theme ],
$this -> chosenTemplates
);
2008-08-14 05:57:46 +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 ));
}
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
}
if ( ! $this -> chosenTemplates ) user_error ( " None of these templates can be found: " . implode ( " .ss, " , $templateList ) . " .ss " , E_USER_WARNING );
}
/**
* 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 ;
if ( self :: $current_theme && isset ( $_TEMPLATE_MANIFEST [ $identifier ][ 'themes' ][ self :: $current_theme ][ $type ])) {
return $_TEMPLATE_MANIFEST [ $identifier ][ 'themes' ][ self :: $current_theme ][ $type ];
} 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;
// Adds an i18n namespace to all <% _t(...) %> calls without an existing one
// 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-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
if ( ! $cache ) $cache = Cache :: factory ( 'cacheblock' );
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 " );
$cacheFile = TEMP_FOLDER . " /.cache " . str_replace ( array ( '\\' , '/' , ':' ), '.' , realpath ( $template ));
$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 ) {
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line );
}
echo " </pre> " ;
}
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
));
}
}
$itemStack = array ();
$val = " " ;
2010-04-12 23:09:54 +02:00
$valStack = array ();
2007-07-19 12:40:28 +02:00
include ( $cacheFile );
$output = $val ;
$output = Requirements :: includeInHTML ( $template , $output );
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 = " " ) {
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 );
}
2009-02-02 00:49:53 +01:00
2009-10-23 04:38:48 +02:00
// Add template filename comments on dev sites
2009-03-04 04:44:11 +01:00
if ( Director :: isDev () && self :: $source_file_comments && $template && stripos ( $content , " <?xml " ) === false ) {
2008-11-22 04:33:00 +01:00
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
if ( stripos ( $content , " <html " ) !== false ) {
$content = preg_replace ( '/(<html[^>]*>)/i' , " \\ 1<!-- template $template --> " , $content );
$content = preg_replace ( '/(<\/html[^>]*>)/i' , " \\ 1<!-- end template $template --> " , $content );
} else {
$content = " <!-- template $template --> \n " . $content . " \n <!-- end template $template --> " ;
}
}
2007-07-19 12:40:28 +02:00
// $val, $val.property, $val(param), etc.
$replacements = array (
2008-11-14 04:43:26 +01:00
'/<%--.*--%>/U' => '' ,
2007-09-15 22:07:32 +02:00
'/\$Iteration/' => '<?= {dlr}key ?>' ,
2008-10-12 23:27:52 +02:00
'/{\\$([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) ?>' ,
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2","\\3"),true)->XML_val("\\4",null,true) ?>' ,
2007-07-19 12:40:28 +02:00
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>' ,
2008-10-12 23:27:52 +02:00
'/{\\$([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) ?>' ,
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",array("\\2"),true)->XML_val("\\3",null,true) ?>' ,
2007-07-19 12:40:28 +02:00
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)}/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>' ,
'/{\\$([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) ?>' ,
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z0-9_]+)}/' => '<?= {dlr}item->obj("\\1",null,true)->XML_val("\\2",null,true) ?>' ,
'/{\\$([A-Za-z_][A-Za-z0-9_]*)}/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2' ,
'/\\$([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' ,
2009-01-04 23:20:36 +01:00
'/\\$([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' ,
2007-07-19 12:40:28 +02:00
2008-10-12 23:27:52 +02:00
'/\\$([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' ,
'/\\$([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' ,
2007-07-19 12:40:28 +02:00
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>\\4' ,
2008-10-12 23:27:52 +02:00
'/\\$([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' ,
'/\\$([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' ,
2007-07-19 12:40:28 +02:00
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+)\\)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",array("\\2"),true) ?>\\3' ,
'/\\$([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' ,
'/\\$([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' ,
'/\\$([A-Za-z_][A-Za-z0-9_]*)([^A-Za-z0-9]|$)/' => '<?= {dlr}item->XML_val("\\1",null,true) ?>\\2' ,
);
$content = preg_replace ( array_keys ( $replacements ), array_values ( $replacements ), $content );
$content = str_replace ( '{dlr}' , '$' , $content );
// legacy
$content = ereg_replace ( '<!-- +pc +([A-Za-z0-9_(),]+) +-->' , '<' . '% control \\1 %' . '>' , $content );
$content = ereg_replace ( '<!-- +pc_end +-->' , '<' . '% end_control %' . '>' , $content );
2009-10-11 02:07:09 +02:00
2010-04-12 23:09:54 +02:00
// < % cacheblock key, key.. % >
$content = SSViewer_PartialParser :: parse ( $template , $content );
2009-10-11 02:07:09 +02:00
// < % control Foo % >
2007-09-15 22:07:32 +02:00
$content = ereg_replace ( '<' . '% +control +([A-Za-z0-9_]+) +%' . '>' , '<? array_push($itemStack, $item); if($loop = $item->obj("\\1")) foreach($loop as $key => $item) { ?>' , $content );
2009-10-11 02:07:09 +02:00
// < % control Foo.Bar % >
2007-09-15 22:07:32 +02:00
$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 );
2009-10-11 02:07:09 +02:00
// < % control Foo.Bar(Baz) % >
$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 );
// < % control Foo(Bar) % >
$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 );
// < % control Foo(Bar, Baz) % >
$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 );
// < % control Foo(Bar, Baz, Buz) % >
$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 );
2007-07-19 12:40:28 +02:00
$content = ereg_replace ( '<' . '% +end_control +%' . '>' , '<? } $item = array_pop($itemStack); ?>' , $content );
$content = ereg_replace ( '<' . '% +debug +%' . '>' , '<? Debug::show($item) ?>' , $content );
$content = ereg_replace ( '<' . '% +debug +([A-Za-z0-9_]+) +%' . '>' , '<? Debug::show($item->cachedCall("\\1")) ?>' , $content );
// < % if val1.property % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+)\\.([A-Za-z0-9_]+) +%' . '>' , '<? if($item->obj("\\1",null,true)->hasValue("\\2")) { ?>' , $content );
// < % if val1(parameter) % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+)\\(([A-Za-z0-9_-]+)\\) +%' . '>' , '<? if($item->hasValue("\\1",array("\\2"))) { ?>' , $content );
// < % if val1 % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+) +%' . '>' , '<? if($item->hasValue("\\1")) { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>' , '<? } else if($item->hasValue("\\1")) { ?>' , $content );
// < % if val1 || val2 % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>' , '<? if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) *\\|\\|? *([A-Za-z0-9_]+) +%' . '>' , '<? else_if($item->hasValue("\\1") || $item->hasValue("\\2")) { ?>' , $content );
// < % if val1 && val2 % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>' , '<? if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) *&&? *([A-Za-z0-9_]+) +%' . '>' , '<? else_if($item->hasValue("\\1") && $item->hasValue("\\2")) { ?>' , $content );
// < % if val1 == val2 % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>' , '<? if($item->XML_val("\\1",null,true) == "\\2") { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) *==? *"?([A-Za-z0-9_-]+)"? +%' . '>' , '<? } else if($item->XML_val("\\1",null,true) == "\\2") { ?>' , $content );
// < % if val1 != val2 % >
$content = ereg_replace ( '<' . '% +if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>' , '<? if($item->XML_val("\\1",null,true) != "\\2") { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) *!= *"?([A-Za-z0-9_-]+)"? +%' . '>' , '<? } else if($item->XML_val("\\1",null,true) != "\\2") { ?>' , $content );
$content = ereg_replace ( '<' . '% +else_if +([A-Za-z0-9_]+) +%' . '>' , '<? } else if(($test = $item->cachedCall("\\1")) && ((!is_object($test) && $test) || ($test && $test->exists()) )) { ?>' , $content );
$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 );
$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 );
$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 );
$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 );
$content = ereg_replace ( '<' . '% +else +%' . '>' , '<? } else { ?>' , $content );
$content = ereg_replace ( '<' . '% +end_if +%' . '>' , '<? } ?>' , $content );
2008-11-06 03:50:14 +01:00
// i18n - get filename of currently parsed template
// CAUTION: No spaces allowed between arguments for all i18n calls!
2007-09-15 22:06:42 +02:00
ereg ( '.*[\/](.*)' , $template , $path );
2008-02-25 03:10:37 +01:00
2010-04-13 01:39:15 +02:00
// i18n _t(...) - with entity only (no dots in namespace),
// meaning the current template filename will be added as a namespace.
// This applies only to "root" templates, not includes which should always have their namespace set already.
// See getTemplateContent() for more information.
2008-11-06 03:50:14 +01:00
$content = ereg_replace ( '<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>' , '<?= _t(\'' . $path [ 1 ] . '.\\2\\3\'\\4) ?>' , $content );
// i18n _t(...)
$content = ereg_replace ( '<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>' , '<?= _t(\'\\2\\3\'\\4) ?>' , $content );
// i18n sprintf(_t(...),$argument) with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
$content = ereg_replace ( '<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>' , '<?= sprintf(_t(\'' . $path [ 1 ] . '.\\2\\3\'\\4),\\6) ?>' , $content );
// i18n sprintf(_t(...),$argument)
$content = ereg_replace ( '<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>' , '<?= sprintf(_t(\'\\2\\3\'\\4),\\6) ?>' , $content );
2007-07-19 12:40:28 +02:00
// </base> isnt valid html? !?
2009-10-31 01:16:54 +01:00
$content = ereg_replace ( '<' . '% +base_tag +%' . '>' , '<?= SSViewer::get_base_tag($val); ?>' , $content );
2007-07-19 12:40:28 +02:00
$content = ereg_replace ( '<' . '% +current_page +%' . '>' , '<?= $_SERVER[SCRIPT_URL] ?>' , $content );
2008-11-10 02:01:55 +01:00
// add all requirements to the $requirements array
preg_match_all ( '/<% require ([a-zA-Z]+)\(([^\)]+)\) %>/' , $content , $requirements );
$content = preg_replace ( '/<% require .* %>/' , null , $content );
2010-03-12 04:08:59 +01:00
// Add include filename comments on dev sites
if ( Director :: isDev () && self :: $source_file_comments ) $replacementCode = ' return " <!-- include " . SSViewer :: getTemplateFile ( $matches [ 1 ]) . " --> \n "
. " <?= SSViewer::parse_template( \\ " " . $matches[1] . " \ " , \$ item); ?> "
. " \n <!-- end include " . SSViewer :: getTemplateFile ( $matches [ 1 ]) . " --> " ; ' ;
else $replacementCode = 'return "<?= SSViewer::execute_template(\\"" . $matches[1] . "\", \$item); ?>";' ;
$content = preg_replace_callback ( '/<' . '% include +([A-Za-z0-9_]+) +%' . '>/' , create_function (
'$matches' , $replacementCode
), $content );
2008-11-10 02:01:55 +01:00
2007-07-19 12:40:28 +02:00
// legacy
$content = ereg_replace ( '<!-- +if +([A-Za-z0-9_]+) +-->' , '<? if($item->cachedCall("\\1")) { ?>' , $content );
$content = ereg_replace ( '<!-- +else +-->' , '<? } else { ?>' , $content );
$content = ereg_replace ( '<!-- +if_end +-->' , '<? } ?>' , $content );
// Fix link stuff
$content = ereg_replace ( 'href *= *"#' , 'href="<?= SSViewer::$options[\'rewriteHashlinks\'] ? Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "" ?>#' , $content );
// Protect xml header
$content = ereg_replace ( '<\?xml([^>]+)\?' . '>' , '<##xml\\1##>' , $content );
// Turn PHP file into string definition
$content = str_replace ( '<?=' , " \n SSVIEWER; \n \$ val .= " , $content );
$content = str_replace ( '<?' , " \n SSVIEWER; \n " , $content );
$content = str_replace ( '?>' , " ; \n \$ val .= <<<SSVIEWER \n " , $content );
2008-11-10 02:01:55 +01:00
$output = " <?php \n " ;
for ( $i = 0 ; $i < count ( $requirements [ 0 ]); $i ++ ) {
$output .= 'Requirements::' . $requirements [ 1 ][ $i ] . '(\'' . $requirements [ 2 ][ $i ] . " '); \n " ;
}
$output .= '$val .= <<<SSVIEWER' . " \n " . $content . " \n SSVIEWER; \n " ;
2007-07-19 12:40:28 +02:00
// Protect xml header @sam why is this run twice ?
2008-11-10 02:01:55 +01:00
$output = ereg_replace ( '<##xml([^>]+)##>' , '<' . '?xml\\1?' . '>' , $output );
2007-07-19 12:40:28 +02:00
2008-11-10 02:01:55 +01:00
return $output ;
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 )) {
return " <base href= \" $base\ " ></ base > " ;
} 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 ;
}
public function process ( $item ) {
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 ) {
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line );
}
echo " </pre> " ;
}
$itemStack = array ();
$val = " " ;
2010-04-12 23:09:54 +02:00
$valStack = array ();
$cache = Cache :: factory ( 'cacheblock' );
2007-07-19 12:40:28 +02:00
include ( $tmpFile );
unlink ( $tmpFile );
return $val ;
}
}
2010-04-12 23:09:54 +02:00
/**
* Handle the parsing for cacheblock tags .
*
* Needs to be handled differently from the other tags , because cacheblock can take any number of arguments
*
* This shouldn ' t be used as an example of how to add functionality to SSViewer - the eventual plan is to re - write
* SSViewer using a proper parser ( probably http :// github . com / hafriedlander / php - peg ), so that extra functionality
* can be added without relying on ad - hoc parsers like this .
*/
class SSViewer_PartialParser {
static $opening_tag = '/< % [ \t]+ cacheblock [ \t]+ ([^%]+ [ \t]+)? % >/xS' ;
static $argument_splitter = ' /^ \s *
( ( \w + ) \s * ( \ ( ([ ^ \ )] * ) \ ) ) ? ) | # A property lookup or a function call
( \ ' [ ^ \ ' ] + \ ' ) | # A string surrounded by \'
( " [^ " ] + " ) # A string surrounded by "
\s */ xS ' ;
static $closing_tag = '/< % [ \t]+ end_cacheblock [ \t]+ % >/xS' ;
static function parse ( $template , $content ) {
$parser = new SSViewer_PartialParser ( $template );
$content = $parser -> replaceOpeningTags ( $content );
$content = $parser -> replaceClosingTags ( $content );
return $content ;
}
2007-07-19 12:40:28 +02:00
2010-04-12 23:09:54 +02:00
function __construct ( $template ) {
$this -> template = $template ;
$this -> cacheblocks = 0 ;
}
function replaceOpeningTags ( $content ) {
return preg_replace_callback ( self :: $opening_tag , array ( $this , 'replaceOpeningTagsCallback' ), $content );
}
function replaceOpeningTagsCallback ( $matches ) {
$this -> cacheblocks += 1 ;
$key = $this -> key ( $matches );
return '<? if ($partial = $cache->load(' . $key . ')) { $val .= $partial; } else { $valStack[] = $val; $val = ""; ?>' ;
}
function key ( $matches ) {
$parts = array ();
$parts [] = " ' " . preg_replace ( '/[^\w+]/' , '_' , $this -> template ) . " ' " ;
// If there weren't any arguments, that'll do
if ( !@ $matches [ 1 ]) return $parts [ 0 ];
$current = 'preg_replace(\'/[^\w+]/\', \'_\', $item->' ;
$keyspec = $matches [ 1 ];
while ( strlen ( $keyspec ) && preg_match ( self :: $argument_splitter , $keyspec , $submatch )) {
$joiner = substr ( $keyspec , strlen ( $submatch [ 0 ]), 1 );
$keyspec = substr ( $keyspec , strlen ( $submatch [ 0 ]) + 1 );
// If it's a property lookup or a function call
if ( $submatch [ 1 ]) {
// Get the property
$what = $submatch [ 2 ];
$args = array ();
// Extract any arguments passed to the function call
if ( @ $submatch [ 3 ]) {
foreach ( explode ( ',' , $submatch [ 4 ]) as $arg ) {
$args [] = is_numeric ( $arg ) ? ( string ) $arg : '"' . $arg . '"' ;
}
}
$args = empty ( $args ) ? 'null' : 'array(' . implode ( ',' , $args ) . ')' ;
// If this fragment ended with '.', then there's another lookup coming, so return an obj for that lookup
if ( $joiner == '.' ) {
$current .= " obj( \" $what\ " , $args , true ) -> " ;
}
// 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
else {
$parts [] = $current . " XML_val( \" $what\ " , $args , true )) " ; $current = 'preg_replace( \ '/[^ \ w+]/ \ ', \ '_ \ ', $item ->';
}
}
// Else it's a quoted string of some kind
else if ( $submatch [ 5 ] || $submatch [ 6 ]) {
$parts [] = $submatch [ 5 ] ? $submatch [ 5 ] : $submatch [ 6 ];
}
}
return implode ( " .'_'. " , $parts );
}
function replaceClosingTags ( $content ) {
return preg_replace ( self :: $closing_tag , '<? $cache->save($val); $val = array_pop($valStack) . $val; } ?>' , $content );
}
}
2007-07-19 12:40:28 +02:00
function supressOutput () {
return " " ;
}
2009-03-10 23:08:52 +01:00
?>