Merge branch '5.3'
authorTim Düsterhus <duesterhus@woltlab.com>
Fri, 4 Jun 2021 08:30:45 +0000 (10:30 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Fri, 4 Jun 2021 08:30:45 +0000 (10:30 +0200)
1  2 
wcfsetup/install/files/lib/data/style/StyleAction.class.php

index cee1d0ecc107d6f7458a658ba8b376351cdb52ad,3c774e8f74aeab875b54df350be45448c3d987a7..73e8e93915a317116a1f181b9ade57a291715097
@@@ -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 <http://opensource.org/licenses/lgpl-license.php>
 - * @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 <http://opensource.org/licenses/lgpl-license.php>
 + * @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 = <<<MANIFEST
 +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 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": [