BLUE-1: A default local storage location is created on install that cannot be deleted. Storage locations can be made inactive and no new albums can be created against them.
BLUE-3: Validation is now performed on the file path selected. Tweaks to the storage locations form to display validation errors against the correct fields.
This commit is contained in:
parent
79111ed6ca
commit
e7fbdaaa66
39
app/Helpers/ValidationHelper.php
Normal file
39
app/Helpers/ValidationHelper.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class ValidationHelper
|
||||
{
|
||||
public function directoryExists($attribute, $value, $parameters, $validator)
|
||||
{
|
||||
return file_exists($value) && is_dir($value);
|
||||
}
|
||||
|
||||
public function isDirectoryEmpty($attribute, $value, $parameters, $validator)
|
||||
{
|
||||
if (!$this->directoryExists($attribute, $value, $parameters, $validator))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$iterator = new \DirectoryIterator($value);
|
||||
$count = 0;
|
||||
|
||||
foreach ($iterator as $item)
|
||||
{
|
||||
if ($item->isDot())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
return ($count == 0);
|
||||
}
|
||||
|
||||
public function isPathWriteable($attribute, $value, $parameters, $validator)
|
||||
{
|
||||
return $this->directoryExists($attribute, $value, $parameters, $validator) && is_writeable($value);
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ class AlbumController extends Controller
|
||||
$this->authorize('admin-access');
|
||||
|
||||
$albumSources = [];
|
||||
foreach (Storage::all()->sortBy('name') as $storage)
|
||||
foreach (Storage::where('is_active', true)->orderBy('name')->get() as $storage)
|
||||
{
|
||||
$albumSources[$storage->id] = $storage->name;
|
||||
}
|
||||
@ -60,11 +60,11 @@ class AlbumController extends Controller
|
||||
return redirect(route('storage.create'));
|
||||
}
|
||||
|
||||
$defaultSourceId = Storage::where('is_default', true)->limit(1)->first();
|
||||
$defaultSource = Storage::where('is_default', true)->limit(1)->first();
|
||||
|
||||
return Theme::render('admin.create_album', [
|
||||
'album_sources' => $albumSources,
|
||||
'default_storage_id' => (!is_null($defaultSourceId) ? $defaultSourceId->id : 0)
|
||||
'default_storage_id' => (!is_null($defaultSource) ? $defaultSource->id : 0)
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,8 @@ class StorageController extends Controller
|
||||
|
||||
return Theme::render('admin.list_storage', [
|
||||
'error' => $request->session()->get('error'),
|
||||
'storageLocations' => $storageLocations
|
||||
'storageLocations' => $storageLocations,
|
||||
'warning' => $request->session()->get('warning'),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -68,7 +69,9 @@ class StorageController extends Controller
|
||||
|
||||
$storage = new Storage();
|
||||
$storage->fill($request->only(['name', 'source', 'location']));
|
||||
$storage->is_active = true;
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
$storage->is_internal = false;
|
||||
$storage->save();
|
||||
|
||||
if ($storage->is_default)
|
||||
@ -106,10 +109,17 @@ class StorageController extends Controller
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
if ($storage->is_internal)
|
||||
{
|
||||
// Can't delete the default storage location
|
||||
$request->session()->flash('warning', trans('admin.delete_storage_internal'));
|
||||
return redirect(route('storage.index'));
|
||||
}
|
||||
|
||||
if ($storage->albums()->count() > 0)
|
||||
{
|
||||
// Can't delete storage location while albums exist
|
||||
$request->session()->set('error', trans('admin.delete_storage_existing_albums'));
|
||||
$request->session()->flash('error', trans('admin.delete_storage_existing_albums'));
|
||||
return redirect(route('storage.index'));
|
||||
}
|
||||
|
||||
@ -153,7 +163,14 @@ class StorageController extends Controller
|
||||
}
|
||||
|
||||
$storage->fill($request->only(['name']));
|
||||
$storage->is_active = (strtolower($request->get('is_active')) == 'on');
|
||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||
|
||||
if ($storage->is_default && !$storage->is_active)
|
||||
{
|
||||
$storage->is_default = false;
|
||||
}
|
||||
|
||||
$storage->save();
|
||||
|
||||
if ($storage->is_default)
|
||||
@ -180,6 +197,13 @@ class StorageController extends Controller
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
if ($storage->is_internal)
|
||||
{
|
||||
// Can't delete the default storage location
|
||||
$request->session()->flash('warning', trans('admin.delete_storage_internal'));
|
||||
return redirect(route('storage.index'));
|
||||
}
|
||||
|
||||
if ($storage->albums()->count() > 0)
|
||||
{
|
||||
// Can't delete storage location while albums exist
|
||||
|
@ -6,6 +6,7 @@ use App\Configuration;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Http\Requests\StoreUserRequest;
|
||||
use App\Storage;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
@ -27,9 +28,7 @@ class InstallController extends Controller
|
||||
|
||||
if ($canSkip && $request->has('skip'))
|
||||
{
|
||||
MiscHelper::setEnvironmentSetting('APP_INSTALLED', true);
|
||||
|
||||
return redirect(route('home'));
|
||||
return $this->completeSetup();
|
||||
}
|
||||
|
||||
if ($request->method() == 'POST')
|
||||
@ -42,11 +41,7 @@ class InstallController extends Controller
|
||||
$user->is_activated = true;
|
||||
$user->save();
|
||||
|
||||
MiscHelper::setEnvironmentSetting('APP_INSTALLED', true);
|
||||
|
||||
$request->session()->flash('success', trans('installer.install_completed_message'));
|
||||
|
||||
return redirect(route('home'));
|
||||
return $this->completeSetup();
|
||||
}
|
||||
|
||||
return view('install.administrator', [
|
||||
@ -160,9 +155,6 @@ class InstallController extends Controller
|
||||
$versionNumber->value = config('app.version');
|
||||
$versionNumber->save();
|
||||
|
||||
// Now the database is up-to-date, we can enable database sessions
|
||||
MiscHelper::setEnvironmentSetting('SESSION_DRIVER', 'database');
|
||||
|
||||
$request->session()->set('install_stage', 3);
|
||||
return redirect(route('install.administrator'));
|
||||
}
|
||||
@ -178,4 +170,43 @@ class InstallController extends Controller
|
||||
'database_error' => $request->session()->get('database_error')
|
||||
]);
|
||||
}
|
||||
|
||||
private function completeSetup()
|
||||
{
|
||||
// Flag as installed
|
||||
MiscHelper::setEnvironmentSetting('APP_INSTALLED', true);
|
||||
|
||||
// Switch to database sessions (more reliable)
|
||||
MiscHelper::setEnvironmentSetting('SESSION_DRIVER', 'database');
|
||||
|
||||
// Add an internal storage if it doesn't already exist
|
||||
$this->createInternalStorageLocationIfNotExist();
|
||||
|
||||
// Skip forward. If you go past Go, collect £200!
|
||||
return redirect(route('home', ['install_completed' => true]));
|
||||
}
|
||||
|
||||
private function createInternalStorageLocationIfNotExist()
|
||||
{
|
||||
$storage = Storage::where('is_internal', true)->first();
|
||||
if (is_null($storage))
|
||||
{
|
||||
$location = sprintf('%s/storage/app/albums', dirname(dirname(dirname(__DIR__))));
|
||||
|
||||
$storage = new Storage();
|
||||
$storage->name = trans('installer.default_storage_name');
|
||||
$storage->is_active = true;
|
||||
$storage->is_default = true;
|
||||
$storage->is_internal = true;
|
||||
$storage->location = $location;
|
||||
$storage->source = 'LocalFilesystemSource';
|
||||
$storage->save();
|
||||
|
||||
// Try and create the physical location
|
||||
if (!file_exists($location))
|
||||
{
|
||||
@mkdir($location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,11 +27,18 @@ class StoreStorageRequest extends FormRequest
|
||||
switch ($this->method())
|
||||
{
|
||||
case 'POST':
|
||||
return [
|
||||
$result = [
|
||||
'name' => 'required|unique:storages|max:255',
|
||||
'source' => 'required|max:255',
|
||||
];
|
||||
|
||||
if ($this->get('source') == 'LocalFilesystemSource')
|
||||
{
|
||||
$result['location'] = 'sometimes|required|is_dir|dir_empty|is_writeable';
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
case 'PATCH':
|
||||
case 'PUT':
|
||||
$storageId = intval($this->segment(3));
|
||||
|
@ -2,17 +2,14 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Album;
|
||||
use App\Configuration;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\ConfigHelper;
|
||||
use App\Helpers\ImageHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use App\Helpers\ThemeHelper;
|
||||
use App\Helpers\ValidationHelper;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Mail\Mailer;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
@ -37,6 +34,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
return new ConfigHelper();
|
||||
});
|
||||
|
||||
Validator::extend('is_dir', (ValidationHelper::class . '@directoryExists'));
|
||||
Validator::extend('dir_empty', (ValidationHelper::class . '@isDirectoryEmpty'));
|
||||
Validator::extend('is_writeable', (ValidationHelper::class . '@isPathWriteable'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,7 @@ class Storage extends Model
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'source', 'is_default', 'location'
|
||||
'name', 'source', 'is_default', 'location', 'is_internal', 'is_active'
|
||||
];
|
||||
|
||||
public function albums()
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddIsInternalColumnToStorage extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->boolean('is_internal');
|
||||
$table->boolean('is_active');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('storages', function (Blueprint $table) {
|
||||
$table->dropColumn('is_active');
|
||||
$table->dropColumn('is_internal');
|
||||
});
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ return [
|
||||
'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!',
|
||||
'default_storage_legend' => 'Default storage location for new albums.',
|
||||
'delete_album' => 'Delete album :name',
|
||||
'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!',
|
||||
@ -45,6 +46,7 @@ return [
|
||||
'delete_storage' => 'Delete storage location: :name',
|
||||
'delete_storage_confirm' => 'Are you sure you want to permanently remove this storage location?',
|
||||
'delete_storage_existing_albums' => 'At least one album is still using the storage location. Please delete all albums before removing the storage location.',
|
||||
'delete_storage_internal' => 'You cannot delete the local, internal storage location. You can de-activate it using the Edit link below.',
|
||||
'delete_storage_warning' => 'This is a permanent action that cannot be reversed!',
|
||||
'delete_user' => 'Delete user account: :name',
|
||||
'delete_user_confirm' => 'Are you sure you want to permanently remove :name\'s user account? They will be immediately logged out.',
|
||||
@ -57,7 +59,9 @@ return [
|
||||
'edit_storage_intro' => 'Use the form below to update the details of the :storage_name storage location.',
|
||||
'edit_user_intro' => 'You can use the form below to edit the above user account. Changes take effect immediately.',
|
||||
'edit_user_title' => 'Edit user account: :name',
|
||||
'inactive_storage_legend' => 'Inactive storage location that cannot be used for new albums.',
|
||||
'is_uploading' => 'Uploading in progress...',
|
||||
'legend' => 'Legend/Key',
|
||||
'manage_widget' => [
|
||||
'panel_header' => 'Manage'
|
||||
],
|
||||
|
@ -29,6 +29,7 @@ return [
|
||||
'settings_hotlink_protection_help' => 'With this option enabled, direct linking to images is not allowed. Photos can only be viewed through Blue Twilight.',
|
||||
'settings_restrict_originals_download' => 'Restrict access to original images',
|
||||
'settings_restrict_originals_download_help' => 'With this option enabled, only the photo\'s owner can download the original high-resolution images.',
|
||||
'storage_active_label' => 'Location is active. Uncheck to prevent creating new albums in this location.',
|
||||
'storage_driver_label' => 'Storage driver:',
|
||||
'storage_location_label' => 'Physical location:',
|
||||
'upload_action' => 'Upload',
|
||||
|
@ -9,6 +9,7 @@ return [
|
||||
],
|
||||
'database_intro' => 'Please provide the connection details for an empty MySQL or MariaDB database.',
|
||||
'database_title' => 'Connect to a Database',
|
||||
'default_storage_name' => 'Local',
|
||||
'install_completed_message' => 'Congratulations, Blue Twilight has been installed successfully. You can now login with an administrator account using the "Login" link above.',
|
||||
'php_config' => [
|
||||
'heading' => 'PHP configuration:',
|
||||
|
@ -112,4 +112,8 @@ return [
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
// Added by Andy H. for custom validators
|
||||
'dir_empty' => 'The path must be an empty folder.',
|
||||
'is_dir' => 'The folder must be a valid directory.',
|
||||
'is_writeable' => 'Unable to write to this folder - please check permissions.'
|
||||
];
|
||||
|
@ -22,32 +22,34 @@
|
||||
<p>@lang('admin.create_storage_intro')</p>
|
||||
<hr/>
|
||||
|
||||
@if (count($errors) > 0)
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{!! Form::open(['route' => 'storage.store', 'method' => 'POST']) !!}
|
||||
<div class="form-group">
|
||||
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
|
||||
{!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!}
|
||||
{!! Form::text('name', old('name'), ['class' => 'form-control']) !!}
|
||||
|
||||
@if ($errors->has('name'))
|
||||
<span class="help-block">
|
||||
<strong>{{ $errors->first('name') }}</strong>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{!! Form::label('source', trans('forms.album_source_label'), ['class' => 'control-label']) !!}
|
||||
{!! Form::label('source', trans('forms.storage_driver_label'), ['class' => 'control-label']) !!}
|
||||
{!! Form::select('source', $album_sources, old('source'), ['class' => 'form-control', 'data-bind' => 'value: selectedLocation']) !!}
|
||||
</div>
|
||||
|
||||
<div id="storage-options">
|
||||
<div id="local-filesystem" data-bind="visible: selectedLocation() == 'LocalFilesystemSource'">
|
||||
<div class="form-group">
|
||||
{!! Form::label('location', trans('forms.storage_driver_label'), ['class' => 'control-label']) !!}
|
||||
<div class="form-group{{ $errors->has('location') ? ' has-error' : '' }}">
|
||||
{!! Form::label('location', trans('forms.storage_location_label'), ['class' => 'control-label']) !!}
|
||||
{!! Form::text('location', old('location', $filesystem_default_location), ['class' => 'form-control']) !!}
|
||||
|
||||
@if ($errors->has('location'))
|
||||
<span class="help-block">
|
||||
<strong>{{ $errors->first('location') }}</strong>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,7 +62,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ route('albums.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
||||
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
||||
{!! Form::submit(trans('forms.create_action'), ['class' => 'btn btn-success']) !!}
|
||||
</div>
|
||||
{!! Form::close() !!}
|
||||
|
@ -22,20 +22,16 @@
|
||||
<p>@lang('admin.edit_storage_intro', ['storage_name' => $storage->name])</p>
|
||||
<hr/>
|
||||
|
||||
@if (count($errors) > 0)
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
@foreach ($errors->all() as $formError)
|
||||
<li>{{ $formError }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{!! Form::model($storage, ['route' => ['storage.update', $storage->id], 'method' => 'PUT']) !!}
|
||||
<div class="form-group">
|
||||
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
|
||||
{!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!}
|
||||
{!! Form::text('name', old('name'), ['class' => 'form-control']) !!}
|
||||
|
||||
@if ($errors->has('name'))
|
||||
<span class="help-block">
|
||||
<strong>{{ $errors->first('name') }}</strong>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
@ -45,6 +41,13 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="is_active"@if ($storage->is_active) checked="checked"@endif>
|
||||
<strong>@lang('forms.storage_active_label')</strong>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
||||
{!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!}
|
||||
|
@ -32,12 +32,17 @@
|
||||
@foreach ($storageLocations as $storage)
|
||||
<tr>
|
||||
<td>
|
||||
<span style="font-size: 1.3em;">{{ $storage->name }}@if ($storage->is_default) <i class="fa fa-fw fa-check text-success"></i>@endif</span><br/>
|
||||
{{ $storage->location }}
|
||||
<span style="font-size: 1.3em;">
|
||||
{{ $storage->name }}
|
||||
@if ($storage->is_default) <i class="fa fa-fw fa-check text-success"></i>@endif
|
||||
@if (!$storage->is_active) <i class="fa fa-fw fa-minus-circle text-danger"></i>@endif
|
||||
</span><br/>{{ $storage->location }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{{ route('storage.edit', ['id' => $storage->id]) }}" class="btn btn-default">@lang('forms.edit_action')</a>
|
||||
@if (!$storage->is_internal)
|
||||
<a href="{{ route('storage.delete', ['id' => $storage->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@ -51,6 +56,20 @@
|
||||
<div class="pull-right" style="margin-top: 10px;">
|
||||
<a href="{{ route('storage.create') }}" class="btn btn-success"><i class="fa fa-fw fa-plus"></i> @lang('admin.create_storage')</a>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"><!-- --></div>
|
||||
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">@lang('admin.legend')</div>
|
||||
<div class="panel-body">
|
||||
<i class="fa fa-fw fa-check text-success" style="font-size: 1.3em;"></i> @lang('admin.default_storage_legend')<br/>
|
||||
<i class="fa fa-fw fa-minus-circle text-danger" style="font-size: 1.3em;"></i> @lang('admin.inactive_storage_legend')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
@ -194,19 +194,7 @@
|
||||
<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="col-sm-6 col-sm-push-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">@lang('admin.save_changes_heading')</div>
|
||||
<div class="panel-body">
|
||||
@ -217,6 +205,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-sm-pull-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>
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user