Compare commits

...

45 Commits

Author SHA1 Message Date
Andy Heathershaw fa861c0b09 #86: Updated more references from Github to Gitea (Admin > About page) 2018-08-16 08:46:19 +01:00
Andy Heathershaw 0836ca5557 #86: Switched the update check from Github to Gitea 2018-07-29 22:07:55 +01:00
Andy Heathershaw c029c6ca00 Bumped version for the 2.1.2 release 2018-07-28 09:00:57 +01:00
Andy Heathershaw aa2998ac70 #85: Changed the way next/previous buttons work, and introduced a more consistent ordering when large numbers of photos were uploaded at the same time 2018-07-28 09:00:23 +01:00
Andy Heathershaw eedfd5abdd #84: Corrected permissions query for a non-admin user returning incorrect child albums 2018-07-28 09:00:18 +01:00
Andy Heathershaw 9a65e8f1c9 Bumped version number for 2.1.1 2018-07-16 03:31:35 +01:00
Andy Heathershaw 9ea1953ada #79: Corrected validation errors on the login screen 2018-07-15 21:57:15 +01:00
Andy Heathershaw 8dd31961e7 #79: Updated the checkbox on the statistics page for Bootstrap 4 final 2018-07-15 21:45:39 +01:00
Andy Heathershaw c784c623ba Include all update steps in update.php 2018-07-15 21:34:15 +01:00
Andy Heathershaw 9064495f1f Updated the Github URL to andysh-uk. Update script now downloads Composer, like the install script does. 2018-07-15 21:32:17 +01:00
Andy Heathershaw e2d66fd228 Merge branch 'master' into v2.1 2018-07-14 08:22:38 +01:00
Andy Heathershaw 9740582b6e #73: Updated the message when a metadata-update fails so it doesn't say it's removing the photo 2018-07-14 08:15:19 +01:00
Andy Heathershaw d63423bc47 Bumped version number ready for the beta release 2018-07-13 08:12:38 +01:00
Andy Heathershaw 2571675b24
Bumped version number ready for the beta release 2018-07-13 07:51:10 +01:00
Andy Heathershaw 6040c7d4ef #65: Don't start uploading if no file was selected 2018-07-13 00:00:45 +01:00
Andy Heathershaw 4b7b99431f #79: Upgraded Bootstrap to 4.1.2. Number of HTML mark-up changes following the BS upgrade. 2018-07-12 23:46:59 +01:00
Andy Heathershaw ab690b1e25 #68: Reworked the upload progress modal to use modal-footer correctly 2018-07-12 23:18:40 +01:00
Andy Heathershaw 393cc590c1 #68: Added a "Close" button when an single file into an upload fails 2018-07-12 23:08:39 +01:00
Andy Heathershaw ef4df1ab32 #59: Added 2 new settings to customise the albums drop-down navigator. It is now possible to choose to only display top-level albums, and also to restrict the number of items. 2018-07-12 22:52:50 +01:00
Andy Heathershaw f96a9cd9f7 #58: It is now possible to create albums named the same within different parent albums. Albums with child albums can now not be deleted, as this could leave duplicate albums in the same parent album. 2018-07-12 21:55:01 +01:00
Andy Heathershaw 790d354167 #72: When counting albums in the admin stats widget, count all, not just the current page 2018-07-12 06:42:57 +01:00
Andy Heathershaw 036814705f #74: Suppress warning on mkdir due to a race condition for multiple uploads 2018-07-12 06:38:56 +01:00
Andy Heathershaw 534c8f6090 #75: Reworked the way metadata is calculated so empty albums are not displayed as upgradable. Also improved the "no albums" message, as it's no longer accurate. 2018-07-12 06:35:08 +01:00
Andy Heathershaw 309d97cb75 #77: Minor improvements to meta-data update page 2018-07-12 06:09:28 +01:00
Andy Heathershaw 06869a157c #68: Updated footer links to andysh.uk 2018-07-12 06:05:29 +01:00
Andy Heathershaw f007371a79 Merge remote-tracking branch 'origin/master' 2018-07-12 05:59:29 +01:00
Andy Heathershaw cb6ae98907 #62: Found another reference to the global $albums variable, now $g_albums 2018-07-12 05:58:40 +01:00
Andy Heathershaw 189aafe61c
#62: another $albums reference 2018-07-11 08:49:43 +01:00
Andy Heathershaw efdcecfca6
#62: Further correction to $albums variable 2018-07-11 08:45:40 +01:00
Andy Heathershaw cc3370c4b1 #62: Don't clobber the $albums variables in the navbar, so it now shows all albums not just the single page you're currently viewing 2018-07-11 08:00:40 +01:00
Andy Heathershaw 04d1e59778 #61: Album breadcrumbs in admin panel now include full path of parent albums 2018-07-11 07:52:59 +01:00
Andy Heathershaw 2bbaa81ffe Added more apps to the readme file 2018-02-10 16:35:52 +00:00
Andy Heathershaw f3f5e5b615 #60: Slight tweak to the layout of the About page 2017-10-01 21:15:47 +01:00
Andy Heathershaw adb86f1d4e #60: Added the new version information when an update is available 2017-10-01 17:03:19 +01:00
Andy Heathershaw dcfcbca530 #60: Added a basic about page with a link to Github's API to fetch the latest release 2017-10-01 16:48:50 +01:00
Andy Heathershaw 89544437cd Bumped version number in readiness for the final 2.1.0 release 2017-09-30 15:17:23 +01:00
Andy Heathershaw f38911f79e Committed version change for v2.1.0-beta.4 2017-09-30 15:08:24 +01:00
Andy Heathershaw 4326bc427c #52: Removed the Bootstrap 4 custom file inputs as they don't show the filename correctly - and I can't get the 'change' event to fire to change manually. This works fine with a native file input. 2017-09-30 08:34:04 +01:00
Andy Heathershaw a6303410cf #48: Added a check to the Composer install if Composer cannot be used due to allow_url_fopen being disabled 2017-09-30 08:14:21 +01:00
Andy Heathershaw c5e22c7a6e #50: Added a check to see if exec() is available to provide more OS-level information, or falls back to standard php_uname if not 2017-09-29 20:15:24 +01:00
Andy Heathershaw 435e47af17 #56: Stop the Open Album/Manage Album shortcuts opening in a new tab 2017-09-29 19:40:52 +01:00
Andy Heathershaw d575560209 #54, #55: Number of corrections to child albums behaviour. The count of child albums is now displayed in the gallery next to the "X photos" text. Child albums are no longer displayed if the user does not have permissions. 2017-09-29 13:57:45 +01:00
Andy Heathershaw cef1ea63cf #49: Corrected instances of undocumented method set() which has now been removed in Laravel 5.4 in favour if put() 2017-09-29 12:54:55 +01:00
Andy Heathershaw 1618ae64c0 #51: Run the database seeders during a clean install as well as during an update 2017-09-29 12:53:49 +01:00
Andy Heathershaw 150f0a4966 #38: Modified the way the metadata upgrade page works - which now does a "re-analyse" the same way as it does an "analyse" 2017-09-17 16:04:07 +01:00
86 changed files with 23002 additions and 13110 deletions

View File

@ -195,12 +195,28 @@ class Album extends Model
return $this->getAlbumSource()->getUrlToPhoto($photo, $thumbnailName);
}
// Rotate standard images
$images = [
asset('themes/base/images/empty-album-1.jpg'),
asset('themes/base/images/empty-album-2.jpg'),
asset('themes/base/images/empty-album-3.jpg')
];
// See if any child albums have an image
$images = [];
/** @var Album $childAlbum */
foreach ($this->children as $childAlbum)
{
if ($childAlbum->photos()->count() > 0)
{
$images[] = $childAlbum->thumbnailUrl($thumbnailName);
}
}
if (count($images) == 0)
{
// Rotate standard images
$images = [
asset('themes/base/images/empty-album-1.jpg'),
asset('themes/base/images/empty-album-2.jpg'),
asset('themes/base/images/empty-album-3.jpg')
];
}
return $images[rand(0, count($images) - 1)];
}

View File

@ -87,6 +87,8 @@ class ConfigHelper
$currentAppName = $this->get('app_name', false);
return array(
'albums_menu_parents_only' => false,
'albums_menu_number_items' => 10,
'allow_self_registration' => true,
'analytics_code' => '',
'app_name' => trans('global.app_name'),

View File

@ -33,17 +33,12 @@ class DbHelper
public static function getAlbumsForCurrentUser($parentID = -1)
{
$query = self::getAlbumsForCurrentUser_NonPaged();
if ($parentID == 0)
{
$query = $query->where('albums.parent_album_id', null);
}
$query = self::getAlbumsForCurrentUser_NonPaged('list', $parentID);
return $query->paginate(UserConfig::get('items_per_page'));
}
public static function getAlbumsForCurrentUser_NonPaged($permission = 'list')
public static function getAlbumsForCurrentUser_NonPaged($permission = 'list', $parentAlbumID = -1)
{
$albumsQuery = Album::query();
$user = Auth::user();
@ -75,17 +70,30 @@ class DbHelper
->leftJoin('permissions AS group_permissions', 'group_permissions.id', '=', 'album_group_permissions.permission_id')
->leftJoin('permissions AS user_permissions', 'user_permissions.id', '=', 'album_user_permissions.permission_id')
->leftJoin('user_groups', 'user_groups.group_id', '=', 'album_group_permissions.group_id')
->where('albums.user_id', $user->id)
->orWhere([
['group_permissions.section', 'album'],
['group_permissions.description', $permission],
['user_groups.user_id', $user->id]
])
->orWhere([
['user_permissions.section', 'album'],
['user_permissions.description', $permission],
['album_user_permissions.user_id', $user->id]
]);
->where(function($query) use ($user, $permission)
{
$query->where('albums.user_id', $user->id)
->orWhere([
['group_permissions.section', 'album'],
['group_permissions.description', $permission],
['user_groups.user_id', $user->id]
])
->orWhere([
['user_permissions.section', 'album'],
['user_permissions.description', $permission],
['album_user_permissions.user_id', $user->id]
]);
});
}
$parentAlbumID = intval($parentAlbumID);
if ($parentAlbumID == 0)
{
$albumsQuery->where('albums.parent_album_id', null);
}
else if ($parentAlbumID > 0)
{
$albumsQuery->where('albums.parent_album_id', $parentAlbumID);
}
return $albumsQuery->select('albums.*')
@ -103,4 +111,14 @@ class DbHelper
{
return Album::where('url_path', $urlPath)->first();
}
public static function getChildAlbumsCount(Album $album)
{
return self::getAlbumsForCurrentUser_NonPaged('list', $album->id)->count();
}
public static function getChildAlbums(Album $album)
{
return self::getAlbumsForCurrentUser_NonPaged('list', $album->id)->get();
}
}

View File

@ -46,7 +46,7 @@ class FileHelper
if (!file_exists($path))
{
mkdir($path, 0755, true);
@mkdir($path, 0755, true);
}
return $path;

View File

