aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Lange <code@nerdmind.de>2021-07-20 18:39:34 +0200
committerThomas Lange <code@nerdmind.de>2021-07-20 18:39:34 +0200
commite6103791f528357197f8afb9ed222a9469cbd177 (patch)
tree2eeb9ac567532c6dbe646dc44c9f9bca4eb8936f
parent01d4727f939c0b9530fe5fc976b7accb9e078db1 (diff)
downloadblog-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
-rw-r--r--core/functions.php63
-rw-r--r--core/namespace/Application.php13
-rw-r--r--core/namespace/Parsers/FunctionParser.php84
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]);
+ }
+}