<li data-style-id="{@$style->styleID}">
<div class="box64">
<span>
- <img src="{@$style->getPreviewImage()}" alt="">
+ <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
</span>
<div class="details">
<div class="containerHeadline">
});
{if $action == 'edit'}new AcpUiStyleFaviconUpload({@$style->styleID});{/if}
- new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}');
+ 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']
<small>{lang}wcf.acp.style.image.description{/lang}</small>
</dd>
</dl>
+ <dl{if $errorField == 'image'} class="formError"{/if}>
+ <dt><label for="image2x">{lang}wcf.acp.style.image2x{/lang}</label></dt>
+ <dd>
+ <div class="selectedImagePreview">
+ <img src="{if $action == 'add'}{@$__wcf->getPath()}images/stylePreview@2x.png{else}{@$style->getPreviewImage2x()}{/if}" alt="" id="styleImage2x">
+ </div>
+ <div id="uploadImage2x"></div>
+ <small>{lang}wcf.acp.style.image2x.description{/lang}</small>
+ </dd>
+ </dl>
{if $availableTemplateGroups|count}
<dl{if $errorField == 'templateGroupID'} class="formError"{/if}>
<dt><label for="templateGroupID">{lang}wcf.acp.style.templateGroupID{/lang}</label></dt>
{foreach from=$objects item=style}
<li>
<div class="box64">
- <span><img src="{@$style->getPreviewImage()}" alt=""></span>
+ <span>
+ <img src="{@$style->getPreviewImage()}" srcset="{@$style->getPreviewImage2x()} 2x" height="64" alt="">
+ </span>
<div class="details">
<div class="containerHeadline">
<h3><a href="{link controller='StyleEdit' id=$style->styleID}{/link}">{$style->styleName}</a></h3>
/**
* @constructor
*/
- function AcpUiStyleImageUpload(styleId, tmpHash) {
+ function AcpUiStyleImageUpload(styleId, tmpHash, is2x) {
+ this._is2x = (is2x === true);
this._styleId = ~~styleId;
this._tmpHash = tmpHash;
- Upload.call(this, 'uploadImage', 'styleImage', {
+ Upload.call(this, 'uploadImage' + (this._is2x ? '2x' : ''), 'styleImage' + (this._is2x ? '2x' : ''), {
className: 'wcf\\data\\style\\StyleAction'
});
}
*/
_getParameters: function() {
return {
+ is2x: this._is2x,
styleId: this._styleId,
tmpHash: this._tmpHash
};
use wcf\data\style\StyleAction;
use wcf\form\AbstractForm;
use wcf\system\exception\IllegalLinkException;
-use wcf\system\exception\UserInputException;
use wcf\system\language\I18nHandler;
use wcf\system\WCF;
* style object
* @var Style
*/
- public $style = null;
+ public $style;
/**
* style id
* @property-read string $styleVersion version number of the style
* @property-read string $styleDate date when the used version of the style has been published
* @property-read string $image link or path (relative to `WCF_DIR`) to the preview image of the style
+ * @property-read string $image2x link or path (relative to `WCF_DIR`) to the preview image of the style (2x version)
* @property-read string $copyright copyright text of the style
* @property-read string $license name of the style's license
* @property-read string $authorName name(s) of the style's author(s)
return WCF::getPath().'images/stylePreview.png';
}
+ /**
+ * Returns the style preview image path (2x version).
+ *
+ * @return string
+ */
+ public function getPreviewImage2x() {
+ if ($this->image2x && file_exists(WCF_DIR.'images/'.$this->image2x)) {
+ return WCF::getPath().'images/'.$this->image2x;
+ }
+
+ return WCF::getPath().'images/stylePreview@2x.png';
+ }
+
public function getFaviconAppleTouchIcon() {
return $this->getFaviconPath('apple-touch-icon.png');
}
return;
}
- $fileExtension = WCF::getSession()->getVar('stylePreview-'.$this->parameters['tmpHash']);
- if ($fileExtension !== null) {
- $oldFilename = WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$fileExtension;
- if (file_exists($oldFilename)) {
- $filename = 'stylePreview-'.$style->styleID.'.'.$fileExtension;
- if (@rename($oldFilename, WCF_DIR.'images/'.$filename)) {
- // delete old file if it has a different file extension
- if ($style->image != $filename) {
- @unlink(WCF_DIR.'images/'.$style->image);
-
- // update filename in database
- $sql = "UPDATE wcf".WCF_N."_style
- SET image = ?
- WHERE styleID = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute([
- $filename,
- $style->styleID
- ]);
+ foreach (['', '@2x'] as $type) {
+ $fileExtension = WCF::getSession()->getVar('stylePreview-' . $this->parameters['tmpHash'] . $type);
+ if ($fileExtension !== null) {
+ $oldFilename = WCF_DIR . 'images/stylePreview-' . $this->parameters['tmpHash'] . $type . '.' . $fileExtension;
+ if (file_exists($oldFilename)) {
+ $filename = 'stylePreview-' . $style->styleID . $type . '.' . $fileExtension;
+ if (@rename($oldFilename, WCF_DIR . 'images/' . $filename)) {
+ // delete old file if it has a different file extension
+ if ($type === '') {
+ if ($style->image != $filename) {
+ @unlink(WCF_DIR . 'images/' . $style->image);
+
+ // update filename in database
+ $sql = "UPDATE wcf" . WCF_N . "_style
+ SET image = ?
+ WHERE styleID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $filename, $style->styleID
+ ]);
+ }
+ }
+ else {
+ if ($style->image2x != $filename) {
+ @unlink(WCF_DIR . 'images/' . $style->image2x);
+
+ // update filename in database
+ $sql = "UPDATE wcf" . WCF_N . "_style
+ SET image2x = ?
+ WHERE styleID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $filename, $style->styleID
+ ]);
+ }
+ }
+ }
+ else {
+ // remove temp file
+ @unlink($oldFilename);
}
- }
- else {
- // remove temp file
- @unlink($oldFilename);
}
}
}
throw new PermissionDeniedException();
}
+ $this->readBoolean('is2x', true);
$this->readString('tmpHash');
$this->readInteger('styleID', true);
$files = $this->parameters['__files']->getFiles();
$file = $files[0];
+ $multiplier = ($this->parameters['is2x']) ? 2 : 1;
+
try {
if (!$file->getValidationErrorType()) {
// shrink preview image if necessary
throw new UserInputException('image');
}
- if ($imageData[0] > Style::PREVIEW_IMAGE_MAX_WIDTH || $imageData[1] > Style::PREVIEW_IMAGE_MAX_HEIGHT) {
+ if ($imageData[0] > (Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier) || $imageData[1] > (Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier)) {
$adapter = ImageHandler::getInstance()->getAdapter();
$adapter->loadFile($fileLocation);
$fileLocation = FileUtil::getTemporaryFilename();
- $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH, Style::PREVIEW_IMAGE_MAX_HEIGHT, false);
+ $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH * $multiplier, Style::PREVIEW_IMAGE_MAX_HEIGHT * $multiplier, false);
$adapter->writeImage($thumbnail, $fileLocation);
}
}
}
// move uploaded file
- if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension())) {
+ if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension())) {
@unlink($fileLocation);
// store extension within session variables
- WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'], $file->getFileExtension());
+ WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : ''), $file->getFileExtension());
if ($this->parameters['styleID']) {
$this->updateStylePreviewImage($this->style);
return [
- 'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].'.'.$file->getFileExtension()
+ 'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
];
}
// return result
return [
- 'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
+ 'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].($this->parameters['is2x'] ? '@2x' : '').'.'.$file->getFileExtension()
];
}
else {
$xpath = $xml->xpath();
$data = [
- 'name' => '', 'description' => [], 'version' => '', 'image' => '', 'copyright' => '', 'default' => false,
+ 'name' => '', 'description' => [], 'version' => '', 'image' => '', 'image2x' => '', 'copyright' => '', 'default' => false,
'license' => '', 'authorName' => '', 'authorURL' => '', 'templates' => '', 'images' => '',
'variables' => '', 'date' => '0000-00-00', 'imagesPath' => '', 'packageName' => '', 'apiVersion' => '3.0'
];
case 'copyright':
case 'image':
+ case 'image2x':
case 'license':
$data[$element->tagName] = $element->nodeValue;
break;
$styleData['packageID'] = $packageID;
$style = new StyleEditor(self::create($styleData));
- // import preview image
- if (!empty($data['image'])) {
- $fileExtension = mb_substr($data['image'], mb_strrpos($data['image'], '.'));
- $index = $tar->getIndexByFilename($data['image']);
- if ($index !== false) {
- $filename = WCF_DIR.'images/stylePreview-'.$style->styleID.$fileExtension;
- $tar->extract($index, $filename);
- FileUtil::makeWritable($filename);
-
- if (file_exists($filename)) {
- try {
- if (($imageData = getimagesize($filename)) !== false) {
- switch ($imageData[2]) {
- case IMAGETYPE_PNG:
- case IMAGETYPE_JPEG:
- case IMAGETYPE_GIF:
- $style->update(['image' => 'stylePreview-'.$style->styleID.$fileExtension]);
- }
- }
- }
- catch (SystemException $e) {
- // broken image
- }
- }
- }
- }
-
// handle descriptions
if (!empty($data['description'])) {
self::saveLocalizedDescriptions($style, $data['description']);
$style->update($styleData);
}
+ // import preview image
+ foreach (['image', 'image2x'] as $type) {
+ if (!empty($data[$type])) {
+ $fileExtension = mb_substr($data[$type], mb_strrpos($data[$type], '.'));
+ $index = $tar->getIndexByFilename($data[$type]);
+ if ($index !== false) {
+ $filename = WCF_DIR . 'images/stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension;
+ $tar->extract($index, $filename);
+ FileUtil::makeWritable($filename);
+
+ if (file_exists($filename)) {
+ try {
+ if (($imageData = getimagesize($filename)) !== false) {
+ switch ($imageData[2]) {
+ case IMAGETYPE_PNG:
+ case IMAGETYPE_JPEG:
+ case IMAGETYPE_GIF:
+ $style->update([$type => 'stylePreview-' . $style->styleID . ($type === 'image2x' ? '@2x' : '') . $fileExtension]);
+ }
+ }
+ }
+ catch (SystemException $e) {
+ // broken image
+ }
+ }
+ }
+ }
+ }
+
$tar->close();
return $style;
if ($this->image && @file_exists(WCF_DIR.'images/'.$this->image)) {
$styleTar->add(WCF_DIR.'images/'.$this->image, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image)));
}
+ if ($this->image2x && @file_exists(WCF_DIR.'images/'.$this->image2x)) {
+ $styleTar->add(WCF_DIR.'images/'.$this->image2x, '', FileUtil::addTrailingSlash(dirname(WCF_DIR.'images/'.$this->image2x)));
+ }
// fetch style description
$sql = "SELECT language.languageCode, language_item.languageItemValue
$xml->writeElement('version', $this->styleVersion);
$xml->writeElement('apiVersion', $this->apiVersion);
if ($this->image) $xml->writeElement('image', $this->image);
+ if ($this->image2x) $xml->writeElement('image2x', $this->image2x);
if ($this->copyright) $xml->writeElement('copyright', $this->copyright);
if ($this->license) $xml->writeElement('license', $this->license);
$xml->endElement();
<item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Flexible Breite verwenden]]></item>
<item name="wcf.acp.style.image"><![CDATA[Vorschaubild]]></item>
<item name="wcf.acp.style.image.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Lade{else}Laden Sie{/if} hier ein Vorschaubild dieses Stiles hoch, als Bildformate sind JPG und PNG zulässig. Es wird empfohlen Vorschaubilder immer mit der Größe 102px × 64px anzulegen, größere Grafiken werden automatisch skaliert.]]></item>
+ <item name="wcf.acp.style.image2x"><![CDATA[Vorschaubild (HD)]]></item>
+ <item name="wcf.acp.style.image2x.description"><![CDATA[Diese Grafik wird für hochauflösende Bildschirme (z. B. Apple Retina-Display oder 4K/UHD-Monitore) verwendet und muss doppelt so groß sein wie das normale Vorschaubild.]]></item>
<item name="wcf.acp.style.imagePath"><![CDATA[Bilder-Pfad]]></item>
<item name="wcf.acp.style.imagePath.description"><![CDATA[Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Stil eigene Grafiken benötigt, {if LANGUAGE_USE_INFORMAL_VARIANT}solltest du{else}sollten Sie{/if} diese in einem Unterordner des Ordners „images“ ablegen. {if LANGUAGE_USE_INFORMAL_VARIANT}Gib{else}Geben Sie{/if} hier den Pfad zu diesem Ordner an.]]></item>
<item name="wcf.acp.style.importStyle"><![CDATA[Stil importieren]]></item>
<item name="wcf.acp.style.globals.useFluidLayout"><![CDATA[Use fluid width]]></item>
<item name="wcf.acp.style.image"><![CDATA[Preview Image]]></item>
<item name="wcf.acp.style.image.description"><![CDATA[Upload a preview image for this style, acceptable image types are JPG and PNG. Dimensions should be 102px × 64px, exceeding images will be scaled.]]></item>
+ <item name="wcf.acp.style.image2x"><![CDATA[Preview Image (HD)]]></item>
+ <item name="wcf.acp.style.image2x.description"><![CDATA[This image is used on high resolution displays such as the Apple Retina or 4K/UHD-displays. The image must be double times the dimensions of the normal image to work.]]></item>
<item name="wcf.acp.style.imagePath"><![CDATA[Image Path]]></item>
<item name="wcf.acp.style.imagePath.description"><![CDATA[The path for optional images for your style, the directory should be located within “images”.]]></item>
<item name="wcf.acp.style.importStyle"><![CDATA[Import Style]]></item>
styleVersion VARCHAR(255) NOT NULL DEFAULT '',
styleDate CHAR(10) NOT NULL DEFAULT '0000-00-00',
image VARCHAR(255) NOT NULL DEFAULT '',
+ image2x VARCHAR(255) NOT NULL DEFAULT '',
copyright VARCHAR(255) NOT NULL DEFAULT '',
license VARCHAR(255) NOT NULL DEFAULT '',
authorName VARCHAR(255) NOT NULL DEFAULT '',