diff --git a/php/class-connect.php b/php/class-connect.php
index d35bb2a89..216e0164c 100644
--- a/php/class-connect.php
+++ b/php/class-connect.php
@@ -447,7 +447,13 @@ function ( $a ) {
public function history( $days = 1 ) {
$return = array();
$history = get_option( self::META_KEYS['history'], array() );
- $plan = ! empty( $this->usage['plan'] ) ? $this->usage['plan'] : $this->credentials['cloud_name'];
+ // Stored option can be a non-array if it was corrupted by a previous
+ // failed write. Reset to empty array to avoid string-offset fatals.
+ if ( ! is_array( $history ) ) {
+ $history = array();
+ }
+ $plan_source = is_array( $this->usage ) && ! empty( $this->usage['plan'] ) ? $this->usage['plan'] : '';
+ $plan = '' !== $plan_source ? $plan_source : ( isset( $this->credentials['cloud_name'] ) ? $this->credentials['cloud_name'] : '' );
for ( $i = 1; $i <= $days; $i++ ) {
$date = date_i18n( 'd-m-Y', strtotime( '- ' . $i . ' days' ) );
if ( ! isset( $history[ $plan ][ $date ] ) || is_wp_error( $history[ $plan ][ $date ] ) ) {
@@ -720,9 +726,14 @@ public function usage_stats( $refresh = false ) {
$last_usage = $this->settings->get_setting( 'last_usage' );
// Get users plan.
$stats = $this->api->usage();
- if ( ! is_wp_error( $stats ) && ! empty( $stats['media_limits'] ) ) {
- $stats['max_image_size'] = $stats['media_limits']['image_max_size_bytes'];
- $stats['max_video_size'] = $stats['media_limits']['video_max_size_bytes'];
+ if (
+ ! is_wp_error( $stats )
+ && is_array( $stats )
+ && isset( $stats['media_limits'] )
+ && is_array( $stats['media_limits'] )
+ ) {
+ $stats['max_image_size'] = isset( $stats['media_limits']['image_max_size_bytes'] ) ? $stats['media_limits']['image_max_size_bytes'] : 0;
+ $stats['max_video_size'] = isset( $stats['media_limits']['video_max_size_bytes'] ) ? $stats['media_limits']['video_max_size_bytes'] : 0;
$last_usage->save_value( $stats );// Save the last successful call to prevgent crashing.
} else {
// Handle error by logging and fetching the last success.
diff --git a/php/class-cron.php b/php/class-cron.php
index ec0203646..9877c0b0b 100644
--- a/php/class-cron.php
+++ b/php/class-cron.php
@@ -203,8 +203,15 @@ public function rest_endpoints( $endpoints ) {
*/
protected function load_schedule() {
$this->schedule = get_option( self::CRON_META_KEY, array() );
+ // Guard against a corrupted option (e.g. a string was previously
+ // stored) so the foreach below cannot fatal on PHP 8.x.
+ if ( ! is_array( $this->schedule ) ) {
+ $this->schedule = array();
+ }
foreach ( $this->schedule as &$item ) {
- $item['active'] = false;
+ if ( is_array( $item ) ) {
+ $item['active'] = false;
+ }
}
}
diff --git a/php/class-media.php b/php/class-media.php
index 2bd521279..0d919ce24 100644
--- a/php/class-media.php
+++ b/php/class-media.php
@@ -446,15 +446,26 @@ public function is_oversize_media( $attachment_id ) {
return $is_oversize[ $attachment_id ];
}
- $file_size = $this->get_attachment_file_size( $attachment_id );
- $max_size = ( wp_attachment_is_image( $attachment_id ) ? 'image_max_size_bytes' : 'video_max_size_bytes' );
- $limit = $this->plugin->components['connect']->usage['media_limits'][ $max_size ];
+ $file_size = $this->get_attachment_file_size( $attachment_id );
+ $max_size_key = ( wp_attachment_is_image( $attachment_id ) ? 'image_max_size_bytes' : 'video_max_size_bytes' );
+ $usage = $this->plugin->components['connect']->usage;
+ $media_limits = is_array( $usage ) && isset( $usage['media_limits'] ) ? $usage['media_limits'] : null;
+
+ // If the limit isn't available yet (e.g. plugin not connected, or API
+ // returned an unexpected response), treat the asset as not oversize
+ // rather than fatal-erroring on a string offset.
+ if ( ! is_array( $media_limits ) || ! isset( $media_limits[ $max_size_key ] ) ) {
+ $is_oversize[ $attachment_id ] = false;
+
+ return $is_oversize[ $attachment_id ];
+ }
+
+ $limit = $media_limits[ $max_size_key ];
$is_oversize[ $attachment_id ] = $file_size > $limit;
if ( $is_oversize[ $attachment_id ] ) {
- $max_size = ( wp_attachment_is_image( $attachment_id ) ? 'image_max_size_bytes' : 'video_max_size_bytes' );
- $max_size_hr = size_format( $this->plugin->components['connect']->usage['media_limits'][ $max_size ] );
+ $max_size_hr = size_format( $limit );
// translators: variable is file size.
$message = sprintf( __( 'File size exceeds the maximum of %s. This media asset will be served from WordPress.', 'cloudinary' ), $max_size_hr );
update_post_meta( $attachment_id, Sync::META_KEYS['sync_error'], $message );
diff --git a/php/class-special-offer.php b/php/class-special-offer.php
index 075a99fb5..a703d2418 100644
--- a/php/class-special-offer.php
+++ b/php/class-special-offer.php
@@ -98,6 +98,12 @@ public function filtered_settings( $settings ) {
protected function is_special_offer_available() {
$last_usage = get_option( Connect::META_KEYS['last_usage'], array( 'plan' => '' ) );
+ // `last_usage` can be a string when the usage API has never returned a
+ // valid response. Only an array with a `plan` key is meaningful here.
+ if ( ! is_array( $last_usage ) || ! isset( $last_usage['plan'] ) ) {
+ return false;
+ }
+
return 'free' === strtolower( $last_usage['plan'] );
}
diff --git a/php/integrations/class-wpml.php b/php/integrations/class-wpml.php
index 56199c037..1f576f174 100644
--- a/php/integrations/class-wpml.php
+++ b/php/integrations/class-wpml.php
@@ -102,6 +102,11 @@ public function wp_generate_attachment_metadata( $metadata, $attachment_id, $con
$original_attachment_id = $attachment_id;
$data = get_transient( self::TRANSIENT_KEY );
+ // Cast to array to avoid string-offset fatals on PHP 8.x when the
+ // transient holds an unexpected scalar.
+ if ( ! is_array( $data ) ) {
+ $data = array();
+ }
// This is a duplicated attachment. Let's restore the metadata via WPML.
if ( ! empty( $data[ $original_attachment_id ] ) ) {
diff --git a/php/sync/class-sync-queue.php b/php/sync/class-sync-queue.php
index 220b9b7c5..883ff1567 100644
--- a/php/sync/class-sync-queue.php
+++ b/php/sync/class-sync-queue.php
@@ -699,8 +699,10 @@ public function get_thread_queue( $thread ) {
);
wp_cache_delete( $thread_option, 'options' );
$return = get_option( $thread_option );
- if ( empty( $return ) ) {
- // Set option to remove notoption and default fro cache.
+ if ( empty( $return ) || ! is_array( $return ) ) {
+ // Reset to default when missing or corrupted (e.g. a string was
+ // previously stored). Without the is_array check, array_merge
+ // below would fatal on PHP 8.x.
$this->set_thread_queue( $thread, $default );
$return = $default;
}
diff --git a/php/ui/component/class-plan-details.php b/php/ui/component/class-plan-details.php
index f4fea528d..ac3f1d0f7 100644
--- a/php/ui/component/class-plan-details.php
+++ b/php/ui/component/class-plan-details.php
@@ -68,10 +68,20 @@ protected function plan( $struct ) {
return $struct;
}
+ // `last_usage` can be a string when the usage API has never returned a
+ // valid response (e.g. fresh install or API error). Without this
+ // guard, PHP 8.x fatals on string-offset access below.
+ if ( ! is_array( $data ) ) {
+ $data = array();
+ }
+
+ $plan_name = isset( $data['plan'] ) ? $data['plan'] : '';
+ $requests = isset( $data['requests'] ) ? $data['requests'] : 0;
+
$struct['element'] = 'div';
$struct['attributes']['class'][] = 'cld-plan';
- $struct['children']['plan'] = $this->make_item( __( 'Plan', 'cloudinary' ), $data['plan'], $this->dir_url . 'css/images/star.svg' );
+ $struct['children']['plan'] = $this->make_item( __( 'Plan', 'cloudinary' ), $plan_name, $this->dir_url . 'css/images/star.svg' );
if ( $connection->get_usage_stat( 'credits', 'limit' ) ) {
$struct = $this->plan_credit( $struct, $connection );
@@ -79,7 +89,7 @@ protected function plan( $struct ) {
$struct = $this->plan_classic( $struct, $connection );
}
- $struct['children']['requests'] = $this->make_item( __( 'Total Requests', 'cloudinary' ), number_format_i18n( $data['requests'] ), $this->dir_url . 'css/images/requests.svg' );
+ $struct['children']['requests'] = $this->make_item( __( 'Total Requests', 'cloudinary' ), number_format_i18n( $requests ), $this->dir_url . 'css/images/requests.svg' );
$struct['children']['assets'] = $this->make_item( __( 'Optimized assets', 'cloudinary' ), number_format_i18n( Sync_Queue::get_optimized_assets() ), $this->dir_url . 'css/images/image.svg' );
return $struct;
diff --git a/ui-definitions/settings-sidebar.php b/ui-definitions/settings-sidebar.php
index 5fd2c8ad5..a21b3a4cb 100644
--- a/ui-definitions/settings-sidebar.php
+++ b/ui-definitions/settings-sidebar.php
@@ -23,10 +23,13 @@
$plugin = get_plugin_instance();
$data = $plugin->settings->get_value( 'last_usage' );
$cloud_name = $plugin->components['connect']->get_cloud_name();
+ // `last_usage` may be a string when no successful usage API
+ // response has been recorded yet; fall back to an empty plan name.
+ $plan_name = is_array( $data ) && isset( $data['plan'] ) ? $data['plan'] : '';
ob_start();
?>
-
+
@