Filter pages collection by multiple URL params derived from tags and pages field

Happy new year everyone, hope you all had a great holiday :smile:

Attempting to filter pages by multiple URL params at the same time — by a tags field and a pages field.

Have read up on all filtering articles and forum posts, I could find. However, still having some trouble.

Any tips would be much appreciated :smile:

Background

  • I am making a restaurant guide website. There are two main content types pages (restaurants and cities) set up as a page with siblings (restaurant and city).
  • Restaurants have different categories, and the restaurant/city association is made with a pages field.
  • I would like to filter restaurants with multiple URL params at the same time (by category and city), eg. www.website.com/restaurants/category:burger/city:copenhagen

I have set up a restaurants.php controller that handles the param filtering. Categories are working, but I am having trouble understanding how to filter by city (pages field, see blueprint below).


return function($page, $kirby) {

  // build restaurant page collection
  $restaurants = page('restaurants')->children()->listed();

  // get restaurant types and cities
  $categories = $restaurants->pluck('categories', ',', true);
  $cities = page('cities')->children()->listed();

  // get params and search strings from url
  $category = param('category');
  $city = param('city');

  $filtered = $restaurants
    ->when($category, function ($category) {
      return $this->filterBy('categories', $category, ',');
    })
    ->when($city, function($city) {
      return $this->filter(function($child) use($page) {
          return $child->city()->toPages()->has($city);
      });
    });

  return [
    'restaurants' => $filtered ?? null,
    'type' => $type,
    'city' => $city,
  ];

};

Have tried different tips from the forum, and the above is the latest attempt. Here I get a: Undefined variable $page. Maybe the when and return statement is wrong?

Here is how I have setup the restaurant/city association in my restaurant.yml blueprint.

title: Restaurant
fields:
  text:
    type: text
  category:
    type: multiselect
    options:
      burger: Burger
      bar: Bar
      …
  city:
    type: pages
    query: site.find('cities').children.listed
    max: 1

Again, any tips would be much appreciated!

Best, Oliver

Should be use ($city)

$filtered will always be a collection in this case, no need for the null coalescing operator

It is also unnessessary to use two variable $restaurants and $filtered, you can instead redefine $restaurants after plucking the categories.

This doesn’t seem to be defined…

Thank you for your kind reply :slight_smile:

The returned type was supposed to be category. Like the $filtered variable, it is actually unnecessary.

I changed use($page) to use($city). That certainly fixed the error, that I described.

However, when filtering by city, no results are returned.

I am guessing that is because the value of a param like /city:copenhagen, doesn’t match the value stored in the restaurant field city?

The value stored in the restaurant content file (for city uuid) looks like this:

City: - page://XqfTh0D1qUdjsu8Z.

How do I match the URL param value with the city page slug with the has method return $child->city()->toPages()->has($city);?


return function($page, $kirby) {

  // build restaurant page collection
  $restaurants = page('restaurants')->children()->listed();

  // get restaurant types and cities
  $categories = $restaurants->pluck('categories', ',', true);

  // get params and search strings from url
  $category = param('category');
  $city = param('city');

  $restaurants = $restaurants
    ->when($category, function ($category) {
      return $this->filterBy('categories', $category, ',');
    })
    ->when($city, function($city) {
      return $this->filter(function($child) use($page) {
          return $child->city()->toPages()->has($city);
      });
    });

  return [
    'restaurants' => $restaurants
  ];

};

Fantastic, with your final suggestion, here is the solution:

return function($page, $kirby) {

  // build restaurant page collection
  $restaurants = page('restaurants')->children()->listed();

  // get restaurant types and cities
  $categories = $restaurants->pluck('categories', ',', true);

  // get params and search strings from url
  $category = param('category');
  $city = param('city');

  $restaurants = $restaurants
    ->when($category, function ($category) {
      return $this->filterBy('categories', $category, ',');
    })
    ->when($city, function($city) {
      return $this->filter(function($child) use($page) {
          return $child->city()->toPage()?->slug() === $city;
      });
    });

  return [
    'restaurants' => $restaurants
  ];

};

As always, your insights are spot on Sonja — thank you so much! :slight_smile:

Assuming that your are using the page slug for the parameter:

return $child->city()->toPage()?->slug() === $city;

Didn’t read to the end…

1 Like