Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Idea] add local cache for response #45

Open
CurlyFlow opened this issue Nov 25, 2024 · 4 comments
Open

[Idea] add local cache for response #45

CurlyFlow opened this issue Nov 25, 2024 · 4 comments

Comments

@CurlyFlow
Copy link

Hi,
what ever Cloudflare is doing, like resizing, or making it smaller, could it be local cached and only new requested from cloudflare when something changes?

Im asking because even if you buy pro, you dont get any space, but you get 5000 free transformations.

Really cool would it be if it would replace the original file.

@Mecanik
Copy link
Owner

Mecanik commented Nov 25, 2024

Unfortunately, you can't. All you can do is supply the image to Cloudflare, and they resize, cache and serve it on their end.

I can't speak for pricing, it's their call... but the service is reliable.

@CurlyFlow
Copy link
Author

CurlyFlow commented Nov 25, 2024

noo... you can. i selected own storage.... then u have the 5000 transformatiuons for free, but you need to store them on your own. Im no developer, but if i understand correct, you have to use the api to get the images.
https://developers.cloudflare.com/images/

https://developers.cloudflare.com/images/pricing/

image

@CurlyFlow
Copy link
Author

CurlyFlow commented Nov 25, 2024

  • find all pictures in wordpress (maybe search directory or just find out what needs to be served)
  • send them to cloudflare (maybe add a max send xxx every month)
  • get the optimized versions (100x100, 200x200, avif, png, what ever...)
  • replace original
  • profit without cost

Why? because then it will get cached for free anyway.... even in free tier and there is no plugin making use of that free 5000 transformations... would be a nice opportunity as far as i know there is no free option like this on the market.

i asked chatgpd, doesnt look that bad, but i couldnt test it. / and like i said im no programmer

