$user->delete();
// Delete user profile images
- $profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get();
+ $profileImages = Image::where('type', '=', 'user')->where('uploaded_to', '=', $user->id)->get();
foreach ($profileImages as $image) {
Images::destroy($image);
}
/**
* Get all images for a specific type, Paginated
+ * @param Request $request
* @param string $type
* @param int $page
* @return \Illuminate\Http\JsonResponse
*/
- public function getAllByType($type, $page = 0)
+ public function getAllByType(Request $request, $type, $page = 0)
{
- $imgData = $this->imageRepo->getPaginatedByType($type, $page);
+ $uploadedToFilter = $request->get('uploaded_to', null);
+
+ // For user profile request, check access to user images
+ if ($type === 'user') {
+ $this->checkPermissionOrCurrentUser('users-manage', $uploadedToFilter ?? 0);
+ }
+
+ $imgData = $this->imageRepo->getPaginatedByType($type, $page, 24, $uploadedToFilter);
return response()->json($imgData);
}
return response()->json($imgData);
}
- /**
- * Get all images for a user.
- * @param int $page
- * @return \Illuminate\Http\JsonResponse
- */
- public function getAllForUserType($page = 0)
- {
- $imgData = $this->imageRepo->getPaginatedByType('user', $page, 24, $this->currentUser->id);
- return response()->json($imgData);
- }
-
/**
* Get gallery images with a specific filter such as book or page
* @param $filter
public function getGalleryFiltered(Request $request, $filter, $page = 0)
{
$this->validate($request, [
- 'page_id' => 'required|integer'
+ 'uploaded_to' => 'required|integer'
]);
$validFilters = collect(['page', 'book']);
return response('Invalid filter', 500);
}
- $pageId = $request->get('page_id');
+ $pageId = $request->get('uploaded_to');
$imgData = $this->imageRepo->getGalleryFiltered(strtolower($filter), $pageId, $page, 24);
return response()->json($imgData);
}
+ public function uploadGalleryImage(Request $request)
+ {
+ // TODO
+ }
+
+ public function uploadUserImage(Request $request)
+ {
+ // TODO
+ }
+
+ public function uploadSystemImage(Request $request)
+ {
+ // TODO
+ }
+
+ public function uploadCoverImage(Request $request)
+ {
+ // TODO
+ }
+
/**
- * Handles image uploads for use on pages.
- * @param string $type
+ * Upload a draw.io image into the system.
* @param Request $request
- * @return \Illuminate\Http\JsonResponse
- * @throws \Exception
+ * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
- public function uploadByType($type, Request $request)
+ public function uploadDrawioImage(Request $request)
{
- $this->checkPermission('image-create-all');
$this->validate($request, [
- 'file' => 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff'
+ 'image' => 'required|string',
+ 'uploaded_to' => 'required|integer'
]);
-
- if (!$this->imageRepo->isValidType($type)) {
- return $this->jsonError(trans('errors.image_upload_type_error'));
- }
-
- $imageUpload = $request->file('file');
+ $uploadedTo = $request->get('uploaded_to', 0);
+ $page = $this->
+ $this->checkPermission('image-create-all');
+ $imageBase64Data = $request->get('image');
try {
- $uploadedTo = $request->get('uploaded_to', 0);
- $image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo);
+ $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
}
}
/**
- * Upload a drawing to the system.
+ * Handles image uploads for use on pages.
+ * @param string $type
* @param Request $request
- * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
+ * @return \Illuminate\Http\JsonResponse
+ * @throws \Exception
*/
- public function uploadDrawing(Request $request)
+ public function uploadByType($type, Request $request)
{
+ $this->checkPermission('image-create-all');
$this->validate($request, [
- 'image' => 'required|string',
- 'uploaded_to' => 'required|integer'
+ 'file' => 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff'
]);
- $this->checkPermission('image-create-all');
- $imageBase64Data = $request->get('image');
+
+ if (!$this->imageRepo->isValidType($type)) {
+ return $this->jsonError(trans('errors.image_upload_type_error'));
+ }
+
+ $imageUpload = $request->file('file');
try {
$uploadedTo = $request->get('uploaded_to', 0);
- $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
+
+ // For user profile request, check access to user images
+ if ($type === 'user') {
+ $this->checkPermissionOrCurrentUser('users-manage', $uploadedTo ?? 0);
+ }
+
+ $image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($image);
}
-
/**
* Get the content of an image based64 encoded.
* @param $id
/**
* Update image details
- * @param integer $imageId
+ * @param integer $id
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws ImageUploadException
* @throws \Exception
*/
- public function update($imageId, Request $request)
+ public function update($id, Request $request)
{
$this->validate($request, [
'name' => 'required|min:2|string'
]);
- $image = $this->imageRepo->getById($imageId);
+
+ $image = $this->imageRepo->getById($id);
$this->checkOwnablePermission('image-update', $image);
+
$image = $this->imageRepo->updateImageDetails($image, $request->all());
return response()->json($image);
}
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Page;
+use BookStack\Http\Requests\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageRepo
* @param $query
* @param int $page
* @param int $pageSize
+ * @param bool $filterOnPage
* @return array
*/
private function returnPaginated($query, $page = 0, $pageSize = 24)
{
- $images = $this->restrictionService->filterRelatedPages($query, 'images', 'uploaded_to');
- $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
+ $images = $query->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
$hasMore = count($images) > $pageSize;
$returnImages = $images->take(24);
* @param string $type
* @param int $page
* @param int $pageSize
- * @param bool|int $userFilter
+ * @param int $uploadedTo
* @return array
*/
- public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false)
+ public function getPaginatedByType(string $type, int $page = 0, int $pageSize = 24, int $uploadedTo = null)
{
- $images = $this->image->where('type', '=', strtolower($type));
+ $images = $this->image->newQuery()->where('type', '=', strtolower($type));
+
+ if ($uploadedTo !== null) {
+ $images = $images->where('uploaded_to', '=', $uploadedTo);
+ }
- if ($userFilter !== false) {
- $images = $images->where('created_by', '=', $userFilter);
+ // Filter by page access if gallery
+ if ($type === 'gallery') {
+ $images = $this->restrictionService->filterRelatedPages($images, 'images', 'uploaded_to');
}
return $this->returnPaginated($images, $page, $pageSize);
* @param string $searchTerm
* @return array
*/
- public function searchPaginatedByType($type, $searchTerm, $page = 0, $pageSize = 24)
+ public function searchPaginatedByType(Request $request, $type, $searchTerm, $page = 0, $pageSize = 24)
{
- $images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%');
+ // TODO - Filter by uploaded_to
+ $images = $this->image->newQuery()
+ ->where('type', '=', strtolower($type))
+ ->where('name', 'LIKE', '%' . $searchTerm . '%');
+
+ if ($type === 'gallery') {
+ $images = $this->restrictionService->filterRelatedPages($images, 'images', 'uploaded_to');
+ }
+
return $this->returnPaginated($images, $page, $pageSize);
}
$images = $images->whereIn('uploaded_to', $validPageIds);
}
+ $images = $this->restrictionService->filterRelatedPages($images, 'images', 'uploaded_to');
return $this->returnPaginated($images, $pageNum, $pageSize);
}
--- /dev/null
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class SetUserProfileImagesUploadedTo extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ DB::table('images')
+ ->where('type', '=', 'user')
+ ->update([
+ 'uploaded_to' => DB::raw('`created_by`')
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ DB::table('images')
+ ->where('type', '=', 'user')
+ ->update([
+ 'uploaded_to' => 0
+ ]);
+ }
+}
fetchData() {
let url = baseUrl + page;
let query = {};
- if (this.uploadedTo !== false) query.page_id = this.uploadedTo;
+ if (this.uploadedTo !== false) query.uploaded_to = this.uploadedTo;
if (this.searching) query.term = this.searchTerm;
this.$http.get(url, {params: query}).then(response => {
},
saveImageDetails() {
- let url = window.baseUrl(`/images/update/${this.selectedImage.id}`);
+ let url = window.baseUrl(`/images/${this.selectedImage.id}`);
this.$http.put(url, this.selectedImage).then(response => {
this.$events.emit('success', trans('components.image_update_success'));
}).catch(error => {
@endif
</div>
- @include('components.image-manager', ['imageType' => 'user'])
+ @include('components.image-manager', ['imageType' => 'user', 'uploaded_to' => $user->id])
@stop
// Image routes
Route::group(['prefix' => 'images'], function() {
// Get for user images
- Route::get('/user/all', 'ImageController@getAllForUserType');
- Route::get('/user/all/{page}', 'ImageController@getAllForUserType');
+// Route::get('/user/all', 'ImageController@getAllForUserType');
+// Route::get('/user/all/{page}', 'ImageController@getAllForUserType');
// Standard get, update and deletion for all types
Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail');
Route::get('/base64/{id}', 'ImageController@getBase64Image');
- Route::put('/update/{imageId}', 'ImageController@update');
- Route::post('/drawing/upload', 'ImageController@uploadDrawing');
Route::get('/usage/{id}', 'ImageController@usage');
- Route::post('/{type}/upload', 'ImageController@uploadByType');
Route::get('/{type}/all', 'ImageController@getAllByType');
Route::get('/{type}/all/{page}', 'ImageController@getAllByType');
Route::get('/{type}/search/{page}', 'ImageController@searchByType');
Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered');
+
+ // TODO - Remove use of abstract "Type" variable (Above)
+ // TODO - Clearly define each endpoint so logic for each is clear
+ // TODO - Move into per-type controllers
+ // TODO - Test and fully think about permissions and each stage
+ Route::post('/drawio', 'ImageController@uploadDrawioImage');
+ Route::post('/gallery', 'ImageController@uploadGalleryImage');
+ Route::post('/user', 'ImageController@uploadUserImage');
+ Route::post('/system', 'ImageController@uploadSystemImage');
+ Route::post('/cover', 'ImageController@uploadCoverImage');
+
+ Route::put('/{id}', 'ImageController@update');
Route::delete('/{id}', 'ImageController@destroy');
});
$this->assertTrue($testImageData === $uploadedImageData, "Uploaded image file data does not match our test image as expected");
}
- public function test_user_images_deleted_on_user_deletion()
+ protected function getTestProfileImage()
+ {
+ $imageName = 'profile.png';
+ $relPath = $this->getTestImagePath('user', $imageName);
+ $this->deleteImage($relPath);
+
+ return $this->getTestImage($imageName);
+ }
+
+ public function test_user_image_upload()
+ {
+ $editor = $this->getEditor();
+ $admin = $this->getAdmin();
+ $this->actingAs($admin);
+
+ $file = $this->getTestProfileImage();
+ $this->call('POST', '/images/user/upload', ['uploaded_to' => $editor->id], [], ['file' => $file], []);
+
+ $this->assertDatabaseHas('images', [
+ 'type' => 'user',
+ 'uploaded_to' => $editor->id,
+ 'created_by' => $admin->id,
+ ]);
+ }
+
+ public function test_standard_user_with_manage_users_permission_can_view_other_profile_images()
{
$editor = $this->getEditor();
+ $this->giveUserPermissions($editor, ['users-manage']);
+
+ $admin = $this->getAdmin();
+
+ $this->actingAs($admin);
+ $file = $this->getTestProfileImage();
+ $this->call('POST', '/images/user/upload', ['uploaded_to' => $admin->id], [], ['file' => $file], []);
+
+ $expectedJson = [
+ 'name' => 'profile.png',
+ 'uploaded_to' => $admin->id,
+ 'type' => 'user'
+ ];
+
$this->actingAs($editor);
+ $adminImagesGet = $this->get("/images/user/all/0?uploaded_to=" . $admin->id);
+ $adminImagesGet->assertStatus(200)->assertJsonFragment($expectedJson);
- $imageName = 'profile.png';
- $relPath = $this->getTestImagePath('gallery', $imageName);
- $this->deleteImage($relPath);
+ $allImagesGet = $this->get("/images/user/all/0");
+ $allImagesGet->assertStatus(200)->assertJsonFragment($expectedJson);
+ }
- $file = $this->getTestImage($imageName);
- $this->call('POST', '/images/user/upload', [], [], ['file' => $file], []);
- $this->call('POST', '/images/user/upload', [], [], ['file' => $file], []);
+ public function test_standard_user_cant_view_other_profile_images()
+ {
+ $editor = $this->getEditor();
+ $admin = $this->getAdmin();
+
+ $this->actingAs($admin);
+ $file = $this->getTestProfileImage();
+ $this->call('POST', '/images/user/upload', ['uploaded_to' => $admin->id], [], ['file' => $file], []);
+
+ $this->actingAs($editor);
+ $adminImagesGet = $this->get("/images/user/all/0?uploaded_to=" . $admin->id);
+ $adminImagesGet->assertStatus(302);
+
+ $allImagesGet = $this->get("/images/user/all/0");
+ $allImagesGet->assertStatus(302);
+ }
+
+ public function test_standard_user_cant_upload_other_profile_images()
+ {
+ $editor = $this->getEditor();
+ $admin = $this->getAdmin();
+
+ $this->actingAs($editor);
+ $file = $this->getTestProfileImage();
+ $upload = $this->call('POST', '/images/user/upload', ['uploaded_to' => $admin->id], [], ['file' => $file], []);
+ $upload->assertStatus(302);
+
+ $this->assertDatabaseMissing('images', [
+ 'type' => 'user',
+ 'uploaded_to' => $admin->id,
+ ]);
+ }
+
+ public function test_user_images_deleted_on_user_deletion()
+ {
+ $editor = $this->getEditor();
+ $this->actingAs($editor);
+
+ $file = $this->getTestProfileImage();
+ $this->call('POST', '/images/user/upload', ['uploaded_to' => $editor->id], [], ['file' => $file], []);
+ $this->call('POST', '/images/user/upload', ['uploaded_to' => $editor->id], [], ['file' => $file], []);
$profileImages = Image::where('type', '=', 'user')->where('created_by', '=', $editor->id)->get();
$this->assertTrue($profileImages->count() === 2, "Found profile images does not match upload count");
'type' => 'user',
'created_by' => $editor->id
]);
+ $this->assertDatabaseMissing('images', [
+ 'type' => 'user',
+ 'uploaded_to' => $editor->id
+ ]);
}
public function test_deleted_unused_images()