classes/BoostIndex.php
<?php
declare(strict_types=1);
namespace Bnomei;
use Kirby\Cms\Page;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
final class BoostIndex
{
public const SEPERATOR = '|!|';
/** @var array */
private $index;
/** @var int */
private $expire;
/** @var bool */
private $isDirty;
public function __construct()
{
$this->expire = option('bnomei.boost.expire', 0);
$this->isDirty = false;
$this->index = $this->read();
$cache = $this->cache();
if ($cache && method_exists($cache, 'register_shutdown_function')) {
$cache->register_shutdown_function(function () {
$this->write();
});
}
if (option('debug') || empty($this->index)) {
$this->index(true);
$success = $this->write();
}
}
public function __destruct()
{
$cache = $this->cache();
if ($cache && method_exists($cache, 'register_shutdown_function') === false) {
$this->write();
}
}
private function cache()
{
return BoostCache::singleton();
}
private function read(): array
{
return $this->cache() ? $this->cache()->get('index', []) : [];
}
public function write(): bool
{
if ($this->cache() && $this->isDirty) {
$this->isDirty = false;
return $this->cache()->set('index', $this->index, $this->expire);
}
return false;
}
public function index(bool $force = false, ?Page $target = null): int
{
$count = $this->index ? count($this->index) : 0;
if ($count > 0 && ! $force) {
return $count;
}
$this->index = [];
$count = 0;
foreach (site()->siteindexfolders() as $page) {
// save memory when indexing
$page = \Bnomei\Bolt::page($page, null, false, false);
if (! $page || $page->hasBoost() !== true) {
$page = null; // free memory, do not use unset()
continue;
}
if ($this->add($page)) {
$count++;
}
// save every n steps
if ($count % 2000 === 0) {
$this->write();
}
// only search until target is found
if ($target && $target->id() === $page->id()) {
$this->write();
break;
}
$page = null; // free memory, do not use unset()
}
return $count;
}
public function flush(): bool
{
$this->index = [];
$this->isDirty = true;
/* on destruct
if ($this->cache()) {
return $this->cache()->set('index', [], $this->expire);
}
*/
return true;
}
public function find(string $uuid, bool $throwException = true): ?Page
{
$uuid = str_replace('page://', '', trim($uuid));
$diruri = $this->diruri($uuid);
if ($diruri && $page = \Bnomei\Bolt::page($diruri)) {
return $page;
} else {
$crawl = null;
// try UUID cache via bolt first
if ($page = \Bnomei\Bolt::page($uuid)) {
$this->add($page);
$crawl = $page;
}
if (! $crawl) {
// then try crawling index
foreach (site()->index(option('bnomei.boost.drafts')) as $page) {
if ($this->add($page) && $page->uuid()->id() === $uuid) {
$crawl = $page;
break;
}
}
}
$this->write();
if ($crawl) {
return $crawl;
} elseif ($throwException) {
throw new \Exception('No page found for uuid: '.$uuid);
}
}
return null;
}
public function data(string $uuid): ?array
{
if ($data = A::get($this->index, $uuid)) {
if (Str::contains($data, self::SEPERATOR)) {
[$diruri, $title, $template] = explode(self::SEPERATOR, $data);
$data = [
'diruri' => $diruri,
'template' => $template,
'title' => $title,
'uuid' => $uuid,
];
}
}
return $data ?? null;
}
public function diruri(string $uuid)
{
$data = $this->data($uuid);
return A::get($data, 'diruri');
}
public function add(Page $page): bool
{
$uuid = $page->uuid()->id();
if (empty($uuid)) {
return false;
}
$id = $page->diruri().self::SEPERATOR.$page->title()->value().self::SEPERATOR.$page->template()->name();
if (kirby()->multilang()) {
$id = $page->diruri().self::SEPERATOR.$page->content(kirby()->defaultLanguage()->code())->title()->value().self::SEPERATOR.$page->template()->name();
}
if (! array_key_exists($uuid, $this->index) ||
$this->index[$uuid] !== $id
) {
$this->isDirty = true;
$this->index[$uuid] = $id;
}
return true;
}
public function remove(Page $page): bool
{
$uuid = $page->uuid()->id();
if (empty($uuid)) {
return false;
}
if (array_key_exists($uuid, $this->index)) {
unset($this->index[$uuid]);
$this->isDirty = true;
}
return true;
}
public function toArray(): array
{
return $this->index;
}
public function toKVs(): array
{
$kv = [];
foreach ($this->toArray() as $uuid => $data) {
[$diruri, $title, $template] = explode(\Bnomei\BoostIndex::SEPERATOR, $data);
$kv[] = [
'id' => $uuid, // needed for kirby\cms\collections class to work
'diruri' => $diruri,
'template' => $template,
'text' => $title,
'value' => $uuid,
];
}
usort($kv, function ($a, $b) {
if ($a['diruri'] == $b['diruri']) {
return 0;
}
return ($a['diruri'] < $b['diruri']) ? -1 : 1;
});
$kv = array_map(function ($item) {
return new \Kirby\Toolkit\Obj($item);
}, $kv);
return $kv;
}
public function count(): int
{
return count($this->index);
}
private static $singleton;
public static function singleton(): self
{
if (! self::$singleton) {
self::$singleton = new self;
}
return self::$singleton;
}
public static function page(string $id): ?Page
{
return self::singleton()->find($id, false);
}
}