View pager incorrect when programmatically removing entities from view results

Created on 20 October 2016, over 8 years ago
Updated 23 January 2025, 4 days ago

Hi,

I want to remove certain entities from the view results, before the view's pager is generated.

By now I am doing it in hook_views_post_execute(). The problem here is, that the number of results on the view pages is different then.

I do not want to create a views filter, which would require manual interaction from the user inside the views interface in the webbrowser.

I have tried also hook_views_query_alter(). The problem here is, that my module's class methods are processing entity id's (node id's in particular). It might would work with the SQL IN statement, but this is probably a problem for bigger site's and a huge performance impact. Since I need to iterate over all entity id's from the site and check access then.

My questions:

  • Is there any method inside the Views module, which allows me to remove the entity id's from the view results or shall I really use hook_views_query_alter()?
  • Also - if it comes to the hook_views_X methods - isn't there any more elegant class method or event? Since hooks are a mostly deprecated way in Drupal 8.

Cheers!

🐛 Bug report
Status

Active

Version

11.0 🔥

Component

views.module

Created by

🇩🇪Germany Peter Majmesku 🇩🇪Düsseldorf

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇳🇱Netherlands johnv

    In D11.1, I encounter the same problem, using a n (exposed) Views filter.

    The problem is that the pager settings limit the RANGE for the SQL statement, .

    In views/src/ViewExecutable->execute():

      public function execute($display_id = NULL) {
        ...
        // Let modules modify the view just prior to executing it.
        $module_handler = \Drupal::moduleHandler();
        $module_handler->invokeAll('views_pre_execute', [$this]);
        ...
        if ($cache->cacheGet('results')) {
          ...
        }
        else {
          $this->query->execute($this);
          // Enforce the array key rule as documented in
          // views_plugin_query::execute().
          $this->result = array_values($this->result);
          $this->_postExecute();
          $cache->cacheSet('results');
        }
    
        // Let modules modify the view just after executing it.
        $module_handler->invokeAll('views_post_execute', [$this]);
    
        return $this->executed = TRUE;
      }
    

    That $this->query->execute($this); calls src/Plugin/views/query/Sql->execute(), where you see that when 'limit' is set, the query itself is limiting the fetched result data, instead of read all data before pagination :

      public function execute(ViewExecutable $view) {
        ...
        if ($query) {
          $additional_arguments = \Drupal::moduleHandler()->invokeAll('views_query_substitutions', [$view]);
          ...
          try {
            if ($view->pager->useCountQuery() || !empty($view->get_total_rows)) {
              $view->pager->executeCountQuery($count_query);
            }
    
            // Let the pager modify the query to add limits.
            $view->pager->preExecute($query);
    
            if (!empty($this->limit) || !empty($this->offset)) {
              // We can't have an offset without a limit, so provide a very large limit instead.
              $limit = intval(!empty($this->limit) ? $this->limit : 999999);
              $offset = intval(!empty($this->offset) ? $this->offset : 0);
              $query->range($offset, $limit);
            }
    
            $result = $query->execute();
            ...
            $view->pager->postExecute($view->result);
            $view->pager->updatePageInfo();
            $view->total_rows = $view->pager->getTotalItems();
    
            // Load all entities contained in the results.
            $this->loadEntities($view->result);
          }
          ...
      }
    

    The $result = $query->execute(); contains a Query->preExecute() with an alter hook, which might be used to remove the RANGE and update the 'total_rows' statistic:
    public function preExecute(?SelectInterface $query = NULL) {
    ...
    // Modules may alter all queries or only those having a particular tag.
    if (isset($this->alterTags)) {
    \Drupal::moduleHandler()->alter($hooks, $query);
    }
    ...
    }

    Then, there is a views_post_execute() hook to restore the pager if needed.

  • 🇳🇱Netherlands johnv
  • 🇳🇱Netherlands johnv
  • 🇬🇧United Kingdom sergiur London, UK

    If anyone is looking for a workaround in the meantime, you could try adding a contextual filter to the view that is set to ‘exclude’ and/or ‘multiple’ as needed, and passing in the items you want to remove through the contextual filter. This doesn’t mess up pagination.

  • 🇳🇱Netherlands johnv

    That won't work if you want the user to make the selection (via the exposed filter), would it?

  • 🇬🇧United Kingdom sergiur London, UK

    I don't know about exposed filters, the original scope of this issue referred to cases where you need to remove results programmatically in a hook_views_pre_view hook or another views hook

  • 🇩🇪Germany Peter Majmesku 🇩🇪Düsseldorf

    I found a solution regarding to custom exposed filters and the pager counter, which might help you.

    There is this Pager-ID field in the views pager settings. After trying a couple things, I've tried to set a different numeric ID there, too. Afterwards this pager and other pagers worked correctly. The description of this field also already suggests, that it can help, if there are "problems". So there might be some side effects, which can be worked around by this field setting. Maybe this information helps others also.

    Lost a couple of hours with this not quite intuitive pager behavior in views...

Production build 0.71.5 2024