#4: Added a framework for manipulating data during system updates. Full URL path to an album is now saved in the database. Fall-back routes are now mapped by the first segment - a = album, p = photo, i = image

This commit is contained in:
Andy Heathershaw 2017-04-17 21:31:45 +01:00
parent e0773ba236
commit a26f9c1c1f
19 changed files with 168 additions and 37 deletions

View File

@ -4,6 +4,7 @@ namespace App;
use App\AlbumSources\IAlbumSource; use App\AlbumSources\IAlbumSource;
use App\AlbumSources\LocalFilesystemSource; use App\AlbumSources\LocalFilesystemSource;
use App\Helpers\MiscHelper;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -19,7 +20,7 @@ class Album extends Model
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id', 'default_view', 'parent_album_id' 'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id', 'default_view', 'parent_album_id', 'url_path'
]; ];
/** /**
@ -30,6 +31,26 @@ class Album extends Model
protected $hidden = [ protected $hidden = [
]; ];
/**
* Gets an array of parent items
* @return array
*/
public function albumParentTree()
{
$albums = [];
$current = $this;
while (!is_null($current))
{
$albums[] = $current;
$current = $current->parent;
}
$albums = array_reverse($albums);
return $albums;
}
public function anonymousPermissions() public function anonymousPermissions()
{ {
return $this->belongsToMany(Permission::class, 'album_anonymous_permissions'); return $this->belongsToMany(Permission::class, 'album_anonymous_permissions');
@ -66,7 +87,23 @@ class Album extends Model
public function generateAlias() public function generateAlias()
{ {
$this->url_alias = ucfirst(preg_replace('/[^a-z0-9\-]/', '-', strtolower($this->name))); $this->url_alias = MiscHelper::capitaliseWord(preg_replace('/[^a-z0-9\-]/', '-', strtolower($this->name)));
}
public function generateUrlPath()
{
$parts = [];
$current = $this;
while (!is_null($current))
{
$parts[] = $current->url_alias;
$current = $current->parent;
}
$parts = array_reverse($parts);
$this->url_path = join('/', $parts);
} }
/** /**
@ -146,19 +183,12 @@ class Album extends Model
public function url() public function url()
{ {
$parts = []; if (is_null($this->url_path))
$current = $this;
while (!is_null($current))
{ {
$parts[] = $current->url_alias; $this->generateUrlPath();
$current = $current->parent;
} }
$parts = array_reverse($parts); return route('viewAlbum', $this->url_path);
return route('viewAlbum', join('/', $parts));
} }
public function userPermissions() public function userPermissions()

View File

@ -62,7 +62,7 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource
public function getUrlToPhoto(Photo $photo, $thumbnail = null) public function getUrlToPhoto(Photo $photo, $thumbnail = null)
{ {
$photoUrl = route('downloadPhoto', [ $photoUrl = route('downloadPhoto', [
'albumUrlAlias' => $this->album->url_alias, 'albumUrlAlias' => $this->album->url_path,
'photoFilename' => $photo->storage_file_name 'photoFilename' => $photo->storage_file_name
]); ]);

8
app/DataMigration.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace App;
abstract class DataMigration
{
abstract function run($currentVersion);
}

View File

@ -72,8 +72,8 @@ class DbHelper
->withCount('photos'); ->withCount('photos');
} }
public static function getAlbumByAlias($urlAlias) public static function getAlbumByPath($urlPath)
{ {
return Album::where('url_alias', $urlAlias)->first(); return Album::where('url_path', $urlPath)->first();
} }
} }

View File

@ -4,6 +4,21 @@ namespace App\Helpers;
class MiscHelper class MiscHelper
{ {
public static function capitaliseWord($word)
{
$word = strtolower($word);
$wordLetters = [];
preg_match_all('/\b[a-z]/i', $word, $wordLetters, PREG_OFFSET_CAPTURE);
foreach ($wordLetters[0] as $wordLetter)
{
$word = substr_replace($word, strtoupper($wordLetter[0]), $wordLetter[1], 1);
}
return $word;
}
public static function convertToBytes($val) public static function convertToBytes($val)
{ {
if(empty($val))return 0; if(empty($val))return 0;

View File

@ -18,7 +18,7 @@ class AlbumController extends Controller
{ {
public function index(Request $request, $albumUrlAlias) public function index(Request $request, $albumUrlAlias)
{ {
$album = DbHelper::getAlbumByAlias($albumUrlAlias); $album = DbHelper::getAlbumByPath($albumUrlAlias);
if (is_null($album)) if (is_null($album))
{ {
App::abort(404); App::abort(404);

View File

@ -16,7 +16,7 @@ class DefaultController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
$albums = DbHelper::getAlbumsForCurrentUser(); $albums = DbHelper::getAlbumsForCurrentUser(0);
$resetStatus = $request->session()->get('status'); $resetStatus = $request->session()->get('status');
// Record the visit to the index (no album or photo to record a hit against though) // Record the visit to the index (no album or photo to record a hit against though)

View File

@ -22,7 +22,7 @@ class PhotoController extends Controller
{ {
public function download(Request $request, $albumUrlAlias, $photoFilename) public function download(Request $request, $albumUrlAlias, $photoFilename)
{ {
$album = DbHelper::getAlbumByAlias($albumUrlAlias); $album = DbHelper::getAlbumByPath($albumUrlAlias);
if (is_null($album)) if (is_null($album))
{ {
App::abort(404); App::abort(404);
@ -89,7 +89,7 @@ class PhotoController extends Controller
public function show(Request $request, $albumUrlAlias, $photoFilename) public function show(Request $request, $albumUrlAlias, $photoFilename)
{ {
$album = DbHelper::getAlbumByAlias($albumUrlAlias); $album = DbHelper::getAlbumByPath($albumUrlAlias);
if (is_null($album)) if (is_null($album))
{ {
App::abort(404); App::abort(404);

View File

@ -3,6 +3,7 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Configuration; use App\Configuration;
use App\DataMigration;
use App\Facade\UserConfig; use App\Facade\UserConfig;
use App\Helpers\MiscHelper; use App\Helpers\MiscHelper;
use Closure; use Closure;
@ -96,6 +97,14 @@ class AppInstallation
Artisan::call('cache:clear'); Artisan::call('cache:clear');
Artisan::call('migrate', ['--force' => true]); Artisan::call('migrate', ['--force' => true]);
$className = sprintf('DataMigrationV%s', str_replace(['.', '-'], '_', $appVersionNumber));
if (class_exists($className))
{
/** @var DataMigration $upgradeClass */
$upgradeClass = new $className;
$upgradeClass->run($versionNumber);
}
} }
$versionNumber->value = $appVersionNumber; $versionNumber->value = $appVersionNumber;

