From fd14a43919aba4fd69d801f334cc4fabd2a882dc Mon Sep 17 00:00:00 2001 From: Andrey Kutejko Date: Sat, 16 Mar 2019 10:46:45 +0100 Subject: [PATCH] Template provider --- ipf/admin/controllers/base.php | 3 +- ipf/controller/function.php | 9 +- ipf/core_services.php | 54 ++++++++ ipf/project.php | 5 + ipf/project_template.php | 96 -------------- ipf/template.php | 220 +++++++++++++++++++++++++++++++++ ipf/twig.php | 46 ------- 7 files changed, 288 insertions(+), 145 deletions(-) create mode 100644 ipf/core_services.php delete mode 100644 ipf/project_template.php create mode 100644 ipf/template.php delete mode 100644 ipf/twig.php diff --git a/ipf/admin/controllers/base.php b/ipf/admin/controllers/base.php index c6a2970..3eede9e 100644 --- a/ipf/admin/controllers/base.php +++ b/ipf/admin/controllers/base.php @@ -13,7 +13,8 @@ abstract class IPF_Admin_Base_Controller extends IPF_Controller protected function render($template, $params) { - return new IPF_HTTP_Response(IPF_Twig::render($template, $params, $this->request)); + $body = $this->container['template']->render($template, $params, $this->request); + return new IPF_HTTP_Response($body); } /** diff --git a/ipf/controller/function.php b/ipf/controller/function.php index 0344c28..598b37d 100644 --- a/ipf/controller/function.php +++ b/ipf/controller/function.php @@ -4,7 +4,12 @@ class IPF_Controller_Function extends IPF_Controller { function process($action, $request, $matches) { - return IPF::callFunction($action, array($request, $matches)); + $get_callable = $this->container['get_callable']; + $callable = call_user_func($get_callable, $action); + + if ($callable !== null) + return call_user_func_array($callable, array($request, $matches)); + else + throw new \Exception('Impossible to load view function: ' . $action . '.'); } } - diff --git a/ipf/core_services.php b/ipf/core_services.php new file mode 100644 index 0000000..1b3ae9d --- /dev/null +++ b/ipf/core_services.php @@ -0,0 +1,54 @@ +get('project_root'); + return function ($function) use ($project_root) { + return self::detectCallable($project_root, $function); + }; + }; + } + + private static function detectCallable($project_root, $function) + { + if (is_array($function) && count($function) === 2) { + // object/class method + return $function; + } + + if (is_string($function) && preg_match('/^([\\\\\w]+)::(\w+)$/i', $function, $m)) { + // static method + return array($m[1], $m[2]); + } + + if (is_string($function) && preg_match('/^\w+_\w+$/i', $function)) { + // plain function + + if (!function_exists($function)) { + $parts = explode('_', $function); + array_pop($parts); + $file = DIRECTORY_SEPARATOR . strtolower(implode(DIRECTORY_SEPARATOR, $parts)) . '.php'; + + $filename = $project_root . DIRECTORY_SEPARATOR . 'project' . $file; + if (is_file($filename)) { + include_once $filename; + if (function_exists($function)) { + return $function; + } + } + } else { + return $function; + } + } + + return null; + } +} diff --git a/ipf/project.php b/ipf/project.php index 7df013c..bb9a787 100644 --- a/ipf/project.php +++ b/ipf/project.php @@ -1,5 +1,7 @@ container->register(new CoreServicesProvider()); + $this->container->register(new TemplateProvider()); + $this->container['databaseConnection'] = function ($c) { return IPF_Database::connect(); }; diff --git a/ipf/project_template.php b/ipf/project_template.php deleted file mode 100644 index 3cbb43d..0000000 --- a/ipf/project_template.php +++ /dev/null @@ -1,96 +0,0 @@ -appList() as $app) { - $applicationTemplates = $app->getPath() . 'templates'; - if (is_dir($applicationTemplates)) - $dirs[] = $applicationTemplates; - } - - return $dirs; - } - - public static function context($params=array(), $request=null) - { - $params = array_merge(array( - 'indexpage_url' => IPF::get('indexpage_url'), - 'STATIC_URL' => IPF::get('static_url'), - 'MEDIA_URL' => IPF::get('media_url'), - 'UPLOAD_URL' => IPF::getUploadUrl(), - ), $params); - - if ($request) { - $params = array_merge(array( - 'request' => $request, - 'user' => $request->user, - 'CURRENT_URL' => $request->query, - ), $params); - - foreach (IPF::get('template_context_processors', array()) as $proc) { - $c = IPF::callFunction($proc, array($request)); - $params = array_merge($c, $params); - } - - foreach (IPF_Project::getInstance()->appList() as $app) { - $params = array_merge($app->templateContext($request), $params); - } - } - return $params; - } - - public static function urlTag($args) - { - $count = count($args); - if ($count === 0) - throw new IPF_Exception('No view specified'); - - $view = array_shift($args); - - if ($count === 2 && is_array($args[0])) { - return IPF_HTTP_URL::urlForView($view, $args[0]); - } elseif ($count === 3 && is_array($args[0]) && is_array($args[1])) { - return IPF_HTTP_URL::urlForView($view, $args[0], $args[1]); - } else { - return IPF_HTTP_URL::urlForView($view, $args); - } - } - - public static function paramsTag($args) - { - $params = array(); - - $count = count($args); - for ($i = 0; $i < $count; ) { - if (is_array($args[$i])) { - foreach ($args[$i] as $key => $value) { - if ($value === null) - unset($params[$key]); - else - $params[$key] = $value; - } - - ++$i; - } else { - $key = $args[$i]; - $value = $args[$i+1]; - if ($value === null) - unset($params[$key]); - else - $params[$key] = $value; - - $i += 2; - } - } - - return IPF_HTTP_URL::generateParams($params); - } -} diff --git a/ipf/template.php b/ipf/template.php new file mode 100644 index 0000000..619b630 --- /dev/null +++ b/ipf/template.php @@ -0,0 +1,220 @@ +settings = $settings; + $this->processors = $processors; + $this->apps = $apps; + $this->loader = $loader; + $this->router = $router; + } + + public function render($template, $context = [], $request = null) + { + $twig = $this->createEnvironment(); + $this->addCommonGlobals($twig); + $this->addRequestGlobals($twig, $request); + $this->addFunctions($twig); + + $template = $twig->load($template); + + return $template->render($context); + } + + private function createEnvironment() + { + $options = array( + 'cache' => $this->settings->get('tmp'), + ); + if ($this->settings->get('debug')) { + $options['debug'] = true; + $options['auto_reload'] = true; + } + + return new Environment($this->loader, $options); + } + + private function addCommonGlobals(Environment $twig) + { + $twig->addGlobal('indexpage_url', $this->settings->get('indexpage_url')); + $twig->addGlobal('STATIC_URL', $this->settings->get('static_url')); + $twig->addGlobal('MEDIA_URL', $this->settings->get('media_url')); + $twig->addGlobal('UPLOAD_URL', $this->settings->get('upload_url')); + } + + private function addRequestGlobals(Environment $twig, $request) + { + if (!$request) + return; + + $twig->addGlobal('request', $request); + $twig->addGlobal('user', $request->user); + $twig->addGlobal('CURRENT_URL', $request->query); + + foreach ($this->processors as $proc) { + $context = call_user_func_array($proc, array($request)); + foreach ($context as $key => $value) { + $twig->addGlobal($key, $value); + } + } + + foreach ($this->apps as $app) { + $context = $app->templateContext($request); + foreach ($context as $key => $value) { + $twig->addGlobal($key, $value); + } + } + } + + private function addFunctions(Environment $twig) + { + $twig->addFunction(new TwigFunction('url', new UrlTag($this->router))); + $twig->addFunction(new TwigFunction('params', function (...$args) { + return paramsTag(...$args); + })); + $twig->addFunction(new TwigFunction('trans', function ($str) { + return __($str); + })); + } +} + +final class TemplateProvider implements ServiceProviderInterface +{ + public function register(Container $container) + { + $container['template_dirs'] = function ($c) { + $project_root = $c['settings']->get('project_root'); + $apps = $c['apps']; + + $dirs = array(); + + $projectTemplates = $project_root . '/project/templates'; + if (is_dir($projectTemplates)) + $dirs[] = $projectTemplates; + + foreach ($apps as $app) { + $applicationTemplates = $app->getPath() . 'templates'; + if (is_dir($applicationTemplates)) + $dirs[] = $applicationTemplates; + } + + return $dirs; + }; + + $container['template_loader'] = function ($c) { + $templateDirs = $c['template_dirs']; + + $loader = new FilesystemLoader(array()); + foreach ($templateDirs as $dir) { + $loader->addPath($dir); + } + return $loader; + }; + + $container['template_context_processors'] = function ($c) { + $get_callable = $c['get_callable']; + $processors = []; + foreach ($c['settings']->get('template_context_processors', array()) as $proc) { + $callable = call_user_func($get_callable, $proc); + if ($callable !== null) + $processors[] = $callable; + else + throw new Exception('Impossible to load template context processor: ' . $proc . '.'); + } + return $processors; + }; + + $container['template'] = function ($c) { + return new TemplateEnvironmentTwig($c['settings'], $c['template_context_processors'], $c['apps'], $c['template_loader'], $c['router']); + }; + } +} + +class UrlTag +{ + /** @var IPF_Router */ + private $router; + + public function __construct(IPF_Router $router) + { + $this->router = $router; + } + + public function __invoke($view, ...$args) + { + $count = count($args); + + if ($count === 1 && is_array($args[0])) { + return $this->router->reverse($view, $args[0]); + } elseif ($count === 2 && is_array($args[0]) && is_array($args[1])) { + $query = count($args[1]) > 0 ? '?' . paramsTag(...$args[1]) : ''; + return $this->router->reverse($view, $args[0]) . $query; + } else { + return $this->router->reverse($view, $args); + } + } +} + +function paramsTag(...$args) +{ + $params = array(); + $count = count($args); + for ($i = 0; $i < $count;) { + if (is_array($args[$i])) { + foreach ($args[$i] as $key => $value) { + if ($value === null) + unset($params[$key]); + else + $params[$key] = $value; + } + + ++$i; + } else { + $key = $args[$i]; + $value = $args[$i + 1]; + if ($value === null) + unset($params[$key]); + else + $params[$key] = $value; + + $i += 2; + } + } + + $params_list = array(); + foreach ($params as $key => $value) + $params_list[] = urlencode($key) . '=' . urlencode($value); + + return implode('&', $params_list); +} diff --git a/ipf/twig.php b/ipf/twig.php deleted file mode 100644 index e68b6a2..0000000 --- a/ipf/twig.php +++ /dev/null @@ -1,46 +0,0 @@ -loadTemplate($tplfile); - return $template->render($context); - } - - private static function createEnvironment() - { - $options = array( - 'cache' => IPF::get('tmp'), - ); - if (IPF::get('debug')) { - $options['debug'] = true; - $options['auto_reload'] = true; - } - - $twig = new Twig_Environment(self::createLoader(), $options); - - $twig->addFunction(new Twig_SimpleFunction('url', function() { - return IPF_Project_Template::urlTag(func_get_args()); - })); - $twig->addFunction(new Twig_SimpleFunction('params', function() { - return IPF_Project_Template::paramsTag(func_get_args()); - })); - $twig->addFunction(new Twig_SimpleFunction('trans', function($str) { - return __($str); - })); - - return $twig; - } - - private static function createLoader() - { - $loader = new Twig_Loader_Filesystem(array()); - foreach (IPF_Project_Template::templateDirs() as $dir) { - $loader->addPath($dir); - } - return $loader; - } -} -- 2.49.0