// Clean the PathInfo of the IMG SRC. // IMPORTANT: This function STRIPS THE UPLOAD FOLDER if it's found // REASON: The reason is that on some installs the uploads folder is linked to a different "unlogical" physical folder // http://wordpress.org/support/topic/cant-find-retina-file-with-custom-uploads-constant?replies=3#post-5078892 function get_pathinfo_from_image_src( $image_src ) { $uploads_url = trailingslashit( $this->get_upload_root_url() ); if ( strpos( $image_src, $uploads_url ) === 0 ) return ltrim( substr( $image_src, strlen( $uploads_url ) ), '/'); else if ( strpos( $image_src, wp_make_link_relative( $uploads_url ) ) === 0 ) return ltrim( substr( $image_src, strlen( wp_make_link_relative( $uploads_url ) ) ), '/'); $img_info = parse_url( $image_src ); return ltrim( $img_info['path'], '/' ); } // Rename this filename with CDN function cdn_this( $url, $mediaId = null ) { $cdn_domain = ""; $cdn_params = array(); $wr2x_easyio_domain = get_option( 'wr2x_easyio_domain', '' ); $wr2x_cdn_domain = get_option( 'wr2x_cdn_domain', '' ); // CDN Domain if ( !empty( $wr2x_easyio_domain ) ) { $cdn_domain = "${wr2x_easyio_domain}"; if ( get_option( 'wr2x_easyio_lossless', false ) ) { $cdn_params['lossy'] = 0; } } else if ( !empty( $wr2x_cdn_domain ) ) { $cdn_domain = $wr2x_cdn_domain; } else { return $url; } // Version if ( !empty( $mediaId ) ) { $version = get_post_meta( $mediaId, '_media_version', true ); if ( $version ) { $cdn_params['version'] = $version; } } $home_url = parse_url( home_url() ); $uploads_url = trailingslashit( $this->get_upload_root_url() ); $uploads_url_cdn = str_replace( $home_url['host'], $cdn_domain, $uploads_url ); // Perform additional CDN check (Issue #1631 by Martin) if ( strpos( $url, $uploads_url_cdn ) === 0 ) { $this->log( "URL already has CDN: $url" ); return $url; } $this->log( "URL before CDN: $url" ); $queryUrl = !empty( $cdn_params ) ? ( '?' . http_build_query( $cdn_params ) ) : ''; $site_url = preg_replace( '#^https?://#', '', rtrim( get_site_url(), '/' ) ); $new_url = str_replace( $site_url, $cdn_domain, $url ) . $queryUrl; $this->log( "URL with CDN: $new_url" ); return $new_url; } // function admin_menu() { // add_options_page( 'Retina', 'Retina', 'manage_options', 'wr2x_settings', 'wr2x_settings_page' ); // } function get_image_sizes( $output_type = OBJECT ) { $sizes = array(); global $_wp_additional_image_sizes; foreach ( get_intermediate_image_sizes() as $s ) { // Get the information $crop = false; if ( isset( $_wp_additional_image_sizes[$s] ) ) { $width = intval( $_wp_additional_image_sizes[$s]['width'] ); $height = intval( $_wp_additional_image_sizes[$s]['height'] ); $crop = $_wp_additional_image_sizes[$s]['crop']; } else { $width = intval( get_option( $s . '_size_w' ) ); $height = intval( get_option( $s . '_size_h' ) ); $crop = intval( get_option( $s . '_crop' ) ); } // Retina shouldn't be active if the size is disabled $enabled = !in_array( $s, $this->disabled_sizes ); $retina = in_array( $s, $this->retina_sizes ); if ( !$enabled && $retina ) { $this->retina_sizes = array_diff( $this->retina_sizes, array( $s ) ); update_option( 'wr2x_retina_sizes', $this->retina_sizes ); $retina = false; } $sizes[$s] = array( 'width' => $width, 'height' => $height, 'crop' => $crop, 'enabled' => $enabled, 'retina' => $retina, 'name' => $s, 'shortname' => Meow_WR2X_Core::size_shortname( $s ) ); } // Let's re-add the disabled sizes $disabled_to_add = array(); foreach ( $this->disabled_sizes as $size ) { $retina = in_array( $size, $this->retina_sizes ); if ( $retina ) { $retina_sizes = array_diff( $this->retina_sizes, array( $size ) ); update_option( 'wr2x_retina_sizes', $retina_sizes ); } if ( !array_key_exists( $size, $sizes ) ) { $disabled_to_add[$size] = array( 'enabled' => false, 'retina' => false, 'name' => $size, 'shortname' => Meow_WR2X_Core::size_shortname( $size ) ); } } usort( $disabled_to_add, array( $this, 'sizes_sort_func' ) ); $sizes = array_merge( $sizes, $disabled_to_add ); // if ( get_option( 'wr2x_disable_medium_large' ) ) // unset( $sizes['medium_large'] ); if ( $output_type === ARRAY_A ) { return array_values( $sizes ); } return $sizes; } function sizes_sort_func( $a, $b ) { return strncmp( $a['shortname'], $b['shortname'], 10 ); } function get_active_image_sizes() { $sizes = $this->get_image_sizes(); $active_sizes = array(); // $ignore = get_option( "wr2x_ignore_sizes", array() ); // if ( empty( $ignore ) ) // $ignore = array(); // $ignore = array_keys( $ignore ); foreach ( $sizes as $name => $attr ) { $validSize = !empty( $attr['width'] ) || !empty( $attr['height'] ); if ( $validSize && $attr['retina'] ) { $active_sizes[$name] = $attr; } } return $active_sizes; } function is_wpml_installed() { return function_exists( 'icl_object_id' ) && !class_exists( 'Polylang' ); } // SQL Query if WPML with an AND to check if the p.ID (p is attachment) is indeed an original // That is to limit the SQL that queries all the attachments function create_sql_if_wpml_original() { $whereIsOriginal = ""; if ( $this->is_wpml_installed() ) { global $wpdb; global $sitepress; $tbl_wpml = $wpdb->prefix . "icl_translations"; $language = $sitepress->get_default_language(); $whereIsOriginal = " AND p.ID IN (SELECT element_id FROM $tbl_wpml WHERE element_type = 'post_attachment' AND language_code = '$language') "; } return $whereIsOriginal; } function increase_media_version( $mediaId ) { $version = get_post_meta( $mediaId, '_media_version', true ); $version = $version ? intval( $version ) + 1 : 2; update_post_meta( $mediaId, '_media_version', $version ); return $version; } function is_debug() { static $debug = -1; if ( $debug == -1 ) { $debug = get_option( "wr2x_debug" ); } return !!$debug; } function log( $data, $isExtra = false ) { if ( !$this->is_debug() ) return; $fh = fopen( trailingslashit( dirname(__FILE__) ) . 'wp-retina-2x.log', 'a' ); $date = date( "Y-m-d H:i:s" ); fwrite( $fh, "$date: {$data}\n" ); fclose( $fh ); } // Based on http://wordpress.stackexchange.com/questions/6645/turn-a-url-into-an-attachment-post-id function get_attachment_id( $file ) { $query = array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attached_file', 'value' => ltrim( $file, '/' ) ) ) ); $posts = get_posts( $query ); foreach( $posts as $post ) return $post->ID; return false; } // Return the retina extension followed by a dot function retina_extension() { return '@2x.'; } function is_image_meta( $meta ) { if ( !isset( $meta ) ) return false; if ( !isset( $meta['sizes'] ) ) return false; if ( !isset( $meta['width'], $meta['height'] ) ) return false; return true; } // Adds the shortname to the metadata function postprocess_metadata( $metadata ) { if ( !empty( $metadata['sizes'] ) ) { foreach ( $metadata['sizes'] as $key => $value ) { $metadata['sizes'][$key]['shortname'] = self::size_shortname( $key ); } } return $metadata; } function retina_info( $id, $output_type = OBJECT ) { $result = array(); $meta = wp_get_attachment_metadata( $id ); if ( !$this->is_image_meta( $meta ) ) return $result; $original_width = $meta['width']; $original_height = $meta['height']; $sizes = $this->get_image_sizes(); $originalfile = get_attached_file( $id ); $pathinfo_fullsize = pathinfo( $originalfile ); $basepath = $pathinfo_fullsize['dirname']; // Image Sizes if ( $sizes ) { foreach ( $sizes as $name => $attr ) { $validSize = !empty( $attr['width'] ) || !empty( $attr['height'] ); if ( !$validSize || !$attr['retina'] ) { $result[$name] = 'IGNORED'; continue; } // Check if the file related to this size is present $pathinfo = null; $retina_file = null; if ( isset( $meta['sizes'][$name]['width'] ) && isset( $meta['sizes'][$name]['height']) && isset($meta['sizes'][$name]) && isset($meta['sizes'][$name]['file']) && file_exists( trailingslashit( $basepath ) . $meta['sizes'][$name]['file'] ) ) { $normal_file = trailingslashit( $basepath ) . $meta['sizes'][$name]['file']; $pathinfo = pathinfo( $normal_file ) ; $retina_file = trailingslashit( $pathinfo['dirname'] ) . $pathinfo['filename'] . $this->retina_extension() . $pathinfo['extension']; } // None of the file exist else { $result[$name] = 'MISSING'; continue; } // The retina file exists if ( $retina_file && file_exists( $retina_file ) ) { $result[$name] = 'EXISTS'; continue; } // The size file exists else if ( $retina_file ) $result[$name] = 'PENDING'; // The retina file exists $required_width = $meta['sizes'][$name]['width'] * 2; $required_height = $meta['sizes'][$name]['height'] * 2; if ( !$this->are_dimensions_ok( $original_width, $original_height, $required_width, $required_height ) ) { $result[$name] = array( 'width' => $required_width, 'height' => $required_height ); } } } // Full-Size (if required in the settings) $fullsize_required = get_option( "wr2x_full_size" ) && class_exists( 'MeowPro_WR2X_Core' ); $retina_file = trailingslashit( $pathinfo_fullsize['dirname'] ) . $pathinfo_fullsize['filename'] . $this->retina_extension() . $pathinfo_fullsize['extension']; if ( $retina_file && file_exists( $retina_file ) ) $result['full-size'] = 'EXISTS'; else if ( $fullsize_required && $retina_file ) $result['full-size'] = array( 'width' => $original_width * 2, 'height' => $original_height * 2 ); if ( $output_type === ARRAY_A ) { $new_results = array(); foreach ( $result as $key => $value ) { array_push( $new_results, array( 'name' => $key, 'shortname' => self::size_shortname( $key ), 'status' => is_array( $value ) ? 'CANNOT' : $value, 'required' => is_array( $value ) ? $value : null ) ); } return $new_results; } return $result; } function delete_attachment( $attach_id, $deleteFullSize = true ) { $meta = wp_get_attachment_metadata( $attach_id ); $this->delete_images( $meta, $deleteFullSize ); $this->remove_issue( $attach_id ); } function wp_generate_attachment_metadata( $meta ) { if ( get_option( "wr2x_auto_generate" ) == true ) if ( $this->is_image_meta( $meta ) ) $this->generate_images( $meta ); return $meta; } /** * @param mixed[] $meta * int width * int height * string file * mixed[][] sizes */ function generate_images( $meta ) { global $_wp_additional_image_sizes; $sizes = $this->get_image_sizes(); if ( !isset( $meta['file'] ) ) return; $uploads = wp_upload_dir(); // Check if the full-size-retina version of the generation source exists. // If it exists, replace the file path and its dimensions if ( $retina = $this->get_retina( $uploads['basedir'] . '/' . $meta['file'] ) ) { $meta['file'] = substr( $retina, strlen( $uploads['basedir'] ) + 1 ); $dim = getimagesize( $retina ); $meta['width'] = $dim[0]; $meta['height'] = $dim[1]; } $originalfile = $meta['file']; $pathinfo = pathinfo( $originalfile ); $original_basename = $pathinfo['basename']; $basepath = trailingslashit( $uploads['basedir'] ) . $pathinfo['dirname']; // $ignore = get_option( "wr2x_ignore_sizes" ); // if ( empty( $ignore ) ) // $ignore = array(); // $ignore = array_keys( $ignore ); $issue = false; $id = $this->get_attachment_id( $meta['file'] ); /** * @param $id ID of the attachment whose retina image is to be generated */ do_action( 'wr2x_before_generate_retina', $id ); $this->log("* GENERATE RETINA FOR ATTACHMENT '{$meta['file']}'"); $this->log( "Full-Size is {$original_basename}." ); foreach ( $sizes as $name => $attr ) { $normal_file = ""; if ( !$attr['retina'] ) { $this->log( "Retina for {$name} ignored (settings)." ); continue; } // Is the file related to this size there? $pathinfo = null; $retina_file = null; if ( isset( $meta['sizes'][$name] ) && isset( $meta['sizes'][$name]['file'] ) ) { $normal_file = trailingslashit( $basepath ) . $meta['sizes'][$name]['file']; $pathinfo = pathinfo( $normal_file ) ; $retina_file = trailingslashit( $pathinfo['dirname'] ) . $pathinfo['filename'] . $this->retina_extension() . $pathinfo['extension']; } if ( $retina_file && file_exists( $retina_file ) ) { $this->log( "Base for {$name} is '{$normal_file }'." ); $this->log( "Retina for {$name} already exists: '$retina_file'." ); continue; } if ( $retina_file ) { $originalfile = trailingslashit( $pathinfo['dirname'] ) . $original_basename; if ( !file_exists( $originalfile ) ) { $this->log( "[ERROR] Original file '{$originalfile}' cannot be found." ); return $meta; } // Maybe that new image is exactly the size of the original image. // In that case, let's make a copy of it. if ( $meta['sizes'][$name]['width'] * 2 == $meta['width'] && $meta['sizes'][$name]['height'] * 2 == $meta['height'] ) { copy ( $originalfile, $retina_file ); $this->log( "Retina for {$name} created: '{$retina_file}' (as a copy of the full-size)." ); } // Otherwise let's resize (if the original size is big enough). else if ( $this->are_dimensions_ok( $meta['width'], $meta['height'], $meta['sizes'][$name]['width'] * 2, $meta['sizes'][$name]['height'] * 2 ) ) { // Change proposed by Nicscott01, slighlty modified by Jordy (+isset) // (https://wordpress.org/support/topic/issue-with-crop-position?replies=4#post-6200271) $crop = isset( $_wp_additional_image_sizes[$name] ) ? $_wp_additional_image_sizes[$name]['crop'] : true; $customCrop = apply_filters( 'wr2x_custom_crop', null, $id, $name ); // // Support for Manual Image Crop // // If the size of the image was manually cropped, let's keep it. // if ( class_exists( 'ManualImageCrop' ) && isset( $meta['micSelectedArea'] ) && isset( $meta['micSelectedArea'][$name] ) && isset( $meta['micSelectedArea'][$name]['scale'] ) ) { // $customCrop = $meta['micSelectedArea'][$name]; // } $image = $this->resize( $originalfile, $meta['sizes'][$name]['width'] * 2, $meta['sizes'][$name]['height'] * 2, $crop, $retina_file, $customCrop ); } if ( !file_exists( $retina_file ) ) { $is_ok = apply_filters( "wr2x_last_chance_generate", false, $id, $retina_file, $meta['sizes'][$name]['width'] * 2, $meta['sizes'][$name]['height'] * 2 ); if ( !$is_ok ) { $this->log( "[ERROR] Retina for {$name} could not be created. Full-Size is " . $meta['width'] . "x" . $meta['height'] . " but Retina requires a file of at least " . $meta['sizes'][$name]['width'] * 2 . "x" . $meta['sizes'][$name]['height'] * 2 . "." ); $issue = true; } } else { do_action( 'wr2x_retina_file_added', $id, $retina_file, $name ); $this->log( "Retina for {$name} created: '{$retina_file}'." ); } } else { if ( empty( $normal_file ) ) $this->log( "[ERROR] Base file for '{$name}' does not exist." ); else $this->log( "[ERROR] Base file for '{$name}' cannot be found here: '{$normal_file}'." ); } } // Checks attachment ID + issues if ( !$id ) return $meta; if ( $issue ) $this->add_issue( $id ); else $this->remove_issue( $id ); /** * @param $id ID of the attachment whose retina image has been generated */ do_action( 'wr2x_generate_retina', $id ); return $meta; } function delete_images( $meta, $deleteFullSize = false ) { if ( !$this->is_image_meta( $meta ) ) return $meta; $sizes = $meta['sizes']; if ( !$sizes || !is_array( $sizes ) ) return $meta; $this->log("* DELETE RETINA FOR ATTACHMENT '{$meta['file']}'"); $originalfile = $meta['file']; $id = $this->get_attachment_id( $originalfile ); $pathinfo = pathinfo( $originalfile ); $uploads = wp_upload_dir(); $basepath = trailingslashit( $uploads['basedir'] ) . $pathinfo['dirname']; foreach ( $sizes as $name => $attr ) { $pathinfo = pathinfo( $attr['file'] ); $retina_file = $pathinfo['filename'] . $this->retina_extension() . $pathinfo['extension']; if ( file_exists( trailingslashit( $basepath ) . $retina_file ) ) { $fullpath = trailingslashit( $basepath ) . $retina_file; unlink( $fullpath ); do_action( 'wr2x_retina_file_removed', $id, $retina_file ); $this->log("Deleted '$fullpath'."); } } // Remove full-size if there is any if ( $deleteFullSize ) { $pathinfo = pathinfo( $originalfile ); $retina_file = $pathinfo[ 'filename' ] . $this->retina_extension() . $pathinfo[ 'extension' ]; if ( file_exists( trailingslashit( $basepath ) . $retina_file ) ) { $fullpath = trailingslashit( $basepath ) . $retina_file; unlink( $fullpath ); do_action( 'wr2x_retina_file_removed', $id, $retina_file ); $this->log( "Deleted '$fullpath'." ); } } return $meta; } // This is called by functions in the REST API // TODO: However, this function seems to be what delete_images does above, // so maybe we could optimize and avoid code redundancy. function delete_retina_fullsize( $mediaId ) { $originalfile = get_attached_file( $mediaId ); $pathinfo = pathinfo( $originalfile ); $retina_file = trailingslashit( $pathinfo['dirname'] ) . $pathinfo['filename'] . $this->retina_extension() . $pathinfo['extension']; if ( $retina_file && file_exists( $retina_file ) ) { return unlink( $retina_file ); } return false; } /** * * FILTERS * */ function validate_src( $src ) { if ( preg_match( "/^data:/i", $src ) ) return null; return $src; } /** * * LOAD SCRIPTS IF REQUIRED * */ function wp_enqueue_scripts () { // Picturefill Debug if ( $this->method == "Picturefill" && $this->is_debug() ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/debug.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-debug-js', trailingslashit( WR2X_URL ) . 'app/debug.js', array(), $cache_buster, false ); } // No Picturefill Script if ( $this->method == "Picturefill" && !get_option( "wr2x_picturefill_noscript" ) ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/picturefill.min.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-picturefill-js', trailingslashit( WR2X_URL ) . 'app/picturefill.min.js', array(), $cache_buster, false ); } // Lazysizes if ( get_option( "wr2x_picturefill_lazysizes" ) && class_exists( 'MeowPro_WR2X_Core' ) ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/lazysizes.min.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-picturefill-js', trailingslashit( WR2X_URL ) . 'app/lazysizes.min.js', array(), $cache_buster, false ); } // Debug + HTML Rewrite = No JS! if ( $this->is_debug() && $this->method == "HTML Rewrite" ) { return; } // Debug mode, we force the devicePixelRatio to be Retina if ( $this->is_debug() ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/debug.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-debug-js', trailingslashit( WR2X_URL ) . 'app/debug.js', array(), $cache_buster, false ); } // Retina-Images and HTML Rewrite both need the devicePixelRatio cookie on the server-side if ( $this->method == "Retina-Images" || $this->method == "HTML Rewrite" ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/retina-cookie.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-debug-js', trailingslashit( WR2X_URL ) . 'app/retina-cookie.js', array(), $cache_buster, false ); } // Retina.js only needs itself if ( $this->method == "retina.js" ) { $physical_file = trailingslashit( WR2X_PATH ) . 'app/retina.min.js'; $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : WR2X_VERSION; wp_enqueue_script( 'wr2x-retinajs-js', trailingslashit( WR2X_URL ) . 'app/retina.min.js', array(), $cache_buster, false ); } } } // Used by WP Rocket (and maybe by other plugins) function wr2x_is_registered() { global $wr2x_core; return $wr2x_core->admin->is_registered(); } ?>