Issue #1787846 by Gábor Hojtsy, EclipseGc, effulgentsia, fubhy: Added Themes should declare their layouts.
parent
699680d265
commit
f4debbf54a
|
@ -0,0 +1,5 @@
|
|||
name = Layout
|
||||
description = Makes it possible to swap different page layouts.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 8.x
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Manages page layouts for content presentation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the layout plugin manager instance.
|
||||
*
|
||||
* @return Drupal\layout\Plugin\Type\LayoutManager
|
||||
* The layout plugin manager instance.
|
||||
*/
|
||||
function layout_manager() {
|
||||
return drupal_container()->get('plugin.manager.layout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*
|
||||
* Expose all layouts as theme items, so themes can override layout markup.
|
||||
*/
|
||||
function layout_theme($existing, $type, $theme, $path) {
|
||||
$items = array();
|
||||
foreach (layout_manager()->getDefinitions() as $name => $layout) {
|
||||
$items[$layout['theme']] = array(
|
||||
'variables' => array('content' => NULL),
|
||||
'path' => $layout['path'],
|
||||
'template' => $layout['template'],
|
||||
);
|
||||
}
|
||||
return $items;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\LayoutBundle.
|
||||
*/
|
||||
|
||||
namespace Drupal\Layout;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Layout dependency injection container.
|
||||
*/
|
||||
class LayoutBundle extends Bundle {
|
||||
|
||||
/**
|
||||
* Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
|
||||
*/
|
||||
public function build(ContainerBuilder $container) {
|
||||
// Register the LayoutManager class with the dependency injection container.
|
||||
$container->register('plugin.manager.layout', 'Drupal\layout\Plugin\Type\LayoutManager');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\Plugin\Derivative\Layout.
|
||||
*/
|
||||
|
||||
namespace Drupal\layout\Plugin\Derivative;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Drupal\Component\Plugin\Derivative\DerivativeInterface;
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
|
||||
/**
|
||||
* Layout plugin derivative definition.
|
||||
*/
|
||||
class Layout implements DerivativeInterface {
|
||||
|
||||
/**
|
||||
* List of derivatives.
|
||||
*
|
||||
* Associative array keyed by 'provider__layoutname' where provider is the
|
||||
* module or theme name and layoutname is the .yml filename, such as
|
||||
* 'bartik__page' or 'layout__one-col'. The values of the array are
|
||||
* associative arrays themselves with metadata about the layout such as
|
||||
* 'template', 'css', 'admin css' and so on.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
|
||||
/**
|
||||
* Layout derivative type.
|
||||
*
|
||||
* Defines the subdirectory under ./layout where layout metadata is loooked
|
||||
* for. Overriding implementations should change this to look for other
|
||||
* types in a different subdirectory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'static';
|
||||
|
||||
/**
|
||||
* Implements DerivativeInterface::getDerivativeDefinition().
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
|
||||
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DerivativeInterface::getDerivativeDefinitions().
|
||||
*/
|
||||
public function getDerivativeDefinitions(array $base_plugin_definition) {
|
||||
$available_layout_providers = array();
|
||||
|
||||
// Add all modules as possible layout providers.
|
||||
foreach (module_list() as $module) {
|
||||
$available_layout_providers[$module] = array(
|
||||
'type' => 'module',
|
||||
'provider' => $module,
|
||||
'dir' => drupal_get_path('module', $module),
|
||||
);
|
||||
}
|
||||
|
||||
// Add all themes as possible layout providers.
|
||||
foreach (list_themes() as $theme_id => $theme) {
|
||||
$available_layout_providers[$theme_id] = array(
|
||||
'type' => 'theme',
|
||||
'provider' => $theme->name,
|
||||
'dir' => drupal_get_path('theme', $theme->name),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($available_layout_providers as $provider) {
|
||||
// Looks for layouts in the 'layout' directory under the module/theme.
|
||||
// There could be subdirectories under there with one layout defined
|
||||
// in each.
|
||||
$dir = $provider['dir'] . DIRECTORY_SEPARATOR . 'layouts' . DIRECTORY_SEPARATOR . $this->type;
|
||||
if (file_exists($dir)) {
|
||||
$this->iterateDirectories($dir, $provider);
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds layout definitions by looking for layout metadata.
|
||||
*/
|
||||
protected function iterateDirectories($dir, $provider) {
|
||||
$directories = new DirectoryIterator($dir);
|
||||
foreach ($directories as $fileinfo) {
|
||||
if ($fileinfo->isDir() && !$fileinfo->isDot()) {
|
||||
// Keep discovering in subdirectories to arbitrary depth.
|
||||
$this->iterateDirectories($fileinfo->getPathname(), $provider);
|
||||
}
|
||||
elseif ($fileinfo->isFile() && pathinfo($fileinfo->getFilename(), PATHINFO_EXTENSION) == 'yml') {
|
||||
// Declarative layout definitions are defined with a .yml file in a
|
||||
// layout subdirectory. This provides all information about the layout
|
||||
// such as layout markup template and CSS and JavaScript files to use.
|
||||
$directory = new FileStorage($fileinfo->getPath());
|
||||
$key = $provider['provider'] . '__' . $fileinfo->getBasename('.yml');
|
||||
$this->derivatives[$key] = $directory->read($fileinfo->getBasename('.yml'));
|
||||
$this->derivatives[$key]['theme'] = $key;
|
||||
$this->derivatives[$key]['path'] = $fileinfo->getPath();
|
||||
// If the layout author didn't specify a template name, assume the same
|
||||
// name as the yml file.
|
||||
if (!isset($this->derivatives[$key]['template'])) {
|
||||
$this->derivatives[$key]['template'] = $fileinfo->getBasename('.yml');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\Plugin\LayoutInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\layout\Plugin;
|
||||
|
||||
/**
|
||||
* Defines the shared interface for all layout plugins.
|
||||
*/
|
||||
interface LayoutInterface {
|
||||
|
||||
/**
|
||||
* Returns a list of regions.
|
||||
*
|
||||
* @return array
|
||||
* An array of region machine names.
|
||||
*/
|
||||
public function getRegions();
|
||||
|
||||
/**
|
||||
* Renders layout and returns the rendered markup.
|
||||
*
|
||||
* @return string
|
||||
* Rendered HTML output from the layout.
|
||||
*/
|
||||
public function renderLayout();
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\Plugin\Type\LayoutManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\layout\Plugin\Type;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerBase;
|
||||
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\Component\Plugin\Factory\ReflectionFactory;
|
||||
|
||||
/**
|
||||
* Layout plugin manager.
|
||||
*/
|
||||
class LayoutManager extends PluginManagerBase {
|
||||
|
||||
protected $defaults = array(
|
||||
'class' => 'Drupal\layout\Plugin\layout\layout\StaticLayout',
|
||||
);
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
|
||||
*/
|
||||
public function __construct() {
|
||||
// Create layout plugin derivatives from declaratively defined layouts.
|
||||
$this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('layout', 'layout'));
|
||||
$this->factory = new ReflectionFactory($this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\Plugin\layout\layout\StaticLayout.
|
||||
*/
|
||||
|
||||
namespace Drupal\layout\Plugin\layout\layout;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\layout\Plugin\LayoutInterface;
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* @Plugin(
|
||||
* id = "static_layout",
|
||||
* derivative = "Drupal\layout\Plugin\Derivative\Layout"
|
||||
* )
|
||||
*/
|
||||
class StaticLayout extends PluginBase implements LayoutInterface {
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Component\Plugin\PluginBase::__construct().
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, DiscoveryInterface $discovery) {
|
||||
// Get definition by discovering the declarative information.
|
||||
$definition = $discovery->getDefinition($plugin_id);
|
||||
foreach ($definition['regions'] as $region => $title) {
|
||||
if (!isset($configuration['regions'][$region])) {
|
||||
$configuration['regions'][$region] = array();
|
||||
}
|
||||
}
|
||||
parent::__construct($configuration, $plugin_id, $discovery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\layout\Plugin\LayoutInterface::getRegions().
|
||||
*/
|
||||
public function getRegions() {
|
||||
$definition = $this->getDefinition();
|
||||
return $definition['regions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of CSS files associated with this layout.
|
||||
*/
|
||||
public function getStylesheetFiles() {
|
||||
$definition = $this->getDefinition();
|
||||
return isset($definition['stylesheets']) ? $definition['stylesheets'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of administrative CSS files associated with this layout.
|
||||
*/
|
||||
public function getAdminStylesheetFiles() {
|
||||
$definition = $this->getDefinition();
|
||||
// Fall back on regular CSS for the admin page if admin CSS not provided.
|
||||
return isset($definition['admin stylesheets']) ? $definition['admin stylesheets'] : $this->getStylesheetFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of JS files associated with this layout.
|
||||
*/
|
||||
public function getScriptFiles() {
|
||||
$definition = $this->getDefinition();
|
||||
return isset($definition['scripts']) ? $definition['scripts'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of administrative JS files associated with this layout.
|
||||
*/
|
||||
public function getAdminScriptFiles() {
|
||||
$definition = $this->getDefinition();
|
||||
return isset($definition['admin scripts']) ? $definition['admin scripts'] : $this->getScriptFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\layout\Plugin\LayoutInterface::renderLayout().
|
||||
*/
|
||||
public function renderLayout($admin = FALSE) {
|
||||
$definition = $this->getDefinition();
|
||||
|
||||
// Assemble a render array with the regions and attached CSS/JS.
|
||||
$build = array(
|
||||
'#theme' => $definition['theme'],
|
||||
'#content' => array(),
|
||||
);
|
||||
|
||||
// Render all regions needed for this layout.
|
||||
foreach ($this->getRegions() as $region => $title) {
|
||||
// @todo This is just stub code to fill in regions with stuff for now.
|
||||
// When blocks are related to layouts and not themes, we can make this
|
||||
// really be filled in with blocks.
|
||||
$build['#content'][$region] = '<h3>' . $title . '</h3>';
|
||||
}
|
||||
|
||||
// Fill in attached CSS and JS files based on metadata.
|
||||
if (!$admin) {
|
||||
$build['#attached'] = array(
|
||||
'css' => $this->getStylesheetFiles(),
|
||||
'js' => $this->getScriptFiles(),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$build['#attached'] = array(
|
||||
'css' => $this->getAdminStylesheetFiles(),
|
||||
'js' => $this->getAdminScriptFiles(),
|
||||
);
|
||||
}
|
||||
|
||||
// Include the path of the definition in all CSS and JS files.
|
||||
foreach (array('css', 'js') as $type) {
|
||||
foreach ($build['#attached'][$type] as &$filename) {
|
||||
$filename = $definition['path'] . '/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
return drupal_render($build);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\layout\Tests\LayoutDerivativesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\layout\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the layout system derivatives.
|
||||
*/
|
||||
class LayoutDerivativesTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('layout', 'layout_test');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Layout derivatives',
|
||||
'description' => 'Tests layout derivatives discovery.',
|
||||
'group' => 'Layout',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for module/theme layout derivatives.
|
||||
*/
|
||||
function testDerivatives() {
|
||||
$manager = drupal_container()->get('plugin.manager.layout');
|
||||
|
||||
$definitions = $manager->getDefinitions();
|
||||
$this->assertTrue(is_array($definitions), 'Definitions found.');
|
||||
$this->assertTrue(count($definitions) == 2, 'Two definitions available.');
|
||||
$this->assertTrue(isset($definitions['static_layout:layout_test__one-col']), 'One column layout found.');
|
||||
$this->assertTrue(isset($definitions['static_layout:layout_test_theme__two-col']), 'Two column layout found.');
|
||||
|
||||
// Get a one column layout instance. This is defined under the layout_test
|
||||
// module.
|
||||
$layout = $manager->createInstance('static_layout:layout_test__one-col', array());
|
||||
// Verify the expected regions are properly available.
|
||||
$regions = $layout->getRegions();
|
||||
$this->assertTrue(is_array($regions), 'Regions array present.');
|
||||
$this->assertTrue(count($regions) == 1, 'One region defined.');
|
||||
$this->assertTrue(isset($regions['middle']), 'Middle region found.');
|
||||
|
||||
// Render the layout and look at whether expected region names and classes
|
||||
// were in the output.
|
||||
$render = $layout->renderLayout();
|
||||
$this->drupalSetContent($render);
|
||||
$this->assertText('Middle column');
|
||||
$this->assertRaw('class="layout-display layout-one-col');
|
||||
|
||||
// Get the two column page layout defined by the layout test theme.
|
||||
$layout = $manager->createInstance('static_layout:layout_test_theme__two-col', array());
|
||||
// Verify the expected regions are properly available.
|
||||
$regions = $layout->getRegions();
|
||||
$this->assertTrue(is_array($regions), 'Regions array present.');
|
||||
$this->assertTrue(count($regions) == 2, 'Two regions defined.');
|
||||
$this->assertTrue(isset($regions['left']), 'Left region found.');
|
||||
$this->assertTrue(isset($regions['right']), 'Right region found.');
|
||||
|
||||
// Render the layout and look at whether expected region names and classes
|
||||
// were in the output.
|
||||
$render = $layout->renderLayout();
|
||||
$this->drupalSetContent($render);
|
||||
$this->assertText('Left side');
|
||||
$this->assertText('Right side');
|
||||
$this->assertRaw('<div class="layout-region layout-col-right">');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test layout functionality as applies to pages.
|
||||
*/
|
||||
function testPageLayout() {
|
||||
// The layout-test page uses the layout_test_theme page layout.
|
||||
$this->drupalGet('layout-test');
|
||||
$this->assertText('Left side');
|
||||
$this->assertText('Right side');
|
||||
$this->assertRaw('<div class="layout-region layout-col-right">');
|
||||
|
||||
// Ensure the CSS was added.
|
||||
$this->assertRaw('@import url("' . url('', array('absolute' => TRUE)) . drupal_get_path('theme', 'layout_test_theme') . '/layouts/static/two-col/two-col.css');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
name = Layout test
|
||||
description = Helps with testing layouts.
|
||||
package = Testing
|
||||
version = VERSION
|
||||
core = 8.x
|
||||
hidden = TRUE
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Layout testing module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function layout_test_menu() {
|
||||
$items['layout-test'] = array(
|
||||
'title' => 'Layout test',
|
||||
'page callback' => 'layout_test_page',
|
||||
'access callback' => TRUE,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page callback for layout testing.
|
||||
*/
|
||||
function layout_test_page() {
|
||||
// Hack to enable and apply the theme to this page and manually invoke its
|
||||
// layout plugin and render it.
|
||||
global $theme;
|
||||
$theme = 'layout_test_theme';
|
||||
theme_enable(array($theme));
|
||||
$layout = layout_manager()->createInstance('static_layout:layout_test_theme__two-col');
|
||||
return $layout->renderLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_system_theme_info().
|
||||
*/
|
||||
function layout_test_system_theme_info() {
|
||||
$themes['layout_test_theme'] = drupal_get_path('module', 'layout_test') . '/themes/layout_test_theme/layout_test_theme.info';
|
||||
return $themes;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Template for a one column layout.
|
||||
*
|
||||
* This template provides a very simple "one column" display layout.
|
||||
*
|
||||
* Variables:
|
||||
* - $content: An array of content, each item in the array is keyed to one
|
||||
* region of the layout. This layout supports the following sections:
|
||||
* $content['middle']: The only region in the layout.
|
||||
*/
|
||||
?>
|
||||
<div class="layout-display layout-one-col clearfix">
|
||||
<div class="layout-region layout-col">
|
||||
<div class="inside"><?php print $content['middle']; ?></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
title: Single column
|
||||
category: Columns: 1
|
||||
template: one-col
|
||||
regions:
|
||||
middle: 'Middle column'
|
|
@ -0,0 +1,4 @@
|
|||
name = Layout test theme
|
||||
description = Theme for testing the layout system
|
||||
core = 8.x
|
||||
hidden = TRUE
|
|
@ -0,0 +1,17 @@
|
|||
.layout-two-col .layout-col-left {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.layout-two-col .layout-col-left .inside {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.layout-two-col .layout-col-right {
|
||||
float: right;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.layout-two-col .layout-col-right .inside {
|
||||
margin-left: .5em;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Template for a 2 column layout.
|
||||
*
|
||||
* This template provides a two column display layout, with each column equal in
|
||||
* width.
|
||||
*
|
||||
* Variables:
|
||||
* - $content: An array of content, each item in the array is keyed to one
|
||||
* region of the layout. This layout supports the following sections:
|
||||
* - $content['left']: Content in the left column.
|
||||
* - $content['right']: Content in the right column.
|
||||
*/
|
||||
?>
|
||||
<div class="layout-display layout-two-col clearfix">
|
||||
<div class="layout-region layout-col-left">
|
||||
<div class="inside"><?php print $content['left']; ?></div>
|
||||
</div>
|
||||
|
||||
<div class="layout-region layout-col-right">
|
||||
<div class="inside"><?php print $content['right']; ?></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
title: Two column
|
||||
category: Columns: 2
|
||||
template: two-col
|
||||
stylesheets:
|
||||
- two-col.css
|
||||
regions:
|
||||
left: 'Left side'
|
||||
right: 'Right side'
|
Loading…
Reference in New Issue