From 90b4b96442e86abc9832d4d58f9a8f51e3bed89f Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 14 Jul 2015 16:22:25 +0200 Subject: [PATCH] Added update support for styles (WIP) --- com.woltlab.wcf/templates/wysiwyg.tpl | 6 +- .../files/acp/templates/codemirror.tpl | 87 ++++---- .../install/files/acp/templates/styleAdd.tpl | 140 ++++++++---- .../js/3rdParty/redactor/plugins/wbbcode.js | 4 +- .../js/WoltLab/WCF/Acp/Ui/Style/Editor.js | 103 +++++++++ .../files/lib/acp/form/StyleAddForm.class.php | 32 ++- .../lib/acp/form/StyleEditForm.class.php | 33 +++ .../files/lib/data/style/Style.class.php | 17 ++ .../lib/data/style/StyleAction.class.php | 23 +- .../lib/data/style/StyleEditor.class.php | 209 ++++++++++-------- .../PackageInstallationDispatcher.class.php | 9 + .../StylePackageInstallationPlugin.class.php | 6 +- .../lib/system/style/StyleHandler.class.php | 20 ++ wcfsetup/install/files/style/dialog.less | 4 + wcfsetup/install/lang/de.xml | 9 +- wcfsetup/install/lang/en.xml | 7 + wcfsetup/setup/db/install.sql | 4 +- 17 files changed, 528 insertions(+), 185 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Style/Editor.js diff --git a/com.woltlab.wcf/templates/wysiwyg.tpl b/com.woltlab.wcf/templates/wysiwyg.tpl index ba20944c48..b051d40b43 100644 --- a/com.woltlab.wcf/templates/wysiwyg.tpl +++ b/com.woltlab.wcf/templates/wysiwyg.tpl @@ -7,10 +7,10 @@ var __REDACTOR_SOURCE_BBCODES = [ {implode from=$__wcf->getBBCodeHandler()->getS 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({ diff --git a/wcfsetup/install/files/acp/templates/codemirror.tpl b/wcfsetup/install/files/acp/templates/codemirror.tpl index a188470aa7..7b18d11ee3 100644 --- a/wcfsetup/install/files/acp/templates/codemirror.tpl +++ b/wcfsetup/install/files/acp/templates/codemirror.tpl @@ -1,60 +1,63 @@ {if !$codemirrorLoaded|isset} + + +{/if} +{if $codemirrorMode|isset} + + + {/if} -{if $codemirrorMode|isset}{/if} {event name='javascriptIncludes'} {assign var='codemirrorLoaded' value=true} diff --git a/wcfsetup/install/files/acp/templates/styleAdd.tpl b/wcfsetup/install/files/acp/templates/styleAdd.tpl index 23da836782..cc85a392a5 100644 --- a/wcfsetup/install/files/acp/templates/styleAdd.tpl +++ b/wcfsetup/install/files/acp/templates/styleAdd.tpl @@ -1,31 +1,17 @@ {include file='header' pageTitle='wcf.acp.style.'|concat:$action} - - +{js application='wcf' acp='true' file='WCF.ACP.Style'} +{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}

{lang}wcf.acp.style.{$action}{/lang}

@@ -87,7 +63,7 @@
-
+
{* advanced *} -
+
+ {if !$isTainted} + + +

{lang}wcf.acp.style.protected{/lang}

+ + {* custom declarations *} +
+
+ {lang}wcf.acp.style.advanced.individualLess{/lang} + +
+
+ + {lang}wcf.acp.style.advanced.individualLess.description{/lang} +
+
+
+ + + {lang}wcf.acp.style.advanced.overrideLess{/lang} + +
+
+ + {if $errorField == 'overrideLessCustom'} + + {lang}wcf.acp.style.advanced.overrideLess.error{/lang} + {implode from=$errorType item=error}{lang}wcf.acp.style.advanced.overrideLess.error.{$error.error}{/lang}{/implode} + + {/if} + {lang}wcf.acp.style.advanced.overrideLess.description{/lang} +
+
+ + {include file='codemirror' codemirrorMode='text/x-less' codemirrorSelector='#individualLessCustom, #overrideLessCustom'} + + {event name='syntaxFieldsetsCustom'} +
+ + {* original declarations / tainted style *} +
+ {/if} +
- {lang}wcf.acp.style.advanced.individualLess{/lang} + {lang}wcf.acp.style.advanced.individualLess{/lang}{if !$isTainted} ({lang}wcf.acp.style.protected.less{/lang}){/if}
@@ -556,7 +589,7 @@
- {lang}wcf.acp.style.advanced.overrideLess{/lang} + {lang}wcf.acp.style.advanced.overrideLess{/lang}{if !$isTainted} ({lang}wcf.acp.style.protected.less{/lang}){/if}
@@ -571,9 +604,13 @@
- {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} +
+ {/if}
{event name='tabMenuContents'} @@ -586,4 +623,17 @@
+
+

{lang}wcf.acp.style.protected.description{/lang}

+ +
+
+
+
+ +
+ +
+
+ {include file='footer'} diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js index 9af01197be..3ab34726c1 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js +++ b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js @@ -303,7 +303,7 @@ RedactorPlugins.wbbcode = function() { /** @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); @@ -322,7 +322,7 @@ RedactorPlugins.wbbcode = function() { /** @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'), diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Style/Editor.js b/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Style/Editor.js new file mode 100644 index 0000000000..c979a76bcc --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Style/Editor.js @@ -0,0 +1,103 @@ +/** + * Provides the basic core functionality. + * + * @author Alexander Ebert + * @copyright 2001-2015 WoltLab GmbH + * @license GNU Lesser General Public License + * @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; +}); diff --git a/wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php b/wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php index 52dafe30e7..6e86490149 100644 --- a/wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php @@ -99,6 +99,12 @@ class StyleAddForm extends AbstractForm { */ public $imagePath = 'images/'; + /** + * tainted style + * @var boolean + */ + public $isTainted = true; + /** * license name * @var string @@ -110,6 +116,12 @@ class StyleAddForm extends AbstractForm { */ public $neededPermissions = array('admin.style.canManageStyle'); + /** + * style package name + * @var string + */ + public $packageName = ''; + /** * last change date * @var string @@ -225,6 +237,7 @@ class StyleAddForm extends AbstractForm { 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']); @@ -267,6 +280,18 @@ class StyleAddForm extends AbstractForm { 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'); @@ -473,7 +498,9 @@ class StyleAddForm extends AbstractForm { '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, @@ -501,10 +528,11 @@ class StyleAddForm extends AbstractForm { $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(); @@ -532,7 +560,9 @@ class StyleAddForm extends AbstractForm { '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, diff --git a/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php b/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php index ef6e32bc00..8b49c403fe 100644 --- a/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/StyleEditForm.class.php @@ -61,6 +61,28 @@ class StyleEditForm extends StyleAddForm { } } 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'; + } } /** @@ -76,7 +98,9 @@ class StyleEditForm extends StyleAddForm { $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; @@ -91,6 +115,14 @@ class StyleEditForm extends StyleAddForm { 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, @@ -99,6 +131,7 @@ class StyleEditForm extends StyleAddForm { 'styleDate' => $this->styleDate, 'imagePath' => $this->imagePath, 'copyright' => $this->copyright, + 'packageName' => $this->packageName, 'license' => $this->license, 'authorName' => $this->authorName, 'authorURL' => $this->authorURL diff --git a/wcfsetup/install/files/lib/data/style/Style.class.php b/wcfsetup/install/files/lib/data/style/Style.class.php index cb6d15180a..81444adf53 100644 --- a/wcfsetup/install/files/lib/data/style/Style.class.php +++ b/wcfsetup/install/files/lib/data/style/Style.class.php @@ -115,4 +115,21 @@ class Style extends DatabaseObject { 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; + } } diff --git a/wcfsetup/install/files/lib/data/style/StyleAction.class.php b/wcfsetup/install/files/lib/data/style/StyleAction.class.php index f16eb2b68c..1aa36b8ea1 100644 --- a/wcfsetup/install/files/lib/data/style/StyleAction.class.php +++ b/wcfsetup/install/files/lib/data/style/StyleAction.class.php @@ -14,6 +14,7 @@ use wcf\system\upload\DefaultUploadFileValidationStrategy; use wcf\system\Regex; use wcf\system\WCF; use wcf\util\FileUtil; +use wcf\util\StringUtil; /** * Executes style-related actions. @@ -49,7 +50,7 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction /** * @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 @@ -633,4 +634,24 @@ class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction '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 + ]); + } } diff --git a/wcfsetup/install/files/lib/data/style/StyleEditor.class.php b/wcfsetup/install/files/lib/data/style/StyleEditor.class.php index fb567af891..aafb03defd 100644 --- a/wcfsetup/install/files/lib/data/style/StyleEditor.class.php +++ b/wcfsetup/install/files/lib/data/style/StyleEditor.class.php @@ -298,7 +298,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject // get style data $data = self::readStyleData($tar); - $styleData = array( + $styleData = [ 'styleName' => $data['name'], 'variables' => $data['variables'], 'styleVersion' => $data['version'], @@ -307,54 +307,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject '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/') { @@ -384,8 +337,61 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject } } - // 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 @@ -395,15 +401,27 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject // 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']; } } @@ -415,10 +433,10 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject 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); @@ -433,12 +451,15 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject 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'] + ]); + } } } } @@ -450,40 +471,52 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject } // 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; } diff --git a/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php b/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php index 497e5b410b..aff9931c5e 100644 --- a/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php +++ b/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php @@ -801,6 +801,15 @@ class PackageInstallationDispatcher { return $this->queue->packageID; } + /** + * Returns current package name. + * + * @return string package name + */ + public function getPackageName() { + return $this->queue->packageName; + } + /** * Returns current package installation type. * diff --git a/wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php index d745a679a4..4cec3b5a47 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php @@ -3,6 +3,7 @@ namespace wcf\system\package\plugin; 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. @@ -29,8 +30,11 @@ class StylePackageInstallationPlugin extends AbstractPackageInstallationPlugin { // 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'])) { diff --git a/wcfsetup/install/files/lib/system/style/StyleHandler.class.php b/wcfsetup/install/files/lib/system/style/StyleHandler.class.php index 0e2edcfa96..9f8c8a8d2a 100644 --- a/wcfsetup/install/files/lib/system/style/StyleHandler.class.php +++ b/wcfsetup/install/files/lib/system/style/StyleHandler.class.php @@ -2,6 +2,7 @@ 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; @@ -180,4 +181,23 @@ class StyleHandler extends 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; + } } diff --git a/wcfsetup/install/files/style/dialog.less b/wcfsetup/install/files/style/dialog.less index 718de712ed..800cd84156 100644 --- a/wcfsetup/install/files/style/dialog.less +++ b/wcfsetup/install/files/style/dialog.less @@ -143,6 +143,10 @@ @media only screen and (max-width: 800px) { } +/* static dialogs */ +.jsStaticDialogContent { + display: none; +} .dialogContentX { diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index d1c4a91ca2..2f95c19c3c 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -1420,8 +1420,10 @@ GmbH=Gesellschaft mit beschränkter Haftung]]> + + @@ -1490,9 +1492,14 @@ GmbH=Gesellschaft mit beschränkter Haftung]]> - + + Schutz aufheben.]]> + +
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.

Bitte speichern Sie eventuelle vorgenommene Änderungen bevor Sie diesen Schritt ausführen.]]>
+ + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 4c49e5be40..748cc3be1b 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1419,8 +1419,10 @@ GmbH=Gesellschaft mit beschränkter Haftung]]> + + @@ -1492,6 +1494,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]> + disable this protection.]]> + +
It is neither recommended nor necessary to remove the protection in most cases, styles can still be fully customized while preserving the preset declarations.

Please save any unsaved changes before proceeding.]]>
+ + diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index ac53487415..b915fb4160 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -1003,7 +1003,9 @@ CREATE TABLE wcf1_style ( 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; -- 2.20.1