From 07298d66e40f0ade1d63e05129826d710f228773 Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Fri, 16 Aug 2013 14:49:18 -0500
Subject: [PATCH] Issue #1911492 by jibran, Manuel Garcia, derhasi, pcambra,
 dawehner, damiankloip: Fixed Views try to find Custom StylePlugin template in
 core/modules/views/templates.

---
 .../block/Plugin/views/display/Block.php      |  1 +
 .../Drupal/comment/Plugin/views/row/Rss.php   |  1 +
 .../Plugin/views/display/EntityReference.php  |  1 +
 .../Plugin/views/row/EntityReference.php      |  1 +
 .../Plugin/views/style/EntityReference.php    |  1 +
 .../lib/Drupal/node/Plugin/views/row/Rss.php  |  1 +
 .../Drupal/views/Plugin/views/PluginBase.php  | 23 ++++++++
 .../lib/Drupal/views/Tests/ModuleTest.php     |  2 +-
 .../Drupal/views/Tests/ViewsTemplateTest.php  | 55 +++++++++++++++++++
 .../views.view.test_view_display_template.yml | 46 ++++++++++++++++
 .../views/display/DisplayNoAreaTest.php       |  1 +
 .../Plugin/views/display/DisplayTest.php      |  1 +
 .../Plugin/views/row/RowTest.php              |  1 +
 .../Plugin/views/style/MappingTest.php        |  1 +
 .../Plugin/views/style/StyleTemplateTest.php  | 37 +++++++++++++
 .../Plugin/views/style/StyleTest.php          |  1 +
 .../views-view-mapping-test.html.twig         | 17 ++++++
 .../views-view-style-template-test.html.twig  |  7 +++
 core/modules/views/views.module               | 45 +++++++++++----
 19 files changed, 230 insertions(+), 13 deletions(-)
 create mode 100644 core/modules/views/lib/Drupal/views/Tests/ViewsTemplateTest.php
 create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_display_template.yml
 create mode 100644 core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTemplateTest.php
 create mode 100644 core/modules/views/tests/modules/views_test_data/templates/views-view-mapping-test.html.twig
 create mode 100644 core/modules/views/tests/modules/views_test_data/templates/views-view-style-template-test.html.twig

diff --git a/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php b/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php
index 43198783660a..bf3dca4b1c3d 100644
--- a/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php
+++ b/core/modules/block/lib/Drupal/block/Plugin/views/display/Block.php
@@ -24,6 +24,7 @@
  *   title = @Translation("Block"),
  *   help = @Translation("Display the view as a block."),
  *   theme = "views_view",
+ *   register_theme = FALSE,
  *   uses_hook_block = TRUE,
  *   contextual_links_locations = {"block"},
  *   admin = @Translation("Block")
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php
index 6cc187c52eec..ffd4968e61ab 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/row/Rss.php
@@ -20,6 +20,7 @@
  *   title = @Translation("Comment"),
  *   help = @Translation("Display the comment as RSS."),
  *   theme = "views_view_row_rss",
+ *   register_theme = FALSE,
  *   base = {"comment"},
  *   display_types = {"feed"}
  * )
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/display/EntityReference.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/display/EntityReference.php
index fc46aa60897a..0bd74943c2eb 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/display/EntityReference.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/display/EntityReference.php
@@ -26,6 +26,7 @@
  *   admin = @Translation("Entity Reference Source"),
  *   help = @Translation("Selects referenceable entities for an entity reference field."),
  *   theme = "views_view",
+ *   register_theme = FALSE,
  *   uses_hook_menu = FALSE,
  *   entity_reference_display = TRUE
  * )
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/row/EntityReference.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/row/EntityReference.php
index 069689604325..f2d7aa9d4de9 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/row/EntityReference.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/row/EntityReference.php
@@ -21,6 +21,7 @@
  *   title = @Translation("Entity Reference inline fields"),
  *   help = @Translation("Displays the fields with an optional template."),
  *   theme = "views_view_fields",
+ *   register_theme = FALSE,
  *   display_types = {"entity_reference"}
  * )
  */
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/style/EntityReference.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/style/EntityReference.php
index c6068562a0a9..75f6877d3881 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/style/EntityReference.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/views/style/EntityReference.php
@@ -21,6 +21,7 @@
  *   title = @Translation("Entity Reference list"),
  *   help = @Translation("Returns results as a PHP array of labels and rendered rows."),
  *   theme = "views_view_unformatted",
