links. This is useful in Ajax applications.
* It returns the SSViewer objects, so that you can call new SSViewer("X")->dontRewriteHashlinks()->process();
*
* @return $this
*/
public function dontRewriteHashlinks()
{
return $this->setRewriteHashLinks(false);
}
/**
* @return string
*/
public function exists()
{
return $this->chosen;
}
/**
* @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 = null)
{
return ThemeResourceLoader::inst()->findTemplate(['type' => $type, $identifier], self::get_themes());
}
/**
* Clears all parsed template files in the cache folder.
*
* Can only be called once per request (there may be multiple SSViewer instances).
*
* @param bool $force Set this to true to force a re-flush. If left to false, flushing
* may only be performed once a request.
*/
public static function flush_template_cache($force = false)
{
if (!self::$template_cache_flushed || $force) {
$dir = dir(TEMP_PATH);
while (false !== ($file = $dir->read())) {
if (strstr($file ?? '', '.cache')) {
unlink(TEMP_PATH . DIRECTORY_SEPARATOR . $file);
}
}
self::$template_cache_flushed = true;
}
}
/**
* Clears all partial cache blocks.
*
* Can only be called once per request (there may be multiple SSViewer instances).
*
* @param bool $force Set this to true to force a re-flush. If left to false, flushing
* may only be performed once a request.
*/
public static function flush_cacheblock_cache($force = false)
{
if (!self::$cacheblock_cache_flushed || $force) {
$cache = Injector::inst()->get(CacheInterface::class . '.cacheblock');
$cache->clear();
self::$cacheblock_cache_flushed = true;
}
}
/**
* Set the cache object to use when storing / retrieving partial cache blocks.
*
* @param CacheInterface $cache
*/
public function setPartialCacheStore($cache)
{
$this->partialCacheStore = $cache;
}
/**
* Get the cache object to use when storing / retrieving partial cache blocks.
*
* @return CacheInterface
*/
public function getPartialCacheStore()
{
if ($this->partialCacheStore) {
return $this->partialCacheStore;
}
return Injector::inst()->get(CacheInterface::class . '.cacheblock');
}
/**
* Flag whether to include the requirements in this response.
*
* @param bool $incl
*/
public function includeRequirements($incl = true)
{
$this->includeRequirements = $incl;
}
/**
* An internal utility function to set up variables in preparation for including a compiled
* template, then do the include
*
* Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call
*
* @param string $cacheFile The path to the file that contains the template compiled to PHP
* @param ViewableData $item The item to use as the root scope for the template
* @param array $overlay Any variables to layer on top of the scope
* @param array $underlay Any variables to layer underneath the scope
* @param ViewableData $inheritedScope The current scope of a parent template including a sub-template
* @return string The result of executing the template
*/
protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay, $inheritedScope = null)
{
if (isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
$lines = file($cacheFile ?? '');
echo "Template: $cacheFile
";
echo "";
foreach ($lines as $num => $line) {
echo str_pad($num+1, 5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
}
echo "
";
}
$cache = $this->getPartialCacheStore();
$scope = new SSViewer_DataPresenter($item, $overlay, $underlay, $inheritedScope);
$val = '';
// Placeholder for values exposed to $cacheFile
[$cache, $scope, $val];
include($cacheFile);
return $val;
}
/**
* The process() method handles the "meat" of the template processing.
*
* It takes care of caching the output (via {@link 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 array|null $arguments Arguments to an included template
* @param ViewableData $inheritedScope The current scope of a parent template including a sub-template
* @return DBHTMLText Parsed template output.
*/
public function process($item, $arguments = null, $inheritedScope = null)
{
// Set hashlinks and temporarily modify global state
$rewrite = $this->getRewriteHashLinks();
$origRewriteDefault = static::getRewriteHashLinksDefault();
static::setRewriteHashLinksDefault($rewrite);
SSViewer::$topLevel[] = $item;
$template = $this->chosen;
$cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache'
. str_replace(['\\','/',':'], '.', Director::makeRelative(realpath($template ?? '')) ?? '');
$lastEdited = filemtime($template ?? '');
if (!file_exists($cacheFile ?? '') || filemtime($cacheFile ?? '') < $lastEdited) {
$content = file_get_contents($template ?? '');
$content = $this->parseTemplateContent($content, $template);
$fh = fopen($cacheFile ?? '', 'w');
fwrite($fh, $content ?? '');
fclose($fh);
}
$underlay = ['I18NNamespace' => basename($template ?? '')];
// Makes the rendered sub-templates available on the parent item,
// through $Content and $Layout placeholders.
foreach (['Content', 'Layout'] as $subtemplate) {
// Detect sub-template to use
$sub = $this->getSubtemplateFor($subtemplate);
if (!$sub) {
continue;
}
// Create lazy-evaluated underlay for this subtemplate
$underlay[$subtemplate] = function () use ($item, $arguments, $sub) {
$subtemplateViewer = clone $this;
// Disable requirements - this will be handled by the parent template
$subtemplateViewer->includeRequirements(false);
// Select the right template
$subtemplateViewer->setTemplate($sub);
// Render if available
if ($subtemplateViewer->exists()) {
return $subtemplateViewer->process($item, $arguments);
}
return null;
};
}
$output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope);
if ($this->includeRequirements) {
$output = Requirements::includeInHTML($output);
}
array_pop(SSViewer::$topLevel);
// If we have our crazy base tag, then fix # links referencing the current page.
if ($rewrite) {
if (strpos($output ?? '', '
PHP;
} else {
$thisURLRelativeToBase = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ?? ''));
}
$output = preg_replace('/(