diff --git a/README.md b/README.md index ad4fcf2..38b898c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Data Transform Plugin for Pattern Lab PHP +# Data Transform Plugin for Pattern Lab + +This plugin now only works with Twig PatternEngine. You can use old 0.x versions of the plugin for Mustache PatternEngine. + ## Installation @@ -13,12 +16,12 @@ composer require aleksip/plugin-data-transform ### Pattern-specific data file support for included patterns -Pattern Lab currently only supports global data files and a pattern-specific data file for the main pattern. This plugin adds pattern-specific data file support for included patterns. This feature only works with Twig PatternEngine. +Pattern Lab core only supports global data files and a pattern-specific data file for the main pattern. This plugin adds pattern-specific data file support for included patterns. ### Data transform functions -Currently the plugin provides 3 transform functions for the data read by Pattern Lab. The examples provided are in JSON but Pattern Lab supports YAML too! These functions should work with both Twig and Mustache PatternEngines. +Currently the plugin provides 3 transform functions for the data read by Pattern Lab. The examples provided are in JSON but Pattern Lab supports YAML too! #### Include pattern files @@ -87,6 +90,11 @@ The value of `key` will be replaced with the joined strings. Note that in the ex The value of `key` will be replaced with an [Attribute object](https://www.drupal.org/node/2513632). +## Global data and includes + +Please note that global data from the `_data` directory is considered to be pattern-specific data and will overwrite data inherited from a parent pattern. If you want to override data of an included pattern you can use the `with` keyword. + + ## More examples All features provided by this plugin are used extensively in [Shila Drupal Theme StarterKit](https://github.com/aleksip/starterkit-shila-drupal-theme). diff --git a/composer.json b/composer.json index adf6cc8..863fe0e 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ }, "require": { "php": ">=5.5.9", + "pattern-lab/core": "^2.6.3", "pattern-lab/patternengine-twig": "^2.0.0" } } diff --git a/src/aleksip/DataTransformPlugin/DataTransformer.php b/src/aleksip/DataTransformPlugin/DataTransformer.php new file mode 100644 index 0000000..3bc15d3 --- /dev/null +++ b/src/aleksip/DataTransformPlugin/DataTransformer.php @@ -0,0 +1,165 @@ +env = $env; + // TODO: Add an accessor function for $reservedKeys to the Data class? + $this->reservedKeys = array("cacheBuster","link","patternSpecific","patternLabHead","patternLabFoot"); + $this->patternDataStore = PatternData::get(); + $this->processed = array(); + } + + public function run() + { + // Process global data. + $dataStore = $this->processData(Data::get()); + Data::replaceStore($dataStore); + // Process pattern specific data. + foreach (array_keys($this->patternDataStore) as $pattern) { + $this->processPattern($pattern); + } + } + + protected function isProcessed($pattern) + { + return isset($this->processed[$pattern]); + } + + protected function setProcessed($pattern) + { + $this->processed[$pattern] = true; + } + + protected function processPattern($pattern) + { + if ( + $this->isProcessed($pattern) + || !isset($this->patternDataStore[$pattern]) + || $this->patternDataStore[$pattern]['category'] != 'pattern' + ) { + return; + } + $patternSpecificData = + $this->processData(Data::getPatternSpecificData($pattern)) + ; + $dataStore = Data::get(); + foreach (array_keys($patternSpecificData) as $key) { + if (!isset($dataStore['patternSpecific'][$pattern]['data'][$key])) { + // Value is default global data. + if (is_object($dataStore[$key])) { + $patternSpecificData[$key] = clone $dataStore[$key]; + } + } + } + Data::initPattern($pattern); + Data::setPatternData($pattern, $patternSpecificData); + $this->setProcessed($pattern); + } + + protected function processData($data) + { + foreach (array_keys($data) as $key) { + if (!in_array($key, $this->reservedKeys)) { + $data = $this->processKey($data, $key); + } + } + + return $data; + } + + protected function processKey($data, $key) + { + $value = $data[$key]; + if (is_array($value)) { + foreach (array_keys($value) as $subKey) { + $value = $this->processKey($value, $subKey); + } + if (isset($value['Attribute()']) && is_array($value['Attribute()'])) { + $data[$key] = new Attribute($value['Attribute()']); + } + elseif (isset($value['include()']) && is_array($value['include()']) && isset($value['include()']['pattern'])) { + $pattern = $value['include()']['pattern']; + if (is_string($pattern) && isset($this->patternDataStore[$pattern])) { + if (!isset($value['include()']['with']) || !is_array($value['include()']['with'])) { + if (!isset($value['include()']['only'])) { + $patternData = $this->getProcessedPatternSpecificData($pattern); + } + else { + $patternData = array(); + } + } + elseif (!isset($value['include()']['only'])) { + $patternData = $this->getProcessedPatternSpecificData($pattern, $value['include()']['with']); + } + else { + $patternData = $value['include()']['with']; + } + $data[$key] = $this->renderPattern($pattern, $patternData); + } + } + elseif (isset($value['join()']) && is_array($value['join()'])) { + $data[$key] = join($value['join()']); + } + else { + $data[$key] = $value; + } + } + elseif (is_string($value) && isset($this->patternDataStore[$value]) && $key !== 'pattern') { + $data[$key] = $this->renderPattern($value, $this->getProcessedPatternSpecificData($value)); + } + + return $data; + } + + public function getProcessedPatternSpecificData($pattern, $extraData = array()) + { + $this->processPattern($pattern); + + return Data::getPatternSpecificData($pattern, $extraData); + } + + protected function renderPattern($pattern, $data) + { + if (isset($this->patternDataStore[$pattern]['patternRaw'])) { + foreach (array_keys($data) as $key) { + $data = $this->cloneObjects($data, $key); + } + $pattern = $this->env->render( + $this->patternDataStore[$pattern]['patternRaw'], + $data + ); + } + + return $pattern; + } + + protected function cloneObjects($data, $key) + { + $value = $data[$key]; + if (is_array($value)) { + foreach (array_keys($value) as $subKey) { + $value = $this->cloneObjects($value, $subKey); + } + $data[$key] = $value; + } + elseif (is_object($value)) { + $data[$key] = clone $value; + } + + return $data; + } +} diff --git a/src/aleksip/DataTransformPlugin/Helper.php b/src/aleksip/DataTransformPlugin/Helper.php deleted file mode 100644 index ad30795..0000000 --- a/src/aleksip/DataTransformPlugin/Helper.php +++ /dev/null @@ -1,124 +0,0 @@ -getBasePath(); - $patternLoaderClass = $patternEngineBasePath . '\Loaders\PatternLoader'; - $this->patternLoader = new $patternLoaderClass($options); - $this->store = PatternData::get(); - // TODO: Add an accessor function for $reservedKeys to the Data class? - $this->reservedKeys = array("listItems","cacheBuster","patternLink","patternSpecific","patternLabHead","patternLabFoot"); - $this->processed = array(); - } - - public function run() - { - foreach (array_keys($this->store) as $patternStoreKey) { - $this->processPattern($patternStoreKey); - } - } - - protected function isProcessed($patternStoreKey) - { - return isset($this->processed[$patternStoreKey]); - } - - protected function setProcessed($patternStoreKey) - { - $this->processed[$patternStoreKey] = true; - } - - protected function processPattern($patternStoreKey) - { - $patternStoreData = $this->store[$patternStoreKey]; - if ($patternStoreData["category"] == "pattern" && !$this->isProcessed($patternStoreKey)) { - $data = Data::getPatternSpecificData($patternStoreKey); - foreach (array_keys($data) as $key) { - if (!in_array($key, $this->reservedKeys)) { - $data = $this->processKey($data, $key); - } - } - Data::setPatternData($patternStoreKey, $data); - $this->setProcessed($patternStoreKey); - } - } - - protected function processKey($data, $key) - { - $value = $data[$key]; - if (is_array($value)) { - foreach (array_keys($value) as $subKey) { - $value = $this->processKey($value, $subKey); - } - if (isset($value['Attribute()']) && is_array($value['Attribute()'])) { - $data[$key] = new Attribute($value['Attribute()']); - } - elseif (isset($value['include()']) && is_array($value['include()']) && isset($value['include()']['pattern'])) { - $pattern = $value['include()']['pattern']; - if (is_string($pattern) && isset($this->store[$pattern])) { - if (!isset($value['include()']['with']) || !is_array($value['include()']['with'])) { - if (!isset($value['include()']['only'])) { - $patternData = $this->getProcessedPatternSpecificData($pattern); - } - else { - $patternData = array(); - } - } - elseif (!isset($value['include()']['only'])) { - $patternData = $this->getProcessedPatternSpecificData($pattern, $value['include()']['with']); - } - else { - $patternData = $value['include()']['with']; - } - $data[$key] = $this->renderPattern($pattern, $patternData); - } - } - elseif (isset($value['join()']) && is_array($value['join()'])) { - $data[$key] = join($value['join()']); - } - else { - $data[$key] = $value; - } - } - elseif (is_string($value) && isset($this->store[$value]) && $key !== 'pattern') { - $data[$key] = $this->renderPattern($value, $this->getProcessedPatternSpecificData($value)); - } - - return $data; - } - - protected function getProcessedPatternSpecificData($pattern, $extraData = array()) - { - $this->processPattern($pattern); - - return Data::getPatternSpecificData($pattern, $extraData); - } - - protected function renderPattern($pattern, $data) - { - if (isset($this->store[$pattern]['patternRaw'])) { - $pattern = $this->patternLoader->render([ - 'pattern' => $this->store[$pattern]['patternRaw'], - 'data' => $data - ]); - } - - return $pattern; - } -} diff --git a/src/aleksip/DataTransformPlugin/PatternLabListener.php b/src/aleksip/DataTransformPlugin/PatternLabListener.php index d3d68b9..01eca8c 100644 --- a/src/aleksip/DataTransformPlugin/PatternLabListener.php +++ b/src/aleksip/DataTransformPlugin/PatternLabListener.php @@ -4,28 +4,24 @@ use aleksip\DataTransformPlugin\Twig\PatternDataNodeVisitor; use PatternLab\Listener; -use PatternLab\PatternData\Event; use PatternLab\PatternEngine\Twig\TwigUtil; class PatternLabListener extends Listener { public function __construct() { - $this->addListener('patternData.codeHelperStart', 'runHelper'); - $this->addListener('twigPatternLoader.customize', 'addNodeVisitor'); + $this->addListener( + 'twigPatternLoader.customize', + 'twigPatternLoaderCustomize' + ); } - public function runHelper(Event $event) + public function twigPatternLoaderCustomize() { - $options = $event->getOptions(); - $helper = new Helper($options); - $helper->run(); - } - - public function addNodeVisitor() - { - $instance = TwigUtil::getInstance(); - $instance->addNodeVisitor(new PatternDataNodeVisitor()); - TwigUtil::setInstance($instance); + $env = TwigUtil::getInstance(); + $dt = new DataTransformer($env); + $env->addNodeVisitor(new PatternDataNodeVisitor($dt)); + TwigUtil::setInstance($env); + $dt->run(); } } diff --git a/src/aleksip/DataTransformPlugin/Twig/PatternDataNodeVisitor.php b/src/aleksip/DataTransformPlugin/Twig/PatternDataNodeVisitor.php index 97fa180..ef307bd 100644 --- a/src/aleksip/DataTransformPlugin/Twig/PatternDataNodeVisitor.php +++ b/src/aleksip/DataTransformPlugin/Twig/PatternDataNodeVisitor.php @@ -2,10 +2,17 @@ namespace aleksip\DataTransformPlugin\Twig; -use PatternLab\Data; +use aleksip\DataTransformPlugin\DataTransformer; class PatternDataNodeVisitor extends \Twig_BaseNodeVisitor { + protected $dt; + + public function __construct(DataTransformer $dt) + { + $this->dt = $dt; + } + protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) { return $node; @@ -16,7 +23,7 @@ protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env) if ($node instanceof \Twig_Node_Include) { if ($node->hasNode('expr') && $node->getNode('expr')->hasAttribute('value')) { $patternStoreKey = $node->getNode('expr')->getAttribute('value'); - $data = Data::getPatternSpecificData($patternStoreKey); + $data = $this->dt->getProcessedPatternSpecificData($patternStoreKey); if ($node instanceof \Twig_Node_Embed) { $dataNode = new PatternDataEmbedNode($node, $data); }