Fixed style editing
authorAlexander Ebert <ebert@woltlab.com>
Wed, 10 Oct 2012 15:38:04 +0000 (17:38 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 10 Oct 2012 15:38:04 +0000 (17:38 +0200)
wcfsetup/install/files/images/stylePreview.png [new file with mode: 0644]
wcfsetup/install/files/lib/acp/action/AJAXUploadAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/style/Style.class.php
wcfsetup/install/files/lib/data/style/StyleAction.class.php
wcfsetup/install/files/lib/data/style/StyleEditor.class.php
wcfsetup/install/files/lib/system/image/adapter/ImageAdapter.class.php
wcfsetup/install/files/lib/system/style/StyleHandler.class.php
wcfsetup/install/files/style/colorPicker.less

diff --git a/wcfsetup/install/files/images/stylePreview.png b/wcfsetup/install/files/images/stylePreview.png
new file mode 100644 (file)
index 0000000..b075d83
Binary files /dev/null and b/wcfsetup/install/files/images/stylePreview.png differ
diff --git a/wcfsetup/install/files/lib/acp/action/AJAXUploadAction.class.php b/wcfsetup/install/files/lib/acp/action/AJAXUploadAction.class.php
new file mode 100644 (file)
index 0000000..f99c6e3
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+namespace wcf\acp\action;
+
+/**
+ * Copy of the default implementation for file uploads using the AJAX-API.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.action
+ * @category   Community Framework
+ */
+class AJAXUploadAction extends \wcf\action\AJAXUploadAction { }
index e000dc2b6f395a91d8d73adb7ef68e0cfd09b228..3c834483de172165260308095a765e9d199cddf9 100644 (file)
@@ -24,6 +24,9 @@ class Style extends DatabaseObject {
         */
        protected static $databaseTableIndexName = 'styleID';
        
+       const PREVIEW_IMAGE_MAX_HEIGHT = 140;
+       const PREVIEW_IMAGE_MAX_WIDTH = 185;
+       
        /**
         * Returns the name of this style.
         * 
@@ -53,4 +56,17 @@ class Style extends DatabaseObject {
                
                return $variables;
        }
+       
+       /**
+        * Returns the style preview image path.
+        * 
+        * @return      string
+        */
+       public function getPreviewImage() {
+               if ($this->image && file_exists(WCF_DIR.'images/'.$this->image)) {
+                       return WCF::getPath().'images/'.$this->image;
+               }
+               
+               return WCF::getPath().'images/stylePreview.png';
+       }
 }
index 218c98e9f7d91f58f8bedad018b9da3076479593..34a3cb53ab006593344fd4d11b6dcd8204d7fefd 100644 (file)
@@ -1,7 +1,15 @@
 <?php
 namespace wcf\data\style;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\image\ImageHandler;
+use wcf\system\style\StyleHandler;
+use wcf\system\upload\DefaultUploadFileValidationStrategy;
 use wcf\system\WCF;
+use wcf\util\FileUtil;
 
 /**
  * Executes style-related actions.
@@ -26,45 +34,250 @@ class StyleAction extends AbstractDatabaseObjectAction {
                $style = parent::create();
                
                // add variables
-               if (isset($this->parameters['variables']) && !empty($this->parameters['variables'])) {
-                       $sql = "SELECT  variableID, variableName, defaultValue
-                               FROM    wcf".WCF_N."_style_variable";
+               $this->updateVariables($style);
+               
+               // handle style preview image
+               $this->updateStylePreviewImage($style);
+               
+               return $style;
+       }
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::update()
+        */
+       public function update() {
+               parent::update();
+               
+               foreach ($this->objects as $style) {
+                       // update variables
+                       $this->updateVariables($style->getDecoratedObject(), true);
+                       
+                       // handle style preview image
+                       $this->updateStylePreviewImage($style->getDecoratedObject());
+                       
+                       // reset stylesheet
+                       StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject());
+               }
+       }
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::delete()
+        */
+       public function delete() {
+               $count = parent::delete();
+               
+               foreach ($this->objects as $style) {
+                       // remove custom icons
+                       if ($style->iconPath && $style->iconPath != 'icon/') {
+                               $this->removeDirectory($style->iconPath);
+                       }
+                       
+                       // remove custom images
+                       if ($style->imagePath && $style->imagePath != 'images/') {
+                               $this->removeDirectory($style->imagePath);
+                       }
+                       
+                       // remove preview image
+                       $previewImage = WCF_DIR.'images/'.$style->image;
+                       if (file_exists($previewImage)) {
+                               @unlink($previewImage);
+                       }
+                       
+                       // remove stylesheet
+                       StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject());
+               }
+               
+               return $count;
+       }
+       
+       /**
+        * Recursively removes a directory and all it's contents.
+        * 
+        * @param       string          $pathComponent
+        */
+       protected function removeDirectory($pathComponent) {
+               $dir = WCF_DIR.$pathComponent;
+               if (is_dir($dir)) {
+                       $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::CHILD_FIRST);
+                       foreach ($iterator as $path) {
+                               if ($path->isDir()) {
+                                       @rmdir($path->__toString());
+                               }
+                               else {
+                                       @unlink($path->__toString());
+                               }
+                       }
+                       
+                       @rmdir($dir);
+               }
+       }
+       
+       /**
+        * Updates style variables for given style.
+        * 
+        * @param       wcf\data\style\Style    $style
+        * @param       boolean                 $removePreviousVariables
+        */
+       protected function updateVariables(Style $style, $removePreviousVariables = false) {
+               if (!isset($this->parameters['variables']) || !is_array($this->parameters['variables'])) {
+                       return;
+               }
+               
+               $sql = "SELECT  variableID, variableName, defaultValue
+                       FROM    wcf".WCF_N."_style_variable";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               $variables = array();
+               while ($row = $statement->fetchArray()) {
+                       $variableName = $row['variableName'];
+                               
+                       // ignore variables with identical value
+                       if (isset($this->parameters['variables'][$variableName])) {
+                               if ($this->parameters['variables'][$variableName] == $row['defaultValue']) {
+                                       continue;
+                               }
+                               else {
+                                       $variables[$row['variableID']] = $this->parameters['variables'][$variableName];
+                               }
+                       }
+               }
+                       
+               // remove previously set variables
+               if ($removePreviousVariables) {
+                       $sql = "DELETE FROM     wcf".WCF_N."_style_variable_value
+                               WHERE           styleID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute(array($style->styleID));
+               }
+                       
+               // insert variables that differ from default values
+               if (!empty($variables)) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_style_variable_value
+                                               (styleID, variableID, variableValue)
+                               VALUES          (?, ?, ?)";
                        $statement = WCF::getDB()->prepareStatement($sql);
-                       $statement->execute();
-                       $variables = array();
-                       while ($row = $statement->fetchArray()) {
-                               $variableName = $row['variableName'];
                                
-                               // ignore variables with identical value
-                               if (isset($this->parameters['variables'][$variableName])) {
-                                       if ($this->parameters['variables'][$variableName] == $row['defaultValue']) {
-                                               continue;
-                                       }
-                                       else {
-                                               $variables[$row['variableID']] = $this->parameters['variables'][$variableName];
+                       WCF::getDB()->beginTransaction();
+                       foreach ($variables as $variableID => $variableValue) {
+                               $statement->execute(array(
+                                       $style->styleID,
+                                       $variableID,
+                                       $variableValue
+                               ));
+                       }
+                       WCF::getDB()->commitTransaction();
+               }
+       }
+       
+       /**
+        * Updates style preview image.
+        * 
+        * @param       wcf\data\style\Style    $style
+        */
+       protected function updateStylePreviewImage(Style $style) {
+               if (!isset($this->parameters['tmpHash'])) {
+                       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(array(
+                                                       $filename,
+                                                       $style->styleID
+                                               ));
                                        }
                                }
+                               else {
+                                       // remove temp file
+                                       @unlink($oldFilename);
+                               }
                        }
-                       
-                       // insert variables that differ from default values
-                       if (!empty($variables)) {
-                               $sql = "INSERT INTO     wcf".WCF_N."_style_variable_value
-                                                       (styleID, variableID, variableValue)
-                                       VALUES          (?, ?, ?)";
-                               $statement = WCF::getDB()->prepareStatement($sql);
+               }
+       }
+       
+       /**
+        * Validates the upload action.
+        */
+       public function validateUpload() {
+               // check upload permissions
+               if (!WCF::getSession()->getPermission('admin.style.canAddStyle')) {
+                       throw new PermissionDeniedException();
+               }
+               
+               if (!isset($this->parameters['tmpHash']) || empty($this->parameters['tmpHash'])) {
+                       throw new UserInputException('tmpHash');
+               }
+               
+               if (count($this->parameters['__files']->getFiles()) != 1) {
+                       throw new IllegalLinkException();
+               }
+               
+               // check max filesize, allowed file extensions etc.
+               $this->parameters['__files']->validateFiles(new DefaultUploadFileValidationStrategy(PHP_INT_MAX, array('jpg', 'jpeg', 'png')));
+       }
+       
+       /**
+        * Handles uploaded attachments.
+        */
+       public function upload() {
+               // save files
+               $files = $this->parameters['__files']->getFiles();
+               $file = $files[0];
+               
+               try {
+                       if (!$file->getValidationErrorType()) {
+                               // shrink avatar if necessary
+                               $fileLocation = $file->getLocation();
+                               $imageData = getimagesize($fileLocation);
+                               if ($imageData[0] > Style::PREVIEW_IMAGE_MAX_WIDTH || $imageData[1] > Style::PREVIEW_IMAGE_MAX_HEIGHT) {
+                                       try {
+                                               $adapter = ImageHandler::getInstance()->getAdapter();
+                                               $adapter->loadFile($fileLocation);
+                                               $fileLocation = FileUtil::getTemporaryFilename();
+                                               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH, Style::PREVIEW_IMAGE_MAX_HEIGHT, false);
+                                               $adapter->writeImage($thumbnail, $fileLocation);
+                                               $imageData = getimagesize($fileLocation);
+                                       }
+                                       catch (SystemException $e) {
+                                               throw new UserInputException('image');
+                                       }
+                               }
                                
-                               WCF::getDB()->beginTransaction();
-                               foreach ($variables as $variableID => $variableValue) {
-                                       $statement->execute(array(
-                                               $style->styleID,
-                                               $variableID,
-                                               $variableValue
-                                       ));
+                               // move uploaded file
+                               if (@copy($fileLocation, WCF_DIR.'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension())) {
+                                       @unlink($fileLocation);
+                                       
+                                       // store extension within session variables
+                                       WCF::getSession()->register('stylePreview-'.$this->parameters['tmpHash'], $file->getFileExtension());
+                                       
+                                       // return result
+                                       return array(
+                                               'errorType' => '',
+                                               'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
+                                       );
+                               }
+                               else {
+                                       throw new UserInputException('image', 'uploadFailed');
                                }
-                               WCF::getDB()->commitTransaction();
                        }
                }
+               catch (UserInputException $e) {
+                       $file->setValidationErrorType($e->getType());
+               }
                
-               return $style;
+               return array('errorType' => $file->getValidationErrorType());
        }
 }
