diff --git a/core/modules/layout/layout.info b/core/modules/layout/layout.info new file mode 100644 index 0000000..187bfea --- /dev/null +++ b/core/modules/layout/layout.info @@ -0,0 +1,5 @@ +name = Layout +description = Makes it possible to swap different page layouts. +package = Core +version = VERSION +core = 8.x diff --git a/core/modules/layout/layout.module b/core/modules/layout/layout.module new file mode 100644 index 0000000..7b0ab2c --- /dev/null +++ b/core/modules/layout/layout.module @@ -0,0 +1,39 @@ +renderLayout(). + */ +function layout_page_build(&$page) { + $request = drupal_container()->get('request'); + $options = array( + 'request' => $request, + ); + $layout = layout_manager()->getInstance($options); + if ($layout) { + $page['#children'] = $layout->renderLayout(); + $page['#theme'] = NULL; + } +} + +/** + * 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'); +} diff --git a/core/modules/layout/layout/onecol/layout--onecol.tpl.php b/core/modules/layout/layout/onecol/layout--onecol.tpl.php new file mode 100644 index 0000000..4a9fa6d --- /dev/null +++ b/core/modules/layout/layout/onecol/layout--onecol.tpl.php @@ -0,0 +1,19 @@ + +
> +
+
+
+
diff --git a/core/modules/layout/layout/onecol/onecol.css b/core/modules/layout/layout/onecol/onecol.css new file mode 100644 index 0000000..9515b54 --- /dev/null +++ b/core/modules/layout/layout/onecol/onecol.css @@ -0,0 +1,22 @@ + +.layout-1col { +/* overflow: hidden; */ +} + +.layout-2col .layout-col-first .inside { + margin: 0; +} + + +.layout-1col .layout-col { + width: 100%; +} + +#layout-edit-display .layout-region, +#layout-edit-display .helperclass { + margin: .5em; +} + +.layout-2col .layout-separator { + margin: 0 0 1em 0; +} diff --git a/core/modules/layout/layout/onecol/onecol.yml b/core/modules/layout/layout/onecol/onecol.yml new file mode 100644 index 0000000..170a449 --- /dev/null +++ b/core/modules/layout/layout/onecol/onecol.yml @@ -0,0 +1,7 @@ +title: Single column +category: Columns: 1 +template: layout--onecol +icon: onecol.png +css: onecol.css +regions: + middle: 'Middle column' diff --git a/core/modules/layout/layout/twocol/layout--twocol.tpl.php b/core/modules/layout/layout/twocol/layout--twocol.tpl.php new file mode 100644 index 0000000..90a69af --- /dev/null +++ b/core/modules/layout/layout/twocol/layout--twocol.tpl.php @@ -0,0 +1,25 @@ + +
> +
+
+
+ +
+
+
+
diff --git a/core/modules/layout/layout/twocol/twocol.css b/core/modules/layout/layout/twocol/twocol.css new file mode 100644 index 0000000..ecbd867 --- /dev/null +++ b/core/modules/layout/layout/twocol/twocol.css @@ -0,0 +1,37 @@ + +.layout-2col { +/* overflow: hidden; */ +} + +.layout-2col .layout-col-first { + float: left; + width: 50%; +} +* html .layout-2col .layout-col-first { + width: 49.9%; +} + +.layout-2col .layout-col-first .inside { + margin: 0 .5em 1em 0; +} + +.layout-2col .layout-col-last { + float: left; + width: 50%; +} +* html .layout-2col .layout-col-last { + width: 49.9%; +} + +.layout-2col .layout-col-last .inside { + margin: 0 0 1em .5em; +} + +#layout-edit-display .layout-region, +#layout-edit-display .helperclass { + margin: .5em; +} + +.layout-2col .layout-separator { + margin: 0 0 1em 0; +} diff --git a/core/modules/layout/layout/twocol/twocol.yml b/core/modules/layout/layout/twocol/twocol.yml new file mode 100644 index 0000000..31fe9fe --- /dev/null +++ b/core/modules/layout/layout/twocol/twocol.yml @@ -0,0 +1,8 @@ +title: Two column +category: Columns: 2 +template: layout--twocol +icon: twocol.png +css: twocol.css +regions: + left: 'Left side' + right: 'Right side' diff --git a/core/modules/layout/lib/Drupal/layout/LayoutBundle.php b/core/modules/layout/lib/Drupal/layout/LayoutBundle.php new file mode 100644 index 0000000..59b8513 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/LayoutBundle.php @@ -0,0 +1,25 @@ +register('plugin.manager.layout', 'Drupal\layout\Plugin\Type\LayoutManager'); + } +} diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php b/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php new file mode 100644 index 0000000..dabfd23 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php @@ -0,0 +1,98 @@ +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 . 'layout'; + 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()); + $this->derivatives[$provider['provider'] . '__' . $fileinfo->getBasename('.yml')] = $directory->read($fileinfo->getBasename('.yml')); + $this->derivatives[$provider['provider'] . '__' . $fileinfo->getBasename('.yml')]['path'] = $fileinfo->getPath(); + } + } + } +} diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/LayoutInterface.php b/core/modules/layout/lib/Drupal/layout/Plugin/LayoutInterface.php new file mode 100644 index 0000000..0863a67 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/Plugin/LayoutInterface.php @@ -0,0 +1,27 @@ +manager = $manager; + } + + /** + * Implements Drupal\Component\Plugin\Mapper\MapperInterface::getInstance(). + */ + public function getInstance(array $options) { + // @todo Replace with proper logic in http://drupal.org/node/1787942. + global $theme; + if ($theme == 'layout_test_theme') { + return $this->manager->createInstance('default_layout:layout_test_theme__page', array()); + } + } +} diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/Type/LayoutManager.php b/core/modules/layout/lib/Drupal/layout/Plugin/Type/LayoutManager.php new file mode 100644 index 0000000..e9852f3 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/Plugin/Type/LayoutManager.php @@ -0,0 +1,33 @@ + 'Drupal\layout\Plugin\layout\layout\DefaultLayout' + ); + + /** + * 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); + $this->mapper = new LayoutMapper($this); + } +} diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/layout/layout/DefaultLayout.php b/core/modules/layout/lib/Drupal/layout/Plugin/layout/layout/DefaultLayout.php new file mode 100644 index 0000000..6d7ebf0 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/Plugin/layout/layout/DefaultLayout.php @@ -0,0 +1,140 @@ +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']; + } + + /** + * Add the CSS files associated with this layout to the page. + */ + public function addCss() { + $definition = $this->getDefinition(); + $files = $definition['css']; + if (is_string($files)) { + $files = array($files); + } + foreach ($files as $filename) { + drupal_add_css($definition['path'] . '/' . $filename); + } + } + + /** + * Add the administrative CSS files associated with this layout to the page. + */ + public function addAdminCss() { + $definition = $this->getDefinition(); + // Fall back on regular CSS for the admin page if admin CSS not provided. + $files = isset($definition['admin css']) ? $definition['admin css'] : $definition['css']; + if (is_string($files)) { + $files = array($files); + } + foreach ($files as $filename) { + drupal_add_css($definition['path'] . '/' . $filename); + } + } + + /** + * Add the JS files associated with this layout to the page. + */ + public function addJs() { + $definition = $this->getDefinition(); + if (isset($definition['js'])) { + $files = $definition['js']; + if (is_string($files)) { + $files = array($files); + } + foreach ($files as $filename) { + drupal_add_js($definition['path'] . '/' . $filename); + } + } + } + + /** + * Add the admin JS files associated with this layout to the page. + */ + public function addAdminJs() { + $definition = $this->getDefinition(); + $files = array(); + if (isset($definition['admin js'])) { + $files = $definition['admin js']; + } + elseif (isset($definition['js'])) { + // Fall back on regular JS for the admin page if admin JS not provided. + $files = $definition['js']; + } + if (is_string($files)) { + $files = array($files); + } + foreach ($files as $filename) { + drupal_add_js($definition['path'] . '/' . $filename); + } + } + + /** + * Implements Drupal\layout\Plugin\LayoutInterface::renderLayout(). + */ + public function renderLayout($admin = FALSE) { + $definition = $this->getDefinition(); + + // Render all regions needed for this layout. + $regions = array(); + 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. + $regions[$region] = '

