You are here

class ComplexGrouping in Views Complex Grouping 8

Class ComplexGrouping.

Plugin annotation


@ViewsStyle(
  id = "complex_grouping",
  title = @Translation("Complex Grouping"),
  help = @Translation("Limit the number of rows under each grouping field"),
  theme = "views_view_complex_grouping_leave",
  display_types = { "normal" }
)

Hierarchy

Expanded class hierarchy of ComplexGrouping

File

src/Plugin/views/style/ComplexGrouping.php, line 21

Namespace

Drupal\views_complex_grouping\Plugin\views\style
View source
class ComplexGrouping extends StylePluginBase {

  /**
   * Whether or not this style uses a row plugin.
   *
   * @var bool
   */
  protected $usesRowPlugin = TRUE;

  /**
   * Does the style plugin support grouping of rows.
   *
   * @var bool
   */
  protected $usesGrouping = TRUE;

  /**
   * Denotes whether the plugin has an additional options form.
   *
   * @var bool
   */
  protected $usesOptions = TRUE;

  /**
   * Does the style plugin for itself support to add fields to its output.
   *
   * This option only makes sense on style plugins without row plugins, like
   * for example table.
   *
   * @var bool
   */
  protected $usesFields = TRUE;

  /**
   * Name of the template used render.
   *
   * @var string
   */
  protected $complexGroupingTheme = 'views_view_complex_grouping_level';

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);
    $field_labels = $this->displayHandler
      ->getFieldLabels(TRUE);
    foreach ($form['grouping'] as $index => $info) {
      $form['grouping'][$index]['complex_grouping'] = [
        '#type' => 'fieldset',
        '#title' => t('Limit and extra fields for grouping field No. @num', [
          '@num' => $index + 1,
        ]),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      ];
      $form['grouping'][$index]['complex_grouping']['grouping_fields'] = [
        '#type' => 'select',
        '#multiple' => TRUE,
        '#title' => t('Selected'),
        '#options' => $field_labels,
        '#default_value' => $this
          ->getGroupingDefaultValues($index, 'grouping_fields'),
        '#description' => t('Select which fields will be displayed alongside the field No. @num', [
          '@num' => $index + 1,
        ]),
      ];
      $form['grouping'][$index]['complex_grouping']['grouping_limit'] = [
        '#type' => 'textfield',
        '#title' => t('Items to display:'),
        '#default_value' => $this
          ->getGroupingDefaultValues($index, 'grouping_limit'),
        '#size' => 6,
        '#element_validate' => [
          [
            get_called_class(),
            'complexGroupingValidate',
          ],
        ],
        '#description' => t('The number of rows to show under the field Nr. @num. Leave 0 to show all of them.', [
          '@num' => $index + 1,
        ]),
      ];
      $form['grouping'][$index]['complex_grouping']['grouping_offset'] = [
        '#type' => 'textfield',
        '#title' => t('Offset:'),
        '#default_value' => $this
          ->getGroupingDefaultValues($index, 'grouping_offset'),
        '#size' => 6,
        '#element_validate' => [
          [
            get_called_class(),
            'complexGroupingValidate',
          ],
        ],
        '#description' => t('The row to start on.'),
      ];
    }
  }

  /**
   * Get default value for complex grouping options.
   */
  private function getGroupingDefaultValues($index, $key) {
    $default_option = NULL;
    $options = $this->options['grouping'];
    switch ($key) {
      case 'grouping_limit':
        $default_value = 0;
        break;
      case 'grouping_offset':
        $default_value = 0;
        break;
      case 'grouping_fields':
      default:
        $default_value = NULL;
        break;
    }
    $default_option = isset($options[$index]) ? $options[$index]['complex_grouping'][$key] ?: $default_value : $default_value;
    return $default_option;
  }

  /**
   * Group records as needed for rendering.
   */
  public function renderGrouping($records, $groupings = [], $group_rendered = NULL) {

    // This is for backward compatibility, when $groupings was a string
    // containing the ID of a single field.
    if (is_string($groupings)) {
      $rendered = $group_rendered === NULL ? TRUE : $group_rendered;
      $groupings = [
        [
          'field' => $groupings,
          'rendered' => $rendered,
        ],
      ];
    }

    // Make sure fields are rendered.
    $this
      ->renderFields($this->view->result);
    $sets = [];
    if ($groupings) {
      foreach ($records as $index => $row) {

        // Iterate through configured grouping fields to determine the
        // hierarchically positioned set where the current row belongs to.
        // While iterating, parent groups, that do not exist yet, are added.
        $set =& $sets;
        foreach ($groupings as $level => $info) {
          $field = $info['field'];
          $rendered = isset($info['rendered']) ? $info['rendered'] : $group_rendered;
          $rendered_strip = isset($info['rendered_strip']) ? $info['rendered_strip'] : FALSE;
          $grouping = '';
          $group_content = '';

          // Group on the rendered version of the field, not the raw.  That way,
          // we can control any special formatting of the grouping field through
          // the admin or theme layer or anywhere else we'd like.
          if (isset($this->view->field[$field])) {
            $group_content = $this
              ->getField($index, $field);
            if ($this->view->field[$field]->options['label']) {
              $group_content = $this->view->field[$field]->options['label'] . ': ' . $group_content;
            }
            if ($rendered) {
              $grouping = (string) $group_content;
              if ($rendered_strip) {
                $group_content = $grouping = strip_tags(htmlspecialchars_decode($group_content));
              }
            }
            else {
              $grouping = $this
                ->getFieldValue($index, $field);

              // Not all field handlers return a scalar value,
              // e.g. views_handler_field_field.
              if (!is_scalar($grouping)) {
                $grouping = hash('sha256', serialize($grouping));
              }
            }
          }

          // Create the group if it does not exist yet.
          if (empty($set[$grouping])) {
            $set[$grouping]['group'] = $group_content;
            $set[$grouping]['level'] = $level;
            $set[$grouping]['rows'] = [];
            $set[$grouping]['fields'] = [];

            // Add selected fields for this level.
            foreach ($this->options['grouping'][$level]['complex_grouping']['grouping_fields'] as $field) {
              $set[$grouping]['fields'][$field] = $this->rendered_fields[$index][$field];
            }
          }

          // Move the set reference into the row set of the group
          // we just determined.
          $set =& $set[$grouping]['rows'];
        }

        // Add the row to the hierarchically positioned row set
        // we just determined.
        $set[$index] = $row;
      }
    }
    else {

      // If this parameter isn't explicitly set, modify the output to be fully
      // backward compatible to code before Views 7.x-3.0-rc2.
      // @TODO Remove this as soon as possible e.g. October 2020
      if ($group_rendered === NULL) {
        $old_style_sets = [];
        foreach ($sets as $group) {
          $old_style_sets[$group['group']] = $group['rows'];
        }
        $sets = $old_style_sets;
      }
    }

    // Apply the offset and limit.
    array_walk($sets, [
      $this,
      'complexGroupingRecursiveLimit',
    ]);
    return $sets;
  }

  /**
   * Render the grouping sets.
   */
  public function renderGroupingSets($sets) {
    $output = [];
    $branch = 0;
    $theme_functions = $this->view
      ->buildThemeFunctions($this->complexGroupingTheme);
    foreach ($sets as $set) {
      $branch++;
      $level = isset($set['level']) ? $set['level'] : 0;
      $row = reset($set['rows']);

      // Render as a grouping set.
      if (is_array($row) && isset($row['group'])) {
        $single_output = [
          '#theme' => $theme_functions,
          '#view' => $this->view,
          '#grouping' => $this->options['grouping'][$level],
          '#grouping_branch' => $branch,
          '#rows' => $set['rows'],
          '#fields' => $set['fields'],
        ];
      }
      else {
        if ($this
          ->usesRowPlugin()) {
          foreach ($set['rows'] as $index => $row) {
            $this->view->row_index = $index;
            $set['rows'][$index] = $this->view->rowPlugin
              ->render($row);
          }
        }
        $single_output = $this
          ->renderRowGroup($set['rows']);
        $single_output['#grouping'] = $this->options['grouping'][$level];
        $single_output['#grouping_branch'] = $branch;
        $single_output['#fields'] = $set['fields'];
      }
      $single_output['#grouping_level'] = $level + 1;
      $single_output['#title'] = $set['group'];
      $output[] = $single_output;
    }
    unset($this->view->row_index);
    return $output;
  }

  /**
   * Recursively limits the number of rows in nested groups.
   */
  protected function complexGroupingRecursiveLimit(array &$group_data, $key = NULL, $level = 1) {
    $settings = $this
      ->complexGroupingSettings($level - 1);
    $settings['grouping_limit'] = $settings['grouping_limit'] != 0 ? $settings['grouping_limit'] : NULL;
    $settings['grouping_offset'] = isset($settings['grouping_offset']) ? $settings['grouping_offset'] : 0;

    // Slice up the rows according to the offset and limit.
    $group_data['rows'] = array_slice($group_data['rows'], $settings['grouping_offset'], $settings['grouping_limit'], TRUE);

    // For each row, if it appears to be another grouping, recurse again.
    foreach ($group_data['rows'] as &$data) {
      if (is_array($data) && isset($data['group']) && isset($data['rows'])) {
        $this
          ->complexGroupingRecursiveLimit($data, NULL, $level + 1);
      }
    }
  }

  /**
   * Validate Complex Grouping form options.
   */
  public static function complexGroupingValidate($element, FormStateInterface $form_state, $form) {
    if (!is_numeric($element['#value'])) {
      $form_state
        ->setError($element, t('%element must be numeric.', [
        '%element' => $element['#title'],
      ]));
    }

    // Checking for negative val  ues.
    if ($element['#value'] < 0) {
      $form_state
        ->setError($element, t('%element cannot be negative.', [
        '%element' => $element['#title'],
      ]));
    }
  }

  /**
   * Helper function to retrieve settings for grouping limit.
   *
   * @param int $index
   *   The grouping level to fetch settings for.
   *
   * @return array
   *   Settings for this grouping level.
   */
  protected function complexGroupingSettings($index) {
    if ($this->options['grouping'][$index] && $this->options['grouping'][$index]['complex_grouping']) {
      return $this->options['grouping'][$index]['complex_grouping'];
    }
    else {
      return [
        'grouping_limit' => 0,
        'grouping_offset' => 0,
      ];
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ComplexGrouping::$complexGroupingTheme protected property Name of the template used render.
ComplexGrouping::$usesFields protected property Does the style plugin for itself support to add fields to its output. Overrides StylePluginBase::$usesFields
ComplexGrouping::$usesGrouping protected property Does the style plugin support grouping of rows. Overrides StylePluginBase::$usesGrouping
ComplexGrouping::$usesOptions protected property Denotes whether the plugin has an additional options form. Overrides StylePluginBase::$usesOptions
ComplexGrouping::$usesRowPlugin protected property Whether or not this style uses a row plugin. Overrides StylePluginBase::$usesRowPlugin
ComplexGrouping::buildOptionsForm public function Provide a form to edit options for this plugin. Overrides StylePluginBase::buildOptionsForm
ComplexGrouping::complexGroupingRecursiveLimit protected function Recursively limits the number of rows in nested groups.
ComplexGrouping::complexGroupingSettings protected function Helper function to retrieve settings for grouping limit.
ComplexGrouping::complexGroupingValidate public static function Validate Complex Grouping form options.
ComplexGrouping::getGroupingDefaultValues private function Get default value for complex grouping options.
ComplexGrouping::renderGrouping public function Group records as needed for rendering. Overrides StylePluginBase::renderGrouping
ComplexGrouping::renderGroupingSets public function Render the grouping sets. Overrides StylePluginBase::renderGroupingSets
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$definition public property Plugins's definition
PluginBase::$displayHandler public property The display object this plugin is for.
PluginBase::$options public property Options for this plugin will be held here.
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::$renderer protected property Stores the render API renderer. 3
PluginBase::$view public property The top object of a view. 1
PluginBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 14
PluginBase::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create 62
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::doFilterByDefinedOptions protected function Do the work to filter out stored options depending on the defined options.
PluginBase::filterByDefinedOptions public function Filter out stored options depending on the defined options. Overrides ViewsPluginInterface::filterByDefinedOptions
PluginBase::getAvailableGlobalTokens public function Returns an array of available token replacements. Overrides ViewsPluginInterface::getAvailableGlobalTokens
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::getProvider public function Returns the plugin provider. Overrides ViewsPluginInterface::getProvider
PluginBase::getRenderer protected function Returns the render API renderer. 1
PluginBase::globalTokenForm public function Adds elements for available core tokens to a form. Overrides ViewsPluginInterface::globalTokenForm
PluginBase::globalTokenReplace public function Returns a string with any core tokens replaced. Overrides ViewsPluginInterface::globalTokenReplace
PluginBase::INCLUDE_ENTITY constant Include entity row languages when listing languages.
PluginBase::INCLUDE_NEGOTIATED constant Include negotiated languages when listing languages.
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::listLanguages protected function Makes an array of languages, optionally including special languages.
PluginBase::pluginTitle public function Return the human readable name of the display. Overrides ViewsPluginInterface::pluginTitle
PluginBase::preRenderAddFieldsetMarkup public static function Moves form elements into fieldsets for presentation purposes. Overrides ViewsPluginInterface::preRenderAddFieldsetMarkup
PluginBase::preRenderFlattenData public static function Flattens the structure of form elements. Overrides ViewsPluginInterface::preRenderFlattenData
PluginBase::queryLanguageSubstitutions public static function Returns substitutions for Views queries for languages.
PluginBase::setOptionDefaults protected function Fills up the options of the plugin with defaults.
PluginBase::submitOptionsForm public function Handle any special handling on the validate form. Overrides ViewsPluginInterface::submitOptionsForm 16
PluginBase::summaryTitle public function Returns the summary of the settings in the display. Overrides ViewsPluginInterface::summaryTitle 6
PluginBase::themeFunctions public function Provide a full list of possible theme templates used by this style. Overrides ViewsPluginInterface::themeFunctions 1
PluginBase::unpackOptions public function Unpack options over our existing defaults, drilling down into arrays so that defaults don't get totally blown away. Overrides ViewsPluginInterface::unpackOptions
PluginBase::usesOptions public function Returns the usesOptions property. Overrides ViewsPluginInterface::usesOptions 8
PluginBase::viewsTokenReplace protected function Replaces Views' tokens in a given string. The resulting string will be sanitized with Xss::filterAdmin. 1
PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT constant Query string to indicate the site default language.
PluginBase::__construct public function Constructs a PluginBase object. Overrides PluginBase::__construct
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
StylePluginBase::$defaultFieldLabels protected property Should field labels be enabled by default. 1
StylePluginBase::$groupingTheme protected property The theme function used to render the grouping set.
StylePluginBase::$rendered_fields protected property Stores the rendered field values, keyed by the row index and field name.
StylePluginBase::$rowTokens protected property Store all available tokens row rows.
StylePluginBase::$usesRowClass protected property Does the style plugin support custom css class for the rows. 3
StylePluginBase::buildSort public function Called by the view builder to see if this style handler wants to interfere with the sorts. If so it should build; if it returns any non-TRUE value, normal sorting will NOT be added to the query. 1
StylePluginBase::buildSortPost public function Called by the view builder to let the style build a second set of sorts that will come after any other sorts in the view. 1
StylePluginBase::defaultFieldLabels public function Return TRUE if this style enables field labels by default. 1
StylePluginBase::defineOptions protected function Information about options for all kinds of purposes will be held here. Overrides PluginBase::defineOptions 9
StylePluginBase::destroy public function Clears a plugin. Overrides PluginBase::destroy
StylePluginBase::elementPreRenderRow public function #pre_render callback for view row field rendering.
StylePluginBase::evenEmpty public function Should the output of the style plugin be rendered even if it's a empty view. 2
StylePluginBase::getField public function Gets a rendered field.
StylePluginBase::getFieldValue public function Get the raw field value.
StylePluginBase::getRowClass public function Return the token replaced row class for the specified row.
StylePluginBase::init public function Overrides \Drupal\views\Plugin\views\PluginBase::init(). Overrides PluginBase::init
StylePluginBase::preRender public function Allow the style to do stuff before each row is rendered.
StylePluginBase::query public function Add anything to the query that we might need to. Overrides PluginBase::query 1
StylePluginBase::render public function Render the display in this style. 7
StylePluginBase::renderFields protected function Renders all of the fields for a given style and store them on the object.
StylePluginBase::renderRowGroup protected function Renders a group of rows of the grouped view.
StylePluginBase::tokenizeValue public function Take a value and apply token replacement logic to it.
StylePluginBase::trustedCallbacks public static function Lists the trusted callbacks provided by the implementing class. Overrides PluginBase::trustedCallbacks
StylePluginBase::usesFields public function Return TRUE if this style also uses fields. 3
StylePluginBase::usesGrouping public function Returns the usesGrouping property. 3
StylePluginBase::usesRowClass public function Returns the usesRowClass property. 3
StylePluginBase::usesRowPlugin public function Returns the usesRowPlugin property. 10
StylePluginBase::usesTokens public function Return TRUE if this style uses tokens.
StylePluginBase::validate public function Validate that the plugin is correct and can be saved. Overrides PluginBase::validate
StylePluginBase::validateOptionsForm public function Validate the options form. Overrides PluginBase::validateOptionsForm
StylePluginBase::wizardForm public function Provide a form in the views wizard if this style is selected.
StylePluginBase::wizardSubmit public function Alter the options of a display before they are added to the view. 1
TrustedCallbackInterface::THROW_EXCEPTION constant Untrusted callbacks throw exceptions.
TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION constant Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
TrustedCallbackInterface::TRIGGER_WARNING constant Untrusted callbacks trigger E_USER_WARNING errors.