<NME> Queue.php
<BEF> <?php

namespace Bolt\Asset\Widget;

use Bolt\Asset\AssetSortTrait;
use Bolt\Asset\Injector;
use Bolt\Asset\QueueInterface;
use Bolt\Asset\Snippet\Snippet;
use Bolt\Asset\Target;
use Bolt\Common\Deprecated;
use Bolt\Common\Thrower;
use Bolt\Controller\Zone;
use Bolt\Render;
use Doctrine\Common\Cache\CacheProvider;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
use Twig\Markup;

/**
 * Widget queue processor.
 *
 * @author Gawain Lynch <gawain.lynch@gmail.com>
 * @author Bob den Otter <bob@twokings.nl>
 */
class Queue implements QueueInterface
{
    use AssetSortTrait;

    /** @var WidgetAssetInterface[] Queue with snippets of HTML to insert. */
    protected $queue = [];
    /** @var \Bolt\Asset\Injector */
    protected $injector;
    /** @var \Doctrine\Common\Cache\CacheProvider */
    protected $cache;
    /** @var Environment */
    protected $render;

    /** @var bool */
    private $deferAdded;

    /**
     * Constructor.
     *
     * NOTE: Constructor type hint for Environment omitted for BC, add in v4
     *
     * @param Injector      $injector
     * @param CacheProvider $cache
     * @param Environment   $render
     */
    public function __construct(Injector $injector, CacheProvider $cache, $render)
    {
        $this->injector = $injector;
        $this->cache = $cache;
        $this->render = $render;

        if ($render instanceof Render) {
            Deprecated::warn('Passing Bolt\Render to the widget queue constructor', 3.3, 'Pass in Twig\Environment instead.');
        }
    }

    /**
     * Add a widget to the queue.
     *
     * @param WidgetAssetInterface $widget
     */
    public function add(WidgetAssetInterface $widget)
    {
        $widget->setKey();
        $this->queue[$widget->getKey()] = $widget;
    }

    /**
     * Get a widget from the queue.
     *
     * @param string $key
     *
     * @return WidgetAssetInterface
     */
    public function get($key)
    {
        return $this->queue[$key];
    }

    /**
     * Get a rendered (and potentially cached) widget from the queue.
     *
     * @param string $key
     *
     * @return Markup|string
     */
    public function getRendered($key)
    {
        return $this->getHtml($this->queue[$key]);
    }

    /**
     * {@inheritdoc}
    {
        /** @var WidgetAssetInterface $widget */
        foreach ($this->queue as $widget) {
            if ($widget->getZone() === Zone::FRONTEND && $widget->isDeferred()) {
                $this->addDeferredJavaScript($widget, $response);
            }
        }
     * {@inheritdoc}
     */
    public function process(Request $request, Response $response)
    {
        /** @var WidgetAssetInterface $widget */
        foreach ($this->queue as $widget) {
            if ($widget->isDeferred()) {
                $this->addDeferredJavaScript($widget, $response);
            }
        }
    }

    /**
     * Get the queued widgets.
     *
     * @return WidgetAssetInterface[]
     */
    public function getQueue()
    {
        return $this->queue;
    }

    public function hasItemsInQueue($location, $zone = Zone::FRONTEND)
    {
        Deprecated::method(3.4, 'Queue::has');

        return $this->has($location, $zone);
    }

    /**
     * Get the number of queued widgets.
     *
     * @param string $location Location (e.g. 'dashboard_aside_top')
     * @param string $zone     Either Zone::FRONTEND or Zone::BACKEND
     *
     * @return bool
     */
    public function has($location, $zone = Zone::FRONTEND)
    {
        return (bool) $this->count($location, $zone);
    }

    public function countItemsInQueue($location, $zone = Zone::FRONTEND)
    {
        Deprecated::method(3.4, 'Queue::count');

        return $this->count($location, $zone);
    }

    /**
     * Get the number of queued widgets.
     *
     * @param string $location Location (e.g. 'dashboard_aside_top')
     * @param string $zone     Either Zone::FRONTEND or Zone::BACKEND
     *
     * @return int
     */
    public function count($location, $zone = Zone::FRONTEND)
        /** @var WidgetAssetInterface $widget */
        foreach ($this->sort($this->queue) as $widget) {
            if ($widget->getZone() === $zone && $widget->getLocation() === $location) {
                $widgets[] = [ 'object' => $widget, 'html' => $this->getHtml($widget) ];
            }
        }

