From: Tim Düsterhus Date: Fri, 4 Jun 2021 08:30:45 +0000 (+0200) Subject: Merge branch '5.3' X-Git-Tag: 5.4.0_Beta_2~37 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=2e74e3bc1887ad665f02728e7b9e6b7540ad51d7;p=GitHub%2FWoltLab%2FWCF.git Merge branch '5.3' --- 2e74e3bc1887ad665f02728e7b9e6b7540ad51d7 diff --cc wcfsetup/install/files/lib/data/style/StyleAction.class.php index cee1d0ecc1,3c774e8f74..73e8e93915 --- a/wcfsetup/install/files/lib/data/style/StyleAction.class.php +++ b/wcfsetup/install/files/lib/data/style/StyleAction.class.php @@@ -21,367 -18,366 +21,369 @@@ use wcf\util\ImageUtil /** * Executes style-related actions. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @package WoltLabSuite\Core\Data\Style - * - * @method StyleEditor[] getObjects() - * @method StyleEditor getSingleObject() + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @package WoltLabSuite\Core\Data\Style + * + * @method StyleEditor[] getObjects() + * @method StyleEditor getSingleObject() */ -class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction { - use TDatabaseObjectToggle; - - /** - * @inheritDoc - */ - protected $allowGuestAccess = ['changeStyle', 'getStyleChooser']; - - /** - * @inheritDoc - */ - protected $className = StyleEditor::class; - - /** - * @inheritDoc - */ - protected $permissionsDelete = ['admin.style.canManageStyle']; - - /** - * @inheritDoc - */ - protected $permissionsUpdate = ['admin.style.canManageStyle']; - - /** - * @inheritDoc - */ - protected $requireACP = ['copy', 'delete', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload',]; - - /** - * style object - * @var Style - */ - public $style; - - /** - * style editor object - * @var StyleEditor - */ - public $styleEditor; - - /** - * @inheritDoc - * @return Style - */ - public function create() { - /** @var Style $style */ - $style = parent::create(); - - // add variables - $this->updateVariables($style); - - // handle style preview image - $this->updateStylePreviewImage($style); - - // create favicon data - $this->updateFavicons($style); - - // handle the cover photo - $this->updateCoverPhoto($style); - - // handle custom assets - $this->updateCustomAssets($style); - - return $style; - } - - /** - * @inheritDoc - */ - public function update() { - parent::update(); - - foreach ($this->getObjects() as $style) { - // update variables - $this->updateVariables($style->getDecoratedObject(), true); - - // handle style preview image - $this->updateStylePreviewImage($style->getDecoratedObject()); - - // create favicon data - $this->updateFavicons($style->getDecoratedObject()); - - // handle the cover photo - $this->updateCoverPhoto($style->getDecoratedObject()); - - // handle custom assets - $this->updateCustomAssets($style->getDecoratedObject()); - - // reset stylesheet - StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject()); - } - } - - /** - * @inheritDoc - */ - public function delete() { - $count = parent::delete(); - - foreach ($this->getObjects() as $style) { - // 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); - } - else { - @unlink($path); - } - } - - @rmdir($dir); - } - } - - /** - * Updates style variables for given style. - * - * @param Style $style - * @param boolean $removePreviousVariables - */ - protected function updateVariables(Style $style, $removePreviousVariables = false) { - if (!isset($this->parameters['variables']) || !is_array($this->parameters['variables'])) { - return; - } - - $style->loadVariables(); - foreach (['pageLogo', 'pageLogoMobile'] as $type) { - if (array_key_exists($type, $this->parameters['uploads'])) { - /** @var \wcf\system\file\upload\UploadFile $file */ - $file = $this->parameters['uploads'][$type]; - - if ($style->getVariable($type) && file_exists($style->getAssetPath().basename($style->getVariable($type)))) { - if (!$file || $style->getAssetPath().basename($style->getVariable($type)) !== $file->getLocation()) { - unlink($style->getAssetPath().basename($style->getVariable($type))); - } - } - - if ($file !== null) { - if (!$file->isProcessed()) { - $fileLocation = $file->getLocation(); - $extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION); - $newName = $type.'-'.\bin2hex(\random_bytes(4)).'.'.$extension; - $newLocation = $style->getAssetPath().$newName; - rename($fileLocation, $newLocation); - $this->parameters['variables'][$type] = $newName; - $file->setProcessed($newLocation); - } - else { - $this->parameters['variables'][$type] = \basename($file->getLocation()); - } - } - else { - $this->parameters['variables'][$type] = ''; - } - } - } - - $sql = "SELECT variableID, variableName, defaultValue - FROM wcf".WCF_N."_style_variable"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute(); - $variables = []; - 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([$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); - - WCF::getDB()->beginTransaction(); - foreach ($variables as $variableID => $variableValue) { - $statement->execute([ - $style->styleID, - $variableID, - $variableValue - ]); - } - WCF::getDB()->commitTransaction(); - } - } - - /** - * Updates style preview image. - * - * @param Style $style - */ - protected function updateStylePreviewImage(Style $style) { - foreach (['image', 'image2x'] as $type) { - if (array_key_exists($type, $this->parameters['uploads'])) { - /** @var \wcf\system\file\upload\UploadFile $file */ - $file = $this->parameters['uploads'][$type]; - - if ($style->{$type} && file_exists($style->getAssetPath().basename($style->{$type}))) { - if (!$file || $style->getAssetPath().basename($style->{$type}) !== $file->getLocation()) { - unlink($style->getAssetPath().basename($style->{$type})); - } - } - if ($file !== null) { - $fileLocation = $file->getLocation(); - if (($imageData = getimagesize($fileLocation)) === false) { - throw new \InvalidArgumentException('The given '.$type.' is not an image'); - } - $extension = ImageUtil::getExtensionByMimeType($imageData['mime']); - if ($type === 'image') { - $newName = 'stylePreview.'.$extension; - } - else if ($type === 'image2x') { - $newName = 'stylePreview@2x.'.$extension; - } - else { - throw new \LogicException('Unreachable'); - } - $newLocation = $style->getAssetPath().$newName; - rename($fileLocation, $newLocation); - (new StyleEditor($style))->update([ - $type => FileUtil::getRelativePath(WCF_DIR.'images/', $style->getAssetPath()).$newName, - ]); - - $file->setProcessed($newLocation); - } - else { - (new StyleEditor($style))->update([ - $type => '', - ]); - } - } - } - } - - /** - * Updates style favicon files. - * - * @param Style $style - * @since 3.1 - */ - protected function updateFavicons(Style $style) { - $images = [ - 'android-chrome-192x192.png' => 192, - 'android-chrome-256x256.png' => 256, - 'apple-touch-icon.png' => 180, - 'mstile-150x150.png' => 150 - ]; - - $hasFavicon = $style->hasFavicon; - if (array_key_exists('favicon', $this->parameters['uploads'])) { - /** @var \wcf\system\file\upload\UploadFile $file */ - $file = $this->parameters['uploads']['favicon']; - - if ($file !== null) { - $fileLocation = $file->getLocation(); - if (($imageData = getimagesize($fileLocation)) === false) { - throw new \InvalidArgumentException('The given favicon is not an image'); - } - $extension = ImageUtil::getExtensionByMimeType($imageData['mime']); - $newName = "favicon-template.".$extension; - $newLocation = $style->getAssetPath().$newName; - rename($fileLocation, $newLocation); - - // Create browser specific files. - $adapter = ImageHandler::getInstance()->getAdapter(); - $adapter->loadFile($newLocation); - foreach ($images as $filename => $length) { - $thumbnail = $adapter->createThumbnail($length, $length); - $adapter->writeImage($thumbnail, $style->getAssetPath().$filename); - // Clear thumbnail as soon as possible to free up the memory. - $thumbnail = null; - } - - // Create ICO file. - require(WCF_DIR . 'lib/system/api/chrisjean/php-ico/class-php-ico.php'); - (new \PHP_ICO($newLocation, [ - [16, 16], - [32, 32] - ]))->save_ico($style->getAssetPath()."favicon.ico"); - - (new StyleEditor($style))->update([ - 'hasFavicon' => 1, - ]); - - $file->setProcessed($newLocation); - $hasFavicon = true; - } - else { - foreach ($images as $filename => $length) { - unlink($style->getAssetPath().$filename); - } - unlink($style->getAssetPath()."favicon.ico"); - foreach (['png', 'jpg', 'gif'] as $extension) { - if (file_exists($style->getAssetPath()."favicon-template.".$extension)) { - unlink($style->getAssetPath()."favicon-template.".$extension); - } - } - (new StyleEditor($style))->update([ - 'hasFavicon' => 0, - ]); - - $hasFavicon = false; - } - } - - if ($hasFavicon) { - // update manifest.json - $manifest = <<updateVariables($style); + + // handle style preview image + $this->updateStylePreviewImage($style); + + // create favicon data + $this->updateFavicons($style); + + // handle the cover photo + $this->updateCoverPhoto($style); + + // handle custom assets + $this->updateCustomAssets($style); + + return $style; + } + + /** + * @inheritDoc + */ + public function update() + { + parent::update(); + + foreach ($this->getObjects() as $style) { + // update variables + $this->updateVariables($style->getDecoratedObject(), true); + + // handle style preview image + $this->updateStylePreviewImage($style->getDecoratedObject()); + + // create favicon data + $this->updateFavicons($style->getDecoratedObject()); + + // handle the cover photo + $this->updateCoverPhoto($style->getDecoratedObject()); + + // handle custom assets + $this->updateCustomAssets($style->getDecoratedObject()); + + // reset stylesheet + StyleHandler::getInstance()->resetStylesheet($style->getDecoratedObject()); + } + } + + /** + * @inheritDoc + */ + public function delete() + { + $count = parent::delete(); + + foreach ($this->getObjects() as $style) { + // 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); + } else { + @\unlink($path); + } + } + + @\rmdir($dir); + } + } + + /** + * Updates style variables for given style. + * + * @param Style $style + * @param bool $removePreviousVariables + */ + protected function updateVariables(Style $style, $removePreviousVariables = false) + { + if (!isset($this->parameters['variables']) || !\is_array($this->parameters['variables'])) { + return; + } + + $style->loadVariables(); + foreach (['pageLogo', 'pageLogoMobile'] as $type) { + if (\array_key_exists($type, $this->parameters['uploads'])) { + /** @var \wcf\system\file\upload\UploadFile $file */ + $file = $this->parameters['uploads'][$type]; + + if ($style->getVariable($type) && \file_exists($style->getAssetPath() . \basename($style->getVariable($type)))) { + if (!$file || $style->getAssetPath() . \basename($style->getVariable($type)) !== $file->getLocation()) { + \unlink($style->getAssetPath() . \basename($style->getVariable($type))); + } + } + + if ($file !== null) { + if (!$file->isProcessed()) { + $fileLocation = $file->getLocation(); + $extension = \pathinfo($file->getFilename(), \PATHINFO_EXTENSION); + $newName = $type . '-' . Hex::encode(\random_bytes(4)) . '.' . $extension; + $newLocation = $style->getAssetPath() . $newName; + \rename($fileLocation, $newLocation); + $this->parameters['variables'][$type] = $newName; + $file->setProcessed($newLocation); ++ } else { ++ $this->parameters['variables'][$type] = \basename($file->getLocation()); + } + } else { + $this->parameters['variables'][$type] = ''; + } + } + } + + $sql = "SELECT variableID, variableName, defaultValue + FROM wcf" . WCF_N . "_style_variable"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(); + $variables = []; + 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([$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); + + WCF::getDB()->beginTransaction(); + foreach ($variables as $variableID => $variableValue) { + $statement->execute([ + $style->styleID, + $variableID, + $variableValue, + ]); + } + WCF::getDB()->commitTransaction(); + } + } + + /** + * Updates style preview image. + * + * @param Style $style + */ + protected function updateStylePreviewImage(Style $style) + { + foreach (['image', 'image2x'] as $type) { + if (\array_key_exists($type, $this->parameters['uploads'])) { + /** @var \wcf\system\file\upload\UploadFile $file */ + $file = $this->parameters['uploads'][$type]; + + if ($style->{$type} && \file_exists($style->getAssetPath() . \basename($style->{$type}))) { + if (!$file || $style->getAssetPath() . \basename($style->{$type}) !== $file->getLocation()) { + \unlink($style->getAssetPath() . \basename($style->{$type})); + } + } + if ($file !== null) { + $fileLocation = $file->getLocation(); + if (($imageData = \getimagesize($fileLocation)) === false) { + throw new \InvalidArgumentException('The given ' . $type . ' is not an image'); + } + $extension = ImageUtil::getExtensionByMimeType($imageData['mime']); + if ($type === 'image') { + $newName = 'stylePreview.' . $extension; + } elseif ($type === 'image2x') { + $newName = 'stylePreview@2x.' . $extension; + } else { + throw new \LogicException('Unreachable'); + } + $newLocation = $style->getAssetPath() . $newName; + \rename($fileLocation, $newLocation); + (new StyleEditor($style))->update([ + $type => FileUtil::getRelativePath(WCF_DIR . 'images/', $style->getAssetPath()) . $newName, + ]); + + $file->setProcessed($newLocation); + } else { + (new StyleEditor($style))->update([ + $type => '', + ]); + } + } + } + } + + /** + * Updates style favicon files. + * + * @param Style $style + * @since 3.1 + */ + protected function updateFavicons(Style $style) + { + $images = [ + 'android-chrome-192x192.png' => 192, + 'android-chrome-256x256.png' => 256, + 'apple-touch-icon.png' => 180, + 'mstile-150x150.png' => 150, + ]; + + $hasFavicon = $style->hasFavicon; + if (\array_key_exists('favicon', $this->parameters['uploads'])) { + /** @var \wcf\system\file\upload\UploadFile $file */ + $file = $this->parameters['uploads']['favicon']; + + if ($file !== null) { + $fileLocation = $file->getLocation(); + if (($imageData = \getimagesize($fileLocation)) === false) { + throw new \InvalidArgumentException('The given favicon is not an image'); + } + $extension = ImageUtil::getExtensionByMimeType($imageData['mime']); + $newName = "favicon-template." . $extension; + $newLocation = $style->getAssetPath() . $newName; + \rename($fileLocation, $newLocation); + + // Create browser specific files. + $adapter = ImageHandler::getInstance()->getAdapter(); + $adapter->loadFile($newLocation); + foreach ($images as $filename => $length) { + $thumbnail = $adapter->createThumbnail($length, $length); + $adapter->writeImage($thumbnail, $style->getAssetPath() . $filename); + // Clear thumbnail as soon as possible to free up the memory. + $thumbnail = null; + } + + // Create ICO file. + require(WCF_DIR . 'lib/system/api/chrisjean/php-ico/class-php-ico.php'); + (new \PHP_ICO($newLocation, [ + [16, 16], + [32, 32], + ]))->save_ico($style->getAssetPath() . "favicon.ico"); + + (new StyleEditor($style))->update([ + 'hasFavicon' => 1, + ]); + + $file->setProcessed($newLocation); + $hasFavicon = true; + } else { + foreach ($images as $filename => $length) { + \unlink($style->getAssetPath() . $filename); + } + \unlink($style->getAssetPath() . "favicon.ico"); + foreach (['png', 'jpg', 'gif'] as $extension) { + if (\file_exists($style->getAssetPath() . "favicon-template." . $extension)) { + \unlink($style->getAssetPath() . "favicon-template." . $extension); + } + } + (new StyleEditor($style))->update([ + 'hasFavicon' => 0, + ]); + + $hasFavicon = false; + } + } + + if ($hasFavicon) { + // update manifest.json + $manifest = <<<'MANIFEST' { "name": "", "icons": [