Browse Source

Implement guest avatar endpoint

Signed-off-by: Michael Weimann <mail@michael-weimann.eu>
Michael Weimann 5 years ago
parent
commit
bf1253cb49

+ 0 - 1
core/Controller/AvatarController.php

@@ -42,7 +42,6 @@ use OCP\IL10N;
 use OCP\IRequest;
 use OCP\IUserManager;
 use OCP\IUserSession;
-use OCP\AppFramework\Http\DataResponse;
 
 /**
  * Class AvatarController

+ 107 - 0
core/Controller/GuestAvatarController.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace OC\Core\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\FileDisplayResponse;
+use OCP\IAvatarManager;
+use OCP\ILogger;
+use OCP\IRequest;
+
+/**
+ * This controller handles guest avatar requests.
+ */
+class GuestAvatarController extends Controller {
+
+	/**
+	 * @var ILogger
+	 */
+	private $logger;
+
+	/**
+	 * @var IAvatarManager
+	 */
+	private $avatarManager;
+
+	/**
+	 * GuestAvatarController constructor.
+	 *
+	 * @param $appName
+	 * @param IRequest $request
+	 * @param IAvatarManager $avatarManager
+	 * @param ILogger $logger
+	 */
+	public function __construct(
+		$appName,
+		IRequest $request,
+		IAvatarManager $avatarManager,
+		ILogger $logger
+	) {
+		parent::__construct($appName, $request);
+		$this->avatarManager = $avatarManager;
+		$this->logger = $logger;
+	}
+
+	/**
+	 * Returns a guest avatar image response.
+	 *
+	 * @PublicPage
+	 * @NoCSRFRequired
+	 *
+	 * @param string $guestName The guest name, e.g. "Albert"
+	 * @param string $size The desired avatar size, e.g. 64 for 64x64px
+	 * @return FileDisplayResponse|Http\Response
+	 */
+	public function getAvatar($guestName, $size) {
+		$size = (int) $size;
+
+		// min/max size
+		if ($size > 2048) {
+			$size = 2048;
+		} elseif ($size <= 0) {
+			$size = 64;
+		}
+
+		try {
+			$avatar = $this->avatarManager->getGuestAvatar($guestName);
+			$avatarFile = $avatar->getFile($size);
+
+			$resp = new FileDisplayResponse(
+				$avatarFile,
+				$avatar->isCustomAvatar() ? Http::STATUS_OK : Http::STATUS_CREATED,
+				['Content-Type' => $avatarFile->getMimeType()]
+			);
+		} catch (\Exception $e) {
+			$this->logger->error('error while creating guest avatar', [
+				'err' => $e,
+			]);
+			$resp = new Http\Response();
+			$resp->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
+			return $resp;
+		}
+
+		// Cache for 30 minutes
+		$resp->cacheFor(1800);
+		return $resp;
+	}
+}

+ 1 - 0
core/routes.php

@@ -46,6 +46,7 @@ $application->registerRoutes($this, [
 		['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'],
 		['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'],
 		['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'],
+		['name' => 'GuestAvatar#getAvatar', 'url' => '/avatar/guest/{guestName}/{size}', 'verb' => 'GET'],
 		['name' => 'CSRFToken#index', 'url' => '/csrftoken', 'verb' => 'GET'],
 		['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'],
 		['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],

+ 6 - 2
lib/composer/composer/autoload_classmap.php

@@ -221,6 +221,7 @@ return array(
     'OCP\\Files\\SimpleFS\\ISimpleFile' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFile.php',
     'OCP\\Files\\SimpleFS\\ISimpleFolder' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleFolder.php',
     'OCP\\Files\\SimpleFS\\ISimpleRoot' => $baseDir . '/lib/public/Files/SimpleFS/ISimpleRoot.php',
+    'OCP\\Files\\SimpleFS\\InMemoryFile' => $baseDir . '/lib/public/Files/SimpleFS/InMemoryFile.php',
     'OCP\\Files\\Storage' => $baseDir . '/lib/public/Files/Storage.php',
     'OCP\\Files\\StorageAuthException' => $baseDir . '/lib/public/Files/StorageAuthException.php',
     'OCP\\Files\\StorageBadConfigException' => $baseDir . '/lib/public/Files/StorageBadConfigException.php',
@@ -510,8 +511,10 @@ return array(
     'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php',
     'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php',
     'OC\\Authentication\\TwoFactorAuth\\Registry' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Registry.php',
-    'OC\\Avatar' => $baseDir . '/lib/private/Avatar.php',
-    'OC\\AvatarManager' => $baseDir . '/lib/private/AvatarManager.php',
+    'OC\\Avatar\\Avatar' => $baseDir . '/lib/private/Avatar/Avatar.php',
+    'OC\\Avatar\\AvatarManager' => $baseDir . '/lib/private/Avatar/AvatarManager.php',
+    'OC\\Avatar\\GuestAvatar' => $baseDir . '/lib/private/Avatar/GuestAvatar.php',
+    'OC\\Avatar\\UserAvatar' => $baseDir . '/lib/private/Avatar/UserAvatar.php',
     'OC\\BackgroundJob\\Job' => $baseDir . '/lib/private/BackgroundJob/Job.php',
     'OC\\BackgroundJob\\JobList' => $baseDir . '/lib/private/BackgroundJob/JobList.php',
     'OC\\BackgroundJob\\Legacy\\QueuedJob' => $baseDir . '/lib/private/BackgroundJob/Legacy/QueuedJob.php',
@@ -648,6 +651,7 @@ return array(
     'OC\\Core\\Controller\\ClientFlowLoginController' => $baseDir . '/core/Controller/ClientFlowLoginController.php',
     'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php',
     'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php',
+    'OC\\Core\\Controller\\GuestAvatarController' => $baseDir . '/core/Controller/GuestAvatarController.php',
     'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php',
     'OC\\Core\\Controller\\LoginController' => $baseDir . '/core/Controller/LoginController.php',
     'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php',

+ 6 - 2
lib/composer/composer/autoload_static.php

@@ -251,6 +251,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Files\\SimpleFS\\ISimpleFile' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFile.php',
         'OCP\\Files\\SimpleFS\\ISimpleFolder' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleFolder.php',
         'OCP\\Files\\SimpleFS\\ISimpleRoot' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/ISimpleRoot.php',
+        'OCP\\Files\\SimpleFS\\InMemoryFile' => __DIR__ . '/../../..' . '/lib/public/Files/SimpleFS/InMemoryFile.php',
         'OCP\\Files\\Storage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage.php',
         'OCP\\Files\\StorageAuthException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageAuthException.php',
         'OCP\\Files\\StorageBadConfigException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageBadConfigException.php',
@@ -540,8 +541,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Authentication\\TwoFactorAuth\\ProviderManager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderManager.php',
         'OC\\Authentication\\TwoFactorAuth\\ProviderSet' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/ProviderSet.php',
         'OC\\Authentication\\TwoFactorAuth\\Registry' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Registry.php',
-        'OC\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar.php',
-        'OC\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/AvatarManager.php',
+        'OC\\Avatar\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/Avatar.php',
+        'OC\\Avatar\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/Avatar/AvatarManager.php',
+        'OC\\Avatar\\GuestAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/GuestAvatar.php',
+        'OC\\Avatar\\UserAvatar' => __DIR__ . '/../../..' . '/lib/private/Avatar/UserAvatar.php',
         'OC\\BackgroundJob\\Job' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Job.php',
         'OC\\BackgroundJob\\JobList' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/JobList.php',
         'OC\\BackgroundJob\\Legacy\\QueuedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/Legacy/QueuedJob.php',
@@ -678,6 +681,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Core\\Controller\\ClientFlowLoginController' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginController.php',
         'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php',
         'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php',
+        'OC\\Core\\Controller\\GuestAvatarController' => __DIR__ . '/../../..' . '/core/Controller/GuestAvatarController.php',
         'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php',
         'OC\\Core\\Controller\\LoginController' => __DIR__ . '/../../..' . '/core/Controller/LoginController.php',
         'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php',

+ 52 - 235
lib/private/Avatar.php → lib/private/Avatar/Avatar.php

@@ -1,4 +1,5 @@
 <?php
+declare(strict_types=1);
 /**
  * @copyright Copyright (c) 2016, ownCloud, Inc.
  * @copyright 2018 John Molakvoæ <skjnldsv@protonmail.com>
@@ -29,36 +30,22 @@
  *
  */
 
-namespace OC;
+namespace OC\Avatar;
 
+use OC\Color;
 use OCP\Files\NotFoundException;
-use OCP\Files\NotPermittedException;
-use OCP\Files\SimpleFS\ISimpleFile;
-use OCP\Files\SimpleFS\ISimpleFolder;
 use OCP\IAvatar;
-use OCP\IConfig;
-use OCP\IImage;
-use OCP\IL10N;
 use OCP\ILogger;
-use OC\User\User;
 use OC_Image;
 use Imagick;
 
 /**
  * This class gets and sets users avatars.
  */
+abstract class Avatar implements IAvatar {
 
-class Avatar implements IAvatar {
-	/** @var ISimpleFolder */
-	private $folder;
-	/** @var IL10N */
-	private $l;
-	/** @var User */
-	private $user;
 	/** @var ILogger  */
-	private $logger;
-	/** @var IConfig */
-	private $config;
+	protected $logger;
 
 	/**
 	 * https://github.com/sebdesign/cap-height -- for 500px height
@@ -77,216 +64,50 @@ class Avatar implements IAvatar {
 		</svg>';
 
 	/**
-	 * constructor
+	 * The base avatar constructor.
 	 *
-	 * @param ISimpleFolder $folder The folder where the avatars are
-	 * @param IL10N $l
-	 * @param User $user
-	 * @param ILogger $logger
-	 * @param IConfig $config
+	 * @param ILogger $logger The logger
 	 */
-	public function __construct(ISimpleFolder $folder,
-								IL10N $l,
-								$user,
-								ILogger $logger,
-								IConfig $config) {
-		$this->folder = $folder;
-		$this->l = $l;
-		$this->user = $user;
+	public function __construct(ILogger $logger) {
 		$this->logger = $logger;
-		$this->config = $config;
 	}
 
 	/**
-	 * @inheritdoc
-	 */
-	public function get($size = 64) {
-		try {
-			$file = $this->getFile($size);
-		} catch (NotFoundException $e) {
-			return false;
-		}
-
-		$avatar = new OC_Image();
-		$avatar->loadFromData($file->getContent());
-		return $avatar;
-	}
-
-	/**
-	 * Check if an avatar exists for the user
+	 * Returns the user display name.
 	 *
-	 * @return bool
+	 * @return string
 	 */
-	public function exists() {
-
-		return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png');
-	}
+	abstract public function getDisplayName(): string;
 
 	/**
-	 * Check if the avatar of a user is a custom uploaded one
+	 * Returns the first letter of the display name, or "?" if no name given.
 	 *
-	 * @return bool
-	 */
-	public function isCustomAvatar(): bool {
-		return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true';
-	}
-
-	/**
-	 * sets the users avatar
-	 * @param IImage|resource|string $data An image object, imagedata or path to set a new avatar
-	 * @throws \Exception if the provided file is not a jpg or png image
-	 * @throws \Exception if the provided image is not valid
-	 * @throws NotSquareException if the image is not square
-	 * @return void
+	 * @return string
 	 */
-	public function set($data) {
-
-		if ($data instanceof IImage) {
-			$img = $data;
-			$data = $img->data();
+	private function getAvatarLetter(): string {
+		$displayName = $this->getDisplayName();
+		if (empty($displayName) === true) {
+			return '?';
 		} else {
-			$img = new OC_Image();
-			if (is_resource($data) && get_resource_type($data) === "gd") {
-				$img->setResource($data);
-			} elseif (is_resource($data)) {
-				$img->loadFromFileHandle($data);
-			} else {
-				try {
-					// detect if it is a path or maybe the images as string
-					$result = @realpath($data);
-					if ($result === false || $result === null) {
-						$img->loadFromData($data);
-					} else {
-						$img->loadFromFile($data);
-					}
-				} catch (\Error $e) {
-					$img->loadFromData($data);
-				}
-			}
-		}
-		$type = substr($img->mimeType(), -3);
-		if ($type === 'peg') {
-			$type = 'jpg';
-		}
-		if ($type !== 'jpg' && $type !== 'png') {
-			throw new \Exception($this->l->t('Unknown filetype'));
-		}
-
-		if (!$img->valid()) {
-			throw new \Exception($this->l->t('Invalid image'));
-		}
-
-		if (!($img->height() === $img->width())) {
-			throw new NotSquareException($this->l->t('Avatar image is not square'));
-		}
-
-		$this->remove();
-		$file = $this->folder->newFile('avatar.' . $type);
-		$file->putContent($data);
-
-		try {
-			$generated = $this->folder->getFile('generated');
-			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'false');
-			$generated->delete();
-		} catch (NotFoundException $e) {
-			//
-		}
-		$this->user->triggerChange('avatar', $file);
-	}
-
-	/**
-	 * remove the users avatar
-	 * @return void
-	 */
-	public function remove() {
-		$avatars = $this->folder->getDirectoryListing();
-
-		$this->config->setUserValue($this->user->getUID(), 'avatar', 'version',
-			(int) $this->config->getUserValue($this->user->getUID(), 'avatar', 'version', 0) + 1);
-
-		foreach ($avatars as $avatar) {
-			$avatar->delete();
+			return mb_strtoupper(mb_substr($displayName, 0, 1), 'UTF-8');
 		}
-		$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
-		$this->user->triggerChange('avatar', '');
 	}
 
 	/**
 	 * @inheritdoc
 	 */
-	public function getFile($size) {
-		try {
-			$ext = $this->getExtension();
-		} catch (NotFoundException $e) {
-			if (!$data = $this->generateAvatarFromSvg(1024)) {
-				$data = $this->generateAvatar($this->user->getDisplayName(), 1024);
-			}
-			$avatar = $this->folder->newFile('avatar.png');
-			$avatar->putContent($data);
-			$ext = 'png';
-
-			$this->folder->newFile('generated');
-			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
-		}
-
-		if ($size === -1) {
-			$path = 'avatar.' . $ext;
-		} else {
-			$path = 'avatar.' . $size . '.' . $ext;
-		}
+	public function get($size = 64) {
+		$size = (int) $size;
 
 		try {
-			$file = $this->folder->getFile($path);
+			$file = $this->getFile($size);
 		} catch (NotFoundException $e) {
-			if ($size <= 0) {
-				throw new NotFoundException;
-			}
-
-			if ($this->folder->fileExists('generated')) {
-				if (!$data = $this->generateAvatarFromSvg($size)) {
-					$data = $this->generateAvatar($this->user->getDisplayName(), $size);
-				}
-
-			} else {
-				$avatar = new OC_Image();
-				/** @var ISimpleFile $file */
-				$file = $this->folder->getFile('avatar.' . $ext);
-				$avatar->loadFromData($file->getContent());
-				$avatar->resize($size);
-				$data = $avatar->data();
-			}
-
-			try {
-				$file = $this->folder->newFile($path);
-				$file->putContent($data);
-			} catch (NotPermittedException $e) {
-				$this->logger->error('Failed to save avatar for ' . $this->user->getUID());
-				throw new NotFoundException();
-			}
-
-		}
-
-		if ($this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', null) === null) {
-			$generated = $this->folder->fileExists('generated') ? 'true' : 'false';
-			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', $generated);
+			return false;
 		}
 
-		return $file;
-	}
-
-	/**
-	 * Get the extension of the avatar. If there is no avatar throw Exception
-	 *
-	 * @return string
-	 * @throws NotFoundException
-	 */
-	private function getExtension() {
-		if ($this->folder->fileExists('avatar.jpg')) {
-			return 'jpg';
-		} elseif ($this->folder->fileExists('avatar.png')) {
-			return 'png';
-		}
-		throw new NotFoundException;
+		$avatar = new OC_Image();
+		$avatar->loadFromData($file->getContent());
+		return $avatar;
 	}
 
 	/**
@@ -295,16 +116,16 @@ class Avatar implements IAvatar {
 	 * {letter} = Letter to display
 	 *
 	 * Generate SVG avatar
+	 *
+	 * @param int $size The requested image size in pixel
 	 * @return string
 	 *
 	 */
-	private function getAvatarVector(int $size): string {
-		$userDisplayName = $this->user->getDisplayName();
-
+	protected function getAvatarVector(int $size): string {
+		$userDisplayName = $this->getDisplayName();
 		$bgRGB = $this->avatarBackgroundColor($userDisplayName);
 		$bgHEX = sprintf("%02x%02x%02x", $bgRGB->r, $bgRGB->g, $bgRGB->b);
-		$letter = mb_strtoupper(mb_substr($userDisplayName, 0, 1), 'UTF-8');
-
+		$letter = $this->getAvatarLetter();
 		$toReplace = ['{size}', '{fill}', '{letter}'];
 		return str_replace($toReplace, [$size, $bgHEX, $letter], $this->svgTemplate);
 	}
@@ -315,7 +136,7 @@ class Avatar implements IAvatar {
 	 * @param int $size
 	 * @return string|boolean
 	 */
-	private function generateAvatarFromSvg(int $size) {
+	protected function generateAvatarFromSvg(int $size) {
 		if (!extension_loaded('imagick')) {
 			return false;
 		}
@@ -341,22 +162,28 @@ class Avatar implements IAvatar {
 	 * @param int $size
 	 * @return string
 	 */
-	private function generateAvatar($userDisplayName, $size) {
-		$text = mb_strtoupper(mb_substr($userDisplayName, 0, 1), 'UTF-8');
+	protected function generateAvatar($userDisplayName, $size) {
+		$letter = $this->getAvatarLetter();
 		$backgroundColor = $this->avatarBackgroundColor($userDisplayName);
 
 		$im = imagecreatetruecolor($size, $size);
-		$background = imagecolorallocate($im, $backgroundColor->r, $backgroundColor->g, $backgroundColor->b);
+		$background = imagecolorallocate(
+			$im,
+			$backgroundColor->r,
+			$backgroundColor->g,
+			$backgroundColor->b
+		);
 		$white = imagecolorallocate($im, 255, 255, 255);
 		imagefilledrectangle($im, 0, 0, $size, $size, $background);
 
-		$font = __DIR__ . '/../../core/fonts/Nunito-Regular.ttf';
+		$font = __DIR__ . '/../../../core/fonts/Nunito-Regular.ttf';
 
 		$fontSize = $size * 0.4;
+		list($x, $y) = $this->imageTTFCenter(
+			$im, $letter, $font, (int)$fontSize
+		);
 
-		list($x, $y) = $this->imageTTFCenter($im, $text, $font, $fontSize);
-
-		imagettftext($im, $fontSize, 0, $x, $y, $white, $font, $text);
+		imagettftext($im, $fontSize, 0, $x, $y, $white, $font, $letter);
 
 		ob_start();
 		imagepng($im);
@@ -376,7 +203,13 @@ class Avatar implements IAvatar {
 	 * @param int $angle
 	 * @return array
 	 */
-	protected function imageTTFCenter($image, string $text, string $font, int $size, $angle = 0): array {
+	protected function imageTTFCenter(
+		$image,
+		string $text,
+		string $font,
+		int $size,
+		$angle = 0
+	): array {
 		// Image width & height
 		$xi = imagesx($image);
 		$yi = imagesy($image);
@@ -413,10 +246,9 @@ class Avatar implements IAvatar {
 	 * Convert a string to an integer evenly
 	 * @param string $hash the text to parse
 	 * @param int $maximum the maximum range
-	 * @return int between 0 and $maximum
+	 * @return int[] between 0 and $maximum
 	 */
 	private function mixPalette($steps, $color1, $color2) {
-		$count = $steps + 1;
 		$palette = array($color1);
 		$step = $this->stepCalc($steps, [$color1, $color2]);
 		for ($i = 1; $i < $steps; $i++) {
@@ -483,19 +315,4 @@ class Avatar implements IAvatar {
 
 		return $finalPalette[$this->hashToInt($hash, $steps * 3)];
 	}
-
-	public function userChanged($feature, $oldValue, $newValue) {
-		// We only change the avatar on display name changes
-		if ($feature !== 'displayName') {
-			return;
-		}
-
-		// If the avatar is not generated (so an uploaded image) we skip this
-		if (!$this->folder->fileExists('generated')) {
-			return;
-		}
-
-		$this->remove();
-	}
-
 }

+ 12 - 2
lib/private/AvatarManager.php → lib/private/Avatar/AvatarManager.php

@@ -26,7 +26,7 @@ declare(strict_types=1);
  *
  */
 
-namespace OC;
+namespace OC\Avatar;
 
 use OC\User\Manager;
 use OCP\Files\IAppData;
@@ -102,7 +102,7 @@ class AvatarManager implements IAvatarManager {
 			$folder = $this->appData->newFolder($userId);
 		}
 
-		return new Avatar($folder, $this->l, $user, $this->logger, $this->config);
+		return new UserAvatar($folder, $this->l, $user, $this->logger, $this->config);
 	}
 
 	/**
@@ -120,4 +120,14 @@ class AvatarManager implements IAvatarManager {
 			$this->config->setUserValue($userId, 'avatar', 'generated', 'false');
 		}
 	}
+
+	/**
+	 * Returns a GuestAvatar.
+	 *
+	 * @param string $name The guest name, e.g. "Albert".
+	 * @return IAvatar
+	 */
+	public function getGuestAvatar(string $name): IAvatar {
+		return new GuestAvatar($name, $this->logger);
+	}
 }

+ 119 - 0
lib/private/Avatar/GuestAvatar.php

@@ -0,0 +1,119 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace OC\Avatar;
+
+use OCP\Files\SimpleFS\InMemoryFile;
+use OCP\ILogger;
+
+/**
+ * This class represents a guest user's avatar.
+ */
+class GuestAvatar extends Avatar {
+	/**
+	 * Holds the guest user display name.
+	 *
+	 * @var string
+	 */
+	private $userDisplayName;
+
+	/**
+	 * GuestAvatar constructor.
+	 *
+	 * @param string $userDisplayName The guest user display name
+	 * @param ILogger $logger The logger
+	 */
+	public function __construct(string $userDisplayName, ILogger $logger) {
+		parent::__construct($logger);
+		$this->userDisplayName = $userDisplayName;
+	}
+
+	/**
+	 * Tests if the user has an avatar.
+	 *
+	 * @return true Guests always have an avatar.
+	 */
+	public function exists() {
+		return true;
+	}
+
+	/**
+	 * Returns the guest user display name.
+	 *
+	 * @return string
+	 */
+	public function getDisplayName(): string {
+		return $this->userDisplayName;
+	}
+
+	/**
+	 * Setting avatars isn't implemented for guests.
+	 *
+	 * @param \OCP\IImage|resource|string $data
+	 * @return void
+	 */
+	public function set($data) {
+		// unimplemented for guest user avatars
+	}
+
+	/**
+	 * Removing avatars isn't implemented for guests.
+	 */
+	public function remove() {
+		// unimplemented for guest user avatars
+	}
+
+	/**
+	 * Generates an avatar for the guest.
+	 *
+	 * @param int $size The desired image size.
+	 * @return InMemoryFile
+	 */
+	public function getFile($size) {
+		$avatar = $this->getAvatarVector($size);
+		return new InMemoryFile('avatar.svg', $avatar);
+	}
+
+	/**
+	 * Updates the display name if changed.
+	 *
+	 * @param string $feature The changed feature
+	 * @param mixed $oldValue The previous value
+	 * @param mixed $newValue The new value
+	 * @return void
+	 */
+	public function userChanged($feature, $oldValue, $newValue) {
+		if ($feature === 'displayName') {
+			$this->userDisplayName = $newValue;
+		}
+	}
+
+	/**
+	 * Guests don't have custom avatars.
+	 *
+	 * @return bool
+	 */
+	public function isCustomAvatar(): bool {
+		return false;
+	}
+}

+ 336 - 0
lib/private/Avatar/UserAvatar.php

@@ -0,0 +1,336 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace OC\Avatar;
+
+use OC\NotSquareException;
+use OC\User\User;
+use OC_Image;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\IConfig;
+use OCP\IImage;
+use OCP\IL10N;
+use OCP\ILogger;
+
+/**
+ * This class represents a registered user's avatar.
+ */
+class UserAvatar extends Avatar {
+	/** @var IConfig */
+	private $config;
+
+	/** @var ISimpleFolder */
+	private $folder;
+
+	/** @var IL10N */
+	private $l;
+
+	/** @var User */
+	private $user;
+
+	/**
+	 * UserAvatar constructor.
+	 *
+	 * @param IConfig $config The configuration
+	 * @param ISimpleFolder $folder The avatar files folder
+	 * @param IL10N $l The localization helper
+	 * @param User $user The user this class manages the avatar for
+	 * @param ILogger $logger The logger
+	 */
+	public function __construct(
+		ISimpleFolder $folder,
+		IL10N $l,
+		$user,
+		ILogger $logger,
+		IConfig $config) {
+		parent::__construct($logger);
+		$this->folder = $folder;
+		$this->l = $l;
+		$this->user = $user;
+		$this->config = $config;
+	}
+
+	/**
+	 * Check if an avatar exists for the user
+	 *
+	 * @return bool
+	 */
+	public function exists() {
+		return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png');
+	}
+
+	/**
+	 * Sets the users avatar.
+	 *
+	 * @param IImage|resource|string $data An image object, imagedata or path to set a new avatar
+	 * @throws \Exception if the provided file is not a jpg or png image
+	 * @throws \Exception if the provided image is not valid
+	 * @throws NotSquareException if the image is not square
+	 * @return void
+	 */
+	public function set($data) {
+		$img = $this->getAvatarImage($data);
+		$data = $img->data();
+
+		$this->validateAvatar($img);
+
+		$this->remove();
+		$type = $this->getAvatarImageType($img);
+		$file = $this->folder->newFile('avatar.' . $type);
+		$file->putContent($data);
+
+		try {
+			$generated = $this->folder->getFile('generated');
+			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'false');
+			$generated->delete();
+		} catch (NotFoundException $e) {
+			//
+		}
+
+		$this->user->triggerChange('avatar', $file);
+	}
+
+	/**
+	 * Returns an image from several sources.
+	 *
+	 * @param IImage|resource|string $data An image object, imagedata or path to the avatar
+	 * @return IImage
+	 */
+	private function getAvatarImage($data) {
+		if ($data instanceof IImage) {
+			return $data;
+		}
+
+		$img = new OC_Image();
+		if (is_resource($data) && get_resource_type($data) === 'gd') {
+			$img->setResource($data);
+		} elseif (is_resource($data)) {
+			$img->loadFromFileHandle($data);
+		} else {
+			try {
+				// detect if it is a path or maybe the images as string
+				$result = @realpath($data);
+				if ($result === false || $result === null) {
+					$img->loadFromData($data);
+				} else {
+					$img->loadFromFile($data);
+				}
+			} catch (\Error $e) {
+				$img->loadFromData($data);
+			}
+		}
+
+		return $img;
+	}
+
+	/**
+	 * Returns the avatar image type.
+	 *
+	 * @param IImage $avatar
+	 * @return string
+	 */
+	private function getAvatarImageType(IImage $avatar) {
+		$type = substr($avatar->mimeType(), -3);
+		if ($type === 'peg') {
+			$type = 'jpg';
+		}
+		return $type;
+	}
+
+	/**
+	 * Validates an avatar image:
+	 * - must be "png" or "jpg"
+	 * - must be "valid"
+	 * - must be in square format
+	 *
+	 * @param IImage $avatar The avatar to validate
+	 * @throws \Exception if the provided file is not a jpg or png image
+	 * @throws \Exception if the provided image is not valid
+	 * @throws NotSquareException if the image is not square
+	 */
+	private function validateAvatar(IImage $avatar) {
+		$type = $this->getAvatarImageType($avatar);
+
+		if ($type !== 'jpg' && $type !== 'png') {
+			throw new \Exception($this->l->t('Unknown filetype'));
+		}
+
+		if (!$avatar->valid()) {
+			throw new \Exception($this->l->t('Invalid image'));
+		}
+
+		if (!($avatar->height() === $avatar->width())) {
+			throw new NotSquareException($this->l->t('Avatar image is not square'));
+		}
+	}
+
+	/**
+	 * Removes the users avatar.
+	 * @return void
+	 * @throws \OCP\Files\NotPermittedException
+	 * @throws \OCP\PreConditionNotMetException
+	 */
+	public function remove() {
+		$avatars = $this->folder->getDirectoryListing();
+
+		$this->config->setUserValue($this->user->getUID(), 'avatar', 'version',
+			(int) $this->config->getUserValue($this->user->getUID(), 'avatar', 'version', 0) + 1);
+
+		foreach ($avatars as $avatar) {
+			$avatar->delete();
+		}
+		$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
+		$this->user->triggerChange('avatar', '');
+	}
+
+	/**
+	 * Get the extension of the avatar. If there is no avatar throw Exception
+	 *
+	 * @return string
+	 * @throws NotFoundException
+	 */
+	private function getExtension() {
+		if ($this->folder->fileExists('avatar.jpg')) {
+			return 'jpg';
+		} elseif ($this->folder->fileExists('avatar.png')) {
+			return 'png';
+		}
+		throw new NotFoundException;
+	}
+
+	/**
+	 * Returns the avatar for an user.
+	 *
+	 * If there is no avatar file yet, one is generated.
+	 *
+	 * @param int $size
+	 * @return ISimpleFile
+	 * @throws NotFoundException
+	 * @throws \OCP\Files\NotPermittedException
+	 * @throws \OCP\PreConditionNotMetException
+	 */
+	public function getFile($size) {
+		$size = (int) $size;
+
+		try {
+			$ext = $this->getExtension();
+		} catch (NotFoundException $e) {
+			if (!$data = $this->generateAvatarFromSvg(1024)) {
+				$data = $this->generateAvatar($this->getDisplayName(), 1024);
+			}
+			$avatar = $this->folder->newFile('avatar.png');
+			$avatar->putContent($data);
+			$ext = 'png';
+
+			$this->folder->newFile('generated');
+			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
+		}
+
+		if ($size === -1) {
+			$path = 'avatar.' . $ext;
+		} else {
+			$path = 'avatar.' . $size . '.' . $ext;
+		}
+
+		try {
+			$file = $this->folder->getFile($path);
+		} catch (NotFoundException $e) {
+			if ($size <= 0) {
+				throw new NotFoundException;
+			}
+
+			if ($this->folder->fileExists('generated')) {
+				if (!$data = $this->generateAvatarFromSvg($size)) {
+					$data = $this->generateAvatar($this->getDisplayName(), $size);
+				}
+
+			} else {
+				$avatar = new OC_Image();
+				$file = $this->folder->getFile('avatar.' . $ext);
+				$avatar->loadFromData($file->getContent());
+				$avatar->resize($size);
+				$data = $avatar->data();
+			}
+
+			try {
+				$file = $this->folder->newFile($path);
+				$file->putContent($data);
+			} catch (NotPermittedException $e) {
+				$this->logger->error('Failed to save avatar for ' . $this->user->getUID());
+				throw new NotFoundException();
+			}
+
+		}
+
+		if ($this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', null) === null) {
+			$generated = $this->folder->fileExists('generated') ? 'true' : 'false';
+			$this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', $generated);
+		}
+
+		return $file;
+	}
+
+	/**
+	 * Returns the user display name.
+	 *
+	 * @return string
+	 */
+	public function getDisplayName(): string {
+		return $this->user->getDisplayName();
+	}
+
+	/**
+	 * Handles user changes.
+	 *
+	 * @param string $feature The changed feature
+	 * @param mixed $oldValue The previous value
+	 * @param mixed $newValue The new value
+	 * @throws NotPermittedException
+	 * @throws \OCP\PreConditionNotMetException
+	 */
+	public function userChanged($feature, $oldValue, $newValue) {
+		// We only change the avatar on display name changes
+		if ($feature !== 'displayName') {
+			return;
+		}
+
+		// If the avatar is not generated (so an uploaded image) we skip this
+		if (!$this->folder->fileExists('generated')) {
+			return;
+		}
+
+		$this->remove();
+	}
+
+	/**
+	 * Check if the avatar of a user is a custom uploaded one
+	 *
+	 * @return bool
+	 */
+	public function isCustomAvatar(): bool {
+		return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true';
+	}
+}

+ 1 - 1
lib/private/Repair.php

@@ -33,7 +33,7 @@ namespace OC;
 use OCP\AppFramework\QueryException;
 use OCP\Migration\IOutput;
 use OCP\Migration\IRepairStep;
-use OC\AvatarManager;
+use OC\Avatar\AvatarManager;
 use OC\Repair\AddCleanupUpdaterBackupsJob;
 use OC\Repair\CleanTags;
 use OC\Repair\ClearGeneratedAvatarCache;

+ 1 - 2
lib/private/Repair/ClearGeneratedAvatarCache.php

@@ -23,11 +23,10 @@
 
 namespace OC\Repair;
 
-use OC\AvatarManager;
+use OC\Avatar\AvatarManager;
 use OCP\IConfig;
 use OCP\Migration\IOutput;
 use OCP\Migration\IRepairStep;
-use OCP\Util;
 
 class ClearGeneratedAvatarCache implements IRepairStep {
 

+ 1 - 0
lib/private/Server.php

@@ -58,6 +58,7 @@ use OC\AppFramework\Utility\SimpleContainer;
 use OC\AppFramework\Utility\TimeFactory;
 use OC\Authentication\LoginCredentials\Store;
 use OC\Authentication\Token\IProvider;
+use OC\Avatar\AvatarManager;
 use OC\Collaboration\Collaborators\GroupPlugin;
 use OC\Collaboration\Collaborators\MailPlugin;
 use OC\Collaboration\Collaborators\RemoteGroupPlugin;

+ 9 - 0
lib/public/AppFramework/Http/FileDisplayResponse.php

@@ -66,4 +66,13 @@ class FileDisplayResponse extends Response implements ICallbackResponse {
 			$output->setOutput($this->file->getContent());
 		}
 	}
+
+	/**
+	 * Returns the response file.
+	 *
+	 * @return \OCP\Files\File|\OCP\Files\SimpleFS\ISimpleFile
+	 */
+	public function getFile() {
+		return $this->file;
+	}
 }

+ 137 - 0
lib/public/Files/SimpleFS/InMemoryFile.php

@@ -0,0 +1,137 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace OCP\Files\SimpleFS;
+
+use OCP\Files\NotPermittedException;
+
+/**
+ * This class represents a file that is only hold in memory.
+ *
+ * @package OC\Files\SimpleFS
+ */
+class InMemoryFile implements ISimpleFile {
+	/**
+	 * Holds the file name.
+	 *
+	 * @var string
+	 */
+	private $name;
+
+	/**
+	 * Holds the file contents.
+	 *
+	 * @var string
+	 */
+	private $contents;
+
+	/**
+	 * InMemoryFile constructor.
+	 *
+	 * @param string $name The file name
+	 * @param string $contents The file contents
+	 */
+	public function __construct(string $name, string $contents) {
+		$this->name = $name;
+		$this->contents = $contents;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getName() {
+		return $this->name;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getSize() {
+		return strlen($this->contents);
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getETag() {
+		return '';
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getMTime() {
+		return time();
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getContent() {
+		return $this->contents;
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function putContent($data) {
+		$this->contents = $data;
+	}
+
+	/**
+	 * In memory files can't be deleted.
+	 */
+	public function delete() {
+		// unimplemented for in memory files
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function getMimeType() {
+		$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
+		return $fileInfo->buffer($this->contents);
+	}
+
+	/**
+	 * Stream reading is unsupported for in memory files.
+	 *
+	 * @throws NotPermittedException
+	 */
+	public function read() {
+		throw new NotPermittedException(
+			'Stream reading is unsupported for in memory files'
+		);
+	}
+
+	/**
+	 * Stream writing isn't available for in memory files.
+	 *
+	 * @throws NotPermittedException
+	 */
+	public function write() {
+		throw new NotPermittedException(
+			'Stream writing is unsupported for in memory files'
+		);
+	}
+}

+ 9 - 0
lib/public/IAvatarManager.php

@@ -45,4 +45,13 @@ interface IAvatarManager {
 	 */
 	public function getAvatar(string $user) : IAvatar;
 
+	/**
+	 * Returns a guest user avatar instance.
+	 *
+	 * @param string $name The guest name, e.g. "Albert".
+	 * @return IAvatar
+	 * @since 16.0.0
+	 */
+	public function getGuestAvatar(string $name): IAvatar;
+
 }

+ 2 - 2
tests/Core/Controller/AvatarControllerTest.php

@@ -53,7 +53,7 @@ use OCP\IUserManager;
  * @package OC\Core\Controller
  */
 class AvatarControllerTest extends \Test\TestCase {
-	/** @var \OC\Core\Controller\AvatarController */
+	/** @var AvatarController */
 	private $avatarController;
 	/** @var IAvatar|\PHPUnit_Framework_MockObject_MockObject */
 	private $avatarMock;
@@ -78,7 +78,7 @@ class AvatarControllerTest extends \Test\TestCase {
 	private $request;
 	/** @var TimeFactory|\PHPUnit_Framework_MockObject_MockObject */
 	private $timeFactory;
-	
+
 	protected function setUp() {
 		parent::setUp();
 

+ 90 - 0
tests/Core/Controller/GuestAvatarControllerTest.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Core\Controller;
+
+use OC\Core\Controller\GuestAvatarController;
+use OCP\AppFramework\Http\FileDisplayResponse;
+use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\IAvatar;
+use OCP\IAvatarManager;
+use OCP\ILogger;
+use OCP\IRequest;
+
+/**
+ * This class provides tests for the guest avatar controller.
+ */
+class GuestAvatarControllerTest extends \Test\TestCase {
+
+	/**
+	 * @var GuestAvatarController
+	 */
+	private $guestAvatarController;
+
+	/**
+	 * @var IRequest|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	private $request;
+
+	/**
+	 * @var IAvatarManager|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	private $avatarManager;
+
+	/**
+	 * @var IAvatar|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	private $avatar;
+
+	/**
+	 * @var \OCP\Files\File|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	private $file;
+
+	/**
+	 * @var ILogger|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	private $logger;
+
+	/**
+	 * Sets up the test environment.
+	 */
+	protected function setUp() {
+		parent::setUp();
+
+		$this->logger = $this->getMockBuilder(ILogger::class)->getMock();
+		$this->request = $this->getMockBuilder(IRequest::class)->getMock();
+		$this->avatar = $this->getMockBuilder(IAvatar::class)->getMock();
+		$this->avatarManager = $this->getMockBuilder(IAvatarManager::class)->getMock();
+		$this->file = $this->getMockBuilder(ISimpleFile::class)->getMock();
+		$this->guestAvatarController = new GuestAvatarController(
+			'core',
+			$this->request,
+			$this->avatarManager,
+			$this->logger
+		);
+	}
+
+	/**
+	 * Tests getAvatar returns the guest avatar.
+	 */
+	public function testGetAvatar() {
+		$this->avatarManager->expects($this->once())
+			->method('getGuestAvatar')
+			->with('Peter')
+			->willReturn($this->avatar);
+
+		$this->avatar->expects($this->once())
+			->method('getFile')
+			->with(128)
+			->willReturn($this->file);
+
+		$this->file->method('getMimeType')
+			->willReturn('image/svg+xml');
+
+		$response = $this->guestAvatarController->getAvatar('Peter', 128);
+
+		$this->assertGreaterThanOrEqual(201, $response->getStatus());
+		$this->assertInstanceOf(FileDisplayResponse::class, $response);
+		$this->assertSame($this->file, $response->getFile());
+	}
+}

+ 5 - 0
tests/data/guest_avatar_einstein_32.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+		<svg width="32" height="32" version="1.1" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+			<rect width="100%" height="100%" fill="#499aa2"></rect>
+			<text x="50%" y="350" style="font-weight:normal;font-size:279px;font-family:'Nunito';text-anchor:middle;fill:#fff">E</text>
+		</svg>

BIN
tests/data/test.pdf


+ 5 - 7
tests/lib/AvatarManagerTest.php → tests/lib/Avatar/AvatarManagerTest.php

@@ -22,11 +22,10 @@
  *
  */
 
-namespace Test;
+namespace Test\Avatar;
 
-use OC\Avatar;
-use OC\AvatarManager;
-use OC\Files\AppData\AppData;
+use OC\Avatar\UserAvatar;
+use OC\Avatar\AvatarManager;
 use OC\User\Manager;
 use OCP\Files\IAppData;
 use OCP\Files\SimpleFS\ISimpleFolder;
@@ -34,7 +33,6 @@ use OCP\IConfig;
 use OCP\IL10N;
 use OCP\ILogger;
 use OCP\IUser;
-use OCP\IUserManager;
 
 /**
  * Class AvatarManagerTest
@@ -103,7 +101,7 @@ class AvatarManagerTest extends \Test\TestCase {
 			->with('valid-user')
 			->willReturn($folder);
 
-		$expected = new Avatar($folder, $this->l10n, $user, $this->logger, $this->config);
+		$expected = new UserAvatar($folder, $this->l10n, $user, $this->logger, $this->config);
 		$this->assertEquals($expected, $this->avatarManager->getAvatar('valid-user'));
 	}
 
@@ -125,7 +123,7 @@ class AvatarManagerTest extends \Test\TestCase {
 			->with('valid-user')
 			->willReturn($folder);
 
-		$expected = new Avatar($folder, $this->l10n, $user, $this->logger, $this->config);
+		$expected = new UserAvatar($folder, $this->l10n, $user, $this->logger, $this->config);
 		$this->assertEquals($expected, $this->avatarManager->getAvatar('vaLid-USER'));
 	}
 }

+ 80 - 0
tests/lib/Avatar/GuestAvatarTest.php

@@ -0,0 +1,80 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace Test\Avatar;
+
+use OC\Avatar\GuestAvatar;
+use OCP\Files\SimpleFS\InMemoryFile;
+use OCP\ILogger;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+/**
+ * This class provides test cases for the GuestAvatar class.
+ *
+ * @package Test\Avatar
+ */
+class GuestAvatarTest extends TestCase {
+	/**
+	 * @var GuestAvatar
+	 */
+	private $guestAvatar;
+
+	/**
+	 * Setups a guest avatar instance for tests.
+	 *
+	 * @before
+	 * @return void
+	 */
+	public function setupGuestAvatar() {
+		/* @var MockObject|ILogger $logger */
+		$logger = $this->getMockBuilder(ILogger::class)->getMock();
+		$this->guestAvatar = new GuestAvatar('einstein', $logger);
+	}
+
+	/**
+	 * Asserts that testGet() returns the expected avatar.
+	 *
+	 * For the test a static name "einstein" is used and
+	 * the generated image is compared with an expected one.
+	 *
+	 * @return void
+	 */
+	public function testGet() {
+		$avatar = $this->guestAvatar->getFile(32);
+		self::assertInstanceOf(InMemoryFile::class, $avatar);
+		$expectedFile = file_get_contents(
+			__DIR__ . '/../../data/guest_avatar_einstein_32.svg'
+		);
+		self::assertEquals(trim($expectedFile), trim($avatar->getContent()));
+	}
+
+	/**
+	 * Asserts that "testIsCustomAvatar" returns false for guests.
+	 *
+	 * @return void
+	 */
+	public function testIsCustomAvatar() {
+		self::assertFalse($this->guestAvatar->isCustomAvatar());
+	}
+}

+ 4 - 4
tests/lib/AvatarTest.php → tests/lib/Avatar/UserAvatarTest.php

@@ -6,7 +6,7 @@
  * See the COPYING-README file.
  */
 
-namespace Test;
+namespace Test\Avatar;
 
 use OC\Files\SimpleFS\SimpleFolder;
 use OC\User\User;
@@ -18,11 +18,11 @@ use OCP\IConfig;
 use OCP\IL10N;
 use OCP\ILogger;
 
-class AvatarTest extends \Test\TestCase {
+class UserAvatarTest extends \Test\TestCase {
 	/** @var Folder | \PHPUnit_Framework_MockObject_MockObject */
 	private $folder;
 
-	/** @var \OC\Avatar */
+	/** @var \OC\Avatar\UserAvatar */
 	private $avatar;
 
 	/** @var \OC\User\User | \PHPUnit_Framework_MockObject_MockObject $user */
@@ -41,7 +41,7 @@ class AvatarTest extends \Test\TestCase {
 		$this->user = $this->createMock(User::class);
 		$this->config = $this->createMock(IConfig::class);
 
-		$this->avatar = new \OC\Avatar(
+		$this->avatar = new \OC\Avatar\UserAvatar(
 			$this->folder,
 			$l,
 			$this->user,

+ 145 - 0
tests/lib/Files/SimpleFS/InMemoryFileTest.php

@@ -0,0 +1,145 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018, Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @author Michael Weimann <mail@michael-weimann.eu>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+namespace Test\File\SimpleFS;
+
+use OCP\Files\NotPermittedException;
+use OCP\Files\SimpleFS\InMemoryFile;
+use Test\TestCase;
+
+/**
+ * This class provide test casesf or the InMemoryFile.
+ *
+ * @package Test\File\SimpleFS
+ */
+class InMemoryFileTest extends TestCase {
+	/**
+	 * Holds a pdf file with know attributes for tests.
+	 *
+	 * @var InMemoryFile
+	 */
+	private $testPdf;
+
+	/**
+	 * Sets the test file from "./resources/test.pdf".
+	 *
+	 * @before
+	 * @return void
+	 */
+	public function setupTestPdf() {
+		$fileContents = file_get_contents(
+			__DIR__ . '/../../../data/test.pdf'
+		);
+		$this->testPdf = new InMemoryFile('test.pdf', $fileContents);
+	}
+
+	/**
+	 * Asserts that putContent replaces the file contents.
+	 *
+	 * @return void
+	 */
+	public function testPutContent() {
+		$this->testPdf->putContent('test');
+		self::assertEquals('test', $this->testPdf->getContent());
+	}
+
+	/**
+	 * Asserts that delete() doesn't rise an exception.
+	 *
+	 * @return void
+	 */
+	public function testDelete() {
+		$this->testPdf->delete();
+		// assert true, otherwise phpunit complains about not doing any assert
+		self::assertTrue(true);
+	}
+
+	/**
+	 * Asserts that getName returns the name passed on file creation.
+	 *
+	 * @return void
+	 */
+	public function testGetName() {
+		self::assertEquals('test.pdf', $this->testPdf->getName());
+	}
+
+	/**
+	 * Asserts that the file size is the size of the test file.
+	 *
+	 * @return void
+	 */
+	public function testGetSize() {
+		self::assertEquals(7083, $this->testPdf->getSize());
+	}
+
+	/**
+	 * Asserts the file contents are the same than the original file contents.
+	 *
+	 * @return void
+	 */
+	public function testGetContent() {
+		self::assertEquals(
+			file_get_contents(__DIR__ . '/../../../data/test.pdf'),
+			$this->testPdf->getContent()
+		);
+	}
+
+	/**
+	 * Asserts the test file modification time is an integer.
+	 *
+	 * @return void
+	 */
+	public function testGetMTime() {
+		self::assertTrue(is_int($this->testPdf->getMTime()));
+	}
+
+	/**
+	 * Asserts the test file mime type is "application/json".
+	 *
+	 * @return void
+	 */
+	public function testGetMimeType() {
+		self::assertEquals('application/pdf', $this->testPdf->getMimeType());
+	}
+
+
+	/**
+	 * Asserts that read() raises an NotPermittedException.
+	 *
+	 * @return void
+	 */
+	public function testRead() {
+		self::expectException(NotPermittedException::class);
+		$this->testPdf->read();
+	}
+
+	/**
+	 * Asserts that write() raises an NotPermittedException.
+	 *
+	 * @return void
+	 */
+	public function testWrite() {
+		self::expectException(NotPermittedException::class);
+		$this->testPdf->write();
+	}
+}

+ 1 - 1
tests/lib/Repair/ClearGeneratedAvatarCacheTest.php

@@ -25,7 +25,7 @@ namespace Test\Repair;
 
 use OCP\IConfig;
 use OCP\Migration\IOutput;
-use OC\AvatarManager;
+use OC\Avatar\AvatarManager;
 use OC\Repair\ClearGeneratedAvatarCache;
 
 class ClearGeneratedAvatarCacheTest extends \Test\TestCase {

+ 1 - 1
tests/lib/ServerTest.php

@@ -57,7 +57,7 @@ class ServerTest extends \Test\TestCase {
 			['AppManager', '\OCP\App\IAppManager'],
 			['AsyncCommandBus', '\OC\Command\AsyncBus'],
 			['AsyncCommandBus', '\OCP\Command\IBus'],
-			['AvatarManager', '\OC\AvatarManager'],
+			['AvatarManager', '\OC\Avatar\AvatarManager'],
 			['AvatarManager', '\OCP\IAvatarManager'],
 
 			['CategoryFetcher', CategoryFetcher::class],