Drupal 8 add another filter to URL aliases page

After i delete duplicate url alias, we needed a way to check if still other duplicate url alias!

So the task was to add another filter to admin/config/search/path page. 

Image
Screenshot url alias page without filter

Something like:

Image
Screenshot url alias page with filter

The url alias page it's not a view so

How to add another filter to url alias page ?

FILTER ALIASES fieldset is a form build in PathFilterForm.php, the first thing to do is to alter this form and add our new filter input using hook_form_BASE_FORM_ID_alter:

use Drupal\Core\Form\FormStateInterface;
/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function MODULENAME_form_path_admin_filter_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $path = \Drupal::request()->request->get('path');
  $keys = \Drupal::request()->request->get('search');
  $form['basic']['path'] = [
    '#type'          => 'search',
    '#title'         => t('System path'),
    '#title_display' => 'invisible',
    '#default_value' => $path,
    '#maxlength'     => 128,
    '#size'          => 25,
    '#attributes'    => [
      'placeholder' => t('Filter by system path'),
    ],
  ];

  // Move submit button to end of the array.
  $submit = $form['basic']['submit'];
  unset($form['basic']['submit']);
  array_push($form['basic'], $submit);

  // If the reset button exist move it to end of the array.
  if (isset($form['basic']['reset'])) {
    $reset = $form['basic']['reset'];
    unset($form['basic']['reset']);
    array_push($form['basic'], $reset);
  }

  if (!$keys && $path) {
    $form['basic']['reset'] = [
      '#type'   => 'submit',
      '#value'  => t('Reset'),
      '#submit' => '_path_admin_filter_reset',
    ];
  }

  // Add new submit function to add other query param.
  $form['#submit'][] = '_path_admin_filter_submit';
}

Now we have our input but it does nothing in submit, what makes us move to second step which is implementing submit and reset callbacks:

Submit handler:

/**
 * Path admin filter submit handler.
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function _path_admin_filter_submit(array &$form, FormStateInterface $form_state) {
  $form_state->setRedirect('entity.path_alias.collection', [], [
    'query' => [
      'search' => trim($form_state->getValue('filter')),
      'path'   => trim($form_state->getValue('path')),
    ],
  ]);
}

Reset handler:

  /**
   * Resets the filter selections.
   */
  public function _path_admin_filter_reset(array &$form, FormStateInterface $form_state) {
    $form_state->setRedirect('entity.path_alias.collection');
  }

Now we manage to send the two params to filters to path alias collection, but still take only the one param search to filter result, so still no effect of our new filter (path) yet.

The search param added as condition to filter result in the method getEntityIds from core/modules/path/src/PathAliasListBuilder.php

To add our new param as query condition along with search param we need to override this method, and to do so:

  1. Create a new class called CustomPathAliasListBuilder inside your src folder extending PathAliasListBuilder class
    <?php
    
    namespace Drupal\MODULENAME;
    
    use Drupal\path\PathAliasListBuilder;
    
    /**
     * Defines a class to build a listing of path_alias entities.
     *
     * @see \Drupal\path_alias\Entity\PathAlias
     */
    class CustomPathAliasListBuilder extends PathAliasListBuilder {
    
      /**
       * {@inheritdoc}
       */
      protected function getEntityIds() {
        $query = $this->getStorage()->getQuery();
    
        $search = $this->currentRequest->query->get('search');
        $path = $this->currentRequest->query->get('path');
    
        if ($search) {
          $query->condition('alias', $search, 'CONTAINS');
        }
    
       // Add our new param here as query condition.
        if ($path) {
          $query->condition('path', $path, 'CONTAINS');
        }
    
        // Only add the pager if a limit is specified.
        if ($this->limit) {
          $query->pager($this->limit);
        }
    
        // Allow the entity query to sort using the table header.
        $header = $this->buildHeader();
        $query->tableSort($header);
    
        return $query->execute();
      }
    
    }
    
  2. Now we have all set but how we gonna tell Drupal use our new class who filter with the two filters instead of the old one (PathAliasListBuilder)
    To do this we need to use hook_entity_type_alter and set the list builder class for path_alias to our new class:
    use Drupal\MODULENAME\CustomPathAliasListBuilder;
    
    /**
     * Implements hook_entity_type_alter().
     */
    function d8_api__entity_type_alter(array &$entity_types) {
      if (isset($entity_types['path_alias'])) {
        $entity_types['path_alias']->setListBuilderClass(CustomPathAliasListBuilder::class);
      }
    }
    

And that's it, just clear the cache when you done!