diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml index 1e6fe5adb0..b0cf59c245 100644 --- a/apps/theming/appinfo/info.xml +++ b/apps/theming/appinfo/info.xml @@ -25,4 +25,7 @@ OCA\Theming\Settings\Admin OCA\Theming\Settings\Section + + OCA\Theming\Command\UpdateConfig + diff --git a/apps/theming/lib/Command/UpdateConfig.php b/apps/theming/lib/Command/UpdateConfig.php new file mode 100644 index 0000000000..7d616879dc --- /dev/null +++ b/apps/theming/lib/Command/UpdateConfig.php @@ -0,0 +1,135 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Theming\Command; + +use OCA\Theming\ImageManager; +use OCA\Theming\ThemingDefaults; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class UpdateConfig extends Command { + public const SUPPORTED_KEYS = [ + 'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color' + ]; + + public const SUPPORTED_IMAGE_KEYS = [ + 'background', 'logo', 'favicon', 'logoheader' + ]; + + private $themingDefaults; + private $imageManager; + private $config; + + public function __construct(ThemingDefaults $themingDefaults, ImageManager $imageManager, IConfig $config) { + parent::__construct(); + + $this->themingDefaults = $themingDefaults; + $this->imageManager = $imageManager; + $this->config = $config; + } + + protected function configure() { + $this + ->setName('theming:config') + ->setDescription('Set theming app config values') + ->addArgument( + 'key', + InputArgument::OPTIONAL, + 'Key to update the theming app configuration (leave empty to get a list of all configured values)' . PHP_EOL . + 'One of: ' . implode(', ', self::SUPPORTED_KEYS) + ) + ->addArgument( + 'value', + InputArgument::OPTIONAL, + 'Value to set (leave empty to obtain the current value)' + ) + ->addOption( + 'reset', + 'r', + InputOption::VALUE_NONE, + 'Reset the given config key to default' + ); + } + + + protected function execute(InputInterface $input, OutputInterface $output): int { + $key = $input->getArgument('key'); + $value = $input->getArgument('value'); + + if ($key === null) { + $output->writeln('Current theming config:'); + foreach (self::SUPPORTED_KEYS as $key) { + $value = $this->config->getAppValue('theming', $key, ''); + $output->writeln('- ' . $key . ': ' . $value . ''); + } + foreach (self::SUPPORTED_IMAGE_KEYS as $key) { + $value = $this->config->getAppValue('theming', $key . 'Mime', ''); + $output->writeln('- ' . $key . ': ' . $value . ''); + } + return 0; + } + + if (!in_array($key, self::SUPPORTED_KEYS, true)) { + $output->writeln('Invalid config key provided'); + return 1; + } + + if ($input->getOption('reset')) { + $defaultValue = $this->themingDefaults->undo($key); + $output->writeln('Reset ' . $key . ' to ' . $defaultValue . ''); + return 0; + } + + if ($value === null) { + $value = $this->config->getAppValue('theming', $key, ''); + if ($value !== '') { + $output->writeln('' . $key . ' is currently set to ' . $value . ''); + } else { + $output->writeln('' . $key . ' is currently not set'); + } + return 0; + } + + if (in_array($key, self::SUPPORTED_IMAGE_KEYS, true)) { + if (file_exists(__DIR__ . $value)) { + $value = __DIR__ . $value; + } + if (!file_exists($value)) { + $output->writeln('File could not be found: ' . $value . ''); + return 1; + } + $value = $this->imageManager->updateImage($key, $value); + $key = $key . 'Mime'; + } + + $this->themingDefaults->set($key, $value); + $output->writeln('Updated ' . $key . ' to ' . $value . ''); + + return 0; + } +} diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 241f88dde6..b15ac22183 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -221,9 +221,6 @@ class ThemingController extends Controller { * @throws NotPermittedException */ public function uploadImage(): DataResponse { - // logo / background - // new: favicon logo-header - // $key = $this->request->getParam('key'); $image = $this->request->getUploadedFile('image'); $error = null; @@ -256,23 +253,14 @@ class ThemingController extends Controller { ); } - $name = ''; try { - $folder = $this->appData->getFolder('images'); - } catch (NotFoundException $e) { - $folder = $this->appData->newFolder('images'); - } - - $this->imageManager->delete($key); - - $target = $folder->newFile($key); - $supportedFormats = $this->getSupportedUploadImageFormats($key); - $detectedMimeType = mime_content_type($image['tmp_name']); - if (!in_array($image['type'], $supportedFormats) || !in_array($detectedMimeType, $supportedFormats)) { + $mime = $this->imageManager->updateImage($key, $image['tmp_name']); + $this->themingDefaults->set($key . 'Mime', $mime); + } catch (\Exception $e) { return new DataResponse( [ 'data' => [ - 'message' => $this->l10n->t('Unsupported image type'), + 'message' => $e->getMessage() ], 'status' => 'failure', ], @@ -280,32 +268,7 @@ class ThemingController extends Controller { ); } - if ($key === 'background' && strpos($detectedMimeType, 'image/svg') === false) { - // Optimize the image since some people may upload images that will be - // either to big or are not progressive rendering. - $newImage = @imagecreatefromstring(file_get_contents($image['tmp_name'], 'r')); - - // Preserve transparency - imagesavealpha($newImage, true); - imagealphablending($newImage, true); - - $tmpFile = $this->tempManager->getTemporaryFile(); - $newWidth = imagesx($newImage) < 4096 ? imagesx($newImage) : 4096; - $newHeight = imagesy($newImage) / (imagesx($newImage) / $newWidth); - $outputImage = imagescale($newImage, $newWidth, $newHeight); - - imageinterlace($outputImage, 1); - imagepng($outputImage, $tmpFile, 8); - imagedestroy($outputImage); - - $target->putContent(file_get_contents($tmpFile, 'r')); - } else { - $target->putContent(file_get_contents($image['tmp_name'], 'r')); - } $name = $image['name']; - - $this->themingDefaults->set($key.'Mime', $image['type']); - $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core'); return new DataResponse( @@ -322,24 +285,6 @@ class ThemingController extends Controller { ); } - /** - * Returns a list of supported mime types for image uploads. - * "favicon" images are only allowed to be SVG when imagemagick with SVG support is available. - * - * @param string $key The image key, e.g. "favicon" - * @return array - */ - private function getSupportedUploadImageFormats(string $key): array { - $supportedFormats = ['image/jpeg', 'image/png', 'image/gif',]; - - if ($key !== 'favicon' || $this->imageManager->shouldReplaceIcons() === true) { - $supportedFormats[] = 'image/svg+xml'; - $supportedFormats[] = 'image/svg'; - } - - return $supportedFormats; - } - /** * Revert setting to default value * @@ -352,11 +297,6 @@ class ThemingController extends Controller { // reprocess server scss for preview $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core'); - if (strpos($setting, 'Mime') !== -1) { - $imageKey = str_replace('Mime', '', $setting); - $this->imageManager->delete($imageKey); - } - return new DataResponse( [ 'data' => diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php index bf52fd8176..1b4848638f 100644 --- a/apps/theming/lib/ImageManager.php +++ b/apps/theming/lib/ImageManager.php @@ -36,6 +36,7 @@ use OCP\Files\SimpleFS\ISimpleFolder; use OCP\ICacheFactory; use OCP\IConfig; use OCP\ILogger; +use OCP\ITempManager; use OCP\IURLGenerator; class ImageManager { @@ -52,27 +53,22 @@ class ImageManager { private $cacheFactory; /** @var ILogger */ private $logger; + /** @var ITempManager */ + private $tempManager; - /** - * ImageManager constructor. - * - * @param IConfig $config - * @param IAppData $appData - * @param IURLGenerator $urlGenerator - * @param ICacheFactory $cacheFactory - * @param ILogger $logger - */ public function __construct(IConfig $config, IAppData $appData, IURLGenerator $urlGenerator, ICacheFactory $cacheFactory, - ILogger $logger + ILogger $logger, + ITempManager $tempManager ) { $this->config = $config; $this->appData = $appData; $this->urlGenerator = $urlGenerator; $this->cacheFactory = $cacheFactory; $this->logger = $logger; + $this->tempManager = $tempManager; } public function getImageUrl(string $key, bool $useSvg = true): string { @@ -211,6 +207,66 @@ class ImageManager { } } + public function updateImage(string $key, string $tmpFile) { + $this->delete($key); + + try { + $folder = $this->appData->getFolder('images'); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder('images'); + } + + $target = $folder->newFile($key); + $supportedFormats = $this->getSupportedUploadImageFormats($key); + $detectedMimeType = mime_content_type($tmpFile); + if (!in_array($detectedMimeType, $supportedFormats, true)) { + throw new \Exception('Unsupported image type'); + } + + if ($key === 'background' && strpos($detectedMimeType, 'image/svg') === false) { + // Optimize the image since some people may upload images that will be + // either to big or are not progressive rendering. + $newImage = @imagecreatefromstring(file_get_contents($tmpFile)); + + // Preserve transparency + imagesavealpha($newImage, true); + imagealphablending($newImage, true); + + $tmpFile = $this->tempManager->getTemporaryFile(); + $newWidth = (int)(imagesx($newImage) < 4096 ? imagesx($newImage) : 4096); + $newHeight = (int)(imagesy($newImage) / (imagesx($newImage) / $newWidth)); + $outputImage = imagescale($newImage, $newWidth, $newHeight); + + imageinterlace($outputImage, 1); + imagepng($outputImage, $tmpFile, 8); + imagedestroy($outputImage); + + $target->putContent(file_get_contents($tmpFile, 'r')); + } else { + $target->putContent(file_get_contents($tmpFile)); + } + + return $detectedMimeType; + } + + /** + * Returns a list of supported mime types for image uploads. + * "favicon" images are only allowed to be SVG when imagemagick with SVG support is available. + * + * @param string $key The image key, e.g. "favicon" + * @return array + */ + private function getSupportedUploadImageFormats(string $key): array { + $supportedFormats = ['image/jpeg', 'image/png', 'image/gif']; + + if ($key !== 'favicon' || $this->shouldReplaceIcons() === true) { + $supportedFormats[] = 'image/svg+xml'; + $supportedFormats[] = 'image/svg'; + } + + return $supportedFormats; + } + /** * remove cached files that are not required any longer * diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index e568084662..3fdbc1a61a 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -398,6 +398,7 @@ class ThemingDefaults extends \OC_Defaults { $this->config->deleteAppValue('theming', $setting); $this->increaseCacheBuster(); + $returnValue = ''; switch ($setting) { case 'name': $returnValue = $this->getEntity(); @@ -411,8 +412,11 @@ class ThemingDefaults extends \OC_Defaults { case 'color': $returnValue = $this->getColorPrimary(); break; - default: - $returnValue = ''; + case 'logo': + case 'logoheader': + case 'background': + case 'favicon': + $this->imageManager->delete($setting); break; } diff --git a/lib/private/Server.php b/lib/private/Server.php index 189b00511e..224ef68a7c 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1126,7 +1126,7 @@ class Server extends ServerContainer implements IServerContainer { $c->getURLGenerator(), $c->getMemCacheFactory(), new Util($c->getConfig(), $this->getAppManager(), $c->getAppDataDir('theming')), - new ImageManager($c->getConfig(), $c->getAppDataDir('theming'), $c->getURLGenerator(), $this->getMemCacheFactory(), $this->getLogger()), + new ImageManager($c->getConfig(), $c->getAppDataDir('theming'), $c->getURLGenerator(), $this->getMemCacheFactory(), $this->getLogger(), $this->getTempManager()), $c->getAppManager(), $c->getNavigationManager() );