View File

@ -0,0 +1,22 @@
<?php
namespace App\ModelObservers;
use App\Album;
class AlbumObserver
{
public function creating(Album $album)
{
// Re-generate the alias and path
$album->generateAlias();
$album->generateUrlPath();
}
public function updating(Album $album)
{
// Re-generate the alias and path
$album->generateAlias();
$album->generateUrlPath();
}
}

View File

@ -65,7 +65,7 @@ class Photo extends Model
public function url() public function url()
{ {
return route('viewPhoto', [ return route('viewPhoto', [
'albumUrlAlias' => $this->album->url_alias, 'albumUrlAlias' => $this->album->url_path,
'photoFilename' => $this->storage_file_name 'photoFilename' => $this->storage_file_name
]); ]);
} }

View File

@ -2,10 +2,12 @@
namespace App\Providers; namespace App\Providers;
use App\Album;
use App\Helpers\ConfigHelper; use App\Helpers\ConfigHelper;
use App\Helpers\ImageHelper; use App\Helpers\ImageHelper;
use App\Helpers\ThemeHelper; use App\Helpers\ThemeHelper;
use App\Helpers\ValidationHelper; use App\Helpers\ValidationHelper;
use App\ModelObservers\AlbumObserver;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Illuminate\Mail\Mailer; use Illuminate\Mail\Mailer;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -38,6 +40,9 @@ class AppServiceProvider extends ServiceProvider
Validator::extend('is_dir', (ValidationHelper::class . '@directoryExists')); Validator::extend('is_dir', (ValidationHelper::class . '@directoryExists'));
Validator::extend('dir_empty', (ValidationHelper::class . '@isDirectoryEmpty')); Validator::extend('dir_empty', (ValidationHelper::class . '@isDirectoryEmpty'));
Validator::extend('is_writeable', (ValidationHelper::class . '@isPathWriteable')); Validator::extend('is_writeable', (ValidationHelper::class . '@isPathWriteable'));
// Model observers
Album::observe(AlbumObserver::class);
} }
/** /**

View File

@ -0,0 +1,27 @@
<?php
use App\Album;
use App\DataMigration;
class DataMigrationV2_0_0_alpha_1 extends DataMigration
{
public function run($currentVersion)
{
$this->storeAlbumUrlPaths();
}
/**
* Ensures all albums have a full URL path stored.
* @return void
*/
private function storeAlbumUrlPaths()
{
$albumsWithNoPath = Album::where('url_path', null)->get();
/** @var Album $album */
foreach ($albumsWithNoPath as $album)
{
$album->touch();
}
}
}

