272 lines
6.7 KiB
PHP
272 lines
6.7 KiB
PHP
<?php
|
||
/**
|
||
* @package FOF
|
||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||
* @license GNU General Public License version 3, or later
|
||
*/
|
||
|
||
namespace FOF40\View\Engine;
|
||
|
||
defined('_JEXEC') || die;
|
||
|
||
use FOF40\Utils\Buffer;
|
||
use FOF40\View\Compiler\CompilerInterface;
|
||
use FOF40\View\Exception\PossiblySuhosin;
|
||
use Joomla\CMS\Filesystem\File;
|
||
|
||
/**
|
||
* View engine for compiling PHP template files.
|
||
*/
|
||
abstract class CompilingEngine extends AbstractEngine implements EngineInterface
|
||
{
|
||
/** @var CompilerInterface The compiler used by this engine */
|
||
protected $compiler;
|
||
|
||
/**
|
||
* Get the 3ναlυa+3d contents of the view template. (I use leetspeak here because of bad quality hosts with broken
|
||
* scanners)
|
||
*
|
||
* @param string $path The path to the view template
|
||
* @param array $forceParams Any additional information to pass to the view template engine
|
||
*
|
||
* @return array Content evaluation information
|
||
*/
|
||
public function get($path, array $forceParams = [])
|
||
{
|
||
// If it's cached return the path to the cached file's path
|
||
if ($this->isCached($path))
|
||
{
|
||
return [
|
||
'type' => 'path',
|
||
'content' => $this->getCachePath($path),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Compile and cache the file. We also add the file path in a comment at the top of the file so phpStorm can
|
||
* debug it.
|
||
*
|
||
* @see https://blog.jetbrains.com/phpstorm/2019/02/phpstorm-2019-1-eap-191-5849-26/
|
||
* @see https://laravel-news.com/laravel-5-8-blade-template-file-path
|
||
*/
|
||
$content = "<?php /* $path */ ?>\n";
|
||
$content .= $this->compile($path, $forceParams);
|
||
$cachePath = $this->putToCache($path, $content);
|
||
$isPHPFile = substr($path, -4) == '.php';
|
||
|
||
// If we could cache it, return the cached file's path
|
||
if ($cachePath !== false)
|
||
{
|
||
// Bust the opcode cache for .php files
|
||
if ($isPHPFile)
|
||
{
|
||
$this->bustOpCache($path);
|
||
}
|
||
|
||
return [
|
||
'type' => 'path',
|
||
'content' => $cachePath,
|
||
];
|
||
}
|
||
|
||
// We could not write to the cache. Hm, can I use a stream wrapper?
|
||
$canUseStreams = Buffer::canRegisterWrapper();
|
||
|
||
if ($canUseStreams)
|
||
{
|
||
$id = $this->getIdentifier($path);
|
||
$streamPath = 'fof://' . $this->view->getContainer()->componentName . '/compiled_templates/' . $id . '.php';
|
||
file_put_contents($streamPath, $content);
|
||
|
||
// Bust the opcode cache for .php files
|
||
if ($isPHPFile)
|
||
{
|
||
$this->bustOpCache($path);
|
||
}
|
||
|
||
return [
|
||
'type' => 'path',
|
||
'content' => $streamPath,
|
||
];
|
||
}
|
||
|
||
// I couldn't use a stream wrapper. I have to give up.
|
||
throw new PossiblySuhosin;
|
||
}
|
||
|
||
/**
|
||
* Returns the path where I can find a precompiled version of the uncompiled view template which lives in $path
|
||
*
|
||
* @param string $path The path to the uncompiled view template
|
||
*
|
||
* @return bool|string False if the view template is outside the component's front- or backend.
|
||
*
|
||
* @since 3.3.1
|
||
*/
|
||
public function getPrecompiledPath($path)
|
||
{
|
||
// Normalize the path to the file
|
||
$path = realpath($path);
|
||
|
||
if ($path === false)
|
||
{
|
||
// The file doesn't exist
|
||
return false;
|
||
}
|
||
|
||
// Is this path under the component's front- or backend?
|
||
$frontendPath = realpath($this->view->getContainer()->frontEndPath);
|
||
$backendPath = realpath($this->view->getContainer()->backEndPath);
|
||
|
||
$backPos = strpos($path, $backendPath);
|
||
$frontPos = strpos($path, $frontendPath);
|
||
|
||
if (($backPos !== 0) && ($frontPos !== 0))
|
||
{
|
||
// This is not a view template shipped with the component, i.e. it can't be precompiled
|
||
return false;
|
||
}
|
||
|
||
// Eliminate the component path from $path to get the relative path to the file
|
||
$componentPath = $frontendPath;
|
||
|
||
if ($backPos === 0)
|
||
{
|
||
$componentPath = $backendPath;
|
||
}
|
||
|
||
$relativePath = ltrim(substr($path, strlen($componentPath)), '\\/');
|
||
|
||
// Break down the relative path to its parts
|
||
$relativePath = str_replace('\\', '/', $relativePath);
|
||
$pathParts = explode('/', $relativePath);
|
||
|
||
// Remove the prefix
|
||
$prefix = array_shift($pathParts);
|
||
|
||
// If it's a legacy view, View, Views, or views prefix remove the 'tmpl' part
|
||
if (($prefix !== 'ViewTemplates') && ($prefix !== 'tmpl'))
|
||
{
|
||
unset($pathParts[1]);
|
||
}
|
||
|
||
// Get the last part and process the extension
|
||
$viewFile = array_pop($pathParts);
|
||
$extensionWithoutDot = $this->compiler->getFileExtension();
|
||
$pathParts[] = substr($viewFile, 0, -strlen($extensionWithoutDot)) . 'php';
|
||
|
||
$precompiledRelativePath = implode(DIRECTORY_SEPARATOR, $pathParts);
|
||
|
||
return $componentPath . DIRECTORY_SEPARATOR . 'PrecompiledTemplates' . DIRECTORY_SEPARATOR . $precompiledRelativePath;
|
||
}
|
||
|
||
/**
|
||
* A method to compile the raw view template into valid PHP
|
||
*
|
||
* @param string $path The path to the view template
|
||
* @param array $forceParams Any additional information to pass to the view template compiler
|
||
*
|
||
* @return string The template compiled to executable PHP
|
||
*/
|
||
protected function compile($path, array $forceParams = [])
|
||
{
|
||
return $this->compiler->compile($path, $forceParams);
|
||
}
|
||
|
||
protected function getIdentifier($path)
|
||
{
|
||
if (function_exists('sha1'))
|
||
{
|
||
return sha1($path);
|
||
}
|
||
|
||
return md5($path);
|
||
}
|
||
|
||
protected function getCachePath($path)
|
||
{
|
||
$id = $this->getIdentifier($path);
|
||
|
||
return JPATH_CACHE . '/' . $this->view->getContainer()->componentName . '/compiled_templates/' . $id . '.php';
|
||
}
|
||
|
||
protected function isCached($path)
|
||
{
|
||
if (!$this->compiler->isCacheable())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
$cachePath = $this->getCachePath($path);
|
||
|
||
if (!file_exists($cachePath))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
$cacheTime = filemtime($cachePath);
|
||
$fileTime = filemtime($path);
|
||
|
||
return $fileTime <= $cacheTime;
|
||
}
|
||
|
||
protected function getCached($path)
|
||
{
|
||
$cachePath = $this->getCachePath($path);
|
||
|
||
return file_get_contents($cachePath);
|
||
}
|
||
|
||
protected function putToCache($path, $content)
|
||
{
|
||
$cachePath = $this->getCachePath($path);
|
||
|
||
if (@file_put_contents($cachePath, $content))
|
||
{
|
||
return $cachePath;
|
||
}
|
||
|
||
if (File::write($cachePath, $content))
|
||
{
|
||
return $cachePath;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Bust the opcode cache for a given .php file
|
||
*
|
||
* This method can address opcode caching with:
|
||
* - Zend OPcache
|
||
* - Alternative PHP Cache (now defunct)
|
||
* - Windows Cache Extension for PHP (versions lower than 2.0.0)
|
||
* - XCache (now defunct)
|
||
*
|
||
* @param string $path The file to bus the cache for
|
||
*
|
||
* @return void
|
||
*/
|
||
private function bustOpCache($path)
|
||
{
|
||
if (function_exists('opcache_invalidate'))
|
||
{
|
||
opcache_invalidate($path);
|
||
}
|
||
|
||
if (function_exists('apc_compile_file'))
|
||
{
|
||
apc_compile_file($path);
|
||
}
|
||
|
||
if (function_exists('wincache_refresh_if_changed'))
|
||
{
|
||
wincache_refresh_if_changed([$path]);
|
||
}
|
||
|
||
if (function_exists('xcache_asm'))
|
||
{
|
||
xcache_asm($path);
|
||
}
|
||
}
|
||
} |