Save image from external url and then use that image to create thumbs

I used this topic Kirby external image upload? to download some images from an external url, which works fine. But after the script has downloaded the image to the server I want to use that image to generate some thumbs.

My script looks like this:

<?php foreach ($items as $item): ?>
  <?php 
  if ( $item->images()->first() ) {
    $image = $item->images()->first();
  } else {
    $imageData = file_get_contents($item->extern_product_image_url());
    $imageName = $item->brand() . '-' . $item->title();
    file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);

    $image = $item->images()->first();
    }
    ?>

I guess I need to tell the script to “wait” for the image to get downloaded before I assign the $image variable with the first image. But how do I do that?

PHP is synchronous, so the file is definitely already downloaded when you set $image.

The issue here is probably that Kirby has already fetched the list of images from $item before the image was stored (not in the background, but even before you started to download the image).
So instead of $item->images()->first(), try new Media($item->root() . DS . f::safeName($imageName) . '.jpg'), that should give you an equivalent object.

Tried it but didn’t really work.
The first time the page gets loaded the first item saves the image, but no thumb is generated.
After a refresh of the page the first item gets a thumb and the other items gets there image saved but no thumbs.
And after a third refresh of the page they all get thumbs.

My code looks like this now:

if ( $item->images()->first() ) {
      $image = $item->images()->first();
    } else {
      $imageData = file_get_contents($item->extern_product_image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);

      new Media($item->root() . DS . f::safeName($imageName) . '.jpg');
    }

