How to create virtual pages for each photo in an album?

How can i create virtual pages for each photo in a album/folder?

This is made in koken.me

Cheers, Maarten

Basically as described here: https://getkirby.com/docs/guide/virtual-pages/content-from-csv

In the album model, you would define the children, so each image would become a page

(note that you can’t use the filename as slug, because that would redirect to the media folder.

class AlbumPage extends Page
{

    public function children()
    {
        $images = [];

        foreach ($this->images() as $image) {
            $images[] = [
                'slug'     => $image->name(), // this could potentially result in errors if images have the same name but different extension and/or if filenames are not sanitized on upload
                'num'      => 0, // or use the image sort number here if available
                'template' => 'image',
                'model'    => 'image',
               // 'content'  => $image->content()->toArray() // you can set the image content to the page content, but maybe you don't want or need that because you can directly get the meta data content from the file
            ];
        }

        return Pages::factory($images, $this);
    }

}

(Maybe store a sluggified version of the filename in the file metadata on file upload, so that can be used for the slug and also to find the image)

Then in the child model, you might want to redefine the image method, but it is probably not necessary. In the image.php template, you could get the file object like this:

<?php snippet('header') ?>
<?php 
$image = $page->parent()->images()->findBy('name', $page->slug());
dump($image);
?>
<?php snippet('footer') ?>

In addition, you could probably enhance it a little bit and use a file upload hook to set the EXIF data into the images meta file automatically.

Thank you so much Sonja, i will try the solution later today!

It works great in the starterkit!! Thank you so much !!

<?php snippet('header') ?>

<header>
      <?php 
      $image = $page->parent()->images()->findBy('name', $page->slug());
      ?>
      <figure class="album-cover">
        <?= $image->crop(1024, 768) ?>
        <figcaption>
          <h1><?= $image->alt()->or($page->title()) ?></h1>
        </figcaption>
      </figure>
    
    </header>

  <ul>
    <li>Alternative Text: <?= $image->alt() ?></li>
    <li>Photographer: <?= $image->photographer() ?></li>
    <li><a href="<?= $image->link()->or($image->url()) ?>">Source Link</a></li>
  </ul>


<?php snippet('footer') ?>
2 Likes

To be safe you need to encode en decode the image name.

'slug'     => urlencode($image->name()),
<?php 
$image = $page->parent()->images()->findBy('name', urldecode($page->slug()));
?> 

Good idea!! I will try this later :slight_smile:

That’s an option, yes. However, urlencoding can end up ugly (depending on your file names), that’s why I suggested to sluggify the filename and store the slug in the data.

A lot of photo’s will be uploaded by FTP or an API, for that reason i choose url encoding. When it’s uploaded in the panel i can sluggify the filename and store it. I will now choose to sluggify the name, and if the slug is not stored to fall back on url encoding. Is that a good idea?

I’d stick with the same method for all files, otherwise it get’s unnecessarily complicated. If you filenames do not contain to many strange characters, the result will probably not be too bad.

When i upload files in the panel, the names will be sanitized (from what i tested). Files with spaces are not visible in the panel, can i automaticly sanitize files afterwards with a button in the panel so they become visible?

Yes, that is the default setting.

Do you mean those uploaded via FTP or API?

Yes, correct!

You could probably use the Janitor plugin.

Thx! That’s a really nice plugin, i will look into it to add custom jobs.

Ive been messing around with the above, and trying to get the image sort number to effect the order of the “pages”. The images are getting listed alphabetically at the moment (which Kirby is doing all by iteself, I havent told it to), and using $image->sort() breaks if the image does not yet have a sort number.

Whats the way forward here?

$images[] = [
    'slug'     => $image->name(),
    'num'      => $image->sort()->value(),
    'template' => 'image',
    'model'    => 'album',
]; 

Im listing the images (which are now pages) out in a grid, and want the images sort order to equal the page position number, without it breaking if it hasnt been set yet. Im guesssing setting num to 0 is equavelent to unlisted - the page does not have a number.

You get the error message with the setup above? Or when sorting the pages in a template? What exactly is the error? A sorting number 0 would usually mean alphabetic sorting so that probably won’t work as expected.

What if you set the value to null if the sort field is empty?

If the images meta file is blank, becuase the images havent yet been sorted in the files section, it breaks. Also, unless I chain ->value() it complains about it being an object instead of an integer.

I want to understand what “it breaks” means. What is the error message?

What if you set

'num'      => $image->sort()->isNotEmpty() ? $image->sort()->value() : null,

It was throwing the message about expecting an integer (because there was no number to get, i think). It might have been just me messing around, its not throwing that error now for some reason.

The code you gave seems to work, however they still come out in alpha order rather then by sort order. I thought sort order was the default? do i need to use ->sortBy() on the loop? These are pages now, so it’s expecting a field to sort on, rather then a file sort number.