d4l-data4life / kirby3-static-site-generator

Static site generator plugin for Kirby 3+. With this plugin you can create a directory with assets, media and static html files generated from your pages. The result is an even faster site with less potential vulnerabilities.
MIT License
137 stars 7 forks source link

✨ Add configuration option custom_filters #52

Closed ShallowRed closed 2 years ago

ShallowRed commented 2 years ago

Description

Add a configuration option 'custom_filters' to plugin. It should work like 'skipTemplate' option while providing more skip/filtering methods.

Option should return an array of filters, matching available Kirby page filter methods arguments (except dates related ones for now).

// config.php

return [
    'd4l' => [
      'static_site_generator' => [
          'custom_filters' => [
              [$page_property, $filter_method, $value]
          ]
      ]
    ]
];

Each filter will then be used to filter pages used for generation in d4l.static-site-generator api routes.

// kirby3-static-site-generator/index.php

$customFilters = $kirby->option('d4l.static_site_generator.custom_filters', []);
$pages = $kirby->site()->index()->filterBy('intendedTemplate', 'not in', $skipTemplates);
foreach ($customFilters as $filter) {
    $pages = $pages->filterBy(...$filter);
}

Examples

// config.php

'custom_filters' => [
    ['slug', '!*=', 'some_string'],
    ['dirname', '!*', 'some-regex'],
    ['uri', 'in', array()],
    ['blueprint', '!*=', 'some_string'],
    ['parent', '!^=', 'some_string],
];

Motivation

When setting some routes in 'custom_routes' plugin option, for example ['path' => 'pagename', 'page' => 'folder/pagename'], the page gets generated twice in /static folder (at /static/folder/pagename/index.html and /static/pagename/index.html).

When using endpoint or field method, we have no access to the $pages that get generated, and thus cannot prevent plugin from generating files in /static/folder/.

In conjonction to such 'custom_routes', 'custom_filters' option could help in matching Kirby's dynamic routing behavior, for example Removing the parent page slug from the URL.

Full use-case

// config.php

<?php

/*
 * My content folder:
 * - 1_home
 * - 2_main
 *   - 1_page1
 *   - 2_page2
 * ...
 *
 * The 'main' page is only used to structure my content,
 * I don't want to be able to access it directly
 * nor it's slug to appear in URLs.
 */

$fantom_page = 'main'; // The page name I want to hide

return [

    /*
     * My dynamic routing
     */

    'routes' => [

        // Redirect 'main' page to homepage
        [
            'pattern' => $fantom_page,
            'action' => function () {
                go(page('home')->uid());
            },
        ],

        // Remove 'main' from its children urls
        [
            'pattern' => $fantom_page . '/(:all)',
            'action' => function ($uid) {
                go($uid);
            },
        ],

        // Redirect 'main' children pages
        [
            'pattern' => '(:all)',
            'action' => function ($uid) use ($fantom_page) {
                if ($uid == '') {
                    $uid = 'home';
                }
                $page = page($uid) ?? page($fantom_page . '/' . $uid) ?? site()->errorPage();
                return site()->visit($page);
            },
        ],
    ],

    /*
     * Now I want to reproduce this behavior in my static website
     */

    'd4l.static_site_generator' => [

        'endpoint' => 'generate-static-site',

        'custom_filters' => [ // new option proposal
            ['url', '!*=', $fantom_page], // Do not generate 'main' children pages in output 'main' folder
        ],
    ],

    'ready' => function () use ($fantom_page) {

        $pages = site()->pages()->index();

        $custom_routes_input_filters = [
            ['slug', '!=', $fantom_page], // Do not take 'main' page as an input
            ['url', '*=', $fantom_page], // Take every 'main' children as 'custom_routes' option value
        ];

        foreach ($custom_routes_input_filters as $filter) {
            $pages = $pages->filterBy(...$filter);
        }

        return [

            'd4l.static_site_generator' => [
                'custom_routes' => A::map(
                    $pages->toArray(),
                    function ($page) use ($fantom_page) { // Generate 'main' children at output root
                        $page = $page['uri'];
                        $path = Str::replace($page, $fantom_page . '/', '');
                        return compact('path', 'page');
                    }
                ),
            ],
        ];
    },
];

Possible enhancements

Testing

Tested with the two endpoints method

jonathan-reisdorf commented 2 years ago

@ShallowRed thank you for this useful extension :raised_hands: Just three very minor things (I could also do these after merging if you don't have time): Your code is currently indented with tabs and ours uses spaces - would be great to align. Also, in composer.json you could bump the version. Since you're introducing a new feature, this could be 1.4.0. And lastly if you want you can already add a small note about the feature in the readme so that others know it exists :) Thank you!

By the way, using a custom page model you could also achieve removing the parent page slug from the URL (e.g. by overriding the url method). Anyway, this is a great and useful enhancement, again thanks a lot! :)

ShallowRed commented 2 years ago

Cool, i'll Be back soon with a cleaner PR !

ShallowRed commented 2 years ago

Also, in my fork of the plugin, I have added the possibility to generate files ending with .xml (in order to generate my sitemap.xml)

//  static-site-generator/class.php l.322

protected function _cleanPath(string $path): string
  {
    $path = str_replace('//', '/', $path);
    $path = preg_replace('/([^\/]+\.(htm(l)?)|(xml))\/index.html$/i', '$1', $path); // <= here I added '|(xml)' in regex

    if (strpos($path, '//') !== false) {
      return $this->_cleanPath($path);
    }

    return $path;
  }

Do you think that could be a feature ?

jonathan-reisdorf commented 2 years ago

Also, in my fork of the plugin, I have added the possibility to generate files ending with .xml (in order to generate my sitemap.xml)

Good idea, didn't think of other non-binary (or even binary) files one might want to generate that way. I think I'll create a separate PR for this, but make it more generic so it works for any file ending. Thanks for the idea :raised_hands: