├── assets ├── banner-1544x500.png ├── banner-772x250.png └── icon.svg ├── tests ├── data │ └── test-large.png ├── bootstrap.php └── test-suite.php ├── .linthub.yml ├── phpunit.xml ├── composer.json ├── .jshintrc ├── .editorconfig ├── .gitattributes ├── .travis.yml ├── wp-tevko-responsive-images.php ├── ruleset.xml ├── .gitignore ├── bin └── install-wp-tests.sh ├── class-wp-image-editor-respimg.php ├── readme.txt ├── class-respimg.php ├── js └── picturefill.min.js ├── wp-tevko-deprecated-functions.php ├── wp-tevko-core-functions.php └── readme.md /assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResponsiveImagesCG/wp-tevko-responsive-images/HEAD/assets/banner-1544x500.png -------------------------------------------------------------------------------- /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResponsiveImagesCG/wp-tevko-responsive-images/HEAD/assets/banner-772x250.png -------------------------------------------------------------------------------- /tests/data/test-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResponsiveImagesCG/wp-tevko-responsive-images/HEAD/tests/data/test-large.png -------------------------------------------------------------------------------- /.linthub.yml: -------------------------------------------------------------------------------- 1 | go: 2 | lint: false 3 | php: 4 | lint: true 5 | config: ruleset.xml 6 | java: 7 | lint: false 8 | scala: 9 | lint: false 10 | shell: 11 | lint: false 12 | css: 13 | lint: true 14 | js: 15 | lint: jshint # jshint (default) | eslint | false 16 | config: .jshintrc -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "ResponsiveImagesCG/wp-tevko-responsive-images", 3 | "description": "Fully responsive image plugin for WordPress.", 4 | "homepage" : "https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images", 5 | "type" : "wordpress-plugin", 6 | "license" : "GPL-2.0+", 7 | "require" : { 8 | "composer/installers": "~1.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "es3": true, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "onevar": true, 11 | "quotmark": "single", 12 | "trailing": true, 13 | "undef": true, 14 | "unused": true, 15 | 16 | "browser": true, 17 | 18 | "globals": { 19 | "_": false, 20 | "Backbone": false, 21 | "jQuery": false, 22 | "JSON": false, 23 | "wp": false 24 | } 25 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [{.jshintrc,*.json,*.yml}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [{*.txt,wp-config-sample.php}] 21 | end_of_line = crlf 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.2 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | - 5.6 9 | - 7.0 10 | 11 | env: 12 | - WP_VERSION=trunk WP_MULTISITE=0 13 | - WP_VERSION=trunk WP_MULTISITE=1 14 | - WP_VERSION=4.3 WP_MULTISITE=0 15 | - WP_VERSION=4.3 WP_MULTISITE=1 16 | - WP_VERSION=4.2 WP_MULTISITE=0 17 | - WP_VERSION=4.2 WP_MULTISITE=1 18 | - WP_VERSION=4.1 WP_MULTISITE=0 19 | - WP_VERSION=4.1 WP_MULTISITE=1 20 | - WP_VERSION=4.0 WP_MULTISITE=0 21 | - WP_VERSION=4.0 WP_MULTISITE=1 22 | 23 | allow_failures: 24 | - php: 7.0 25 | 26 | fast_finish: true 27 | 28 | before_script: 29 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 30 | 31 | script: phpunit 32 | 33 | notifications: 34 | email: false 35 | 36 | sudo: false 37 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /wp-tevko-responsive-images.php: -------------------------------------------------------------------------------- 1 | ID, $size ); 77 | $sizes = wp_get_attachment_image_sizes( $attachment->ID, $size ); 78 | 79 | if ( $srcset && $sizes ) { 80 | $attr['srcset'] = $srcset; 81 | 82 | if ( empty( $attr['sizes'] ) ) { 83 | $attr['sizes'] = $sizes; 84 | } 85 | } 86 | } 87 | 88 | return $attr; 89 | } 90 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Non-controversial generally-agreed upon WordPress Coding Standards 4 | 5 | 6 | 7 | 8 | 9 | 0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 71 | 72 | 0 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .classpath 15 | .settings/ 16 | .loadpath 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # PDT-specific 28 | .buildpath 29 | 30 | 31 | ################# 32 | ## Visual Studio 33 | ################# 34 | 35 | ## Ignore Visual Studio temporary files, build results, and 36 | ## files generated by popular Visual Studio add-ons. 37 | 38 | # User-specific files 39 | *.suo 40 | *.user 41 | *.sln.docstates 42 | 43 | # Build results 44 | 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | x64/ 48 | build/ 49 | [Bb]in/ 50 | [Oo]bj/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | *_i.c 57 | *_p.c 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.log 78 | *.scc 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | *.ncrunch* 108 | .*crunch*.local.xml 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.Publish.xml 128 | *.pubxml 129 | 130 | # NuGet Packages Directory 131 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 132 | #packages/ 133 | 134 | # Windows Azure Build Output 135 | csx 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.[Pp]ublish.xml 150 | *.pfx 151 | *.publishsettings 152 | 153 | # RIA/Silverlight projects 154 | Generated_Code/ 155 | 156 | # Backup & report files from converting an old project file to a newer 157 | # Visual Studio version. Backup files are not needed, because we have git ;-) 158 | _UpgradeReport_Files/ 159 | Backup*/ 160 | UpgradeLog*.XML 161 | UpgradeLog*.htm 162 | 163 | # SQL Server files 164 | App_Data/*.mdf 165 | App_Data/*.ldf 166 | 167 | ############# 168 | ## Windows detritus 169 | ############# 170 | 171 | # Windows image file caches 172 | Thumbs.db 173 | ehthumbs.db 174 | 175 | # Folder config file 176 | Desktop.ini 177 | 178 | # Recycle Bin used on file shares 179 | $RECYCLE.BIN/ 180 | 181 | # Mac crap 182 | .DS_Store 183 | 184 | 185 | ############# 186 | ## Python 187 | ############# 188 | 189 | *.py[co] 190 | 191 | # Packages 192 | *.egg 193 | *.egg-info 194 | dist/ 195 | build/ 196 | eggs/ 197 | parts/ 198 | var/ 199 | sdist/ 200 | develop-eggs/ 201 | .installed.cfg 202 | 203 | # Installer logs 204 | pip-log.txt 205 | 206 | # Unit test / coverage reports 207 | .coverage 208 | .tox 209 | 210 | #Translations 211 | *.mo 212 | 213 | #Mr Developer 214 | .mr.developer.cfg 215 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | 14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 15 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 16 | 17 | download() { 18 | if [ `which curl` ]; then 19 | curl -s "$1" > "$2"; 20 | elif [ `which wget` ]; then 21 | wget -nv -O "$2" "$1" 22 | fi 23 | } 24 | 25 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 26 | WP_TESTS_TAG="tags/$WP_VERSION" 27 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 28 | WP_TESTS_TAG="trunk" 29 | else 30 | # http serves a single offer, whereas https serves multiple. we only want one 31 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 32 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 33 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 34 | if [[ -z "$LATEST_VERSION" ]]; then 35 | echo "Latest WordPress version could not be found" 36 | exit 1 37 | fi 38 | WP_TESTS_TAG="tags/$LATEST_VERSION" 39 | fi 40 | 41 | set -ex 42 | 43 | install_wp() { 44 | 45 | if [ -d $WP_CORE_DIR ]; then 46 | return; 47 | fi 48 | 49 | mkdir -p $WP_CORE_DIR 50 | 51 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 52 | mkdir -p /tmp/wordpress-nightly 53 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip 54 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ 55 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR 56 | else 57 | if [ $WP_VERSION == 'latest' ]; then 58 | local ARCHIVE_NAME='latest' 59 | else 60 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 61 | fi 62 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 63 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 64 | fi 65 | 66 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 67 | } 68 | 69 | install_test_suite() { 70 | # portable in-place argument for both GNU sed and Mac OSX sed 71 | if [[ $(uname -s) == 'Darwin' ]]; then 72 | local ioption='-i .bak' 73 | else 74 | local ioption='-i' 75 | fi 76 | 77 | # set up testing suite if it doesn't yet exist 78 | if [ ! -d $WP_TESTS_DIR ]; then 79 | # set up testing suite 80 | mkdir -p $WP_TESTS_DIR 81 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 82 | fi 83 | 84 | cd $WP_TESTS_DIR 85 | 86 | if [ ! -f wp-tests-config.php ]; then 87 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 88 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php 89 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 90 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 91 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 92 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 93 | fi 94 | 95 | } 96 | 97 | install_db() { 98 | # parse DB_HOST for port or socket references 99 | local PARTS=(${DB_HOST//\:/ }) 100 | local DB_HOSTNAME=${PARTS[0]}; 101 | local DB_SOCK_OR_PORT=${PARTS[1]}; 102 | local EXTRA="" 103 | 104 | if ! [ -z $DB_HOSTNAME ] ; then 105 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 106 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 107 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 108 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 109 | elif ! [ -z $DB_HOSTNAME ] ; then 110 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 111 | fi 112 | fi 113 | 114 | # create database 115 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 116 | } 117 | 118 | install_wp 119 | install_test_suite 120 | install_db 121 | -------------------------------------------------------------------------------- /class-wp-image-editor-respimg.php: -------------------------------------------------------------------------------- 1 | file into new Respimg Object. 23 | * 24 | * @access protected 25 | * 26 | * @return boolean|WP_Error True if loaded; WP_Error on failure. 27 | */ 28 | public function load() { 29 | if ( $this->image instanceof Respimg ) { 30 | return true; 31 | } 32 | 33 | if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) { 34 | return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); 35 | } 36 | 37 | /* 38 | * This filter is documented in wp-includes/class-wp-image-editor-imagick.php 39 | * 40 | * Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits. 41 | */ 42 | @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); 43 | 44 | try { 45 | $this->image = new Respimg( $this->file ); 46 | 47 | if ( ! $this->image->valid() ) { 48 | return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file); 49 | } 50 | // Select the first frame to handle animated images properly. 51 | if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) { 52 | $this->image->setIteratorIndex(0); 53 | } 54 | 55 | $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); 56 | } catch ( Exception $e ) { 57 | return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); 58 | } 59 | 60 | $updated_size = $this->update_size(); 61 | if ( is_wp_error( $updated_size ) ) { 62 | return $updated_size; 63 | } 64 | 65 | return $this->set_quality(); 66 | } 67 | 68 | /** 69 | * Resizes current image. 70 | * 71 | * At minimum, either a height or width must be provided. 72 | * If one of the two is set to null, the resize will 73 | * maintain aspect ratio according to the provided dimension. 74 | * 75 | * @access public 76 | * 77 | * @param int|null $max_w Image width. 78 | * @param int|null $max_h Image height. 79 | * @param boolean $crop 80 | * @return boolean|WP_Error 81 | */ 82 | public function resize( $max_w, $max_h, $crop = false ) { 83 | if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) { 84 | return true; 85 | } 86 | 87 | $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); 88 | if ( ! $dims ) { 89 | return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') ); 90 | } 91 | 92 | list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; 93 | 94 | if ( $crop ) { 95 | return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); 96 | } 97 | 98 | try { 99 | if ($this->mime_type === 'image/gif') { 100 | $this->image->scaleImage( $dst_w, $dst_h ); 101 | } else { 102 | $this->image->smartResize( $dst_w, $dst_h, false ); 103 | } 104 | } 105 | catch ( Exception $e ) { 106 | return new WP_Error( 'image_resize_error', $e->getMessage() ); 107 | } 108 | 109 | return $this->update_size( $dst_w, $dst_h ); 110 | } 111 | 112 | /** 113 | * Resize multiple images from a single source. 114 | * 115 | * @access public 116 | * 117 | * @param array $sizes { 118 | * An array of image size arrays. Default sizes are 'small', 'medium', 'large'. 119 | * 120 | * Either a height or width must be provided. 121 | * If one of the two is set to null, the resize will 122 | * maintain aspect ratio according to the provided dimension. 123 | * 124 | * @type array $size { 125 | * @type int ['width'] Optional. Image width. 126 | * @type int ['height'] Optional. Image height. 127 | * @type bool $crop Optional. Whether to crop the image. Default false. 128 | * } 129 | * } 130 | * @return array An array of resized images' metadata by size. 131 | */ 132 | public function multi_resize( $sizes ) { 133 | $metadata = array(); 134 | $orig_size = $this->size; 135 | $orig_image = clone $this->image; 136 | 137 | foreach ( $sizes as $size => $size_data ) { 138 | if ( ! $this->image ) { 139 | $this->image = clone $orig_image; 140 | } 141 | 142 | if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { 143 | continue; 144 | } 145 | 146 | if ( ! isset( $size_data['width'] ) ) { 147 | $size_data['width'] = null; 148 | } 149 | if ( ! isset( $size_data['height'] ) ) { 150 | $size_data['height'] = null; 151 | } 152 | 153 | if ( ! isset( $size_data['crop'] ) ) { 154 | $size_data['crop'] = false; 155 | } 156 | 157 | $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); 158 | $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) ); 159 | 160 | if ( ! is_wp_error( $resize_result ) && ! $duplicate ) { 161 | $resized = $this->_save( $this->image ); 162 | 163 | $this->image->clear(); 164 | $this->image->destroy(); 165 | $this->image = null; 166 | 167 | if ( ! is_wp_error( $resized ) && $resized ) { 168 | unset( $resized['path'] ); 169 | $metadata[$size] = $resized; 170 | } 171 | } 172 | 173 | $this->size = $orig_size; 174 | } 175 | 176 | $this->image = $orig_image; 177 | 178 | return $metadata; 179 | } 180 | 181 | /** 182 | * Crops Image. 183 | * 184 | * @access public 185 | * 186 | * @param int $src_x The start x position to crop from. 187 | * @param int $src_y The start y position to crop from. 188 | * @param int $src_w The width to crop. 189 | * @param int $src_h The height to crop. 190 | * @param int $dst_w Optional. The destination width. 191 | * @param int $dst_h Optional. The destination height. 192 | * @param boolean $src_abs Optional. If the source crop points are absolute. 193 | * @return boolean|WP_Error 194 | */ 195 | public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { 196 | if ( $src_abs ) { 197 | $src_w -= $src_x; 198 | $src_h -= $src_y; 199 | } 200 | 201 | try { 202 | $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); 203 | $this->image->setImagePage( $src_w, $src_h, 0, 0); 204 | 205 | if ( $dst_w || $dst_h ) { 206 | /* 207 | * If destination width/height isn't specified, use same as 208 | * width/height from source. 209 | */ 210 | if ( ! $dst_w ) { 211 | $dst_w = $src_w; 212 | } 213 | 214 | if ( ! $dst_h ) { 215 | $dst_h = $src_h; 216 | } 217 | 218 | if ($this->mime_type === 'image/gif') { 219 | $this->image->scaleImage( $dst_w, $dst_h ); 220 | } else { 221 | $this->image->smartResize( $dst_w, $dst_h, false ); 222 | } 223 | return $this->update_size(); 224 | } 225 | } catch ( Exception $e ) { 226 | return new WP_Error( 'image_crop_error', $e->getMessage() ); 227 | } 228 | return $this->update_size(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === RICG Responsive Images === 2 | Contributors: tevko, wilto, joemcgill, jaspermdegroot, chriscoyier, Michael McGinnis, ryelle, drrobotnik, nacin, georgestephanis, helen, wordpressdotorg, Bocoup 3 | Donate link: https://app.etapestry.com/hosted/BoweryResidentsCommittee/OnlineDonation.html 4 | Tags: Responsive, Images, Responsive Images, SRCSET, Picturefill 5 | Requires at least: 4.0 6 | Tested up to: 4.4 7 | Stable tag: 3.1.1 8 | License: GPLv2 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.txt 10 | 11 | Bringing automatic default responsive images to WordPress. 12 | 13 | == Description == 14 | 15 | Bringing automatic default responsive images to WordPress. 16 | 17 | This plugin works by including all available image sizes for each image upload. Whenever WordPress outputs the image through the media uploader, or whenever a featured image is generated, those sizes will be included in the image tag via the srcset attribute. 18 | 19 | **Important notes** 20 | 21 | * As of WordPress 4.4, images are responsive by default. If you are on WordPress 4.4 or plan to update, you will not need to install this plugin. 22 | 23 | If you have had this plugin installed since before version 2.5 but are running version 4.4 of WordPress, it is important that you leave the plugin installed. This is because all versions of the plugin before version 2.5 relied on a `data-sizes` attribute being present on an image in order to provide the responsive markup needed. If the plugin in this case is removed, then images in posts will be left with invalid markup. We are working to address this issue, and you can keep track of our progress here at https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/issues/178. 24 | 25 | You can still use the plugin for advanced image compression support or as a simple way to include the picturefill script. The plugin will fall back to WordPress default functions if responsive image support is detected in your installation. 26 | 27 | 28 | * Version 3.1.0 includes important changes that make this plugin compatible with WordPress version 4.4. Upgrading is highly recommended. 29 | 30 | * As of version 2.5.0, the plugin adds `srcset` and `sizes` attributes to images on the front end instead of adding them to the image markup saved in posts. 31 | 32 | **Full documentation and contributor guidelines can be found on [Github](https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images)** 33 | 34 | == Installation == 35 | 36 | 1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory 37 | 2. Activate the plugin through the 'Plugins' menu in WordPress 38 | 3. If you'd like to enable the advanced image compression feature, Please see the instructions at https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/tree/dev#advanced-image-compression 39 | 40 | == Changelog == 41 | 42 | = 3.1.1 = 43 | * Fixes a bug where the srcset of images in imported content was missing or broken. 44 | * Improved calculation of ratio difference for images to be included in the srcset. 45 | * Fixes a bug where `img` tags without ending slash don't get responsive images. 46 | * Deprecates the helper function `tevkori_get_media_embedded_in_content()` which is no longer used. 47 | * Makes sure that the setup of default themes doesn't break the tests. 48 | * Adds more examples to the Hook Reference in readme.md. 49 | * Corrections and improvements to inline documentation. 50 | 51 | = 3.1.0 = 52 | * Adds special handling of GIFs in srcset attributes to preserve animation. 53 | * Makes internal srcset/sizes functions more consistent. 54 | * Fixes a bug where functions hooked into `tevkori_image_sizes_args` were not firing. 55 | * Fixes a bug where custom sizes attributes added via the post editor were being overwritten. 56 | * Deprecates hook `wp_get_attachment_image_sizes`. 57 | * Fixes a bug where `the_post_thumbnail()` would fail to add srcset/sizes attributes. 58 | * Several improvements to internal inline documentation. 59 | * Major improvements to function/hook documentation in readme.md after 3.0.0 changes. 60 | 61 | = 3.0.0 = 62 | * Deprecates all core functions that will be merged into WordPress core in 4.4. 63 | * Adds compatibility shims for sites using the plugin's internal functions and hooks. 64 | * Adds a new display filter callback which can be use as general utility function for adding srcset and sizes attributes. 65 | * Fixes a bug when `wp_get_attachment_metadata()` failed to return an array. 66 | * Update our tests to be compatible with WordPress 4.4 67 | * Upgrade to Picturefill 3.0.1 68 | * Clean up inline docs. 69 | 70 | = 2.5.2 = 71 | * Numerous performance and usability improvements 72 | * Pass height and width to `tevkori_get_sizes()` 73 | * Improved regex in display filter 74 | * Avoid calling `wp_get_attachment_image_src()` in srcset functions 75 | * Improved coding standards 76 | * Removed second regular expression in content filter 77 | * Improved cache warning function 78 | * Change default `$size` value for all functions to 'medium' 79 | 80 | = 2.5.1 = 81 | * Query all images in single request before replacing 82 | * Minor fix to prevent a potential undefined variable notice 83 | * Remove third fallback query from the display filter 84 | 85 | = 2.5.0 = 86 | * Responsify all post images by adding `srcset` and `sizes` through a display filter. 87 | * Improve method used to build paths in `tevkori_get_srcset_array()` 88 | * Added Linthub config files 89 | * Returns single source arrays in `tevkori_get_srcset_array()` 90 | * Add tests for PHP7 to our Travis matrix 91 | * Add test coverage for `tevkori_filter_attachment_image_attributes()` 92 | 93 | = 2.4.0 = 94 | * Added filter for `tevkori_get_sizes`, with tests 95 | * Added Composer support 96 | * Compare aspect ratio in relative values, not absolute values 97 | * Cleanup of code style and comments added 98 | * Added PHP 5.2 to our Travis test matrix 99 | * Fixed unit test loading 100 | * Preventing duplicates in srcset array 101 | * Updated docs for advanced image compression 102 | * Formatting cleanup in readme.md 103 | * Bump plugin 'Tested up to:' value to 4.3 104 | * Remove extra line from readme.txt 105 | * Added changelog items from 2.3.1 to the readme.txt file 106 | * Added 'sudo: false' to travis.ci to use new TravisCI infrastructure 107 | * Removing the srcset and sizes attributes if there is only one source present for the image 108 | * Use edited image hash to filter out originals from edited images 109 | * Make output of `tevkori_get_srcset_array` filterable 110 | 111 | = 2.3.1 = 112 | * First char no longer stripped from file name if there's no slash 113 | * Adding test for when uploads directory not organized by date 114 | * Don't calculate a srcset when the image data returns no width 115 | * Add test for image_downsize returning 0 as a width 116 | 117 | = 2.3.0 = 118 | * Improved performance of get_srcset_array 119 | * Added advanced image compression option (available by adding hook to functions.php) 120 | * Duplicate entires now filtered out from srcset array 121 | * Upgrade Picturefill to 2.3.1 122 | * Refactoring plugin JavaScript, including a switch to ajax for updating the srcset value when the image is changed in the editor 123 | * Now using `wp_get_attachment_image_attributes` filter for post thumbnails 124 | * Readme and other general code typo fixes 125 | * Gallery images will now contain a srcset attribute 126 | 127 | = 2.2.1 = 128 | * Patch fixing missing JavaScript error 129 | 130 | = 2.2.0 = 131 | * The mandatory sizes attribute is now included on all images 132 | * Updated to Picturefill v2.3.0 133 | * Extensive documentation included in readme 134 | * Integrated testing with Travis CLI 135 | * Check if wp.media exists before running JavaScript 136 | * Account for rounding variance when matching ascpect ratios 137 | 138 | = 2.1.1 = 139 | * Adding in wp-tevko-responsive-images.js after file not found to be in WordPress repository 140 | * Adjusts the aspect ratio check in `tevkori_get_srcset_array()` to account for rounding variance 141 | 142 | = 2.1.0 = 143 | * **This version introduces a breaking change**: There are now two functions. One returns an array of srcset values, and the other returns a string with the `srcset=".."` html needed to generate the responsive image. To retrieve the srcset array, us `tevkori_get_srcset_array( $id, $size )` 144 | * When the image size is changed in the post editor, the srcset values will adjust to match the change. 145 | 146 | = 2.0.2 = 147 | * A bugfix correcting a divide by zero error. Some users may have seen this after upgrading to 2.0.1 148 | 149 | = 2.0.1 = 150 | * Only outputs the default WordPress sizes, giving theme developers the option to extend as needed 151 | * Added support for featured images 152 | 153 | = 2.0.0 = 154 | * Uses [Picturefill 2.2.0 (Beta)](http://scottjehl.github.io/picturefill/) 155 | * Scripts are output to footer 156 | * Image sizes adjusted 157 | * Most importantly, the srcset syntax is being used 158 | * Works for cropped images! 159 | * Backwards compatible (images added before plugin install will still be responsive)! 160 | -------------------------------------------------------------------------------- /class-respimg.php: -------------------------------------------------------------------------------- 1 | ) based on 14 | * research into optimal image resizing techniques (). 15 | * 16 | * Using these methods with their default settings should provide image resizing that is 17 | * visually indistinguishable from Photoshop’s “Save for Web…”, but at lower file sizes. 18 | * 19 | * @author David Newton 20 | * @copyright 2015 David Newton 21 | * @license https://raw.githubusercontent.com/nwtn/php-respimg/master/LICENSE MIT 22 | * @version 1.0.0 23 | */ 24 | 25 | class Respimg extends Imagick { 26 | 27 | /** 28 | * Resizes the image using smart defaults for high quality and low file size. 29 | * 30 | * This function is basically equivalent to: 31 | * 32 | * $optim == true: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB INPUT_PATH` 33 | * 34 | * $optim == false: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip INPUT_PATH` 35 | * 36 | * @access public 37 | * 38 | * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. 39 | * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. 40 | * @param bool $optim Whether you intend to perform optimization on the resulting image. 41 | * Note that setting this to 'true' doesn't actually perform any optimization. 42 | */ 43 | 44 | public function smartResize( $columns, $rows, $optim = false ) { 45 | 46 | $this->setOption( 'filter:support', '2.0' ); 47 | $this->thumbnailImage( $columns, $rows, false, false, Imagick::FILTER_TRIANGLE ); 48 | if ( $optim ) { 49 | $this->unsharpMaskImage( 0.25, 0.08, 8.3, 0.045 ); 50 | } else { 51 | $this->unsharpMaskImage( 0.25, 0.25, 8, 0.065 ); 52 | } 53 | $this->posterizeImage( 136, false ); 54 | $this->setImageCompressionQuality( 82 ); 55 | $this->setOption( 'jpeg:fancy-upsampling', 'off' ); 56 | $this->setOption( 'png:compression-filter', '5' ); 57 | $this->setOption( 'png:compression-level', '9' ); 58 | $this->setOption( 'png:compression-strategy', '1' ); 59 | $this->setOption( 'png:exclude-chunk', 'all' ); 60 | $this->setInterlaceScheme( Imagick::INTERLACE_NO ); 61 | $this->setColorspace( Imagick::COLORSPACE_SRGB ); 62 | if ( ! $optim ) { 63 | $this->stripImage(); 64 | } 65 | } 66 | 67 | /** 68 | * Changes the size of an image to the given dimensions and removes any associated profiles. 69 | * 70 | * `thumbnailImage` changes the size of an image to the given dimensions and 71 | * removes any associated profiles. The goal is to produce small low cost 72 | * thumbnail images suited for display on the Web. 73 | * 74 | * With the original Imagick thumbnailImage implementation, there is no way to choose a 75 | * resampling filter. This class recreates Imagick’s C implementation and adds this 76 | * additional feature. 77 | * 78 | * Note: https://github.com/mkoppanen/imagick/issues/90 has been filed for this issue. 79 | * 80 | * @access public 81 | * 82 | * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. 83 | * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. 84 | * @param bool $bestfit Treat $columns and $rows as a bounding box in which to fit the image. 85 | * @param bool $fill Fill in the bounding box with the background colour. 86 | * @param integer $filter The resampling filter to use. Refer to the list of filter constants at . 87 | * 88 | * @return bool Indicates whether the operation was performed successfully. 89 | */ 90 | 91 | public function thumbnailImage( $columns, $rows, $bestfit = false, $fill = false, $filter = Imagick::FILTER_TRIANGLE ) { 92 | 93 | /* 94 | * Sample factor; defined in original ImageMagick thumbnailImage function 95 | * the scale to which the image should be resized using the 'sample' function. 96 | */ 97 | $SampleFactor = 5; 98 | 99 | // Filter whitelist. 100 | $filters = array( 101 | Imagick::FILTER_POINT, 102 | Imagick::FILTER_BOX, 103 | Imagick::FILTER_TRIANGLE, 104 | Imagick::FILTER_HERMITE, 105 | Imagick::FILTER_HANNING, 106 | Imagick::FILTER_HAMMING, 107 | Imagick::FILTER_BLACKMAN, 108 | Imagick::FILTER_GAUSSIAN, 109 | Imagick::FILTER_QUADRATIC, 110 | Imagick::FILTER_CUBIC, 111 | Imagick::FILTER_CATROM, 112 | Imagick::FILTER_MITCHELL, 113 | Imagick::FILTER_LANCZOS, 114 | Imagick::FILTER_BESSEL, 115 | Imagick::FILTER_SINC 116 | ); 117 | 118 | // Parse parameters given to function. 119 | $columns = (double) $columns; 120 | $rows = (double) $rows; 121 | $bestfit = (bool) $bestfit; 122 | $fill = (bool) $fill; 123 | 124 | // We can’t resize to (0,0). 125 | if ( $rows < 1 && $columns < 1 ) { 126 | return false; 127 | } 128 | 129 | // Set a default filter if an acceptable one wasn’t passed. 130 | if ( ! in_array( $filter, $filters ) ) { 131 | $filter = Imagick::FILTER_TRIANGLE; 132 | } 133 | 134 | // Figure out the output width and height. 135 | $width = (double) $this->getImageWidth(); 136 | $height = (double) $this->getImageHeight(); 137 | $new_width = $columns; 138 | $new_height = $rows; 139 | 140 | $x_factor = $columns / $width; 141 | $y_factor = $rows / $height; 142 | if ( $rows < 1 ) { 143 | $new_height = round( $x_factor * $height ); 144 | } elseif ( $columns < 1 ) { 145 | $new_width = round( $y_factor * $width ); 146 | } 147 | 148 | /* 149 | * If bestfit is true, the new_width/new_height of the image will be different than 150 | * the columns/rows parameters; those will define a bounding box in which the image will be fit. 151 | */ 152 | if ( $bestfit && $x_factor > $y_factor ) { 153 | $x_factor = $y_factor; 154 | $new_width = round( $y_factor * $width ); 155 | } elseif ( $bestfit && $y_factor > $x_factor ) { 156 | $y_factor = $x_factor; 157 | $new_height = round( $x_factor * $height ); 158 | } 159 | if ( $new_width < 1 ) { 160 | $new_width = 1; 161 | } 162 | if ( $new_height < 1 ) { 163 | $new_height = 1; 164 | } 165 | 166 | /* 167 | * If we’re resizing the image to more than about 1/3 it’s original size 168 | * then just use the resize function. 169 | */ 170 | if ( ( $x_factor * $y_factor ) > 0.1 ) { 171 | $this->resizeImage( $new_width, $new_height, $filter, 1 ); 172 | 173 | // if we’d be using sample to scale to smaller than 128x128, just use resize 174 | } elseif ( ( ( $SampleFactor * $new_width ) < 128) || ( ( $SampleFactor * $new_height ) < 128 ) ) { 175 | $this->resizeImage( $new_width, $new_height, $filter, 1 ); 176 | 177 | // otherwise, use sample first, then resize 178 | } else { 179 | $this->sampleImage( $SampleFactor * $new_width, $SampleFactor * $new_height ); 180 | $this->resizeImage( $new_width, $new_height, $filter, 1 ); 181 | } 182 | 183 | // if the alpha channel is not defined, make it opaque 184 | if ( $this->getImageAlphaChannel() == Imagick::ALPHACHANNEL_UNDEFINED ) { 185 | $this->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE ); 186 | } 187 | 188 | // set the image’s bit depth to 8 bits 189 | $this->setImageDepth( 8 ); 190 | 191 | // turn off interlacing 192 | $this->setInterlaceScheme( Imagick::INTERLACE_NO ); 193 | 194 | // Strip all profiles except color profiles. 195 | foreach ( $this->getImageProfiles( '*', true ) as $key => $value ) { 196 | if ( $key != 'icc' && $key != 'icm' ) { 197 | $this->removeImageProfile( $key ); 198 | } 199 | } 200 | 201 | if ( method_exists( $this, 'deleteImageProperty' ) ) { 202 | $this->deleteImageProperty( 'comment' ); 203 | $this->deleteImageProperty( 'Thumb::URI' ); 204 | $this->deleteImageProperty( 'Thumb::MTime' ); 205 | $this->deleteImageProperty( 'Thumb::Size' ); 206 | $this->deleteImageProperty( 'Thumb::Mimetype' ); 207 | $this->deleteImageProperty( 'software' ); 208 | $this->deleteImageProperty( 'Thumb::Image::Width' ); 209 | $this->deleteImageProperty( 'Thumb::Image::Height' ); 210 | $this->deleteImageProperty( 'Thumb::Document::Pages' ); 211 | } else { 212 | $this->setImageProperty( 'comment', '' ); 213 | $this->setImageProperty( 'Thumb::URI', '' ); 214 | $this->setImageProperty( 'Thumb::MTime', '' ); 215 | $this->setImageProperty( 'Thumb::Size', '' ); 216 | $this->setImageProperty( 'Thumb::Mimetype', '' ); 217 | $this->setImageProperty( 'software', '' ); 218 | $this->setImageProperty( 'Thumb::Image::Width', '' ); 219 | $this->setImageProperty( 'Thumb::Image::Height', '' ); 220 | $this->setImageProperty( 'Thumb::Document::Pages', '' ); 221 | } 222 | 223 | /* 224 | * In case user wants to fill use extent for it rather than creating a new canvas 225 | * fill out the bounding box. 226 | */ 227 | if ( $bestfit && $fill && ( $new_width != $columns || $new_height != $rows ) ) { 228 | $extent_x = 0; 229 | $extent_y = 0; 230 | 231 | if ( $columns > $new_width ) { 232 | $extent_x = ( $columns - $new_width ) / 2; 233 | } 234 | 235 | if ( $rows > $new_height ) { 236 | $extent_y = ( $rows - $new_height ) / 2; 237 | } 238 | 239 | $this->extentImage( $columns, $rows, 0 - $extent_x, $extent_y ); 240 | } 241 | 242 | return true; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /js/picturefill.min.js: -------------------------------------------------------------------------------- 1 | /*! Picturefill - v3.0.1 - 2015-09-30 2 | * http://scottjehl.github.io/picturefill 3 | * Copyright (c) 2015 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT 4 | */ 5 | !function(a){var b=navigator.userAgent;a.HTMLPictureElement&&/ecko/.test(b)&&b.match(/rv\:(\d+)/)&&RegExp.$1<41&&addEventListener("resize",function(){var b,c=document.createElement("source"),d=function(a){var b,d,e=a.parentNode;"PICTURE"===e.nodeName.toUpperCase()?(b=c.cloneNode(),e.insertBefore(b,e.firstElementChild),setTimeout(function(){e.removeChild(b)})):(!a._pfLastSize||a.offsetWidth>a._pfLastSize)&&(a._pfLastSize=a.offsetWidth,d=a.sizes,a.sizes+=",100vw",setTimeout(function(){a.sizes=d}))},e=function(){var a,b=document.querySelectorAll("picture > img, img[srcset][sizes]");for(a=0;a2.7?h=c+1:(f=b-c,e=Math.pow(a-.6,1.5),g=f*e,d&&(g+=.1*e),h=a+g):h=c>1?Math.sqrt(a*b):a,h>c}function h(a){var b,c=s.getSet(a),d=!1;"pending"!==c&&(d=r,c&&(b=s.setRes(c),s.applySetCandidate(b,a))),a[s.ns].evaled=d}function i(a,b){return a.res-b.res}function j(a,b,c){var d;return!c&&b&&(c=a[s.ns].sets,c=c&&c[c.length-1]),d=k(b,c),d&&(b=s.makeUrl(b),a[s.ns].curSrc=b,a[s.ns].curCan=d,d.res||_(d,d.set.sizes)),d}function k(a,b){var c,d,e;if(a&&b)for(e=s.parseSet(b),a=s.makeUrl(a),c=0;cc;c++)e=g[c],e[s.ns]=!0,f=e.getAttribute("srcset"),f&&b.push({srcset:f,media:e.getAttribute("media"),type:e.getAttribute("type"),sizes:e.getAttribute("sizes")})}function m(a,b){function c(b){var c,d=b.exec(a.substring(m));return d?(c=d[0],m+=c.length,c):void 0}function e(){var a,c,d,e,f,i,j,k,l,m=!1,o={};for(e=0;el?m=!0:c=l):W.test(j)&&"h"===i?((d||c)&&(m=!0),0===k?m=!0:d=k):m=!0;m||(o.url=g,a&&(o.w=a),c&&(o.d=c),d&&(o.h=d),d||c||a||(o.d=1),1===o.d&&(b.has1x=!0),o.set=b,n.push(o))}function f(){for(c(S),i="",j="in descriptor";;){if(k=a.charAt(m),"in descriptor"===j)if(d(k))i&&(h.push(i),i="",j="after descriptor");else{if(","===k)return m+=1,i&&h.push(i),void e();if("("===k)i+=k,j="in parens";else{if(""===k)return i&&h.push(i),void e();i+=k}}else if("in parens"===j)if(")"===k)i+=k,j="in descriptor";else{if(""===k)return h.push(i),void e();i+=k}else if("after descriptor"===j)if(d(k));else{if(""===k)return void e();j="in descriptor",m-=1}m+=1}}for(var g,h,i,j,k,l=a.length,m=0,n=[];;){if(c(T),m>=l)return n;g=c(U),h=[],","===g.slice(-1)?(g=g.replace(V,""),e()):f()}}function n(a){function b(a){function b(){f&&(g.push(f),f="")}function c(){g[0]&&(h.push(g),g=[])}for(var e,f="",g=[],h=[],i=0,j=0,k=!1;;){if(e=a.charAt(j),""===e)return b(),c(),h;if(k){if("*"===e&&"/"===a[j+1]){k=!1,j+=2,b();continue}j+=1}else{if(d(e)){if(a.charAt(j-1)&&d(a.charAt(j-1))||!f){j+=1;continue}if(0===i){b(),j+=1;continue}e=" "}else if("("===e)i+=1;else if(")"===e)i-=1;else{if(","===e){b(),c(),j+=1;continue}if("/"===e&&"*"===a.charAt(j+1)){k=!0,j+=2;continue}}f+=e,j+=1}}}function c(a){return k.test(a)&&parseFloat(a)>=0?!0:l.test(a)?!0:"0"===a||"-0"===a||"+0"===a?!0:!1}var e,f,g,h,i,j,k=/^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)$/i,l=/^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)$/i;for(f=b(a),g=f.length,e=0;g>e;e++)if(h=f[e],i=h[h.length-1],c(i)){if(j=i,h.pop(),0===h.length)return j;if(h=h.join(" "),s.matchesMedia(h))return j}return"100vw"}b.createElement("picture");var o,p,q,r,s={},t=function(){},u=b.createElement("img"),v=u.getAttribute,w=u.setAttribute,x=u.removeAttribute,y=b.documentElement,z={},A={algorithm:""},B="data-pfsrc",C=B+"set",D=navigator.userAgent,E=/rident/.test(D)||/ecko/.test(D)&&D.match(/rv\:(\d+)/)&&RegExp.$1>35,F="currentSrc",G=/\s+\+?\d+(e\d+)?w/,H=/(\([^)]+\))?\s*(.+)/,I=a.picturefillCFG,J="position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)",K="font-size:100%!important;",L=!0,M={},N={},O=a.devicePixelRatio,P={px:1,"in":96},Q=b.createElement("a"),R=!1,S=/^[ \t\n\r\u000c]+/,T=/^[, \t\n\r\u000c]+/,U=/^[^ \t\n\r\u000c]+/,V=/[,]+$/,W=/^\d+$/,X=/^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/,Y=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d||!1):a.attachEvent&&a.attachEvent("on"+b,c)},Z=function(a){var b={};return function(c){return c in b||(b[c]=a(c)),b[c]}},$=function(){var a=/^([\d\.]+)(em|vw|px)$/,b=function(){for(var a=arguments,b=0,c=a[0];++b in a;)c=c.replace(a[b],a[++b]);return c},c=Z(function(a){return"return "+b((a||"").toLowerCase(),/\band\b/g,"&&",/,/g,"||",/min-([a-z-\s]+):/g,"e.$1>=",/max-([a-z-\s]+):/g,"e.$1<=",/calc([^)]+)/g,"($1)",/(\d+[\.]*[\d]*)([a-z]+)/g,"($1 * e.$2)",/^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/gi,"")+";"});return function(b,d){var e;if(!(b in M))if(M[b]=!1,d&&(e=b.match(a)))M[b]=e[1]*P[e[2]];else try{M[b]=new Function("e",c(b))(P)}catch(f){}return M[b]}}(),_=function(a,b){return a.w?(a.cWidth=s.calcListLength(b||"100vw"),a.res=a.w/a.cWidth):a.res=a.d,a},aa=function(a){var c,d,e,f=a||{};if(f.elements&&1===f.elements.nodeType&&("IMG"===f.elements.nodeName.toUpperCase()?f.elements=[f.elements]:(f.context=f.elements,f.elements=null)),c=f.elements||s.qsa(f.context||b,f.reevaluate||f.reselect?s.sel:s.selShort),e=c.length){for(s.setupRun(f),R=!0,d=0;e>d;d++)s.fillImg(c[d],f);s.teardownRun(f)}};o=a.console&&console.warn?function(a){console.warn(a)}:t,F in u||(F="src"),z["image/jpeg"]=!0,z["image/gif"]=!0,z["image/png"]=!0,z["image/svg+xml"]=b.implementation.hasFeature("http://wwwindow.w3.org/TR/SVG11/feature#Image","1.1"),s.ns=("pf"+(new Date).getTime()).substr(0,9),s.supSrcset="srcset"in u,s.supSizes="sizes"in u,s.supPicture=!!a.HTMLPictureElement,s.supSrcset&&s.supPicture&&!s.supSizes&&!function(a){u.srcset="data:,a",a.src="data:,a",s.supSrcset=u.complete===a.complete,s.supPicture=s.supSrcset&&s.supPicture}(b.createElement("img")),s.selShort="picture>img,img[srcset]",s.sel=s.selShort,s.cfg=A,s.supSrcset&&(s.sel+=",img["+C+"]"),s.DPR=O||1,s.u=P,s.types=z,q=s.supSrcset&&!s.supSizes,s.setSize=t,s.makeUrl=Z(function(a){return Q.href=a,Q.href}),s.qsa=function(a,b){return a.querySelectorAll(b)},s.matchesMedia=function(){return a.matchMedia&&(matchMedia("(min-width: 0.1em)")||{}).matches?s.matchesMedia=function(a){return!a||matchMedia(a).matches}:s.matchesMedia=s.mMQ,s.matchesMedia.apply(this,arguments)},s.mMQ=function(a){return a?$(a):!0},s.calcLength=function(a){var b=$(a,!0)||!1;return 0>b&&(b=!1),b},s.supportsType=function(a){return a?z[a]:!0},s.parseSize=Z(function(a){var b=(a||"").match(H);return{media:b&&b[1],length:b&&b[2]}}),s.parseSet=function(a){return a.cands||(a.cands=m(a.srcset,a)),a.cands},s.getEmValue=function(){var a;if(!p&&(a=b.body)){var c=b.createElement("div"),d=y.style.cssText,e=a.style.cssText;c.style.cssText=J,y.style.cssText=K,a.style.cssText=K,a.appendChild(c),p=c.offsetWidth,a.removeChild(c),p=parseFloat(p,10),y.style.cssText=d,a.style.cssText=e}return p||16},s.calcListLength=function(a){if(!(a in N)||A.uT){var b=s.calcLength(n(a));N[a]=b?b:P.width}return N[a]},s.setRes=function(a){var b;if(a){b=s.parseSet(a);for(var c=0,d=b.length;d>c;c++)_(b[c],a.sizes)}return b},s.setRes.res=_,s.applySetCandidate=function(a,b){if(a.length){var c,d,e,f,h,k,l,m,n,o=b[s.ns],p=s.DPR;if(k=o.curSrc||b[F],l=o.curCan||j(b,k,a[0].set),l&&l.set===a[0].set&&(n=E&&!b.complete&&l.res-.1>p,n||(l.cached=!0,l.res>=p&&(h=l))),!h)for(a.sort(i),f=a.length,h=a[f-1],d=0;f>d;d++)if(c=a[d],c.res>=p){e=d-1,h=a[e]&&(n||k!==s.makeUrl(c.url))&&g(a[e].res,c.res,p,a[e].cached)?a[e]:c;break}h&&(m=s.makeUrl(h.url),o.curSrc=m,o.curCan=h,m!==k&&s.setSrc(b,h),s.setSize(b))}},s.setSrc=function(a,b){var c;a.src=b.url,"image/svg+xml"===b.set.type&&(c=a.style.width,a.style.width=a.offsetWidth+1+"px",a.offsetWidth+1&&(a.style.width=c))},s.getSet=function(a){var b,c,d,e=!1,f=a[s.ns].sets;for(b=0;bf?c=setTimeout(e,b-f):(c=null,a())};return function(){d=new Date,c||(c=setTimeout(e,b))}},h=y.clientHeight,i=function(){L=Math.max(a.innerWidth||0,y.clientWidth)!==P.width||y.clientHeight!==h,h=y.clientHeight,L&&s.fillImgs()};Y(a,"resize",g(i,99)),Y(b,"readystatechange",e)}(),s.picturefill=aa,s.fillImgs=aa,s.teardownRun=t,aa._=s,a.picturefillCFG={pf:s,push:function(a){var b=a.shift();"function"==typeof s[b]?s[b].apply(s,a):(A[b]=a[0],R&&s.fillImgs({reselect:!0}))}};for(;I&&I.length;)a.picturefillCFG.push(I.shift());a.picturefill=aa,"object"==typeof module&&"object"==typeof module.exports?module.exports=aa:"function"==typeof define&&define.amd&&define("picturefill",function(){return aa}),s.supPicture||(z["image/webp"]=e("image/webp","data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA=="))}(window,document); -------------------------------------------------------------------------------- /wp-tevko-deprecated-functions.php: -------------------------------------------------------------------------------- 1 | deprecated since version %2$s! Use %3$s instead.', $filter, $version, $replacement ) ); 16 | } else { 17 | trigger_error( sprintf( 'Filter hook %1$s is deprecated since version %2$s with no alternative available.', $filter, $version ) ); 18 | } 19 | } 20 | } 21 | 22 | /** 23 | * Returns an array of image sources for a 'srcset' attribute. 24 | * 25 | * @since 2.1.0 26 | * @deprecated 3.0.0 Use 'wp_get_attachment_image_srcset()' 27 | * 28 | * @see wp_get_attachment_image_srcset() 29 | * 30 | * @param int $id Image attachment ID. 31 | * @param array|string $size Image size. Accepts any valid image size, or an array of width and height 32 | * values in pixels (in that order). Default 'medium'. 33 | * @return array|bool An array of 'srcset' values or false. 34 | */ 35 | function tevkori_get_srcset_array( $id, $size = 'medium' ) { 36 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_get_attachment_image_srcset()' ); 37 | 38 | $srcset = wp_get_attachment_image_srcset( $id, $size ); 39 | 40 | // Transform the 'srcset' value string to a pre-core style array. 41 | if ( ! $srcset ) { 42 | return false; 43 | } 44 | 45 | $sources = explode( ', ', $srcset ); 46 | $arr = array(); 47 | 48 | foreach ( $sources as $source ) { 49 | $split = explode( ' ', $source ); 50 | $width = rtrim( $split[1], "w" ); 51 | $arr[ $width ] = $source; 52 | } 53 | 54 | /** 55 | * Filter the output of 'tevkori_get_srcset_array()'. 56 | * 57 | * @since 2.4.0 58 | * @deprecated 3.0.0 Use 'wp_calculate_image_srcset' 59 | * 60 | * @see wp_calculate_image_srcset 61 | * 62 | * @param array $arr An array of image sources. 63 | * @param int $id Attachment ID for image. 64 | * @param array|string $size Image size. Image size or an array of width and height 65 | * values in pixels (in that order). 66 | */ 67 | if ( has_filter( 'tevkori_srcset_array' ) ) { 68 | _tevkori_deprecated_filter( 'tevkori_srcset_array', '3.0.0', 'wp_calculate_image_srcset' ); 69 | } 70 | return apply_filters( 'tevkori_srcset_array', $arr, $id, $size ); 71 | } 72 | 73 | /** 74 | * Returns the value for a 'srcset' attribute. 75 | * 76 | * @since 2.3.0 77 | * @deprecated 3.0.0 Use 'wp_get_attachment_image_srcset()' 78 | * 79 | * @see wp_get_attachment_image_srcset() 80 | * 81 | * @param int $id Image attachment ID. 82 | * @param array|string $size Image size. Accepts any valid image size, or an array of width and height 83 | * values in pixels (in that order). Default 'medium'. 84 | * @return string|bool A 'srcset' value string or false. 85 | */ 86 | function tevkori_get_srcset( $id, $size = 'medium' ) { 87 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_get_attachment_image_srcset()' ); 88 | 89 | if ( has_filter( 'tevkori_srcset_array' ) ) { 90 | $srcset_array = tevkori_get_srcset_array( $id, $size ); 91 | 92 | return $scrset_array ? implode( ', ', $srcset_array ) : false; 93 | } else { 94 | return wp_get_attachment_image_srcset( $id, $size ); 95 | } 96 | } 97 | 98 | /** 99 | * Returns a 'srcset' attribute. 100 | * 101 | * @since 2.1.0 102 | * @deprecated 3.0.0 Use 'wp_get_attachment_image_srcset()' 103 | * 104 | * @see wp_get_attachment_image_srcset() 105 | * 106 | * @param int $id Image attachment ID. 107 | * @param array|string $size Image size. Accepts any valid image size, or an array of width and height 108 | * values in pixels (in that order). Default 'medium'. 109 | * @return string|bool A full 'srcset' string or false. 110 | */ 111 | function tevkori_get_srcset_string( $id, $size = 'medium' ) { 112 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_get_attachment_image_srcset()' ); 113 | 114 | if ( has_filter( 'tevkori_srcset_array' ) ) { 115 | $srcset_value = tevkori_get_srcset( $id, $size ); 116 | 117 | return $srcset_value ? 'srcset="' . $srcset_value . '"' : false; 118 | } else { 119 | $srcset_value = wp_get_attachment_image_srcset( $id, $size ); 120 | 121 | return $srcset_value ? 'srcset="' . $srcset_value . '"' : false; 122 | } 123 | } 124 | 125 | /** 126 | * Returns the value for a 'sizes' attribute. 127 | * 128 | * @since 2.2.0 129 | * @deprecated 3.0.0 Use 'wp_get_attachment_image_sizes()' 130 | * 131 | * @see wp_get_attachment_image_sizes() 132 | * 133 | * @param int $id Image attachment ID. 134 | * @param array|string $size Image size. Accepts any valid image size, or an array of width and height 135 | * values in pixels (in that order). Default 'medium'. 136 | * @param array $args { 137 | * Optional. Arguments to retrieve posts. 138 | * 139 | * @type array|string $sizes An array or string containing of size information. 140 | * @type int $width A single width value used in the default 'sizes' string. 141 | * } 142 | * @return string|bool A valid source size value for use in a 'sizes' attribute or false. 143 | */ 144 | function tevkori_get_sizes( $id, $size = 'medium', $args = null ) { 145 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_get_attachment_image_sizes()' ); 146 | 147 | /* 148 | * If this function is passed with an $args parameter, we need to parse the 149 | * $args parameter as we were doing previously, and then save it to a static 150 | * variable using '_tevkori_sizes_has_args()' which can be accessed from the 151 | * '_tevkori_image_sizes_args_shim()' filter in 'wp_calculate_image_sizes()' 152 | */ 153 | if ( $args ) { 154 | // Try to get the image width from '$args' before calling 'image_downsize()'. 155 | if ( is_array( $args ) && ! empty( $args['width'] ) ) { 156 | $img_width = (int) $args['width']; 157 | } else { 158 | $img_width = ( $img = image_downsize( $id, $size ) ) ? $img[1] : false; 159 | } 160 | 161 | // Bail early if '$img_width' isn't set. 162 | if ( ! $img_width ) { 163 | return false; 164 | } 165 | 166 | // Set the image width in pixels. 167 | $img_width = $img_width . 'px'; 168 | // Set up our default values. 169 | $defaults = array( 170 | 'sizes' => array( 171 | array( 172 | 'size_value' => '100vw', 173 | 'mq_value' => $img_width, 174 | 'mq_name' => 'max-width' 175 | ), 176 | array( 177 | 'size_value' => $img_width 178 | ), 179 | ) 180 | ); 181 | 182 | $args = wp_parse_args( $args, $defaults ); 183 | 184 | // Set our static sizes args. 185 | _tevkori_sizes_has_args( $args ); 186 | 187 | $sizes = wp_get_attachment_image_sizes( $id, $size ); 188 | 189 | // Unset our static sizes args. 190 | _tevkori_sizes_has_args( false ); 191 | 192 | return $sizes; 193 | } else { 194 | return wp_get_attachment_image_sizes( $id, $size ); 195 | } 196 | } 197 | 198 | /** 199 | * Private function to cache '$args' param from 'tevkori_get_sizes()'. 200 | * 201 | * @since 3.1.0 202 | * @access private 203 | * 204 | * @param array|bool $args Optional. The parsed value of the '$args' param from 205 | * 'tevkori_get_sizes()' or 'false' to unset the saved value. 206 | * @return array|bool The cached '$args' value or false is none is set. 207 | */ 208 | function _tevkori_sizes_has_args( $args = null ) { 209 | static $return_args = false; 210 | 211 | if ( false === $args ) { 212 | $return_args = false; 213 | } elseif ( is_array( $args ) ) { 214 | $return_args = $args; 215 | } 216 | 217 | return $return_args; 218 | } 219 | 220 | /** 221 | * Provides backward compatibility of the 'tevkori_image_sizes_args' filter 222 | * and the '$args' parameter of 'tevkori_get_sizes()'. 223 | * 224 | * @since 3.1.0 225 | * @access private 226 | * 227 | * @see wp_calculate_image_srcset 228 | * 229 | * @return string A source size value for use in a 'sizes' attribute. 230 | */ 231 | function _tevkori_image_sizes_args_shim( $sizes, $size, $image_src, $image_meta, $id ) { 232 | // Check for '$args' that were passed to 'tevkori_get_sizes()'; 233 | $args = _tevkori_sizes_has_args(); 234 | 235 | // Bail early if no '$args' were passed or the old filter wasn't used. 236 | if ( ! $args && ! has_filter( 'tevkori_image_sizes_args' ) ) { 237 | return $sizes; 238 | } 239 | 240 | // If no '$args' are present, we'll build the default '$args'. 241 | if ( ! $args ) { 242 | // Recreate default '$args'. 243 | if ( is_array( $size ) ) { 244 | $img_width = (int) $size[0]; 245 | } else { 246 | $img = image_downsize( $id, $size ); 247 | $img_width = $img[1]; 248 | } 249 | 250 | $args = array( 251 | 'sizes' => array( 252 | array( 253 | 'size_value' => '100vw', 254 | 'mq_value' => $img_width, 255 | 'mq_name' => 'max-width' 256 | ), 257 | array( 258 | 'size_value' => $img_width 259 | ), 260 | ) 261 | ); 262 | } 263 | 264 | /** 265 | * Filter arguments used to create the 'sizes' attribute value. 266 | * 267 | * @since 2.4.0 268 | * @deprecated 3.0.0 Use 'wp_calculate_image_sizes' 269 | * 270 | * @see wp_calculate_image_sizes 271 | * 272 | * @param array $args An array of arguments used to create a 'sizes' attribute. 273 | * @param int $id Post ID of the original image. 274 | * @param array|string $size Image size. Image size or an array of width and height 275 | * values in pixels (in that order). 276 | */ 277 | if ( has_filter( 'tevkori_image_sizes_args' ) ) { 278 | _tevkori_deprecated_filter( 'tevkori_image_sizes_args', '3.0.0', 'wp_calculate_image_sizes' ); 279 | } 280 | $args = apply_filters( 'tevkori_image_sizes_args', $args, $id, $size ); 281 | 282 | // If sizes is passed as a string, just use the string. 283 | if ( is_string( $args['sizes'] ) ) { 284 | $size_list = $args['sizes']; 285 | 286 | // Otherwise, breakdown the array and build a sizes string. 287 | } elseif ( is_array( $args['sizes'] ) ) { 288 | $size_list = ''; 289 | 290 | foreach ( $args['sizes'] as $size ) { 291 | // Use 100vw as the size value unless something else is specified. 292 | $size_value = ( $size['size_value'] ) ? $size['size_value'] : '100vw'; 293 | 294 | // If a media length is specified, build the media query. 295 | if ( ! empty( $size['mq_value'] ) ) { 296 | $media_length = $size['mq_value']; 297 | 298 | // Use max-width as the media condition unless min-width is specified. 299 | $media_condition = ( ! empty( $size['mq_name'] ) ) ? $size['mq_name'] : 'max-width'; 300 | 301 | // If a media length was set, create the media query. 302 | $media_query = '(' . $media_condition . ": " . $media_length . ') '; 303 | } else { 304 | // If no media length was set, '$media_query' is blank. 305 | $media_query = ''; 306 | } 307 | // Add to the source size list string. 308 | $size_list .= $media_query . $size_value . ', '; 309 | } 310 | // Remove the trailing comma and space from the end of the string. 311 | $size_list = substr( $size_list, 0, -2 ); 312 | } 313 | 314 | // If '$size_list' is defined return the string, otherwise return '$sizes'. 315 | return ( $size_list ) ? $size_list : $sizes; 316 | } 317 | add_filter( 'wp_calculate_image_sizes', '_tevkori_image_sizes_args_shim', 1, 5 ); 318 | 319 | /** 320 | * Returns a 'sizes' attribute. 321 | * 322 | * @since 2.2.0 323 | * @deprecated 3.0.0 Use 'wp_get_attachment_image_sizes()' 324 | * 325 | * @see wp_get_attachment_image_sizes() 326 | * 327 | * @param int $id Image attachment ID. 328 | * @param array|string $size Image size. Accepts any valid image size, or an array of width and height 329 | * values in pixels (in that order). Default 'medium'. 330 | * @param array $args { 331 | * Optional. Arguments to retrieve posts. 332 | * 333 | * @type array|string $sizes An array or string containing of size information. 334 | * @type int $width A single width value used in the default 'sizes' string. 335 | * } 336 | * @return string|bool A valid source size list as a 'sizes' attribute or false. 337 | */ 338 | function tevkori_get_sizes_string( $id, $size = 'medium', $args = null ) { 339 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_get_attachment_image_sizes()' ); 340 | 341 | if ( $args ) { 342 | $sizes = tevkori_get_sizes( $id, $size, $args ); 343 | } else { 344 | $sizes = wp_get_attachment_image_sizes( $id, $size ); 345 | } 346 | 347 | return $sizes ? 'sizes="' . esc_attr( $sizes ) . '"' : false; 348 | } 349 | 350 | /** 351 | * Filter to add 'srcset' and 'sizes' attributes to images in the post content. 352 | * 353 | * @since 2.5.0 354 | * @deprecated 3.0.0 Use 'wp_make_content_images_responsive()' 355 | * 356 | * @see wp_make_content_images_responsive() 357 | * 358 | * @param string $content The raw post content to be filtered. 359 | * @return string Converted content with 'srcset' and 'sizes' added to images. 360 | */ 361 | function tevkori_filter_content_images( $content ) { 362 | _deprecated_function( __FUNCTION__, '3.0.0', 'wp_make_content_images_responsive()' ); 363 | return wp_make_content_images_responsive( $content ); 364 | } 365 | 366 | /** 367 | * Backward compatibility shim for 'data-sizes' attributes in content. 368 | * 369 | * Prior to version 2.5 a 'srcset' and 'data-sizes' attribute were added to the image 370 | * while inserting the image in the content. We replace the 'data-sizes' attribute by 371 | * a 'sizes' attribute. 372 | * 373 | * @since 3.0.0 374 | * 375 | * @param string $content The content to filter; 376 | * @return string The filtered content with `data-sizes` replaced by `sizes` attributes. 377 | */ 378 | function tevkori_replace_data_sizes( $content ) { 379 | return str_replace( ' data-sizes="', ' sizes="', $content ); 380 | } 381 | add_filter( 'the_content', 'tevkori_replace_data_sizes' ); 382 | 383 | /** 384 | * Backward compatibility shim for the deprecated 'wp_get_attachment_image_sizes' filter. 385 | * 386 | * @since 3.1.0 387 | * @access private 388 | * 389 | * @see 'wp_calculate_image_sizes' 390 | */ 391 | function _wp_get_attachment_image_sizes_filter_shim( $sizes, $size, $image_src, $image_meta, $attachment_id ) { 392 | if ( has_filter( 'wp_get_attachment_image_sizes' ) ) { 393 | /** 394 | * Filter the output of 'wp_get_attachment_image_sizes()'. 395 | * 396 | * @since 3.0.0 397 | * @deprecated 3.1.0 Use 'wp_calculate_image_sizes' 398 | * 399 | * @see wp_calculate_image_sizes 400 | * 401 | * @param string $sizes A source size value for use in a 'sizes' attribute. 402 | * @param array|string $size Image size. Image size name, or an array of width and height 403 | * values in pixels (in that order). 404 | * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. 405 | * @param int $attachment_id Image attachment ID of the original image. 406 | * @param string $image_src Optional. The URL to the image file. 407 | */ 408 | _tevkori_deprecated_filter( 'wp_get_attachment_image_sizes', '3.1.0', 'wp_calculate_image_sizes' ); 409 | return apply_filters( 'wp_get_attachment_image_sizes', $sizes, $size, $image_meta, $attachment_id, $image_src ); 410 | } else { 411 | return $sizes; 412 | } 413 | } 414 | add_filter( 'wp_calculate_image_sizes', '_wp_get_attachment_image_sizes_filter_shim', 10, 5 ); 415 | 416 | /** 417 | * Check the content blob for an audio, video, object, embed, iframe, or img tags. 418 | * This is a modified version of `get_media_embedded_in_content()` that was modified 419 | * during the WP 4.4 cycle to return images in content, before being reverted. 420 | * 421 | * @since 3.0.0 422 | * @deprecated 3.1.1 Use 'get_media_embedded_in_content()' 423 | * 424 | * @param string $content A string which might contain media data. 425 | * @param array $types An array of media types: 'audio', 'video', 'object', 'embed', 'iframe', or 'img'. 426 | * @return array A list of found HTML media embeds. 427 | */ 428 | function tevkori_get_media_embedded_in_content( $content, $types = null ) { 429 | _deprecated_function( __FUNCTION__, '3.1.1', 'get_media_embedded_in_content()' ); 430 | $html = array(); 431 | 432 | /** 433 | * Filter the embedded media types that are allowed to be returned from the content blob. 434 | * 435 | * @param array $allowed_media_types An array of allowed media types. Default media types are 436 | * 'audio', 'video', 'object', 'embed', 'iframe', and 'img'. 437 | */ 438 | $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe', 'img' ) ); 439 | 440 | if ( ! empty( $types ) ) { 441 | if ( ! is_array( $types ) ) { 442 | $types = array( $types ); 443 | } 444 | 445 | $allowed_media_types = array_intersect( $allowed_media_types, $types ); 446 | } 447 | 448 | $tags = implode( '|', $allowed_media_types ); 449 | 450 | if ( preg_match_all( '#<(?P' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ) ) { 451 | foreach ( $matches[0] as $match ) { 452 | $html[] = $match; 453 | } 454 | } 455 | 456 | return $html; 457 | } 458 | -------------------------------------------------------------------------------- /wp-tevko-core-functions.php: -------------------------------------------------------------------------------- 1 | $image_meta['width'], 122 | 'height' => $image_meta['height'], 123 | 'file' => $image_basename, 124 | ); 125 | } elseif ( strpos( $image_src, $image_meta['file'] ) ) { 126 | return false; 127 | } 128 | 129 | // Uploads are (or have been) in year/month sub-directories. 130 | if ( $image_basename !== $image_meta['file'] ) { 131 | $dirname = dirname( $image_meta['file'] ); 132 | 133 | if ( $dirname !== '.' ) { 134 | $image_baseurl = trailingslashit( $image_baseurl ) . $dirname; 135 | } 136 | } 137 | 138 | $image_baseurl = trailingslashit( $image_baseurl ); 139 | 140 | /* 141 | * Images that have been edited in WordPress after being uploaded will 142 | * contain a unique hash. Look for that hash and use it later to filter 143 | * out images that are leftovers from previous versions. 144 | */ 145 | $image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash ); 146 | 147 | /** 148 | * Filter the maximum image width to be included in a 'srcset' attribute. 149 | * 150 | * @since 3.0.0 151 | * 152 | * @param int $max_width The maximum image width to be included in the 'srcset'. Default '1600'. 153 | * @param array $size_array Array of width and height values in pixels (in that order). 154 | */ 155 | $max_srcset_image_width = apply_filters( 'max_srcset_image_width', 1600, $size_array ); 156 | 157 | // Array to hold URL candidates. 158 | $sources = array(); 159 | 160 | /* 161 | * Loop through available images. Only use images that are resized 162 | * versions of the same edit. 163 | */ 164 | foreach ( $image_sizes as $image ) { 165 | 166 | // Filter out images that are from previous edits. 167 | if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) { 168 | continue; 169 | } 170 | 171 | // Filter out images that are wider than '$max_srcset_image_width'. 172 | if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width ) { 173 | continue; 174 | } 175 | 176 | /** 177 | * To check for varying crops, we calculate the expected size of the smaller 178 | * image if the larger were contstrained by the width of the smaller and then 179 | * see if it matches what we're expecting. 180 | */ 181 | if ( $image_width > $image['width'] ) { 182 | $constrained_size = wp_constrain_dimensions( $image_width, $image_height, $image['width'] ); 183 | $expected_size = array( $image['width'], $image['height'] ); 184 | } else { 185 | $constrained_size = wp_constrain_dimensions( $image['width'], $image['height'], $image_width ); 186 | $expected_size = array( $image_width, $image_height ); 187 | } 188 | 189 | // If the image dimensions are within 1px of the expected size, use it. 190 | if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) { 191 | // Add the URL, descriptor, and value to the sources array to be returned. 192 | $sources[ $image['width'] ] = array( 193 | 'url' => $image_baseurl . $image['file'], 194 | 'descriptor' => 'w', 195 | 'value' => $image['width'], 196 | ); 197 | } 198 | } 199 | 200 | /** 201 | * Filter an image's 'srcset' sources. 202 | * 203 | * @since 3.0.0 204 | * 205 | * @param array $sources { 206 | * One or more arrays of source data to include in the 'srcset'. 207 | * 208 | * @type array $width { 209 | * @type string $url The URL of an image source. 210 | * @type string $descriptor The descriptor type used in the image candidate string, 211 | * either 'w' or 'x'. 212 | * @type int $value The source width if paired with a 'w' descriptor, or a 213 | * pixel density value if paired with an 'x' descriptor. 214 | * } 215 | * } 216 | * @param array $size_array Array of width and height values in pixels (in that order). 217 | * @param string $image_src The 'src' of the image. 218 | * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. 219 | * @param int $attachment_id Image attachment ID or 0. 220 | */ 221 | $sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id ); 222 | 223 | // Only return a 'srcset' value if there is more than one source. 224 | if ( count( $sources ) < 2 ) { 225 | return false; 226 | } 227 | 228 | $srcset = ''; 229 | 230 | foreach ( $sources as $source ) { 231 | $srcset .= $source['url'] . ' ' . $source['value'] . $source['descriptor'] . ', '; 232 | } 233 | 234 | return rtrim( $srcset, ', ' ); 235 | } 236 | 237 | /** 238 | * Retrieves the value for an image attachment's 'sizes' attribute. 239 | * 240 | * @since 3.0.0 241 | * 242 | * @see wp_calculate_image_sizes() 243 | * 244 | * @param int $attachment_id Image attachment ID. 245 | * @param array|string $size Optional. Image size. Accepts any valid image size, or an array of width 246 | * and height values in pixels (in that order). Default 'medium'. 247 | * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. 248 | * Default null. 249 | * @return string|bool A valid source size value for use in a 'sizes' attribute or false. 250 | */ 251 | function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) { 252 | if ( ! $image = wp_get_attachment_image_src( $attachment_id, $size ) ) { 253 | return false; 254 | } 255 | 256 | if ( ! is_array( $image_meta ) ) { 257 | $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true ); 258 | } 259 | 260 | $image_src = $image[0]; 261 | $size_array = array( 262 | absint( $image[1] ), 263 | absint( $image[2] ) 264 | ); 265 | 266 | return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id ); 267 | } 268 | 269 | /** 270 | * Creates a 'sizes' attribute value for an image. 271 | * 272 | * @since 3.1.0 273 | * 274 | * @param array|string $size Image size to retrieve. Accepts any valid image size, or an array 275 | * of width and height values in pixels (in that order). 276 | * @param string $image_src Optional. The URL to the image file. Default null. 277 | * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. Default null. 278 | * @param int $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id` is needed 279 | * when using the image size name as argument for `$size`. Default 0. 280 | 281 | * 282 | * @return string|bool A valid source size value for use in a 'sizes' attribute or false. 283 | */ 284 | function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) { 285 | $width = 0; 286 | 287 | if ( is_array( $size ) ) { 288 | $width = absint( $size[0] ); 289 | } elseif ( is_string( $size ) ) { 290 | if ( ! $image_meta && $attachment_id ) { 291 | $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true ); 292 | } 293 | 294 | if ( is_array( $image_meta ) ) { 295 | $size_array = _wp_get_image_size_from_meta( $size, $image_meta ); 296 | if ( $size_array ) { 297 | $width = absint( $size_array[0] ); 298 | } 299 | } 300 | } 301 | 302 | if ( ! $width ) { 303 | return false; 304 | } 305 | 306 | // Setup the default 'sizes' attribute. 307 | $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width ); 308 | 309 | /** 310 | * Filter the output of 'wp_calculate_image_sizes()'. 311 | * 312 | * @since 3.1.0 313 | * 314 | * @param string $sizes A source size value for use in a 'sizes' attribute. 315 | * @param array|string $size Requested size. Image size or array of width and height values 316 | * in pixels (in that order). 317 | * @param string|null $image_src The URL to the image file or null. 318 | * @param array|null $image_meta The image meta data as returned by 'wp_get_attachment_metadata()' or null. 319 | * @param int $attachment_id Image attachment ID of the original image or 0. 320 | */ 321 | return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id ); 322 | } 323 | 324 | /** 325 | * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes. 326 | * 327 | * @since 3.0.0 328 | * 329 | * @see wp_image_add_srcset_and_sizes() 330 | * 331 | * @param string $content The raw post content to be filtered. 332 | * @return string Converted content with 'srcset' and 'sizes' attributes added to images. 333 | */ 334 | function wp_make_content_images_responsive( $content ) { 335 | if ( ! preg_match_all( '/]+>/', $content, $matches ) ) { 336 | return $content; 337 | } 338 | 339 | $selected_images = $attachment_ids = array(); 340 | 341 | foreach( $matches[0] as $image ) { 342 | if ( false === strpos( $image, ' srcset=' ) && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) && 343 | ( $attachment_id = absint( $class_id[1] ) ) ) { 344 | 345 | /* 346 | * If exactly the same image tag is used more than once, overwrite it. 347 | * All identical tags will be replaced later with 'str_replace()'. 348 | */ 349 | $selected_images[ $image ] = $attachment_id; 350 | // Overwrite the ID when the same image is included more than once. 351 | $attachment_ids[ $attachment_id ] = true; 352 | } 353 | } 354 | 355 | if ( count( $attachment_ids ) > 1 ) { 356 | /* 357 | * Warm object cache for use with 'get_post_meta()'. 358 | * 359 | * To avoid making a database call for each image, a single query 360 | * warms the object cache with the meta information for all images. 361 | */ 362 | update_meta_cache( 'post', array_keys( $attachment_ids ) ); 363 | } 364 | 365 | foreach ( $selected_images as $image => $attachment_id ) { 366 | $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true ); 367 | $content = str_replace( $image, wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content ); 368 | } 369 | 370 | return $content; 371 | } 372 | add_filter( 'the_content', 'wp_make_content_images_responsive', 5, 1 ); 373 | 374 | /** 375 | * Adds 'srcset' and 'sizes' attributes to an existing 'img' element. 376 | * 377 | * @since 3.0.0 378 | * 379 | * @see wp_calculate_image_srcset() 380 | * @see wp_calculate_image_sizes() 381 | * 382 | * @param string $image An HTML 'img' element to be filtered. 383 | * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'. 384 | * @param int $attachment_id Image attachment ID. 385 | * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added. 386 | */ 387 | function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) { 388 | // Ensure the image meta exists. 389 | if ( empty( $image_meta['sizes'] ) ) { 390 | return $image; 391 | } 392 | 393 | $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : ''; 394 | list( $image_src ) = explode( '?', $image_src ); 395 | 396 | // Return early if we couldn't get the image source. 397 | if ( ! $image_src ) { 398 | return $image; 399 | } 400 | 401 | // Bail early if an image has been inserted and later edited. 402 | if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) && 403 | strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) { 404 | 405 | return $image; 406 | } 407 | 408 | /** 409 | * To make sure that our ID and image src match, we loop through all the sizes 410 | * in our attachment metadata and bail early if our src file isn't included. 411 | */ 412 | $file_name = wp_basename( $image_src ); 413 | 414 | $all_sizes = wp_list_pluck( $image_meta['sizes'], 'file' ); 415 | $all_sizes[] = $image_meta['file']; 416 | 417 | $matched = false; 418 | 419 | foreach( $all_sizes as $size ) { 420 | if ( false !== strpos( $size, $file_name ) ) { 421 | $matched = true; 422 | break; 423 | } 424 | } 425 | 426 | // Bail early if the image src doesn't match any of the known image sizes. 427 | if ( ! $matched ) { 428 | return $image; 429 | } 430 | 431 | $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0; 432 | $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0; 433 | 434 | if ( ! $width || ! $height ) { 435 | /* 436 | * If attempts to parse the size value failed, attempt to use the image meta data to match 437 | * the image file name from 'src' against the available sizes for an attachment. 438 | */ 439 | $image_filename = wp_basename( $image_src ); 440 | 441 | if ( $image_filename === wp_basename( $image_meta['file'] ) ) { 442 | $width = (int) $image_meta['width']; 443 | $height = (int) $image_meta['height']; 444 | } else { 445 | foreach( $image_meta['sizes'] as $image_size_data ) { 446 | if ( $image_filename === $image_size_data['file'] ) { 447 | $width = (int) $image_size_data['width']; 448 | $height = (int) $image_size_data['height']; 449 | break; 450 | } 451 | } 452 | } 453 | } 454 | 455 | if ( ! $width || ! $height ) { 456 | return $image; 457 | } 458 | 459 | $size_array = array( $width, $height ); 460 | $srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id ); 461 | 462 | if ( $srcset ) { 463 | // Check if there is already a 'sizes' attribute. 464 | $sizes = strpos( $image, ' sizes=' ); 465 | 466 | if ( ! $sizes ) { 467 | $sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id ); 468 | } 469 | } 470 | 471 | if ( $srcset && $sizes ) { 472 | // Format the 'srcset' and 'sizes' string and escape attributes. 473 | $attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) ); 474 | 475 | if ( is_string( $sizes ) ) { 476 | $attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) ); 477 | } 478 | 479 | // Add 'srcset' and 'sizes' attributes to the image markup. 480 | $image = preg_replace( '/]+?)[\/ ]*>/', '', $image ); 481 | } 482 | 483 | return $image; 484 | } 485 | 486 | /* 487 | * Add the filter to add 'srcset' and 'sizes' attributes to post thumbnails and gallery images. 488 | * 'tevkori_filter_attachment_image_attributes()' can be found in wp-tevko-responsive-images.php 489 | */ 490 | add_filter( 'wp_get_attachment_image_attributes', 'tevkori_filter_attachment_image_attributes', 0, 3 ); 491 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ##WordPress 4.4 and Native Responsive Images 2 | 3 | As of WordPress 4.4, images are responsive by default. If you are on WordPress 4.4 or plan to update, you will not need to install this plugin. 4 | 5 | If you have had this plugin installed since before version 2.5 but are running version 4.4 of WordPress, it is important that you leave the plugin installed. This is because all versions of the plugin before version 2.5 relied on a `data-sizes` attribute being present on an image in order to provide the responsive markup needed. If the plugin in this case is removed, then images in posts will be left with invalid markup. We are working to address this issue, and you can [keep track of our progress here](https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/issues/178). 6 | 7 | You can still use the plugin for advanced image compression support or as a simple way to include the picturefill script. The plugin will fall back to WordPress default functions if responsive image support is detected in your installation. 8 | 9 | RICG-responsive-images 10 | --- 11 | 12 | [![Build Status](https://travis-ci.org/ResponsiveImagesCG/wp-tevko-responsive-images.svg?branch=dev)](https://travis-ci.org/ResponsiveImagesCG/wp-tevko-responsive-images) 13 | 14 | Bringing automatic default responsive images to WordPress. 15 | 16 | This plugin works by including all available image sizes for each image upload. Whenever WordPress outputs the image through the media uploader, or whenever a featured image is generated, those sizes will be included in the image tag via the [srcset](http://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-srcset/) attribute. 17 | 18 | ## Contribution Guidelines 19 | 20 | Please submit pull requests to our dev branch. If your contribution requires such, please aim to include appropriate tests with your PR as well. 21 | 22 | ## Documentation 23 | 24 | ### For General Users 25 | 26 | No configuration is needed! Just install the plugin and enjoy automatic responsive images! 27 | 28 | ### For Theme Developers 29 | 30 | This plugin includes several functions that can be used by theme and plugin developers in templates, as well as hooks to filter their output. 31 | 32 | ### Advanced Image Compression 33 | 34 | Advanced image compression is an experimental image editor that makes use of ImageMagick's compression setting to deliver deliver higher quality images at a smaller file sizes. As such, **ImageMagick is required for this feature to work**. To learn more about the actual compression settings being used, read Dave Newton's [excellent writeup at Smashing Magazine](http://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/). 35 | 36 | To enable, place the following code in your `functions.php` file 37 | 38 | ``` 39 | function custom_theme_setup() { 40 | add_theme_support( 'advanced-image-compression' ); 41 | } 42 | add_action( 'after_setup_theme', 'custom_theme_setup' ); 43 | ``` 44 | 45 | ***Known issues:*** 46 | * Some people have encountered memory limits when uploading large files with the advanced image compression settings enabled (see [#150](https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/issues/150)). 47 | 48 | --- 49 | 50 | ### Function/Hook Reference 51 | 52 | #### Function wp_get_attachment_image_srcset 53 | 54 | `wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null )` 55 | 56 | Retrieves the value for an image attachment's `srcset` attribute. 57 | 58 | **Return:** 59 | (string|bool) A `srcset` value string or false. 60 | 61 | ##### Parameters 62 | 63 | **$attachment_id** (int) 64 | Image attachment ID. 65 | 66 | **$size** (array|string) 67 | (Optional) Image size. Accepts any valid image size ('thumbnail', 'medium', etc.), or an array of width and height values in pixels (in that order). Default 'medium'. 68 | 69 | **$image_meta** (array) 70 | (Optional) The image meta data as returned by `wp_get_attachment_metadata()`. Default null. 71 | 72 | ##### Uses 73 | 74 | `wp_calculate_image_srcset()` 75 | 76 | ##### Usage Example 77 | 78 | ``` 79 | 83 | 84 | sizes="{{custom sizes value}}"> 85 | ``` 86 | 87 | ##### Note 88 | 89 | By default, the maximum width of images that are included in the `srcset` is 1600 pixels. You can override this default by adding a filter to `max_srcset_image_width`. 90 | 91 | --- 92 | 93 | #### Function wp_calculate_image_srcset 94 | 95 | `wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 )` 96 | 97 | A helper function to calculate the image sources to include in a `srcset` attribute. 98 | 99 | **Return:** (string|bool) 100 | The `srcset` attribute value. False on error or when only one source exists. 101 | 102 | ##### Parameters 103 | 104 | **$size_array** (array) 105 | Array of width and height values in pixels (in that order). 106 | 107 | **$image_src** (string) 108 | The `src` of the image. 109 | 110 | **$image_meta** (array) 111 | The image meta data as returned by `wp_get_attachment_metadata()`. 112 | 113 | **$attachment_id** (int) 114 | (Optional) Image attachment ID. Default 0. 115 | 116 | ##### Used by 117 | 118 | `wp_get_attachment_image_srcset()` 119 | 120 | ##### Usage Example 121 | 122 | ``` 123 | 136 | 137 | sizes="{{custom sizes value}}"> 138 | ``` 139 | 140 | ##### Note 141 | 142 | By default, the maximum width of images that are included in the `srcset` is 1600 pixels. You can override this default by adding a filter to `max_srcset_image_width`. 143 | 144 | --- 145 | 146 | #### Hook max_srcset_image_width 147 | 148 | `apply_filters( 'max_srcset_image_width', 1600, $size_array )` 149 | 150 | Filter the maximum image width to be included in a `srcset` attribute. 151 | 152 | ##### Parameters 153 | 154 | **$max_width** (int) 155 | The maximum image width to be included in the `srcset`. Default '1600'. 156 | 157 | **$size_array** (array) 158 | Array of width and height values in pixels (in that order). 159 | 160 | ##### Used by 161 | 162 | `wp_calculate_image_srcset()` 163 | 164 | ##### Usage Example 165 | 166 | ``` 167 | 800 ) { 174 | $max_width = 2048; 175 | } 176 | 177 | return $max_width; 178 | } 179 | add_filter( 'max_srcset_image_width', 'custom_max_srcset_image_width', 10, 2 ); 180 | 181 | ?> 182 | ``` 183 | 184 | --- 185 | 186 | #### Hook wp_calculate_image_srcset 187 | 188 | `apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id )` 189 | 190 | Filter an image's `srcset` sources. 191 | 192 | ##### Parameters 193 | 194 | **$sources** (array) 195 | One or more arrays of source data to include in the `srcset`. 196 | 197 | ``` 198 | $width (array) { 199 | $url (string) The URL of an image source. 200 | $descriptor (string) The descriptor type used in the image candidate string, 201 | either 'w' or 'x'. 202 | $value (int) The source width, if paired with a 'w' descriptor or a 203 | pixel density value if paired with an 'x' descriptor. 204 | } 205 | ``` 206 | 207 | **$size_array** (array) 208 | Array of width and height values in pixels (in that order). 209 | 210 | **$image_src** (string) 211 | The `src` of the image. 212 | 213 | **$image_meta** (array) 214 | The image meta data as returned by `wp_get_attachment_metadata()`. 215 | 216 | **$attachment_id** (int) 217 | Image attachment ID or 0. 218 | 219 | ##### Used by 220 | 221 | `wp_calculate_image_srcset()` 222 | 223 | ##### Usage Example 224 | 225 | ``` 226 | $source ) { 236 | if ( $source['value'] > 800 ) { 237 | unset( $sources[ $key ] ); 238 | } 239 | } 240 | } 241 | } 242 | 243 | return $sources; 244 | } 245 | add_filter( 'wp_calculate_image_srcset', 'custom_wp_calculate_image_srcset', 10, 5 ); 246 | 247 | ?> 248 | ``` 249 | 250 | --- 251 | 252 | #### Function wp_get_attachment_image_sizes 253 | 254 | `wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null )` 255 | 256 | Retrieves the value for an image attachment's `sizes` attribute. 257 | 258 | **Return:** (string|bool) 259 | A valid source size value for use in a `sizes` attribute or false. 260 | 261 | ##### Parameters 262 | 263 | **$attachment_id** (int) 264 | Image attachment ID. 265 | 266 | **$size** (array|string) 267 | (Optional) Image size. Accepts any valid image size name ('thumbnail', 'medium', etc.), or an array of width and height values in pixels (in that order). Default 'medium'. 268 | 269 | **$image_meta** (array) 270 | (Optional) The image meta data as returned by `wp_get_attachment_metadata()`. Default null. 271 | 272 | ##### Uses 273 | 274 | `wp_calculate_image_sizes()` 275 | 276 | ##### Usage Example 277 | 278 | ``` 279 | 283 | 284 | srcset="{{custom srcset value}}"> 285 | ``` 286 | 287 | ##### Note 288 | 289 | By default, the sizes attribute will be declared as 100% of the viewport width when the viewport width is smaller than the width of the image, or to the width of the image itself when the viewport is larger than the image: 290 | 291 | `(max-width: {{image-width}}) 100vw, {{image-width}}` 292 | 293 | You can override this default by adding a filter to `wp_calculate_image_sizes`. 294 | 295 | --- 296 | 297 | #### Function wp_calculate_image_sizes 298 | 299 | `wp_calculate_image_sizes( $size, $image_src, $image_meta, $attachment_id = 0 )` 300 | 301 | Creates the `sizes` attribute value for an image. 302 | 303 | **Return:** (string|bool) 304 | A valid source size value for use in a `sizes` attribute or false. 305 | 306 | ##### Parameters 307 | 308 | **$size** (array|string) 309 | Image size. Accepts any valid image size name ('thumbnail', 'medium', etc.), or an array of width and height values in pixels (in that order). 310 | 311 | **$image_src** (string) 312 | (Optional) The URL to the image file. Default null. 313 | 314 | **$image_meta** (array) 315 | (Optional) The image meta data as returned by `wp_get_attachment_metadata()`. Default null. 316 | 317 | **$attachment_id** (int) 318 | (Optional) Image attachment ID. Default 0. 319 | 320 | Either `$image_meta` or `$attachment_id` is needed when using the image size name as argument for `$size`. 321 | 322 | ##### Used by 323 | 324 | `wp_get_attachment_image_sizes()` 325 | 326 | ##### Usage Example 327 | 328 | ``` 329 | 333 | 334 | srcset="{{custom srcset value}}"> 335 | ``` 336 | 337 | ##### Note 338 | 339 | By default, the sizes attribute will be declared as 100% of the viewport width when the viewport width is smaller than the width of the image, or to the width of the image itself when the viewport is larger than the image: 340 | 341 | `(max-width: {{image-width}}) 100vw, {{image-width}}` 342 | 343 | You can override this default by adding a filter to `wp_calculate_image_sizes`. 344 | 345 | --- 346 | 347 | #### Hook wp_calculate_image_sizes 348 | 349 | `apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id )` 350 | 351 | Filter the output of `wp_calculate_image_sizes()`. 352 | 353 | ##### Parameters 354 | 355 | **$sizes** (string) 356 | A source size value for use in a `sizes` attribute. 357 | 358 | **$size** (array|string) 359 | Requested size. Image size name or array of width and height values in pixels (in that order). 360 | 361 | **$image_src** (string|null) 362 | The URL to the image file or null. 363 | 364 | **$image_meta** (array|null) 365 | The image meta data as returned by `wp_get_attachment_metadata()` or null. 366 | 367 | **$attachment_id** (int) 368 | Image attachment ID of the original image or 0. 369 | 370 | ##### Used by 371 | 372 | `wp_calculate_image_sizes()` 373 | 374 | ##### Usage Example 375 | 376 | ``` 377 | $content_width ) { 386 | $upload_dir = wp_upload_dir(); 387 | $upload_baseurl = $upload_dir['baseurl']; 388 | $fullsize_file = $image_meta['file']; 389 | $fullsize_url = trailingslashit( $upload_baseurl ) . $fullsize_file; 390 | 391 | if ( $image_src === $fullsize_url ) { 392 | $sizes = '(max-width: ' . $content_width . 'px) 100vw, ' . $content_width . 'px'; 393 | } 394 | } 395 | } 396 | 397 | return $sizes; 398 | } 399 | add_filter( 'wp_calculate_image_sizes', 'custom_wp_calculate_image_sizes', 10, 5 ); 400 | 401 | ?> 402 | ``` 403 | 404 | --- 405 | 406 | ### Backward Compatibility 407 | 408 | The following filters are used for backward compatibility. If the described case is not applicable you may want to remove the filter from its hook. 409 | 410 | #### data-sizes 411 | 412 | Prior to version 2.5 a `srcset` and `data-sizes` attribute were added to the image while inserting the image in the content and we used a content filter to replace `data-sizes` by `sizes`. As from 2.5 both `srcset` and `sizes` are added to images using a content filter, but images that already have a `srcset` attribute are skipped. For this reason we still replace `data-sizes` by `sizes`. 413 | If you did not use the plugin before version 2.5 or if you have removed `data-sizes` from your content you can remove the filter: 414 | 415 | ``` 416 | remove_filter( 'the_content', 'tevkori_replace_data_sizes' ); 417 | ``` 418 | 419 | #### tevkori_get_sizes() $args param and filter 420 | 421 | The deprecated function `tevkori_get_sizes()` had an `$args` param and a `tevkori_image_sizes_args` filter. To make those still work we added a shim. If you do not use `tevkori_get_sizes()` in your templates, or at least not pass an argument to the `$args` param, and if you don't use the deprecated `tevkori_image_sizes_args` filter hook, you can remove the filter: 422 | 423 | ``` 424 | remove_filter( 'wp_calculate_image_sizes', '_tevkori_image_sizes_args_shim', 1, 5 ); 425 | ``` 426 | 427 | #### wp_get_attachment_image_sizes filter 428 | 429 | In version 3.0 we introduced a new filter: `wp_get_attachment_image_sizes`. In version 3.1 this has been replaced by `wp_calculate_image_sizes`. If you don't use the `wp_get_attachment_image_sizes` filter you can remove the filter that has been added for backward compatibility: 430 | 431 | ``` 432 | remove_filter( 'wp_calculate_image_sizes', 'wp_get_attachment_image_sizes_filter_shim', 10, 5 ); 433 | ``` 434 | 435 | --- 436 | 437 | ### Dependencies 438 | 439 | The only external dependency included in this plugin is [Picturefill](http://scottjehl.github.io/picturefill/) - v3.0.1. If you would like to remove Picturefill (see notes about [browser support](http://scottjehl.github.io/picturefill/#support)), add the following to your functions.php file: 440 | 441 | function mytheme_dequeue_scripts() { 442 | wp_dequeue_script('picturefill'); 443 | } 444 | 445 | add_action('wp_enqueue_scripts', 'mytheme_dequeue_scripts'); 446 | 447 | We use a hook because if you attempt to dequeue a script before it's enqueued, wp_dequeue_script has no effect. (If it's still being loaded, you may need to specify a [priority](http://codex.wordpress.org/Function_Reference/add_action).) 448 | 449 | ## Version 450 | 451 | 3.1.1 452 | 453 | ## Changelog 454 | 455 | **3.1.1** 456 | 457 | - Fixes a bug where the srcset of images in imported content was missing or broken (issue #263). 458 | - Improved calculation of ratio difference for images to be included in the srcset. (issue #260). 459 | - Fixes a bug where `img` tags without ending slash don't get responsive images (issue #259). 460 | - Deprecates the helper function `tevkori_get_media_embedded_in_content()` which is no longer used. 461 | - Makes sure that the setup of default themes doesn't break the tests (issue #261). 462 | - Adds more examples to the Hook Reference in readme.md. 463 | - Corrections and improvements to inline documentation. 464 | 465 | **3.1.0** 466 | 467 | - Adds special handling of GIFs in srcset attributes to preserve animation (issue #223). 468 | - Makes internal srcset/sizes functions more consistent (issue #224). 469 | - Fixes a bug where functions hooked into `tevkori_image_sizes_args` were not firing (issue #226). 470 | - Fixes a bug where custom sizes attributes added via the post editor were being overwritten (issue #227). 471 | - Deprecates hook `wp_get_attachment_image_sizes` (issue #228). 472 | - Fixes a bug where `the_post_thumbnail()` would fail to add srcset/sizes attributes (issue #232). 473 | - Several improvements to internal inline documentation. 474 | - Major improvements to function/hook documentation in readme.md after 3.0.0 changes. 475 | 476 | **3.0.0** 477 | 478 | - Deprecates all core functions that will be merged into WordPress core in 4.4. 479 | - Adds compatibility shims for sites using the plugin's internal functions and hooks. 480 | - Adds a new display filter callback which can be use as general utility function for adding srcset and sizes attributes. 481 | - Fixes a bug when `wp_get_attachment_metadata()` failed to return an array. 482 | - Update our tests to be compatible with WordPress 4.4 483 | - Upgrade to Picturefill 3.0.1 484 | - Clean up inline docs. 485 | 486 | **2.5.2** 487 | 488 | - Numerous performance and usability improvements 489 | - Pass height and width to `tevkori_get_sizes()` 490 | - Improved regex in display filter 491 | - Avoid calling `wp_get_attachment_image_src()` in srcset functions 492 | - Improved coding standards 493 | - Removed second regular expression in content filter 494 | - Improved cache warning function 495 | - Change default `$size` value for all functions to 'medium' 496 | 497 | **2.5.1** 498 | 499 | - Query all images in single request before replacing 500 | - Minor fix to prevent a potential undefined variable notice 501 | - Remove third fallback query from the display filter 502 | 503 | **2.5.0** 504 | 505 | - Responsify all post images by adding `srcset` and `sizes` through a display filter. 506 | - Improve method used to build paths in `tevkori_get_srcset_array()` 507 | - Added Linthub config files 508 | - Returns single source arrays in `tevkori_get_srcset_array()` 509 | - Add tests for PHP7 to our Travis matrix 510 | - Add test coverage for `tevkori_filter_attachment_image_attributes()` 511 | 512 | **2.4.0** 513 | 514 | - Added filter for `tevkori_get_sizes`, with tests 515 | - Added Composer support 516 | - Compare aspect ratio in relative values, not absolute values 517 | - Cleanup of code style and comments added 518 | - Added PHP 5.2 to our Travis test matrix 519 | - Fixed unit test loading 520 | - Preventing duplicates in srcset array 521 | - Updated docs for advanced image compression 522 | - Formatting cleanup in readme.md 523 | - Bump plugin 'Tested up to:' value to 4.3 524 | - Remove extra line from readme.txt 525 | - Added changelog items from 2.3.1 to the readme.txt file 526 | - Added 'sudo: false' to travis.ci to use new TravisCI infrastructure 527 | - Removing the srcset and sizes attributes if there is only one source present for the image 528 | - Use edited image hash to filter out originals from edited images 529 | - Make output of `tevkori_get_srcset_array` filterable 530 | 531 | **2.3.1** 532 | 533 | - First char no longer stripped from file name if there's no slash 534 | - Adding test for when uploads directory not organized by date 535 | - Don't calculate a srcset when the image data returns no width 536 | - Add test for image_downsize returning 0 as a width 537 | 538 | **2.3.0** 539 | 540 | - Improved performance of `get_srcset_array` 541 | - Added advanced image compression option (available by adding hook to functions.php) 542 | - Duplicate entires now filtered out from srcset array 543 | - Upgrade Picturefill to 2.3.1 544 | - Refactoring plugin JavaScript, including a switch to ajax for updating the srcset value when the image is changed in the editor 545 | - Now using `wp_get_attachment_image_attributes` filter for post thumbnails 546 | - Readme and other general code typo fixes 547 | - Gallery images will now contain a srcset attribute 548 | 549 | **2.2.1** 550 | 551 | - JavaScript patch for WordPress 552 | 553 | **2.2.0** 554 | 555 | - The mandatory sizes attribute is now included on all images 556 | - Updated to Picturefill v2.3.0 557 | - Extensive documentation included in readme 558 | - Integrated testing with Travis CLI 559 | - Check if wp.media exists before running JavaScript 560 | - Account for rounding variance when matching ascpect ratios 561 | 562 | **2.1.1** 563 | 564 | - Adding in wp-tevko-responsive-images.js after file not found to be in WordPress repository 565 | - Adjusts the aspect ratio check in `tevkori_get_srcset_array()` to account for rounding variance 566 | 567 | **2.1.0** 568 | 569 | - **This version introduces a breaking change**: There are now two functions. One returns an array of srcset values, and the other returns a string with the `srcset=".."` html needed to generate the responsive image. To retrieve the srcset array, use `tevkori_get_srcset_array( $id, $size )` 570 | - When the image size is changed in the post editor, the srcset values will adjust to match the change. 571 | 572 | **2.0.2** 573 | 574 | - A bugfix correcting a divide by zero error. Some users may have seen this after upgrading to 2.0.1 575 | 576 | **2.0.1** 577 | 578 | - Only outputs the default WordPress sizes, giving theme developers the option to extend as needed 579 | - Added support for featured images 580 | 581 | **2.0.0** 582 | 583 | - Uses [Picturefill 2.2.0 (Beta)](http://scottjehl.github.io/picturefill/) 584 | - Scripts are output to footer 585 | - Image sizes adjusted 586 | - Most importantly, the srcset syntax is being used 587 | - The structure of the plugin is significantly different. The plugin now works by extending the default WordPress image tag functionality to include the srcset attribute. 588 | - Works for cropped images! 589 | - Backwards compatible (images added before plugin install will still be responsive)! 590 | -------------------------------------------------------------------------------- /tests/test-suite.php: -------------------------------------------------------------------------------- 1 | basename( $upload['file'] ), 40 | 'post_content' => '', 41 | 'post_type' => 'attachment', 42 | 'post_parent' => $parent, 43 | 'post_mime_type' => $type, 44 | 'guid' => $upload[ 'url' ], 45 | ); 46 | 47 | // Save the data. 48 | $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $parent ); 49 | wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) ); 50 | 51 | return $id; 52 | } 53 | 54 | /* OUR TESTS */ 55 | 56 | /** 57 | * @expectedDeprecated tevkori_get_srcset_array 58 | */ 59 | function test_tevkori_get_srcset_array() { 60 | global $_wp_additional_image_sizes; 61 | 62 | // Make an image. 63 | $id = self::$large_id; 64 | $srcset = tevkori_get_srcset_array( $id, 'medium' ); 65 | 66 | $year_month = date('Y/m'); 67 | $image_meta = wp_get_attachment_metadata( $id ); 68 | 69 | $intermediates = array( 'medium', 'medium_large', 'large', 'full' ); 70 | 71 | // Add any soft crop intermediate sizes. 72 | foreach ( $_wp_additional_image_sizes as $name => $additional_size ) { 73 | if ( ! $_wp_additional_image_sizes[$name]['crop'] || 0 === $_wp_additional_image_sizes[$name]['height'] ) { 74 | $intermediates[] = $name; 75 | } 76 | } 77 | 78 | foreach( $image_meta['sizes'] as $name => $size ) { 79 | // Whitelist the sizes that should be included so we pick up 'medium_large' in 4.4. 80 | if ( in_array( $name, $intermediates ) ) { 81 | $expected[$size['width']] = 'http://example.org/wp-content/uploads/' . $year_month = date('Y/m') . '/' . $size['file'] . ' ' . $size['width'] . 'w'; 82 | } 83 | } 84 | 85 | // Add the full size width at the end. 86 | $expected[$image_meta['width']] = 'http://example.org/wp-content/uploads/' . $image_meta['file'] . ' ' . $image_meta['width'] .'w'; 87 | 88 | $this->assertSame( $expected, $srcset ); 89 | } 90 | 91 | /** 92 | * A test filter for tevkori_get_srcset_array() that removes any sources 93 | * that are larger than 500px wide. 94 | */ 95 | function _test_tevkori_srcset_array( $array ) { 96 | foreach ( $array as $size => $file ) { 97 | if ( $size > 500 ) { 98 | unset( $array[ $size ] ); 99 | } 100 | } 101 | return $array; 102 | } 103 | 104 | /** 105 | * @expectedDeprecated tevkori_get_srcset_array 106 | * @expectedException PHPUnit_Framework_Error_Notice 107 | */ 108 | function test_filter_tevkori_srcset_array() { 109 | // Add test filter. 110 | add_filter( 'tevkori_srcset_array', array( $this, '_test_tevkori_srcset_array' ) ); 111 | 112 | // Set up our test. 113 | $id = self::$large_id; 114 | $srcset = tevkori_get_srcset_array( $id, 'medium' ); 115 | 116 | // Evaluate that the sizes returned is what we expected. 117 | foreach( $srcset as $width => $source ) { 118 | $this->assertTrue( $width <= 500 ); 119 | } 120 | 121 | // Remove test filter. 122 | remove_filter( 'tevkori_srcset_array', array( $this, '_test_tevkori_srcset_array' ) ); 123 | } 124 | 125 | /** 126 | * @expectedDeprecated tevkori_get_srcset_array 127 | */ 128 | function test_tevkori_get_srcset_array_false() { 129 | // Make an image. 130 | $id = self::$large_id; 131 | $srcset = tevkori_get_srcset_array( 99999, 'foo' ); 132 | 133 | // For canola.jpg we should return. 134 | $this->assertFalse( $srcset ); 135 | } 136 | 137 | /** 138 | * @expectedDeprecated tevkori_get_srcset_array 139 | */ 140 | function test_tevkori_get_srcset_array_random_size_name() { 141 | global $_wp_additional_image_sizes; 142 | 143 | // Make an image. 144 | $id = self::$large_id; 145 | $srcset = tevkori_get_srcset_array( $id, 'foo' ); 146 | 147 | $year_month = date('Y/m'); 148 | $image_meta = wp_get_attachment_metadata( $id ); 149 | 150 | $intermediates = array( 'medium', 'medium_large', 'large', 'full' ); 151 | 152 | // Add any soft crop intermediate sizes. 153 | foreach ( $_wp_additional_image_sizes as $name => $additional_size ) { 154 | if ( ! $_wp_additional_image_sizes[$name]['crop'] || 0 === $_wp_additional_image_sizes[$name]['height'] ) { 155 | $intermediates[] = $name; 156 | } 157 | } 158 | 159 | foreach( $image_meta['sizes'] as $name => $size ) { 160 | // Whitelist the sizes that should be included so we pick up 'medium_large' in 4.4. 161 | if ( in_array( $name, $intermediates ) ) { 162 | $expected[$size['width']] = 'http://example.org/wp-content/uploads/' . $year_month = date('Y/m') . '/' . $size['file'] . ' ' . $size['width'] . 'w'; 163 | } 164 | } 165 | 166 | // Add the full size width at the end. 167 | $expected[$image_meta['width']] = 'http://example.org/wp-content/uploads/' . $image_meta['file'] . ' ' . $image_meta['width'] .'w'; 168 | 169 | $this->assertSame( $expected, $srcset ); 170 | } 171 | 172 | /** 173 | * @expectedDeprecated tevkori_get_srcset_array 174 | */ 175 | function test_tevkori_get_srcset_array_no_date_upoads() { 176 | global $_wp_additional_image_sizes; 177 | 178 | // Save the current setting for uploads folders. 179 | $uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' ); 180 | 181 | // Disable date organized uploads. 182 | update_option( 'uploads_use_yearmonth_folders', 0 ); 183 | 184 | // Make an image. 185 | $id = self::create_upload_object( self::$test_file_name ); 186 | $srcset = tevkori_get_srcset_array( $id, 'medium' ); 187 | $image_meta = wp_get_attachment_metadata( $id ); 188 | 189 | $intermediates = array( 'medium', 'medium_large', 'large', 'full' ); 190 | 191 | // Add any soft crop intermediate sizes. 192 | foreach ( $_wp_additional_image_sizes as $name => $additional_size ) { 193 | if ( ! $_wp_additional_image_sizes[$name]['crop'] || 0 === $_wp_additional_image_sizes[$name]['height'] ) { 194 | $intermediates[] = $name; 195 | } 196 | } 197 | 198 | foreach( $image_meta['sizes'] as $name => $size ) { 199 | // Whitelist the sizes that should be included so we pick up 'medium_large' in 4.4. 200 | if ( in_array( $name, $intermediates ) ) { 201 | $expected[$size['width']] = 'http://example.org/wp-content/uploads/' . $size['file'] . ' ' . $size['width'] . 'w'; 202 | } 203 | } 204 | 205 | // Add the full size width at the end. 206 | $expected[$image_meta['width']] = 'http://example.org/wp-content/uploads/' . $image_meta['file'] . ' ' . $image_meta['width'] .'w'; 207 | 208 | $this->assertSame( $expected, $srcset ); 209 | 210 | // Leave the uploads option the way you found it. 211 | update_option( 'uploads_use_yearmonth_folders', $uploads_use_yearmonth_folders ); 212 | } 213 | 214 | /** 215 | * @expectedDeprecated tevkori_get_srcset 216 | * @expectedDeprecated tevkori_get_srcset_array 217 | */ 218 | function test_tevkori_get_srcset_single_srcset() { 219 | // Make an image. 220 | $id = self::$large_id; 221 | /* 222 | * In our tests, thumbnails would only return a single srcset candidate, 223 | * in which case we don't bother returning a srcset array. 224 | */ 225 | $this->assertTrue( 1 === count( tevkori_get_srcset_array( $id, 'thumbnail' ) ) ); 226 | $this->assertFalse( tevkori_get_srcset( $id, 'thumbnail' ) ); 227 | } 228 | 229 | /** 230 | * Test for filtering out leftover sizes after an image is edited. 231 | * @group 155 232 | * @expectedDeprecated tevkori_get_srcset_array 233 | */ 234 | function test_tevkori_get_srcset_array_with_edits() { 235 | // Make an image. 236 | $id = self::$large_id; 237 | 238 | /* 239 | * For this test we're going to mock metadata changes from an edit. 240 | * Start by getting the attachment metadata. 241 | */ 242 | $meta = wp_get_attachment_metadata( $id ); 243 | 244 | // Mimick hash generation method used in wp_save_image(). 245 | $hash = 'e' . time() . rand( 100, 999 ); 246 | 247 | // Replace file paths for full and medium sizes with hashed versions. 248 | $filename_base = basename( $meta['file'], '.png' ); 249 | $meta['file'] = str_replace( $filename_base, $filename_base . '-' . $hash, $meta['file'] ); 250 | $meta['sizes']['medium']['file'] = str_replace( $filename_base, $filename_base . '-' . $hash, $meta['sizes']['medium']['file'] ); 251 | 252 | // Save edited metadata. 253 | wp_update_attachment_metadata( $id, $meta ); 254 | 255 | // Get the edited image and observe that a hash was created. 256 | $img_url = wp_get_attachment_url( $id ); 257 | 258 | // Calculate a srcset array. 259 | $srcset = tevkori_get_srcset_array( $id, 'medium' ); 260 | 261 | // Test to confirm all sources in the array include the same edit hash. 262 | foreach ( $srcset as $source ) { 263 | $this->assertTrue( false !== strpos( $source, $hash ) ); 264 | } 265 | } 266 | 267 | /** 268 | * Helper function to filter image_downsize and return zero values for width and height. 269 | */ 270 | public function _filter_image_downsize( $out, $id, $size ) { 271 | $img_url = wp_get_attachment_url( $id ); 272 | return array( $img_url, 0, 0 ); 273 | } 274 | 275 | /** 276 | * @expectedDeprecated tevkori_get_srcset_array 277 | */ 278 | function test_tevkori_get_srcset_array_no_width() { 279 | // Filter image_downsize() output. 280 | add_filter( 'image_downsize', array( $this, '_filter_image_downsize' ), 10, 3 ); 281 | 282 | // Make our attachment. 283 | $id = self::create_upload_object( self::$test_file_name ); 284 | $srcset = tevkori_get_srcset_array( $id, 'medium' ); 285 | 286 | // The srcset should be false. 287 | $this->assertFalse( $srcset ); 288 | 289 | // Remove filter. 290 | remove_filter( 'image_downsize', array( $this, '_filter_image_downsize' ) ); 291 | } 292 | 293 | function test_wp_calculate_image_srcset_ratio_variance() { 294 | // Mock data for this test. 295 | $size_array = array( 218, 300); 296 | $image_src = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/2015/12/test-768x1055-218x300.png'; 297 | $image_meta = array( 298 | 'width' => 768, 299 | 'height' => 1055, 300 | 'file' => '2015/12/test-768x1055.png', 301 | 'sizes' => array( 302 | 'thumbnail' => array( 303 | 'file' => 'test-768x1055-150x150.png', 304 | 'width' => 150, 305 | 'height' => 150, 306 | 'mime-type' => 'image/png', 307 | ), 308 | 'medium' => array( 309 | 'file' => 'test-768x1055-218x300.png', 310 | 'width' => 218, 311 | 'height' => 300, 312 | 'mime-type' => 'image/png', 313 | ), 314 | 'custom-600' => array( 315 | 'file' => 'test-768x1055-600x824.png', 316 | 'width' => 600, 317 | 'height' => 824, 318 | 'mime-type' => 'image/png', 319 | ), 320 | 'post-thumbnail' => array( 321 | 'file' => 'test-768x1055-768x510.png', 322 | 'width' => 768, 323 | 'height' => 510, 324 | 'mime-type' => 'image/png', 325 | ), 326 | ), 327 | ); 328 | 329 | $expected_srcset = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/2015/12/test-768x1055-218x300.png 218w, http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/2015/12/test-768x1055-600x824.png 600w, http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/2015/12/test-768x1055.png 768w'; 330 | 331 | $this->assertSame( $expected_srcset, wp_calculate_image_srcset( $size_array, $image_src, $image_meta ) ); 332 | } 333 | 334 | /** 335 | * @expectedDeprecated tevkori_get_srcset_string 336 | */ 337 | function test_tevkori_get_srcset_string() { 338 | global $_wp_additional_image_sizes; 339 | 340 | // Make an image. 341 | $id = self::$large_id; 342 | 343 | $srcset = tevkori_get_srcset_string( $id, 'full' ); 344 | $image_meta = wp_get_attachment_metadata( $id ); 345 | $year_month = date('Y/m'); 346 | 347 | $intermediates = array( 'medium', 'medium_large', 'large', 'full' ); 348 | 349 | // Add any soft crop intermediate sizes. 350 | foreach ( $_wp_additional_image_sizes as $name => $additional_size ) { 351 | if ( ! $_wp_additional_image_sizes[$name]['crop'] || 0 === $_wp_additional_image_sizes[$name]['height'] ) { 352 | $intermediates[] = $name; 353 | } 354 | } 355 | 356 | $expected = ''; 357 | 358 | foreach( $image_meta['sizes'] as $name => $size ) { 359 | // Whitelist the sizes that should be included so we pick up 'medium_large' in 4.4. 360 | if ( in_array( $name, $intermediates ) ) { 361 | $expected .= 'http://example.org/wp-content/uploads/' . $year_month = date('Y/m') . '/' . $size['file'] . ' ' . $size['width'] . 'w, '; 362 | } 363 | } 364 | // Add the full size width at the end. 365 | $expected .= 'http://example.org/wp-content/uploads/' . $image_meta['file'] . ' ' . $image_meta['width'] .'w'; 366 | 367 | $expected = sprintf( 'srcset="%s"', $expected ); 368 | 369 | $this->assertSame( $expected, $srcset ); 370 | } 371 | 372 | /** 373 | * @expectedDeprecated tevkori_get_sizes 374 | */ 375 | function test_tevkori_get_sizes() { 376 | // Make an image. 377 | $id = self::$large_id; 378 | 379 | global $content_width; 380 | 381 | // Test sizes against the default WP sizes. 382 | $intermediates = array( 'thumbnail', 'medium', 'large' ); 383 | 384 | // Make sure themes aren't filtering the sizes array. 385 | remove_all_filters( 'wp_calculate_image_sizes' ); 386 | 387 | foreach( $intermediates as $int ) { 388 | $width = get_option( $int . '_size_w' ); 389 | 390 | $width = ( $width <= $content_width ) ? $width : $content_width; 391 | 392 | $expected = '(max-width: ' . $width . 'px) 100vw, ' . $width . 'px'; 393 | $sizes = tevkori_get_sizes( $id, $int ); 394 | 395 | $this->assertSame( $expected, $sizes ); 396 | } 397 | } 398 | 399 | /** 400 | * @group 226 401 | * @expectedDeprecated tevkori_get_sizes 402 | */ 403 | function test_tevkori_get_sizes_with_args() { 404 | // Make an image. 405 | $id = self::$large_id; 406 | 407 | $args = array( 408 | 'sizes' => array( 409 | array( 410 | 'size_value' => '10em', 411 | 'mq_value' => '60em', 412 | 'mq_name' => 'min-width' 413 | ), 414 | array( 415 | 'size_value' => '20em', 416 | 'mq_value' => '30em', 417 | 'mq_name' => 'min-width' 418 | ), 419 | array( 420 | 'size_value' => 'calc(100vw - 30px)' 421 | ), 422 | ) 423 | ); 424 | 425 | $expected = '(min-width: 60em) 10em, (min-width: 30em) 20em, calc(100vw - 30px)'; 426 | $sizes = tevkori_get_sizes( $id, 'medium', $args ); 427 | 428 | $this->assertSame( $expected, $sizes ); 429 | } 430 | 431 | /** 432 | * A simple test filter for tevkori_get_sizes(). 433 | */ 434 | function _test_tevkori_image_sizes_args( $args ) { 435 | $args['sizes'] = "100vw"; 436 | return $args; 437 | } 438 | 439 | /** 440 | * @group 226 441 | * @expectedDeprecated tevkori_get_sizes 442 | * @expectedException PHPUnit_Framework_Error_Notice 443 | */ 444 | function test_filter_tevkori_get_sizes() { 445 | // Add our test filter. 446 | add_filter( 'tevkori_image_sizes_args', array( $this, '_test_tevkori_image_sizes_args' ) ); 447 | 448 | // Set up our test. 449 | $id = self::$large_id; 450 | $sizes = tevkori_get_sizes($id, 'medium'); 451 | 452 | // Evaluate that the sizes returned is what we expected. 453 | $this->assertSame( $sizes, '100vw' ); 454 | 455 | remove_filter( 'tevkori_image_sizes_args', array( $this, '_test_tevkori_image_sizes_args' ) ); 456 | } 457 | 458 | /** 459 | * @group 226 460 | * @expectedException PHPUnit_Framework_Error_Notice 461 | */ 462 | function test_filter_shim_calculate_image_sizes() { 463 | // Add our test filter. 464 | add_filter( 'tevkori_image_sizes_args', array( $this, '_test_tevkori_image_sizes_args' ) ); 465 | 466 | // A size array is the min required data for `wp_calculate_image_sizes()`. 467 | $size = array( 300, 150 ); 468 | $sizes = wp_calculate_image_sizes( $size, null, null ); 469 | 470 | // Evaluate that the sizes returned is what we expected. 471 | $this->assertSame( $sizes, '100vw' ); 472 | 473 | remove_filter( 'tevkori_image_sizes_args', array( $this, '_test_tevkori_image_sizes_args' ) ); 474 | } 475 | 476 | /** 477 | * @expectedDeprecated tevkori_get_sizes 478 | * @expectedDeprecated tevkori_get_sizes_string 479 | */ 480 | function test_tevkori_get_sizes_string() { 481 | // Make an image. 482 | $id = self::$large_id; 483 | 484 | $sizes = tevkori_get_sizes( $id, 'medium' ); 485 | $sizes_string = tevkori_get_sizes_string( $id, 'medium' ); 486 | 487 | $expected = 'sizes="' . $sizes . '"'; 488 | 489 | $this->assertSame( $expected, $sizes_string ); 490 | } 491 | 492 | /** 493 | * @group 170 494 | */ 495 | function test_wp_make_content_images_responsive() { 496 | // Make an image. 497 | $image_meta = wp_get_attachment_metadata( self::$large_id ); 498 | $size_array = array( $image_meta['sizes']['medium']['width'], $image_meta['sizes']['medium']['height'] ); 499 | 500 | $srcset = sprintf( 'srcset="%s"', esc_attr( wp_get_attachment_image_srcset( self::$large_id, 'medium', $image_meta ) ) ); 501 | $sizes = sprintf( 'sizes="%s"', esc_attr( wp_get_attachment_image_sizes( self::$large_id, 'medium', $image_meta ) ) ); 502 | 503 | // Function used to build HTML for the editor. 504 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); 505 | $img_no_size_in_class = str_replace( 'size-', '', $img ); 506 | $img_no_width_height = str_replace( ' width="' . $size_array[0] . '"', '', $img ); 507 | $img_no_width_height = str_replace( ' height="' . $size_array[1] . '"', '', $img_no_width_height ); 508 | $img_no_size_id = str_replace( 'wp-image-', 'id-', $img ); 509 | $img_with_sizes_attr = str_replace( '', '/>', $img ); 511 | $img_html5 = str_replace( ' />', '>', $img ); 512 | 513 | // Manually add srcset and sizes to the markup from get_image_tag(). 514 | $respimg = preg_replace( '|]+) />|', '', $img ); 515 | $respimg_no_size_in_class = preg_replace( '|]+) />|', '', $img_no_size_in_class ); 516 | $respimg_no_width_height = preg_replace( '|]+) />|', '', $img_no_width_height ); 517 | $respimg_with_sizes_attr = preg_replace('|]+) />|', '', $img_with_sizes_attr ); 518 | $respimg_xhtml = preg_replace( '|]+)/>|', '', $img_xhtml ); 519 | $respimg_html5 = preg_replace( '|]+)>|', '', $img_html5 ); 520 | 521 | $content = ' 522 |

