Plugin Directory

Changeset 3349092


Ignore:
Timestamp:
08/24/2025 12:57:41 AM (7 months ago)
Author:
kodeala
Message:

v1.0.1
Fixed Fatal Error due to file removed from C3 Cloudfront Cache Controller

Location:
rocketfront-connect/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • rocketfront-connect/trunk/functions.php

    r3225218 r3349092  
    44Plugin URI: https://www.kodeala.com
    55Description: Automate AWS CloudFront CDN clearing and cache management by seamlessly integrating the WP Rocket plugin with C3 Cloudfront Cache Controller.
    6 Version: 1.0.0
     6Version: 1.0.1
    77Author: Kodeala
    8 Tags: WP Rocket, CloudFront, C3 Cloudfront Cache Controller, CDN, RocketFront
    98License: GPL-2.0-or-later
    109License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1211
    1312Requires at least: 5.0
    14 Tested up to: 6.7.1
     13Tested up to: 6.4.3
    1514
    1615Requires WP Rocket: Yes
     
    2120
    2221require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
    23 use Aws\CloudFront\CloudFrontClient;
    24 function kodealarocketfront_initialize_cloudfront_wprocket_connection() {
    25     //Check if Cloudfront plugin is active
    26     if (is_plugin_active('c3-cloudfront-clear-cache/c3-cloudfront-clear-cache.php')) {
    27         //Fetch Autoload.php from cloudfront plugin.
    28         require_once WP_PLUGIN_DIR . '/c3-cloudfront-clear-cache/vendor/autoload.php';
    29 
    30         $region = get_option('kodealarocketfront_selected_region', 'us-east-1');
    31 
    32         $c3_settings = get_option('c3_settings');
    33         $cloudfront = null;
    34        
    35         if (is_array($c3_settings) && !empty($c3_settings['access_key']) && !empty($c3_settings['secret_key'])) {
    36             $cloudfront = new CloudFrontClient([
    37                 'version' => 'latest',
    38                 'region' => esc_html(sanitize_text_field($region)),
     22
     23function kodealarocketfront_try_load_aws_sdk(): bool {
     24    if ( class_exists('\Aws\CloudFront\CloudFrontClient') ) return true;
     25
     26    $candidates = [
     27        WP_PLUGIN_DIR . '/c3-cloudfront-clear-cache/vendor/autoload.php',
     28        WP_PLUGIN_DIR . '/c3-cloudfront-clear-cache/aws/aws-autoloader.php',
     29        ABSPATH       . 'vendor/autoload.php',
     30    ];
     31    foreach ( $candidates as $path ) {
     32        if ( is_readable( $path ) ) {
     33            @include_once $path;
     34            if ( class_exists('\Aws\CloudFront\CloudFrontClient') ) return true;
     35        }
     36    }
     37    return false;
     38}
     39
     40function kodealarocketfront_get_c3_creds(): array {
     41    $opt = get_option('c3_settings', []);
     42    $dist   = $opt['distribution_id'] ?? ( defined('C3_DISTRIBUTION_ID') ? C3_DISTRIBUTION_ID : '' );
     43    $key    = $opt['access_key']      ?? ( defined('AWS_ACCESS_KEY_ID')   ? AWS_ACCESS_KEY_ID   : '' );
     44    $secret = $opt['secret_key']      ?? ( defined('AWS_SECRET_ACCESS_KEY') ? AWS_SECRET_ACCESS_KEY : '' );
     45    $token  = defined('AWS_SESSION_TOKEN') ? AWS_SESSION_TOKEN : '';
     46
     47    return [
     48        'distribution_id' => trim((string) $dist),
     49        'key'             => trim((string) $key),
     50        'secret'          => trim((string) $secret),
     51        'token'           => trim((string) $token),
     52    ];
     53}
     54
     55function kodealarocketfront_cf_http_invalidate(array $creds, array $paths): bool {
     56    if ( empty($creds['distribution_id']) || empty($creds['key']) || empty($creds['secret']) ) {
     57        return false;
     58    }
     59
     60    $service   = 'cloudfront';
     61    $region    = 'us-east-1';
     62    $host      = 'cloudfront.amazonaws.com';
     63    $now       = gmdate('Ymd\THis\Z');
     64    $dateStamp = gmdate('Ymd');
     65
     66    $itemsXml = '';
     67    foreach ($paths as $p) {
     68        $p = (string) $p;
     69        if ($p === '') continue;
     70        $p = (strpos($p, '/') === 0) ? $p : '/' . $p;
     71        $itemsXml .= '<Path>' . esc_html($p) . '</Path>';
     72    }
     73    if ($itemsXml === '') $itemsXml = '<Path>/*</Path>';
     74
     75    $callerRef = (string) (microtime(true) * 1000000);
     76    $body = <<<XML
     77<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
     78  <Paths>
     79    <Quantity>1</Quantity>
     80    <Items>
     81      {$itemsXml}
     82    </Items>
     83  </Paths>
     84  <CallerReference>{$callerRef}</CallerReference>
     85</InvalidationBatch>
     86XML;
     87
     88    $uriPath   = '/2020-05-31/distribution/' . rawurlencode($creds['distribution_id']) . '/invalidation';
     89    $canonicalURI    = $uriPath;
     90    $canonicalQuery  = '';
     91    $contentSha256   = hash('sha256', $body);
     92    $headers = [
     93        'content-type'        => 'application/xml',
     94        'host'                => $host,
     95        'x-amz-content-sha256'=> $contentSha256,
     96        'x-amz-date'          => $now,
     97    ];
     98    if (!empty($creds['token'])) {
     99        $headers['x-amz-security-token'] = $creds['token'];
     100    }
     101
     102    ksort($headers);
     103    $canonicalHeaders = '';
     104    $signedHeaderKeys = [];
     105    foreach ($headers as $k => $v) {
     106        $canonicalHeaders .= strtolower($k) . ':' . trim($v) . "\n";
     107        $signedHeaderKeys[] = strtolower($k);
     108    }
     109    $signedHeaders = implode(';', $signedHeaderKeys);
     110
     111    $canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQuery}\n{$canonicalHeaders}\n{$signedHeaders}\n{$contentSha256}";
     112    $hashedCanonical  = hash('sha256', $canonicalRequest);
     113
     114    $scope = "{$dateStamp}/{$region}/{$service}/aws4_request";
     115    $stringToSign = "AWS4-HMAC-SHA256\n{$now}\n{$scope}\n{$hashedCanonical}";
     116
     117    $kDate    = hash_hmac('sha256', $dateStamp, 'AWS4' . $creds['secret'], true);
     118    $kRegion  = hash_hmac('sha256', $region,     $kDate, true);
     119    $kService = hash_hmac('sha256', $service,    $kRegion, true);
     120    $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
     121
     122    $signature = hash_hmac('sha256', $stringToSign, $kSigning);
     123
     124    $authHeader = sprintf(
     125        'AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s',
     126        $creds['key'],
     127        $scope,
     128        $signedHeaders,
     129        $signature
     130    );
     131
     132    $wp_headers = [
     133        'Authorization'         => $authHeader,
     134        'Content-Type'          => $headers['content-type'],
     135        'X-Amz-Date'            => $headers['x-amz-date'],
     136        'X-Amz-Content-Sha256'  => $headers['x-amz-content-sha256'],
     137        'Host'                  => $headers['host'],
     138    ];
     139    if (!empty($creds['token'])) {
     140        $wp_headers['X-Amz-Security-Token'] = $creds['token'];
     141    }
     142
     143    $resp = wp_remote_request(
     144        "https://{$host}{$uriPath}",
     145        [
     146            'method'  => 'POST',
     147            'timeout' => 20,
     148            'headers' => $wp_headers,
     149            'body'    => $body,
     150        ]
     151    );
     152
     153    if ( is_wp_error($resp) ) {
     154        error_log('[RocketFront] HTTP invalidation error: ' . $resp->get_error_message());
     155        return false;
     156    }
     157    $code = wp_remote_retrieve_response_code($resp);
     158    if ( $code < 200 || $code >= 300 ) {
     159        error_log('[RocketFront] HTTP invalidation failed with status ' . $code . ' body: ' . wp_remote_retrieve_body($resp));
     160        return false;
     161    }
     162    return true;
     163}
     164
     165function kodealarocketfront_invalidate(array $paths = ['/*']): bool {
     166    static $done = false;
     167    if ( $done ) return true;
     168    $done = true;
     169
     170    $creds = kodealarocketfront_get_c3_creds();
     171    if ( empty($creds['distribution_id']) ) {
     172        error_log('[RocketFront] Missing C3 distribution id – skipping invalidation.');
     173        return false;
     174    }
     175
     176    if ( kodealarocketfront_try_load_aws_sdk() && class_exists('\Aws\CloudFront\CloudFrontClient') ) {
     177        try {
     178            $client = new \Aws\CloudFront\CloudFrontClient([
     179                'version'     => 'latest',
     180                'region'      => 'us-east-1',
    39181                'credentials' => [
    40                     'key' => esc_html(sanitize_text_field($c3_settings['access_key'])),
    41                     'secret' => esc_html(sanitize_text_field($c3_settings['secret_key'])),
     182                    'key'    => $creds['key'],
     183                    'secret' => $creds['secret'],
    42184                ],
    43185            ]);
    44         }
    45         function kodealarocketfront_invalidate_cloudfront_cache($cloudfront) {
    46             if (defined('KODEALAROCKETFRONT_CACHE_INVALIDATED')) {
    47                 return;
    48             }
    49             define('KODEALAROCKETFRONT_CACHE_INVALIDATED', true);
    50            
    51             $c3_settings = get_option('c3_settings');
    52             $result = $cloudfront->createInvalidation([
    53                 'DistributionId' => esc_html(sanitize_text_field($c3_settings['distribution_id'])),
    54                 'InvalidationBatch' => [
    55                     'CallerReference' => time(),
     186            $client->createInvalidation([
     187                'DistributionId'   => $creds['distribution_id'],
     188                'InvalidationBatch'=> [
     189                    'CallerReference' => (string) (microtime(true) * 1000000),
    56190                    'Paths' => [
    57                         'Quantity' => 1,
    58                         'Items' => ['/*'],
     191                        'Quantity' => count($paths),
     192                        'Items'    => $paths,
    59193                    ],
    60194                ],
    61195            ]);
     196            return true;
     197        } catch (\Throwable $e) {
     198            error_log('[RocketFront] SDK invalidation error: ' . $e->getMessage() . ' – falling back to HTTP.');
    62199        }
    63 
    64         if (!empty($cloudfront)) {
    65             add_action('after_rocket_clean_domain', function () use ($cloudfront) {
    66                 kodealarocketfront_invalidate_cloudfront_cache($cloudfront);
    67             });
    68             add_action('after_rocket_clean_minify', function () use ($cloudfront) {
    69                 kodealarocketfront_invalidate_cloudfront_cache($cloudfront);
    70             });
    71             add_action('after_rocket_clean_cache', function () use ($cloudfront) {
    72                 kodealarocketfront_invalidate_cloudfront_cache($cloudfront);
    73             });
    74         }
    75 
    76     function kodealarocketfront_add_region_dropdown() {
    77         add_options_page(
    78             'CloudFront Regions',
    79             'CloudFront Regions',
    80             'manage_options',
    81             'kodealarocketfront-region-settings',
    82             'kodealarocketfront_region_settings_page'
    83         );
    84     }
    85     add_action('admin_menu', 'kodealarocketfront_add_region_dropdown');
    86 
    87     function kodealarocketfront_region_settings_page() {
    88         if (!isset($_POST['kodealarocketfront_nonce_field']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['kodealarocketfront_nonce_field'])), 'kodealarocketfront_nonce_action')) {
    89             $selected_region = isset($_POST['kodealarocketfront_selected_region']) ? sanitize_text_field(wp_unslash($_POST['kodealarocketfront_selected_region'])) : '';
    90             if (!empty($selected_region)) {
    91                 update_option('kodealarocketfront_selected_region', $selected_region);
    92             }
    93         }
    94         $regions = [
    95             'us-east-1' => 'US East (N. Virginia)',
    96             'us-east-2' => 'US East (Ohio)',
    97             'us-west-1' => 'US West (N. California)',
    98             'us-west-2' => 'US West (Oregon)',
    99             'ap-south-1' => 'Asia Pacific (Mumbai)',
    100             'ap-northeast-3' => 'Asia Pacific (Osaka)',
    101             'ap-northeast-2' => 'Asia Pacific (Seoul)',
    102             'ap-southeast-1' => 'Asia Pacific (Singapore)',
    103             'ap-southeast-2' => 'Asia Pacific (Sydney)',
    104             'ap-northeast-1' => 'Asia Pacific (Tokyo)',
    105             'ca-central-1' => 'Canada (Central)',
    106             'eu-central-1' => 'Europe (Frankfurt)',
    107             'eu-west-1' => 'Europe (Ireland)',
    108             'eu-west-2' => 'Europe (London)',
    109             'eu-west-3' => 'Europe (Paris)',
    110             'eu-north-1' => 'Europe (Stockholm)',
    111             'sa-east-1' => 'South America (São Paulo)',
    112             'af-south-1' => 'Africa (Cape Town)',
    113             'ap-east-1' => 'Asia Pacific (Hong Kong)',
    114             'ap-south-2' => 'Asia Pacific (Hyderabad)',
    115             'ap-southeast-3' => 'Asia Pacific (Jakarta)',
    116             'ap-southeast-4' => 'Asia Pacific (Melbourne)',
    117             'ca-west-1' => 'Canada (Calgary)',
    118             'eu-south-1' => 'Europe (Milan)',
    119             'eu-south-2' => 'Europe (Spain)',
    120             'eu-central-2' => 'Europe (Zurich)',
    121             'me-south-1' => 'Middle East (Bahrain)',
    122             'me-central-1' => 'Middle East (UAE)',
    123             'il-central-1' => 'Israel (Tel Aviv)'
    124         ];
    125 
    126         $region = get_option('kodealarocketfront_selected_region', 'us-east-1');
    127         ?>
    128         <div class="wrap">
    129             <h1>Region Settings</h1>
    130             <form method="post">
    131                 <label for="kodealarocketfront_selected_region">Select CloudFront Region:</label>
    132                 <select name="kodealarocketfront_selected_region" id="kodealarocketfront_selected_region">
    133                     <?php foreach ($regions as $region_code => $region_name){
    134                         $selected = ($region == $region_code) ? 'selected' : '';
    135                     ?>
    136                         <option value="<?php echo esc_html(sanitize_text_field($region_code)); ?>" <?php echo esc_html(sanitize_text_field($selected)); ?>><?php echo esc_html(sanitize_text_field($region_name)); ?></option>
    137                     <?php }; ?>
    138                 </select>
    139                 <?php
    140                     wp_nonce_field('kodealarocketfront_nonce_action', 'kodealarocketfront_nonce_field', true, false);
    141                     submit_button('Save Changes');
    142                 ?>
    143             </form>
    144         </div>
    145         <?php
    146         }
    147     }
    148 }
    149 add_action('plugins_loaded', 'kodealarocketfront_initialize_cloudfront_wprocket_connection');
    150 ?>
     200    }
     201
     202    return kodealarocketfront_cf_http_invalidate($creds, $paths);
     203}
     204
     205function kodealarocketfront_boot() {
     206    if ( ! function_exists('is_plugin_active') ) {
     207        @include_once ABSPATH . 'wp-admin/includes/plugin.php';
     208    }
     209    if ( ! is_plugin_active('c3-cloudfront-clear-cache/c3-cloudfront-clear-cache.php') ) {
     210        return;
     211    }
     212
     213    $callback = function() { kodealarocketfront_invalidate(['/*']); };
     214
     215    add_action('after_rocket_clean_domain', $callback);
     216    add_action('after_rocket_clean_minify', $callback);
     217    add_action('after_rocket_clean_cache',  $callback);
     218
     219    add_action('after_rocket_clean_files',  $callback);
     220}
     221add_action('plugins_loaded', 'kodealarocketfront_boot');
  • rocketfront-connect/trunk/readme.txt

    r3129792 r3349092  
    17173. Configure the plugin settings under the CloudFront Regions on the settings pages.
    1818== Changelog ==
     19v1.0.1
     20Fixed Fatal Error due to file removed from C3 Cloudfront Cache Controller
     21
    1922v1.0
    2023Initial creation of plugin
Note: See TracChangeset for help on using the changeset viewer.