mirror of
https://github.com/nextcloud/server.git
synced 2026-02-12 07:14:44 -05:00
To continue this formatting madness, here's a tiny patch that adds unified formatting for control structures like if and loops as well as classes, their methods and anonymous functions. This basically forces the constructs to start on the same line. This is not exactly what PSR2 wants, but I think we can have a few exceptions with "our" style. The starting of braces on the same line is pracrically standard for our code. This also removes and empty lines from method/function bodies at the beginning and end. Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
489 lines
15 KiB
PHP
489 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
|
|
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
|
*
|
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
|
* @author Bjoern Schiessle <bjoern@schiessle.org>
|
|
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
|
|
* @author Daniel Kesselberg <mail@danielkesselberg.de>
|
|
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
|
|
* @author Joas Schilling <coding@schilljs.com>
|
|
* @author Julius Haertl <jus@bitgrid.net>
|
|
* @author Julius Härtl <jus@bitgrid.net>
|
|
* @author Kyle Fazzari <kyrofa@ubuntu.com>
|
|
* @author Lukas Reschke <lukas@statuscode.ch>
|
|
* @author Michael Weimann <mail@michael-weimann.eu>
|
|
* @author rakekniven <mark.ziegler@rakekniven.de>
|
|
* @author Robin Appelman <robin@icewind.nl>
|
|
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
|
* @author Thomas Citharel <nextcloud@tcit.fr>
|
|
*
|
|
* @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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
namespace OCA\Theming\Controller;
|
|
|
|
use OC\Template\SCSSCacher;
|
|
use OCA\Theming\ImageManager;
|
|
use OCA\Theming\ThemingDefaults;
|
|
use OCA\Theming\Util;
|
|
use OCP\App\IAppManager;
|
|
use OCP\AppFramework\Controller;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Http\DataDownloadResponse;
|
|
use OCP\AppFramework\Http\DataResponse;
|
|
use OCP\AppFramework\Http\FileDisplayResponse;
|
|
use OCP\AppFramework\Http\NotFoundResponse;
|
|
use OCP\Files\IAppData;
|
|
use OCP\Files\NotFoundException;
|
|
use OCP\Files\NotPermittedException;
|
|
use OCP\IConfig;
|
|
use OCP\IL10N;
|
|
use OCP\IRequest;
|
|
use OCP\ITempManager;
|
|
use OCP\IURLGenerator;
|
|
|
|
/**
|
|
* Class ThemingController
|
|
*
|
|
* handle ajax requests to update the theme
|
|
*
|
|
* @package OCA\Theming\Controller
|
|
*/
|
|
class ThemingController extends Controller {
|
|
/** @var ThemingDefaults */
|
|
private $themingDefaults;
|
|
/** @var Util */
|
|
private $util;
|
|
/** @var IL10N */
|
|
private $l10n;
|
|
/** @var IConfig */
|
|
private $config;
|
|
/** @var ITempManager */
|
|
private $tempManager;
|
|
/** @var IAppData */
|
|
private $appData;
|
|
/** @var SCSSCacher */
|
|
private $scssCacher;
|
|
/** @var IURLGenerator */
|
|
private $urlGenerator;
|
|
/** @var IAppManager */
|
|
private $appManager;
|
|
/** @var ImageManager */
|
|
private $imageManager;
|
|
|
|
/**
|
|
* ThemingController constructor.
|
|
*
|
|
* @param string $appName
|
|
* @param IRequest $request
|
|
* @param IConfig $config
|
|
* @param ThemingDefaults $themingDefaults
|
|
* @param Util $util
|
|
* @param IL10N $l
|
|
* @param ITempManager $tempManager
|
|
* @param IAppData $appData
|
|
* @param SCSSCacher $scssCacher
|
|
* @param IURLGenerator $urlGenerator
|
|
* @param IAppManager $appManager
|
|
* @param ImageManager $imageManager
|
|
*/
|
|
public function __construct(
|
|
$appName,
|
|
IRequest $request,
|
|
IConfig $config,
|
|
ThemingDefaults $themingDefaults,
|
|
Util $util,
|
|
IL10N $l,
|
|
ITempManager $tempManager,
|
|
IAppData $appData,
|
|
SCSSCacher $scssCacher,
|
|
IURLGenerator $urlGenerator,
|
|
IAppManager $appManager,
|
|
ImageManager $imageManager
|
|
) {
|
|
parent::__construct($appName, $request);
|
|
|
|
$this->themingDefaults = $themingDefaults;
|
|
$this->util = $util;
|
|
$this->l10n = $l;
|
|
$this->config = $config;
|
|
$this->tempManager = $tempManager;
|
|
$this->appData = $appData;
|
|
$this->scssCacher = $scssCacher;
|
|
$this->urlGenerator = $urlGenerator;
|
|
$this->appManager = $appManager;
|
|
$this->imageManager = $imageManager;
|
|
}
|
|
|
|
/**
|
|
* @param string $setting
|
|
* @param string $value
|
|
* @return DataResponse
|
|
* @throws NotPermittedException
|
|
*/
|
|
public function updateStylesheet($setting, $value) {
|
|
$value = trim($value);
|
|
$error = null;
|
|
switch ($setting) {
|
|
case 'name':
|
|
if (strlen($value) > 250) {
|
|
$error = $this->l10n->t('The given name is too long');
|
|
}
|
|
break;
|
|
case 'url':
|
|
if (strlen($value) > 500) {
|
|
$error = $this->l10n->t('The given web address is too long');
|
|
}
|
|
if (!$this->isValidUrl($value)) {
|
|
$error = $this->l10n->t('The given web address is not a valid URL');
|
|
}
|
|
break;
|
|
case 'imprintUrl':
|
|
if (strlen($value) > 500) {
|
|
$error = $this->l10n->t('The given legal notice address is too long');
|
|
}
|
|
if (!$this->isValidUrl($value)) {
|
|
$error = $this->l10n->t('The given legal notice address is not a valid URL');
|
|
}
|
|
break;
|
|
case 'privacyUrl':
|
|
if (strlen($value) > 500) {
|
|
$error = $this->l10n->t('The given privacy policy address is too long');
|
|
}
|
|
if (!$this->isValidUrl($value)) {
|
|
$error = $this->l10n->t('The given privacy policy address is not a valid URL');
|
|
}
|
|
break;
|
|
case 'slogan':
|
|
if (strlen($value) > 500) {
|
|
$error = $this->l10n->t('The given slogan is too long');
|
|
}
|
|
break;
|
|
case 'color':
|
|
if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
|
|
$error = $this->l10n->t('The given color is invalid');
|
|
}
|
|
break;
|
|
}
|
|
if ($error !== null) {
|
|
return new DataResponse([
|
|
'data' => [
|
|
'message' => $error,
|
|
],
|
|
'status' => 'error'
|
|
], Http::STATUS_BAD_REQUEST);
|
|
}
|
|
|
|
$this->themingDefaults->set($setting, $value);
|
|
|
|
// reprocess server scss for preview
|
|
$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
|
|
|
|
return new DataResponse(
|
|
[
|
|
'data' =>
|
|
[
|
|
'message' => $this->l10n->t('Saved'),
|
|
'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
|
|
],
|
|
'status' => 'success'
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check that a string is a valid http/https url
|
|
*/
|
|
private function isValidUrl(string $url): bool {
|
|
return ((strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) &&
|
|
filter_var($url, FILTER_VALIDATE_URL) !== false);
|
|
}
|
|
|
|
/**
|
|
* @return DataResponse
|
|
* @throws NotPermittedException
|
|
*/
|
|
public function uploadImage(): DataResponse {
|
|
// logo / background
|
|
// new: favicon logo-header
|
|
//
|
|
$key = $this->request->getParam('key');
|
|
$image = $this->request->getUploadedFile('image');
|
|
$error = null;
|
|
$phpFileUploadErrors = [
|
|
UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
|
|
UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
|
|
UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
|
|
UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
|
|
UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
|
|
UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
|
|
UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
|
|
UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
|
|
];
|
|
if (empty($image)) {
|
|
$error = $this->l10n->t('No file uploaded');
|
|
}
|
|
if (!empty($image) && array_key_exists('error', $image) && $image['error'] !== UPLOAD_ERR_OK) {
|
|
$error = $phpFileUploadErrors[$image['error']];
|
|
}
|
|
|
|
if ($error !== null) {
|
|
return new DataResponse(
|
|
[
|
|
'data' => [
|
|
'message' => $error
|
|
],
|
|
'status' => 'failure',
|
|
],
|
|
Http::STATUS_UNPROCESSABLE_ENTITY
|
|
);
|
|
}
|
|
|
|
$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)) {
|
|
return new DataResponse(
|
|
[
|
|
'data' => [
|
|
'message' => $this->l10n->t('Unsupported image type'),
|
|
],
|
|
'status' => 'failure',
|
|
],
|
|
Http::STATUS_UNPROCESSABLE_ENTITY
|
|
);
|
|
}
|
|
|
|
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'));
|
|
|
|
$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);
|
|
imagejpeg($outputImage, $tmpFile, 75);
|
|
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(
|
|
[
|
|
'data' =>
|
|
[
|
|
'name' => $name,
|
|
'url' => $this->imageManager->getImageUrl($key),
|
|
'message' => $this->l10n->t('Saved'),
|
|
'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
|
|
],
|
|
'status' => 'success'
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @param string $setting setting which should be reverted
|
|
* @return DataResponse
|
|
* @throws NotPermittedException
|
|
*/
|
|
public function undo(string $setting): DataResponse {
|
|
$value = $this->themingDefaults->undo($setting);
|
|
// 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' =>
|
|
[
|
|
'value' => $value,
|
|
'message' => $this->l10n->t('Saved'),
|
|
'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
|
|
],
|
|
'status' => 'success'
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @PublicPage
|
|
* @NoCSRFRequired
|
|
*
|
|
* @param string $key
|
|
* @param bool $useSvg
|
|
* @return FileDisplayResponse|NotFoundResponse
|
|
* @throws NotPermittedException
|
|
*/
|
|
public function getImage(string $key, bool $useSvg = true) {
|
|
try {
|
|
$file = $this->imageManager->getImage($key, $useSvg);
|
|
} catch (NotFoundException $e) {
|
|
return new NotFoundResponse();
|
|
}
|
|
|
|
$response = new FileDisplayResponse($file);
|
|
$csp = new Http\ContentSecurityPolicy();
|
|
$csp->allowInlineStyle();
|
|
$response->setContentSecurityPolicy($csp);
|
|
$response->cacheFor(3600);
|
|
$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
|
|
$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
|
|
if (!$useSvg) {
|
|
$response->addHeader('Content-Type', 'image/png');
|
|
} else {
|
|
$response->addHeader('Content-Type', $this->config->getAppValue($this->appName, $key . 'Mime', ''));
|
|
}
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @NoCSRFRequired
|
|
* @PublicPage
|
|
* @NoSameSiteCookieRequired
|
|
*
|
|
* @return FileDisplayResponse|NotFoundResponse
|
|
* @throws NotPermittedException
|
|
* @throws \Exception
|
|
* @throws \OCP\App\AppPathNotFoundException
|
|
*/
|
|
public function getStylesheet() {
|
|
$appPath = $this->appManager->getAppPath('theming');
|
|
|
|
/* SCSSCacher is required here
|
|
* We cannot rely on automatic caching done by \OC_Util::addStyle,
|
|
* since we need to add the cacheBuster value to the url
|
|
*/
|
|
$cssCached = $this->scssCacher->process($appPath, 'css/theming.scss', 'theming');
|
|
if (!$cssCached) {
|
|
return new NotFoundResponse();
|
|
}
|
|
|
|
try {
|
|
$cssFile = $this->scssCacher->getCachedCSS('theming', 'theming.css');
|
|
$response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']);
|
|
$response->cacheFor(86400);
|
|
return $response;
|
|
} catch (NotFoundException $e) {
|
|
return new NotFoundResponse();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @NoCSRFRequired
|
|
* @PublicPage
|
|
* @NoSameSiteCookieRequired
|
|
*
|
|
* @return DataDownloadResponse
|
|
*/
|
|
public function getJavascript() {
|
|
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
|
|
$responseJS = '(function() {
|
|
OCA.Theming = {
|
|
name: ' . json_encode($this->themingDefaults->getName()) . ',
|
|
url: ' . json_encode($this->themingDefaults->getBaseUrl()) . ',
|
|
slogan: ' . json_encode($this->themingDefaults->getSlogan()) . ',
|
|
color: ' . json_encode($this->themingDefaults->getColorPrimary()) . ',
|
|
imprintUrl: ' . json_encode($this->themingDefaults->getImprintUrl()) . ',
|
|
privacyUrl: ' . json_encode($this->themingDefaults->getPrivacyUrl()) . ',
|
|
inverted: ' . json_encode($this->util->invertTextColor($this->themingDefaults->getColorPrimary())) . ',
|
|
cacheBuster: ' . json_encode($cacheBusterValue) . '
|
|
};
|
|
})();';
|
|
$response = new DataDownloadResponse($responseJS, 'javascript', 'text/javascript');
|
|
$response->cacheFor(3600);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @NoCSRFRequired
|
|
* @PublicPage
|
|
*
|
|
* @return Http\JSONResponse
|
|
*/
|
|
public function getManifest($app) {
|
|
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
|
|
$responseJS = [
|
|
'name' => $this->themingDefaults->getName(),
|
|
'start_url' => $this->urlGenerator->getBaseUrl(),
|
|
'icons' =>
|
|
[
|
|
[
|
|
'src' => $this->urlGenerator->linkToRoute('theming.Icon.getTouchIcon',
|
|
['app' => $app]) . '?v=' . $cacheBusterValue,
|
|
'type'=> 'image/png',
|
|
'sizes'=> '128x128'
|
|
],
|
|
[
|
|
'src' => $this->urlGenerator->linkToRoute('theming.Icon.getFavicon',
|
|
['app' => $app]) . '?v=' . $cacheBusterValue,
|
|
'type' => 'image/svg+xml',
|
|
'sizes' => '16x16'
|
|
]
|
|
],
|
|
'display' => 'standalone'
|
|
];
|
|
$response = new Http\JSONResponse($responseJS);
|
|
$response->cacheFor(3600);
|
|
return $response;
|
|
}
|
|
}
|