Support for WebP images (#3861)
authorAlexander Ebert <ebert@woltlab.com>
Mon, 11 Jan 2021 15:02:46 +0000 (16:02 +0100)
committerGitHub <noreply@github.com>
Mon, 11 Jan 2021 15:02:46 +0000 (16:02 +0100)
* Support for WebP images

* Inconsistent quote style

19 files changed:
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/systemCheck.tpl
wcfsetup/install/files/js/3rdParty/redactor2/redactor.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLabSuite/Core/Upload.js
wcfsetup/install/files/lib/acp/page/SystemCheckPage.class.php
wcfsetup/install/files/lib/data/style/StyleEditor.class.php
wcfsetup/install/files/lib/data/user/avatar/UserAvatarAction.class.php
wcfsetup/install/files/lib/page/AttachmentPage.class.php
wcfsetup/install/files/lib/page/MediaPage.class.php
wcfsetup/install/files/lib/system/file/upload/UploadHandler.class.php
wcfsetup/install/files/lib/system/image/adapter/GDImageAdapter.class.php
wcfsetup/install/files/lib/system/upload/UserCoverPhotoUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/util/FileUtil.class.php
wcfsetup/install/files/lib/util/ImageUtil.class.php
wcfsetup/install/files/ts/WoltLabSuite/Core/Upload.ts
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index db4b49739625248e7689979ebf0548bb71dbfe3f..79a68163a5b54fc13147efeacf51bd8c8ee0a1f3 100644 (file)
 jpg
 jpeg
 png
+webp
 bmp
 zip
 txt
@@ -624,6 +625,7 @@ pdf</defaultvalue>
 jpg
 jpeg
 png
+webp
 bmp
 zip
 txt
@@ -685,6 +687,7 @@ pdf</defaultvalue>
 jpg
 jpeg
 png
+webp
 bmp</defaultvalue>
                                <wildcard>*</wildcard>
                                <options>module_user_signature</options>
@@ -811,7 +814,8 @@ bmp</defaultvalue>
                                <defaultvalue>gif
 jpg
 jpeg
-png</defaultvalue>
+png
+webp</defaultvalue>
                                <usersonly>1</usersonly>
                        </option>
                        <option name="user.profile.coverPhoto.canSeeCoverPhotos">
index 4a61d864249ef9c386eb13f50a2408fa8bcc84bb..5885be15637f3cb3830bb1f2acf5c3e032eeb236 100644 (file)
                                        {if !$results[php][gd][png]}
                                                <li>{@$statusInsufficient} <kbd>png</kbd></li>
                                        {/if}
+                                       {if !$results[php][gd][webp]}
+                                               <li>{@$statusInsufficient} <kbd>webp</kbd></li>
+                                       {/if}
                                </ul>
                        {/if}
                        <small>{lang}wcf.acp.systemCheck.php.gd.description{/lang}</small>
index 3f9b99569949ff4748313ebd659a71582cd4d57b..3e27079dd9b4acb0fc91597a09279abee6558ac5 100644 (file)
                emptyHtml: '<p>&#x200b;</p>',
                invisibleSpace: '&#x200b;',
                emptyHtmlRendered: $('').html('​').html(),
-               imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
+               imageTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
                userAgent: navigator.userAgent.toLowerCase(),
                observe: {
                        dropdowns: []
index 80c031f577686420e8ec9a5c491c40dee73f5aff..4ced08677cbcf123a84059edeb803c4dfef5f3cb 100644 (file)
@@ -335,8 +335,8 @@ WCF.Attachment.Upload = WCF.Upload.extend({
                                // As our resizer is based on Pica it will use multiple workers per image if possible.
                                promise = Array.prototype.reduce.call(files, (function (acc, file) {
                                        return acc.then((function (arr) {
-                                               // Ignore anything that is not one of the 3 basic image types.
-                                               if (['image/png', 'image/gif', 'image/jpeg'].indexOf(file.type) === -1) {
+                                               // Ignore anything that is not one of the 4 basic image types.
+                                               if (['image/png', 'image/gif', 'image/jpeg', 'image/webp'].indexOf(file.type) === -1) {
                                                        arr.push(file);
                                                        return arr;
                                                }
index 2bfd8782b7bc77fec98c49f1fa152c450ba5e942..9e80380a4ac28ebbdb8ea29eb28ba49e2a90918d 100755 (executable)
@@ -6076,6 +6076,10 @@ WCF.Upload = Class.extend({
                                        case 'image/gif':
                                                $ext = '.gif';
                                                break;
+                                       
+                                       case 'image/webp':
+                                               $ext = '.webp';
+                                               break;
                                }
                                
                                $files.push({
index b97bf49df01fec7ae6475ec8cc965aa340f0c62c..74a86faab1d64f83acc2b1bbdb48eb161d3f899f 100644 (file)
@@ -215,6 +215,9 @@ define(["require", "exports", "tslib", "./Ajax/Request", "./Core", "./Dom/Change
                     case "image/png":
                         fileExtension = "png";
                         break;
+                    case "image/webp":
+                        fileExtension = "webp";
+                        break;
                 }
                 files.push({
                     name: `pasted-from-clipboard.${fileExtension}`,
index 198788a85d27bfd7a48fa65f143200754aa19e40..a0c35089da552349a4d90556aaebf94fad48c1df 100644 (file)
@@ -133,6 +133,7 @@ class SystemCheckPage extends AbstractPage {
                        'gd' => [
                                'jpeg' => false,
                                'png' => false,
+                               'webp' => false,
                                'result' => false,
                        ],
                        'extension' => [],
@@ -384,8 +385,11 @@ class SystemCheckPage extends AbstractPage {
                $gdInfo = \gd_info();
                $this->results['php']['gd']['jpeg'] = !empty($gdInfo['JPEG Support']);
                $this->results['php']['gd']['png'] = !empty($gdInfo['PNG Support']);
+               $this->results['php']['gd']['webp'] = !empty($gdInfo['WebP Support']);
                
-               $this->results['php']['gd']['result'] = $this->results['php']['gd']['jpeg'] && $this->results['php']['gd']['png'];
+               $this->results['php']['gd']['result'] = $this->results['php']['gd']['jpeg']
+                       && $this->results['php']['gd']['png']
+                       && $this->results['php']['gd']['webp'];
                
                $this->results['status']['php'] = $this->results['status']['php'] && $this->results['php']['gd']['result'];
        }
index e8cc533f4146563c5ea89d3ed6d6f175779ab3d6..51354466ce3900be22101463abcfbb726b4ac4b7 100644 (file)
@@ -43,7 +43,7 @@ use wcf\util\XMLWriter;
 class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject {
        const EXCLUDE_WCF_VERSION = '6.0.0 Alpha 1';
        const INFO_FILE = 'style.xml';
-       const VALID_IMAGE_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg', 'xml', 'json'];
+       const VALID_IMAGE_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg', 'xml', 'json', 'webp'];
        
        /**
         * list of compatible API versions
@@ -904,7 +904,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        $imagesTar = new TarWriter($imagesTarName);
                        FileUtil::makeWritable($imagesTarName);
                        
-                       $regEx = new Regex('\.(jpg|jpeg|gif|png|svg|ico|json|xml|txt)$', Regex::CASE_INSENSITIVE);
+                       $regEx = new Regex('\.(jpg|jpeg|gif|png|svg|ico|json|xml|txt|webp)$', Regex::CASE_INSENSITIVE);
                        $iterator = new \RecursiveIteratorIterator(
                                new \RecursiveDirectoryIterator(
                                        $this->getAssetPath(),
index e63fac3b3b28dfc0d5c6e9e86b7a0c32150809e1..9c99416f2db0bfe3fb92b500f7f681a9c6f963d9 100644 (file)
@@ -149,7 +149,7 @@ class UserAvatarAction extends AbstractDatabaseObjectAction {
                if ($imageData !== false) {
                        $tmp['extension'] = ImageUtil::getExtensionByMimeType($imageData['mime']);
                        
-                       if (!in_array($tmp['extension'], ['jpeg', 'jpg', 'png', 'gif'])) {
+                       if (!in_array($tmp['extension'], ['jpeg', 'jpg', 'png', 'gif', 'webp'])) {
                                @unlink($filename);
                                return;
                        }
index 182f98d674dc382bebecb4b92689a12a29313674..ab25f3f70a26a839493e672b33b0004a661efb3a 100644 (file)
@@ -56,7 +56,7 @@ class AttachmentPage extends AbstractPage {
         * list of mime types which belong to files that are displayed inline
         * @var string[]
         */
-       public static $inlineMimeTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/x-png', 'application/pdf', 'image/pjpeg'];
+       public static $inlineMimeTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/x-png', 'application/pdf', 'image/pjpeg', 'image/webp'];
        
        /**
         * etag for this attachment
index 6d7e1c2a7cab2a7f58932a5214bb6f3d5289cf35..2a922fe7dd2532f438709701585ed682d65d39b0 100644 (file)
@@ -64,7 +64,8 @@ class MediaPage extends AbstractPage {
                'image/png',
                'image/x-png',
                'application/pdf',
-               'image/pjpeg'
+               'image/pjpeg',
+               'image/webp',
        ];
        
        /**
index 7c4e92903de5d7ffcfc7458e5251d74a30581d4d..f27ac697b8b105e71bc06cceb0146a5ea9cbb01c 100644 (file)
@@ -27,7 +27,7 @@ class UploadHandler extends SingletonFactory {
         * @var string
         * @deprecated 5.3 Use \wcf\util\ImageUtil::$imageExtensions instead (direct replacement).
         */
-       const VALID_IMAGE_EXTENSIONS = ['jpeg', 'jpg', 'png', 'gif'];
+       const VALID_IMAGE_EXTENSIONS = ['jpeg', 'jpg', 'png', 'gif', 'webp'];
        
        /**
         * Contains the registered upload fields. 
index 5ffad1643824055f0135486255984268f62ccf5b..9d61e08f67ac7047db35861509ea6eb8331742f5 100644 (file)
@@ -111,9 +111,16 @@ class GDImageAdapter implements IImageAdapter {
                                }
                        break;
                        
+                       case IMAGETYPE_WEBP:
+                               // suppress warnings and properly handle errors
+                               $this->image = @imagecreatefromwebp($file);
+                               if ($this->image === false) {
+                                       throw new SystemException("Could not read webp image '".$file."'.");
+                               }
+                       break;
+                       
                        default:
                                throw new SystemException("Could not read image '".$file."', format is not recognized.");
-                       break;
                }
        }
        
@@ -348,6 +355,9 @@ class GDImageAdapter implements IImageAdapter {
                else if ($this->type == IMAGETYPE_PNG) {
                        imagepng($image);
                }
+               else if ($this->type == IMAGETYPE_WEBP) {
+                       imagewebp($image);
+               }
                else if (function_exists('imageJPEG')) {
                        imagejpeg($image, null, 90);
                }
index efc03f53877643e0122b346bf3d044a443f8e849..d937531bb63984182d1ecc6c83c9d8fc97d972c1 100644 (file)
@@ -19,7 +19,7 @@ class UserCoverPhotoUploadFileValidationStrategy implements IUploadFileValidatio
         * list of allowed file extensions
         * @var string[]
         */
-       public static $allowedExtensions = ['gif', 'jpg', 'jpeg', 'png'];
+       public static $allowedExtensions = ['gif', 'jpg', 'jpeg', 'png', 'webp'];
        
        /**
         * @inheritDoc
index 95b998227bf181fdfc3e90c4f60072c515a09eaf..2e4887656de12db653891a556f380c426f5ca886 100644 (file)
@@ -659,7 +659,7 @@ final class FileUtil {
                        // excel
                        'xls' => 'excel', 'ods' => 'excel', 'xlsx' => 'excel',
                        // image
-                       'gif' => 'image', 'jpg' => 'image', 'jpeg' => 'image', 'png' => 'image', 'bmp' => 'image',
+                       'gif' => 'image', 'jpg' => 'image', 'jpeg' => 'image', 'png' => 'image', 'bmp' => 'image', 'webp' => 'image',
                        // video
                        'avi' => 'video', 'wmv' => 'video', 'mov' => 'video', 'mp4' => 'video', 'mpg' => 'video', 'mpeg' => 'video', 'flv' => 'video',
                        // pdf
index 51dd03b3f33b97fedd87f79395874873dda89aaf..9bfa60939d8b44d66b07a3ff822beb71858fbabd 100644 (file)
@@ -16,7 +16,7 @@ final class ImageUtil {
         * image extensions
         * @var array
         */
-       protected static $imageExtensions = ['jpeg', 'jpg', 'png', 'gif'];
+       protected static $imageExtensions = ['jpeg', 'jpg', 'png', 'gif', "webp"];
        
        /**
         * Checks the content of an image for bad sections, e.g. the use of javascript
@@ -92,6 +92,8 @@ final class ImageUtil {
                                return 'bmp';
                        case 'image/tiff':
                                return 'tiff';
+                       case 'image/webp':
+                               return 'webp';
                        default:
                                return '';
                }
index e54c181f0515828d95ba5c4c095b94de98fc6323..b7b6cd321479c115c05612ae92a3ac5d441bf737 100644 (file)
@@ -278,6 +278,9 @@ abstract class Upload<TOptions extends UploadOptions = UploadOptions> {
         case "image/png":
           fileExtension = "png";
           break;
+        case "image/webp":
+          fileExtension = "webp";
+          break;
       }
       files.push({
         name: `pasted-from-clipboard.${fileExtension}`,
index 0f192deea99ac46c712891d89e357f699ab43173..7b72bbe8c9cfd1776c4b59196696658603d88e11 100644 (file)
@@ -2766,7 +2766,7 @@ Kein Abschnitt darf leer sein und alle Abschnitten dürfen nur folgende Zeichen
                <item name="wcf.acp.systemCheck.directories.writable"><![CDATA[Beschreibbare Verzeichnisse]]></item>
                <item name="wcf.acp.systemCheck.directories.writable.description"><![CDATA[Einige Verzeichnisse werden zur Laufzeit durch die Software beschrieben, der PHP-Benutzer muss Schreibrechte haben.]]></item>
                <item name="wcf.acp.systemCheck.php.gd"><![CDATA[Unterstützte Formate der GD-Bibliothek]]></item>
-               <item name="wcf.acp.systemCheck.php.gd.description"><![CDATA[Die GD-Bibliothek muss inklusive der Unterstützung der Formate „jpeg“ und „png“ installiert sein.]]></item>
+               <item name="wcf.acp.systemCheck.php.gd.description"><![CDATA[Die GD-Bibliothek muss inklusive der Unterstützung der Formate „jpeg“, „png“ und „webp“ installiert sein.]]></item>
        </category>
        <category name="wcf.acp.updateServer">
                <item name="wcf.acp.updateServer.add"><![CDATA[Server hinzufügen]]></item>
@@ -4019,7 +4019,7 @@ Dateianhänge:
                <item name="wcf.image.coverPhoto.upload.error.minWidth"><![CDATA[Das Bild ist zu schmal.]]></item>
                <item name="wcf.image.coverPhoto.upload.error.uploadFailed"><![CDATA[Beim Hochladen der Datei ist ein unbekannter Fehler aufgetreten.]]></item>
                <item name="wcf.image.coverPhoto.upload.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
-               <item name="wcf.image.coverPhoto.upload.limits"><![CDATA[Minimale Bildgröße: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} Pixel<br>Maximale Bildgröße: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} Pixel<br>Erlaubte Dateiendungen: gif, jpg, jpeg, png<br>Maximale Dateigröße: {$__wcf->session->getPermission($coverPhotoPermissionMaxSize)|filesize}]]></item>
+               <item name="wcf.image.coverPhoto.upload.limits"><![CDATA[Minimale Bildgröße: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} Pixel<br>Maximale Bildgröße: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} Pixel<br>Erlaubte Dateiendungen: gif, jpg, jpeg, png, webp<br>Maximale Dateigröße: {$__wcf->session->getPermission($coverPhotoPermissionMaxSize)|filesize}]]></item>
        </category>
        <category name="wcf.imageViewer">
                <item name="wcf.imageViewer.button.enlarge"><![CDATA[Vollbild-Modus]]></item>
@@ -5184,7 +5184,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.coverPhoto.error.disabled"><![CDATA[Der Administrator hat{if $__wcf->user->coverPhotoHash} {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} derzeitiges Titelbild gesperrt und{/if} {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} die weitere Nutzungsberechtigung der Titelbild-Funktion {if !$__wcf->user->disableCoverPhotoReason}entzogen.{else} aus folgenden Gründen entzogen: {$__wcf->user->disableCoverPhotoReason}{/if}]]></item>
                <item name="wcf.user.coverPhoto.noImage"><![CDATA[Der Benutzer hat noch kein Titelbild hochgeladen.]]></item>
                <item name="wcf.user.coverPhoto.upload"><![CDATA[Titelbild hochladen]]></item>
-               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimale Bildgröße: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} Pixel<br>Maximale Bildgröße: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} Pixel<br>Erlaubte Dateiendungen: gif, jpg, jpeg, png<br>Maximale Dateigröße: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
+               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimale Bildgröße: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} Pixel<br>Maximale Bildgröße: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} Pixel<br>Erlaubte Dateiendungen: gif, jpg, jpeg, png, webp<br>Maximale Dateigröße: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
                <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} kein gültiges Bild hochgeladen.]]></item>
                <item name="wcf.user.coverPhoto.upload.error.fileExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
                <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[Das Bild ist zu hoch.]]></item>
index 073855adbcea230f76e1cee09396b82e007627ff..8e7e61aee83d03c79a80ec359ece3eae21bd361d 100644 (file)
@@ -2694,7 +2694,7 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.systemCheck.directories.writable"><![CDATA[Writable Directories]]></item>
                <item name="wcf.acp.systemCheck.directories.writable.description"><![CDATA[Some directories are being written to during the normal operation. The user that runs the PHP process must have write access.]]></item>
                <item name="wcf.acp.systemCheck.php.gd"><![CDATA[Supported formats of the GD library]]></item>
-               <item name="wcf.acp.systemCheck.php.gd.description"><![CDATA[The GD library must be installed with support for the formats “jpeg” and “png”.]]></item>
+               <item name="wcf.acp.systemCheck.php.gd.description"><![CDATA[The GD library must be installed with support for the formats “jpeg”, “png” and “webp”.]]></item>
        </category>
        <category name="wcf.acp.updateServer">
                <item name="wcf.acp.updateServer.add"><![CDATA[Add Server]]></item>
@@ -3965,7 +3965,7 @@ Attachments:
                <item name="wcf.image.coverPhoto.upload.error.minWidth"><![CDATA[The image is too small.]]></item>
                <item name="wcf.image.coverPhoto.upload.error.uploadFailed"><![CDATA[An unknown error occurred during the upload.]]></item>
                <item name="wcf.image.coverPhoto.upload.error.invalidExtension"><![CDATA[The file extension is invalid.]]></item>
-               <item name="wcf.image.coverPhoto.upload.limits"><![CDATA[Minimum Image Size: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} pixels<br>Maximum Image Size: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} pixels<br>Allowed File Extensions: gif, jpg, jpeg, png<br>Maximum Filesize: {$__wcf->session->getPermission($coverPhotoPermissionMaxSize)|filesize}]]></item>
+               <item name="wcf.image.coverPhoto.upload.limits"><![CDATA[Minimum Image Size: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} pixels<br>Maximum Image Size: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} pixels<br>Allowed File Extensions: gif, jpg, jpeg, png, webp<br>Maximum Filesize: {$__wcf->session->getPermission($coverPhotoPermissionMaxSize)|filesize}]]></item>
        </category>
        <category name="wcf.imageViewer">
                <item name="wcf.imageViewer.button.enlarge"><![CDATA[Full Screen Mode]]></item>
@@ -5181,7 +5181,7 @@ You also received a list of emergency codes to use when your second factor becom
                <item name="wcf.user.coverPhoto.error.disabled"><![CDATA[The administrators {if $__wcf->user->coverPhotoHash}have banned your cover photo and {/if}disallowed you from using a cover photo{if $__wcf->user->disableCoverPhotoReason}: {$__wcf->user->disableCoverPhotoReason}{/if}.]]></item>
                <item name="wcf.user.coverPhoto.noImage"><![CDATA[The user has not yet uploaded a cover photo.]]></item>
                <item name="wcf.user.coverPhoto.upload"><![CDATA[Upload Cover Photo]]></item>
-               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimum Image Size: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} pixels<br>Maximum Image Size: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} pixels<br>Allowed File Extensions: gif, jpg, jpeg, png<br>Maximum Filesize: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
+               <item name="wcf.user.coverPhoto.upload.description"><![CDATA[Minimum Image Size: {$coverPhotoDimensions.min.width}×{$coverPhotoDimensions.min.height} pixels<br>Maximum Image Size: {$coverPhotoDimensions.max.width}×{$coverPhotoDimensions.max.height} pixels<br>Allowed File Extensions: gif, jpg, jpeg, png, webp<br>Maximum Filesize: {$__wcf->session->getPermission('user.profile.coverPhoto.maxSize')|filesize}]]></item>
                <item name="wcf.user.coverPhoto.upload.error.badImage"><![CDATA[The uploaded file is not an image.]]></item>
                <item name="wcf.user.coverPhoto.upload.error.fileExtension"><![CDATA[The file has an invalid extension.]]></item>
                <item name="wcf.user.coverPhoto.upload.error.maxHeight"><![CDATA[The image is too tall.]]></item>