From 8d26e285abe29ac66e2452f4d10bbca6a9480a0c Mon Sep 17 00:00:00 2001
From: Fabian Franz <github@fabian-franz.de>
Date: Thu, 4 Oct 2012 08:31:24 +0200
Subject: [PATCH 1/2] Issue #1696786: Add twig engine and simple example to core.

---
 core/lib/Drupal/Core/Template/TwigNodeVisitor.php  |   62 +++++++
 core/lib/Drupal/Core/Template/TwigReference.php    |   54 ++++++
 .../Core/Template/TwigReferenceFunctions.php       |   37 +++++
 core/themes/engines/twig/twig.engine               |  172 ++++++++++++++++++++
 core/themes/stark/stark.info                       |    1 +
 core/themes/stark/templates/node/node.twig         |  112 +++++++++++++
 .../themes/stark/templates/theme.inc/datetime.twig |   31 ++++
 7 files changed, 469 insertions(+), 0 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Template/TwigNodeVisitor.php
 create mode 100644 core/lib/Drupal/Core/Template/TwigReference.php
 create mode 100644 core/lib/Drupal/Core/Template/TwigReferenceFunctions.php
 create mode 100644 core/themes/engines/twig/twig.engine
 create mode 100644 core/themes/stark/templates/node/node.twig
 create mode 100644 core/themes/stark/templates/theme.inc/datetime.twig

diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
new file mode 100644
index 0000000..64ee54d
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigNodeVisitor.
+ */
+
+namespace Drupal\Core\Template;
+use Twig_NodeVisitorInterface;
+use Twig_NodeInterface;
+use Twig_Environment;
+use Twig_Node_Print;
+use Twig_Node;
+use Twig_Node_Expression_Function;
+
+class TwigNodeVisitor implements Twig_NodeVisitorInterface {
+
+  /**
+   * Called before child nodes are visited.
+   *
+   * @param Twig_NodeInterface $node The node to visit
+   * @param Twig_Environment   $env  The Twig environment instance
+   *
+   * @return Twig_NodeInterface The modified node
+   */
+  function enterNode(Twig_NodeInterface $node, Twig_Environment $env) {
+    return $node;
+  }
+
+  /**
+   * Called after child nodes are visited.
+   *
+   * We use this to inject a call to render -> twig_render() before
+   * anything is printed.
+   *
+   * @param Twig_NodeInterface $node The node to visit
+   * @param Twig_Environment   $env  The Twig environment instance
+   *
+   * @return Twig_NodeInterface The modified node
+   */
+  function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) {
+    if ($node instanceof Twig_Node_Print) {
+      $class = get_class($node);
+      return new $class(
+        new Twig_Node_Expression_Function('render', new Twig_Node(array($node->getNode('expr'))), $node->getLine()),
+        $node->getLine()
+      );
+    }
+    return $node;
+  }
+
+  /**
+   * Returns the priority for this visitor.
+   *
+   * Priority should be between -10 and 10 (0 is the default).
+   *
+   * @return integer The priority level
+   */
+  function getPriority() {
+    return 0;
+  }
+}
diff --git a/core/lib/Drupal/Core/Template/TwigReference.php b/core/lib/Drupal/Core/Template/TwigReference.php
new file mode 100644
index 0000000..cce1cfa
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigReference.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigReference.
+ */
+
+namespace Drupal\Core\Template;
+use ArrayObject;
+
+class TwigReference extends ArrayObject {
+
+  /** Internally hold reference to original array */
+  protected $writable_ref = array();
+
+  /**
+   * Sets a reference in the internal storage.
+   */
+  public function setReference(&$array) {
+    $this->exchangeArray($array);
+    $this->writable_ref = &$array;
+  }
+
+  /**
+  * Gets a reference to the internal storage.
+  */
+  public function &getReference() {
+    return $this->writable_ref;
+  }
+
+  /**
+   * Retrieves offset from internal reference.
+   *
+   * In case of a render array, it is wrapped
+   * again within a TwigReference object.
+   *
+   * @link http://php.net/manual/en/arrayaccess.offsetget.php
+   * @param mixed $offset <p>
+   * The offset to retrieve.
+   *
+   * @return mixed Can return all value types.
+   */
+  public function offsetGet($offset) {
+    if (!is_array($this->writable_ref[$offset]) || $offset[0] == '#') {
+      return $this->writable_ref[$offset];
+    }
+
+    // Wrap the returned array in a new TwigReference
+    $x = new TwigReference();
+    $x->setReference($this->writable_ref[$offset]);
+    return $x;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Template/TwigReferenceFunctions.php b/core/lib/Drupal/Core/Template/TwigReferenceFunctions.php
new file mode 100644
index 0000000..52b235b
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigReferenceFunctions.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigReferenceFunctions.
+ */
+
+namespace Drupal\Core\Template;
+
+class TwigReferenceFunctions {
+
+    /**
+     * Magic function to call functions called from twig templates by reference.
+     *
+     * This checks if the array provided by value is containing a reference to the
+     * original version. If yes it replaces the argument with its reference.
+     *
+     * @see TwigReference
+     * @see twig_convert_to_reference
+    */
+    public static function __callStatic($name, $arguments) {
+      foreach ($arguments as $key => $val) {
+        if (is_object($val) && $val instanceof TwigReference) {
+          $arguments[$key] = &$val->getReference();
+        }
+      }
+
+      // Needed to pass by reference -- could also restrict to maximum one argument instead
+      $args = array();
+      foreach($arguments as $key => &$arg) {
+        $args[$key] = &$arg;
+      }
+
+      return call_user_func_array( $name, $args );
+    }
+}
+
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
new file mode 100644
index 0000000..a610f81
--- /dev/null
+++ b/core/themes/engines/twig/twig.engine
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * @file
+ * Handles integration of Twig templates with the Drupal theme system.
+ */
+use Drupal\Core\Template\TwigNodeVisitor;
+use Drupal\Core\Template\TwigReferenceFunctions;
+use Drupal\Core\Template\TwigReference;
+
+/**
+ * Implements hook_theme().
+ */
+function twig_theme($existing, $type, $theme, $path) {
+  return drupal_find_theme_templates($existing, '.twig', $path);
+}
+
+/**
+ * Implements hook_extension().
+ */
+function twig_extension() {
+  return '.twig';
+}
+
+/**
+ * Implements hook_init().
+ */
+function twig_init($template) {
+  $file = dirname($template->filename) . '/template.php';
+  if (file_exists($file)) {
+    include_once DRUPAL_ROOT . '/' . $file;
+  }
+}
+
+/**
+ * Convert into a TwigReference object.
+ *
+ * This is needed to workaround a limitation
+ * of twig to only provide variables by value.
+ *
+ * By saving a reference to the original array
+ * the variable can be passed by reference.
+ *
+ * Only complex variables that are no attributes
+ * are converted.
+ *
+ * @see TwigReference
+ * @see TwigReferenceFunctions
+ *
+*/
+function twig_convert_to_reference(&$elements, &$original) {
+  $needs_selfreference = FALSE;
+
+  foreach ($elements as $key => $val) {
+    if (!is_array($val) || $key[0] == '#') {
+      continue;
+    }
+    if (!is_numeric($key)) {
+      $needs_selfreference = TRUE;
+    }
+  }
+  if ($needs_selfreference) {
+    $elements = new TwigReference();
+    $elements->setReference($original);
+  }
+}
+
+/**
+ * Render twig templates.
+ *
+ * All complex variables that are no attributes are converted
+ * into TwigReference ArrayObjects that allow passing of arrays
+ * by reference within twig.
+ *
+ * @see twig_convert_to_reference
+ */
+function twig_render_template($template_file, $variables) {
+  $original = $variables;
+  foreach ($variables as $key => $val) {
+    if (!is_array($val) || $key[0] == '#') {
+      continue;
+    }
+    twig_convert_to_reference($variables[$key], $original[$key]);
+  }
+
+  return twig()->loadTemplate($template_file)->render($variables);
+}
+
+/**
+ * Singleton for the twig environment
+ *
+ * This is used for rendering templates with twig.
+ */
+function twig() {
+  $twig = &drupal_static(__FUNCTION__);
+
+  if (empty($twig)) {
+    // @TODO: Maybe we will have our own loader later.
+    $loader = new Twig_Loader_Filesystem(DRUPAL_ROOT);
+    $twig = new Twig_Environment($loader, array(
+        'cache' => DRUPAL_ROOT . '/compilation_cache',
+        // @TODO Maybe enable cache, once rebuild has been sorted.
+        'cache' => FALSE,
+        // @TODO: Remove in followup issue.
+        'autoescape' => FALSE,
+        // @TODO: Remove in followup issue to handle exceptions gracefully.
+        'strict_variables' => FALSE,
+    ));
+
+    // The node visitor is needed to wrap all variables with render -> twig_render() function.
+    $twig->addNodeVisitor(new TwigNodeVisitor);
+
+    $functions = array(
+      'hide' => 'twig_hide',
+      'render' => 'twig_render',
+      'show' => 'twig_show',
+      'unset' => 'unset'
+    );
+    $filters = array(
+      't' => 't'
+    );
+
+    foreach ($functions as $function => $php_function) {
+      $twig->addFunction($function, new Twig_Function_Function('Drupal\Core\Template\TwigReferenceFunctions::' . $php_function));
+    }
+
+    foreach ($filters as $filter => $php_function) {
+      $twig->addFilter($filter, new Twig_Filter_Function('Drupal\Core\Template\TwigReferenceFunctions::' . $php_function));
+    }
+
+    // @TODO remove URL once http://drupal.org/node/1778610 is resolved.
+    $twig->addFunction('url', new Twig_Function_Function('Drupal\Core\Template\TwigReferenceFunctions::' . 'url'));
+
+    // Allow other modules to alter the twig experience
+    drupal_alter('twig_engine', $twig);
+  }
+
+  return $twig;
+}
+
+/**
+ * Wrapper around render for twig content.
+ *
+ * @param arg String, Object or Render Array
+ */
+function twig_render($arg) {
+  // Keep Twig_Markup objects intact to prepare for later autoescaping support
+  if ($arg instanceOf Twig_Markup) {
+    return $arg;
+  }
+
+  if (is_string($arg) || (is_object($arg) && method_exists($arg, '__toString'))) {
+    return (string) $arg;
+  }
+
+  // This is a normal render array.
+  return render($arg);
+}
+
+/**
+ * Wrapper around hide() that does not return the content.
+ */
+function twig_hide(&$element) {
+  hide($element);
+}
+
+/**
+ * Wrapper around show() that does not return the content.
+ */
+function twig_show(&$element) {
+  show($element);
+}
diff --git a/core/themes/stark/stark.info b/core/themes/stark/stark.info
index fbab2a0..6fb1348 100644
--- a/core/themes/stark/stark.info
+++ b/core/themes/stark/stark.info
@@ -3,4 +3,5 @@ description = This theme demonstrates Drupal's default HTML markup and CSS style
 package = Core
 version = VERSION
 core = 8.x
+engine = twig
 stylesheets[all][] = css/layout.css
diff --git a/core/themes/stark/templates/node/node.twig b/core/themes/stark/templates/node/node.twig
new file mode 100644
index 0000000..0986a52
--- /dev/null
+++ b/core/themes/stark/templates/node/node.twig
@@ -0,0 +1,112 @@
+{#
+ /**
+ * @file
+ * Default theme implementation to display a node.
+ *
+ * Available variables:
+ * - $title: the (sanitized) title of the node.
+ * - $content: An array of node items. Use render($content) to print them all,
+ *   or print a subset such as render($content['field_example']). Use
+ *   hide($content['field_example']) to temporarily suppress the printing of a
+ *   given element.
+ * - $user_picture: The node author's picture from user-picture.tpl.php.
+ * - $date: Formatted creation date. Preprocess functions can reformat it by
+ *   calling format_date() with the desired parameters on the $created variable.
+ * - $name: Themed username of node author output from theme_username().
+ * - $node_url: Direct url of the current node.
+ * - $display_submitted: Whether submission information should be displayed.
+ * - $submitted: Submission information created from $name and $date during
+ *   template_preprocess_node().
+ * - $attributes: An instance of Attributes class that can be manipulated as an
+ *    array and printed as a string.
+ *    It includes the 'class' information, which includes:
+ *   - node: The current template type; for example, "theming hook".
+ *   - node-[type]: The current node type. For example, if the node is a
+ *     "Article" it would result in "node-article". Note that the machine
+ *     name will often be in a short form of the human readable label.
+ *   - view-mode-[view_mode]: The View Mode of the node; for example, "teaser"
+ *     or "full".
+ *   - preview: Nodes in preview mode.
+ *   The following are controlled through the node publishing options.
+ *   - promoted: Nodes promoted to the front page.
+ *   - sticky: Nodes ordered above other non-sticky nodes in teaser
+ *     listings.
+ *   - unpublished: Unpublished nodes visible only to administrators.
+ * - $title_prefix (array): An array containing additional output populated by
+ *   modules, intended to be displayed in front of the main title tag that
+ *   appears in the template.
+ * - $title_suffix (array): An array containing additional output populated by
+ *   modules, intended to be displayed after the main title tag that appears in
+ *   the template.
+ *
+ * Other variables:
+ * - $node: Full node entity. Contains data that may not be safe.
+ * - $type: Node type; for example, page, article, etc.
+ * - $comment_count: Number of comments attached to the node.
+ * - $uid: User ID of the node author.
+ * - $created: Time the node was published formatted in Unix timestamp.
+ * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in
+ *   teaser listings.
+ * - $id: Position of the node. Increments each time it's output.
+ *
+ * Node status variables:
+ * - $view_mode: View mode; for example, "teaser" or "full".
+ * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser').
+ * - $page: Flag for the full page state.
+ * - $promote: Flag for front page promotion state.
+ * - $sticky: Flags for sticky post setting.
+ * - $status: Flag for published status.
+ * - $comment: State of comment settings for the node.
+ * - $readmore: Flags true if the teaser content of the node cannot hold the
+ *   main body content.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * Field variables: for each field instance attached to the node a corresponding
+ * variable is defined; for example, $node->body becomes $body. When needing to
+ * access a field's raw values, developers/themers are strongly encouraged to
+ * use these variables. Otherwise they will have to explicitly specify the
+ * desired field language; for example, $node->body['en'], thus overriding any
+ * language negotiation rule that was previously applied.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_node()
+ * @see template_process()
+ */
+ * TODO: move node classes into preprocess line 86
+ * @todo: Get hide() working for line(s) 105 & 106 & 114
+#}
+{#
+  @todo: might be a good idea to remove the id attribute, because if that gets
+  renderd twice on a page this is invalid CSS
+  two lists in different view modes.. for example
+#}
+<article id="node-{{ node.nid }}" class="{{ classes }}" {{- attributes }}>
+
+  {{ title_prefix }}
+  {% if page != true %}
+    <h2{{ title_attributes }}>
+      <a href="{{ node_url }}">{{ title }}</a>
+    </h2>
+  {% endif %}
+  {{ title_suffix }}
+
+  {% if display_submitted %}
+    <footer>
+      {{ user_picture }}
+      <p class="submitted">{{ submitted }}</p>
+    </footer>
+  {% endif %}
+
+  <div class="content" {{- content_attributes }}>
+    {# We hide the comments and links now so that we can render them later. #}
+    {{ hide(content.comments) }}
+    {{ hide(content.links) }}
+    {{ content }}
+  </div>
+
+  {{ content.links }}
+  {{ content.comments }}
+
+</article>
diff --git a/core/themes/stark/templates/theme.inc/datetime.twig b/core/themes/stark/templates/theme.inc/datetime.twig
new file mode 100644
index 0000000..a74aeac
--- /dev/null
+++ b/core/themes/stark/templates/theme.inc/datetime.twig
@@ -0,0 +1,31 @@
+{#
+/**
+ * Returns HTML for a date / time.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - timestamp: (optional) A UNIX timestamp for the datetime attribute. If the
+ *     datetime cannot be represented as a UNIX timestamp, use a valid datetime
+ *     attribute value in $variables['attributes']['datetime'].
+ *   - text: (optional) The content to display within the <time> element. Set
+ *     'html' to TRUE if this value is already sanitized for output in HTML.
+ *     Defaults to a human-readable representation of the timestamp value or the
+ *     datetime attribute value using format_date().
+ *     When invoked as #theme or #theme_wrappers of a render element, the
+ *     rendered #children are autoamtically taken over as 'text', unless #text
+ *     is explicitly set.
+ *   - attributes: (optional) An associative array of HTML attributes to apply
+ *     to the <time> element. A datetime attribute in 'attributes' overrides the
+ *     'timestamp'. To create a valid datetime attribute value from a UNIX
+ *     timestamp, use format_date() with one of the predefined 'html_*' formats.
+ *   - html: (optional) Whether 'text' is HTML markup (TRUE) or plain-text
+ *     (FALSE). Defaults to FALSE. For example, to use a SPAN tag within the
+ *     TIME element, this must be set to TRUE, or the SPAN tag will be escaped.
+ *     It is the responsibility of the caller to properly sanitize the value
+ *     contained in 'text' (or within the SPAN tag in aforementioned example).
+ *
+ * @see template_preprocess_datetime()
+ * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
+ */
+#}
+<time class="{{ attributes.class }}" {{ attributes }}>{{ html?text:(text|e) }}</time>
-- 
1.7.4.1


From 242e50cbacfef82797d8cb61290059b3e7356d74 Mon Sep 17 00:00:00 2001
From: Fabian Franz <github@fabian-franz.de>
Date: Thu, 4 Oct 2012 08:41:00 +0200
Subject: [PATCH 2/2] Issue #1696786: Add a comment to node.twig

* Make it possible to see the difference.
---
 core/themes/stark/templates/node/node.twig |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/core/themes/stark/templates/node/node.twig b/core/themes/stark/templates/node/node.twig
index 0986a52..51d9db8 100644
--- a/core/themes/stark/templates/node/node.twig
+++ b/core/themes/stark/templates/node/node.twig
@@ -82,6 +82,7 @@
   renderd twice on a page this is invalid CSS
   two lists in different view modes.. for example
 #}
+<!-- node.twig - proudly powered by twig engine -->
 <article id="node-{{ node.nid }}" class="{{ classes }}" {{- attributes }}>
 
   {{ title_prefix }}
-- 
1.7.4.1

