template = $template; if (isset($parentData)) { $this->data = $parentData; } } public function set(string $key, $value) { $this->data[$key] = $value; } public function render($options = null): string { Logger::log("Rendering template " . $this->template, Logger::IOREAD, "Template"); $class = is_object($options) && isset($options->class) ? new $options->class($this, $options) : new EmptyTemplate($this, $options); $class->load(); $contents = Cache::getTemplateFromCache($this->template); if (is_null($contents)) { $contents = file_get_contents(TEMPLATE_DIR . "/" . $this->template); Cache::setTemplateToCache($this->template, $contents); } $class->pretransform(); // Replace includes of templates $matches = array(); preg_match_all("/{{(include|includeCached) (.*\.html)(.*)}}/", $contents, $matches); for ($i = 0; $i < count($matches[1]); $i++) { $matchString = $matches[0][$i]; $method = $matches[1][$i]; $match = $matches[2][$i]; $options = trim($matches[3][$i]); if ($method === "includeCached") { $crc = hash("crc32b", $match . $options); $include = Cache::getIncludeCache($crc); if (!is_null($include)) { $contents = str_replace($matchString, $include, $contents); continue; } } $include = new Template(dirname($this->template) . "/" . $match, $this->data); $rendered = $include->render( !empty($options) ? json_decode($options, false, 512, JSON_THROW_ON_ERROR) : null ); if ($method === "includeCached") { Cache::setIncludeCache($crc, $rendered); } $contents = str_replace($matchString, $rendered, $contents); } $class->transform(); // Replace variables foreach ($this->data as $key => $value) { if (is_string($value) || is_numeric($value)) { $contents = str_replace("{{" . $key . "}}", $value, $contents); } else { $contents = str_replace("{{" . $key . "}}", "", $contents); } } // Conditional tags are written as {{?}}some content{{/}}, find conditional tags and evaluate them if they exist in $this->data $matches = array(); preg_match_all("/{{\?(.*)}}(.*){{\/(.*)}}/sU", $contents, $matches); for ($i = 0; $i < count($matches[1]); $i++) { $matchString = $matches[0][$i]; $match = $matches[1][$i]; $content = $matches[2][$i]; if (isset($this->data[$match])) { $contents = str_replace($matchString, $content, $contents); } else { $contents = str_replace($matchString, "", $contents); } } $class->save(); return $contents; } public function save(string $filename, string $contents, bool $minify = true) { Logger::log("Saving template to " . $filename, Logger::IOWRITE, "Template"); if ($minify) { $size = strlen($contents); $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; $doc->formatOutput = false; $doc->normalizeDocument(); libxml_use_internal_errors(true); $doc->loadHTML($contents); libxml_use_internal_errors(false); $options = [ 'keep_whitespace_around' => [ // keep whitespace around inline elements 'b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'br', 'img', 'map', 'object', 'q', 'span', 'sub', 'sup', 'button', 'input', 'label', 'select', 'textarea' ], 'keep_whitespace_in' => [/*'script', 'style',*/ 'pre'], 'remove_empty_attributes' => ['style', 'class'], 'indent_characters' => "\t" ]; //Comments,empty,whitespace $xpath = new \DOMXPath($doc); foreach ($xpath->query('//comment()') as $comment) { $comment->parentNode->removeChild($comment); } $xpath = new \DOMXPath($doc); foreach ($options['remove_empty_attributes'] as $attr) { foreach ($xpath->query('//*[@' . $attr . ']') as $el) { if (trim($el->getAttribute($attr)) == '') { $el->removeAttribute($attr); } } } $x = new \DOMXPath($doc); $nodeList = $x->query("//text()"); foreach ($nodeList as $node) { /** @var \DOMNode $node */ if (in_array($node->parentNode->nodeName, $options['keep_whitespace_in'])) { continue; }; $node->nodeValue = str_replace(["\r", "\n", "\t"], ' ', $node->nodeValue); while (strpos($node->nodeValue, ' ') !== false) { $node->nodeValue = str_replace(' ', ' ', $node->nodeValue); } if (!in_array($node->parentNode->nodeName, $options['keep_whitespace_around'])) { if (!($node->previousSibling && in_array( $node->previousSibling->nodeName, $options['keep_whitespace_around'] ))) { $node->nodeValue = ltrim($node->nodeValue); } if (!($node->nextSibling && in_array( $node->nextSibling->nodeName, $options['keep_whitespace_around'] ))) { $node->nodeValue = rtrim($node->nodeValue); } } if ((strlen($node->nodeValue) == 0)) { $node->parentNode->removeChild($node); } } $contents = $doc->saveHTML(); Logger::log("Minified template from " . $size . " to " . strlen($contents) . " bytes", Logger::METRICS, "Template"); } file_put_contents(PUBLIC_DIR . "/" . $filename, $contents); } public function saveCss(string $filename, string $contents, bool $minify = true) { Logger::log("Saving css to " . $filename, Logger::IOWRITE, "Template"); if ($minify) { $size = strlen($contents); $contents = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $contents); $contents = str_replace(': ', ':', $contents); $contents = str_replace(array("\r\n", "\r", "\n", "\t", ' ', ' ', ' '), '', $contents); Logger::log("Minified " . $filename . " from " . $size . " to " . strlen($contents) . " bytes", Logger::METRICS, "Template"); } file_put_contents(PUBLIC_DIR . "/" . $filename, $contents); } public function saveScript(string $filename, string $contents, bool $minify = true) { Logger::log("Saving script to " . $filename, Logger::IOWRITE, "Template"); if ($minify) { $size = strlen($contents); $contents = preg_replace('/([-\+])\s+\+([^\s;]*)/', '$1 (+$2)', $contents); $contents = preg_replace("/\s*\n\s*/", "\n", $contents); $contents = preg_replace("/\h+/", " ", $contents); $contents = preg_replace("/\h([^A-Za-z0-9\_\$])/", '$1', $contents); $contents = preg_replace("/([^A-Za-z0-9\_\$])\h/", '$1', $contents); $contents = preg_replace("/\s?([\(\[{])\s?/", '$1', $contents); $contents = preg_replace("/\s([\)\]}])/", '$1', $contents); $contents = preg_replace("/\s?([\.=:\-+,])\s?/", '$1', $contents); $contents = preg_replace("/;\n/", ";", $contents); $contents = preg_replace('/;}/', '}', $contents); $contents = preg_replace('/\s+/', ' ', $contents); $contents = preg_replace('/\s*([;{}])\s*/', '$1', $contents); $contents = preg_replace('/\s*(;{})\s*/', '$1', $contents); $contents = str_replace(array("\r\n", "\r", "\n", "\t", ' ', ' ', ' '), '', $contents); Logger::log("Minified " . $filename . " from " . $size . " to " . strlen($contents) . " bytes", Logger::METRICS, "Template"); } file_put_contents(PUBLIC_DIR . "/" . $filename, $contents); } }