Best way to get a page collection with a month and year structure?

I am working on a blog with categories as folders and a year structure. There are 8 categories, hence, 8 folders. Every category has years as folders and each year has 12 folders/months. Now, each month holds about 20 or 30 posts. That means we would have somewhat about 1500 posts for 4 years. I understand that’s OK in terms of performance because Kirby works better with this folder structure instead of a 1000+ subpages in just one folder.

The question is, what would be the best way to get a collection of pages under that structure, let’s say the 20 newest posts or a “filtered by tags collection” without using the index() function. What would be a healthy performance-wise alternative to just calling the index() function with a filterBy() and getting some posts?

Assuming blog posts use the same fields, I wouldn’t divide them in category folders. Instead…

  • categories
    • science
    • education
    • tech
    • health …
  • blog
    • 2017
      • post-5
      • post-4
      • post-3
    • 2016
      • post-2
      • post-1

You can use the select field on the post blueprint to select the category a given post belongs to. I prefer to put page filtering logic inside controllers instead of polluting templates with them. Go check the docs in case you are not familiar with them. Check out some examples:

<?php // site/controllers/category

return function($site, $pages, $page) {
    $posts = $pages->find('blog')
                   ->grandChildren()
                   ->visible()
                   ->filterBy('category', $page->uid())
                   ->paginate(12);

    $pagination = $posts->pagination();

    return compact('posts', 'pagination');
};

// https://site.com/categories/science

Since you would already have posts inside year folders, you will only need to use routes to allow filtering by month.

<?php // site/config/config.php

c::set('routes', [
    [
        // https://site.com/blog/2017/01
        'pattern' => 'blog/\d{4}/(\d{2})',
        'action'  => function($month) {
            return ['blog', compact('month')];
        }
    ]
]);
<?php // site/controllers/blog

// the 4th argument comes from the route
return function($site, $pages, $page, $args) {
    $posts = $page->grandChildren()
                  ->visible();

    if (isset($args['month'])) {
        $posts = $posts->filter(function($post) use ($args) {
            // ajust the date format you have enabled strftime
            return $post->date('m') === $args['month'];
        });
    }

    $posts = $posts->sortBy('date', 'desc')
                   ->paginate(10);

    $pagination = $posts->pagination();

    return compact('posts', 'pagination');
};

In situations like this I also like to enable cache. This way no matter how crazy your filter is, it will run a once and get cached by Kirby until the content is updated.

1 Like

Wow, thank you very much!

I’m using different templates for each category post so I’ll use different sections and controllers.

The second part is awesome. It is a great way to get content dynamically instead of making actual folders. Smooth and classy.

Thanks for the detailed explanation. :smiley:

1 Like

I haven’t test it my self but I guess you can use a custom post type for each category to apply different templates while using a single controller to drive them all.

https://getkirby.com/docs/cookbook/custom-post-types

I don’t think you can use a single controller with different template names. An alternative would be to use the same template/controller but include different snippets bases on category.

Bad guess then :frowning:

Anyways, then you could have a base category controller named category and require it on the other category controllers to avoid repeating yourself.

But you could also do something like this:

Snippets:

  • article-category1.php
  • article-category2.php
  • etc.
<?php
// article.php template
$category = $page->category()->value();
snippet('article-' . $category );
?>
2 Likes

Great, I’ve used conditioned templates and controllers before but only with the intendedTemplate() function and not a category variable.

Now, I was wondering (maybe this needs a new thread, If that’s the case I’ll create it)…

I’ve seen some posts where some of you guys talk about using a database for indexing articles and storing them through the panel and retrieving them on front-end with the Kirby classes and that index.

So, what would be a good proof of concept for this? Including the panel. I mean, would the panel really need some hard custom changes or can it all be done through the default hooks and Kirby database classes?

Maybe @bastianallgeier’s library project is a starting point: https://github.com/bastianallgeier/library#sqlite-index

1 Like

Thanks Sonja! I’ll check it out.