Completed
Push — master ( 33412e...913026 )
by Joas
26:50 queued 14s
created
apps/theming/lib/IconBuilder.php 1 patch
Indentation   +194 added lines, -194 removed lines patch added patch discarded remove patch
@@ -10,198 +10,198 @@
 block discarded – undo
10 10
 use OCP\Files\SimpleFS\ISimpleFile;
11 11
 
12 12
 class IconBuilder {
13
-	/**
14
-	 * IconBuilder constructor.
15
-	 *
16
-	 * @param ThemingDefaults $themingDefaults
17
-	 * @param Util $util
18
-	 * @param ImageManager $imageManager
19
-	 */
20
-	public function __construct(
21
-		private ThemingDefaults $themingDefaults,
22
-		private Util $util,
23
-		private ImageManager $imageManager,
24
-	) {
25
-	}
26
-
27
-	/**
28
-	 * @param $app string app name
29
-	 * @return string|false image blob
30
-	 */
31
-	public function getFavicon($app) {
32
-		if (!$this->imageManager->shouldReplaceIcons()) {
33
-			return false;
34
-		}
35
-		try {
36
-			$favicon = new Imagick();
37
-			$favicon->setFormat('ico');
38
-			$icon = $this->renderAppIcon($app, 128);
39
-			if ($icon === false) {
40
-				return false;
41
-			}
42
-			$icon->setImageFormat('png32');
43
-
44
-			$clone = clone $icon;
45
-			$clone->scaleImage(16, 0);
46
-			$favicon->addImage($clone);
47
-
48
-			$clone = clone $icon;
49
-			$clone->scaleImage(32, 0);
50
-			$favicon->addImage($clone);
51
-
52
-			$clone = clone $icon;
53
-			$clone->scaleImage(64, 0);
54
-			$favicon->addImage($clone);
55
-
56
-			$clone = clone $icon;
57
-			$clone->scaleImage(128, 0);
58
-			$favicon->addImage($clone);
59
-
60
-			$data = $favicon->getImagesBlob();
61
-			$favicon->destroy();
62
-			$icon->destroy();
63
-			$clone->destroy();
64
-			return $data;
65
-		} catch (\ImagickException $e) {
66
-			return false;
67
-		}
68
-	}
69
-
70
-	/**
71
-	 * @param $app string app name
72
-	 * @return string|false image blob
73
-	 */
74
-	public function getTouchIcon($app) {
75
-		try {
76
-			$icon = $this->renderAppIcon($app, 512);
77
-			if ($icon === false) {
78
-				return false;
79
-			}
80
-			$icon->setImageFormat('png32');
81
-			$data = $icon->getImageBlob();
82
-			$icon->destroy();
83
-			return $data;
84
-		} catch (\ImagickException $e) {
85
-			return false;
86
-		}
87
-	}
88
-
89
-	/**
90
-	 * Render app icon on themed background color
91
-	 * fallback to logo
92
-	 *
93
-	 * @param string $app app name
94
-	 * @param int $size size of the icon in px
95
-	 * @return Imagick|false
96
-	 */
97
-	public function renderAppIcon($app, $size) {
98
-		$appIcon = $this->util->getAppIcon($app);
99
-		if ($appIcon instanceof ISimpleFile) {
100
-			$appIconContent = $appIcon->getContent();
101
-			$mime = $appIcon->getMimeType();
102
-		} elseif (!file_exists($appIcon)) {
103
-			return false;
104
-		} else {
105
-			$appIconContent = file_get_contents($appIcon);
106
-			$mime = mime_content_type($appIcon);
107
-		}
108
-
109
-		if ($appIconContent === false || $appIconContent === '') {
110
-			return false;
111
-		}
112
-
113
-		$color = $this->themingDefaults->getColorPrimary();
114
-
115
-		// generate background image with rounded corners
116
-		$cornerRadius = 0.2 * $size;
117
-		$background = '<?xml version="1.0" encoding="UTF-8"?>' .
118
-			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" width="' . $size . '" height="' . $size . '" xmlns:xlink="http://www.w3.org/1999/xlink">' .
119
-			'<rect x="0" y="0" rx="' . $cornerRadius . '" ry="' . $cornerRadius . '" width="' . $size . '" height="' . $size . '" style="fill:' . $color . ';" />' .
120
-			'</svg>';
121
-		// resize svg magic as this seems broken in Imagemagick
122
-		if ($mime === 'image/svg+xml' || substr($appIconContent, 0, 4) === '<svg') {
123
-			if (substr($appIconContent, 0, 5) !== '<?xml') {
124
-				$svg = '<?xml version="1.0"?>' . $appIconContent;
125
-			} else {
126
-				$svg = $appIconContent;
127
-			}
128
-			$tmp = new Imagick();
129
-			$tmp->setBackgroundColor(new ImagickPixel('transparent'));
130
-			$tmp->setResolution(72, 72);
131
-			$tmp->readImageBlob($svg);
132
-			$x = $tmp->getImageWidth();
133
-			$y = $tmp->getImageHeight();
134
-			$tmp->destroy();
135
-
136
-			// convert svg to resized image
137
-			$appIconFile = new Imagick();
138
-			$resX = (int)(72 * $size / $x);
139
-			$resY = (int)(72 * $size / $y);
140
-			$appIconFile->setResolution($resX, $resY);
141
-			$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
142
-			$appIconFile->readImageBlob($svg);
143
-
144
-			/**
145
-			 * invert app icons for bright primary colors
146
-			 * the default nextcloud logo will not be inverted to black
147
-			 */
148
-			if ($this->util->isBrightColor($color)
149
-				&& !$appIcon instanceof ISimpleFile
150
-				&& $app !== 'core'
151
-			) {
152
-				$appIconFile->negateImage(false);
153
-			}
154
-		} else {
155
-			$appIconFile = new Imagick();
156
-			$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
157
-			$appIconFile->readImageBlob($appIconContent);
158
-		}
159
-		// offset for icon positioning
160
-		$padding = 0.15;
161
-		$border_w = (int)($appIconFile->getImageWidth() * $padding);
162
-		$border_h = (int)($appIconFile->getImageHeight() * $padding);
163
-		$innerWidth = ($appIconFile->getImageWidth() - $border_w * 2);
164
-		$innerHeight = ($appIconFile->getImageHeight() - $border_h * 2);
165
-		$appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
166
-		// center icon
167
-		$offset_w = (int)($size / 2 - $innerWidth / 2);
168
-		$offset_h = (int)($size / 2 - $innerHeight / 2);
169
-
170
-		$finalIconFile = new Imagick();
171
-		$finalIconFile->setBackgroundColor(new ImagickPixel('transparent'));
172
-		$finalIconFile->readImageBlob($background);
173
-		$finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
174
-		$finalIconFile->setImageArtifact('compose:args', '1,0,-0.5,0.5');
175
-		$finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
176
-		$finalIconFile->setImageFormat('png24');
177
-		if (defined('Imagick::INTERPOLATE_BICUBIC') === true) {
178
-			$filter = Imagick::INTERPOLATE_BICUBIC;
179
-		} else {
180
-			$filter = Imagick::FILTER_LANCZOS;
181
-		}
182
-		$finalIconFile->resizeImage($size, $size, $filter, 1, false);
183
-
184
-		$appIconFile->destroy();
185
-		return $finalIconFile;
186
-	}
187
-
188
-	/**
189
-	 * @param string $app app name
190
-	 * @param string $image relative path to svg file in app directory
191
-	 * @return string|false content of a colorized svg file
192
-	 */
193
-	public function colorSvg($app, $image) {
194
-		$imageFile = $this->util->getAppImage($app, $image);
195
-		if ($imageFile === false || $imageFile === '' || !file_exists($imageFile)) {
196
-			return false;
197
-		}
198
-		$svg = file_get_contents($imageFile);
199
-		if ($svg !== false && $svg !== '') {
200
-			$color = $this->util->elementColor($this->themingDefaults->getColorPrimary());
201
-			$svg = $this->util->colorizeSvg($svg, $color);
202
-			return $svg;
203
-		} else {
204
-			return false;
205
-		}
206
-	}
13
+    /**
14
+     * IconBuilder constructor.
15
+     *
16
+     * @param ThemingDefaults $themingDefaults
17
+     * @param Util $util
18
+     * @param ImageManager $imageManager
19
+     */
20
+    public function __construct(
21
+        private ThemingDefaults $themingDefaults,
22
+        private Util $util,
23
+        private ImageManager $imageManager,
24
+    ) {
25
+    }
26
+
27
+    /**
28
+     * @param $app string app name
29
+     * @return string|false image blob
30
+     */
31
+    public function getFavicon($app) {
32
+        if (!$this->imageManager->shouldReplaceIcons()) {
33
+            return false;
34
+        }
35
+        try {
36
+            $favicon = new Imagick();
37
+            $favicon->setFormat('ico');
38
+            $icon = $this->renderAppIcon($app, 128);
39
+            if ($icon === false) {
40
+                return false;
41
+            }
42
+            $icon->setImageFormat('png32');
43
+
44
+            $clone = clone $icon;
45
+            $clone->scaleImage(16, 0);
46
+            $favicon->addImage($clone);
47
+
48
+            $clone = clone $icon;
49
+            $clone->scaleImage(32, 0);
50
+            $favicon->addImage($clone);
51
+
52
+            $clone = clone $icon;
53
+            $clone->scaleImage(64, 0);
54
+            $favicon->addImage($clone);
55
+
56
+            $clone = clone $icon;
57
+            $clone->scaleImage(128, 0);
58
+            $favicon->addImage($clone);
59
+
60
+            $data = $favicon->getImagesBlob();
61
+            $favicon->destroy();
62
+            $icon->destroy();
63
+            $clone->destroy();
64
+            return $data;
65
+        } catch (\ImagickException $e) {
66
+            return false;
67
+        }
68
+    }
69
+
70
+    /**
71
+     * @param $app string app name
72
+     * @return string|false image blob
73
+     */
74
+    public function getTouchIcon($app) {
75
+        try {
76
+            $icon = $this->renderAppIcon($app, 512);
77
+            if ($icon === false) {
78
+                return false;
79
+            }
80
+            $icon->setImageFormat('png32');
81
+            $data = $icon->getImageBlob();
82
+            $icon->destroy();
83
+            return $data;
84
+        } catch (\ImagickException $e) {
85
+            return false;
86
+        }
87
+    }
88
+
89
+    /**
90
+     * Render app icon on themed background color
91
+     * fallback to logo
92
+     *
93
+     * @param string $app app name
94
+     * @param int $size size of the icon in px
95
+     * @return Imagick|false
96
+     */
97
+    public function renderAppIcon($app, $size) {
98
+        $appIcon = $this->util->getAppIcon($app);
99
+        if ($appIcon instanceof ISimpleFile) {
100
+            $appIconContent = $appIcon->getContent();
101
+            $mime = $appIcon->getMimeType();
102
+        } elseif (!file_exists($appIcon)) {
103
+            return false;
104
+        } else {
105
+            $appIconContent = file_get_contents($appIcon);
106
+            $mime = mime_content_type($appIcon);
107
+        }
108
+
109
+        if ($appIconContent === false || $appIconContent === '') {
110
+            return false;
111
+        }
112
+
113
+        $color = $this->themingDefaults->getColorPrimary();
114
+
115
+        // generate background image with rounded corners
116
+        $cornerRadius = 0.2 * $size;
117
+        $background = '<?xml version="1.0" encoding="UTF-8"?>' .
118
+            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" width="' . $size . '" height="' . $size . '" xmlns:xlink="http://www.w3.org/1999/xlink">' .
119
+            '<rect x="0" y="0" rx="' . $cornerRadius . '" ry="' . $cornerRadius . '" width="' . $size . '" height="' . $size . '" style="fill:' . $color . ';" />' .
120
+            '</svg>';
121
+        // resize svg magic as this seems broken in Imagemagick
122
+        if ($mime === 'image/svg+xml' || substr($appIconContent, 0, 4) === '<svg') {
123
+            if (substr($appIconContent, 0, 5) !== '<?xml') {
124
+                $svg = '<?xml version="1.0"?>' . $appIconContent;
125
+            } else {
126
+                $svg = $appIconContent;
127
+            }
128
+            $tmp = new Imagick();
129
+            $tmp->setBackgroundColor(new ImagickPixel('transparent'));
130
+            $tmp->setResolution(72, 72);
131
+            $tmp->readImageBlob($svg);
132
+            $x = $tmp->getImageWidth();
133
+            $y = $tmp->getImageHeight();
134
+            $tmp->destroy();
135
+
136
+            // convert svg to resized image
137
+            $appIconFile = new Imagick();
138
+            $resX = (int)(72 * $size / $x);
139
+            $resY = (int)(72 * $size / $y);
140
+            $appIconFile->setResolution($resX, $resY);
141
+            $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
142
+            $appIconFile->readImageBlob($svg);
143
+
144
+            /**
145
+             * invert app icons for bright primary colors
146
+             * the default nextcloud logo will not be inverted to black
147
+             */
148
+            if ($this->util->isBrightColor($color)
149
+                && !$appIcon instanceof ISimpleFile
150
+                && $app !== 'core'
151
+            ) {
152
+                $appIconFile->negateImage(false);
153
+            }
154
+        } else {
155
+            $appIconFile = new Imagick();
156
+            $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
157
+            $appIconFile->readImageBlob($appIconContent);
158
+        }
159
+        // offset for icon positioning
160
+        $padding = 0.15;
161
+        $border_w = (int)($appIconFile->getImageWidth() * $padding);
162
+        $border_h = (int)($appIconFile->getImageHeight() * $padding);
163
+        $innerWidth = ($appIconFile->getImageWidth() - $border_w * 2);
164
+        $innerHeight = ($appIconFile->getImageHeight() - $border_h * 2);
165
+        $appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
166
+        // center icon
167
+        $offset_w = (int)($size / 2 - $innerWidth / 2);
168
+        $offset_h = (int)($size / 2 - $innerHeight / 2);
169
+
170
+        $finalIconFile = new Imagick();
171
+        $finalIconFile->setBackgroundColor(new ImagickPixel('transparent'));
172
+        $finalIconFile->readImageBlob($background);
173
+        $finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
174
+        $finalIconFile->setImageArtifact('compose:args', '1,0,-0.5,0.5');
175
+        $finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
176
+        $finalIconFile->setImageFormat('png24');
177
+        if (defined('Imagick::INTERPOLATE_BICUBIC') === true) {
178
+            $filter = Imagick::INTERPOLATE_BICUBIC;
179
+        } else {
180
+            $filter = Imagick::FILTER_LANCZOS;
181
+        }
182
+        $finalIconFile->resizeImage($size, $size, $filter, 1, false);
183
+
184
+        $appIconFile->destroy();
185
+        return $finalIconFile;
186
+    }
187
+
188
+    /**
189
+     * @param string $app app name
190
+     * @param string $image relative path to svg file in app directory
191
+     * @return string|false content of a colorized svg file
192
+     */
193
+    public function colorSvg($app, $image) {
194
+        $imageFile = $this->util->getAppImage($app, $image);
195
+        if ($imageFile === false || $imageFile === '' || !file_exists($imageFile)) {
196
+            return false;
197
+        }
198
+        $svg = file_get_contents($imageFile);
199
+        if ($svg !== false && $svg !== '') {
200
+            $color = $this->util->elementColor($this->themingDefaults->getColorPrimary());
201
+            $svg = $this->util->colorizeSvg($svg, $color);
202
+            return $svg;
203
+        } else {
204
+            return false;
205
+        }
206
+    }
207 207
 }
