<commit_before><?php

namespace KirbyStaticBuilder;

use C;
use Exception;
use F;
use Folder;
use Page;
use Pages;
use Response;
use Site;
use Str;
use Tpl;

/**
 * Static HTML builder class for Kirby CMS
 * Exports page content as HTML files, and copies assets.
 */
class Builder
{
    // Used for building relative URLs
    const URLPREFIX = 'STATICBUILDER_URL_PREFIX';

    // Kirby instance
    protected $kirby;

    // Project root
    protected $root;

    // Language codes
    protected $langs = [];

    // Config (there is a 'plugin.staticbuilder.[key]' for each one)
    protected $outputdir  = 'static';
    protected $baseurl    = '/';
    protected $assets     = ['assets', 'content', 'thumbs'];
    protected $filter     = null;
    protected $filename   = '.html';
    protected $uglyurls   = false;
    protected $pagefiles  = false;
    protected $catcherror = true;

    // Callable for PHP Errors
    public $shutdown;
    public $lastpage;

    // Storing results
    public $summary = [];

    /**
     * Builder constructor.
     * Resolve config and stuff.
     * @throws Exception
     */
    public function __construct()
    {
        // Kirby instance with some hacks
        $this->kirby = $this->kirbyInstance();

        // Project root
        $this->root = $this->normalizeSlashes($this->kirby->roots()->index);

        // Multilingual
        if ($this->kirby->site()->multilang()) {
            foreach ($this->kirby->site()->languages() as $language) {
                $this->langs[] = $language->code();
            }
        }
        else $this->langs[] = null;

        // Ouptut directory
        $dir = C::get('plugin.staticbuilder.outputdir', $this->outputdir);
        $dir = $this->isAbsolutePath($dir) ? $dir : $this->root . '/' . $dir;
        $folder = new Folder($this->normalizePath($dir));

        if ($folder->name() !== 'static') {
            throw new Exception('StaticBuilder: outputdir MUST be "static" or end with "/static"');
        }
        if ($folder->exists() === false) {
            $folder->create();
        }
        if ($folder->isWritable() === false) {
            throw new Exception('StaticBuilder: outputdir is not writeable.');
        }
        $this->outputdir = $folder->root();

        // URL root
        $this->baseurl = C::get('plugin.staticbuilder.baseurl', $this->baseurl);

        // Normalize assets config
        $assets = C::get('plugin.staticbuilder.assets', $this->assets);
        $this->assets = [];
        foreach ($assets as $a) {
            if (is_string($a)) $this->assets[$a] = $a;
            elseif (is_array($a) && count($a) > 1)
                $this->assets[array_shift($a)] = array_shift($a);
        }

        // Filter for pages to build or ignore
        if (is_callable($filter = C::get('plugin.staticbuilder.filter'))) {
            $this->filter = $filter;
        }

        // File name or extension for output pages
        if ($fn = C::get('plugin.staticbuilder.filename')) {
            $fn = str_replace(['/', '\\'], '', $fn);
            $this->filename = Str::startsWith($fn,'.') ? $fn : '/' . $fn;
        }

        // Output ugly URLs (e.g. '/my/page/index.html')?
        $this->uglyurls = C::get('plugin.staticbuilder.uglyurls', $this->uglyurls);

        // Copy page files to a folder named after the page URL?
        $this->pagefiles = C::get('plugin.staticbuilder.pagefiles', $this->pagefiles);

        // Catch PHP errors while generating pages?
        $this->catcherror = C::get('plugin.staticbuilder.catcherror', $this->catcherror);
    }

    /**
     * Change some of Kirby’s settings to help us building HTML that
     * is a tiny bit different from the live pages.
     * @return \Kirby
     */
    protected function kirbyInstance()
    {
        // This will retrieve the existing instance with stale settings
        $kirby = kirby();
        // We need to call configure again with the new url prefix
        C::set('url', static::URLPREFIX);
        $kirby->configure();
        // But this one stays cached anyway, so we have to update it manually
        $kirby->site->url = static::URLPREFIX;
        return $kirby;
    }

