Commit 3f8aa20c authored by Cottser's avatar Cottser

Issue #2574767 by dipakmdhrm, Manjit.Singh, malavya, Dom., yoroy, xjm,...

Issue #2574767 by dipakmdhrm, Manjit.Singh, malavya, Dom., yoroy, xjm, tkoleary, Gábor Hojtsy, dawehner, Cottser, eelkeblok, Bojhan, fgm, DuaelFr: Views listing page displays too few items on a page
parent cea3af0f
......@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px;
margin-top: 6px;
}
.views-ui-view-title {
.views-ui-view-name h3 {
font-weight: bold;
margin-top: 0;
margin: 0.25em 0;
}
.view-changed {
margin-bottom: 21px;
......@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0;
margin-top: 18px;
}
.views-ui-view-displays ul {
margin-left: 0; /* LTR */
padding-left: 0; /* LTR */
list-style: none;
}
[dir="rtl"] .views-ui-view-displays ul {
margin-right: 0;
padding-right: 0;
margin-left: inherit;
padding-left: inherit;
}
/* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name {
width: 18%;
width: 20%;
}
.views-ui-description {
width: 26%;
width: 30%;
}
.views-ui-tag {
width: 8%;
.views-ui-machine-name {
width: 15%;
}
.views-ui-path {
width: auto;
.views-ui-displays {
width: 25%;
}
.views-ui-operations {
width: 24%;
width: 10%;
}
/**
......
......@@ -161,10 +161,11 @@ function testDefaultViews() {
*/
function testSplitListing() {
// Build a re-usable xpath query.
$xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//tr[@title = :title]';
$xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//td/text()[contains(., :title)]';
$arguments = array(
':status' => 'views-list-section enabled',
':title' => t('Machine name: test_view_status'),
':title' => 'test_view_status',
);
$this->drupalGet('admin/structure/views');
......
......@@ -17,9 +17,6 @@ class XssTest extends UITestBase {
public static $modules = array('node', 'user', 'views_ui', 'views_ui_test');
public function testViewsUi() {
$this->drupalGet('admin/structure/views');
$this->assertEscaped('<script>alert("foo");</script>, <marquee>test</marquee>', 'The view tag is properly escaped.');
$this->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
$this->assertEscaped('<marquee>test</marquee>', 'Field admin label is properly escaped.');
......
......@@ -89,34 +89,30 @@ public function buildRow(EntityInterface $view) {
'data' => array(
'view_name' => array(
'data' => array(
'#theme' => 'views_ui_view_info',
'#view' => $view,
'#displays' => $this->getDisplaysList($view)
'#plain_text' => $view->label(),
),
),
'description' => array(
'machine_name' => array(
'data' => array(
'#plain_text' => $view->get('description'),
'#plain_text' => $view->id(),
),
'data-drupal-selector' => 'views-table-filter-text-source',
),
'tag' => array(
'description' => array(
'data' => array(
'#plain_text' => $view->get('tag'),
'#plain_text' => $view->get('description'),
),
'data-drupal-selector' => 'views-table-filter-text-source',
),
'path' => array(
'displays' => array(
'data' => array(
'#theme' => 'item_list',
'#items' => $this->getDisplayPaths($view),
'#context' => ['list_style' => 'comma-list'],
'#theme' => 'views_ui_view_displays_list',
'#displays' => $this->getDisplaysList($view),
),
),
'operations' => $row['operations'],
),
'title' => $this->t('Machine name: @name', array('@name' => $view->id())),
'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'),
'#attributes' => array(
'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'),
),
);
}
......@@ -127,23 +123,33 @@ public function buildHeader() {
return array(
'view_name' => array(
'data' => $this->t('View name'),
'class' => array('views-ui-name'),
'#attributes' => array(
'class' => array('views-ui-name'),
),
),
'machine_name' => array(
'data' => $this->t('Machine name'),
'#attributes' => array(
'class' => array('views-ui-machine-name'),
),
),
'description' => array(
'data' => $this->t('Description'),
'class' => array('views-ui-description'),
),
'tag' => array(
'data' => $this->t('Tag'),
'class' => array('views-ui-tag'),
'#attributes' => array(
'class' => array('views-ui-description'),
),
),
'path' => array(
'data' => $this->t('Path'),
'class' => array('views-ui-path'),
'displays' => array(
'data' => $this->t('Displays'),
'#attributes' => array(
'class' => array('views-ui-displays'),
),
),
'operations' => array(
'data' => $this->t('Operations'),
'class' => array('views-ui-operations'),
'#attributes' => array(
'class' => array('views-ui-operations'),
),
),
);
}
......@@ -196,13 +202,13 @@ public function render() {
'#type' => 'search',
'#title' => $this->t('Filter'),
'#title_display' => 'invisible',
'#size' => 40,
'#placeholder' => $this->t('Filter by view name or description'),
'#size' => 60,
'#placeholder' => $this->t('Filter by view name, machine name, description, or display path'),
'#attributes' => array(
'class' => array('views-filter-text'),
'data-table' => '.views-listing-table',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the view name or description to filter by.'),
'title' => $this->t('Enter a part of the view name, machine name, description, or display path to filter by.'),
),
);
......@@ -212,12 +218,9 @@ public function render() {
$list[$status]['#type'] = 'container';
$list[$status]['#attributes'] = array('class' => array('views-list-section', $status));
$list[$status]['table'] = array(
'#type' => 'table',
'#attributes' => array(
'class' => array('views-listing-table'),
),
'#header' => $this->buildHeader(),
'#rows' => array(),
'#theme' => 'views_ui_views_listing_table',
'#headers' => $this->buildHeader(),
'#attributes' => array('class' => array('views-listing-table', $status)),
);
foreach ($entities[$status] as $entity) {
$list[$status]['table']['#rows'][$entity->id()] = $this->buildRow($entity);
......@@ -242,46 +245,33 @@ public function render() {
*/
protected function getDisplaysList(EntityInterface $view) {
$displays = array();
foreach ($view->get('display') as $display) {
$definition = $this->displayManager->getDefinition($display['display_plugin']);
if (!empty($definition['admin'])) {
// Cast the admin label to a string since it is an object.
// @see \Drupal\Core\StringTranslation\TranslatableMarkup
$displays[] = (string) $definition['admin'];
}
}
sort($displays);
return $displays;
}
/**
* Gets a list of paths assigned to the view.
*
* @param \Drupal\Core\Entity\EntityInterface $view
* The view entity.
*
* @return array
* An array of paths for this view.
*/
protected function getDisplayPaths(EntityInterface $view) {
$all_paths = array();
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
if ($display->hasPath()) {
$path = $display->getPath();
if ($view->status() && strpos($path, '%') === FALSE) {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$all_paths[] = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path));
}
else {
$all_paths[] = '/' . $path;
$rendered_path = FALSE;
$definition = $display->getPluginDefinition();
if (!empty($definition['admin'])) {
if ($display->hasPath()) {
$path = $display->getPath();
if ($view->status() && strpos($path, '%') === FALSE) {
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$rendered_path = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path));
}
else {
$rendered_path = '/' . $path;
}
}
$displays[] = array(
'display' => $definition['admin'],
'path' => $rendered_path,
);
}
}
return array_unique($all_paths);
sort($displays);
return $displays;
}
}
{#
/**
* @file
* Default theme implementation for views displays on the views listing page.
*
* Available variables:
* - displays: Contains multiple display instances. Each display contains:
* - display: Display name.
* - path: Path to display, if any.
*
* @ingroup themeable
*/
#}
<ul>
{% for display in displays %}
<li>
{% if display.path %}
{{ display.display }} <span data-drupal-selector="views-table-filter-text-source">({{ display.path }})</span>
{% else %}
{{ display.display }}
{% endif %}
</li>
{% endfor %}
</ul>
{#
/**
* @file
* Default theme implementation for views listing table.
*
* Available variables:
* - headers: Contains table headers.
* - rows: Contains multiple rows. Each row contains:
* - view_name: The human-readable name of the view.
* - machine_name: Machine name of the view.
* - description: The description of the view.
* - displays: List of displays attached to the view.
* - operations: List of available operations.
*
* @see template_preprocess_views_ui_views_listing_table()
*
* @ingroup themeable
*/
#}
<table{{ attributes.addClass('responsive-enabled') }}>
<thead>
<tr>
{% for header in headers %}
<th{{ header.attributes }}>{{ header.data }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr{{ row.attributes }}>
<td class="views-ui-view-name">
<h3 data-drupal-selector="views-table-filter-text-source">{{ row.data.view_name.data }}</h3>
</td>
<td class="views-ui-view-machine-name" data-drupal-selector="views-table-filter-text-source">
{{ row.data.machine_name.data }}
</td>
<td class="views-ui-view-description" data-drupal-selector="views-table-filter-text-source">
{{ row.data.description.data }}
</td>
<td class="views-ui-view-displays">
{{ row.data.displays.data }}
</td>
<td class="views-ui-view-operations">
{{ row.data.operations.data }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
......@@ -152,21 +152,34 @@ public function testBuildRowEntityList() {
$view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager);
$view_list_builder->setStringTranslation($this->getStringTranslationStub());
// Create new view with test values.
$view = new View($values, 'view');
// Get the row object created by ViewListBuilder for this test view.
$row = $view_list_builder->buildRow($view);
// Expected output array for view's displays.
$expected_displays = array(
'Embed admin label',
'Page admin label',
'Page admin label',
'Page admin label',
'0' => array(
'display' => 'Embed admin label',
'path' => FALSE,
),
'1' => array(
'display' => 'Page admin label',
'path' => '/<object>malformed_path</object>',
),
'2' => array(
'display' => 'Page admin label',
'path' => '/<script>alert("placeholder_page/%")</script>',
),
'3' => array(
'display' => 'Page admin label',
'path' => '/test_page',
),
);
$this->assertEquals($expected_displays, $row['data']['view_name']['data']['#displays']);
$display_paths = $row['data']['path']['data']['#items'];
// These values will be escaped by Twig when rendered.
$this->assertEquals('/test_page, /<object>malformed_path</object>, /<script>alert("placeholder_page/%")</script>', implode(', ', $display_paths));
// Compare the expected and generated output.
$this->assertEquals($expected_displays, $row['data']['displays']['data']['#displays']);
}
}
......
......@@ -80,12 +80,25 @@ function views_ui_theme() {
'file' => 'views_ui.theme.inc',
),
// list views
// Legacy theme hook for displaying views info.
'views_ui_view_info' => array(
'variables' => array('view' => NULL, 'displays' => NULL),
'file' => 'views_ui.theme.inc',
),
// List views.
'views_ui_views_listing_table' => array(
'variables' => array(
'headers' => NULL,
'rows' => NULL,
'attributes' => array(),
),
'file' => 'views_ui.theme.inc',
),
'views_ui_view_displays_list' => array(
'variables' => array('displays' => array()),
),
// Group of filters.
'views_ui_build_group_filter_form' => array(
'render element' => 'form',
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Render\Element\Radios;
use Drupal\Core\Url;
use Drupal\Core\Template\Attribute;
/**
* Prepares variables for Views UI display tab setting templates.
......@@ -42,6 +43,31 @@ function template_preprocess_views_ui_display_tab_setting(&$variables) {
}
}
/**
* Prepares variables for Views UI view listing templates.
*
* Default template: views-ui-view-listing-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - headers: An associative array containing the headers for the view
* listing table.
* - rows: An associative array containing the rows data for the view
* listing table.
*/
function template_preprocess_views_ui_views_listing_table(&$variables) {
// Convert the attributes to valid attribute objects.
foreach ($variables['headers'] as $key => $header) {
$variables['headers'][$key]['attributes'] = new Attribute($header['#attributes']);
}
if (!empty($variables['rows'])) {
foreach ($variables['rows'] as $key => $row) {
$variables['rows'][$key]['attributes'] = new Attribute($row['#attributes']);
}
}
}
/**
* Prepares variables for Views UI display tab bucket templates.
*
......
......@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px;
margin-top: 6px;
}
.views-ui-view-title {
.views-ui-view-name h3 {
font-weight: bold;
margin-top: 0;
margin: 0.25em 0;
}
.view-changed {
margin-bottom: 21px;
......@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0;
margin-top: 18px;
}
.views-ui-view-displays ul {
margin-left: 0; /* LTR */
padding-left: 0; /* LTR */
list-style: none;
}
[dir="rtl"] .views-ui-view-displays ul {
margin-right: 0;
padding-right: 0;
margin-left: inherit;
padding-left: inherit;
}
/* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name {
width: 18%;
width: 20%;
}
.views-ui-description {
width: 26%;
width: 30%;
}
.views-ui-tag {
width: 8%;
.views-ui-machine-name {
width: 15%;
}
.views-ui-path {
width: auto;
.views-ui-displays {
width: 25%;
}
.views-ui-operations {
width: 24%;
width: 10%;
}
/**
......
{#
/**
* @file
* Theme override for views displays on the views listing page.
*
* Available variables:
* - displays: Contains multiple display instances. Each display contains:
* - display: Display name.
* - path: Path to display, if any.
*/
#}
<ul>
{% for display in displays %}
<li>
{% if display.path %}
{{ display.display }} <span data-drupal-selector="views-table-filter-text-source">({{ display.path }})</span>
{% else %}
{{ display.display }}
{% endif %}
</li>
{% endfor %}
</ul>
{#
/**
* @file
* Theme override for views listing table.
*
* Available variables:
* - headers: Contains table headers.
* - rows: Contains multiple rows. Each row contains:
* - view_name: The human-readable name of the view.
* - machine_name: Machine name of the view.
* - description: The description of the view.
* - displays: List of displays attached to the view.
* - operations: List of available operations.
*
* @see template_preprocess_views_ui_views_listing_table()
*/
#}
<table{{ attributes.addClass('responsive-enabled') }}>
<thead>
<tr>
{% for header in headers %}
<th{{ header.attributes }}>{{ header.data }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr{{ row.attributes }}>
<td class="views-ui-view-name">
<h3 data-drupal-selector="views-table-filter-text-source">{{ row.data.view_name.data }}</h3>
</td>
<td class="views-ui-view-machine-name" data-drupal-selector="views-table-filter-text-source">
{{ row.data.machine_name.data }}
</td>
<td class="views-ui-view-description" data-drupal-selector="views-table-filter-text-source">
{{ row.data.description.data }}
</td>
<td class="views-ui-view-displays">
{{ row.data.displays.data }}
</td>
<td class="views-ui-view-operations">
{{ row.data.operations.data }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment