refs #3: it's now possible to set a default view for an album when a user hasn't requested one. Finished off the "edit album" page in admin. Switched session management to database sessions (much more reliable.)

This commit is contained in:
Andy Heathershaw 2016-10-05 14:49:44 +01:00
parent 58b055e9cd
commit 36fcb6d765
17 changed files with 284 additions and 30 deletions

View File

@ -19,7 +19,7 @@ class Album extends Model
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id' 'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id', 'default_view'
]; ];
/** /**

View File

@ -5,6 +5,7 @@ namespace App\Exceptions;
use Exception; use Exception;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Session\TokenMismatchException;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -44,6 +45,11 @@ class Handler extends ExceptionHandler
*/ */
public function render($request, Exception $exception) public function render($request, Exception $exception)
{ {
if ($exception instanceof TokenMismatchException)
{
return redirect()->back()->withInput()->with('error', 'Your session has expired. Please try the request again.');
}
return parent::render($request, $exception); return parent::render($request, $exception);
} }

View File

@ -9,6 +9,11 @@ use App\Configuration;
class ConfigHelper class ConfigHelper
{ {
public function allowedAlbumViews()
{
return ['default', 'slideshow'];
}
public function allowedDateFormats() public function allowedDateFormats()
{ {
return [ return [
@ -78,6 +83,7 @@ class ConfigHelper
'allow_self_registration' => true, 'allow_self_registration' => true,
'app_name' => trans('global.app_name'), 'app_name' => trans('global.app_name'),
'date_format' => $this->allowedDateFormats()[0], 'date_format' => $this->allowedDateFormats()[0],
'default_album_view' => $this->allowedAlbumViews()[0],
'hotlink_protection' => false, 'hotlink_protection' => false,
'items_per_page' => 12, 'items_per_page' => 12,
'items_per_page_admin' => 10, 'items_per_page_admin' => 10,

View File

@ -155,8 +155,15 @@ class AlbumController extends Controller
$postLimit = MiscHelper::convertToBytes(ini_get('post_max_size')) / (1024*1024); $postLimit = MiscHelper::convertToBytes(ini_get('post_max_size')) / (1024*1024);
$fileUploadOrPostLowerLimit = ($postLimit < $fileUploadLimit) ? $postLimit : $fileUploadLimit; $fileUploadOrPostLowerLimit = ($postLimit < $fileUploadLimit) ? $postLimit : $fileUploadLimit;
$allowedAlbumViews = [];
foreach (UserConfig::allowedAlbumViews() as $view)
{
$allowedAlbumViews[$view] = trans(sprintf('gallery.album_views.%s', $view));
}
return Theme::render('admin.show_album', [ return Theme::render('admin.show_album', [
'album' => $album, 'album' => $album,
'allowed_views' => $allowedAlbumViews,
'bulk_actions' => [ 'bulk_actions' => [
'rotate_left' => trans('admin.photo_actions.rotate_left'), 'rotate_left' => trans('admin.photo_actions.rotate_left'),
'rotate_right' => trans('admin.photo_actions.rotate_right'), 'rotate_right' => trans('admin.photo_actions.rotate_right'),
@ -193,6 +200,7 @@ class AlbumController extends Controller
$album = new Album(); $album = new Album();
$album->fill($request->only(['name', 'description', 'storage_id'])); $album->fill($request->only(['name', 'description', 'storage_id']));
$album->default_view = UserConfig::get('default_album_view');
$album->is_private = (strtolower($request->get('is_private')) == 'on'); $album->is_private = (strtolower($request->get('is_private')) == 'on');
$album->user_id = Auth::user()->id; $album->user_id = Auth::user()->id;
@ -214,10 +222,22 @@ class AlbumController extends Controller
$this->authorize('admin-access'); $this->authorize('admin-access');
$album = $this->loadAlbum($id); $album = $this->loadAlbum($id);
$album->fill($request->only(['name', 'description'])); $album->fill(['name', 'description']);
$album->save(); $album->is_private = (strtolower($request->get('is_private')) == 'on');
return Theme::render('admin.show_album', ['album' => $album]); // These keys are optional and may or may not be in the request, depending on the page requesting it
foreach (['storage_id', 'default_view'] as $key)
{
if ($request->has($key))
{
$album->$key = $request->get($key);
}
}
$album->save();
$request->session()->flash('success', trans('admin.album_saved_successfully', ['name' => $album->name]));
return redirect(route('albums.show', ['id' => $id]));
} }
/** /**

View File

@ -377,7 +377,7 @@ class PhotoController extends Controller
public function updateBulk(Request $request, $albumId) public function updateBulk(Request $request, $albumId)
{ {
$photoIds = $request->get('select-photo'); $this->authorize('admin-access');
/** @var Album $album */ /** @var Album $album */
$album = Album::where('id', intval($albumId))->first(); $album = Album::where('id', intval($albumId))->first();
@ -388,6 +388,31 @@ class PhotoController extends Controller
return null; return null;
} }
$numberChanged = 0;
if ($request->has('bulk-apply'))
{
$numberChanged = $this->applyBulkActions($request, $album);
}
else
{
$numberChanged = $this->updatePhotoDetails($request, $album);
}
$request->session()->flash('success', trans_choice('admin.bulk_photos_changed', $numberChanged, ['number' => $numberChanged]));
return redirect(route('albums.show', array('id' => $albumId)));
}
private function applyBulkActions(Request $request, Album $album)
{
$photoIds = $request->get('select-photo');
if (is_null($photoIds) || !is_array($photoIds) || count($photoIds) == 0)
{
$request->session()->flash('warning', trans('admin.no_photo_selected_message'));
return 0;
}
$action = $request->get('bulk-action'); $action = $request->get('bulk-action');
$numberChanged = 0; $numberChanged = 0;
@ -449,12 +474,12 @@ class PhotoController extends Controller
break; break;
} }
$photo->save();
$numberChanged++; $numberChanged++;
} }
$request->session()->flash('success', trans_choice('admin.bulk_photos_changed', $numberChanged, ['number' => $numberChanged])); return $numberChanged;
return redirect(route('albums.show', array('id' => $albumId)));
} }
/** /**
@ -472,4 +497,27 @@ class PhotoController extends Controller
return $album; return $album;
} }
private function updatePhotoDetails(Request $request, Album $album)
{
$numberChanged = 0;
$photos = $request->get('photo');
foreach ($photos as $photoId => $value)
{
/** @var Photo $photo */
$photo = $album->photos()->where('id', intval($photoId))->first();
if (is_null($photo))
{
continue;
}
$photo->fill($value);
$photo->save();
$numberChanged++;
}
return $numberChanged;
}
} }

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Gallery;
use App\Album; use App\Album;
use App\Facade\Theme; use App\Facade\Theme;
use App\Facade\UserConfig; use App\Facade\UserConfig;
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; use App\Http\Requests;
@ -23,12 +24,17 @@ class AlbumController extends Controller
return null; return null;
} }
$validViews = ['default', 'slideshow']; $validViews = UserConfig::allowedAlbumViews();
$requestedView = strtolower($request->get('view')); $requestedView = strtolower($request->get('view'));
if (!in_array($requestedView, $validViews))
{
$requestedView = $album->default_view;
if (!in_array($requestedView, $validViews)) if (!in_array($requestedView, $validViews))
{ {
$requestedView = $validViews[0]; $requestedView = $validViews[0];
} }
}
$this->authorizeForUser($this->getUser(), 'album.view', $album); $this->authorizeForUser($this->getUser(), 'album.view', $album);
@ -47,6 +53,7 @@ class AlbumController extends Controller
return Theme::render(sprintf('gallery.album_%s', $requestedView), [ return Theme::render(sprintf('gallery.album_%s', $requestedView), [
'album' => $album, 'album' => $album,
'allowed_views' => $validViews,
'current_view' => $requestedView, 'current_view' => $requestedView,
'photos' => $photos 'photos' => $photos
]); ]);

