From 36fb3e9e79233dc8c2863ea4434bb48cbd82a856 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 07:42:14 +0000 Subject: [PATCH 1/7] Define an AssetInterface based on that of Assetic. --- core/lib/Drupal/Core/Asset/AssetInterface.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/lib/Drupal/Core/Asset/AssetInterface.php diff --git a/core/lib/Drupal/Core/Asset/AssetInterface.php b/core/lib/Drupal/Core/Asset/AssetInterface.php new file mode 100644 index 0000000..1bd3b26 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetInterface.php @@ -0,0 +1,21 @@ + Date: Wed, 27 Feb 2013 07:42:54 +0000 Subject: [PATCH 2/7] Build a specialised AssetGraph for producing TSLs. --- core/lib/Drupal/Core/Asset/AssetGraph.php | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 core/lib/Drupal/Core/Asset/AssetGraph.php diff --git a/core/lib/Drupal/Core/Asset/AssetGraph.php b/core/lib/Drupal/Core/Asset/AssetGraph.php new file mode 100644 index 0000000..69d3764 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetGraph.php @@ -0,0 +1,98 @@ +vertices = array(); + $this->edges = array(); + + $this->incoming = array(); + + $this->sequencing = array(); + } + + public function add(AssetInterface $asset) { + $assetId = $asset->getId(); + if (!isset($this->vertices[$assetId])) { + $this->vertices[$assetId] = $asset; + $this->incoming[$assetId] = 0; + + if (isset($this->sequencing[$assetId])) { + foreach ($this->sequencing[$assetId] as $successorId) { + $this->edges[$assetId][$successorId] = $successorId; + $this->incoming[$successorId]++; + } + } + + if ($asset->hasPredecessors()) { + foreach ($asset->getPredecessors() as $predecessor) { + $predecessorId = $predecessor->getId(); + + $this->sequencing[$predecessorId][$assetId] = $assetId; + } + } + + if ($asset->hasDependencies()) { + foreach ($asset->getDependencies() as $dependency) { + $dependencyId = $dependency->getId(); + + $this->add($dependency); + $this->edges[$dependencyId][$assetId] = $assetId; + $this->incoming[$assetId]++; + } + } + } + + return $this; + } + + public function getSources() { + $sources = array(); + foreach ($this->vertices as $id => $asset) { + if ($this->incoming[$id] == 0) { + $sources[] = $id; + } + } + + return $sources; + } + + public function getTSL() { + $ids = array(); + $edges = $this->edges; + $incoming = $this->incoming; + + $queue = $this->getSources(); + while ($id = array_shift($queue)) { + $ids[] = $id; + + if (isset($edges[$id])) { + foreach ($edges[$id] as $to) { + unset($edges[$id][$to]); + $incoming[$to]--; + + if ($incoming[$to] == 0) { + $queue[] = $to; + } + } + } + } + + return $ids; + } +} -- 1.8.1.4 From d1e7faad66c43c9a8b76ae50c752371c0612b501 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 07:43:12 +0000 Subject: [PATCH 3/7] Add BaseAsset, FileAsset and AssetBag classes. --- core/lib/Drupal/Core/Asset/AssetBag.php | 23 ++++++++++++ core/lib/Drupal/Core/Asset/BaseAsset.php | 60 ++++++++++++++++++++++++++++++++ core/lib/Drupal/Core/Asset/FileAsset.php | 44 +++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 core/lib/Drupal/Core/Asset/AssetBag.php create mode 100644 core/lib/Drupal/Core/Asset/BaseAsset.php create mode 100644 core/lib/Drupal/Core/Asset/FileAsset.php diff --git a/core/lib/Drupal/Core/Asset/AssetBag.php b/core/lib/Drupal/Core/Asset/AssetBag.php new file mode 100644 index 0000000..efd1c2d --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetBag.php @@ -0,0 +1,23 @@ +assetGraph = new AssetGraph(); + } + + public function add(AssetInterface $asset) { + $this->assetGraph->add($asset); + + return $this; + } + + public function dump() { + return $this->assetGraph->getTSL(); + } +} diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php new file mode 100644 index 0000000..9ba152e --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseAsset.php @@ -0,0 +1,60 @@ +id = $id; + + $this->predecessors = array(); + $this->hasPredecessors = FALSE; + + $this->dependencies = array(); + $this->hasDependencies = FALSE; + } + + public function getId() { + return $this->id; + } + + public function after($asset) { + $this->predecessors[] = $asset; + $this->hasPredecessors = TRUE; + } + + public function hasPredecessors() { + return $this->hasPredecessors; + } + + public function getPredecessors() { + return $this->predecessors; + } + + public function dependsOn($dependency) { + $this->dependencies[] = $dependency; + $this->hasDependencies = TRUE; + } + + public function hasDependencies() { + return $this->hasDependencies; + } + + public function getDependencies() { + return $this->dependencies; + } +} diff --git a/core/lib/Drupal/Core/Asset/FileAsset.php b/core/lib/Drupal/Core/Asset/FileAsset.php new file mode 100644 index 0000000..a4583fc --- /dev/null +++ b/core/lib/Drupal/Core/Asset/FileAsset.php @@ -0,0 +1,44 @@ +source = $source; + } + + public function load(FilterInterface $additionFilter = NULL) { + + } + + public function getLastModified() { + + } +} -- 1.8.1.4 From a4d10c790c38f7ded9c313dd9dcb4ab6825d7e85 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 07:43:25 +0000 Subject: [PATCH 4/7] Add some simple tests for Assets. --- .../lib/Drupal/system/Tests/Asset/AssetsTest.php | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 core/modules/system/lib/Drupal/system/Tests/Asset/AssetsTest.php diff --git a/core/modules/system/lib/Drupal/system/Tests/Asset/AssetsTest.php b/core/modules/system/lib/Drupal/system/Tests/Asset/AssetsTest.php new file mode 100644 index 0000000..591f0de --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Asset/AssetsTest.php @@ -0,0 +1,93 @@ + 'Asset tests', + 'description' => 'Asset tests.', + 'group' => 'Asset', + ); + } + + public function assertArraysEqual($type, $expected, $actual) { + $this->assertEqual($expected, $actual, + t( + '

Expected @type:

@expected

' . + '

Actual @type:

@actual

', + + array( + '@type' => $type, + '@expected' => print_r($expected, TRUE), + '@actual' => print_r($actual, TRUE), + ) + )); + } + + public function testSimpleDependencies() { + $jquery = new FileAsset('core/misc/jquery.js'); + $ajax = new FileAsset('core/misc/ajax.js'); + $dropdowns = new FileAsset('core/misc/dropdowns.js'); + + $ajax->dependsOn($jquery); + $dropdowns->dependsOn($jquery); + + $bag = new AssetBag(); + $bag->add($dropdowns)->add($jquery)->add($ajax); + + $actual = $bag->dump(); + $expected = array( + 'core/misc/jquery.js', + 'core/misc/dropdowns.js', + 'core/misc/ajax.js', + ); + + $this->assertArraysEqual('TSL', $expected, $actual); + } + + public function testSequencing() { + $before = new FileAsset('core/misc/jquery.before.js'); + $jquery = new FileAsset('core/misc/jquery.js'); + $ajax = new FileAsset('core/misc/ajax.js'); + $dropdowns = new FileAsset('core/misc/dropdowns.js'); + + $jquery->after($before); + $ajax->dependsOn($jquery); + $dropdowns->dependsOn($jquery); + + $bag = new AssetBag; + $bag->add($dropdowns)->add($jquery)->add($ajax); + + $actual = $bag->dump(); + $expected = array( + 'core/misc/jquery.js', + 'core/misc/dropdowns.js', + 'core/misc/ajax.js', + ); + + $this->assertArraysEqual('TSL', $expected, $actual); + + $bag->add($before); + + $actual = $bag->dump(); + $expected = array( + 'core/misc/jquery.before.js', + 'core/misc/jquery.js', + 'core/misc/dropdowns.js', + 'core/misc/ajax.js', + ); + + $this->assertArraysEqual('TSL', $expected, $actual); + } +} -- 1.8.1.4 From 66d8202dededf2c657e89e957f22168f489fc482 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 07:55:22 +0000 Subject: [PATCH 5/7] Autoload all Assetic classes properly. --- core/vendor/composer/autoload_namespaces.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index 54b2551..3bff62e 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -26,5 +26,5 @@ 'Guzzle\\Common' => $vendorDir . '/guzzle/common/', 'EasyRdf_' => $vendorDir . '/easyrdf/easyrdf/lib/', 'Doctrine\\Common' => $vendorDir . '/doctrine/common/lib/', - 'Assetic' => $vendorDir . '/kriswallsmith/assetic/src/', + 'Assetic\\' => $vendorDir . '/kriswallsmith/assetic/src/', ); -- 1.8.1.4 From bde1fa2cd51d938f60c84ee80b7006349d4202ca Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 07:57:16 +0000 Subject: [PATCH 6/7] Apply the PartialResponse patch from #1871596. --- core/lib/Drupal/Core/CoreBundle.php | 3 + .../Core/EventSubscriber/HtmlViewSubscriber.php | 98 ++++++++++++++++++++++ .../Drupal/Core/EventSubscriber/ViewSubscriber.php | 2 +- core/lib/Drupal/Core/PartialResponse.php | 92 ++++++++++++++++++++ .../system/Tests/Common/PartialResponseTest.php | 56 +++++++++++++ .../system/tests/modules/html_test/html_test.info | 6 ++ .../tests/modules/html_test/html_test.module | 4 + .../tests/modules/html_test/html_test.routing.yml | 6 ++ .../lib/Drupal/html_test/TestController.php | 18 ++++ 9 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php create mode 100644 core/lib/Drupal/Core/PartialResponse.php create mode 100644 core/modules/system/lib/Drupal/system/Tests/Common/PartialResponseTest.php create mode 100644 core/modules/system/tests/modules/html_test/html_test.info create mode 100644 core/modules/system/tests/modules/html_test/html_test.module create mode 100644 core/modules/system/tests/modules/html_test/html_test.routing.yml create mode 100644 core/modules/system/tests/modules/html_test/lib/Drupal/html_test/TestController.php diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index cc4e4aa..2f7b3a6 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -232,6 +232,9 @@ public function build(ContainerBuilder $container) { ->addArgument(new Reference('router')) ->addTag('event_subscriber'); $container->register('content_negotiation', 'Drupal\Core\ContentNegotiation'); + $container->register('html_view_subscriber', 'Drupal\Core\EventSubscriber\HtmlViewSubscriber') + ->addArgument(new Reference('module_handler')) + ->addTag('event_subscriber'); $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber') ->addArgument(new Reference('content_negotiation')) ->addTag('event_subscriber'); diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php new file mode 100644 index 0000000..c777a33 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php @@ -0,0 +1,98 @@ + tag + * and its contents. + * + */ +class HtmlViewSubscriber implements EventSubscriberInterface { + + /** + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + public function __construct(ModuleHandlerInterface $moduleHandler) { + $this->moduleHandler = $moduleHandler; + } + + /** + * Processes a specialized Drupal Response into fully-formed HTML. + * + * This subscriber only acts if presented with a specialized Response object + * that contains a partial HTML page, plus the metadata necessary to construct + * the remainder of the page. + * + * @todo we're ignoring content negotiation and assuming HTML. could be JSON, at least... + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event + * The Event to process. + * + * @return \Symfony\Component\HttpFoundation\Response; + */ + public function onPartialHtmlResponse(GetResponseForControllerResultEvent $event) { + // Only act if we have a specialized Drupal response to work with. + if (!($response = $event->getControllerResult()) instanceof PartialResponse) { + return; + } + + // @todo this is really lazy, but the easiest way to get $page_{top,bottom}. this can be removed once the responsibility is shifted into displays/controllers + $page = element_info('page'); + + if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) { + foreach ($this->moduleHandler->getImplementations('page_build') as $module) { + $function = $module . '_page_build'; + $function($page); + } + $this->moduleHandler->alter('page', $page); + } + + // @todo these are horrible passthrough hacks; remove them incrementally as we implement the respective pieces elsewhere. + $vars = array( + 'page' => array( + '#children' => $response->getContent(), + 'page_top' => empty($page['page_top']) ? array(): $page['page_top'], + 'page_bottom' => empty($page['page_bottom']) ? array(): $page['page_bottom'], + ), + 'response' => $response, + ); + + $event->setResponse(new Response(theme('html', $vars))); + // Stop propagation, as the ViewSubscriber will handle this poorly. + $event->stopPropagation(); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::VIEW][] = array('onPartialHtmlResponse', 40); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php index ac48bc8..ccdb595 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -152,7 +152,7 @@ public function onHtml(GetResponseForControllerResultEvent $event) { * An array of event listener definitions. */ static function getSubscribedEvents() { - $events[KernelEvents::VIEW][] = array('onView'); + $events[KernelEvents::VIEW][] = array('onView', 30); return $events; } diff --git a/core/lib/Drupal/Core/PartialResponse.php b/core/lib/Drupal/Core/PartialResponse.php new file mode 100644 index 0000000..8976e64 --- /dev/null +++ b/core/lib/Drupal/Core/PartialResponse.php @@ -0,0 +1,92 @@ +content = $content; + } + + /** + * Sets the response content. + * + * This should be the bulk of the page content, and will ultimately be placed + * within the tag in final HTML output. + * + * Valid types are strings, numbers, and objects that implement a __toString() + * method. + * + * @param mixed $content + * + * @return PartialResponse + */ + public function setContent($content) { + $this->content = $content; + return $this; + } + + /** + * Gets the main content of this Response. + * + * @return string + */ + public function getContent() { + return $this->content; + } + + /** + * Sets the title of this PartialResponse. + * + * Handling of this title varies depending on what is consuming this + * PartialResponse object. If it's a block, it may only be used as the block's + * title; if it's at the page level, it will be used in a number of places, + * including the title in HTML head. + */ + public function setTitle($title, $output = CHECK_PLAIN) { + $this->title = ($output == PASS_THROUGH) ? $title : check_plain($title); + } + + /** + * Indicates whether or not this PartialResponse has a title. + * + * @return bool + */ + public function hasTitle() { + return !empty($this->title); + } + + /** + * Gets the title for this PartialResponse, if any. + * + * @return string + */ + public function getTitle() { + return $this->title; + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/PartialResponseTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/PartialResponseTest.php new file mode 100644 index 0000000..caa73c2 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Common/PartialResponseTest.php @@ -0,0 +1,56 @@ + 'Partial HTML Response', + 'description' => 'Tests that PartialResponse data is properly rendered into the html template.', + 'group' => 'Common', // @todo put me somewhere better + ); + } + + /** + * Tests that a PartialResponse object is rendered correctly by issuing a web + * request against it. + */ + public function testPartialResponseByWeb() { + $this->drupalGet('html_test'); + + // Check that our basic body text is there + $this->assertRaw('hello world'); + // Check that we didn't get a page within a page, inception-style. + $this->assertNoPattern('#.*#s', 'There was no double-page effect from a misrendered subrequest.'); + } + + /** + * Tests that a PartialResponse object is rendered correctly by mocking the + * environment. + */ + public function testPartialResponseByUnit() { + $kernel = $this->container->get('http_kernel'); + $request = Request::create('/foo'); + $response = new PartialResponse('hello world'); + $subscriber = new HtmlViewSubscriber($this->container->get('module_handler')); + $event = new GetResponseForControllerResultEvent($kernel, $request, HttpKernel::MASTER_REQUEST, $response); + $subscriber->onPartialHtmlResponse($event); + $this->assertTrue(preg_match('/hello world/', $event->getResponse()->getContent()), 'Expected raw output found within HTML response.'); + } +} diff --git a/core/modules/system/tests/modules/html_test/html_test.info b/core/modules/system/tests/modules/html_test/html_test.info new file mode 100644 index 0000000..6408ea4 --- /dev/null +++ b/core/modules/system/tests/modules/html_test/html_test.info @@ -0,0 +1,6 @@ +name = "HTML test" +description = "Support module for testing finalized HTML rendering." +package = Testing +version = VERSION +core = 8.x +hidden = TRUE diff --git a/core/modules/system/tests/modules/html_test/html_test.module b/core/modules/system/tests/modules/html_test/html_test.module new file mode 100644 index 0000000..ea3dfb5 --- /dev/null +++ b/core/modules/system/tests/modules/html_test/html_test.module @@ -0,0 +1,4 @@ +hello world

'); + } +} -- 1.8.1.4 From df3aaf20dbfd9c2a016acbb76de2b6064b29b401 Mon Sep 17 00:00:00 2001 From: Will Jones Date: Wed, 27 Feb 2013 08:01:27 +0000 Subject: [PATCH 7/7] Add asset-tracking properties to PartialResponse. --- core/lib/Drupal/Core/PartialResponse.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/lib/Drupal/Core/PartialResponse.php b/core/lib/Drupal/Core/PartialResponse.php index 8976e64..532c167 100644 --- a/core/lib/Drupal/Core/PartialResponse.php +++ b/core/lib/Drupal/Core/PartialResponse.php @@ -7,6 +7,8 @@ namespace Drupal\Core; +use Drupal\Core\Asset\AssetBag; + /** * Response object that contains variables for injection into the html template. * @@ -22,6 +24,8 @@ class PartialResponse { */ protected $content; + protected $assets; + /** * The title of this PartialResponse. * @@ -31,6 +35,7 @@ class PartialResponse { public function __construct($content = '') { $this->content = $content; + $this->assets = new AssetBag(); } /** @@ -89,4 +94,13 @@ public function hasTitle() { public function getTitle() { return $this->title; } + + public function addAsset(AssetInterface $asset) { + $this->assets->add($asset); + } + + public function getAssets() { + return $this->assets->dump(); + } + } -- 1.8.1.4