Commit b15e9213 authored by alexpott's avatar alexpott

Issue #1962234 by dawehner, damiankloip, alexpott: Move views_fetch_fields()...

Issue #1962234 by dawehner, damiankloip, alexpott: Move views_fetch_fields() into an autoloadable class.
parent a93de787
......@@ -88,7 +88,7 @@ public function buildOptionsForm(&$form, &$form_state) {
parent::buildOptionsForm($form, $form_state);
// Get the sorts that apply to our base.
$sorts = views_fetch_fields($this->definition['base'], 'sort');
$sorts = Views::viewsDataHelper()->fetchFields($this->definition['base'], 'sort');
foreach ($sorts as $sort_id => $sort) {
$sort_options[$sort_id] = "$sort[group]: $sort[title]";
}
......
......@@ -831,7 +831,7 @@ protected function defaultDisplayFiltersUser(array $form, array &$form_state) {
// the base table for the view; the taxonomy vocabulary machine_name, for
// example, is stored in taxonomy_vocabulary, not taxonomy_term_data.
module_load_include('inc', 'views_ui', 'admin');
$fields = views_fetch_fields($this->base_table, 'filter');
$fields = Views::viewsDataHelper()->fetchFields($this->base_table, 'filter');
if (isset($fields[$this->base_table . '.' . $bundle_key])) {
$table = $this->base_table;
}
......
......@@ -137,18 +137,18 @@ public static function schemaDefinition() {
public static function viewsData() {
// Declaration of the base table.
$data['views_test_data']['table'] = array(
'group' => t('Views test'),
'group' => 'Views test',
'base' => array(
'field' => 'id',
'title' => t('Views test data'),
'help' => t('Users who have created accounts on your site.'),
'title' => 'Views test data',
'help' => 'Users who have created accounts on your site.',
),
);
// Declaration of fields.
$data['views_test_data']['id'] = array(
'title' => t('ID'),
'help' => t('The test data ID'),
'title' => 'ID',
'help' => 'The test data ID',
'field' => array(
'id' => 'numeric',
),
......@@ -163,8 +163,8 @@ public static function viewsData() {
),
);
$data['views_test_data']['name'] = array(
'title' => t('Name'),
'help' => t('The name of the person'),
'title' => 'Name',
'help' => 'The name of the person',
'field' => array(
'id' => 'standard',
),
......@@ -179,8 +179,8 @@ public static function viewsData() {
),
);
$data['views_test_data']['age'] = array(
'title' => t('Age'),
'help' => t('The age of the person'),
'title' => 'Age',
'help' => 'The age of the person',
'field' => array(
'id' => 'numeric',
),
......@@ -195,8 +195,8 @@ public static function viewsData() {
),
);
$data['views_test_data']['job'] = array(
'title' => t('Job'),
'help' => t('The job of the person'),
'title' => 'Job',
'help' => 'The job of the person',
'field' => array(
'id' => 'standard',
),
......@@ -211,8 +211,8 @@ public static function viewsData() {
),
);
$data['views_test_data']['created'] = array(
'title' => t('Created'),
'help' => t('The creation date of this record'),
'title' => 'Created',
'help' => 'The creation date of this record',
'field' => array(
'id' => 'date',
),
......
......@@ -8,6 +8,7 @@
namespace Drupal\views\Tests;
use Drupal\Core\Cache\MemoryCounterBackend;
use Drupal\Core\Language\LanguageManager;
use Drupal\views\ViewsData;
/**
......@@ -231,7 +232,7 @@ public function testCacheRequests() {
*/
protected function initViewsData() {
$this->memoryCounterBackend->resetCounter();
$this->viewsData = new ViewsData($this->memoryCounterBackend, $this->container->get('config.factory'), $this->container->get('module_handler'));
$this->viewsData = new ViewsData($this->memoryCounterBackend, $this->container->get('config.factory'), $this->container->get('module_handler'), $this->container->get('language_manager'));
}
/**
......@@ -267,7 +268,8 @@ protected function assertCountIncrement($increment = TRUE) {
protected function viewsData() {
$data = parent::viewsData();
// Tweak the views data to have a base for testing views_fetch_fields().
// Tweak the views data to have a base for testing
// \Drupal\views\ViewsDataHelper::fetchFields().
unset($data['views_test_data']['id']['field']);
unset($data['views_test_data']['name']['argument']);
unset($data['views_test_data']['age']['filter']);
......@@ -282,75 +284,6 @@ protected function viewsData() {
return $data;
}
/**
* Tests the views_fetch_fields function().
*/
public function testViewsFetchFields() {
$this->enableModules(array('views_ui'));
$this->container->get('module_handler')->loadInclude('views_ui', 'inc', 'admin');
$expected = array(
'field' => array(
'name',
'age',
'job',
'created',
),
'argument' => array(
'id',
'age',
'job',
'created',
),
'filter' => array(
'id',
'name',
'job',
'created',
),
'sort' => array(
'id',
'name',
'age',
'created',
),
'area' => array(
'created',
'job',
'age'
),
'header' => array(
'created',
'job',
'age'
),
'footer' => array(
'created',
'job',
),
);
$handler_types = array('field', 'argument', 'filter', 'sort', 'area');
foreach ($handler_types as $handler_type) {
$fields = views_fetch_fields('views_test_data', $handler_type);
$expected_keys = array_walk($expected[$handler_type], function(&$item) {
$item = "views_test_data.$item";
});
$this->assertEqual($expected_keys, array_keys($fields), format_string('Handlers of type @handler_type are listed as expected.', array('@handler_type' => $handler_type)));
}
// Check for subtype filtering, so header and footer.
foreach (array('header', 'footer') as $sub_type) {
$fields = views_fetch_fields('views_test_data', 'area', FALSE, $sub_type);
$expected_keys = array_walk($expected[$sub_type], function(&$item) {
$item = "views_test_data.$item";
});
$this->assertEqual($expected_keys, array_keys($fields), format_string('Sub_type @sub_type is filtered as expected.', array('@sub_type' => $sub_type)));
}
}
/**
* Tests the fetchBaseTables() method.
*/
......
......@@ -24,6 +24,16 @@ public static function viewsData() {
return Drupal::service('views.views_data');
}
/**
* Returns the views data helper service.
*
* @return \Drupal\views\ViewsData
* Returns a views data helper object.
*/
public static function viewsDataHelper() {
return Drupal::service('views.views_data_helper');
}
/**
* Returns the view executable factory service.
*
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager;
/**
* Class to manage and lazy load cached views data.
......@@ -71,6 +72,13 @@ class ViewsData {
*/
protected $moduleHandler;
/**
* The language manager
*
* @var \Drupal\Core\Language\LanguageManager
*/
protected $languageManager;
/**
* Constructs this ViewsData object.
*
......@@ -80,12 +88,15 @@ class ViewsData {
* The configuration factory object to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler class to use for invoking hooks.
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager.
*/
public function __construct(CacheBackendInterface $cache_backend, ConfigFactory $config, ModuleHandlerInterface $module_handler) {
public function __construct(CacheBackendInterface $cache_backend, ConfigFactory $config, ModuleHandlerInterface $module_handler, LanguageManager $language_manager) {
$this->cacheBackend = $cache_backend;
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
$this->langcode = language(Language::TYPE_INTERFACE)->langcode;
$this->langcode = $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode;
$this->skipCache = $config->get('views.settings')->get('skip_cache');
}
......
<?php
/**
* @file
* Contains \Drupal\views\ViewsDataHelper.
*/
namespace Drupal\views;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\String;
/**
* Defines a helper class for stuff related to views data.
*/
class ViewsDataHelper {
/**
* The views data object, containing the cached information.
*
* @var \Drupal\views\ViewsData
*/
protected $data;
/**
* A prepared list of all fields, keyed by base_table and handler type.
*
* @param array
*/
protected $fields;
/**
* Constructs a ViewsData object.
*
* @param \Drupal\views\ViewsData $views_data
* The views data object, containing the cached table information.
*/
public function __construct(ViewsData $views_data) {
$this->data = $views_data;
}
/**
* Fetches a list of all fields available for a given base type.
*
* @param (array|string) $base
* A list or a single base_table, for example node.
* @param string $type
* The handler type, for example field or filter.
* @param bool $grouping
* Should the result grouping by its 'group' label.
* @param string $sub_type
* An optional sub type. E.g. Allows making an area plugin available for
* header only, instead of header, footer, and empty regions.
*
* @return array
* A keyed array of in the form of 'base_table' => 'Description'.
*/
public function fetchFields($base, $type, $grouping = FALSE, $sub_type = NULL) {
if (!$this->fields) {
$data = $this->data->get();
// This constructs this ginormous multi dimensional array to
// collect the important data about fields. In the end,
// the structure looks a bit like this (using nid as an example)
// $strings['nid']['filter']['title'] = 'string'.
//
// This is constructed this way because the above referenced strings
// can appear in different places in the actual data structure so that
// the data doesn't have to be repeated a lot. This essentially lets
// each field have a cheap kind of inheritance.
foreach ($data as $table => $table_data) {
$bases = array();
$strings = array();
$skip_bases = array();
foreach ($table_data as $field => $info) {
// Collect table data from this table
if ($field == 'table') {
// calculate what tables this table can join to.
if (!empty($info['join'])) {
$bases = array_keys($info['join']);
}
// And it obviously joins to itself.
$bases[] = $table;
continue;
}
foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
if (!empty($info[$key])) {
if ($grouping && !empty($info[$key]['no group by'])) {
continue;
}
if ($sub_type && isset($info[$key]['sub_type']) && (!in_array($sub_type, (array) $info[$key]['sub_type']))) {
continue;
}
if (!empty($info[$key]['skip base'])) {
foreach ((array) $info[$key]['skip base'] as $base_name) {
$skip_bases[$field][$key][$base_name] = TRUE;
}
}
elseif (!empty($info['skip base'])) {
foreach ((array) $info['skip base'] as $base_name) {
$skip_bases[$field][$key][$base_name] = TRUE;
}
}
foreach (array('title', 'group', 'help', 'base', 'aliases') as $string) {
// First, try the lowest possible level
if (!empty($info[$key][$string])) {
$strings[$field][$key][$string] = $info[$key][$string];
}
// Then try the field level
elseif (!empty($info[$string])) {
$strings[$field][$key][$string] = $info[$string];
}
// Finally, try the table level
elseif (!empty($table_data['table'][$string])) {
$strings[$field][$key][$string] = $table_data['table'][$string];
}
else {
if ($string != 'base' && $string != 'base') {
$strings[$field][$key][$string] = String::format("Error: missing @component", array('@component' => $string));
}
}
}
}
}
}
foreach ($bases as $base_name) {
foreach ($strings as $field => $field_strings) {
foreach ($field_strings as $type_name => $type_strings) {
if (empty($skip_bases[$field][$type_name][$base_name])) {
$this->fields[$base_name][$type_name]["$table.$field"] = $type_strings;
}
}
}
}
}
}
// If we have an array of base tables available, go through them
// all and add them together. Duplicate keys will be lost and that's
// Just Fine.
if (is_array($base)) {
$strings = array();
foreach ($base as $base_table) {
if (isset($this->fields[$base_table][$type])) {
$strings += $this->fields[$base_table][$type];
}
}
uasort($strings, array('self', 'fetchedFieldSort'));
return $strings;
}
if (isset($this->fields[$base][$type])) {
uasort($this->fields[$base][$type], array($this, 'fetchedFieldSort'));
return $this->fields[$base][$type];
}
return array();
}
/**
* Sort function for fetched fields.
*
* @param array $a
* First item for comparison. The compared items should be associative arrays
* that include a 'group' and a 'title' key.
* @param array $b
* Second item for comparison.
*
* @return int
* Returns -1 if $a comes before $b, 1 other way round and 0 if it cannot be
* decided.
*/
protected static function fetchedFieldSort($a, $b) {
$a_group = Unicode::strtolower($a['group']);
$b_group = Unicode::strtolower($b['group']);
if ($a_group != $b_group) {
return $a_group < $b_group ? -1 : 1;
}
$a_title = Unicode::strtolower($a['title']);
$b_title = Unicode::strtolower($b['title']);
if ($a_title != $b_title) {
return $a_title < $b_title ? -1 : 1;
}
return 0;
}
}
<?php
/**
* @file
* Contains \Drupal\views\Tests\ViewsDataHelperTest.
*/
namespace Drupal\views\Tests;
use Drupal\Component\Utility\String;
use Drupal\Tests\UnitTestCase;
use Drupal\views\ViewsDataHelper;
/**
* Tests the views data helper class.
*
* @see \Drupal\views\ViewsDataHelper
*/
class ViewsDataHelperTest extends UnitTestCase {
public static function getInfo() {
return array(
'name' => 'Views data helper',
'description' => 'Tests the views data helper class.',
'group' => 'Views',
);
}
/**
* Returns the views data definition.
*
* @return array
*/
protected function viewsData() {
$data = ViewTestData::viewsData();
// Tweak the views data to have a base for testing
// \Drupal\views\ViewsDataHelper::fetchFields().
unset($data['views_test_data']['id']['field']);
unset($data['views_test_data']['name']['argument']);
unset($data['views_test_data']['age']['filter']);
unset($data['views_test_data']['job']['sort']);
$data['views_test_data']['created']['area']['id'] = 'text';
$data['views_test_data']['age']['area']['id'] = 'text';
$data['views_test_data']['age']['area']['sub_type'] = 'header';
$data['views_test_data']['job']['area']['id'] = 'text';
$data['views_test_data']['job']['area']['sub_type'] = array('header', 'footer');
return $data;
}
/**
* Tests fetchFields.
*/
public function testFetchFields() {
$views_data = $this->getMockBuilder('Drupal\views\ViewsData')
->disableOriginalConstructor()
->getMock();
$views_data->expects($this->once())
->method('get')
->will($this->returnValue($this->viewsData()));
$data_helper = new ViewsDataHelper($views_data);
$expected = array(
'field' => array(
'age',
'created',
'job',
'name',
'status',
),
'argument' => array(
'age',
'created',
'id',
'job',
),
'filter' => array(
'created',
'id',
'job',
'name',
'status',
),
'sort' => array(
'age',
'created',
'id',
'name',
'status',
),
'area' => array(
'age',
'created',
'job',
),
'header' => array(
'age',
'created',
'job',
),
'footer' => array(
'age',
'created',
'job',
),
);
$handler_types = array('field', 'argument', 'filter', 'sort', 'area');
foreach ($handler_types as $handler_type) {
$fields = $data_helper->fetchFields('views_test_data', $handler_type);
$expected_keys = $expected[$handler_type];
array_walk($expected_keys, function(&$item) {
$item = "views_test_data.$item";
});
$this->assertEquals($expected_keys, array_keys($fields), String::format('Handlers of type @handler_type are not listed as expected.', array('@handler_type' => $handler_type)));
}
// Check for subtype filtering, so header and footer.
foreach (array('header', 'footer') as $sub_type) {
$fields = $data_helper->fetchFields('views_test_data', 'area', FALSE, $sub_type);
$expected_keys = $expected[$sub_type];
array_walk($expected_keys, function(&$item) {
$item = "views_test_data.$item";
});
$this->assertEquals($expected_keys, array_keys($fields), String::format('Sub_type @sub_type is not filtered as expected.', array('@sub_type' => $sub_type)));
}
}
}
......@@ -58,7 +58,10 @@ services:
arguments: [wizard, '@container.namespaces']
views.views_data:
class: Drupal\views\ViewsData
arguments: ['@cache.views_info', '@config.factory', '@module_handler']
arguments: ['@cache.views_info', '@config.factory', '@module_handler', '@language_manager']
views.views_data_helper:
class: Drupal\views\ViewsDataHelper
arguments: ['@views.views_data']
views.executable:
class: Drupal\views\ViewExecutableFactory
views.analyzer:
......
......@@ -408,141 +408,6 @@ function views_ui_build_form_url($form_state) {
return $url;
}
function _views_sort_types($a, $b) {
$a_group = drupal_strtolower($a['group']);
$b_group = drupal_strtolower($b['group']);
if ($a_group != $b_group) {
return $a_group < $b_group ? -1 : 1;
}
$a_title = drupal_strtolower($a['title']);
$b_title = drupal_strtolower($b['title']);
if ($a_title != $b_title) {
return $a_title < $b_title ? -1 : 1;
}
return 0;
}
/**
* Fetch a list of all fields available for a given base type.
*
* @param (array|string) $base
* A list or a single base_table, for example node.
* @param string $type
* The handler type, for example field or filter.
* @param bool $grouping
* Should the result grouping by its 'group' label.
* @param string $sub_type
* An optional sub type. E.g. Allows making an area plugin available for
* header only, instead of header, footer, and empty regions.
*
* @return array
* A keyed array of in the form of 'base_table' => 'Description'.
*/
function views_fetch_fields($base, $type, $grouping = FALSE, $sub_type = NULL) {
static $fields = array();
if (empty($fields)) {
$data = Views::viewsData()->get();
$start = microtime(TRUE);
// This constructs this ginormous multi dimensional array to
// collect the important data about fields. In the end,
// the structure looks a bit like this (using nid as an example)
// $strings['nid']['filter']['title'] = 'string'.
//
// This is constructed this way because the above referenced strings
// can appear in different places in the actual data structure so that
// the data doesn't have to be repeated a lot. This essentially lets
// each field have a cheap kind of inheritance.
foreach ($data as $table => $table_data) {
$bases = array();
$strings = array();
$skip_bases = array();
foreach ($table_data as $field => $info) {
// Collect table data from this table
if ($field == 'table') {
// calculate what tables this table can join to.
if (!empty($info['join'])) {
$bases = array_keys($info['join']);
}
// And it obviously joins to itself.
$bases[] = $table;
continue;
}
foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
if (!empty($info[$key])) {
if ($grouping && !empty($info[$key]['no group by'])) {
continue;
}
if ($sub_type && isset($info[$key]['sub_type']) && (!in_array($sub_type, (array) $info[$key]['sub_type']))) {
continue;
}
if (!empty($info[$key]['skip base'])) {
foreach ((array) $info[$key]['skip base'] as $base_name) {
$skip_bases[$field][$key][$base_name] = TRUE;
}
}
elseif (!empty($info['skip base'])) {
foreach ((array) $info['skip base'] as $base_name) {
$skip_bases[$field][$key][$base_name] = TRUE;
}
}
foreach (array('title', 'group', 'help', 'base', 'aliases') as $string) {
// First, try the lowest possible level
if (!empty($info[$key][$string])) {
$strings[$field][$key][$string] = $info[$key][$string];
}
// Then try the field level
elseif (!empty($info[$string])) {
$strings[$field][$key][$string] = $info[$string];
}
// Finally, try the table level
elseif (!empty($table_data['table'][$string])) {
$strings[$field][$key][$string] = $table_data['table'][$string];
}
else {
if ($string != 'base' && $string != 'base') {
$strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
}
}
}
}
}
}
foreach ($bases as $base_name) {
foreach ($strings as $field => $field_strings) {
foreach ($field_strings as $type_name => $type_strings) {
if (empty($skip_bases[$field][$type_name][$base_name])) {
$fields[$base_name][$type_name]["$table.$field"] = $type_strings;
}
}
}
}
}
}
// If we have an array of base tables available, go through them
// all and add them together. Duplicate keys will be lost and that's
// Just Fine.
if (is_array($base)) {
$strings = array();