Further cleanup of docblocks and standardisation of repos.
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
/**
* The roles that belong to the role.
*/
- public function users()
+ public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
}
/**
* The RolePermissions that belong to the role.
*/
- public function permissions()
+ public function permissions(): BelongsToMany
{
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id');
}
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Uploads\Image;
+use BookStack\Uploads\UserAvatars;
use Exception;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Images;
use Log;
class UserRepo
{
-
- protected $user;
- protected $role;
+ protected $userAvatar;
/**
* UserRepo constructor.
*/
- public function __construct(User $user, Role $role)
+ public function __construct(UserAvatars $userAvatar)
{
- $this->user = $user;
- $this->role = $role;
+ $this->userAvatar = $userAvatar;
}
/**
*/
public function getByEmail(string $email): ?User
{
- return $this->user->where('email', '=', $email)->first();
+ return User::query()->where('email', '=', $email)->first();
}
/**
- * @param int $id
- * @return User
+ * Get a user by their ID.
*/
- public function getById($id)
+ public function getById(int $id): User
{
- return $this->user->newQuery()->findOrFail($id);
+ return User::query()->findOrFail($id);
}
/**
* Get all the users with their permissions.
- * @return Builder|static
*/
- public function getAllUsers()
+ public function getAllUsers(): Collection
{
- return $this->user->with('roles', 'avatar')->orderBy('name', 'asc')->get();
+ return User::query()->with('roles', 'avatar')->orderBy('name', 'asc')->get();
}
/**
->take(1);
}
- $query = $this->user->with(['roles', 'avatar', 'latestActivity'])
+ $query = User::query()->with(['roles', 'avatar', 'latestActivity'])
->orderBy($sort, $sortData['order']);
if ($sortData['search']) {
/**
* Assign a user to a system-level role.
- * @param User $user
- * @param $systemRoleName
* @throws NotFoundException
*/
- public function attachSystemRole(User $user, $systemRoleName)
+ public function attachSystemRole(User $user, string $systemRoleName)
{
- $role = $this->role->newQuery()->where('system_name', '=', $systemRoleName)->first();
- if ($role === null) {
+ $role = Role::getSystemRole($systemRoleName);
+ if (is_null($role)) {
throw new NotFoundException("Role '{$systemRoleName}' not found");
}
$user->attachRole($role);
/**
* Checks if the give user is the only admin.
- * @param User $user
- * @return bool
*/
- public function isOnlyAdmin(User $user)
+ public function isOnlyAdmin(User $user): bool
{
if (!$user->hasSystemRole('admin')) {
return false;
}
- $adminRole = $this->role->getSystemRole('admin');
- if ($adminRole->users->count() > 1) {
+ $adminRole = Role::getSystemRole('admin');
+ if ($adminRole->users()->count() > 1) {
return false;
}
+
return true;
}
/**
* Set the assigned user roles via an array of role IDs.
- * @param User $user
- * @param array $roles
* @throws UserUpdateException
*/
public function setUserRoles(User $user, array $roles)
/**
* Check if the given user is the last admin and their new roles no longer
* contains the admin role.
- * @param User $user
- * @param array $newRoles
- * @return bool
*/
protected function demotingLastAdmin(User $user, array $newRoles) : bool
{
if ($this->isOnlyAdmin($user)) {
- $adminRole = $this->role->getSystemRole('admin');
+ $adminRole = Role::getSystemRole('admin');
if (!in_array(strval($adminRole->id), $newRoles)) {
return true;
}
*/
public function create(array $data, bool $emailConfirmed = false): User
{
- return $this->user->forceCreate([
+ $details = [
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'email_confirmed' => $emailConfirmed,
'external_auth_id' => $data['external_auth_id'] ?? '',
- ]);
+ ];
+ return User::query()->forceCreate($details);
}
/**
* Remove the given user from storage, Delete all related content.
- * @param User $user
* @throws Exception
*/
public function destroy(User $user)
$user->delete();
// Delete user profile images
- $profileImages = Image::where('type', '=', 'user')->where('uploaded_to', '=', $user->id)->get();
+ $profileImages = Image::query()->where('type', '=', 'user')
+ ->where('uploaded_to', '=', $user->id)
+ ->get();
+
foreach ($profileImages as $image) {
Images::destroy($image);
}
/**
* Get the latest activity for a user.
- * @param User $user
- * @param int $count
- * @param int $page
- * @return array
*/
- public function getActivity(User $user, $count = 20, $page = 0)
+ public function getActivity(User $user, int $count = 20, int $page = 0): array
{
return Activity::userActivity($user, $count, $page);
}
/**
* Get the roles in the system that are assignable to a user.
- * @return mixed
*/
- public function getAllRoles()
+ public function getAllRoles(): Collection
{
- return $this->role->newQuery()->orderBy('display_name', 'asc')->get();
+ return Role::query()->orderBy('display_name', 'asc')->get();
}
/**
* Get an avatar image for a user and set it as their avatar.
* Returns early if avatars disabled or not set in config.
- * @param User $user
- * @return bool
*/
- public function downloadAndAssignUserAvatar(User $user)
+ public function downloadAndAssignUserAvatar(User $user): void
{
- if (!Images::avatarFetchEnabled()) {
- return false;
- }
-
try {
- $avatar = Images::saveUserAvatar($user);
- $user->avatar()->associate($avatar);
- $user->save();
- return true;
+ $this->userAvatar->fetchAndAssignToUser($user);
} catch (Exception $e) {
Log::error('Failed to save user avatar image');
- return false;
}
}
}
/**
* Create a new command instance.
- *
- * @param UserRepo $userRepo
*/
public function __construct(UserRepo $userRepo)
{
}
/**
- * Get entity type.
- * @return mixed
+ * Get the entity type as a simple lowercase word.
*/
- public static function getType()
+ public static function getType(): string
{
- return strtolower(static::getClassName());
+ $className = array_slice(explode('\\', static::class), -1, 1)[0];
+ return strtolower($className);
}
/**
$user->image_id = $image->id;
}
- // Delete the profile image if set to
+ // Delete the profile image if reset option is in request
if ($request->has('profile_image_reset')) {
$this->imageRepo->destroyImage($user->avatar);
}
return $this->belongsTo(User::class, 'updated_by');
}
- /**
- * Gets the class name.
- * @return string
- */
- public static function getClassName()
- {
- return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
- }
}
<?php namespace BookStack\Uploads;
-use BookStack\Auth\User;
-use BookStack\Exceptions\HttpFetchException;
use BookStack\Exceptions\ImageUploadException;
use DB;
use ErrorException;
class ImageService
{
-
protected $imageTool;
protected $cache;
protected $storageUrl;
protected $image;
- protected $http;
protected $fileSystem;
/**
* ImageService constructor.
*/
- public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache, HttpFetcher $http)
+ public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
{
$this->image = $image;
$this->imageTool = $imageTool;
$this->fileSystem = $fileSystem;
$this->cache = $cache;
- $this->http = $http;
}
/**
/**
* Save a new image from a uri-encoded base64 string of data.
- * @param string $base64Uri
- * @param string $name
- * @param string $type
- * @param int $uploadedTo
- * @return Image
* @throws ImageUploadException
*/
- public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, $uploadedTo = 0)
+ public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, int $uploadedTo = 0): Image
{
$splitData = explode(';base64,', $base64Uri);
if (count($splitData) < 2) {
return $this->saveNew($name, $data, $type, $uploadedTo);
}
- /**
- * Gets an image from url and saves it to the database.
- * @param $url
- * @param string $type
- * @param bool|string $imageName
- * @return mixed
- * @throws Exception
- */
- private function saveNewFromUrl($url, $type, $imageName = false)
- {
- $imageName = $imageName ? $imageName : basename($url);
- try {
- $imageData = $this->http->fetch($url);
- } catch (HttpFetchException $exception) {
- throw new Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
- }
- return $this->saveNew($imageName, $imageData, $type);
- }
-
/**
* Save a new image into storage.
* @throws ImageUploadException
*/
- private function saveNew(string $imageName, string $imageData, string $type, int $uploadedTo = 0): Image
+ public function saveNew(string $imageName, string $imageData, string $type, int $uploadedTo = 0): Image
{
$storage = $this->getStorage($type);
$secureUploads = setting('app-secure-images');
return (count($files) === 0 && count($folders) === 0);
}
- /**
- * Save an avatar image from an external service.
- * @throws Exception
- */
- public function saveUserAvatar(User $user, int $size = 500): Image
- {
- $avatarUrl = $this->getAvatarUrl();
- $email = strtolower(trim($user->email));
-
- $replacements = [
- '${hash}' => md5($email),
- '${size}' => $size,
- '${email}' => urlencode($email),
- ];
-
- $userAvatarUrl = strtr($avatarUrl, $replacements);
- $imageName = str_replace(' ', '-', $user->name . '-avatar.png');
- $image = $this->saveNewFromUrl($userAvatarUrl, 'user', $imageName);
- $image->created_by = $user->id;
- $image->updated_by = $user->id;
- $image->uploaded_to = $user->id;
- $image->save();
-
- return $image;
- }
-
- /**
- * Check if fetching external avatars is enabled.
- */
- public function avatarFetchEnabled(): bool
- {
- $fetchUrl = $this->getAvatarUrl();
- return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0;
- }
-
- /**
- * Get the URL to fetch avatars from.
- * @return string|mixed
- */
- protected function getAvatarUrl()
- {
- $url = trim(config('services.avatar_url'));
-
- if (empty($url) && !config('services.disable_services')) {
- $url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon';
- }
-
- return $url;
- }
-
/**
* Delete gallery and drawings that are not within HTML content of pages or page revisions.
* Checks based off of only the image name.
--- /dev/null
+<?php namespace BookStack\Uploads;
+
+use BookStack\Auth\User;
+use BookStack\Exceptions\HttpFetchException;
+use Exception;
+
+class UserAvatars
+{
+ protected $imageService;
+ protected $http;
+
+ public function __construct(ImageService $imageService, HttpFetcher $http)
+ {
+ $this->imageService = $imageService;
+ $this->http = $http;
+ }
+
+ /**
+ * Fetch and assign an avatar image to the given user.
+ */
+ public function fetchAndAssignToUser(User $user): void
+ {
+ if (!$this->avatarFetchEnabled()) {
+ return;
+ }
+
+ try {
+ $avatar = $this->saveAvatarImage($user);
+ $user->avatar()->associate($avatar);
+ $user->save();
+ } catch (Exception $e) {
+ Log::error('Failed to save user avatar image');
+ }
+ }
+
+ /**
+ * Save an avatar image from an external service.
+ * @throws Exception
+ */
+ protected function saveAvatarImage(User $user, int $size = 500): Image
+ {
+ $avatarUrl = $this->getAvatarUrl();
+ $email = strtolower(trim($user->email));
+
+ $replacements = [
+ '${hash}' => md5($email),
+ '${size}' => $size,
+ '${email}' => urlencode($email),
+ ];
+
+ $userAvatarUrl = strtr($avatarUrl, $replacements);
+ $imageName = str_replace(' ', '-', $user->id . '-avatar.png');
+ $imageData = $this->getAvatarImageData($userAvatarUrl);
+
+ $image = $this->imageService->saveNew($imageName, $imageData, 'user', $user->id);
+ $image->created_by = $user->id;
+ $image->updated_by = $user->id;
+ $image->save();
+
+ return $image;
+ }
+
+ /**
+ * Gets an image from url and returns it as a string of image data.
+ * @throws Exception
+ */
+ protected function getAvatarImageData(string $url): string
+ {
+ try {
+ $imageData = $this->http->fetch($url);
+ } catch (HttpFetchException $exception) {
+ throw new Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
+ }
+ return $imageData;
+ }
+
+ /**
+ * Check if fetching external avatars is enabled.
+ */
+ protected function avatarFetchEnabled(): bool
+ {
+ $fetchUrl = $this->getAvatarUrl();
+ return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0;
+ }
+
+ /**
+ * Get the URL to fetch avatars from.
+ */
+ protected function getAvatarUrl(): string
+ {
+ $url = trim(config('services.avatar_url'));
+
+ if (empty($url) && !config('services.disable_services')) {
+ $url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon';
+ }
+
+ return $url;
+ }
+
+}
\ No newline at end of file
<ul class="sortable-page-list sort-list">
@foreach($bookChildren as $bookChild)
- <li class="text-{{ $bookChild->getClassName() }}"
- data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getClassName() }}"
+ <li class="text-{{ $bookChild->getType() }}"
+ data-id="{{$bookChild->id}}" data-type="{{ $bookChild->getType() }}"
data-name="{{ $bookChild->name }}" data-created="{{ $bookChild->created_at->timestamp }}"
data-updated="{{ $bookChild->updated_at->timestamp }}">
<div class="entity-list-item">
@endif
@foreach($sidebarTree as $bookChild)
- <li class="list-item-{{ $bookChild->getClassName() }} {{ $bookChild->getClassName() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
+ <li class="list-item-{{ $bookChild->getType() }} {{ $bookChild->getType() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
@include('partials.entity-list-item-basic', ['entity' => $bookChild, 'classes' => $current->matches($bookChild)? 'selected' : ''])
@if($bookChild->isA('chapter') && count($bookChild->pages) > 0)