' . $title . '

'; + } + + if (!$admin) { + $this->addCss(); + $this->addJs(); + } + else { + $this->addAdminCss(); + $this->addAdminJs(); + } + + $template = $definition['path'] . '/' . $definition['template'] . '.tpl.php'; + $output = theme_render_template($template, array('content' => $regions)); + + return $output; + } +} diff --git a/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php b/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php new file mode 100644 index 0000000..adf77c4 --- /dev/null +++ b/core/modules/layout/lib/Drupal/layout/Tests/LayoutDerivativesTest.php @@ -0,0 +1,92 @@ + '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) == 3, 'Three definitions available.'); + $this->assertTrue(isset($definitions['default_layout:layout__onecol']), 'One column layout found.'); + $this->assertTrue(isset($definitions['default_layout:layout__twocol']), 'Two column layout found.'); + $this->assertTrue(isset($definitions['default_layout:layout_test_theme__page']), 'Test theme page layout found.'); + + // Get a two column layout instance. This is defined under the layout + // module. + $layout = $manager->createInstance('default_layout:layout__twocol', 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('class="layout-display layout-2col'); + + // Get the page layout defined by the layout test theme. + $layout = $manager->createInstance('default_layout:layout_test_theme__page', array()); + // Verify the expected regions are properly available. + $regions = $layout->getRegions(); + $this->assertTrue(is_array($regions), 'Regions array present.'); + $this->assertTrue(count($regions) == 15, '15 regions defined.'); + $this->assertTrue(isset($regions['footer']), 'Footer region found.'); + $this->assertTrue(isset($regions['triptych_middle']), 'Triptych 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('Triptych middle'); + $this->assertText('Footer'); + $this->assertRaw('id="triptych-wrapper"'); + } + + /** + * 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('Triptych middle'); + $this->assertText('Footer'); + $this->assertRaw('id="triptych-wrapper"'); + } + +} diff --git a/core/modules/layout/tests/layout_test.info b/core/modules/layout/tests/layout_test.info new file mode 100644 index 0000000..7b054a9 --- /dev/null +++ b/core/modules/layout/tests/layout_test.info @@ -0,0 +1,6 @@ +name = Layout test +description = Helps with testing layouts. +package = Testing +version = VERSION +core = 8.x +hidden = TRUE diff --git a/core/modules/layout/tests/layout_test.module b/core/modules/layout/tests/layout_test.module new file mode 100644 index 0000000..fd778e6 --- /dev/null +++ b/core/modules/layout/tests/layout_test.module @@ -0,0 +1,39 @@ + '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, so the final page layout + // rendering will use the page layout from the theme itself (based on the + // temporary LayoutMapper implementation). + global $theme; + $theme = 'layout_test_theme'; + theme_enable(array($theme)); + return t('Layout test'); +} + +/** + * 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; +} diff --git a/core/modules/layout/tests/themes/layout_test_theme/layout/page/layout_test_theme--page.tpl.php b/core/modules/layout/tests/themes/layout_test_theme/layout/page/layout_test_theme--page.tpl.php new file mode 100644 index 0000000..3a5db91 --- /dev/null +++ b/core/modules/layout/tests/themes/layout_test_theme/layout/page/layout_test_theme--page.tpl.php @@ -0,0 +1,62 @@ +
+ + + + + + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + +
+ + + +
+ + + + +
diff --git a/core/modules/layout/tests/themes/layout_test_theme/layout/page/page.yml b/core/modules/layout/tests/themes/layout_test_theme/layout/page/page.yml new file mode 100644 index 0000000..8c57a68 --- /dev/null +++ b/core/modules/layout/tests/themes/layout_test_theme/layout/page/page.yml @@ -0,0 +1,21 @@ +title: Layout test page +category: Other +template: layout_test_theme--page +icon: page.png +css: page.css +regions: + header: 'Header' + help: 'Help' + highlighted: 'Highlighted' + featured: 'Featured' + content: 'Content' + sidebar_first: 'Sidebar first' + sidebar_second: 'Sidebar second' + triptych_first: 'Triptych first' + triptych_middle: 'Triptych middle' + triptych_last: 'Triptych last' + footer_firstcolumn: 'Footer first column' + footer_secondcolumn: 'Footer second column' + footer_thirdcolumn: 'Footer third column' + footer_fourthcolumn: 'Footer fourth column' + footer: 'Footer' diff --git a/core/modules/layout/tests/themes/layout_test_theme/layout_test_theme.info b/core/modules/layout/tests/themes/layout_test_theme/layout_test_theme.info new file mode 100644 index 0000000..84bcff0 --- /dev/null +++ b/core/modules/layout/tests/themes/layout_test_theme/layout_test_theme.info @@ -0,0 +1,4 @@ +name = Layout test theme +description = Theme for testing the layout system +core = 8.x +hidden = TRUE