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.

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:

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 );
?>

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

Thanks Sonja! I’ll check it out.