diff --git a/classes/Khulan.php b/classes/Khulan.php index badb924..a6acefc 100644 --- a/classes/Khulan.php +++ b/classes/Khulan.php @@ -14,23 +14,53 @@ class Khulan { - public static function index(): array + public static function index(int $iterations = 2): array { $count = 0; $hash = []; // reading a field like the title will make sure // that the page is loaded and cached + /** @var Page $page */ foreach (site()->index(true) as $page) { - $hash[] = $page->title()->value(); + if ($page->hasKhulan() !== true) { + continue; + } + if (kirby()->multilang()) { + foreach (kirby()->languages() as $language) { + $content = $page->content($language->code())->toArray(); + $hash[] = $content['title']; + $page->writeKhulan($content, $language->code()); + } + } else { + $hash[] = $page->title()->value(); + $page->writeKhulan($page->content()->toArray()); + } $count++; } // TODO: files, users - return [ + $meta = [ 'count' => $count, 'hash' => hash('xxh3', implode('|', $hash)), ]; + + // call twice by default to make it possible to resolve relations + if ($iterations > 1) { + $iterations--; + $meta = self::index($iterations); + } + + // add indexes for better performance + if ($iterations === 1) { + khulan()->createIndex(['id' => 1]); + khulan()->createIndex(['uuid' => 1]); + khulan()->createIndex(['language' => 1]); + khulan()->createIndex(['template' => 1]); + khulan()->createIndex(['modelType' => 1]); + } + + return $meta; } public static function flush(): bool @@ -92,7 +122,7 @@ public static function documentToModel($document = null): Page|File|User|Site|nu return kirby()->site(); } elseif ($document['modelType'] === 'page') { $document = iterator_to_array($document); - $id = A::get($document, 'uuid', A::get($document, 'id')); + $id = A::get($document, 'id', A::get($document, 'uuid')); return kirby()->page($id); } diff --git a/classes/ModelWithKhulan.php b/classes/ModelWithKhulan.php index f69720d..7a75d5a 100644 --- a/classes/ModelWithKhulan.php +++ b/classes/ModelWithKhulan.php @@ -20,7 +20,7 @@ public function hasKhulan(): bool return true; } - public function setBoostWillBeDeleted(bool $value): void + public function setKhulanCacheWillBeDeleted(bool $value): void { $this->khulanCacheWillBeDeleted = $value; } @@ -101,7 +101,7 @@ public function writeKhulan(?array $data = null, ?string $languageCode = null): 'language' => $languageCode, 'modelType' => $modelType, ]; - $data = $this->encodeKhulan($data) + $meta; + $data = $this->encodeKhulan($data, $languageCode) + $meta; // _id is not allowed as data key if (array_key_exists('_id', $data)) { @@ -131,7 +131,7 @@ public function deleteKhulan(): bool return true; } - $this->setBoostWillBeDeleted(true); + $this->setKhulanCacheWillBeDeleted(true); // using many and by id to delete all language versions // as well as the version without a language code @@ -155,7 +155,7 @@ public function delete(bool $force = false): bool return $success; } - public function encodeKhulan(array $data): array + public function encodeKhulan(array $data, ?string $languageCode = null): array { // foreach each key value pairs $copy = $data; @@ -175,7 +175,56 @@ public function encodeKhulan(array $data): array // if it is a valid yaml string, convert it to an array if (in_array( - $type, ['pages', 'files', 'users', 'object', 'structure'] + $type, ['pages', 'files', 'users'] + )) { + try { + $v = Yaml::decode($value); + if (is_array($v)) { + $copy[$key.'[]'] = $v; + $copy[$key.'{}'] = []; + // resolve each and set objectid + foreach ($v as $vv) { + $modelType = null; + if (Str::startsWith($vv, 'page://')) { + $modelType = 'page'; + } elseif (Str::startsWith($vv, 'file://')) { + $modelType = 'file'; + } elseif (Str::startsWith($vv, 'user://')) { + $modelType = 'user'; + } elseif (Str::startsWith($vv, 'site://')) { + $modelType = 'site'; + } + if (! $modelType) { + continue; + } + $vv = str_replace($modelType.'://', '', $vv); + $query = [ + '$or' => [ + ['id' => $vv], + ['uuid' => $vv], + ], + ]; + if (kirby()->multilang() && $languageCode) { + $query = [ + '$and' => [ + $query, + ['language' => $languageCode], + ], + ]; + } + $document = khulan()->findOne($query); + if ($document) { + $copy[$key.'{}'][] = $document['_id']; + } + } + } + } catch (\Exception $e) { + // do nothing + } + } + // if it is a valid yaml string, convert it to an array + if (in_array( + $type, ['object', 'structure'] )) { try { $v = Yaml::decode($value); diff --git a/index.php b/index.php index 06e023b..449384f 100644 --- a/index.php +++ b/index.php @@ -79,6 +79,11 @@ function khulan(string|array|null $search = null): mixed ], 'hooks' => [ 'system.loadPlugins:after' => function () { + if (option('bnomei.mongodb.khulan.read') && + option('bnomei.mongodb.khulan.write') && + khulan()->countDocuments() === 0) { + Khulan::index(); + } if (option('bnomei.mongodb.khulan.patch-files-class')) { $filesClass = kirby()->roots()->kirby().'/src/Cms/Files.php'; if (F::exists($filesClass) && F::isWritable($filesClass)) { diff --git a/tests/MongodbTest.php b/tests/MongodbTest.php index 67a4111..fc59f45 100644 --- a/tests/MongodbTest.php +++ b/tests/MongodbTest.php @@ -68,6 +68,21 @@ expect($value)->toBe('value'); }); +it('will not use the cache in debug mode', function () { + Mongodb::$singleton = null; + + $mongodb = Mongodb::singleton([ + 'debug' => true, + ]); + + expect($mongodb->option('debug'))->toBeTrue(); + + $mongodb->set('test', 'value'); + expect($mongodb->get('test'))->toBeNull(); + + Mongodb::$singleton = null; +}); + it('can use the cache with expiration', function () { $mongodb = mongo(); $mongodb->set('test', 'value', 1); @@ -76,6 +91,14 @@ expect($value)->toBe('value'); }); +it('can use a closure in the cache', function () { + $mongodb = mongo(); + $mongodb->getOrSet('swatch', fn () => date('B')); + $value = $mongodb->get('swatch'); + + expect($value)->toBeNumeric(); +}); + it('can find a page by id and uuid', function () { Khulan::index(); diff --git a/tests/index.php b/tests/index.php index b8b3209..73452fb 100644 --- a/tests/index.php +++ b/tests/index.php @@ -3,5 +3,7 @@ const KIRBY_HELPER_DUMP = false; const KIRBY_HELPER_E = false; +// require __DIR__.'/patch.php'; + require __DIR__.'/../vendor/autoload.php'; echo (new Kirby())->render();