From 271593124e3867fd0fb082dbda2e837c8ff4b665 Mon Sep 17 00:00:00 2001
From: Frederic G. MARAND <fgm@osinet.fr>
Date: Mon, 20 Aug 2012 23:37:16 +0200
Subject: [PATCH 2/2] Issue #1026616: introduce an entity render controller. First pass WIP.

- All 4 controllers have been introduced, along with their abstract parent
- view() and buildContent() have been implemented
- original procedural functions have not yet been removed
- not tested, this is just to show where we're going.
---
 .../lib/Drupal/comment/CommentViewController.php   |   91 +++++++++++++++++
 .../lib/Drupal/entity/EntityViewController.php     |  107 ++++++++++++++++++++
 .../entity/EntityViewControllerInterface.php       |   14 +++
 .../node/lib/Drupal/node/NodeViewController.php    |   68 +++++++++++++
 .../lib/Drupal/taxonomy/TermViewController.php     |   59 +++++++++++
 .../user/lib/Drupal/user/UserViewController.php    |   32 ++++++
 6 files changed, 371 insertions(+), 0 deletions(-)
 create mode 100644 core/modules/comment/lib/Drupal/comment/CommentViewController.php
 create mode 100644 core/modules/entity/lib/Drupal/entity/EntityViewController.php
 create mode 100644 core/modules/entity/lib/Drupal/entity/EntityViewControllerInterface.php
 create mode 100644 core/modules/node/lib/Drupal/node/NodeViewController.php
 create mode 100644 core/modules/taxonomy/lib/Drupal/taxonomy/TermViewController.php
 create mode 100644 core/modules/user/lib/Drupal/user/UserViewController.php

