<NME> TwigTrait.php
<BEF> <?php

namespace Bolt\Extension;

use Bolt\Filesystem\Handler\DirectoryInterface;
use Bolt\Twig\SecurityPolicy;
use Pimple as Container;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Extension\SandboxExtension;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;
use Twig\TwigFunction;

/**
 * Twig function/filter addition and interface functions for an extension.
 *
 * @author Carson Full <carsonfull@gmail.com>
 * @author Gawain Lynch <gawain.lynch@gmail.com>
 */
trait TwigTrait
{
    /** @var TwigFilter[] */
    private $twigFilters = [];
    /** @var TwigFunction[] */
    private $twigFunctions = [];
    /** @var string[] */
    private $safeFilterNames = [];
    /** @var string[] */
    private $safeFunctionNames = [];
    /** @var bool */
    private $pathAdded = false;

     * <pre>
     *  return [
     *      'foo' => 'fooFunction',
     *      'bar' => ['barFunction', ['is_safe' => 'html']]
     *  ];
     * </pre>
     *
     *      'bar' => ['barFunction', ['is_safe' => ['html']]]
     *  ];
     * </pre>
     *
     * @return array
     */
    protected function registerTwigFunctions()
    {
        return [];
    }

     * <pre>
     *  return [
     *      'foo' => 'fooFilter',
     *      'bar' => ['barFilter', ['is_safe' => 'html']]
     *  ];
     * </pre>
     *
     *      'bar' => ['barFilter', ['is_safe' => ['html']]]]
     *  ];
     * </pre>
     *
     * @return array
     */
    protected function registerTwigFilters()
    {
        return [];
    }

    /**
     * Returns a list of relative paths to add to Twig's path array.
     *
     * Example:
     * <pre>
     *  return [
     *      'templates/foo',
     *      'templates/bar' => ['position' => 'prepend', 'namespace' => 'MyExtension']
     *  ];
     * </pre>
     *
     * @return array
     */
    protected function registerTwigPaths()
    {
        return [];
    }

    /**
     * Call this in register method.
     *
     * @internal
     */
    final protected function extendTwigService()
    {
        $app = $this->getContainer();

        $app['twig'] = $app->share(
            $app->extend(
                'twig',
                function ($twig) {
                    $this->loadTwig($twig);

                    return $twig;
                }
            )
        );
    }

    /**
     * Adds filters and functions to Twig Environment.
     *
     * @param Environment $twig
     */
    private function loadTwig(Environment $twig)
    {
        foreach ($this->registerTwigFunctions() as $name => $options) {
            if (is_string($options)) {
                $this->addTwigFunction($name, $options);
            } else {
                $this->addTwigFunction($name, $options[0], isset($options[1]) ? $options[1] : []);
            }
        }

        foreach ($this->registerTwigFilters() as $name => $options) {
            if (is_string($options)) {
                $this->addTwigFilter($name, $options);
            } else {
                $this->addTwigFilter($name, $options[0], isset($options[1]) ? $options[1] : []);
            }
        }

        foreach ($this->registerTwigPaths() as $key => $value) {
            if (is_array($value)) {
                $this->addTwigPath($key, $value);
            } else {
                $this->addTwigPath($value);
            }
        }

        foreach ($this->twigFunctions as $twigFunction) {
            $twig->addFunction($twigFunction);
        }

        foreach ($this->twigFilters as $twigFilter) {
            $twig->addFilter($twigFilter);
        }

        $this->updateSandboxPolicy($twig);
    }

    private function updateSandboxPolicy(Environment $twig)
    {
        if (!$twig->hasExtension(SandboxExtension::class)) {
            return;
        }

        /** @var SandboxExtension $sandbox */
        $sandbox = $twig->getExtension(SandboxExtension::class);
        $policy = $sandbox->getSecurityPolicy();
        if (!$policy instanceof SecurityPolicy) {
            return;
        }

        foreach ($this->safeFunctionNames as $name) {
            $policy->addAllowedFunction($name);
        }

        foreach ($this->safeFilterNames as $name) {
            $policy->addAllowedFilter($name);
        }
    }

    /**
     * Append a path to Twig's path array.
     *
     * @param string $path
     * @param array  $options
     *
     * @throws LoaderError
     */
    private function addTwigPath($path, array $options = [])
    {
        if ($path === 'templates' && $this->pathAdded) {
            return;
        }

        $app = $this->getContainer();

        $position = isset($options['position']) ? $options['position'] : 'append';
        $namespace = isset($options['namespace']) ? $options['namespace'] : FilesystemLoader::MAIN_NAMESPACE;

        $dir = $this->getBaseDirectory()->getDir($path);
        if (!$dir->exists()) {
            return;
        }
        try {
            if ($position === 'prepend') {
                $app['twig.loader.bolt_filesystem']->prependDir($dir, $namespace);
            } else {
                $app['twig.loader.bolt_filesystem']->addDir($dir, $namespace);
            }
        } catch (LoaderError $e) {
            $app['logger.system']->critical(sprintf('%s was unable to add the Twig path %s. %s', $this->getName(), $path, $e->getMessage()), ['event' => 'exception', 'exception' => $e]);
        }
    }

    /**
     * Add a Twig Function.
     *
     * @internal Will be made private in 4.0. Use registerTwigFunctions() instead.
     *
     * @param string          $name
     * @param string|callable $callback
     * @param array           $options
     */
    protected function addTwigFunction($name, $callback, $options = [])
    {
        // If we pass a callback as a simple string, we need to turn it into an array.
        if (is_string($callback) && method_exists($this, $callback)) {
            $callback = [$this, $callback];
        }

        $safe = method_exists($this, 'isSafe') && $this->isSafe();
        if (isset($options['safe'])) {
            $safe = (bool) $options['safe'];
            unset($options['safe']);
        }

        $this->twigFunctions[] = new TwigFunction($name, $callback, $options);

        if ($safe) {
            $this->safeFunctionNames[] = $name;
        }
    }

    /**
     * Add a Twig Filter.
     *
     * @internal Will be made private in 4.0. Use registerTwigFilters() instead.
     *
     * @param string          $name
     * @param string|callable $callback
     * @param array           $options
     */
    protected function addTwigFilter($name, $callback, $options = [])
    {
        // If we pass a callback as a simple string, we need to turn it into an array.
        if (is_string($callback) && method_exists($this, $callback)) {
            $callback = [$this, $callback];
        }

        $safe = method_exists($this, 'isSafe') && $this->isSafe();
        if (isset($options['safe'])) {
            $safe = (bool) $options['safe'];
            unset($options['safe']);
        }

        $this->twigFilters[] = new TwigFilter($name, $callback, $options);

        if ($safe) {
            $this->safeFilterNames[] = $name;
        }
    }

    /**
     * Render a Twig template.
     *
     * @param string $template
     * @param array  $context
     *
     * @return string
     */
    protected function renderTemplate($template, array $context = [])
    {
        $app = $this->getContainer();

        if ($this->pathAdded === false) {
            $this->addTwigPath('templates');
            $this->pathAdded = true;
        }

        return $app['twig']->render($template, $context);
    }

    /** @return Container */
    abstract protected function getContainer();

    /** @return string */
    abstract public function getName();

    /** @return DirectoryInterface */
    abstract protected function getBaseDirectory();
}

