diff options
author | Thomas Lange <code@nerdmind.de> | 2021-07-20 18:39:34 +0200 |
---|---|---|
committer | Thomas Lange <code@nerdmind.de> | 2021-07-20 18:39:34 +0200 |
commit | e6103791f528357197f8afb9ed222a9469cbd177 (patch) | |
tree | 2eeb9ac567532c6dbe646dc44c9f9bca4eb8936f /core | |
parent | 01d4727f939c0b9530fe5fc976b7accb9e078db1 (diff) | |
download | blog-e6103791f528357197f8afb9ed222a9469cbd177.tar.gz blog-e6103791f528357197f8afb9ed222a9469cbd177.tar.xz blog-e6103791f528357197f8afb9ed222a9469cbd177.zip |
Implement new *content functions* feature (readme)
This commit implements a new feature called *content functions* that is
similar but much more powerful than the already existing *content tags*
which you may have already used (`{POST[1]}`, for example).
You now can also add your own *content functions* to do some interesting
things like embedding a YouTube video or other things to prevent typing
repetitive lines of text or code in your entities content.
Read the corresponding wiki page to learn more about this:
https://github.com/Nerdmind/Blog/wiki/Content-functions
Diffstat (limited to 'core')
-rw-r--r-- | core/functions.php | 63 | ||||
-rw-r--r-- | core/namespace/Application.php | 13 | ||||
-rw-r--r-- | core/namespace/Parsers/FunctionParser.php | 84 |
3 files changed, 159 insertions, 1 deletions
diff --git a/core/functions.php b/core/functions.php index 97ff802..cf35aa9 100644 --- a/core/functions.php +++ b/core/functions.php @@ -9,6 +9,7 @@ use Template\Template as Template; use Template\Factory as TemplateFactory; use Parsers\ArgumentParser; +use Parsers\FunctionParser; use Parsers\EmoticonParser; use Parsers\MarkdownParser; @@ -190,7 +191,8 @@ function parseContentTags(string $text): string { $text = preg_replace($base_tag, \Application::getURL('$1'), $text); $text = preg_replace($file_tag, \Application::getFileURL('$1'), $text); - return $text; + $FunctionParser = new FunctionParser; + return $FunctionParser->transform($text); } #=============================================================================== @@ -318,6 +320,23 @@ function generateSlug($string, $separator = '-') { return trim($string, $separator); } +#=========================================================================== +# Callback for (CATEGORY|PAGE|POST|USER) content function +#=========================================================================== +function getEntityMarkdownLink($ns, $id, $text = NULL, $info = NULL): string { + if(!$Entity = Application::getRepository($ns)->find($id)) { + return sprintf('`{%s: *Reference error*}`', strtoupper($ns)); + } + + $title = htmlspecialchars($Entity->get('name') ?? $Entity->get('fullname')); + $href = Application::getEntityURL($Entity); + $text = $text ?: "»{$title}«"; + $info = $info ?: sprintf('%s »%s«', + Application::getLanguage()->text(strtolower($ns)), $title); + + return sprintf('[%s](%s "%s")', $text, $href, $info); +} + #=============================================================================== # Function for use in templates to get data of a category #=============================================================================== @@ -369,3 +388,45 @@ function USER(int $id): array { return []; } + +#=========================================================================== +# Get base URL (optionally extended by $extend) +#=========================================================================== +FunctionParser::register('BASE_URL', function($extend = '') { + return Application::getURL($extend); +}); + +#=========================================================================== +# Get file URL (optionally extended by $extend) +#=========================================================================== +FunctionParser::register('FILE_URL', function($extend = '') { + return Application::getFileURL($extend); +}); + +#=========================================================================== +# Get Markdown formatted *category* link +#=========================================================================== +FunctionParser::register('CATEGORY', function($id, $text = NULL, $title = NULL) { + return getEntityMarkdownLink('Category', $id, $text, $title); +}); + +#=========================================================================== +# Get Markdown formatted *page* link +#=========================================================================== +FunctionParser::register('PAGE', function($id, $text = NULL, $title = NULL) { + return getEntityMarkdownLink('Page', $id, $text, $title); +}); + +#=========================================================================== +# Get Markdown formatted *post* link +#=========================================================================== +FunctionParser::register('POST', function($id, $text = NULL, $title = NULL) { + return getEntityMarkdownLink('Post', $id, $text, $title); +}); + +#=========================================================================== +# Get Markdown formatted *user* link +#=========================================================================== +FunctionParser::register('USER', function($id, $text = NULL, $title = NULL) { + return getEntityMarkdownLink('User', $id, $text, $title); +}); diff --git a/core/namespace/Application.php b/core/namespace/Application.php index 5690841..140baac 100644 --- a/core/namespace/Application.php +++ b/core/namespace/Application.php @@ -1,5 +1,6 @@ <?php use ORM\EntityInterface; +use Parsers\FunctionParser; class Application { @@ -214,6 +215,18 @@ class Application { } #=============================================================================== + # Add a custom content function + #=============================================================================== + public static function addContentFunction(string $name, callable $callback): void { + if(!preg_match('#^([0-9A-Z_]+)$#', $name)) { + throw new Exception('The name for adding a content function must + contain only numbers, uppercase letters and underscores!'); + } + + FunctionParser::register($name, $callback); + } + + #=============================================================================== # Exit application with a custom message and status code #=============================================================================== public static function exit($message = '', $code = 503): void { diff --git a/core/namespace/Parsers/FunctionParser.php b/core/namespace/Parsers/FunctionParser.php new file mode 100644 index 0000000..9f2f72e --- /dev/null +++ b/core/namespace/Parsers/FunctionParser.php @@ -0,0 +1,84 @@ +<?php +namespace Parsers; +use ReflectionFunction; + +class FunctionParser implements ParserInterface { + private static $functions = []; + + #=============================================================================== + # Regular expressions + #=============================================================================== + private const FUNCTION_SHELL_REGEX = '#\{\s?(%s)%s\s?\}#'; + private const ARGUMENT_PARTS_REGEX = '(?:\:( (?:(?:"[^"]*"|[0-9]+)(?:,[\s]*)?)+))?'; + private const ARGUMENT_SPLIT_REGEX = '#("[^"]*"|[0-9]+)(?:,[\s]*)?#'; + + #=============================================================================== + # Register function + #=============================================================================== + public static function register(string $name, callable $callback): void { + $Function = new ReflectionFunction($callback); + self::$functions[$name] = [ + 'callback' => $callback, + 'required' => $Function->getNumberOfRequiredParameters() + ]; + } + + #=============================================================================== + # Parse functions + #=============================================================================== + public function parse(string $text): array { + $functionNames = array_keys(self::$functions); + $functionNames = implode('|', $functionNames); + + $pattern = self::FUNCTION_SHELL_REGEX; + $options = self::ARGUMENT_PARTS_REGEX; + + preg_match_all(sprintf($pattern, $functionNames, $options), $text, $matches); + + foreach(array_map(function($name, $parameters) { + return [$name , $this->parseParameterString($parameters)]; + }, $matches[1], $matches[2]) as $match) { + $functions[$match[0]][] = $match[1]; + } + + return $functions ?? []; + } + + #=============================================================================== + # Transform functions + #=============================================================================== + public function transform(string $text): string { + $functionData = self::$functions; + $functionNames = array_keys($functionData); + $functionNames = implode('|', $functionNames); + + $pattern = self::FUNCTION_SHELL_REGEX; + $options = self::ARGUMENT_PARTS_REGEX; + + return preg_replace_callback(sprintf($pattern, $functionNames, $options), + function($matches) use($functionData) { + $function = $matches[1]; + $callback = $functionData[$function]['callback']; + $required = $functionData[$function]['required']; + + $arguments = $this->parseParameterString($matches[2] ?? ''); + + if(count($arguments) < $required) { + return sprintf('`{%s: *Missing arguments*}`', $function); + } + + return $callback(...$arguments); + }, $text); + } + + #=============================================================================== + # Parse the parameter string found within the function shell + #=============================================================================== + private function parseParameterString(string $parameters): array { + preg_match_all(self::ARGUMENT_SPLIT_REGEX, $parameters, $matches); + + return array_map(function($argument) { + return trim($argument, '"'); + }, $matches[1]); + } +} |