diff --git a/core/modules/comment/lib/Drupal/comment/CommentViewController.php b/core/modules/comment/lib/Drupal/comment/CommentViewController.php
new file mode 100644
index 0000000..3d01cf5
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentViewController.php
@@ -0,0 +1,91 @@
+<?php
+namespace Drupal\comment;
+
+use Drupal\comment\Comment;
+use Drupal\entity\EntityInterface;
+use Drupal\entity\EntityViewController;
+use Drupal\node\Node;
+
+class CommentViewController extends EntityViewController {
+
+  /**
+   * $dependencies msust contain a valid Node as the value for the "node" key.
+   */
+  protected static function buildContent(EntityInterface $comment, $view_mode, $langcode, array $dependencies = array()) {
+    // Remove previously built content, if exists.
+    $comment->content = array();
+
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+    drupal_alter('entity_view_mode', $view_mode, $comment, $context);
+
+    // Build fields content.
+    field_attach_prepare_view('comment', array($comment->cid => $comment), $view_mode, $langcode);
+    entity_prepare_view('comment', array($comment->cid => $comment), $langcode);
+    $comment->content += field_attach_view('comment', $comment, $view_mode, $langcode);
+
+    $comment->content['links'] = array(
+        '#theme' => 'links__comment',
+        '#pre_render' => array('drupal_pre_render_links'),
+        '#attributes' => array('class' => array('links', 'inline')),
+    );
+    if (empty($comment->in_preview)) {
+      $comment->content['links']['comment'] = array(
+        '#theme' => 'links__comment__comment',
+        // The "node" key is specified to be present, so no need to check.
+        '#links' => comment_links($comment, $dependencies['node']),
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+    }
+  }
+
+  protected static function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode, array $dependencies) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+    $node = $dependencies['node'];
+    $return = array_merge($return, array(
+      '#theme' => 'comment__node_' . $node->type,
+      '#node' => $node,
+    ));
+    return $return;
+  }
+
+  public static function normalizeArguments(&$view_mode, array &$dependencies = array(), &$langcode) {
+    parent::normalizeArguments($view_mode, $dependencies, $langcode);
+
+    if (!isset($dependencies['node'])) {
+      $node = node_load($comment->nid);
+      if (empty($node)) {
+        throw new InvalidArgumentException(t('Invalid node for comment'));
+      }
+      $dependencies['node'] = $node;
+    }
+  }
+
+  protected static function prepareBuild(array $build, EntityInterface $comment, $view_mode, $dependencies = array(), $langcode = NULL) {
+	  if (empty($comment->in_preview)) {
+	    $prefix = '';
+	    $is_threaded = isset($comment->divs) && variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED;
+
+	    // Add 'new' anchor if needed.
+	    if (!empty($comment->first_new)) {
+	      $prefix .= "<a id=\"new\"></a>\n";
+	    }
+
+	    // Add indentation div or close open divs as needed.
+	    if ($is_threaded) {
+	      $prefix .= $comment->divs <= 0 ? str_repeat('</div>', abs($comment->divs)) : "\n" . '<div class="indented">';
+	    }
+
+	    // Add anchor for each comment.
+	    $prefix .= "<a id=\"comment-$comment->cid\"></a>\n";
+	    $build['#prefix'] = $prefix;
+
+	    // Close all open divs.
+	    if ($is_threaded && !empty($comment->divs_final)) {
+	      $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
+	    }
+	  }
+
+    return $build;
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/EntityViewController.php b/core/modules/entity/lib/Drupal/entity/EntityViewController.php
new file mode 100644
index 0000000..6512426
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/EntityViewController.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\EntityViewController.
+ */
+
+namespace Drupal\entity;
+
+/**
+ * Base class for entity view controllers.
+ */
+abstract class EntityViewController implements EntityViewControllerInterface {
+
+  /**
+   * Build the structured $content property on the entity.
+   *
+   * @param EntityInterface $comment
+   * @param string $view_mode
+   * @param string $langcode
+   * @param array $dependencies
+   *   Required contents are per-entity type.
+   */
+  abstract public static function buildContent(EntityInterface $entity, $view_mode, $langcode, array $dependencies = array());
+
+  /**
+   * Provide entity-specific defaults to the build process.
+   *
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param string $langcode
+   *
+   * @return array
+   */
+  protected static function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $entity_type = $entity->entityType();
+    $return = array(
+      '#theme' => $entity_type,
+      "#$entity_type" => $entity,
+      '#view_mode' => $view_mode,
+      '#langcode' => $langcode,
+    );
+
+    return $return;
+  }
+
+  /**
+   * Fill in the needed default values from the environment.
+   *
+   * After that step, all needed parameters can be required instead of having to
+   * be defaulted each time.
+   *
+   * @param string $view_mode
+   * @param array $dependencies
+   * @param string $langcode
+   */
+  public static function normalizeArguments(&$view_mode, array &$dependencies = array(), &$langcode) {
+    if (!isset($langcode)) {
+      $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
+    }
+  }
+
+  /**
+   * Specific per-entity building.
+   *
+   * @param array $build
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param array $dependencies
+   * @param string $langcode
+   *
+   * @return array
+   *   The build array.
+   */
+  abstract protected static function prepareBuild(array $build, EntityInterface $entity, $view_mode, $dependencies = array(), $langcode = NULL);
+
+  /**
+   *
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param array $dependencies
+   * @param string $langcode
+   *
+   * @throws \InvalidArgumentException
+   */
+  public static function view(EntityInterface $entity, $view_mode, $dependencies = array(), $langcode = NULL) {
+    static::normalizeArguments($view_mode, $dependencies, $langcode);
+    static::buildContent($entity, $view_mode, $langcode, $dependencies);
+
+    $view_hook = $entity->entityType() . '_view';
+    module_invoke_all($view_hook, $entity, $view_mode, $langcode);
+    module_invoke_all('entity_view', $entity, $view_mode, $langcode);
+
+    $build = $entity->content;
+	  // We don't need duplicate rendering info in $entity->content.
+	  unset($entity->content);
+
+	  $build += static::getBuildDefaults($entity, $view_mode, $langcode);
+	  $build = static::prepareBuild($build, $entity, $view_mode, $dependencies, $langcode);
+
+	  // Allow modules to modify the structured comment.
+	  drupal_alter(array($view_hook, 'entity_view'), $build, $entity);
+
+	  return $build;
+  }
+}
+
diff --git a/core/modules/entity/lib/Drupal/entity/EntityViewControllerInterface.php b/core/modules/entity/lib/Drupal/entity/EntityViewControllerInterface.php
new file mode 100644
index 0000000..70c7846
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/EntityViewControllerInterface.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\EntityViewControllerInterface.
+ */
+
+namespace Drupal\entity;
+
+/**
+ * Defines a common interface for entity view controller classes.
+ */
+interface EntityViewControllerInterface {
+}
diff --git a/core/modules/node/lib/Drupal/node/NodeViewController.php b/core/modules/node/lib/Drupal/node/NodeViewController.php
new file mode 100644
index 0000000..fdea312
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeViewController.php
@@ -0,0 +1,68 @@
+<?php
+namespace Drupal\node;
+
+use Drupal\entity\EntityInterface;
+use Drupal\entity\EntityViewController;
+
+class NodeViewController extends EntityViewController {
+
+  protected static function buildContent(EntityInterface $node, $view_mode, $langcode, array $dependencies = array()) {
+    // Remove previously built content, if exists.
+    $node->content = array();
+
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+    drupal_alter('entity_view_mode', $view_mode, $node, $context);
+
+    // The 'view' hook can be implemented to overwrite the default function
+    // to display nodes.
+    if (node_hook($node, 'view')) {
+      $node = node_invoke($node, 'view', $view_mode, $langcode);
+    }
+
+    // Build fields content.
+    // In case of a multiple view, node_view_multiple() already ran the
+    // 'prepare_view' step. An internal flag prevents the operation from running
+    // twice.
+    field_attach_prepare_view('node', array($node->nid => $node), $view_mode, $langcode);
+    entity_prepare_view('node', array($node->nid => $node), $langcode);
+    $node->content += field_attach_view('node', $node, $view_mode, $langcode);
+
+	  $node->content['links'] = array(
+	    '#theme' => 'links__node',
+	    '#pre_render' => array('drupal_pre_render_links'),
+	    '#attributes' => array('class' => array('links', 'inline')),
+	  );
+
+	  // Always display a read more link on teasers because we have no way
+	  // to know when a teaser view is different than a full view.
+	  $links = array();
+	  if ($view_mode == 'teaser') {
+	    $node_title_stripped = strip_tags($node->label());
+	    $links['node-readmore'] = array(
+        'title' => t('Read more<span class="element-invisible"> about @title</span>', array('@title' => $node_title_stripped)),
+        'href' => 'node/' . $node->nid,
+        'html' => TRUE,
+        'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped),
+	    );
+	  }
+
+    $node->content['links']['node'] = array(
+      '#theme' => 'links__node__node',
+      '#links' => $links,
+      '#attributes' => array('class' => array('links', 'inline')),
+    );
+  }
+
+  protected static function prepareBuild(array $build, EntityInterface $node, $view_mode, $dependencies = array(), $langcode = NULL) {
+    // Add contextual links for this node, except when the node is already being
+	  // displayed on its own page. Modules may alter this behavior (for example,
+	  // to restrict contextual links to certain view modes) by implementing
+	  // hook_node_view_alter().
+	  if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
+	    $build['#contextual_links']['node'] = array('node', array($node->nid));
+	  }
+
+	  return $build;
+  }
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewController.php
new file mode 100644
index 0000000..1bb9c41
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewController.php
@@ -0,0 +1,59 @@
+<?php
+namespace Drupal\taxonomy;
+
+use Drupal\entity\EntityInterface;
+use Drupal\entity\EntityViewController;
+
+class TermViewController extends EntityViewController {
+
+  protected static function buildContent(EntityInterface $node, $view_mode, $langcode, array $dependencies = array()) {
+    // Remove previously built content, if exists.
+    $term->content = array();
+
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+    drupal_alter('entity_view_mode', $view_mode, $term, $context);
+
+    // The 'view' hook can be implemented to overwrite the default function
+    // to display nodes.
+    if (node_hook($node, 'view')) {
+      $node = node_invoke($node, 'view', $view_mode, $langcode);
+    }
+
+    // Try to add in the core taxonomy pieces like description and nodes
+	  $type = 'taxonomy_term';
+	  $settings = field_view_mode_settings($term->entityType(), $term->bundle());
+	  $fields = field_extra_fields_get_display($term->entityType(), $term->bundle(), $view_mode);
+	  if (!empty($term->description) && isset($fields['description']) && $fields['description']['visible']) {
+	    $term->content['description'] = array(
+	      '#markup' => check_markup($term->description, $term->format, '', TRUE),
+	      '#weight' => $fields['description']['weight'],
+	      '#prefix' => '<div class="taxonomy-term-description">',
+	      '#suffix' => '</div>',
+	    );
+	  }
+
+    // Build fields content.
+    // In case of a multiple view, taxonomy_term_view_multiple() already ran the
+    // 'prepare_view' step. An internal flag prevents the operation from running
+    // twice.
+    field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
+    entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
+    $term->content += field_attach_view('taxonomy_term', $term, $view_mode, $langcode);
+  }
+
+  protected static function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode, array $dependencies) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+
+    // TODO: rename "term" to "taxonomy_term" in theme_taxonomy_term() ?
+    $return['term'] = $return['taxonomy_term'];
+    unset($return['taxonomy_term']);
+
+    return $return;
+  }
+
+  protected static function prepareBuild(array $build, EntityInterface $node, $view_mode, $dependencies = array(), $langcode = NULL) {
+    $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
+    return $build;
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/UserViewController.php b/core/modules/user/lib/Drupal/user/UserViewController.php
new file mode 100644
index 0000000..cfd0f7f
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserViewController.php
@@ -0,0 +1,32 @@
+<?php
+namespace Drupal\user;
+
+use Drupal\entity\EntityInterface;
+use Drupal\entity\EntityViewController;
+
+class UserViewController extends EntityViewController {
+
+  protected static function buildContent(EntityInterface $account, $view_mode, $langcode, array $dependencies = array()) {
+    // Remove previously built content, if exists.
+    $account->content = array();
+
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+    drupal_alter('entity_view_mode', $view_mode, $account, $context);
+
+	  // Build fields content.
+	  field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
+	  entity_prepare_view('user', array($account->uid => $account), $langcode);
+	  $account->content += field_attach_view('user', $account, $view_mode, $langcode);
+  }
+
+  protected static function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode, array $dependencies) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+
+    // TODO: rename "theme_user_profile" to "theme_user", and 'account' to 'user' ?
+    $return['#theme'] = 'user_profile';
+    $return['#account'] = $return['#user'];
+
+    return $return;
+  }
+}
-- 
1.7.4.1

