Content loss on pages with virtual data

After creating a minimal test case with Kirby Plainkit and a multilingual setup, I was able to identify the core issue. When a page with mixed content is saved, the virtual data is written to the physical text file. This is not the desired behavior, as this data should always remain virtual and be loaded fresh from its source on each request.

The definitive solution is to extend the MixedStorage handler with a custom write() method. This method intercepts the save operation and filters out the virtual fields, ensuring only the real content is ever written to disk.

    /**
     * Intercept the write process to filter out virtual data.
     */
    public function write(VersionId $versionId, Language $language, array $data): void
    {
        // 1. Get the keys of the data that has been set as virtual for the current context.
        $virtualKeys = array_keys($this->readVirtual($versionId, $language));

        // 2. Use A::without() to remove these dynamic virtual keys from the data array
        $saveData = A::without($data, $virtualKeys);

        // 3. Pass only the "real" data to be saved to the file.
        parent::write($versionId, $language, $saveData);
    }

The full MixedStorageHandler:

<?php

namespace Kirby\Content\MixedStorage;

use Kirby\Cms\Language;
use Kirby\Content\PlainTextStorage;
use Kirby\Content\VersionId;
use Kirby\Toolkit\A;

class MixedStorage extends PlainTextStorage
{
    protected array $virtual = [];

    /**
     * Read the original content from disk and merge it with the virtual content
     */
	public function read(VersionId $versionId, Language $language): array
	{
        $content = parent::read($versionId, $language);

        return [
            ...$this->readVirtual($versionId, $language),
            ...$content,
        ];
	}

    /**
     * Intercept the write process to filter out virtual data.
     */
    public function write(VersionId $versionId, Language $language, array $data): void
    {
        // 1. Get the keys of the data that has been set as virtual for the current context.
        $virtualKeys = array_keys($this->readVirtual($versionId, $language));

        // 2. Use A::without() to remove these dynamic virtual keys from the data array
        $saveData = A::without($data, $virtualKeys);

        // 3. Pass only the "real" data to be saved to the file.
        parent::write($versionId, $language, $saveData);
    }

    /**
     * Check if the page exists on disk and otherwise check if there is any virtual content
     */
    public function exists(VersionId $versionId, Language $language): bool
    {
        return parent::exists($versionId, $language) || $this->readVirtual($versionId, $language) !== [];
    }

    /**
     * Read virtual content for a given version and language from our
     * in-memory storage array
     */
    public function readVirtual(VersionId $versionId, Language $language): array
    {
        return $this->virtual[$versionId->value()][$language->code()] ?? [];
    }

    /**
     * Write virtual content for a given version and language to our
     * in-memory storage array
     */
    public function writeVirtual(VersionId $versionId, Language $language, array $data): void
    {
        $this->virtual[$versionId->value()][$language->code()] = $data;
    }
}
3 Likes