    /**
     * Figure out if a filesystem path is absolute or if we should treat
     * it as relative (to the project folder or output folder).
     * @param string $path
     * @return boolean
     */
    protected function isAbsolutePath($path)
    {
        $pattern = '/^([\/\\\]|[a-z]:)/i';
        return preg_match($pattern, $path) == 1;
    }

    /**
     * Normalize a file path string to remove ".." etc.
     * @param string $path
     * @param string $sep Path separator to use in output
     * @return string
     */
    protected function normalizePath($path, $sep='/')
    {
        $path = $this->normalizeSlashes($path, $sep);
        $out = [];
        foreach (explode($sep, $path) as $i => $fold) {
            if ($fold == '..' && $i > 0 && end($out) != '..') array_pop($out);
            $fold = preg_replace('/\.{2,}/', '.', $fold);
            if ($fold == '' || $fold == '.') continue;
            else $out[] = $fold;
        }
        return ($path[0] == $sep ? $sep : '') . join($sep, $out);
    }

    /**
     * Normalize slashes in a string to use forward slashes only
     * @param string $str
     * @param string $sep
     * @return string
     */
    function normalizeSlashes($str, $sep='/')
    {
        $result = preg_replace('/[\\/\\\]+/', $sep, $str);
        return $result === null ? '' : $result;
    }

