Merge pull request #840 from nextcloud/theming-icon-endpoint

Add dynamic icon creation
This commit is contained in:
Morris Jobke 2016-11-18 15:35:34 +01:00 committed by GitHub
commit faee255ff4
22 changed files with 1350 additions and 6 deletions

View File

@ -60,5 +60,23 @@ return ['routes' => [
'url' => '/js/theming',
'verb' => 'GET',
],
[
'name' => 'Icon#getFavicon',
'url' => '/favicon/{app}',
'verb' => 'GET',
'defaults' => array('app' => 'core'),
],
[
'name' => 'Icon#getTouchIcon',
'url' => '/icon/{app}',
'verb' => 'GET',
'defaults' => array('app' => 'core'),
],
[
'name' => 'Icon#getThemedIcon',
'url' => '/img/{app}/{image}',
'verb' => 'GET',
'requirements' => array('image' => '.+')
],
]];

View File

@ -0,0 +1,174 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Haertl <jus@bitgrid.net>
*
* @author Julius Haertl <jus@bitgrid.net>
*
* @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 OCA\Theming\IconBuilder;
use OCA\Theming\ImageManager;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\NotFoundException;
use OCP\IRequest;
use OCA\Theming\Util;
use OCP\IConfig;
class IconController extends Controller {
/** @var ThemingDefaults */
private $themingDefaults;
/** @var Util */
private $util;
/** @var ITimeFactory */
private $timeFactory;
/** @var IConfig */
private $config;
/** @var IconBuilder */
private $iconBuilder;
/** @var ImageManager */
private $imageManager;
/**
* IconController constructor.
*
* @param string $appName
* @param IRequest $request
* @param ThemingDefaults $themingDefaults
* @param Util $util
* @param ITimeFactory $timeFactory
* @param IConfig $config
* @param IconBuilder $iconBuilder
* @param ImageManager $imageManager
*/
public function __construct(
$appName,
IRequest $request,
ThemingDefaults $themingDefaults,
Util $util,
ITimeFactory $timeFactory,
IConfig $config,
IconBuilder $iconBuilder,
ImageManager $imageManager
) {
parent::__construct($appName, $request);
$this->themingDefaults = $themingDefaults;
$this->util = $util;
$this->timeFactory = $timeFactory;
$this->config = $config;
$this->iconBuilder = $iconBuilder;
$this->imageManager = $imageManager;
}
/**
* @PublicPage
* @NoCSRFRequired
*
* @param $app string app name
* @param $image string image file name (svg required)
* @return FileDisplayResponse|NotFoundResponse
*/
public function getThemedIcon($app, $image) {
try {
$iconFile = $this->imageManager->getCachedImage("icon-" . $app . '-' . str_replace("/","_",$image));
} catch (NotFoundException $exception) {
$icon = $this->iconBuilder->colorSvg($app, $image);
$iconFile = $this->imageManager->setCachedImage("icon-" . $app . '-' . str_replace("/","_",$image), $icon);
}
if ($iconFile !== false) {
$response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']);
$response->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$response->addHeader('Pragma', 'cache');
return $response;
} else {
return new NotFoundResponse();
}
}
/**
* Return a 32x32 favicon as png
*
* @PublicPage
* @NoCSRFRequired
*
* @param $app string app name
* @return FileDisplayResponse|NotFoundResponse
*/
public function getFavicon($app = "core") {
if ($this->themingDefaults->shouldReplaceIcons()) {
try {
$iconFile = $this->imageManager->getCachedImage('favIcon-' . $app);
} catch (NotFoundException $exception) {
$icon = $this->iconBuilder->getFavicon($app);
$iconFile = $this->imageManager->setCachedImage('favIcon-' . $app, $icon);
}
if ($iconFile !== false) {
$response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']);
$response->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$response->addHeader('Pragma', 'cache');
return $response;
}
}
return new NotFoundResponse();
}
/**
* Return a 512x512 icon for touch devices
*
* @PublicPage
* @NoCSRFRequired
*
* @param $app string app name
* @return FileDisplayResponse|NotFoundResponse
*/
public function getTouchIcon($app = "core") {
if ($this->themingDefaults->shouldReplaceIcons()) {
try {
$iconFile = $this->imageManager->getCachedImage('touchIcon-' . $app);
} catch (NotFoundException $exception) {
$icon = $this->iconBuilder->getTouchIcon($app);
$iconFile = $this->imageManager->setCachedImage('touchIcon-' . $app, $icon);
}
if ($iconFile !== false) {
$response = new FileDisplayResponse($iconFile, Http::STATUS_OK, ['Content-Type' => 'image/png']);
$response->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$response->addHeader('Pragma', 'cache');
return $response;
}
}
return new NotFoundResponse();
}
}

View File

