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
cea3af0f94
commit
3f8aa20c32
|
|
@ -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 @@ class DefaultViewsTest extends UITestBase {
|
|||
*/
|
||||
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 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
'data' => array(
|
||||
'view_name' => array(
|
||||
'data' => array(
|
||||
'#theme' => 'views_ui_view_info',
|
||||
'#view' => $view,
|
||||
'#displays' => $this->getDisplaysList($view)
|
||||
'#plain_text' => $view->label(),
|
||||
),
|
||||
),
|
||||
'machine_name' => array(
|
||||
'data' => array(
|
||||
'#plain_text' => $view->id(),
|
||||
),
|
||||
),
|
||||
'description' => array(
|
||||
'data' => array(
|
||||
'#plain_text' => $view->get('description'),
|
||||
),
|
||||
'data-drupal-selector' => 'views-table-filter-text-source',
|
||||
),
|
||||
'tag' => array(
|
||||
'displays' => array(
|
||||
'data' => array(
|
||||
'#plain_text' => $view->get('tag'),
|
||||
),
|
||||
'data-drupal-selector' => 'views-table-filter-text-source',
|
||||
),
|
||||
'path' => 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 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
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'),
|
||||
'#attributes' => array(
|
||||
'class' => array('views-ui-description'),
|
||||
),
|
||||
),
|
||||
'tag' => array(
|
||||
'data' => $this->t('Tag'),
|
||||
'class' => array('views-ui-tag'),
|
||||
),
|
||||
'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 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
'#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 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
$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,12 +245,28 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
*/
|
||||
protected function getDisplaysList(EntityInterface $view) {
|
||||
$displays = array();
|
||||
foreach ($view->get('display') as $display) {
|
||||
$definition = $this->displayManager->getDefinition($display['display_plugin']);
|
||||
|
||||
$executable = $view->getExecutable();
|
||||
$executable->initDisplay();
|
||||
foreach ($executable->displayHandlers as $display) {
|
||||
$rendered_path = FALSE;
|
||||
$definition = $display->getPluginDefinition();
|
||||
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'];
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,33 +274,4 @@ class ViewListBuilder extends ConfigEntityListBuilder {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($all_paths);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
{#
|
||||
/**
|
||||
* @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>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{#
|
||||
/**
|
||||
* @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 @@ class ViewListBuilderTest extends UnitTestCase {
|
|||
$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;
|
|||
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%;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
{#
|
||||
/**
|
||||
* @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>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{#
|
||||
/**
|
||||
* @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>
|
||||
Loading…
Reference in New Issue