Pull #106 and #148 Dropbox and external services #149

Merged
aheathershaw merged 9 commits from feature/106-dropbox-storage into master 2020-04-22 06:58:15 +01:00
61 changed files with 2153 additions and 1272 deletions
Showing only changes of commit fdf4d72236 - Show all commits

View File

@ -42,6 +42,11 @@ abstract class AlbumSourceBase
return self::$albumSourceCache[$fullClassName]; return self::$albumSourceCache[$fullClassName];
} }
public function getConfiguration()
{
return $this->configuration;
}
public function setAlbum(Album $album) public function setAlbum(Album $album)
{ {
$this->album = $album; $this->album = $album;

View File

@ -130,7 +130,19 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
*/ */
public function getUrlToPhoto(Photo $photo, $thumbnail = null) public function getUrlToPhoto(Photo $photo, $thumbnail = null)
{ {
return $this->getClient()->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail)); $client = $this->getClient();
if ($this->configuration->s3_signed_urls)
{
$cmd = $client->getCommand('GetObject', [
'Bucket' => $this->configuration->container_name,
'Key' => $this->getPathToPhoto($photo, $thumbnail)
]);
return $client->createPresignedRequest($cmd, '+5 minutes')->getUri();
}
return $client->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail));
} }
/** /**
@ -144,7 +156,11 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ
{ {
$photoPath = $this->getPathToPhoto($photo, $thumbnail); $photoPath = $this->getPathToPhoto($photo, $thumbnail);
$this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), 'public-read'); $uploadAcl = $this->configuration->s3_signed_urls
? 'private'
: 'public-read';
$this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), $uploadAcl);
} }
/** /**

View File

@ -6,7 +6,6 @@ use App\Album;
use App\Photo; use App\Photo;
use App\Storage; use App\Storage;
use Guzzle\Http\EntityBody; use Guzzle\Http\EntityBody;
use Symfony\Component\HttpFoundation\File\File;
interface IAlbumSource interface IAlbumSource
{ {
@ -32,6 +31,12 @@ interface IAlbumSource
*/ */
function fetchPhotoContent(Photo $photo, $thumbnail = null); function fetchPhotoContent(Photo $photo, $thumbnail = null);
/**
* Gets the configuration of the source.
* @return mixed
*/
function getConfiguration();
/** /**
* Gets the name of this album source. * Gets the name of this album source.
* @return string * @return string

View File

@ -11,7 +11,6 @@ use App\Facade\Theme;
use App\Facade\UserConfig; use App\Facade\UserConfig;
use App\Group; use App\Group;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Helpers\FileHelper;
use App\Helpers\MiscHelper; use App\Helpers\MiscHelper;
use App\Helpers\PermissionsHelper; use App\Helpers\PermissionsHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -74,7 +73,7 @@ class AlbumController extends Controller
if (count($photos) == 0) if (count($photos) == 0)
{ {
return redirect(route('albums.show', ['id' => $album->id])); return redirect(route('albums.show', ['album' => $album->id]));
} }
return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]); return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]);
@ -171,7 +170,7 @@ class AlbumController extends Controller
$redirect->delete(); $redirect->delete();
$request->session()->flash('success', trans('admin.delete_redirect_success_message')); $request->session()->flash('success', trans('admin.delete_redirect_success_message'));
return redirect(route('albums.show', ['id' => $id, 'tab' => 'redirects'])); return redirect(route('albums.show', ['album' => $id, 'tab' => 'redirects']));
} }
/** /**
@ -706,7 +705,7 @@ class AlbumController extends Controller
$helper = new PermissionsHelper(); $helper = new PermissionsHelper();
$helper->rebuildCache(); $helper->rebuildCache();
return redirect(route('albums.show', ['id' => $album->id])); return redirect(route('albums.show', ['album' => $album->id]));
} }
public function storeRedirect(Requests\StoreAlbumRedirectRequest $request, $id) public function storeRedirect(Requests\StoreAlbumRedirectRequest $request, $id)
@ -721,7 +720,7 @@ class AlbumController extends Controller
$redirect->save(); $redirect->save();
$request->session()->flash('success', trans('admin.create_redirect_success_message')); $request->session()->flash('success', trans('admin.create_redirect_success_message'));
return redirect(route('albums.show', ['id' => $id, 'tab' => 'redirects'])); return redirect(route('albums.show', ['album' => $id, 'tab' => 'redirects']));
} }
/** /**
@ -783,7 +782,7 @@ class AlbumController extends Controller
$request->session()->flash('success', trans('admin.album_saved_successfully', ['name' => $album->name])); $request->session()->flash('success', trans('admin.album_saved_successfully', ['name' => $album->name]));
return redirect(route('albums.show', ['id' => $id])); return redirect(route('albums.show', ['album' => $id]));
} }
private function createActivityRecord(Album $album, $type, $activityDateTime = null) private function createActivityRecord(Album $album, $type, $activityDateTime = null)

View File

@ -134,7 +134,7 @@ class PhotoCommentController extends Controller
if (count($commentIDs) == 1) if (count($commentIDs) == 1)
{ {
// Single comment selected - redirect to the single delete page // Single comment selected - redirect to the single delete page
return redirect(route('comments.delete', ['id' => $commentIDs[0]])); return redirect(route('comments.delete', ['comment' => $commentIDs[0]]));
} }
// Show the view to confirm the delete // Show the view to confirm the delete
@ -148,7 +148,7 @@ class PhotoCommentController extends Controller
if (count($commentIDs) == 1) if (count($commentIDs) == 1)
{ {
// Single comment selected - redirect to the single approve page // Single comment selected - redirect to the single approve page
return redirect(route('comments.approve', ['id' => $commentIDs[0]])); return redirect(route('comments.approve', ['comment' => $commentIDs[0]]));
} }
// Show the view to confirm the approval // Show the view to confirm the approval
@ -162,7 +162,7 @@ class PhotoCommentController extends Controller
if (count($commentIDs) == 1) if (count($commentIDs) == 1)
{ {
// Single comment selected - redirect to the single reject page // Single comment selected - redirect to the single reject page
return redirect(route('comments.reject', ['id' => $commentIDs[0]])); return redirect(route('comments.reject', ['comment' => $commentIDs[0]]));
} }
// Show the view to confirm the rejection // Show the view to confirm the rejection

View File

@ -392,7 +392,7 @@ class PhotoController extends Controller
else else
{ {
return redirect(route('albums.analyse', [ return redirect(route('albums.analyse', [
'id' => $album->id, 'album' => $album->id,
'queue_token' => $queueUid 'queue_token' => $queueUid
])); ]));
} }
@ -408,7 +408,7 @@ class PhotoController extends Controller
if (is_null($request->files->get('archive'))) if (is_null($request->files->get('archive')))
{ {
$request->session()->flash('error', trans('admin.upload_bulk_no_file')); $request->session()->flash('error', trans('admin.upload_bulk_no_file'));
return redirect(route('albums.show', ['id' => $album->id])); return redirect(route('albums.show', ['album' => $album->id]));
} }
$archiveFile = UploadedFile::createFromBase($request->files->get('archive')); $archiveFile = UploadedFile::createFromBase($request->files->get('archive'));
@ -416,7 +416,7 @@ class PhotoController extends Controller
{ {
Log::error('Bulk image upload failed.', ['error' => $archiveFile->getError(), 'reason' => $archiveFile->getErrorMessage()]); Log::error('Bulk image upload failed.', ['error' => $archiveFile->getError(), 'reason' => $archiveFile->getErrorMessage()]);
$request->session()->flash('error', $archiveFile->getErrorMessage()); $request->session()->flash('error', $archiveFile->getErrorMessage());
return redirect(route('albums.show', ['id' => $album->id])); return redirect(route('albums.show', ['album' => $album->id]));
} }
// Create the folder to hold the analysis results if not already present // Create the folder to hold the analysis results if not already present
@ -446,7 +446,7 @@ class PhotoController extends Controller
default: default:
$request->session()->flash('error', sprintf('The file type "%s" is not supported for bulk uploads.', $mimeType)); $request->session()->flash('error', sprintf('The file type "%s" is not supported for bulk uploads.', $mimeType));
return redirect(route('albums.show', ['id' => $album->id])); return redirect(route('albums.show', ['album' => $album->id]));
} }
$di = new \RecursiveDirectoryIterator($temporaryFolder, \RecursiveDirectoryIterator::SKIP_DOTS); $di = new \RecursiveDirectoryIterator($temporaryFolder, \RecursiveDirectoryIterator::SKIP_DOTS);
@ -523,7 +523,7 @@ class PhotoController extends Controller
} }
return redirect(route('albums.analyse', [ return redirect(route('albums.analyse', [
'id' => $album->id, 'album' => $album->id,
'queue_token' => $queueUid 'queue_token' => $queueUid
])); ]));
} }
@ -586,7 +586,7 @@ class PhotoController extends Controller
) )
); );
return redirect(route('albums.show', array('id' => $albumId, 'page' => $request->get('page', 1)))); return redirect(route('albums.show', array('album' => $albumId, 'page' => $request->get('page', 1))));
} }
private function applyBulkActions(Request $request, Album $album) private function applyBulkActions(Request $request, Album $album)

View File

@ -55,12 +55,14 @@ class StorageController extends Controller
$this->authorizeAccessToAdminPanel('admin:manage-storage'); $this->authorizeAccessToAdminPanel('admin:manage-storage');
$filesystemDefaultLocation = sprintf('%s/storage/app/albums', dirname(dirname(dirname(dirname(__DIR__))))); $filesystemDefaultLocation = sprintf('%s/storage/app/albums', dirname(dirname(dirname(dirname(__DIR__)))));
$storage = new Storage();
$storage->s3_signed_urls = true;
return Theme::render('admin.create_storage', [ return Theme::render('admin.create_storage', [
'album_sources' => UserConfig::albumSources(), 'album_sources' => UserConfig::albumSources(),
'filesystem_default_location' => $filesystemDefaultLocation, 'filesystem_default_location' => $filesystemDefaultLocation,
'info' => $request->session()->get('info'), 'info' => $request->session()->get('info'),
'storage' => new Storage() 'storage' => $storage
]); ]);
} }
@ -95,6 +97,7 @@ class StorageController extends Controller
$storage->is_active = true; $storage->is_active = true;
$storage->is_default = (strtolower($request->get('is_default')) == 'on'); $storage->is_default = (strtolower($request->get('is_default')) == 'on');
$storage->is_internal = false; $storage->is_internal = false;
$storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on');
if ($storage->source != 'LocalFilesystemSource' && isset($storage->location)) if ($storage->source != 'LocalFilesystemSource' && isset($storage->location))
{ {
@ -221,10 +224,12 @@ class StorageController extends Controller
'access_key', 'access_key',
'secret_key', 'secret_key',
'b2_bucket_type', 'b2_bucket_type',
'access_token' 'access_token',
's3_signed_urls'
])); ]));
$storage->is_active = (strtolower($request->get('is_active')) == 'on'); $storage->is_active = (strtolower($request->get('is_active')) == 'on');
$storage->is_default = (strtolower($request->get('is_default')) == 'on'); $storage->is_default = (strtolower($request->get('is_default')) == 'on');
$storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on');
if ($storage->is_default && !$storage->is_active) if ($storage->is_default && !$storage->is_active)
{ {

View File

@ -2,14 +2,11 @@
namespace App\Http\Controllers\Gallery; namespace App\Http\Controllers\Gallery;
use App\Album;
use App\AlbumRedirect; use App\AlbumRedirect;
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\VisitorHit; use App\VisitorHit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
@ -34,7 +31,7 @@ class AlbumController extends Controller
} }
$album = DbHelper::getAlbumById($redirect->album_id); $album = DbHelper::getAlbumById($redirect->album_id);
return redirect($album->url()); return redirect($album->url(), 301);
} }
$this->authorizeForUser($this->getUser(), 'view', $album); $this->authorizeForUser($this->getUser(), 'view', $album);

View File

@ -2,6 +2,7 @@
namespace App; namespace App;
use App\AlbumSources\IAlbumSource;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -110,9 +111,14 @@ class Photo extends Model
public function thumbnailUrl($thumbnailName = null, $cacheBust = true) public function thumbnailUrl($thumbnailName = null, $cacheBust = true)
{ {
$url = $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName); /** @var IAlbumSource $source */
$source = $this->album->getAlbumSource();
$sourceConfiguration = $source->getConfiguration();
if ($cacheBust) $url = $source->getUrlToPhoto($this, $thumbnailName);
// Cache busting doesn't work with S3 signed URLs
if ($cacheBust && !$sourceConfiguration->s3_signed_urls)
{ {
// Append the timestamp of the last update to avoid browser caching // Append the timestamp of the last update to avoid browser caching
$theDate = is_null($this->updated_at) ? $this->created_at : $this->updated_at; $theDate = is_null($this->updated_at) ? $this->created_at : $this->updated_at;

View File

@ -5,58 +5,54 @@
"license": "MIT", "license": "MIT",
"type": "project", "type": "project",
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.2.0",
"ext-curl": "*", "ext-curl": "*",
"ext-json": "*", "ext-json": "*",
"laravel/framework": "5.5.*", "aws/aws-sdk-php": "^3.134",
"rackspace/php-opencloud": "^1.16",
"doctrine/dbal": "^2.5", "doctrine/dbal": "^2.5",
"aws/aws-sdk-php": "^3.19", "laravel/framework": "^6.0",
"laravel/socialite": "^3.0", "laravel/socialite": "^4.3",
"php-amqplib/php-amqplib": "^2.9" "php-amqplib/php-amqplib": "^2.9",
"php-opencloud/openstack": "^3.0"
}, },
"require-dev": { "require-dev": {
"filp/whoops": "~2.0", "facade/ignition": "^1.4",
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "^1.9.1",
"mockery/mockery": "0.9.*", "mockery/mockery": "^1.0",
"phpunit/phpunit": "~6.0", "phpunit/phpunit": "^8.0"
"symfony/css-selector": "3.1.*",
"symfony/dom-crawler": "3.1.*"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [
"database" "database/seeds",
"database/factories"
], ],
"psr-4": { "psr-4": {
"App\\": "app/" "App\\": "app/"
} }
}, },
"autoload-dev": { "autoload-dev": {
"classmap": [ "psr-4": {
] "Tests\\": "tests/"
}
}, },
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": { "scripts": {
"post-root-package-install": [
"php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"php artisan key:generate"
],
"post-install-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postInstall",
"php artisan optimize"
],
"post-update-cmd": [
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"php artisan optimize"
],
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover" "@php artisan package:discover --ansi"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
] ]
}, },
"config": { "config": {
"optimize-autoloader": true,
"preferred-install": "dist", "preferred-install": "dist",
"discard-changes": true "discard-changes": true,
"sort-packages": true
} }
} }

