#29: Labels can now be added and managed through the admin panel

This commit is contained in:
Andy Heathershaw 2017-09-10 09:07:56 +01:00
parent aa99d76ae5
commit 6280766d70
17 changed files with 341 additions and 3 deletions

View File

@ -11,6 +11,7 @@ use App\Helpers\ConfigHelper;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\SaveSettingsRequest; use App\Http\Requests\SaveSettingsRequest;
use App\Label;
use App\Mail\TestMailConfig; use App\Mail\TestMailConfig;
use App\Photo; use App\Photo;
use App\User; use App\User;
@ -36,12 +37,14 @@ class DefaultController extends Controller
$albumCount = DbHelper::getAlbumsForCurrentUser()->count(); $albumCount = DbHelper::getAlbumsForCurrentUser()->count();
$photoCount = Photo::all()->count(); $photoCount = Photo::all()->count();
$groupCount = Group::all()->count(); $groupCount = Group::all()->count();
$labelCount = Label::all()->count();
$userCount = User::where('is_activated', true)->count(); $userCount = User::where('is_activated', true)->count();
return Theme::render('admin.index', [ return Theme::render('admin.index', [
'album_count' => $albumCount, 'album_count' => $albumCount,
'app_version' => config('app.version'), 'app_version' => config('app.version'),
'group_count' => $groupCount, 'group_count' => $groupCount,
'label_count' => $labelCount,
'memory_limit' => ini_get('memory_limit'), 'memory_limit' => ini_get('memory_limit'),
'photo_count' => $photoCount, 'photo_count' => $photoCount,
'php_version' => phpversion(), 'php_version' => phpversion(),

View File

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Facade\Theme;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreLabelRequest;
use App\Label;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\View;
use Symfony\Component\HttpFoundation\Request;
class LabelController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'max_post_size_exceeded']);
View::share('is_admin', true);
}
public function delete($id)
{
$this->authorizeAccessToAdminPanel();
$label = $this->loadLabel($id);
return Theme::render('admin.delete_label', ['label' => $label]);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request, $id)
{
$this->authorizeAccessToAdminPanel('admin:manage-labels');
$label = $this->loadLabel($id);
$label->delete();
$request->session()->flash('success', trans('admin.delete_label_success_message', ['name' => $label->name]));
return redirect(route('labels.index'));
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$labels = Label::withCount('photos')->get();
return Theme::render('admin.list_labels', [
'labels' => $labels
]);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(StoreLabelRequest $request)
{
$this->authorizeAccessToAdminPanel('admin:manage-labels');
$label = new Label();
$label->fill($request->only(['name']));
$label->save();
return redirect(route('labels.index'));
}
/**
* @param $id
* @return Album
*/
private function loadLabel($id)
{
$label = Label::where('id', intval($id))->first();
if (is_null($label))
{
App::abort(404);
return null;
}
return $label;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreLabelRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return ['name' => 'required:max:255|unique:labels,name'];
}
}

22
app/Label.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Label extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name'
];
public function photos()
{
return $this->belongsToMany(Photo::class, 'photo_labels');
}
}

View File

