├── .gitignore ├── components ├── Maintenance.php ├── MediaMetaInfo.php ├── PostAttachment.php └── UploadsCleanup.php ├── core ├── Autoload.php ├── Controller.php ├── Singleton.php └── helpers.php ├── data ├── config-example.php └── rwd-sizes.php ├── index.php ├── just-responsive-images.php ├── just-rwd-functions.php ├── models ├── ImageSize.php ├── RwdImage.php ├── RwdOption.php └── RwdSet.php ├── readme.md ├── readme.txt ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png └── views ├── cleanup └── index.php └── media └── meta-box.php /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # editor 3 | /nbproject/private/ 4 | /nbproject/ 5 | .project 6 | .idea 7 | 8 | # composer 9 | composer.lock 10 | /vendor/ 11 | 12 | # svn 13 | .svn 14 | 15 | # Ignore Mac DS_Store files 16 | .DS_Store 17 | ._* 18 | node_modules/ 19 | 20 | # linux backup files 21 | *~ 22 | 23 | # markup 24 | .sass-cache 25 | node_modules 26 | *.map 27 | .tmp 28 | tmp/ 29 | *.psd 30 | -------------------------------------------------------------------------------- /components/Maintenance.php: -------------------------------------------------------------------------------- 1 | version = \JustResponsiveImages::$version; 33 | if ( $this->prev_version = get_option( self::VER_OPTION, null ) ) { 34 | $this->maintenance( $this->prev_version, $this->version ); 35 | } 36 | 37 | update_option( self::VER_OPTION, $this->version ); 38 | } 39 | 40 | /** 41 | * Perform maintenance tasks on upgrade. 42 | * 43 | * @param float $prev_version Previous plugin version. 44 | * @param float $new_version Current plugin version. 45 | */ 46 | public function maintenance( $prev_version, $new_version ) { 47 | // TODO: write new code if necessary. 48 | } 49 | } -------------------------------------------------------------------------------- /components/MediaMetaInfo.php: -------------------------------------------------------------------------------- 1 | post_mime_type, 'image/' ) !== 0 ) { 32 | return; 33 | } 34 | add_meta_box( 'jri-attachement-meta-info', __( 'Image sizes' ), array( $this, 'render_meta_info' ) ); 35 | } 36 | 37 | /** 38 | * Get meta information and renders it. 39 | * 40 | * @param \WP_Post $post Post object. 41 | */ 42 | public function render_meta_info( $post ) { 43 | $meta = wp_get_attachment_metadata( $post->ID ); 44 | 45 | if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) { 46 | $meta['gcd'] = jri_greatest_commod_divisor( $meta['width'], $meta['height'] ); 47 | $meta['x_ratio'] = (int) $meta['width'] / $meta['gcd']; 48 | $meta['y_ratio'] = (int) $meta['height'] / $meta['gcd']; 49 | if ( 20 < $meta['x_ratio'] || 20 < $meta['y_ratio'] ) { 50 | $meta['x_ratio'] = round( $meta['x_ratio'] / 10 ); 51 | $meta['y_ratio'] = round( $meta['y_ratio'] / 10 ); 52 | $meta['avr_ratio'] = true; 53 | } 54 | } 55 | 56 | $this->_render( 'media/meta-box', array( 57 | 'meta' => $meta, 58 | ) 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /components/PostAttachment.php: -------------------------------------------------------------------------------- 1 | "srcset" attribute generation 10 | */ 11 | class PostAttachment { 12 | 13 | /** 14 | * Internal cache for advanced post thumbnails srcset feature 15 | * 16 | * @var string $calculated_image_size 17 | */ 18 | private $calculated_image_size; 19 | 20 | /** 21 | * Class constructor. 22 | * initialize WordPress hooks 23 | */ 24 | public function __construct() { 25 | add_action( 'after_setup_theme', array( $this, 'after_theme_setup' ) ); 26 | 27 | // load rwd template functions. 28 | include( JRI_ROOT . '/just-rwd-functions.php' ); 29 | add_action( 'wp_footer', 'rwd_print_styles' ); 30 | } 31 | 32 | /** 33 | * Add hooks which patch WordPress srcset and sizes attributes. 34 | */ 35 | public function add_image_responsive_hooks() { 36 | // Starting from WordPress 4.6 or 4.7 it change the way main content responsive images works. 37 | // We remove standard filter and replace it with our filter. 38 | remove_filter( 'the_content', 'wp_make_content_images_responsive' ); 39 | add_filter( 'the_content', array( $this, 'make_content_images_responsive' ) ); 40 | 41 | // hooks to make responsive usual WordPress thumbnail functions. 42 | // Probably doesn't work for WordPress 4.7+ or 4.8+. 43 | add_filter( 'wp_get_attachment_image_src', array( $this, 'set_calculated_image_size_cache' ), 10, 4 ); 44 | add_filter( 'wp_calculate_image_srcset', array( $this, 'calculate_image_srcset' ), 10, 5 ); 45 | add_filter( 'wp_calculate_image_sizes', array( $this, 'calculate_image_sizes' ), 10, 5 ); 46 | 47 | // patch image sizes list for "Just Image Optimizer" to show all available image sizes. 48 | add_filter( 'jio_settings_image_sizes', array( $this, 'add_jio_image_sizes' ) ); 49 | 50 | // Add filters for compatibility with manual "Crop Images" Plugins. 51 | if ( isset( $_GET['action'] ) 52 | && ( $_GET['action'] === 'pte_ajax' || $_GET['action'] === 'mic_editor_window' ) 53 | ) { 54 | add_filter( 'intermediate_image_sizes', array( $this, 'force_register_rwd_image_sizes' ) ); 55 | add_filter( 'wp_update_attachment_metadata', array( $this, 'update_attachment_rwd_metadata' ), 10, 2 ); 56 | } 57 | } 58 | 59 | /** 60 | * Check custom values on after theme setup hook. 61 | * 62 | * If current theme has configuration for this plugin - we init hooks required and parse image settings. 63 | * 64 | * @return void; 65 | */ 66 | public function after_theme_setup() { 67 | 68 | $settings = apply_filters( 'rwd_image_sizes', array() ); 69 | if ( empty( $settings ) ) { 70 | // theme or plugin doesn't add any filters and we don't have any. 71 | return; 72 | } 73 | 74 | $rwd_defaults = include JRI_ROOT . '/data/rwd-sizes.php'; 75 | $settings = array_merge( $rwd_defaults, $settings ); 76 | 77 | global $rwd_image_sizes, $rwd_image_options; 78 | $rwd_image_sizes = $rwd_image_options = array(); 79 | 80 | foreach ( $settings as $key => $params ) { 81 | $rwd_image_sizes[ $key ] = new RwdSet( $key, $params ); 82 | } 83 | 84 | add_theme_support( 'post-thumbnails' ); 85 | $this->add_image_responsive_hooks(); 86 | } 87 | 88 | /** 89 | * Set image size 90 | * 91 | * @param string $image image source. 92 | * @param int $attachment_id media attachment ID. 93 | * @param mixed $size size details. 94 | * @param boolean $icon not used. 95 | * 96 | * @return string 97 | */ 98 | public function set_calculated_image_size_cache( $image, $attachment_id, $size, $icon ) { 99 | $this->calculated_image_size = $size; 100 | 101 | return $image; 102 | } 103 | 104 | /** 105 | * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes. 106 | * 107 | * @since 4.4.0 108 | * 109 | * @see wp_image_add_srcset_and_sizes() 110 | * 111 | * @param string $content The raw post content to be filtered. 112 | * 113 | * @return string Converted content with 'srcset' and 'sizes' attributes added to images. 114 | */ 115 | public function make_content_images_responsive( $content ) { 116 | if ( ! preg_match_all( '/]+>/', $content, $matches ) ) { 117 | return $content; 118 | } 119 | 120 | $selected_images = $attachment_ids = array(); 121 | 122 | foreach ( $matches[0] as $image ) { 123 | if ( false === strpos( $image, ' srcset=' ) 124 | && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) 125 | && ( $attachment_id = absint( $class_id[1] ) ) 126 | ) { 127 | /** 128 | * If exactly the same image tag is used more than once, overwrite it. 129 | * All identical tags will be replaced later with 'str_replace()'. 130 | */ 131 | $selected_images[ $image ] = $attachment_id; 132 | // Overwrite the ID when the same image is included more than once. 133 | $attachment_ids[ $attachment_id ] = true; 134 | } 135 | } 136 | 137 | if ( count( $attachment_ids ) > 1 ) { 138 | /* 139 | * Warm the object cache with post and meta information for all found 140 | * images to avoid making individual database calls. 141 | */ 142 | _prime_post_caches( array_keys( $attachment_ids ), false, true ); 143 | } 144 | 145 | foreach ( $selected_images as $image => $attachment_id ) { 146 | if ( preg_match( '/size-([a-z]+)/i', $image, $size ) && has_image_size( $size[1] ) ) { 147 | // TODO: check that alt is stay the same as it was in content. 148 | $content = str_replace( $image, get_rwd_attachment_image( $attachment_id, $size[1], 'img' ), $content ); 149 | } else { 150 | $image_meta = wp_get_attachment_metadata( $attachment_id ); 151 | $content = str_replace( $image, wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content ); 152 | } 153 | } 154 | 155 | return $content; 156 | } 157 | 158 | /** 159 | * Calculate image sizes for srcset 160 | * 161 | * @param array $sources Image file pathes grouped by image width dimention. 162 | * @param array $size_array Image widthes, which are lower than image, which should be displayed. 163 | * @param string $image_src Image src of resized image of "last_image_size_called" size. 164 | * @param array $image_meta Image information with final dimension for each registered image size. 165 | * @param int $attachment Attachment ID. 166 | * 167 | * @return array 168 | */ 169 | public function calculate_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment ) { 170 | /* @var $rwd_image_sizes RwdSet[] */ 171 | global $rwd_image_sizes; 172 | if ( empty( $this->calculated_image_size ) 173 | || is_array( $this->calculated_image_size ) 174 | || ! array_key_exists( $this->calculated_image_size, $image_meta['sizes'] ) 175 | || empty( $rwd_image_sizes[ $this->calculated_image_size ]->options ) 176 | ) { 177 | return $sources; 178 | } 179 | 180 | $rwd_set = $rwd_image_sizes[ $this->calculated_image_size ]; 181 | $rwd_sources = array(); 182 | 183 | if ( ! isset( $image_meta['sizes'][ $rwd_set->key ] ) ) { 184 | return $sources; 185 | } 186 | 187 | $set_image_width = $image_meta['sizes'][ $rwd_set->key ]['width']; 188 | foreach ( $rwd_set->options as $rwd_option ) { 189 | // check that we have image resized in required image size. 190 | if ( ! isset( $image_meta['sizes'][ $rwd_option->key ] ) ) { 191 | continue; 192 | } 193 | 194 | $option_image_width = $image_meta['sizes'][ $rwd_option->key ]['width']; 195 | // Check that option width is lower than main image source and option image really exists. 196 | if ( $option_image_width > $set_image_width || ! isset( $sources[ $option_image_width ] ) ) { 197 | continue; 198 | } 199 | 200 | $rwd_sources[ $option_image_width ] = array( 201 | 'url' => $sources[ $option_image_width ]['url'], 202 | 'value' => strtr( $rwd_option->srcset, array( '{w}' => $option_image_width ) ), 203 | 'descriptor' => '', 204 | ); 205 | } 206 | 207 | return $rwd_sources; 208 | } 209 | 210 | /** 211 | * Calculate image sizes 212 | * 213 | * @param array $sizes Img sizes attribute, generated by WP. 214 | * @param mixed $size_array Some width and height, not sure how it's used. 215 | * @param string $image_src Resized image original source. 216 | * @param array $image_meta Image information with final dimension for each registered image size. 217 | * @param int $attachment_id Attachment ID. 218 | * 219 | * @return array 220 | */ 221 | public function calculate_image_sizes( $sizes, $size_array, $image_src, $image_meta, $attachment_id ) { 222 | /* @var $rwd_image_sizes RwdSet[] */ 223 | global $rwd_image_sizes; 224 | if ( empty( $this->calculated_image_size ) 225 | || is_array( $this->calculated_image_size ) 226 | || empty( $rwd_image_sizes[ $this->calculated_image_size ]->options ) 227 | ) { 228 | return $sizes; 229 | } 230 | 231 | $rwd_set = $rwd_image_sizes[ $this->calculated_image_size ]; 232 | $rwd_sizes = array(); 233 | 234 | if ( ! isset( $image_meta['sizes'][ $rwd_set->key ] ) ) { 235 | return $sizes; 236 | } 237 | 238 | $set_image_width = $image_meta['sizes'][ $rwd_set->key ]['width']; 239 | foreach ( $rwd_set->options as $rwd_option ) { 240 | // check that we have image resized in required image size. 241 | if ( ! isset( $image_meta['sizes'][ $rwd_option->key ] ) ) { 242 | continue; 243 | } 244 | 245 | $option_image_width = $image_meta['sizes'][ $rwd_option->key ]['width']; 246 | // Check that option width is lower than main image source and option image really exists. 247 | if ( $option_image_width > $set_image_width || empty( $rwd_option->sizes ) ) { 248 | continue; 249 | } 250 | 251 | $rwd_sizes[ $option_image_width ] = strtr( $rwd_option->sizes, array( '{w}' => $option_image_width ) ); 252 | } 253 | 254 | $rwd_sizes = implode( ', ', $rwd_sizes ); 255 | 256 | return $rwd_sizes; 257 | } 258 | 259 | /** 260 | * Clean up "src" attribute at all if we generate correct srcset and sizes attributes. 261 | * 262 | * @param array $attr Attributes for the image markup. 263 | * @param WP_Post $attachment Image attachment post. 264 | * @param string|array $size Requested size. Image size or array of width and height values 265 | * (in that order). Default 'thumbnail'. 266 | * 267 | * @return mixed 268 | */ 269 | public function attachment_image_attributes( $attr, $attachment, $size ) { 270 | global $rwd_image_sizes; 271 | if ( empty( $size ) 272 | || is_array( $size ) 273 | || empty( $rwd_image_sizes[ $size ] ) 274 | ) { 275 | return $attr; 276 | } 277 | 278 | // remove src attribute at all, because we have the best srcset/sizes attributes. 279 | if ( isset( $attr['src'] ) ) { 280 | unset( $attr['src'] ); 281 | } 282 | 283 | return $attr; 284 | } 285 | 286 | /** 287 | * WordPress by default add keys like thumbnail, medium, etc. 288 | * To speed up loop of regeneration we need to remove duplicated keys. 289 | * 290 | * @param array $image_sizes Image size names array. 291 | * 292 | * @return mixed 293 | */ 294 | public function add_jio_image_sizes( $image_sizes ) { 295 | global $rwd_image_options; 296 | foreach ( $rwd_image_options as $subkey => $option ) { 297 | 298 | $image_sizes[ $subkey ] = array( 299 | 'width' => $option->size->w, 300 | 'height' => $option->size->h, 301 | 'crop' => $option->size->crop, 302 | ); 303 | 304 | if ( $option->retina_options ) { 305 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 306 | $retina_key = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 307 | 308 | $image_sizes[ $retina_key ] = array( 309 | 'width' => $option->size->w * $multiplier, 310 | 'height' => $option->size->h * $multiplier, 311 | 'crop' => $option->size->crop, 312 | ); 313 | } 314 | } 315 | } 316 | 317 | return $image_sizes; 318 | } 319 | 320 | /** 321 | * Filters the list of intermediate image sizes for cropped images. 322 | * 323 | * @param array $image_sizes An array of intermediate image sizes. Defaults 324 | * are 'thumbnail', 'medium', 'medium_large', 'large'. 325 | * 326 | * @return array 327 | */ 328 | public function force_register_rwd_image_sizes( $image_sizes ) { 329 | global $rwd_image_options, $_wp_additional_image_sizes; 330 | foreach ( $rwd_image_options as $subkey => $option ) { 331 | $image_sizes[] = $subkey; 332 | 333 | $_wp_additional_image_sizes[ $subkey ] = array( 334 | 'width' => $option->size->w, 335 | 'height' => $option->size->h, 336 | 'crop' => $option->size->crop, 337 | ); 338 | 339 | if ( $option->retina_options ) { 340 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 341 | $retina_key = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 342 | $image_sizes[] = $retina_key; 343 | 344 | $_wp_additional_image_sizes[ $retina_key ] = array( 345 | 'width' => $option->size->w * $multiplier, 346 | 'height' => $option->size->h * $multiplier, 347 | 'crop' => $option->size->crop, 348 | ); 349 | } 350 | } 351 | } 352 | 353 | return array_unique( $image_sizes ); 354 | } 355 | 356 | /** 357 | * Filters the updated attachment meta data for cropped images. 358 | * Set param rwd_width, rwd_height, crop. 359 | * 360 | * @param array $data Array of updated attachment meta data. 361 | * @param int $attachment_id Attachment post ID. 362 | * 363 | * @return array 364 | */ 365 | public function update_attachment_rwd_metadata( $data, $attachment_id ) { 366 | $current_metadata = wp_get_attachment_metadata( $attachment_id ); 367 | if ( empty( $current_metadata ) ) { 368 | return $data; 369 | } 370 | foreach ( $current_metadata['sizes'] as $current_key => $current_sizes ) { 371 | foreach ( $data['sizes'] as $data_key => $data_sizes ) { 372 | if ( $current_key === $data_key ) { 373 | $data['sizes'][ $data_key ] = array_merge( $current_sizes, $data_sizes ); 374 | } 375 | } 376 | } 377 | 378 | return $data; 379 | } 380 | } -------------------------------------------------------------------------------- /components/UploadsCleanup.php: -------------------------------------------------------------------------------- 1 | cleanup( $uploads_dir ); 39 | $results = implode( '
', $results ); 40 | $results = str_replace( '{indent}', '  - ', $results ); 41 | } 42 | 43 | if ( ! empty( $_POST['do_stats'] ) && check_admin_referer( 'jri_cleanup_uploads' ) ) { 44 | $results = $this->get_stats( $uploads_dir ); 45 | } 46 | 47 | // load template. 48 | return $this->_render('cleanup/index', array( 49 | 'results' => $results, 50 | )); 51 | } 52 | 53 | /** 54 | * Run image uploads cleanup 55 | * 56 | * @param string $dir Directory to cleanup. 57 | * @param integer $depth Directory depth. 58 | * @return array 59 | */ 60 | protected function cleanup( $dir, $depth = 0 ) { 61 | $res = array(); 62 | $res[] = str_repeat( '{indent}', $depth ) . "Start cleaning: $dir"; 63 | 64 | $entries = scandir( $dir ); 65 | if ( empty( $entries ) ) { 66 | $res[] = str_repeat( '{indent}', $depth + 1 ) . 'directory is empty'; 67 | return $res; 68 | } 69 | 70 | $entries = array_reverse( $entries ); // (because . is after -) 71 | $original_names = array(); 72 | 73 | foreach ( $entries as $entry ) { 74 | if ( '.' === $entry || '..' === $entry ) { 75 | continue; 76 | } 77 | 78 | if ( is_dir( "$dir/$entry" ) ) { 79 | if ( $recursive_res = $this->cleanup( "$dir/$entry", $depth + 1 ) ) { 80 | $res = array_merge( $res, $recursive_res ); 81 | } 82 | } elseif ( is_file( "$dir/$entry" ) ) { 83 | $filename = "$dir/$entry"; 84 | if ( preg_match( '/^(.*?)(\-[0-9]+x[0-9]+)\.(jpeg|jpg|png)$/i', $entry, $match ) ) { 85 | $base_name = str_replace( $match[0], "{$match[1]}.{$match[3]}", $entry ); 86 | if ( ! isset( $original_names[ $base_name ] ) ) { 87 | $res[] = str_repeat( '{indent}', $depth + 1 ) . "Found base name: $entry"; 88 | $original_names[ $entry ] = 1; 89 | continue; 90 | } else { 91 | $res[] = str_repeat( '{indent}', $depth + 2 ) . "Cleaning subsize: $entry"; 92 | // this is a partial size - need cleanup. 93 | if ( wp_is_writable( $filename ) ) { 94 | unlink( $filename ); 95 | } else { 96 | $res[] = str_repeat( '{indent}', $depth + 2 ) . 'FAILED: NOT WRITABLE!'; 97 | } 98 | } 99 | } elseif ( preg_match( '/\.(jpeg|jpg|png)$/i', $entry, $match ) ) { 100 | $res[] = str_repeat( '{indent}', $depth + 1 ) . "Found base name: $entry"; 101 | $original_names[ $entry ] = 1; 102 | } 103 | } 104 | } 105 | 106 | return $res; 107 | } 108 | 109 | /** 110 | * Calculate disk space usage. 111 | * 112 | * @return string 113 | */ 114 | protected function get_stats() { 115 | // TODO: stats feature. 116 | return null; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/Autoload.php: -------------------------------------------------------------------------------- 1 | _render($template, $params); 51 | $responce = ob_get_clean(); 52 | } 53 | echo $responce; 54 | exit(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /core/Singleton.php: -------------------------------------------------------------------------------- 1 | array( 5 | array( 300, 200, true ), 6 | ), 7 | 'medium' => array( 8 | array( 9 | array( 640, 480 ), 10 | 'picture' => '', 11 | 'bg' => '@media (min-width: 415px)', 12 | 'srcset' => '{w}w', 13 | 'sizes' => '(min-width: 415px) {w}px', 14 | ), 15 | 'rwd-mobile' => 'inherit', 16 | ), 17 | 'large' => array( 18 | array( 19 | array( 1100, 800 ), 20 | 'picture' => '', 21 | 'bg' => '@media (min-width: 981px)', 22 | 'srcset' => '{w}w', 23 | 'sizes' => '(min-width: 981px) {w}px', 24 | ), 25 | 'rwd-tablet' => 'inherit', 26 | 'rwd-mobile' => 'inherit', 27 | ), 28 | 'hd' => array( 29 | array( 30 | array( 1600, 1200 ), 31 | 'picture' => '', 32 | 'bg' => '@media (min-width: 1281px)', 33 | 'srcset' => '{w}w', 34 | 'sizes' => '(min-width: 1281px) {w}px', 35 | ), 36 | 'rwd-tablet' => 'inherit', 37 | 'rwd-laptop' => 'inherit', 38 | 'rwd-mobile' => 'inherit', 39 | ), 40 | 'uhd' => array( 41 | 'rwd-desktop' => 'inherit', 42 | 'rwd-laptop' => 'inherit', 43 | 'rwd-tablet' => 'inherit', 44 | 'rwd-mobile' => 'inherit', 45 | ), 46 | ); -------------------------------------------------------------------------------- /data/rwd-sizes.php: -------------------------------------------------------------------------------- 1 | array( 5 | 'desktop' => array( 6 | array( 1920, null ), 7 | 'picture' => '', 8 | 'bg' => '@media (min-width:1281px)', 9 | 'bg_retina' => '@media (min-width:1281px) and {dpr}, (min-width:1281px) and {min_res}', 10 | 'srcset' => '{w}w', 11 | 'sizes' => '(min-width: 1281px) {w}px', 12 | ), 13 | 'laptop' => array( 14 | array( 1280, null ), 15 | 'picture' => '', 16 | 'bg' => '@media (min-width: 981px) ', 17 | 'bg_retina' => '@media (min-width: 981px) and {dpr}, (min-width: 981px) and {min_res}', 18 | 'srcset' => '{w}w', 19 | 'sizes' => '(min-width: 981px) {w}px', 20 | ), 21 | 'tablet' => array( 22 | array( 980, null ), 23 | 'picture' => '', 24 | 'bg' => '@media (min-width: 415px)', 25 | 'bg_retina' => '@media (min-width: 415px) and {dpr}, (min-width: 415px) and {min_res}', 26 | 'srcset' => '{w}w', 27 | 'sizes' => '(min-width: 415px) {w}px', 28 | ), 29 | 'mobile' => array( 30 | array( 414, null ), 31 | 'picture' => '{alt}', // mobile-first strategy picture img. 32 | 'bg' => '', // mobile-first strategy bg. 33 | 'bg_retina' => '@media {dpr}, {min_res}', 34 | 'srcset' => '{w}w', 35 | 'sizes' => '{w}px', 36 | ), 37 | ), 38 | ); 39 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | tags and dynamic mobile-friendly backgrounds. 4 | */ 5 | 6 | use jri\models\RwdImage; 7 | 8 | /** 9 | * Displays or for post attachment image specifying correct responsive rules, 10 | * which should be set through 'rwd_image_sizes' filter hook. 11 | * 12 | * @param WP_Post|int|null $attachment WordPress attachment object, ID or null. If null passed will take featured image of current post. 13 | * @param string|array $size { 14 | * Single image size name OR array 15 | * 16 | * @type int 0 => $size (first element should be the name of image size), 17 | * @type string $subsize => $attachment_id ($attachment to be used to rewrite image in another resolution) 18 | * } 19 | * Image size name or array with custom attachment IDs for specific rwd sizes. 20 | * 21 | * @param string $tag Specify which tag should be used: picture|img. 22 | * @param array $attr Additional html attributes to be used for main tag. 23 | */ 24 | function rwd_attachment_image( $attachment = null, $size = 'thumbnail', $tag = null, $attr = array() ) { 25 | if( empty( $tag ) ) { 26 | $tag = apply_filters( 'rwd_tag_type', 'picture' ); 27 | } 28 | echo get_rwd_attachment_image( $attachment, $size, $tag, $attr ); 29 | } 30 | 31 | /** 32 | * Generate or html for post attachment image specifying correct responsive rules, 33 | * which should be set through 'rwd_image_sizes' filter hook. 34 | * 35 | * @param WP_Post|int|null $attachment WordPress attachment object, ID or null. If null passed will take featured image of current post. 36 | * @param string|array $size { 37 | * Single image size name OR array 38 | * 39 | * @type int 0 => $size (first element should be the name of image size), 40 | * @type string $subsize => $attachment_id ($attachment to be used to rewrite image in another resolution) 41 | * } 42 | * @param string $tag Specify which tag should be used: picture|img. 43 | * @param array $attr Additional html attributes to be used for main tag. 44 | * 45 | * @return string Generated html. 46 | */ 47 | function get_rwd_attachment_image( $attachment = null, $size = 'thumbnail', $tag = null, $attr = array() ) { 48 | $rwd_image = new RwdImage( $attachment ); 49 | 50 | if( empty( $tag ) ) { 51 | $tag = apply_filters( 'rwd_tag_type', 'picture' ); 52 | } 53 | 54 | $size = apply_filters( 'post_thumbnail_size', $size ); 55 | 56 | if ( 'img' != $tag ) { 57 | $html = $rwd_image->picture( $size, $attr ); 58 | } else { 59 | $html = $rwd_image->img( $size, $attr ); 60 | } 61 | 62 | return $html; 63 | } 64 | 65 | /** 66 | * Generate css media styles for background image for specific css selector and specific rwd sizes, 67 | * which should be set through 'rwd_image_sizes' filter hook. 68 | * 69 | * Generated styles are add to cache and then print them in wp_foot or by calling rwd_print_css(); 70 | * 71 | * @param string $selector Dynamic css selector 72 | * @param WP_Post|int|null $attachment WordPress attachment object, ID or null. If null passed will take featured image of current post. 73 | * @param string|array $size { 74 | * Single image size name OR array 75 | * 76 | * @type int 0 => $size (first element should be the name of image size), 77 | * @type string $subsize => $attachment_id ($attachment to be used to rewrite image in another resolution) 78 | * } 79 | */ 80 | function rwd_attachment_background( $selector, $attachment = null, $size = 'thumbnail' ) { 81 | $rwd_image = new RwdImage( $attachment ); 82 | echo $rwd_image->background( $selector, $size ); 83 | } 84 | 85 | /** 86 | * Print styles from global cache 87 | */ 88 | function rwd_print_styles() { 89 | global $rwd_background_styles; 90 | 91 | if ( empty( $rwd_background_styles ) || ! is_array( $rwd_background_styles ) ) { 92 | return; 93 | } 94 | 95 | $styles = ''; 96 | 97 | $primary_media = RwdImage::get_background_primary_sizes(); 98 | // merge with media keys to get correct sorting. 99 | $ordered_styles = array_merge( array_flip( $primary_media ), $rwd_background_styles ); 100 | foreach ( $ordered_styles as $media => $selectors ) { 101 | if ( empty( $selectors ) || ! is_array( $selectors ) ) { 102 | continue; 103 | } 104 | 105 | $media_css = implode( ' ', $selectors ); 106 | if ( '' === $media ) { 107 | $styles .= " $media_css "; 108 | } else { 109 | $styles .= " $media{ $media_css } "; 110 | } 111 | } 112 | 113 | print " \n"; 114 | $rwd_background_styles = array(); 115 | } -------------------------------------------------------------------------------- /models/ImageSize.php: -------------------------------------------------------------------------------- 1 | count( $params ) ) { 60 | $params[] = false; 61 | } 62 | $params = array_values( $params ); 63 | $this->key = $key; 64 | $this->w = ! is_null( $params[0] ) ? absint( $params[0] ) : null; 65 | $this->h = ! is_null( $params[1] ) ? absint( $params[1] ) : null; 66 | $this->crop = is_array( $params[2] ) ? $params[2] : absint( $params[2] ); 67 | 68 | $this->register(); 69 | } 70 | 71 | /** 72 | * Call WordPress function to register current valid size. 73 | */ 74 | public function register() { 75 | if ( in_array( $this->key, array( 'thumbnail', 'medium', 'large', 'medium_large' ), true ) ) { 76 | update_site_option( "{$this->key}_size_w", $this->w ); 77 | update_site_option( "{$this->key}_size_h", $this->h ); 78 | update_site_option( "{$this->key}_crop", ! empty( $this->crop ) ); 79 | add_image_size( $this->key, $this->w, $this->h, $this->crop ); 80 | } 81 | } 82 | 83 | /** 84 | * Prepare unique image size for retina size. 85 | * 86 | * @param string $key Image size name. 87 | * @param string $retina_descriptor Retina descriptor (like 2x, 3x). 88 | * 89 | * @return string 90 | */ 91 | public static function get_retina_key( $key, $retina_descriptor ) { 92 | return "{$key} @{$retina_descriptor}"; 93 | } 94 | 95 | /** 96 | * Convert crop parameter to a single string to be able compare it. 97 | * 98 | * @param bool|array $crop Crop parameter. 99 | * 100 | * @return string Crop string value. 101 | */ 102 | public static function crop_string( $crop ) { 103 | return is_array( $crop ) ? implode( ',', $crop ) : (string) $crop; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /models/RwdImage.php: -------------------------------------------------------------------------------- 1 | attachment = $this->load_attachment( $attachment ); 85 | } 86 | 87 | /** 88 | * Verify that attachment mime type is SVG image 89 | * 90 | * @param \WP_Post $attachment Post-attachment object to be validated. 91 | * 92 | * @return boolean 93 | */ 94 | public function verify_svg_mime_type( $attachment ) { 95 | if ( false !== strpos( $attachment->post_mime_type, 'image/svg' ) ) { 96 | return true; 97 | } else { 98 | return false; 99 | } 100 | } 101 | 102 | 103 | /** 104 | * Generate tag for the current attachment with specified size 105 | * 106 | * @param string|array $size Required image size. 107 | * @param array $attributes Additional html attributes to be used for main tag. 108 | * 109 | * @return string 110 | */ 111 | public function picture( $size, $attributes = array() ) { 112 | if ( ! $this->attachment ) { 113 | return ''; 114 | } 115 | 116 | /* Check if svg and print it */ 117 | if ( $this->verify_svg_mime_type( $this->attachment ) ) { 118 | return $this->svg( $size, $attributes ); 119 | } 120 | 121 | $html = ''; 122 | if ( $this->set_sizes( $size ) && $sources = $this->get_set_sources() ) { 123 | // prepare image attributes (class, alt, title etc). 124 | $attr = array( 125 | 'class' => "attachment-{$this->rwd_set->key} size-{$this->rwd_set->key} wp-post-picture", 126 | 'alt' => trim( strip_tags( get_post_meta( $this->attachment->ID, '_wp_attachment_image_alt', true ) ) ), 127 | 'src' => '', // it's not used, but included for compatibility with other plugins. 128 | ); 129 | if ( ! empty( $attributes['class'] ) ) { 130 | $attributes['class'] = $attr['class'] . ' ' . $attributes['class']; 131 | } 132 | $attr = array_merge( $attr, $attributes ); 133 | $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $this->attachment, $this->rwd_set->key ); 134 | if ( isset( $attr['src'] ) ) { // remove compatibility key, which is not used actually. 135 | unset( $attr['src'] ); 136 | } 137 | $attr = array_map( 'esc_attr', $attr ); 138 | 139 | // default template (if we have only 1 size). 140 | $default_template = '{alt}'; 141 | 142 | // generation of responsive sizes. 143 | $html = ' $value ) { 145 | if ( 'alt' !== $name ) { 146 | $html .= " $name=" . '"' . $value . '"'; 147 | } 148 | } 149 | $html .= '>' . $this->eol; 150 | 151 | foreach ( $this->rwd_set->options as $subkey => $option ) { 152 | if ( ! isset( $sources[ $subkey ] ) || is_null( $option->picture ) ) { 153 | continue; 154 | } 155 | 156 | $meta_data = $this->get_attachment_metadata( $sources[ $subkey ]['attachment_id'] ); 157 | $baseurl = $this->get_attachment_baseurl( $sources[ $subkey ]['attachment_id'] ); 158 | 159 | $src = array( 160 | $this->get_attachment_url( $baseurl, $sources[ $subkey ] ), 161 | ); 162 | // get retina sources. 163 | if ( $option->retina_options ) { 164 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 165 | $retina_image_size = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 166 | if ( ! empty( $meta_data['sizes'][ $retina_image_size ] ) ) { 167 | $src[] = $this->get_attachment_url( $baseurl, $meta_data['sizes'][ $retina_image_size ] ) 168 | . ' ' . $retina_descriptor; 169 | } 170 | } 171 | } 172 | 173 | $tokens = array( 174 | '{src}' => esc_attr( implode( ', ', $src ) ), 175 | '{alt}' => $attr['alt'], 176 | '{w}' => $meta_data['sizes'][ $option->key ]['width'], 177 | '{single-src}' => reset( $src ), 178 | ); 179 | 180 | $template = $option->picture ? $option->picture : $default_template; 181 | $html .= strtr( $template, $tokens ) . $this->eol; 182 | } 183 | $html .= ''; 184 | } // End if(). 185 | 186 | $html = $this->get_warnings_comment() . $html; 187 | 188 | return $html; 189 | } 190 | 191 | /** 192 | * Generate tag for the current attachment with specified size 193 | * 194 | * @param string|array $size Required image size. 195 | * @param array $attributes Additional html attributes to be used for main tag. 196 | * 197 | * @return string 198 | */ 199 | public function img( $size, $attributes = array() ) { 200 | if ( ! $this->attachment ) { 201 | return ''; 202 | } 203 | 204 | /* Check if svg and print it */ 205 | if ( $this->verify_svg_mime_type( $this->attachment ) ) { 206 | return $this->svg( $size, $attributes ); 207 | } 208 | 209 | $html = ''; 210 | if ( $this->set_sizes( $size ) && $sources = $this->get_set_sources() ) { 211 | // prepare image attributes (class, alt, title etc). 212 | $attr = array( 213 | 'class' => "attachment-{$this->rwd_set->key} size-{$this->rwd_set->key} wp-post-image", 214 | 'alt' => trim( strip_tags( get_post_meta( $this->attachment->ID, '_wp_attachment_image_alt', true ) ) ), 215 | ); 216 | if ( ! empty( $attributes['class'] ) ) { 217 | $attributes['class'] = $attr['class'] . ' ' . $attributes['class']; 218 | } 219 | $attr = array_merge( $attr, $attributes ); 220 | 221 | $src = ''; 222 | $srcset = array(); 223 | $sizes = array(); 224 | // generation of responsive sizes. 225 | foreach ( $this->rwd_set->options as $subkey => $option ) { 226 | if ( ! isset( $sources[ $subkey ] ) || is_null( $option->srcset ) ) { 227 | continue; 228 | } 229 | 230 | $baseurl = $this->get_attachment_baseurl( $sources[ $subkey ]['attachment_id'] ); 231 | $meta_data = $this->get_attachment_metadata( $sources[ $subkey ]['attachment_id'] ); 232 | 233 | $tokens = array( 234 | '{src}' => esc_attr( $this->get_attachment_url( $baseurl, $sources[ $subkey ] ) ), 235 | '{w}' => $meta_data['sizes'][ $option->key ]['width'], 236 | ); 237 | 238 | // get retina sources. 239 | if ( $option->retina_options ) { 240 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 241 | $retina_image_size = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 242 | if ( ! empty( $meta_data['sizes'][ $retina_image_size ]['width'] ) ) { 243 | $retina_width = $meta_data['sizes'][ $retina_image_size ]['width']; 244 | $srcset[] = $this->get_attachment_url( $baseurl, $meta_data['sizes'][ $retina_image_size ] ) 245 | . ' ' . $retina_width . 'w'; 246 | } 247 | } 248 | } 249 | 250 | $src = $tokens['{src}']; 251 | $srcset[] = strtr( "{src} $option->srcset", $tokens ); 252 | if ( $option->sizes ) { 253 | $sizes[] = strtr( $option->sizes, $tokens ); 254 | } 255 | } 256 | 257 | $attr['src'] = $src; 258 | $attr['srcset'] = implode( ', ', $srcset ); 259 | if ( ! empty( $sizes ) ) { 260 | $attr['sizes'] = implode( ', ', $sizes ); 261 | } 262 | 263 | // the part taken from WP core. 264 | $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $this->attachment, $this->rwd_set->key ); 265 | $attr = array_map( 'esc_attr', $attr ); 266 | $html = ' $value ) { 268 | $html .= " $name=" . '"' . $value . '"'; 269 | } 270 | $html .= '>'; 271 | } // End if(). 272 | 273 | $html = $this->get_warnings_comment() . $html; 274 | 275 | return $html; 276 | } 277 | 278 | /** 279 | * Generate background media queries 280 | * 281 | * @param string $selector CSS selector. 282 | * @param string|array $size Required image size. 283 | * 284 | * @return string Generated html comments warnings. 285 | */ 286 | public function background( $selector, $size ) { 287 | if ( ! $this->attachment ) { 288 | return; 289 | } 290 | 291 | if ( $this->set_sizes( $size ) && $sources = $this->get_set_sources() ) { 292 | global $rwd_background_styles; 293 | 294 | // define the strategy: mobile- or desktop- first. Desktop-first will start from empty media query, mobile-first will start with min-width media query. 295 | $rwd_options = $this->rwd_set->options; 296 | if ( false !== strpos( reset( $rwd_options )->bg, 'min-width' ) ) { 297 | $rwd_options = array_reverse( $rwd_options, true ); 298 | } 299 | // generation of responsive sizes. 300 | foreach ( $rwd_options as $subkey => $option ) { 301 | if ( ! isset( $sources[ $subkey ] ) || is_null( $option->bg ) ) { 302 | continue; 303 | } 304 | $baseurl = $this->get_attachment_baseurl( $sources[ $subkey ]['attachment_id'] ); 305 | $meta_data = $this->get_attachment_metadata( $sources[ $subkey ]['attachment_id'] ); 306 | 307 | $src = $this->get_attachment_url( $baseurl, $sources[ $subkey ] ); 308 | $media = str_replace( '{w}', $meta_data['sizes'][ $option->key ]['width'], $option->bg ); 309 | 310 | if ( ! isset( $rwd_background_styles[ $media ] ) ) { 311 | $rwd_background_styles[ $media ] = array(); 312 | } 313 | $rwd_background_styles[ $media ][ $selector ] = "$selector{background-image:url('$src');}"; 314 | 315 | // get retina sources. 316 | if ( $option->retina_options ) { 317 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 318 | // Check media pixel and media resolution dpi. 319 | $media_pixel_ration = ( $multiplier < 2.5 ? 1.5 : 2.5 ); 320 | $media_resolution = ( $multiplier < 2.5 ? '144dpi' : '192dpi' ); 321 | 322 | $retina_image_size = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 323 | 324 | if ( ! empty( $meta_data['sizes'][ $retina_image_size ] ) ) { 325 | $src_retina = $this->get_attachment_url( $baseurl, $meta_data['sizes'][ $retina_image_size ] ); 326 | $media_retina = strtr( $option->bg_retina, array( 327 | '{dpr}' => "(-webkit-min-device-pixel-ratio:{$media_pixel_ration})", 328 | '{min_res}' => "(min-resolution : {$media_resolution})", 329 | ) ); 330 | if ( ! isset( $rwd_background_styles[ $media_retina ] ) ) { 331 | $rwd_background_styles[ $media_retina ] = array(); 332 | } 333 | $rwd_background_styles[ $media_retina ][ $selector ] = "$selector{background-image:url('$src_retina');}"; 334 | } 335 | } 336 | } // End if(). 337 | } // End foreach(). 338 | } // End if(). 339 | 340 | return $this->get_warnings_comment(); 341 | } 342 | 343 | /** 344 | * Generate img tag for svg image 345 | * 346 | * @param string|array $size Required image size. 347 | * @param array $attributes Image attributes. 348 | * 349 | * @return string Generated html comments warnings. 350 | */ 351 | public function svg( $size, $attributes ) { 352 | 353 | // prepare image attributes (class, alt, title etc). 354 | $attr = array( 355 | 'class' => 'wp-post-image', 356 | 'alt' => trim( strip_tags( get_post_meta( $this->attachment->ID, '_wp_attachment_image_alt', true ) ) ), 357 | ); 358 | 359 | if ( ! empty( $attributes['class'] ) ) { 360 | $attributes['class'] = $attr['class'] . ' ' . $attributes['class']; 361 | } 362 | 363 | $attr = array_merge( $attr, $attributes ); 364 | $attr['src'] = esc_url( wp_get_attachment_url( $this->attachment->ID ) ); 365 | $attr['alt'] = trim( strip_tags( get_post_meta( $this->attachment->ID, '_wp_attachment_image_alt', true ) ) ); 366 | 367 | if ( $this->set_sizes( $size ) ) { 368 | $attr['width'] = $this->rwd_set->size->w; 369 | $attr['height'] = $this->rwd_set->size->h; 370 | } 371 | 372 | // the part taken from WP core. 373 | $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $this->attachment, $this->rwd_set->key ); 374 | $attr = array_map( 'esc_attr', $attr ); 375 | $html = ' $value ) { 377 | $html .= " $name=" . '"' . $value . '"'; 378 | } 379 | $html .= '>'; 380 | 381 | $html = $this->get_warnings_comment() . $html; 382 | 383 | return $html; 384 | } 385 | 386 | /** 387 | * Set rwd_set and rwd_rewrite based on size. 388 | * 389 | * @param string|array $size Required image size. 390 | * 391 | * @return bool 392 | */ 393 | public function set_sizes( $size ) { 394 | $rwd_sizes = $this->get_registered_rwd_sizes(); 395 | if ( is_string( $size ) ) { 396 | $size = array( $size ); 397 | } 398 | 399 | if ( empty( $size[0] ) || ! isset( $rwd_sizes[ $size[0] ] ) ) { 400 | $this->warnings[] = 'RwdImage::set_size() : Unknown image size "' . esc_html( @$size[0] ) . '"'; 401 | 402 | return false; 403 | } else { 404 | $this->rwd_set = $rwd_sizes[ $size[0] ]; 405 | 406 | if ( 1 < count( $size ) ) { 407 | unset( $size[0] ); 408 | foreach ( $size as $subkey => $attachment ) { 409 | if ( $attachment = $this->load_attachment( $attachment ) ) { 410 | $this->rwd_rewrite[ $subkey ] = $attachment; 411 | } 412 | } 413 | } 414 | } 415 | 416 | return true; 417 | } 418 | 419 | /** 420 | * Prepare rwd set real file sources to be displayed 421 | * 422 | * @return array|null 423 | */ 424 | public function get_set_sources() { 425 | if ( empty( $this->rwd_set ) ) { 426 | return null; 427 | } 428 | $sources = array(); 429 | $attachment_meta = $this->get_attachment_metadata( $this->attachment->ID ); 430 | $is_attachment_svg = $this->verify_svg_mime_type( $this->attachment ); 431 | 432 | // for non-svg define main image size. 433 | if ( ! $is_attachment_svg ) { 434 | $attachment_width = ! empty( $attachment_meta['sizes'][ $this->rwd_set->key ] ) ? 435 | $attachment_meta['sizes'][ $this->rwd_set->key ]['width'] : $attachment_meta['width']; 436 | } 437 | 438 | $dummy_sizes = array(); 439 | $dummy_meta = array(); 440 | foreach ( $this->rwd_set->options as $subkey => $option ) { 441 | $attachment = empty( $this->rwd_rewrite[ $subkey ] ) ? $this->attachment : $this->rwd_rewrite[ $subkey ]; 442 | $meta_data = $this->get_attachment_metadata( $attachment->ID ); 443 | $is_subsize_svg = $this->verify_svg_mime_type( $attachment ); 444 | 445 | // svg images doesn't have meta data, so we need to generate it. 446 | if ( $is_subsize_svg ) { 447 | if ( ! is_array( $meta_data ) ) { 448 | $meta_data = array(); 449 | } 450 | 451 | $upload_dir = wp_upload_dir(); 452 | $meta_data['file'] = str_replace( 453 | $upload_dir['basedir'] . '/', 454 | '', 455 | get_attached_file( $attachment->ID, true ) 456 | ); 457 | 458 | $meta_data['sizes'][ $option->key ] = array( 459 | 'width' => $option->size->w, 460 | 'height' => $option->size->h, 461 | 'file' => basename( $meta_data['file'] ), 462 | ); 463 | // save to cache. 464 | $this->set_attachment_metadata( $attachment->ID, $meta_data ); 465 | } else { 466 | // Resize image if not exists. 467 | $meta_data = $this->resize_image( 468 | $attachment->ID, 469 | $meta_data, 470 | $option->key, 471 | $option->size->w, 472 | $option->size->h, 473 | $option->size->crop 474 | ); 475 | if ( JRI_DUMMY_IMAGE && empty( $meta_data['sizes'][ $option->key ]['valid'] ) ) { 476 | $dummy_sizes[ $option->key ] = $this->dummy_source( $option, false, $attachment->ID, $meta_data ); 477 | } 478 | 479 | // Resize retina images if not exists. 480 | if ( $option->retina_options ) { 481 | foreach ( $option->retina_options as $retina_descriptor => $multiplier ) { 482 | $retina_image_size = ImageSize::get_retina_key( $option->key, $retina_descriptor ); 483 | // generate retina only if original size is bigger in all dimensions than retina size. 484 | $retina_w = $option->size->w * $multiplier; 485 | $retina_h = $option->size->h * $multiplier; 486 | if ( $retina_w <= $meta_data['width'] && $retina_h <= $meta_data['height'] ) { 487 | $meta_data = $this->resize_image( 488 | $attachment->ID, 489 | $meta_data, 490 | $retina_image_size, 491 | $retina_w, 492 | $retina_h, 493 | $option->size->crop 494 | ); 495 | } 496 | 497 | if ( JRI_DUMMY_IMAGE && empty( $meta_data['sizes'][ $retina_image_size ]['valid'] ) ) { 498 | $dummy_sizes[ $retina_image_size ] = $this->dummy_source( $option, $multiplier, $attachment->ID, $meta_data ); 499 | } 500 | } 501 | } 502 | } 503 | 504 | if ( JRI_DUMMY_IMAGE ) { 505 | if ( ! isset( $dummy_meta[ $attachment->ID ] ) ) { 506 | $dummy_meta[ $attachment->ID ] = $meta_data; 507 | } 508 | $meta_data['sizes'] = array_merge( $dummy_meta[ $attachment->ID ]['sizes'], $dummy_sizes ); 509 | $dummy_meta[ $attachment->ID ] = $meta_data; 510 | } 511 | 512 | // however if we didn't find correct size - we skip this size with warning. 513 | if ( ! isset( $meta_data['sizes'][ $option->key ] ) ) { 514 | $this->warnings[] = "Attachment {$attachment->ID}: missing image size \"{$this->rwd_set->key}:{$subkey}\""; 515 | continue; 516 | } 517 | 518 | $sources[ $subkey ] = $meta_data['sizes'][ $option->key ]; 519 | $sources[ $subkey ]['attachment_id'] = $attachment->ID; 520 | } // End foreach(). 521 | 522 | // cache all dummy retina sizes to get correct width/height options for retina. 523 | if ( JRI_DUMMY_IMAGE ) { 524 | foreach ( $dummy_meta as $attachment_id => $meta_data ) { 525 | $this->set_attachment_metadata( $attachment_id, $meta_data ); 526 | } 527 | } 528 | 529 | return $sources; 530 | } 531 | 532 | /** 533 | * Generate generate fake src for empty image sizes 534 | * 535 | * @param RwdOption $option empty image size options. 536 | * @param int|bool $retina_multiplier retina multiplier for retina size. 537 | * @param int $attachment_id attachment ID to get dummy image. 538 | * @param array $meta_data image attachment WP metadata. 539 | * 540 | * @return string 541 | */ 542 | public function dummy_source( $option, $retina_multiplier, $attachment_id, $meta_data ) { 543 | 544 | $sizename = $option->key; 545 | 546 | $w = $option->size->w; 547 | $h = $option->size->h; 548 | 549 | $ratio = null; 550 | if ( ! empty( $meta_data['width'] ) && ! empty( $meta_data['height'] ) ) { 551 | $ratio = $meta_data['width'] / $meta_data['height']; 552 | } 553 | 554 | if ( is_null( $h ) || 9999 === $h ) { 555 | $h = $ratio ? floor( $w / $ratio ) : $w; 556 | } elseif ( is_null( $w ) || 9999 === $w ) { 557 | $w = $ratio ? floor( $h * $ratio ) : $h; 558 | } 559 | 560 | if ( $retina_multiplier ) { 561 | $w *= $retina_multiplier; 562 | $h *= $retina_multiplier; 563 | } 564 | 565 | $color = substr( md5( "{$meta_data['file']}-$w-$h" ), 0, 6 ); 566 | $dummy_url = "http://via.placeholder.com/{$w}x{$h}/$color?text=%23{$attachment_id}:+{$w}x{$h}"; 567 | 568 | return [ 569 | 'file' => $dummy_url, 570 | 'width' => $w, 571 | 'height' => $h, 572 | ]; 573 | } 574 | 575 | /** 576 | * Dynamically resize image. 577 | * 578 | * @param int $attach_id Attachment ID. 579 | * @param array $meta_data Attachment meta data. 580 | * @param string $key Image size key. 581 | * @param int $width Image width. 582 | * @param int $height Image height. 583 | * @param int $crop Crop image. 584 | * 585 | * @return array 586 | */ 587 | public function resize_image( $attach_id, $meta_data, $key, $width, $height, $crop ) { 588 | $crop_str = ImageSize::crop_string( $crop ); 589 | 590 | $upload_dir = wp_get_upload_dir(); 591 | $intermediate_size = image_get_intermediate_size( $attach_id, $key ); 592 | $intermediate_path = ''; 593 | 594 | if ( ! empty( $intermediate_size ) ) { 595 | $intermediate_path = $intermediate_size['path']; 596 | } 597 | 598 | $image_baseurl = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . $intermediate_path; 599 | 600 | $meta_data['sizes'][ $key ]['valid'] = true; 601 | 602 | if ( ! file_exists( $image_baseurl ) || ! isset( $meta_data['sizes'][ $key ]['rwd_width'] ) 603 | || $meta_data['sizes'][ $key ]['rwd_width'] !== $width || $meta_data['sizes'][ $key ]['rwd_height'] !== $height 604 | || ( 0 !== strcmp( $crop_str, $meta_data['sizes'][ $key ]['crop'] ) ) 605 | ) { 606 | // in dummy mode we do not resize anything. 607 | if ( JRI_DUMMY_IMAGE ) { 608 | $meta_data['sizes'][ $key ]['valid'] = false; 609 | return $meta_data; 610 | } 611 | 612 | // Get WP Image Editor Instance. 613 | $image_path = get_attached_file( $attach_id ); 614 | $image_editor = wp_get_image_editor( $image_path ); 615 | if ( ! is_wp_error( $image_editor ) ) { 616 | // Create new image. 617 | $auto_width = ( 19998 === $width || 9999 === $width ? null : $width ); 618 | $auto_height = ( 19998 === $height || 9999 === $height ? null : $height ); 619 | // WP Image Editor resize. 620 | $image_editor->resize( $auto_width, $auto_height, $crop ); 621 | // Generate filename. 622 | $resize_filename = basename( $image_editor->generate_filename() ); 623 | $resize_sizes = $image_editor->get_size(); 624 | 625 | // We taked resized image only if we sure that resize was successful and resized dimensions are correct. 626 | // - if original image is bigger than resized copy - resize was successful. 627 | if ( ( $meta_data['width'] > $resize_sizes['width'] && $meta_data['height'] > $resize_sizes['height'] ) 628 | // - if crop enabled and resized image match the requested size - resize was successful. 629 | || ( ! empty( $crop ) && $resize_sizes['width'] == $width && $resize_sizes['height'] == $height ) 630 | ) { 631 | // WP Image Editor save image. 632 | $image_editor->save(); 633 | $meta_data['sizes'][ $key ] = array( 634 | 'width' => $resize_sizes['width'], 635 | 'height' => $resize_sizes['height'], 636 | 'rwd_width' => $width, 637 | 'rwd_height' => $height, 638 | 'crop' => $crop_str, 639 | 'file' => $resize_filename, 640 | 'mime-type' => get_post_mime_type( $attach_id ), 641 | ); 642 | } else { 643 | // use max image size for the bigger sizes. 644 | if ( ! strpos( $key, '@' ) ) { 645 | $meta_data['sizes'][ $key ] = array( 646 | 'width' => $meta_data['width'], 647 | 'height' => $meta_data['height'], 648 | 'file' => basename( $meta_data['file'] ), 649 | 'rwd_width' => $width, 650 | 'rwd_height' => $height, 651 | 'crop' => $crop_str, 652 | 'mime-type' => get_post_mime_type( $attach_id ), 653 | ); 654 | } else { 655 | unset( $meta_data['sizes'][ $key ] ); 656 | } 657 | } 658 | // save to cache. 659 | $this->set_attachment_metadata( $attach_id, $meta_data ); 660 | // update metadata. 661 | wp_update_attachment_metadata( $attach_id, $meta_data ); 662 | // update JIO attachment status. 663 | update_post_meta( $attach_id, '_just_img_opt_status', self::STATUS_IN_QUEUE ); 664 | } 665 | } 666 | 667 | return $meta_data; 668 | } 669 | 670 | /** 671 | * Validate $attachment argument, find media post in DB and return it. 672 | * 673 | * @param \WP_Post|int|null $attachment Attachment argument to validate. 674 | * 675 | * @return \WP_Post|null| 676 | */ 677 | protected function load_attachment( $attachment ) { 678 | if ( empty( $attachment ) ) { 679 | if ( ! empty( $this->attachment ) ) { 680 | $attachment = $this->attachment; 681 | } else { 682 | $attachment = get_post_thumbnail_id( get_the_ID() ); 683 | } 684 | } 685 | if ( is_numeric( $attachment ) && $attachment = get_post( $attachment ) ) { 686 | // check that ID passed is really an attachment. 687 | if ( 'attachment' !== $attachment->post_type ) { 688 | $attachment = null; 689 | } 690 | } 691 | if ( is_a( $attachment, '\WP_Post' ) ) { 692 | return $attachment; 693 | } 694 | 695 | return null; 696 | } 697 | 698 | /** 699 | * Generate HTML comments for warnings 700 | * 701 | * @return string 702 | */ 703 | protected function get_warnings_comment() { 704 | if ( ! empty( $this->warnings ) ) { 705 | return '{$this->eol}' . $this->eol; 706 | } else { 707 | return ''; 708 | } 709 | } 710 | 711 | /** 712 | * Cache for wp_get_attachment_metadata function. 713 | * 714 | * @param int $attachment_id Attachment post to get it's metadata. 715 | * 716 | * @return mixed 717 | */ 718 | protected function get_attachment_metadata( $attachment_id ) { 719 | if ( ! isset( static::$meta_datas[ $attachment_id ] ) ) { 720 | static::$meta_datas[ $attachment_id ] = wp_get_attachment_metadata( $attachment_id ); 721 | } 722 | 723 | return static::$meta_datas[ $attachment_id ]; 724 | } 725 | 726 | /** 727 | * Set updated values to cache 728 | * 729 | * @param int $attachment_id Attachment post to update it's metadata cache. 730 | * @param array $meta_data New meta data values. 731 | */ 732 | protected function set_attachment_metadata( $attachment_id, $meta_data ) { 733 | static::$meta_datas[ $attachment_id ] = $meta_data; 734 | } 735 | 736 | /** 737 | * Cache for attachment baseurl generation 738 | * 739 | * @param int $attachment_id Attachment ID to find out baseurl to. 740 | * 741 | * @return mixed 742 | */ 743 | protected function get_attachment_baseurl( $attachment_id ) { 744 | if ( ! isset( static::$base_urls[ $attachment_id ] ) ) { 745 | $image_meta = $this->get_attachment_metadata( $attachment_id ); 746 | 747 | $dirname = _wp_get_attachment_relative_path( $image_meta['file'] ); 748 | 749 | if ( $dirname ) { 750 | $dirname = trailingslashit( $dirname ); 751 | } 752 | 753 | $upload_dir = wp_get_upload_dir(); 754 | $image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname; 755 | 756 | if ( is_ssl() && 'https' !== substr( $image_baseurl, 0, 5 ) && parse_url( $image_baseurl, PHP_URL_HOST ) === $_SERVER['HTTP_HOST'] ) { 757 | $image_baseurl = set_url_scheme( $image_baseurl, 'https' ); 758 | } 759 | 760 | static::$base_urls[ $attachment_id ] = $image_baseurl; 761 | } 762 | 763 | return static::$base_urls[ $attachment_id ]; 764 | } 765 | 766 | /** 767 | * Generate final file URL. 768 | * 769 | * @param string $baseurl Attachment folder base url. 770 | * @param array $source File sources array. 771 | * 772 | * @return string 773 | */ 774 | protected function get_attachment_url( $baseurl, $source ) { 775 | $url = $source['file']; 776 | 777 | if ( ! preg_match( '/^http/', $url ) ) { 778 | $url = $baseurl . $url; 779 | } 780 | return $url; 781 | } 782 | 783 | /** 784 | * Alias for global variable to simlify code. 785 | * 786 | * @return mixed 787 | */ 788 | protected function get_registered_rwd_sizes() { 789 | global $rwd_image_sizes; 790 | 791 | return $rwd_image_sizes; 792 | } 793 | 794 | /** 795 | * List of primary sizes, which should be printed before all other styles 796 | * 797 | * @return array 798 | */ 799 | public static function get_background_primary_sizes() { 800 | return array( 801 | '', // no media query. 802 | '@media (-webkit-min-device-pixel-ratio:1.5), (min-resolution : 144dpi)', // 2x retina media query. 803 | '@media (-webkit-min-device-pixel-ratio:2.5), (min-resolution : 192dpi)', // 3x retina media query. 804 | ); 805 | } 806 | } -------------------------------------------------------------------------------- /models/RwdOption.php: -------------------------------------------------------------------------------- 1 | null, 77 | 'bg' => null, 78 | 'bg_retina' => null, 79 | 'srcset' => null, 80 | 'sizes' => null, 81 | ), $params ); 82 | if ( ! isset( $params[0] ) ) { 83 | $params[0] = ''; 84 | } 85 | 86 | $this->key = $key; 87 | $this->size = new ImageSize( $key, $params[0], $retina_options ); 88 | $this->picture = $params['picture']; 89 | $this->bg = $params['bg']; 90 | $this->bg_retina = $params['bg_retina']; 91 | $this->srcset = $params['srcset']; 92 | $this->sizes = $params['sizes']; 93 | $this->retina_options = $retina_options; 94 | 95 | if ( ! empty( $this->retina_options ) && empty( $this->bg_retina ) ) { 96 | if ( empty( $this->bg ) ) { 97 | $this->bg_retina = '@media {dpr}, {min_res}'; 98 | } else { 99 | preg_match( '%\(\b(max-width.*?|min-width.*?)\b\)%', $this->bg, $bg_media_size ); 100 | $pattern = str_replace( $bg_media_size[0], "{$bg_media_size[0]} and {dpr}, {$bg_media_size[0]} and {min_res}", $this->bg ); 101 | // fix old settings and remove "screen and" option from media query. 102 | $this->bg_retina = str_replace( 'screen and ', '', $pattern ); 103 | } 104 | } 105 | 106 | // save to global. 107 | global $rwd_image_options; 108 | $rwd_image_options[ $key ] = $this; 109 | } 110 | } -------------------------------------------------------------------------------- /models/RwdSet.php: -------------------------------------------------------------------------------- 1 | key = $this->clean_key( $key ); 52 | $this->parse_retina_options( $key ); 53 | 54 | // this means we doesn't have any responsive options (for example this is a small thumbnail). 55 | if ( 1 === count( $params ) ) { 56 | $this->size = new ImageSize( $this->key, array_shift( $params ), $this->retina_options ); 57 | $this->options[ $this->key ] = new RwdOption( $this->key, array( 58 | array( $this->size->w, $this->size->h, $this->size->crop ), 59 | 'picture' => '{alt}', 60 | 'bg' => '', 61 | 'srcset' => '{w}w', 62 | 'sizes' => '{w}px', 63 | ), $this->retina_options); 64 | } else { 65 | $this->parse_options( $params ); 66 | } 67 | 68 | // save to global. 69 | global $rwd_image_sizes; 70 | $rwd_image_sizes[ $this->key ] = $this; 71 | } 72 | 73 | /** 74 | * Parse argument to create RwdOption objects. 75 | * 76 | * @param array $params Set intiail params passed in constructor. 77 | * 78 | * @throws \Exception Using unregistered preset. 79 | */ 80 | public function parse_options( $params ) { 81 | global $rwd_image_options; 82 | 83 | foreach ( $params as $subkey => $conf ) { 84 | // If we have numeric key, that means we set dimension for the Set itself. 85 | // If we find numeric index not first time - this is error in config, however we just add numeric suffix and continue. 86 | if ( is_numeric( $subkey ) && 0 === $subkey ) { 87 | $subkey = $this->key; 88 | } 89 | 90 | // Check if we have inherit property. 91 | if ( is_string( $conf ) ) { 92 | if ( isset( $rwd_image_options[ $subkey ] ) ) { 93 | $this->options[ $subkey ] = $rwd_image_options[ $subkey ]; 94 | } else { 95 | throw new \Exception( "RwdSet::__construct() : Using not registered preset '$subkey' in '$this->key'" ); 96 | } 97 | } else { 98 | $nested_key = ( $this->key === $subkey ) ? $this->key : "$this->key-$subkey"; 99 | $this->options[ $subkey ] = new RwdOption( $nested_key, $conf, $this->retina_options ); 100 | } 101 | } 102 | 103 | // get first option and take size object to save it as Set size. 104 | $first = reset( $this->options ); 105 | $this->size = new ImageSize( $this->key, array( $first->size->w, $first->size->h, $first->size->crop ), $this->retina_options ); 106 | } 107 | 108 | /** 109 | * Parse argument to create Retina array options size. 110 | * 111 | * @param string $key Base image size key. 112 | */ 113 | public function parse_retina_options( $key ) { 114 | // generate retina keys array. 115 | preg_match_all( '/(\s([0-9.]+x))/', $key, $retina_parse ); 116 | if ( ! empty( $retina_parse[2] ) ) { 117 | foreach ( $retina_parse[2] as $descriptor ) { 118 | $multiplier = floatval( $descriptor ); 119 | if ( 0 < $multiplier ) { 120 | $this->retina_options[ $descriptor ] = $multiplier; 121 | } 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Clean image size key without retina sizes. 128 | * 129 | * @param string $key Base image size key. 130 | * 131 | * @return string Image size key. 132 | */ 133 | public function clean_key( $key ) { 134 | $clean_key = preg_replace( '/(\s[0-9.]+x)/', null , $key ); 135 | return trim( $clean_key ); 136 | } 137 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Just Responsive Images 2 | ### WordPress plugin 3 | 4 | 5 | The Just Responsive Images plugin gives you control of responsive image properties, which WordPress 4.4+ 6 | inserts to all post thumbnails by default. 7 | 8 | The default solution is to insert all available image sizes as srcset attribute into img tag. 9 | This is not optimal, because the browser gets too much image resolutions, it can generate more requests to 10 | the server (to get the right image) and it takes longer to display the image itself. Not to mention, Google 11 | Page Speed inspector is not satisfied with such a method. 12 | 13 | If you have hand-coded a mobile-friendly HTML/CSS for your theme it usually has media queries for background 14 | images and \ tags instead of \ tags. A lot of images are used as block backgrounds very often, 15 | which should be editable from CMS. All these best practices are not supported in the WordPress core by default 16 | and you end up wasting your time re-writing standard functions. 17 | 18 | That's why we're here! We want to provide easy-to-use control for customizing srcset for each image size 19 | dimension you use inside your theme. Also we're happy to provide you with a few helpers which will generate 20 | tags and generate required media queries for backgrounds. 21 | 22 | 23 | ## Installation 24 | 25 | 1. Download, unzip and upload to your WordPress plugins directory 26 | 2. Activate the plugin within you WordPress Administration Backend 27 | 28 | ## Register configuration hook 29 | 30 | To set configuration options you should create a new function and register a new filter hook `rwd_image_sizes` inside your `functions.php` file: 31 | 32 | add_filter('rwd_image_sizes', 'my_rwd_image_sizes'); 33 | function my_rwd_image_sizes( $image_sizes ) { 34 | $my_image_sizes = array( ... ); 35 | return $my_image_sizes; 36 | } 37 | 38 | If you have complex sizes with a lot of different image sizes - we recommend using a separate file to store settings. 39 | In this case, load function will look like this: 40 | 41 | add_filter('rwd_image_sizes', 'my_rwd_image_sizes'); 42 | function my_rwd_image_sizes( $image_sizes ) { 43 | return include get_stylesheet_directory() . '/rwd-image-sizes.php'; 44 | } 45 | 46 | After that you need to create a new file called `rwd-image-sizes.php` and set a configuration array there. 47 | 48 | ## Configuration array 49 | 50 | Here comes the most important part, you need to configure it very carefully. I will try to explain how to create 51 | this array step-by-step. 52 | 53 | Configuration array is an associative multidimensional array. 54 | 55 | _*All examples is based on a second option of configuration settings - as a separate configuration file._ 56 | 57 | #### Main image sizes 58 | 59 | The first level indicates the main image sizes, which should be registered with the Wordpress `add_image_size()` 60 | function. We recommend re-writing standard sizes as well, like thumbnail, medium, and large. 61 | Each main size should be an array as well, so it looks like this: 62 | 63 | 64 | array( ... ), 67 | 'medium' => array( ... ), 68 | 'large' => array( ... ), 69 | 'hd' => array( ... ), 70 | ); 71 | 72 | In this example we overwrite the 3 standard WordPress sizes: thumbnail, medium and large, 73 | and register a new one called 'hd'. 74 | 75 | #### Single image size 76 | 77 | For small images you don't need to add any responsive tags, because they are smaller than any screen size 78 | (even mobile). For example, you have post featured image displayed on a blog listing in a 250 x 200 size . 79 | 80 | In this case you should specify the size array under your main image size: 81 | 82 | array( 85 | array( 250, 200, true ), // single size, dimensions set here 86 | ), 87 | ... 88 | ); 89 | 90 | The dimensions array looks the same as parameters passed to the WordPress `add_image_size()` function. function. So they are: 91 | 92 | * width, integer 93 | * height, integer 94 | * crop, true|false|array, which set crop position 95 | 96 | #### Nested sizes 97 | 98 | Let's imagine a more complex example. You have a full-width visual image at the top of your single post. 99 | For desktop versions you will use a 1920px or even 2400px image. However, if you use a phone to browse the site, 100 | the user doesn't like to wait while such big images load because what he sees is 320-400px on his screen. 101 | So we need to set, that for big visual images we should use smaller versions on smaller resolutions. 102 | 103 | Here we will use a more complex configuration. Now all arrays inside the main size will be much more complex 104 | and will have a special structure like this: 105 | 106 | array( 109 | array( 110 | array( 1920, 500, true ), 111 | // responsive options: 112 | ... 113 | ), 114 | 'tablet' => array( 115 | array( 1024, 300, true ), 116 | // responsive options: 117 | ... 118 | ), 119 | 'mobile' => array( 120 | array( 414, 200, true ), 121 | // responsive options: 122 | ... 123 | ), 124 | ), 125 | ); 126 | 127 | As you can see here we registered a new image size "visual" with the size of 1920x500px, and it has lower resolutions 128 | which has it’s own name: 129 | 130 | * 1024x300 (the full qualified name will be "visual-tablet") 131 | * 414x200 (the full qualified name will be "visual-mobile") 132 | 133 | In addition, each size has to have responsive options. 134 | 135 | #### Responsive options 136 | 137 | These are options, which will be used to generate \, \ or background HTML/CSS. 138 | 139 | Let's start with an example: 140 | 141 | return array( 142 | 'visual' => array( 143 | array( 144 | array( 1920, 500, true ), 145 | 'picture' => '', 146 | 'bg' => '', // main image, no media wrapper will be used. 147 | 'srcset' => '1920w', // descriptor 148 | 'sizes' => '(min-width: 1281px) 1920px', // condition 149 | ), 150 | 'tablet' => array( 151 | array( 980, 9999 ), 152 | 'picture' => '{alt}',, 153 | 'bg' => '@media screen and (max-width:980px)', 154 | 'srcset' => '980w', 155 | 'sizes' => '(min-width: 415px) 980px', 156 | ), 157 | ), 158 | ); 159 | 160 | For each image size you can specify any of the following 4 keys: 161 | 162 | * `picture` - a code part to generate \ or \ tags inside a \ tag. 163 | * `bg` - media query to be used to wrap the css selector. Keep blank to set default image here. 164 | * `srcset` - \ attribute part - width descriptor for this image size. 165 | * `sizes` - \ attribute part - condition when current dimension should be visible. 166 | 167 | If you use only one way to print your image (for example \ tag) - you can skip all other parts and 168 | set only 1 key `picture` inside responsive options. 169 | 170 | #### Re-usable sizes 171 | 172 | Some images can have similar small sizes inside. In this case you can use previously defined keys inside other 173 | main sizes. Imagine that we already have the code for "visual" image size, as shown above, then we can re-use 174 | it like this: 175 | 176 | return array( 177 | 'visual' => array( ... ), 178 | 'big-banner' => array( 179 | array( 180 | array( 2400, 500, true ), 181 | 'picture' => '', 182 | 'bg' => '', // main image, no media wrapper will be used. 183 | 'srcset' => '1920w', // descriptor 184 | 'sizes' => '(min-width: 1281px) 1920px', // condition 185 | ), 186 | 'visual-tablet' => 'inherit', 187 | ), 188 | ); 189 | 190 | #### Retina support 191 | 192 | To support retina screens and print both usual and bigger images 193 | you should add retina multiplier to your size name, separated with a space: 194 | 195 | return array( 196 | 'visual 2x' => array( ... ), 197 | ); 198 | 199 | You can add more different multipliers for same size to generate even more retina sizes: 200 | 201 | return array( 202 | 'visual 2x 3x' => array( ... ), 203 | ); 204 | 205 | However we recommend to use one retina size - `2x`, this size provides good visual image quality on screen and keep your file space less 206 | (comparing to setting several retina multipliers). 207 | 208 | ##### Retina background @media queries 209 | 210 | By default plugin search such patterns inside `'bg'` property: 211 | 212 | (min-width: XXXpx) or (max-width: XXXpx) 213 | 214 | If such entries found, then plugin replace them with the structure below to generate @media retina query: 215 | 216 | (min-width: XXXpx) and , (min-width: XXXpx) and 217 | 218 | If you want to set your own specific media query for retina size you can use `'bg_retina'` property like this: 219 | 220 | return array( 221 | 'big-banner 2x' => array( 222 | array( 223 | array( 2400, 500, true ), 224 | ... 225 | 'bg' => '@media (min-width:1281px)', 226 | 'bg_retina' => '@media (min-width:1281px) and {dpr}, (min-width:1281px) and {min_res}', 227 | ... 228 | ), 229 | ... 230 | ), 231 | ); 232 | 233 | Special tokens `{dpr}` and `{min_res}` will be automatically replaced with a corresponding min device pixel ratio 234 | and min resolution values based on retina multiplier (2x or 3x). 235 | 236 | #### Pre-defined RWD set 237 | 238 | The plugin has its own set of rwd styles, which are optimal for big images display and resize them to smaller 239 | resolutions, which passed Google Page Speed tests well. This set has retina 2x support by default. 240 | 241 | It looks like this: 242 | 243 | return array( 244 | 'rwd 2x' => array( 245 | 'desktop' => array( 246 | array( 1920, 9999 ), 247 | 'picture' => '', 248 | 'bg' => '@media (min-width:1281px)', 249 | 'bg_retina' => '@media (min-width:1281px) and {dpr}, (min-width:1281px) and {min_res}', 250 | 'srcset' => '{w}w', 251 | 'sizes' => '(min-width: 1281px) {w}px', 252 | ), 253 | 'laptop' => array( 254 | array( 1280, 9999 ), 255 | 'picture' => '', 256 | 'bg' => '@media (min-width: 981px) ', 257 | 'bg_retina' => '@media (min-width: 981px) and {dpr}, (min-width: 981px) and {min_res}', 258 | 'srcset' => '{w}w', 259 | 'sizes' => '(min-width: 981px) {w}px', 260 | ), 261 | 'tablet' => array( 262 | array( 980, 9999 ), 263 | 'picture' => '', 264 | 'bg' => '@media (min-width: 415px)', 265 | 'bg_retina' => '@media (min-width: 415px) and {dpr}, (min-width: 415px) and {min_res}', 266 | 'srcset' => '{w}w', 267 | 'sizes' => '(min-width: 415px) {w}px', 268 | ), 269 | 'mobile' => array( 270 | array( 414, 9999 ), 271 | 'picture' => '{alt}', // mobile-first strategy picture img. 272 | 'bg' => '', // mobile-first strategy bg. 273 | 'bg_retina' => '@media {dpr}, {min_res}', 274 | 'srcset' => '{w}w', 275 | 'sizes' => '{w}px', 276 | ), 277 | ), 278 | ); 279 | 280 | You can use these presets inside your own styles. 281 | 282 | ## Template functions 283 | 284 | We have 3 new template functions: 285 | 286 | ### rwd_attachment_image 287 | 288 | `rwd_attachment_image( $attachment = null, $size = 'thumbnail', $tag = 'picture', $attr = array() )` 289 | 290 | - WP_Post|int|null **$attachment** Atatchment object, Attachment ID or null. In case of null the function will search current post featured image. 291 | - string|array **$size** Image size name or array of sizes. We will check this option in more details below. 292 | - string **$tag** Available options are 'picture' and 'img'. The html tag, which will be used for image generation. 293 | - array **$attr** Additional html attributes to be used for main tag (for example "class", "id", etc.). 294 | 295 | **$size** option as array: 296 | 297 | Sometimes mobile images are different from desktop images and there is a need to use another attachment image for 298 | smaller sizes. To support this feature you can pass array here and specify which size use another attachment ID 299 | or object. 300 | 301 | Let's check the example: 302 | 303 | rwd_attachment_image( $featured_image_id, array( 304 | 'visual', // main size, which should be rendered; it should has a zero key! 305 | 'mobile' => $mobile_image_id, // rewrite a key under 'visual' main size to use another image 306 | )); 307 | 308 | **Important**: If you use retina multipliers we do not recommend to use `'img'` tag with sized array. 309 | In this case browser will automatically decide which image will be renedered and you won't get the same 310 | images on specific resolution in all browsers/devices. 311 | 312 | 313 | ### rwd_attachment_background 314 | 315 | `rwd_attachment_background( $selector, $attachment = null, $size = 'thumbnail' )` 316 | 317 | - string **selector** CSS selector to be used inside inline styles code. 318 | - WP_Post|int|null **$attachment** Atatchment object, Attachment ID or null. In case of null the function will search current post featured image. 319 | - string|array **$size** Image size name or array of sizes. Same as for `rwd_attachment_image()`. 320 | 321 | This function generate css styles and place them inside "buffer". This buffer will be printed in wp_footer(). 322 | 323 | If you want to print it earlier you can use next template function: 324 | 325 | ### rwd_print_styles 326 | 327 | `rwd_print_styles()` 328 | 329 | This function print styles from buffer, which were generated in `rwd_attachment_background()`. 330 | 331 | ## Generated code samples 332 | 333 | It can generate code similar to this one: 334 | 335 | **picture** 336 | 337 | 338 | 339 | 340 | 341 | test 342 | 343 | 344 | **img** 345 | 346 | ... 355 | 356 | **background** 357 | 358 | 389 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Just Responsive Images === 2 | Contributors: aprokopenko 3 | Plugin Name: Just Responsive Images 4 | Description: Providing full control to set your own responsive image properties for WordPress 4.4+, the ability to use the <picture> tag, auto-generate image backgrounds and supports retina images. 5 | Tags: responsive post thumbnail, post thumbnail as background, retina support, retina image, retina post thumbnail, responsive post attachment, responsive images, responsive attachments, post thumbnails, media 6 | Author: JustCoded / Alex Prokopenko 7 | Author URI: http://justcoded.com/ 8 | Requires at least: 4.5 9 | Tested up to: 5.8 10 | Requires PHP: >=5.6 11 | License: GPL3 12 | Stable tag: trunk 13 | 14 | Providing full control to set your own responsive image properties for WP 4.4+, the ability to use the <picture> tag and image backgrounds. 15 | 16 | == Description == 17 | 18 | The Just Responsive Images plugin gives you control of responsive image properties, which WordPress 4.4+ inserts to all post thumbnails by default. 19 | 20 | The default solution is to insert all available image sizes as srcset attribute into img tag. This is not optimal, because the browser gets too much image resolutions, it can generate more requests to the server (to get the right image) and it takes longer to display the image itself. Not to mention, Google Page Speed inspector is not satisfied with such a method. 21 | 22 | If you have hand-coded a mobile-friendly HTML/CSS for your theme it usually has media queries for background images and <picture> tags instead of <img> tags. A lot of images are used as block backgrounds very often, which should be editable from CMS. All these best practices are not supported in the WordPress core by default and you end up wasting your time re-writing standard functions. 23 | 24 | That's why we're here! We want to provide easy-to-use control for customizing srcset for each image size dimension you use inside your theme. Also we're happy to provide you with a few helpers which will generate tags and generate required media queries for backgrounds. 25 | 26 | 27 | Full documentation and configuration options are available on our github page: 28 | [https://github.com/justcoded/just-responsive-images](https://github.com/justcoded/just-responsive-images). 29 | 30 | Feel free to post your suggestions or bugs under Issues section on github. 31 | 32 | = Generating image sizes on request = 33 | 34 | This plugin DOES NOT register image sizes with WordPress `add_image_size` function. So they are not resized on upload in admin panel. This is done because on big sites there are too much image sizes (especially with enabled retina option) and in this case WordPress wastes the disk space and database with useless files. 35 | 36 | Our plugin resize images ONLY when you open the page with an image, printed with rwd functions. Resize is performed through WordPress built in functions, so once resized - WordPress will have information about new sizes available for your image. **Unfortunately it's impact the site speed, when you load the page with images by a first time.** Any good copywriter/administrator checks his content before publishing, so this delay will be seen by admin users in most cases. 37 | 38 | = IMPORTANT = 39 | 40 | In version 1.2 default RWD set background options updated to mobile-first strategy (from desktop-first). 41 | If you use nested rules from RWD set you should update your main size background option to have @media query with `min-width` rule. 42 | 43 | = DEV Mode = 44 | 45 | In DEV mode plugin does not resize any images and simply use placeholder images. This can be used to reduce disk space, while you develop and configure our plugin to match all required screen sizes. 46 | 47 | To enable it you need to define new constant in your wp-config.php file: 48 | 49 | `define( 'JRI_DUMMY_IMAGE', true );` 50 | 51 | == Installation == 52 | 53 | 1. Download, unzip and upload to your WordPress plugins directory 54 | 2. Activate the plugin within you WordPress Administration Backend 55 | 3. Add hook `add_filter('rwd_image_sizes', 'my_rwd_image_sizes');` 56 | 4. Create new function `my_rwd_image_sizes` and set the configuration array. The example can be found inside the plugin folder in `/data/config-example.php`. 57 | 58 | == Screenshots == 59 | 60 | 1. Responsive image sizes declaration/config. 61 | 2. Template basic usage example. 62 | 3. Generated HTML. 63 | 64 | == Upgrade Notice == 65 | 66 | = Version 1.4 = 67 | By default plugin doesn't require any special requirements through upgrade. 68 | 69 | However if you want to clean up your disk from unused images/image sizes you need to complete several steps: 70 | 71 | BEFORE UPGRADE: 72 | - Install plugins [Image Cleanup](https://wordpress.org/plugins/image-cleanup/) and [Regenerate Thumbnails](https://wordpress.org/plugins/regenerate-thumbnails/) 73 | - Backup your Database and `wp-content/uploads` folder! 74 | - Go to Dashboard > Tools > Uploads Cleanup (this page is provided by Just Responsive Images plugin) 75 | - Press "Remove cropped/resized images" - this will clean up your disk from all intermediate image files. 76 | - Now activate "Image Cleanup" plugin and open Dashboard > Tools > Image Cleanup. 77 | - Press "Index Images" button. 78 | - You should see a filter called "Invalid Attachment Meta (XXX)" after indexing, click on it. 79 | - Now select all rows, choose "Delete, Except [Full]" and press "Apply". If you have pagination available - repeat this action for all pages. 80 | - Now you should upgrade Just Responsive Images plugin 81 | 82 | AFTER UPGRADE: 83 | - After upgrade the only registered image sizes will be `thumbnail`, `medium` and `large` and your theme specific if there are some. After "Uploads cleanup" we don't have any resized images on disk, so Media library will show missing images. 84 | - To fix missing images in Media Library - use Regenerate Thumbnails plugin. 85 | - Now you can open your site and Just Responsive Images will generate only required sizes. 86 | - We recommend to click through the most important pages of your site to generate required images and do not annoy your visitors with long delay in page load. 87 | 88 | = Version 1.0 - 1.3 = 89 | There are no any special upgrade instructions for version 1.0 - 1.3 90 | To upgrade remove the old plugin folder. After than follow the installation steps 1-2. 91 | 92 | == Changelog == 93 | 94 | = Version 1.6.7 - 6 September 2021 = 95 | * PHP 8 support 96 | = Version 1.6.6 - 16 November 2020 = 97 | * PHP 7.4 support 98 | = Version 1.6.5 - 5 March 2019 = 99 | * Fixed retina size, which match image original size. 100 | = Version 1.6.4 - 25 January 2019 = 101 | * Fixed always resize for retina if 2x is bigger than original image. 102 | * Add ability to set default tag for rwd_attachment_image() with a filter. 103 | = Version 1.6.3 - 9 August 2018 = 104 | * Fix background usage if no other options specified. 105 | = Version 1.6.2 - 3 July 2018 = 106 | * Fix dev mode dummy images with sizes set in DB but missing on file system. 107 | = Version 1.6.1 - 28 June 2018 = 108 | * Added attachment ID text to dev mode dummy images. 109 | = Version 1.6.0 - 27 June 2018 = 110 | * Dev mode with placeholder images instead of real images in case correct sizes are missing. 111 | = Version 1.5.1 - 3 April 2018 = 112 | * Added compatibility with Crop Images plugin 113 | * Fix main editor content responsive images (it was broken after some WP update) 114 | = Version 1.5 - 15 March 2018 = 115 | * Added compatibility with Just Image Optimizer plugin 116 | = Version 1.4.1 - 9 March 2018 = 117 | * Bug fix: Wrong image source taken when image crop enabled and original width/height is the same and required height/width are less than origin. 118 | = Version 1.4 - 7 March 2018 = 119 | * Upgrate: Changed image size generation to generate images on request from site frontend. This impact site speed on first page request! 120 | * Update: Add alt to svg and set default class 121 | = Version 1.3 - 16 January 2018 = 122 | * Update: If media is SVG - then we always print with hard-coded width/height attributes from configuration array 123 | = Version 1.2.1 - 12 September 2017 = 124 | * Bug fix: Small images generate warning in featured image box 125 | = Version 1.2 - 12 July 2017 = 126 | * New: True retina support 127 | * Bug fix: responsive image with "img" tag does not work in different IE/Edge versions 128 | = Version 1.1.4 - 4 May 2017 = 129 | * New: SVG images support (If user used SVG instead of usual image - it will be printed) 130 | = Version 1.1.3 - 25 April 2017 = 131 | * Bug fix: responsive image with "img" tag does not work in IE11 132 | = Version 1.1.2 - 14 April 2017 = 133 | * Bug fix: responsive image with "img" tag does not work in IE 134 | = Version 1.1.1 - 31 March 2017 = 135 | * New: Print available image sizes on Media attachment edit screen. Useful for debugging. 136 | = Version 1.1 - 24 March 2017 = 137 | * New: Ability to clean up all resized images from disk. To use go to Tools > Uploads Cleanup. 138 | = Version 1.0.8 - 22 March 2017 = 139 | * Bug fix: Background main styles were printed after media queries. 140 | = Version 1.0.7 - 16 March 2017 = 141 | * Bug fix: Post can be passed instead of attachment (now can't fixed) 142 | * Bug fix: Wrong image is taken as default if main image is not a featured image and mobile image missing (now default is correct) 143 | = Version 1.0.6 = 144 | * Code refactoring (basically renaming files and class names) 145 | = Version 1.0.5 = 146 | * Critical fix: Non-inherit settings were ignored during html generation with "Missing image size" warning. 147 | = Version 1.0.4 = 148 | * Bug fix: Added "src" to tag (Google Chrome has a bug with max-width css property without "src" attribute). 149 | = Version 1.0.3 = 150 | * Bug fix: Fixed options which passed 'wp_get_attachment_image_attributes' filter. Some plugins use keys, which were missing in previous version. 151 | = Version 1.0.2 = 152 | * New: Added parameter to pass additional html attributes into `rwd_attachment_image()`. 153 | = Version 1.0.1 = 154 | * Bug fix: Single size option return empty result 155 | * Improvement: Smaller images now display lower resolution size even for big screens. 156 | = Version 1.0 = 157 | * First version of our plugin. 158 | -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-responsive-images/741503dc5f9f427abb08bcd7ec56d9325bf4d105/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-responsive-images/741503dc5f9f427abb08bcd7ec56d9325bf4d105/screenshot-2.png -------------------------------------------------------------------------------- /screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoded/just-responsive-images/741503dc5f9f427abb08bcd7ec56d9325bf4d105/screenshot-3.png -------------------------------------------------------------------------------- /views/cleanup/index.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |

Uploads Cleanup

6 | 7 | 8 |
9 |

Warning! We strongly recommend to make the backup of your uploads folder before doing any actions on this page.

10 |
11 | 12 |
13 |

Operation completed.

14 |
15 | 16 | 17 |

This page can help you to remove all cropped and resized images from uploads folder.

18 |

This is useful in development phase, when you set different image sizes and regenerate them often.

19 |

After cleanup you will need to regenerate your thumbnails one more time! 20 | Plugins similar to Regenerate thumbnails can help with this.

21 |

The script will affect only filesystem and doesn't affect database at all.

22 | 23 |
24 | 25 | 26 | 27 | 30 | 31 |
32 | 33 | 34 |

Results

35 |

36 | 37 |

38 | 39 |
-------------------------------------------------------------------------------- /views/media/meta-box.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | $params ) : ?> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 |
Full size px 9 | 10 | ( 11 | ) 12 | 13 |
Additional sizes
px
No additional sizes.
30 | It seems you didn't configure your image sizes correctly. 31 | You should configure responsive image sizes and regenerate thumnbails after that. 32 | 33 |
37 | --------------------------------------------------------------------------------