bnomei/kirby3-boost

View on GitHub
index.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

use Bnomei\Bolt;
use Bnomei\BoostCache;
use Bnomei\BoostDirInventory;
use Bnomei\BoostIndex;
use Kirby\Cms\Page;
use Kirby\Cms\Pages;
use Kirby\Toolkit\Obj;
use Kirby\Toolkit\Str;
use Kirby\Uuid\FileUuid;
use Kirby\Uuid\Uuid;
use Kirby\Uuid\Uuids;

@include_once __DIR__.'/vendor/autoload.php';

load([
    'bnomei\\bolt' => 'classes/Bolt.php',
    'bnomei\\boostcache' => 'classes/BoostCache.php',
    'bnomei\\boostdirinventory' => 'classes/BoostDirInventory.php',
    'bnomei\\boostfile' => 'classes/BoostFile.php',
    'bnomei\\boostindex' => 'classes/BoostIndex.php',
    'bnomei\\boostpage' => 'classes/BoostPage.php',
    'bnomei\\boostuser' => 'classes/BoostUser.php',
    'bnomei\\cachebenchmark' => 'classes/CacheBenchmark.php',
    'bnomei\\modelhasboost' => 'classes/ModelHasBoost.php',
], __DIR__);

if (! function_exists('bolt')) {
    function bolt(string $id, ?Page $parent = null, bool $cache = true, bool $extend = true)
    {
        $id = str_replace('page://', '', $id);
        $cacheKey = 'page/'.Str::substr($id, 0, 2).'/'.Str::substr($id, 2);
        if ($pageIdFromUuidCache = Uuids::cache()->get($cacheKey)) {
            $id = $pageIdFromUuidCache;
        }

        return Bolt::page($id, $parent, $cache, $extend);
    }
}

if (! function_exists('modified')) {
    function modified($model)
    {
        return BoostCache::modified($model);
    }
}

if (! function_exists('boost')) {
    function boost($id)
    {
        if (! $id || ! option('bnomei.boost.helper')) {
            return null;
        }

        if (is_array($id)) {
            $pages = [];
            foreach ($id as $uuid) {
                $pages[] = boost($uuid);
            }

            return new Pages($pages);
        }

        $schema = 'page';

        if ($id instanceof Uuid) {
            $schema = $id->uri->type();
            $id = $id->id();
        }

        if (is_string($id) && Str::contains($id, '://')) {
            [$schema, $id] = explode('://', $id);
        }

        if ($schema === 'file' && is_string($id)) {
            // get id for file, guess parent page, get that then file
            // since that is faster than letting kirby core do it
            // FileUuuid::findByCache() would resolve so we do it manually
            if ($uuid = FileUuid::for($schema.'://'.$id)) {
                // $value = $uuid->value(); // would resolve parent
                $value = Uuids::cache()->get($uuid->key());
                if (! $value) {
                    return null;
                }
                $parent = boost($value['parent']);

                return $parent?->file($value['filename']);
            }
        }

        if ($schema === 'page' && is_string($id)) {
            $page = BoostIndex::page($id);
            if (! $page) {
                $page = Bolt::page($id);
            }

            return $page;
        }

        return null;
    }
}

BoostDirInventory::singleton(); // init