index 29170f24dbbc390f89f6d36c56646949d569f8fe..ef54f61894b46ae8988c5518d2dac9d9b3642750 100644 (file)
@@ -32,8 +32,6 @@ use wcf\util\XML;
  */
 class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject {
        const INFO_FILE = 'style.xml';
-       const STYLE_PREVIEW_IMAGE_MAX_WIDTH = 185;
-       const STYLE_PREVIEW_IMAGE_MAX_HEIGHT = 140;
        
        /**
         * @see wcf\data\DatabaseObjectDecorator::$baseClass
@@ -344,7 +342,6 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                        'styleDescription' => $data['description'],
                        'styleVersion' => $data['version'],
                        'styleDate' => $data['date'],
-                       'image' => ($data['image'] ? 'images/' : '').$data['image'],
                        'copyright' => $data['copyright'],
                        'license' => $data['license'],
                        'authorName' => $data['authorName'],
@@ -356,15 +353,19 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
                }
                else {
                        $styleData['packageID'] = $packageID;
-                       $style = self::create($styleData);
+                       $style = new StyleEditor(self::create($styleData));
                }
                
                // import preview image
                if (!empty($data['image'])) {
-                       $i = $tar->getIndexByFilename($data['image']);
-                       if ($i !== false) {
-                               $tar->extract($i, WCF_DIR.'images/'.$data['image']);
-                               @chmod(WCF_DIR.'images/'.$data['image'], 0777);
+                       $fileExtension = StringUtil::substring($data['image'], StringUtil::lastIndexOf($data['image'], '.'));
+                       $index = $tar->getIndexByFilename($data['image']);
+                       if ($index !== false) {
+                               $filename = WCF_DIR.'images/stylePreview-'.$style->styleID.'.'.$fileExtension;
+                               $tar->extract($index, $filename);
+                               @chmod($filename, 0777);
+                               
+                               $style->update(array('image' => $filename));
                        }
                }
                
@@ -809,7 +810,7 @@ class StyleEditor extends DatabaseObjectEditor implements IEditableCachedObject
        public static function scalePreviewImage($filename) {
                $adapter = ImageHandler::getInstance()->getAdapter();
                $adapter->load($filename);
-               $thumbnail = $adapter->createThumbnail(self::STYLE_PREVIEW_IMAGE_MAX_WIDTH, self::STYLE_PREVIEW_IMAGE_MAX_HEIGHT);
+               $thumbnail = $adapter->createThumbnail(Style::PREVIEW_IMAGE_MAX_WIDTH, Style::PREVIEW_IMAGE_MAX_HEIGHT);
                $adapter->writeImage($thumbnail, $filename);
        }
        
index 9e6b8d68fa7fe40b99758964e11e0855b909a74c..94cfb9bf45a00878d82e8f1b18df462dd289fb0a 100644 (file)
@@ -57,10 +57,13 @@ class ImageAdapter implements IImageAdapter {
         * @see wcf\system\image\adapter\IImageAdapter::createThumbnail()
         */
        public function createThumbnail($maxWidth, $maxHeight, $obtainDimensions = true) {
-               if ($maxWidth > $this->getWidth() || $maxHeight > $this->getHeight()) {
+               if ($maxWidth > $this->getWidth() && $maxHeight > $this->getHeight()) {
                        throw new SystemException("Dimensions for thumbnail can not exceed image dimensions.");
                }
                
+               $maxHeight = min($maxHeight, $this->getHeight());
+               $maxWidth = min($maxWidth, $this->getWidth());
+               
                return $this->adapter->createThumbnail($maxWidth, $maxHeight, $obtainDimensions);
        }
        
index 8a0a5d36d559f50beaa4bf3d0e0d810e23d6f6af..6bcdfd7e16c44355441226d9d193fca7454e93d3 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\style;
 use wcf\data\style\ActiveStyle;
+use wcf\data\style\Style;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\CacheHandler;
 use wcf\system\exception\SystemException;
@@ -140,4 +141,16 @@ class StyleHandler extends SingletonFactory {
                
                return '<link rel="stylesheet" type="text/css" href="'.WCF::getPath().$filename.'?m='.filemtime(WCF_DIR.$filename).'" />';
        }
+       
+       /**
+        * Resets stylesheet for given style.
+        * 
+        * @param       wcf\data\style\Style    $style
+        */
+       public function resetStylesheet(Style $style) {
+               $stylesheets = glob(WCF_DIR.'style/style-*-'.$style->styleID.'*.css');
+               foreach ($stylesheets as $stylesheet) {
+                       @unlink($stylesheet);
+               }
+       }
 }
index 58e0ae2b34358f7ce17427bc91df3e72d3ca6096..852859c9e0472aec2e14e49b6e6d13acf3554439 100644 (file)
@@ -1,6 +1,6 @@
 #colorPickerGradient {
        background-color: #f00;
-       background-image: url('images/colorPickerGradient.png');
+       background-image: url('../images/colorPickerGradient.png');
        background-repeat: no-repeat;
        border: 1px solid rgba(0, 0, 0, 1);
        cursor: default;
@@ -31,7 +31,7 @@
 } 
 
 #colorPickerBar {
-       background-image: url('images/colorPickerBar.png');
+       background-image: url('../images/colorPickerBar.png');
        background-repeat: repeat-x;
        border: 1px solid rgba(0, 0, 0, 1);
        cursor: default;