From 27b699bdbcd080ec9d5400a2391cdb2b725f7ee1 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Tue, 21 Jun 2016 21:21:46 +0200 Subject: [PATCH] Migrate logic to dynamic controller Also adds support for having custom login backgrounds --- apps/theming/appinfo/app.php | 31 +- apps/theming/appinfo/info.xml | 2 +- apps/theming/appinfo/routes.php | 17 +- apps/theming/js/settings-admin.js | 32 +- .../lib/controller/themingcontroller.php | 124 +++++- apps/theming/lib/init.php | 94 ---- apps/theming/lib/template.php | 117 ++--- apps/theming/settings/settings-admin.php | 32 +- apps/theming/templates/settings-admin.php | 24 +- apps/theming/tests/lib/TemplateTest.php | 301 +++++++++++++ .../lib/controller/ThemingControllerTest.php | 405 ++++++++++++++++++ core/js/config.php | 2 +- core/shipped.json | 1 + lib/private/Server.php | 20 + lib/private/legacy/defaults.php | 37 +- lib/private/legacy/template.php | 2 +- 16 files changed, 982 insertions(+), 259 deletions(-) delete mode 100644 apps/theming/lib/init.php create mode 100644 apps/theming/tests/lib/TemplateTest.php create mode 100644 apps/theming/tests/lib/controller/ThemingControllerTest.php diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php index ed7ea3e20f..edf2c7d345 100644 --- a/apps/theming/appinfo/app.php +++ b/apps/theming/appinfo/app.php @@ -1,23 +1,38 @@ + * @copyright Copyright (c) 2016 Bjoern Schiessle + * @copyright Copyright (c) 2016 Lukas Reschke * - * @copyright Copyright (c) 2016, Bjoern Schiessle - * @license AGPL-3.0 + * @license GNU AGPL version 3 or any later version * - * This code is free software: you can redistribute it and/or modify + * 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 opinion) any later version. + * 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 + * 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 + * along with this program. If not, see . * */ \OCP\App::registerAdmin('theming', 'settings/settings-admin'); + +$linkToCSS = \OC::$server->getURLGenerator()->linkToRoute( + 'theming.Theming.getStylesheet', + [ + 'v' => \OC::$server->getConfig()->getAppValue('theming', 'cachebuster', '0'), + ] +); +\OC_Util::addHeader( + 'link', + [ + 'rel' => 'stylesheet', + 'href' => $linkToCSS, + ] +); + diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml index f0f2fb80af..58c839f275 100644 --- a/apps/theming/appinfo/info.xml +++ b/apps/theming/appinfo/info.xml @@ -4,7 +4,7 @@ Theming Adjust the Nextcloud theme AGPL - Bjoern Schiessle + Nextcloud 0.1.0 Theming other diff --git a/apps/theming/appinfo/routes.php b/apps/theming/appinfo/routes.php index 7a2ff1f9db..dbbae372ff 100644 --- a/apps/theming/appinfo/routes.php +++ b/apps/theming/appinfo/routes.php @@ -1,6 +1,7 @@ + * @copyright Copyright (c) 2016 Lukas Reschke * * @license GNU AGPL version 3 or any later version * @@ -19,7 +20,6 @@ * */ - namespace OCA\Theming\AppInfo; (new \OCP\AppFramework\App('theming'))->registerRoutes($this, array('routes' => array( @@ -38,5 +38,20 @@ namespace OCA\Theming\AppInfo; 'url' => '/ajax/updateLogo', 'verb' => 'POST' ], + [ + 'name' => 'Theming#getStylesheet', + 'url' => '/styles.css', + 'verb' => 'GET', + ], + [ + 'name' => 'Theming#getLogo', + 'url' => '/logo', + 'verb' => 'GET', + ], + [ + 'name' => 'Theming#getLoginBackground', + 'url' => '/loginbackground', + 'verb' => 'GET', + ], ))); diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js index dd2f051163..1acd6a97e9 100644 --- a/apps/theming/js/settings-admin.js +++ b/apps/theming/js/settings-admin.js @@ -36,22 +36,37 @@ function preview(setting, value) { var headerClass = document.getElementById('header'); headerClass.style.background = value; headerClass.style.backgroundImage = '../img/logo-icon.svg'; - } - if (setting === 'logoName') { + if (setting === 'logoMime') { + console.log(setting); var logos = document.getElementsByClassName('logo-icon'); - for (var i = 0; i < logos.length; i++) { - logos[i].style.background= "url('" + OC.getRootPath() + "/themes/theming-app/core/img/" + value + "')"; + if(value !== '') { + logos[0].style.background = "url('" + OC.generateUrl('/apps/theming/logo') + "')"; + } else { + logos[0].style.background = "url('" + OC.getRootPath() + '/core/img/logo-icon.svg'+"')"; } } } $(document).ready(function () { - var uploadparms = { + var uploadParamsLogo = { pasteZone: null, done: function (e, response) { - preview('logoName', response.result.data.name); + preview('logoMime', response.result.data.name); + OC.msg.finishedSaving('#theming_settings_msg', response.result); + }, + submit: function(e, response) { + OC.msg.startSaving('#theming_settings_msg'); + }, + fail: function (e, data){ + OC.msg.finishedSaving('#theming_settings_msg', response); + } + }; + var uploadParamsLogin = { + pasteZone: null, + done: function (e, response) { + preview('backgroundMime', response.result.data.name); OC.msg.finishedSaving('#theming_settings_msg', response.result); }, submit: function(e, response) { @@ -62,7 +77,8 @@ $(document).ready(function () { } }; - $('#uploadlogo').fileupload(uploadparms); + $('#uploadlogo').fileupload(uploadParamsLogo); + $('#upload-login-background').fileupload(uploadParamsLogin); $('#theming-name').keyup(function (e) { if (e.keyCode == 13) { @@ -102,7 +118,7 @@ $(document).ready(function () { var colorPicker = document.getElementById('theming-color'); colorPicker.style.backgroundColor = response.data.value; colorPicker.value = response.data.value.slice(1); - } else if (setting !== 'logoName') { + } else if (setting !== 'logoMime' && setting !== 'backgroundMime') { var input = document.getElementById('theming-'+setting); input.value = response.data.value; } diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php index 5ffbbf7176..dd4ff82195 100644 --- a/apps/theming/lib/controller/themingcontroller.php +++ b/apps/theming/lib/controller/themingcontroller.php @@ -1,6 +1,7 @@ + * @copyright Copyright (c) 2016 Lukas Reschke * * @license GNU AGPL version 3 or any later version * @@ -19,14 +20,13 @@ * */ - namespace OCA\Theming\Controller; - use OCA\Theming\Template; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; +use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; @@ -38,24 +38,26 @@ use OCP\IRequest; * @package OCA\Theming\Controller */ class ThemingController extends Controller { - /** @var Template */ private $template; - /** @var IL10N */ private $l; + /** @var IConfig */ + private $config; /** * ThemingController constructor. * * @param string $appName * @param IRequest $request + * @param IConfig $config * @param Template $template * @param IL10N $l */ public function __construct( $appName, IRequest $request, + IConfig $config, Template $template, IL10N $l ) { @@ -63,11 +65,12 @@ class ThemingController extends Controller { $this->template = $template; $this->l = $l; + $this->config = $config; } /** - * @param $setting - * @param $value + * @param string $setting + * @param string $value * @return DataResponse * @internal param string $color */ @@ -85,29 +88,39 @@ class ThemingController extends Controller { } /** - * update Nextcloud logo + * Update the logos and background image * * @return DataResponse */ public function updateLogo() { $newLogo = $this->request->getUploadedFile('uploadlogo'); - if (empty($newLogo)) { + $newBackgroundLogo = $this->request->getUploadedFile('upload-login-background'); + if (empty($newLogo) && empty($newBackgroundLogo)) { return new DataResponse( [ 'data' => [ - 'message' => $this->l->t('No logo uploaded') + 'message' => $this->l->t('No file uploaded') ] ], Http::STATUS_UNPROCESSABLE_ENTITY); } - $this->template->set('logoName', $newLogo['name']); - rename($newLogo['tmp_name'], \OC::$SERVERROOT . '/themes/theming-app/core/img/' . $newLogo['name']); - + $name = ''; + if(!empty($newLogo)) { + rename($newLogo['tmp_name'], $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/themedinstancelogo'); + $this->template->set('logoMime', $newLogo['type']); + $name = $newLogo['name']; + } + if(!empty($newBackgroundLogo)) { + rename($newBackgroundLogo['tmp_name'], $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/themedbackgroundlogo'); + $this->template->set('backgroundMime', $newBackgroundLogo['type']); + $name = $newBackgroundLogo['name']; + } + return new DataResponse( [ 'data' => [ - 'name' => $newLogo['name'], + 'name' => $name, 'message' => $this->l->t('Saved') ], 'status' => 'success' @@ -116,7 +129,7 @@ class ThemingController extends Controller { } /** - * revert setting to default value + * Revert setting to default value * * @param string $setting setting which should be reverted * @return DataResponse @@ -134,4 +147,87 @@ class ThemingController extends Controller { ] ); } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @return Http\StreamResponse + */ + public function getLogo() { + $pathToLogo = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedinstancelogo'; + if(!file_exists($pathToLogo)) { + return new DataResponse(); + } + + \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT'); + \OC_Response::enableCaching(); + $response = new Http\StreamResponse($pathToLogo); + $response->cacheFor(3600); + $response->addHeader('Content-Disposition', 'attachment'); + $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, 'logoMime', '')); + return $response; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @return Http\StreamResponse + */ + public function getLoginBackground() { + $pathToLogo = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedbackgroundlogo'; + if(!file_exists($pathToLogo)) { + return new DataResponse(); + } + + \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT'); + \OC_Response::enableCaching(); + $response = new Http\StreamResponse($pathToLogo); + $response->cacheFor(3600); + $response->addHeader('Content-Disposition', 'attachment'); + $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, 'backgroundMime', '')); + return $response; + } + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return Http\DataDownloadResponse + */ + public function getStylesheet() { + $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0'); + $responseCss = ''; + $color = $this->config->getAppValue($this->appName, 'color'); + if($color !== '') { + $responseCss .= sprintf( + '#body-user #header,#body-settings #header,#body-public #header {background-color: %s}', + $color + ); + } + $logo = $this->config->getAppValue($this->appName, 'logoMime'); + if($logo !== '') { + $responseCss .= sprintf('#header .logo { + background-image: url(\'./logo?v='.$cacheBusterValue.'\'); + } + #header .logo-icon { + background-image: url(\'./logo?v='.$cacheBusterValue.'\'); + background-size: 62px 34px; + }' + ); + } + $backgroundLogo = $this->config->getAppValue($this->appName, 'backgroundMime'); + if($backgroundLogo !== '') { + $responseCss .= '#body-login { + background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\'); + }'; + } + + \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT'); + \OC_Response::enableCaching(); + $response = new Http\DataDownloadResponse($responseCss, 'style.css', 'text/css'); + $response->cacheFor(3600); + return $response; + } } diff --git a/apps/theming/lib/init.php b/apps/theming/lib/init.php deleted file mode 100644 index 287aa589ca..0000000000 --- a/apps/theming/lib/init.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * @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; - - -use OCP\App\ManagerEvent; -use OCP\IConfig; -use OCP\ILogger; - -/** - * Class Init - * - * Initialize the app and make sure that all directories and files exists - * - * @package OCA\Theming - */ -class Init { - - /** @var IConfig */ - private $config; - - /** @var ILogger */ - private $logger; - - /** - * Init constructor. - * - * @param IConfig $config - * @param ILogger $logger - */ - public function __construct(IConfig $config, ILogger $logger) { - $this->config = $config; - $this->logger = $logger; - } - - /** - * prepare folders with the theming app and add the default values to it - */ - public function prepareThemeFolder() { - - if ($this->config->getSystemValue('theme', 'default') === 'theming-app') { - return; - } - - if (!is_writable(\OC::$SERVERROOT . '/themes')) { - $this->logger->warning('Themes folder is read only, can not prepare the theming-app folder', - ['app' => 'theming'] - ); - } - - $this->config->setSystemValue('theme', 'theming-app'); - - if(!file_exists(\OC::$SERVERROOT . '/themes/theming-app')) { - mkdir(\OC::$SERVERROOT . '/themes/theming-app'); - } - - if(!file_exists(\OC::$SERVERROOT . '/themes/theming-app/core')) { - mkdir(\OC::$SERVERROOT . '/themes/theming-app/core'); - } - - if(!file_exists(\OC::$SERVERROOT . '/themes/theming-app/core/img')) { - mkdir(\OC::$SERVERROOT . '/themes/theming-app/core/img'); - } - - if(!file_exists(\OC::$SERVERROOT . '/themes/theming-app/core/css')) { - mkdir(\OC::$SERVERROOT . '/themes/theming-app/core/css'); - } - - if(!file_exists(\OC::$SERVERROOT . '/themes/theming-app/core/img/logo-icon.svg')) { - copy(\OC::$SERVERROOT . '/core/img/logo-icon.svg' ,\OC::$SERVERROOT . '/themes/theming-app/core/img/logo-icon.svg'); - } - } - -} diff --git a/apps/theming/lib/template.php b/apps/theming/lib/template.php index 177ead6988..741fc1daa6 100644 --- a/apps/theming/lib/template.php +++ b/apps/theming/lib/template.php @@ -1,6 +1,7 @@ + * @copyright Copyright (c) 2016 Lukas Reschke * * @license GNU AGPL version 3 or any later version * @@ -19,10 +20,8 @@ * */ - namespace OCA\Theming; - use OCP\IConfig; use OCP\IL10N; use OCP\IURLGenerator; @@ -34,65 +33,55 @@ use OCP\IURLGenerator; * * @package OCA\Theming */ -class Template { - +class Template extends \OC_Defaults { /** @var IConfig */ private $config; - /** @var IL10N */ private $l; - /** @var IURLGenerator */ private $urlGenerator; - - /** @var Init */ - private $init; - /** @var string */ private $name; - /** @var string */ private $url; - /** @var string */ private $slogan; - /** @var string */ private $color; - /** @var string */ - private $logoName; - /** * Template constructor. * * @param IConfig $config * @param IL10N $l * @param IURLGenerator $urlGenerator - * @param Init $init + * @param \OC_Defaults $defaults */ public function __construct(IConfig $config, IL10N $l, IURLGenerator $urlGenerator, - Init $init + \OC_Defaults $defaults ) { + parent::__construct(); $this->config = $config; $this->l = $l; $this->urlGenerator = $urlGenerator; - $this->init = $init; - $this->name = 'Nextcloud'; - $this->url = 'https://nextcloud.com'; - $this->slogan = $this->l->t('a safe home for all your data'); - $this->color = '#0082c9'; - $this->logoName = 'logo-icon.svg'; + $this->name = $defaults->getName(); + $this->url = $defaults->getBaseUrl(); + $this->slogan = $defaults->getSlogan(); + $this->color = $defaults->getMailHeaderColor(); } public function getName() { return $this->config->getAppValue('theming', 'name', $this->name); } + + public function getEntity() { + return $this->config->getAppValue('theming', 'name', $this->name); + } - public function getUrl() { + public function getBaseUrl() { return $this->config->getAppValue('theming', 'url', $this->url); } @@ -100,73 +89,57 @@ class Template { return $this->config->getAppValue('theming', 'slogan', $this->slogan); } - public function getColor() { + public function getMailHeaderColor() { return $this->config->getAppValue('theming', 'color', $this->color); } - public function getLogoName() { - return $this->config->getAppValue('theming', 'logoName', $this->logoName); + /** + * Increases the cache buster key + */ + private function increaseCacheBuster() { + $cacheBusterKey = $this->config->getAppValue('theming', 'cachebuster', '0'); + $this->config->setAppValue('theming', 'cachebuster', (int)$cacheBusterKey+1); } /** - * update setting in the database + * Update setting in the database * - * @param $setting - * @param $value + * @param string $setting + * @param string $value */ public function set($setting, $value) { - $this->init->prepareThemeFolder(); $this->config->setAppValue('theming', $setting, $value); - $this->writeCSSFile(); + $this->increaseCacheBuster(); } /** - * revert settings to the default value + * Revert settings to the default value * * @param string $setting setting which should be reverted * @return string default value */ public function undo($setting) { - $returnValue = ''; - if ($this->$setting) { - $this->config->setAppValue('theming', $setting, $this->$setting); - $this->writeCSSFile(); - $returnValue = $this->$setting; + $this->config->deleteAppValue('theming', $setting); + $this->increaseCacheBuster(); + + switch ($setting) { + case 'name': + $returnValue = $this->getEntity(); + break; + case 'url': + $returnValue = $this->getBaseUrl(); + break; + case 'slogan': + $returnValue = $this->getSlogan(); + break; + case 'color': + $returnValue = $this->getMailHeaderColor(); + break; + default: + $returnValue = ''; + break; } return $returnValue; } - - /** - * write setting to a css file - */ - private function writeCSSFile() { - $logo = $this->getLogoName(); - $color = $this->getColor(); - - $css = " - #body-user #header, - #body-settings #header, - #body-public #header { - background-color: $color; - } - - - /* use logos from theme */ - #header .logo { - background-image: url('../img/$logo'); - width: 250px; - height: 121px; - } - #header .logo-icon { - background-image: url('../img/$logo'); - width: 62px; - height: 34px; - }"; - - $root = \OC::$SERVERROOT . '/themes/theming-app/core'; - - file_put_contents($root . '/css/styles.css', $css); - } - } diff --git a/apps/theming/settings/settings-admin.php b/apps/theming/settings/settings-admin.php index c79eb1475f..59da90a47f 100644 --- a/apps/theming/settings/settings-admin.php +++ b/apps/theming/settings/settings-admin.php @@ -1,22 +1,22 @@ + * @copyright Copyright (c) 2016 Bjoern Schiessle + * @copyright Copyright (c) 2016 Lukas Reschke * - * @copyright Copyright (c) 2016, Bjoern Schiessle - * @license AGPL-3.0 + * @license GNU AGPL version 3 or any later version * - * This code is free software: you can redistribute it and/or modify + * 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 opinion) any later version. + * 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 + * 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 + * along with this program. If not, see . * */ @@ -25,35 +25,31 @@ $config = \OC::$server->getConfig(); $l = \OC::$server->getL10N('theming'); $urlGenerator = \OC::$server->getURLGenerator(); -$init = new \OCA\Theming\Init($config, \OC::$server->getLogger()); $theming = new \OCA\Theming\Template( $config, $l, \OC::$server->getURLGenerator(), - $init + new OC_Defaults() ); $themable = true; $errorMessage = ''; -$theme = $config->getSystemValue('theme', 'default'); +$theme = $config->getSystemValue('theme', ''); -if ($theme !== 'theming-app' && $theme !== 'default') { +if ($theme !== '') { $themable = false; $errorMessage = $l->t('You already use a custom theme'); -} elseif (!is_writable(\OC::$SERVERROOT . '/themes')) { - $themable = false; - $errorMessage = $l->t('Themes folder is read-only, please update the permissions to read-write'); } $template = new OCP\Template('theming', 'settings-admin'); $template->assign('themable', $themable); $template->assign('errorMessage', $errorMessage); -$template->assign('name', $theming->getName()); -$template->assign('url', $theming->getUrl()); +$template->assign('name', $theming->getEntity()); +$template->assign('url', $theming->getBaseUrl()); $template->assign('slogan', $theming->getSlogan()); -$template->assign('color', $theming->getColor()); +$template->assign('color', $theming->getMailHeaderColor()); $path = $urlGenerator->linkToRoute('theming.Theming.updateLogo'); $template->assign('uploadLogoRoute', $path); diff --git a/apps/theming/templates/settings-admin.php b/apps/theming/templates/settings-admin.php index 4e2277b053..3a55deca0c 100644 --- a/apps/theming/templates/settings-admin.php +++ b/apps/theming/templates/settings-admin.php @@ -14,27 +14,35 @@ style('theming', 'settings-admin');

- Name: + t('Name:')) ?>

- URL: + t('URL:')) ?>

- Slogan: + t('Slogan:')) ?>

- Color: + t('Color:')) ?> +

+

+

+ t('Logo:')) ?> + + + +

- Logo: - - - + t('Login img.:')) ?> + + +

diff --git a/apps/theming/tests/lib/TemplateTest.php b/apps/theming/tests/lib/TemplateTest.php new file mode 100644 index 0000000000..b9623e437b --- /dev/null +++ b/apps/theming/tests/lib/TemplateTest.php @@ -0,0 +1,301 @@ + + * + * @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\Tests; + +use OCA\Theming\Template; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IURLGenerator; +use Test\TestCase; + +class TemplateTest extends TestCase { + /** @var IConfig */ + private $config; + /** @var IL10N */ + private $l10n; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var \OC_Defaults */ + private $defaults; + /** @var Template */ + private $template; + + public function setUp() { + $this->config = $this->getMock('\\OCP\\IConfig'); + $this->l10n = $this->getMock('\\OCP\\IL10N'); + $this->urlGenerator = $this->getMock('\\OCP\\IURLGenerator'); + $this->defaults = $this->getMockBuilder('\\OC_Defaults') + ->disableOriginalConstructor() + ->getMock(); + $this->defaults + ->expects($this->at(0)) + ->method('getName') + ->willReturn('Nextcloud'); + $this->defaults + ->expects($this->at(1)) + ->method('getBaseUrl') + ->willReturn('https://nextcloud.com/'); + $this->defaults + ->expects($this->at(2)) + ->method('getSlogan') + ->willReturn('Safe Data'); + $this->defaults + ->expects($this->at(3)) + ->method('getMailHeaderColor') + ->willReturn('#000'); + $this->template = new Template( + $this->config, + $this->l10n, + $this->urlGenerator, + $this->defaults + ); + + return parent::setUp(); + } + + public function testGetNameWithDefault() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'name', 'Nextcloud') + ->willReturn('Nextcloud'); + + $this->assertEquals('Nextcloud', $this->template->getName()); + } + + public function testGetNameWithCustom() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'name', 'Nextcloud') + ->willReturn('MyCustomCloud'); + + $this->assertEquals('MyCustomCloud', $this->template->getName()); + } + + public function testGetEntityWithDefault() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'name', 'Nextcloud') + ->willReturn('Nextcloud'); + + $this->assertEquals('Nextcloud', $this->template->getEntity()); + } + + public function testGetEntityWithCustom() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'name', 'Nextcloud') + ->willReturn('MyCustomCloud'); + + $this->assertEquals('MyCustomCloud', $this->template->getEntity()); + } + + public function testGetBaseUrlWithDefault() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'url', 'https://nextcloud.com/') + ->willReturn('https://nextcloud.com/'); + + $this->assertEquals('https://nextcloud.com/', $this->template->getBaseUrl()); + } + + public function testGetBaseUrlWithCustom() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'url', 'https://nextcloud.com/') + ->willReturn('https://example.com/'); + + $this->assertEquals('https://example.com/', $this->template->getBaseUrl()); + } + + public function testGetSloganWithDefault() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'slogan', 'Safe Data') + ->willReturn('Safe Data'); + + $this->assertEquals('Safe Data', $this->template->getSlogan()); + } + + public function testGetSloganWithCustom() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'slogan', 'Safe Data') + ->willReturn('My custom Slogan'); + + $this->assertEquals('My custom Slogan', $this->template->getSlogan()); + } + + public function testGetMailHeaderColorWithDefault() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'color', '#000') + ->willReturn('#000'); + + $this->assertEquals('#000', $this->template->getMailHeaderColor()); + } + + public function testGetMailHeaderColorWithCustom() { + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'color', '#000') + ->willReturn('#fff'); + + $this->assertEquals('#fff', $this->template->getMailHeaderColor()); + } + + public function testSet() { + $this->config + ->expects($this->at(0)) + ->method('setAppValue') + ->with('theming', 'MySetting', 'MyValue'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + + $this->template->set('MySetting', 'MyValue'); + } + + public function testUndoName() { + $this->config + ->expects($this->at(0)) + ->method('deleteAppValue') + ->with('theming', 'name'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'name', 'Nextcloud') + ->willReturn('Nextcloud'); + + $this->assertSame('Nextcloud', $this->template->undo('name')); + } + + public function testUndoBaseUrl() { + $this->config + ->expects($this->at(0)) + ->method('deleteAppValue') + ->with('theming', 'url'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'url', 'https://nextcloud.com/') + ->willReturn('https://nextcloud.com/'); + + $this->assertSame('https://nextcloud.com/', $this->template->undo('url')); + } + + public function testUndoSlogan() { + $this->config + ->expects($this->at(0)) + ->method('deleteAppValue') + ->with('theming', 'slogan'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'slogan', 'Safe Data') + ->willReturn('Safe Data'); + + $this->assertSame('Safe Data', $this->template->undo('slogan')); + } + + public function testUndoColor() { + $this->config + ->expects($this->at(0)) + ->method('deleteAppValue') + ->with('theming', 'color'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'color', '#000') + ->willReturn('#000'); + + $this->assertSame('#000', $this->template->undo('color')); + } + + public function testUndoDefaultAction() { + $this->config + ->expects($this->at(0)) + ->method('deleteAppValue') + ->with('theming', 'defaultitem'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('15'); + $this->config + ->expects($this->at(2)) + ->method('setAppValue') + ->with('theming', 'cachebuster', 16); + + $this->assertSame('', $this->template->undo('defaultitem')); + } +} diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php new file mode 100644 index 0000000000..82aa7d1381 --- /dev/null +++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php @@ -0,0 +1,405 @@ + + * + * @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\Tests\Controller; + +use OCA\Theming\Controller\ThemingController; +use OCA\Theming\Template; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use Test\TestCase; + +class ThemingControllerTest extends TestCase { + /** @var IRequest */ + private $request; + /** @var IConfig */ + private $config; + /** @var Template */ + private $template; + /** @var IL10N */ + private $l10n; + /** @var ThemingController */ + private $themingController; + + public function setUp() { + $this->request = $this->getMock('\\OCP\\IRequest'); + $this->config = $this->getMock('\\OCP\\IConfig'); + $this->template = $this->getMockBuilder('\\OCA\\Theming\\Template') + ->disableOriginalConstructor()->getMock(); + $this->l10n = $this->getMock('\\OCP\\IL10N'); + $this->themingController = new ThemingController( + 'theming', + $this->request, + $this->config, + $this->template, + $this->l10n + ); + + return parent::setUp(); + } + + public function testUpdateStylesheet() { + $this->template + ->expects($this->once()) + ->method('set') + ->with('MySetting', 'MyValue'); + $this->l10n + ->expects($this->once()) + ->method('t') + ->with('Saved') + ->willReturn('Saved'); + + $expected = new DataResponse( + [ + 'data' => + [ + 'message' => 'Saved', + ], + 'status' => 'success' + ] + ); + $this->assertEquals($expected, $this->themingController->updateStylesheet('MySetting', 'MyValue')); + } + + public function testUpdateLogoNoData() { + $this->request + ->expects($this->at(0)) + ->method('getUploadedFile') + ->with('uploadlogo') + ->willReturn(null); + $this->request + ->expects($this->at(1)) + ->method('getUploadedFile') + ->with('upload-login-background') + ->willReturn(null); + $this->l10n + ->expects($this->once()) + ->method('t') + ->with('No file uploaded') + ->willReturn('No file uploaded'); + + $expected = new DataResponse( + [ + 'data' => + [ + 'message' => 'No file uploaded', + ], + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + + $this->assertEquals($expected, $this->themingController->updateLogo()); + } + + public function testUpdateLogoNormalLogoUpload() { + $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg'; + $destination = \OC::$server->getTempManager()->getTemporaryFolder(); + + touch($tmpLogo); + $this->request + ->expects($this->at(0)) + ->method('getUploadedFile') + ->with('uploadlogo') + ->willReturn([ + 'tmp_name' => $tmpLogo, + 'type' => 'text/svg', + 'name' => 'logo.svg', + ]); + $this->request + ->expects($this->at(1)) + ->method('getUploadedFile') + ->with('upload-login-background') + ->willReturn(null); + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('datadirectory', \OC::$SERVERROOT . '/data') + ->willReturn($destination); + $this->l10n + ->expects($this->once()) + ->method('t') + ->with('Saved') + ->willReturn('Saved'); + + $expected = new DataResponse( + [ + 'data' => + [ + 'name' => 'logo.svg', + 'message' => 'Saved', + ], + 'status' => 'success' + ] + ); + + $this->assertEquals($expected, $this->themingController->updateLogo()); + } + + public function testUpdateLogoLoginScreenUpload() { + $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg'; + $destination = \OC::$server->getTempManager()->getTemporaryFolder(); + + touch($tmpLogo); + $this->request + ->expects($this->at(0)) + ->method('getUploadedFile') + ->with('uploadlogo') + ->willReturn(null); + $this->request + ->expects($this->at(1)) + ->method('getUploadedFile') + ->with('upload-login-background') + ->willReturn([ + 'tmp_name' => $tmpLogo, + 'type' => 'text/svg', + 'name' => 'logo.svg', + ]); + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('datadirectory', \OC::$SERVERROOT . '/data') + ->willReturn($destination); + $this->l10n + ->expects($this->once()) + ->method('t') + ->with('Saved') + ->willReturn('Saved'); + + $expected = new DataResponse( + [ + 'data' => + [ + 'name' => 'logo.svg', + 'message' => 'Saved', + ], + 'status' => 'success' + ] + ); + $this->assertEquals($expected, $this->themingController->updateLogo()); + } + + public function testUndo() { + $this->l10n + ->expects($this->once()) + ->method('t') + ->with('Saved') + ->willReturn('Saved'); + $this->template + ->expects($this->once()) + ->method('undo') + ->with('MySetting') + ->willReturn('MyValue'); + + $expected = new DataResponse( + [ + 'data' => + [ + 'value' => 'MyValue', + 'message' => 'Saved', + ], + 'status' => 'success' + ] + ); + $this->assertEquals($expected, $this->themingController->undo('MySetting')); + } + + public function testGetLogoNotExistent() { + $expected = new DataResponse(); + $this->assertEquals($expected, $this->themingController->getLogo()); + } + + public function testGetLogo() { + $dataFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $tmpLogo = $dataFolder . '/themedinstancelogo'; + touch($tmpLogo); + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('datadirectory', \OC::$SERVERROOT . '/data/') + ->willReturn($dataFolder); + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn('text/svg'); + + @$expected = new Http\StreamResponse($tmpLogo); + $expected->cacheFor(3600); + $expected->addHeader('Content-Disposition', 'attachment'); + $expected->addHeader('Content-Type', 'text/svg'); + @$this->assertEquals($expected, $this->themingController->getLogo()); + } + + + public function testGetLoginBackgroundNotExistent() { + $expected = new DataResponse(); + $this->assertEquals($expected, $this->themingController->getLoginBackground()); + } + + public function testGetLoginBackground() { + $dataFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $tmpLogo = $dataFolder . '/themedbackgroundlogo'; + touch($tmpLogo); + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('datadirectory', \OC::$SERVERROOT . '/data/') + ->willReturn($dataFolder); + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn('image/png'); + + @$expected = new Http\StreamResponse($tmpLogo); + $expected->cacheFor(3600); + $expected->addHeader('Content-Disposition', 'attachment'); + $expected->addHeader('Content-Type', 'image/png'); + @$this->assertEquals($expected, $this->themingController->getLoginBackground()); + } + + public function testGetStylesheetWithOnlyColor() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn('#fff'); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn(''); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn(''); + + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}', 'style.css', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } + + public function testGetStylesheetWithOnlyHeaderLogo() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn(''); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn('image/png'); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn(''); + + $expected = new Http\DataDownloadResponse('#header .logo { + background-image: url(\'./logo?v=0\'); + } + #header .logo-icon { + background-image: url(\'./logo?v=0\'); + background-size: 62px 34px; + }', 'style.css', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } + + public function testGetStylesheetWithOnlyBackgroundLogin() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn(''); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn(''); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn('text/svg'); + + $expected = new Http\DataDownloadResponse('#body-login { + background-image: url(\'./loginbackground?v=0\'); + }', 'style.css', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } + + public function testGetStylesheetWithAllCombined() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn('#abc'); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn('text/svg'); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn('image/png'); + + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #abc}#header .logo { + background-image: url(\'./logo?v=0\'); + } + #header .logo-icon { + background-image: url(\'./logo?v=0\'); + background-size: 62px 34px; + }#body-login { + background-image: url(\'./loginbackground?v=0\'); + }', 'style.css', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } + +} diff --git a/core/js/config.php b/core/js/config.php index dc84d1cf2b..197047ed8b 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -43,7 +43,7 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); $l = \OC::$server->getL10N('core'); // Enable OC_Defaults support -$defaults = new OC_Defaults(); +$defaults = \OC::$server->getThemingDefaults(); // Get the config $apps_paths = array(); diff --git a/core/shipped.json b/core/shipped.json index a3abe22d8d..ed9cf52fa7 100644 --- a/core/shipped.json +++ b/core/shipped.json @@ -32,6 +32,7 @@ "systemtags", "systemtags_management", "templateeditor", + "theming", "updatenotification", "user_external", "user_ldap", diff --git a/lib/private/Server.php b/lib/private/Server.php index 8345a0b66e..df7bb61920 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -78,6 +78,7 @@ use OC\Security\SecureRandom; use OC\Security\TrustedDomainHelper; use OC\Session\CryptoWrapper; use OC\Tagging\TagMapper; +use OCA\Theming\Template; use OCP\IL10N; use OCP\IServerContainer; use OCP\Security\IContentSecurityPolicyManager; @@ -618,6 +619,17 @@ class Server extends ServerContainer implements IServerContainer { $factory = new $factoryClass($this); return $factory->getManager(); }); + $this->registerService('ThemingDefaults', function(Server $c) { + if($this->getAppManager()->isInstalled('theming')) { + return new Template( + $this->getConfig(), + $this->getL10N('theming'), + $this->getURLGenerator(), + new \OC_Defaults() + ); + } + return new \OC_Defaults(); + }); $this->registerService('EventDispatcher', function () { return new EventDispatcher(); }); @@ -1288,6 +1300,14 @@ class Server extends ServerContainer implements IServerContainer { return $this->query('CommentsManager'); } + /** + * @internal Not public by intention. + * @return \OC_Defaults + */ + public function getThemingDefaults() { + return $this->query('ThemingDefaults'); + } + /** * @return \OC\IntegrityCheck\Checker */ diff --git a/lib/private/legacy/defaults.php b/lib/private/legacy/defaults.php index f0cc4c9185..2a97cfe89e 100644 --- a/lib/private/legacy/defaults.php +++ b/lib/private/legacy/defaults.php @@ -31,13 +31,8 @@ class OC_Defaults { private $theme; - - /** @var \OCP\IL10N */ private $l; - /** @var \OCA\Theming\Template */ - private $template; - private $defaultEntity; private $defaultName; private $defaultTitle; @@ -54,45 +49,21 @@ class OC_Defaults { function __construct() { $this->l = \OC::$server->getL10N('lib'); - $config = \OC::$server->getConfig(); - - - try { - $themingAppEnabled = $config->getSystemValue('installed', false) && \OCP\App::isEnabled('theming'); - } catch (\Exception $e) { - $themingAppEnabled = false; - } - - $config = \OC::$server->getConfig(); - - if ($themingAppEnabled) { - $this->template = new \OCA\Theming\Template( - $config, - $this->l, - \OC::$server->getURLGenerator(), - new \OCA\Theming\Init($config, \OC::$server->getLogger()) - ); - $this->defaultName = $this->template->getName(); /* short name, used when referring to the software */ - $this->defaultBaseUrl = $this->template->getUrl(); - $this->defaultSlogan = $this->template->getSlogan(); - $this->defaultMailHeaderColor = $this->template->getColor(); /* header color of mail notifications */ - } else { - $this->defaultName = 'Nextcloud'; - $this->defaultBaseUrl = 'https://nextcloud.com'; - $this->defaultSlogan = $this->l->t('a safe home for all your data'); - $this->defaultMailHeaderColor = '#0082c9'; /* header color of mail notifications */ - } $version = \OCP\Util::getVersion(); $this->defaultEntity = 'Nextcloud'; /* e.g. company name, used for footers and copyright notices */ + $this->defaultName = 'Nextcloud'; /* short name, used when referring to the software */ $this->defaultTitle = 'Nextcloud'; /* can be a longer name, for titles */ + $this->defaultBaseUrl = 'https://nextcloud.com'; $this->defaultSyncClientUrl = 'https://nextcloud.com/install'; $this->defaultiOSClientUrl = 'https://itunes.apple.com/us/app/owncloud/id543672169?mt=8'; $this->defaultiTunesAppId = '543672169'; $this->defaultAndroidClientUrl = 'https://play.google.com/store/apps/details?id=com.owncloud.android'; $this->defaultDocBaseUrl = 'https://doc.owncloud.org'; $this->defaultDocVersion = $version[0] . '.' . $version[1]; // used to generate doc links + $this->defaultSlogan = $this->l->t('a safe home for all your data'); $this->defaultLogoClaim = ''; + $this->defaultMailHeaderColor = '#0082c9'; /* header color of mail notifications */ $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php'; if (file_exists($themePath)) { diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php index 5023e3a60c..e295650809 100644 --- a/lib/private/legacy/template.php +++ b/lib/private/legacy/template.php @@ -80,7 +80,7 @@ class OC_Template extends \OC\Template\Base { $parts = explode('/', $app); // fix translation when app is something like core/lostpassword $l10n = \OC::$server->getL10N($parts[0]); - $themeDefaults = new OC_Defaults(); + $themeDefaults = \OC::$server->getThemingDefaults(); list($path, $template) = $this->findTemplate($theme, $app, $name);