@ -124,6 +124,12 @@ class MiscHelper
return MiscHelper::getEnvironmentSetting('APP_INSTALLED');
}
public static function isExecEnabled()
{
$disabled = explode(',', ini_get('disable_functions'));
return !in_array('exec', $disabled);
}
/**
* Tests whether the provided URL belongs to the current application (i.e. both scheme and hostname match.)
* @param $url

View File

@ -2,8 +2,37 @@
namespace App\Helpers;
use Illuminate\Support\Facades\DB;
class ValidationHelper
{
public function albumPathUnique($attribute, $value, $parameters, $validator)
{
$data = $validator->getData();
$parentID = intval($data['parent_album_id']);
$name = $data['name'];
if ($parentID === 0)
{
$parentID = null;
}
$queryParams = [
['name', $name],
['parent_album_id', $parentID]
];
if (count($parameters) > 0)
{
$existingAlbumID = intval($parameters[0]);
$queryParams[] = ['id', '<>', $existingAlbumID];
}
$count = DB::table('albums')->where($queryParams)->count();
return ($count == 0);
}
public function directoryExists($attribute, $value, $parameters, $validator)
{
return file_exists($value) && is_dir($value);

View File

@ -122,6 +122,12 @@ class AlbumController extends Controller
$album = $this->loadAlbum($id, 'delete');
if ($album->children()->count() > 0)
{
$request->session()->flash('error', trans('admin.delete_album_failed_children', ['album' => $album->name]));
return redirect(route('albums.index'));
}
// Delete all the photo files
/** @var Photo $photo */
foreach ($album->photos as $photo)
@ -174,6 +180,10 @@ class AlbumController extends Controller
// Only get top-level albums
$albums = DbHelper::getAlbumsForCurrentUser(0);
foreach ($albums as $album)
{
$this->loadChildAlbums($album);
}
return Theme::render('admin.list_albums', [
'albums' => $albums,
@ -194,39 +204,14 @@ class AlbumController extends Controller
/** @var Album $album */
$album = $this->loadAlbum($id);
return Theme::render('admin.album_metadata', ['album' => $album, 'current_metadata' => PhotoService::METADATA_VERSION]);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function metadataPost(Request $request, $id)
{
$this->authorizeAccessToAdminPanel('admin:manage-albums');
/** @var Album $album */
$album = $this->loadAlbum($id);
$photosNeededToUpdate = $album->photos()->where('metadata_version', '<', PhotoService::METADATA_VERSION)->get();
$queueToken = MiscHelper::randomString();
// First download the original of each photo that needs updating and mark it as needing analysis
foreach ($photosNeededToUpdate as $photo)
{
/** @var Photo $photo */
$photo->is_analysed = false;
$photoService = new PhotoService($photo);
$photoService->downloadOriginalToFolder(FileHelper::getQueuePath($queueToken));
$photo->save();
}
// Now redirect to the analysis page
return response()->redirectToRoute('albums.analyse', ['id' => $id, 'queue_token' => $queueToken]);
return Theme::render('admin.album_metadata', [
'album' => $album,
'current_metadata' => PhotoService::METADATA_VERSION,
'photos' => $photosNeededToUpdate,
'queue_token' => MiscHelper::randomString()
]);
}
public function setGroupPermissions(Request $request, $id)
@ -585,4 +570,13 @@ class AlbumController extends Controller
return $album;
}
private function loadChildAlbums(Album $album)
{
$album->child_albums = DbHelper::getChildAlbums($album);
foreach ($album->child_albums as $childAlbum)
{
$this->loadChildAlbums($childAlbum);
}
}
}

View File

