margin-bottom: 5px;
}
}
+
+#coverPhotoPreview {
+ background: no-repeat center center;
+ background-size: cover;
+ height: 200px;
+ margin-bottom: 5px;
+
+ @include screen-xs {
+ height: 150px;
+ }
+}
{js application='wcf' acp='true' file='WCF.ACP.Style'}
{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
<script data-relocate="true">
- require(['WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Image/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Editor', 'WoltLabSuite/Core/Ui/Toggle/Input'], function(AcpUiStyleFaviconUpload, AcpUiStyleImageUpload, AcpUiStyleEditor, UiToggleInput) {
+ require([
+ 'WoltLabSuite/Core/Acp/Ui/Style/CoverPhoto/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Favicon/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Image/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Editor', 'WoltLabSuite/Core/Ui/Toggle/Input', 'Language'
+ ], function(
+ AcpUiStyleCoverPhotoUpload, AcpUiStyleFaviconUpload, AcpUiStyleImageUpload, AcpUiStyleEditor, UiToggleInput, Language
+ ) {
AcpUiStyleEditor.setup({
isTainted: {if $isTainted}true{else}false{/if},
styleId: {if $action === 'edit'}{@$style->styleID}{else}0{/if},
styleRuleMap: styleRuleMap
});
- {if $action == 'edit'}new AcpUiStyleFaviconUpload({@$style->styleID});{/if}
new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', false);
new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}', true);
new UiToggleInput('input[name="useGoogleFont"]', {
show: ['#wcfFontFamilyGoogleContainer']
});
+
+ {if $action === 'edit'}
+ Language.addObject({
+ 'wcf.user.coverPhoto.upload.error.invalidExtension': '{lang}wcf.user.coverPhoto.upload.error.invalidExtension{/lang}',
+ 'wcf.user.coverPhoto.upload.error.minHeight': '{lang}wcf.user.coverPhoto.upload.error.minHeight{/lang}',
+ 'wcf.user.coverPhoto.upload.error.minWidth': '{lang}wcf.user.coverPhoto.upload.error.minWidth{/lang}',
+ 'wcf.user.coverPhoto.upload.error.uploadFailed': '{lang}wcf.user.coverPhoto.upload.error.uploadFailed{/lang}'
+ });
+
+ new AcpUiStyleFaviconUpload({@$style->styleID});
+ new AcpUiStyleCoverPhotoUpload({@$style->styleID});
+ {/if}
});
$(function() {
$('.jsUnitSelect').change(function(event) {
var $target = $(event.currentTarget);
- $target.prev().attr('step', (($target.val() == 'em' || $target.val() == 'rem') ? '0.01' : '1'));
+ $target.prev().attr('step', (($target.val() === 'em' || $target.val() === 'rem') ? '0.01' : '1'));
}).trigger('change');
});
</script>
{event name='fileFields'}
</section>
- {if $action == 'edit'}
+ {if $action === 'edit'}
<section class="section">
<h2 class="sectionTitle">{lang}wcf.acp.style.general.favicon{/lang}</h2>
<dt><label for="favicon">{lang}wcf.acp.style.favicon{/lang}</label></dt>
<dd>
<div class="selectedFaviconPreview">
- <img src="{if $action == 'add'}{@$__wcf->getPath()}images/favicon/default.apple-touch-icon.png{else}{@$style->getFaviconAppleTouchIcon()}{/if}" alt="" id="faviconImage" style="height: 32px; width: 32px;">
+ <img src="{@$style->getFaviconAppleTouchIcon()}" alt="" id="faviconImage" style="height: 32px; width: 32px;">
</div>
<div id="uploadFavicon"></div>
<small>{lang}wcf.acp.style.favicon.description{/lang}</small>
{event name='faviconFields'}
</section>
+
+ <section class="section">
+ <h2 class="sectionTitle">{lang}wcf.acp.style.general.coverPhoto{/lang}</h2>
+
+ <dl>
+ <dt><label for="coverPhoto">{lang}wcf.acp.style.coverPhoto{/lang}</label></dt>
+ <dd>
+ <div id="coverPhotoPreview" style="background-image: url({@$__wcf->getPath()}images/coverPhotos/{@$style->getCoverPhoto()})"></div>
+ <div id="uploadCoverPhoto"></div>
+ <small>{lang}wcf.acp.style.coverPhoto.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='coverPhotoFields'}
+ </section>
{/if}
{event name='generalFieldsets'}
--- /dev/null
+/**
+ * Handles uploading the style's cover photo.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+});
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;
'action' => 'edit',
'isTainted' => $this->style->isTainted,
'style' => $this->style,
- 'styleID' => $this->styleID
+ 'styleID' => $this->styleID,
+ 'coverPhotoMinHeight' => UserCoverPhoto::MIN_HEIGHT,
+ 'coverPhotoMinWidth' => UserCoverPhoto::MIN_WIDTH
]);
}
}
* @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 {
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.
*
<?php
namespace wcf\data\style;
+use wcf\data\user\cover\photo\UserCoverPhoto;
use wcf\data\user\UserAction;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\IToggleAction;
// create favicon data
$this->updateFavicons($style->getDecoratedObject());
+ // handle the cover photo
+ $this->updateCoverPhoto($style->getDecoratedObject());
+
// reset stylesheet
StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject());
}
* Updates style favicon files.
*
* @param Style $style
+ * @since 3.1
*/
protected function updateFavicons(Style $style) {
$styleID = $style->styleID;
}
}
+ /**
+ * 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
*/
/**
* Validates parameters to upload a favicon.
+ *
+ * @since 3.1
*/
public function validateUploadFavicon() {
// ignore tmp hash, uploading is supported for existing styles only
* Handles favicon upload.
*
* @return string[]
+ * @since 3.1
*/
public function uploadFavicon() {
// save files
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.
*/
* @inheritDoc
*/
public function getFilename() {
- /*if ($coverPhoto = StyleHandler::getInstance()->getStyle()->getDefaultCoverPhoto()) {
- return $coverPhoto;
- }*/
-
- return 'default.png';
+ return StyleHandler::getInstance()->getStyle()->getCoverPhoto();
}
}
<item name="wcf.acp.style.copyright"><![CDATA[Copyright]]></item>
<item name="wcf.acp.style.copyStyle"><![CDATA[Stil kopieren]]></item>
<item name="wcf.acp.style.copyStyle.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Stil <span class="confirmationObject">{$style->styleName}</span> wirklich duplizieren?]]></item>
+ <item name="wcf.acp.style.coverPhoto"><![CDATA[Standard-Profilbild]]></item>
+ <item name="wcf.acp.style.coverPhoto.description"><![CDATA[Das Bild muss mindestens {$coverPhotoMinWidth}×{$coverPhotoMinHeight} Pixel groß sein, als Dateiendung sind GIF, JPG, JPEG und PNG zulässig.]]></item>
<item name="wcf.acp.style.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Stil <span class="confirmationObject">{$style->styleName}</span> wirklich löschen?]]></item>
<item name="wcf.acp.style.edit"><![CDATA[Stil bearbeiten]]></item>
<item name="wcf.acp.style.exportAsPackage"><![CDATA[Als Paket exportieren]]></item>
<item name="wcf.acp.style.exportStyle.components"><![CDATA[Optionen]]></item>
<item name="wcf.acp.style.exportStyle.components.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier aus, welche Bestandteile des Stils „{$style->styleName}“ mit exportiert werden sollen.]]></item>
<item name="wcf.acp.style.general"><![CDATA[Daten]]></item>
+ <item name="wcf.acp.style.general.coverPhoto"><![CDATA[Profilbild]]></item>
<item name="wcf.acp.style.general.files"><![CDATA[Dateien]]></item>
<item name="wcf.acp.style.globals"><![CDATA[Globale Einstellungen]]></item>
<item name="wcf.acp.style.globals.fixedLayoutWidth"><![CDATA[Breite]]></item>
<item name="wcf.acp.style.copyright"><![CDATA[Copyright]]></item>
<item name="wcf.acp.style.copyStyle"><![CDATA[Duplicate Style]]></item>
<item name="wcf.acp.style.copyStyle.confirmMessage"><![CDATA[Do you really want to duplicate the style <span class="confirmationObject">{$style->styleName}</span>?]]></item>
+ <item name="wcf.acp.style.coverPhoto"><![CDATA[Default Cover Photo]]></item>
+ <item name="wcf.acp.style.coverPhoto.description"><![CDATA[The image must be at least {$coverPhotoMinWidth}×{$coverPhotoMinHeight} pixels large, acceptable image types are GIF, JPG, JPEG and PNG.]]></item>
<item name="wcf.acp.style.delete.confirmMessage"><![CDATA[Do you really want to delete the style <span class="confirmationObject">{$style->styleName}</span>?]]></item>
<item name="wcf.acp.style.edit"><![CDATA[Edit Style]]></item>
<item name="wcf.acp.style.exportAsPackage"><![CDATA[Export as package]]></item>
<item name="wcf.acp.style.exportStyle.components"><![CDATA[Options]]></item>
<item name="wcf.acp.style.exportStyle.components.description"><![CDATA[Select components included in the export for the style “{$style->styleName}”.]]></item>
<item name="wcf.acp.style.general"><![CDATA[Data]]></item>
+ <item name="wcf.acp.style.general.coverPhoto"><![CDATA[Cover Photo]]></item>
<item name="wcf.acp.style.general.favicon"><![CDATA[Favicon]]></item>
<item name="wcf.acp.style.general.files"><![CDATA[Files]]></item>
<item name="wcf.acp.style.globals"><![CDATA[Global Settings]]></item>
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'
);