From 87fc55012b2727d5e4bb6942dc860e182d5ced0c Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Wed, 29 Nov 2017 14:20:01 +0100 Subject: [PATCH] Added support for per-style cover photos See #2484 --- wcfsetup/install/files/acp/style/layout.scss | 11 ++ .../install/files/acp/templates/styleAdd.tpl | 40 ++++++- .../Core/Acp/Ui/Style/CoverPhoto/Upload.js | 59 ++++++++++ .../lib/acp/form/StyleEditForm.class.php | 5 +- .../files/lib/data/style/Style.class.php | 14 +++ .../lib/data/style/StyleAction.class.php | 111 ++++++++++++++++++ .../photo/DefaultUserCoverPhoto.class.php | 6 +- wcfsetup/install/lang/de.xml | 3 + wcfsetup/install/lang/en.xml | 3 + wcfsetup/setup/db/install.sql | 1 + 10 files changed, 242 insertions(+), 11 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js diff --git a/wcfsetup/install/files/acp/style/layout.scss b/wcfsetup/install/files/acp/style/layout.scss index 7c2e75a806..a7efb29927 100644 --- a/wcfsetup/install/files/acp/style/layout.scss +++ b/wcfsetup/install/files/acp/style/layout.scss @@ -371,3 +371,14 @@ $wcfAcpSubMenuWidth: 300px; margin-bottom: 5px; } } + +#coverPhotoPreview { + background: no-repeat center center; + background-size: cover; + height: 200px; + margin-bottom: 5px; + + @include screen-xs { + height: 150px; + } +} diff --git a/wcfsetup/install/files/acp/templates/styleAdd.tpl b/wcfsetup/install/files/acp/templates/styleAdd.tpl index 53a82c6fe4..9b206c46b6 100644 --- a/wcfsetup/install/files/acp/templates/styleAdd.tpl +++ b/wcfsetup/install/files/acp/templates/styleAdd.tpl @@ -5,20 +5,35 @@ {js application='wcf' acp='true' file='WCF.ACP.Style'} {js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'} @@ -308,7 +323,7 @@ {event name='fileFields'} - {if $action == 'edit'} + {if $action === 'edit'}

{lang}wcf.acp.style.general.favicon{/lang}

@@ -316,7 +331,7 @@
- +
{lang}wcf.acp.style.favicon.description{/lang} @@ -325,6 +340,21 @@ {event name='faviconFields'}
+ +
+

{lang}wcf.acp.style.general.coverPhoto{/lang}

