text(trim(file_get_contents($path))); } public static function exists($template): bool { return is_readable(static::getFullPath($template)); } protected static function isMarkdown($nameOrPath): bool { return strlen($nameOrPath) > 3 && substr($nameOrPath, -3) == '.md'; } protected static function getFullPath($template): string { if ($template && $template[0] == '/') { return $template; } if (static::isMarkdown($template)) { return ContentActions::CONTENT_DIR . '/' . $template; } return ROOT_DIR . '/view/template/' . $template . '.php'; } public static function imagePath($image): string { return '/img/' . $image; } public static function parseMarkdown($template): array { $path = static::getFullPath($template); list($ignored, $frontMatter, $markdown) = explode('---', file_get_contents($path), 3); $metadata = Spyc::YAMLLoadString(trim($frontMatter)); $html = ParsedownExtra::instance()->text(trim($markdown)); return [$metadata, $html]; } public static function compileCss() { $scssCompiler = new \scssphp\ScssPhp\Compiler(); $scssCompiler->setImportPaths([self::COMPONENTS_DIR, self::COLORS_DIR, self::SCSS_DIR]); $compress = true; if ($compress) { $scssCompiler->setFormatter('scssphp\ScssPhp\Formatter\Crunched'); } else { $scssCompiler->setFormatter('scssphp\ScssPhp\Formatter\Expanded'); $scssCompiler->setLineNumberStyle(scssphp\ScssPhp\Compiler::LINE_COMMENTS); } $all_css = $scssCompiler->compile(file_get_contents(self::SCSS_DIR . '/all.scss')); file_put_contents(self::CSS_DIR . '/all.css', $all_css); } public static function gzipAssets() { foreach ([self::CSS_DIR => 'css', self::JS_DIR => 'js'] as $dir => $ext) { foreach (glob("$dir/*.$ext") as $file) { Gzip::compressFile($file); } } } protected static function interpolateTokens($html): string { return preg_replace_callback('/{{[\w\.]+}}/is', function ($m) { return i18n::translate(trim($m[0], '}{')); }, $html); } protected static function escapeOnce($value): string { return preg_replace('/&([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', htmlspecialchars((string)$value, ENT_QUOTES, 'utf-8')); } protected static function attributesToHtml($attributes): string { return implode('', array_map(function ($k, $v) { return $v === true ? " $k" : ($v === false || $v === null || ($v === '' && $k != 'value') ? '' : sprintf(' %s="%s"', $k, static::escapeOnce($v))); }, array_keys($attributes), array_values($attributes))); } public static function renderTag($tag, $attributes = []): string { return $tag ? sprintf('<%s%s />', $tag, static::attributesToHtml($attributes)) : ''; } public static function renderContentTag($tag, $content = null, $attributes = []): string { return $tag ? sprintf('<%s%s>%s', $tag, static::attributesToHtml($attributes), $content, $tag) : ''; } public static function renderJson($data): array { Response::setHeader(Response::HEADER_CONTENT_TYPE, 'application/json'); return ['internal/json', ['json' => $data, '_no_layout' => true]]; } public static function safeExternalLinks(string $html, string $domain): string { try { $parser = new Masterminds\HTML5(); $dom = $parser->loadHTML($html); $links = $dom->getElementsByTagName('body') ? $dom->getElementsByTagName('body')[0]->getElementsByTagName('a') : $dom->getElementsByTagName('a'); foreach ($links as $link) { if ($link->getAttribute('href') && static::isLinkExternal($link->getAttribute('href'), $domain)) { $link->setAttribute('rel', "noopener"); } } return $parser->saveHTML($dom); } catch (Error $e) { Slack::slackGrin(); return $html; } } public static function isLinkExternal(string $url, string $domain): bool { $components = parse_url(strtolower($url)); $domain = strtolower($domain); return $url && !empty($components['host']) // relative urls are not external && $components['host'] !== $domain && mb_substr($components['host'], -mb_strlen('.' . $domain)) !== '.' . $domain; } }