Signed-off-by: Julius Haertl <jus@bitgrid.net>tags/v11.0RC2
@@ -53,6 +53,7 @@ class IconController extends Controller { | |||
/** @var IRootFolder */ | |||
private $rootFolder; | |||
/** | |||
* IconController constructor. | |||
* | |||
@@ -94,10 +95,10 @@ class IconController extends Controller { | |||
* @return StreamResponse|DataResponse | |||
*/ | |||
public function getThemedIcon($app, $image) { | |||
$image = $this->getAppImage($app, $image); | |||
$image = $this->util->getAppImage($app, $image); | |||
$svg = file_get_contents($image); | |||
$color = $this->util->elementColor($this->themingDefaults->getMailHeaderColor()); | |||
$svg = $this->colorizeSvg($svg, $color); | |||
$svg = $this->util->colorizeSvg($svg, $color); | |||
$response = new DataDisplayResponse($svg, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']); | |||
$response->cacheFor(86400); | |||
@@ -152,24 +153,19 @@ class IconController extends Controller { | |||
* @return Imagick | |||
*/ | |||
private function renderAppIcon($app) { | |||
$appIcon = $this->getAppIcon($app); | |||
$appIcon = $this->util->getAppIcon($app); | |||
$color = $this->themingDefaults->getMailHeaderColor(); | |||
$mime = mime_content_type($appIcon); | |||
// FIXME: test if we need this | |||
if ($color === "") { | |||
$color = '#0082c9'; | |||
} | |||
// 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") { | |||
$svg = file_get_contents($appIcon); | |||
$tmp = new Imagick(); | |||
$tmp->readImageBlob($svg); | |||
$x = $tmp->getImageWidth(); | |||
@@ -214,76 +210,6 @@ class IconController extends Controller { | |||
return $finalIconFile; | |||
} | |||
/** | |||
* @param $app app name | |||
* @return string path to app icon / logo | |||
*/ | |||
private function getAppIcon($app) { | |||
$appPath = \OC_App::getAppPath($app); | |||
$icon = $appPath . '/img/' . $app . '.svg'; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
$icon = $appPath . '/img/app.svg'; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
if($this->rootFolder->nodeExists('/themedinstancelogo')) { | |||
return $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedinstancelogo'; | |||
} | |||
return \OC::$SERVERROOT . '/core/img/logo.svg'; | |||
} | |||
/** | |||
* @param $app app name | |||
* @param $image relative path to image in app folder | |||
* @return string absolute path to image | |||
*/ | |||
private function getAppImage($app, $image) { | |||
$appPath = \OC_App::getAppPath($app); | |||
if($app==="core") { | |||
$icon = \OC::$SERVERROOT . '/core/img/' . $image; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
} | |||
$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 content of a svg file | |||
* @param $color color to match | |||
* @return string | |||
*/ | |||
private function colorizeSvg($svg, $color) { | |||
$svg = preg_replace('/#0082c9/i', $color, $svg); | |||
return $svg; | |||
} | |||
} |
@@ -23,8 +23,19 @@ | |||
namespace OCA\Theming; | |||
use OCP\IConfig; | |||
use OCP\Files\IRootFolder; | |||
class Util { | |||
private $config; | |||
private $rootFolder; | |||
public function __construct(IConfig $config, IRootFolder $rootFolder) { | |||
$this->config = $config; | |||
$this->rootFolder = $rootFolder; | |||
} | |||
/** | |||
* @param string $color rgb color value | |||
* @return bool | |||
@@ -81,4 +92,77 @@ class Util { | |||
return base64_encode($radioButtonIcon); | |||
} | |||
/** | |||
* @param $app app name | |||
* @return string path to app icon / logo | |||
*/ | |||
public function getAppIcon($app) { | |||
$appPath = \OC_App::getAppPath($app); | |||
$icon = $appPath . '/img/' . $app . '.svg'; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
$icon = $appPath . '/img/app.svg'; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
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 app name | |||
* @param $image relative path to image in app folder | |||
* @return string absolute path to image | |||
*/ | |||
public function getAppImage($app, $image) { | |||
$appPath = \OC_App::getAppPath($app); | |||
if($app==="core") { | |||
$icon = \OC::$SERVERROOT . '/core/img/' . $image; | |||
if(file_exists($icon)) { | |||
return $icon; | |||
} | |||
} | |||
$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 content of a svg file | |||
* @param $color color to match | |||
* @return string | |||
*/ | |||
public function colorizeSvg($svg, $color) { | |||
$svg = preg_replace('/#0082c9/i', $color, $svg); | |||
return $svg; | |||
} | |||
} |
@@ -35,7 +35,7 @@ use Test\TestCase; | |||
use OCA\Theming\ThemingDefaults; | |||
use \Imagick; | |||
class ThemingControllerTest extends TestCase { | |||
class IconControllerTest extends TestCase { | |||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ | |||
private $request; | |||
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ | |||
@@ -54,11 +54,21 @@ class ThemingControllerTest extends TestCase { | |||
private $rootFolder; | |||
public function setUp() { | |||
if(!extension_loaded('imagick')) { | |||
$this->markTestSkipped('Tests skipped as Imagemagick is required for dynamic icon generation.'); | |||
} | |||
$checkImagick = new \Imagick(); | |||
if (count($checkImagick->queryFormats('SVG')) < 1) { | |||
$this->markTestSkipped('No SVG provider present'); | |||
} | |||
$this->request = $this->getMockBuilder('OCP\IRequest')->getMock(); | |||
$this->config = $this->getMockBuilder('OCP\IConfig')->getMock(); | |||
$this->themingDefaults = $this->getMockBuilder('OCA\Theming\ThemingDefaults') | |||
->disableOriginalConstructor()->getMock(); | |||
$this->util = new Util(); | |||
$this->util = $this->getMockBuilder('\OCA\Theming\Util')->disableOriginalConstructor() | |||
->setMethods(['getAppImage', 'getAppIcon', 'elementColor'])->getMock(); | |||
$this->timeFactory = $this->getMockBuilder('OCP\AppFramework\Utility\ITimeFactory') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
@@ -84,10 +94,19 @@ class ThemingControllerTest extends TestCase { | |||
} | |||
public function testGetThemedIcon() { | |||
$this->util->expects($this->once()) | |||
->method('getAppImage') | |||
->with('core','filetypes/folder.svg') | |||
->willReturn(\OC::$SERVERROOT . "/core/img/filetypes/folder.svg"); | |||
$this->themingDefaults | |||
->expects($this->once()) | |||
->method('getMailHeaderColor') | |||
->willReturn('#000000'); | |||
$this->util | |||
->expects($this->once()) | |||
->method('elementColor') | |||
->willReturn('#000000'); | |||
$svg = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> | |||
<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"16\" width=\"16\" version=\"1.0\"> | |||
<g fill-rule=\"evenodd\" transform=\"matrix(.86667 0 0 .86667 -172.05 -864.43)\" fill=\"#000000\"> | |||
@@ -102,23 +121,27 @@ class ThemingControllerTest extends TestCase { | |||
} | |||
public function testGetFaviconDefault() { | |||
$favicon = $this->iconController->getFavicon(); | |||
$expectedIcon = $this->invokePrivate($this->iconController, 'renderAppIcon', ["core"]); | |||
$expectedIcon->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); | |||
$expectedIcon->setImageFormat("png24"); | |||
$this->util->expects($this->once()) | |||
->method('getAppIcon') | |||
->with('core') | |||
->willReturn(\OC::$SERVERROOT . "/core/img/logo.svg"); | |||
$favicon = $this->iconController->getFavicon(); | |||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)) . '/../data/favicon-original.ico'); | |||
$expected = new DataDisplayResponse($expectedIcon, Http::STATUS_OK, ['Content-Type' => 'image/x-icon']); | |||
$expected->cacheFor(86400); | |||
$expected->addHeader('Expires', date(\DateTime::RFC2822, $this->timeFactory->getTime())); | |||
$this->assertEquals($expected, $favicon); | |||
} | |||
public function testGetTouchIconDefault() { | |||
$favicon = $this->iconController->getTouchIcon(); | |||
$expectedIcon = $this->invokePrivate($this->iconController, 'renderAppIcon', ["core"]); | |||
$expectedIcon->resizeImage(512, 512, Imagick::FILTER_LANCZOS, 1); | |||
$expectedIcon->setImageFormat("png24"); | |||
$this->util->expects($this->once()) | |||
->method('getAppIcon') | |||
->with('core') | |||
->willReturn(\OC::$SERVERROOT . "/core/img/logo.svg"); | |||
$favicon = $this->iconController->getTouchIcon(); | |||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)) . '/../data/touch-original.png'); | |||
$expected = new DataDisplayResponse($expectedIcon, Http::STATUS_OK, ['Content-Type' => 'image/png']); | |||
$expected->cacheFor(86400); | |||
@@ -126,76 +149,42 @@ class ThemingControllerTest extends TestCase { | |||
$this->assertEquals($expected, $favicon); | |||
} | |||
public function testRenderAppIcon() { | |||
$this->themingDefaults->expects($this->once()) | |||
->method('getMailHeaderColor') | |||
->willReturn('#000000'); | |||
/** | |||
* @dataProvider dataRenderAppIcon | |||
* @param $appicon | |||
* @param $color | |||
* @param $file | |||
*/ | |||
public function testRenderAppIcon($app, $appicon, $color, $file) { | |||
$icon = $this->invokePrivate($this->iconController, 'renderAppIcon', ['core']); | |||
$this->assertEquals(true, $icon->valid()); | |||
$this->assertEquals(512, $icon->getImageWidth()); | |||
$this->assertEquals(512, $icon->getImageHeight()); | |||
} | |||
public function testRenderAppIconColor() { | |||
$this->util->expects($this->once()) | |||
->method('getAppIcon') | |||
->with($app) | |||
->willReturn(\OC::$SERVERROOT . "/" . $appicon); | |||
$this->themingDefaults->expects($this->once()) | |||
->method('getMailHeaderColor') | |||
->willReturn('#0082c9'); | |||
->willReturn($color); | |||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/../data/" . $file); | |||
$icon = $this->invokePrivate($this->iconController, 'renderAppIcon', [$app]); | |||
$icon = $this->invokePrivate($this->iconController, 'renderAppIcon', ['core']); | |||
$this->assertEquals(true, $icon->valid()); | |||
$this->assertEquals(512, $icon->getImageWidth()); | |||
$this->assertEquals(512, $icon->getImageHeight()); | |||
} | |||
/** | |||
* @dataProvider dataGetAppIcon | |||
*/ | |||
public function testGetAppIcon($app, $expected) { | |||
$icon = $this->invokePrivate($this->iconController, '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->invokePrivate($this->iconController, 'getAppIcon', ['noapplikethis']); | |||
$this->assertEquals($expected, $icon); | |||
$this->assertEquals($icon, $expectedIcon); | |||
//$this->assertLessThan(0.0005, $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]); | |||
} | |||
/** | |||
* @dataProvider dataGetAppImage | |||
*/ | |||
public function testGetAppImage($app, $image, $expected) { | |||
$this->assertEquals($expected, $this->invokePrivate($this->iconController, 'getAppImage', [$app, $image])); | |||
} | |||
public function dataGetAppImage() { | |||
public function dataRenderAppIcon() { | |||
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], | |||
['core','core/img/logo.svg', '#0082c9', 'touch-original.png'], | |||
['core','core/img/logo.svg', '#FF0000', 'touch-core-red.png'], | |||
['testing','apps/testing/img/app.svg', '#FF0000', 'touch-testing-red.png'], | |||
['comments','apps/comments/img/comments.svg', '#0082c9', 'touch-comments.png'], | |||
['core','core/img/logo.png', '#0082c9', 'touch-original-png.png'], | |||
]; | |||
} | |||
public function testColorizeSvg() { | |||
$input = "#0082c9 #0082C9 #000000 #FFFFFF"; | |||
$expected = "#AAAAAA #AAAAAA #000000 #FFFFFF"; | |||
$result = $this->invokePrivate($this->iconController, 'colorizeSvg', [$input, '#AAAAAA']); | |||
$this->assertEquals($expected, $result); | |||
} | |||
} |
@@ -61,12 +61,12 @@ class ThemingControllerTest extends TestCase { | |||
$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->util = new Util($this->config, $this->rootFolder); | |||
$this->timeFactory->expects($this->any()) | |||
->method('getTime') | |||
->willReturn(123); |
@@ -23,16 +23,22 @@ | |||
namespace OCA\Theming\Tests; | |||
use OCA\Theming\Util; | |||
use OCP\IConfig; | |||
use OCP\Files\IRootFolder; | |||
use Test\TestCase; | |||
class UtilTest extends TestCase { | |||
/** @var Util */ | |||
protected $util; | |||
/** @var IConfig */ | |||
protected $config; | |||
protected $rootFolder; | |||
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->util = new Util($this->config, $this->rootFolder); | |||
} | |||
public function testInvertTextColorLight() { | |||
@@ -94,4 +100,55 @@ class UtilTest extends TestCase { | |||
$expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiMwMDAwMDAiLz48L3N2Zz4='; | |||
$this->assertEquals($expected, $button); | |||
} | |||
/** | |||
* @dataProvider dataGetAppIcon | |||
*/ | |||
public function testGetAppIcon($app, $expected) { | |||
$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) { | |||
$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); | |||
} | |||
} |