postCache)) { return $this->postCache; } $markdownSource = file_get_contents($this->filePath); // Evaluate all PHP code blocks which have the magic eval comment $markdownSource = preg_replace_callback( "#```php\n//\[eval\](?.+?)```#s", function (array $matches): string { ob_start(); eval($matches['code']); return ob_get_clean(); }, $markdownSource, ); // Replace magic include statements $markdownSource = preg_replace_callback( "#\+\((?.+?)\)#s", function (array $matches): string { $path = __DIR__ . '/../' . $matches['includePath']; if (file_exists($path) === false) { throw new \Exception('Unable to include file [' . $path . ']'); } return file_get_contents($path); }, $markdownSource, ); // Evaluate all HTML code blocks which have the magic eval comment $markdownSource = preg_replace_callback( "#```html\n(?.+?)```#s", function (array $matches): string { $key = 'FRAME' . base64_encode(random_bytes(8)); $this->frameInjections[$key] = ''; return $key; }, $markdownSource, ); $highlighter = new Highlighter() ->addLanguage(new OpenscadLanguage()) ->addLanguage(new BashLanguage()) ; $environment = new Environment() ->addExtension(new CommonMarkCoreExtension()) ->addExtension(new SmartImageExtension()) ->addExtension(new MarkdownTagExtension()) ->addExtension(new HighlightExtension($highlighter)) ->addExtension(new TableExtension()) ->addExtension(new StlModelViewerExtension()) ->addExtension(new StrikethroughExtension()) ->addExtension(new AttributesExtension()) ; return $this->postCache = new MarkdownConverter($environment) ->convert($markdownSource); } public function getPublishedAt(): DateTimeImmutable { $date = explode('_', pathinfo($this->filePath, PATHINFO_FILENAME))[0]; return new DateTimeImmutable($date); } /** @return string[] */ public function getTags(): iterable { $nodes = new Query() ->where(Query::type(Tag::class)) ->findAll($this->parseMarkdown()->getDocument()); $tags = array_map( fn(Tag $tag): string => $tag->getLiteral(), iterator_to_array($nodes), ); asort($tags); return $tags; } public function getTitle(): string { return new Query() ->where(Query::type(Heading::class)) ->findOne($this->parseMarkdown()->getDocument()) ?->firstChild() ->getLiteral() ?? throw new \Exception('No title found'); } public function getBody(): string { $content = $this->parseMarkdown()->getContent(); foreach ($this->frameInjections as $key => $iframeHtml) { $content = str_replace('

' . $key . '

', $iframeHtml, $content); } return preg_replace('/

[^<]+<\/h1>/', '', $content); } public function __toString(): string { return $this->getBody(); } public function getUrl(): string { return '/' . explode('_', pathinfo($this->filePath, PATHINFO_FILENAME))[1]; } public function getId(): string { return pathinfo($this->filePath, PATHINFO_FILENAME); } public function getWordCount(): int { return str_word_count($this->getBody()); } }