That`s probably because the Media class does not support thumbnails. So you create a media object in the else statement and the next time you reload the page, this image then becomes an image object and the thumb creation works.

Ok. Any ide how to fix that? I tried

$image = new Media($item->root() . DS . f::safeName($imageName) . '.jpg');

But still just create one items thumb on each page load.

What you could try is:

$image = new File($item->images(), f::safeName($imageName) . '.jpg');

This creates a full file object that also supports generating thumbnails.

Works better, but still needs two page loads - one to save the images and one to generate the thumbs.

Where do you create the thumbs? Have you tried to create them within the foreach loop?

The thumbs are generated inside the foreach loop.
This is the complete template code:

<?php $asId = $site->affiliate_site_id(); ?>
<section class="products-container">
  <ul class="products-list">
    <?php foreach ($items as $item): ?>
    <?php $price = $item->price()->int()/100; ?>
    <?php 
    if ( $item->images()->first() ) {
      $image = $item->images()->first();
    } else {
      $imageData = file_get_contents($item->extern_product_image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);

      $image = new File($item->images(), f::safeName($imageName) . '.jpg');
    }
    ?>
    
    <li class="product js-mh" 
      data-description="<?php echo $item->description()->html(); ?>" 
      data-link="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" 
      data-name="<?php echo $item->name()->html(); ?>"
      data-image="<?php echo $image->width(700)->url(); ?>">
      <?php if($item->hasFiles()): ?>
        <a href="<?php echo $item->url(); ?>" class="image-placeholder pop-up-btn">
          <img class="image lazyload"

            src="<?php echo $image->width(140)->url() ?>"
            
            srcset="<?php echo $image->width(140)->url() ?> 140w,
            <?php echo $image->width(280)->url() ?> 280w,
            <?php echo $image->width(330)->url() ?> 330w,
            <?php echo $image->width(400)->url() ?> 400w,
            <?php echo $image->width(540)->url() ?> 540w,
            <?php echo $image->width(700)->url() ?> 700w"

            sizes="(min-width: 87.5em) 14vw,
              (min-width: 65.625em) 16vw,
              (min-width: 53.125em) 20vw,
              (min-width: 31.25em) 27vw,
              43vw"
              
            alt="<?php echo $item->name()->html(); ?>"
          />
        </a>
      <?php endif ?>
      <span class="brand"><?php echo $item->brand()->html(); ?></span>
      <a href="<?php echo $item->url(); ?>" class="pop-up-btn"><span class="name"><?php echo $item->name()->excerpt(5, 'words'); ?></span></a>
      <span class="price"><?php echo number_format($price, 2, ',', ' '); ?> kr</span>
      <a href="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" class="buy-now-btn" target="_blank">Köp nu</a>
    </li>
  <?php endforeach ?>
  </ul>
</section><!-- END .products_container -->

Don’t know if it helps but I would try to change the code a bit:

<?php $asId = $site->affiliate_site_id(); ?>
<section class="products-container">
  <ul class="products-list">
    <?php foreach ($items as $item): ?>
    <?php $price = $item->price()->int()/100; ?>
    <?php 
    if ( ! $item->hasImages() ) {
      $imageData = file_get_contents($item->extern_product_image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);
    }
    ?>
    
    <li class="product js-mh" 
      data-description="<?php echo $item->description()->html(); ?>" 
      data-link="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" 
      data-name="<?php echo $item->name()->html(); ?>"
      data-image="<?php echo $image->width(700)->url(); ?>">
      <?php if($item->hasImages()): ?>
       <?php $image = $item->images()->first() ?>
        <a href="<?php echo $item->url(); ?>" class="image-placeholder pop-up-btn">
          <img class="image lazyload"

            src="<?php echo $image->width(140)->url() ?>"
            
            srcset="<?php echo $image->width(140)->url() ?> 140w,
            <?php echo $image->width(280)->url() ?> 280w,
            <?php echo $image->width(330)->url() ?> 330w,
            <?php echo $image->width(400)->url() ?> 400w,
            <?php echo $image->width(540)->url() ?> 540w,
            <?php echo $image->width(700)->url() ?> 700w"

            sizes="(min-width: 87.5em) 14vw,
              (min-width: 65.625em) 16vw,
              (min-width: 53.125em) 20vw,
              (min-width: 31.25em) 27vw,
              43vw"
              
            alt="<?php echo $item->name()->html(); ?>"
          />
        </a>
      <?php endif ?>
      <span class="brand"><?php echo $item->brand()->html(); ?></span>
      <a href="<?php echo $item->url(); ?>" class="pop-up-btn"><span class="name"><?php echo $item->name()->excerpt(5, 'words'); ?></span></a>
      <span class="price"><?php echo number_format($price, 2, ',', ' '); ?> kr</span>
      <a href="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" class="buy-now-btn" target="_blank">Köp nu</a>
    </li>
  <?php endforeach ?>
  </ul>
</section><!-- END .products_container -->

Thank you, but no luck with that code texnixe. Maybe this one is hard do fix?

Does my code work at all? What is the result?

The first item get the image saved but then nothing happens. And on page reload still nothing more happens.

Tried to split the script in to two foreach loops, one for downloading the image to the server and one to generate the thumbs. But still, the first time the page load all the images gets downloaded but no thumbs. After reload the page the thumbs is generated.

code:

<section class="products-container">
  <ul class="products-list">
    <?php foreach ($items as $item): ?>
    <?php $price = $item->price()->int()/100; ?>
    <?php 
    if ( ! $item->hasImages() ) {
      $imageData = file_get_contents($item->extern_product_image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);
    }
    ?>
    <?php endforeach ?>
    
    <?php foreach ($items as $item): ?>
    <?php $image = $item->images()->first(); ?>
    <li class="product js-mh" 
      data-description="<?php echo $item->description()->html(); ?>" 
      data-link="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" 
      data-name="<?php echo $item->name()->html(); ?>"
      data-image="<?php echo $image->width(700)->url(); ?>">
      <?php if( $item->hasImages() ): ?>
        <a href="<?php echo $item->url(); ?>" class="image-placeholder pop-up-btn">
          <img class="image lazyload"

            src="<?php echo $image->width(140)->url() ?>"
            
            srcset="<?php echo $image->width(140)->url() ?> 140w,
            <?php echo $image->width(280)->url() ?> 280w,
            <?php echo $image->width(330)->url() ?> 330w,
            <?php echo $image->width(400)->url() ?> 400w,
            <?php echo $image->width(540)->url() ?> 540w,
            <?php echo $image->width(700)->url() ?> 700w"

            sizes="(min-width: 87.5em) 14vw,
              (min-width: 65.625em) 16vw,
              (min-width: 53.125em) 20vw,
              (min-width: 31.25em) 27vw,
              43vw"
              
            alt="<?php echo $item->name()->html(); ?>"
          />
        </a>
      <?php endif ?>
      <span class="brand"><?php echo $item->brand()->html(); ?></span>
      <a href="<?php echo $item->url(); ?>" class="pop-up-btn"><span class="name"><?php echo $item->name()->excerpt(5, 'words'); ?></span></a>
      <span class="price"><?php echo number_format($price, 2, ',', ' '); ?> kr</span>
      <a href="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" class="buy-now-btn" target="_blank">Köp nu</a>
    </li>
  <?php endforeach ?>
  </ul>
</section><!-- END .products_container -->

Same thing here, you can’t use:

<?php $image = $item->images()->first(); ?>

as the list of images is cached for the current page load.

Instead try the code I have posted above in this post.
Please note that you need to merge the two foreach loops to one again to make this work, otherwise $imageName is undefined.

I tried that lukasbestle but still doesn’t work. Need two page loads, one to download the images and one to generate the thumbs.

code:

<section class="products-container">
  <ul class="products-list">
    <?php foreach ($items as $item): ?>
    <?php $price = $item->price()->int()/100; ?>
    <?php 
    if ( ! $item->hasImages() ) {
      $imageData = file_get_contents($item->extern_product_image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);

      $image = new File($item->images(), f::safeName($imageName) . '.jpg');
    }
    else {
      $image = $item->images()->first();
    }
    ?>
    
    <li class="product js-mh" 
      data-description="<?php echo $item->description()->html(); ?>" 
      data-link="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" 
      data-name="<?php echo $item->name()->html(); ?>"
      data-image="<?php echo $image->width(700)->url(); ?>">
      <?php if( $item->hasImages() ): ?>
        <a href="<?php echo $item->url(); ?>" class="image-placeholder pop-up-btn">
          <img class="image lazyload"

            src="<?php echo $image->width(140)->url() ?>"
            
            srcset="<?php echo $image->width(140)->url() ?> 140w,
            <?php echo $image->width(280)->url() ?> 280w,
            <?php echo $image->width(330)->url() ?> 330w,
            <?php echo $image->width(400)->url() ?> 400w,
            <?php echo $image->width(540)->url() ?> 540w,
            <?php echo $image->width(700)->url() ?> 700w"

            sizes="(min-width: 87.5em) 14vw,
              (min-width: 65.625em) 16vw,
              (min-width: 53.125em) 20vw,
              (min-width: 31.25em) 27vw,
              43vw"
              
            alt="<?php echo $item->name()->html(); ?>"
          />
        </a>
      <?php endif ?>
      <span class="brand"><?php echo $item->brand()->html(); ?></span>
      <a href="<?php echo $item->url(); ?>" class="pop-up-btn"><span class="name"><?php echo $item->name()->excerpt(5, 'words'); ?></span></a>
      <span class="price"><?php echo number_format($price, 2, ',', ' '); ?> kr</span>
      <a href="<?php echo str_replace('@@@',$asId,$item->affiliate_product_url()->html()); ?>" class="buy-now-btn" target="_blank">Köp nu</a>
    </li>
  <?php endforeach ?>
  </ul>
</section><!-- END .products_container -->

I really appreciate your help on this.

I have tried to move the image generation to a function, this seems to work in my test (I have shortened your code to use it in my test environment)

  <section class="products-container">
      <ul class="products-list">
        <?php foreach ($items as $item): ?>
        <?php
          $image = makeImage($item);
        ?>

        <li class="product js-mh">
          <?php if($image): ?>
            <a href="<?php echo $item->url(); ?>" class="image-placeholder pop-up-btn">
              <img class="image lazyload" src="<?php echo $image->width(140)->url() ?>" />
            </a>
          <?php endif ?>
        </li>
      <?php endforeach ?>
      </ul>
    </section><!-- END .products_container -->

function makeImage() in /site/plugins

<?php
  function makeImage($item) {
    if($item->hasImages()) {
      $image = $item->images()->first();
    } else {
      $imageData = file_get_contents($item->image_url());
      $imageName = $item->brand() . '-' . $item->title();
      file_put_contents($item->root() . DS . f::safeName($imageName) . '.jpg', $imageData);
      $image = new File($item->images(), f::safeName($imageName) . '.jpg');
    }
    return $image;
  }

Or, as an alternative, try to use a page model.

YES! Works good, finally.
Thank you both @lukasbestle and @texnixe, you are such a great support team.

3 Likes