<MSG> Fix PHPDoc typo
<DFF> @@ -34,7 +34,7 @@ trait TwigTrait
      * <pre>
      *  return [
      *      'foo' => 'fooFunction',
-     *      'bar' => ['barFunction', ['is_safe' => 'html']]
+     *      'bar' => ['barFunction', ['is_safe' => ['html']]]
      *  ];
      * </pre>
      *
@@ -52,7 +52,7 @@ trait TwigTrait
      * <pre>
      *  return [
      *      'foo' => 'fooFilter',
-     *      'bar' => ['barFilter', ['is_safe' => 'html']]
+     *      'bar' => ['barFilter', ['is_safe' => ['html']]]]
      *  ];
      * </pre>
      *

{'commit': "diff --git a/src/Extension/TwigTrait.php b/src/Extension/TwigTrait.php\nindex bf9397503..d1ef999b0 100644\n--- a/src/Extension/TwigTrait.php\n+++ b/src/Extension/TwigTrait.php\n@@ -34,7 +34,7 @@ trait TwigTrait\n      * <pre>\n      *  return [\n      *      'foo' => 'fooFunction',\n-     *      'bar' => ['barFunction', ['is_safe' => 'html']]\n+     *      'bar' => ['barFunction', ['is_safe' => ['html']]]\n      *  ];\n      * </pre>\n      *\n@@ -52,7 +52,7 @@ trait TwigTrait\n      * <pre>\n      *  return [\n      *      'foo' => 'fooFilter',\n-     *      'bar' => ['barFilter', ['is_safe' => 'html']]\n+     *      'bar' => ['barFilter', ['is_safe' => ['html']]]]\n      *  ];\n      * </pre>\n      *\n", 'commit_subject': 'Fix PHPDoc typo', 'repo_name': 'bolt/bolt', 'committer': {'name': 'GitHub', 'email': '1505422b2465e9a84f6fdfaa161078890c593f06@github.com', 'time_sec': '1466692210', 'tz_offset': '120', 'date': {'seconds': '1466692210'}}, 'commit_tree': '583d2e1d4d35a7f6b4432eb4cdc4f7a09072ad75', 'commit_message': 'Fix PHPDoc typo', 'language_name': 'Shell', 'license': 'mit', 'hunks': "@@ -34,7 +34,7 @@ trait TwigTrait\n      * <pre>\n      *  return [\n      *      'foo' => 'fooFunction',\n-     *      'bar' => ['barFunction', ['is_safe' => 'html']]\n+     *      'bar' => ['barFunction', ['is_safe' => ['html']]]\n      *  ];\n      * </pre>\n      *\n@@ -52,7 +52,7 @@ trait TwigTrait\n      * <pre>\n      *  return [\n      *      'foo' => 'fooFilter',\n-     *      'bar' => ['barFilter', ['is_safe' => 'html']]\n+     *      'bar' => ['barFilter', ['is_safe' => ['html']]]]\n      *  ];\n      * </pre>\n      *\n", 'addition_count': 2, 'deletion_count': 2, 'src_file': 'a/src/Extension/TwigTrait.php', 'tgt_file': 'b/src/Extension/TwigTrait.php', 'file_extension': '.php', 'before_file': "<?php\n\nnamespace Bolt\\Extension;\n\nuse Bolt\\Filesystem\\Handler\\DirectoryInterface;\nuse Bolt\\Twig\\SecurityPolicy;\nuse Pimple as Container;\nuse Twig\\Environment;\nuse Twig\\Error\\LoaderError;\nuse Twig\\Extension\\SandboxExtension;\nuse Twig\\Loader\\FilesystemLoader;\nuse Twig\\TwigFilter;\nuse Twig\\TwigFunction;\n\n/**\n * Twig function/filter addition and interface functions for an extension.\n *\n * @author Carson Full <carsonfull@gmail.com>\n * @author Gawain Lynch <gawain.lynch@gmail.com>\n */\ntrait TwigTrait\n{\n    /** @var TwigFilter[] */\n    private $twigFilters = [];\n    /** @var TwigFunction[] */\n    private $twigFunctions = [];\n    /** @var string[] */\n    private $safeFilterNames = [];\n    /** @var string[] */\n    private $safeFunctionNames = [];\n    /** @var bool */\n    private $pathAdded = false;\n\n     * <pre>\n     *  return [\n     *      'foo' => 'fooFunction',\n     *      'bar' => ['barFunction', ['is_safe' => 'html']]\n     *  ];\n     * </pre>\n     *\n     *      'bar' => ['barFunction', ['is_safe' => ['html']]]\n     *  ];\n     * </pre>\n     *\n     * @return array\n     */\n    protected function registerTwigFunctions()\n    {\n        return [];\n    }\n\n     * <pre>\n     *  return [\n     *      'foo' => 'fooFilter',\n     *      'bar' => ['barFilter', ['is_safe' => 'html']]\n     *  ];\n     * </pre>\n     *\n     *      'bar' => ['barFilter', ['is_safe' => ['html']]]]\n     *  ];\n     * </pre>\n     *\n     * @return array\n     */\n    protected function registerTwigFilters()\n    {\n        return [];\n    }\n\n    /**\n     * Returns a list of relative paths to add to Twig's path array.\n     *\n     * Example:\n     * <pre>\n     *  return [\n     *      'templates/foo',\n     *      'templates/bar' => ['position' => 'prepend', 'namespace' => 'MyExtension']\n     *  ];\n     * </pre>\n     *\n     * @return array\n     */\n    protected function registerTwigPaths()\n    {\n        return [];\n    }\n\n    /**\n     * Call this in register method.\n     *\n     * @internal\n     */\n    final protected function extendTwigService()\n    {\n        $app = $this->getContainer();\n\n        $app['twig'] = $app->share(\n            $app->extend(\n                'twig',\n                function ($twig) {\n                    $this->loadTwig($twig);\n\n                    return $twig;\n                }\n            )\n        );\n    }\n\n    /**\n     * Adds filters and functions to Twig Environment.\n     *\n     * @param Environment $twig\n     */\n    private function loadTwig(Environment $twig)\n    {\n        foreach ($this->registerTwigFunctions() as $name => $options) {\n            if (is_string($options)) {\n                $this->addTwigFunction($name, $options);\n            } else {\n                $this->addTwigFunction($name, $options[0], isset($options[1]) ? $options[1] : []);\n            }\n        }\n\n        foreach ($this->registerTwigFilters() as $name => $options) {\n            if (is_string($options)) {\n                $this->addTwigFilter($name, $options);\n            } else {\n                $this->addTwigFilter($name, $options[0], isset($options[1]) ? $options[1] : []);\n            }\n        }\n\n        foreach ($this->registerTwigPaths() as $key => $value) {\n            if (is_array($value)) {\n                $this->addTwigPath($key, $value);\n            } else {\n                $this->addTwigPath($value);\n            }\n        }\n\n        foreach ($this->twigFunctions as $twigFunction) {\n            $twig->addFunction($twigFunction);\n        }\n\n        foreach ($this->twigFilters as $twigFilter) {\n            $twig->addFilter($twigFilter);\n        }\n\n        $this->updateSandboxPolicy($twig);\n    }\n\n    private function updateSandboxPolicy(Environment $twig)\n    {\n        if (!$twig->hasExtension(SandboxExtension::class)) {\n            return;\n        }\n\n        /** @var SandboxExtension $sandbox */\n        $sandbox = $twig->getExtension(SandboxExtension::class);\n        $policy = $sandbox->getSecurityPolicy();\n        if (!$policy instanceof SecurityPolicy) {\n            return;\n        }\n\n        foreach ($this->safeFunctionNames as $name) {\n            $policy->addAllowedFunction($name);\n        }\n\n        foreach ($this->safeFilterNames as $name) {\n            $policy->addAllowedFilter($name);\n        }\n    }\n\n    /**\n     * Append a path to Twig's path array.\n     *\n     * @param string $path\n     * @param array  $options\n     *\n     * @throws LoaderError\n     */\n    private function addTwigPath($path, array $options = [])\n    {\n        if ($path === 'templates' && $this->pathAdded) {\n            return;\n        }\n\n        $app = $this->getContainer();\n\n        $position = isset($options['position']) ? $options['position'] : 'append';\n        $namespace = isset($options['namespace']) ? $options['namespace'] : FilesystemLoader::MAIN_NAMESPACE;\n\n        $dir = $this->getBaseDirectory()->getDir($path);\n        if (!$dir->exists()) {\n            return;\n        }\n        try {\n            if ($position === 'prepend') {\n                $app['twig.loader.bolt_filesystem']->prependDir($dir, $namespace);\n            } else {\n                $app['twig.loader.bolt_filesystem']->addDir($dir, $namespace);\n            }\n        } catch (LoaderError $e) {\n            $app['logger.system']->critical(sprintf('%s was unable to add the Twig path %s. %s', $this->getName(), $path, $e->getMessage()), ['event' => 'exception', 'exception' => $e]);\n        }\n    }\n\n    /**\n     * Add a Twig Function.\n     *\n     * @internal Will be made private in 4.0. Use registerTwigFunctions() instead.\n     *\n     * @param string          $name\n     * @param string|callable $callback\n     * @param array           $options\n     */\n    protected function addTwigFunction($name, $callback, $options = [])\n    {\n        // If we pass a callback as a simple string, we need to turn it into an array.\n        if (is_string($callback) && method_exists($this, $callback)) {\n            $callback = [$this, $callback];\n        }\n\n        $safe = method_exists($this, 'isSafe') && $this->isSafe();\n        if (isset($options['safe'])) {\n            $safe = (bool) $options['safe'];\n            unset($options['safe']);\n        }\n\n        $this->twigFunctions[] = new TwigFunction($name, $callback, $options);\n\n        if ($safe) {\n            $this->safeFunctionNames[] = $name;\n        }\n    }\n\n    /**\n     * Add a Twig Filter.\n     *\n     * @internal Will be made private in 4.0. Use registerTwigFilters() instead.\n     *\n     * @param string          $name\n     * @param string|callable $callback\n     * @param array           $options\n     */\n    protected function addTwigFilter($name, $callback, $options = [])\n    {\n        // If we pass a callback as a simple string, we need to turn it into an array.\n        if (is_string($callback) && method_exists($this, $callback)) {\n            $callback = [$this, $callback];\n        }\n\n        $safe = method_exists($this, 'isSafe') && $this->isSafe();\n        if (isset($options['safe'])) {\n            $safe = (bool) $options['safe'];\n            unset($options['safe']);\n        }\n\n        $this->twigFilters[] = new TwigFilter($name, $callback, $options);\n\n        if ($safe) {\n            $this->safeFilterNames[] = $name;\n        }\n    }\n\n    /**\n     * Render a Twig template.\n     *\n     * @param string $template\n     * @param array  $context\n     *\n     * @return string\n     */\n    protected function renderTemplate($template, array $context = [])\n    {\n        $app = $this->getContainer();\n\n        if ($this->pathAdded === false) {\n            $this->addTwigPath('templates');\n            $this->pathAdded = true;\n        }\n\n        return $app['twig']->render($template, $context);\n    }\n\n    /** @return Container */\n    abstract protected function getContainer();\n\n    /** @return string */\n    abstract public function getName();\n\n    /** @return DirectoryInterface */\n    abstract protected function getBaseDirectory();\n}\n", 'message': 'Fix PHPDoc typo', 'author_name': 'GitHub', 'lang': 'php'}