Check if virtual page exists

I have a setup in my LemonSqueezy plugin that generates pages on disk and also alows mixing local content with the api data. At the moment the virtual pages are unlisted but all pages get updated when the API is called after the cache has been flushed. This could be slow with 100s of products.

What would be the cleanest way to check the page exists already and only update the page if the data from the api has changed since the page was created. This would dramatically speed things up in the panel.

My model looks like this currently

<?php

class ShopPage extends Page
{


    public function subpages()
    {
        return Pages::factory($this->inventory()['children'], $this);
    }



    public function children()
    {

        site()->products(null);
        $apiCache = kirby()->cache('hashandsalt.lemonsqueezy.products');
        $apiData = $apiCache->get('hashandsalt.lemonsqueezy.products');


        foreach ($apiData['data'] as $key => $productitem) {

            if ($productitem['attributes']['status'] === 'published') {

                $slug = $productitem['attributes']['slug'];
                $page = $this->subpages()->find($slug);

                $lemonSquezyProduct =  [
                    'title'             => $productitem['attributes']['name'],
                    'description'       => $productitem['attributes']['description'],
                    'productstatus'     => $productitem['attributes']['status'],
                    'thumburl'          => $productitem['attributes']['thumb_url'],
                    'largethumburl'     => $productitem['attributes']['large_thumb_url'],
                    'price'             => $productitem['attributes']['price'],
                    'formattedprice'    => $productitem['attributes']['price_formatted'],
                    'buynowurl'         => $productitem['attributes']['buy_now_url'],
                    'seotitle'          => $page ? $page->seotitle()->value() : null,
                    'seometa'           => $page ? $page->seotitle()->value() : null,
                    'shareimage'        => $page ? $page->shareimage()->value() : null,
                    'seotags'           => $page ? $page->seotags()->value() : null,
                    'thumbnail'         => $page ? $page->thumbnail()->value() : null,
                    'productdemo'       => $page ? $page->productdemo()->value() : null,
                    'detail'            => $page ? $page->detail()->value() : null,
                    'install'           => $page ? $page->install()->value() : null,
                ];
         
                    $pages[] = [
                        'slug'     => $productitem['attributes']['slug'],

                        'content'  => $lemonSquezyProduct,

                        'template' => kirby()->option('hashandsalt.lemonsqueezy.template'),
                        'model'    => kirby()->option('hashandsalt.model'),

                   
                        'files'    => $page ? $page->files()->toArray() : null,

                    ];
              
            }
        }


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

first try adding a static cache to the children collection. the panel calls that function more then once in a single request. thus you would have like 100 api calls not one.

the guide was recently updated:

i assume you have the all products data as an json array from the api somewhere.

this is what i would do when merging

  • iterate over json
  • for each virtual product get array of unique files, sort array by key then do json and a md5 hash
  • compare that array against data from content of existing pages. you could either write the hash in the content (on update) or i would just create another cache layer and write the hash there.
  • this way you can per product compare json data hash with content hash quickly to check if product was changed.

Thanks for the tips @bnomei

I have implemented the first bit. Yes i have the data as JSON. The model calls a custom site method:

site()->products(null);

Which hits the API endpoint and creates the cache if it doesnt exist via this function https://github.com/HashandSalt/kirby3-lemonsqueezy/blob/main/src/classes/LemonSqueezy.php#L14

im storing the MD5 hash in the content file now. so i guess now i just need to tweak the foreach loop in the model to compare the two?

Not quite sure what you mean here - not all the pages will have files but probably images. i stringyfied the data from the API for that page, and passed that through to md5() - is that not enough?

Why is it important to sort the array by key?

i did not know if the json dataset and the content file have the same data in all fields. i assumed you would need only to match a certain set of fields from each to determine uniqueness via the hash.
if you get arrays from json or oage object you can not be 100% sure the arraykeys are in same order. so comparing a jsonfied-md5-hash might not yield same result for both - unless you sort the array by keys.
it happened to me that i hashed POST data and forwared that to a page update call. problem is that kirby sorts the fields in the update and thus the content array i was comparing to did have a different order of keys than i expected.