Tags: list results of all existing pages

This seems to be pretty simple but I am still in a struggle with code. Because most of the tag discussions here in the forums deal with the tag cloud - this is not my question.

I have a site with different pages, all of them built like a blog (and in fact using the method for blogs that is described in the Kirby documentation), and all of them using a blog.php template. Each article (blogarticle.php) has a set of tags. What I want to achieve is a result page that shows me all the articles that I marked with a certain tag as soon as I click on that tag on the article page.

Example:
(page) Projects > (article) Hygiene > (tags) Women, Customs, Population
(page) Events > (article) Conference > (tags) Health, Asia, Women
(page) Publications > (article) Children’s care > (tags) Asia, Population, Education

When I click on the tag “Asia” I would want a list showing

  • Conference
  • Children’s care

Since my use of PHP is more based on logic thinking (uf, thanks god it helps in most cases) rather than on profound knowledge I would appreciate some help, unless it causes too much work and attention.

I tried to start with a controller for blogarticle.php

<?php return function($site, $pages, $page) {

  // fetch the basic set of pages
  $articles = $page('???????')->children()->visible()->flip();

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

 // ??????

  // apply pagination
  $articles   = $articles->paginate(10);
  $pagination = $articles->pagination();

}; ?>

And this is the idea for the blogarticle.php template:

<ul class="tag menu">
  <?php foreach($page->tags()->split(',') as $tag): ?>
  <li><a <?php ecco($tag == urlencode(param('tag')), ' class="active"') ?> href="<?php echo url('link to page one level up' . $tag - here one level down again)?>"><?php echo $tag ?></a></li>
  <?php endforeach ?>
</ul>

First, rename the blogarticle.php controller to the template name of your overview page. You want to display the list of articles filtered by tag on a separate page, not on the page of a single article.

The $articles variable can be set to:

$site->index()->filterBy('template', 'blogarticle');

which will get all pages with the template blogarticle, no matter where they are.

The second component is the link from your articles. Since you now know the page URI of that overview page you created, you can get the URL to the tag using url('OVERVIEWPAGE/tag:' . $tag).

Thanks for now – I will check that! I will probably be back next Monday to give you a feedback. Have a nice weekend in the meantime.

Just to be certain: this is something you mean to be in the blogarticle.php template, right?

This does not work for me, alas!

The link that is generated looks like that: |www.mysite.org/events/tag:tag1, and I get a list with ALL events, with the page title “Events” even those that have no “tag1” tag - or no tag at all for that matter. No other articles from other pages with the “tag1” tag are displayed. It looks as if tag:tag1 is completely ignored.

The template blogarticle.php is directed to by all overviewpages, not only the one and single OVERVIEWPAGE (in my example “events”). So I need something general that first refers to blog.php and then to blogarticle.php, like |www.mysite.org/tag-search/tag:tag1

As I said, you need to create an overview page that is used for all categories/main blog pages.
This page needs to use the template that has the controller to filter by tag.

Let’s go through this step by step again:

  1. Create a folder for the result page with a text file in it (or use a router that calls the template), let’s suppose, /results with results.txt.
  2. Create a controller results.php with the filter code
  <?php return function($site, $pages, $page) {

  // fetch the basic set of pages
  $articles = $site->index()->filterBy('template', 'blogarticle');;

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }
  return compact('articles');
  };
  1. Create a results.php template to display the results.

  2. In your blogarticle.php tag menu:

<ul class="tag menu">
  <?php foreach($page->tags()->split(',') as $tag): ?>
  <li><a <?php ecco($tag == urlencode(param('tag')), ' class="active"') ?> href="<?php echo url('results/tag:' . $tag)?>"><?php echo $tag ?></a></li>
  <?php endforeach ?>
</ul>
2 Likes

You made my day! Thanks so much to you both (and sorry for the confusion @lukasbestle ).

There is only one tiny thing to add: Instead of

<?php echo url('results/' . $tag)?>

it should be

<?php echo url('results/tag:' . $tag)?>

PS: Maybe this could be something for the documentation?

Here a little extra question:

I have a basic setup such as the starterkit with both the project page/s (and respective files) and the blog (and respective files) because both serve completely different purposes. How would I do the same as above, but including also all the projects?

The code snippet for projects and articles is certainly the same, directing to the result page, but what about the result.php controller?

<?php return function($site, $pages, $page) {

  // fetch the basic set of pages
  $articles = $site->index()->filterBy('template', 'blogarticle');
  $projects = $site->index()->filterBy('template', 'project');

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
    $projects = $projects->filterBy('tags', $tag, ',');
  }
  
  return compact('articles');
  ?????

};
?>

So far I get a correct output for the articles but still a list of all unfiltered projects.

You could use a custom collection filter, not tested:

$templates = array('blogarticle', 'project');
$articles = $site->index()->filter(function($child) use ($templates) {
  return in_array($child->template(), $templates);
});
1 Like

Very cool, thanks a lot! This solved everything.

Trying this stuff out and most seems to work. What did you put in the results.php page to display filtered results?

You don’t need anything special in the results.php template, the logic goes into the controller and the filtering is done their. You either return the filtered articles or all articles depending on the if statement. In the template, all you need to do is loop through $articles.