+ *   register_theme = FALSE,
  *   display_types = {"entity_reference"}
  * )
  */
diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/row/Rss.php b/core/modules/node/lib/Drupal/node/Plugin/views/row/Rss.php
index 9d73565c8f31..124f0bb7337c 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/views/row/Rss.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/views/row/Rss.php
@@ -20,6 +20,7 @@
  *   title = @Translation("Content"),
  *   help = @Translation("Display the content with standard node view."),
  *   theme = "views_view_row_rss",
+ *   register_theme = FALSE,
  *   base = {"node"},
  *   display_types = {"feed"},
  *   module = "node"
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php
index 285f53a60a9c..df65cc5e25e0 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php
@@ -13,6 +13,29 @@
 use Drupal\views\ViewExecutable;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
+/**
+ * Base class for any views plugin types.
+ *
+ * Via the @Plugin definition the plugin may specify a theme function or
+ * template to be used for the plugin. It also can auto-register the theme
+ * implementation for that file or function.
+ * - theme: the theme implementation to use in the plugin. This may be the name
+ *   of the function (without theme_ prefix) or the template file (without
+ *   template engine extension).
+ *   If a template file should be used, the file has to be placed in the
+ *   module's templates folder.
+ *   Example: theme = "mymodule_row" of module "mymodule" will implement either
+ *   theme_mymodule_row() or mymodule-row.tpl.php in the
+ *   [..]/modules/mymodule/templates folder.
+ * - register_theme: (optional) When set to TRUE (default) the theme is
+ *   registered automatically. When set to FALSE the plugin reuses an existing
+ *   theme implementation, defined by another module or views plugin.
+ * - theme_file: (optional) the location of an include file that may hold the
+ *   theme or preprocess function. The location has to be relative to module's
+ *   root directory.
+ * - module: machine name of the module. It must be present for any plugin that
+ *   wants to register a theme.
+ */
 abstract class PluginBase extends ComponentPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Tests/ModuleTest.php b/core/modules/views/lib/Drupal/views/Tests/ModuleTest.php
index 925294c17049..87dc3a59cf61 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ModuleTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ModuleTest.php
@@ -247,7 +247,7 @@ public function testViewsFetchPluginNames() {
     // Test using the 'test' style plugin type only returns the test_style and
     // mapping_test plugins.
     $plugins = views_fetch_plugin_names('style', 'test');
-    $this->assertIdentical(array_keys($plugins), array('mapping_test', 'test_style'));
+    $this->assertIdentical(array_keys($plugins), array('mapping_test', 'test_style', 'test_template_style'));
 
     // Test a non existent style plugin type returns no plugins.
     $plugins = views_fetch_plugin_names('style', $this->randomString());
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewsTemplateTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewsTemplateTest.php
new file mode 100644
index 000000000000..f6be8770bfab
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewsTemplateTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Tests\ViewsTemplateTest.
+ */
+
+namespace Drupal\views\Tests;
+
+use Drupal\views\Tests\ViewTestBase;
+use Drupal\views\Views;
+
+/**
+ * Tests the views custom templates.
+ *
+ * @see Drupal\views_test_data\Plugin\views\style\StyleTemplateTest
+ */
+class ViewsTemplateTest extends ViewTestBase {
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = array('test_view_display_template');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'View template tests',
+      'description' => 'Tests the template retrieval of views.',
+      'group' => 'Views'
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->enableViewsTestModule();
+  }
+
+  /**
+   * Tests render functionality.
+   */
+  public function testTemplate() {
+
+    // Make sure that the rendering just calls the preprocess function once.
+    $view = Views::getView('test_view_display_template');
+    $output = $view->preview();
+
+    // Check if we got the rendered output of our template file.
+    $this->assertTrue(strpos(drupal_render($output), 'This module defines its own display template.') !== FALSE, 'Display plugin DisplayTemplateTest defines its own template.');
+
+  }
+
+}
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_display_template.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_display_template.yml
new file mode 100644
index 000000000000..f005f0075297
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_display_template.yml
@@ -0,0 +1,46 @@
+base_field: id
+base_table: views_test_data
+core: 8.x
+description: ''
+status: '1'
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: '1'
+    display_options:
+      access: {  }
+      cache: {  }
+      query: {  }
+      pager: {  }
+      style:
+        type: test_template_style
+      row:
+        type: fields
+        options: {  }
+      fields:
+        id:
+          id: id
+          table: views_test_data
+          field: id
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: '0'
+          alter: {  }
+          provider: views_test_config
+      filters: {  }
+      sorts: {  }
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments: {  }
+label: test_view_display_template
+module: views
+id: test_view_display_template
+tag: ''
+uuid: a8e37ebf-573e-4cb9-83f5-a259f12bdc7a
+langcode: en
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayNoAreaTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayNoAreaTest.php
index bc208497c85f..1adab692faf1 100644
--- a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayNoAreaTest.php
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayNoAreaTest.php
@@ -17,6 +17,7 @@
  *   id = "display_no_area_test",
  *   title = @Translation("Display test no area"),
  *   theme = "views_view",