Kirby::plugin('bnomei/boost', [
    'options' => [
        'hashalgo' => 'xxh3,crc32', // best starting php 8.1 (use crc32 on php 8.0)
        'cache' => true,
        'expire' => 0,
        'fileModifiedCheck' => false, // expects content file to not be altered outside of kirby
        'read' => true, // read from cache
        'write' => true, // write to cache
        'drafts' => true, // index drafts as well
        'patch' => [
            'files' => true, // monkey patch files class
        ],
        'helper' => true, // use boost helper
    ],
    'blueprints' => [
        'fields/boostidkv' => __DIR__.'/blueprints/fields/boostidkv.yml',
        'fields/boostidkvs' => __DIR__.'/blueprints/fields/boostidkvs.yml',
    ],
    'collections' => [
        'boostidkvs' => require __DIR__.'/collections/boostidkvs.php',
    ],
    'pageMethods' => [ // PAGE
        'bolt' => function (string $id) {
            return Bolt::page($id, $this);
        },
        'boost' => function () {
            $page = $this;

            // has boost?
            if ($page->hasBoost() === true) {
                // needs write?
                $lang = kirby()->languageCode();

                // if needs write
                if (! ($page->readContentCache($lang))) {
                    // then write
                    $page->writeContentCache($page->content()->toArray(), $lang);
                }
            }

            // add after cache was read and id exists
            $page->boostIndexAdd();

            return true;
        },
        'unboost' => function () {
            // has boost?
            if ($this->hasBoost() === true) {
                $this->deleteContentCache();
            }

            $this->boostIndexRemove();
        },
        'isBoosted' => function () {
            // has boost?
            if ($this->hasBoost() !== true) {
                return false;
            }

            // $this->boostIndexAdd(); // this would trigger content add
            return $this->isContentBoosted(kirby()->languageCode());
        },
        'boostIndexAdd' => function () {
            return BoostIndex::singleton()->add($this);
        },
        'boostIndexRemove' => function () {
            return BoostIndex::singleton()->remove($this);
        },
        'boostCacheDirUri' => function () {
            BoostCache::singleton()->set(
                $this->uuid()->id().'/diruri',
                $this->diruri(),
                option('bnomei.boost.expire')
            );
        },
        'searchForTemplate' => function (string $template): Pages {
            $pages = [];
            foreach (BoostIndex::singleton()->toKVs() as $obj) {
                /* @var Obj $obj */
                $diruri = $obj->diruri;
                if ($obj->template === $template && Str::contains($diruri, $this->diruri())) {
                    $pages[] = bolt($diruri);
                }
            }

            return new Pages($pages);
        },
    ],
    'pagesMethods' => [ // PAGES
        'boost' => function () {
            $time = -microtime(true);
            $count = 0;
            BoostCache::beginTransaction();
            foreach ($this as $page) {
                $count += $page->boost() ? 1 : 0;
            }
            BoostCache::endTransaction();

            return round(($time + microtime(true)) * 1000);
        },
        'unboost' => function () {
            $time = -microtime(true);
            $count = 0;
            BoostCache::beginTransaction();
            foreach ($this as $page) {
                $count += $page->unboost() ? 1 : 0;
            }
            BoostCache::endTransaction();

            return round(($time + microtime(true)) * 1000);
        },
        'boostIndexAdd' => function () {
            $time = -microtime(true);
            foreach ($this as $page) {
                BoostIndex::singleton()->add($page);
            }

            return round(($time + microtime(true)) * 1000);
        },
        'boostIndexRemove' => function () {
            $time = -microtime(true);
            foreach ($this as $page) {
                BoostIndex::singleton()->remove($page);
            }

            return round(($time + microtime(true)) * 1000);
        },
        'boostmark' => function (): array {
            $time = -microtime(true);
            $str = '';
            $count = 0;
            BoostCache::beginTransaction();
            foreach ($this as $page) {
                if ($page->hasBoost() === true) {
                    // uuid and a field to force reading from cache calling the uuid & title from content file
                    $str .= $page->diruri().$page->modified().$page->uuid()->id().$page->title()->value();
                    $page->boostIndexAdd();
                    $count++;
                }
            }
            BoostCache::endTransaction();

            return [
                'duration' => round(($time + microtime(true)) * 1000),
                'count' => $count,
                'checksum' => md5($str),
            ];
        },
    ],
    'siteMethods' => [
        'boost' => function () {
            $count = 0;
            Bolt::index(function ($page) use (&$count) {
                $count += $page->boost() ? 1 : 0;
            });

            return $count;
        },
        'boostmark' => function () {
            $drafts = option('bnomei.boost.drafts');

            return site()->index($drafts)->boostmark();
        },
        'searchForTemplate' => function (string $template): Pages {
            $pages = [];
            foreach (BoostIndex::singleton()->toKVs() as $obj) {
                /* @var Obj $obj */
                if ($obj->template === $template) {
                    $pages[] = bolt($obj->diruri);
                }
            }

            return new Pages($pages);
        },
        'siteindexfolders' => function () {
            $paths = kirby()->cache('bnomei.boost')->get('siteindexfolders', []);

            if (count($paths) === 0) {
                $drafts = option('bnomei.boost.drafts');
                $root = kirby()->roots()->content();

                $iter = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS),
                    RecursiveIteratorIterator::SELF_FIRST,
                    RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied"
                );

                $paths = [];
                foreach ($iter as $path => $dir) {
                    if ($dir->isDir()) {
                        if (! $drafts && strstr($path, '_drafts') === false) {
                            continue;
                        }
                        $paths[] = str_replace($root.DIRECTORY_SEPARATOR, '', $path);
                    }
                }
                kirby()->cache('bnomei.boost')->set('siteindexfolders', $paths, 1);
            }

            return $paths;
        },
    ],
    'fieldMethods' => [
        'toPageBoosted' => function ($field): ?Page {
            return boost($field->value);
        },
        'toPagesBoosted' => function ($field): Pages {
            $pages = [];
            foreach (explode(',', $field->value) as $value) {
                if ($page = boost($value)) {
                    $pages[] = $page;
                }
            }

            return new Pages($pages);
        },
    ],
    'hooks' => [
        'page.*:after' => function ($event, $page) {
            if ($event->action() !== 'render' && option('bnomei.boost.helper')) {
                BoostDirInventory::singleton()->flush();
            }
        },
        'file.*:after' => function ($event, $page) {
            if ($event->action() !== 'render' && option('bnomei.boost.helper')) {
                BoostDirInventory::singleton()->flush();
            }
        },
        'page.create:after' => function ($page) {
            if (option('bnomei.boost.helper')) {
                $page->boostIndexAdd();
            }
        },
        'page.update:after' => function ($newPage, $oldPage) {
            if (option('bnomei.boost.helper')) {
                $newPage->boostIndexAdd();
            }
        },
        'page.duplicate:after' => function ($duplicatePage, $originalPage) {
            if (option('bnomei.boost.helper')) {
                $duplicatePage->boostIndexAdd();
            }
        },
        'page.changeNum:after' => function ($newPage, $oldPage) {
            if (option('bnomei.boost.helper')) {
                $newPage->boostIndexAdd();
                $newPage->index(option('bnomei.boost.drafts'))->boostIndexAdd();
            }
        },
        'page.changeSlug:after' => function ($newPage, $oldPage) {
            if (option('bnomei.boost.helper')) {
                $newPage->boostIndexAdd();
                $newPage->index(option('bnomei.boost.drafts'))->boostIndexAdd();
            }
        },
        'page.changeStatus:after' => function ($newPage, $oldPage) {
            if (option('bnomei.boost.helper')) {
                $newPage->boostIndexAdd();
                $newPage->index(option('bnomei.boost.drafts'))->boostIndexAdd();
            }
        },
        'page.changeTemplate:after' => function ($newPage, $oldPage) {
            if (option('bnomei.boost.helper')) {
                $newPage->boostIndexAdd();
            }
        },
        'page.delete:after' => function ($page, $force) {
            if (option('bnomei.boost.helper')) {
                $page->boostIndexRemove();
                $page->index(option('bnomei.boost.drafts'))->boostIndexRemove();
            }
        },
    ],
]);