Image, standard. Should have srcset and sizes.

523 | %1$s 524 | 525 |

Image, no size class. Should have srcset and sizes.

526 | %2$s 527 | 528 |

Image, no width and height attributes. Should have srcset and sizes (from matching the file name).

529 | %3$s 530 | 531 |

Image, no attachment ID class. Should NOT have srcset and sizes.

532 | %4$s 533 | 534 |

Image, with sizes attribute. Should NOT have two sizes attributes.

535 | %5$s 536 | 537 |

Image, XHTML 1.0 style (no space before the closing slash). Should have srcset and sizes.

538 | %6$s 539 | 540 |

Image, HTML 5.0 style. Should have srcset and sizes.

541 | %7$s'; 542 | 543 | $content_unfiltered = sprintf( $content, $img, $img_no_size_in_class, $img_no_width_height, $img_no_size_id, $img_with_sizes_attr, $img_xhtml, $img_html5 ); 544 | $content_filtered = sprintf( $content, $respimg, $respimg_no_size_in_class, $respimg_no_width_height, $img_no_size_id, $respimg_with_sizes_attr, $respimg_xhtml, $respimg_html5 ); 545 | 546 | $this->assertSame( $content_filtered, wp_make_content_images_responsive( $content_unfiltered ) ); 547 | } 548 | 549 | /** 550 | * When rendering attributes for responsive images, 551 | * we rely on the 'wp-image-*' class to find the image by ID. 552 | * The class name may not be consistent with attachment IDs in DB when 553 | * working with imported content or when a user has edited 554 | * the 'src' attribute manually. To avoid incorrect images 555 | * being displayed, ensure we don't add attributes in this case. 556 | */ 557 | function test_wp_make_content_images_responsive_wrong() { 558 | $image = get_image_tag( self::$large_id, '', '', '', 'medium' ); 559 | 560 | // Replace the src URL. 561 | $image_wrong_src = preg_replace( '|src="[^"]+"|', 'src="http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/foo.jpg"', $image ); 562 | 563 | $this->assertSame( $image_wrong_src, wp_make_content_images_responsive( $image_wrong_src ) ); 564 | } 565 | 566 | /** 567 | * @group 170 568 | * @expectedDeprecated tevkori_filter_content_images 569 | */ 570 | function test_tevkori_filter_content_images_with_preexisting_srcset() { 571 | // Make an image. 572 | $id = self::$large_id; 573 | 574 | // Generate HTML and add a dummy srcset attribute. 575 | $image_html = get_image_tag( $id, '', '', '', 'medium' ); 576 | $image_html = preg_replace('|]+) />|', '', $image_html ); 577 | 578 | // The content filter should return the image unchanged. 579 | $this->assertSame( $image_html, tevkori_filter_content_images( $image_html ) ); 580 | } 581 | 582 | /** 583 | * @group 159 584 | */ 585 | function test_tevkori_filter_attachment_image_attributes() { 586 | // Make an image. 587 | $id = self::$large_id; 588 | 589 | // Get attachment post data. 590 | $attachment = get_post( $id ); 591 | $image = wp_get_attachment_image_src( $id, 'medium' ); 592 | list( $src, $width, $height ) = $image; 593 | 594 | // Create dummy attributes array. 595 | $attr = array( 596 | 'src' => $src, 597 | 'width' => $width, 598 | 'height' => $height, 599 | ); 600 | 601 | // Apply filter. 602 | $resp_attr = tevkori_filter_attachment_image_attributes( $attr, $attachment, 'medium' ); 603 | 604 | // Test output. 605 | $this->assertTrue( isset( $resp_attr['srcset'] ) ); 606 | $this->assertTrue( isset( $resp_attr['sizes'] ) ); 607 | } 608 | 609 | /** 610 | * @group 159 611 | */ 612 | function test_tevkori_filter_attachment_image_attributes_thumbnails() { 613 | // Make an image. 614 | $id = self::$large_id; 615 | 616 | // Get attachment post data. 617 | $attachment = get_post( $id ); 618 | $image = wp_get_attachment_image_src( $id, 'thumbnail' ); 619 | list( $src, $width, $height ) = $image; 620 | 621 | // Create dummy attributes array. 622 | $attr = array( 623 | 'src' => $src, 624 | 'width' => $width, 625 | 'height' => $height, 626 | ); 627 | 628 | // Apply filter. 629 | $resp_attr = tevkori_filter_attachment_image_attributes( $attr, $attachment, 'thumbnail' ); 630 | 631 | // Test output. 632 | $this->assertFalse( isset( $resp_attr['srcset'] ) ); 633 | $this->assertFalse( isset( $resp_attr['sizes'] ) ); 634 | } 635 | 636 | /** 637 | * Test if full size GIFs don't get a srcset. 638 | */ 639 | function test_wp_calculate_image_srcset_animated_gifs() { 640 | // Mock meta for an animated gif. 641 | $image_meta = array( 642 | 'width' => 1200, 643 | 'height' => 600, 644 | 'file' => 'animated.gif', 645 | 'sizes' => array( 646 | 'thumbnail' => array( 647 | 'file' => 'animated-150x150.gif', 648 | 'width' => 150, 649 | 'height' => 150, 650 | 'mime-type' => 'image/gif' 651 | ), 652 | 'medium' => array( 653 | 'file' => 'animated-300x150.gif', 654 | 'width' => 300, 655 | 'height' => 150, 656 | 'mime-type' => 'image/gif' 657 | ), 658 | 'large' => array( 659 | 'file' => 'animated-1024x512.gif', 660 | 'width' => 1024, 661 | 'height' => 512, 662 | 'mime-type' => 'image/gif' 663 | ), 664 | ) 665 | ); 666 | 667 | $full_src = 'http://example.org/wp-content/uploads/' . $image_meta['file']; 668 | $large_src = 'http://example.org/wp-content/uploads/' . $image_meta['sizes']['large']['file']; 669 | 670 | // Test with soft resized size array. 671 | $size_array = array( 900, 450 ); 672 | 673 | // Full size GIFs should not return a srcset. 674 | $this->assertFalse( wp_calculate_image_srcset( $full_src, $size_array, $image_meta ) ); 675 | // Intermediate sized GIFs should not include the full size in the srcset. 676 | $this->assertFalse( strpos( wp_calculate_image_srcset( $large_src, $size_array, $image_meta ), $full_src ) ); 677 | } 678 | 679 | function test_wp_make_content_images_responsive_schemes() { 680 | $image_meta = wp_get_attachment_metadata( self::$large_id ); 681 | $size_array = array( (int) $image_meta['sizes']['medium']['width'], (int) $image_meta['sizes']['medium']['height'] ); 682 | 683 | $srcset = sprintf( 'srcset="%s"', wp_get_attachment_image_srcset( self::$large_id, $size_array, $image_meta ) ); 684 | $sizes = sprintf( 'sizes="%s"', wp_get_attachment_image_sizes( self::$large_id, $size_array, $image_meta ) ); 685 | 686 | // Build HTML for the editor. 687 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); 688 | $img_https = str_replace( 'http://', 'https://', $img ); 689 | $img_relative = str_replace( 'http://', '//', $img ); 690 | 691 | // Manually add srcset and sizes to the markup from get_image_tag(); 692 | $respimg = preg_replace( '|]+) />|', '', $img ); 693 | $respimg_https = preg_replace( '|]+) />|', '', $img_https ); 694 | $respimg_relative = preg_replace( '|]+) />|', '', $img_relative ); 695 | 696 | $content = ' 697 |

Image, http: protocol. Should have srcset and sizes.

698 | %1$s 699 | 700 |

Image, http: protocol. Should have srcset and sizes.

701 | %2$s 702 | 703 |

Image, protocol-relative. Should have srcset and sizes.

704 | %3$s'; 705 | 706 | $unfiltered = sprintf( $content, $img, $img_https, $img_relative ); 707 | $expected = sprintf( $content, $respimg, $respimg_https, $respimg_relative ); 708 | $actual = wp_make_content_images_responsive( $unfiltered ); 709 | 710 | $this->assertSame( $expected, $actual ); 711 | } 712 | } 713 | --------------------------------------------------------------------------------