diff --git a/core/modules/views/config/schema/views.area.schema.yml b/core/modules/views/config/schema/views.area.schema.yml index 51042e9587..a17668a349 100644 --- a/core/modules/views/config/schema/views.area.schema.yml +++ b/core/modules/views/config/schema/views.area.schema.yml @@ -51,6 +51,17 @@ views.area.result: type: text label: 'The shown text of the result summary area' +views.area.result_plural: + type: views.area.result + label: 'Result plural' + mapping: + content_plural: + type: text + label: 'Text to use for the plural form' + plural_count_token: + type: text + label: 'Plural count token' + views.area.title: type: views_area label: 'Title' diff --git a/core/modules/views/src/Plugin/views/area/Result.php b/core/modules/views/src/Plugin/views/area/Result.php index efe78d0770..99343a1b03 100644 --- a/core/modules/views/src/Plugin/views/area/Result.php +++ b/core/modules/views/src/Plugin/views/area/Result.php @@ -3,6 +3,7 @@ namespace Drupal\views\Plugin\views\area; use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\style\DefaultSummary; @@ -15,6 +16,20 @@ */ class Result extends AreaPluginBase { + /** + * Total number of results. + * + * @var int + */ + protected $total; + + /** + * The token replacements. + * + * @var array + */ + protected $replacements = []; + /** * {@inheritdoc} */ @@ -74,53 +89,60 @@ public function render($empty = FALSE) { return []; } $output = ''; + $this->calculateTotalAndReplacements(); $format = $this->options['content']; + + // Send the output. + if (!empty($this->total) || !empty($this->options['empty'])) { + $output .= Xss::filterAdmin(str_replace(array_keys($this->replacements), array_values($this->replacements), $format)); + // Return as render array. + return [ + '#markup' => $output, + ]; + } + + return []; + } + + /** + * Calculate the replacement tokens. + */ + protected function calculateTotalAndReplacements() { // Calculate the page totals. $current_page = (int) $this->view->getCurrentPage() + 1; $per_page = (int) $this->view->getItemsPerPage(); // @TODO: Maybe use a possible is views empty functionality. // Not every view has total_rows set, use view->result instead. - $total = $this->view->total_rows ?? count($this->view->result); + $this->total = $this->view->total_rows ?? count($this->view->result); $label = Html::escape($this->view->storage->label()); // If there is no result the "start" and "current_record_count" should be // equal to 0. To have the same calculation logic, we use a "start offset" // to handle all the cases. - $start_offset = empty($total) ? 0 : 1; + $start_offset = empty($this->total) ? 0 : 1; if ($per_page === 0) { $page_count = 1; $start = $start_offset; - $end = $total; + $end = $this->total; } else { - $page_count = (int) ceil($total / $per_page); + $page_count = (int) ceil($this->total / $per_page); $total_count = $current_page * $per_page; - if ($total_count > $total) { - $total_count = $total; + if ($total_count > $this->total) { + $total_count = $this->total; } $start = ($current_page - 1) * $per_page + $start_offset; $end = $total_count; } $current_record_count = ($end - $start) + $start_offset; // Get the search information. - $replacements = []; - $replacements['@start'] = $start; - $replacements['@end'] = $end; - $replacements['@total'] = $total; - $replacements['@label'] = $label; - $replacements['@per_page'] = $per_page; - $replacements['@current_page'] = $current_page; - $replacements['@current_record_count'] = $current_record_count; - $replacements['@page_count'] = $page_count; - // Send the output. - if (!empty($total) || !empty($this->options['empty'])) { - $output .= str_replace(array_keys($replacements), array_values($replacements), $format); - // Return as render array. - return [ - '#markup' => $output, - ]; - } - - return []; + $this->replacements['@start'] = $start; + $this->replacements['@end'] = $end; + $this->replacements['@total'] = $this->total; + $this->replacements['@label'] = $label; + $this->replacements['@per_page'] = $per_page; + $this->replacements['@current_page'] = $current_page; + $this->replacements['@current_record_count'] = $current_record_count; + $this->replacements['@page_count'] = $page_count; } } diff --git a/core/modules/views/src/Plugin/views/area/ResultPlural.php b/core/modules/views/src/Plugin/views/area/ResultPlural.php new file mode 100644 index 0000000000..5a6ba52d80 --- /dev/null +++ b/core/modules/views/src/Plugin/views/area/ResultPlural.php @@ -0,0 +1,106 @@ + $this->t('Displaying @start - @end of @total'), + ]; + $options['plural_count_token'] = ['default' => '@total']; + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + $format_plural_count_options = [ + '@start', + '@end', + '@total', + '@name', + '@per_page', + '@current_page', + '@current_record_count', + '@page_count', + ]; + $description = $form['content']['#description']; + + // Overrides parent plugin form. + unset($form['content']['#description']); + $form['content']['#title'] = $this->t('Singular form'); + $form['content']['#weight'] = 5; + + $form['plural_count_token'] = [ + '#type' => 'select', + '#title' => $this->t('Count token'), + '#description' => $this->t('Token used to detect plurality. If the token value is more than one, the "Plural form" textarea will be used.'), + '#default_value' => (isset($this->options['plural_count_token']) ? $this->options['plural_count_token'] : ''), + '#options' => array_combine($format_plural_count_options, $format_plural_count_options), + ]; + $form['content_plural'] = [ + '#type' => 'textarea', + '#title' => $this->t('Plural form'), + '#description' => $description, + '#default_value' => $this->options['content_plural'], + '#weight' => 10, + '#rows' => 3, + ]; + } + + /** + * {@inheritdoc} + */ + public function render($empty = FALSE) { + // Must have options and does not work on summaries. + if (!isset($this->options['content']) || !isset($this->options['content_plural']) || !isset($this->options['plural_count_token']) || $this->view->style_plugin instanceof DefaultSummary) { + return []; + } + $output = ''; + $this->calculateTotalAndReplacements(); + $format = PluralTranslatableMarkup::createFromTranslatedString( + $this->replacements[$this->options['plural_count_token']], + implode( + PoItem::DELIMITER, + [ + $this->options['content'], + $this->options['content_plural'], + ] + ) + ); + + // Send the output. + if (!empty($this->total) || !empty($this->options['empty'])) { + $output .= Xss::filterAdmin(str_replace(array_keys($this->replacements), array_values($this->replacements), $format)); + // Return as render array. + return [ + '#markup' => $output, + ]; + } + + return []; + } + +} diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result_plural.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result_plural.yml new file mode 100644 index 0000000000..fc459ef290 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result_plural.yml @@ -0,0 +1,82 @@ +langcode: en +status: true +dependencies: { } +id: test_area_result_plural +label: '' +module: views +description: '' +tag: '' +base_table: views_test_data +base_field: nid +display: + default: + display_options: + defaults: + fields: false + pager: false + sorts: false + fields: + id: + field: id + id: id + relationship: none + table: views_test_data + plugin_id: numeric + pager: + options: + offset: 0 + type: none + sorts: + id: + field: id + id: id + order: ASC + relationship: none + table: views_test_data + plugin_id: numeric + empty: + title: + field: title + id: title + table: views + plugin_id: title + title: test_title_empty + header: + result_plural: + id: result_plural + table: views + field: result_plural + relationship: none + group_type: group + admin_label: '' + empty: true + content: "SINGULAR start: @start | end: @end | total: @total | label: @label | per page: @per_page | current page: @current_page | current record count: @current_record_count | page count: @page_count" + content_plural: 'PLURAL start: @start | end: @end | total: @total | label: @label | per page: @per_page | current page: @current_page | current record count: @current_record_count | page count: @page_count' + plural_count_token: '@start' + plugin_id: result_plural + display_plugin: default + display_title: Master + id: default + position: 0 + page_1: + display_options: + path: test-area-result-plural + defaults: + header: false + header: + result_plural: + id: result_plural + table: views + field: result_plural + relationship: none + group_type: group + admin_label: '' + empty: false + content: "SINGULAR start: @start | end: @end | total: @total | label: @label | per page: @per_page | current page: @current_page | current record count: @current_record_count | page count: @page_count" + content_plural: 'PLURAL start: @start | end: @end | total: @total | label: @label | per page: @per_page | current page: @current_page | current record count: @current_record_count | page count: @page_count' + plural_count_token: '@start' + plugin_id: result_plural + display_plugin: page + display_title: 'Page 1' + id: page_1 + position: 1 diff --git a/core/modules/views/tests/src/Kernel/Handler/AreaResultPluralTest.php b/core/modules/views/tests/src/Kernel/Handler/AreaResultPluralTest.php new file mode 100644 index 0000000000..70337af8bd --- /dev/null +++ b/core/modules/views/tests/src/Kernel/Handler/AreaResultPluralTest.php @@ -0,0 +1,77 @@ +setDisplay('default'); + $this->executeView($view); + $output = $view->render(); + $output = \Drupal::service('renderer')->renderRoot($output); + $this->setRawContent($output); + $this->assertText('SINGULAR start: 1 | end: 5 | total: 5 | label: test_area_result_plural | per page: 0 | current page: 1 | current record count: 5 | page count: 1'); + } + + /** + * Tests that the plural text is displayed. + */ + public function testResultEmpty() { + $view = Views::getView('test_area_result_plural'); + + // Test that the area is displayed if we have checked the empty checkbox. + $view->setDisplay('default'); + + // Add a filter that will make the result set empty. + $view->displayHandlers->get('default')->overrideOption('filters', [ + 'name' => [ + 'id' => 'name', + 'table' => 'views_test_data', + 'field' => 'name', + 'relationship' => 'none', + 'operator' => '=', + 'value' => 'non-existing-name', + ], + ]); + + $this->executeView($view); + $output = $view->render(); + $output = \Drupal::service('renderer')->renderRoot($output); + $this->setRawContent($output); + $this->assertText('PLURAL start: 0 | end: 0 | total: 0 | label: test_area_result_plural | per page: 0 | current page: 1 | current record count: 0 | page count: 1'); + $this->assertRaw('
'); + + // Test that the area is not displayed if we have not checked the empty + // checkbox. + $view->setDisplay('page_1'); + + $this->executeView($view); + $output = $view->render(); + $output = \Drupal::service('renderer')->renderRoot($output); + $this->setRawContent($output); + $this->assertNoText('PLURAL start: 0 | end: 0 | total: 0 | label: test_area_result_plural | per page: 0 | current page: 1 | current record count: 0 | page count: 1'); + // Make sure the empty header region isn't rendered. + $this->assertNoRaw('
'); + } + +} diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc index de79773321..37a871ee94 100644 --- a/core/modules/views/views.views.inc +++ b/core/modules/views/views.views.inc @@ -99,6 +99,14 @@ function views_views_data() { ], ]; + $data['views']['result_plural'] = [ + 'title' => t('Result summary (plural)'), + 'help' => t('Shows result summary, for example the items per page, with support for having singular and plural text depending on the number of results.'), + 'area' => [ + 'id' => 'result_plural', + ], + ]; + $data['views']['messages'] = [ 'title' => t('Messages'), 'help' => t('Displays messages in an area.'),