mirror of
https://github.com/flarum/framework.git
synced 2025-02-21 11:45:57 +08:00
Merge pull request #50 from flarum/avatar-api
API for uploading avatars
This commit is contained in:
commit
0fdbb75914
45
framework/core/ember/app/components/user/avatar-editor.js
Normal file
45
framework/core/ember/app/components/user/avatar-editor.js
Normal file
@ -0,0 +1,45 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
import config from 'flarum/config/environment';
|
||||
|
||||
var $ = Ember.$;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
layoutName: 'components/user/avatar-editor',
|
||||
classNames: ['avatar-editor', 'dropdown'],
|
||||
classNameBindings: ['loading'],
|
||||
|
||||
click: function(e) {
|
||||
if (! this.get('user.avatarUrl')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.send('upload');
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
upload: function() {
|
||||
if (this.get('loading')) { return; }
|
||||
|
||||
var $input = $('<input type="file">');
|
||||
var userId = this.get('user.id');
|
||||
var component = this;
|
||||
$input.appendTo('body').hide().click().on('change', function() {
|
||||
var formData = new FormData();
|
||||
formData.append('avatar', $(this)[0].files[0]);
|
||||
component.set('loading', true);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.apiURL+'/users/'+userId+'/avatar',
|
||||
data: formData,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
complete: function() {
|
||||
component.set('loading', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -47,10 +47,17 @@
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
& .avatar {
|
||||
.avatar-size(96px);
|
||||
& .user-avatar {
|
||||
float: left;
|
||||
margin-left: -130px;
|
||||
}
|
||||
& .avatar-editor .dropdown-toggle {
|
||||
margin: 4px;
|
||||
line-height: 96px;
|
||||
font-size: 26px;
|
||||
}
|
||||
& .avatar {
|
||||
.avatar-size(96px);
|
||||
border: 4px solid #fff;
|
||||
.box-shadow(0 2px 6px @fl-shadow-color);
|
||||
}
|
||||
@ -170,3 +177,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-editor {
|
||||
position: relative;
|
||||
|
||||
& .dropdown-toggle {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
&:hover .dropdown-toggle, &.open .dropdown-toggle, &.loading .dropdown-toggle {
|
||||
opacity: 1;
|
||||
}
|
||||
& .loading-indicator {
|
||||
color: #fff;
|
||||
}
|
||||
& .dropdown-menu {
|
||||
left: 35%;
|
||||
top: 65%;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
{{user-avatar user}}
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
{{#if loading}}
|
||||
{{ui/loading-indicator}}
|
||||
{{else}}
|
||||
{{fa-icon "pencil"}}
|
||||
{{/if}}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" {{action "upload"}}>{{fa-icon "upload"}} Upload</a></li>
|
||||
<li><a href="#" {{action "remove"}}>{{fa-icon "times"}} Remove</a></li>
|
||||
</ul>
|
@ -6,7 +6,11 @@
|
||||
|
||||
<div class="user-profile">
|
||||
<h2 class="user-identity">
|
||||
{{#link-to "user" user}}{{user-avatar user}}{{user-name user}}{{/link-to}}
|
||||
{{#if editable}}
|
||||
{{user/avatar-editor user=user class="user-avatar"}}{{user-name user}}
|
||||
{{else}}
|
||||
{{#link-to "user" user}}{{user-avatar user class="user-avatar"}}{{user-name user}}{{/link-to}}
|
||||
{{/if}}
|
||||
</h2>
|
||||
|
||||
{{ui/item-list items=user.badges class="badges user-badges"}}
|
||||
|
@ -23,6 +23,7 @@ class CreateUsersTable extends Migration {
|
||||
$table->string('password');
|
||||
$table->text('bio')->nullable();
|
||||
$table->text('bio_html')->nullable();
|
||||
$table->string('avatar_path')->nullable();
|
||||
$table->dateTime('join_time')->nullable();
|
||||
$table->dateTime('last_seen_time')->nullable();
|
||||
$table->dateTime('read_time')->nullable();
|
||||
|
@ -13,8 +13,6 @@ use Response;
|
||||
|
||||
abstract class BaseAction extends Action
|
||||
{
|
||||
abstract protected function run(ApiParams $params);
|
||||
|
||||
public function __construct(Actor $actor, Dispatcher $bus)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
@ -40,6 +38,15 @@ abstract class BaseAction extends Action
|
||||
return $this->run($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiParams $params
|
||||
* @return mixed
|
||||
*/
|
||||
protected function run(ApiParams $params)
|
||||
{
|
||||
// Should be implemented by subclasses
|
||||
}
|
||||
|
||||
public function hydrate($object, $params)
|
||||
{
|
||||
foreach ($params as $k => $v) {
|
||||
|
21
framework/core/src/Api/Actions/Users/UploadAvatarAction.php
Normal file
21
framework/core/src/Api/Actions/Users/UploadAvatarAction.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace Flarum\Api\Actions\Users;
|
||||
|
||||
use Flarum\Api\Actions\BaseAction;
|
||||
use Flarum\Core\Commands\UploadAvatarCommand;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UploadAvatarAction extends BaseAction
|
||||
{
|
||||
public function handle(Request $request, $routeParams = [])
|
||||
{
|
||||
$userId = array_get($routeParams, 'id');
|
||||
$file = $request->file('avatar');
|
||||
|
||||
$this->dispatch(
|
||||
new UploadAvatarCommand($userId, $file, $this->actor->getUser()),
|
||||
$routeParams
|
||||
);
|
||||
|
||||
return $this->respondWithoutContent(201);
|
||||
}
|
||||
}
|
@ -59,6 +59,11 @@ Route::group(['prefix' => 'api', 'middleware' => 'Flarum\Api\Middleware\LoginWit
|
||||
'uses' => $action('Flarum\Api\Actions\Users\DeleteAction')
|
||||
]);
|
||||
|
||||
Route::post('users/{id}/avatar', [
|
||||
'as' => 'flarum.api.users.avatar.upload',
|
||||
'uses' => $action('Flarum\Api\Actions\Users\UploadAvatarAction')
|
||||
]);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Activity
|
||||
|
30
framework/core/src/Core/Commands/UploadAvatarCommand.php
Normal file
30
framework/core/src/Core/Commands/UploadAvatarCommand.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class UploadAvatarCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\HttpFoundation\File\UploadedFile
|
||||
*/
|
||||
public $file;
|
||||
|
||||
public $actor;
|
||||
|
||||
public function __construct($userId, $file, $actor)
|
||||
{
|
||||
if (empty($userId) || !intval($userId)) {
|
||||
throw new RuntimeException('No valid user ID specified.');
|
||||
}
|
||||
|
||||
if (is_null($file)) {
|
||||
throw new RuntimeException('No file to upload');
|
||||
}
|
||||
|
||||
$this->userId = $userId;
|
||||
$this->file = $file;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<?php namespace Flarum\Core;
|
||||
|
||||
use Illuminate\Bus\Dispatcher as Bus;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Flarum\Core\Formatter\FormatterManager;
|
||||
@ -12,6 +13,7 @@ use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Models\Discussion;
|
||||
use Flarum\Core\Models\Notification;
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
|
||||
class CoreServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -83,6 +85,16 @@ class CoreServiceProvider extends ServiceProvider
|
||||
'Flarum\Core\Repositories\NotificationRepositoryInterface',
|
||||
'Flarum\Core\Repositories\EloquentNotificationRepository'
|
||||
);
|
||||
|
||||
$this->app->singleton('flarum.avatars.storage', function () {
|
||||
return new Local(__DIR__.'/../../ember/public/avatars');
|
||||
});
|
||||
|
||||
$this->app->when('Flarum\Core\Handlers\Commands\UploadAvatarCommandHandler')
|
||||
->needs('League\Flysystem\FilesystemInterface')
|
||||
->give(function(Container $app) {
|
||||
return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('avatars')->getDriver();
|
||||
});
|
||||
}
|
||||
|
||||
public function registerGambits()
|
||||
|
16
framework/core/src/Core/Events/AvatarWillBeUploaded.php
Normal file
16
framework/core/src/Core/Events/AvatarWillBeUploaded.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class AvatarWillBeUploaded
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $command;
|
||||
|
||||
public function __construct(User $user, $command)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->command = $command;
|
||||
}
|
||||
}
|
13
framework/core/src/Core/Events/UserAvatarWasChanged.php
Normal file
13
framework/core/src/Core/Events/UserAvatarWasChanged.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserAvatarWasChanged
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php namespace Flarum\Core\Handlers\Commands;
|
||||
|
||||
use Flarum\Core\Commands\UploadAvatarCommand;
|
||||
use Flarum\Core\Events\AvatarWillBeUploaded;
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use League\Flysystem\MountManager;
|
||||
|
||||
class UploadAvatarCommandHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var UserRepositoryInterface
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @var FilesystemInterface
|
||||
*/
|
||||
protected $uploadDir;
|
||||
|
||||
public function __construct(UserRepositoryInterface $users, FilesystemInterface $uploadDir)
|
||||
{
|
||||
$this->users = $users;
|
||||
$this->uploadDir = $uploadDir;
|
||||
}
|
||||
|
||||
public function handle(UploadAvatarCommand $command)
|
||||
{
|
||||
$user = $this->users->findOrFail($command->userId);
|
||||
|
||||
// Make sure the current user is allowed to edit the user profile.
|
||||
// This will let admins and the user themselves pass through, and
|
||||
// throw an exception otherwise.
|
||||
$user->assertCan($command->actor, 'edit');
|
||||
|
||||
$filename = $command->file->getFilename();
|
||||
$uploadName = Str::lower(Str::quickRandom()) . '.jpg';
|
||||
|
||||
$mount = new MountManager([
|
||||
'source' => new Filesystem(new Local($command->file->getPath())),
|
||||
'target' => $this->uploadDir,
|
||||
]);
|
||||
|
||||
$user->changeAvatarPath($uploadName);
|
||||
|
||||
event(new AvatarWillBeUploaded($user, $command));
|
||||
|
||||
$mount->move("source://$filename", "target://$uploadName");
|
||||
$user->save();
|
||||
$this->dispatchEventsFor($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use Flarum\Core\Events\UserWasRenamed;
|
||||
use Flarum\Core\Events\UserEmailWasChanged;
|
||||
use Flarum\Core\Events\UserPasswordWasChanged;
|
||||
use Flarum\Core\Events\UserBioWasChanged;
|
||||
use Flarum\Core\Events\UserAvatarWasChanged;
|
||||
use Flarum\Core\Events\UserWasActivated;
|
||||
use Flarum\Core\Events\UserEmailWasConfirmed;
|
||||
|
||||
@ -210,6 +211,21 @@ class User extends Model
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the path of the user avatar.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
*/
|
||||
public function changeAvatarPath($path)
|
||||
{
|
||||
$this->avatar_path = $path;
|
||||
|
||||
$this->raise(new UserAvatarWasChanged($this));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given password matches the user's password.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user