    /**
     * Render a location's widget.
     *
     * @param string $location        Location (e.g. 'dashboard_aside_top')
     * @param string $zone            Either Zone::FRONTEND or Zone::BACKEND
     * @param string $wrapperTemplate Template file for wrapper
     *
     * @return string|null
     */
    public function render($location, $zone = Zone::FRONTEND, $wrapperTemplate = 'widgetwrapper.twig')
    {
        $widgets = [];

        /** @var WidgetAssetInterface $widget */
        foreach ($this->sort($this->queue) as $widget) {
            if ($widget->getZone() !== $zone || $widget->getLocation() !== $location) {
                continue;
            }
            $html = $widget->isDeferred() ? null : $this->getHtml($widget);
            $widgets[] = ['object' => $widget, 'html' => $html];
        }

        if (empty($widgets)) {
            return null;
        }

        return $this->render->render($wrapperTemplate, ['location' => $location, 'widgets' => $widgets]);
    }

    /**
     * Get the HTML content from the widget.
     *
     * @param WidgetAssetInterface $widget
     *
     * @throws \Exception
     *
     * @return string
     */
    protected function getHtml(WidgetAssetInterface $widget)
    {
        $key = 'widget_' . $widget->getKey();
        if ($html = $this->cache->fetch($key)) {
            return $html;
        }

        $html = Thrower::call([$widget, '__toString']);

        if ($widget->getCacheDuration() !== null) {
            $this->cache->save($key, $html, $widget->getCacheDuration());
        }

        return $html;
    }

    /**
     * Insert a snippet of Javascript to fetch the actual widget's contents.
     *
     * @param WidgetAssetInterface $widget
     * @param Response             $response
     */
    protected function addDeferredJavaScript(WidgetAssetInterface $widget, Response $response)
    {
        if ($this->deferAdded) {
            return;
        }

        $javaScript = $this->render->render(
            'widgetjavascript.twig',
            [
                'widget' => $widget,
            ]
        );
        $snippet = (new Snippet())
            ->setLocation(Target::AFTER_BODY_JS)
            ->setCallback((string) $javaScript)
        ;

        $this->deferAdded = true;

        $this->injector->inject($snippet, Target::AFTER_BODY_JS, $response);
    }
}

<MSG> Fix two issues with deferred widgets.

