Virtual Files for page?

Hi,

with Kirby 3 we can build virtual pages. Is it also possible to have “virtual files”? I.e. having the files stored on another server (for instance Microsoft Azure or something else) but work with them like a regular file object from a page?

Kind regards,
Georg

In theory, yes, you can create a Kirby\Image\Image object from those images. But the downside of this approach is that it doesn’t seem possible to then resize etc. these objects.

Okay, thank you. I will give it a try if that is enough for my use case.

https://getkirby.com/docs/reference/plugins/components/file-urls

There’s another way to handle that: Spacer GIF :slight_smile: Just kidding! Almost.

Use a file create hook to upload the file to azure or whatever and once the upload succeeded, replace the actual file on the disk with a 1x1 px version. I have done this a couple times in client projects and it works pretty great. You can store additional meta data in the text file. For example the original dimensions or a reference id of the uploaded file.

Another way is really working with virtual file objects. There’s a Files::factory() method that’s comparable to the Pages::factory() method. You can pass a set of file props to that method with an array.

class CustomPage extends Page 
{

    public function files()
    {

        $files = [
            [
                'filename' => 'a.jpg',
                'url'      => 'https://cdn.somewhere.com/a.jpg',
            ],
            [
                'filename' => 'b.jpg',
                'url'      => 'https://cdn.somewhere.com/b.jpg',
            ]
        ];

        return Files::factory($files, $this);

    }

}

The only problem with this is that you can’t use the File object methods that rely on reading the file. I.e. file size, dimensions, etc.

For that you would need to create your very own files collection with your own file objects


class CustomFile extends File
{
  // overwrite file methods here
}

class CustomPage extends Page 
{

    public function files()
    {
        $files = new Files([], $this);
    
        $a = new CustomFile([
            'filename' => 'a.jpg',
            'parent'   => $this,
            'url'      => 'https://cdn.somewhere.com/a.jpg',
        ]);

        $b = new CustomFile([
            'filename' => 'b.jpg',
            'parent'   => $this,
            'url'      => 'https://cdn.somewhere.com/b.jpg',
        ]);

        $files->append($a->id(), $a);
        $files->append($b->id(), $b);

        return $files;

    }

}

I haven’t tested this, but in theory it should work :slight_smile:

7 Likes

Sorry, for not replying earlier, but once I started with this I found myself into a massive refactoring which I wanted to finish first. :slight_smile: Thank you both, I was able to implement this “virtual file” construction.

I need to have virtual files, because the metadata for our files are also in a database. I started working with the standard File::factory approach and a custom file url() function, but got stuck quickly because of some issues with panel previews. I was then looking for something like “File Models” but this doesn’t exist I think, Bastians idea with an extended File class was the way to go.

Here is a rough skeleton of the code I ended up with:

The custom file class:

class MyFile extends File {

  function __construct($rawData, $parent = null) {
    return parent::__construct([
      'filename' => $rawData->name,
      'parent' => $parent,
      'url' => $rawData->url,
      'template' => 'myFile',
      'content' => [
        'title' => $rawData->title,
        // Some other content fields
      ],
    ]);
  }

  public function url(): string {
    return $this->url;
  }

  public function thumb(array $options = NULL) {
    return $this;
  }

  public function delete(bool $force = false): bool {
    // Delete logic here
  }

  public function writeContent(array $data, string $languageCode = null): bool {
    // Update logic here
  }
}

I needed to have the url and thumb functions, otherwise the Panel didn’t work. Also the constructor needed to be like that, I had issues with filtering and sorting when using other data structures.
The writeContent function calls the constructor with the content array. So it is important that the constructor can deal with that. If the original object is build by for instance with a stdClass the constructor needs to make sure it is converted to that if a content array from the write function arrives. Also the data structure of both have to be the same.

The page class:

class MyPage extends Page {

  public function files(): Files {
    $files = new Files([], $this);

    $children = // Some logic to get the remote data here

    foreach ($children as $key => $child) {
      $picture = new MyFile($child, $this);
      $files->append($picture->id(), $picture);
    }

    return $files;
  }
}

One thing that I found out the hard way, is that the id() function needs to return a string in the structure page-uri/file-name.suffix. Otherwise the collection is not behaving correctly in each scenario. So it is the best to use the original id() function from the Kirby File class.

I hope this is helpful.

I think this is pretty amazing and impressive what Kirby can do here. I is not super complicated but opens a lot of possibilities. :slight_smile: :+1:

Thank you,
Georg

5 Likes

@bastianallgeier If going this route, how to show the original dimensions in the file’s detail page (instead of the 1x1 px version’s dimensions)?

You would store them in the meta file and read them from there.

Yes, but how to “read them from there” (in the panel’s file detail view)? This is the part I can’t figure out.

From discussions on Discord, it seems that it’s not possible to have most fields in the file-detail view read from the file’s metadata file (template seems to be the only one). This is unfortunate, as it makes the “1x1 placeholder” approach here feel broken. It seems that if a file’s dimensions, size, ratio, or orientation are defined in its meta file, this data should be used (rather than these properties being read from the file on disk).

From what I can tell, this should be easily achieved by editing kirby/FilePreview.vue at 0d2751af7cb478df14f30fac47fe9a2cd3b38114 · getkirby/kirby · GitHub. The general idea would be to check for the existence of the field on the content property, and if not there, use the default. Would a PR for this be welcomed?

Also, for clarity, this is the “file-detail view” I’m referring to:

You could try and overwritede the default file view:

When does thumb generation happen? On upload or on request ?
Ie. will either of these methods work (virtual file or spacer gif.) when I use an avif or webp version of the image in my template?

Thumbs are generated dynamically on request.