debug_message( '' . __METHOD__ . '()' ); global $exactdn; if ( \is_object( $exactdn ) ) { $this->debug_message( 'you are doing it wrong' ); return; } $this->content_url(); // Bail out on customizer. if ( \is_customize_preview() ) { return; } $this->request_uri = \add_query_arg( '', '' ); if ( false === \strpos( $this->request_uri, 'page=ewww-image-optimizer-options' ) ) { $this->debug_message( "request uri is {$this->request_uri}" ); } else { $this->debug_message( 'request uri is EWWW IO settings' ); } if ( '/robots.txt' === $this->request_uri || '/sitemap.xml' === $this->request_uri ) { return; } \add_filter( 'exactdn_skip_page', array( $this, 'skip_page' ), 10, 2 ); /** * Allow pre-empting the parsers by page. * * @param bool Whether to skip parsing the page. * @param string The URI/path of the page. */ if ( \apply_filters( 'exactdn_skip_page', false, $this->request_uri ) ) { return; } if ( ! $this->scheme ) { $scheme = 'http'; if ( \strpos( $this->site_url, 'https://' ) !== false ) { $this->debug_message( "{$this->site_url} contains https" ); $scheme = 'https'; } elseif ( \strpos( \get_home_url(), 'https://' ) !== false ) { $this->debug_message( \get_home_url() . ' contains https' ); $scheme = 'https'; } elseif ( \strpos( $this->content_url, 'https://' ) !== false ) { $this->debug_message( $this->content_url . ' contains https' ); $scheme = 'https'; } elseif ( isset( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) { $this->debug_message( 'page requested over https' ); $scheme = 'https'; } else { $this->debug_message( 'using plain http' ); } $this->scheme = $scheme; } if ( \is_multisite() && \defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) { $this->debug_message( 'working in sub-domain mode' ); } elseif ( \is_multisite() ) { if ( \defined( 'EXACTDN_SUB_FOLDER' ) && EXACTDN_SUB_FOLDER ) { $this->sub_folder = true; $this->debug_message( 'working in sub-folder mode due to constant override' ); } elseif ( \defined( 'EXACTDN_SUB_FOLDER' ) ) { $this->debug_message( 'working in sub-domain mode due to constant override' ); } elseif ( \get_site_option( 'exactdn_sub_folder' ) ) { $this->sub_folder = true; $this->debug_message( 'working in sub-folder mode due to global option' ); } elseif ( \get_current_blog_id() > 1 ) { $network_site_url = \network_site_url(); $network_domain = $this->parse_url( $network_site_url, PHP_URL_HOST ); if ( $network_domain === $this->upload_domain ) { $this->sub_folder = true; $this->debug_message( 'working in sub-folder mode due to matching domain' ); } } } // Make sure we have an ExactDN domain to use. if ( ! $this->setup() ) { return; } // Enables scheduled health checks via wp-cron. \add_action( 'easyio_verification_checkin', array( $this, 'health_check' ) ); // Images in post content and galleries. \add_filter( 'the_content', array( $this, 'filter_the_content' ), 999999 ); // Start an output buffer before any output starts. \add_filter( $this->prefix . 'filter_page_output', array( $this, 'filter_page_output' ), 5 ); // Core image retrieval. if ( \defined( 'EIO_DISABLE_DEEP_INTEGRATION' ) && EIO_DISABLE_DEEP_INTEGRATION ) { $this->debug_message( 'deep (image_downsize) integration disabled' ); } elseif ( ! \function_exists( '\aq_resize' ) ) { \add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 ); } else { $this->debug_message( 'aq_resize detected, image_downsize filter disabled' ); } // Disable image_downsize filter during themify_get_image(). \add_action( 'themify_before_post_image', array( $this, 'disable_image_downsize' ) ); if ( \defined( 'EXACTDN_IMAGE_DOWNSIZE_SCALE' ) && EXACTDN_IMAGE_DOWNSIZE_SCALE ) { \add_action( 'exactdn_image_downsize_array', array( $this, 'image_downsize_scale' ) ); } // Check REST API requests to see if ExactDN should be running. \add_filter( 'rest_request_before_callbacks', array( $this, 'parse_restapi_maybe' ), 10, 3 ); // Check to see if the OMGF plugin is active, and suppress our font rewriting if it is. if ( ( \defined( 'OMGF_PLUGIN_FILE' ) || \defined( 'OMGF_DB_VERSION' ) ) && ! \defined( 'EASYIO_REPLACE_GOOGLE_FONTS' ) ) { \define( 'EASYIO_REPLACE_GOOGLE_FONTS', false ); } // Overrides for admin-ajax images. \add_filter( 'exactdn_admin_allow_image_downsize', array( $this, 'allow_admin_image_downsize' ), 10, 2 ); \add_filter( 'exactdn_admin_allow_image_srcset', array( $this, 'allow_admin_image_downsize' ), 10, 2 ); \add_filter( 'exactdn_admin_allow_plugin_url', array( $this, 'allow_admin_image_downsize' ), 10, 2 ); // Overrides for "pass through" images. \add_filter( 'exactdn_pre_args', array( $this, 'exactdn_remove_args' ), 10, 3 ); // Overrides for user exclusions. \add_filter( 'exactdn_skip_image', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 ); \add_filter( 'exactdn_skip_for_url', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 ); // Responsive image srcset substitution. \add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 1001, 5 ); \add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still filter. // Filter for generic use by other plugins/themes. \add_filter( 'exactdn_local_to_cdn_url', array( $this, 'plugin_get_image_url' ) ); // Filter to check for Elementor full_width layouts. \add_filter( 'elementor/frontend/builder_content_data', array( $this, 'elementor_builder_content_data' ) ); // Filter for FacetWP JSON responses. \add_filter( 'facetwp_render_output', array( $this, 'filter_facetwp_json_output' ) ); // Filter for Envira image URLs. \add_filter( 'envira_gallery_output_item_data', array( $this, 'envira_gallery_output_item_data' ) ); \add_filter( 'envira_gallery_image_src', array( $this, 'plugin_get_image_url' ) ); // Filters for BuddyBoss image URLs. \add_filter( 'bp_media_get_preview_image_url', array( $this, 'bp_media_get_preview_image_url' ), PHP_INT_MAX, 5 ); \add_filter( 'bb_video_get_thumb_url', array( $this, 'bb_video_get_thumb_url' ), PHP_INT_MAX, 5 ); \add_filter( 'bp_document_get_preview_url', array( $this, 'bp_document_get_preview_url' ), PHP_INT_MAX, 6 ); \add_filter( 'bb_video_get_symlink', array( $this, 'bb_video_get_symlink' ), PHP_INT_MAX, 4 ); // Filters for BuddyBoss URL symlinks. \add_filter( 'bb_media_do_symlink', array( $this, 'buddyboss_do_symlink' ), PHP_INT_MAX ); \add_filter( 'bb_document_do_symlink', array( $this, 'buddyboss_do_symlink' ), PHP_INT_MAX ); \add_filter( 'bb_video_do_symlink', array( $this, 'buddyboss_do_symlink' ), PHP_INT_MAX ); \add_filter( 'bb_video_create_thumb_symlinks', array( $this, 'buddyboss_do_symlink' ), PHP_INT_MAX ); // Filters for BuddyBoss media access checks. \add_filter( 'bb_media_check_default_access', '__return_true', PHP_INT_MAX ); \add_filter( 'bb_media_settings_callback_symlink_direct_access', array( $this, 'buddyboss_media_directory_allow_access' ), PHP_INT_MAX, 2 ); // Filter for NextGEN image URLs within JS. \add_filter( 'ngg_pro_lightbox_images_queue', array( $this, 'ngg_pro_lightbox_images_queue' ) ); \add_filter( 'ngg_get_image_url', array( $this, 'plugin_get_image_url' ) ); // Filter for Spotlight Social Media Feeds. \add_filter( 'spotlight/instagram/server/transform_item', array( $this, 'spotlight_instagram_response' ) ); // Filter for legacy WooCommerce API endpoints. \add_filter( 'woocommerce_api_product_response', array( $this, 'woocommerce_api_product_response' ) ); // DNS prefetching. \add_filter( 'wp_resource_hints', array( $this, 'resource_hints' ), 100, 2 ); // Get all the script/css urls and rewrite them (if enabled). if ( $this->get_option( 'exactdn_all_the_things' ) ) { \add_filter( 'style_loader_src', array( $this, 'parse_enqueue' ), 9999 ); \add_filter( 'script_loader_src', array( $this, 'parse_enqueue' ), 9999 ); } if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) ) { $this->set_option( 'exactdn_prevent_db_queries', true ); } // Improve the default content_width for Twenty Nineteen. global $content_width; if ( \function_exists( '\twentynineteen_setup' ) && 640 === (int) $content_width ) { $content_width = 932; } // Configure Autoptimize with our CDN domain. \add_filter( 'autoptimize_filter_cssjs_multidomain', array( $this, 'add_cdn_domain' ) ); if ( $this->is_as3cf_cname_active() ) { \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_as3cf_cname_active' ); return; } $upload_url_parts = $this->parse_url( $this->site_url ); if ( empty( $upload_url_parts ) ) { $this->debug_message( "could not break down URL: $this->site_url" ); return; } $stored_local_domain = $this->get_exactdn_option( 'local_domain' ); if ( empty( $stored_local_domain ) ) { $this->set_exactdn_option( 'local_domain', \base64_encode( $this->upload_domain ) ); $stored_local_domain = $this->upload_domain; } elseif ( false !== \strpos( $stored_local_domain, '.' ) ) { $this->set_exactdn_option( 'local_domain', \base64_encode( $stored_local_domain ) ); } else { $stored_local_domain = \base64_decode( $stored_local_domain ); } $this->debug_message( "saved domain is $stored_local_domain" ); $this->debug_message( "allowing images from here: $this->upload_domain" ); if ( ( false !== \strpos( $this->upload_domain, 'amazonaws.com' ) || false !== \strpos( $this->upload_domain, 'digitaloceanspaces.com' ) || false !== \strpos( $this->upload_domain, 'storage.googleapis.com' ) ) && ! empty( $upload_url_parts['path'] ) ) { $this->remove_path = \rtrim( $upload_url_parts['path'], '/' ); $this->debug_message( "removing this from urls: $this->remove_path" ); } if ( $stored_local_domain !== $this->upload_domain && ! $this->allow_image_domain( $stored_local_domain ) && \is_admin() ) { \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_domain_mismatch' ); } $this->allowed_domains[] = $this->exactdn_domain; $this->allowed_domains = \apply_filters( 'exactdn_allowed_domains', $this->allowed_domains ); $this->debug_message( 'allowed domains: ' . \implode( ',', $this->allowed_domains ) ); $this->get_allowed_paths(); $this->validate_user_exclusions(); } /** * If ExactDN is enabled, validates and configures the ExactDN domain name. */ public function setup() { $this->debug_message( '' . __METHOD__ . '()' ); // If we don't have a domain yet, go grab one. $this->plan_id = (int) $this->get_exactdn_option( 'plan_id' ); $new_site = false; if ( ! $this->get_exactdn_domain() ) { $this->debug_message( 'attempting to activate exactDN' ); $exactdn_domain = $this->activate_site(); $new_site = true; } else { $this->debug_message( 'grabbing existing exactDN domain' ); $exactdn_domain = $this->get_exactdn_domain(); } if ( ! $exactdn_domain ) { \delete_option( $this->prefix . 'exactdn' ); \delete_site_option( $this->prefix . 'exactdn' ); $this->cron_setup( false ); return false; } $verified = true; // If we have a domain, verify it. if ( $new_site ) { $verified = $this->verify_domain( $exactdn_domain ); if ( $verified ) { // When this is a new site that is verified, setup health check. $this->cron_setup(); } } if ( $verified ) { $this->exactdn_domain = $exactdn_domain; $this->debug_message( 'exactdn_domain: ' . $exactdn_domain ); $this->debug_message( 'exactdn_plan_id: ' . $this->plan_id ); return true; } \delete_option( $this->prefix . 'exactdn_domain' ); \delete_option( $this->prefix . 'exactdn_verified' ); \delete_site_option( $this->prefix . 'exactdn_domain' ); \delete_site_option( $this->prefix . 'exactdn_verified' ); $this->cron_setup( false ); return false; } /** * Setup wp_cron tasks for scheduled verification. * * @global object $wpdb * * @param bool $schedule True to add event, false to remove/unschedule it. */ public function cron_setup( $schedule = true ) { $this->debug_message( '' . __FUNCTION__ . '()' ); $event = 'easyio_verification_checkin'; // Setup scheduled optimization if the user has enabled it, and it isn't already scheduled. if ( $schedule && ! \wp_next_scheduled( $event ) ) { $this->debug_message( "scheduling $event" ); \wp_schedule_event( \time() + DAY_IN_SECONDS, \apply_filters( 'easyio_verification_schedule', 'daily', $event ), $event ); } elseif ( $schedule ) { $this->debug_message( "$event already scheduled: " . \wp_next_scheduled( $event ) ); } elseif ( \wp_next_scheduled( $event ) ) { $this->debug_message( "un-scheduling $event" ); \wp_clear_scheduled_hook( $event ); if ( ! \function_exists( '\is_plugin_active_for_network' ) && \is_multisite() ) { // Need to include the plugin library for the is_plugin_active function. require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( \is_multisite() && \is_plugin_active_for_network( \constant( \strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) ) ) { global $wpdb; $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = %d", $wpdb->siteid ), ARRAY_A ); if ( $this->is_iterable( $blogs ) ) { foreach ( $blogs as $blog ) { \switch_to_blog( $blog['blog_id'] ); \wp_clear_scheduled_hook( $event ); \restore_current_blog(); } } } } } /** * Use the Site URL to get the zone domain. */ public function activate_site() { $this->debug_message( '' . __METHOD__ . '()' ); if ( $this->is_as3cf_cname_active() ) { global $exactdn_activate_error; $exactdn_activate_error = 'as3cf_cname_active'; \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_as3cf_cname_active' ); return false; } $site_url = $this->content_url(); $url = 'http://optimize.exactlywww.com/exactdn/activate.php'; $ssl = \wp_http_supports( array( 'ssl' ) ); if ( $ssl ) { $url = \set_url_scheme( $url, 'https' ); } \add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX ); $result = \wp_remote_post( $url, array( 'timeout' => 10, 'body' => array( 'site_url' => $site_url, 'home_url' => \home_url(), ), ) ); if ( \is_wp_error( $result ) ) { $error_message = $result->get_error_message(); $this->debug_message( "exactdn activation request failed: $error_message" ); global $exactdn_activate_error; $exactdn_activate_error = $error_message; \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } elseif ( ! empty( $result['body'] ) && \strpos( $result['body'], 'domain' ) !== false ) { $response = \json_decode( $result['body'], true ); if ( ! empty( $response['domain'] ) ) { if ( false !== \strpos( $site_url, 'amazonaws.com' ) || false !== \strpos( $site_url, 'digitaloceanspaces.com' ) || false !== \strpos( $site_url, 'storage.googleapis.com' ) || $this->s3_active ) { $this->set_exactdn_option( 'verify_method', -1, false ); } if ( ! empty( $response['plan_id'] ) ) { $this->set_exactdn_option( 'plan_id', (int) $response['plan_id'] ); $this->plan_id = (int) $response['plan_id']; } if ( \get_option( 'exactdn_never_been_active' ) ) { $this->set_option( $this->prefix . 'lazy_load', true ); $this->set_option( 'exactdn_lossy', true ); $this->set_option( 'exactdn_all_the_things', true ); \delete_option( 'exactdn_never_been_active' ); } if ( \function_exists( '\envira_flush_all_cache' ) ) { \envira_flush_all_cache(); } return $this->set_exactdn_domain( $response['domain'] ); } } elseif ( ! empty( $result['body'] ) && false !== \strpos( $result['body'], 'error' ) ) { $response = \json_decode( $result['body'], true ); $error_message = $response['error']; $this->debug_message( "exactdn activation request failed: $error_message" ); global $exactdn_activate_error; $exactdn_activate_error = $error_message; \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } return false; } /** * Do a health check to verify the Easy IO domain is still good. */ public function health_check() { $this->debug_message( '' . __METHOD__ . '()' ); $this->verify_domain( $this->exactdn_domain ); $this->set_exactdn_option( 'checkin', time() - 60 ); } /** * Verify the ExactDN domain. * * @param string $domain The ExactDN domain to verify. * @return bool Whether the domain is still valid. */ public function verify_domain( $domain ) { if ( empty( $domain ) ) { return false; } $this->debug_message( '' . __METHOD__ . '()' ); // Check the time, to see how long it has been since we verified the domain. $last_checkin = (int) $this->get_exactdn_option( 'checkin' ); if ( $this->get_exactdn_option( 'verified' ) && $last_checkin > time() ) { $this->debug_message( 'not time yet: ' . $this->human_time_diff( $last_checkin ) ); return true; } $this->check_verify_method(); $this->set_exactdn_option( 'checkin', \time() + HOUR_IN_SECONDS ); // Set a default error. global $exactdn_activate_error; $exactdn_activate_error = 'zone not verified'; // Primary check sends the test URL to the API for full verification. $api_url = 'http://optimize.exactlywww.com/exactdn/verify.php'; $ssl = \wp_http_supports( array( 'ssl' ) ); if ( $ssl ) { $api_url = \set_url_scheme( $api_url, 'https' ); } if ( ! \defined( 'EXACTDN_LOCAL_DOMAIN' ) && (int) $this->get_exactdn_option( 'verify_method' ) > 0 ) { // Test with an image file that should be available on the ExactDN zone. $test_url = \plugins_url( '/images/test.png', \constant( \strtoupper( $this->prefix ) . 'PLUGIN_FILE' ) ); $local_domain = $this->parse_url( $test_url, PHP_URL_HOST ); $test_url = \str_replace( $local_domain, $domain, $test_url ); $this->debug_message( "test url is $test_url" ); \add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX ); $test_result = \wp_remote_post( $api_url, array( 'timeout' => 10, 'body' => array( 'alias' => $domain, 'url' => $test_url, 'origin' => $this->content_url(), ), ) ); if ( \is_wp_error( $test_result ) ) { $error_message = $test_result->get_error_message(); $this->debug_message( "exactdn (1) verification request failed: $error_message" ); $exactdn_activate_error = $error_message; \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } elseif ( ! empty( $test_result['body'] ) && false === \strpos( $test_result['body'], 'error' ) ) { $response = \json_decode( $test_result['body'], true ); if ( ! empty( $response['success'] ) ) { $this->debug_message( 'exactdn (real-world) verification succeeded' ); $this->set_exactdn_option( 'verified', 1, false ); $this->set_exactdn_option( 'verify_method', -1, false ); // After initial activation, use simpler API verification. \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_success' ); return true; } } elseif ( ! empty( $test_result['body'] ) ) { $response = \json_decode( $test_result['body'], true ); $error_message = $response['error']; $this->debug_message( "exactdn (1) verification request failed: $error_message" ); $exactdn_activate_error = $error_message; if ( false !== \strpos( $error_message, 'not found' ) ) { \delete_option( $this->prefix . 'exactdn_domain' ); \delete_site_option( $this->prefix . 'exactdn_domain' ); } \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } if ( ! empty( $test_result['response']['code'] ) && 200 !== (int) $test_result['response']['code'] ) { $this->debug_message( 'received response code: ' . $test_result['response']['code'] ); } \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } // Secondary test against the API db. \add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX ); $result = \wp_remote_post( $api_url, array( 'timeout' => 10, 'body' => array( 'alias' => $domain, 'origin' => $this->content_url(), ), ) ); if ( \is_wp_error( $result ) ) { $error_message = $result->get_error_message(); $this->debug_message( "exactdn verification request failed: $error_message" ); $exactdn_activate_error = $error_message; \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } elseif ( ! empty( $result['body'] ) && false === \strpos( $result['body'], 'error' ) ) { $response = \json_decode( $result['body'], true ); if ( ! empty( $response['success'] ) ) { if ( ! empty( $response['plan_id'] ) ) { $this->set_exactdn_option( 'plan_id', (int) $response['plan_id'] ); $this->plan_id = (int) $response['plan_id']; } $this->debug_message( 'exactdn verification via API succeeded' ); $this->set_exactdn_option( 'verified', 1, false ); if ( empty( $last_checkin ) ) { \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_success' ); } return true; } } elseif ( ! empty( $result['body'] ) ) { $response = \json_decode( $result['body'], true ); $error_message = $response['error']; $this->debug_message( "exactdn verification request failed: $error_message" ); $exactdn_activate_error = $error_message; if ( false !== \strpos( $error_message, 'not found' ) ) { \delete_option( $this->prefix . 'exactdn_domain' ); \delete_site_option( $this->prefix . 'exactdn_domain' ); } \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } if ( ! empty( $result['response']['code'] ) && 200 !== (int) $result['response']['code'] ) { $this->debug_message( 'received response code: ' . $result['response']['code'] ); } \add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' ); return false; } /** * Run a simulation to decide which verification method to use. */ public function check_verify_method() { if ( ! $this->get_exactdn_option( 'verify_method' ) ) { $this->debug_message( '' . __METHOD__ . '()' ); // Prelim test with a known valid image to ensure http(s) connectivity. $sim_url = 'https://optimize.exactdn.com/exactdn/testorig.jpg'; \add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX ); $sim_result = \wp_remote_get( $sim_url ); if ( \is_wp_error( $sim_result ) ) { $error_message = $sim_result->get_error_message(); $this->debug_message( "exactdn (simulated) verification request failed: $error_message" ); } elseif ( ! empty( $sim_result['body'] ) && \strlen( $sim_result['body'] ) > 300 ) { if ( 'ffd8ff' === \bin2hex( \substr( $sim_result['body'], 0, 3 ) ) ) { $this->debug_message( 'exactdn (simulated) verification succeeded' ); $this->set_exactdn_option( 'verify_method', 1, false ); return; } } else { $this->debug_message( 'exactdn (simulated) verification request failed, error unknown' ); } $this->set_exactdn_option( 'verify_method', -1, false ); } } /** * Allow external classes/functions to check the Easy IO Plan ID (to customize UI). * * @return int The currently validated plan ID (1-3). */ public function get_plan_id() { return (int) $this->plan_id; } /** * Validate the ExactDN domain. * * @param string $domain The unverified ExactDN domain. * @return string The validated ExactDN domain. */ public function sanitize_domain( $domain ) { $this->debug_message( '' . __METHOD__ . '()' ); if ( ! $domain ) { return; } $domain = \trim( $domain ); if ( \strlen( $domain ) > 80 ) { $this->debug_message( "$domain too long" ); return false; } if ( ! \preg_match( '#^[A-Za-z0-9\.\-]+$#', $domain ) ) { $this->debug_message( "$domain has bad characters" ); return false; } return $domain; } /** * Get the ExactDN domain name to use. * * @return string The ExactDN domain name for this site or network. */ public function get_exactdn_domain() { if ( \defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) { return $this->sanitize_domain( EXACTDN_DOMAIN ); } if ( \is_multisite() ) { if ( $this->sub_folder ) { return $this->sanitize_domain( \get_site_option( $this->prefix . 'exactdn_domain' ) ); } } return $this->sanitize_domain( \get_option( $this->prefix . 'exactdn_domain' ) ); } /** * Method to override the ExactDN domain at runtime, use with caution. * * @param string $domain The ExactDN domain to use instead. */ public function set_domain( $domain ) { if ( \is_string( $domain ) ) { $this->exactdn_domain = $domain; } } /** * Get the ExactDN option. * * @param string $option_name The name of the ExactDN option. * @return int The numerical value of the option. */ public function get_exactdn_option( $option_name ) { if ( \defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) { return \get_option( $this->prefix . 'exactdn_' . $option_name ); } if ( \is_multisite() ) { if ( $this->sub_folder ) { return \get_site_option( $this->prefix . 'exactdn_' . $option_name ); } } return \get_option( $this->prefix . 'exactdn_' . $option_name ); } /** * Set the ExactDN domain name to use. * * @param string $domain The ExactDN domain name for this site or network. */ public function set_exactdn_domain( $domain ) { $this->debug_message( '' . __METHOD__ . '()' ); if ( \defined( 'EXACTDN_DOMAIN' ) && $this->sanitize_domain( EXACTDN_DOMAIN ) ) { return true; } $domain = $this->sanitize_domain( $domain ); if ( ! $domain ) { return false; } if ( \is_multisite() ) { if ( $this->sub_folder ) { \update_site_option( $this->prefix . 'exactdn_domain', $domain ); return $domain; } } \update_option( $this->prefix . 'exactdn_domain', $domain ); return $domain; } /** * Set an option for ExactDN. * * @param string $option_name The name of the ExactDN option. * @param int $option_value The value to set for the ExactDN option. * @param bool $autoload Optional. Whether to load the option when WordPress starts up. */ public function set_exactdn_option( $option_name, $option_value, $autoload = null ) { $this->debug_message( '' . __METHOD__ . '()' ); if ( \defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) { return \update_option( $this->prefix . 'exactdn_' . $option_name, $option_value, $autoload ); } if ( \is_multisite() ) { if ( $this->sub_folder ) { return \update_site_option( $this->prefix . 'exactdn_' . $option_name, $option_value ); } } return \update_option( $this->prefix . 'exactdn_' . $option_name, $option_value, $autoload ); } /** * Check to see if a CNAME is configured in WP Offload Media. * * @return bool True if a CNAME is active, false otherwise. */ public function is_as3cf_cname_active() { // Find the WP Offload Media domain/path. global $as3cf; if ( \class_exists( '\Amazon_S3_And_CloudFront' ) && \is_object( $as3cf ) ) { if ( 'storage' !== $as3cf->get_setting( 'delivery-provider' ) ) { $this->debug_message( 'active delivery provider: ' . $as3cf->get_setting( 'delivery-provider' ) ); if ( $as3cf->get_setting( 'enable-delivery-domain' ) && $as3cf->get_setting( 'delivery-domain' ) ) { $delivery_domain = $as3cf->get_setting( 'delivery-domain' ); $this->debug_message( "found WOM CNAME domain: $delivery_domain" ); return true; } } } return false; } /** * Get the paths for wp-content, wp-includes, and the uploads directory. * These are used to help determine which URLs are allowed to be rewritten for Easy IO. */ public function get_allowed_paths() { $wp_content_path = \trim( $this->parse_url( \content_url(), PHP_URL_PATH ), '/' ); $wp_include_path = \trim( $this->parse_url( \includes_url(), PHP_URL_PATH ), '/' ); $this->debug_message( "wp-content path: $wp_content_path" ); $this->debug_message( "wp-includes path: $wp_include_path" ); $this->content_path = \wp_basename( $wp_content_path ); $this->include_path = \wp_basename( $wp_include_path ); $this->uploads_path = \wp_basename( $wp_content_path ); // NOTE: $this->uploads_path is not currently in use, so we'll see if anyone needs it. $uploads_info = \wp_get_upload_dir(); if ( ! empty( $uploads_info['baseurl'] ) && ! empty( $wp_content_path ) && false === \strpos( $uploads_info['baseurl'], $wp_content_path ) ) { $uploads_path = \trim( $this->parse_url( $uploads_info['baseurl'], PHP_URL_PATH ), '/' ); $this->debug_message( "wp uploads path: $uploads_path" ); $this->uploads_path = \wp_basename( $uploads_path ); } } /** * Validate the user-defined exclusions for "all the things" rewriting. */ public function validate_user_exclusions() { $user_exclusions = $this->get_option( 'exactdn_exclude' ); if ( ! empty( $user_exclusions ) ) { if ( \is_string( $user_exclusions ) ) { $user_exclusions = array( $user_exclusions ); } if ( \is_array( $user_exclusions ) ) { foreach ( $user_exclusions as $exclusion ) { if ( ! \is_string( $exclusion ) ) { continue; } $exclusion = \trim( $exclusion ); if ( 0 === \strpos( $exclusion, 'page:' ) ) { $this->user_page_exclusions[] = \str_replace( 'page:', '', $exclusion ); continue; } if ( $this->content_path && false !== \strpos( $exclusion, $this->content_path ) ) { $exclusion = \preg_replace( '#([^"\'?>]+?)?' . $this->content_path . '/#i', '', $exclusion ); } $this->user_exclusions[] = \ltrim( $exclusion, '/' ); } } } $this->user_exclusions[] = 'plugins/anti-captcha/'; } /** * Parse Elementor content data to check for full_width layouts. * * @param array $data The builder content data. * @return array $data */ public function elementor_builder_content_data( $data ) { $this->debug_message( '' . __METHOD__ . '()' ); if ( $this->is_iterable( $data ) ) { foreach ( $data as $section_data ) { if ( ! empty( $section_data['settings']['layout'] ) && 'full_width' === $section_data['settings']['layout'] ) { $this->debug_message( 'we have a winner (full_width container)!' ); $this->full_width = true; break; } } } return $data; } /** * Get $content_width, with a filter. * * @return bool|string The content width, if set. Default false. */ public function get_content_width() { $content_width = isset( $GLOBALS['content_width'] ) && \is_numeric( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 100 ? $GLOBALS['content_width'] : 1920; if ( \function_exists( '\twentynineteen_setup' ) && 640 === (int) $content_width ) { $content_width = 932; } if ( \defined( 'EXACTDN_CONTENT_WIDTH' ) && EXACTDN_CONTENT_WIDTH ) { $content_width = EXACTDN_CONTENT_WIDTH; } elseif ( $this->full_width ) { $content_width = 1920; } /** * Filter the Content Width value. * * @param string $content_width Content Width value. */ return (int) \apply_filters( 'exactdn_content_width', $content_width ); } /** * Get width within an ExactDN url. * * @param string $url The ExactDN url to parse. * @return string The width, if found. */ public function get_exactdn_width_from_url( $url ) { $url_args = $this->parse_url( $url, PHP_URL_QUERY ); if ( ! $url_args ) { return ''; } $args = \explode( '&', $url_args ); foreach ( $args as $arg ) { if ( \preg_match( '#w=(\d+)#', $arg, $width_match ) ) { return $width_match[1]; } if ( \preg_match( '#resize=(\d+)#', $arg, $width_match ) ) { return $width_match[1]; } if ( \preg_match( '#fit=(\d+)#', $arg, $width_match ) ) { return $width_match[1]; } } return ''; } /** * Identify images in page content, and if images are local (uploaded to the current site), pass through ExactDN. * * @param string $content The page/post content. * @return string The content with ExactDN image urls. */ public function filter_page_output( $content ) { $this->debug_message( '' . __METHOD__ . '()' ); $this->filtering_the_page = true; $content = $this->filter_the_content( $content ); /** * Allow parsing the full page content after ExactDN is finished with it. * * @param string $content The fully-parsed HTML code of the page. */ $content = \apply_filters( 'exactdn_the_page', $content ); $this->filtering_the_page = false; $this->debug_message( "parsing page took $this->elapsed_time seconds" ); return $content; } /** * Identify images in the content, and if images are local (uploaded to the current site), pass through ExactDN. * * @param string $content The page/post content. * @return string The content with ExactDN image urls. */ public function filter_the_content( $content ) { if ( $this->is_json( $content ) ) { return $content; } if ( \apply_filters( 'exactdn_skip_page', false, $this->request_uri ) ) { return $content; } $started = \microtime( true ); $this->debug_message( '' . __METHOD__ . '()' ); $images = $this->get_images_from_html( $content, true ); if ( ! empty( $images ) ) { $this->debug_message( 'we have images to parse' ); if ( false !== \strpos( $content, 'elementor-section-full_width' ) ) { $this->debug_message( 'elementor full_width section found!' ); $this->full_width = true; } $content_width = false; if ( ! $this->filtering_the_page ) { $this->filtering_the_content = true; $this->debug_message( 'filtering the content' ); $content_width = $this->get_content_width(); $this->debug_message( "configured/filtered content_width: $content_width" ); } $resize_existing = \defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING; $image_sizes = $this->image_sizes(); foreach ( $images[0] as $index => $tag ) { // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained. $transform = 'resize'; // Start with a clean slate each time. $attachment_id = false; $exactdn_url = false; $width = false; $lazy = false; $srcset_fill = false; // Flag if we need to munge a fullsize URL. $fullsize_url = false; // Identify image source. $src = \trim( $images['img_url'][ $index ] ); $src_orig = $images['img_url'][ $index ]; // Don't trim, because we'll use it for search/replacement later. if ( \is_string( $src ) ) { $this->debug_message( $src ); } else { $this->debug_message( '$src is not a string?' ); } /** * Allow specific images to be skipped by ExactDN. * * @param bool false Should ExactDN ignore this image. Default false. * @param string $src Image URL. * @param string $tag Image HTML Tag. */ if ( \apply_filters( 'exactdn_skip_image', false, $src, $tag ) ) { continue; } $this->debug_message( 'made it passed the filters' ); // Log 0-size Pinterest schema images. if ( \strpos( $tag, 'data-pin-description=' ) && \strpos( $tag, 'width="0" height="0"' ) ) { $this->debug_message( 'data-pin/Pinterest image' ); } // Pre-empt srcset fill if the surrounding link has a background image or if there is a data-desktop attribute indicating a potential slider. if ( \strpos( $tag, 'background-image:' ) || \strpos( $tag, 'data-desktop=' ) ) { $srcset_fill = false; } /** * Documented in generate_url, in this case used to detect images that should bypass srcset fill. * * @param array|string $args Array of ExactDN arguments. * @param string $image_url Image URL. * @param string|null $scheme Image scheme. Default to null. */ $args = \apply_filters( 'exactdn_pre_args', array( 'test' => 'lazy-test' ), $src, null ); if ( empty( $args ) ) { $srcset_fill = false; } // Support Lazy Load plugins. // Don't modify $tag yet as we need unmodified version later. $lazy_load_src = \trim( $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-src' ) ); if ( $lazy_load_src ) { $placeholder_src = $src; $placeholder_src_orig = $src; $src = $lazy_load_src; $src_orig = $lazy_load_src; $this->srcset_attr = 'data-lazy-srcset'; $lazy = true; $srcset_fill = true; } // Must be a legacy Jetpack thing as far as I can tell, no matches found in any currently installed plugins. $lazy_load_src = \trim( $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-original' ) ); if ( ! $lazy && $lazy_load_src ) { $placeholder_src = $src; $placeholder_src_orig = $src; $src = $lazy_load_src; $src_orig = $lazy_load_src; $lazy = true; } if ( ! $lazy && \strpos( $images['img_tag'][ $index ], 'a3-lazy-load/assets/images/lazy_placeholder' ) ) { $lazy_load_src = \trim( $this->get_attribute( $images['img_tag'][ $index ], 'data-src' ) ); } if ( ! $lazy && \strpos( $images['img_tag'][ $index ], ' data-src=' ) && \strpos( $images['img_tag'][ $index ], 'lazyload' ) && ( \strpos( $images['img_tag'][ $index ], 'data:image/gif' ) || \strpos( $images['img_tag'][ $index ], 'data:image/svg' ) ) ) { $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-src' ); $this->debug_message( "found eio ll src: $lazy_load_src" ); } if ( ! $lazy && $lazy_load_src ) { $placeholder_src = $src; $placeholder_src_orig = $src; $src = $lazy_load_src; $src_orig = $lazy_load_src; $this->srcset_attr = 'data-srcset'; $lazy = true; $srcset_fill = true; } if ( ! $lazy && \strpos( $images['img_tag'][ $index ], 'revslider/admin/assets/images/dummy' ) ) { $lazy_load_src = \trim( $this->get_attribute( $images['img_tag'][ $index ], 'data-lazyload' ) ); } if ( ! $lazy && $lazy_load_src ) { $placeholder_src = $src; $placeholder_src_orig = $src; $src = $lazy_load_src; $src_orig = $lazy_load_src; $lazy = true; } if ( $lazy ) { $this->debug_message( 'handling lazy image' ); } $is_relative = false; // Check for relative URLs that start with a slash. if ( '/' === \substr( $src, 0, 1 ) && '/' !== \substr( $src, 1, 1 ) && false === \strpos( $this->upload_domain, 'amazonaws.com' ) && false === \strpos( $this->upload_domain, 'digitaloceanspaces.com' ) && false === \strpos( $this->upload_domain, 'storage.googleapis.com' ) ) { $src = '//' . $this->upload_domain . $src; $is_relative = true; } // Check if image URL should be used with ExactDN. if ( $this->validate_image_url( $src ) ) { $this->debug_message( 'url validated' ); // Find the width and height attributes. $width = $this->get_attribute( $images['img_tag'][ $index ], 'width' ); $height = $this->get_attribute( $images['img_tag'][ $index ], 'height' ); // Can't pass both a relative width and height, so unset the dimensions in favor of not breaking the horizontal layout. if ( false !== \strpos( $width, '%' ) ) { $width = false; } if ( false !== \strpos( $height, '%' ) ) { $height = false; } // Falsify them if empty. $width = $width && \is_numeric( $width ) ? $width : false; $height = $height && \is_numeric( $height ) ? $height : false; // Get width/height attributes from the URL/file if they are missing. $insert_dimensions = false; if ( \apply_filters( 'eio_add_missing_width_height_attrs', $this->get_option( $this->prefix . 'add_missing_dims' ) ) && ( empty( $width ) || empty( $height ) ) ) { $this->debug_message( 'missing width attr or height attr' ); $insert_dimensions = true; } // See if there is a width/height set in the style attribute. $style_width = $this->get_img_style_width( $images['img_tag'][ $index ] ); $style_height = $this->get_img_style_height( $images['img_tag'][ $index ] ); if ( $style_width && $style_height ) { $width = \min( $style_width, $width ); $height = \min( $style_height, $height ); } elseif ( $style_width && $style_width < $width ) { $width = $style_width; $transform = 'fit'; } elseif ( $style_height && $style_height < $height ) { $height = $style_height; $transform = 'fit'; } // Detect WP registered image size from HTML class. if ( \preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) { $size = \array_pop( $size ); $this->debug_message( "detected $size" ); if ( false === $width && false === $height && 'full' !== $size && \array_key_exists( $size, $image_sizes ) ) { $width = (int) $image_sizes[ $size ]['width']; $height = (int) $image_sizes[ $size ]['height']; $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit'; } } else { unset( $size ); } list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $src ); if ( false === $width && false === $height ) { $width = $filename_width; $height = $filename_height; } // WP Attachment ID, if uploaded to this site. $attachment_id = $this->get_attribute( $images['img_tag'][ $index ], 'data-id' ); if ( empty( $attachment_id ) ) { $this->debug_message( 'data-id not found, looking for wp-image-x in class' ); \preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ); } if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && empty( $attachment_id ) ) { $this->debug_message( 'looking for attachment id' ); $attachment_id = attachment_url_to_postid( $src ); } if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) { if ( \is_array( $attachment_id ) ) { $attachment_id = \intval( \array_pop( $attachment_id ) ); } $this->debug_message( "using attachment id ($attachment_id) to get source image" ); if ( $attachment_id ) { $this->debug_message( "detected attachment $attachment_id" ); $attachment = get_post( $attachment_id ); // Basic check on returned post object. if ( \is_object( $attachment ) && ! \is_wp_error( $attachment ) && 'attachment' === $attachment->post_type ) { $src_per_wp = \wp_get_attachment_image_src( $attachment_id, 'full' ); if ( $src_per_wp && \is_array( $src_per_wp ) ) { $this->debug_message( "src retrieved from db: {$src_per_wp[0]}, checking for match" ); $fullsize_url_path = $this->parse_url( $src_per_wp[0], PHP_URL_PATH ); if ( \is_null( $fullsize_url_path ) ) { $src_per_wp = false; } elseif ( $fullsize_url_path ) { $fullsize_url_basename = \pathinfo( $fullsize_url_path, PATHINFO_FILENAME ); $this->debug_message( "looking for $fullsize_url_basename in $src" ); if ( \strpos( \wp_basename( $src ), $fullsize_url_basename ) === false ) { $this->debug_message( 'fullsize url does not match' ); $src_per_wp = false; } } else { $src_per_wp = false; } } if ( $src_per_wp && $this->validate_image_url( $src_per_wp[0] ) ) { $this->debug_message( "detected $width filenamew $filename_width" ); if ( $resize_existing || ( $width && (int) $filename_width !== (int) $width ) ) { $this->debug_message( 'resizing existing or width does not match' ); $src = $src_per_wp[0]; } $fullsize_url = true; // Prevent image distortion if a detected dimension exceeds the image's natural dimensions. if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) { $width = false === $width ? false : min( $width, $src_per_wp[1] ); $height = false === $height ? false : min( $height, $src_per_wp[2] ); $this->debug_message( "constrained to attachment dims, w=$width and h=$height" ); } // If no width and height are found, max out at source image's natural dimensions. // Otherwise, respect registered image sizes' cropping setting. if ( false === $width && false === $height ) { $width = $src_per_wp[1]; $height = $src_per_wp[2]; $transform = 'fit'; $this->debug_message( "no dims, using attachment dims, w=$width and h=$height" ); } elseif ( isset( $size ) && \array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) { $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit'; $this->debug_message( 'attachment size set to crop' ); } } } else { unset( $attachment_id ); unset( $attachment ); } } } $constrain_width = (int) $content_width; if ( ! empty( $images['figure_class'][ $index ] ) && false !== \strpos( $images['figure_class'][ $index ], 'alignfull' ) && \current_theme_supports( 'align-wide' ) ) { $constrain_width = (int) \apply_filters( 'exactdn_full_align_image_width', \max( 1920, $content_width ) ); } elseif ( ! empty( $images['figure_class'][ $index ] ) && false !== \strpos( $images['figure_class'][ $index ], 'alignwide' ) && \current_theme_supports( 'align-wide' ) ) { $constrain_width = (int) \apply_filters( 'exactdn_wide_align_image_width', \max( 1500, $content_width ) ); } if ( ! empty( $images['div_class'][ $index ] ) && false !== \strpos( $images['div_class'][ $index ], 'alignfull' ) && \current_theme_supports( 'align-wide' ) ) { $constrain_width = (int) \apply_filters( 'exactdn_full_align_image_width', \max( 1920, $content_width ) ); } elseif ( ! empty( $images['div_class'][ $index ] ) && false !== \strpos( $images['div_class'][ $index ], 'alignwide' ) && \current_theme_supports( 'align-wide' ) ) { $constrain_width = (int) \apply_filters( 'exactdn_wide_align_image_width', \max( 1500, $content_width ) ); } // If width is available, constrain to $content_width. if ( false !== $width && false === \strpos( $width, '%' ) && \is_numeric( $constrain_width ) ) { if ( $width > $constrain_width && false !== $height && false === \strpos( $height, '%' ) ) { $this->debug_message( 'constraining to content width' ); $height = \round( ( $constrain_width * $height ) / $width ); $width = $constrain_width; } elseif ( $width > $constrain_width ) { $this->debug_message( 'constraining to content width' ); $width = $constrain_width; } } // Set a width if none is found and $content_width is available. // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing. if ( false === $width && \is_numeric( $constrain_width ) ) { $width = (int) $constrain_width; if ( false !== $height ) { $transform = 'fit'; } } // Override fit by class/id/attr 'img-crop'. if ( 'fit' === $transform && \strpos( $images['img_tag'][ $index ], 'img-crop' ) ) { $transform = 'resize'; } // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation. if ( ! $fullsize_url && \preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . \implode( '|', $this->extensions ) . '){1}$#i', \wp_basename( $src ), $filename ) ) { $fullsize_url = true; } // Build array of ExactDN args and expose to filter before passing to ExactDN URL function. $args = array(); if ( false !== $width && false !== $height && false === \strpos( $width, '%' ) && false === \strpos( $height, '%' ) ) { $args[ $transform ] = $width . ',' . $height; } elseif ( false !== $width ) { $args['w'] = $width; } elseif ( false !== $height ) { $args['h'] = $height; } if ( ! $resize_existing && ( ! $width || (int) $filename_width === (int) $width ) ) { $this->debug_message( 'preventing resize' ); $args = array(); } elseif ( ! $fullsize_url ) { // Build URL, first maybe removing WP's resized string so we pass the original image to ExactDN (for higher quality). $src = $this->strip_image_dimensions_maybe( $src ); } if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) { $this->debug_message( 'using attachment id to check smart crop' ); $args = $this->maybe_smart_crop( $args, $attachment_id ); } /** * Filter the array of ExactDN arguments added to an image. * By default, only includes width and height values. * * @param array $args Array of ExactDN Arguments. * @param array $args { * Array of image details. * * @type $tag Image tag (Image HTML output). * @type $src Image URL. * @type $src_orig Original Image URL. * @type $width Image width. * @type $height Image height. * } */ $args = \apply_filters( 'exactdn_post_image_args', $args, \compact( 'tag', 'src', 'src_orig', 'width', 'height' ) ); $this->debug_message( "width $width" ); $this->debug_message( "height $height" ); $this->debug_message( "transform $transform" ); $exactdn_url = $this->generate_url( $src, $args ); $this->debug_message( "new url $exactdn_url" ); // Modify image tag if ExactDN function provides a URL // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version. if ( $src !== $exactdn_url ) { $new_tag = $tag; // If present, replace the link href with an ExactDN URL for the full-size image. if ( \defined( 'EIO_PRESERVE_LINKED_IMAGES' ) && EIO_PRESERVE_LINKED_IMAGES && ! empty( $images['link_url'][ $index ] ) && $this->validate_image_url( $images['link_url'][ $index ] ) ) { $new_tag = \preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . $this->generate_url( $images['link_url'][ $index ], array( 'lossy' => 0, 'strip' => 'none', ) ) . '\2', $new_tag, 1 ); } elseif ( ! empty( $images['link_url'][ $index ] ) && $this->validate_image_url( $images['link_url'][ $index ] ) ) { $new_tag = \preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . $this->generate_url( $images['link_url'][ $index ], array( 'w' => 2560 ) ) . '\2', $new_tag, 1 ); } $srcset_url = false; // Insert new image src into the srcset as well, if we have a width. if ( false !== $width && false === \strpos( $width, '%' ) && $width ) { $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, '; } $srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr ); if ( $srcset_attr ) { $new_srcset_attr = $srcset_attr; if ( $srcset_url && false === \strpos( $srcset_attr, ' ' . (int) $width . 'w' ) && ! \preg_match( '/\s(1|2|3)x/', $srcset_attr ) ) { $this->debug_message( 'src not in srcset, adding' ); $new_srcset_attr = $srcset_url . $new_srcset_attr; } $new_srcset_attr = $this->srcset_replace( $new_srcset_attr ); if ( $new_srcset_attr && $new_srcset_attr !== $srcset_attr ) { $this->set_attribute( $new_tag, $this->srcset_attr, $new_srcset_attr, true ); } } // Check if content width pushed the respimg sizes attribute too far down. if ( ! empty( $constrain_width ) && (int) $constrain_width !== (int) $content_width ) { $sizes_attr = $this->get_attribute( $new_tag, 'sizes' ); $new_sizes_attr = \str_replace( ' ' . $content_width . 'px', ' ' . $constrain_width . 'px', $sizes_attr ); if ( $sizes_attr !== $new_sizes_attr ) { $new_tag = \str_replace( $sizes_attr, $new_sizes_attr, $new_tag ); } } // Cleanup ExactDN URL. $exactdn_url = \str_replace( '&', '&', \esc_url( \trim( $exactdn_url ) ) ); // Supplant the original source value with our ExactDN URL. $this->debug_message( "replacing $src_orig with $exactdn_url" ); if ( $is_relative ) { $this->set_attribute( $new_tag, 'src', $exactdn_url, true ); } else { $new_tag = \str_replace( $src_orig, $exactdn_url, $new_tag ); } // If Lazy Load is in use, pass placeholder image through ExactDN. if ( isset( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) { $placeholder_src = $this->generate_url( $placeholder_src ); if ( $placeholder_src !== $placeholder_src_orig ) { $new_tag = \str_replace( $placeholder_src_orig, \str_replace( '&', '&', \esc_url( \trim( $placeholder_src ) ) ), $new_tag ); } unset( $placeholder_src ); } if ( $insert_dimensions && $filename_width > 0 && $filename_height > 0 ) { $this->debug_message( "filling in width = $filename_width and height = $filename_height" ); $this->set_attribute( $new_tag, 'width', $filename_width, true ); $this->set_attribute( $new_tag, 'height', $filename_height, true ); } // Replace original tag with modified version. $content = \str_replace( $tag, $new_tag, $content ); } } elseif ( ! $lazy && $this->validate_image_url( $src, true ) ) { $this->debug_message( "found a potential exactdn src url to wrangle, and maybe insert into srcset: $src" ); $args = array(); $new_tag = $tag; $width = $this->get_attribute( $images['img_tag'][ $index ], 'width' ); $height = $this->get_attribute( $images['img_tag'][ $index ], 'height' ); // Making sure the width/height are numeric. if ( false === \strpos( $new_tag, 'srcset' ) && \strpos( $src, '?' ) && (int) $width > 2 && (int) $height > 2 ) { $url_params = \urldecode( $this->parse_url( $src, PHP_URL_QUERY ) ); if ( $url_params && false !== \strpos( $url_params, 'resize=' ) ) { $this->debug_message( 'existing resize param' ); } elseif ( $url_params && false !== \strpos( $url_params, 'fit=' ) ) { $this->debug_message( 'existing fit param' ); } elseif ( $url_params && false === \strpos( $url_params, 'w=' ) && false === \strpos( $url_params, 'h=' ) && false === \strpos( $url_params, 'crop=' ) ) { $this->debug_message( 'no size params, so add the width/height' ); $args = array(); $transform = 'fit'; // Or optionally as crop/resize. if ( \strpos( $new_tag, 'img-crop' ) ) { $transform = 'resize'; } $args[ $transform ] = $width . ',' . $height; } } if ( $args ) { $args = \apply_filters( 'exactdn_post_image_args', $args, \compact( 'new_tag', 'src', 'src', 'width', 'height' ) ); $new_src = $this->generate_url( $src, $args ); if ( $new_src && $src !== $new_src ) { $new_tag = \str_replace( $src, $new_src, $new_tag ); } } $srcset_url = false; if ( $width ) { $this->debug_message( 'found the width' ); // Insert new image src into the srcset as well, if we have a width. if ( false !== $width && false === \strpos( $width, '%' ) && false !== \strpos( $src, $width ) && false !== \strpos( $src, $this->exactdn_domain ) ) { $exactdn_url = $src; $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, '; } } $srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr ); if ( $srcset_attr ) { $new_srcset_attr = $srcset_attr; if ( $srcset_url && false === \strpos( $srcset_attr, ' ' . (int) $width . 'w' ) && ! \preg_match( '/\s(1|2|3)x/', $srcset_attr ) ) { $this->debug_message( 'src not in srcset, adding' ); $new_srcset_attr = $srcset_url . $new_srcset_attr; } $new_srcset_attr = $this->srcset_replace( $new_srcset_attr ); if ( $new_srcset_attr && $new_srcset_attr !== $srcset_attr ) { $this->set_attribute( $new_tag, $this->srcset_attr, $new_srcset_attr, true ); } } if ( $new_tag && $new_tag !== $tag ) { // Replace original tag with modified version. $content = \str_replace( $tag, $new_tag, $content ); } } elseif ( $lazy && ! empty( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) { $this->debug_message( "parsing $placeholder_src for $src" ); $new_tag = $tag; // If Lazy Load is in use, pass placeholder image through ExactDN. $placeholder_src = $this->generate_url( $placeholder_src ); if ( $placeholder_src !== $placeholder_src_orig ) { $new_tag = \str_replace( $placeholder_src_orig, \str_replace( '&', '&', \esc_url( \trim( $placeholder_src ) ) ), $new_tag ); // Replace original tag with modified version. $content = \str_replace( $tag, $new_tag, $content ); } unset( $placeholder_src ); } else { $this->debug_message( "unparsed $src, srcset fill coming up" ); } // End if(). // At this point, we discard the original src in favor of the ExactDN url. if ( ! empty( $exactdn_url ) ) { $src = $exactdn_url; } // This is disabled by default, not much reason to do this with the lazy loader auto-scaling. if ( ! \is_feed() && $srcset_fill && \defined( 'EIO_SRCSET_FILL' ) && EIO_SRCSET_FILL && false !== \strpos( $src, $this->exactdn_domain ) ) { if ( ! $this->get_attribute( $images['img_tag'][ $index ], $this->srcset_attr ) && ! $this->get_attribute( $images['img_tag'][ $index ], 'sizes' ) ) { $this->debug_message( "srcset filling with $src" ); $zoom = false; // If $width is empty, we'll search the url for a width param, then we try searching the img element, with fall back to the filename. if ( empty( $width ) || ! \is_numeric( $width ) ) { // This only searches for w, resize, or fit flags, others are ignored. $width = $this->get_exactdn_width_from_url( $src ); if ( $width ) { $zoom = true; } } $width_attr = $this->get_attribute( $images['img_tag'][ $index ], 'width' ); // Get width/height attributes from the URL/file if they are missing. $insert_dimensions = false; if ( \apply_filters( 'eio_add_missing_width_height_attrs', $this->get_option( $this->prefix . 'add_missing_dims' ) ) && empty( $width_attr ) ) { $this->debug_message( 'missing width attr or height attr' ); $insert_dimensions = true; } if ( empty( $width ) || ! \is_numeric( $width ) ) { $width = $width_attr; } list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $src ); if ( empty( $width ) || ! \is_numeric( $width ) ) { $width = $filename_width; } if ( empty( $width ) || ! \is_numeric( $width ) ) { $width = $this->get_attribute( $images['img_tag'][ $index ], 'data-actual-width' ); } if ( false !== \strpos( $src, 'crop=' ) || false !== \strpos( $src, '&h=' ) || false !== \strpos( $src, '?h=' ) ) { $width = false; } $new_tag = $images['img_tag'][ $index ]; // Then add a srcset and sizes. if ( $width ) { $srcset = $this->generate_image_srcset( $src, $width, $zoom, $filename_width ); if ( $srcset ) { $this->set_attribute( $new_tag, $this->srcset_attr, $srcset ); $this->set_attribute( $new_tag, 'sizes', \sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width ) ); } } if ( $insert_dimensions && $filename_width > 0 && $filename_height > 0 ) { $this->debug_message( "filling in width = $filename_width and height = $filename_height" ); $this->set_attribute( $new_tag, 'width', $filename_width, true ); $this->set_attribute( $new_tag, 'height', $filename_height, true ); } if ( $new_tag !== $images['img_tag'][ $index ] ) { // Replace original tag with modified version. $content = \str_replace( $images['img_tag'][ $index ], $new_tag, $content ); } } // End if() -- no srcset or sizes attributes found. } // End if() -- not a feed and EIO_SRCSET_FILL enabled. } // End foreach() -- of all images found in the page. } // End if() -- we found images in the page at all. // Process elements in the page for image URLs. $content = $this->filter_image_links( $content ); // Process elements in the page. $content = $this->filter_picture_images( $content ); // Process