@ -15,6 +15,8 @@ use App\Http\Requests\SaveSettingsRequest;
use App\Label;
use App\Mail\TestMailConfig;
use App\Photo;
use App\Services\GiteaService;
use App\Services\GithubService;
use App\Services\PhotoService;
use App\Storage;
use App\User;
@ -33,9 +35,50 @@ class DefaultController extends Controller
View::share('is_admin', true);
}
public function about()
{
return Theme::render('admin.about', [
'current_version' => config('app.version'),
'licence_text' => file_get_contents(sprintf('%s/LICENSE', dirname(dirname(dirname(dirname(__DIR__))))))
]);
}
public function aboutLatestRelease()
{
try
{
$giteaService = new GiteaService();
$releaseInfo = $giteaService->checkForLatestRelease();
// Convert the publish date so we can re-format it with the user's settings
$publishDate = \DateTime::createFromFormat('Y-m-d\TH:i:sP', $releaseInfo->published_at);
// HTML-ify the body text
$body = nl2br($releaseInfo->body);
$body = preg_replace('/\*\*(.+)\*\*/', '<b>$1</b>', $body);
// Remove the "v" from the release name
$version = substr($releaseInfo->tag_name, 1);
// Determine if we can upgrade
$canUpgrade = version_compare($version, config('app.version')) > 0;
return response()->json([
'can_upgrade' => $canUpgrade,
'body' => $body,
'name' => $version,
'publish_date' => $publishDate->format(UserConfig::get('date_format')),
'url' => $releaseInfo->html_url
]);
}
catch (\Exception $ex)
{
return response()->json(['error' => $ex->getMessage()]);
}
}
public function metadataUpgrade()
{
$albums = DbHelper::getAlbumsForCurrentUser();
$albumIDs = DbHelper::getAlbumIDsForCurrentUser();
$photoMetadata = DB::table('photos')
@ -47,14 +90,28 @@ class DefaultController extends Controller
->groupBy('album_id')
->get();
$resultingAlbumIDs = [];
foreach ($photoMetadata as $metadata)
{
/** @var Album $album */
if (isset($metadata->min_metadata_version) && $metadata->min_metadata_version > 0)
{
$resultingAlbumIDs[$metadata->album_id] = $metadata->min_metadata_version;
}
}
// Now load the full album definitions
$albumsQuery = DbHelper::getAlbumsForCurrentUser_NonPaged();
$albumsQuery->whereIn('id', array_keys($resultingAlbumIDs));
$albums = $albumsQuery->paginate(UserConfig::get('items_per_page'));
/** @var Album $album */
foreach ($resultingAlbumIDs as $albumID => $metadataMinVersion)
{
foreach ($albums as $album)
{
if ($album->id == $metadata->album_id)
if ($album->id == $albumID)
{
$album->min_metadata_version = $metadata->min_metadata_version;
$album->min_metadata_version = $metadataMinVersion;
}
}
}
@ -69,13 +126,23 @@ class DefaultController extends Controller
{
$this->authorizeAccessToAdminPanel();
$albumCount = DbHelper::getAlbumsForCurrentUser()->count();
$albumCount = count(DbHelper::getAlbumIDsForCurrentUser());
$photoCount = Photo::all()->count();
$groupCount = Group::all()->count();
$labelCount = Label::all()->count();
$userCount = User::where('is_activated', true)->count();
$metadataUpgradeNeeded = Photo::min('metadata_version') < PhotoService::METADATA_VERSION;
$minMetadataVersion = Photo::min('metadata_version');
$metadataUpgradeNeeded = $minMetadataVersion > 0 && $minMetadataVersion < PhotoService::METADATA_VERSION;
// Default to a supported function call to get the OS version
$osVersion = sprintf('%s %s', php_uname('s'), php_uname('r'));
// If the exec() function is enabled, we can do a bit better
if (MiscHelper::isExecEnabled())
{
$osVersion = exec('lsb_release -ds 2>/dev/null || cat /etc/*release 2>/dev/null | head -n1 || uname -om');
}
return Theme::render('admin.index', [
'album_count' => $albumCount,
@ -86,7 +153,7 @@ class DefaultController extends Controller
'metadata_upgrade_needed' => $metadataUpgradeNeeded,
'photo_count' => $photoCount,
'php_version' => phpversion(),
'os_version' => exec('lsb_release -ds 2>/dev/null || cat /etc/*release 2>/dev/null | head -n1 || uname -om'),
'os_version' => $osVersion,
'server_name' => gethostname(),
'upload_file_size' => ini_get('upload_max_filesize'),
'upload_max_limit' => ini_get('post_max_size'),
@ -148,6 +215,7 @@ class DefaultController extends Controller
'smtp_password'
];
$checkboxKeys = [
'albums_menu_parents_only',
'allow_self_registration',
'enable_visitor_hits',
'hotlink_protection',
@ -158,6 +226,7 @@ class DefaultController extends Controller
'smtp_encryption',
];
$updateKeys = [
'albums_menu_number_items',
'app_name',
'date_format',
'sender_address',

View File

@ -60,10 +60,7 @@ class PhotoController extends Controller
$result['message'] = $ex->getMessage();
// Remove the photo if it cannot be analysed (only if there isn't currently a version of metadata)
if (is_null($photo->metadata_version))
{
$photo->delete();
}
$photo->delete();
}
return response()->json($result);
@ -147,6 +144,33 @@ class PhotoController extends Controller
}
}
public function reAnalyse($id, $queue_token)
{
$this->authorizeAccessToAdminPanel();
/** @var Photo $photo */
$photo = $this->loadPhoto($id);
$result = ['is_successful' => false, 'message' => ''];
try
{
$photoService = new PhotoService($photo);
$photoService->downloadOriginalToFolder(FileHelper::getQueuePath($queue_token));
$photoService->analyse($queue_token);
$result['is_successful'] = true;
}
catch (\Exception $ex)
{
$result['is_successful'] = false;
$result['message'] = $ex->getMessage();
// Unlike the analyse method, we don't remove the photo if it cannot be analysed
}
return response()->json($result);
}
public function regenerateThumbnails($photoId)
{
$this->authorizeAccessToAdminPanel();
@ -272,6 +296,12 @@ class PhotoController extends Controller
// Load the linked album
$album = $this->loadAlbum($request->get('album_id'));
if (is_null($request->files->get('archive')))
{
$request->session()->flash('error', trans('admin.upload_bulk_no_file'));
return redirect(route('albums.show', ['id' => $album->id]));
}
$archiveFile = UploadedFile::createFromBase($request->files->get('archive'));
if ($archiveFile->getError() != UPLOAD_ERR_OK)
{

View File

@ -71,20 +71,28 @@ class AlbumController extends Controller
else if ($requestedView != 'slideshow')
{
$photos = $album->photos()
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
->orderBy(DB::raw('COALESCE(taken_at, created_at), name, id'))
->paginate(UserConfig::get('items_per_page'));
}
else
{
// The slideshow view needs access to all photos, not paged
$photos = $album->photos()
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
->orderBy(DB::raw('COALESCE(taken_at, created_at), name, id'))
->get();
}
// Load child albums and their available children
$childAlbums = DbHelper::getChildAlbums($album);
foreach ($childAlbums as $childAlbum)
{
$childAlbum->children_count = DbHelper::getChildAlbumsCount($childAlbum);
}
return Theme::render(sprintf('gallery.album_%s', $requestedView), [
'album' => $album,
'allowed_views' => $validViews,
'child_albums' => $childAlbums,
'current_view' => $requestedView,
'photos' => $photos
]);

View File

@ -19,6 +19,13 @@ class DefaultController extends Controller
public function index(Request $request)
{
$albums = DbHelper::getAlbumsForCurrentUser(0);
/** @var Album $album */
foreach ($albums as $album)
{
$album->children_count = DbHelper::getChildAlbumsCount($album);
}
$resetStatus = $request->session()->get('status');
// Record the visit to the index (no album or photo to record a hit against though)

View File

@ -105,14 +105,40 @@ class PhotoController extends Controller
// Load the Next/Previous buttons
$thisPhotoDate = is_null($photo->taken_at) ? $photo->created_at : $photo->taken_at;
$previousPhoto = $album->photos()
->where(DB::raw('COALESCE(taken_at, created_at)'), '<', $thisPhotoDate)
->orderBy(DB::raw('COALESCE(taken_at, created_at)'), 'desc')
->first();
$nextPhoto = $album->photos()
->where(DB::raw('COALESCE(taken_at, created_at)'), '>', $thisPhotoDate)
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
->first();
// I don't like the idea of using a totally raw SQL query, but it's the only sure-fire way to number the rows
// so we can get the previous/next photos accurately - and we don't have to load all data for the photo objects
$previousPhoto = null;
$nextPhoto = null;
$allAlbumPhotos = DB::select(
DB::raw(
'SELECT p.id, (@row_number:=@row_number + 1) AS row_number
FROM photos p, (SELECT @row_number:=0) AS t
WHERE p.album_id = :album_id
ORDER BY COALESCE(p.taken_at, p.created_at), p.name, p.id;'
),
[
'album_id' => $album->id
]
);
for ($i = 0; $i < count($allAlbumPhotos); $i++)
{
if ($allAlbumPhotos[$i]->id === $photo->id)
{
if ($i > 0)
{
$previousPhoto = Photo::where('id', $allAlbumPhotos[$i - 1]->id)->first();
}
if ($i + 1 < count($allAlbumPhotos))
{
$nextPhoto = Photo::where('id', $allAlbumPhotos[$i + 1]->id)->first();
}
break;
}
}
// Record the visit to the photo
if (UserConfig::get('enable_visitor_hits'))

View File

@ -55,7 +55,7 @@ class InstallController extends Controller
if ($request->getMethod() == 'POST')
{
$request->session()->set('install_stage', 2);
$request->session()->put('install_stage', 2);
return redirect(route('install.database'));
}
@ -150,12 +150,13 @@ class InstallController extends Controller
Artisan::call('cache:clear');
Artisan::call('migrate', ['--force' => true]);
Artisan::call('db:seed', ['--force' => true]);
$versionNumber = UserConfig::getOrCreateModel('app_version');
$versionNumber->value = config('app.version');
$versionNumber->save();
$request->session()->set('install_stage', 3);
$request->session()->put('install_stage', 3);
return redirect(route('install.administrator'));
}
catch (\Exception $ex)

View File

@ -19,6 +19,7 @@ class CheckMaxPostSizeExceeded
protected $exclude = [
'/admin/photos/analyse/*',
'/admin/photos/flip/*',
'/admin/photos/reanalyse/*',
'/admin/photos/regenerate-thumbnails/*',
'/admin/photos/rotate/*'
];

View File

@ -63,7 +63,25 @@ class GlobalConfiguration
private function addAlbumsToView()
{
$albums = DbHelper::getAlbumsForCurrentUser_NonPaged()->get();
View::share('albums', $albums);
View::share('g_albums', $albums);
if (UserConfig::get('albums_menu_parents_only'))
{
// Only show top-level albums in the nav bar
$navbarAlbums = $albums->filter(function($value, $key)
{
return is_null($value->parent_album_id);
});
}
else
{
// If not just showing top-level albums, we can show all
$navbarAlbums = $albums;
}
$navbarAlbumsToDisplay = UserConfig::get('albums_menu_number_items');
View::share('g_albums_menu', $navbarAlbums->take($navbarAlbumsToDisplay));
View::share('g_more_albums', $navbarAlbums->count() - $navbarAlbumsToDisplay);
$albumsToUpload = DbHelper::getAlbumsForCurrentUser_NonPaged('upload-photos')->get();
View::share('g_albums_upload', $albumsToUpload);

View File

@ -24,10 +24,11 @@ class SaveSettingsRequest extends FormRequest
public function rules()
{
return [
'albums_menu_number_items' => 'required|integer|min:1',
'app_name' => 'required|max:255',
'date_format' => 'required',
'smtp_server' => 'required',
'smtp_port' => 'required:integer'
'smtp_port' => 'required|integer'
];
}
}

View File

@ -28,7 +28,7 @@ class StoreAlbumRequest extends FormRequest
case 'POST':
return [
'description' => '',
'name' => 'required|unique:albums|max:255',
'name' => 'required|album_path_unique|max:255',
'storage_id' => 'required|sometimes'
];
@ -38,7 +38,7 @@ class StoreAlbumRequest extends FormRequest
return [
'description' => 'sometimes',
'name' => 'required|sometimes|max:255|unique:albums,name,' . $albumId,
'name' => 'required|sometimes|max:255|album_path_unique:' . $albumId,
'storage_id' => 'required|sometimes'
];
}

View File

@ -50,6 +50,7 @@ class AppServiceProvider extends ServiceProvider
Validator::extend('is_dir', (ValidationHelper::class . '@directoryExists'));
Validator::extend('dir_empty', (ValidationHelper::class . '@isDirectoryEmpty'));
Validator::extend('is_writeable', (ValidationHelper::class . '@isPathWriteable'));
Validator::extend('album_path_unique', (ValidationHelper::class . '@albumPathUnique'));
// Model observers
Album::observe(AlbumObserver::class);

View File

@ -0,0 +1,112 @@
<?php
namespace App\Services;
class GiteaService
{
private $cacheFile = null;
private $config = [];
public function __construct()
{
$this->config = config('services.gitea');
$this->cacheFile = storage_path('app/gitea_cache.txt');
}
public function checkForLatestRelease()
{
$cacheData = null;
if ($this->doesCacheExist())
{
// Get the etag from the cache
$cacheData = $this->getCacheData();
}
else
{
// Lookup and store the version information
$statusCode = -1;
$result = $this->getLatestReleaseFromGitea($statusCode);
if ($statusCode == 200)
{
$releases = json_decode($result[1]);
$latestRelease = null;
foreach ($releases as $release)
{
if (is_null($latestRelease) || version_compare($release->tag_name, $latestRelease->tag_name) > 0)
{
$latestRelease = $release;
}
}
$cacheData = $this->setCacheData($latestRelease);
}
}
// GitHub compatibility
$cacheData->html_url = sprintf($this->config['releases_url'], $this->config['repo_owner'], $this->config['repo_name']);
return $cacheData;
}
private function doesCacheExist()
{
$exists = file_exists($this->cacheFile);
if ($exists)
{
// Check modified time on the file
$stat = stat($this->cacheFile);
$diff = time() - $stat['mtime'];
if ($diff > $this->config['cache_time_seconds'])
{
$exists = false;
}
}
return $exists;
}
private function getCacheData()
{
return json_decode(file_get_contents($this->cacheFile));
}
private function getLatestReleaseFromGitea(&$statusCode)
{
$httpHeaders = [
sprintf('User-Agent: aheathershaw/blue-twilight (v%s)', config('app.version'))
];
if (isset($this->config['api_key']) && !empty($this->config['api_key']))
{
$httpHeaders[] = sprintf('Authorization: %s', $this->config['api_key']);
}
$apiUrl = sprintf('%s/repos/%s/%s/releases', $this->config['api_url'], $this->config['repo_owner'], $this->config['repo_name']);
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if ($result === false)
{
throw new \Exception(sprintf('Error from Gitea: %s', curl_error($ch)));
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return explode("\r\n\r\n", $result, 2);
}
private function setCacheData($data)
{
file_put_contents($this->cacheFile, json_encode(get_object_vars($data)));
return $data;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Services;
class GithubService
{
private $cacheFile = null;
private $config = [];
public function __construct()
{
$this->config = config('services.github');
$this->cacheFile = storage_path('app/github_cache.txt');
}
public function checkForLatestRelease()
{
$releaseInfo = [];
$etag = '';
if ($this->doesCacheExist())
{
// Get the etag from the cache
$cacheData = $this->getCacheData();
$etag = $cacheData->latest_release->etag;
$releaseInfo = $cacheData->latest_release->release_info;
}
// Lookup and store the version information
$statusCode = -1;
$result = $this->getLatestReleaseFromGithub($etag, $statusCode);
if ($statusCode == 200)
{
// Store the etag (in HTTP headers) for future reference
$matches = [];
$etag = '';
if (preg_match('/^etag: "(.+)"/mi', $result[0], $matches))
{
$etag = $matches[1];
}
$releaseInfo = json_decode($result[1]);
}
if (!empty($etag))
{
$this->setCacheData([
'latest_release' => [
'etag' => $etag,
'release_info' => $releaseInfo
]
]);
}
return $releaseInfo;
}
private function doesCacheExist()
{
return file_exists($this->cacheFile);
}
private function getCacheData()
{
return json_decode(file_get_contents($this->cacheFile));
}
private function getLatestReleaseFromGithub($etag = '', &$statusCode)
{
$httpHeaders = [
sprintf('User-Agent: pandy06269/blue-twilight (v%s)', config('app.version'))
];
if (!empty($etag))
{
$httpHeaders[] = sprintf('If-None-Match: "%s"', $etag);
}
$ch = curl_init($this->config['latest_release_url']);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
if ($result === false)
{
throw new \Exception(sprintf('Error from Github: %s', curl_error($ch)));
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return explode("\r\n\r\n", $result, 2);
}
private function setCacheData(array $data)
{
file_put_contents($this->cacheFile, json_encode($data));
}
}

View File

@ -50,7 +50,7 @@ class PhotoService
$this->themeHelper = new ThemeHelper();
}
public function analyse($queueToken)
public function analyse($queueToken, $isReanalyse = false)
{
$queuePath = FileHelper::getQueuePath($queueToken);
$photoFile = join(DIRECTORY_SEPARATOR, [$queuePath, $this->photo->storage_file_name]);
@ -85,7 +85,7 @@ class PhotoService
// If Exif data contains an Orientation, ensure we rotate the original image as such (providing we don't
// currently have a metadata version - i.e. it hasn't been read and rotated already before)
if ($isExifDataFound && isset($exifData['Orientation']) && is_null($this->photo->metadata_version))
if ($isExifDataFound && isset($exifData['Orientation']) && !$isReanalyse)
{
switch ($exifData['Orientation'])
{

View File

@ -2,7 +2,7 @@
return [
// Version number of Blue Twilight
'version' => '2.1.0-beta.2',
'version' => '2.1.2',
/*
|--------------------------------------------------------------------------

View File

@ -14,29 +14,15 @@ return [
|
*/
'gitea' => [
'api_url' => 'https://apps.andysh.uk/api/v1',
'cache_time_seconds' => 3600,
'releases_url' => 'https://apps.andysh.uk/%s/%s/releases',
'repo_name' => 'blue-twilight',
'repo_owner' => 'aheathershaw'
],
'recaptcha' => [
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
]
/*'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),
'region' => 'us-east-1',
],
'sparkpost' => [
'secret' => env('SPARKPOST_SECRET'),
],
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],*/
];

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -136,6 +136,14 @@ class BlueTwilightInstaller
private function fetchComposerSignature()
{
if (!boolval(ini_get('allow_url_fopen')))
{
$this->echoError('allow_url_fopen is disabled so we cannot use Composer');
echo '<br/>';
$this->echoError('You will need to install the vendor libraries manually - <a href="https://github.com/pandy06269/blue-twilight/wiki/Install-Vendor-libraries-manually" target="_blank">see this page for more details</a>');
return false;
}
$signatureUrl = 'https://composer.github.io/installer.sig';
$this->composerSignature = trim(file_get_contents($signatureUrl));
if (strlen($this->composerSignature) == 0)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -48,6 +48,9 @@ class BlueTwilightUpdater
cd <?php echo $this->baseDirectory; ?>
php artisan clear-compiled
php artisan cache:clear
php artisan config:clear
php artisan view:clear
/path/to/composer.phar install
</pre>
</body>
@ -65,6 +68,8 @@ php artisan clear-compiled
<?php
$steps = [
['Removing compiled cache', 'removeCompiledCached'],
['Fetching Composer signature', 'fetchComposerSignature'],
['Installing Composer', 'installComposer'],
['Updating dependencies using Composer', 'runComposer']
];
@ -92,6 +97,31 @@ php artisan clear-compiled
<?php
}
private function fetchComposerSignature()
{
if (!boolval(ini_get('allow_url_fopen')))
{
$this->echoError('allow_url_fopen is disabled so we cannot use Composer');
echo '<br/>';
$this->echoError('You will need to install the vendor libraries manually - <a href="https://github.com/pandy06269/blue-twilight/wiki/Install-Vendor-libraries-manually" target="_blank">see this page for more details</a>');
return false;
}
$signatureUrl = 'https://composer.github.io/installer.sig';
$this->composerSignature = trim(file_get_contents($signatureUrl));
if (strlen($this->composerSignature) == 0)
{
$this->echoError(sprintf("Failed downloading the Composer signature from %s", $signatureUrl));
return false;
}
else
{
$this->echoOK($this->composerSignature);
}
return true;
}
private function removeCompiledCached()
{
ob_start();
@ -114,6 +144,16 @@ php artisan clear-compiled
return false;
}
ob_start();
system('php artisan config:clear', $rc);
$result = ob_get_clean();
echo nl2br($result);
if ($rc != 0)
{
$this->echoError('config:clear command failed');
return false;
}
ob_start();
system('php artisan view:clear', $rc);
$result = ob_get_clean();
@ -145,6 +185,55 @@ php artisan clear-compiled
echo '</span>' . PHP_EOL;
}
private function installComposer()
{
$rc = -1;
ob_start();
system('php -r "copy(\'https://getcomposer.org/installer\', \'composer-setup.php\');"', $rc);
$result = ob_get_clean();
echo nl2br($result);
if ($rc != 0)
{
$this->echoError('Failed to fetch Composer');
return false;
}
ob_start();
system(sprintf('php -r "if (hash_file(\'SHA384\', \'composer-setup.php\') === \'%s\') { echo \'Installer verified\'; } else { echo \'Installer corrupt\'; unlink(\'composer-setup.php\'); } echo PHP_EOL;"', $this->composerSignature), $rc);
$result = ob_get_clean();
echo nl2br($result);
if ($rc != 0)
{
$this->echoError('Composer verification failed');
return false;
}
ob_start();
system('php composer-setup.php', $rc);
$result = ob_get_clean();
echo nl2br($result);
if ($rc != 0)
{
$this->echoError('Failed to install Composer');
return false;
}
ob_start();
system('php -r "unlink(\'composer-setup.php\');"', $rc);
$result = ob_get_clean();
echo nl2br($result);
if ($rc != 0)
{
$this->echoError('Failed to remove Composer setup file');
return false;
}
$this->echoOK();
return true;
}
private function runComposer()
{
ob_start();

View File

@ -35,4 +35,18 @@ The link to the demo system is: http://demo.showmy.photos. Login with:
I'd love to get you up and running. If you need assistance installing the Blue Twilight PHP photo gallery, or would like to me to do it for you, please get in touch using the contact form located at:
[www.andyheathershaw.uk/contact](http://www.andyheathershaw.uk/contact)
[www.andyheathershaw.uk/contact](https://www.andyheathershaw.uk/contact)
## More Apps by Andy
Be sure to check out more apps written by Andy:
[Simply Remind Me - email & SMS service](https://simplyremind.me)
A free email & SMS reminder service so you don't forget the important things in life. Supports recurring reminders and repeated events.
Create a public countdown page to share your holiday or birthday with the world.
[Solid Tools for Developers - online debugging tools](https://soliddevtools.com)
A suite of online debugging and diagnostics tools aimed at software developers. Includes a dig tool for DNS lookups, a ping tool to check server availability, a strong password generator and many more.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,39 +1,46 @@
html {
/*!
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: transparent;
}
*,
*::before,
*::after {
box-sizing: inherit;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 1rem;
font-weight: normal;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: none !important;
outline: 0 !important;
}
hr {
@ -44,7 +51,7 @@ hr {
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
p {
@ -56,7 +63,7 @@ abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
}
@ -82,7 +89,7 @@ ul ol {
}
dt {
font-weight: bold;
font-weight: 700;
}
dd {
@ -140,7 +147,7 @@ a:not([href]):not([tabindex]) {
text-decoration: none;
}
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
@ -153,7 +160,7 @@ pre,
code,
kbd,
samp {
font-family: monospace, monospace;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
@ -161,6 +168,7 @@ pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
@ -174,19 +182,7 @@ img {
svg:not(:root) {
overflow: hidden;
}
a,
area,
button,
[role="button"],
input,
label,
select,
summary,
textarea {
-ms-touch-action: manipulation;
touch-action: manipulation;
vertical-align: middle;
}
table {
@ -196,18 +192,22 @@ table {
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #868e96;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: left;
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
@ -318,6 +318,7 @@ output {
summary {
display: list-item;
cursor: pointer;
}
template {

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,8 @@
html{box-sizing:border-box;font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}*,::after,::before{box-sizing:inherit}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#868e96;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}
/*!
* Bootstrap Reboot v4.1.2 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,11 @@
margin-top: 5px;
}
.meta-label,
.meta-value {
vertical-align: middle !important;
}
.photo .loading {
background-color: #ffffff;
display: none;
@ -22,4 +27,12 @@
.photo .loading img {
margin-top: 40px;
}
.text-red {
color: #ff0000;
}
[v-cloak] {
display: none;
}

View File

@ -0,0 +1,47 @@
/**
* This model is used by admin/about.blade.php, to perform a version check against Github.
* @constructor
*/
function AboutViewModel(urls) {
this.el = '#about-app';
this.data = {
can_upgrade: false,
is_loading: true,
version_body: '',
version_date: '',
version_name: '',
version_url: ''
};
this.computed = {
};
this.methods = {
init: function () {
var self = this;
$.ajax(
urls.latest_release_url,
{
complete: function() {
self.is_loading = false;
},
dataType: 'json',
error: function (xhr, textStatus, errorThrown) {
},
method: 'GET',
success: function (data) {
self.version_body = data.body;
self.version_date = data.publish_date;
self.version_name = data.name;
self.version_url = data.url;
// Set this last so any watchers on this property execute after all version data has been set
self.can_upgrade = data.can_upgrade;
}
}
);
}
};
}

View File

@ -676,6 +676,13 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
// Get the selected files
var files = fileSelect[0].files;
if (files.length === 0)
{
alert(language.no_file_selected);
event.preventDefault();
return false;
}
// Reset statistics
this.currentStatus = '';
this.statusMessages = [];

View File

@ -4,6 +4,23 @@ return [
'create_album_link' => 'Create album',
'panel_header' => 'Actions',
],
'about' => [
'current_version' => 'You are running version',
'date_published_label' => 'Release date:',
'intro' => 'Blue Twilight is an <a href="https://andysh.uk/software/" target="_blank">App by Andy</a>.',
'intro_2' => 'It is made with <i class="fa fa-heart text-red"></i> in the UK by software developer <a href="https://andysh.uk" target="_blank">Andy Heathershaw</a>.',
'latest_version_loading' => 'Checking for the latest release...',
'licence_header' => 'Licence',
'links_header' => 'Useful Links',
'project_website_link' => 'Project Website',
'title' => 'About Blue Twilight',
'up_to_date' => 'Good job - your installation of Blue Twilight is up-to-date!',
'update_available' => 'An update is available to Blue Twilight.',
'user_guide_link' => 'User Guide',
'version_label' => 'Version',
'versions_header' => 'About &amp; Version',
'website_link' => 'Main Website'
],
'album_appearance_heading' => 'Appearance',
'album_appearance_intro' => 'The settings shown below control how this album appears to visitors in the gallery.',
'album_basic_info_heading' => 'Album information',
@ -34,6 +51,7 @@ return [
'others' => ' others'
],
'analyse_photos_failed' => 'The following items could not be analysed and were removed:',
'analyse_photos_failed_metadata' => 'The following items could not be analysed:',
'anonymous_users' => 'Anonymous (not logged in)',
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.',
@ -61,6 +79,7 @@ return [
'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_failed_children' => 'The album ":album" contains child albums and cannot be deleted. Please delete or move the child albums first.',
'delete_album_success_message' => 'The album ":album" was deleted successfully.',
'delete_album_warning' => 'This is a permanent action that cannot be undone!',
'delete_bulk_photos_message' => 'Are you sure you want to delete the selected photos? This action cannot be undone!',
@ -110,6 +129,7 @@ return [
'is_uploading' => 'Uploading in progress...',
'labels_intro' => 'Your labels are displayed below. The number in brackets indicates the number of photos linked to that label. Click a label to delete it.',
'legend' => 'Legend/Key',
'list_albums' => 'Go to your albums',
'list_albums_intro' => 'Albums contain collections of individual photographs in the same way as a physical photo album or memory book.',
'list_albums_title' => 'Albums',
'list_groups_intro' => 'Organise your users into categories or types by using groups. You can assign permissions on albums to groups of users to make administration and management easier.',
@ -123,8 +143,10 @@ return [
'manage_widget' => [
'panel_header' => 'Manage'
],
'metadata_no_albums_text' => 'You have no photo albums yet or all your albums are empty. Click the button below to list your albums or create a new album.',
'metadata_no_albums_title' => 'No Albums or Photos',
'metadata_upgrade' => [
'can_be_upgraded' => 'Metadata can be updated (version :version_from --&gt; :version_to)',
'can_be_upgraded' => 'Metadata can be updated (version :version_from &raquo; :version_to)',
'confirm' => 'Are you sure you want to update the metadata of the :name album?',
'intro' => 'Blue Twilight analyses your photos to capture metadata such as the camera and location used to capture the photo. Sometimes we capture more metadata than we have done previously, which requires your original photos to be re-analysed.',
'intro_2' => 'This page shows you which of your albums contain photos that need to be re-analysed to get the latest metadata information.',
@ -177,6 +199,9 @@ return [
'security_text' => 'You can assign permissions on this album to either groups (recommended) or directly to users.',
'security_users_heading' => 'User Permissions',
'settings' => [
'albums_menu_heading' => 'Albums Navigation Menu',
'albums_menu_number_items' => 'Number of albums to display:',
'albums_menu_parents_only' => 'Only show top-level albums',
'analytics_cookie_link_1' => 'Information about the EU Cookie Law directive',
'analytics_cookie_link_2' => 'Cookie Consent by Insites',
'analytics_cookie_warning_1' => 'If you are based in Europe and you enable visitor tracking, you will need to comply with the EU Cookie Law. The easiest way to do so is using a script like Cookie Consent by Insites.',
@ -234,6 +259,7 @@ return [
],
'title' => 'Gallery Admin',
'upload_bulk_heading' => 'Upload a zip archive',
'upload_bulk_no_file' => 'No archive was uploaded. Please select a file to upload.',
'upload_bulk_text' => 'You can use the form below to upload a zip archive that contains your photographs. Any valid image files in the zip archive will be imported. Common hidden folders used by operating systems will be ignored.',
'upload_bulk_text2' => 'Your web server is configured to allow files up to :max_upload_size to be uploaded.',
'upload_disabled_heading' => 'Uploading is not supported on this system.',
@ -244,6 +270,7 @@ return [
'upload_file_status_failed' => ':file_name failed to upload',
'upload_file_status_progress' => ':current of :total files completed',
'upload_file_status_success' => ':file_name uploaded successfully',
'upload_no_file' => 'Please select a photo file to upload.',
'upload_single_file_heading' => 'Upload photos individually',
'upload_single_file_text' => 'You can use the form below to upload individual files. To upload multiple files at once, hold down CTRL in the file browser.',
'upload_single_file_text2' => 'Your web server is configured to allow files up to :file_size. If you browser does not support HTML 5 (most modern browsers do), the combined size of all selected files must be less than :max_upload_size.',

View File

@ -13,6 +13,7 @@ return [
'bulk_edit_photos_label' => 'Bulk edit selected photos:',
'bulk_edit_photos_placeholder' => 'Select an action',
'cancel_action' => 'Cancel',
'close_action' => 'Close',
'continue_action' => 'Continue',
'create_action' => 'Create',
'create_album_label' => 'Create a new album:',
@ -20,6 +21,7 @@ return [
'default_storage_label' => 'Use as the default storage location for new albums',
'delete_action' => 'Delete',
'description_label' => 'Description:',
'download_action' => 'Download',
'edit_action' => 'Edit',
'email_label' => 'E-mail address:',
'labels_label' => 'Labels:',

View File

@ -12,6 +12,7 @@ return [
'camera_make' => 'Camera make:',
'camera_model' => 'Camera model:',
'camera_software' => 'Camera software:',
'child_albums' => 'more album|more albums',
'date_taken' => 'Date taken:',
'file_name' => 'File name:',
'focal_length' => 'Focal length:',
@ -29,9 +30,11 @@ return [
'next_button' => 'Next Photo &raquo;',
'open_album_link' => 'Open Album',
'other_albums_description' => 'You may also be interested in the following albums.',
'other_albums_description_empty' => 'The <b>:album_name</b> album does not contain any photos - however you may also be interested in the following albums.',
'other_albums_heading' => 'More Albums in :album_name',
'photos' => 'photo|photos',
'previous_button' => '&laquo; Previous Photo',
'show_more_albums' => '... and :count other|... and :count others',
'show_more_labels' => '... and :count other|... and :count others',
'show_raw_exif_data' => 'Show all EXIF data',
'shutter_speed' => 'Shutter speed:',

View File

@ -1,6 +1,7 @@
<?php
return [
'breadcrumb' => [
'about' => 'About',
'admin' => 'Admin',
'albums' => 'Albums',
'create_album' => 'Create album',

View File

@ -113,6 +113,7 @@ return [
'attributes' => [],
// Added by Andy H. for custom validators
'album_path_unique' => 'An album with the same name already exists.',
'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.',

View File

@ -0,0 +1,115 @@
@extends(Theme::viewName('layout'))
@section('title', trans('admin.about.title'))
@section('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('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.about')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col">
<h1>@lang('admin.about.title')</h1>
<p>
@lang('admin.about.intro')<br/>
@lang('admin.about.intro_2')
</p>
<ul class="nav nav-tabs mt-5">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#versions-tabpanel" role="tab">@lang('admin.about.versions_header')</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#licence-tabpanel" role="tab">@lang('admin.about.licence_header')</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">@lang('admin.about.links_header')</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="https://andysh.uk/software/blue-twilight-php-photo-gallery/" target="_blank">@lang('admin.about.website_link')</a>
<a class="dropdown-item" href="https://andysh.uk/software/blue-twilight-php-photo-gallery/manual/" target="_blank">@lang('admin.about.user_guide_link')</a>
<a class="dropdown-item" href="https://apps.andysh.uk/aheathershaw/blue-twilight" target="_blank">@lang('admin.about.project_website_link')</a>
</div>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="versions-tabpanel" role="tabpanel">
<div class="row" id="about-app">
<div class="col-md-6 text-center">
<p>@lang('admin.about.current_version')</p>
<p class="m-0" style="font-size: 2.5rem;">{{ $current_version }}</p>
</div>
<div class="col-md-6 text-center pt-2">
<div v-if="is_loading">
<p><img src="{{ asset('ripple.svg') }}"></p>
<p class="m-0">@lang('admin.about.latest_version_loading')</p>
</div>
<p v-cloak v-if="!is_loading && !can_upgrade" class="text-success">
<i class="fa fa-check fa-fw"></i> @lang('admin.about.up_to_date')
</p>
<div v-cloak v-if="!is_loading && can_upgrade" class="text-danger">
<p><i class="fa fa-warning fa-fw"></i> @lang('admin.about.update_available')</p>
<p class="mt-0" style="font-size: 2.5rem;" v-text="version_name"></p>
<p><a class="btn btn-secondary text-white" href="#version-update-modal" data-toggle="modal">View details</a></p>
</div>
</div>
<div class="modal" id="version-update-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@lang('admin.about.version_label') <span v-text="version_name"></span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<p class="m-0"><b>@lang('admin.about.update_available')</b></p>
<p class="m-0">@lang('admin.about.date_published_label') <span v-text="version_date"></span></p>
</div>
<p v-html="version_body"></p>
</div>
<div class="modal-footer">
<a class="btn btn-primary" v-bind:href="version_url">@lang('forms.download_action')</a>
<a class="btn btn-secondary" href="#" data-dismiss="modal">@lang('forms.cancel_action')</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="licence-tabpanel" role="tabpanel">
<textarea class="form-control" rows="20" style="border: 0;">
{!! $licence_text !!}
</textarea>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$(document).ready(function()
{
var viewModel = new AboutViewModel({
latest_release_url: '{{ route('admin.aboutLatestRelease') }}'
});
var app = new Vue(viewModel);
app.$watch('can_upgrade', function(value)
{
$('#version-update-modal').modal();
});
app.init();
});
</script>
@endpush

View File

@ -12,21 +12,69 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 ml-md-auto mr-md-auto">
<div class="card bg-primary">
<div class="card-header text-white">@yield('title')</div>
<div class="card-body bg-light">
<p>@lang('admin.metadata_upgrade.confirm', ['name' => $album->name])</p>
<p class="text-danger"><b>@lang('admin.metadata_upgrade.warning')</b></p>
<div class="col-md-8 ml-md-auto mr-md-auto" id="analyse-album">
<div v-if="!isCompleted">
<div class="card" id="analysis-card" style="display: none;">
<div class="card-header">Analysing...</div>
<div class="card-body">
<p>Your photos are now being analysed.</p>
<div class="text-right">
<form action="{{ route('albums.metadataPost', [$album->id]) }}" method="POST">
{{ csrf_field() }}
<div class="progress">
<div class="progress-bar bg-success" v-bind:style="{ width: successfulPercentage }">
</div>
<div class="progress-bar bg-danger" v-bind:style="{ width: failedPercentage }">
</div>
</div>
{{-- We display a queue of 3 recently completed items, and 5 up-next items, as well as a counter saying "... and XYZ more" --}}
{{-- That's what the 3's and 5's are in the next couple of blocks --}}
<div v-for="image in latestCompletedImages" style="margin-top: 20px;">
<p class="text-success"><span v-text="image.name"></span> ... <i class="fa fa-fw fa-check"></i></p>
</div>
<div v-for="image in imagesInProgress.slice(0, 5)" style="margin-top: 20px;">
<p><span v-text="image.name"></span> ... <i class="fa fa-fw fa-refresh"></i></p>
</div>
<div v-if="imagesInProgress.length > 5">
<p>@lang('admin.analyse_and_more.and') <span v-text="imagesInProgress.length - 5"></span> @lang('admin.analyse_and_more.others')</p>
</div>
<div v-for="image in imagesFailed" style="margin-top: 20px;">
<p class="text-danger"><span v-text="image.name"></span> ... <i class="fa fa-fw fa-times"></i></p>
</div>
</div>
</div>
<div class="card bg-primary" id="confirm-card">
<div class="card-header text-white">@yield('title')</div>
<div class="card-body bg-light">
<p>@lang('admin.metadata_upgrade.confirm', ['name' => $album->name])</p>
<p class="text-danger"><b>@lang('admin.metadata_upgrade.warning')</b></p>
<div class="text-right">
<a href="{{ route('admin.metadataUpgrade', ['id' => $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>
</form>
</div>
</div>
</div>
</div>
<div class="card" v-if="isCompleted">
<div class="card-header">Analysis completed</div>
<div class="card-body">
<p>Your analysis has completed.</p>
<div v-if="numberFailed > 0">
<p class="text-danger">@lang('admin.analyse_photos_failed_metadata')</p>
<ul class="text-danger" v-for="image in imagesFailed">
<li><span v-text="image.name"></span>: <span v-text="image.reason"></span></li>
</ul>
</div>
<div class="text-right">
<a class="btn btn-link" href="{{ $album->url() }}">View album</a>
<a class="btn btn-primary" href="{{ route('admin.metadataUpgrade') }}"><i class="fa fa-fw fa-chevron-left"></i> Back to metadata upgrade</a>
</div>
</pdiv>
</div>
</div>
</div>
@ -35,10 +83,23 @@
@push('scripts')
<script type="text/javascript">
var viewModel = new AnalyseAlbumViewModel();
var app = new Vue(viewModel);
$(document).ready(function() {
$('#submit-button').click(function() {
$(this).prop('disabled', true);
$(this).closest('form').submit();
$('#submit-button').click(function()
{
$('#analysis-card').show();
$('#confirm-card').hide();
{{-- For each photo to analyse, push an instance of AnalyseImageViewModel to our master view model --}}
@foreach ($photos as $photo)
app.analyseImage(new AnalyseImageViewModel({
'id': '{{ $photo->id }}',
'name': '{!! addslashes($photo->name) !!}',
'url': '{{ route('photos.re-analyse', ['id' => $photo->id, 'queue_token' => $queue_token]) }}'
}));
@endforeach
});
});
</script>

View File

@ -63,11 +63,10 @@
@include(Theme::viewName('partials.admin_storages_rackspace_options'))
</div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_default"@if (old('is_default')) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.default_storage_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is-default" name="is_default"@if (old('is_default')) checked="checked"@endif>
<label class="form-check-label" for="is-default">@lang('forms.default_storage_label')</label>
</div>
<div class="text-right">
<a href="{{ route('storage.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>

View File

@ -75,12 +75,9 @@
</div>
</div>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_admin" @if (old('is_admin'))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.admin_user_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is_admin" name="is_admin" @if (old('is_admin'))checked="checked"@endif>
<label class="form-check-label" for="is_admin">@lang('forms.admin_user_label')</label>
</div>
<div class="text-right">

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('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.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li>
@include(Theme::viewName('partials.admin_album_breadcrumb'), ['show_current_link' => true])
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.delete_album')</li>
@endsection

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('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.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li>
@include(Theme::viewName('partials.admin_album_breadcrumb'), ['show_current_link' => true])
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.edit_album')</li>
@endsection

View File

@ -31,20 +31,14 @@
@endif
</div>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_default"@if (old('is_default', $storage->is_default)) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.default_storage_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is-default" name="is_default"@if (old('is_default', $storage->is_default)) checked="checked"@endif>
<label class="form-check-label" for="is-default">@lang('forms.default_storage_label')</label>
</div>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_active"@if (old('is_active', $storage->is_active)) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.storage_active_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is-active" name="is_active"@if (old('is_active', $storage->is_active)) checked="checked"@endif>
<label class="form-check-label" for="is-active">@lang('forms.storage_active_label')</label>
</div>
@if ($storage->source == 'LocalFilesystemSource')

View File

@ -87,21 +87,15 @@
</div>
</div>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_admin"@if (old('is_admin', $user->is_admin)) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.admin_user_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is-admin" name="is_admin"@if (old('is_admin', $user->is_admin)) checked="checked"@endif>
<label class="form-check-label" for="is-admin">@lang('forms.admin_user_label')</label>
</div>
@if (!$user->is_activated)
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="is_activated"@if (old('is_activated', $user->is_activated)) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('forms.activate_user_label')</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="is-activated" name="is_activated"@if (old('is_activated', $user->is_activated)) checked="checked"@endif>
<label class="form-check-label" for="is-activated">@lang('forms.activate_user_label')</label>
</div>
@endif
</div>
@ -112,12 +106,9 @@
<p style="margin-bottom: 20px;">@lang('admin.user_groups_list_select', ['name' => $user->name])</p>
@foreach ($groups as $group)
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="user_group_id[]" value="{{ $group->id }}" @if (in_array($group->id, $users_groups)) checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ $group->name }}</span>
</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="user-group-{{ $group->id }}" name="user_group_id[]" value="{{ $group->id }}" @if (in_array($group->id, $users_groups)) checked="checked"@endif>
<label class="form-check-label" for="user-group-{{ $group->id }}">{{ $group->name }}</label>
</div>
@endforeach
@else

View File

@ -16,18 +16,18 @@
<i class="fa fa-fw fa-info"></i> @lang('admin.metadata_upgrade.intro')
</div>
<p>@lang('admin.metadata_upgrade.intro_2')</p>
<p class="mb-4">@lang('admin.metadata_upgrade.intro_3')</p>
@if (count($albums) == 0)
<div class="text-center">
<h4 class="text-danger"><b>@lang('admin.no_albums_title')</b></h4>
<p>@lang('admin.no_albums_text')</p>
<div class="text-center mt-5">
<h4 class="text-danger"><b>@lang('admin.metadata_no_albums_title')</b></h4>
<p>@lang('admin.metadata_no_albums_text')</p>
<p style="margin-top: 40px;">
<a href="{{ route('albums.create') }}" class="btn btn-lg btn-success"><i class="fa fa-fw fa-plus"></i> @lang('admin.create_album')</a>
<a href="{{ route('albums.index') }}" class="btn btn-lg btn-success"><i class="fa fa-fw fa-plus"></i> @lang('admin.list_albums')</a>
</p>
</div>
@else
<p>@lang('admin.metadata_upgrade.intro_2')</p>
<p class="mb-4">@lang('admin.metadata_upgrade.intro_3')</p>
<table class="table table-hover table-striped">
<tbody>
@foreach ($albums as $album)

View File

@ -77,6 +77,29 @@
</div>
</div>
</div>
<hr/>
<fieldset>
<legend>@lang('admin.settings.albums_menu_heading')</legend>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="albums-menu-parents-only" name="albums_menu_parents_only" @if (old('albums_menu_parents_only', UserConfig::get('albums_menu_parents_only')))checked="checked"@endif>
<label class="form-check-label" for="albums-menu-parents-only">
<strong>@lang('admin.settings.albums_menu_parents_only')</strong>
</label>
</div>
<div class="form-group">
<label class="form-control-label" for="albums-menu-number-items">@lang('admin.settings.albums_menu_number_items')</label>
<input type="number" class="form-control{{ $errors->has('albums_menu_number_items') ? ' is-invalid' : '' }}" id="albums-menu-number-items" name="albums_menu_number_items" value="{{ old('albums_menu_number_items', $config['albums_menu_number_items']) }}" style="max-width: 100px;">
@if ($errors->has('albums_menu_number_items'))
<div class="invalid-feedback">
<strong>{{ $errors->first('albums_menu_number_items') }}</strong>
</div>
@endif
</div>
</fieldset>
<hr/>
<fieldset>
@ -84,11 +107,10 @@
<p>To help spread the word about Blue Twilight, I'd really appreciate it if you left the &quot;Powered by&quot; notice in your gallery's footer.</p>
<p>If you do want to remove it, however, I understand - just check the box below.</p>
<div class="form-check" style="margin-top: 20px;">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="remove_copyright" @if (old('remove_copyright', UserConfig::get('remove_copyright')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>Remove &quot;Powered by&quot; notice from the public gallery</strong></span>
<div class="form-check mt-2">
<input type="checkbox" class="form-check-input" id="remove-copyright" name="remove_copyright" @if (old('remove_copyright', UserConfig::get('remove_copyright')))checked="checked"@endif>
<label class="form-check-label" for="remove-copyright">
<strong>Remove &quot;Powered by&quot; notice from the public gallery</strong>
</label>
</div>
</fieldset>
@ -166,11 +188,10 @@
@endif
</div>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="smtp_encryption" @if (UserConfig::get('smtp_encryption'))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>Requires encrypted connection</strong></span>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="smtp-encryption" name="smtp_encryption" @if (UserConfig::get('smtp_encryption'))checked="checked"@endif>
<label class="form-check-label" for="smtp-encryption">
<strong>Requires encrypted connection</strong>
</label>
</div>
@ -193,30 +214,27 @@
{{-- Security --}}
<div role="tabpanel" class="tab-pane" id="security-tab">
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="allow_self_registration" @if (old('allow_self_registration', UserConfig::get('allow_self_registration')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="allow-self-registration" name="allow_self_registration" @if (old('allow_self_registration', UserConfig::get('allow_self_registration')))checked="checked"@endif>
<label class="form-check-label" for="allow-self-registration">
<span class="custom-control-description"><strong>@lang('admin.settings.security_allow_self_registration')</strong><br/>
@lang('admin.settings.security_allow_self_registration_description')</span>
</label>
</div>
<div style="margin-top: 20px;">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="require_email_verification" @if (old('require_email_verification', UserConfig::get('require_email_verification')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>Require e-mail verification for self-registered accounts</strong><br/>
<span class="text-danger">It is strongly recommended to enable this option.</span></span>
<div class="form-check mt-3">
<input type="checkbox" class="form-check-input" id="require-email-verification" name="require_email_verification" @if (old('require_email_verification', UserConfig::get('require_email_verification')))checked="checked"@endif>
<label class="form-check-label" for="require-email-verification">
<strong>Require e-mail verification for self-registered accounts</strong><br/>
<span class="text-danger">It is strongly recommended to enable this option.</span>
</label>
</div>
<div style="margin-top: 20px;">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="recaptcha_enabled_registration" @if (old('recaptcha_enabled_registration', UserConfig::get('recaptcha_enabled_registration')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>Enable <a href="https://www.google.com/recaptcha" target="_blank">reCAPTCHA</a> for self-registrations</strong><br/>
<span class="text-danger">It is strongly recommended to enable this option.</span></span>
<div class="form-check mt-3">
<input type="checkbox" class="form-check-input" id="recaptcha-enabled-registration" name="recaptcha_enabled_registration" @if (old('recaptcha_enabled_registration', UserConfig::get('recaptcha_enabled_registration')))checked="checked"@endif>
<label class="form-check-label" for="recaptcha-enabled-registration">
<strong>Enable <a href="https://www.google.com/recaptcha" target="_blank">reCAPTCHA</a> for self-registrations</strong><br/>
<span class="text-danger">It is strongly recommended to enable this option.</span>
</label>
</div>
@ -249,19 +267,17 @@
<fieldset style="margin-top: 20px;">
<legend>@lang('admin.settings_image_protection')</legend>
<div>
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="restrict_original_download" @if (old('restrict_original_download', UserConfig::get('restrict_original_download')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>@lang('forms.settings_restrict_originals_download')</strong><br/>
@lang('forms.settings_restrict_originals_download_help')</span>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="restrict-original-download" name="restrict_original_download" @if (old('restrict_original_download', UserConfig::get('restrict_original_download')))checked="checked"@endif>
<label class="form-check-label" for="restrict-original-download">
<strong>@lang('forms.settings_restrict_originals_download')</strong><br/>
@lang('forms.settings_restrict_originals_download_help')
</label>
</div>
<div style="margin-top: 20px;">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="hotlink_protection" @if (old('hotlink_protection', UserConfig::get('hotlink_protection')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<div class="form-check mt-3">
<input type="checkbox" class="form-check-input" id="hotlink-protection" name="hotlink_protection" @if (old('hotlink_protection', UserConfig::get('hotlink_protection')))checked="checked"@endif>
<label class="form-check-label" for="hotlink-protection">
<span class="custom-control-description"><strong>@lang('forms.settings_hotlink_protection')</strong><br/>
@lang('forms.settings_hotlink_protection_help')</span>
</label>
@ -280,12 +296,11 @@
</p>
</div>
<div class="mt-4">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="enable_visitor_hits" @if (old('enable_visitor_hits', UserConfig::get('enable_visitor_hits')))checked="checked"@endif>
<span class="custom-control-indicator"></span>
<span class="custom-control-description"><strong>@lang('admin.settings.analytics_enable_visitor_hits')</strong><br/>
@lang('admin.settings.analytics_enable_visitor_hits_description')</span>
<div class="form-check mt-4">
<input type="checkbox" class="form-check-input" id="enable-visitor-hits" name="enable_visitor_hits" @if (old('enable_visitor_hits', UserConfig::get('enable_visitor_hits')))checked="checked"@endif>
<label class="form-check-label" for="enable-visitor-hits">
<strong>@lang('admin.settings.analytics_enable_visitor_hits')</strong><br/>
@lang('admin.settings.analytics_enable_visitor_hits_description')
</label>
</div>

View File

@ -5,14 +5,20 @@
<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('albums.index') }}">@lang('navigation.breadcrumb.albums')</a></li>
<li class="breadcrumb-item active">{{ $album->name }}</li>
@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="{{ route('albums.show', ['id' => $parentAlbum->id]) }}">{{ $parentAlbum->name }}</a></li>
@endif
@endforeach
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col">
<a class="pull-right btn btn-secondary" href="{{ $album->url() }}" target="_blank"><i class="fa fa-fw fa-eye"></i> @lang('admin.open_album')</a>
<a class="pull-right btn btn-secondary" href="{{ $album->url() }}"><i class="fa fa-fw fa-eye"></i> @lang('admin.open_album')</a>
<h1 class="page-title">{{ $album->name }}</h1>
<p>{!! nl2br(e($album->description)) !!}</p>
@ -85,6 +91,7 @@
language.select_all_choice_visible_action = '{!! addslashes(trans('admin.select_all_choice.visible_action')) !!}';
language.image_failed = '{!! addslashes(trans('admin.upload_file_status_failed')) !!}';
language.image_uploaded = '{!! addslashes(trans('admin.upload_file_status_success')) !!}';
language.no_file_selected = '{!! addslashes(trans('admin.upload_no_file')) !!}';
language.replace_image_message = '{!! addslashes(trans('admin.replace_image_message')) !!}';
language.replace_image_title = '{!! addslashes(trans('admin.replace_image_title')) !!}';
language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}';
@ -105,7 +112,7 @@
@endforeach
// Populate the list of albums in the view model
@foreach ($albums as $album)
@foreach ($g_albums as $album)
@if(Gate::check('edit', $album) && Gate::check('upload-photos', $album))
editViewModel.data.albums.push({
'id': '{{ $album->id }}',
@ -214,6 +221,9 @@
$('#upload-progress-modal').modal('hide');
}
});
appUpload.$watch('statusMessages', function($value) {
$('#upload-progress-modal').modal('handleUpdate');
})
});
</script>
@endpush

View File

@ -1,8 +1,6 @@
@extends(Theme::viewName('layout'))
@section('title', $album->name)
@php ($hasChildren = $album->children()->count() > 0)
@section('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'))
@ -15,7 +13,7 @@
<div class="pull-right">
@can('edit', $album)
<div class="mb-3">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}" target="_blank"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
<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>
</div>
@endcan
@ -51,24 +49,34 @@
</div>
</div>
@if ($hasChildren)
<h2 style="margin-top: 60px;"><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description')</p>
@if (count($child_albums) > 0)
<div class="row">
@foreach ($album->children as $childAlbum)
<div class="col-sm-4 col-md-3" style="max-width: 250px;">
<div class="card mb-3">
<img class="card-img-top" src="{{ $childAlbum->thumbnailUrl('preview') }}" style="max-height: 120px;"/>
<div class="card-body">
<h5 class="card-title"><a href="{{ $childAlbum->url() }}">{{ $childAlbum->name }}</a></h5>
<div class="col text-center">
<h2 style="margin-top: 60px;"><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description')</p>
<div class="row">
@foreach ($child_albums as $childAlbum)
<div class="col-sm-4 col-md-3 text-left" style="max-width: 250px;">
<div class="card mb-3">
<img class="card-img-top" src="{{ $childAlbum->thumbnailUrl('preview') }}" style="max-height: 120px;"/>
<div class="card-body">
<h5 class="card-title"><a href="{{ $childAlbum->url() }}">{{ $childAlbum->name }}</a></h5>
</div>
<div class="card-footer">
<small class="text-muted">
<i class="fa fa-fw fa-photo"></i> {{ number_format($childAlbum->photos_count) }} {{ trans_choice('gallery.photos', $childAlbum->photos_count) }}
@if ($childAlbum->children_count > 0)
<i class="fa fa-fw fa-book ml-3"></i> {{ number_format($childAlbum->children_count) }} {{ trans_choice('gallery.child_albums', $childAlbum->children_count) }}
@endif
</small>
</div>
</div>
</div>
<div class="card-footer">
<small class="text-muted"><i class="fa fa-fw fa-photo"></i> {{ $childAlbum->photos_count }} photos</small>
</div>
</div>
@endforeach
</div>
@endforeach
</div>
</div>
@endif
</div>

View File

@ -1,8 +1,6 @@
@extends(Theme::viewName('layout'))
@section('title', $album->name)
@php ($hasChildren = $album->children()->count() > 0)
@section('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'))
@ -14,7 +12,7 @@
<div class="row">
<div class="col">
<div class="pull-right">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}" target="_blank"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
<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>
</div>
</div>
</div>
@ -22,26 +20,32 @@
<div class="row">
<div class="col text-center">
<h1>@lang('gallery.album_no_results_heading')</h1>
<p style="line-height: 1.6em;">@lang('gallery.album_no_results_text', ['admin_link' => sprintf('<a href="%s">%s</a>', route('admin'), trans('admin.title'))])</p>
<p style="margin-bottom: 30px; line-height: 1.6em;">@lang('gallery.album_no_results_text_2')</p>
@if (count($child_albums) == 0)
<h1>@lang('gallery.album_no_results_heading')</h1>
<p style="line-height: 1.6em;">@lang('gallery.album_no_results_text', ['admin_link' => sprintf('<a href="%s">%s</a>', route('admin'), trans('admin.title'))])</p>
<p style="margin-bottom: 30px; line-height: 1.6em;">@lang('gallery.album_no_results_text_2')</p>
<img src="{{ asset('themes/base/images/smartphone-photo.jpg') }}" class="img-fluid rounded" style="display: inline;" />
@if ($hasChildren)
<h2 style="margin-top: 60px;"><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description')</p>
<img src="{{ asset('themes/base/images/smartphone-photo.jpg') }}" class="img-fluid rounded" style="display: inline;" />
@else
<h2><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description_empty', ['album_name' => $album->name])</p>
<div class="row">
@foreach ($album->children as $childAlbum)
<div class="col-sm-4 col-md-3" style="max-width: 250px;">
@foreach ($child_albums as $childAlbum)
<div class="col-sm-4 col-md-3 text-left" style="max-width: 250px;">
<div class="card mb-3">
<img class="card-img-top" src="{{ $childAlbum->thumbnailUrl('preview') }}" style="max-height: 120px;"/>
<div class="card-body">
<h5 class="card-title"><a href="{{ $childAlbum->url() }}">{{ $childAlbum->name }}</a></h5>
</div>
<div class="card-footer">
<small class="text-muted"><i class="fa fa-fw fa-photo"></i> {{ $childAlbum->photos_count }} photos</small>
<small class="text-muted">
<i class="fa fa-fw fa-photo"></i> {{ number_format($childAlbum->photos_count) }} {{ trans_choice('gallery.photos', $childAlbum->photos_count) }}
@if ($childAlbum->children_count > 0)
<i class="fa fa-fw fa-book ml-3"></i> {{ number_format($childAlbum->children_count) }} {{ trans_choice('gallery.child_albums', $childAlbum->children_count) }}
@endif
</small>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@
<div class="pull-right">
@can('edit', $album)
<div class="mb-3">
<a class="btn btn-secondary" href="{{ route('albums.show', ['id' => $album->id]) }}" target="_blank"><i class="fa fa-fw fa-eye"></i> @lang('gallery.manage_album_link_2')</a>
<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>
</div>
@endcan
@ -56,24 +56,34 @@
</div>
</div>
@if ($album->children()->count() > 0)
<h2 style="margin-top: 60px;"><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description')</p>
@if (count($child_albums) > 0)
<div class="row">
@foreach ($album->children as $childAlbum)
<div class="col-sm-4 col-md-3" style="max-width: 250px;">
<div class="card mb-3">
<img class="card-img-top" src="{{ $childAlbum->thumbnailUrl('preview') }}" style="max-height: 120px;"/>
<div class="card-body">
<h5 class="card-title"><a href="{{ $childAlbum->url() }}">{{ $childAlbum->name }}</a></h5>
<div class="col text-center">
<h2 style="margin-top: 60px;"><small class="text-muted">@lang('gallery.other_albums_heading', ['album_name' => $album->name])</small></h2>
<p class="mb-4">@lang('gallery.other_albums_description')</p>
<div class="row">
@foreach ($child_albums as $childAlbum)
<div class="col-sm-4 col-md-3 text-left" style="max-width: 250px;">
<div class="card mb-3">
<img class="card-img-top" src="{{ $childAlbum->thumbnailUrl('preview') }}" style="max-height: 120px;"/>
<div class="card-body">
<h5 class="card-title"><a href="{{ $childAlbum->url() }}">{{ $childAlbum->name }}</a></h5>
</div>
<div class="card-footer">
<small class="text-muted">
<i class="fa fa-fw fa-photo"></i> {{ number_format($childAlbum->photos_count) }} {{ trans_choice('gallery.photos', $childAlbum->photos_count) }}
@if ($childAlbum->children_count > 0)
<i class="fa fa-fw fa-book ml-3"></i> {{ number_format($childAlbum->children_count) }} {{ trans_choice('gallery.child_albums', $childAlbum->children_count) }}
@endif
</small>
</div>
</div>
</div>
<div class="card-footer">
<small class="text-muted"><i class="fa fa-fw fa-photo"></i> {{ $childAlbum->photos_count }} photos</small>
</div>
</div>
@endforeach
</div>
@endforeach
</div>
</div>
@endif
</div>

View File

@ -20,7 +20,13 @@
@endcan
</div>
<div class="card-footer">
<small class="text-muted"><i class="fa fa-fw fa-photo"></i> {{ $album->photos_count }} photos</small>
<small class="text-muted">
<i class="fa fa-fw fa-photo"></i> {{ number_format($album->photos_count) }} {{ trans_choice('gallery.photos', $album->photos_count) }}
@if ($album->children_count > 0)
<i class="fa fa-fw fa-book ml-3"></i> {{ number_format($album->children_count) }} {{ trans_choice('gallery.child_albums', $album->children_count) }}
@endif
</small>
</div>
</div>
</div>

View File

@ -21,11 +21,10 @@
<p>@lang('admin.statistics_enable_public_intro')</p>
<form method="post" action="{{ route('admin.statistics.save') }}">
{{ csrf_field() }}
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="enable_public_statistics" @if (UserConfig::get('public_statistics')) checked="checked"@endif/>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">@lang('admin.statistics_enable_public_checkbox')</span>
</label><br/>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="enable-public-statistics" name="enable_public_statistics" @if (UserConfig::get('public_statistics')) checked="checked"@endif/>
<label class="form-check-label" for="enable-public-statistics">@lang('admin.statistics_enable_public_checkbox')</label>
</div><br/>
<button class="btn btn-success"><i class="fa fa-floppy-o"></i> @lang('forms.save_action')</button>
</form>
</div>

View File

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

View File

@ -5,7 +5,7 @@
<tbody>
<tr>
<td class="meta-label">@lang('admin.sysinfo_widget.app_version')</td>
<td class="meta-value">{{ $app_version }}</td>
<td class="meta-value">{{ $app_version }} <a href="{{ route('admin.about') }}" class="btn btn-info ml-2"><i class="fa fa-question"></i></a></td>
</tr>
<tr>
<td class="meta-label">@lang('admin.sysinfo_widget.hostname')</td>

View File

@ -7,25 +7,27 @@
@else
<h4>@lang('admin.album_cameras_heading')</h4>
<p>@lang('admin.album_cameras_text')</p>
<table class="table table-striped table-responsive">
<thead>
<tr>
<th>@lang('admin.album_camera_make')</th>
<th>@lang('admin.album_camera_model')</th>
<th>@lang('admin.album_camera_software')</th>
<th>@lang('admin.album_camera_photo_count')</th>
</tr>
</thead>
<tbody>
@foreach ($cameras as $camera)
<tr>
<td>{{ $camera->camera_make }}</td>
<td>{{ $camera->camera_model }}</td>
<td>{{ $camera->camera_software }}</td>
<td>{{ $camera->photo_count }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>@lang('admin.album_camera_make')</th>
<th>@lang('admin.album_camera_model')</th>
<th>@lang('admin.album_camera_software')</th>
<th>@lang('admin.album_camera_photo_count')</th>
</tr>
</thead>
<tbody>
@foreach ($cameras as $camera)
<tr>
<td>{{ $camera->camera_make }}</td>
<td>{{ $camera->camera_model }}</td>
<td>{{ $camera->camera_software }}</td>
<td>{{ $camera->photo_count }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>

View File

@ -23,10 +23,11 @@
<input type="hidden" name="queue_token" value="{{ $queue_token }}"/>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" id="single-upload-files" class="custom-file-input" multiple="multiple">
{{--<label class="custom-file">
<input type="file" name="photo[]" id="single-upload-files" multiple="multiple">
<span class="custom-file-control"></span>
</label>
</label>--}}
<input type="file" name="photo[]" id="single-upload-files" multiple="multiple">
</div>
<div>
@ -50,10 +51,11 @@
<input type="hidden" name="queue_token" value="{{ $queue_token }}"/>
<div class="form-group">
<label class="custom-file">
{{--<label class="custom-file">
<input type="file" id="single-upload-files" class="custom-file-input" multiple="multiple" name="archive">
<span class="custom-file-control"></span>
</label>
</label>--}}
<input type="file" id="single-upload-files" multiple="multiple" name="archive">
</div>
<div>
@ -80,20 +82,23 @@
<p v-text="currentStatus"></p>
</div>
<div v-if="statusMessages.length > 0">
<div v-if="statusMessages.length > 0"style="max-height: 300px; overflow: scroll;">
<p v-if="!isUploadInProgress" class="text-danger" style="font-weight: bold">
<span v-text="imagesFailed"></span> @lang('admin.upload_file_number_failed')
</p>
<p v-if="imagesUploaded > 0">
<p v-if="imagesUploaded > 0" class="text-right">
@lang('admin.upload_file_failed_continue')<br /><br/>
<a href="{{ route('albums.analyse', ['id' => $album->id, 'queue_token' => $queue_token]) }}" class="btn btn-primary">@lang('forms.continue_action')</a>
</p>
<ul v-for="message in statusMessages">
<li v-bind:class="{ message: message.message_class }" v-text="message.message_text"></li>
<li v-bind:class="message.message_class" v-text="message.message_text"></li>
</ul>
</div>
</div>
<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>
<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>
</div>
</div>
</div>
</div>

View File

@ -5,12 +5,12 @@
<p style="font-size: smaller;">
<b>
@lang('global.powered_by', [
'link_start' => '<a href="https://www.andyheathershaw.uk/software/blue-twilight" target="_blank">',
'link_start' => '<a href="https://andysh.uk/software/blue-twilight-php-photo-gallery/" target="_blank">',
'link_end' => '</a>',
])
</b><br/>
@lang('global.copyright', [
'link_start' => '<a href="https://www.andyheathershaw.uk/" target="_blank">',
'link_start' => '<a href="https://andysh.uk/" target="_blank">',
'link_end' => '</a>',
'years' => (date('Y') == 2016 ? '2016' : '2016-' . date('Y'))
])

View File

@ -5,12 +5,12 @@
<p style="font-size: smaller;">
<b>
@lang('global.powered_by', [
'link_start' => '<a href="https://www.andyheathershaw.uk/software/blue-twilight" target="_blank">',
'link_start' => '<a href="https://andysh.uk/software/blue-twilight-php-photo-gallery/" target="_blank">',
'link_end' => '</a>',
])
</b><br/>
@lang('global.copyright', [
'link_start' => '<a href="https://www.andyheathershaw.uk/" target="_blank">',
'link_start' => '<a href="https://andysh.uk/" target="_blank">',
'link_end' => '</a>',
'years' => (date('Y') == 2016 ? '2016' : '2016-' . date('Y'))
])

View File

@ -5,10 +5,10 @@
<label for="email" class="col-md-4 col-form-label text-md-right">@lang('forms.email_label')</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" autofocus>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" autofocus>
@if ($errors->has('email'))
<div class="form-control-feedback">
<div class="invalid-feedback">
<strong>{{ $errors->first('email') }}</strong>
</div>
@endif
@ -19,10 +19,10 @@
<label for="password" class="col-md-4 col-form-label text-md-right">@lang('forms.password_label')</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password">
@if ($errors->has('password'))
<div class="form-control-feedback">
<div class="invalid-feedback">
<strong>{{ $errors->first('password') }}</strong>
</div>
@endif
@ -33,8 +33,8 @@
<div class="col-md-4"><!-- --></div>
<div class="col-md-6">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="remember"> @lang('forms.remember_me_label')
<input class="form-check-input" type="checkbox" id="remember-me" name="remember">
<label class="form-check-label" for="remember-me">@lang('forms.remember_me_label')
</label>
</div>
</div>

View File

@ -13,7 +13,11 @@
<p>{{ $album->description }}</p>
<p style="margin-bottom: 0;">
<b>{{ $album->photos_count }}</b> {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }} &middot;
@if ($album->min_metadata_version < $current_metadata_version)
@if (
isset($album->min_metadata_version) &&
intval($album->min_metadata_version) > 0 &&
intval($album->min_metadata_version) < $current_metadata_version
)
<span class="text-danger">@lang('admin.metadata_upgrade.can_be_upgraded', ['version_from' => $album->min_metadata_version, 'version_to' => $current_metadata_version])</span>
@else
<span class="text-success">@lang('admin.metadata_upgrade.is_up_to_date')</span>

View File

@ -14,15 +14,19 @@
</li>
@endcan
@if (count($albums) > 0)
@if (count($g_albums) > 0)
<li class="nav-item dropdown ml-2">
<a class="nav-link dropdown-toggle" href="{{ url('/') }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-book"></i> @lang('navigation.navbar.albums')
</a>
<div class="dropdown-menu">
@foreach ($albums as $album)
@foreach ($g_albums_menu as $album)
<a class="dropdown-item" href="{{ $album->url() }}">{{ $album->name }}</a>
@endforeach
@if ($g_more_albums > 0)
<a class="dropdown-item" href="{{ route('home') }}">{{ trans_choice('gallery.show_more_albums', $g_more_albums) }}</a>
@endif
</div>
</li>
@endif
@ -44,7 +48,7 @@
</li>
@endif
@if (count($albums) > 0 && \App\User::currentOrAnonymous()->can('statistics.public-access'))
@if (count($g_albums) > 0 && \App\User::currentOrAnonymous()->can('statistics.public-access'))
<li class="nav-item ml-2">
<a class="nav-link" href="{{ route('statistics.index') }}"><i class="fa fa-bar-chart"></i> @lang('navigation.navbar.statistics')</a>
</li>
@ -82,4 +86,4 @@
</ul>
@endif
</div>
</nav>
</nav>

View File

@ -1,7 +1,6 @@
<div>
<label class="custom-control custom-checkbox" for="permission|{{ $key_id }}|{{ $permission->id }}">
<input type="checkbox" class="custom-control-input" id="permission|{{ $key_id }}|{{ $permission->id }}" name="permissions[{{ $object_id }}][]" value="{{ $permission->id }}"{{ call_user_func($callback, $callback_object, $permission) ? ' checked="checked"' : '' }}>
<span class="custom-control-indicator"></span>
<span class="custom-control-description">{{ trans(sprintf('permissions.%s.%s', $permission->section, $permission->description)) }}</span>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="permission|{{ $key_id }}|{{ $permission->id }}" name="permissions[{{ $object_id }}][]" value="{{ $permission->id }}"{{ call_user_func($callback, $callback_object, $permission) ? ' checked="checked"' : '' }}>
<label class="form-check-label" for="permission|{{ $key_id }}|{{ $permission->id }}">
{{ trans(sprintf('permissions.%s.%s', $permission->section, $permission->description)) }}
</label>
</div>

View File

@ -1,6 +1,6 @@
<tr data-album-id="{{ $album->id }}" class="{{ $is_child ? 'hidden' : '' }}" @if (!is_null($album->parent_album_id)) data-parent-album-id="{{ $album->parent_album_id }}" @endif>
<td style="width: 20px;">
@if ($album->children()->count() > 0)
@if (count($album->child_albums) > 0)
<i class="album-expand-handle fa fa-fw fa-plus mt-2"></i>
@endif
</td>
@ -14,7 +14,12 @@
@endcannot
</span><br/>
<p>{{ $album->description }}</p>
<p style="margin-bottom: 0;"><b>{{ $album->photos_count }}</b> {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }}</p>
<p style="margin-bottom: 0;">
<b>{{ $album->photos_count }}</b> {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }}
@if (count($album->child_albums) > 0)
&middot; <b>{{ count($album->child_albums) }}</b> {{ trans_choice('admin.stats_widget.albums', count($album->child_albums)) }}
@endif
</p>
</td>
<td class="text-right">
<div class="btn-group">
@ -27,6 +32,6 @@
</div>
</td>
</tr>
@foreach ($album->children as $album)
@foreach ($album->child_albums as $album)
@include (Theme::viewName('partials.single_album_admin'), ['is_child' => true])
@endforeach

View File

@ -16,6 +16,8 @@ Auth::routes();
// Administration
Route::group(['prefix' => 'admin'], function () {
Route::get('/', 'Admin\DefaultController@index')->name('admin');
Route::get('/about', 'Admin\DefaultController@about')->name('admin.about');
Route::get('/about/latest-release', 'Admin\DefaultController@aboutLatestRelease')->name('admin.aboutLatestRelease');
Route::get('/photo-metadata', 'Admin\DefaultController@metadataUpgrade')->name('admin.metadataUpgrade');
Route::post('quick-upload', 'Admin\DefaultController@quickUpload')->name('admin.quickUpload');
Route::post('settings/save', 'Admin\DefaultController@saveSettings')->name('admin.saveSettings');
@ -27,7 +29,6 @@ Route::group(['prefix' => 'admin'], function () {
Route::get('albums/{id}/analyse/{queue_token}', 'Admin\AlbumController@analyse')->name('albums.analyse');
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
Route::get('/albums/{id}/metadata', 'Admin\AlbumController@metadata')->name('albums.metadata');
Route::post('/albums/{id}/metadata', 'Admin\AlbumController@metadataPost')->name('albums.metadataPost');
Route::post('albums/{id}/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::delete('albums/{id}/delete-redirect/{redirectId}', 'Admin\AlbumController@deleteRedirect')->name('albums.delete_redirect');
@ -38,6 +39,7 @@ Route::group(['prefix' => 'admin'], function () {
Route::post('photos/analyse/{id}/{queue_token}', 'Admin\PhotoController@analyse')->name('photos.analyse');
Route::post('photos/flip/{photoId}/{horizontal}/{vertical}', 'Admin\PhotoController@flip')->name('photos.flip');
Route::post('photos/move/{photoId}', 'Admin\PhotoController@move')->name('photos.move');
Route::post('photos/reanalyse/{id}/{queue_token}', 'Admin\PhotoController@reAnalyse')->name('photos.re-analyse');
Route::post('photos/regenerate-thumbnails/{photoId}', 'Admin\PhotoController@regenerateThumbnails')->name('photos.regenerateThumbnails');
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');