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;
self::$options['rewriteHashlinks'] = false;
return $this;
}
public function exists() {
return $this->chosenTemplates;
}
/**
* 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
*/
public static function getTemplateFile($identifier) {
global $_TEMPLATE_MANIFEST;
$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];
} else {
return false;
}
}
/**
* 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.
* @return string content of template
*/
public static function getTemplateContent($identifier) {
if(!SSViewer::getTemplateFile($identifier)) {
return null;
}
return file_get_contents(SSViewer::getTemplateFile($identifier));
}
/**
* @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).
*/
static function flush_template_cache() {
if (!self::$flushed) {
$dir = dir(TEMP_FOLDER);
while (false !== ($file = $dir->read())) {
if (strstr($file, '.cache')) { unlink(TEMP_FOLDER.'/'.$file); }
}
self::$flushed = true;
}
}
/**
* The process() method handles the "meat" of the template processing.
*/
public function process($item) {
SSViewer::$topLevel[] = $item;
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);
$content = SSViewer::parseTemplateContent($content, $template);
$fh = fopen($cacheFile,'w');
fwrite($fh, $content);
fclose($fh);
if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process - compile", " for $template");
}
if(isset($_GET['showtemplate']) && !Director::isLive()) {
$lines = file($cacheFile);
echo "Template: $cacheFile
";
echo "";
foreach($lines as $num => $line) {
echo str_pad($num+1,5) . htmlentities($line);
}
echo "
";
}
foreach(array('Content', 'Layout') as $subtemplate) {
if(isset($this->chosenTemplates[$subtemplate])) {
$subtemplateViewer = new SSViewer($this->chosenTemplates[$subtemplate]);
$item = $item->customise(array(
$subtemplate => $subtemplateViewer->process($item)
));
}
}
$itemStack = array();
$val = "";
include($cacheFile);
$output = $val;
$output = Requirements::includeInHTML($template, $output);
array_pop(SSViewer::$topLevel);
if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process", " for $template");
// If we have our crazy base tag, then fix # links referencing the current page.
if($this->rewriteHashlinks && self::$options['rewriteHashlinks']) {
if(strpos($output, '+]href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
}
}
return $output;
}
static function parseTemplateContent($content, $template="") {
// Add template filename comments on dev sites
if(Director::isDev() && self::$source_file_comments && $template && stripos($content, "]*>)/i', "\\1", $content);
$content = preg_replace('/(<\/html[^>]*>)/i', "\\1", $content);
} else {
$content = "\n" . $content . "\n";
}
}
while(true) {
$oldContent = $content;
// Add include filename comments on dev sites
if(Director::isDev() && self::$source_file_comments) $replacementCode = 'return "\n"
. SSViewer::getTemplateContent($matches[1])
. "\n";';
else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);';
$content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(
'$matches', $replacementCode
), $content);
if($oldContent == $content) break;
}
// $val, $val.property, $val(param), etc.
$replacements = array(
'/<%--.*--%>/U' => '',
'/\$Iteration/' => '= {dlr}key ?>',
'/{\\$([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) ?>',
'/{\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)}/' => '= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>',
'/{\\$([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) ?>',
'/{\\$([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',
'/\\$([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',
'/\\$([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',
'/\\$([A-Za-z_][A-Za-z0-9_]*)\\(([^),]+), *([^),]+)\\)([^A-Za-z0-9]|$)/' => '= {dlr}item->XML_val("\\1",array("\\2","\\3"),true) ?>\\4',
'/\\$([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',
'/\\$([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('', '<' . '% control \\1 %' . '>', $content);
$content = ereg_replace('', '<' . '% end_control %' . '>', $content);
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+) +%' . '>', ' array_push($itemStack, $item); if($loop = $item->obj("\\1")) foreach($loop as $key => $item) { ?>', $content);
$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);
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\.([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);
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([A-Za-z0-9_-]+)\\) +%' . '>', ' array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2"))) foreach($loop as $key => $item) { ?>', $content);
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([A-Za-z0-9_-]+), *([A-Za-z0-9_-]+)\\) +%' . '>', ' array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2","\\3"))) foreach($loop as $key => $item) { ?>', $content);
$content = ereg_replace('<' . '% +control +([A-Za-z0-9_]+)\\(([A-Za-z0-9_-]+), *([A-Za-z0-9_-]+), *([A-Za-z0-9_-]+)\\) +%' . '>', ' array_push($itemStack, $item); if($loop = $item->obj("\\1", array("\\2", "\\3", "\\4"))) foreach($loop as $key => $item) { ?>', $content);
$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);
// i18n - get filename of currently parsed template
// CAUTION: No spaces allowed between arguments for all i18n calls!
ereg('.*[\/](.*)',$template,$path);
// i18n _t(...) - with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
$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);
// isnt valid html? !?
$content = ereg_replace('<' . '% +base_tag +%' . '>', '', $content);
$content = ereg_replace('<' . '% +current_page +%' . '>', '= $_SERVER[SCRIPT_URL] ?>', $content);
// add all requirements to the $requirements array
preg_match_all('/<% require ([a-zA-Z]+)\(([^\)]+)\) %>/', $content, $requirements);
$content = preg_replace('/<% require .* %>/', null, $content);
// legacy
$content = ereg_replace('', ' if($item->cachedCall("\\1")) { ?>', $content);
$content = ereg_replace('', ' } else { ?>', $content);
$content = ereg_replace('', ' } ?>', $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('=',"\nSSVIEWER;\n\$val .= ", $content);
$content = str_replace('',"\nSSVIEWER;\n", $content);
$content = str_replace('?>',";\n \$val .= <<]+)##>', '<' . '?xml\\1?' . '>', $output);
return $output;
}
/**
* 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;
}
/**
* @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;
}
}
/**
* Special SSViewer that will process a template passed as a string, rather than a filename.
* @package sapphire
* @subpackage view
*/
class SSViewer_FromString extends SSViewer {
protected $content;
public function __construct($content) {
$this->content = $content;
}
public function process($item) {
$template = SSViewer::parseTemplateContent($this->content);
$tmpFile = tempnam(TEMP_FOLDER,"");
$fh = fopen($tmpFile, 'w');
fwrite($fh, $template);
fclose($fh);
if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
$lines = file($tmpFile);
echo "Template: $tmpFile
";
echo "";
foreach($lines as $num => $line) {
echo str_pad($num+1,5) . htmlentities($line);
}
echo "
";
}
$itemStack = array();
$val = "";
include($tmpFile);
unlink($tmpFile);
return $val;
}
}
function supressOutput() {
return "";
}
?>