Skip to content

Commit fc4ca6e

Browse files
author
Jason Smith
committed
Refactored to use twig from library folder
1 parent 473020c commit fc4ca6e

File tree

5 files changed

+245
-75
lines changed

5 files changed

+245
-75
lines changed

README.md

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,45 @@ When pattern configurations are saved, the template is downloaded locally (to mi
99

1010
Rendered twigs may contain drupal tokens, which are then processed in context.
1111

12+
## Installation
13+
Install the patternkit module as usual, and review the important variables below to determine if you would like to change the defaults.
14+
15+
The patternkit module by itself only provides the glue for other modules to present components. Define one by implementing ```hook_patternkit_library```
16+
17+
An example implementation follows
18+
```
19+
/**
20+
* Implements hook_patternkit_library().
21+
*/
22+
function webrh_patternkit_library() {
23+
$libraries = array();
24+
25+
$namespaces = array(
26+
'Web RH Patterns' => 'webrh/src/library',
27+
);
28+
29+
$module_path = drupal_get_path('module', 'webrh');
30+
foreach ($namespaces as $namespace => $path) {
31+
$lib_path = $module_path . DIRECTORY_SEPARATOR . $path;
32+
$libraries[] = new PatternkitDrupalTwigLib($namespace, $lib_path);
33+
}
34+
35+
return $libraries;
36+
}
37+
```
38+
39+
There are two different plugins currently available,
40+
* PatternkitRESTLib
41+
* PatternkitDrupalTwigLib
42+
43+
Use the former for dynamic REST based components, and the latter for locally sourced.
44+
1245
## Important Variables
13-
* patternkit_cache_enabled - Whether or not the metadata and render cache are enabled. (Disable during development)
14-
* patternkit_pl_host - The scheme://hostname:port/ of the PatternLab library host.
15-
* patternkit_default_module_ttl - How long the rendered pattern should be cached.
16-
* patternkit_show_errors - Whether or not to display messages on the site.
17-
* patternkit_log_errors - Whether or not to log errors to php error log.
46+
* ```patternkit_cache_enabled``` - Whether or not the metadata and render cache are enabled. (Disable during development)
47+
* ```patternkit_pl_host``` - The scheme://hostname:port/ of the PatternLab library host.
48+
* ```patternkit_default_module_ttl``` - How long the rendered pattern should be cached.
49+
* ```patternkit_show_errors``` - Whether or not to display messages on the site.
50+
* ```patternkit_log_errors``` - Whether or not to log errors to php error log.
1851

1952
## TODOs
2053
* https://github.com/drupal-pattern-lab/roadmap/issues/8 Solve the problem of mapping Drupal fields to pattern Variables.

include/utility.inc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* Utility functions for Patternkit.
66
*/
77

8-
98
/**
109
* Fetch and cache assets to render this pattern.
1110
*

patternkit.module

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,26 +69,20 @@ function patternkit_flush_metadata_cache() {
6969
* An array of Pattern Kit Library objects.
7070
*/
7171
function patternkit_pattern_libraries() {
72-
// @todo Add caching.
73-
return module_invoke_all('patternkit_library');
74-
}
72+
static $libraries;
7573

76-
/**
77-
* Implements hook_patternkit_library().
78-
*/
79-
function patternkit_patternkit_library() {
80-
// @todo Replace with a Service that calls a Factory.
81-
$rest_lib = new PatternkitRESTLib();
82-
$libraries = array($rest_lib);
83-
/** @var object $theme */
84-
foreach (list_themes() as $theme_name => $theme) {
85-
if ($theme->engine !== 'twig' || !isset($theme->info['namespaces'])) {
86-
continue;
74+
// If undefined, create the libraries metadata.
75+
if (is_null($libraries)) {
76+
if ($cache = cache_get('patternkit_libraries')) {
77+
$libraries = $cache->data;
8778
}
88-
foreach ($theme->info['namespaces'] as $namespace => $path) {
89-
$theme_path = dirname($theme->filename);
90-
$lib_path = $theme_path . DIRECTORY_SEPARATOR . $path;
91-
$libraries[] = new PatternkitDrupalTwigLib($namespace, $lib_path);
79+
else {
80+
$libraries = module_invoke_all('patternkit_library');
81+
82+
if (!empty($libraries)) {
83+
// Cache for one day.
84+
cache_set('patternkit_library', $libraries, time() + 60 * 60 * 24);
85+
}
9286
}
9387
}
9488

src/PatternkitDrupalTwigLib.php

Lines changed: 186 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -134,53 +134,93 @@ public function getEditor($subtype = NULL,
134134
* Array of metadata objects found.
135135
*/
136136
protected function getRawMetadata() {
137-
$id = $this->getId();
138-
$it = new RecursiveDirectoryIterator($this->path);
139-
$filter = array('json');
140-
$metadata = array();
141-
/** @var \SplFileInfo $file */
142-
foreach (new RecursiveIteratorIterator($it) as $file) {
143-
if (!$file->isFile()) {
144-
continue;
145-
}
146-
$file_path = $file->getPath();
147-
$dirs = explode(DIRECTORY_SEPARATOR, $file_path);
148-
// All JSON schema must be in an 'api' folder at this time.
149-
// @todo Add support for custom setups.
150-
// @todo Look at a standard for JSON Schema + JSON Sample data.
151-
$num_dirs = count($dirs);
152-
if ($num_dirs < 2
153-
|| array_pop($dirs) !== 'api') {
154-
continue;
155-
}
156-
$file_ext = $file->getExtension();
157-
if (!in_array(strtolower($file_ext), $filter, TRUE)) {
158-
continue;
137+
static $metadata;
138+
139+
// Use static pattern to avoid rebuilding multiple times per request.
140+
if (is_null($metadata)) {
141+
142+
$it = new RecursiveDirectoryIterator($this->path);
143+
$filter = ['json', 'twig'];
144+
$metadata = [];
145+
$components = [];
146+
147+
/** @var \SplFileInfo $file */
148+
foreach (new RecursiveIteratorIterator($it) as $file) {
149+
// Skip directories and non-files.
150+
if (!$file->isFile()) {
151+
continue;
152+
}
153+
$file_path = $file->getPath();
154+
155+
// Skip tests folders.
156+
if (strpos($file_path, '/tests') !== FALSE) {
157+
continue;
158+
}
159+
160+
// Get the file extension for the file.
161+
$file_ext = $file->getExtension();
162+
if (!in_array(strtolower($file_ext), $filter, TRUE)) {
163+
continue;
164+
}
165+
166+
// We use file_basename as a unique key, it is required that the
167+
// JSON and twig file share this basename.
168+
$file_basename = $file->getBasename('.' . $file_ext);
169+
170+
// Build an array of all the filenames of interest, keyed by name.
171+
$components[$file_basename][$file_ext] = $file_path;
159172
}
160-
if ($file_contents = file_get_contents($file)) {
161-
$pattern = $this->createPattern(json_decode($file_contents));
162-
$file_basename = $file->getBasename('.json');
163-
$subtype = "pk_$file_basename";
164-
$pattern->subtype = $subtype;
165-
$pattern->url = url("patternkit/ajax/$id/$subtype/schema");
166-
$twig_file = $file_path
167-
. DIRECTORY_SEPARATOR . $file_basename . '.twig';
168-
if (file_exists($twig_file)) {
169-
$pattern->filename = $twig_file;
170-
$pattern->template = file_get_contents($twig_file);
173+
174+
foreach ($components as $module_name => $data) {
175+
// If the component has a json file, create the pattern from it.
176+
if (!empty($data['json']) && $file_contents = file_get_contents($data['json'])) {
177+
$pattern = $this->createPattern(json_decode($file_contents));
178+
179+
$subtype = "pk_$module_name";
180+
$pattern->subtype = $subtype;
181+
// URL is redundant for the twig based components.
182+
$pattern->url = $module_name;
183+
}
184+
else {
185+
// Create the pattern from defaults.
186+
$pattern = $this->createPattern(
187+
(object) [
188+
'$schema' => 'http =>//json-schema.org/draft-04/schema#',
189+
'category' => 'atom',
190+
'title' => $module_name,
191+
'type' => 'object',
192+
'format' => 'grid',
193+
'properties' => (object) [],
194+
'required' => [],
195+
]
196+
);
197+
}
198+
199+
if (!empty($data['twig'])) {
200+
$twig_file = $data['twig']
201+
. DIRECTORY_SEPARATOR . $module_name . '.twig';
202+
if (file_exists($twig_file)) {
203+
$pattern->filename = $twig_file;
204+
$pattern->template = file_get_contents($twig_file);
205+
}
171206
}
172-
$metadata[$file_basename] = $pattern;
207+
208+
$metadata[$module_name] = $pattern;
173209
}
174-
}
175-
foreach ($metadata as $pattern_type => $pattern) {
176-
// Replace any $ref links with relative paths.
177-
if (!isset($pattern->schema->properties)) {
178-
continue;
210+
211+
foreach ($metadata as $pattern_type => $pattern) {
212+
// Replace any $ref links with relative paths.
213+
if (!isset($pattern->schema->properties)) {
214+
continue;
215+
}
216+
$pattern->schema->properties = _patternkit_schema_ref(
217+
$pattern->schema->properties,
218+
$metadata
219+
);
220+
$metadata[$pattern_type] = $pattern;
179221
}
180-
$pattern->schema->properties = _patternkit_schema_ref($pattern->schema->properties,
181-
$metadata);
182-
$metadata[$pattern_type] = $pattern;
183222
}
223+
184224
return $metadata;
185225
}
186226

@@ -195,14 +235,115 @@ protected function getRawMetadata() {
195235
* @return string
196236
* The rendered pattern HTML.
197237
*/
198-
public function getRenderedPatternMarkup(PatternkitPattern $pattern,
199-
PatternkitEditorConfig $config) {
238+
public function getRenderedPatternMarkup(
239+
PatternkitPattern $pattern,
240+
PatternkitEditorConfig $config
241+
) {
200242
if (empty($pattern->filename) || empty($config->fields)) {
201243
return '';
202244
}
203245
$template = $pattern->filename;
204246
$variables = $config->fields;
205-
return twig_render_template($template, $variables);
247+
248+
return $this->renderTwigTemplate($template, $variables);
249+
}
250+
251+
/**
252+
* Returns a singleton version of the twig template engine.
253+
*
254+
* @return Twig_Environment
255+
* Twig environment object.
256+
*
257+
* @throws \Twig_Error_Loader
258+
* Twig engine instance object.
259+
*/
260+
public function getTwigInstance() {
261+
static $twig_engine;
262+
263+
if (!is_object($twig_engine)) {
264+
// Setup twig environment.
265+
// @TODO: Properly libraryize this.
266+
require_once DRUPAL_ROOT . '/sites/all/libraries/Twig/Autoloader.php';
267+
Twig_Autoloader::register();
268+
269+
$loader = new Twig_Loader_Filesystem();
270+
271+
$metadata = $this->getRawMetadata();
272+
foreach ($metadata as $module_name => $module) {
273+
if (!empty($module->filename)) {
274+
$templatesDirectory = DRUPAL_ROOT . DIRECTORY_SEPARATOR . dirname(
275+
$module->filename
276+
);
277+
$loader->addPath($templatesDirectory);
278+
}
279+
}
280+
281+
$twig_engine = new Twig_Environment(
282+
$loader,
283+
array(
284+
'autorender' => (bool) variable_get('pktwig_auto_render', TRUE),
285+
'autoescape' => (bool) variable_get('pktwig_auto_escape', FALSE),
286+
'auto_reload' => (bool) variable_get('pktwig_auto_reload', FALSE),
287+
'cache' => variable_get('pktwig_template_cache_path', '/tmp/twig_compilation_cache'),
288+
'debug' => (bool) variable_get('pktwig_debug', FALSE),
289+
)
290+
);
291+
}
292+
293+
return $twig_engine;
294+
}
295+
296+
/**
297+
* Renders a twig template on demand.
298+
*
299+
* @param string $template
300+
* Template filename.
301+
* @param array $variables
302+
* Variables to be assigned to template.
303+
*
304+
* @return string
305+
* Rendered template.
306+
*/
307+
public function renderTwigTemplate($template, array $variables = array()) {
308+
309+
$content = '';
310+
if (file_exists($template)) {
311+
try {
312+
$twig = $this->getTwigInstance();
313+
$template = $twig->loadTemplate(basename($template));
314+
$content = $template->render($variables);
315+
}
316+
catch (Exception $e) {
317+
$content = t('Twig error "!error"', array('!error' => $e->getMessage()));
318+
watchdog(
319+
'patternkit',
320+
'Twig engine failure: @msg',
321+
array(
322+
'@msg' => $e->getMessage(),
323+
),
324+
WATCHDOG_ERROR
325+
);
326+
}
327+
}
328+
else {
329+
$content = t(
330+
'Template (!template) not found',
331+
array(
332+
'!template' => $template,
333+
)
334+
);
335+
watchdog(
336+
'patternkit',
337+
'Twig template not found: @msg',
338+
array(
339+
'@msg' => $template,
340+
),
341+
WATCHDOG_ERROR
342+
);
343+
}
344+
345+
$content .= "poop";
346+
return $content;
206347
}
207348

208349
}

src/PatternkitPattern.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ class PatternkitPattern {
1010
*
1111
* @param \PatternkitLibInterface $library
1212
* The library the pattern belongs to.
13-
*
1413
* @param object|array $schema
1514
* An optional JSON Schema object to use.
1615
*/
1716
public function __construct(PatternkitLibInterface $library, $schema = array()) {
18-
$this->subtype = NULL;
19-
$this->title = NULL;
20-
$this->html = NULL;
21-
$this->version = NULL;
17+
$this->subtype = NULL;
18+
$this->title = NULL;
19+
$this->html = NULL;
20+
$this->version = NULL;
2221
$this->attachments = NULL;
23-
$this->schema = $schema;
22+
$this->schema = $schema;
23+
24+
// If schema is undefined, initialize empty.
2425
if (empty($schema)) {
2526
return;
2627
}
28+
29+
// Walk the provided schemas and generate the library.
2730
foreach ($schema as $key => $value) {
2831
if ($key !== 'schema' && property_exists($this, (string) $key)) {
2932
$this->{$key} = $value;

0 commit comments

Comments
 (0)