diff --git a/app/Helpers/ValidationHelper.php b/app/Helpers/ValidationHelper.php new file mode 100644 index 0000000..97effd0 --- /dev/null +++ b/app/Helpers/ValidationHelper.php @@ -0,0 +1,39 @@ +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); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/AlbumController.php b/app/Http/Controllers/Admin/AlbumController.php index 1131ac6..9af7df0 100644 --- a/app/Http/Controllers/Admin/AlbumController.php +++ b/app/Http/Controllers/Admin/AlbumController.php @@ -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) ]); } diff --git a/app/Http/Controllers/Admin/StorageController.php b/app/Http/Controllers/Admin/StorageController.php index 978ea16..09cba04 100644 --- a/app/Http/Controllers/Admin/StorageController.php +++ b/app/Http/Controllers/Admin/StorageController.php @@ -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 diff --git a/app/Http/Controllers/InstallController.php b/app/Http/Controllers/InstallController.php index 22c1432..37a4dd9 100644 --- a/app/Http/Controllers/InstallController.php +++ b/app/Http/Controllers/InstallController.php @@ -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); + } + } + } } \ No newline at end of file diff --git a/app/Http/Requests/StoreStorageRequest.php b/app/Http/Requests/StoreStorageRequest.php index d393928..ce4ed0d 100644 --- a/app/Http/Requests/StoreStorageRequest.php +++ b/app/Http/Requests/StoreStorageRequest.php @@ -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)); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0a70cb2..2a30aba 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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')); } /** diff --git a/app/Storage.php b/app/Storage.php index 9d36fbe..918d726 100644 --- a/app/Storage.php +++ b/app/Storage.php @@ -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() diff --git a/database/migrations/2016_10_26_160719_add_is_internal_column_to_storage.php b/database/migrations/2016_10_26_160719_add_is_internal_column_to_storage.php new file mode 100644 index 0000000..252af00 --- /dev/null +++ b/database/migrations/2016_10_26_160719_add_is_internal_column_to_storage.php @@ -0,0 +1,34 @@ +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'); + }); + } +} diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index f5d5d7e..a367630 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -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' ], diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index fdeaca4..4d91b85 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -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', diff --git a/resources/lang/en/installer.php b/resources/lang/en/installer.php index 2b690f0..e49e4dd 100644 --- a/resources/lang/en/installer.php +++ b/resources/lang/en/installer.php @@ -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:', diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 28c6677..dd4cb1e 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -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.' ]; diff --git a/resources/views/themes/base/admin/create_storage.blade.php b/resources/views/themes/base/admin/create_storage.blade.php index a75db79..b88bf3a 100644 --- a/resources/views/themes/base/admin/create_storage.blade.php +++ b/resources/views/themes/base/admin/create_storage.blade.php @@ -22,32 +22,34 @@

@lang('admin.create_storage_intro')


- @if (count($errors) > 0) -
- -
- @endif - {!! Form::open(['route' => 'storage.store', 'method' => 'POST']) !!} -
+
{!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!} {!! Form::text('name', old('name'), ['class' => 'form-control']) !!} + + @if ($errors->has('name')) + + {{ $errors->first('name') }} + + @endif
- {!! 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']) !!}
-
- {!! Form::label('location', trans('forms.storage_driver_label'), ['class' => 'control-label']) !!} +
+ {!! 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')) + + {{ $errors->first('location') }} + + @endif
@@ -60,7 +62,7 @@
- @lang('forms.cancel_action') + @lang('forms.cancel_action') {!! Form::submit(trans('forms.create_action'), ['class' => 'btn btn-success']) !!}
{!! Form::close() !!} diff --git a/resources/views/themes/base/admin/edit_storage.blade.php b/resources/views/themes/base/admin/edit_storage.blade.php index fb2416c..4f1c322 100644 --- a/resources/views/themes/base/admin/edit_storage.blade.php +++ b/resources/views/themes/base/admin/edit_storage.blade.php @@ -22,20 +22,16 @@

@lang('admin.edit_storage_intro', ['storage_name' => $storage->name])


- @if (count($errors) > 0) -
- -
- @endif - {!! Form::model($storage, ['route' => ['storage.update', $storage->id], 'method' => 'PUT']) !!} -
+
{!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!} {!! Form::text('name', old('name'), ['class' => 'form-control']) !!} + + @if ($errors->has('name')) + + {{ $errors->first('name') }} + + @endif
@@ -45,6 +41,13 @@
+
+ +
+
@lang('forms.cancel_action') {!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!} diff --git a/resources/views/themes/base/admin/list_storage.blade.php b/resources/views/themes/base/admin/list_storage.blade.php index 71ab967..9921933 100644 --- a/resources/views/themes/base/admin/list_storage.blade.php +++ b/resources/views/themes/base/admin/list_storage.blade.php @@ -32,12 +32,17 @@ @foreach ($storageLocations as $storage) - {{ $storage->name }}@if ($storage->is_default) @endif
- {{ $storage->location }} + + {{ $storage->name }} + @if ($storage->is_default) @endif + @if (!$storage->is_active) @endif +
{{ $storage->location }} @lang('forms.edit_action') - @lang('forms.delete_action') + @if (!$storage->is_internal) + @lang('forms.delete_action') + @endif @endforeach @@ -51,6 +56,20 @@ + +
+ +
+
+
+
@lang('admin.legend')
+
+ @lang('admin.default_storage_legend')
+ @lang('admin.inactive_storage_legend') +
+
+
+
@endif
diff --git a/resources/views/themes/base/admin/show_album.blade.php b/resources/views/themes/base/admin/show_album.blade.php index 2c7ae17..0c4e01d 100644 --- a/resources/views/themes/base/admin/show_album.blade.php +++ b/resources/views/themes/base/admin/show_album.blade.php @@ -194,19 +194,7 @@
-
-
-
@lang('admin.danger_zone_heading')
-
-

@lang('admin.danger_zone_intro')

-

- @lang('forms.delete_action') -

-
-
-
- -
+
@lang('admin.save_changes_heading')
@@ -217,6 +205,18 @@
+ +
+
+
@lang('admin.danger_zone_heading')
+
+

@lang('admin.danger_zone_intro')

+

+ @lang('forms.delete_action') +

+
+
+
{!! Form::close() !!}