<?php
/**
 * Plugin Name: Cloudflare Image Transformer
 * Description: A plugin to find all images in the theme and uploads, send them to Cloudflare for transformation into AVIF and PNG, replace the original images, and generate and cache resized versions.
 * Version: 1.4
 * Author: Your Name
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

class CloudflareImageTransformer
{
    private $apiEndpoint = 'https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/images/v1';
    private $apiKey = 'YOUR_CLOUDFLARE_API_KEY';
    private $resizeVariants = [
        ['width' => 100, 'height' => 100],
        ['width' => 300, 'height' => 300],
        ['width' => 800, 'height' => 600]
    ];
    private $allowedExtensions = ['jpg', 'jpeg', 'png'];
    private $maxUploadsPer30Days = 1000; // Set maximum number of images to send to Cloudflare every 30 days
    private $uploadCountOption = 'cloudflare_image_upload_count';

    public function __construct()
    {
        add_action('init', array($this, 'initialize_upload_count'));
        add_action('admin_menu', array($this, 'add_admin_menu')); // Add manual action button
        add_action('admin_post_run_image_transform', array($this, 'run_manual_image_transform')); // Hook for manual action
        add_action('cloudflare_daily_cron', array($this, 'transform_images')); // Daily cron action
        if (!wp_next_scheduled('cloudflare_daily_cron')) {
            wp_schedule_event(time(), 'daily', 'cloudflare_daily_cron'); // Schedule daily cron event
        }
    }

    public function add_admin_menu()
    {
        add_menu_page('Cloudflare Image Transformer', 'Image Transformer', 'manage_options', 'cloudflare-image-transformer', array($this, 'admin_page_callback'), 'dashicons-images-alt2');
    }

    public function admin_page_callback()
    {
        echo '<div class="wrap">';
        echo '<h1>Cloudflare Image Transformer</h1>';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        echo '<input type="hidden" name="action" value="run_image_transform">';
        submit_button('Run Image Transformation');
        echo '</form>';
        echo '</div>';
    }

    public function run_manual_image_transform()
    {
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have sufficient permissions to access this page.'));
        }
        $this->transform_images();
        wp_redirect(admin_url('admin.php?page=cloudflare-image-transformer&success=1'));
        exit;
    }

    private function initialize_upload_count()
    {
        if (false === get_option($this->uploadCountOption)) {
            update_option($this->uploadCountOption, 0);
            update_option($this->uploadCountOption . '_last_reset', time());
        }
    }

    public function transform_images()
    {
        $this->reset_upload_count_if_needed();

        if ($this->get_current_upload_count() >= $this->maxUploadsPer30Days) {
            error_log('Cloudflare Image Transformer: Max uploads per 30 days reached.');
            return;
        }

        // Search for theme images
        $theme_images = $this->find_theme_images();
        // Search for uploaded images
        $uploaded_images = $this->find_uploaded_images();
        
        $images = array_merge($theme_images, $uploaded_images);

        foreach ($images as $image) {
            if ($this->get_current_upload_count() >= $this->maxUploadsPer30Days) {
                error_log('Cloudflare Image Transformer: Max uploads per 30 days reached during processing.');
                break;
            }
            
            $this->send_image_to_cloudflare($image);
            $this->generate_resized_variants($image);
            $this->increment_upload_count();
        }
    }

    private function reset_upload_count_if_needed()
    {
        $last_reset = get_option($this->uploadCountOption . '_last_reset');
        if ($last_reset && (time() - $last_reset) >= 30 * DAY_IN_SECONDS) {
            update_option($this->uploadCountOption, 0);
            update_option($this->uploadCountOption . '_last_reset', time());
        }
    }

    private function get_current_upload_count()
    {
        return (int) get_option($this->uploadCountOption);
    }

    private function increment_upload_count()
    {
        $count = $this->get_current_upload_count();
        update_option($this->uploadCountOption, $count + 1);
    }

    private function find_theme_images()
    {
        $theme_dir = get_template_directory();
        $images = $this->get_images_recursively($theme_dir);
        return $images ? $images : [];
    }

    private function find_uploaded_images()
    {
        $upload_dir = wp_get_upload_dir();
        $images = $this->get_images_recursively($upload_dir['basedir']);
        return $images ? $images : [];
    }

    private function get_images_recursively($directory)
    {
        $images = [];
        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
        foreach ($iterator as $file) {
            if ($file->isFile() && preg_match('/\.(' . implode('|', $this->allowedExtensions) . ')$/i', $file->getFilename())) {
                $images[] = $file->getPathname();
            }
        }
        return $images;
    }

    private function send_image_to_cloudflare($image_path)
    {
        $image_data = file_get_contents($image_path);
        $response = wp_remote_post($this->apiEndpoint, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type'  => 'application/octet-stream',
            ),
            'body' => $image_data,
        ));

        if (is_wp_error($response)) {
            error_log('Cloudflare API request failed: ' . $response->get_error_message());
            return;
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (isset($body['success']) && $body['success'] && !empty($body['result']['variants'])) {
            // Replace original image with transformed image
            $transformed_image_url = $body['result']['variants'][0];
            $transformed_image_data = file_get_contents($transformed_image_url);
            if ($transformed_image_data) {
                file_put_contents($image_path, $transformed_image_data);
            }
        } else {
            error_log('Cloudflare API request failed: Invalid response received');
        }
    }

    private function generate_resized_variants($image_path)
    {
        $upload_dir = wp_get_upload_dir();
        $cache_dir = $upload_dir['basedir'] . '/cloudflare_cache';
        if (!file_exists($cache_dir)) {
            mkdir($cache_dir, 0755, true);
        }

        foreach ($this->resizeVariants as $variant) {
            $cache_file_path = $cache_dir . '/' . md5($image_path) . '_' . $variant['width'] . 'x' . $variant['height'] . '.avif';
            if (file_exists($cache_file_path) && filemtime($cache_file_path) >= filemtime($image_path)) {
                // Skip if cached version is up-to-date
                continue;
            }

            $resize_params = http_build_query([ 'width' => $variant['width'], 'height' => $variant['height'] ]);
            $resize_url = $this->apiEndpoint . '/resize?' . $resize_params;
            $image_data = file_get_contents($image_path);

            $response = wp_remote_post($resize_url, array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $this->apiKey,
                    'Content-Type'  => 'application/octet-stream',
                ),
                'body' => $image_data,
            ));

            if (is_wp_error($response)) {
                error_log('Cloudflare API resize request failed: ' . $response->get_error_message());
                continue;
            }

            $body = json_decode(wp_remote_retrieve_body($response), true);
            if (isset($body['success']) && $body['success'] && !empty($body['result']['variants'])) {
                $resized_image_url = $body['result']['variants'][0];
                $resized_image_data = file_get_contents($resized_image_url);
                if ($resized_image_data) {
                    file_put_contents($cache_file_path, $resized_image_data);
                }
            } else {
                error_log('Cloudflare API resize request failed: Invalid response received');
            }
        }
    }
}

new CloudflareImageTransformer();

@CurlyFlow
Copy link
Author

CurlyFlow commented Nov 27, 2024

i nearly completed the plugin (like proof of concept, but didnt figure out the syntax cloudflare expects) and then i had a idea... just use a reverse proxy... and cache...it, should work, or am i stupid? and this could even be done, much simpler in a plugin, right? xD Have to test it tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants