You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
7.2 KiB

  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  7. * @author Julius Haertl <jus@bitgrid.net>
  8. * @author Julius Härtl <jus@bitgrid.net>
  9. * @author Michael Weimann <mail@michael-weimann.eu>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. *
  12. * @license GNU AGPL version 3 or any later version
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License as
  16. * published by the Free Software Foundation, either version 3 of the
  17. * License, or (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. *
  27. */
  28. namespace OCA\Theming;
  29. use OCP\Files\IAppData;
  30. use OCP\Files\NotFoundException;
  31. use OCP\Files\NotPermittedException;
  32. use OCP\Files\SimpleFS\ISimpleFile;
  33. use OCP\Files\SimpleFS\ISimpleFolder;
  34. use OCP\ICacheFactory;
  35. use OCP\IConfig;
  36. use OCP\ILogger;
  37. use OCP\IURLGenerator;
  38. class ImageManager {
  39. /** @var IConfig */
  40. private $config;
  41. /** @var IAppData */
  42. private $appData;
  43. /** @var IURLGenerator */
  44. private $urlGenerator;
  45. /** @var array */
  46. private $supportedImageKeys = ['background', 'logo', 'logoheader', 'favicon'];
  47. /** @var ICacheFactory */
  48. private $cacheFactory;
  49. /** @var ILogger */
  50. private $logger;
  51. /**
  52. * ImageManager constructor.
  53. *
  54. * @param IConfig $config
  55. * @param IAppData $appData
  56. * @param IURLGenerator $urlGenerator
  57. * @param ICacheFactory $cacheFactory
  58. * @param ILogger $logger
  59. */
  60. public function __construct(IConfig $config,
  61. IAppData $appData,
  62. IURLGenerator $urlGenerator,
  63. ICacheFactory $cacheFactory,
  64. ILogger $logger
  65. ) {
  66. $this->config = $config;
  67. $this->appData = $appData;
  68. $this->urlGenerator = $urlGenerator;
  69. $this->cacheFactory = $cacheFactory;
  70. $this->logger = $logger;
  71. }
  72. public function getImageUrl(string $key, bool $useSvg = true): string {
  73. $cacheBusterCounter = $this->config->getAppValue('theming', 'cachebuster', '0');
  74. try {
  75. $image = $this->getImage($key, $useSvg);
  76. return $this->urlGenerator->linkToRoute('theming.Theming.getImage', [ 'key' => $key ]) . '?v=' . $cacheBusterCounter;
  77. } catch (NotFoundException $e) {
  78. }
  79. switch ($key) {
  80. case 'logo':
  81. case 'logoheader':
  82. case 'favicon':
  83. return $this->urlGenerator->imagePath('core', 'logo/logo.png') . '?v=' . $cacheBusterCounter;
  84. case 'background':
  85. return $this->urlGenerator->imagePath('core', 'background.png') . '?v=' . $cacheBusterCounter;
  86. }
  87. }
  88. public function getImageUrlAbsolute(string $key, bool $useSvg = true): string {
  89. return $this->urlGenerator->getAbsoluteURL($this->getImageUrl($key, $useSvg));
  90. }
  91. /**
  92. * @param string $key
  93. * @param bool $useSvg
  94. * @return ISimpleFile
  95. * @throws NotFoundException
  96. * @throws NotPermittedException
  97. */
  98. public function getImage(string $key, bool $useSvg = true): ISimpleFile {
  99. $pngFile = null;
  100. $logo = $this->config->getAppValue('theming', $key . 'Mime', false);
  101. $folder = $this->appData->getFolder('images');
  102. if ($logo === false || !$folder->fileExists($key)) {
  103. throw new NotFoundException();
  104. }
  105. if (!$useSvg && $this->shouldReplaceIcons()) {
  106. if (!$folder->fileExists($key . '.png')) {
  107. try {
  108. $finalIconFile = new \Imagick();
  109. $finalIconFile->setBackgroundColor('none');
  110. $finalIconFile->readImageBlob($folder->getFile($key)->getContent());
  111. $finalIconFile->setImageFormat('png32');
  112. $pngFile = $folder->newFile($key . '.png');
  113. $pngFile->putContent($finalIconFile->getImageBlob());
  114. } catch (\ImagickException $e) {
  115. $this->logger->info('The image was requested to be no SVG file, but converting it to PNG failed: ' . $e->getMessage());
  116. $pngFile = null;
  117. }
  118. } else {
  119. $pngFile = $folder->getFile($key . '.png');
  120. }
  121. }
  122. if ($pngFile !== null) {
  123. return $pngFile;
  124. }
  125. return $folder->getFile($key);
  126. }
  127. public function getCustomImages(): array {
  128. $images = [];
  129. foreach ($this->supportedImageKeys as $key) {
  130. $images[$key] = [
  131. 'mime' => $this->config->getAppValue('theming', $key . 'Mime', ''),
  132. 'url' => $this->getImageUrl($key),
  133. ];
  134. }
  135. return $images;
  136. }
  137. /**
  138. * Get folder for current theming files
  139. *
  140. * @return ISimpleFolder
  141. * @throws NotPermittedException
  142. */
  143. public function getCacheFolder(): ISimpleFolder {
  144. $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
  145. try {
  146. $folder = $this->appData->getFolder($cacheBusterValue);
  147. } catch (NotFoundException $e) {
  148. $folder = $this->appData->newFolder($cacheBusterValue);
  149. $this->cleanup();
  150. }
  151. return $folder;
  152. }
  153. /**
  154. * Get a file from AppData
  155. *
  156. * @param string $filename
  157. * @throws NotFoundException
  158. * @return \OCP\Files\SimpleFS\ISimpleFile
  159. * @throws NotPermittedException
  160. */
  161. public function getCachedImage(string $filename): ISimpleFile {
  162. $currentFolder = $this->getCacheFolder();
  163. return $currentFolder->getFile($filename);
  164. }
  165. /**
  166. * Store a file for theming in AppData
  167. *
  168. * @param string $filename
  169. * @param string $data
  170. * @return \OCP\Files\SimpleFS\ISimpleFile
  171. * @throws NotFoundException
  172. * @throws NotPermittedException
  173. */
  174. public function setCachedImage(string $filename, string $data): ISimpleFile {
  175. $currentFolder = $this->getCacheFolder();
  176. if ($currentFolder->fileExists($filename)) {
  177. $file = $currentFolder->getFile($filename);
  178. } else {
  179. $file = $currentFolder->newFile($filename);
  180. }
  181. $file->putContent($data);
  182. return $file;
  183. }
  184. public function delete(string $key) {
  185. /* ignore exceptions, since we don't want to fail hard if something goes wrong during cleanup */
  186. try {
  187. $file = $this->appData->getFolder('images')->getFile($key);
  188. $file->delete();
  189. } catch (NotFoundException $e) {
  190. } catch (NotPermittedException $e) {
  191. }
  192. try {
  193. $file = $this->appData->getFolder('images')->getFile($key . '.png');
  194. $file->delete();
  195. } catch (NotFoundException $e) {
  196. } catch (NotPermittedException $e) {
  197. }
  198. }
  199. /**
  200. * remove cached files that are not required any longer
  201. *
  202. * @throws NotPermittedException
  203. * @throws NotFoundException
  204. */
  205. public function cleanup() {
  206. $currentFolder = $this->getCacheFolder();
  207. $folders = $this->appData->getDirectoryListing();
  208. foreach ($folders as $folder) {
  209. if ($folder->getName() !== 'images' && $folder->getName() !== $currentFolder->getName()) {
  210. $folder->delete();
  211. }
  212. }
  213. }
  214. /**
  215. * Check if Imagemagick is enabled and if SVG is supported
  216. * otherwise we can't render custom icons
  217. *
  218. * @return bool
  219. */
  220. public function shouldReplaceIcons() {
  221. $cache = $this->cacheFactory->createDistributed('theming-' . $this->urlGenerator->getBaseUrl());
  222. if ($value = $cache->get('shouldReplaceIcons')) {
  223. return (bool)$value;
  224. }
  225. $value = false;
  226. if (extension_loaded('imagick')) {
  227. if (count(\Imagick::queryFormats('SVG')) >= 1) {
  228. $value = true;
  229. }
  230. }
  231. $cache->set('shouldReplaceIcons', $value);
  232. return $value;
  233. }
  234. }