@ -56,6 +56,10 @@ class AuthServiceProvider extends ServiceProvider
{ {
return $this->userHasAdminPermission($user, 'manage-groups'); return $this->userHasAdminPermission($user, 'manage-groups');
}); });
Gate::define('admin:manage-labels', function ($user)
{
return $this->userHasAdminPermission($user, 'manage-labels');
});
Gate::define('admin:manage-storage', function ($user) Gate::define('admin:manage-storage', function ($user)
{ {
return $this->userHasAdminPermission($user, 'manage-storage'); return $this->userHasAdminPermission($user, 'manage-storage');

View File

@ -2,7 +2,7 @@
return [ return [
// Version number of Blue Twilight // Version number of Blue Twilight
'version' => '2.0.1', 'version' => '2.1.0-beta.1',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePhotoLabelsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('labels', function ($table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('photo_labels', function ($table) {
$table->unsignedBigInteger('photo_id');
$table->unsignedInteger('label_id');
$table->foreign('photo_id')
->references('id')->on('photos')
->onDelete('cascade');
$table->foreign('label_id')
->references('id')->on('labels')
->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('photo_labels');
Schema::dropIfExists('labels');
}
}

View File

@ -64,6 +64,14 @@ class PermissionsSeeder extends Seeder
'is_default' => false, 'is_default' => false,
'sort_order' => 0 'sort_order' => 0
]); ]);
// admin:manage-labels = controls if photo labels can be managed
DatabaseSeeder::createOrUpdate('permissions', [
'section' => 'admin',
'description' => 'manage-labels',
'is_default' => false,
'sort_order' => 0
]);
} }
private function seedAlbumPermissions() private function seedAlbumPermissions()

View File

@ -46,6 +46,8 @@ return [
'create_album_no_storage' => 'There are currently no storage locations set up. Please create a location to store your photos before creating an album.', 'create_album_no_storage' => 'There are currently no storage locations set up. Please create a location to store your photos before creating an album.',
'create_group' => 'Create a group', 'create_group' => 'Create a group',
'create_group_intro' => 'Complete the form below to create a group in which to organise your users.', 'create_group_intro' => 'Complete the form below to create a group in which to organise your users.',
'create_label_intro' => 'Use the form below to add a new label that can be added to your photos.',
'create_label_title' => 'Create a label',
'create_redirect_heading' => 'Add a Redirect', 'create_redirect_heading' => 'Add a Redirect',
'create_redirect_success_message' => 'The redirect was added successfully.', 'create_redirect_success_message' => 'The redirect was added successfully.',
'create_redirect_text' => 'Enter the source address you would like to redirect and click the Create button to add a new redirect to this album.', 'create_redirect_text' => 'Enter the source address you would like to redirect and click the Create button to add a new redirect to this album.',
@ -66,6 +68,9 @@ return [
'delete_group' => 'Delete group: :name', 'delete_group' => 'Delete group: :name',
'delete_group_confirm' => 'Are you sure you want to permanently remove the ":name" group? Users who are members of this group may lose permissions.', 'delete_group_confirm' => 'Are you sure you want to permanently remove the ":name" group? Users who are members of this group may lose permissions.',
'delete_group_warning' => 'This is a permanent action that cannot be reversed!', 'delete_group_warning' => 'This is a permanent action that cannot be reversed!',
'delete_label' => 'Delete label: :name',
'delete_label_confirm' => 'Are you sure you want to permanently remove the ":name" label?',
'delete_label_success_message' => 'The :name label was deleted successfully.',
'delete_photo_message' => 'Are you sure you want to delete this photo? This action cannot be undone!', 'delete_photo_message' => 'Are you sure you want to delete this photo? This action cannot be undone!',
'delete_photo_successful_message' => 'The photo ":name" was deleted successfully.', 'delete_photo_successful_message' => 'The photo ":name" was deleted successfully.',
'delete_photo_title' => 'Delete photo', 'delete_photo_title' => 'Delete photo',
@ -103,11 +108,14 @@ return [
'group_users_tab' => 'Users', 'group_users_tab' => 'Users',
'inactive_storage_legend' => 'Inactive storage location that cannot be used for new albums.', 'inactive_storage_legend' => 'Inactive storage location that cannot be used for new albums.',
'is_uploading' => 'Uploading in progress...', 'is_uploading' => 'Uploading in progress...',
'labels_intro' => 'Your labels are displayed below. The number in brackets indicates the number of photos linked to that label. Click a label to delete it.',
'legend' => 'Legend/Key', 'legend' => 'Legend/Key',
'list_albums_intro' => 'Albums contain collections of individual photographs in the same way as a physical photo album or memory book.', 'list_albums_intro' => 'Albums contain collections of individual photographs in the same way as a physical photo album or memory book.',
'list_albums_title' => 'Albums', 'list_albums_title' => 'Albums',
'list_groups_intro' => 'Organise your users into categories or types by using groups. You can assign permissions on albums to groups of users to make administration and management easier.', 'list_groups_intro' => 'Organise your users into categories or types by using groups. You can assign permissions on albums to groups of users to make administration and management easier.',
'list_groups_title' => 'Groups', 'list_groups_title' => 'Groups',
'list_labels_intro' => 'Organise your photos differently using labels. Assign one or more labels to your photos and your visitors can view all photos with a specific tag in a single view.',
'list_labels_title' => 'Labels',
'list_storages_intro' => 'Storage locations specify the physical location where your photograph files are held. This may be on your local server\'s filesystem, or on a cloud storage provider such as Rackspace or Amazon S3.', 'list_storages_intro' => 'Storage locations specify the physical location where your photograph files are held. This may be on your local server\'s filesystem, or on a cloud storage provider such as Rackspace or Amazon S3.',
'list_storages_title' => 'Storage Locations', 'list_storages_title' => 'Storage Locations',
'list_users_intro' => 'User accounts allow people to login to your gallery to manage your albums. If you have disabled self-registration, you can create user accounts here to allow people to login.', 'list_users_intro' => 'User accounts allow people to login to your gallery to manage your albums. If you have disabled self-registration, you can create user accounts here to allow people to login.',
@ -121,6 +129,8 @@ return [
'no_albums_title' => 'No Photo Albums', 'no_albums_title' => 'No Photo Albums',
'no_groups_text' => 'You have no groups yet. Click the button below to create one.', 'no_groups_text' => 'You have no groups yet. Click the button below to create one.',
'no_groups_title' => 'No Groups', 'no_groups_title' => 'No Groups',
'no_labels_text' => 'You have no labels yet. Use the form below to create one.',
'no_labels_title' => 'No Labels',
'no_photo_selected_message' => 'Please select at least one photo.', 'no_photo_selected_message' => 'Please select at least one photo.',
'no_storages_text' => 'You need a storage location to store your uploaded photographs.', 'no_storages_text' => 'You need a storage location to store your uploaded photographs.',
'no_storages_text2' => 'This can be on your server\'s local filesystem or a cloud location such as Amazon S3 or Rackspace.', 'no_storages_text2' => 'This can be on your server\'s local filesystem or a cloud location such as Amazon S3 or Rackspace.',
@ -182,6 +192,7 @@ return [
'stats_widget' => [ 'stats_widget' => [
'albums' => 'album|albums', 'albums' => 'album|albums',
'groups' => 'group|groups', 'groups' => 'group|groups',
'labels' => 'label|labels',
'panel_header' => 'Statistics', 'panel_header' => 'Statistics',
'photos' => 'photo|photos', 'photos' => 'photo|photos',
'users' => 'user|users', 'users' => 'user|users',

View File

@ -9,6 +9,7 @@ return [
'create_user' => 'Create user', 'create_user' => 'Create user',
'delete_album' => 'Delete album', 'delete_album' => 'Delete album',
'delete_group' => 'Delete group', 'delete_group' => 'Delete group',
'delete_label' => 'Delete label',
'delete_storage' => 'Delete storage location', 'delete_storage' => 'Delete storage location',
'delete_user' => 'Delete user', 'delete_user' => 'Delete user',
'edit_album' => 'Edit album', 'edit_album' => 'Edit album',
@ -16,6 +17,7 @@ return [
'edit_storage' => 'Edit storage location', 'edit_storage' => 'Edit storage location',
'edit_user' => 'Edit user', 'edit_user' => 'Edit user',
'groups' => 'Groups', 'groups' => 'Groups',
'labels' => 'Labels',
'home' => 'Gallery', 'home' => 'Gallery',
'settings' => 'Settings', 'settings' => 'Settings',
'storage' => 'Storage', 'storage' => 'Storage',

View File

@ -5,6 +5,7 @@ return [
'configure' => 'Configure the application', 'configure' => 'Configure the application',
'manage-albums' => 'Manage photo albums', 'manage-albums' => 'Manage photo albums',
'manage-groups' => 'Manage user groups', 'manage-groups' => 'Manage user groups',
'manage-labels' => 'Manage photo labels',
'manage-storage' => 'Manage storage locations', 'manage-storage' => 'Manage storage locations',
'manage-users' => 'Manage users' 'manage-users' => 'Manage users'
], ],

View File

@ -0,0 +1,34 @@
@extends('themes.base.layout')
@section('title', trans('admin.delete_label', ['name' => $label->name]))
@section('breadcrumb')
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item"><a href="{{ route('labels.index') }}">@lang('navigation.breadcrumb.labels')</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.delete_label')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 ml-md-auto mr-md-auto">
<div class="card bg-danger">
<div class="card-header text-white">@yield('title')</div>
<div class="card-body bg-light">
<p>@lang('admin.delete_label_confirm', ['name' => $label->name])</p>
<p class="text-danger"><b>@lang('admin.delete_group_warning')</b></p>
<div class="text-right">
<form action="{{ route('labels.destroy', [$label->id]) }}" method="post">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<a href="{{ route('labels.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-danger"><i class="fa fa-fw fa-trash"></i> @lang('forms.delete_action')</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -83,6 +83,10 @@
'permission' => Theme::getPermission($all_permissions, 'admin', 'manage-albums') 'permission' => Theme::getPermission($all_permissions, 'admin', 'manage-albums')
]) ])
@include(Theme::viewName('partials.permission_checkbox'), [
'permission' => Theme::getPermission($all_permissions, 'admin', 'manage-labels')
])
@include(Theme::viewName('partials.permission_checkbox'), [ @include(Theme::viewName('partials.permission_checkbox'), [
'permission' => Theme::getPermission($all_permissions, 'admin', 'manage-users') 'permission' => Theme::getPermission($all_permissions, 'admin', 'manage-users')
]) ])

View File

@ -0,0 +1,73 @@
@extends('themes.base.layout')
@section('title', trans('admin.list_labels_title'))
@section('breadcrumb')
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.labels')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<div class="alert alert-info mb-4">
<i class="fa fa-fw fa-info"></i> @lang('admin.list_labels_intro')
</div>
@if (count($labels) == 0)
<div class="text-center mb-4">
<h4 class="text-danger"><b>@lang('admin.no_labels_title')</b></h4>
<p>@lang('admin.no_labels_text')</p>
</div>
@else
<p>@lang('admin.labels_intro')</p>
<ul class="nav nav-pills">
@foreach ($labels as $label)
<li class="nav-item">
<a class="nav-link" href="{{ route('labels.delete', [$label->id]) }}">
{{ $label->name }} ({{ number_format($label->photos_count, 0) }})&nbsp;&nbsp;
<i class="fa fa-trash text-danger"></i>
</a>
</li>
@endforeach
</ul>
@endif
<hr/>
<h3>@lang('admin.create_label_title')</h3>
<p class="mb-5">@lang('admin.create_label_intro')</p>
<form action="{{ route('labels.store') }}" method="POST">
{{ csrf_field() }}
<div class="form-group row">
<label class="form-control-label col-md-1" for="label-name">@lang('forms.name_label')</label>
<div class="col-md-9 mb-3">
<input type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" id="label-name" name="name" value="{{ old('name') }}">
@if ($errors->has('name'))
<div class="invalid-feedback">
<strong>{{ $errors->first('name') }}</strong>
</div>
@endif
</div>
<div class="col-md-2 text-right">
<button type="submit" class="btn btn-success" style="width: 100%;"><i class="fa fa-check"></i> @lang('forms.create_action')</button>
</div>
</div>
</form>
</div>
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$(document).ready(function()
{
$('#label-name').focus();
});
</script>
@endpush

View File

@ -2,6 +2,7 @@
$canConfigure = Auth::user()->can('admin:configure'); $canConfigure = Auth::user()->can('admin:configure');
$canManageAlbums = Auth::user()->can('admin:manage-albums'); $canManageAlbums = Auth::user()->can('admin:manage-albums');
$canManageGroups = Auth::user()->can('admin:manage-groups'); $canManageGroups = Auth::user()->can('admin:manage-groups');
$canManageLabels = Auth::user()->can('admin:manage-labels');
$canManageStorage = Auth::user()->can('admin:manage-storage'); $canManageStorage = Auth::user()->can('admin:manage-storage');
$canManageUsers = Auth::user()->can('admin:manage-users'); $canManageUsers = Auth::user()->can('admin:manage-users');
@endphp @endphp
@ -13,6 +14,9 @@
@if ($canManageAlbums) @if ($canManageAlbums)
<a class="btn btn-link" href="{{ route('albums.index') }}"><i class="fa fa-fw fa-picture-o"></i> @lang('navigation.breadcrumb.albums')</a> <a class="btn btn-link" href="{{ route('albums.index') }}"><i class="fa fa-fw fa-picture-o"></i> @lang('navigation.breadcrumb.albums')</a>
@endif @endif
@if ($canManageLabels)
<a class="btn btn-link" href="{{ route('labels.index') }}"><i class="fa fa-fw fa-tags"></i> @lang('navigation.breadcrumb.labels')</a>
@endif
@if ($canManageUsers) @if ($canManageUsers)
<a class="btn btn-link" href="{{ route('users.index') }}"><i class="fa fa-fw fa-user"></i> @lang('navigation.breadcrumb.users')</a> <a class="btn btn-link" href="{{ route('users.index') }}"><i class="fa fa-fw fa-user"></i> @lang('navigation.breadcrumb.users')</a>
@endif @endif

View File

@ -1,8 +1,9 @@
<div class="card admin-sidebar-card"> <div class="card admin-sidebar-card">
<div class="card-header">@lang('admin.stats_widget.panel_header')</div> <div class="card-header">@lang('admin.stats_widget.panel_header')</div>
<div class="card-body"> <div class="card-body">
<b>{{ $album_count }}</b> {{ trans_choice('admin.stats_widget.albums', $album_count) }}<br/> <b>{{ $album_count }}</b> {{ trans_choice('admin.stats_widget.albums', $album_count) }} &middot;
<b>{{ $photo_count }}</b> {{ trans_choice('admin.stats_widget.photos', $photo_count) }} <b>{{ $photo_count }}</b> {{ trans_choice('admin.stats_widget.photos', $photo_count) }}<br/>
<b>{{ $label_count }}</b> {{ trans_choice('admin.stats_widget.labels', $label_count) }}
@can('admin:access') @can('admin:access')
<br/> <br/>
<b>{{ $user_count }}</b> {{ trans_choice('admin.stats_widget.users', $user_count) }} / <b>{{ $group_count }}</b> {{ trans_choice('admin.stats_widget.groups', $group_count) }} <b>{{ $user_count }}</b> {{ trans_choice('admin.stats_widget.users', $user_count) }} / <b>{{ $group_count }}</b> {{ trans_choice('admin.stats_widget.groups', $group_count) }}

View File

@ -39,6 +39,10 @@ Route::group(['prefix' => 'admin'], function () {
Route::put('photos/update-bulk/{albumId}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk'); Route::put('photos/update-bulk/{albumId}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk');
Route::resource('photos', 'Admin\PhotoController'); Route::resource('photos', 'Admin\PhotoController');
// Label management
Route::get('labels/{id}/delete', 'Admin\LabelController@delete')->name('labels.delete');
Route::resource('labels', 'Admin\LabelController');
// Storage management // Storage management
Route::get('storage/{id}/delete', 'Admin\StorageController@delete')->name('storage.delete'); Route::get('storage/{id}/delete', 'Admin\StorageController@delete')->name('storage.delete');
Route::resource('storage', 'Admin\StorageController'); Route::resource('storage', 'Admin\StorageController');