var __REDACTOR_CODE_HIGHLIGHTERS = { {implode from=$__wcf->getBBCodeHandler()->getHighlighters() item=__highlighter}'{@$__highlighter}': '{lang}wcf.bbcode.code.{@$__highlighter}.title{/lang}'{/implode} };
var __REDACTOR_AMD_DEPENDENCIES = { };
-require(['Language', 'WoltLab/WCF/BBCode/FromHtml', 'WoltLab/WCF/BBCode/ToHtml'], function(Language, BBCodeFromHTML, BBCodeToHTML) {
+require(['Language', 'WoltLab/WCF/Bbcode/FromHtml', 'WoltLab/WCF/Bbcode/ToHtml'], function(Language, BbcodeFromHTML, BbcodeToHTML) {
__REDACTOR_AMD_DEPENDENCIES = {
- BBCodeFromHTML: BBCodeFromHTML,
- BBCodeToHTML: BBCodeToHTML
+ BbcodeFromHTML: BbcodeFromHTML,
+ BbcodeToHTML: BbcodeToHTML
};
Language.addObject({
{if !$codemirrorLoaded|isset}
+ <script data-relocate="true">window.define.amd = undefined;</script>
<script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/codemirror.js"></script>
<script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/addon/dialog/dialog.js"></script>
<script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/addon/search/searchcursor.js"></script>
<script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/addon/search/search.js"></script>
+ <script data-relocate="true">window.define.amd = window.__require_define_amd;</script>
+{/if}
+{if $codemirrorMode|isset}
+ <script data-relocate="true">window.define.amd = undefined;</script>
+ <script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/mode/{if $codemirrorMode == 'text/x-less'}css/css{else}{$codemirrorMode}/{$codemirrorMode}{/if}.js"></script>
+ <script data-relocate="true">window.define.amd = window.__require_define_amd;</script>
{/if}
-{if $codemirrorMode|isset}<script data-relocate="true" src="{@$__wcf->getPath()}js/3rdParty/codemirror/mode/{if $codemirrorMode == 'text/x-less'}css/css{else}{$codemirrorMode}/{$codemirrorMode}{/if}.js"></script>{/if}
{event name='javascriptIncludes'}
<script data-relocate="true">
-//<![CDATA[
{if !$codemirrorLoaded|isset}
- $('<link rel="stylesheet" href="{@$__wcf->getPath()}js/3rdParty/codemirror/codemirror.css" />').appendTo('head');
- $('<link rel="stylesheet" href="{@$__wcf->getPath()}js/3rdParty/codemirror/addon/dialog/dialog.css" />').appendTo('head');
+ ['{@$__wcf->getPath()}js/3rdParty/codemirror/codemirror.css', '{@$__wcf->getPath()}js/3rdParty/codemirror/addon/dialog/dialog.css'].forEach(function(href) {
+ var link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = href;
+ document.head.appendChild(link);
+ });
{/if}
- $(function() {
- var $elements = $('{@$codemirrorSelector|encodeJS}');
- var $config = {
- {if $codemirrorMode|isset}mode: '{@$codemirrorMode|encodeJS}',{/if}
- lineWrapping: true,
- indentWithTabs: true,
- lineNumbers: true,
- indentUnit: 4
- };
+ var elements = document.querySelectorAll('{@$codemirrorSelector|encodeJS}');
+ var config = {
+ {if $codemirrorMode|isset}mode: '{@$codemirrorMode|encodeJS}',{/if}
+ lineWrapping: true,
+ indentWithTabs: true,
+ lineNumbers: true,
+ indentUnit: 4,
+ readOnly: {if !$editable|isset || $editable}false{else}true{/if}
+ };
+
+ [].forEach.call(elements, function(element) {
+ {event name='javascriptInit'}
- for (var $i = 0; $i < $elements.length; $i++) {
- (function () {
- var $element = $elements[$i];
-
- {event name='javascriptInit'}
-
- if ($element.codemirror) {
- for (var name in $config) {
- if (!$config.hasOwnProperty($name)) continue;
-
- $element.codemirror.setOption($name, $config[$name]);
- }
- }
- else {
- $element.codemirror = CodeMirror.fromTextArea($element, $config);
- var oldToTextArea = $element.codemirror.toTextArea;
- $element.codemirror.toTextArea = function() {
- oldToTextArea();
- $element.codemirror = null;
- };
+ if (element.codemirror) {
+ for (var key in config) {
+ if (config.hasOwnProperty(key)) {
+ element.codemirror.setOption(key, config[key]);
}
-
- setTimeout(function () {
- $element.codemirror.refresh();
- }, 250);
- setTimeout(function () {
- $element.codemirror.refresh();
- }, 1000);
- })();
+ }
}
+ else {
+ element.codemirror = CodeMirror.fromTextArea(element, config);
+ var oldToTextArea = element.codemirror.toTextArea;
+ element.codemirror.toTextArea = function() {
+ oldToTextArea();
+ element.codemirror = null;
+ };
+ }
+
+ setTimeout(function () {
+ element.codemirror.refresh();
+ }, 250);
+ setTimeout(function () {
+ element.codemirror.refresh();
+ }, 1000);
});
-//]]>
</script>
{assign var='codemirrorLoaded' value=true}
{include file='header' pageTitle='wcf.acp.style.'|concat:$action}
-<script data-relocate="true" src="{@$__wcf->getPath()}acp/js/WCF.ACP.Style.js?v={@LAST_UPDATE_TIME}"></script>
-<script data-relocate="true" src="{@$__wcf->getPath()}js/WCF.ColorPicker.js?v={@LAST_UPDATE_TIME}"></script>
+{js application='wcf' acp='true' file='WCF.ACP.Style'}
+{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
<script data-relocate="true">
- //<![CDATA[
+ require(['WoltLab/WCF/Acp/Ui/Style/Editor'], function(AcpUiStyleEditor) {
+ AcpUiStyleEditor.setup({
+ isTainted: {if $isTainted}true{else}false{/if},
+ styleId: {if $action === 'edit'}{@$style->styleID}{else}0{/if}
+ });
+ });
+
$(function() {
new WCF.ColorPicker('.jsColorPicker');
- WCF.TabMenu.init();
-
- var $useFluidLayout = $('#useFluidLayout');
- var $fluidLayoutMinWidth = $('#fluidLayoutMinWidth');
- var $fluidLayoutMaxWidth = $('#fluidLayoutMaxWidth');
- var $fixedLayoutVariables = $('#fixedLayoutVariables');
- function useFluidLayout() {
- if ($useFluidLayout.is(':checked')) {
- $fluidLayoutMinWidth.show();
- $fluidLayoutMaxWidth.show();
- $fixedLayoutVariables.hide();
- }
- else {
- $fluidLayoutMinWidth.hide();
- $fluidLayoutMaxWidth.hide();
- $fixedLayoutVariables.show();
- }
- }
- $useFluidLayout.change(useFluidLayout);
- useFluidLayout();
WCF.Language.addObject({
'wcf.style.colorPicker': '{lang}wcf.style.colorPicker{/lang}',
var $target = $(event.currentTarget);
$target.prev().attr('step', ($target.val() == 'em' ? '0.01' : '1'));
}).trigger('change');
-
- $('.tabMenuContainer').on('wcftabsactivate', function (event, ui) {
- if (ui.newPanel.selector !== '#advanced') return;
-
- setTimeout(function() {
- $('#individualLess')[0].codemirror.refresh();
- $('#overrideLess')[0].codemirror.refresh();
- }, 10);
- });
});
- //]]>
</script>
<header class="boxHeadline">
<h1>{lang}wcf.acp.style.{$action}{/lang}</h1>
</div>
<form method="post" action="{if $action == 'add'}{link controller='StyleAdd'}{/link}{else}{link controller='StyleEdit' id=$styleID}{/link}{/if}">
- <div class="tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem">
+ <div class="tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem" id="styleTabMenuContainer">
<nav class="tabMenu">
<ul>
<li><a href="{@$__wcf->getAnchor('general')}">{lang}wcf.acp.style.general{/lang}</a></li>
<dl{if $errorField == 'authorName'} class="formError"{/if}>
<dt><label for="authorName">{lang}wcf.acp.style.authorName{/lang}</label></dt>
<dd>
- <input type="text" name="authorName" id="authorName" value="{$authorName}" class="long" />
+ <input type="text" name="authorName" id="authorName" value="{$authorName}" class="long"{if !$isTainted} readonly{/if} />
{if $errorField == 'authorName'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'copyright'} class="formError"{/if}>
<dt><label for="copyright">{lang}wcf.acp.style.copyright{/lang}</label></dt>
<dd>
- <input type="text" name="copyright" id="copyright" value="{$copyright}" class="long" />
+ <input type="text" name="copyright" id="copyright" value="{$copyright}" class="long"{if !$isTainted} readonly{/if} />
{if $errorField == 'copyright'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'styleVersion'} class="formError"{/if}>
<dt><label for="styleVersion">{lang}wcf.acp.style.styleVersion{/lang}</label></dt>
<dd>
- <input type="text" name="styleVersion" id="styleVersion" value="{$styleVersion}" class="small" />
+ <input type="text" name="styleVersion" id="styleVersion" value="{$styleVersion}" class="small"{if !$isTainted} readonly{/if} />
{if $errorField == 'styleVersion'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'styleDate'} class="formError"{/if}>
<dt><label for="styleDate">{lang}wcf.acp.style.styleDate{/lang}</label></dt>
<dd>
- <input type="date" name="styleDate" id="styleDate" value="{$styleDate}" class="small" />
+ <input type="date" name="styleDate" id="styleDate" value="{$styleDate}" class="small"{if !$isTainted} readonly{/if} />
{if $errorField == 'styleDate'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'license'} class="formError"{/if}>
<dt><label for="license">{lang}wcf.acp.style.license{/lang}</label></dt>
<dd>
- <input type="text" name="license" id="license" value="{$license}" class="long" />
+ <input type="text" name="license" id="license" value="{$license}" class="long"{if !$isTainted} readonly{/if} />
{if $errorField == 'license'}
<small class="innerError">
{if $errorType == 'empty'}
<dl{if $errorField == 'authorURL'} class="formError"{/if}>
<dt><label for="authorURL">{lang}wcf.acp.style.authorURL{/lang}</label></dt>
<dd>
- <input type="text" name="authorURL" id="authorURL" value="{$authorURL}" class="long" />
+ <input type="text" name="authorURL" id="authorURL" value="{$authorURL}" class="long"{if !$isTainted} readonly{/if} />
{if $errorField == 'authorURL'}
<small class="innerError">
{if $errorType == 'empty'}
{/if}
</dd>
</dl>
+ <dl{if $errorField == 'packageName'} class="formError"{/if}>
+ <dt><label for="packageName">{lang}wcf.acp.style.packageName{/lang}</label></dt>
+ <dd>
+ <input type="text" name="packageName" id="packageName" value="{$packageName}" class="long"{if !$isTainted} readonly{/if} />
+ {if $errorField == 'packageName'}
+ <small class="innerError">{lang}wcf.acp.style.packageName.error.{$errorType}{/lang}</small>
+ {/if}
+ </dd>
+ </dl>
<dl{if $errorField == 'styleDescription'} class="formError"{/if}>
<dt><label for="styleDescription">{lang}wcf.acp.style.styleDescription{/lang}</label></dt>
<dd>
</div>
{* advanced *}
- <div id="advanced" class="container containerPadding tabMenuContent">
+ <div id="advanced" class="container containerPadding tabMenuContainer tabMenuContent">
+ {if !$isTainted}
+ <nav class="menu">
+ <ul>
+ <li data-name="advanced-custom"><a href="{@$__wcf->getAnchor('advanced-custom')}">{lang}wcf.acp.style.advanced.custom{/lang}</a></li>
+ <li data-name="advanced-original"><a href="{@$__wcf->getAnchor('advanced-original')}">{lang}wcf.acp.style.advanced.original{/lang}</a></li>
+ </ul>
+ </nav>
+
+ <p class="info">{lang}wcf.acp.style.protected{/lang}</p>
+
+ {* custom declarations *}
+ <div id="advanced-custom">
+ <fieldset class="marginTop">
+ <legend>{lang}wcf.acp.style.advanced.individualLess{/lang}</legend>
+
+ <dl class="wide">
+ <dd>
+ <textarea id="individualLessCustom" rows="20" cols="40" name="individualLessCustom">{$variables[individualLessCustom]}</textarea>
+ <small>{lang}wcf.acp.style.advanced.individualLess.description{/lang}</small>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <fieldset{if $errorField == 'overrideLessCustom'} class="formError"{/if}>
+ <legend>{lang}wcf.acp.style.advanced.overrideLess{/lang}</legend>
+
+ <dl class="wide">
+ <dd>
+ <textarea id="overrideLessCustom" rows="20" cols="40" name="overrideLessCustom">{$variables[overrideLessCustom]}</textarea>
+ {if $errorField == 'overrideLessCustom'}
+ <small class="innerError">
+ {lang}wcf.acp.style.advanced.overrideLess.error{/lang}
+ {implode from=$errorType item=error}{lang}wcf.acp.style.advanced.overrideLess.error.{$error.error}{/lang}{/implode}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.style.advanced.overrideLess.description{/lang}</small>
+ </dd>
+ </dl>
+ </fieldset>
+ {include file='codemirror' codemirrorMode='text/x-less' codemirrorSelector='#individualLessCustom, #overrideLessCustom'}
+
+ {event name='syntaxFieldsetsCustom'}
+ </div>
+
+ {* original declarations / tainted style *}
+ <div id="advanced-original">
+ {/if}
+
<fieldset class="marginTop">
- <legend>{lang}wcf.acp.style.advanced.individualLess{/lang}</legend>
+ <legend>{lang}wcf.acp.style.advanced.individualLess{/lang}{if !$isTainted} ({lang}wcf.acp.style.protected.less{/lang}){/if}</legend>
<dl class="wide">
<dd>
</fieldset>
<fieldset{if $errorField == 'overrideLess'} class="formError"{/if}>
- <legend>{lang}wcf.acp.style.advanced.overrideLess{/lang}</legend>
+ <legend>{lang}wcf.acp.style.advanced.overrideLess{/lang}{if !$isTainted} ({lang}wcf.acp.style.protected.less{/lang}){/if}</legend>
<dl class="wide">
<dd>
</dd>
</dl>
</fieldset>
- {include file='codemirror' codemirrorMode='text/x-less' codemirrorSelector='#individualLess, #overrideLess'}
+ {include file='codemirror' codemirrorMode='text/x-less' codemirrorSelector='#individualLess, #overrideLess' editable=$isTainted}
+
+ {event name='syntaxFieldsetsOriginal'}
- {event name='syntaxFieldsets'}
+ {if !$isTainted}
+ </div>
+ {/if}
</div>
{event name='tabMenuContents'}
</div>
</form>
+<div id="styleDisableProtection" class="jsStaticDialogContent" data-title="{lang}wcf.acp.style.protected.title{/lang}">
+ <p>{lang}wcf.acp.style.protected.description{/lang}</p>
+
+ <dl class="marginTop">
+ <dt></dt>
+ <dd><label for="styleDisableProtectionConfirm"><input type="checkbox" id="styleDisableProtectionConfirm"> {lang}wcf.acp.style.protected.confirm{/lang}</label></dd>
+ </dl>
+
+ <div class="formSubmit">
+ <button id="styleDisableProtectionSubmit" disabled>{lang}wcf.global.button.submit{/lang}</button>
+ </div>
+</div>
+
{include file='footer'}
/** @deprecated legacy event */
WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'convertFromHtml', obj);
- obj.html = __REDACTOR_AMD_DEPENDENCIES.BBCodeFromHTML.convert(obj.html);
+ obj.html = __REDACTOR_AMD_DEPENDENCIES.BbcodeFromHTML.convert(obj.html);
/** @deprecated legacy event */
WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertFromHtml', obj);
/** @deprecated legacy event */
WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertToHtml', obj);
- obj.data = __REDACTOR_AMD_DEPENDENCIES.BBCodeToHTML.convert(obj.data, {
+ obj.data = __REDACTOR_AMD_DEPENDENCIES.BbcodeToHTML.convert(obj.data, {
attachments: {
images: this.wbbcode._getImageAttachments(),
thumbnailUrl: this.wutil.getOption('woltlab.attachmentThumbnailUrl'),
--- /dev/null
+/**
+ * Provides the basic core functionality.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Core
+ */
+define(['Ajax', 'EventHandler'], function(Ajax, EventHandler) {
+ "use strict";
+
+ /**
+ * @module WoltLab/WCF/Acp/Ui/Style/Editor
+ */
+ var AcpUiStyleEditor = {
+ /**
+ * Sets up dynamic style options.
+ */
+ setup: function(options) {
+ this._handleLayoutWidth();
+ this._handleLess(options.isTainted);
+
+ if (!options.isTainted) {
+ this._handleProtection(options.styleId);
+ }
+ },
+
+ /**
+ * Handles the switch between static and fluid layout.
+ */
+ _handleLayoutWidth: function() {
+ var useFluidLayout = elById('useFluidLayout');
+ var fluidLayoutMinWidth = elById('fluidLayoutMinWidth');
+ var fluidLayoutMaxWidth = elById('fluidLayoutMaxWidth');
+ var fixedLayoutVariables = elById('fixedLayoutVariables');
+
+ function change() {
+ var checked = useFluidLayout.checked;
+
+ fluidLayoutMinWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none');
+ fluidLayoutMaxWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none');
+ fixedLayoutVariables.style[(checked ? 'set' : 'remove') + 'Property']('display', 'none');
+ }
+
+ useFluidLayout.addEventListener('change', change);
+
+ change();
+ },
+
+ /**
+ * Handles LESS input fields.
+ *
+ * @param {boolean} isTainted false if style is in protected mode
+ */
+ _handleLess: function(isTainted) {
+ var individualLess = elById('individualLess');
+ var overrideLess = elById('overrideLess');
+
+ if (isTainted) {
+ EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function(data) {
+ individualLess.codemirror.refresh();
+ overrideLess.codemirror.refresh();
+ });
+ }
+ else {
+ EventHandler.add('com.woltlab.wcf.simpleTabMenu_advanced', 'select', function(data) {
+ if (data.activeName === 'advanced-custom') {
+ elById('individualLessCustom').codemirror.refresh();
+ elById('overrideLessCustom').codemirror.refresh();
+ }
+ else if (data.activeName === 'advanced-original') {
+ individualLess.codemirror.refresh();
+ overrideLess.codemirror.refresh();
+ }
+ });
+ }
+ },
+
+ _handleProtection: function(styleId) {
+ var button = elById('styleDisableProtectionSubmit');
+ var checkbox = elById('styleDisableProtectionConfirm');
+
+ checkbox.addEventListener('change', function() {
+ button.disabled = !checkbox.checked;
+ });
+
+ button.addEventListener('click', function() {
+ Ajax.apiOnce({
+ data: {
+ actionName: 'markAsTainted',
+ className: 'wcf\\data\\style\\StyleAction',
+ objectIDs: [styleId]
+ },
+ success: function() {
+ window.location.reload();
+ }
+ });
+ });
+ }
+ };
+
+ return AcpUiStyleEditor;
+});
*/
public $imagePath = 'images/';
+ /**
+ * tainted style
+ * @var boolean
+ */
+ public $isTainted = true;
+
/**
* license name
* @var string
*/
public $neededPermissions = array('admin.style.canManageStyle');
+ /**
+ * style package name
+ * @var string
+ */
+ public $packageName = '';
+
/**
* last change date
* @var string
if (isset($_POST['copyright'])) $this->copyright = StringUtil::trim($_POST['copyright']);
if (isset($_POST['imagePath'])) $this->imagePath = StringUtil::trim($_POST['imagePath']);
if (isset($_POST['license'])) $this->license = StringUtil::trim($_POST['license']);
+ if (isset($_POST['packageName'])) $this->packageName = StringUtil::trim($_POST['packageName']);
if (isset($_POST['styleDate'])) $this->styleDate = StringUtil::trim($_POST['styleDate']);
if (isset($_POST['styleDescription'])) $this->styleDescription = StringUtil::trim($_POST['styleDescription']);
if (isset($_POST['styleName'])) $this->styleName = StringUtil::trim($_POST['styleName']);
throw new UserInputException('styleVersion', 'notValid');
}
+ // validate style package name
+ if (!empty($this->packageName)) {
+ if (!Package::isValidPackageName($this->packageName)) {
+ throw new UserInputException('packageName', 'notValid');
+ }
+
+ // 3rd party styles may never have com.woltlab.* as name
+ if (strpos($this->packageName, 'com.woltlab.') === 0) {
+ throw new UserInputException('packageName', 'reserved');
+ }
+ }
+
// validate style description
if (!I18nHandler::getInstance()->validateValue('styleDescription', true, true)) {
throw new UserInputException('styleDescription');
'data' => array_merge($this->additionalFields, array(
'styleName' => $this->styleName,
'templateGroupID' => $this->templateGroupID,
+ 'packageName' => $this->packageName,
'isDisabled' => 1, // styles are disabled by default
+ 'isTainted' => 1,
'styleDescription' => '',
'styleVersion' => $this->styleVersion,
'styleDate' => $this->styleDate,
$this->saved();
// reset variables
- $this->authorName = $this->authorURL = $this->copyright = $this->image = '';
+ $this->authorName = $this->authorURL = $this->copyright = $this->packageName = $this->image = '';
$this->license = $this->styleDate = $this->styleDescription = $this->styleName = $this->styleVersion = '';
$this->imagePath = 'images/';
+ $this->isTainted = true;
$this->templateGroupID = 0;
I18nHandler::getInstance()->reset();
'availableUnits' => $this->availableUnits,
'copyright' => $this->copyright,
'imagePath' => $this->imagePath,
+ 'isTainted' => $this->isTainted,
'license' => $this->license,
+ 'packageName' => $this->packageName,
'styleDate' => $this->styleDate,
'styleDescription' => $this->styleDescription,
'styleName' => $this->styleName,
}
}
unset($variableValue);
+
+ if (!$this->style->isTainted) {
+ $tmp = Style::splitLessVariables($this->variables['individualLess']);
+ $this->variables['individualLess'] = $tmp['original'];
+ $this->variables['individualLessCustom'] = $tmp['custom'];
+
+ $tmp = Style::splitLessVariables($this->variables['overrideLess']);
+ $this->variables['overrideLess'] = $tmp['original'];
+ $this->variables['overrideLessCustom'] = $tmp['custom'];
+ }
+ }
+
+ /**
+ * @see \wcf\acp\form\StyleAddForm::setVariables()
+ */
+ protected function setVariables() {
+ parent::setVariables();
+
+ if (!$this->style->isTainted) {
+ $this->specialVariables[] = 'individualLessCustom';
+ $this->specialVariables[] = 'overrideLessCustom';
+ }
}
/**
$this->authorURL = $this->style->authorURL;
$this->copyright = $this->style->copyright;
$this->imagePath = $this->style->imagePath;
+ $this->isTainted = $this->style->isTainted;
$this->license = $this->style->license;
+ $this->packageName = $this->style->packageName;
$this->styleDate = $this->style->styleDate;
$this->styleDescription = $this->style->styleDescription;
$this->styleName = $this->style->styleName;
public function save() {
AbstractForm::save();
+ if (!$this->style->isTainted) {
+ $this->variables['individualLess'] = Style::joinLessVariables($this->variables['individualLess'], $this->variables['individualLessCustom']);
+ $this->variables['overrideLess'] = Style::joinLessVariables($this->variables['overrideLess'], $this->variables['overrideLessCustom']);
+
+ unset($this->variables['individualLessCustom']);
+ unset($this->variables['overrideLessCustom']);
+ }
+
$this->objectAction = new StyleAction(array($this->style), 'update', array(
'data' => array_merge($this->additionalFields, array(
'styleName' => $this->styleName,
'styleDate' => $this->styleDate,
'imagePath' => $this->imagePath,
'copyright' => $this->copyright,
+ 'packageName' => $this->packageName,
'license' => $this->license,
'authorName' => $this->authorName,
'authorURL' => $this->authorURL
return WCF::getPath().'images/stylePreview.png';
}
+
+ public static function splitLessVariables($variables) {
+ $tmp = explode("/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n", $variables, 2);
+
+ return [
+ 'preset' => $tmp[0],
+ 'custom' => (isset($tmp[1])) ? $tmp[1] : ''
+ ];
+ }
+
+ public static function joinLessVariables($preset, $custom) {
+ if (empty($custom)) {
+ return $preset;
+ }
+
+ return $preset . "/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n" . $custom;
+ }
}
use wcf\system\Regex;
use wcf\system\WCF;
use wcf\util\FileUtil;
+use wcf\util\StringUtil;
/**
* Executes style-related actions.
/**
* @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
*/
- protected $requireACP = array('copy', 'delete', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadLogo');
+ protected $requireACP = array('copy', 'delete', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadLogo');
/**
* style object
'template' => WCF::getTPL()->fetch('styleChooser')
);
}
+
+ public function validateMarkAsTainted() {
+ if (!WCF::getSession()->getPermission('admin.style.canManageStyle')) {
+ throw new PermissionDeniedException();
+ }
+
+ $this->styleEditor = $this->getSingleObject();
+ }
+
+ public function markAsTainted() {
+ // merge definitions
+ $variables = $this->styleEditor->getVariables();
+ $variables['individualLess'] = str_replace("/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n", '', $variables['individualLess']);
+ $variables['overrideLess'] = str_replace("/* WCF_STYLE_CUSTOM_USER_MODIFICATIONS */\n", '', $variables['overrideLess']);
+ $this->styleEditor->setVariables($variables);
+
+ $this->styleEditor->update([
+ 'isTainted' => 1
+ ]);
+ }
}
// get style data
$data = self::readStyleData($tar);
- $styleData = array(
+ $styleData = [
'styleName' => $data['name'],
'variables' => $data['variables'],
'styleVersion' => $data['version'],
'license' => $data['license'],
'authorName' => $data['authorName'],
'authorURL' => $data['authorURL']
- );
-
- // create template group
- if (!empty($data['templates'])) {
- $templateGroupName = $originalTemplateGroupName = $data['name'];
- $templateGroupFolderName = preg_replace('/[^a-z0-9_-]/i', '', $templateGroupName);
- if (empty($templateGroupFolderName)) $templateGroupFolderName = 'generic'.mb_substr(StringUtil::getRandomID(), 0, 8);
- $originalTemplateGroupFolderName = $templateGroupFolderName;
-
- // get unique template group name
- $i = 1;
- while (true) {
- $sql = "SELECT COUNT(*) AS count
- FROM wcf".WCF_N."_template_group
- WHERE templateGroupName = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array($templateGroupName));
- $row = $statement->fetchArray();
- if (!$row['count']) break;
- $templateGroupName = $originalTemplateGroupName . '_' . $i;
- $i++;
- }
-
- // get unique folder name
- $i = 1;
- while (true) {
- $sql = "SELECT COUNT(*) AS count
- FROM wcf".WCF_N."_template_group
- WHERE templateGroupFolderName = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
- FileUtil::addTrailingSlash($templateGroupFolderName)
- ));
- $row = $statement->fetchArray();
- if (!$row['count']) break;
- $templateGroupFolderName = $originalTemplateGroupFolderName . '_' . $i;
- $i++;
- }
-
- $templateGroupAction = new TemplateGroupAction(array(), 'create', array(
- 'data' => array(
- 'templateGroupName' => $templateGroupName,
- 'templateGroupFolderName' => FileUtil::addTrailingSlash($templateGroupFolderName)
- )
- ));
- $returnValues = $templateGroupAction->executeAction();
- $styleData['templateGroupID'] = $returnValues['returnValues']->templateGroupID;
- }
+ ];
// import images
if (!empty($data['images']) && $data['imagesPath'] != 'images/') {
}
}
- // import templates
+ // handle templates
if (!empty($data['templates'])) {
+ $templateGroupFolderName = '';
+ if ($style !== null && $style->templateGroupID) {
+ $templateGroupFolderName = (new TemplateGroup($style->templateGroupID))->templateGroupFolderName;
+ }
+
+ if (empty($templateGroupFolderName)) {
+ // create template group
+ $templateGroupName = $originalTemplateGroupName = $data['name'];
+ $templateGroupFolderName = preg_replace('/[^a-z0-9_-]/i', '', $templateGroupName);
+ if (empty($templateGroupFolderName)) $templateGroupFolderName = 'generic'.mb_substr(StringUtil::getRandomID(), 0, 8);
+ $originalTemplateGroupFolderName = $templateGroupFolderName;
+
+ // get unique template group name
+ $i = 1;
+ while (true) {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_template_group
+ WHERE templateGroupName = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$templateGroupName]);
+ $row = $statement->fetchArray();
+ if (!$row['count']) break;
+ $templateGroupName = $originalTemplateGroupName . '_' . $i;
+ $i++;
+ }
+
+ // get unique folder name
+ $i = 1;
+ while (true) {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_template_group
+ WHERE templateGroupFolderName = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ FileUtil::addTrailingSlash($templateGroupFolderName)
+ ]);
+ $row = $statement->fetchArray();
+ if (!$row['count']) break;
+ $templateGroupFolderName = $originalTemplateGroupFolderName . '_' . $i;
+ $i++;
+ }
+
+ $templateGroupAction = new TemplateGroupAction([], 'create', [
+ 'data' => [
+ 'templateGroupName' => $templateGroupName,
+ 'templateGroupFolderName' => FileUtil::addTrailingSlash($templateGroupFolderName)
+ ]
+ ]);
+ $returnValues = $templateGroupAction->executeAction();
+ $styleData['templateGroupID'] = $returnValues['returnValues']->templateGroupID;
+ }
+
+ // import templates
$index = $tar->getIndexByFilename($data['templates']);
if ($index !== false) {
// extract templates tar
// open templates tar and group templates by package
$templatesTar = new Tar($destination);
$contentList = $templatesTar->getContentList();
- $packageToTemplates = array();
+ $packageToTemplates = [];
foreach ($contentList as $val) {
if ($val['type'] == 'file') {
$folders = explode('/', $val['filename']);
$packageName = array_shift($folders);
if (!isset($packageToTemplates[$packageName])) {
- $packageToTemplates[$packageName] = array();
+ $packageToTemplates[$packageName] = [];
}
- $packageToTemplates[$packageName][] = array('index' => $val['index'], 'filename' => implode('/', $folders));
+ $packageToTemplates[$packageName][] = ['index' => $val['index'], 'filename' => implode('/', $folders)];
+ }
+ }
+
+ $knownTemplates = [];
+ if ($style !== null && $style->templateGroupID) {
+ $sql = "SELECT templateName
+ FROM wcf".WCF_N."_template
+ WHERE templateGroupID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$style->templateGroupID]);
+ while ($row = $statement->fetchArray()) {
+ $knownTemplates[] = $row['templateName'];
}
}
WHERE package = ?
AND isApplication = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
+ $statement->execute([
$package,
1
- ));
+ ]);
while ($row = $statement->fetchArray()) {
// get template path
$templatesDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR.$row['packageDir']).'templates/'.$templateGroupFolderName);
foreach ($templates as $template) {
$templatesTar->extract($template['index'], $templatesDir.$template['filename']);
- TemplateEditor::create(array(
- 'application' => Package::getAbbreviation($package),
- 'packageID' => $row['packageID'],
- 'templateName' => str_replace('.tpl', '', $template['filename']),
- 'templateGroupID' => $styleData['templateGroupID']
- ));
+ $templateName = str_replace('.tpl', '', $template['filename']);
+ if (!in_array($templateName, $knownTemplates)) {
+ TemplateEditor::create([
+ 'application' => Package::getAbbreviation($package),
+ 'packageID' => $row['packageID'],
+ 'templateName' => $templateName,
+ 'templateGroupID' => $styleData['templateGroupID']
+ ]);
+ }
}
}
}
}
// save style
- if ($style !== null) {
- $style->update($styleData);
- }
- else {
+ if ($style === null) {
$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)) {
- $style->update(array('image' => 'stylePreview-'.$style->styleID.$fileExtension));
+
+ // 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)) {
+ $style->update(['image' => 'stylePreview-'.$style->styleID.$fileExtension]);
+ }
}
}
+
+ // handle descriptions
+ if (!empty($data['description'])) {
+ self::saveLocalizedDescriptions($style, $data['description']);
+ LanguageFactory::getInstance()->deleteLanguageCache();
+ }
+
+ if ($data['default']) {
+ $style->setAsDefault();
+ }
}
-
- $tar->close();
-
- // handle descriptions
- if (!empty($data['description'])) {
- self::saveLocalizedDescriptions($style, $data['description']);
- LanguageFactory::getInstance()->deleteLanguageCache();
+ else {
+ unset($styleData['styleName']);
+
+ $variables = $style->getVariables();
+
+ $individualLess = Style::splitLessVariables($variables['individualLess']);
+ $variables['individualLess'] = Style::joinLessVariables($styleData['variables']['individualLess'], $individualLess['custom']);
+
+ $overrideLess = Style::splitLessVariables($variables['overrideLess']);
+ $variables['overrideLess'] = Style::joinLessVariables($styleData['variables']['overrideLess'], $overrideLess['custom']);
+
+ $styleData['variables'] = $variables;
+
+ $style->update($styleData);
}
- if ($data['default']) {
- $style->setAsDefault();
- }
+ $tar->close();
return $style;
}
return $this->queue->packageID;
}
+ /**
+ * Returns current package name.
+ *
+ * @return string package name
+ */
+ public function getPackageName() {
+ return $this->queue->packageName;
+ }
+
/**
* Returns current package installation type.
*
use wcf\data\style\StyleEditor;
use wcf\data\style\StyleList;
use wcf\system\event\EventHandler;
+use wcf\system\style\StyleHandler;
/**
* Installs, updates and deletes styles.
// extract style tar
$filename = $this->installation->getArchive()->extractTar($this->instruction['value'], 'style_');
+ // searches for non-tainted style for updating
+ $styleEditor = StyleHandler::getInstance()->getStyleByName($this->installation->getPackageName(), false);
+
// import style
- $style = StyleEditor::import($filename, $this->installation->getPackageID());
+ $style = StyleEditor::import($filename, $this->installation->getPackageID(), $styleEditor);
// set style as default
if (isset($this->instruction['attributes']['default'])) {
namespace wcf\system\style;
use wcf\data\style\ActiveStyle;
use wcf\data\style\Style;
+use wcf\data\style\StyleEditor;
use wcf\system\cache\builder\StyleCacheBuilder;
use wcf\system\exception\SystemException;
use wcf\system\SingletonFactory;
}
}
}
+
+ /**
+ * Returns a style by package name, optionally filtering tainted styles.
+ *
+ * @param string $packageName style package name
+ * @param boolean $skipTainted ignore tainted styles
+ * @return \wcf\data\style\StyleEditor
+ */
+ public function getStyleByName($packageName, $skipTainted = false) {
+ foreach ($this->cache['styles'] as $style) {
+ if ($style->packageName === $packageName) {
+ if (!$skipTainted || !$style->isTainted) {
+ return new StyleEditor($style);
+ }
+ }
+ }
+
+ return null;
+ }
}
@media only screen and (max-width: 800px) {
}
+/* static dialogs */
+.jsStaticDialogContent {
+ display: none;
+}
.dialogContentX {
<category name="wcf.acp.style">
<item name="wcf.acp.style.add"><![CDATA[Stil hinzufügen]]></item>
<item name="wcf.acp.style.advanced"><![CDATA[Erweiterte Einstellungen]]></item>
+ <item name="wcf.acp.style.advanced.custom"><![CDATA[Eigene Deklarationen]]></item>
<item name="wcf.acp.style.advanced.individualLess"><![CDATA[Individuelles CSS und LESS]]></item>
<item name="wcf.acp.style.advanced.individualLess.description"><![CDATA[Die Eingabe wird am Ende des Stils eingefügt und kann vollständig aus CSS bestehen. Sie haben zusätzlich den Zugriff auf LESS und alle von Community Framework zur Verfügung gestellten Mixins.]]></item>
+ <item name="wcf.acp.style.advanced.original"><![CDATA[Vorgegebene Deklarationen]]></item>
<item name="wcf.acp.style.advanced.overrideLess"><![CDATA[Überschreiben von LESS-Variablen]]></item>
<item name="wcf.acp.style.advanced.overrideLess.description"><![CDATA[Sie können innerhalb dieser Eingabe beliebige LESS-Variablen überschreiben, die nicht durch den Stil-Editor direkt bearbeitbar sind. Beim Bezug auf andere Variablen muss sichergestellt werden, dass diese in der Reihenfolge vorher definiert wurden. Die Syntax muss wie folgt lauten: „@variableName: variableValue;“]]></item>
<item name="wcf.acp.style.advanced.overrideLess.error"><![CDATA[Ihre Eingabe war ungültig, bitte überprüfen Sie die folgenden Einträge:]]></item>
<item name="wcf.acp.style.license"><![CDATA[Lizenz]]></item>
<item name="wcf.acp.style.list"><![CDATA[Stile auflisten]]></item>
<item name="wcf.acp.style.packageName"><![CDATA[Paketbezeichner]]></item>
- <item name="wcf.acp.style.packageName.description"><![CDATA[Geben Sie hier den Paketbezeichner in der Form „tld.domain.paketName“ an. Wenn Ihnen beispielsweise die Domain „example.com“ gehört und Ihr Stil „Blue Sunrise“ heißt, so wäre „com.example.style.blueSunrise“ ein passender und gültiger Paketbezeichner.]]></item>
+ <item name="wcf.acp.style.packageName.description"><![CDATA[Geben Sie hier optional den Paketbezeichner in der Form „tld.domain.paketName“ an. Wenn Ihnen beispielsweise die Domain „example.com“ gehört und Ihr Stil „Blue Sunrise“ heißt, so wäre „com.example.style.blueSunrise“ ein passender und gültiger Paketbezeichner.]]></item>
<item name="wcf.acp.style.packageName.error.notValid"><![CDATA[Der eingegebene Paketbezeichner ist ungültig]]></item>
<item name="wcf.acp.style.packageName.error.reserved"><![CDATA[Der Paketbezeichner darf nicht mit „com.woltlab.“ beginnen]]></item>
+ <item name="wcf.acp.style.protected"><![CDATA[Dieser Stil ist geschützt und kann nur eingeschränkt verändert werden, Sie können diesen <a class="jsStaticDialog" data-dialog-id="styleDisableProtection">Schutz aufheben</a>.]]></item>
+ <item name="wcf.acp.style.protected.confirm"><![CDATA[Schutz aufheben]]></item>
+ <item name="wcf.acp.style.protected.description"><![CDATA[Importierte bzw. installierte Stile erhalten automatisch einen geschützten Status, dieser unterbindet die Veränderungen der unmittelbar durch den Stil vorgegebenen Deklarationen. Die Aufhebung dieses Schutzes gestattet Ihnen die vollständige Veränderung, verhindert dadurch aber auch eine Aktualisierung mit einer neueren Version des Stils, beispielsweise im Rahmen eines Updates.<br><br>Dieser Schritt wird nicht empfohlen und ist grundsätzlich nicht notwendig, Stile können weiterhin nach belieben angepasst werden, nur die Vorlage selbst kann nicht verändert werden.<br><br>Bitte speichern Sie eventuelle vorgenommene Änderungen bevor Sie diesen Schritt ausführen.]]></item>
+ <item name="wcf.acp.style.protected.less"><![CDATA[Nur lesend]]></item>
+ <item name="wcf.acp.style.protected.title"><![CDATA[Schutz aufheben]]></item>
<item name="wcf.acp.style.styleDate"><![CDATA[Datum]]></item>
<item name="wcf.acp.style.styleDescription"><![CDATA[Beschreibung]]></item>
<item name="wcf.acp.style.styleName"><![CDATA[Name]]></item>
<category name="wcf.acp.style">
<item name="wcf.acp.style.add"><![CDATA[Add Style]]></item>
<item name="wcf.acp.style.advanced"><![CDATA[Advanced Settings]]></item>
+ <item name="wcf.acp.style.advanced.custom"><![CDATA[Own Declarations]]></item>
<item name="wcf.acp.style.advanced.individualLess"><![CDATA[Individual CSS and LESS]]></item>
<item name="wcf.acp.style.advanced.individualLess.description"><![CDATA[Content will be appended to the style sheet and may contain pure CSS. Furthermore you can use LESS including all Mixins provided by Community Framework.]]></item>
+ <item name="wcf.acp.style.advanced.original"><![CDATA[Preset Declarations]]></item>
<item name="wcf.acp.style.advanced.overrideLess"><![CDATA[LESS Variables Override]]></item>
<item name="wcf.acp.style.advanced.overrideLess.description"><![CDATA[Allows you to override LESS variables not available through the style editor itself. Syntax: “@variableName: variableValue;”, referred variables must be declared prior any attempt to read their value.]]></item>
<item name="wcf.acp.style.advanced.overrideLess.error"><![CDATA[Provided value was invalid, please verify these items:]]></item>
<item name="wcf.acp.style.packageName.description"><![CDATA[Enter a package identifier matching the pattern “tld.domain.packageName”. For example if you own the domain “example.com” and created a style called “Blue Sunrise”, you could pick “com.example.style.blueSunrise” as a valid and descriptive identifier.]]></item>
<item name="wcf.acp.style.packageName.error.notValid"><![CDATA[Package Identifier is invalid.]]></item>
<item name="wcf.acp.style.packageName.error.reserved"><![CDATA[Package Identifiers cannot begin with “com.woltlab.”]]></item>
+ <item name="wcf.acp.style.protected"><![CDATA[This style is protected and editing is limited; You can <a class="jsStaticDialog" data-dialog-id="styleDisableProtection">disable this protection</a>.]]></item>
+ <item name="wcf.acp.style.protected.confirm"><![CDATA[Disable protection]]></item>
+ <item name="wcf.acp.style.protected.description"><![CDATA[Imported and installed styles are automatically protected to prevent editing the original declaration, preserving the ability to update this style. You may disable this protection and recover full editing permissions, but it can no longer be updated.<br><br>It is neither recommended nor necessary to remove the protection in most cases, styles can still be fully customized while preserving the preset declarations.<br><br>Please save any unsaved changes before proceeding.]]></item>
+ <item name="wcf.acp.style.protected.less"><![CDATA[Read only]]></item>
+ <item name="wcf.acp.style.protected.title"><![CDATA[Disable Protection]]></item>
<item name="wcf.acp.style.styleDate"><![CDATA[Date]]></item>
<item name="wcf.acp.style.styleDescription"><![CDATA[Description]]></item>
<item name="wcf.acp.style.styleName"><![CDATA[Name]]></item>
license VARCHAR(255) NOT NULL DEFAULT '',
authorName VARCHAR(255) NOT NULL DEFAULT '',
authorURL VARCHAR(255) NOT NULL DEFAULT '',
- imagePath VARCHAR(255) NOT NULL DEFAULT ''
+ imagePath VARCHAR(255) NOT NULL DEFAULT '',
+ packageName VARCHAR(255) NOT NULL DEFAULT '',
+ isTainted TINYINT(1) NOT NULL DEFAULT 0
);
DROP TABLE IF EXISTS wcf1_style_variable;