From 72ef050ca449501b02b9c2541a485b6dde68da92 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Wed, 8 Jul 2026 15:40:49 +0530 Subject: [PATCH 1/2] Bump PHPStan to level 2 and fix type/annotation issues Raise the PHPStan analysis level from 1 to 2 and resolve the resulting issues that are safe, mechanical fixes: - Correct malformed PHPDoc tags (trailing periods, invalid array()/[] syntax, \array, $var ordering). - Use fully-qualified class names in PHPDoc where the containing namespace shadowed WP/global classes (WP_REST_*, Cloudinary\Connect). - Add an Elementor stub for the optional integration and register it. - Replace the removed Requests_Transport_cURL type hint with resource. - Narrow get_component()/managers[...] union return types at call sites so type-specific method/property access resolves. - Add \@property/@method annotations for magic __get/__call access (Settings, Setting, Relationship, Api). - Fix genuine argument-count/type bugs: save_value($media), drop ignored extra args to has_param()/get_settings(), cast numeric stats before arithmetic, correct WP_Post case, make Relationship::$post_id public. - Suppress one intentional dynamic WP_Post property write inline. --- php/cache/class-cache-point.php | 2 +- php/class-admin.php | 2 +- php/class-assets.php | 5 +- php/class-connect.php | 2 + php/class-dashboard.php | 5 +- php/class-deactivation.php | 1 + php/class-delivery.php | 12 ++-- php/class-media.php | 20 +++---- php/class-plugin.php | 5 +- php/class-settings.php | 4 +- php/class-sync.php | 12 ++-- php/class-url.php | 4 +- php/connect/class-api.php | 9 ++- php/media/class-global-transformations.php | 6 +- php/media/class-upgrade.php | 2 +- php/relate/class-relationship.php | 3 +- php/settings/class-setting.php | 7 +++ php/sync/class-push-sync.php | 1 + php/sync/class-sync-queue.php | 8 ++- php/ui/class-branch.php | 2 +- php/ui/component/class-asset.php | 2 +- php/ui/component/class-cache-status.php | 1 + php/ui/component/class-connect.php | 2 +- php/ui/component/class-folder-table.php | 2 +- php/ui/component/class-line-stat.php | 4 +- php/ui/component/class-on-off.php | 4 +- php/ui/component/class-opt-level.php | 2 +- php/ui/component/class-page.php | 6 +- php/ui/component/class-sync.php | 4 +- phpstan.neon.dist | 3 +- tests/phpstan/stubs/elementor.php | 67 ++++++++++++++++++++++ 31 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 tests/phpstan/stubs/elementor.php diff --git a/php/cache/class-cache-point.php b/php/cache/class-cache-point.php index 91abc3777..af4dd0239 100644 --- a/php/cache/class-cache-point.php +++ b/php/cache/class-cache-point.php @@ -34,7 +34,7 @@ class Cache_Point { /** * Holds a list of pre-found cached urls before querying to find cached items * - * @var array. + * @var array */ protected $pre_cached = array(); /** diff --git a/php/class-admin.php b/php/class-admin.php index be9642dd9..67b8ef26e 100644 --- a/php/class-admin.php +++ b/php/class-admin.php @@ -38,7 +38,7 @@ class Admin { /** * Holds notices object. * - * @var Settings + * @var Setting */ protected $notices; diff --git a/php/class-assets.php b/php/class-assets.php index afdd8e79f..749ae9f78 100644 --- a/php/class-assets.php +++ b/php/class-assets.php @@ -1141,7 +1141,7 @@ public function get_active_asset_parents() { * * @param int $asset_id The asset id. * - * @return \WP_Post|null; + * @return \WP_Post|null */ public function find_parent( $asset_id ) { $path = $this->clean_path( $this->media->local_url( $asset_id ) ); @@ -1792,7 +1792,8 @@ protected function add_external_settings() { */ protected function add_asset_parent( $post ) { if ( is_multisite() ) { - $post->blog_id = get_current_blog_id(); + // Dynamically stash the current blog ID on the post object for later multisite checks. + $post->blog_id = get_current_blog_id(); // @phpstan-ignore property.notFound } $this->asset_parents[ $post->post_title ] = $post; diff --git a/php/class-connect.php b/php/class-connect.php index 1779cb676..196175738 100644 --- a/php/class-connect.php +++ b/php/class-connect.php @@ -153,6 +153,7 @@ public function rest_test_connection( WP_REST_Request $request ) { $url = $request->get_param( 'cloudinary_url' ); $result = $this->test_connection( $url ); + /** @var \Cloudinary\Analytics $analytics */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $success = isset( $result['type'] ) && 'connection_success' === $result['type']; @@ -240,6 +241,7 @@ public function rest_save_wizard( WP_REST_Request $request ) { ); } + /** @var \Cloudinary\Analytics $analytics */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $analytics->track( diff --git a/php/class-dashboard.php b/php/class-dashboard.php index 6096923e4..31b037f63 100644 --- a/php/class-dashboard.php +++ b/php/class-dashboard.php @@ -37,8 +37,11 @@ public function __construct( Plugin $plugin ) { * @return bool */ public function has_data() { + /** @var \Cloudinary\Sync $sync */ $sync = $this->plugin->get_component( 'sync' ); - $data = $sync->managers['queue']->get_total_synced_media(); + /** @var \Cloudinary\Sync\Sync_Queue $queue */ + $queue = $sync->managers['queue']; + $data = $queue->get_total_synced_media(); return ! empty( $data ); } diff --git a/php/class-deactivation.php b/php/class-deactivation.php index 6edb7fcac..a25408cc2 100644 --- a/php/class-deactivation.php +++ b/php/class-deactivation.php @@ -575,6 +575,7 @@ protected function cleanup_options() { $this->settings->delete( $slug ); } + /** @var \Cloudinary\Sync\Sync_Queue $queue */ $queue = $this->plugin->get_component( 'sync' )->managers['queue']; $all_threads = $queue->get_threads( 'all' ); foreach ( $all_threads as $threads ) { diff --git a/php/class-delivery.php b/php/class-delivery.php index e8e55273d..365be1160 100644 --- a/php/class-delivery.php +++ b/php/class-delivery.php @@ -162,11 +162,11 @@ function ( $post ) { /** * Determine if attributes should be added to image tags. * - * @param WP_REST_Response $response The response object. - * @param WP_REST_Server $handler The request handler. - * @param WP_REST_Request|null $request The request object, if available. + * @param \WP_REST_Response $response The response object. + * @param \WP_REST_Server $handler The request handler. + * @param \WP_REST_Request|null $request The request object, if available. * - * @return WP_REST_Response + * @return \WP_REST_Response */ public function maybe_unset_attributes( $response, $handler, $request ) { $route = $request->get_route(); @@ -2110,7 +2110,9 @@ public function catch_urls( $content, $context = 'view' ) { // Check if we are saving. If so, bail. // This is to prevent the replacement from happening in the shutdown, signaling content changes in the editor. - if ( $this->plugin->get_component( 'replace' )->doing_save() ) { + /** @var \Cloudinary\String_Replace $replace */ + $replace = $this->plugin->get_component( 'replace' ); + if ( $replace->doing_save() ) { return; } $this->init_delivery(); diff --git a/php/class-media.php b/php/class-media.php index 829893dba..5dd7e7090 100644 --- a/php/class-media.php +++ b/php/class-media.php @@ -38,7 +38,7 @@ class Media extends Settings_Component implements Setup { * * @since 0.1 * - * @var string. + * @var string */ public $base_url; @@ -47,7 +47,7 @@ class Media extends Settings_Component implements Setup { * * @since 0.1 * - * @var string. + * @var string */ private $cloudinary_folder; @@ -56,49 +56,49 @@ class Media extends Settings_Component implements Setup { * * @since 0.1 * - * @var array. + * @var array */ private $cloudinary_ids = array(); /** * Cloudinary credentials. * - * @var array. + * @var array */ public $credentials; /** * Cloudinary url filtering instance. * - * @var \Cloudinary\Media\Filter. + * @var \Cloudinary\Media\Filter */ public $filter; /** * Cloudinary upgrade instance. * - * @var \Cloudinary\Media\Upgrade. + * @var \Cloudinary\Media\Upgrade */ public $upgrade; /** * Cloudinary global transformations. * - * @var \Cloudinary\Media\Global_Transformations. + * @var \Cloudinary\Media\Global_Transformations */ public $global_transformations; /** * Video filter instance. * - * @var \Cloudinary\Media\Video. + * @var \Cloudinary\Media\Video */ public $video; /** * Gallery instance. * - * @var \Cloudinary\Media\Gallery. + * @var \Cloudinary\Media\Gallery */ public $gallery; @@ -3275,7 +3275,7 @@ public function upgrade_settings( $previous_version, $new_version ) { // Update value. $setting->set_value( $media ); // Save to DB. - $setting->save_value(); + $setting->save_value( $media ); } } diff --git a/php/class-plugin.php b/php/class-plugin.php index cec471844..c1a74e678 100644 --- a/php/class-plugin.php +++ b/php/class-plugin.php @@ -242,7 +242,7 @@ private function init_component_settings( $components ) { /** * Component that implements Settings. * - * @var Component\Settings $component + * @var Settings_Component $component */ $component->init_settings( $this->settings ); @@ -265,6 +265,7 @@ public function set_config() { $components = array_filter( $this->components, array( $this, 'is_config_component' ) ); foreach ( $components as $slug => $component ) { + /** @var Config $component */ $component->get_config(); } } @@ -481,7 +482,7 @@ public function admin_notices() { /** * An array of classes that implement the Notice interface. * - * @var $components Notice[] + * @var Notice[] $components */ $components = array_filter( $this->components, array( $this, 'is_notice_component' ) ); $default = array( diff --git a/php/class-settings.php b/php/class-settings.php index eee38f218..4507ba0bb 100644 --- a/php/class-settings.php +++ b/php/class-settings.php @@ -15,6 +15,8 @@ * Class Settings * * @package Cloudinary + * + * @property-read Setting|null $image_settings Chainable access to the image_settings child setting. */ class Settings { @@ -338,7 +340,7 @@ protected function create_child( $slug, $params ) { /** * Get a setting value. * - * @param [string] ...$slugs Additional slugs to get settings for. + * @param string ...$slugs Additional slugs to get settings for. * * @return mixed */ diff --git a/php/class-sync.php b/php/class-sync.php index fbf39a698..b6835101d 100644 --- a/php/class-sync.php +++ b/php/class-sync.php @@ -606,8 +606,10 @@ public function setup_sync_base_struct() { 'generate' => array( $this->managers['connect'], 'get_cloud_name' ), 'validate' => function ( $attachment_id ) { - $valid = true; - $credentials = $this->managers['connect']->get_credentials(); + $valid = true; + /** @var \Cloudinary\Connect $connect */ + $connect = $this->managers['connect']; + $credentials = $connect->get_credentials(); if ( isset( $credentials['cname'] ) ) { $url = get_post_meta( $attachment_id, '_wp_attached_file', true ); if ( wp_http_validate_url( $url ) ) { @@ -1049,8 +1051,10 @@ public function set_signature_item( $attachment_id, $type, $value = null ) { */ public function init_background_upload() { if ( ! empty( $this->to_sync ) ) { - $this->managers['queue']->add_to_queue( $this->to_sync, 'autosync' ); - $this->managers['queue']->start_threads( 'autosync' ); + /** @var \Cloudinary\Sync\Sync_Queue $queue */ + $queue = $this->managers['queue']; + $queue->add_to_queue( $this->to_sync, 'autosync' ); + $queue->start_threads( 'autosync' ); } } diff --git a/php/class-url.php b/php/class-url.php index 91fa7e43d..3baeaa3e5 100644 --- a/php/class-url.php +++ b/php/class-url.php @@ -51,7 +51,7 @@ class URL implements Setup { /** * Holds a list of the WordPress URL objects. * - * @var array + * @var WordPress[] */ protected $wordpress_urls = array(); @@ -120,7 +120,7 @@ public function get_cloudinary_urls() { * * @param string $url The WordPress URL to break apart. * - * @return array + * @return WordPress */ public function wordpress_url( $url ) { if ( ! isset( $this->wordpress_urls[ $url ] ) ) { diff --git a/php/connect/class-api.php b/php/connect/class-api.php index 33ec732c6..796762bf2 100644 --- a/php/connect/class-api.php +++ b/php/connect/class-api.php @@ -18,6 +18,9 @@ * Class API. * * Push media to Cloudinary on upload. + * + * @method array|\WP_Error usage( string ...$args ) Get account usage stats. + * @method array|\WP_Error ping( string ...$args ) Ping the Cloudinary API. */ class Api { @@ -828,9 +831,9 @@ function ( $value, $key ) { /** * Set the POSTFIELDS to the correct array type, not the string based. * - * @param \Requests_Transport_cURL $handle The transport handle to set. - * @param array $request The request array. - * @param string $url The url to send to. + * @param resource $handle The cURL handle to set. + * @param array $request The request array. + * @param string $url The url to send to. */ public function set_data( $handle, $request, $url ) { // Ensure that this request is in fact ours. diff --git a/php/media/class-global-transformations.php b/php/media/class-global-transformations.php index 75f93534c..3fc092d4a 100644 --- a/php/media/class-global-transformations.php +++ b/php/media/class-global-transformations.php @@ -326,7 +326,7 @@ public function taxonomy_ordering( $type, $post ) { /** * Check if the post has any public taxonomies. * - * @param \WP_POST $post The post to check. + * @param \WP_Post $post The post to check. * * @return bool */ @@ -656,7 +656,9 @@ public function transformations_column_value( $column_name, $attachment_id ) { return; } - $item = $this->media->plugin->get_component( 'assets' )->get_asset( $attachment_id, 'dataset' ); + /** @var \Cloudinary\Assets $assets */ + $assets = $this->media->plugin->get_component( 'assets' ); + $item = $assets->get_asset( $attachment_id, 'dataset' ); if ( ! empty( $item['data']['public_id'] ) ) { $text = __( 'Add Effects', 'cloudinary' ); $transformations = Relate::get_transformations( $attachment_id, true, true ); diff --git a/php/media/class-upgrade.php b/php/media/class-upgrade.php index be45fe56b..b0d0e0ccd 100644 --- a/php/media/class-upgrade.php +++ b/php/media/class-upgrade.php @@ -197,7 +197,7 @@ public function get_fetch_public_id( $path, $attachment_id ) { * * @param int $attachment_id The attachment ID to migrate. * - * @return array(); + * @return array */ public function migrate_legacy_meta( $attachment_id ) { diff --git a/php/relate/class-relationship.php b/php/relate/class-relationship.php index dee31bd4f..96bc5a7d9 100644 --- a/php/relate/class-relationship.php +++ b/php/relate/class-relationship.php @@ -24,6 +24,7 @@ * @property string|null $sized_url * @property string|null $url_hash * @property string|null $media_context + * @property string|null $format */ class Relationship { @@ -32,7 +33,7 @@ class Relationship { * * @var int */ - protected $post_id; + public $post_id; /** * Flag to save the relationship on shutdown. diff --git a/php/settings/class-setting.php b/php/settings/class-setting.php index 28de73f29..ab943464b 100644 --- a/php/settings/class-setting.php +++ b/php/settings/class-setting.php @@ -15,6 +15,13 @@ * Class Setting * * @package Cloudinary\Settings + * + * Chainable child-setting access is provided dynamically via __get(). The + * properties documented below are the child slugs accessed directly in the + * codebase; the leading-underscore variant returns the child's value. + * + * @property-read Setting|null $overlay Chainable access to the overlay child setting. + * @property-read mixed $_overlay Value of the overlay child setting. */ class Setting { diff --git a/php/sync/class-push-sync.php b/php/sync/class-push-sync.php index c0fe35203..03fc2883b 100644 --- a/php/sync/class-push-sync.php +++ b/php/sync/class-push-sync.php @@ -203,6 +203,7 @@ public function process_assets( $attachments = array() ) { $thread = $this->plugin->settings->get_param( 'current_sync_thread' ); // Activation funnel: first sync started (emitted once per account). + /** @var \Cloudinary\Analytics $analytics */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $connect = $this->plugin->get_component( 'connect' ); diff --git a/php/sync/class-sync-queue.php b/php/sync/class-sync-queue.php index b25bf7a34..b5f1a6be4 100644 --- a/php/sync/class-sync-queue.php +++ b/php/sync/class-sync-queue.php @@ -137,13 +137,17 @@ public function setup( $sync ) { // Enable sync queue. if ( filter_input( INPUT_GET, 'enable-bulk', FILTER_VALIDATE_BOOLEAN ) ) { $this->bulk_sync( true ); - wp_safe_redirect( $this->sync->settings->get_component()->get_url() ); + /** @var \Cloudinary\UI\Component\Page $page */ + $page = $this->sync->settings->get_component(); + wp_safe_redirect( $page->get_url() ); exit; } // Stop sync queue. if ( filter_input( INPUT_GET, 'disable-bulk', FILTER_VALIDATE_BOOLEAN ) ) { $this->bulk_sync( false ); - wp_safe_redirect( $this->sync->settings->get_component()->get_url() ); + /** @var \Cloudinary\UI\Component\Page $page */ + $page = $this->sync->settings->get_component(); + wp_safe_redirect( $page->get_url() ); exit; } diff --git a/php/ui/class-branch.php b/php/ui/class-branch.php index b8ca6c2b9..db82f850d 100644 --- a/php/ui/class-branch.php +++ b/php/ui/class-branch.php @@ -34,7 +34,7 @@ class Branch { /** * Holds the ID of the main input. * - * @var array() + * @var array */ public $main = array(); diff --git a/php/ui/component/class-asset.php b/php/ui/component/class-asset.php index 85209fcb8..0b00b548b 100644 --- a/php/ui/component/class-asset.php +++ b/php/ui/component/class-asset.php @@ -156,7 +156,7 @@ protected function get_item_row( $item ) { /** * Get the manager part. * - * @param array $item The item to get the manager for. + * @param Setting $item The item to get the manager for. * * @return array */ diff --git a/php/ui/component/class-cache-status.php b/php/ui/component/class-cache-status.php index 317a0f6ff..bb644ecb6 100644 --- a/php/ui/component/class-cache-status.php +++ b/php/ui/component/class-cache-status.php @@ -33,6 +33,7 @@ class Cache_Status extends Media_Status { */ protected function box_status( $struct ) { + /** @var \Cloudinary\Cache $cache */ $cache = $this->plugin->get_component( 'cache' ); $this->cache = $cache->cache_point; $cache_points = $this->cache->get_active_cache_points(); diff --git a/php/ui/component/class-connect.php b/php/ui/component/class-connect.php index 8249dd9e5..77579812a 100644 --- a/php/ui/component/class-connect.php +++ b/php/ui/component/class-connect.php @@ -28,7 +28,7 @@ class Connect extends Component { /** * Holder the Connect object. * - * @var Connect + * @var \Cloudinary\Connect */ protected $connect; diff --git a/php/ui/component/class-folder-table.php b/php/ui/component/class-folder-table.php index f291b1753..4952cc5e2 100644 --- a/php/ui/component/class-folder-table.php +++ b/php/ui/component/class-folder-table.php @@ -67,7 +67,7 @@ public function setup() { /** * Build the header. * - * @return \array[][] + * @return array[][] */ protected function build_headers() { $header_columns = array( diff --git a/php/ui/component/class-line-stat.php b/php/ui/component/class-line-stat.php index 9675425f0..3f4e33be4 100644 --- a/php/ui/component/class-line-stat.php +++ b/php/ui/component/class-line-stat.php @@ -86,8 +86,8 @@ class Line_Stat extends Component { public function setup() { parent::setup(); $this->set_stats(); - $used = $this->limit * $this->used_percent / 100; - $avail = $this->limit - $used; + $used = (float) $this->limit * (float) $this->used_percent / 100; + $avail = (float) $this->limit - $used; if ( $this->setting->get_param( 'format_size' ) ) { $used = empty( $used ) ? 0 : $used; $avail = empty( $avail ) ? 0 : $avail; diff --git a/php/ui/component/class-on-off.php b/php/ui/component/class-on-off.php index fe6beff79..f68130720 100644 --- a/php/ui/component/class-on-off.php +++ b/php/ui/component/class-on-off.php @@ -92,7 +92,7 @@ protected function input( $struct ) { $struct['attributes']['data-main'] = wp_json_encode( $controllers ); } - if ( $this->is_readonly() || ( true === $this->setting->has_param( 'main_required', false ) && empty( $struct['attributes']['data-main'] ) ) ) { + if ( $this->is_readonly() || ( true === $this->setting->has_param( 'main_required' ) && empty( $struct['attributes']['data-main'] ) ) ) { $struct['attributes']['type'] = 'hidden'; } @@ -119,7 +119,7 @@ protected function input( $struct ) { */ protected function shadow( $struct ) { // Add the toggle stub. - if ( $this->is_readonly() || ( true === $this->setting->has_param( 'main_required', false ) && empty( $struct['attributes']['data-main'] ) ) ) { + if ( $this->is_readonly() || ( true === $this->setting->has_param( 'main_required' ) && empty( $struct['attributes']['data-main'] ) ) ) { $struct = $this->get_part( 'input' ); $struct['attributes']['type'] = 'checkbox'; $struct['attributes']['disabled'] = 'disabled'; diff --git a/php/ui/component/class-opt-level.php b/php/ui/component/class-opt-level.php index 24beccd19..c2383f94c 100644 --- a/php/ui/component/class-opt-level.php +++ b/php/ui/component/class-opt-level.php @@ -157,7 +157,7 @@ protected function get_url( $slug ) { */ protected function set_texts() { - $used_percent = round( $this->used / $this->limit * 100 ); + $used_percent = round( (float) $this->used / (float) $this->limit * 100 ); /* translators: %s is the percentage optimized. */ $this->used_text = sprintf( __( '%s optimized', 'cloudinary' ), $used_percent . '%' ); diff --git a/php/ui/component/class-page.php b/php/ui/component/class-page.php index e3a870783..21744f04e 100644 --- a/php/ui/component/class-page.php +++ b/php/ui/component/class-page.php @@ -154,7 +154,7 @@ protected function get_option_name() { */ protected function tabs( $struct ) { - if ( $this->setting->has_param( 'has_tabs' ) && 1 < count( $this->setting->get_settings( 'page' ) ) ) { + if ( $this->setting->has_param( 'has_tabs' ) && 1 < count( $this->setting->get_settings() ) ) { $struct['element'] = 'ul'; $struct['attributes']['class'] = array( 'cld-page-tabs', @@ -190,7 +190,9 @@ protected function get_tabs() { // Create the link. $link = $this->get_part( 'a' ); $link['content'] = $setting->get_param( 'menu_title', $setting->get_param( 'page_title' ) ); - $link['attributes']['href'] = $setting->get_component()->get_url(); + /** @var \Cloudinary\UI\Component\Page $page */ + $page = $setting->get_component(); + $link['attributes']['href'] = $page->get_url(); // Add tab to list. $tab['children'][ $setting->get_slug() ] = $link; diff --git a/php/ui/component/class-sync.php b/php/ui/component/class-sync.php index 3f98f0222..1767d620d 100644 --- a/php/ui/component/class-sync.php +++ b/php/ui/component/class-sync.php @@ -76,7 +76,9 @@ protected function action( $struct ) { } $struct['element'] = 'a'; - $href = $this->setting->find_setting( 'sync_media' )->get_component()->get_url(); + /** @var \Cloudinary\UI\Component\Page $page */ + $page = $this->setting->find_setting( 'sync_media' )->get_component(); + $href = $page->get_url(); $args = array(); if ( ! $this->setting->get_param( 'queue' )->is_enabled() ) { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 63e08f19b..d585b56fe 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -14,7 +14,7 @@ parameters: - identifier: return.void message: '#^Action callback returns .+ but should not return anything\.$#' - level: 1 + level: 2 paths: - php - cloudinary.php @@ -24,6 +24,7 @@ parameters: - vendor/php-stubs/wp-cli-stubs/wp-cli-commands-stubs.php - vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php - tests/phpstan/stubs/wpml.php + - tests/phpstan/stubs/elementor.php - tests/phpstan/stubs/wpcom-vip.php - tests/phpstan/stubs/constants.php excludePaths: diff --git a/tests/phpstan/stubs/elementor.php b/tests/phpstan/stubs/elementor.php new file mode 100644 index 000000000..7eca6d6a0 --- /dev/null +++ b/tests/phpstan/stubs/elementor.php @@ -0,0 +1,67 @@ + Date: Wed, 8 Jul 2026 15:55:36 +0530 Subject: [PATCH 2/2] Fix broken references to removed settings API Repair genuine runtime bugs surfaced by PHPStan level 2 that referenced methods deleted in the earlier settings/UI decoupling refactor: - class-cache.php: the cache settings tabs called the removed Settings::create_setting() via a callback indirection that would fatal. Convert add_plugin/theme/wp/content_settings() to return their params and embed them inline in settings() (matching the working Assets pattern), and register the cache_point path hooks directly in setup_setting_tabs(). Remove the now-unused get_cache_settings(). - class-cache-status.php: box_status() built a table via the removed create_setting()/render_component(); rebuild it using the component part system so it renders correctly. - class-page.php + class-admin.php: notice() called the non-existent Setting::get_admin_notices(); route it through the Admin component and have get_admin_notices() return the renderable notice Setting built via init_components() (consistent with render_notices()). Also convert the inline union-narrowing @var hints added for the level bump to multi-line docblocks to satisfy WPCS. --- php/class-admin.php | 14 +++- php/class-cache.php | 88 ++++++++++------------ php/class-connect.php | 12 ++- php/class-dashboard.php | 12 ++- php/class-deactivation.php | 6 +- php/class-delivery.php | 6 +- php/class-plugin.php | 6 +- php/class-sync.php | 12 ++- php/media/class-global-transformations.php | 6 +- php/sync/class-push-sync.php | 6 +- php/sync/class-sync-queue.php | 12 ++- php/ui/component/class-cache-status.php | 70 ++++++++++------- php/ui/component/class-page.php | 28 ++++--- php/ui/component/class-sync.php | 12 ++- 14 files changed, 180 insertions(+), 110 deletions(-) diff --git a/php/class-admin.php b/php/class-admin.php index 67b8ef26e..e9fef78fb 100644 --- a/php/class-admin.php +++ b/php/class-admin.php @@ -522,9 +522,10 @@ public function render_notices() { } /** - * Get admin notices. + * Absorb WordPress settings errors into the plugin notice store and return + * the renderable notice setting, if any notices are pending. * - * @return Setting[] + * @return Setting|null */ public function get_admin_notices() { $setting_notices = get_settings_errors(); @@ -532,6 +533,13 @@ public function get_admin_notices() { $this->add_admin_notice( $notice['code'], $notice['message'], $notice['type'], true ); } - return $setting_notices; + $notices = $this->notices->get_value(); + if ( empty( $notices ) ) { + return null; + } + + sort( $notices ); + + return $this->init_components( $notices, self::NOTICE_SLUG ); } } diff --git a/php/class-cache.php b/php/class-cache.php index bb89c51e1..98bddd37e 100644 --- a/php/class-cache.php +++ b/php/class-cache.php @@ -10,7 +10,6 @@ use Cloudinary\Cache\Cache_Point; use Cloudinary\Cache\File_System; use Cloudinary\Component\Setup; -use Cloudinary\Settings\Setting; use WP_Error; use WP_HTTP_Response; use WP_REST_Request; @@ -720,33 +719,17 @@ public function setup() { } /** - * Adds the individual setting tabs. - */ - protected function setup_setting_tabs() { - $cache_settings = $this->get_cache_settings(); - foreach ( $cache_settings as $setting ) { - $callback = $setting->get_param( 'callback' ); - if ( is_callable( $callback ) ) { - call_user_func( $callback ); // Init the settings. - } - } - } - - /** - * Get all the Cache settings. + * Register the cache-path hooks for each cache tab. * - * @return Setting[] + * The settings structures themselves are registered declaratively via + * settings(); this only wires the per-tab cache_point path callbacks that + * run on the cloudinary_cache_init_cache_points action. */ - public function get_cache_settings() { - static $settings = array(); - if ( empty( $settings ) ) { - $main_setting = $this->settings->get_setting( 'cache_paths' ); - foreach ( $main_setting->get_settings() as $slug => $setting ) { - $settings[ $slug ] = $setting; - } - } - - return $settings; + protected function setup_setting_tabs() { + add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_plugin_cache_paths' ) ); + add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_theme_cache_paths' ) ); + add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_wp_cache_paths' ) ); + add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_content_cache_paths' ) ); } /** @@ -783,7 +766,9 @@ public function add_cache_paths( $setting, $cache_point_setting, $all_cache_sett } /** - * Add the plugin cache settings page. + * Get the plugin cache settings structure. + * + * @return array */ protected function add_plugin_settings() { @@ -821,8 +806,8 @@ protected function add_plugin_settings() { $plugins_setup, ), ); - $this->settings->create_setting( 'plugins_settings', $params, $this->settings->get_setting( 'cache_plugins' ) ); - add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_plugin_cache_paths' ) ); + + return $params; } /** @@ -834,7 +819,9 @@ public function add_plugin_cache_paths() { } /** - * Add Theme Settings page. + * Get the theme cache settings structure. + * + * @return array */ protected function add_theme_settings() { @@ -873,8 +860,7 @@ protected function add_theme_settings() { ), ); - $this->settings->create_setting( 'theme_settings', $params, $this->settings->get_setting( 'cache_themes' ) ); - add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_theme_cache_paths' ) ); + return $params; } /** @@ -886,7 +872,9 @@ public function add_theme_cache_paths() { } /** - * Add WP Settings page. + * Get the WordPress cache settings structure. + * + * @return array */ protected function add_wp_settings() { @@ -925,8 +913,7 @@ protected function add_wp_settings() { ), ); - $this->settings->create_setting( 'wordpress_settings', $params, $this->settings->get_setting( 'cache_wordpress' ) ); - add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_wp_cache_paths' ) ); + return $params; } /** @@ -938,7 +925,9 @@ public function add_wp_cache_paths() { } /** - * Add WP Settings page. + * Get the content cache settings structure. + * + * @return array */ protected function add_content_settings() { @@ -977,8 +966,7 @@ protected function add_content_settings() { ), ); - $this->settings->create_setting( 'content_settings', $params, $this->settings->get_setting( 'cache_content' ) ); - add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_content_cache_paths' ) ); + return $params; } /** @@ -1046,24 +1034,24 @@ public function settings() { 'default' => 'off', ), array( - 'slug' => 'cache_plugins', - 'type' => 'frame', - 'callback' => array( $this, 'add_plugin_settings' ), + 'slug' => 'cache_plugins', + 'type' => 'frame', + $this->add_plugin_settings(), ), array( - 'slug' => 'cache_themes', - 'type' => 'frame', - 'callback' => array( $this, 'add_theme_settings' ), + 'slug' => 'cache_themes', + 'type' => 'frame', + $this->add_theme_settings(), ), array( - 'slug' => 'cache_wordpress', - 'type' => 'frame', - 'callback' => array( $this, 'add_wp_settings' ), + 'slug' => 'cache_wordpress', + 'type' => 'frame', + $this->add_wp_settings(), ), array( - 'slug' => 'cache_content', - 'type' => 'frame', - 'callback' => array( $this, 'add_content_settings' ), + 'slug' => 'cache_content', + 'type' => 'frame', + $this->add_content_settings(), ), ), array( diff --git a/php/class-connect.php b/php/class-connect.php index 196175738..403eb7d1f 100644 --- a/php/class-connect.php +++ b/php/class-connect.php @@ -153,7 +153,11 @@ public function rest_test_connection( WP_REST_Request $request ) { $url = $request->get_param( 'cloudinary_url' ); $result = $this->test_connection( $url ); - /** @var \Cloudinary\Analytics $analytics */ + /** + * The analytics component. + * + * @var \Cloudinary\Analytics $analytics + */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $success = isset( $result['type'] ) && 'connection_success' === $result['type']; @@ -241,7 +245,11 @@ public function rest_save_wizard( WP_REST_Request $request ) { ); } - /** @var \Cloudinary\Analytics $analytics */ + /** + * The analytics component. + * + * @var \Cloudinary\Analytics $analytics + */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $analytics->track( diff --git a/php/class-dashboard.php b/php/class-dashboard.php index 31b037f63..a93f45e03 100644 --- a/php/class-dashboard.php +++ b/php/class-dashboard.php @@ -37,9 +37,17 @@ public function __construct( Plugin $plugin ) { * @return bool */ public function has_data() { - /** @var \Cloudinary\Sync $sync */ + /** + * The sync component. + * + * @var \Cloudinary\Sync $sync + */ $sync = $this->plugin->get_component( 'sync' ); - /** @var \Cloudinary\Sync\Sync_Queue $queue */ + /** + * The sync queue manager. + * + * @var \Cloudinary\Sync\Sync_Queue $queue + */ $queue = $sync->managers['queue']; $data = $queue->get_total_synced_media(); diff --git a/php/class-deactivation.php b/php/class-deactivation.php index a25408cc2..07a8cd726 100644 --- a/php/class-deactivation.php +++ b/php/class-deactivation.php @@ -575,7 +575,11 @@ protected function cleanup_options() { $this->settings->delete( $slug ); } - /** @var \Cloudinary\Sync\Sync_Queue $queue */ + /** + * The sync queue manager. + * + * @var \Cloudinary\Sync\Sync_Queue $queue + */ $queue = $this->plugin->get_component( 'sync' )->managers['queue']; $all_threads = $queue->get_threads( 'all' ); foreach ( $all_threads as $threads ) { diff --git a/php/class-delivery.php b/php/class-delivery.php index 365be1160..2745ae87b 100644 --- a/php/class-delivery.php +++ b/php/class-delivery.php @@ -2110,7 +2110,11 @@ public function catch_urls( $content, $context = 'view' ) { // Check if we are saving. If so, bail. // This is to prevent the replacement from happening in the shutdown, signaling content changes in the editor. - /** @var \Cloudinary\String_Replace $replace */ + /** + * The string replace component. + * + * @var \Cloudinary\String_Replace $replace + */ $replace = $this->plugin->get_component( 'replace' ); if ( $replace->doing_save() ) { return; diff --git a/php/class-plugin.php b/php/class-plugin.php index c1a74e678..6603bf055 100644 --- a/php/class-plugin.php +++ b/php/class-plugin.php @@ -265,7 +265,11 @@ public function set_config() { $components = array_filter( $this->components, array( $this, 'is_config_component' ) ); foreach ( $components as $slug => $component ) { - /** @var Config $component */ + /** + * Component that implements Config. + * + * @var Config $component + */ $component->get_config(); } } diff --git a/php/class-sync.php b/php/class-sync.php index b6835101d..a38e2b8ab 100644 --- a/php/class-sync.php +++ b/php/class-sync.php @@ -607,7 +607,11 @@ public function setup_sync_base_struct() { 'validate' => function ( $attachment_id ) { $valid = true; - /** @var \Cloudinary\Connect $connect */ + /** + * The connect manager. + * + * @var \Cloudinary\Connect $connect + */ $connect = $this->managers['connect']; $credentials = $connect->get_credentials(); if ( isset( $credentials['cname'] ) ) { @@ -1051,7 +1055,11 @@ public function set_signature_item( $attachment_id, $type, $value = null ) { */ public function init_background_upload() { if ( ! empty( $this->to_sync ) ) { - /** @var \Cloudinary\Sync\Sync_Queue $queue */ + /** + * The sync queue manager. + * + * @var \Cloudinary\Sync\Sync_Queue $queue + */ $queue = $this->managers['queue']; $queue->add_to_queue( $this->to_sync, 'autosync' ); $queue->start_threads( 'autosync' ); diff --git a/php/media/class-global-transformations.php b/php/media/class-global-transformations.php index 3fc092d4a..3f41e745a 100644 --- a/php/media/class-global-transformations.php +++ b/php/media/class-global-transformations.php @@ -656,7 +656,11 @@ public function transformations_column_value( $column_name, $attachment_id ) { return; } - /** @var \Cloudinary\Assets $assets */ + /** + * The assets component. + * + * @var \Cloudinary\Assets $assets + */ $assets = $this->media->plugin->get_component( 'assets' ); $item = $assets->get_asset( $attachment_id, 'dataset' ); if ( ! empty( $item['data']['public_id'] ) ) { diff --git a/php/sync/class-push-sync.php b/php/sync/class-push-sync.php index 03fc2883b..52be58e61 100644 --- a/php/sync/class-push-sync.php +++ b/php/sync/class-push-sync.php @@ -203,7 +203,11 @@ public function process_assets( $attachments = array() ) { $thread = $this->plugin->settings->get_param( 'current_sync_thread' ); // Activation funnel: first sync started (emitted once per account). - /** @var \Cloudinary\Analytics $analytics */ + /** + * The analytics component. + * + * @var \Cloudinary\Analytics $analytics + */ $analytics = $this->plugin->get_component( 'analytics' ); if ( $analytics ) { $connect = $this->plugin->get_component( 'connect' ); diff --git a/php/sync/class-sync-queue.php b/php/sync/class-sync-queue.php index b5f1a6be4..48d097c7d 100644 --- a/php/sync/class-sync-queue.php +++ b/php/sync/class-sync-queue.php @@ -137,7 +137,11 @@ public function setup( $sync ) { // Enable sync queue. if ( filter_input( INPUT_GET, 'enable-bulk', FILTER_VALIDATE_BOOLEAN ) ) { $this->bulk_sync( true ); - /** @var \Cloudinary\UI\Component\Page $page */ + /** + * The settings page component. + * + * @var \Cloudinary\UI\Component\Page $page + */ $page = $this->sync->settings->get_component(); wp_safe_redirect( $page->get_url() ); exit; @@ -145,7 +149,11 @@ public function setup( $sync ) { // Stop sync queue. if ( filter_input( INPUT_GET, 'disable-bulk', FILTER_VALIDATE_BOOLEAN ) ) { $this->bulk_sync( false ); - /** @var \Cloudinary\UI\Component\Page $page */ + /** + * The settings page component. + * + * @var \Cloudinary\UI\Component\Page $page + */ $page = $this->sync->settings->get_component(); wp_safe_redirect( $page->get_url() ); exit; diff --git a/php/ui/component/class-cache-status.php b/php/ui/component/class-cache-status.php index bb644ecb6..53f51b012 100644 --- a/php/ui/component/class-cache-status.php +++ b/php/ui/component/class-cache-status.php @@ -33,7 +33,11 @@ class Cache_Status extends Media_Status { */ protected function box_status( $struct ) { - /** @var \Cloudinary\Cache $cache */ + /** + * The cache component. + * + * @var \Cloudinary\Cache $cache + */ $cache = $this->plugin->get_component( 'cache' ); $this->cache = $cache->cache_point; $cache_points = $this->cache->get_active_cache_points(); @@ -41,36 +45,44 @@ protected function box_status( $struct ) { $title = $this->get_part( 'h3' ); $title['content'] = __( 'Assets cached to Cloudinary', 'cloudinary' ); - $struct['element'] = 'div'; - $table = array( - 'type' => 'table', - 'columns' => array( - 'cache_point' => __( 'Cache Point', 'cloudinary' ), - 'cached_items' => array( - 'content' => __( 'Cached items', 'cloudinary' ), - 'attributes' => array( - 'style' => 'text-align:center;', - ), - ), - ), - 'rows' => array(), - ); + $struct['element'] = 'div'; + $struct['children']['title'] = $title; + + // Table header. + $header_point = $this->get_part( 'th' ); + $header_point['content'] = __( 'Cache Point', 'cloudinary' ); + $header_items = $this->get_part( 'th' ); + $header_items['content'] = __( 'Cached items', 'cloudinary' ); + $header_items['attributes']['style'] = 'text-align:center;'; + $header_row = $this->get_part( 'tr' ); + $header_row['children']['cache_point'] = $header_point; + $header_row['children']['cached_items'] = $header_items; + $table_head = $this->get_part( 'thead' ); + $table_head['children']['row'] = $header_row; + + // Table body rows. + $table_body = $this->get_part( 'tbody' ); foreach ( $cache_points as $cache_point ) { - $items = $this->cache->get_cache_point_cache( $cache_point->ID ); - $table['rows'][ $cache_point->ID ] = array( - 'cache_point' => array( - 'content' => wp_basename( untrailingslashit( $cache_point->post_title ) ), - ), - 'cached_items' => array( - 'content' => ' ' . $items['total'] . ' ', - 'attributes' => array( - 'style' => 'text-align:center;', - ), - ), - ); + $items = $this->cache->get_cache_point_cache( $cache_point->ID ); + + $point_cell = $this->get_part( 'td' ); + $point_cell['content'] = wp_basename( untrailingslashit( $cache_point->post_title ) ); + + $items_cell = $this->get_part( 'td' ); + $items_cell['content'] = ' ' . $items['total'] . ' '; + $items_cell['attributes']['style'] = 'text-align:center;'; + + $row = $this->get_part( 'tr' ); + $row['children']['cache_point'] = $point_cell; + $row['children']['cached_items'] = $items_cell; + $table_body['children'][ $cache_point->ID ] = $row; } - $table_obj = $this->setting->create_setting( 'cached_status', $table, $this->setting ); - $struct['content'] = $table_obj->render_component(); + + $table = $this->get_part( 'table' ); + $table['attributes']['class'] = array( 'widefat', 'striped' ); + $table['children']['head'] = $table_head; + $table['children']['body'] = $table_body; + $struct['children']['table'] = $table; return $struct; } diff --git a/php/ui/component/class-page.php b/php/ui/component/class-page.php index 21744f04e..83e46161a 100644 --- a/php/ui/component/class-page.php +++ b/php/ui/component/class-page.php @@ -78,18 +78,20 @@ protected function notice( $struct ) { if ( Utils::get_active_setting() !== $this->setting ) { return null; } - $html = array(); - - if ( empty( $this->setting->get_admin_notices() ) ) { + /** + * The admin component. + * + * @var \Cloudinary\Admin $admin + */ + $admin = get_plugin_instance()->get_component( 'admin' ); + $notice = $admin->get_admin_notices(); + + if ( null === $notice ) { return $struct; } - foreach ( $this->setting->get_admin_notices() as $setting ) { - $html[] = $setting->get_component()->render(); - $setting->set_param( 'enabled', false ); - } $struct['element'] = null; - $struct['content'] = self::compile_html( $html ); + $struct['content'] = $notice->get_component()->render(); return $struct; } @@ -188,9 +190,13 @@ protected function get_tabs() { } // Create the link. - $link = $this->get_part( 'a' ); - $link['content'] = $setting->get_param( 'menu_title', $setting->get_param( 'page_title' ) ); - /** @var \Cloudinary\UI\Component\Page $page */ + $link = $this->get_part( 'a' ); + $link['content'] = $setting->get_param( 'menu_title', $setting->get_param( 'page_title' ) ); + /** + * The settings page component. + * + * @var \Cloudinary\UI\Component\Page $page + */ $page = $setting->get_component(); $link['attributes']['href'] = $page->get_url(); diff --git a/php/ui/component/class-sync.php b/php/ui/component/class-sync.php index 1767d620d..e6386d83d 100644 --- a/php/ui/component/class-sync.php +++ b/php/ui/component/class-sync.php @@ -76,10 +76,14 @@ protected function action( $struct ) { } $struct['element'] = 'a'; - /** @var \Cloudinary\UI\Component\Page $page */ - $page = $this->setting->find_setting( 'sync_media' )->get_component(); - $href = $page->get_url(); - $args = array(); + /** + * The settings page component. + * + * @var \Cloudinary\UI\Component\Page $page + */ + $page = $this->setting->find_setting( 'sync_media' )->get_component(); + $href = $page->get_url(); + $args = array(); if ( ! $this->setting->get_param( 'queue' )->is_enabled() ) { $args['enable-bulk'] = true;