View File

@ -23,10 +23,24 @@ class StoreAlbumRequest extends FormRequest
*/ */
public function rules() public function rules()
{ {
switch ($this->method())
{
case 'POST':
return [ return [
'description' => '', 'description' => '',
'name' => 'required|unique:albums|max:255', 'name' => 'required|unique:albums|max:255',
'storage_id' => 'required' 'storage_id' => 'required|sometimes'
];
case 'PATCH':
case 'PUT':
$albumId = intval($this->segment(3));
return [
'description' => 'sometimes',
'name' => 'required|sometimes|max:255|unique:albums,name,' . $albumId,
'storage_id' => 'required|sometimes'
]; ];
} }
}
} }

View File

@ -30,7 +30,9 @@ class Photo extends Model
'camera_software', 'camera_software',
'width', 'width',
'height', 'height',
'is_analysed' 'is_analysed',
'created_at',
'updated_at'
]; ];
/** /**
@ -46,9 +48,18 @@ class Photo extends Model
return $this->belongsTo(Album::class); return $this->belongsTo(Album::class);
} }
public function thumbnailUrl($thumbnailName = null) public function thumbnailUrl($thumbnailName = null, $cacheBust = true)
{ {
return $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName); $url = $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName);
if ($cacheBust)
{
// Append the timestamp of the last update to avoid browser caching
$theDate = is_null($this->updated_at) ? $this->created_at : $this->updated_at;
$url .= sprintf('%s_=%d', (strpos($url, '?') === false ? '?' : '&'), $theDate->format('U'));
}
return $url;
} }
public function url() public function url()

View File

@ -16,7 +16,7 @@ return [
| |
*/ */
'driver' => env('SESSION_DRIVER', 'file'), 'driver' => env('SESSION_DRIVER', 'database'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -31,7 +31,7 @@ return [
'lifetime' => 120, 'lifetime' => 120,
'expire_on_close' => false, 'expire_on_close' => true,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -70,7 +70,7 @@ return [
| |
*/ */
'connection' => null, 'connection' => 'mysql',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -122,7 +122,7 @@ return [
| |
*/ */
'cookie' => 'laravel_session', 'cookie' => 'twilight_session',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddAlbumView extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('albums', function (Blueprint $table) {
$table->string('default_view', 50);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('albums', function (Blueprint $table) {
$table->dropColumn('default_view');
});
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSessionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sessions', function ($table) {
$table->string('id')->unique();
$table->integer('user_id')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sessions');
}
}

View File

@ -1,4 +1,4 @@
.album-slideshow-container .image-preview { .album-slideshow-container #image-preview {
height: 600px; height: 600px;
max-width: 100%; max-width: 100%;
width: 800px; width: 800px;

View File

@ -4,10 +4,17 @@ return [
'create_album_link' => 'Create album', 'create_album_link' => 'Create album',
'panel_header' => 'Actions', 'panel_header' => 'Actions',
], ],
'album_appearance_heading' => 'Appearance',
'album_appearance_intro' => 'The settings shown below control how this album appears to visitors in the gallery.',
'album_basic_info_heading' => 'Album information',
'album_basic_info_intro' => 'The album\'s name and description are displayed to visitors in the gallery. If you wish to change them, you can do so below.',
'album_no_photos_p1' => 'No photos in this album', 'album_no_photos_p1' => 'No photos in this album',
'album_no_photos_p2' => 'Click the "Upload photos" button below to get started.', 'album_no_photos_p2' => 'Click the "Upload photos" button below to get started.',
'album_no_photos_button' => 'Upload photos', 'album_no_photos_button' => 'Upload photos',
'album_photos_tab' => 'Photos', 'album_photos_tab' => 'Photos',
'album_saved_successfully' => 'The ":name" album was updated successfully.',
'album_security_heading' => 'Security',
'album_security_intro' => 'The settings below affect the visibility of this album to other users.',
'album_settings_tab' => 'Settings', 'album_settings_tab' => 'Settings',
'album_upload_tab' => 'Upload', 'album_upload_tab' => 'Upload',
'analyse_photos_failed' => 'The following items could not be analysed and were removed:', 'analyse_photos_failed' => 'The following items could not be analysed and were removed:',
@ -25,6 +32,8 @@ return [
'create_user' => 'Create user', 'create_user' => 'Create user',
'create_user_intro' => 'You can use the form below to create a user account. Users created using this form will be activate immediately.', 'create_user_intro' => 'You can use the form below to create a user account. Users created using this form will be activate immediately.',
'create_user_title' => 'Create a user account', 'create_user_title' => 'Create a user account',
'danger_zone_heading' => 'Danger zone',
'danger_zone_intro' => 'The options below WILL cause data loss - please be careful!',
'delete_album' => 'Delete album :name', 'delete_album' => 'Delete album :name',
'delete_album_confirm' => 'Are you sure you want to permanently delete this album and all its contents?', 'delete_album_confirm' => 'Are you sure you want to permanently delete this album and all its contents?',
'delete_album_warning' => 'This is a permanent action that cannot be undone!', 'delete_album_warning' => 'This is a permanent action that cannot be undone!',
@ -41,6 +50,7 @@ return [
'delete_user_confirm' => 'Are you sure you want to permanently remove :name\'s user account? They will be immediately logged out.', 'delete_user_confirm' => 'Are you sure you want to permanently remove :name\'s user account? They will be immediately logged out.',
'delete_user_warning' => 'This is a permanent action that cannot be reversed!', 'delete_user_warning' => 'This is a permanent action that cannot be reversed!',
'edit_album' => 'Edit photo album: :album_name', 'edit_album' => 'Edit photo album: :album_name',
'edit_album_action' => 'Edit album details',
'edit_album_intro' => 'Photo albums contain individual photographs together in the same way as a physical photo album or memory book.', 'edit_album_intro' => 'Photo albums contain individual photographs together in the same way as a physical photo album or memory book.',
'edit_album_intro2' => 'Complete the form below to edit the properties of the album: :album_name.', 'edit_album_intro2' => 'Complete the form below to edit the properties of the album: :album_name.',
'edit_storage' => 'Edit storage location: :storage_name', 'edit_storage' => 'Edit storage location: :storage_name',
@ -55,6 +65,7 @@ return [
'move_successful_message' => 'The photo ":name" was moved successfully to the ":album" album.', 'move_successful_message' => 'The photo ":name" was moved successfully to the ":album" album.',
'no_albums_text' => 'You have no photo albums yet. Click the button below to create one.', 'no_albums_text' => 'You have no photo albums yet. Click the button below to create one.',
'no_albums_title' => 'No Photo Albums', 'no_albums_title' => 'No Photo Albums',
'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.',
'no_storages_title' => 'No storage locations defined', 'no_storages_title' => 'No storage locations defined',
@ -69,6 +80,8 @@ return [
'rotate_left' => 'Rotate left', 'rotate_left' => 'Rotate left',
'rotate_right' => 'Rotate right' 'rotate_right' => 'Rotate right'
], ],
'save_changes_heading' => 'Save changes',
'save_changes_intro' => 'If you have made any changes to the above fields, you\'ll need to hit the Save Changes button below.',
'settings_image_protection' => 'Image Protection', 'settings_image_protection' => 'Image Protection',
'settings_recaptcha' => 'reCAPTCHA settings', 'settings_recaptcha' => 'reCAPTCHA settings',
'settings_save_action' => 'Update Settings', 'settings_save_action' => 'Update Settings',

View File

@ -10,6 +10,7 @@ return [
'cancel_action' => 'Cancel', 'cancel_action' => 'Cancel',
'continue_action' => 'Continue', 'continue_action' => 'Continue',
'create_action' => 'Create', 'create_action' => 'Create',
'default_album_view_label' => 'Default view mode in the gallery:',
'default_storage_label' => 'Use as the default storage location for new albums', 'default_storage_label' => 'Use as the default storage location for new albums',
'delete_action' => 'Delete', 'delete_action' => 'Delete',
'description_label' => 'Description:', 'description_label' => 'Description:',

View File

@ -26,8 +26,8 @@
@if (count($errors) > 0) @if (count($errors) > 0)
<div class="alert alert-danger"> <div class="alert alert-danger">
<ul> <ul>
@foreach ($errors->all() as $error) @foreach ($errors->all() as $form_error)
<li>{{ $error }}</li> <li>{{ $form_error }}</li>
@endforeach @endforeach
</ul> </ul>
</div> </div>

View File

@ -155,10 +155,70 @@
{{-- Settings --}} {{-- Settings --}}
<div role="tabpanel" class="tab-pane" id="settings-tab"> <div role="tabpanel" class="tab-pane" id="settings-tab">
<div class="btn-toolbar"> {!! Form::model($album, ['route' => ['albums.update', $album->id], 'method' => 'PUT']) !!}
<a href="{{ route('albums.edit', ['id' => $album->id]) }}" class="btn btn-default">@lang('forms.edit_action')</a> <h4><i class="fa fa-fw fa-info"></i> @lang('admin.album_basic_info_heading')</h4>
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <p>@lang('admin.album_basic_info_intro')</p>
<div class="form-group" style="margin-top: 20px;">
{!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!}
{!! Form::text('name', old('name'), ['class' => 'form-control']) !!}
</div> </div>
<div class="form-group">
{!! Form::label('description', trans('forms.description_label'), ['class' => 'control-label']) !!}
{!! Form::textarea('description', old('description'), ['class' => 'form-control']) !!}
</div>
<hr/>
<h4><i class="fa fa-fw fa-paint-brush"></i> @lang('admin.album_appearance_heading')</h4>
<p>@lang('admin.album_appearance_intro')</p>
<div class="form-group" style="margin-top: 20px;">
<label class="control-label">@lang('forms.default_album_view_label')</label>
{!! Form::select('default_view', $allowed_views, old('default_view'), ['class' => 'form-control']) !!}
</div>
<hr/>
<h4><i class="fa fa-fw fa-lock"></i> @lang('admin.album_security_heading')</h4>
<p>@lang('admin.album_security_intro')</p>
<div class="checkbox" style="margin-top: 20px;">
<label>
<input type="checkbox" name="is_private"@if ($album->is_private) checked="checked"@endif>
<strong>@lang('forms.private_album_label')</strong>
</label>
</div>
<hr/>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-danger">
<div class="panel-heading">@lang('admin.danger_zone_heading')</div>
<div class="panel-body">
<p class="text-danger">@lang('admin.danger_zone_intro')</p>
<p>
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">@lang('admin.save_changes_heading')</div>
<div class="panel-body">
<p>@lang('admin.save_changes_intro')</p>
<p class="text-right">
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
</p>
</div>
</div>
</div>
</div>
{!! Form::close() !!}
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,8 +2,9 @@
<div class="field-group"> <div class="field-group">
<label class="control-label">@lang('forms.album_view_label')</label> <label class="control-label">@lang('forms.album_view_label')</label>
<select name="view" class="form-control" id="album-view-selector"> <select name="view" class="form-control" id="album-view-selector">
<option value="default"@if (isset($current_view) && $current_view == 'default') selected="selected"@endif>@lang('gallery.album_views.default')</option> @foreach ($allowed_views as $view)
<option value="slideshow"@if (isset($current_view) && $current_view == 'slideshow') selected="selected"@endif>@lang('gallery.album_views.slideshow')</option> <option value="{{ $view }}"@if (isset($current_view) && $current_view == $view) selected="selected"@endif>@lang('gallery.album_views.' . $view)</option>
@endforeach
</select> </select>
</div> </div>
</form> </form>