--- /dev/null
+<img
+ src="{@$__wcf->getPath()}images/trophy/{$trophy->iconFile}"
+ style="width:{$size}px;height:{$size}px"
+ title="{$trophy->getTitle()}"
+ class="jsTooltip trophyIcon"
+ data-trophy-id="{$trophy->getObjectID()}"
+/>
'wcf.style.colorPicker.current': '{lang}wcf.style.colorPicker.current{/lang}',
'wcf.style.colorPicker.button.apply': '{lang}wcf.style.colorPicker.button.apply{/lang}',
'wcf.acp.style.image.error.invalidExtension': '{lang}wcf.acp.style.image.error.invalidExtension{/lang}',
- 'wcf.acp.trophy.badge.edit': '{lang}wcf.acp.trophy.badge.edit{/lang}'
+ 'wcf.acp.trophy.badge.edit': '{lang}wcf.acp.trophy.badge.edit{/lang}',
+ 'wcf.acp.trophy.imageUpload.error.notSquared': '{lang}wcf.acp.trophy.imageUpload.error.notSquared{/lang}',
+ 'wcf.acp.trophy.imageUpload.error.tooSmall': '{lang}wcf.acp.trophy.imageUpload.error.tooSmall{/lang}',
+ 'wcf.acp.trophy.imageUpload.error.noImage': '{lang}wcf.acp.trophy.imageUpload.error.noImage{/lang}'
});
elBySel('select[name=type]').addEventListener('change', function () {
</dl>
<dl>
- <dt>{lang}wcf.acp.trophy.type{/lang}</dt>
+ <dt><label for="type">{lang}wcf.acp.trophy.type{/lang}</label></dt>
<dd>
<select name="type" id="type">
{foreach from=$availableTypes item=trophyType key=key}
<section id="imageContainer" class="section"{if $type == 2} style="display: none;"{/if}>
<header class="sectionHeader">
- <h2 class="sectionTitle">{lang}wcf.acp.trophy.type.image{/lang}</h2>
+ <h2 class="sectionTitle">{lang}wcf.acp.trophy.type.imageUpload{/lang}</h2>
</header>
- {* @TODO *}
+ <dl{if $errorField == 'imageUpload'} class="formError"{/if}>
+ <dt>{lang}wcf.acp.trophy.type.imageUpload{/lang}</dt>
+ <dd>
+ <input type="hidden" name="tmpHash" value="{$tmpHash}" />
+ <div class="row">
+ <div class="col-md-6">
+ <div id="uploadIconFileButton"></div>
+ {if $errorField == 'imageUpload'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.trophy.type.imageUpload.description{/lang}</small>
+ </div>
+ <div class="col-md-6">
+ <div id="uploadIconFileContent">{if $action == 'add'}{if !$uploadedImageURL|empty}<img src="{$uploadedImageURL}">{/if}{else}{if $trophy->type == 2}<img src="{$__wcf->getPath()}images/trophy/{$trophy->iconFile}">{/if}{/if}</div>
+ </div>
+ </div>
+
+ <script data-relocate="true">
+ require(['WoltLabSuite/Core/Acp/Ui/Trophy/Upload'], function(IconUpload) {
+ new IconUpload({if $action == 'add'}0{else}{$trophy->trophyID}{/if}, '{$tmpHash}', {
+ input: 'uploadIconFile'
+ });
+ });
+ </script>
+ </dd>
+ </dl>
</section>
<section id="badgeContainer" class="section"{if $type == 1} style="display: none;"{/if}>
--- /dev/null
+<img
+ src="{@$__wcf->getPath()}images/trophy/{$trophy->iconFile}"
+ style="width:{$size}px;height:{$size}px"
+ title="{$trophy->getTitle()}"
+ class="jsTooltip trophyIcon"
+ data-trophy-id="{$trophy->getObjectID()}"
+/>
<tr class="trophyRow">
<td class="columnIcon">
<span class="icon icon16 fa-{if !$trophy->isDisabled}check-{/if}square-o jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if !$trophy->isDisabled}disable{else}enable{/if}{/lang}" data-object-id="{@$trophy->getObjectID()}"></span>
- <a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" data-tooltip="{lang}wcf.global.edit{/lang}"><span class="icon icon16 fa-pencil"></span></a>
- <span class="icon icon16 fa-times pointer jsDeleteButton" data-confirm-message-html="{lang __encode=true}wcf.acp.trophy.delete.confirmMessage{/lang}" data-object-id="{@$trophy->getObjectID()}" data-tooltip="{lang}wcf.global.delete{/lang}"></span>
+ <a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+ <span class="icon icon16 fa-times pointer jsDeleteButton jsTooltip" data-confirm-message-html="{lang __encode=true}wcf.acp.trophy.delete.confirmMessage{/lang}" data-object-id="{@$trophy->getObjectID()}" title="{lang}wcf.global.button.delete{/lang}"></span>
</td>
<td class="columnID columnTrophyID">{@$trophy->trophyID}</td>
<td class="columnIcon">{@$trophy->renderTrophy(32)}</td>
- <td class="columnTitle columnTrophyTitle"><a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" data-tooltip="{lang}wcf.global.edit{/lang}">{$trophy->getTitle()}</a></td>
+ <td class="columnTitle columnTrophyTitle"><a href="{link controller='TrophyEdit' id=$trophy->getObjectID()}{/link}" title="{lang}wcf.global.button.edit{/lang}">{$trophy->getTitle()}</a></td>
<td class="columnText columnCategory">{$trophy->getCategory()->getTitle()}</td>
{event name='columns'}
--- /dev/null
+/**
+ * Handles the trophy image upload.
+ *
+ * @author Joshua Ruesweg
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Trophy/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Upload', 'Ui/Notification'], function(Core, DomTraverse, Language, Upload, UINotification) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function TrophyUpload(trophyID, tmpHash, options) {
+ options = options || {};
+
+ this._trophyID = ~~trophyID;
+ this._tmpHash = tmpHash;
+
+ if (options.input === undefined) {
+ throw new TypeError("invalid input given");
+ }
+
+ Upload.call(this, 'uploadIconFileButton', 'uploadIconFileContent', Core.extend({
+ className: 'wcf\\data\\trophy\\TrophyAction'
+ }, options));
+ }
+
+ Core.inherit(TrophyUpload, Upload, {
+ /**
+ * @see WoltLabSuite/Core/Upload#_getParameters
+ */
+ _getParameters: function() {
+ return {
+ trophyID: this._trophyID,
+ tmpHash: this._tmpHash
+ };
+ },
+
+ /**
+ * @see WoltLabSuite/Core/Upload#_success
+ */
+ _success: function(uploadId, data) {
+ var error = DomTraverse.childByClass(this._button.parentNode, 'innerError');
+
+ this._target.innerHTML = "<img src=\"" + data.returnValues.url + "?timestamp=" + Date.now() + "\" />";
+
+ if (error) {
+ elRemove(error);
+ }
+
+ UINotification.show();
+ },
+
+ /**
+ * @see WoltLabSuite/Core/Upload#_failure
+ */
+ _failure: function(uploadId, data, responseText, xhr, requestOptions) {
+ var error = DomTraverse.childByClass(this._button.parentNode, 'innerError');
+ if (!error) {
+ error = elCreate('small');
+ error.className = 'innerError';
+
+ this._button.parentNode.appendChild(error);
+ }
+
+ error.textContent = Language.get('wcf.acp.trophy.imageUpload.error.' + data.returnValues.errorType);
+
+ // remove previous images
+ this._target.innerHTML = "";
+
+ return false;
+ }
+ });
+
+ return TrophyUpload;
+});
\ No newline at end of file
* @var []
*/
public $availableTypes = [
- Trophy::TYPE_IMAGE => 'image',
+ Trophy::TYPE_IMAGE => 'imageUpload',
Trophy::TYPE_BADGE => 'badge'
];
public $type = Trophy::TYPE_BADGE;
/**
- * the location of the uploaded icon
+ * temporary hash for image icon
* @var string
*/
- public $uploadIconFile = '';
+ public $tmpHash = '';
+
+ /**
+ * the url for the uploaded image
+ * @var string
+ */
+ public $uploadedImageURL = '';
/**
* the icon name for CSS icons (FA-Icon)
$descriptionI18n = new I18nValue('description');
$descriptionI18n->setLanguageItem('wcf.trophy.description', 'wcf.trophy', 'com.woltlab.wcf');
$this->registerI18nValue($descriptionI18n);
+
+ if (isset($_POST['tmpHash'])) {
+ $this->tmpHash = StringUtil::trim($_POST['tmpHash']);
+ }
+
+ if (empty($this->tmpHash)) {
+ $this->tmpHash = StringUtil::getRandomID();
+ }
}
/**
if (isset($_POST['categoryID'])) $this->categoryID = intval($_POST['categoryID']);
if (isset($_POST['type'])) $this->type = intval($_POST['type']);
if (isset($_POST['isDisabled'])) $this->isDisabled = intval($_POST['isDisabled']);
- if (isset($_POST['uploadIconFile'])) $this->uploadIconFile = StringUtil::trim($_POST['uploadIconFile']);
if (isset($_POST['iconName'])) $this->iconName = StringUtil::trim($_POST['iconName']);
if (isset($_POST['iconColor'])) $this->iconColor = $_POST['iconColor'];
if (isset($_POST['badgeColor'])) $this->badgeColor = $_POST['badgeColor'];
- if (isset($_POST['awardAutomatically'])) $this->awardAutomatically = intval($_POST['awardAutomatically']);
+ if (isset($_POST['awardAutomatically'])) $this->awardAutomatically = 1;
+
+ // read file upload
+ $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->tmpHash);
+
+ if ($fileExtension !== null && file_exists(WCF_DIR.'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension)) {
+ $this->uploadedImageURL = WCF::getPath().'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension;
+ }
$this->category = TrophyCategoryCache::getInstance()->getCategoryByID($this->categoryID);
switch ($this->type) {
case Trophy::TYPE_IMAGE:
- // @TODO
+ $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->tmpHash);
+
+ if ($fileExtension === null) {
+ throw new UserInputException('imageUpload');
+ }
+
+ if (!file_exists(WCF_DIR.'images/trophy/tmp_'.$this->tmpHash.'.'.$fileExtension)) {
+ throw new UserInputException('imageUpload');
+ }
break;
case Trophy::TYPE_BADGE:
parent::save();
$data = [];
- if ($this->type == Trophy::TYPE_IMAGE) {
- // @ TODO
- } else if ($this->type == Trophy::TYPE_BADGE) {
+ if ($this->type == Trophy::TYPE_BADGE) {
$data['iconName'] = $this->iconName;
$data['iconColor'] = $this->iconColor;
$data['badgeColor'] = $this->badgeColor;
'categoryID' => $this->categoryID,
'type' => $this->type,
'isDisabled' => $this->isDisabled
- ])
+ ]),
+ 'tmpHash' => $this->tmpHash
]);
$this->objectAction->executeAction();
$this->isDisabled = $this->awardAutomatically = $this->categoryID = 0;
$this->type = Trophy::TYPE_BADGE;
- $this->uploadIconFile = $this->iconName = '';
+ $this->iconName = $this->uploadedImageURL = '';
$this->iconColor = 'rgba(255, 255, 255, 1)';
$this->badgeColor = 'rgba(50, 92, 132, 1)';
$this->iconName = 'trophy';
+ $this->tmpHash = StringUtil::getRandomID();
foreach ($this->conditions as $conditions) {
foreach ($conditions as $condition) {
WCF::getTPL()->assign([
'categoryID' => $this->categoryID,
'type' => $this->type,
- 'iconFile' => $this->uploadIconFile,
'isDisabled' => $this->isDisabled,
'iconName' => $this->iconName,
'iconColor' => $this->iconColor,
'trophyCategories' => TrophyCategoryCache::getInstance()->getCategories(),
'groupedObjectTypes' => $this->conditions,
'awardAutomatically' => $this->awardAutomatically,
- 'availableTypes' => $this->availableTypes
+ 'availableTypes' => $this->availableTypes,
+ 'tmpHash' => $this->tmpHash,
+ 'uploadedImageURL' => $this->uploadedImageURL
]);
}
}
public function renderTrophy($size = self::DEFAULT_SIZE) {
switch ($this->type) {
case self::TYPE_IMAGE: {
- // @TODO
+ return WCF::getTPL()->fetch('trophyImage', 'wcf', [
+ 'size' => $size,
+ 'trophy' => $this
+ ], true);
break;
}
return $parameters['renderedTemplate'];
}
- // no one has rendered the trophy ; throw an exception
throw new \LogicException("Unable to render the trophy with the type '". $this->type ."'.");
break;
}
namespace wcf\data\trophy;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\IToggleAction;
+use wcf\data\IUploadAction;
+use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\UserInputException;
+use wcf\system\image\ImageHandler;
+use wcf\system\upload\TrophyImageUploadFileValidationStrategy;
+use wcf\system\upload\UploadFile;
use wcf\system\WCF;
/**
* @package WoltLabSuite\Core\Data\Trophy
* @since 3.1
*/
-class TrophyAction extends AbstractDatabaseObjectAction implements IToggleAction {
+class TrophyAction extends AbstractDatabaseObjectAction implements IToggleAction, IUploadAction {
/**
* @inheritDoc
*/
*/
protected $requireACP = ['toggle', 'delete'];
+ /**
+ * @inheritDoc
+ */
+ public function create() {
+ $trophy = parent::create();
+
+ if (isset($this->parameters['tmpHash']) && $this->parameters['data']['type'] === Trophy::TYPE_IMAGE) {
+ $this->updateTrophyImage($trophy);
+ }
+
+ return $trophy;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function update() {
+ parent::update();
+
+ if (isset($this->parameters['data']['type']) && $this->parameters['data']['type'] === Trophy::TYPE_IMAGE) {
+ foreach ($this->getObjects() as $trophy) {
+ if (isset($this->parameters['tmpHash'])) {
+ $this->updateTrophyImage($trophy);
+ }
+ }
+ }
+ }
+
/**
* @inheritDoc
*/
}
}
}
+
+ /**
+ * @inheritDoc
+ */
+ public function validateUpload() {
+ WCF::getSession()->checkPermissions(['admin.trophy.canManageTrophy']);
+
+ $this->readString('tmpHash');
+ $this->readInteger('trophyID', true);
+
+ if ($this->parameters['trophyID']) {
+ $this->parameters['trophy'] = new Trophy($this->parameters['trophyID']);
+
+ if (!$this->parameters['trophy']->trophyID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ $this->parameters['__files']->validateFiles(new TrophyImageUploadFileValidationStrategy());
+
+ /** @var UploadFile[] $files */
+ $files = $this->parameters['__files']->getFiles();
+
+ // only one file is allowed
+ if (count($files) !== 1) {
+ throw new UserInputException('file');
+ }
+
+ $this->parameters['file'] = reset($files);
+
+ if ($this->parameters['file']->getValidationErrorType()) {
+ throw new UserInputException('file', $this->parameters['file']->getValidationErrorType());
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function upload() {
+ $fileName = WCF_DIR.'images/trophy/tmp_'.$this->parameters['tmpHash'].'.'.$this->parameters['file']->getFileExtension();
+ if ($this->parameters['file']->getImageData()['height'] > 128) {
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($this->parameters['file']->getLocation());
+ $adapter->resize(0, 0, $this->parameters['file']->getImageData()['height'], $this->parameters['file']->getImageData()['height'], 128, 128);
+ $adapter->writeImage($adapter->getImage(), $fileName);
+ }
+ else {
+ copy($this->parameters['file']->getLocation(), $fileName);
+ }
+
+ // remove old image
+ @unlink($this->parameters['file']->getLocation());
+
+ // store extension within session variables
+ WCF::getSession()->register('trophyImage-'.$this->parameters['tmpHash'], $this->parameters['file']->getFileExtension());
+
+ if ($this->parameters['trophyID']) {
+ $this->updateTrophyImage($this->parameters['trophy']);
+
+ return [
+ 'url' => WCF::getPath().'images/trophy/trophyImage-'.$this->parameters['trophyID'].'.'.$this->parameters['file']->getFileExtension()
+ ];
+ }
+
+ return [
+ 'url' => WCF::getPath() . 'images/trophy/'. basename($fileName)
+ ];
+ }
+
+ /**
+ * Updates style preview image.
+ *
+ * @param Trophy $trophy
+ */
+ protected function updateTrophyImage(Trophy $trophy) {
+ if (!isset($this->parameters['tmpHash'])) {
+ return;
+ }
+
+ $fileExtension = WCF::getSession()->getVar('trophyImage-'.$this->parameters['tmpHash']);
+ if ($fileExtension !== null) {
+ $oldFilename = WCF_DIR.'images/trophy/tmp_'.$this->parameters['tmpHash'].'.'.$fileExtension;
+ if (file_exists($oldFilename)) {
+ $filename = 'trophyImage-'.$trophy->trophyID.'.'.$fileExtension;
+ if (@rename($oldFilename, WCF_DIR.'images/trophy/'.$filename)) {
+ // delete old file if it has a different file extension
+ if ($trophy->iconFile != $filename) {
+ @unlink(WCF_DIR.'images/trophy/'.$trophy->iconFile);
+
+ $trophyEditor = new TrophyEditor($trophy);
+ $trophyEditor->update([
+ 'iconFile' => $filename
+ ]);
+ }
+ }
+ else {
+ // remove temp file
+ @unlink($oldFilename);
+ }
+ }
+ }
+ }
}
--- /dev/null
+<?php
+namespace wcf\system\upload;
+use wcf\util\ImageUtil;
+
+/**
+ * Upload file validation strategy implementation for trophy images.
+ *
+ * @author Joshua Ruesweg
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Upload
+ * @since 3.1
+ */
+class TrophyImageUploadFileValidationStrategy implements IUploadFileValidationStrategy {
+ /**
+ * minimum trophy image width and height
+ * @var integer
+ */
+ const MIN_TROPHY_IMAGE_SIZE = 64;
+
+ /**
+ * @inheritDoc
+ */
+ public function validate(UploadFile $uploadFile) {
+ if ($uploadFile->getErrorCode()) {
+ $uploadFile->setValidationErrorType('uploadFailed');
+ return false;
+ }
+
+ if ($uploadFile->getImageData() === null) {
+ $uploadFile->setValidationErrorType('noImage');
+ return false;
+ }
+
+ if ($uploadFile->getImageData()['width'] != $uploadFile->getImageData()['height']) {
+ $uploadFile->setValidationErrorType('notSquared');
+ return false;
+ }
+
+ if ($uploadFile->getImageData()['width'] < self::MIN_TROPHY_IMAGE_SIZE) {
+ $uploadFile->setValidationErrorType('tooSmall');
+ return false;
+ }
+
+ if (!ImageUtil::checkImageContent($uploadFile->getLocation())) {
+ $uploadFile->setValidationErrorType('noImage');
+ return false;
+ }
+
+ return true;
+ }
+}
<item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Trophäe automatisch vergeben]]></item>
<item name="wcf.acp.trophy.type"><![CDATA[Trophäen-Typ]]></item>
<item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
- <item name="wcf.acp.trophy.type.image"><![CDATA[Bild-Datei]]></item>
+ <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Bild-Datei]]></item>
+ <item name="wcf.acp.trophy.type.imageUpload.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Lade{else}Laden Sie{/if} hier ein quadratisches Bild mit mindestens 64×64 Pixel hoch.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.notSquared"><![CDATA[Das Bild ist nicht quadratisch.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.tooSmall"><![CDATA[Das Bild muss mindestens 64×64 Pixel groß sein.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.noImage"><![CDATA[Die hochgeladene Datei ist kein Bild.]]></item>
<item name="wcf.acp.trophy.badge.iconName"><![CDATA[Icon]]></item>
<item name="wcf.acp.trophy.badge.iconColor"><![CDATA[Icon-Farbe]]></item>
<item name="wcf.acp.trophy.badge.badgeColor"><![CDATA[Badge-Farbe]]></item>
<item name="wcf.acp.trophy.conditions"><![CDATA[Bedingungen]]></item>
<item name="wcf.acp.trophy.conditions.description"><![CDATA[Der aktive Benutzer muss die folgenden Bedingungen erfüllen, damit der die Trophäe vergeben wird.]]></item>
<item name="wcf.acp.trophy.conditions.error.noConditions"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} keine Bedingungen ausgewählt.]]></item>
- <item name="wcf.acp.trophy.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Trophäe <span class="confirmationObject">{trophy->getTitle()}</span> wirklich löschen?]]></item>
+ <item name="wcf.acp.trophy.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} die Trophäe <span class="confirmationObject">{$trophy->getTitle()}</span> wirklich löschen?]]></item>
</category>
<category name="wcf.user.usersOnline">
<item name="wcf.acp.trophy.awardAutomatically"><![CDATA[Award trophy automatically]]></item>
<item name="wcf.acp.trophy.type"><![CDATA[Trophy Type]]></item>
<item name="wcf.acp.trophy.type.badge"><![CDATA[Badge]]></item>
- <item name="wcf.acp.trophy.type.image"><![CDATA[Image]]></item>
+ <item name="wcf.acp.trophy.type.imageUpload"><![CDATA[Image]]></item>
+ <item name="wcf.acp.trophy.type.imageUpload.description"><![CDATA[Upload a square image with at least 64×64 pixels.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.notSquared"><![CDATA[The image isn't square.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.tooSmall"><![CDATA[The image must be at least 64×64 pixels.]]></item>
+ <item name="wcf.acp.trophy.imageUpload.error.noImage"><![CDATA[The uploaded file isn't a image..]]></item>
<item name="wcf.acp.trophy.badge.iconName"><![CDATA[Icon]]></item>
<item name="wcf.acp.trophy.badge.iconColor"><![CDATA[Icon Color]]></item>
<item name="wcf.acp.trophy.badge.badgeColor"><![CDATA[Badge Color]]></item>