<DFF> @@ -99,7 +99,7 @@ class Queue implements QueueInterface
     {
         /** @var WidgetAssetInterface $widget */
         foreach ($this->queue as $widget) {
-            if ($widget->getZone() === Zone::FRONTEND && $widget->isDeferred()) {
+            if ($widget->isDeferred()) {
                 $this->addDeferredJavaScript($widget, $response);
             }
         }
@@ -164,7 +164,12 @@ class Queue implements QueueInterface
         /** @var WidgetAssetInterface $widget */
         foreach ($this->sort($this->queue) as $widget) {
             if ($widget->getZone() === $zone && $widget->getLocation() === $location) {
-                $widgets[] = [ 'object' => $widget, 'html' => $this->getHtml($widget) ];
+                if ($widget->isDeferred()) {
+                    $html = '';
+                } else {
+                    $html = $this->getHtml($widget);
+                }
+                $widgets[] = [ 'object' => $widget, 'html' => $html ];
             }
         }
 

{'commit': "diff --git a/src/Asset/Widget/Queue.php b/src/Asset/Widget/Queue.php\nindex 21033e7ee..e27e8dd93 100644\n--- a/src/Asset/Widget/Queue.php\n+++ b/src/Asset/Widget/Queue.php\n@@ -99,7 +99,7 @@ class Queue implements QueueInterface\n     {\n         /** @var WidgetAssetInterface $widget */\n         foreach ($this->queue as $widget) {\n-            if ($widget->getZone() === Zone::FRONTEND && $widget->isDeferred()) {\n+            if ($widget->isDeferred()) {\n                 $this->addDeferredJavaScript($widget, $response);\n             }\n         }\n@@ -164,7 +164,12 @@ class Queue implements QueueInterface\n         /** @var WidgetAssetInterface $widget */\n         foreach ($this->sort($this->queue) as $widget) {\n             if ($widget->getZone() === $zone && $widget->getLocation() === $location) {\n-                $widgets[] = [ 'object' => $widget, 'html' => $this->getHtml($widget) ];\n+                if ($widget->isDeferred()) {\n+                    $html = '';\n+                } else {\n+                    $html = $this->getHtml($widget);\n+                }\n+                $widgets[] = [ 'object' => $widget, 'html' => $html ];\n             }\n         }\n \n", 'commit_subject': 'Fix two issues with deferred widgets.', 'repo_name': 'bolt/bolt', 'committer': {'name': 'Bob den Otter', 'email': '48181acd22b3edaebc8a447868a7df7ce629920a@twokings.nl', 'time_sec': '1470405639', 'tz_offset': '120', 'date': {'seconds': '1470405639'}}, 'commit_tree': 'ace73686cf46157e1ac79f3d32af47c5b8e0aabb', 'commit_message': 'Fix two issues with deferred widgets.\n', 'language_name': 'JavaScript', 'license': 'mit', 'hunks': "@@ -99,7 +99,7 @@ class Queue implements QueueInterface\n     {\n         /** @var WidgetAssetInterface $widget */\n         foreach ($this->queue as $widget) {\n-            if ($widget->getZone() === Zone::FRONTEND && $widget->isDeferred()) {\n+            if ($widget->isDeferred()) {\n                 $this->addDeferredJavaScript($widget, $response);\n             }\n         }\n@@ -164,7 +164,12 @@ class Queue implements QueueInterface\n         /** @var WidgetAssetInterface $widget */\n         foreach ($this->sort($this->queue) as $widget) {\n             if ($widget->getZone() === $zone && $widget->getLocation() === $location) {\n-                $widgets[] = [ 'object' => $widget, 'html' => $this->getHtml($widget) ];\n+                if ($widget->isDeferred()) {\n+                    $html = '';\n+                } else {\n+                    $html = $this->getHtml($widget);\n+                }\n+                $widgets[] = [ 'object' => $widget, 'html' => $html ];\n             }\n         }\n \n", 'addition_count': 7, 'deletion_count': 2, 'src_file': 'a/src/Asset/Widget/Queue.php', 'tgt_file': 'b/src/Asset/Widget/Queue.php', 'file_extension': '.php', 'before_file': "<?php\n\nnamespace Bolt\\Asset\\Widget;\n\nuse Bolt\\Asset\\AssetSortTrait;\nuse Bolt\\Asset\\Injector;\nuse Bolt\\Asset\\QueueInterface;\nuse Bolt\\Asset\\Snippet\\Snippet;\nuse Bolt\\Asset\\Target;\nuse Bolt\\Common\\Deprecated;\nuse Bolt\\Common\\Thrower;\nuse Bolt\\Controller\\Zone;\nuse Bolt\\Render;\nuse Doctrine\\Common\\Cache\\CacheProvider;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Twig\\Environment;\nuse Twig\\Markup;\n\n/**\n * Widget queue processor.\n *\n * @author Gawain Lynch <gawain.lynch@gmail.com>\n * @author Bob den Otter <bob@twokings.nl>\n */\nclass Queue implements QueueInterface\n{\n    use AssetSortTrait;\n\n    /** @var WidgetAssetInterface[] Queue with snippets of HTML to insert. */\n    protected $queue = [];\n    /** @var \\Bolt\\Asset\\Injector */\n    protected $injector;\n    /** @var \\Doctrine\\Common\\Cache\\CacheProvider */\n    protected $cache;\n    /** @var Environment */\n    protected $render;\n\n    /** @var bool */\n    private $deferAdded;\n\n    /**\n     * Constructor.\n     *\n     * NOTE: Constructor type hint for Environment omitted for BC, add in v4\n     *\n     * @param Injector      $injector\n     * @param CacheProvider $cache\n     * @param Environment   $render\n     */\n    public function __construct(Injector $injector, CacheProvider $cache, $render)\n    {\n        $this->injector = $injector;\n        $this->cache = $cache;\n        $this->render = $render;\n\n        if ($render instanceof Render) {\n            Deprecated::warn('Passing Bolt\\Render to the widget queue constructor', 3.3, 'Pass in Twig\\Environment instead.');\n        }\n    }\n\n    /**\n     * Add a widget to the queue.\n     *\n     * @param WidgetAssetInterface $widget\n     */\n    public function add(WidgetAssetInterface $widget)\n    {\n        $widget->setKey();\n        $this->queue[$widget->getKey()] = $widget;\n    }\n\n    /**\n     * Get a widget from the queue.\n     *\n     * @param string $key\n     *\n     * @return WidgetAssetInterface\n     */\n    public function get($key)\n    {\n        return $this->queue[$key];\n    }\n\n    /**\n     * Get a rendered (and potentially cached) widget from the queue.\n     *\n     * @param string $key\n     *\n     * @return Markup|string\n     */\n    public function getRendered($key)\n    {\n        return $this->getHtml($this->queue[$key]);\n    }\n\n    /**\n     * {@inheritdoc}\n    {\n        /** @var WidgetAssetInterface $widget */\n        foreach ($this->queue as $widget) {\n            if ($widget->getZone() === Zone::FRONTEND && $widget->isDeferred()) {\n                $this->addDeferredJavaScript($widget, $response);\n            }\n        }\n     * {@inheritdoc}\n     */\n    public function process(Request $request, Response $response)\n    {\n        /** @var WidgetAssetInterface $widget */\n        foreach ($this->queue as $widget) {\n            if ($widget->isDeferred()) {\n                $this->addDeferredJavaScript($widget, $response);\n            }\n        }\n    }\n\n    /**\n     * Get the queued widgets.\n     *\n     * @return WidgetAssetInterface[]\n     */\n    public function getQueue()\n    {\n        return $this->queue;\n    }\n\n    public function hasItemsInQueue($location, $zone = Zone::FRONTEND)\n    {\n        Deprecated::method(3.4, 'Queue::has');\n\n        return $this->has($location, $zone);\n    }\n\n    /**\n     * Get the number of queued widgets.\n     *\n     * @param string $location Location (e.g. 'dashboard_aside_top')\n     * @param string $zone     Either Zone::FRONTEND or Zone::BACKEND\n     *\n     * @return bool\n     */\n    public function has($location, $zone = Zone::FRONTEND)\n    {\n        return (bool) $this->count($location, $zone);\n    }\n\n    public function countItemsInQueue($location, $zone = Zone::FRONTEND)\n    {\n        Deprecated::method(3.4, 'Queue::count');\n\n        return $this->count($location, $zone);\n    }\n\n    /**\n     * Get the number of queued widgets.\n     *\n     * @param string $location Location (e.g. 'dashboard_aside_top')\n     * @param string $zone     Either Zone::FRONTEND or Zone::BACKEND\n     *\n     * @return int\n     */\n    public function count($location, $zone = Zone::FRONTEND)\n        /** @var WidgetAssetInterface $widget */\n        foreach ($this->sort($this->queue) as $widget) {\n            if ($widget->getZone() === $zone && $widget->getLocation() === $location) {\n                $widgets[] = [ 'object' => $widget, 'html' => $this->getHtml($widget) ];\n            }\n        }\n\n    /**\n     * Render a location's widget.\n     *\n     * @param string $location        Location (e.g. 'dashboard_aside_top')\n     * @param string $zone            Either Zone::FRONTEND or Zone::BACKEND\n     * @param string $wrapperTemplate Template file for wrapper\n     *\n     * @return string|null\n     */\n    public function render($location, $zone = Zone::FRONTEND, $wrapperTemplate = 'widgetwrapper.twig')\n    {\n        $widgets = [];\n\n        /** @var WidgetAssetInterface $widget */\n        foreach ($this->sort($this->queue) as $widget) {\n            if ($widget->getZone() !== $zone || $widget->getLocation() !== $location) {\n                continue;\n            }\n            $html = $widget->isDeferred() ? null : $this->getHtml($widget);\n            $widgets[] = ['object' => $widget, 'html' => $html];\n        }\n\n        if (empty($widgets)) {\n            return null;\n        }\n\n        return $this->render->render($wrapperTemplate, ['location' => $location, 'widgets' => $widgets]);\n    }\n\n    /**\n     * Get the HTML content from the widget.\n     *\n     * @param WidgetAssetInterface $widget\n     *\n     * @throws \\Exception\n     *\n     * @return string\n     */\n    protected function getHtml(WidgetAssetInterface $widget)\n    {\n        $key = 'widget_' . $widget->getKey();\n        if ($html = $this->cache->fetch($key)) {\n            return $html;\n        }\n\n        $html = Thrower::call([$widget, '__toString']);\n\n        if ($widget->getCacheDuration() !== null) {\n            $this->cache->save($key, $html, $widget->getCacheDuration());\n        }\n\n        return $html;\n    }\n\n    /**\n     * Insert a snippet of Javascript to fetch the actual widget's contents.\n     *\n     * @param WidgetAssetInterface $widget\n     * @param Response             $response\n     */\n    protected function addDeferredJavaScript(WidgetAssetInterface $widget, Response $response)\n    {\n        if ($this->deferAdded) {\n            return;\n        }\n\n        $javaScript = $this->render->render(\n            'widgetjavascript.twig',\n            [\n                'widget' => $widget,\n            ]\n        );\n        $snippet = (new Snippet())\n            ->setLocation(Target::AFTER_BODY_JS)\n            ->setCallback((string) $javaScript)\n        ;\n\n        $this->deferAdded = true;\n\n        $this->injector->inject($snippet, Target::AFTER_BODY_JS, $response);\n    }\n}\n", 'message': 'Fix two issues with deferred widgets.\n', 'author_name': 'Bob den Otter', 'lang': 'php'}