2915
composer.lock generated

File diff suppressed because it is too large Load Diff

52
config/hashing.php Normal file
View File

@ -0,0 +1,52 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 10),
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [
'memory' => 1024,
'threads' => 2,
'time' => 2,
],
];

81
config/logging.php Normal file
View File

@ -0,0 +1,81 @@
<?php
use Monolog\Handler\StreamHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
],
];

View File

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

1
public/svg/403.svg Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

1
public/svg/404.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><defs><linearGradient id="a" x1="50.31%" x2="50%" y1="74.74%" y2="0%"><stop offset="0%" stop-color="#FFE98A"/><stop offset="67.7%" stop-color="#B63E59"/><stop offset="100%" stop-color="#68126F"/></linearGradient><circle id="c" cx="603" cy="682" r="93"/><filter id="b" width="203.2%" height="203.2%" x="-51.6%" y="-51.6%" filterUnits="objectBoundingBox"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="32"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><linearGradient id="d" x1="49.48%" x2="49.87%" y1="11.66%" y2="77.75%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="e" x1="91.59%" x2="66.97%" y1="5.89%" y2="100%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient><linearGradient id="f" x1="49.48%" x2="49.61%" y1="11.66%" y2="98.34%"><stop offset="0%" stop-color="#F7EAB9"/><stop offset="100%" stop-color="#E5765E"/></linearGradient><linearGradient id="g" x1="78.5%" x2="36.4%" y1="106.76%" y2="26.41%"><stop offset="0%" stop-color="#A22A50"/><stop offset="100%" stop-color="#EE7566"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><rect width="1024" height="1024" fill="url(#a)"/><use fill="black" filter="url(#b)" xlink:href="#c"/><use fill="#FFF6CB" xlink:href="#c"/><g fill="#FFFFFF" opacity=".3" transform="translate(14 23)"><circle cx="203" cy="255" r="3" fill-opacity=".4"/><circle cx="82" cy="234" r="2"/><circle cx="22" cy="264" r="2" opacity=".4"/><circle cx="113" cy="65" r="3"/><circle cx="202" cy="2" r="2"/><circle cx="2" cy="114" r="2"/><circle cx="152" cy="144" r="2"/><circle cx="362" cy="224" r="2"/><circle cx="453" cy="65" r="3" opacity=".4"/><circle cx="513" cy="255" r="3"/><circle cx="593" cy="115" r="3"/><circle cx="803" cy="5" r="3" opacity=".4"/><circle cx="502" cy="134" r="2"/><circle cx="832" cy="204" r="2"/><circle cx="752" cy="114" r="2"/><circle cx="933" cy="255" r="3" opacity=".4"/><circle cx="703" cy="225" r="3"/><circle cx="903" cy="55" r="3"/><circle cx="982" cy="144" r="2"/><circle cx="632" cy="14" r="2"/></g><g transform="translate(0 550)"><path fill="#8E2C15" d="M259 5.47c0 5.33 3.33 9.5 10 12.5s9.67 9.16 9 18.5h1c.67-6.31 1-11.8 1-16.47 8.67 0 13.33-1.33 14-4 .67 4.98 1.67 8.3 3 9.97 1.33 1.66 2 5.16 2 10.5h1c0-5.65.33-9.64 1-11.97 1-3.5 4-10.03-1-14.53S295 7 290 3c-5-4-10-3-13 2s-5 7-9 7-5-3.53-5-5.53c0-2 2-5-1.5-5s-7.5 0-7.5 2c0 1.33 1.67 2 5 2z"/><path fill="url(#d)" d="M1024 390H0V105.08C77.3 71.4 155.26 35 297.4 35c250 0 250.76 125.25 500 125 84.03-.08 160.02-18.2 226.6-40.93V390z"/><path fill="url(#d)" d="M1024 442H0V271.82c137.51-15.4 203.1-50.49 356.67-60.1C555.24 199.3 606.71 86.59 856.74 86.59c72.78 0 124.44 10.62 167.26 25.68V442z"/><path fill="url(#e)" d="M1024 112.21V412H856.91c99.31-86.5 112.63-140.75 39.97-162.78C710.24 192.64 795.12 86.58 856.9 86.58c72.7 0 124.3 10.6 167.09 25.63z"/><path fill="url(#e)" d="M1024 285.32V412H857c99.31-86.6 112.63-140.94 39.97-163L1024 285.32z"/><path fill="url(#f)" d="M0 474V223.93C67.12 190.69 129.55 155 263 155c250 0 331.46 162.6 530 175 107.42 6.71 163-26.77 231-58.92V474H0z"/><path fill="url(#e)" d="M353.02 474H0V223.93C67.12 190.69 129.55 155 263 155c71.14 0 151.5 12.76 151.5 70.5 0 54.5-45.5 79.72-112.5 109-82.26 35.95-54.57 111.68 51.02 139.5z"/><path fill="url(#g)" d="M353.02 474H0v-14.8l302-124.7c-82.26 35.95-54.57 111.68 51.02 139.5z"/></g><g fill="#FFFFFF" opacity=".2" transform="translate(288 523)"><circle cx="250" cy="110" r="110"/><circle cx="420" cy="78" r="60"/><circle cx="70" cy="220" r="70"/></g><g fill="#FFFFFF" fill-rule="nonzero" opacity=".08" transform="translate(135 316)"><path d="M10 80.22a14.2 14.2 0 0 1 20 0 14.2 14.2 0 0 0 20 0l20-19.86a42.58 42.58 0 0 1 60 0l15 14.9a21.3 21.3 0 0 0 30 0 21.3 21.3 0 0 1 30 0l.9.9A47.69 47.69 0 0 1 220 110H0v-5.76c0-9.02 3.6-17.67 10-24.02zm559.1-66.11l5.9-5.86c11.07-11 28.93-11 40 0l10 9.94a14.19 14.19 0 0 0 20 0 14.19 14.19 0 0 1 20 0 16.36 16.36 0 0 0 21.3 1.5l8.7-6.47a33.47 33.47 0 0 1 40 0l4.06 3.03A39.6 39.6 0 0 1 755 48H555a47.77 47.77 0 0 1 14.1-33.89z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

