mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
removed my prior changes to the code behaviour removed errorous PHPDocs fixed some PHPDocs removed my prior changes to the code behaviour removed errorous PHPDocs
237 lines
6.6 KiB
PHP
237 lines
6.6 KiB
PHP
<?php namespace Cms\Classes;
|
|
|
|
use File;
|
|
use Lang;
|
|
use Cache;
|
|
use Config;
|
|
use SystemException;
|
|
|
|
/**
|
|
* Parses the PHP code section of CMS objects.
|
|
*
|
|
* @package october\cms
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
class CodeParser
|
|
{
|
|
/**
|
|
* @var \Cms\Classes\CmsCompoundObject A reference to the CMS object being parsed.
|
|
*/
|
|
protected $object;
|
|
|
|
/**
|
|
* @var string Contains a path to the CMS object's file being parsed.
|
|
*/
|
|
protected $filePath;
|
|
|
|
/**
|
|
* @var mixed The internal cache, keeps parsed object information during a request.
|
|
*/
|
|
static protected $cache = [];
|
|
|
|
/**
|
|
* @var string Key for the parsed PHP file information cache.
|
|
*/
|
|
protected $dataCacheKey = 'cms-php-file-data';
|
|
|
|
/**
|
|
* Creates the class instance
|
|
* @param \Cms\Classes\CmsCompoundObject A reference to a CMS object to parse.
|
|
*/
|
|
public function __construct(CmsCompoundObject $object)
|
|
{
|
|
$this->object = $object;
|
|
$this->filePath = $object->getFullPath();
|
|
}
|
|
|
|
/**
|
|
* Parses the CMS object's PHP code section and returns an array with the following keys:
|
|
* - className
|
|
* - filePath (path to the parsed PHP file)
|
|
* - offset (PHP section offset in the template file)
|
|
* - source ('parser', 'request-cache', or 'cache')
|
|
* @return array
|
|
*/
|
|
public function parse()
|
|
{
|
|
/*
|
|
* If the object has already been parsed in this request return the cached data.
|
|
*/
|
|
if (array_key_exists($this->filePath, self::$cache)) {
|
|
self::$cache[$this->filePath]['source'] = 'request-cache';
|
|
return self::$cache[$this->filePath];
|
|
}
|
|
|
|
/*
|
|
* Try to load the parsed data from the file cache
|
|
*/
|
|
$path = $this->getFilePath();
|
|
$result = [
|
|
'filePath' => $path,
|
|
'offset' => 0
|
|
];
|
|
|
|
if (File::isFile($path)) {
|
|
$cachedInfo = $this->getCachedFileInfo();
|
|
if ($cachedInfo !== null && $cachedInfo['mtime'] == $this->object->mtime) {
|
|
$result['className'] = $cachedInfo['className'];
|
|
$result['source'] = 'cache';
|
|
|
|
return self::$cache[$this->filePath] = $result;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the file was not found, or the cache is stale, prepare the new file and cache information about it
|
|
*/
|
|
$uniqueName = uniqid().'_'.abs(crc32(md5(mt_rand())));
|
|
$className = 'Cms'.$uniqueName.'Class';
|
|
|
|
$body = $this->object->code;
|
|
$body = preg_replace('/^\s*function/m', 'public function', $body);
|
|
|
|
$codeNamespaces = [];
|
|
$pattern = '/(use\s+[a-z0-9_\\\\]+;\n?)/mi';
|
|
preg_match_all($pattern, $body, $namespaces);
|
|
$body = preg_replace($pattern, '', $body);
|
|
|
|
$parentClass = $this->object->getCodeClassParent();
|
|
if ($parentClass !== null) {
|
|
$parentClass = ' extends '.$parentClass;
|
|
}
|
|
|
|
$fileContents = '<?php '.PHP_EOL;
|
|
|
|
foreach ($namespaces[0] as $namespace) {
|
|
$fileContents .= $namespace;
|
|
}
|
|
|
|
$fileContents .= 'class '.$className.$parentClass.PHP_EOL;
|
|
$fileContents .= '{'.PHP_EOL;
|
|
$fileContents .= $body.PHP_EOL;
|
|
$fileContents .= '}'.PHP_EOL;
|
|
|
|
$this->validate($fileContents);
|
|
|
|
$dir = dirname($path);
|
|
if (!File::isDirectory($dir) && !@File::makeDirectory($dir, 0777, true)) {
|
|
throw new SystemException(Lang::get('system::lang.directory.create_fail', ['name'=>$dir]));
|
|
}
|
|
|
|
if (!@File::put($path, $fileContents)) {
|
|
throw new SystemException(Lang::get('system::lang.file.create_fail', ['name'=>$dir]));
|
|
}
|
|
|
|
$cached = $this->getCachedInfo();
|
|
if (!$cached) {
|
|
$cached = [];
|
|
}
|
|
|
|
$result['className'] = $className;
|
|
$result['source'] = 'parser';
|
|
|
|
$cacheItem = $result;
|
|
$cacheItem['mtime'] = $this->object->mtime;
|
|
$cached[$this->filePath] = $cacheItem;
|
|
|
|
Cache::put($this->dataCacheKey, serialize($cached), 1440);
|
|
|
|
return self::$cache[$this->filePath] = $result;
|
|
}
|
|
|
|
/**
|
|
* Runs the object's PHP file and returns the corresponding object.
|
|
* @param \Cms\Classes\Page $page Specifies the CMS page.
|
|
* @param \Cms\Classes\Layout $layout Specifies the CMS layout.
|
|
* @param \Cms\Classes\Controller $controller Specifies the CMS controller.
|
|
* @return mixed
|
|
*/
|
|
public function source($page, $layout, $controller)
|
|
{
|
|
$data = $this->parse();
|
|
$className = $data['className'];
|
|
|
|
if (!class_exists($className)) {
|
|
require_once $data['filePath'];
|
|
}
|
|
|
|
if (!class_exists($className) && ($data = $this->handleCorruptCache())) {
|
|
$className = $data['className'];
|
|
}
|
|
|
|
return new $className($page, $layout, $controller);
|
|
}
|
|
|
|
/**
|
|
* In some rare cases the cache file will not contain the class
|
|
* name we expect. When this happens, destroy the corrupt file,
|
|
* flush the request cache, and repeat the cycle.
|
|
* @return void
|
|
*/
|
|
protected function handleCorruptCache()
|
|
{
|
|
$path = $this->getFilePath();
|
|
if (File::isFile($path)) {
|
|
File::delete($path);
|
|
}
|
|
|
|
unset(self::$cache[$this->filePath]);
|
|
|
|
return $this->parse();
|
|
}
|
|
|
|
/**
|
|
* Evaluates PHP content in order to detect syntax errors.
|
|
* The method handles PHP errors and throws exceptions.
|
|
*/
|
|
protected function validate($php)
|
|
{
|
|
eval('?>'.$php);
|
|
}
|
|
|
|
/**
|
|
* Returns path to the cached parsed file
|
|
* @return string
|
|
*/
|
|
protected function getFilePath()
|
|
{
|
|
$hash = abs(crc32($this->filePath));
|
|
$result = storage_path().'/cms/cache/';
|
|
$result .= substr($hash, 0, 2).'/';
|
|
$result .= substr($hash, 2, 2).'/';
|
|
$result .= basename($this->filePath).'.php';
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns information about all cached files.
|
|
* @return mixed Returns an array representing the cached data or NULL.
|
|
*/
|
|
protected function getCachedInfo()
|
|
{
|
|
$cached = Cache::get($this->dataCacheKey, false);
|
|
if ($cached !== false && ($cached = @unserialize($cached)) !== false) {
|
|
return $cached;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns information about a cached file
|
|
* @return integer
|
|
*/
|
|
protected function getCachedFileInfo()
|
|
{
|
|
$cached = $this->getCachedInfo();
|
|
if ($cached !== null) {
|
|
if (array_key_exists($this->filePath, $cached)) {
|
|
return $cached[$this->filePath];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|