2007-07-19 12:40:28 +02:00
< ? php
/**
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 ) {
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line );
}
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
));
}
}
$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 );
2010-10-12 23:41:48 +02:00
// Cache block
$content = SSViewer_PartialParser :: process ( $template , $content );
2007-07-19 12:40:28 +02:00
// 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
// < % control Foo % >
2010-10-19 03:23:00 +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 % >
2010-10-19 03:23:00 +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) % >
2010-10-19 03:23:00 +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", array("\\3")))) foreach($loop as $key => $item) { ?>' , $content );
2009-10-11 02:07:09 +02:00
// < % control Foo(Bar) % >
2010-10-19 03:23:00 +02:00
$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 );
2009-10-11 02:07:09 +02:00
// < % control Foo(Bar, Baz) % >
2010-10-19 03:23:00 +02:00
$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 );
2009-10-11 02:07:09 +02:00
// < % control Foo(Bar, Baz, Buz) % >
2010-10-19 03:23:00 +02:00
$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 );
2010-10-13 03:15:47 +02:00
// change < % require x() % > calls to corresponding Requirement::x() ones, including 0, 1 or 2 options
$content = preg_replace ( '/<% +require +([a-zA-Z]+)(?:\(([^),]+)\))? +%>/' , '<? Requirements::\\1("\\2"); ?>' , $content );
$content = preg_replace ( '/<% +require +([a-zA-Z]+)\(([^),]+), *([^),]+)\) +%>/' , '<? Requirements::\\1("\\2", "\\3"); ?>' , $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 " ;
$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 )) {
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 ) {
echo str_pad ( $num + 1 , 5 ) . htmlentities ( $line );
}
echo " </pre> " ;
}
$itemStack = array ();
$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 ;
}
}
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 .
2010-04-23 02:11:41 +02:00
*
* @ package sapphire
* @ subpackage view
2010-04-12 23:09:54 +02:00
*/
class SSViewer_PartialParser {
2010-10-12 23:41:48 +02:00
static $tag = '/< % [ \t]+ (cached|cacheblock|uncached|end_cached|end_cacheblock|end_uncached) [ \t]+ ([^%]+ [ \t]+)? % >/xS' ;
static $argument_splitter = ' /^ \s *
# The argument itself
(
( ? P < conditional > if | unless ) | # The if or unless keybreak
( ? P < property > ( ? P < identifier > \w + ) \s * # A property lookup or a function call
( \ ( ( ? P < arguments > [ ^ \ )] * ) \ ) ) ?
) |
( ? P < sqstring > \ ' ( \\\ ' | [ ^ \ ' ]) + \ ' ) | # A string surrounded by \'
( ? P < dqstring > " ( \\ " | [ ^ " ])+ " ) # A string surrounded by "
)
# Some seperator after the argument
(
\s * ( ? P < comma > ,) \s * | # A comma (maybe with whitespace before or after)
( ? P < fullstop > \ . ) # A period (no whitespace before)
) ?
/ xS ' ;
static function process ( $template , $content ) {
2010-10-13 03:14:49 +02:00
$parser = new SSViewer_PartialParser ( $template , $content , 0 );
2010-10-12 23:41:48 +02:00
$parser -> parse ();
return $parser -> generate ();
2010-04-12 23:09:54 +02:00
}
2007-07-19 12:40:28 +02:00
2010-10-13 03:14:49 +02:00
function __construct ( $template , $content , $offset ) {
2010-04-12 23:09:54 +02:00
$this -> template = $template ;
2010-10-12 23:41:48 +02:00
$this -> content = $content ;
$this -> offset = $offset ;
$this -> blocks = array ();
2010-04-12 23:09:54 +02:00
}
2010-10-12 23:41:48 +02:00
function controlcheck ( $text ) {
2010-10-13 03:14:49 +02:00
// NOP - hook for Cached_PartialParser
2010-04-12 23:09:54 +02:00
}
2010-10-12 23:41:48 +02:00
function parse () {
$current_tag_offset = 0 ;
2010-04-12 23:09:54 +02:00
2010-10-12 23:41:48 +02:00
while ( preg_match ( self :: $tag , $this -> content , $matches , PREG_OFFSET_CAPTURE , $this -> offset )) {
$tag = $matches [ 1 ][ 0 ];
$startpos = $matches [ 0 ][ 1 ];
$endpos = $matches [ 0 ][ 1 ] + strlen ( $matches [ 0 ][ 0 ]);
switch ( $tag ) {
case 'cached' :
case 'uncached' :
case 'cacheblock' :
$pretext = substr ( $this -> content , $this -> offset , $startpos - $this -> offset );
$this -> controlcheck ( $pretext );
$this -> blocks [] = $pretext ;
if ( $tag == 'cached' || $tag == 'cacheblock' ) {
list ( $keyparts , $conditional , $condition ) = $this -> parseargs ( @ $matches [ 2 ][ 0 ]);
2010-10-13 03:14:49 +02:00
$parser = new SSViewer_Cached_PartialParser ( $this -> template , $this -> content , $endpos , $keyparts , $conditional , $condition );
2010-10-12 23:41:48 +02:00
}
else {
2010-10-13 03:14:49 +02:00
$parser = new SSViewer_PartialParser ( $this -> template , $this -> content , $endpos );
2010-10-12 23:41:48 +02:00
}
$parser -> parse ();
$this -> blocks [] = $parser ;
$this -> offset = $parser -> offset ;
break ;
case 'end_cached' :
case 'end_cacheblock' :
case 'end_uncached' :
$this -> blocks [] = substr ( $this -> content , $this -> offset , $startpos - $this -> offset );
$this -> content = null ;
$this -> offset = $endpos ;
return $this ;
}
}
$this -> blocks [] = substr ( $this -> content , $this -> offset );
$this -> content = null ;
2010-04-12 23:09:54 +02:00
}
2010-10-12 23:41:48 +02:00
function parseargs ( $string ) {
preg_match_all ( self :: $argument_splitter , $string , $matches , PREG_SET_ORDER );
2010-04-12 23:09:54 +02:00
$parts = array ();
2010-10-12 23:41:48 +02:00
$conditional = null ; $condition = null ;
$current = '$item->' ;
while ( strlen ( $string ) && preg_match ( self :: $argument_splitter , $string , $match )) {
$string = substr ( $string , strlen ( $match [ 0 ]));
// If this is a conditional keyword, break, and the next loop will grab the conditional
if ( @ $match [ 'conditional' ]) {
$conditional = $match [ 'conditional' ];
continue ;
}
2010-04-12 23:09:54 +02:00
// If it's a property lookup or a function call
2010-10-12 23:41:48 +02:00
if ( @ $match [ 'property' ]) {
2010-04-12 23:09:54 +02:00
// Get the property
2010-10-12 23:41:48 +02:00
$what = $match [ 'identifier' ];
2010-04-12 23:09:54 +02:00
$args = array ();
2010-10-12 23:41:48 +02:00
2010-04-12 23:09:54 +02:00
// Extract any arguments passed to the function call
2010-10-12 23:41:48 +02:00
if ( @ $match [ 'arguments' ]) {
foreach ( explode ( ',' , $match [ 'arguments' ]) as $arg ) {
2010-04-12 23:09:54 +02:00
$args [] = is_numeric ( $arg ) ? ( string ) $arg : '"' . $arg . '"' ;
}
}
2010-10-12 23:41:48 +02:00
2010-04-12 23:09:54 +02:00
$args = empty ( $args ) ? 'null' : 'array(' . implode ( ',' , $args ) . ')' ;
2010-10-12 23:41:48 +02:00
2010-04-12 23:09:54 +02:00
// If this fragment ended with '.', then there's another lookup coming, so return an obj for that lookup
2010-10-12 23:41:48 +02:00
if ( @ $match [ 'fullstop' ]) {
$current .= " obj(' $what ', $args , true)-> " ;
2010-04-12 23:09:54 +02:00
}
// 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 {
2010-10-12 23:41:48 +02:00
$accessor = $current . " XML_val(' $what ', $args , true) " ; $current = '$item->' ;
// If we've hit a conditional already, this is the condition. Set it and be done.
if ( $conditional ) {
$condition = $accessor ;
break ;
}
// Otherwise we're another key component. Add it to array.
else $parts [] = $accessor ;
2010-04-12 23:09:54 +02:00
}
}
2010-10-12 23:41:48 +02:00
2010-04-12 23:09:54 +02:00
// Else it's a quoted string of some kind
2010-10-12 23:41:48 +02:00
else if ( @ $match [ 'sqstring' ]) $parts [] = $match [ 'sqstring' ];
else if ( @ $match [ 'dqstring' ]) $parts [] = $match [ 'dqstring' ];
}
if ( $conditional && ! $condition ) {
throw new Exception ( " You need to have a condition after the conditional $conditional in your cache block " );
}
return array ( $parts , $conditional , $condition );
}
2010-10-13 03:14:49 +02:00
function generate () {
$res = array ();
foreach ( $this -> blocks as $i => $block ) {
if ( $block instanceof SSViewer_PartialParser )
$res [] = $block -> generate ();
else {
$res [] = $block ;
}
}
return implode ( '' , $res );
}
}
2010-10-13 05:53:12 +02:00
/**
* @ package sapphire
* @ subpackage view
*/
2010-10-13 03:14:49 +02:00
class SSViewer_Cached_PartialParser extends SSViewer_PartialParser {
function __construct ( $template , $content , $offset , $keyparts , $conditional , $condition ) {
$this -> keyparts = $keyparts ;
$this -> conditional = $conditional ;
$this -> condition = $condition ;
parent :: __construct ( $template , $content , $offset );
}
function controlcheck ( $text ) {
$ifs = preg_match_all ( '/<' . '% +if +/' , $text , $matches );
$end_ifs = preg_match_all ( '/<' . '% +end_if +/' , $text , $matches );
if ( $ifs != $end_ifs ) throw new Exception ( 'You can\'t have cached or uncached blocks within condition structures' );
$controls = preg_match_all ( '/<' . '% +control +/' , $text , $matches );
$end_controls = preg_match_all ( '/<' . '% +end_control +/' , $text , $matches );
if ( $controls != $end_controls ) throw new Exception ( 'You can\'t have cached or uncached blocks within control structures' );
}
2010-10-12 23:41:48 +02:00
function key () {
if ( empty ( $this -> keyparts )) return " '' " ;
return 'sha1(' . implode ( " .'_'. " , $this -> keyparts ) . ')' ;
}
function generate () {
$res = array ();
$key = $this -> key ();
$condition = " " ;
switch ( $this -> conditional ) {
case 'if' :
$condition = " { $this -> condition } && " ;
break ;
case 'unless' :
$condition = " !( { $this -> condition } ) && " ;
break ;
}
/* Output this set of blocks */
foreach ( $this -> blocks as $i => $block ) {
if ( $block instanceof SSViewer_PartialParser )
$res [] = $block -> generate ();
else {
// Include the template name and this cache block's current contents as a sha hash, so we get auto-seperation
// of cache blocks, and invalidation of the cache when the template changes
$partialkey = " ' " . sha1 ( $this -> template . $block ) . " _'. $key .'_ $i ' " ;
2010-10-13 03:14:49 +02:00
// Try to load from cache
$res [] = " <? \n " . 'if (' . $condition . ' ($partial = $cache->load(' . $partialkey . '))) $val .= $partial;' . " \n " ;
2010-10-12 23:41:48 +02:00
2010-10-13 03:14:49 +02:00
// Cache miss - regenerate
$res [] = " else { \n " ;
$res [] = '$oldval = $val; $val = "";' . " \n " ;
$res [] = " \n ?> " . $block . " <? \n " ;
$res [] = $condition . ' $cache->save($val); $val = $oldval . $val ;' . " \n " ;
$res [] = " } \n ?> " ;
2010-04-12 23:09:54 +02:00
}
}
2010-10-12 23:41:48 +02:00
return implode ( '' , $res );
2010-04-12 23:09:54 +02:00
}
}
2007-07-19 12:40:28 +02:00
function supressOutput () {
return " " ;
}
2010-10-13 05:40:05 +02:00
?>