Skip to content
Snippets Groups Projects
Commit 1946b65e authored by Antonio De Marco's avatar Antonio De Marco
Browse files

Initial work on #91

parent 6bebcdaa
No related branches found
No related tags found
No related merge requests found
......@@ -2,8 +2,11 @@
namespace Drupal\ui_patterns_library\Plugin\UiPatterns\Pattern;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Drupal\ui_patterns\UiPatternBase;
use Drupal\ui_patterns\UiPatternInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The UI Pattern plugin.
......@@ -17,6 +20,123 @@ use Drupal\ui_patterns\UiPatternInterface;
* deriver = "\Drupal\ui_patterns_library\Plugin\Deriver\LibraryDeriver"
* )
*/
class LibraryPattern extends UiPatternBase implements UiPatternInterface {
class LibraryPattern extends UiPatternBase {
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* UiPatternsManager constructor.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, $root, TypedDataManager $typed_data_manager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $root, $typed_data_manager);
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('app.root'),
$container->get('typed_data_manager'),
$container->get('module_handler'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function getThemeImplementation() {
$item = [];
$definition = $this->getPluginDefinition();
foreach ($definition['fields'] as $field) {
$item['variables'][$field['name']] = NULL;
}
$item['variables']['attributes'] = [];
$item['variables']['context'] = [];
$item['variables']['use'] = '';
$item += $this->processUseProperty($definition);
$item += $this->processCustomThemeHookProperty($definition);
$item += $this->processTemplateProperty($definition);
return [
$definition['theme hook'] => $item,
];
}
/**
* Process 'custom hook theme' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*/
protected function processCustomThemeHookProperty(array $definition) {
/** @var \Drupal\Core\Extension\Extension $module */
$return = [];
if (!$definition['custom theme hook'] && $this->moduleHandler->moduleExists($definition['provider'])) {
$module = $this->moduleHandler->getModule($definition['provider']);
$return['path'] = $module->getPath() . '/templates';
}
return $return;
}
/**
* Process 'template' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*/
protected function processTemplateProperty(array $definition) {
$return = [];
if (isset($definition['template'])) {
$return = ['template' => $definition['template']];
}
return $return;
}
/**
* Process 'use' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*/
protected function processUseProperty(array $definition) {
$return = [];
if (!empty($definition['use'])) {
$return = [
'path' => $this->moduleHandler->getModule('ui_patterns')->getPath() . '/templates',
'template' => 'patterns-use-wrapper',
];
}
return $return;
}
}
......@@ -3,19 +3,58 @@
namespace Drupal\ui_patterns;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class UiPatternBase.
*
* @package Drupal\ui_patterns
*/
abstract class UiPatternBase extends PluginBase implements UiPatternInterface {
abstract class UiPatternBase extends PluginBase implements UiPatternInterface, ContainerFactoryPluginInterface {
/**
* Prefix for locally defined libraries.
*/
const LIBRARY_PREFIX = 'ui_patterns';
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* Typed data manager service.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* UiPatternsManager constructor.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, $root, TypedDataManager $typed_data_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->root = $root;
$this->typedDataManager = $typed_data_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('app.root'),
$container->get('typed_data_manager')
);
}
/**
* {@inheritdoc}
*/
......@@ -153,4 +192,59 @@ abstract class UiPatternBase extends PluginBase implements UiPatternInterface {
return $value;
}
/**
* {@inheritdoc}
*/
public function getLibraryDefinitions() {
// @codingStandardsIgnoreStart
$libraries = [];
$definition = $this->getPluginDefinition();
// Get only locally defined libraries.
$items = array_filter($definition['libraries'], function ($library) {
return is_array($library);
});
// Attach pattern base path to assets.
if (!empty($definition['base path'])) {
$base_path = str_replace($this->root, '', $definition['base path']);
$this->processLibraries($items, $base_path);
}
// Produce final libraries array.
$id = $definition['id'];
array_walk($items, function ($value) use (&$libraries, $id) {
$libraries[$id . '.' . key($value)] = reset($value);
});
// @codingStandardsIgnoreEnd
return $libraries;
}
/**
* Process libraries.
*
* @param array $libraries
* Libraries array.
* @param string $base_path
* Pattern base path.
* @param string $parent
* Item parent set in previous recursive iteration, if any.
*/
protected function processLibraries(array &$libraries, $base_path, $parent = '') {
$parents = ['js', 'base', 'layout', 'component', 'state', 'theme'];
$_libraries = $libraries;
foreach ($_libraries as $name => $values) {
$is_asset = in_array($parent, $parents, TRUE);
$is_external = isset($values['type']) && $values['type'] == 'external';
if ($is_asset && !$is_external) {
$libraries[$base_path . DIRECTORY_SEPARATOR . $name] = $values;
unset($libraries[$name]);
}
elseif (!$is_asset) {
$this->processLibraries($libraries[$name], $base_path, $name);
}
}
}
}
......@@ -136,4 +136,25 @@ interface UiPatternInterface {
*/
public function getUse();
/**
* Build and return Drupal theme implementation for current pattern.
*
* @return array
* Theme implementation.
*
* @see ui_patterns_theme()
*/
public function getThemeImplementation();
/**
* Get library definitions for current pattern.
*
* @return array
* List of library definitions.
*
* @see hook_library_info_build()
* @see ui_patterns_library_info_build()
*/
public function getLibraryDefinitions();
}
......@@ -7,7 +7,6 @@ use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\TypedData\TypedDataManager;
/**
* Provides the default ui_patterns manager.
......@@ -16,13 +15,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage
use StringTranslationTrait;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* The theme handler.
*
......@@ -30,36 +22,23 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage
*/
protected $themeHandler;
/**
* Typed data manager service.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* UiPatternsManager constructor.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param string $root
* Application root directory.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* Module handler service.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* Theme handler service.
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* Typed data manager service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend service.
*/
public function __construct(\Traversable $namespaces, $root, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TypedDataManager $typed_data_manager, CacheBackendInterface $cache_backend) {
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend) {
parent::__construct('Plugin/UiPatterns/Pattern', $namespaces, $module_handler, 'Drupal\ui_patterns\UiPatternInterface', 'Drupal\ui_patterns\Annotation\UiPattern');
$this->root = $root;
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
$this->typedDataManager = $typed_data_manager;
$this->alterInfo('ui_patterns_info');
$this->setCacheBackend($cache_backend, 'ui_patterns', ['ui_patterns']);
}
......@@ -68,10 +47,22 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage
* {@inheritdoc}
*/
public function getPattern($id) {
// @todo should we cache pattern object instances?
// @todo should we statically cache this?
return $this->getFactory()->createInstance($id);
}
/**
* {@inheritdoc}
*/
public function getPatterns() {
// @todo should we statically cache this?
$patterns = [];
foreach ($this->getDefinitions() as $definition) {
$patterns[] = $this->getPattern($definition['id']);
}
return $patterns;
}
/**
* {@inheritdoc}
*/
......@@ -99,62 +90,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage
}, $this->getDefinitions());
}
/**
* {@inheritdoc}
*/
public function hookTheme() {
$items = [];
foreach ($this->getDefinitions() as $definition) {
$item = [];
$hook = $definition['theme hook'];
foreach ($definition['fields'] as $field) {
$item['variables'][$field['name']] = NULL;
}
$item['variables']['attributes'] = [];
$item['variables']['context'] = [];
$item['variables']['use'] = '';
$item += $this->processUseProperty($definition);
$item += $this->processCustomThemeHookProperty($definition);
$item += $this->processTemplateProperty($definition);
$items[$hook] = $item;
}
return $items;
}
/**
* {@inheritdoc}
*/
public function hookLibraryInfoBuild() {
// @codingStandardsIgnoreStart
$libraries = [];
foreach ($this->getDefinitions() as $definition) {
// Get only locally defined libraries.
$items = array_filter($definition['libraries'], function ($library) {
return is_array($library);
});
// Attach pattern base path to assets.
if (!empty($definition['base path'])) {
$base_path = str_replace($this->root, '', $definition['base path']);
$this->processLibraries($items, $base_path);
}
// Produce final libraries array.
$id = $definition['id'];
array_walk($items, function ($value) use (&$libraries, $id) {
$libraries[$id . '.' . key($value)] = reset($value);
});
}
// @codingStandardsIgnoreEnd
return $libraries;
}
/**
* {@inheritdoc}
*/
......@@ -165,94 +100,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage
return !empty($definitions);
}
/**
* Process libraries.
*
* @param array $libraries
* Libraries array.
* @param string $base_path
* Pattern base path.
* @param string $parent
* Item parent set in previous recursive iteration, if any.
*/
protected function processLibraries(array &$libraries, $base_path, $parent = '') {
$parents = ['js', 'base', 'layout', 'component', 'state', 'theme'];
$_libraries = $libraries;
foreach ($_libraries as $name => $values) {
$is_asset = in_array($parent, $parents, TRUE);
$is_external = isset($values['type']) && $values['type'] == 'external';
if ($is_asset && !$is_external) {
$libraries[$base_path . DIRECTORY_SEPARATOR . $name] = $values;
unset($libraries[$name]);
}
elseif (!$is_asset) {
$this->processLibraries($libraries[$name], $base_path, $name);
}
}
}
/**
* Process 'custom hook theme' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*
* @see UiPatternsManager::hookTheme()
*/
protected function processCustomThemeHookProperty(array $definition) {
/** @var \Drupal\Core\Extension\Extension $module */
$return = [];
if (!$definition['custom theme hook'] && $this->moduleHandler->moduleExists($definition['provider'])) {
$module = $this->moduleHandler->getModule($definition['provider']);
$return['path'] = $module->getPath() . '/templates';
}
return $return;
}
/**
* Process 'template' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*
* @see UiPatternsManager::hookTheme()
*/
protected function processTemplateProperty(array $definition) {
$return = [];
if (isset($definition['template'])) {
$return = ['template' => $definition['template']];
}
return $return;
}
/**
* Process 'use' definition property.
*
* @param array $definition
* Pattern definition array.
*
* @return array
* Processed hook definition portion.
*
* @see UiPatternsManager::hookTheme()
*/
protected function processUseProperty(array $definition) {
$return = [];
if (!empty($definition['use'])) {
$return = [
'path' => $this->moduleHandler->getModule('ui_patterns')->getPath() . '/templates',
'template' => 'patterns-use-wrapper',
];
}
return $return;
}
/**
* {@inheritdoc}
*/
......
......@@ -21,33 +21,20 @@ interface UiPatternsManagerInterface extends PluginManagerInterface {
public function getPattern($id);
/**
* Return list of available patterns to be used as select options.
*
* @return array
* List of available patterns.
*/
public function getPatternsOptions();
/**
* Build and return pattern theme definitions.
*
* @return array
* Theme definitions.
* Get a fully instantiated list of pattern objects.
*
* @see ui_patterns_theme()
* @return \Drupal\ui_patterns\UiPatternInterface[]
* List of pattern object instances.
*/
public function hookTheme();
public function getPatterns();
/**
* Get patterns library info.
* Return list of available patterns to be used as select options.
*
* @return array
* Array of libraries as expected by hook_library_info_build().
*
* @see hook_library_info_build()
* @see ui_patterns_library_info_build()
* List of available patterns.
*/
public function hookLibraryInfoBuild();
public function getPatternsOptions();
/**
* Check whereas the given theme hook is an actual pattern hook.
......
......@@ -65,7 +65,6 @@ Feature: Overview
Given I am logged in as a user with the "access patterns page" permission
And I am on "/patterns/media"
Then the response should contain "/ui_patterns_test_theme/templates/patterns/media/css/media1.css"
And the response should contain "/ui_patterns_test_theme/templates/patterns/media/css/media2.css"
And the response should contain "/ui_patterns_test_theme/templates/patterns/media/js/media1.js"
......
......@@ -5,6 +5,7 @@ namespace Drupal\Tests\ui_patterns\Kernel;
use function bovigo\assert\assert;
use function bovigo\assert\predicate\isTrue;
use function bovigo\assert\predicate\equals;
use Drupal\Component\Serialization\Yaml;
use Drupal\ui_patterns\UiPatternBase;
/**
......@@ -34,8 +35,7 @@ class UiPatternBaseTest extends AbstractUiPatternsTest {
foreach ($definitions as $definition) {
/** @var \Drupal\ui_patterns\UiPatternBase $pattern */
$arguments = [[], 'plugin_id', $definition];
$pattern = $this->getMockForAbstractClass(UiPatternBase::class, $arguments);
$pattern = $this->getUiPatternBaseMock($definition);
assert($pattern->getId(), equals($definition['id']));
assert($pattern->getLabel(), equals($definition['label']));
......@@ -55,4 +55,39 @@ class UiPatternBaseTest extends AbstractUiPatternsTest {
}
}
/**
* Test hookLibraryInfoBuild.
*
* @covers ::hookLibraryInfoBuild
*/
public function testHookLibraryInfoBuild() {
$items = Yaml::decode(file_get_contents(dirname(__FILE__) . '/../fixtures/libraries.yml'));
foreach ($items as $item) {
$pattern = $this->getUiPatternBaseMock($item['actual']);
/** @var \Drupal\ui_patterns\UiPatternBase $pattern */
$libraries = $pattern->getLibraryDefinitions();
assert($libraries, equals($item['expected']));
}
}
/**
* Get UiPatternBase mock.
*
* @param array $configuration
* Plugin configuration.
* @param array $methods
* List of methods to mock.
*
* @return \PHPUnit_Framework_MockObject_MockObject
* Mock object.
*/
protected function getUiPatternBaseMock(array $configuration = [], array $methods = []) {
$root = \Drupal::service('app.root');
$typed_data_manager = \Drupal::service('typed_data_manager');
$arguments = [[], 'plugin_id', $configuration, $root, $typed_data_manager];
return $this->getMockForAbstractClass(UiPatternBase::class, $arguments, '', TRUE, TRUE, TRUE, $methods);
}
}
<?php
namespace Drupal\Tests\ui_patterns\Unit;
use function bovigo\assert\assert;
use function bovigo\assert\predicate\equals;
use Drupal\Component\Serialization\Yaml;
use Drupal\ui_patterns\UiPatternsManager;
/**
* @coversDefaultClass \Drupal\ui_patterns\UiPatternsManager
*
* @group ui_patterns
*/
class UiPatternsManagerTest extends AbstractUiPatternsTest {
/**
* Test hookLibraryInfoBuild.
*
* @covers ::hookLibraryInfoBuild
*/
public function testHookLibraryInfoBuild() {
$items = Yaml::decode(file_get_contents(dirname(__FILE__) . '/../fixtures/libraries.yml'));
foreach ($items as $item) {
$manager = $this->createPartialMock(UiPatternsManager::class, ['getDefinitions']);
$manager->method('getDefinitions')->willReturn([$item['actual']]);
/** @var \Drupal\ui_patterns\UiPatternsManager $manager */
$libraries = $manager->hookLibraryInfoBuild();
assert($libraries, equals($item['expected']));
}
}
}
......@@ -11,14 +11,33 @@ use Drupal\ui_patterns\UiPatterns;
* Implements hook_theme().
*/
function ui_patterns_theme() {
return [
/** @var \Drupal\ui_patterns\UiPatternInterface $pattern */
$items = [
'patterns_destination' => [
'variables' => ['sources' => NULL, 'context' => NULL],
],
'patterns_use_wrapper' => [
'variables' => ['use' => NULL],
],
] + UiPatterns::getManager()->hookTheme();
];
foreach (UiPatterns::getManager()->getPatterns() as $pattern) {
$items += $pattern->getThemeImplementation();
}
return $items;
}
/**
* Implements hook_library_info_build()
*/
function ui_patterns_library_info_build() {
/** @var \Drupal\ui_patterns\UiPatternInterface $pattern */
$definitions = [];
foreach (UiPatterns::getManager()->getPatterns() as $pattern) {
$definitions += $pattern->getLibraryDefinitions();
}
return $definitions;
}
/**
......@@ -34,10 +53,3 @@ function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variabl
\Drupal::moduleHandler()->alter('ui_patterns_destination_suggestions', $suggestions, $variables, $variables['context']);
}
}
/**
* Implements hook_library_info_build()
*/
function ui_patterns_library_info_build() {
return UiPatterns::getManager()->hookLibraryInfoBuild();
}
services:
plugin.manager.ui_patterns:
class: Drupal\ui_patterns\UiPatternsManager
arguments: ['@container.namespaces', '@app.root', '@module_handler', '@theme_handler', '@typed_data_manager', '@cache.discovery']
arguments: ['@container.namespaces', '@module_handler', '@theme_handler', '@cache.discovery']
plugin.manager.ui_patterns_source:
class: Drupal\ui_patterns\Plugin\UiPatternsSourceManager
parent: default_plugin_manager
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment