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 = '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(); if (!class_exists($data['className'])) require_once $data['filePath']; $className = $data['className']; return new $className($page, $layout, $controller); } /** * 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().'/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; } }