+ *   register_theme = FALSE,
  *   contextual_links_locations = {"view"}
  * )
  */
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayTest.php
index 83a4336af42e..7f35dafb17e8 100644
--- a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayTest.php
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/display/DisplayTest.php
@@ -18,6 +18,7 @@
  *   id = "display_test",
  *   title = @Translation("Display test"),
  *   theme = "views_view",
+ *   register_theme = FALSE,
  *   contextual_links_locations = {"view"}
  * )
  */
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/row/RowTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/row/RowTest.php
index d6dedf9dcad0..400ccc32464d 100644
--- a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/row/RowTest.php
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/row/RowTest.php
@@ -21,6 +21,7 @@
  *   title = @Translation("Test row plugin"),
  *   help = @Translation("Provides a generic row test plugin."),
  *   theme = "views_view_row_test",
+ *   module = "views_test_data",
  *   display_types = {"normal", "test"}
  * )
  */
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php
index 3a4e4865fadd..4dc3ea85f51c 100644
--- a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/MappingTest.php
@@ -22,6 +22,7 @@
  *   title = @Translation("Field mapping"),
  *   help = @Translation("Maps specific fields to specific purposes."),
  *   theme = "views_view_mapping_test",
+ *   module = "views_test_data",
  *   display_types = {"normal", "test"}
  * )
  */
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTemplateTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTemplateTest.php
new file mode 100644
index 000000000000..df7547acebfa
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTemplateTest.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\views_test_data\Plugin\views\style\StyleTemplateTest.
+ */
+
+namespace Drupal\views_test_data\Plugin\views\style;
+
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\views\Plugin\views\style\StylePluginBase;
+
+/**
+ * Provides a general test style template plugin.
+ *
+ * @ingroup views_style_plugins
+ *
+ * @Plugin(
+ *   id = "test_template_style",
+ *   module = "views_test_data",
+ *   title = @Translation("Test style template plugin"),
+ *   help = @Translation("Provides a generic style template test plugin."),
+ *   theme = "views_view_style_template_test",
+ *   display_types = {"normal", "test"}
+ * )
+ */
+class StyleTemplateTest extends StylePluginBase {
+
+  /**
+   * Can the style plugin use row plugins.
+   *
+   * @var bool
+   */
+  protected $usesRowPlugin = TRUE;
+
+}
diff --git a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTest.php b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTest.php
index 26a31121057d..b6fc20a83542 100644
--- a/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTest.php
+++ b/core/modules/views/tests/modules/views_test_data/lib/Drupal/views_test_data/Plugin/views/style/StyleTest.php
@@ -21,6 +21,7 @@
  *   title = @Translation("Test style plugin"),
  *   help = @Translation("Provides a generic style test plugin."),
  *   theme = "views_view_style_test",
+ *   register_theme = FALSE,
  *   display_types = {"normal", "test"}
  * )
  */