    /**
     * Should we include this page and its files in the static build?
     * @param Page $page
     * @return bool
     * @throws Exception
     */
    protected function filterPage(Page $page)
    {
        if ($this->filter != null) {
            $val = call_user_func($this->filter, $page);
            if (!is_bool($val)) throw new Exception(
                "StaticBuilder page filter must return a boolean value");
            return $val;
        } else {
            // Only include pages which have an existing text file
            // (We check that it exists because Kirby sets the text file
            // name to the folder name when it can't find one.)<commit_msg>feat: exclude folders with Kirby Modules by default<commit_after><?php

namespace KirbyStaticBuilder;

use C;
use Exception;
use F;
use Folder;
use Page;
use Pages;
use Response;
use Site;
use Str;
use Tpl;

/**
 * Static HTML builder class for Kirby CMS
 * Exports page content as HTML files, and copies assets.
 */
class Builder
{
    // Used for building relative URLs
    const URLPREFIX = 'STATICBUILDER_URL_PREFIX';

    // Kirby instance
    protected $kirby;

    // Project root
    protected $root;

    // Language codes
    protected $langs = [];

    // Config (there is a 'plugin.staticbuilder.[key]' for each one)
    protected $outputdir  = 'static';
    protected $baseurl    = '/';
    protected $assets     = ['assets', 'content', 'thumbs'];
    protected $filter     = null;
    protected $filename   = '.html';
    protected $uglyurls   = false;
    protected $pagefiles  = false;
    protected $catcherror = true;

    // Callable for PHP Errors
    public $shutdown;
    public $lastpage;

    // Storing results
    public $summary = [];

    /**
     * Builder constructor.
     * Resolve config and stuff.
     * @throws Exception
     */
    public function __construct()
    {
        // Kirby instance with some hacks
        $this->kirby = $this->kirbyInstance();

        // Project root
        $this->root = $this->normalizeSlashes($this->kirby->roots()->index);

        // Multilingual
        if ($this->kirby->site()->multilang()) {
            foreach ($this->kirby->site()->languages() as $language) {
                $this->langs[] = $language->code();
            }
        }
        else $this->langs[] = null;

        // Ouptut directory
        $dir = C::get('plugin.staticbuilder.outputdir', $this->outputdir);
        $dir = $this->isAbsolutePath($dir) ? $dir : $this->root . '/' . $dir;
        $folder = new Folder($this->normalizePath($dir));

        if ($folder->name() !== 'static') {
            throw new Exception('StaticBuilder: outputdir MUST be "static" or end with "/static"');
        }
        if ($folder->exists() === false) {
            $folder->create();
        }
        if ($folder->isWritable() === false) {
            throw new Exception('StaticBuilder: outputdir is not writeable.');
        }
        $this->outputdir = $folder->root();

        // URL root
        $this->baseurl = C::get('plugin.staticbuilder.baseurl', $this->baseurl);

        // Normalize assets config
        $assets = C::get('plugin.staticbuilder.assets', $this->assets);
        $this->assets = [];
        foreach ($assets as $a) {
            if (is_string($a)) $this->assets[$a] = $a;
            elseif (is_array($a) && count($a) > 1)
                $this->assets[array_shift($a)] = array_shift($a);
        }

        // Filter for pages to build or ignore
        if (is_callable($filter = C::get('plugin.staticbuilder.filter'))) {
            $this->filter = $filter;
        }

        // File name or extension for output pages
        if ($fn = C::get('plugin.staticbuilder.filename')) {
            $fn = str_replace(['/', '\\'], '', $fn);
            $this->filename = Str::startsWith($fn,'.') ? $fn : '/' . $fn;
        }

        // Output ugly URLs (e.g. '/my/page/index.html')?
        $this->uglyurls = C::get('plugin.staticbuilder.uglyurls', $this->uglyurls);

        // Copy page files to a folder named after the page URL?
        $this->pagefiles = C::get('plugin.staticbuilder.pagefiles', $this->pagefiles);

        // Catch PHP errors while generating pages?
        $this->catcherror = C::get('plugin.staticbuilder.catcherror', $this->catcherror);
    }

    /**
     * Change some of Kirby’s settings to help us building HTML that
     * is a tiny bit different from the live pages.
     * @return \Kirby
     */
    protected function kirbyInstance()
    {
        // This will retrieve the existing instance with stale settings
        $kirby = kirby();
        // We need to call configure again with the new url prefix
        C::set('url', static::URLPREFIX);
        $kirby->configure();
        // But this one stays cached anyway, so we have to update it manually
        $kirby->site->url = static::URLPREFIX;
        return $kirby;
    }

    /**
     * Figure out if a filesystem path is absolute or if we should treat
     * it as relative (to the project folder or output folder).
     * @param string $path
     * @return boolean
     */
    protected function isAbsolutePath($path)
    {
        $pattern = '/^([\/\\\]|[a-z]:)/i';
        return preg_match($pattern, $path) == 1;
    }

    /**
     * Normalize a file path string to remove ".." etc.
     * @param string $path
     * @param string $sep Path separator to use in output
     * @return string
     */
    protected function normalizePath($path, $sep='/')
    {
        $path = $this->normalizeSlashes($path, $sep);
        $out = [];
        foreach (explode($sep, $path) as $i => $fold) {
            if ($fold == '..' && $i > 0 && end($out) != '..') array_pop($out);
            $fold = preg_replace('/\.{2,}/', '.', $fold);
            if ($fold == '' || $fold == '.') continue;
            else $out[] = $fold;
        }
        return ($path[0] == $sep ? $sep : '') . join($sep, $out);
    }

    /**
     * Normalize slashes in a string to use forward slashes only
     * @param string $str
     * @param string $sep
     * @return string
     */
    function normalizeSlashes($str, $sep='/')
    {
        $result = preg_replace('/[\\/\\\]+/', $sep, $str);
        return $result === null ? '' : $result;
    }

    /**
     * Should we include this page and its files in the static build?
     * @param Page $page
     * @return bool
     * @throws Exception
     */
    protected function filterPage(Page $page)
    {
        if ($this->filter != null) {
            $val = call_user_func($this->filter, $page);
            if (!is_bool($val)) throw new Exception(
                "StaticBuilder page filter must return a boolean value");
            return $val;
        } else {
            // Exclude folders containing Kirby Modules
            // https://github.com/getkirby-plugins/modules-plugin
            if (strpos($page->intendedTemplate(), 'module.') === 0 ) {
                return false;
            }
            // Only include pages which have an existing text file
            // (We check that it exists because Kirby sets the text file
            // name to the folder name when it can't find one.)
{'fraction_non_alphanumeric': 0.1511453987247107, 'fraction_numerical': 0.000865937180193655, 'mean_word_length': 2.0797575757575757, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 102, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7081288', 'n_tokens_mistral': 3713, 'n_tokens_neox': 3655, 'n_words': 1088}