View File

@ -16,6 +16,7 @@ class AddParentAlbumColumn extends Migration
Schema::table('albums', function (Blueprint $table) Schema::table('albums', function (Blueprint $table)
{ {
$table->unsignedInteger('parent_album_id')->nullable(); $table->unsignedInteger('parent_album_id')->nullable();
$table->string('url_path')->nullable();
$table->foreign('parent_album_id') $table->foreign('parent_album_id')
->references('id')->on('albums') ->references('id')->on('albums')
@ -34,6 +35,7 @@ class AddParentAlbumColumn extends Migration
{ {
$table->dropForeign('albums_parent_album_id_foreign'); $table->dropForeign('albums_parent_album_id_foreign');
$table->dropColumn('parent_album_id'); $table->dropColumn('parent_album_id');
$table->dropColumn('url_path');
}); });
} }
} }

View File

@ -7,7 +7,7 @@
<div class="col"> <div class="col">
<ol class="breadcrumb"> <ol class="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('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item active">{{ $album->name }}</li> @include(Theme::viewName('partials.album_breadcrumb'))
</ol> </ol>
</div> </div>
</div> </div>

View File

@ -1,12 +1,18 @@
@extends('themes.base.layout') @extends('themes.base.layout')
@section('title', $album->name) @section('title', $album->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 active">{{ $album->name }}</li>
@endsection
@section('content') @section('content')
<div class="container-fluid">
<div class="row">
<div class="col">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
@include(Theme::viewName('partials.album_breadcrumb'))
</ol>
</div>
</div>
</div>
<div class="container album-container"> <div class="container album-container">
<div class="row"> <div class="row">
<div class="col-md-8 offset-md-2 text-center"> <div class="col-md-8 offset-md-2 text-center">

View File

@ -2,14 +2,8 @@
@section('title', $album->name) @section('title', $album->name)
@section('breadcrumb') @section('breadcrumb')
<div class="breadcrumb"> <li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<div class="container"> @include(Theme::viewName('partials.album_breadcrumb'))
<ol class="breadcrumb">
<li><a href="{{ route('home') }}">Gallery</a></li>
<li class="active">{{ $album->name }}</li>
</ol>
</div>
</div>
@endsection @endsection
@section('content') @section('content')

View File

@ -0,0 +1,7 @@
@foreach ($album->albumParentTree() as $parentAlbum)
@if ($parentAlbum->id == $album->id)
<li class="breadcrumb-item active">{{ $album->name }}</li>
@else
<li class="breadcrumb-item"><a href="{{ $parentAlbum->url() }}">{{ $parentAlbum->name }}</a></li>
@endif
@endforeach

View File

@ -66,6 +66,12 @@ Route::get('/', 'Gallery\DefaultController@index')->name('home');
Route::get('/activate/{token}', 'Auth\ActivateController@activate')->name('auth.activate'); Route::get('/activate/{token}', 'Auth\ActivateController@activate')->name('auth.activate');
Route::get('/password/change', 'Auth\ChangePasswordController@showChangePasswordForm')->name('auth.changePassword'); Route::get('/password/change', 'Auth\ChangePasswordController@showChangePasswordForm')->name('auth.changePassword');
Route::post('/password/change', 'Auth\ChangePasswordController@processChangePassword')->name('auth.processChangePassword'); Route::post('/password/change', 'Auth\ChangePasswordController@processChangePassword')->name('auth.processChangePassword');
Route::get('{albumUrlAlias}', 'Gallery\AlbumController@index')->name('viewAlbum'); Route::get('a/{albumUrlAlias}', 'Gallery\AlbumController@index')
Route::get('{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@show')->name('viewPhoto'); ->name('viewAlbum')
Route::get('photo/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@download')->name('downloadPhoto'); ->where('albumUrlAlias', '.*');
Route::get('p/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@show')
->name('viewPhoto')
->where('albumUrlAlias', '.*');
Route::get('i/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@download')
->name('downloadPhoto')
->where('albumUrlAlias', '.*');