Please login to merge, or discard this patch.
apps/theming/tests/IconBuilderTest.php 1 patch
Indentation   +176 added lines, -176 removed lines patch added patch discarded remove patch
@@ -18,180 +18,180 @@
 block discarded – undo
18 18
 
19 19
 class IconBuilderTest extends TestCase {
20 20
 
21
-	/** @var IConfig */
22
-	protected $config;
23
-	/** @var AppData */
24
-	protected $appData;
25
-	/** @var ThemingDefaults */
26
-	protected $themingDefaults;
27
-	/** @var Util */
28
-	protected $util;
29
-	/** @var ImageManager */
30
-	protected $imageManager;
31
-	/** @var IconBuilder */
32
-	protected $iconBuilder;
33
-	/** @var IAppManager */
34
-	protected $appManager;
35
-
36
-	protected function setUp(): void {
37
-		parent::setUp();
38
-
39
-		$this->config = $this->createMock(IConfig::class);
40
-		$this->appData = $this->createMock(AppData::class);
41
-		$this->themingDefaults = $this->createMock(ThemingDefaults::class);
42
-		$this->appManager = $this->createMock(IAppManager::class);
43
-		$this->imageManager = $this->createMock(ImageManager::class);
44
-		$this->util = new Util($this->createMock(ServerVersion::class), $this->config, $this->appManager, $this->appData, $this->imageManager);
45
-		$this->iconBuilder = new IconBuilder($this->themingDefaults, $this->util, $this->imageManager);
46
-	}
47
-
48
-	private function checkImagick() {
49
-		if (!extension_loaded('imagick')) {
50
-			$this->markTestSkipped('Imagemagick is required for dynamic icon generation.');
51
-		}
52
-		$checkImagick = new \Imagick();
53
-		if (count($checkImagick->queryFormats('SVG')) < 1) {
54
-			$this->markTestSkipped('No SVG provider present.');
55
-		}
56
-		if (count($checkImagick->queryFormats('PNG')) < 1) {
57
-			$this->markTestSkipped('No PNG provider present.');
58
-		}
59
-	}
60
-
61
-	public function dataRenderAppIcon() {
62
-		return [
63
-			['core', '#0082c9', 'touch-original.png'],
64
-			['core', '#FF0000', 'touch-core-red.png'],
65
-			['testing', '#FF0000', 'touch-testing-red.png'],
66
-			['comments', '#0082c9', 'touch-comments.png'],
67
-			['core', '#0082c9', 'touch-original-png.png'],
68
-		];
69
-	}
70
-
71
-	/**
72
-	 * @dataProvider dataRenderAppIcon
73
-	 * @param $app
74
-	 * @param $color
75
-	 * @param $file
76
-	 */
77
-	public function testRenderAppIcon($app, $color, $file): void {
78
-		$this->checkImagick();
79
-		$this->themingDefaults->expects($this->once())
80
-			->method('getColorPrimary')
81
-			->willReturn($color);
82
-		$this->appData->expects($this->once())
83
-			->method('getFolder')
84
-			->with('global/images')
85
-			->willThrowException(new NotFoundException());
86
-
87
-		$expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
88
-		$icon = $this->iconBuilder->renderAppIcon($app, 512);
89
-
90
-		$this->assertEquals(true, $icon->valid());
91
-		$this->assertEquals(512, $icon->getImageWidth());
92
-		$this->assertEquals(512, $icon->getImageHeight());
93
-		$this->assertEquals($icon, $expectedIcon);
94
-		$icon->destroy();
95
-		$expectedIcon->destroy();
96
-		// FIXME: We may need some comparison of the generated and the test images
97
-		// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
98
-	}
99
-
100
-	/**
101
-	 * @dataProvider dataRenderAppIcon
102
-	 * @param $app
103
-	 * @param $color
104
-	 * @param $file
105
-	 */
106
-	public function testGetTouchIcon($app, $color, $file): void {
107
-		$this->checkImagick();
108
-		$this->themingDefaults->expects($this->once())
109
-			->method('getColorPrimary')
110
-			->willReturn($color);
111
-		$this->appData->expects($this->once())
112
-			->method('getFolder')
113
-			->with('global/images')
114
-			->willThrowException(new NotFoundException());
115
-
116
-		$expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
117
-		$icon = new \Imagick();
118
-		$icon->readImageBlob($this->iconBuilder->getTouchIcon($app));
119
-
120
-		$this->assertEquals(true, $icon->valid());
121
-		$this->assertEquals(512, $icon->getImageWidth());
122
-		$this->assertEquals(512, $icon->getImageHeight());
123
-		$this->assertEquals($icon, $expectedIcon);
124
-		$icon->destroy();
125
-		$expectedIcon->destroy();
126
-		// FIXME: We may need some comparison of the generated and the test images
127
-		// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
128
-	}
129
-
130
-	/**
131
-	 * @dataProvider dataRenderAppIcon
132
-	 * @param $app
133
-	 * @param $color
134
-	 * @param $file
135
-	 */
136
-	public function testGetFavicon($app, $color, $file): void {
137
-		$this->checkImagick();
138
-		$this->imageManager->expects($this->once())
139
-			->method('shouldReplaceIcons')
140
-			->willReturn(true);
141
-		$this->themingDefaults->expects($this->once())
142
-			->method('getColorPrimary')
143
-			->willReturn($color);
144
-		$this->appData->expects($this->once())
145
-			->method('getFolder')
146
-			->with('global/images')
147
-			->willThrowException(new NotFoundException());
148
-
149
-		$expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
150
-		$actualIcon = $this->iconBuilder->getFavicon($app);
151
-
152
-		$icon = new \Imagick();
153
-		$icon->setFormat('ico');
154
-		$icon->readImageBlob($actualIcon);
155
-
156
-		$this->assertEquals(true, $icon->valid());
157
-		$this->assertEquals(128, $icon->getImageWidth());
158
-		$this->assertEquals(128, $icon->getImageHeight());
159
-		$icon->destroy();
160
-		$expectedIcon->destroy();
161
-		// FIXME: We may need some comparison of the generated and the test images
162
-		// cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
163
-	}
164
-
165
-	public function testGetFaviconNotFound(): void {
166
-		$this->checkImagick();
167
-		$util = $this->createMock(Util::class);
168
-		$iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
169
-		$this->imageManager->expects($this->once())
170
-			->method('shouldReplaceIcons')
171
-			->willReturn(true);
172
-		$util->expects($this->once())
173
-			->method('getAppIcon')
174
-			->willReturn('notexistingfile');
175
-		$this->assertFalse($iconBuilder->getFavicon('noapp'));
176
-	}
177
-
178
-	public function testGetTouchIconNotFound(): void {
179
-		$this->checkImagick();
180
-		$util = $this->createMock(Util::class);
181
-		$iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
182
-		$util->expects($this->once())
183
-			->method('getAppIcon')
184
-			->willReturn('notexistingfile');
185
-		$this->assertFalse($iconBuilder->getTouchIcon('noapp'));
186
-	}
187
-
188
-	public function testColorSvgNotFound(): void {
189
-		$this->checkImagick();
190
-		$util = $this->createMock(Util::class);
191
-		$iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
192
-		$util->expects($this->once())
193
-			->method('getAppImage')
194
-			->willReturn('notexistingfile');
195
-		$this->assertFalse($iconBuilder->colorSvg('noapp', 'noimage'));
196
-	}
21
+    /** @var IConfig */
22
+    protected $config;
23
+    /** @var AppData */
24
+    protected $appData;
25
+    /** @var ThemingDefaults */
26
+    protected $themingDefaults;
27
+    /** @var Util */
28
+    protected $util;
29
+    /** @var ImageManager */
30
+    protected $imageManager;
31
+    /** @var IconBuilder */
32
+    protected $iconBuilder;
33
+    /** @var IAppManager */
34
+    protected $appManager;
35
+
36
+    protected function setUp(): void {
37
+        parent::setUp();
38
+
39
+        $this->config = $this->createMock(IConfig::class);
40
+        $this->appData = $this->createMock(AppData::class);
41
+        $this->themingDefaults = $this->createMock(ThemingDefaults::class);
42
+        $this->appManager = $this->createMock(IAppManager::class);
43
+        $this->imageManager = $this->createMock(ImageManager::class);
44
+        $this->util = new Util($this->createMock(ServerVersion::class), $this->config, $this->appManager, $this->appData, $this->imageManager);
45
+        $this->iconBuilder = new IconBuilder($this->themingDefaults, $this->util, $this->imageManager);
46
+    }
47
+
48
+    private function checkImagick() {
49
+        if (!extension_loaded('imagick')) {
50
+            $this->markTestSkipped('Imagemagick is required for dynamic icon generation.');
51
+        }
52
+        $checkImagick = new \Imagick();
53
+        if (count($checkImagick->queryFormats('SVG')) < 1) {
54
+            $this->markTestSkipped('No SVG provider present.');
55
+        }
56
+        if (count($checkImagick->queryFormats('PNG')) < 1) {
57
+            $this->markTestSkipped('No PNG provider present.');
58
+        }
59
+    }
60
+
61
+    public function dataRenderAppIcon() {
62
+        return [
63
+            ['core', '#0082c9', 'touch-original.png'],
64
+            ['core', '#FF0000', 'touch-core-red.png'],
65
+            ['testing', '#FF0000', 'touch-testing-red.png'],
66
+            ['comments', '#0082c9', 'touch-comments.png'],
67
+            ['core', '#0082c9', 'touch-original-png.png'],
68
+        ];
69
+    }
70
+
71
+    /**
72
+     * @dataProvider dataRenderAppIcon
73
+     * @param $app
74
+     * @param $color
75
+     * @param $file
76
+     */
77
+    public function testRenderAppIcon($app, $color, $file): void {
78
+        $this->checkImagick();
79
+        $this->themingDefaults->expects($this->once())
80
+            ->method('getColorPrimary')
81
+            ->willReturn($color);
82
+        $this->appData->expects($this->once())
83
+            ->method('getFolder')
84
+            ->with('global/images')
85
+            ->willThrowException(new NotFoundException());
86
+
87
+        $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
88
+        $icon = $this->iconBuilder->renderAppIcon($app, 512);
89
+
90
+        $this->assertEquals(true, $icon->valid());
91
+        $this->assertEquals(512, $icon->getImageWidth());
92
+        $this->assertEquals(512, $icon->getImageHeight());
93
+        $this->assertEquals($icon, $expectedIcon);
94
+        $icon->destroy();
95
+        $expectedIcon->destroy();
96
+        // FIXME: We may need some comparison of the generated and the test images
97
+        // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
98
+    }
99
+
100
+    /**
101
+     * @dataProvider dataRenderAppIcon
102
+     * @param $app
103
+     * @param $color
104
+     * @param $file
105
+     */
106
+    public function testGetTouchIcon($app, $color, $file): void {
107
+        $this->checkImagick();
108
+        $this->themingDefaults->expects($this->once())
109
+            ->method('getColorPrimary')
110
+            ->willReturn($color);
111
+        $this->appData->expects($this->once())
112
+            ->method('getFolder')
113
+            ->with('global/images')
114
+            ->willThrowException(new NotFoundException());
115
+
116
+        $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
117
+        $icon = new \Imagick();
118
+        $icon->readImageBlob($this->iconBuilder->getTouchIcon($app));
119
+
120
+        $this->assertEquals(true, $icon->valid());
121
+        $this->assertEquals(512, $icon->getImageWidth());
122
+        $this->assertEquals(512, $icon->getImageHeight());
123
+        $this->assertEquals($icon, $expectedIcon);
124
+        $icon->destroy();
125
+        $expectedIcon->destroy();
126
+        // FIXME: We may need some comparison of the generated and the test images
127
+        // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
128
+    }
129
+
130
+    /**
131
+     * @dataProvider dataRenderAppIcon
132
+     * @param $app
133
+     * @param $color
134
+     * @param $file
135
+     */
136
+    public function testGetFavicon($app, $color, $file): void {
137
+        $this->checkImagick();
138
+        $this->imageManager->expects($this->once())
139
+            ->method('shouldReplaceIcons')
140
+            ->willReturn(true);
141
+        $this->themingDefaults->expects($this->once())
142
+            ->method('getColorPrimary')
143
+            ->willReturn($color);
144
+        $this->appData->expects($this->once())
145
+            ->method('getFolder')
146
+            ->with('global/images')
147
+            ->willThrowException(new NotFoundException());
148
+
149
+        $expectedIcon = new \Imagick(realpath(__DIR__) . '/data/' . $file);
150
+        $actualIcon = $this->iconBuilder->getFavicon($app);
151
+
152
+        $icon = new \Imagick();
153
+        $icon->setFormat('ico');
154
+        $icon->readImageBlob($actualIcon);
155
+
156
+        $this->assertEquals(true, $icon->valid());
157
+        $this->assertEquals(128, $icon->getImageWidth());
158
+        $this->assertEquals(128, $icon->getImageHeight());
159
+        $icon->destroy();
160
+        $expectedIcon->destroy();
161
+        // FIXME: We may need some comparison of the generated and the test images
162
+        // cloud be something like $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1])
163
+    }
164
+
165
+    public function testGetFaviconNotFound(): void {
166
+        $this->checkImagick();
167
+        $util = $this->createMock(Util::class);
168
+        $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
169
+        $this->imageManager->expects($this->once())
170
+            ->method('shouldReplaceIcons')
171
+            ->willReturn(true);
172
+        $util->expects($this->once())
173
+            ->method('getAppIcon')
174
+            ->willReturn('notexistingfile');
175
+        $this->assertFalse($iconBuilder->getFavicon('noapp'));
176
+    }
177
+
178
+    public function testGetTouchIconNotFound(): void {
179
+        $this->checkImagick();
180
+        $util = $this->createMock(Util::class);
181
+        $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
182
+        $util->expects($this->once())
183
+            ->method('getAppIcon')
184
+            ->willReturn('notexistingfile');
185
+        $this->assertFalse($iconBuilder->getTouchIcon('noapp'));
186
+    }
187
+
188
+    public function testColorSvgNotFound(): void {
189
+        $this->checkImagick();
190
+        $util = $this->createMock(Util::class);
191
+        $iconBuilder = new IconBuilder($this->themingDefaults, $util, $this->imageManager);
192
+        $util->expects($this->once())
193
+            ->method('getAppImage')
194
+            ->willReturn('notexistingfile');
195
+        $this->assertFalse($iconBuilder->colorSvg('noapp', 'noimage'));
196
+    }
197 197
 }
Please login to merge, or discard this patch.