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 { ...@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px; margin-bottom: 6px;
margin-top: 6px; margin-top: 6px;
} }
.views-ui-view-title { .views-ui-view-name h3 {
font-weight: bold; font-weight: bold;
margin-top: 0; margin: 0.25em 0;
} }
.view-changed { .view-changed {
margin-bottom: 21px; margin-bottom: 21px;
...@@ -183,22 +183,33 @@ details.box-padding { ...@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0; margin-bottom: 0;
margin-top: 18px; 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 */ /* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name { .views-ui-name {
width: 18%; width: 20%;
} }
.views-ui-description { .views-ui-description {
width: 26%; width: 30%;
} }
.views-ui-tag { .views-ui-machine-name {
width: 8%; width: 15%;
} }
.views-ui-path { .views-ui-displays {
width: auto; width: 25%;
} }
.views-ui-operations { .views-ui-operations {
width: 24%; width: 10%;
} }
/** /**
......
...@@ -161,10 +161,11 @@ function testDefaultViews() { ...@@ -161,10 +161,11 @@ function testDefaultViews() {
*/ */
function testSplitListing() { function testSplitListing() {
// Build a re-usable xpath query. // 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( $arguments = array(
':status' => 'views-list-section enabled', ':status' => 'views-list-section enabled',
':title' => t('Machine name: test_view_status'), ':title' => 'test_view_status',
); );
$this->drupalGet('admin/structure/views'); $this->drupalGet('admin/structure/views');
......
...@@ -17,9 +17,6 @@ class XssTest extends UITestBase { ...@@ -17,9 +17,6 @@ class XssTest extends UITestBase {
public static $modules = array('node', 'user', 'views_ui', 'views_ui_test'); public static $modules = array('node', 'user', 'views_ui', 'views_ui_test');
public function testViewsUi() { 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->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
$this->assertEscaped('<marquee>test</marquee>', 'Field admin label is properly escaped.'); $this->assertEscaped('<marquee>test</marquee>', 'Field admin label is properly escaped.');
......
...@@ -89,34 +89,30 @@ public function buildRow(EntityInterface $view) { ...@@ -89,34 +89,30 @@ public function buildRow(EntityInterface $view) {
'data' => array( 'data' => array(
'view_name' => array( 'view_name' => array(
'data' => array( 'data' => array(
'#theme' => 'views_ui_view_info', '#plain_text' => $view->label(),
'#view' => $view,
'#displays' => $this->getDisplaysList($view)
), ),
), ),
'description' => array( 'machine_name' => array(
'data' => 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( '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( 'data' => array(
'#theme' => 'item_list', '#theme' => 'views_ui_view_displays_list',
'#items' => $this->getDisplayPaths($view), '#displays' => $this->getDisplaysList($view),
'#context' => ['list_style' => 'comma-list'],
), ),
), ),
'operations' => $row['operations'], 'operations' => $row['operations'],
), ),
'title' => $this->t('Machine name: @name', array('@name' => $view->id())), '#attributes' => array(
'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'), 'class' => array($view->status() ? 'views-ui-list-enabled' : 'views-ui-list-disabled'),
),
); );
} }
...@@ -127,23 +123,33 @@ public function buildHeader() { ...@@ -127,23 +123,33 @@ public function buildHeader() {
return array( return array(
'view_name' => array( 'view_name' => array(
'data' => $this->t('View name'), '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( 'description' => array(
'data' => $this->t('Description'), 'data' => $this->t('Description'),
'class' => array('views-ui-description'), '#attributes' => array(
), 'class' => array('views-ui-description'),
'tag' => array( ),
'data' => $this->t('Tag'),
'class' => array('views-ui-tag'),
), ),
'path' => array( 'displays' => array(
'data' => $this->t('Path'), 'data' => $this->t('Displays'),
'class' => array('views-ui-path'), '#attributes' => array(
'class' => array('views-ui-displays'),
),
), ),
'operations' => array( 'operations' => array(
'data' => $this->t('Operations'), 'data' => $this->t('Operations'),
'class' => array('views-ui-operations'), '#attributes' => array(
'class' => array('views-ui-operations'),
),
), ),
); );
} }
...@@ -196,13 +202,13 @@ public function render() { ...@@ -196,13 +202,13 @@ public function render() {
'#type' => 'search', '#type' => 'search',
'#title' => $this->t('Filter'), '#title' => $this->t('Filter'),
'#title_display' => 'invisible', '#title_display' => 'invisible',
'#size' => 40, '#size' => 60,
'#placeholder' => $this->t('Filter by view name or description'), '#placeholder' => $this->t('Filter by view name, machine name, description, or display path'),
'#attributes' => array( '#attributes' => array(
'class' => array('views-filter-text'), 'class' => array('views-filter-text'),
'data-table' => '.views-listing-table', 'data-table' => '.views-listing-table',
'autocomplete' => 'off', '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() { ...@@ -212,12 +218,9 @@ public function render() {
$list[$status]['#type'] = 'container'; $list[$status]['#type'] = 'container';
$list[$status]['#attributes'] = array('class' => array('views-list-section', $status)); $list[$status]['#attributes'] = array('class' => array('views-list-section', $status));
$list[$status]['table'] = array( $list[$status]['table'] = array(
'#type' => 'table', '#theme' => 'views_ui_views_listing_table',
'#attributes' => array( '#headers' => $this->buildHeader(),
'class' => array('views-listing-table'), '#attributes' => array('class' => array('views-listing-table', $status)),
),
'#header' => $this->buildHeader(),
'#rows' => array(),
); );
foreach ($entities[$status] as $entity) { foreach ($entities[$status] as $entity) {
$list[$status]['table']['#rows'][$entity->id()] = $this->buildRow($entity); $list[$status]['table']['#rows'][$entity->id()] = $this->buildRow($entity);
...@@ -242,46 +245,33 @@ public function render() { ...@@ -242,46 +245,33 @@ public function render() {
*/ */
protected function getDisplaysList(EntityInterface $view) { protected function getDisplaysList(EntityInterface $view) {
$displays = array(); $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 = $view->getExecutable();
$executable->initDisplay(); $executable->initDisplay();
foreach ($executable->displayHandlers as $display) { foreach ($executable->displayHandlers as $display) {
if ($display->hasPath()) { $rendered_path = FALSE;
$path = $display->getPath(); $definition = $display->getPluginDefinition();
if ($view->status() && strpos($path, '%') === FALSE) { if (!empty($definition['admin'])) {
// @todo Views should expect and store a leading /. See: if ($display->hasPath()) {
// https://www.drupal.org/node/2423913 $path = $display->getPath();
$all_paths[] = \Drupal::l('/' . $path, Url::fromUserInput('/' . $path)); if ($view->status() && strpos($path, '%') === FALSE) {
} // @todo Views should expect and store a leading /. See:
else { // https://www.drupal.org/node/2423913
$all_paths[] = '/' . $path; $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() { ...@@ -152,21 +152,34 @@ public function testBuildRowEntityList() {
$view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager); $view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager);
$view_list_builder->setStringTranslation($this->getStringTranslationStub()); $view_list_builder->setStringTranslation($this->getStringTranslationStub());
// Create new view with test values.
$view = new View($values, 'view'); $view = new View($values, 'view');
// Get the row object created by ViewListBuilder for this test view.
$row = $view_list_builder->buildRow($view); $row = $view_list_builder->buildRow($view);
// Expected output array for view's displays.
$expected_displays = array( $expected_displays = array(
'Embed admin label', '0' => array(
'Page admin label', 'display' => 'Embed admin label',
'Page admin label', 'path' => FALSE,
'Page admin label', ),
'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']; // Compare the expected and generated output.
// These values will be escaped by Twig when rendered. $this->assertEquals($expected_displays, $row['data']['displays']['data']['#displays']);
$this->assertEquals('/test_page, /<object>malformed_path</object>, /<script>alert("placeholder_page/%")</script>', implode(', ', $display_paths));
} }
} }
......
...@@ -80,12 +80,25 @@ function views_ui_theme() { ...@@ -80,12 +80,25 @@ function views_ui_theme() {
'file' => 'views_ui.theme.inc', 'file' => 'views_ui.theme.inc',
), ),
// list views // Legacy theme hook for displaying views info.
'views_ui_view_info' => array( 'views_ui_view_info' => array(
'variables' => array('view' => NULL, 'displays' => NULL), 'variables' => array('view' => NULL, 'displays' => NULL),
'file' => 'views_ui.theme.inc', '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. // Group of filters.
'views_ui_build_group_filter_form' => array( 'views_ui_build_group_filter_form' => array(
'render element' => 'form', 'render element' => 'form',
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
use Drupal\Core\Render\Element\Checkboxes; use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Render\Element\Radios; use Drupal\Core\Render\Element\Radios;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\Core\Template\Attribute;
/** /**
* Prepares variables for Views UI display tab setting templates. * Prepares variables for Views UI display tab setting templates.
...@@ -42,6 +43,31 @@ function template_preprocess_views_ui_display_tab_setting(&$variables) { ...@@ -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. * Prepares variables for Views UI display tab bucket templates.
* *
......
...@@ -170,9 +170,9 @@ details.box-padding { ...@@ -170,9 +170,9 @@ details.box-padding {
margin-bottom: 6px; margin-bottom: 6px;
margin-top: 6px; margin-top: 6px;
} }
.views-ui-view-title { .views-ui-view-name h3 {
font-weight: bold; font-weight: bold;
margin-top: 0; margin: 0.25em 0;
} }
.view-changed { .view-changed {
margin-bottom: 21px; margin-bottom: 21px;
...@@ -183,22 +183,33 @@ details.box-padding { ...@@ -183,22 +183,33 @@ details.box-padding {
margin-bottom: 0; margin-bottom: 0;
margin-top: 18px; 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 */ /* These header classes are ambiguous and should be scoped to th elements */
.views-ui-name { .views-ui-name {
width: 18%; width: 20%;
} }
.views-ui-description { .views-ui-description {
width: 26%; width: 30%;
} }
.views-ui-tag { .views-ui-machine-name {
width: 8%; width: 15%;
} }
.views-ui-path { .views-ui-displays {
width: auto; width: 25%;
} }
.views-ui-operations { .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