@ -410,6 +410,15 @@ class ThemingController extends Controller {
$responseCss .= '.nc-theming-contrast {color: #ffffff}' . "\n";
}
if($logo !== '' or $color !== '') {
$responseCss .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v='.$cacheBusterValue.'\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v='.$cacheBusterValue.'\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v='.$cacheBusterValue.'\')!important;' . "}\n";
}
$response = new DataDownloadResponse($responseCss, 'style', 'text/css');
$response->addHeader('Expires', date(\DateTime::RFC2822, $this->timeFactory->getTime()));
$response->addHeader('Pragma', 'cache');
@ -423,6 +432,7 @@ class ThemingController extends Controller {
* @return DataDownloadResponse
*/
public function getJavascript() {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
$responseJS = '(function() {
OCA.Theming = {
name: ' . json_encode($this->template->getName()) . ',
@ -430,6 +440,7 @@ class ThemingController extends Controller {
slogan: ' . json_encode($this->template->getSlogan()) . ',
color: ' . json_encode($this->template->getMailHeaderColor()) . ',
inverted: ' . json_encode($this->util->invertTextColor($this->template->getMailHeaderColor())) . ',
cacheBuster: ' . json_encode($cacheBusterValue). '
};
})();';
$response = new Http\DataDisplayResponse($responseJS);

View File

@ -0,0 +1,182 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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;
use Imagick;
use ImagickPixel;
use OCP\App\AppPathNotFoundException;
class IconBuilder {
/** @var ThemingDefaults */
private $themingDefaults;
/** @var Util */
private $util;
/**
* IconBuilder constructor.
*
* @param ThemingDefaults $themingDefaults
* @param Util $util
*/
public function __construct(
ThemingDefaults $themingDefaults,
Util $util
) {
$this->themingDefaults = $themingDefaults;
$this->util = $util;
}
/**
* @param $app string app name
* @return string|false image blob
*/
public function getFavicon($app) {
$icon = $this->renderAppIcon($app);
if($icon === false) {
return false;
}
$icon->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
$icon->setImageFormat("png24");
$data = $icon->getImageBlob();
$icon->destroy();
return $data;
}
/**
* @param $app string app name
* @return string|false image blob
*/
public function getTouchIcon($app) {
$icon = $this->renderAppIcon($app);
if($icon === false) {
return false;
}
$icon->setImageFormat("png24");
$data = $icon->getImageBlob();
$icon->destroy();
return $data;
}
/**
* Render app icon on themed background color
* fallback to logo
*
* @param $app string app name
* @return Imagick|false
*/
public function renderAppIcon($app) {
try {
$appIcon = $this->util->getAppIcon($app);
$appIconContent = file_get_contents($appIcon);
} catch (AppPathNotFoundException $e) {
return false;
}
if($appIconContent === false) {
return false;
}
$color = $this->themingDefaults->getMailHeaderColor();
$mime = mime_content_type($appIcon);
// generate background image with rounded corners
$background = '<?xml version="1.0" encoding="UTF-8"?>' .
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink">' .
'<rect x="0" y="0" rx="75" ry="75" width="512" height="512" style="fill:' . $color . ';" />' .
'</svg>';
// resize svg magic as this seems broken in Imagemagick
if($mime === "image/svg+xml" || substr($appIconContent, 0, 4) === "<svg") {
if(substr($appIconContent, 0, 5) !== "<?xml") {
$svg = "<?xml version=\"1.0\"?>".$appIconContent;
} else {
$svg = $appIconContent;
}
$tmp = new Imagick();
$tmp->readImageBlob($svg);
$x = $tmp->getImageWidth();
$y = $tmp->getImageHeight();
$res = $tmp->getImageResolution();
$tmp->destroy();
if($x>$y) {
$max = $x;
} else {
$max = $y;
}
// convert svg to resized image
$appIconFile = new Imagick();
$resX = (int)(512 * $res['x'] / $max * 2.53);
$resY = (int)(512 * $res['y'] / $max * 2.53);
$appIconFile->setResolution($resX, $resY);
$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
$appIconFile->readImageBlob($svg);
$appIconFile->scaleImage(512, 512, true);
} else {
$appIconFile = new Imagick();
$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
$appIconFile->readImageBlob(file_get_contents($appIcon));
$appIconFile->scaleImage(512, 512, true);
}
// offset for icon positioning
$border_w = (int)($appIconFile->getImageWidth() * 0.05);
$border_h = (int)($appIconFile->getImageHeight() * 0.05);
$innerWidth = (int)($appIconFile->getImageWidth() - $border_w * 2);
$innerHeight = (int)($appIconFile->getImageHeight() - $border_h * 2);
$appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
// center icon
$offset_w = 512 / 2 - $innerWidth / 2;
$offset_h = 512 / 2 - $innerHeight / 2;
$appIconFile->setImageFormat("png24");
$finalIconFile = new Imagick();
$finalIconFile->readImageBlob($background);
$finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
$finalIconFile->setImageArtifact('compose:args', "1,0,-0.5,0.5");
$finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
$finalIconFile->resizeImage(512, 512, Imagick::FILTER_LANCZOS, 1);
$appIconFile->destroy();
return $finalIconFile;
}
public function colorSvg($app, $image) {
try {
$imageFile = $this->util->getAppImage($app, $image);
} catch (AppPathNotFoundException $e) {
return false;
}
$svg = file_get_contents($imageFile);
if ($svg !== false) {
$color = $this->util->elementColor($this->themingDefaults->getMailHeaderColor());
$svg = $this->util->colorizeSvg($svg, $color);
return $svg;
} else {
return false;
}
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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;
use OCP\IConfig;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
class ImageManager {
/** @var IConfig */
private $config;
/** @var IAppData */
private $appData;
/**
* ImageManager constructor.
*
* @param IConfig $config
* @param IAppData $appData
*/
public function __construct(IConfig $config,
IAppData $appData
) {
$this->config = $config;
$this->appData = $appData;
}
/**
* Get folder for current theming files
*
* @return \OCP\Files\SimpleFS\ISimpleFolder
* @throws NotPermittedException
* @throws \RuntimeException
*/
public function getCacheFolder() {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
try {
$folder = $this->appData->getFolder($cacheBusterValue);
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder($cacheBusterValue);
$this->cleanup();
}
return $folder;
}
/**
* Get a file from AppData
*
* @param string $filename
* @throws NotFoundException
* @return \OCP\Files\SimpleFS\ISimpleFile
*/
public function getCachedImage($filename) {
$currentFolder = $this->getCacheFolder();
return $currentFolder->getFile($filename);
}
/**
* Store a file for theming in AppData
*
* @param string $filename
* @param string $data
* @return \OCP\Files\SimpleFS\ISimpleFile
*/
public function setCachedImage($filename, $data) {
$currentFolder = $this->getCacheFolder();
if ($currentFolder->fileExists($filename)) {
$file = $currentFolder->getFile($filename);
} else {
$file = $currentFolder->newFile($filename);
}
$file->putContent($data);
return $file;
}
/**
* remove cached files that are not required any longer
*/
public function cleanup() {
$currentFolder = $this->getCacheFolder();
$folders = $this->appData->getDirectoryListing();
foreach ($folders as $folder) {
if ($folder->getName() !== $currentFolder->getName()) {
$folder->delete();
}
}
}
}

View File

@ -23,6 +23,7 @@
namespace OCA\Theming;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
@ -38,6 +39,8 @@ class ThemingDefaults extends \OC_Defaults {
private $urlGenerator;
/** @var IRootFolder */
private $rootFolder;
/** @var ICacheFactory */
private $cacheFactory;
/** @var string */
private $name;
/** @var string */
@ -55,18 +58,21 @@ class ThemingDefaults extends \OC_Defaults {
* @param IURLGenerator $urlGenerator
* @param \OC_Defaults $defaults
* @param IRootFolder $rootFolder
* @param ICacheFactory $cacheFactory
*/
public function __construct(IConfig $config,
IL10N $l,
IURLGenerator $urlGenerator,
\OC_Defaults $defaults,
IRootFolder $rootFolder
IRootFolder $rootFolder,
ICacheFactory $cacheFactory
) {
parent::__construct();
$this->config = $config;
$this->l = $l;
$this->urlGenerator = $urlGenerator;
$this->rootFolder = $rootFolder;
$this->cacheFactory = $cacheFactory;
$this->name = $defaults->getName();
$this->url = $defaults->getBaseUrl();
@ -144,6 +150,29 @@ class ThemingDefaults extends \OC_Defaults {
}
}
/**
* Check if Imagemagick is enabled and if SVG is supported
* otherwise we can't render custom icons
*
* @return bool
*/
public function shouldReplaceIcons() {
$cache = $this->cacheFactory->create('theming');
if($value = $cache->get('shouldReplaceIcons')) {
return (bool)$value;
}
$value = false;
if(extension_loaded('imagick')) {
$checkImagick = new \Imagick();
if (count($checkImagick->queryFormats('SVG')) >= 1) {
$value = true;
}
$checkImagick->clear();
}
$cache->set('shouldReplaceIcons', $value);
return $value;
}
/**
* Increases the cache buster key
*/

View File

@ -23,8 +23,35 @@
namespace OCA\Theming;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\Files\IRootFolder;
class Util {
/** @var IConfig */
private $config;
/** @var IRootFolder */
private $rootFolder;
/** @var IAppManager */
private $appManager;
/**
* Util constructor.
*
* @param IConfig $config
* @param IRootFolder $rootFolder
* @param IAppManager $appManager
*/
public function __construct(IConfig $config, IRootFolder $rootFolder, IAppManager $appManager) {
$this->config = $config;
$this->rootFolder = $rootFolder;
$this->appManager = $appManager;
}
/**
* @param string $color rgb color value
* @return bool
@ -81,4 +108,86 @@ class Util {
return base64_encode($radioButtonIcon);
}
/**
* @param $app string app name
* @return string path to app icon / logo
*/
public function getAppIcon($app) {
$app = str_replace(array('\0', '/', '\\', '..'), '', $app);
try {
$appPath = $this->appManager->getAppPath($app);
$icon = $appPath . '/img/' . $app . '.svg';
if (file_exists($icon)) {
return $icon;
}
$icon = $appPath . '/img/app.svg';
if (file_exists($icon)) {
return $icon;
}
} catch (AppPathNotFoundException $e) {}
if($this->config->getAppValue('theming', 'logoMime', '') !== '' && $this->rootFolder->nodeExists('/themedinstancelogo')) {
return $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedinstancelogo';
}
return \OC::$SERVERROOT . '/core/img/logo.svg';
}
/**
* @param $app string app name
* @param $image string relative path to image in app folder
* @return string|false absolute path to image
*/
public function getAppImage($app, $image) {
$app = str_replace(array('\0', '/', '\\', '..'), '', $app);
$image = str_replace(array('\0', '\\', '..'), '', $image);
if ($app === "core") {
$icon = \OC::$SERVERROOT . '/core/img/' . $image;
if (file_exists($icon)) {
return $icon;
}
}
try {
$appPath = $this->appManager->getAppPath($app);
} catch (AppPathNotFoundException $e) {
return false;
}
$icon = $appPath . '/img/' . $image;
if (file_exists($icon)) {
return $icon;
}
$icon = $appPath . '/img/' . $image . '.svg';
if (file_exists($icon)) {
return $icon;
}
$icon = $appPath . '/img/' . $image . '.png';
if (file_exists($icon)) {
return $icon;
}
$icon = $appPath . '/img/' . $image . '.gif';
if (file_exists($icon)) {
return $icon;
}
$icon = $appPath . '/img/' . $image . '.jpg';
if (file_exists($icon)) {
return $icon;
}
return false;
}
/**
* replace default color with a custom one
*
* @param $svg string content of a svg file
* @param $color string color to match
* @return string
*/
public function colorizeSvg($svg, $color) {
$svg = preg_replace('/#0082c9/i', $color, $svg);
return $svg;
}
}

View File

@ -0,0 +1,201 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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\Tests\Controller;
use OC\Files\SimpleFS\SimpleFile;
use OCA\Theming\ImageManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use Test\TestCase;
use OCA\Theming\Util;
use OCA\Theming\Controller\IconController;
use OCP\AppFramework\Http\FileDisplayResponse;
class IconControllerTest extends TestCase {
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
private $request;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
private $themingDefaults;
/** @var Util */
private $util;
/** @var \OCP\AppFramework\Utility\ITimeFactory */
private $timeFactory;
/** @var IconController|\PHPUnit_Framework_MockObject_MockObject */
private $iconController;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */
private $iconBuilder;
/** @var ImageManager */
private $imageManager;
public function setUp() {
$this->request = $this->getMockBuilder('OCP\IRequest')->getMock();
$this->themingDefaults = $this->getMockBuilder('OCA\Theming\ThemingDefaults')
->disableOriginalConstructor()->getMock();
$this->util = $this->getMockBuilder('\OCA\Theming\Util')->disableOriginalConstructor()
->setMethods(['getAppImage', 'getAppIcon', 'elementColor'])->getMock();
$this->timeFactory = $this->getMockBuilder('OCP\AppFramework\Utility\ITimeFactory')
->disableOriginalConstructor()
->getMock();
$this->config = $this->getMockBuilder('OCP\IConfig')->getMock();
$this->iconBuilder = $this->getMockBuilder('OCA\Theming\IconBuilder')
->disableOriginalConstructor()->getMock();
$this->imageManager = $this->getMockBuilder('OCA\Theming\ImageManager')->disableOriginalConstructor()->getMock();
$this->timeFactory->expects($this->any())
->method('getTime')
->willReturn(123);
$this->iconController = new IconController(
'theming',
$this->request,
$this->themingDefaults,
$this->util,
$this->timeFactory,
$this->config,
$this->iconBuilder,
$this->imageManager
);
parent::setUp();
}
private function iconFileMock($filename, $data) {
$icon = $this->getMockBuilder('OCP\Files\File')->getMock();
$icon->expects($this->any())->method('getContent')->willReturn($data);
$icon->expects($this->any())->method('getMimeType')->willReturn('image type');
$icon->expects($this->any())->method('getEtag')->willReturn('my etag');
$icon->method('getName')->willReturn($filename);
return new SimpleFile($icon);
}
public function testGetThemedIcon() {
$file = $this->iconFileMock('icon-core-filetypes_folder.svg', 'filecontent');
$this->imageManager->expects($this->once())
->method('getCachedImage')
->with('icon-core-filetypes_folder.svg')
->willReturn($file);
$expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']);
$expected->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$expected->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$expected->addHeader('Pragma', 'cache');
@$this->assertEquals($expected, $this->iconController->getThemedIcon('core', 'filetypes/folder.svg'));
}
public function testGetFaviconDefault() {
if (!extension_loaded('imagick')) {
$this->markTestSkipped('Imagemagick is required for dynamic icon generation.');
}
$checkImagick = new \Imagick();
if (count($checkImagick->queryFormats('SVG')) < 1) {
$this->markTestSkipped('No SVG provider present.');
}
$this->themingDefaults->expects($this->any())
->method('shouldReplaceIcons')
->willReturn(true);
$this->iconBuilder->expects($this->once())
->method('getFavicon')
->with('core')
->willReturn('filecontent');
$file = $this->iconFileMock('filename', 'filecontent');
$this->imageManager->expects($this->once())
->method('getCachedImage')
->will($this->throwException(new NotFoundException()));
$this->imageManager->expects($this->once())
->method('setCachedImage')
->willReturn($file);
$expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']);
$expected->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$expected->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$expected->addHeader('Pragma', 'cache');
$this->assertEquals($expected, $this->iconController->getFavicon());
}
public function testGetFaviconFail() {
$this->themingDefaults->expects($this->any())
->method('shouldReplaceIcons')
->willReturn(false);
$expected = new Http\Response();
$expected->setStatus(Http::STATUS_NOT_FOUND);
$expected->cacheFor(0);
$expected->setLastModified(new \DateTime('now', new \DateTimeZone('GMT')));
$this->assertInstanceOf(NotFoundResponse::class, $this->iconController->getFavicon());
}
public function testGetTouchIconDefault() {
if (!extension_loaded('imagick')) {
$this->markTestSkipped('Imagemagick is required for dynamic icon generation.');
}
$checkImagick = new \Imagick();
if (count($checkImagick->queryFormats('SVG')) < 1) {
$this->markTestSkipped('No SVG provider present.');
}
$this->themingDefaults->expects($this->any())
->method('shouldReplaceIcons')
->willReturn(true);
$this->iconBuilder->expects($this->once())
->method('getTouchIcon')
->with('core')
->willReturn('filecontent');
$file = $this->iconFileMock('filename', 'filecontent');
$this->imageManager->expects($this->once())
->method('getCachedImage')
->will($this->throwException(new NotFoundException()));
$this->imageManager->expects($this->once())
->method('setCachedImage')
->willReturn($file);
$expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'image/png']);
$expected->cacheFor(86400);
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT24H'));
$expected->addHeader('Expires', $expires->format(\DateTime::RFC2822));
$expected->addHeader('Pragma', 'cache');
$this->assertEquals($expected, $this->iconController->getTouchIcon());
}
public function testGetTouchIconFail() {
$this->themingDefaults->expects($this->any())
->method('shouldReplaceIcons')
->willReturn(false);
$this->assertInstanceOf(NotFoundResponse::class, $this->iconController->getTouchIcon());
}
}

View File

@ -26,6 +26,7 @@ namespace OCA\Theming\Tests\Controller;
use OCA\Theming\Controller\ThemingController;
use OCA\Theming\Util;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\Files\File;
@ -57,18 +58,21 @@ class ThemingControllerTest extends TestCase {
private $rootFolder;
/** @var ITempManager */
private $tempManager;
/** @var IAppManager */
private $appManager;
public function setUp() {
$this->request = $this->getMockBuilder('OCP\IRequest')->getMock();
$this->config = $this->getMockBuilder('OCP\IConfig')->getMock();
$this->template = $this->getMockBuilder('OCA\Theming\ThemingDefaults')
->disableOriginalConstructor()->getMock();
$this->util = new Util();
$this->timeFactory = $this->getMockBuilder('OCP\AppFramework\Utility\ITimeFactory')
->disableOriginalConstructor()
->getMock();
$this->l10n = $this->getMockBuilder('OCP\IL10N')->getMock();
$this->rootFolder = $this->getMockBuilder('OCP\Files\IRootFolder')->getMock();
$this->appManager = $this->getMockBuilder('OCP\App\IAppManager')->getMock();
$this->util = new Util($this->config, $this->rootFolder, $this->appManager);
$this->timeFactory->expects($this->any())
->method('getTime')
->willReturn(123);
@ -484,6 +488,12 @@ class ThemingControllerTest extends TestCase {
$expectedData .= sprintf('.nc-theming-main-background {background-color: %s}' . "\n", $color);
$expectedData .= sprintf('.nc-theming-main-text {color: %s}' . "\n", $color);
$expectedData .= '.nc-theming-contrast {color: #ffffff}' . "\n";
$expectedData .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v=0\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v=0\')!important;' . "}\n";
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
@ -578,6 +588,12 @@ class ThemingControllerTest extends TestCase {
$expectedData .= '#body-login input.login { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/confirm.svg?v=2\'); }' . "\n";
$expectedData .= '.nc-theming-contrast {color: #000000}' . "\n";
$expectedData .= '.ui-widget-header { color: #000000; }' . "\n";
$expectedData .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v=0\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v=0\')!important;' . "}\n";
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
@ -623,6 +639,12 @@ class ThemingControllerTest extends TestCase {
'background-size: contain;' .
'}' . "\n";
$expectedData .= '.nc-theming-contrast {color: #ffffff}' . "\n";
$expectedData .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v=0\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v=0\')!important;' . "}\n";
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
@ -765,6 +787,12 @@ class ThemingControllerTest extends TestCase {
'background-image: url(\'./loginbackground?v=0\');' .
'}' . "\n";
$expectedData .= '.nc-theming-contrast {color: #ffffff}' . "\n";
$expectedData .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v=0\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v=0\')!important;' . "}\n";
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
$expected->cacheFor(3600);
@ -876,6 +904,13 @@ class ThemingControllerTest extends TestCase {
$expectedData .= '#body-login input.login { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/confirm.svg?v=2\'); }' . "\n";
$expectedData .= '.nc-theming-contrast {color: #000000}' . "\n";
$expectedData .= '.ui-widget-header { color: #000000; }' . "\n";
$expectedData .= '.icon-file,.icon-filetype-text {' .
'background-image: url(\'./img/core/filetypes/text.svg?v=0\');' . "}\n" .
'.icon-folder, .icon-filetype-folder {' .
'background-image: url(\'./img/core/filetypes/folder.svg?v=0\');' . "}\n" .
'.icon-filetype-folder-drag-accept {' .
'background-image: url(\'./img/core/filetypes/folder-drag-accept.svg?v=0\')!important;' . "}\n";
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
$expected = new Http\DataDownloadResponse($expectedData, 'style', 'text/css');
$expected->cacheFor(3600);
@ -910,6 +945,7 @@ class ThemingControllerTest extends TestCase {
slogan: "",
color: "#000",
inverted: false,
cacheBuster: null
};
})();';
$expected = new Http\DataDisplayResponse($expectedResponse);
@ -944,6 +980,7 @@ class ThemingControllerTest extends TestCase {
slogan: "awesome",
color: "#ffffff",
inverted: true,
cacheBuster: null
};
})();';
$expected = new Http\DataDisplayResponse($expectedResponse);

View File

@ -0,0 +1,192 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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\Tests;
use OCA\Theming\IconBuilder;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use Test\TestCase;
class IconBuilderTest extends TestCase {
/** @var IConfig */
protected $config;
/** @var IRootFolder */
protected $rootFolder;
/** @var ThemingDefaults */
protected $themingDefaults;
/** @var Util */
protected $util;
/** @var IconBuilder */
protected $iconBuilder;
/** @var IAppManager */
protected $appManager;
protected function setUp() {
parent::setUp();
$this->config = $this->getMockBuilder('\OCP\IConfig')->getMock();
$this->rootFolder = $this->getMockBuilder('OCP\Files\IRootFolder')->getMock();
$this->themingDefaults = $this->getMockBuilder('OCA\Theming\ThemingDefaults')
->disableOriginalConstructor()->getMock();
$this->appManager = $this->getMockBuilder('OCP\App\IAppManager')->getMock();
$this->util = new Util($this->config, $this->rootFolder, $this->appManager);
$this->iconBuilder = new IconBuilder($this->themingDefaults, $this->util);
}
private function checkImagick() {
if(!extension_loaded('imagick')) {
$this->markTestSkipped('Imagemagick is required for dynamic icon generation.');
}
$checkImagick = new \Imagick();
if (count($checkImagick->queryFormats('SVG')) < 1) {
$this->markTestSkipped('No SVG provider present.');
}
}
public function dataRenderAppIcon() {
return [
['core', '#0082c9', 'touch-original.png'],
['core', '#FF0000', 'touch-core-red.png'],
['testing', '#FF0000', 'touch-testing-red.png'],
['comments', '#0082c9', 'touch-comments.png'],
['core', '#0082c9', 'touch-original-png.png'],
];
}
/**
* @dataProvider dataRenderAppIcon
* @param $app
* @param $color
* @param $file
*/
public function testRenderAppIcon($app, $color, $file) {
$this->checkImagick();
$this->themingDefaults->expects($this->once())
->method('getMailHeaderColor')
->willReturn($color);
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file);
$icon = $this->iconBuilder->renderAppIcon($app);
$this->assertEquals(true, $icon->valid());
$this->assertEquals(512, $icon->getImageWidth());
$this->assertEquals(512, $icon->getImageHeight());
$this->assertEquals($icon, $expectedIcon);
$icon->destroy();
$expectedIcon->destroy();
// FIXME: We may need some comparison of the generated and the test images
// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
}
/**
* @dataProvider dataRenderAppIcon
* @param $app
* @param $color
* @param $file
*/
public function testGetTouchIcon($app, $color, $file) {
$this->checkImagick();
$this->themingDefaults->expects($this->once())
->method('getMailHeaderColor')
->willReturn($color);
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file);
$icon = new \Imagick();
$icon->readImageBlob($this->iconBuilder->getTouchIcon($app));
$this->assertEquals(true, $icon->valid());
$this->assertEquals(512, $icon->getImageWidth());
$this->assertEquals(512, $icon->getImageHeight());
$this->assertEquals($icon, $expectedIcon);
$icon->destroy();
$expectedIcon->destroy();
// FIXME: We may need some comparison of the generated and the test images
// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
}
/**
* @dataProvider dataRenderAppIcon
* @param $app
* @param $color
* @param $file
*/
public function testGetFavicon($app, $color, $file) {
$this->checkImagick();
$this->themingDefaults->expects($this->once())
->method('getMailHeaderColor')
->willReturn($color);
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file);
$icon = new \Imagick();
$icon->readImageBlob($this->iconBuilder->getFavicon($app));
$this->assertEquals(true, $icon->valid());
$this->assertEquals(32, $icon->getImageWidth());
$this->assertEquals(32, $icon->getImageHeight());
$icon->destroy();
$expectedIcon->destroy();
// FIXME: We may need some comparison of the generated and the test images
// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testGetFaviconNotFound() {
$util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock();
$iconBuilder = new IconBuilder($this->themingDefaults, $util);
$util->expects($this->once())
->method('getAppIcon')
->willReturn('notexistingfile');
$this->assertFalse($iconBuilder->getFavicon('noapp'));
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testGetTouchIconNotFound() {
$util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock();
$iconBuilder = new IconBuilder($this->themingDefaults, $util);
$util->expects($this->once())
->method('getAppIcon')
->willReturn('notexistingfile');
$this->assertFalse($iconBuilder->getTouchIcon('noapp'));
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testColorSvgNotFound() {
$util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock();
$iconBuilder = new IconBuilder($this->themingDefaults, $util);
$util->expects($this->once())
->method('getAppImage')
->willReturn('notexistingfile');
$this->assertFalse($iconBuilder->colorSvg('noapp','noimage'));
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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\Tests;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IConfig;
use Test\TestCase;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
class ImageManager extends TestCase {
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */
protected $appData;
/** @var ImageManager */
protected $imageManager;
protected function setUp() {
parent::setUp();
$this->config = $this->getMockBuilder('\OCP\IConfig')->getMock();
$this->appData = $this->getMockBuilder('OCP\Files\IAppData')->getMock();
$this->imageManager = new \OCA\Theming\ImageManager(
$this->config,
$this->appData
);
}
public function testGetCacheFolder() {
$folder = $this->createMock(ISimpleFolder::class);
$this->config->expects($this->once())
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->at(0))
->method('getFolder')
->with('0')
->willReturn($folder);
$this->assertEquals($folder, $this->imageManager->getCacheFolder());
}
public function testGetCacheFolderCreate() {
$folder = $this->createMock(ISimpleFolder::class);
$this->config->expects($this->exactly(2))
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->at(0))
->method('getFolder')
->willThrowException(new NotFoundException());
$this->appData->expects($this->at(1))
->method('newFolder')
->with('0')
->willReturn($folder);
$this->appData->expects($this->at(2))
->method('getFolder')
->with('0')
->willReturn($folder);
$this->appData->expects($this->once())
->method('getDirectoryListing')
->willReturn([]);
$this->assertEquals($folder, $this->imageManager->getCacheFolder());
}
public function testGetCachedImage() {
$folder = $this->setupCacheFolder();
$folder->expects($this->once())
->method('getFile')
->with('filename')
->willReturn('filecontent');
$expected = 'filecontent';
$this->assertEquals($expected, $this->imageManager->getCachedImage('filename'));
}
/**
* @expectedException \OCP\Files\NotFoundException
*/
public function testGetCachedImageNotFound() {
$folder = $this->setupCacheFolder();
$folder->expects($this->once())
->method('getFile')
->with('filename')
->will($this->throwException(new \OCP\Files\NotFoundException()));
$image = $this->imageManager->getCachedImage('filename');
}
public function testSetCachedImage() {
$folder = $this->setupCacheFolder();
$file = $this->createMock(ISimpleFile::class);
$folder->expects($this->once())
->method('fileExists')
->with('filename')
->willReturn(true);
$folder->expects($this->once())
->method('getFile')
->with('filename')
->willReturn($file);
$file->expects($this->once())
->method('putContent')
->with('filecontent');
$this->assertEquals($file, $this->imageManager->setCachedImage('filename', 'filecontent'));
}
public function testSetCachedImageCreate() {
$folder = $this->setupCacheFolder();
$file = $this->createMock(ISimpleFile::class);
$folder->expects($this->once())
->method('fileExists')
->with('filename')
->willReturn(false);
$folder->expects($this->once())
->method('newFile')
->with('filename')
->willReturn($file);
$file->expects($this->once())
->method('putContent')
->with('filecontent');
$this->assertEquals($file, $this->imageManager->setCachedImage('filename', 'filecontent'));
}
private function setupCacheFolder() {
$folder = $this->createMock(ISimpleFolder::class);
$this->config->expects($this->once())
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->at(0))
->method('getFolder')
->with('0')
->willReturn($folder);
return $folder;
}
public function testCleanup() {
$folders = [
$this->createMock(ISimpleFolder::class),
$this->createMock(ISimpleFolder::class),
$this->createMock(ISimpleFolder::class)
];
foreach ($folders as $index=>$folder) {
$folder->expects($this->any())
->method('getName')
->willReturn($index);
}
$folders[0]->expects($this->once())->method('delete');
$folders[1]->expects($this->once())->method('delete');
$folders[2]->expects($this->never())->method('delete');
$this->config->expects($this->once())
->method('getAppValue')
->with('theming','cachebuster','0')
->willReturn('2');
$this->appData->expects($this->once())
->method('getDirectoryListing')
->willReturn($folders);
$this->appData->expects($this->once())
->method('getFolder')
->with('2')
->willReturn($folders[2]);
$this->imageManager->cleanup();
}
}

View File

@ -24,6 +24,7 @@
namespace OCA\Theming\Tests;
use OCA\Theming\ThemingDefaults;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
@ -43,6 +44,8 @@ class ThemingDefaultsTest extends TestCase {
private $template;
/** @var IRootFolder */
private $rootFolder;
/** @var ICacheFactory */
private $cacheFactory;
public function setUp() {
parent::setUp();
@ -52,6 +55,7 @@ class ThemingDefaultsTest extends TestCase {
$this->rootFolder = $this->getMockBuilder(IRootFolder::class)
->disableOriginalConstructor()
->getMock();
$this->cacheFactory = $this->getMockBuilder(ICacheFactory::class)->getMock();
$this->defaults = $this->getMockBuilder(\OC_Defaults::class)
->disableOriginalConstructor()
->getMock();
@ -76,7 +80,8 @@ class ThemingDefaultsTest extends TestCase {
$this->l10n,
$this->urlGenerator,
$this->defaults,
$this->rootFolder
$this->rootFolder,
$this->cacheFactory
);
}

View File

@ -23,16 +23,28 @@
namespace OCA\Theming\Tests;
use OCA\Theming\Util;
use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\Files\IRootFolder;
use Test\TestCase;
class UtilTest extends TestCase {
/** @var Util */
protected $util;
/** @var IConfig */
protected $config;
/** @var IRootFolder */
protected $rootFolder;
/** @var IAppManager */
protected $appManager;
protected function setUp() {
parent::setUp();
$this->util = new Util();
$this->config = $this->getMockBuilder('\OCP\IConfig')->getMock();
$this->rootFolder = $this->getMockBuilder('OCP\Files\IRootFolder')->getMock();
$this->appManager = $this->getMockBuilder('OCP\App\IAppManager')->getMock();
$this->util = new Util($this->config, $this->rootFolder, $this->appManager);
}
public function testInvertTextColorLight() {
@ -89,9 +101,70 @@ class UtilTest extends TestCase {
$expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiNmZmZmZmYiLz48L3N2Zz4=';
$this->assertEquals($expected, $button);
}
public function testGenerateRadioButtonBlack() {
$button = $this->util->generateRadioButton('#000000');
$expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiMwMDAwMDAiLz48L3N2Zz4=';
$this->assertEquals($expected, $button);
}
/**
* @dataProvider dataGetAppIcon
*/
public function testGetAppIcon($app, $expected) {
$this->appManager->expects($this->once())
->method('getAppPath')
->with($app)
->willReturn(\OC_App::getAppPath($app));
$icon = $this->util->getAppIcon($app);
$this->assertEquals($expected, $icon);
}
public function dataGetAppIcon() {
return [
['user_ldap', \OC_App::getAppPath('user_ldap') . '/img/app.svg'],
['noapplikethis', \OC::$SERVERROOT . '/core/img/logo.svg'],
['comments', \OC_App::getAppPath('comments') . '/img/comments.svg'],
];
}
public function testGetAppIconThemed() {
$this->rootFolder->expects($this->once())
->method('nodeExists')
->with('/themedinstancelogo')
->willReturn(true);
$expected = '/themedinstancelogo';
$icon = $this->util->getAppIcon('noapplikethis');
$this->assertEquals($expected, $icon);
}
/**
* @dataProvider dataGetAppImage
*/
public function testGetAppImage($app, $image, $expected) {
if($app !== 'core') {
$this->appManager->expects($this->once())
->method('getAppPath')
->with($app)
->willReturn(\OC_App::getAppPath($app));
}
$this->assertEquals($expected, $this->util->getAppImage($app, $image));
}
public function dataGetAppImage() {
return [
['core', 'logo.svg', \OC::$SERVERROOT . '/core/img/logo.svg'],
['files', 'external', \OC::$SERVERROOT . '/apps/files/img/external.svg'],
['files', 'external.svg', \OC::$SERVERROOT . '/apps/files/img/external.svg'],
['noapplikethis', 'foobar.svg', false],
];
}
public function testColorizeSvg() {
$input = "#0082c9 #0082C9 #000000 #FFFFFF";
$expected = "#AAAAAA #AAAAAA #000000 #FFFFFF";
$result = $this->util->colorizeSvg($input, '#AAAAAA');
$this->assertEquals($expected, $result);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -91,6 +91,11 @@ OC.MimeType = {
path += icon;
}
}
if(OCA.Theming) {
path = OC.generateUrl('/apps/theming/img/core/filetypes/');
path += OC.MimeType._getFile(mimeType, OC.MimeTypeList.files);
gotIcon = true;
}
// If we do not yet have an icon fall back to the default
if (gotIcon === null) {
@ -100,6 +105,10 @@ OC.MimeType = {
path += '.svg';
if(OCA.Theming) {
path += "?v=" + OCA.Theming.cacheBuster;
}
// Cache the result
OC.MimeType._mimeTypeIcons[mimeType] = path;
return path;

View File

@ -703,7 +703,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->getL10N('theming'),
$c->getURLGenerator(),
new \OC_Defaults(),
$c->getLazyRootFolder()
$c->getLazyRootFolder(),
$c->getMemCacheFactory()
);
}
return new \OC_Defaults();

View File

@ -157,7 +157,15 @@ class URLGenerator implements IURLGenerator {
// Check if the app is in the app folder
$path = '';
if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$image")) {
if(\OCP\App::isEnabled('theming') && $image === "favicon.ico" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
if($app==="") { $app = "core"; }
$path = $this->linkToRoute('theming.Icon.getFavicon', [ 'app' => $app ]) . '?v='. $cacheBusterValue;
} elseif(\OCP\App::isEnabled('theming') && $image === "favicon-touch.png" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
if($app==="") { $app = "core"; }
$path = $this->linkToRoute('theming.Icon.getTouchIcon', [ 'app' => $app ]) . '?v='. $cacheBusterValue;
} elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$image")) {
$path = \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$image";
} elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.svg")
&& file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.png")) {