2014-06-28 21:23:13 +10:00
|
|
|
<?php namespace System\Classes;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-07-16 18:28:15 +10:00
|
|
|
use Str;
|
|
|
|
use Twig_TokenParser;
|
|
|
|
use Twig_SimpleFilter;
|
|
|
|
use Twig_SimpleFunction;
|
2015-01-28 18:03:35 +11:00
|
|
|
use ApplicationException;
|
2014-05-14 23:24:20 +10:00
|
|
|
use System\Classes\PluginManager;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class manages Twig functions, token parsers and filters.
|
|
|
|
*
|
|
|
|
* @package october\cms
|
|
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
|
|
*/
|
|
|
|
class MarkupManager
|
|
|
|
{
|
|
|
|
use \October\Rain\Support\Traits\Singleton;
|
|
|
|
|
2014-05-22 07:57:36 +10:00
|
|
|
const EXTENSION_FILTER = 'filters';
|
|
|
|
const EXTENSION_FUNCTION = 'functions';
|
2014-05-14 23:24:20 +10:00
|
|
|
const EXTENSION_TOKEN_PARSER = 'tokens';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Cache of registration callbacks.
|
|
|
|
*/
|
2014-08-01 18:20:55 +10:00
|
|
|
protected $callbacks = [];
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Registered extension items
|
|
|
|
*/
|
|
|
|
protected $items;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var \System\Classes\PluginManager
|
|
|
|
*/
|
|
|
|
protected $pluginManager;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize this singleton.
|
|
|
|
*/
|
|
|
|
protected function init()
|
|
|
|
{
|
|
|
|
$this->pluginManager = PluginManager::instance();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function loadExtensions()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Load module items
|
|
|
|
*/
|
|
|
|
foreach ($this->callbacks as $callback) {
|
|
|
|
$callback($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load plugin items
|
|
|
|
*/
|
|
|
|
$plugins = $this->pluginManager->getPlugins();
|
|
|
|
|
|
|
|
foreach ($plugins as $id => $plugin) {
|
|
|
|
$items = $plugin->registerMarkupTags();
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_array($items)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
continue;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
foreach ($items as $type => $definitions) {
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_array($definitions)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
continue;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
$this->registerExtensions($type, $definitions);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a callback function that defines simple Twig extensions.
|
|
|
|
* The callback function should register menu items by calling the manager's
|
|
|
|
* registerFunctions(), registerFilters(), registerTokenParsers() function.
|
|
|
|
* The manager instance is passed to the callback function as an argument.
|
|
|
|
* Usage:
|
|
|
|
* <pre>
|
|
|
|
* MarkupManager::registerCallback(function($manager){
|
|
|
|
* $manager->registerFilters([...]);
|
|
|
|
* $manager->registerFunctions([...]);
|
|
|
|
* $manager->registerTokenParsers([...]);
|
|
|
|
* });
|
|
|
|
* </pre>
|
|
|
|
* @param callable $callback A callable function.
|
|
|
|
*/
|
|
|
|
public function registerCallback(callable $callback)
|
|
|
|
{
|
|
|
|
$this->callbacks[] = $callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers the CMS Twig extension items.
|
|
|
|
* The argument is an array of the extension definitions. The array keys represent the
|
|
|
|
* function/filter name, specific for the plugin/module. Each element in the
|
|
|
|
* array should be an associative array.
|
|
|
|
* @param string $type The extension type: filters, functions, tokens
|
|
|
|
* @param array $definitions An array of the extension definitions.
|
|
|
|
*/
|
|
|
|
public function registerExtensions($type, array $definitions)
|
|
|
|
{
|
2014-10-18 11:58:50 +02:00
|
|
|
if (is_null($this->items)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
$this->items = [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!array_key_exists($type, $this->items)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
$this->items[$type] = [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
foreach ($definitions as $name => $definition) {
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case self::EXTENSION_TOKEN_PARSER:
|
|
|
|
$this->items[$type][] = $definition;
|
2014-10-18 11:58:50 +02:00
|
|
|
break;
|
2014-05-14 23:24:20 +10:00
|
|
|
case self::EXTENSION_FILTER:
|
|
|
|
case self::EXTENSION_FUNCTION:
|
|
|
|
$this->items[$type][$name] = $definition;
|
2014-10-18 11:58:50 +02:00
|
|
|
break;
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a CMS Twig Filter
|
|
|
|
* @param array $definitions An array of the extension definitions.
|
|
|
|
*/
|
|
|
|
public function registerFilters(array $definitions)
|
|
|
|
{
|
|
|
|
$this->registerExtensions(self::EXTENSION_FILTER, $definitions);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a CMS Twig Function
|
|
|
|
* @param array $definitions An array of the extension definitions.
|
|
|
|
*/
|
|
|
|
public function registerFunctions(array $definitions)
|
|
|
|
{
|
|
|
|
$this->registerExtensions(self::EXTENSION_FUNCTION, $definitions);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a CMS Twig Token Parser
|
|
|
|
* @param array $definitions An array of the extension definitions.
|
|
|
|
*/
|
|
|
|
public function registerTokenParsers(array $definitions)
|
|
|
|
{
|
|
|
|
$this->registerExtensions(self::EXTENSION_TOKEN_PARSER, $definitions);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of the registered Twig extensions of a type.
|
|
|
|
* @param $type string The Twig extension type
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function listExtensions($type)
|
|
|
|
{
|
2014-10-18 11:58:50 +02:00
|
|
|
if ($this->items === null) {
|
2014-05-14 23:24:20 +10:00
|
|
|
$this->loadExtensions();
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!isset($this->items[$type]) || !is_array($this->items[$type])) {
|
2014-05-14 23:24:20 +10:00
|
|
|
return [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
return $this->items[$type];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of the registered Twig filters.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function listFilters()
|
|
|
|
{
|
|
|
|
return $this->listExtensions(self::EXTENSION_FILTER);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of the registered Twig functions.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function listFunctions()
|
|
|
|
{
|
|
|
|
return $this->listExtensions(self::EXTENSION_FUNCTION);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of the registered Twig token parsers.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function listTokenParsers()
|
|
|
|
{
|
|
|
|
return $this->listExtensions(self::EXTENSION_TOKEN_PARSER);
|
|
|
|
}
|
|
|
|
|
2014-07-16 18:28:15 +10:00
|
|
|
/**
|
|
|
|
* Makes a set of Twig functions for use in a twig extension.
|
|
|
|
* @param array $functions Current collection
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function makeTwigFunctions($functions = [])
|
|
|
|
{
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_array($functions)) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$functions = [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
foreach ($this->listFunctions() as $name => $callable) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a wildcard function
|
|
|
|
*/
|
|
|
|
if (strpos($name, '*') !== false && $this->isWildCallable($callable)) {
|
2014-10-18 11:58:50 +02:00
|
|
|
$callable = function ($name) use ($callable) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$arguments = array_slice(func_get_args(), 1);
|
|
|
|
$method = $this->isWildCallable($callable, Str::camel($name));
|
|
|
|
return call_user_func_array($method, $arguments);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_callable($callable)) {
|
2014-07-16 18:28:15 +10:00
|
|
|
throw new ApplicationException(sprintf('The markup function for %s is not callable.', $name));
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
$functions[] = new Twig_SimpleFunction($name, $callable, ['is_safe' => ['html']]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $functions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes a set of Twig filters for use in a twig extension.
|
|
|
|
* @param array $filters Current collection
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function makeTwigFilters($filters = [])
|
|
|
|
{
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_array($filters)) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$filters = [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
foreach ($this->listFilters() as $name => $callable) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a wildcard function
|
|
|
|
*/
|
|
|
|
if (strpos($name, '*') !== false && $this->isWildCallable($callable)) {
|
2014-10-18 11:58:50 +02:00
|
|
|
$callable = function ($name) use ($callable) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$arguments = array_slice(func_get_args(), 1);
|
|
|
|
$method = $this->isWildCallable($callable, Str::camel($name));
|
|
|
|
return call_user_func_array($method, $arguments);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_callable($callable)) {
|
2014-07-16 18:28:15 +10:00
|
|
|
throw new ApplicationException(sprintf('The markup filter for %s is not callable.', $name));
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
$filters[] = new Twig_SimpleFilter($name, $callable, ['is_safe' => ['html']]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $filters;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes a set of Twig token parsers for use in a twig extension.
|
|
|
|
* @param array $parsers Current collection
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function makeTwigTokenParsers($parsers = [])
|
|
|
|
{
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!is_array($parsers)) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$parsers = [];
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
$extraParsers = $this->listTokenParsers();
|
|
|
|
foreach ($extraParsers as $obj) {
|
2014-10-18 11:58:50 +02:00
|
|
|
if (!$obj instanceof Twig_TokenParser) {
|
2014-07-16 18:28:15 +10:00
|
|
|
continue;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
$parsers[] = $obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parsers;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if a callable type contains a wildcard, also acts as a
|
|
|
|
* utility to replace the wildcard with a string.
|
|
|
|
* @param callable $callable
|
|
|
|
* @param string $replaceWith
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
protected function isWildCallable($callable, $replaceWith = false)
|
|
|
|
{
|
|
|
|
$isWild = false;
|
|
|
|
|
2014-10-18 11:58:50 +02:00
|
|
|
if (is_string($callable) && strpos($callable, '*') !== false) {
|
2014-07-16 18:28:15 +10:00
|
|
|
$isWild = $replaceWith ? str_replace('*', $replaceWith, $callable) : true;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
|
|
|
|
if (is_array($callable)) {
|
|
|
|
if (is_string($callable[0]) && strpos($callable[0], '*') !== false) {
|
|
|
|
if ($replaceWith) {
|
|
|
|
$isWild = $callable;
|
|
|
|
$isWild[0] = str_replace('*', $replaceWith, $callable[0]);
|
2014-11-01 12:00:45 +11:00
|
|
|
}
|
|
|
|
else {
|
2014-07-16 18:28:15 +10:00
|
|
|
$isWild = true;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($callable[1]) && strpos($callable[1], '*') !== false) {
|
|
|
|
if ($replaceWith) {
|
|
|
|
$isWild = $isWild ?: $callable;
|
|
|
|
$isWild[1] = str_replace('*', $replaceWith, $callable[1]);
|
2014-11-01 12:00:45 +11:00
|
|
|
}
|
|
|
|
else {
|
2014-07-16 18:28:15 +10:00
|
|
|
$isWild = true;
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|
2014-07-16 18:28:15 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $isWild;
|
|
|
|
}
|
2014-10-18 11:58:50 +02:00
|
|
|
}
|