<link rel="manifest" href="{@$__wcf->getStyleHandler()->getStyle()->getFaviconManifest()}">
<link rel="shortcut icon" href="{@$__wcf->getFavicon()}">
<meta name="msapplication-config" content="{@$__wcf->getStyleHandler()->getStyle()->getFaviconBrowserconfig()}">
-<meta name="theme-color" content="{$__wcf->getStyleHandler()->getStyle()->getVariable('wcfHeaderBackground')}">
+<meta name="theme-color" content="{$__wcf->getStyleHandler()->getStyle()->getVariable('wcfHeaderBackground', true)}">
<script data-relocate="true">
$(function() {
min-width: 20px;
}
-.selectedImagePreview {
+.selectedImagePreview,
+.selectedFaviconPreview {
img {
margin-bottom: 5px;
}
{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/Image/Upload', 'WoltLabSuite/Core/Acp/Ui/Style/Editor', 'WoltLabSuite/Core/Ui/Toggle/Input'], function(AcpUiStyleImageUpload, AcpUiStyleEditor, UiToggleInput) {
+ 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) {
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}');
new UiToggleInput('input[name="useGoogleFont"]', {
'wcf.style.colorPicker.new': '{lang}wcf.style.colorPicker.new{/lang}',
'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.favicon.error.dimensions': '{lang}wcf.acp.style.favicon.error.dimensions{/lang}',
+ 'wcf.acp.style.favicon.error.invalidExtension': '{lang}wcf.acp.style.favicon.error.invalidExtension{/lang}',
'wcf.acp.style.image.error.invalidExtension': '{lang}wcf.acp.style.image.error.invalidExtension{/lang}'
});
new WCF.ACP.Style.LogoUpload('{$tmpHash}', '{@$__wcf->getPath()}images/');
{event name='fileFields'}
</section>
+ {if $action == 'edit'}
+ <section class="section">
+ <h2 class="sectionTitle">{lang}wcf.acp.style.general.favicon{/lang}</h2>
+
+ <dl>
+ <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;">
+ </div>
+ <div id="uploadFavicon"></div>
+ <small>{lang}wcf.acp.style.favicon.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='faviconFields'}
+ </section>
+ {/if}
+
{event name='generalFieldsets'}
</div>
<browserconfig>
<msapplication>
<tile>
- <square150x150logo src="/abc/test/mstile-150x150.png"/>
+ <square150x150logo src="default.mstile-150x150.png"/>
<TileColor>#3a6d9c</TileColor>
</tile>
</msapplication>
"name": "",
"icons": [
{
- "src": "/abc/test/android-chrome-192x192.png",
+ "src": "default.android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
- "src": "/abc/test/android-chrome-256x256.png",
+ "src": "default.android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
--- /dev/null
+/**
+ * Handles uploading the style favicon.
+ *
+ * @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/Favicon/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], function(Core, DomTraverse, Language, UiNotification, Upload) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function AcpUiStyleImageUpload(styleId) {
+ this._styleId = ~~styleId;
+
+ Upload.call(this, 'uploadFavicon', 'faviconImage', {
+ action: 'uploadFavicon',
+ className: 'wcf\\data\\style\\StyleAction'
+ });
+ }
+ Core.inherit(AcpUiStyleImageUpload, 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 error = DomTraverse.childByClass(this._button.parentNode, 'innerError');
+ if (data.returnValues.url) {
+ elAttr(this._target, 'src', data.returnValues.url + '?timestamp=' + Date.now());
+
+ if (error) {
+ elRemove(error);
+ }
+
+ UiNotification.show();
+ }
+ else if (data.returnValues.errorType) {
+ if (!error) {
+ error = elCreate('small');
+ error.className = 'innerError';
+
+ this._button.parentNode.appendChild(error);
+ }
+
+ error.textContent = Language.get('wcf.acp.style.favicon.error.' + data.returnValues.errorType);
+ }
+ }
+ });
+
+ return AcpUiStyleImageUpload;
+});
return WCF::getPath() . 'images/default-logo-small.png';
}
-
- public function getFaviconAppleTouchIcon() {
- return $this->getFaviconPath('apple-touch-icon.png');
- }
-
- public function getFaviconManifest() {
- return $this->getFaviconPath('manifest.json');
- }
-
- public function getFaviconBrowserconfig() {
- return $this->getFaviconPath('browserconfig.xml');
- }
-
- public function getRelativeFavicon() {
- return $this->getFaviconPath('favicon.ico', false);
- }
-
- protected function getFaviconPath($filename, $absolutePath = true) {
- $path = 'images/favicon/'. ($this->getDecoratedObject()->hasFavicon ? $this->getDecoratedObject()->styleID : 'default') . ".{$filename}";
- if ($absolutePath) {
- return WCF::getPath() . $path;
- }
-
- return $path;
- }
}
const PREVIEW_IMAGE_MAX_HEIGHT = 64;
const PREVIEW_IMAGE_MAX_WIDTH = 102;
+ const FAVICON_IMAGE_HEIGHT = 256;
+ const FAVICON_IMAGE_WIDTH = 256;
+
/**
* Returns the name of this style.
*
return WCF::getPath().'images/stylePreview.png';
}
+ public function getFaviconAppleTouchIcon() {
+ return $this->getFaviconPath('apple-touch-icon.png');
+ }
+
+ public function getFaviconManifest() {
+ return $this->getFaviconPath('manifest.json');
+ }
+
+ public function getFaviconBrowserconfig() {
+ return $this->getFaviconPath('browserconfig.xml');
+ }
+
+ public function getRelativeFavicon() {
+ return $this->getFaviconPath('favicon.ico', false);
+ }
+
+ protected function getFaviconPath($filename, $absolutePath = true) {
+ $path = 'images/favicon/'. ($this->hasFavicon ? $this->styleID : 'default') . ".{$filename}";
+ if ($absolutePath) {
+ return WCF::getPath() . $path;
+ }
+
+ return $path;
+ }
+
/**
* Splits the less variables string.
*
// handle style preview image
$this->updateStylePreviewImage($style->getDecoratedObject());
+ // create favicon data
+ $this->updateFavicons($style->getDecoratedObject());
+
// reset stylesheet
StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject());
}
}
}
+ /**
+ * Updates style favicon files.
+ *
+ * @param Style $style
+ */
+ protected function updateFavicons(Style $style) {
+ $styleID = $style->styleID;
+ $fileExtension = WCF::getSession()->getVar('styleFavicon-template-'.$styleID);
+ $hasFavicon = (bool)$style->hasFavicon;
+ if ($fileExtension) {
+ $template = WCF_DIR . "images/favicon/{$styleID}.favicon-template.{$fileExtension}";
+ $images = [
+ 'android-chrome-192x192.png' => 192,
+ 'android-chrome-256x256.png' => 256,
+ 'apple-touch-icon.png' => 180,
+ 'mstile-150x150.png' => 150
+ ];
+
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($template);
+ foreach ($images as $filename => $length) {
+ $thumbnail = $adapter->createThumbnail($length, $length);
+ $adapter->writeImage($thumbnail, WCF_DIR."images/favicon/{$styleID}.{$filename}");
+ }
+
+ // create ico
+ require(WCF_DIR . 'lib/system/api/chrisjean/php-ico/class-php-ico.php');
+ $phpIco = new \PHP_ICO($template, [
+ [16, 16],
+ [32, 32]
+ ]);
+ $phpIco->save_ico(WCF_DIR . "images/favicon/{$styleID}.favicon.ico");
+
+ $hasFavicon = true;
+
+ (new StyleEditor($style))->update(['hasFavicon' => 1]);
+ WCF::getSession()->unregister('styleFavicon-template-'.$style->styleID);
+ }
+
+ if ($hasFavicon) {
+ // update manifest.json
+ $manifest = <<<MANIFEST
+{
+ "name": "",
+ "icons": [
+ {
+ "src": "{$styleID}.android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "{$styleID}.android-chrome-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
+MANIFEST;
+ file_put_contents(WCF_DIR . "images/favicon/{$styleID}.manifest.json", $manifest);
+
+ $style->loadVariables();
+ $tileColor = $style->getVariable('wcfHeaderBackground', true);
+
+ // update browserconfig.xml
+ $browserconfig = <<<BROWSERCONFIG
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="{$styleID}.mstile-150x150.png"/>
+ <TileColor>{$tileColor}</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
+BROWSERCONFIG;
+ file_put_contents(WCF_DIR . "images/favicon/{$styleID}.browserconfig.xml", $browserconfig);
+ }
+ }
+
/**
* @inheritDoc
*/
return ['errorType' => $file->getValidationErrorType()];
}
+ /**
+ * Validates parameters to upload a favicon.
+ */
+ public function validateUploadFavicon() {
+ // 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 favicon upload.
+ *
+ * @return string[]
+ */
+ public function uploadFavicon() {
+ // 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('favicon');
+ }
+ switch ($imageData[2]) {
+ case IMAGETYPE_PNG:
+ case IMAGETYPE_JPEG:
+ case IMAGETYPE_GIF:
+ // fine
+ break;
+ default:
+ throw new UserInputException('favicon');
+ }
+
+ if ($imageData[0] != Style::FAVICON_IMAGE_WIDTH || $imageData[1] != Style::FAVICON_IMAGE_HEIGHT) {
+ throw new UserInputException('favicon', 'dimensions');
+ }
+ }
+ catch (SystemException $e) {
+ throw new UserInputException('favicon');
+ }
+
+ // move uploaded file
+ if (@copy($fileLocation, WCF_DIR.'images/favicon/'.$this->style->styleID.'.favicon-template.'.$file->getFileExtension())) {
+ @unlink($fileLocation);
+
+ // store extension within session variables
+ WCF::getSession()->register('styleFavicon-template-'.$this->style->styleID, $file->getFileExtension());
+
+ // return result
+ return [
+ 'url' => WCF::getPath().'images/favicon/'.$this->style->styleID.'.favicon-template.'.$file->getFileExtension()
+ ];
+ }
+ else {
+ throw new UserInputException('favicon', 'uploadFailed');
+ }
+ }
+ }
+ catch (UserInputException $e) {
+ $file->setValidationErrorType($e->getType());
+ }
+
+ return ['errorType' => $file->getValidationErrorType()];
+ }
+
/**
* Validates parameters to assign a new default style.
*/
<item name="wcf.acp.style.globals.useGoogleFont"><![CDATA[Google-Schriftart aktivieren]]></item>
<item name="wcf.acp.style.globals.fontFamilyGoogle"><![CDATA[Schriftart]]></item>
<item name="wcf.acp.style.globals.fontFamilyFallback"><![CDATA[Schriftart (Fallback)]]></item>
+ <item name="wcf.acp.style.general.favicon"><![CDATA[Favicon]]></item>
+ <item name="wcf.acp.style.favicon"><![CDATA[Individuelles Favicon]]></item>
+ <item name="wcf.acp.style.favicon.description"><![CDATA[Laden Sie hier ein 256px × 256px großes Bild hoch, als Bildformate sind JPG und PNG zulässig. Das hochgeladene Bild wird für die Erzeugung aller notwendigen Grafiken verwendet.]]></item>
+ <item name="wcf.acp.style.favicon.error.dimensions"><![CDATA[Das Bild muss exakt 256px × 256px groß sein.]]></item>
+ <item name="wcf.acp.style.favicon.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
</category>
<category name="wcf.acp.tag">
<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.favicon"><![CDATA[Favicon]]></item>
<item name="wcf.acp.style.general.files"><![CDATA[Files]]></item>
<item name="wcf.acp.style.globals"><![CDATA[Global Settings]]></item>
<item name="wcf.acp.style.globals.fixedLayoutWidth"><![CDATA[Width]]></item>
<item name="wcf.acp.style.globals.useGoogleFont"><![CDATA[Use Google font face]]></item>
<item name="wcf.acp.style.globals.fontFamilyGoogle"><![CDATA[Font Face]]></item>
<item name="wcf.acp.style.globals.fontFamilyFallback"><![CDATA[Font Face (Fallback)]]></item>
+ <item name="wcf.acp.style.favicon"><![CDATA[Individual Favicon]]></item>
+ <item name="wcf.acp.style.favicon.description"><![CDATA[Upload an 256px × 256px image, acceptable image types are JPG and PNG. The uploaded image will be used to derive all required favicon sizes.]]></item>
+ <item name="wcf.acp.style.favicon.error.dimensions"><![CDATA[The image must be exactly 256px × 256px large.]]></item>
+ <item name="wcf.acp.style.favicon.error.invalidExtension"><![CDATA[The file extension is invalid.]]></item>
</category>
<category name="wcf.acp.tag">