diff --git a/core/modules/views/tests/modules/views_test_data/templates/views-view-mapping-test.html.twig b/core/modules/views/tests/modules/views_test_data/templates/views-view-mapping-test.html.twig
new file mode 100644
index 000000000000..8d30f81e93ea
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_data/templates/views-view-mapping-test.html.twig
@@ -0,0 +1,17 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a view of mapping_test rows.
+ *
+ * Available variables:
+ *   - rows: A list of view rows.
+ *   - options: Various view options, including the row style mapping.
+ *   - view: The view object.
+ *   - element: Render array of views rows.
+ *
+ * @see template_preprocess_views_view_mapping_test()
+ *
+ * @ingroup themeable
+ */
+#}
+{{ element }}
diff --git a/core/modules/views/tests/modules/views_test_data/templates/views-view-style-template-test.html.twig b/core/modules/views/tests/modules/views_test_data/templates/views-view-style-template-test.html.twig
new file mode 100644
index 000000000000..f065ee1a8c43
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_data/templates/views-view-style-template-test.html.twig
@@ -0,0 +1,7 @@
+{#
+/**
+ * @file
+ * Views template test template to test the module defined templates.
+ */
+#}
+This module defines its own display template.
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index cbb04b26952c..81b3e5962f45 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -78,7 +78,10 @@ function views_pre_render_view_element($element) {
 }
 
 /**
- * Implement hook_theme(). Register views theming functions.
+ * Implements hook_theme().
+ *
+ * Register views theming functions and those that are defined via views plugin
+ * definitions.
  */
 function views_theme($existing, $type, $theme, $path) {
   Drupal::moduleHandler()->loadInclude('views', 'inc', 'views.theme');
@@ -118,7 +121,10 @@ function views_theme($existing, $type, $theme, $path) {
 
   $plugins = views_get_plugin_definitions();
 
-  // Register theme functions for all style plugins
+  // Register theme functions for all style plugins. It provides a basic auto
+  // implementation of theme functions or template files by using the plugin
+  // definitions (theme, theme_file, module, register_theme). Template files are
+  // assumed to be located in the templates folder.
   foreach ($plugins as $type => $info) {
     foreach ($info as $def) {
       // Not all plugins have theme functions, and they can also explicitly
@@ -126,32 +132,47 @@ function views_theme($existing, $type, $theme, $path) {
       if (!isset($def['theme']) || empty($def['register_theme'])) {
         continue;
       }
+      // For each theme registration we a base directory to look for the
+      // templates folder. This will be in any case the root of the given module
+      // so we always need a module definition.
+      // @todo: watchdog or exception?
+      if (!isset($def['provider'])) {
+        continue;
+      }
 
       $hooks[$def['theme']] = array(
         'variables' => $variables[$type],
       );
 
-      if ($def['module'] == 'views') {
+      // For the views module we ensure views.theme.inc is included.
+      if ($def['provider'] == 'views') {
         $def['theme_file'] = 'views.theme.inc';
       }
-      elseif (isset($def['theme_file'])) {
-        $def['theme_path'] = drupal_get_path('module', $def['module']);
-      }
+      // We always use the module directory as base dir.
+      $module_dir = drupal_get_path('module', $def['provider']);
 
-      if (isset($def['theme_path'])) {
-        $hooks[$def['theme']]['path'] = $def['theme_path'];
-      }
+      // The theme_file definition is always relative to the modules directory.
       if (isset($def['theme_file'])) {
+        $hooks[$def['theme']]['path'] = $module_dir;
         $hooks[$def['theme']]['file'] = $def['theme_file'];
       }
-      if (isset($def['theme_path']) && isset($def['theme_file'])) {
-        $include = DRUPAL_ROOT . '/' . $def['theme_path'] . '/' . $def['theme_file'];
+      // Whenever we got a theme file, we include it directly so we can
+      // auto-detect the theme function.
+      if (isset($def['theme_file'])) {
+        $include = DRUPAL_ROOT . '/' . $module_dir. '/' . $def['theme_file'];
         if (is_file($include)) {
           require_once $include;
         }
       }
+     // If there is no theme function for the given theme definition, we assume
+     // a template file shall be used. By default this file is located in the
+     // /templates directory of the module's folder.
+     // If a module wants to define its own location it has to set
+     // register_theme of the plugin to FALSE and implement hook_theme() by
+     // itself.
       if (!function_exists('theme_' . $def['theme'])) {
-        $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
+        $hooks[$def['theme']]['path'] = $module_dir;
+        $hooks[$def['theme']]['template'] = 'templates/' . drupal_clean_css_identifier($def['theme']);
       }
     }
   }
-- 
GitLab