+ +
+
+
+
+
+ {lang}wcf.acp.style.coverPhoto.description{/lang} +
+
+ + {event name='coverPhotoFields'} +
{/if} {event name='generalFieldsets'} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js new file mode 100644 index 0000000000..cf4db82b1f --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload.js @@ -0,0 +1,59 @@ +/** + * Handles uploading the style's cover photo. + * + * @author Alexander Ebert + * @copyright 2001-2017 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload + */ +define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], function(Core, DomTraverse, Language, UiNotification, Upload) { + "use strict"; + + /** + * @constructor + */ + function AcpUiStyleCoverPhotoUpload(styleId) { + this._styleId = ~~styleId; + + Upload.call(this, 'uploadCoverPhoto', 'coverPhotoPreview', { + action: 'uploadCoverPhoto', + className: 'wcf\\data\\style\\StyleAction' + }); + } + Core.inherit(AcpUiStyleCoverPhotoUpload, Upload, { + /** + * @see WoltLabSuite/Core/Upload#_createFileElement + */ + _createFileElement: function(file) { + return this._target; + }, + + /** + * @see WoltLabSuite/Core/Upload#_getParameters + */ + _getParameters: function() { + return { + styleID: this._styleId + }; + }, + + /** + * @see WoltLabSuite/Core/Upload#_success + */ + _success: function(uploadId, data) { + var errorMessage = ''; + if (data.returnValues.url) { + this._target.style.setProperty('background-image', 'url(' + data.returnValues.url + '?timestamp=' + Date.now() + ')', ''); + + UiNotification.show(); + } + else if (data.returnValues.errorType) { + errorMessage = Language.get('wcf.user.coverPhoto.upload.error.' + data.returnValues.errorType); + } + + elInnerError(this._button, errorMessage); + } + }); + + return AcpUiStyleCoverPhotoUpload; +}); diff --git a/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php b/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php index f479c2b5a4..2ddc531f2e 100644 --- a/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php @@ -2,6 +2,7 @@ namespace wcf\acp\form; use wcf\data\style\Style; use wcf\data\style\StyleAction; +use wcf\data\user\cover\photo\UserCoverPhoto; use wcf\form\AbstractForm; use wcf\system\exception\IllegalLinkException; use wcf\system\language\I18nHandler; @@ -207,7 +208,9 @@ class StyleEditForm extends StyleAddForm { 'action' => 'edit', 'isTainted' => $this->style->isTainted, 'style' => $this->style, - 'styleID' => $this->styleID + 'styleID' => $this->styleID, + 'coverPhotoMinHeight' => UserCoverPhoto::MIN_HEIGHT, + 'coverPhotoMinWidth' => UserCoverPhoto::MIN_WIDTH ]); } } diff --git a/wcfsetup/install/files/lib/data/style/Style.class.php b/wcfsetup/install/files/lib/data/style/Style.class.php index 1844e91389..71abce2e0a 100644 --- a/wcfsetup/install/files/lib/data/style/Style.class.php +++ b/wcfsetup/install/files/lib/data/style/Style.class.php @@ -31,6 +31,7 @@ use wcf\system\WCF; * @property-read string $packageName package identifier used to export the style as a package or empty (thus style cannot be exported as package) * @property-read integer $isTainted is `0` if the original declarations of an imported or installed style are not and cannot be altered, otherwise `1` * @property-read integer $hasFavicon is `0` if the default favicon data should be used + * @property-read integer $coverPhotoExtension extension of the style's cover photo file * @property-read string $apiVersion the style's compatibility version, possible values: '3.0' or '3.1' */ class Style extends DatabaseObject { @@ -186,6 +187,19 @@ class Style extends DatabaseObject { return $this->getFaviconPath('favicon.ico', false); } + /** + * Returns the cover photo filename. + * + * @return string + */ + public function getCoverPhoto() { + if ($this->coverPhotoExtension) { + return $this->styleID . '.' . $this->coverPhotoExtension; + } + + return 'default.png'; + } + /** * Returns the path to a favicon-related file. * diff --git a/wcfsetup/install/files/lib/data/style/StyleAction.class.php b/wcfsetup/install/files/lib/data/style/StyleAction.class.php index 513917105f..b67d70ebeb 100644 --- a/wcfsetup/install/files/lib/data/style/StyleAction.class.php +++ b/wcfsetup/install/files/lib/data/style/StyleAction.class.php @@ -1,5 +1,6 @@ updateFavicons($style->getDecoratedObject()); + // handle the cover photo + $this->updateCoverPhoto($style->getDecoratedObject()); + // reset stylesheet StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject()); } @@ -270,6 +274,7 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction, * Updates style favicon files. * * @param Style $style + * @since 3.1 */ protected function updateFavicons(Style $style) { $styleID = $style->styleID; @@ -348,6 +353,31 @@ BROWSERCONFIG; } } + /** + * Updates the style cover photo. + * + * @param Style $style + * @since 3.1 + */ + protected function updateCoverPhoto(Style $style) { + $styleID = $style->styleID; + $fileExtension = WCF::getSession()->getVar('styleCoverPhoto-'.$styleID); + if ($fileExtension) { + // remove old image + if ($style->coverPhotoExtension) { + @unlink(WCF_DIR . 'images/coverPhotos/' . $style->getCoverPhoto()); + } + + rename( + WCF_DIR . 'images/coverPhotos/' . $styleID . '.tmp.' . $fileExtension, + WCF_DIR . 'images/coverPhotos/' . $styleID . '.' . $fileExtension + ); + + (new StyleEditor($style))->update(['coverPhotoExtension' => $fileExtension]); + WCF::getSession()->unregister('styleCoverPhoto-'.$style->styleID); + } + } + /** * @inheritDoc */ @@ -558,6 +588,8 @@ BROWSERCONFIG; /** * Validates parameters to upload a favicon. + * + * @since 3.1 */ public function validateUploadFavicon() { // ignore tmp hash, uploading is supported for existing styles only @@ -571,6 +603,7 @@ BROWSERCONFIG; * Handles favicon upload. * * @return string[] + * @since 3.1 */ public function uploadFavicon() { // save files @@ -628,6 +661,84 @@ BROWSERCONFIG; return ['errorType' => $file->getValidationErrorType()]; } + /** + * Validates parameters to upload a cover photo. + * + * @since 3.1 + */ + public function validateUploadCoverPhoto() { + // ignore tmp hash, uploading is supported for existing styles only + // and files will be finally processed on form submit + $this->parameters['tmpHash'] = '@@@WCF_INVALID_TMP_HASH@@@'; + + $this->validateUpload(); + } + + /** + * Handles the cover photo upload. + * + * @return string[] + * @since 3.1 + */ + public function uploadCoverPhoto() { + // save files + /** @noinspection PhpUndefinedMethodInspection */ + /** @var UploadFile[] $files */ + $files = $this->parameters['__files']->getFiles(); + $file = $files[0]; + + try { + if (!$file->getValidationErrorType()) { + $fileLocation = $file->getLocation(); + try { + if (($imageData = getimagesize($fileLocation)) === false) { + throw new UserInputException('coverPhoto'); + } + switch ($imageData[2]) { + case IMAGETYPE_PNG: + case IMAGETYPE_JPEG: + case IMAGETYPE_GIF: + // fine + break; + default: + throw new UserInputException('coverPhoto'); + } + + if ($imageData[0] < UserCoverPhoto::MIN_WIDTH) { + throw new UserInputException('coverPhoto', 'minWidth'); + } + else if ($imageData[1] < UserCoverPhoto::MIN_HEIGHT) { + throw new UserInputException('coverPhoto', 'minHeight'); + } + } + catch (SystemException $e) { + throw new UserInputException('coverPhoto'); + } + + // move uploaded file + if (@copy($fileLocation, WCF_DIR.'images/coverPhotos/'.$this->style->styleID.'.tmp.'.$file->getFileExtension())) { + @unlink($fileLocation); + + // store extension within session variables + WCF::getSession()->register('styleCoverPhoto-'.$this->style->styleID, $file->getFileExtension()); + + // return result + return [ + 'url' => WCF::getPath().'images/coverPhotos/'.$this->style->styleID.'.tmp.'.$file->getFileExtension() + ]; + } + else { + throw new UserInputException('coverPhoto', 'uploadFailed'); + } + } + } + catch (UserInputException $e) { + $file->setValidationErrorType($e->getType()); + } + + return ['errorType' => $file->getValidationErrorType()]; + } + /** * Validates parameters to assign a new default style. */ diff --git a/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php b/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php index a5e8ea7154..09f6e90db1 100644 --- a/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php +++ b/wcfsetup/install/files/lib/data/user/cover/photo/DefaultUserCoverPhoto.class.php @@ -37,10 +37,6 @@ class DefaultUserCoverPhoto implements IUserCoverPhoto { * @inheritDoc */ public function getFilename() { - /*if ($coverPhoto = StyleHandler::getInstance()->getStyle()->getDefaultCoverPhoto()) { - return $coverPhoto; - }*/ - - return 'default.png'; + return StyleHandler::getInstance()->getStyle()->getCoverPhoto(); } } diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index b18c6bc8ca..c5616dcd46 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -1930,6 +1930,8 @@ Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigu {$style->styleName} wirklich duplizieren?]]> + + {$style->styleName} wirklich löschen?]]> @@ -1941,6 +1943,7 @@ Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigu styleName}“ mit exportiert werden sollen.]]> + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index a8ddd0bdf4..5d9b2b6c76 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1871,6 +1871,8 @@ When prompted for the notification URL for the instant payment notifications, pl {$style->styleName}?]]> + + {$style->styleName}?]]> @@ -1882,6 +1884,7 @@ When prompted for the notification URL for the instant payment notifications, pl styleName}”.]]> + diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index a6079a06c5..30634e9126 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -1271,6 +1271,7 @@ CREATE TABLE wcf1_style ( packageName VARCHAR(255) NOT NULL DEFAULT '', isTainted TINYINT(1) NOT NULL DEFAULT 0, hasFavicon TINYINT(1) NOT NULL DEFAULT 0, + coverPhotoExtension VARCHAR(4) NOT NULL DEFAULT '', apiVersion ENUM('3.0', '3.1') NOT NULL DEFAULT '3.0' ); -- 2.20.1