1
public/svg/500.svg Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

1
public/svg/503.svg Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -323,7 +323,10 @@ return [
'photos' => 'photo|photos', 'photos' => 'photo|photos',
'users' => 'user|users', 'users' => 'user|users',
], ],
'storage_auth_url_label_help' => 'Leave blank to authenticate with Amazon S3. For an S3-compatible provider, enter your provider\'s authentication URL here.',
'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.', 'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.',
'storage_s3_signed_urls_help' => 'When enabled, Blue Twilight will upload your photos with a private ACL and will use signed URLs to display the photos to your visitors.',
'storage_s3_signed_urls_tooltip' => 'This location is set to use private images with signed URLs.',
'storage_title' => 'Storage Locations', 'storage_title' => 'Storage Locations',
'sysinfo_panel' => 'System information', 'sysinfo_panel' => 'System information',
'sysinfo_widget' => [ 'sysinfo_widget' => [

View File

@ -115,6 +115,7 @@ return [
'storage_driver_label' => 'Storage driver:', 'storage_driver_label' => 'Storage driver:',
'storage_endpoint_url_label' => 'Endpoint URL (leave blank if using Amazon):', 'storage_endpoint_url_label' => 'Endpoint URL (leave blank if using Amazon):',
'storage_location_label' => 'Physical location:', 'storage_location_label' => 'Physical location:',
'storage_s3_signed_urls' => 'Upload files privately and use signed URLs',
'storage_secret_key_label' => 'Secret key:', 'storage_secret_key_label' => 'Secret key:',
'storage_service_name_label' => 'Service name:', 'storage_service_name_label' => 'Service name:',
'storage_service_region_label' => 'Region:', 'storage_service_region_label' => 'Region:',

View File

@ -5,7 +5,7 @@
<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"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li> <li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item"><a href="{{ route('albums.index') }}">@lang('navigation.breadcrumb.albums')</a></li> <li class="breadcrumb-item"><a href="{{ route('albums.index') }}">@lang('navigation.breadcrumb.albums')</a></li>
<li class="breadcrumb-item"><a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li> <li class="breadcrumb-item"><a href="{{ route('albums.show', ['album' => $album->id]) }}">{{ $album->name }}</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.metadata_upgrade')</li> <li class="breadcrumb-item active">@lang('navigation.breadcrumb.metadata_upgrade')</li>
@endsection @endsection
@ -52,7 +52,7 @@
<p class="text-danger"><b>@lang('admin.metadata_upgrade.warning')</b></p> <p class="text-danger"><b>@lang('admin.metadata_upgrade.warning')</b></p>
<div class="text-right"> <div class="text-right">
<a href="{{ route('admin.metadataUpgrade', ['id' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a> <a href="{{ route('admin.metadataUpgrade', ['album' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a>
<button id="submit-button" type="submit" class="btn btn-primary"><i class="fa fa-fw fa-check"></i> @lang('forms.continue_action')</button> <button id="submit-button" type="submit" class="btn btn-primary"><i class="fa fa-fw fa-check"></i> @lang('forms.continue_action')</button>
</div> </div>
</div> </div>
@ -97,7 +97,7 @@
app.analyseImage(new AnalyseImageViewModel({ app.analyseImage(new AnalyseImageViewModel({
'id': '{{ $photo->id }}', 'id': '{{ $photo->id }}',
'name': '{!! addslashes($photo->name) !!}', 'name': '{!! addslashes($photo->name) !!}',
'url': '{{ route('photos.re-analyse', ['id' => $photo->id, 'queue_token' => $queue_token]) }}' 'url': '{{ route('photos.re-analyse', ['photo' => $photo->id, 'queue_token' => $queue_token]) }}'
})); }));
@endforeach @endforeach
}); });

View File

@ -50,7 +50,7 @@
<div class="text-right"> <div class="text-right">
<a class="btn btn-link" href="{{ $album->url() }}">View album</a> <a class="btn btn-link" href="{{ $album->url() }}">View album</a>
<a class="btn btn-primary" href="{{ route('albums.show', ['id' => $album->id]) }}"><i class="fa fa-fw fa-chevron-left"></i> Back to album settings</a> <a class="btn btn-primary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-chevron-left"></i> Back to album settings</a>
</div> </div>
</div> </div>
</div> </div>
@ -69,7 +69,7 @@
app.analyseImage(new AnalyseImageViewModel({ app.analyseImage(new AnalyseImageViewModel({
'id': '{{ $photo->id }}', 'id': '{{ $photo->id }}',
'name': '{!! addslashes($photo->name) !!}', 'name': '{!! addslashes($photo->name) !!}',
'url': '{{ route('photos.analyse', ['id' => $photo->id, 'queue_token' => $queue_token]) }}' 'url': '{{ route('photos.analyse', ['photo' => $photo->id, 'queue_token' => $queue_token]) }}'
})); }));
@endforeach @endforeach
</script> </script>

View File

@ -23,7 +23,7 @@
<form action="{{ route('albums.destroy', [$album->id]) }}" method="POST"> <form action="{{ route('albums.destroy', [$album->id]) }}" method="POST">
{{ csrf_field() }} {{ csrf_field() }}
{{ method_field('DELETE') }} {{ method_field('DELETE') }}
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a> <a href="{{ route('albums.show', ['album' => $album->id]) }}" 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> <button type="submit" class="btn btn-danger"><i class="fa fa-fw fa-trash"></i> @lang('forms.delete_action')</button>
</form> </form>
</div> </div>

View File

@ -75,7 +75,7 @@
</div> </div>
<div class="text-right"> <div class="text-right">
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a> <a href="{{ route('albums.show', ['album' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button> <button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
</div> </div>
</form> </form>

View File

@ -66,11 +66,6 @@
@include(Theme::viewName('partials.admin_storages_backblaze_b2_options')) @include(Theme::viewName('partials.admin_storages_backblaze_b2_options'))
@endif @endif
@if ($storage->source == 'DropboxSource')
<hr/>
@include(Theme::viewName('partials.admin_storages_dropbox_options'))
@endif
<div class="text-right"> <div class="text-right">
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a> <a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button> <button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>

View File

@ -45,7 +45,7 @@
</td> </td>
<td> <td>
<p> <p>
{{-- TODO: edit comments <a href="{{ route('comments.edit', ['id' => $comment->id]) }}"><span style="font-size: 1.3em;">{{ $comment->name }}</span></a> --}} {{-- TODO: edit comments <a href="{{ route('comments.edit', ['comment' => $comment->id]) }}"><span style="font-size: 1.3em;">{{ $comment->name }}</span></a> --}}
<span style="font-size: 1.3em;">{{ $comment->name }}</span> <span style="font-size: 1.3em;">{{ $comment->name }}</span>
<br/> <br/>
@ -67,16 +67,16 @@
<div class="btn-toolbar pull-right"> <div class="btn-toolbar pull-right">
<div class="btn-group mr-2"> <div class="btn-group mr-2">
@if (!$comment->isModerated()) @if (!$comment->isModerated())
<a href="{{ route('comments.approve', ['id' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.approve_action')</a> <a href="{{ route('comments.approve', ['comment' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.approve_action')</a>
<a href="{{ route('comments.reject', ['id' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.reject_action')</a> <a href="{{ route('comments.reject', ['comment' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.reject_action')</a>
@elseif ($comment->isApproved()) @elseif ($comment->isApproved())
<a href="{{ route('comments.reject', ['id' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.reject_action')</a> <a href="{{ route('comments.reject', ['comment' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.reject_action')</a>
@elseif ($comment->isRejected()) @elseif ($comment->isRejected())
<a href="{{ route('comments.approve', ['id' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.approve_action')</a> <a href="{{ route('comments.approve', ['comment' => $comment->id]) }}" class="btn btn-outline-info">@lang('forms.approve_action')</a>
@endif @endif
</div> </div>
<a href="{{ route('comments.delete', ['id' => $comment->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('comments.delete', ['comment' => $comment->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -30,11 +30,11 @@
@foreach ($groups as $group) @foreach ($groups as $group)
<tr> <tr>
<td> <td>
<a href="{{ route('groups.edit', ['id' => $group->id]) }}"><span style="font-size: 1.3em;">{{ $group->name }}</span></a><br/> <a href="{{ route('groups.edit', ['group' => $group->id]) }}"><span style="font-size: 1.3em;">{{ $group->name }}</span></a><br/>
<span class="{{ $group->users()->count() == 0 ? "text-danger" : "text-success" }}">{{ trans_choice('admin.group_number_users', $group->users()->count()) }}</span> <span class="{{ $group->users()->count() == 0 ? "text-danger" : "text-success" }}">{{ trans_choice('admin.group_number_users', $group->users()->count()) }}</span>
</td> </td>
<td class="text-right"> <td class="text-right">
<a href="{{ route('groups.delete', ['id' => $group->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('groups.delete', ['group' => $group->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@ -32,20 +32,25 @@
<tr> <tr>
<td> <td>
<span style="font-size: 1.3em;"> <span style="font-size: 1.3em;">
<a href="{{ route('storage.edit', ['id' => $storage->id]) }}">{{ $storage->name }}</a> <a href="{{ route('storage.edit', ['storage' => $storage->id]) }}">{{ $storage->name }}</a>
@if ($storage->is_default) <i class="fa fa-fw fa-check text-success"></i>@endif @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 @if (!$storage->is_active) <i class="fa fa-fw fa-minus-circle text-danger"></i>@endif
</span><br/> </span><br/>
<span style="color: #888; font-style: italic;"> <span style="color: #888; font-style: italic;">
@if ($storage->source == 'LocalFilesystemSource'){{ $storage->location }}@endif @if ($storage->source == 'LocalFilesystemSource'){{ $storage->location }}@endif
@if ($storage->source == 'OpenStackSource'){{ $storage->container_name }} - {{ $storage->service_name }}, {{ $storage->service_region }}@endif @if ($storage->source == 'OpenStackSource'){{ $storage->container_name }} - {{ $storage->service_name }}, {{ $storage->service_region }}@endif
@if ($storage->source == 'AmazonS3Source'){{ $storage->container_name }} - {{ $storage->service_region }}@endif @if ($storage->source == 'AmazonS3Source')
{{ $storage->container_name }} - {{ $storage->service_region }}
@if ($storage->s3_signed_urls)
<i class="fa fa-key ml-2" data-toggle="tooltip" title="@lang('admin.storage_s3_signed_urls_tooltip')"></i>
@endif
@endif
@if ($storage->source == 'RackspaceSource'){{ $storage->container_name }} - {{ $storage->service_region }}@endif @if ($storage->source == 'RackspaceSource'){{ $storage->container_name }} - {{ $storage->service_region }}@endif
</span> </span>
</td> </td>
<td class="text-right"> <td class="text-right">
@if (!$storage->is_internal) @if (!$storage->is_internal)
<a href="{{ route('storage.delete', ['id' => $storage->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('storage.delete', ['storage' => $storage->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
@endif @endif
</td> </td>
</tr> </tr>
@ -81,3 +86,11 @@
</div> </div>
</div> </div>
@endsection @endsection
@push('scripts')
<script type="text/javascript">
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
</script>
@endpush

View File

@ -21,7 +21,7 @@
@foreach ($users as $user) @foreach ($users as $user)
<tr> <tr>
<td> <td>
<span style="font-size: 1.3em;"><a href="{{ route('users.edit', ['id' => $user->id]) }}">{{ $user->name }}</a>@if ($user->is_admin) <i class="fa fa-fw fa-cog"></i>@endif</span><br/> <span style="font-size: 1.3em;"><a href="{{ route('users.edit', ['user' => $user->id]) }}">{{ $user->name }}</a>@if ($user->is_admin) <i class="fa fa-fw fa-cog"></i>@endif</span><br/>
{{ $user->email }} <a href="mailto:{{ $user->email }}"><i class="fa fa-fw fa-mail-forward"></i></a> {{ $user->email }} <a href="mailto:{{ $user->email }}"><i class="fa fa-fw fa-mail-forward"></i></a>
</td> </td>
<td> <td>
@ -30,7 +30,7 @@
@endif @endif
</td> </td>
<td class="text-right"> <td class="text-right">
<a href="{{ route('users.delete', ['id' => $user->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('users.delete', ['user' => $user->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@ -9,7 +9,7 @@
@if ($parentAlbum->id == $album->id) @if ($parentAlbum->id == $album->id)
<li class="breadcrumb-item active">{{ $album->name }}</li> <li class="breadcrumb-item active">{{ $album->name }}</li>
@else @else
<li class="breadcrumb-item"><a href="{{ route('albums.show', ['id' => $parentAlbum->id]) }}">{{ $parentAlbum->name }}</a></li> <li class="breadcrumb-item"><a href="{{ route('albums.show', ['album' => $parentAlbum->id]) }}">{{ $parentAlbum->name }}</a></li>
@endif @endif
@endforeach @endforeach
@endsection @endsection
@ -97,12 +97,12 @@
language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}'; language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}';
var urls = []; var urls = [];
urls.analyse = '{{ route('albums.analyse', ['id' => $album->id, 'queue_token' => $queue_token]) }}'; urls.analyse = '{{ route('albums.analyse', ['album' => $album->id, 'queue_token' => $queue_token]) }}';
urls.delete_photo = '{{ route('photos.destroy', ['id' => 0]) }}'; urls.delete_photo = '{{ route('photos.destroy', ['photo' => 0]) }}';
urls.flip_photo = '{{ route('photos.flip', ['id' => 0, 'horizontal' => -1, 'vertical' => -2]) }}'; urls.flip_photo = '{{ route('photos.flip', ['photo' => 0, 'horizontal' => -1, 'vertical' => -2]) }}';
urls.move_photo = '{{ route('photos.move', ['photoId' => 0]) }}'; urls.move_photo = '{{ route('photos.move', ['photo' => 0]) }}';
urls.regenerate_thumbnails = '{{ route('photos.regenerateThumbnails', ['photoId' => 0]) }}'; urls.regenerate_thumbnails = '{{ route('photos.regenerateThumbnails', ['photo' => 0]) }}';
urls.rotate_photo = '{{ route('photos.rotate', ['id' => 0, 'angle' => 1]) }}'; urls.rotate_photo = '{{ route('photos.rotate', ['photo' => 0, 'angle' => 1]) }}';
var viewModel = new UploadPhotosViewModel('{{ $album->id }}', '{{ $queue_token }}', language, urls); var viewModel = new UploadPhotosViewModel('{{ $album->id }}', '{{ $queue_token }}', language, urls);
var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls); var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls);

View File

@ -23,7 +23,7 @@
<label for="email" class="col-md-4 col-form-label text-md-right">@lang('forms.email_label')</label> <label for="email" class="col-md-4 col-form-label text-md-right">@lang('forms.email_label')</label>
<div class="col-md-6"> <div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email or old('email') }}" autofocus> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" autofocus>
@if ($errors->has('email')) @if ($errors->has('email'))
<div class="invalid-feedback"> <div class="invalid-feedback">

View File

@ -13,7 +13,7 @@
<div class="pull-right"> <div class="pull-right">
@can('edit', $album) @can('edit', $album)
<div class="mb-3"> <div class="mb-3">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a> <a class="btn btn-secondary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
</div> </div>
@endcan @endcan

View File

@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a> <a class="btn btn-secondary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@
<div class="pull-right"> <div class="pull-right">
@can('edit', $album) @can('edit', $album)
<div class="mb-3"> <div class="mb-3">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a> <a class="btn btn-secondary" href="{{ route('albums.show', ['album' => $album->id]) }}"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
</div> </div>
@endcan @endcan

View File

@ -16,7 +16,7 @@
<p class="card-text">{!! nl2br(e($album->description)) !!}</p> <p class="card-text">{!! nl2br(e($album->description)) !!}</p>
@can('edit', $album) @can('edit', $album)
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="card-link">@lang('gallery.manage_album_link')</a> <a href="{{ route('albums.show', ['album' => $album->id]) }}" class="card-link">@lang('gallery.manage_album_link')</a>
@endcan @endcan
</div> </div>
<div class="card-footer"> <div class="card-footer">
@ -29,7 +29,7 @@
@if (UserConfig::get('social_user_profiles')) @if (UserConfig::get('social_user_profiles'))
<i class="fa fa-fw fa-user ml-2"></i> <i class="fa fa-fw fa-user ml-2"></i>
@can('view', $album->user) @if (\App\User::currentOrAnonymous()->can('view', $album->user))
<a href="{{ $album->user->profileUrl() }}">{{ $album->user->publicDisplayName() }}</a> <a href="{{ $album->user->profileUrl() }}">{{ $album->user->publicDisplayName() }}</a>
@else @else
{{ $album->user->publicDisplayName() }} {{ $album->user->publicDisplayName() }}

View File

@ -1,11 +1,11 @@
@foreach ($album->albumParentTree() as $parentAlbum) @foreach ($album->albumParentTree() as $parentAlbum)
@if ($parentAlbum->id == $album->id) @if ($parentAlbum->id == $album->id)
@if (!empty($show_current_link) && $show_current_link) @if (!empty($show_current_link) && $show_current_link)
<li class="breadcrumb-item active"><a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li> <li class="breadcrumb-item active"><a href="{{ route('albums.show', ['album' => $album->id]) }}">{{ $album->name }}</a></li>
@else @else
<li class="breadcrumb-item active">{{ $album->name }}</li> <li class="breadcrumb-item active">{{ $album->name }}</li>
@endif @endif
@else @else
<li class="breadcrumb-item"><a href="{{ route('albums.show', ['id' => $parentAlbum->id]) }}">{{ $parentAlbum->name }}</a></li> <li class="breadcrumb-item"><a href="{{ route('albums.show', ['album' => $parentAlbum->id]) }}">{{ $parentAlbum->name }}</a></li>
@endif @endif
@endforeach @endforeach

View File

@ -55,6 +55,7 @@
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="auth-url">@lang('forms.storage_auth_url_label')</label> <label class="form-control-label" for="auth-url">@lang('forms.storage_auth_url_label')</label>
<input type="text" class="form-control{{ $errors->has('auth_url') ? ' is-invalid' : '' }}" id="auth-url" name="auth_url" value="{{ old('auth_url', $storage->auth_url) }}"> <input type="text" class="form-control{{ $errors->has('auth_url') ? ' is-invalid' : '' }}" id="auth-url" name="auth_url" value="{{ old('auth_url', $storage->auth_url) }}">
<small class="form-text text-muted">@lang('admin.storage_auth_url_label_help')</small>
@if ($errors->has('auth_url')) @if ($errors->has('auth_url'))
<div class="invalid-feedback"> <div class="invalid-feedback">
@ -62,3 +63,11 @@
</div> </div>
@endif @endif
</div> </div>
<div class="form-check form-group">
<input type="checkbox" class="form-check-input" id="s3-signed-urls" name="s3_signed_urls"@if (old('s3_signed_urls', $storage->s3_signed_urls)) checked="checked"@endif>
<label for="s3-signed-urls" class="form-check-label">
<strong>@lang('forms.storage_s3_signed_urls')</strong><br>
@lang('admin.storage_s3_signed_urls_help')
</label>
</div>

View File

@ -32,7 +32,7 @@
<hr/> <hr/>
<h5 style="font-weight: bold;">@lang('admin.security_groups_heading')</h5> <h5 style="font-weight: bold;">@lang('admin.security_groups_heading')</h5>
<form action="{{ route('albums.set_group_permissions', ['id' => $album->id]) }}" method="post"> <form action="{{ route('albums.set_group_permissions', ['album' => $album->id]) }}" method="post">
{{ csrf_field() }} {{ csrf_field() }}
@if (count($existing_groups) > 0) @if (count($existing_groups) > 0)
@ -72,7 +72,7 @@
<h5 style="font-weight: bold;">@lang('admin.security_users_heading')</h5> <h5 style="font-weight: bold;">@lang('admin.security_users_heading')</h5>
<form action="{{ route('albums.set_user_permissions', ['id' => $album->id]) }}" method="post"> <form action="{{ route('albums.set_user_permissions', ['album' => $album->id]) }}" method="post">
{{ csrf_field() }} {{ csrf_field() }}
<div id="users-accordion" role="tablist" aria-multiselectable="true"> <div id="users-accordion" role="tablist" aria-multiselectable="true">

View File

@ -65,7 +65,7 @@
<div class="card-body bg-light"> <div class="card-body bg-light">
<p class="text-danger">@lang('admin.danger_zone_intro')</p> <p class="text-danger">@lang('admin.danger_zone_intro')</p>
<div> <div>
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('albums.delete', ['album' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -97,7 +97,7 @@
</div> </div>
<div class="modal-footer" v-if="!isUploadInProgress"> <div class="modal-footer" v-if="!isUploadInProgress">
<button type="button" class="btn btn-secondary" data-dismiss="modal" v-if="imagesUploaded === 0">@lang('forms.close_action')</button> <button type="button" class="btn btn-secondary" data-dismiss="modal" v-if="imagesUploaded === 0">@lang('forms.close_action')</button>
<a href="{{ route('albums.analyse', ['id' => $album->id, 'queue_token' => $queue_token]) }}" class="btn btn-primary" v-if="imagesUploaded > 0">@lang('forms.continue_action')</a> <a href="{{ route('albums.analyse', ['album' => $album->id, 'queue_token' => $queue_token]) }}" class="btn btn-primary" v-if="imagesUploaded > 0">@lang('forms.continue_action')</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
@endif @endif
@if (UserConfig::get('social_user_profiles')) @if (UserConfig::get('social_user_profiles'))
@can('view', $childAlbum->user) @if (\App\User::currentOrAnonymous()->can('view', $childAlbum->user))
<a href="{{ $childAlbum->user->profileUrl() }}" data-toggle="tooltip" data-placement="top" title="{{ $childAlbum->user->publicDisplayName() }}"><i class="fa fa-fw fa-user ml-2"></i></a> <a href="{{ $childAlbum->user->profileUrl() }}" data-toggle="tooltip" data-placement="top" title="{{ $childAlbum->user->publicDisplayName() }}"><i class="fa fa-fw fa-user ml-2"></i></a>
@else @else
<i class="fa fa-fw fa-user ml-2" data-toggle="tooltip" data-placement="top" title="{{ $childAlbum->user->publicDisplayName() }}"></i> <i class="fa fa-fw fa-user ml-2" data-toggle="tooltip" data-placement="top" title="{{ $childAlbum->user->publicDisplayName() }}"></i>

View File

@ -4,7 +4,7 @@
<td> <td>
<span style="font-size: 1.3em;"> <span style="font-size: 1.3em;">
@can('edit', $album) @can('edit', $album)
<a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a> <a href="{{ route('albums.show', ['album' => $album->id]) }}">{{ $album->name }}</a>
@endcan @endcan
@cannot('edit', $album) @cannot('edit', $album)
{{ $album->name }} <i class="fa fa-fw fa-lock"></i> {{ $album->name }} <i class="fa fa-fw fa-lock"></i>
@ -27,7 +27,7 @@
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
@if ($album->min_metadata_version < $current_metadata_version) @if ($album->min_metadata_version < $current_metadata_version)
<a href="{{ route('albums.metadata', ['id' => $album->id]) }}" class="btn btn-primary">@lang('admin.metadata_upgrade.upgrade_button')</a> <a href="{{ route('albums.metadata', ['album' => $album->id]) }}" class="btn btn-primary">@lang('admin.metadata_upgrade.upgrade_button')</a>
@endcan @endcan
</div> </div>
</td> </td>

View File

@ -7,7 +7,7 @@
<td> <td>
<span style="font-size: 1.3em;"> <span style="font-size: 1.3em;">
@can('edit', $album) @can('edit', $album)
<a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a> <a href="{{ route('albums.show', ['album' => $album->id]) }}">{{ $album->name }}</a>
@endcan @endcan
@cannot('edit', $album) @cannot('edit', $album)
{{ $album->name }} <i class="fa fa-fw fa-lock"></i> {{ $album->name }} <i class="fa fa-fw fa-lock"></i>
@ -24,10 +24,10 @@
<td class="text-right"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
@can('edit', $album) @can('edit', $album)
<a href="{{ route('albums.edit', ['id' => $album->id]) }}" class="btn btn-secondary">@lang('forms.edit_action')</a> <a href="{{ route('albums.edit', ['album' => $album->id]) }}" class="btn btn-secondary">@lang('forms.edit_action')</a>
@endcan @endcan
@can('delete', $album) @can('delete', $album)
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a> <a href="{{ route('albums.delete', ['album' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
@endcan @endcan
</div> </div>
</td> </td>

View File

@ -1 +0,0 @@

View File

@ -7,7 +7,7 @@
<table border="0" cellpadding="0" cellspacing="0"> <table border="0" cellpadding="0" cellspacing="0">
<tr> <tr>
<td> <td>
<a href="{{ $url }}" class="button button-{{ $color or 'blue' }}" target="_blank">{{ $slot }}</a> <a href="{{ $url }}" class="button button-{{ $color ?? 'primary' }}" target="_blank">{{ $slot }}</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -27,7 +27,7 @@
<tr> <tr>
<td align="center"> <td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0"> <table class="content" width="100%" cellpadding="0" cellspacing="0">
{{ $header or '' }} {{ $header ?? '' }}
<!-- Email Body --> <!-- Email Body -->
<tr> <tr>
@ -38,14 +38,14 @@
<td class="content-cell"> <td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }} {{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy or '' }} {{ $subcopy ?? '' }}
</td> </td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
{{ $footer or '' }} {{ $footer ?? '' }}
</table> </table>
</td> </td>
</tr> </tr>

View File

@ -21,7 +21,7 @@
{{-- Footer --}} {{-- Footer --}}
@slot('footer') @slot('footer')
@component('mail::footer') @component('mail::footer')
Sent using <a href="http://showmy.photos">Blue Twilight</a>. &copy; 2016-{{ date('Y') }} <a href="https://www.andyheathershaw.uk">Andy Heathershaw</a>. Sent using <a href="https://showmy.photos">Blue Twilight</a>. &copy; 2016-{{ date('Y') }} <a href="https://andysh.uk">Andy Heathershaw</a>.
@endcomponent @endcomponent
@endslot @endslot
@endcomponent @endcomponent

View File

@ -182,6 +182,7 @@ img {
.table th { .table th {
border-bottom: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2;
padding-bottom: 8px; padding-bottom: 8px;
margin: 0;
} }
.table td { .table td {
@ -189,6 +190,7 @@ img {
font-size: 15px; font-size: 15px;
line-height: 18px; line-height: 18px;
padding: 10px 0; padding: 10px 0;
margin: 0;
} }
.content-cell { .content-cell {
@ -216,7 +218,8 @@ img {
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
} }
.button-blue { .button-blue,
.button-primary {
background-color: #3097D1; background-color: #3097D1;
border-top: 10px solid #3097D1; border-top: 10px solid #3097D1;
border-right: 18px solid #3097D1; border-right: 18px solid #3097D1;
@ -224,7 +227,8 @@ img {
border-left: 18px solid #3097D1; border-left: 18px solid #3097D1;
} }
.button-green { .button-green,
.button-success {
background-color: #2ab27b; background-color: #2ab27b;
border-top: 10px solid #2ab27b; border-top: 10px solid #2ab27b;
border-right: 18px solid #2ab27b; border-right: 18px solid #2ab27b;
@ -232,7 +236,8 @@ img {
border-left: 18px solid #2ab27b; border-left: 18px solid #2ab27b;
} }
.button-red { .button-red,
.button-error {
background-color: #bf5329; background-color: #bf5329;
border-top: 10px solid #bf5329; border-top: 10px solid #bf5329;
border-right: 18px solid #bf5329; border-right: 18px solid #bf5329;

View File

@ -21,7 +21,7 @@
{{-- Footer --}} {{-- Footer --}}
@slot('footer') @slot('footer')
@component('mail::footer') @component('mail::footer')
Sent using [Blue Twilight](http://showmy.photos). © 2016-{{ date('Y') }} [Andy Heathershaw](https://www.andyheathershaw.uk). Sent using [Blue Twilight](https://showmy.photos). © 2016-{{ date('Y') }} [Andy Heathershaw](https://andysh.uk).
@endcomponent @endcomponent
@endslot @endslot
@endcomponent @endcomponent

View File

@ -30,51 +30,51 @@ Route::group(['prefix' => 'admin'], function () {
Route::get('albums/default-permissions', 'Admin\AlbumController@defaultPermissions')->name('albums.defaultPermissions'); Route::get('albums/default-permissions', 'Admin\AlbumController@defaultPermissions')->name('albums.defaultPermissions');
Route::post('albums/set-default-group-permissions', 'Admin\AlbumController@setDefaultGroupPermissions')->name('albums.set_default_group_permissions'); Route::post('albums/set-default-group-permissions', 'Admin\AlbumController@setDefaultGroupPermissions')->name('albums.set_default_group_permissions');
Route::post('albums/set-default-user-permissions', 'Admin\AlbumController@setDefaultUserPermissions')->name('albums.set_default_user_permissions'); Route::post('albums/set-default-user-permissions', 'Admin\AlbumController@setDefaultUserPermissions')->name('albums.set_default_user_permissions');
Route::get('albums/{id}/analyse/{queue_token}', 'Admin\AlbumController@analyse')->name('albums.analyse'); Route::get('albums/{album}/analyse/{queue_token}', 'Admin\AlbumController@analyse')->name('albums.analyse');
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete'); Route::get('albums/{album}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
Route::get('/albums/{id}/metadata', 'Admin\AlbumController@metadata')->name('albums.metadata'); Route::get('/albums/{album}/metadata', 'Admin\AlbumController@metadata')->name('albums.metadata');
Route::post('albums/{id}/set-group-permissions', 'Admin\AlbumController@setGroupPermissions')->name('albums.set_group_permissions'); Route::post('albums/{album}/set-group-permissions', 'Admin\AlbumController@setGroupPermissions')->name('albums.set_group_permissions');
Route::post('albums/{id}/set-user-permissions', 'Admin\AlbumController@setUserPermissions')->name('albums.set_user_permissions'); Route::post('albums/{album}/set-user-permissions', 'Admin\AlbumController@setUserPermissions')->name('albums.set_user_permissions');
Route::delete('albums/{id}/delete-redirect/{redirectId}', 'Admin\AlbumController@deleteRedirect')->name('albums.delete_redirect'); Route::delete('albums/{album}/delete-redirect/{redirectId}', 'Admin\AlbumController@deleteRedirect')->name('albums.delete_redirect');
Route::post('albums/{id}/store-redirect', 'Admin\AlbumController@storeRedirect')->name('albums.store_redirect'); Route::post('albums/{album}/store-redirect', 'Admin\AlbumController@storeRedirect')->name('albums.store_redirect');
Route::resource('albums', 'Admin\AlbumController'); Route::resource('albums', 'Admin\AlbumController');
// Photo management // Photo management
Route::post('photos/analyse/{id}/{queue_token}', 'Admin\PhotoController@analyse')->name('photos.analyse'); Route::post('photos/analyse/{photo}/{queue_token}', 'Admin\PhotoController@analyse')->name('photos.analyse');
Route::post('photos/flip/{photoId}/{horizontal}/{vertical}', 'Admin\PhotoController@flip')->name('photos.flip'); Route::post('photos/flip/{photo}/{horizontal}/{vertical}', 'Admin\PhotoController@flip')->name('photos.flip');
Route::post('photos/move/{photoId}', 'Admin\PhotoController@move')->name('photos.move'); Route::post('photos/move/{photo}', 'Admin\PhotoController@move')->name('photos.move');
Route::post('photos/reanalyse/{id}/{queue_token}', 'Admin\PhotoController@reAnalyse')->name('photos.re-analyse'); Route::post('photos/reanalyse/{photo}/{queue_token}', 'Admin\PhotoController@reAnalyse')->name('photos.re-analyse');
Route::post('photos/regenerate-thumbnails/{photoId}', 'Admin\PhotoController@regenerateThumbnails')->name('photos.regenerateThumbnails'); Route::post('photos/regenerate-thumbnails/{photo}', 'Admin\PhotoController@regenerateThumbnails')->name('photos.regenerateThumbnails');
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate'); Route::post('photos/rotate/{photo}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk'); Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
Route::put('photos/update-bulk/{albumId}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk'); Route::put('photos/update-bulk/{photo}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk');
Route::resource('photos', 'Admin\PhotoController'); Route::resource('photos', 'Admin\PhotoController');
// Label management // Label management
Route::get('labels/{id}/delete', 'Admin\LabelController@delete')->name('labels.delete'); Route::get('labels/{label}/delete', 'Admin\LabelController@delete')->name('labels.delete');
Route::resource('labels', 'Admin\LabelController'); Route::resource('labels', 'Admin\LabelController');
// Storage management // Storage management
Route::get('storage/{id}/delete', 'Admin\StorageController@delete')->name('storage.delete'); Route::get('storage/{storage}/delete', 'Admin\StorageController@delete')->name('storage.delete');
Route::resource('storage', 'Admin\StorageController'); Route::resource('storage', 'Admin\StorageController');
// User management // User management
Route::get('users/{id}/delete', 'Admin\UserController@delete')->name('users.delete'); Route::get('users/{user}/delete', 'Admin\UserController@delete')->name('users.delete');
Route::get('users.json', 'Admin\UserController@searchJson')->name('users.searchJson'); Route::get('users.json', 'Admin\UserController@searchJson')->name('users.searchJson');
Route::resource('users', 'Admin\UserController'); Route::resource('users', 'Admin\UserController');
// Group management // Group management
Route::get('groups/{id}/delete', 'Admin\GroupController@delete')->name('groups.delete'); Route::get('groups/{group}/delete', 'Admin\GroupController@delete')->name('groups.delete');
Route::resource('groups', 'Admin\GroupController'); Route::resource('groups', 'Admin\GroupController');
// Comments management // Comments management
Route::get('comments/{id}/approve', 'Admin\PhotoCommentController@approve')->name('comments.approve'); Route::get('comments/{comment}/approve', 'Admin\PhotoCommentController@approve')->name('comments.approve');
Route::post('comments/{id}/approve', 'Admin\PhotoCommentController@confirmApprove')->name('comments.confirmApprove'); Route::post('comments/{comment}/approve', 'Admin\PhotoCommentController@confirmApprove')->name('comments.confirmApprove');
Route::get('comments/{id}/reject', 'Admin\PhotoCommentController@reject')->name('comments.reject'); Route::get('comments/{comment}/reject', 'Admin\PhotoCommentController@reject')->name('comments.reject');
Route::post('comments/{id}/reject', 'Admin\PhotoCommentController@confirmReject')->name('comments.confirmReject'); Route::post('comments/{comment}/reject', 'Admin\PhotoCommentController@confirmReject')->name('comments.confirmReject');
Route::get('comments/{id}/delete', 'Admin\PhotoCommentController@delete')->name('comments.delete'); Route::get('comments/{comment}/delete', 'Admin\PhotoCommentController@delete')->name('comments.delete');
Route::post('comments/apply-bulk-action', 'Admin\PhotoCommentController@applyBulkAction')->name('comments.applyBulkAction'); Route::post('comments/apply-bulk-action', 'Admin\PhotoCommentController@applyBulkAction')->name('comments.applyBulkAction');
Route::post('comments/bulk-action', 'Admin\PhotoCommentController@bulkAction')->name('comments.bulkAction'); Route::post('comments/bulk-action', 'Admin\PhotoCommentController@bulkAction')->name('comments.bulkAction');

View File

@ -1,2 +1,3 @@
* *
!data/
!.gitignore !.gitignore

View File

@ -0,0 +1,2 @@
*
!.gitignore