Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Зуев Егор
/
wiki.dev
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
Dan Brown
2015-12-09 22:30:55 +0000
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
8f7c642f320f87e5c6b11b11c64089d56a51c1b9
8f7c642f
1 parent
db3acabc
Added custom user avatars
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
219 additions
and
57 deletions
.env.example
app/Http/Controllers/ImageController.php
app/Http/Controllers/UserController.php
app/Http/routes.php
app/Image.php
app/Repos/ImageRepo.php
app/Services/ImageService.php
app/User.php
database/migrations/2015_12_09_195748_add_user_avatars.php
public/user_avatar.png
resources/assets/js/components/image-manager.vue
resources/assets/js/components/image-picker.vue
resources/assets/sass/styles.scss
resources/views/settings/index.blade.php
resources/views/users/edit.blade.php
resources/views/users/form.blade.php
.env.example
View file @
8f7c642
...
...
@@ -33,6 +33,9 @@ GOOGLE_APP_SECRET=false
# URL used for social login redirects, NO TRAILING SLASH
APP_URL=http://bookstack.dev
# External services
USE_GRAVATAR=true
# Mail settings
MAIL_DRIVER=smtp
MAIL_HOST=localhost
...
...
app/Http/Controllers/ImageController.php
View file @
8f7c642
...
...
@@ -33,7 +33,7 @@ class ImageController extends Controller
/**
* Get all
gallery images
, Paginated
* Get all
images for a specific type
, Paginated
* @param int $page
* @return \Illuminate\Http\JsonResponse
*/
...
...
@@ -43,6 +43,17 @@ class ImageController extends Controller
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
);
}
/**
* Handles image uploads for use on pages.
...
...
app/Http/Controllers/UserController.php
View file @
8f7c642
...
...
@@ -62,7 +62,7 @@ class UserController extends Controller
$this
->
checkPermission
(
'user-create'
);
$this
->
validate
(
$request
,
[
'name'
=>
'required'
,
'email'
=>
'required|email'
,
'email'
=>
'required|email
|unique:users,email
'
,
'password'
=>
'required|min:5'
,
'password-confirm'
=>
'required|same:password'
,
'role'
=>
'required|exists:roles,id'
...
...
app/Http/routes.php
View file @
8f7c642
...
...
@@ -57,6 +57,9 @@ Route::group(['middleware' => 'auth'], function () {
// Image routes
Route
::
group
([
'prefix'
=>
'images'
],
function
()
{
// Get for user images
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
::
put
(
'/update/{imageId}'
,
'ImageController@update'
);
...
...
app/Image.php
View file @
8f7c642
...
...
@@ -3,9 +3,10 @@
namespace
BookStack
;
use
Illuminate\Database\Eloquent\Model
;
use
Images
;
class
Image
class
Image
extends
Model
{
use
Ownable
;
...
...
@@ -16,9 +17,10 @@ class Image
* @param int $width
* @param int $height
* @param bool|false $hardCrop
* @return string
*/
public
function
getThumb
(
$width
,
$height
,
$hardCrop
=
false
)
{
Images
::
getThumbnail
(
$this
,
$width
,
$height
,
$hardCrop
);
return
Images
::
getThumbnail
(
$this
,
$width
,
$height
,
$hardCrop
);
}
}
...
...
app/Repos/ImageRepo.php
View file @
8f7c642
...
...
@@ -17,7 +17,7 @@ class ImageRepo
* @param Image $image
* @param ImageService $imageService
*/
public
function
__construct
(
Image
$image
,
ImageService
$imageService
)
public
function
__construct
(
Image
$image
,
ImageService
$imageService
)
{
$this
->
image
=
$image
;
$this
->
imageService
=
$imageService
;
...
...
@@ -40,12 +40,18 @@ class ImageRepo
* @param string $type
* @param int $page
* @param int $pageSize
* @param bool|int $userFilter
* @return array
*/
public
function
getPaginatedByType
(
$type
,
$page
=
0
,
$pageSize
=
24
)
public
function
getPaginatedByType
(
$type
,
$page
=
0
,
$pageSize
=
24
,
$userFilter
=
false
)
{
$images
=
$this
->
image
->
where
(
'type'
,
'='
,
strtolower
(
$type
))
->
orderBy
(
'created_at'
,
'desc'
)
->
skip
(
$pageSize
*
$page
)
->
take
(
$pageSize
+
1
)
->
get
();
$images
=
$this
->
image
->
where
(
'type'
,
'='
,
strtolower
(
$type
));
if
(
$userFilter
!==
false
)
{
$images
=
$images
->
where
(
'created_by'
,
'='
,
$userFilter
);
}
$images
=
$images
->
orderBy
(
'created_at'
,
'desc'
)
->
skip
(
$pageSize
*
$page
)
->
take
(
$pageSize
+
1
)
->
get
();
$hasMore
=
count
(
$images
)
>
$pageSize
;
$returnImages
=
$images
->
take
(
24
);
...
...
@@ -67,7 +73,7 @@ class ImageRepo
*/
public
function
saveNew
(
UploadedFile
$uploadFile
,
$type
)
{
$image
=
$this
->
imageService
->
saveNew
(
$this
->
image
,
$uploadFile
,
$type
);
$image
=
$this
->
imageService
->
saveNew
FromUpload
(
$uploadFile
,
$type
);
$this
->
loadThumbs
(
$image
);
return
$image
;
}
...
...
app/Services/ImageService.php
View file @
8f7c642
<?php
namespace
BookStack\Services
;
use
BookStack\Image
;
use
BookStack\User
;
use
Intervention\Image\ImageManager
;
use
Illuminate\Contracts\Filesystem\Factory
as
FileSystem
;
use
Illuminate\Contracts\Filesystem\Filesystem
as
FileSystemInstance
;
...
...
@@ -34,11 +35,48 @@ class ImageService
$this
->
cache
=
$cache
;
}
public
function
saveNew
(
Image
$image
,
UploadedFile
$uploadedFile
,
$type
)
/**
* Saves a new image from an upload.
* @param UploadedFile $uploadedFile
* @param string $type
* @return mixed
*/
public
function
saveNewFromUpload
(
UploadedFile
$uploadedFile
,
$type
)
{
$imageName
=
$uploadedFile
->
getClientOriginalName
();
$imageData
=
file_get_contents
(
$uploadedFile
->
getRealPath
());
return
$this
->
saveNew
(
$imageName
,
$imageData
,
$type
);
}
/**
* 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
);
$imageData
=
file_get_contents
(
$url
);
if
(
$imageData
===
false
)
throw
new
\Exception
(
'Cannot get image from '
.
$url
);
return
$this
->
saveNew
(
$imageName
,
$imageData
,
$type
);
}
/**
* Saves a new image
* @param string $imageName
* @param string $imageData
* @param string $type
* @return Image
*/
private
function
saveNew
(
$imageName
,
$imageData
,
$type
)
{
$storage
=
$this
->
getStorage
();
$secureUploads
=
Setting
::
get
(
'app-secure-images'
);
$imageName
=
str_replace
(
' '
,
'-'
,
$
uploadedFile
->
getClientOriginalName
()
);
$imageName
=
str_replace
(
' '
,
'-'
,
$
imageName
);
if
(
$secureUploads
)
$imageName
=
str_random
(
16
)
.
'-'
.
$imageName
;
...
...
@@ -48,10 +86,10 @@ class ImageService
}
$fullPath
=
$imagePath
.
$imageName
;
$storage
->
put
(
$fullPath
,
file_get_contents
(
$uploadedFile
->
getRealPath
())
);
$storage
->
put
(
$fullPath
,
$imageData
);
$userId
=
auth
()
->
user
()
->
id
;
$image
=
$image
->
forceCreate
([
$image
=
Image
::
forceCreate
([
'name'
=>
$imageName
,
'path'
=>
$fullPath
,
'url'
=>
$this
->
getPublicUrl
(
$fullPath
),
...
...
@@ -138,6 +176,26 @@ class ImageService
}
/**
* Save a gravatar image and set a the profile image for a user.
* @param User $user
* @param int $size
* @return mixed
*/
public
function
saveUserGravatar
(
User
$user
,
$size
=
500
)
{
if
(
!
env
(
'USE_GRAVATAR'
,
false
))
return
false
;
$emailHash
=
md5
(
strtolower
(
trim
(
$user
->
email
)));
$url
=
'http://www.gravatar.com/avatar/'
.
$emailHash
.
'?s='
.
$size
.
'&d=identicon'
;
$imageName
=
str_replace
(
' '
,
'-'
,
$user
->
name
.
'-gravatar.png'
);
$image
=
$this
->
saveNewFromUrl
(
$url
,
'user'
,
$imageName
);
$image
->
created_by
=
$user
->
id
;
$image
->
save
();
$user
->
avatar
()
->
associate
(
$image
);
$user
->
save
();
return
$image
;
}
/**
* Get the storage that will be used for storing images.
* @return FileSystemInstance
*/
...
...
app/User.php
View file @
8f7c642
...
...
@@ -24,7 +24,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*
* @var array
*/
protected
$fillable
=
[
'name'
,
'email'
,
'password'
];
protected
$fillable
=
[
'name'
,
'email'
,
'password'
,
'image_id'
];
/**
* The attributes excluded from the model's JSON form.
...
...
@@ -145,8 +145,17 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public
function
getAvatar
(
$size
=
50
)
{
$emailHash
=
md5
(
strtolower
(
trim
(
$this
->
email
)));
return
'//www.gravatar.com/avatar/'
.
$emailHash
.
'?s='
.
$size
.
'&d=identicon'
;
if
(
$this
->
image_id
===
0
||
$this
->
image_id
===
null
)
return
'/user_avatar.png'
;
return
$this
->
avatar
->
getThumb
(
$size
,
$size
,
true
);
}
/**
* Get the avatar for the user.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public
function
avatar
()
{
return
$this
->
belongsTo
(
'BookStack\Image'
,
'image_id'
);
}
/**
...
...
database/migrations/2015_12_09_195748_add_user_avatars.php
0 → 100644
View file @
8f7c642
<?php
use
Illuminate\Database\Schema\Blueprint
;
use
Illuminate\Database\Migrations\Migration
;
class
AddUserAvatars
extends
Migration
{
/**
* Run the migrations.
*
* @return void
*/
public
function
up
()
{
Schema
::
table
(
'users'
,
function
(
Blueprint
$table
)
{
$table
->
integer
(
'image_id'
)
->
default
(
0
);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public
function
down
()
{
Schema
::
table
(
'users'
,
function
(
Blueprint
$table
)
{
$table
->
dropColumn
(
'image_id'
);
});
}
}
public/user_avatar.png
0 → 100644
View file @
8f7c642
7.23 KB
resources/assets/js/components/image-manager.vue
View file @
8f7c642
...
...
@@ -80,15 +80,6 @@
imageType: {
type: String,
required: true
},
resizeWidth: {
type: String
},
resizeHeight: {
type: String
},
resizeCrop: {
type: Boolean
}
},
...
...
@@ -137,21 +128,7 @@
},
returnCallback: function (image) {
var _this = this;
var isResized = _this.resizeWidth && _this.resizeHeight;
if (!isResized) {
_this.callback(image);
return;
}
var cropped = _this.resizeCrop ? 'true' : 'false';
var requestString = '/images/thumb/' + image.id + '/' + _this.resizeWidth + '/' + _this.resizeHeight + '/' + cropped;
_this.$http.get(requestString, function(data) {
image.thumbs.custom = data.url;
_this.callback(image);
});
this.callback(image);
},
imageClick: function (image) {
...
...
resources/assets/js/components/image-picker.vue
View file @
8f7c642
...
...
@@ -7,31 +7,89 @@
</div>
<button class="button" type="button" @click="showImageManager">Select Image</button>
<br>
<button class="text-button" @click="reset" type="button">Reset</button> <span
class="sep">|</span> <button class="text-button neg" v-on:
click="remove" type="button">Remove</button>
<input type="hidden" :name="name" :id="name" v-model="
imag
e">
<button class="text-button" @click="reset" type="button">Reset</button> <span
v-show="showRemove" class="sep">|</span> <button v-show="showRemove" class="text-button neg" @
click="remove" type="button">Remove</button>
<input type="hidden" :name="name" :id="name" v-model="
valu
e">
</div>
</template>
<script>
module.exports = {
props: ['currentImage', 'name', 'imageClass', 'defaultImage'],
props: {
currentImage: {
required: true,
type: String
},
currentId: {
required: false,
default: 'false',
type: String
},
name: {
required: true,
type: String
},
defaultImage: {
required: true,
type: String
},
imageClass: {
required: true,
type: String
},
resizeWidth: {
type: String
},
resizeHeight: {
type: String
},
resizeCrop: {
type: Boolean
},
showRemove: {
type: Boolean,
default: 'true'
}
},
data: function() {
return {
image: this.currentImage
image: this.currentImage,
value: false
}
},
compiled: function() {
this.value = this.currentId === 'false' ? this.currentImage : this.currentId;
},
methods: {
setCurrentValue: function(imageModel, imageUrl) {
this.image = imageUrl;
this.value = this.currentId === 'false' ? imageUrl : imageModel.id;
},
showImageManager: function(e) {
var _this = this;
ImageManager.show(function(image) {
_this.
image = image.thumbs.custom || image.url
;
_this.
updateImageFromModel(image)
;
});
},
reset: function() {
this.
image = ''
;
this.
setCurrentValue({id: 0}, this.defaultImage)
;
},
remove: function() {
this.image = 'none';
},
updateImageFromModel: function(model) {
var _this = this;
var isResized = _this.resizeWidth && _this.resizeHeight;
if (!isResized) {
_this.setCurrentValue(model, model.url);
return;
}
var cropped = _this.resizeCrop ? 'true' : 'false';
var requestString = '/images/thumb/' + model.id + '/' + _this.resizeWidth + '/' + _this.resizeHeight + '/' + cropped;
_this.$http.get(requestString, function(data) {
_this.setCurrentValue(model, data.url);
});
}
}
};
...
...
resources/assets/sass/styles.scss
View file @
8f7c642
...
...
@@ -36,6 +36,10 @@ body.dragging, body.dragging * {
width
:
40px
;
height
:
40px
;
}
&
.large
{
width
:
80px
;
height
:
80px
;
}
}
// System wide notifications
...
...
resources/views/settings/index.blade.php
View file @
8f7c642
...
...
@@ -33,7 +33,7 @@
<div
class=
"form-group"
id=
"logo-control"
>
<label
for=
"setting-app-logo"
>
Application Logo
</label>
<p
class=
"small"
>
This image should be 43px in height.
<br>
Large images will be scaled down.
</p>
<image-picker
current-image=
"{{ Setting::get('app-logo', '') }}"
default-image=
"/logo.png"
name=
"setting-app-logo"
image-class=
"logo-image"
></image-picker>
<image-picker
resize-height=
"43"
resize-width=
"200"
current-image=
"{{ Setting::get('app-logo', '') }}"
default-image=
"/logo.png"
name=
"setting-app-logo"
image-class=
"logo-image"
></image-picker>
</div>
</div>
</div>
...
...
@@ -86,6 +86,6 @@
</div>
<image-manager
image-type=
"system"
resize-height=
"43"
resize-width=
"200"
></image-manager>
<image-manager
image-type=
"system"
></image-manager>
@stop
...
...
resources/views/users/edit.blade.php
View file @
8f7c642
...
...
@@ -19,26 +19,25 @@
<div
class=
"container small"
>
<form
action=
"/users/{{$user->id}}"
method=
"post"
>
<div
class=
"row"
>
<div
class=
"col-md-6"
>
<h1>
Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}
</h1>
<form
action=
"/users/{{$user->id}}"
method=
"post"
>
{!! csrf_field() !!}
<input
type=
"hidden"
name=
"_method"
value=
"put"
>
@include('users/form', ['model' => $user])
</form>
</div>
<div
class=
"col-md-6"
>
<h1>
</h1>
<div
class=
"shaded padded margin-top"
>
<p>
<img
class=
"avatar"
src=
"{{ $user->getAvatar(80) }}"
alt=
"{{ $user->name }}"
>
</p>
<p
class=
"text-muted"
>
You can change your profile picture at
<a
href=
"http://en.gravatar.com/"
>
Gravatar
</a>
.
</p>
<div
class=
"form-group"
id=
"logo-control"
>
<label
for=
"user-avatar"
>
User Avatar
</label>
<p
class=
"small"
>
This image should be approx 256px square.
</p>
<image-picker
resize-height=
"512"
resize-width=
"512"
current-image=
"{{ $user->getAvatar(80) }}"
current-id=
"{{ $user->image_id }}"
default-image=
"/user_avatar.png"
name=
"image_id"
show-remove=
"false"
image-class=
"avatar large"
></image-picker>
</div>
</div>
</div>
</form>
<hr
class=
"margin-top large"
>
...
...
@@ -80,5 +79,5 @@
</div>
<p
class=
"margin-top large"
><br></p>
<image-manager
image-type=
"user"
></image-manager>
@stop
...
...
resources/views/users/form.blade.php
View file @
8f7c642
...
...
@@ -37,3 +37,4 @@
<a
href=
"/users"
class=
"button muted"
>
Cancel
</a>
<button
class=
"button pos"
type=
"submit"
>
Save
</button>
</div>
...
...
Please
register
or
sign in
to post a comment