Massive refactoring of the image processing, so it's now driven by the front-end and we can completely remove the command-line tasks - which will allow the app to work completely encoded using SourceGuardian and domain-locking.
This commit is contained in:
parent
821bfceb09
commit
56cfade23c
@ -30,15 +30,6 @@ class Album extends Model
|
|||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
];
|
];
|
||||||
|
|
||||||
public function fromRequest(Request $request)
|
|
||||||
{
|
|
||||||
$this->name = $request->get('name');
|
|
||||||
$this->description = $request->get('description');
|
|
||||||
$this->generateAlias();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generateAlias()
|
public function generateAlias()
|
||||||
{
|
{
|
||||||
$this->url_alias = ucfirst(preg_replace('/[^a-z0-9\-]/', '-', strtolower($this->name)));
|
$this->url_alias = ucfirst(preg_replace('/[^a-z0-9\-]/', '-', strtolower($this->name)));
|
||||||
@ -50,7 +41,7 @@ class Album extends Model
|
|||||||
public function getAlbumSource()
|
public function getAlbumSource()
|
||||||
{
|
{
|
||||||
// TODO allow albums to specify different storage locations - e.g. Amazon S3, SFTP/FTP, OpenStack
|
// TODO allow albums to specify different storage locations - e.g. Amazon S3, SFTP/FTP, OpenStack
|
||||||
return new LocalFilesystemSource(dirname(__DIR__) . '/storage/app/albums');
|
return new LocalFilesystemSource($this, dirname(__DIR__) . '/storage/app/albums');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function photos()
|
public function photos()
|
||||||
@ -66,7 +57,7 @@ class Album extends Model
|
|||||||
|
|
||||||
if (!is_null($photo))
|
if (!is_null($photo))
|
||||||
{
|
{
|
||||||
return $this->getAlbumSource()->getUrlToPhoto($this, $photo, $thumbnailName);
|
return $this->getAlbumSource()->getUrlToPhoto($photo, $thumbnailName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
@ -12,37 +12,33 @@ interface IAlbumSource
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the absolute path to the given photo file.
|
* Gets the absolute path to the given photo file.
|
||||||
* @param Album $album Album containing the photo.
|
|
||||||
* @param Photo $photo Photo to get the path to.
|
* @param Photo $photo Photo to get the path to.
|
||||||
* @param string $thumbnail Thumbnail to get the image to.
|
* @param string $thumbnail Thumbnail to get the image to.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function getPathToPhoto(Album $album, Photo $photo, $thumbnail = null);
|
function getPathToPhoto(Photo $photo, $thumbnail = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the absolute URL to the given photo file.
|
* Gets the absolute URL to the given photo file.
|
||||||
* @param Album $album Album containing the photo.
|
|
||||||
* @param Photo $photo Photo to get the URL to.
|
* @param Photo $photo Photo to get the URL to.
|
||||||
* @param string $thumbnail Thumbnail to get the image to.
|
* @param string $thumbnail Thumbnail to get the image to.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function getUrlToPhoto(Album $album, Photo $photo, $thumbnail = null);
|
function getUrlToPhoto(Photo $photo, $thumbnail = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a generated thumbnail to its permanent location.
|
* Saves a generated thumbnail to its permanent location.
|
||||||
* @param Album $album Album containing the photo.
|
|
||||||
* @param Photo $photo Photo the thumbnail relates to.
|
* @param Photo $photo Photo the thumbnail relates to.
|
||||||
* @param string $thumbnailInfo Information about the thumbnail.
|
* @param string $thumbnailInfo Information about the thumbnail.
|
||||||
* @param string $tempFilename Filename containing the thumbnail.
|
* @param string $tempFilename Filename containing the thumbnail.
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
function saveThumbnail(Album $album, Photo $photo, $thumbnailInfo, $tempFilename);
|
function saveThumbnail(Photo $photo, $thumbnailInfo, $tempFilename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves an uploaded file to the container and returns the filename.
|
* Saves an uploaded file to the container and returns the filename.
|
||||||
* @param Album $album The album containing the photo
|
|
||||||
* @param File $uploadedFile The photo uploaded
|
* @param File $uploadedFile The photo uploaded
|
||||||
* @return File
|
* @return File
|
||||||
*/
|
*/
|
||||||
function saveUploadedPhoto(Album $album, File $uploadedFile);
|
function saveUploadedPhoto(File $uploadedFile);
|
||||||
}
|
}
|
@ -13,10 +13,19 @@ use Symfony\Component\HttpFoundation\File\File;
|
|||||||
*/
|
*/
|
||||||
class LocalFilesystemSource implements IAlbumSource
|
class LocalFilesystemSource implements IAlbumSource
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var Album
|
||||||
|
*/
|
||||||
|
private $album;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $parentFolder;
|
private $parentFolder;
|
||||||
|
|
||||||
public function __construct($parentFolder)
|
public function __construct(Album $album, $parentFolder)
|
||||||
{
|
{
|
||||||
|
$this->album = $album;
|
||||||
$this->parentFolder = $parentFolder;
|
$this->parentFolder = $parentFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,20 +34,20 @@ class LocalFilesystemSource implements IAlbumSource
|
|||||||
return '_originals';
|
return '_originals';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPathToPhoto(Album $album, Photo $photo, $thumbnail = null)
|
public function getPathToPhoto(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
if (is_null($thumbnail))
|
if (is_null($thumbnail))
|
||||||
{
|
{
|
||||||
$thumbnail = $this->getOriginalsFolder();
|
$thumbnail = $this->getOriginalsFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf('%s/%s/%s', $this->getPathToAlbum($album), $thumbnail, $photo->storage_file_name);
|
return sprintf('%s/%s/%s', $this->getPathToAlbum(), $thumbnail, $photo->storage_file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrlToPhoto(Album $album, Photo $photo, $thumbnail = null)
|
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
$photoUrl = route('downloadPhoto', [
|
$photoUrl = route('downloadPhoto', [
|
||||||
'albumUrlAlias' => $album->url_alias,
|
'albumUrlAlias' => $this->album->url_alias,
|
||||||
'photoFilename' => $photo->storage_file_name
|
'photoFilename' => $photo->storage_file_name
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -50,15 +59,15 @@ class LocalFilesystemSource implements IAlbumSource
|
|||||||
return $photoUrl;
|
return $photoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveThumbnail(Album $album, Photo $photo, $thumbnailInfo, $tempFilename)
|
public function saveThumbnail(Photo $photo, $thumbnailInfo, $tempFilename)
|
||||||
{
|
{
|
||||||
$fileInfo = new File($tempFilename);
|
$fileInfo = new File($tempFilename);
|
||||||
$fileInfo->move(sprintf('%s/%s', $this->getPathToAlbum($album), $thumbnailInfo['name']), $photo->storage_file_name);
|
$fileInfo->move(sprintf('%s/%s', $this->getPathToAlbum(), $thumbnailInfo['name']), $photo->storage_file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveUploadedPhoto(Album $album, File $uploadedFile)
|
public function saveUploadedPhoto(File $uploadedFile)
|
||||||
{
|
{
|
||||||
$tempFilename = sprintf('%s/%s/%s', $this->getPathToAlbum($album), $this->getOriginalsFolder(), MiscHelper::randomString(20));
|
$tempFilename = sprintf('%s/%s/%s', $this->getPathToAlbum(), $this->getOriginalsFolder(), MiscHelper::randomString(20));
|
||||||
|
|
||||||
$extension = $uploadedFile->guessExtension();
|
$extension = $uploadedFile->guessExtension();
|
||||||
if (!is_null($extension))
|
if (!is_null($extension))
|
||||||
@ -70,8 +79,8 @@ class LocalFilesystemSource implements IAlbumSource
|
|||||||
return new File($tempFilename);
|
return new File($tempFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPathToAlbum(Album $album)
|
private function getPathToAlbum()
|
||||||
{
|
{
|
||||||
return sprintf('%s/%s', $this->parentFolder, $album->url_alias);
|
return sprintf('%s/%s', $this->parentFolder, $this->album->url_alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,234 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Album;
|
|
||||||
use App\Helpers\ImageHelper;
|
|
||||||
use App\Helpers\ThemeHelper;
|
|
||||||
use App\Photo;
|
|
||||||
use App\Upload;
|
|
||||||
use App\UploadPhoto;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class ProcessUploadCommand extends Command
|
|
||||||
{
|
|
||||||
const METADATA_VERSION = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ImageHelper
|
|
||||||
*/
|
|
||||||
private $imageHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ThemeHelper
|
|
||||||
*/
|
|
||||||
private $themeHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'twilight:process-uploads';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Processes uploads made through the web application.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(ImageHelper $imageHelper, ThemeHelper $themeHelper)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->imageHelper = $imageHelper;
|
|
||||||
$this->themeHelper = $themeHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$uploadsToProcess = Upload::where([
|
|
||||||
['is_completed', false],
|
|
||||||
['is_processing', false],
|
|
||||||
['is_ready', true]
|
|
||||||
])
|
|
||||||
->orderBy('created_at')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
/** @var Upload $upload */
|
|
||||||
foreach ($uploadsToProcess as $upload)
|
|
||||||
{
|
|
||||||
$upload->is_processing = 1;
|
|
||||||
$upload->save();
|
|
||||||
|
|
||||||
$this->output->writeln(sprintf('Processing upload #%d', $upload->id));
|
|
||||||
$this->handleUpload($upload);
|
|
||||||
|
|
||||||
$upload->is_completed = 1;
|
|
||||||
$upload->is_processing = 0;
|
|
||||||
$upload->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleUpload(Upload $upload)
|
|
||||||
{
|
|
||||||
$photos = $upload->uploadPhotos;
|
|
||||||
|
|
||||||
/** @var UploadPhoto $photo */
|
|
||||||
foreach ($photos as $photo)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$this->handlePhoto($photo);
|
|
||||||
$upload->number_successful++;
|
|
||||||
}
|
|
||||||
catch (\Exception $ex)
|
|
||||||
{
|
|
||||||
$upload->number_failed++;
|
|
||||||
$photo->photo->delete();
|
|
||||||
$photo->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
$upload->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handlePhoto(UploadPhoto $uploadPhoto)
|
|
||||||
{
|
|
||||||
/** @var Photo $photo */
|
|
||||||
$photo = $uploadPhoto->photo;
|
|
||||||
|
|
||||||
/** @var Album $album */
|
|
||||||
$album = $photo->album;
|
|
||||||
$albumSource = $album->getAlbumSource();
|
|
||||||
|
|
||||||
$photoFile = $albumSource->getPathToPhoto($album, $photo);
|
|
||||||
|
|
||||||
// Read and analyse Exif data
|
|
||||||
$this->output->writeln(sprintf('Analysing photo #%d: %s', $photo->id, $photo->name));
|
|
||||||
|
|
||||||
// Open the photo
|
|
||||||
$imageInfo = null;
|
|
||||||
$originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo);
|
|
||||||
$photo->width = $imageInfo[0];
|
|
||||||
$photo->height = $imageInfo[1];
|
|
||||||
$photo->mime_type = $imageInfo['mime'];
|
|
||||||
|
|
||||||
// Read the Exif data
|
|
||||||
$exifData = @exif_read_data($photoFile);
|
|
||||||
$isExifDataFound = ($exifData !== false && is_array($exifData));
|
|
||||||
$angleToRotate = 0;
|
|
||||||
|
|
||||||
if ($isExifDataFound && isset($exifData['Orientation']))
|
|
||||||
{
|
|
||||||
switch ($exifData['Orientation'])
|
|
||||||
{
|
|
||||||
case 3:
|
|
||||||
$angleToRotate = 180;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
$angleToRotate = 270;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
$angleToRotate = 90;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($angleToRotate > 0)
|
|
||||||
{
|
|
||||||
$originalPhotoResource = $this->imageHelper->rotateImage($originalPhotoResource, $angleToRotate);
|
|
||||||
|
|
||||||
if ($angleToRotate == 90 || $angleToRotate == 270)
|
|
||||||
{
|
|
||||||
$photo->width = $imageInfo[1];
|
|
||||||
$photo->height = $imageInfo[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isExifDataFound)
|
|
||||||
{
|
|
||||||
$photo->metadata_version = ProcessUploadCommand::METADATA_VERSION;
|
|
||||||
$photo->taken_at = $this->metadataDateTime($exifData);
|
|
||||||
$photo->camera_make = $this->metadataCameraMake($exifData);
|
|
||||||
$photo->camera_model = $this->metadataCameraModel($exifData);
|
|
||||||
$photo->camera_software = $this->metadataCameraSoftware($exifData);
|
|
||||||
$photo->rotation = $angleToRotate;
|
|
||||||
}
|
|
||||||
|
|
||||||
$photo->save();
|
|
||||||
|
|
||||||
// Generate and save thumbnails
|
|
||||||
$this->output->writeln('Generating thumbnails');
|
|
||||||
$themeInfo = $this->themeHelper->info();
|
|
||||||
$thumbnailsRequired = $themeInfo['thumbnails'];
|
|
||||||
|
|
||||||
/** @var mixed $thumbnail */
|
|
||||||
foreach ($thumbnailsRequired as $thumbnail)
|
|
||||||
{
|
|
||||||
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $photo, $thumbnail);
|
|
||||||
$albumSource->saveThumbnail($album, $photo, $thumbnail, $generatedThumbnailPath);
|
|
||||||
|
|
||||||
$this->output->writeln(sprintf('Thumbnail \'%s\' (%dx%d) created', $thumbnail['name'], $thumbnail['width'], $thumbnail['height']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function metadataCameraMake(array $exifData)
|
|
||||||
{
|
|
||||||
if (isset($exifData['Make']))
|
|
||||||
{
|
|
||||||
return $exifData['Make'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function metadataCameraModel(array $exifData)
|
|
||||||
{
|
|
||||||
if (isset($exifData['Model']))
|
|
||||||
{
|
|
||||||
return $exifData['Model'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function metadataCameraSoftware(array $exifData)
|
|
||||||
{
|
|
||||||
if (isset($exifData['Software']))
|
|
||||||
{
|
|
||||||
return $exifData['Software'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function metadataDateTime(array $exifData)
|
|
||||||
{
|
|
||||||
$dateTime = null;
|
|
||||||
if (isset($exifData['DateTime']))
|
|
||||||
{
|
|
||||||
$dateTime = $exifData['DateTime'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_null($dateTime))
|
|
||||||
{
|
|
||||||
$dateTime = preg_replace('/^([\d]{4}):([\d]{2}):([\d]{2})/', '$1-$2-$3', $dateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dateTime;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Album;
|
|
||||||
use App\Helpers\ImageHelper;
|
|
||||||
use App\Helpers\ThemeHelper;
|
|
||||||
use App\Photo;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class RegenerateThumbnailsCommand extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'twilight:regenerate-thumbnails {--album} {id}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Recreates thumbnails for an image or an album.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ImageHelper
|
|
||||||
*/
|
|
||||||
private $imageHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ThemeHelper
|
|
||||||
*/
|
|
||||||
private $themeHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(ImageHelper $imageHelper, ThemeHelper $themeHelper)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->imageHelper = $imageHelper;
|
|
||||||
$this->themeHelper = $themeHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$id = intval($this->argument('id'));
|
|
||||||
if ($this->option('album'))
|
|
||||||
{
|
|
||||||
$this->handleAlbum($id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->handlePhoto($id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleAlbum($id)
|
|
||||||
{
|
|
||||||
$album = Album::where('id', $id)->first();
|
|
||||||
if (is_null($album))
|
|
||||||
{
|
|
||||||
throw new \Exception(sprintf('The album with ID %d could not be found', $id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Photo $photo */
|
|
||||||
foreach ($album->photos as $photo)
|
|
||||||
{
|
|
||||||
$this->regenerateThumbnailsForPhoto($album, $photo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handlePhoto($id)
|
|
||||||
{
|
|
||||||
$photo = Photo::where('id', $id)->first();
|
|
||||||
if (is_null($photo))
|
|
||||||
{
|
|
||||||
throw new \Exception(sprintf('The photo with ID %d could not be found', $id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Album $album */
|
|
||||||
$album = $photo->album;
|
|
||||||
$this->regenerateThumbnailsForPhoto($album, $photo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function regenerateThumbnailsForPhoto(Album $album, Photo $photo)
|
|
||||||
{
|
|
||||||
$albumSource = $album->getAlbumSource();
|
|
||||||
|
|
||||||
$originalPhotoResource = $this->imageHelper->openImage($albumSource->getPathToPhoto($album, $photo), $imageInfo);
|
|
||||||
if (!is_resource($originalPhotoResource))
|
|
||||||
{
|
|
||||||
throw new \Exception(sprintf('The original image for photo ID %d could not be found', $id));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->writeln(sprintf('Generating thumbnails for "%s"...', $photo->file_name));
|
|
||||||
|
|
||||||
$themeInfo = $this->themeHelper->info();
|
|
||||||
$thumbnailsRequired = $themeInfo['thumbnails'];
|
|
||||||
|
|
||||||
/** @var mixed $thumbnail */
|
|
||||||
foreach ($thumbnailsRequired as $thumbnail)
|
|
||||||
{
|
|
||||||
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $photo, $thumbnail);
|
|
||||||
$albumSource->saveThumbnail($album, $photo, $thumbnail, $generatedThumbnailPath);
|
|
||||||
|
|
||||||
$this->output->writeln(sprintf('Thumbnail \'%s\' (%dx%d) created', $thumbnail['name'], $thumbnail['width'], $thumbnail['height']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,8 +16,6 @@ class Kernel extends ConsoleKernel
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $commands = [
|
protected $commands = [
|
||||||
ProcessUploadCommand::class,
|
|
||||||
RegenerateThumbnailsCommand::class
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,15 +26,6 @@ class Kernel extends ConsoleKernel
|
|||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule)
|
protected function schedule(Schedule $schedule)
|
||||||
{
|
{
|
||||||
$schedule->command('twilight:process-uploads')
|
|
||||||
->everyMinute()
|
|
||||||
->when(function () {
|
|
||||||
return (Upload::where([
|
|
||||||
'is_completed' => 0,
|
|
||||||
'is_processing' => 0,
|
|
||||||
'is_ready' => 1
|
|
||||||
])->count() > 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
18
app/Facade/Image.php
Normal file
18
app/Facade/Image.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Facade;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
|
||||||
|
class Image extends Facade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the registered name of the component.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function getFacadeAccessor()
|
||||||
|
{
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,6 @@ class ImageHelper
|
|||||||
public function openImage($imagePath, &$imageInfo)
|
public function openImage($imagePath, &$imageInfo)
|
||||||
{
|
{
|
||||||
$imageInfo = getimagesize($imagePath);
|
$imageInfo = getimagesize($imagePath);
|
||||||
|
|
||||||
if ($imageInfo === false)
|
if ($imageInfo === false)
|
||||||
{
|
{
|
||||||
throw new \Exception(sprintf('The image "%s" does not appear to be a valid image, or cannot be read', pathinfo($imagePath, PATHINFO_FILENAME)));
|
throw new \Exception(sprintf('The image "%s" does not appear to be a valid image, or cannot be read', pathinfo($imagePath, PATHINFO_FILENAME)));
|
||||||
@ -97,4 +96,26 @@ class ImageHelper
|
|||||||
|
|
||||||
return imagerotate($imageResource, $angle, 0);
|
return imagerotate($imageResource, $angle, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function saveImage($imageResource, $imagePath, $imageInfo)
|
||||||
|
{
|
||||||
|
switch ($imageInfo[2])
|
||||||
|
{
|
||||||
|
case IMG_GIF:
|
||||||
|
imagegif($imageResource, $imagePath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMG_JPEG:
|
||||||
|
imagejpeg($imageResource, $imagePath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMG_PNG:
|
||||||
|
imagepng($imageResource, $imagePath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMG_WBMP:
|
||||||
|
imagewbmp($imageResource, $imagePath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,6 +14,71 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class AlbumController extends Controller
|
class AlbumController extends Controller
|
||||||
{
|
{
|
||||||
|
public function analyse($id)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
$album = $this->loadAlbum($id);
|
||||||
|
$photos = $album->photos()
|
||||||
|
->where('is_analysed', false)
|
||||||
|
->orderBy('created_at')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return Theme::render('admin.album_analyse_progress', ['album' => $album, 'photos' => $photos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
return Theme::render('admin.create_album');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($id)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
$album = $this->loadAlbum($id);
|
||||||
|
|
||||||
|
return Theme::render('admin.delete_album', ['album' => $album]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
$album = $this->loadAlbum($id);
|
||||||
|
$album->delete();
|
||||||
|
|
||||||
|
return redirect(route('albums.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
$album = $this->loadAlbum($id);
|
||||||
|
|
||||||
|
return Theme::render('admin.edit_album', ['album' => $album]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
@ -32,27 +97,6 @@ class AlbumController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
return Theme::render('admin.create_album');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete($id)
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
$album = AlbumController::loadAlbum($id);
|
|
||||||
|
|
||||||
return Theme::render('admin.delete_album', ['album' => $album]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
@ -63,7 +107,7 @@ class AlbumController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
$album = AlbumController::loadAlbum($id);
|
$album = $this->loadAlbum($id);
|
||||||
$photos = $album->photos()
|
$photos = $album->photos()
|
||||||
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
||||||
->paginate(UserConfig::get('items_per_page_admin'));
|
->paginate(UserConfig::get('items_per_page_admin'));
|
||||||
@ -86,42 +130,13 @@ class AlbumController extends Controller
|
|||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
$album = new Album();
|
$album = new Album();
|
||||||
$album->fromRequest($request)->save();
|
$album->fill($request->only(['name', 'description']));
|
||||||
|
$album->generateAlias();
|
||||||
|
$album->save();
|
||||||
|
|
||||||
return redirect(route('albums.index'));
|
return redirect(route('albums.index'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function edit($id)
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
$album = AlbumController::loadAlbum($id);
|
|
||||||
|
|
||||||
return Theme::render('admin.edit_album', ['album' => $album]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function monitorUpload($id, $uploadId)
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
$upload = AlbumController::loadUpload($uploadId, $id);
|
|
||||||
|
|
||||||
return Theme::render('admin.album_upload_progress', ['upload' => $upload, 'album' => $upload->album]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function monitorUploadJson($id, $uploadId)
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
return response()->json(AlbumController::loadUpload($uploadId, $id)->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
@ -133,33 +148,18 @@ class AlbumController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
$album = AlbumController::loadAlbum($id);
|
$album = $this->loadAlbum($id);
|
||||||
$album->fromRequest($request)->save();
|
$album->fill($request->only(['name', 'description']));
|
||||||
|
$album->save();
|
||||||
|
|
||||||
return Theme::render('admin.show_album', ['album' => $album]);
|
return Theme::render('admin.show_album', ['album' => $album]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function destroy($id)
|
|
||||||
{
|
|
||||||
$this->authorize('admin-access');
|
|
||||||
|
|
||||||
$album = $this->loadAlbum($id);
|
|
||||||
$album->delete();
|
|
||||||
|
|
||||||
return redirect(route('albums.index'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $id
|
* @param $id
|
||||||
* @return Album
|
* @return Album
|
||||||
*/
|
*/
|
||||||
public static function loadAlbum($id)
|
private function loadAlbum($id)
|
||||||
{
|
{
|
||||||
$album = Album::where('id', intval($id))->first();
|
$album = Album::where('id', intval($id))->first();
|
||||||
if (is_null($album))
|
if (is_null($album))
|
||||||
@ -170,25 +170,4 @@ class AlbumController extends Controller
|
|||||||
|
|
||||||
return $album;
|
return $album;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $id
|
|
||||||
* @param $albumId
|
|
||||||
* @return Upload|null
|
|
||||||
*/
|
|
||||||
private static function loadUpload($id, $albumId)
|
|
||||||
{
|
|
||||||
$upload = Upload::where([
|
|
||||||
'id' => intval($id),
|
|
||||||
'album_id' => intval($albumId)
|
|
||||||
])->first();
|
|
||||||
|
|
||||||
if (is_null($upload))
|
|
||||||
{
|
|
||||||
App::abort(404);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $upload;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,8 +3,13 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Album;
|
use App\Album;
|
||||||
|
use App\AlbumSources\IAlbumSource;
|
||||||
|
use App\Facade\Image;
|
||||||
|
use App\Facade\Theme;
|
||||||
|
use App\Helpers\ImageHelper;
|
||||||
use App\Helpers\MiscHelper;
|
use App\Helpers\MiscHelper;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
|
use App\Services\PhotoService;
|
||||||
use App\Upload;
|
use App\Upload;
|
||||||
use App\UploadPhoto;
|
use App\UploadPhoto;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -16,6 +21,38 @@ use Symfony\Component\HttpFoundation\File\File;
|
|||||||
|
|
||||||
class PhotoController extends Controller
|
class PhotoController extends Controller
|
||||||
{
|
{
|
||||||
|
public function analyse($photoId)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
/** @var Photo $photo */
|
||||||
|
$photo = Photo::where('id', intval($photoId))->first();
|
||||||
|
if (is_null($photo))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = ['is_successful' => false, 'message' => ''];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$photoService = new PhotoService($photo);
|
||||||
|
$photoService->analyse();
|
||||||
|
|
||||||
|
$result['is_successful'] = true;
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
$result['is_successful'] = false;
|
||||||
|
$result['message'] = $ex->getMessage();
|
||||||
|
|
||||||
|
$photo->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
@ -36,6 +73,27 @@ class PhotoController extends Controller
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function rotate($photoId, $angle)
|
||||||
|
{
|
||||||
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
|
$photo = Photo::where('id', intval($photoId))->first();
|
||||||
|
if (is_null($photo))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($angle != 90 && $angle != 180 && $angle != 270)
|
||||||
|
{
|
||||||
|
App::aport(400);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$photoService = new PhotoService($photo);
|
||||||
|
$photoService->rotate($angle);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
*
|
*
|
||||||
@ -49,22 +107,14 @@ class PhotoController extends Controller
|
|||||||
$photoFiles = $request->files->get('photo');
|
$photoFiles = $request->files->get('photo');
|
||||||
|
|
||||||
// Load the linked album
|
// Load the linked album
|
||||||
$album = AlbumController::loadAlbum($request->get('album_id'));
|
$album = $this->loadAlbum($request->get('album_id'));
|
||||||
|
|
||||||
$upload = new Upload();
|
|
||||||
$upload->album_id = $album->id;
|
|
||||||
$upload->is_completed = false;
|
|
||||||
$upload->is_processing = false;
|
|
||||||
$upload->is_ready = false;
|
|
||||||
$upload->number_photos = 0;
|
|
||||||
$upload->save();
|
|
||||||
|
|
||||||
foreach ($photoFiles as $photoFile)
|
foreach ($photoFiles as $photoFile)
|
||||||
{
|
{
|
||||||
$photoFile = UploadedFile::createFromBase($photoFile);
|
$photoFile = UploadedFile::createFromBase($photoFile);
|
||||||
|
|
||||||
/** @var File $savedFile */
|
/** @var File $savedFile */
|
||||||
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($album, $photoFile);
|
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($photoFile);
|
||||||
|
|
||||||
$photo = new Photo();
|
$photo = new Photo();
|
||||||
$photo->album_id = $album->id;
|
$photo->album_id = $album->id;
|
||||||
@ -73,22 +123,12 @@ class PhotoController extends Controller
|
|||||||
$photo->storage_file_name = $savedFile->getFilename();
|
$photo->storage_file_name = $savedFile->getFilename();
|
||||||
$photo->mime_type = $savedFile->getMimeType();
|
$photo->mime_type = $savedFile->getMimeType();
|
||||||
$photo->file_size = $savedFile->getSize();
|
$photo->file_size = $savedFile->getSize();
|
||||||
|
$photo->is_analysed = false;
|
||||||
$photo->save();
|
$photo->save();
|
||||||
|
|
||||||
$upload->number_photos++;
|
|
||||||
|
|
||||||
$uploadPhoto = new UploadPhoto();
|
|
||||||
$uploadPhoto->upload_id = $upload->id;
|
|
||||||
$uploadPhoto->photo_id = $photo->id;
|
|
||||||
$uploadPhoto->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$upload->is_ready = true;
|
return redirect(route('albums.analyse', [
|
||||||
$upload->save();
|
'id' => $album->id
|
||||||
|
|
||||||
return redirect(route('albums.monitorUpload', [
|
|
||||||
'id' => $album->id,
|
|
||||||
'upload_id' => $upload->id
|
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +139,7 @@ class PhotoController extends Controller
|
|||||||
$archiveFile = UploadedFile::createFromBase($request->files->get('archive'));
|
$archiveFile = UploadedFile::createFromBase($request->files->get('archive'));
|
||||||
|
|
||||||
// Load the linked album
|
// Load the linked album
|
||||||
$album = AlbumController::loadAlbum($request->get('album_id'));
|
$album = $this->loadAlbum($request->get('album_id'));
|
||||||
|
|
||||||
// Create a temporary folder to hold the extracted files
|
// Create a temporary folder to hold the extracted files
|
||||||
$tempFolder = sprintf('%s/btw_upload_%s', env('TEMP_FOLDER', '/tmp'), MiscHelper::randomString());
|
$tempFolder = sprintf('%s/btw_upload_%s', env('TEMP_FOLDER', '/tmp'), MiscHelper::randomString());
|
||||||
@ -120,14 +160,6 @@ class PhotoController extends Controller
|
|||||||
return redirect(route('albums.show', ['id' => $album->id]));
|
return redirect(route('albums.show', ['id' => $album->id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$upload = new Upload();
|
|
||||||
$upload->album_id = $album->id;
|
|
||||||
$upload->is_completed = false;
|
|
||||||
$upload->is_processing = false;
|
|
||||||
$upload->is_ready = false;
|
|
||||||
$upload->number_photos = 0;
|
|
||||||
$upload->save();
|
|
||||||
|
|
||||||
$di = new \RecursiveDirectoryIterator($tempFolder, \RecursiveDirectoryIterator::SKIP_DOTS);
|
$di = new \RecursiveDirectoryIterator($tempFolder, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||||
$recursive = new \RecursiveIteratorIterator($di);
|
$recursive = new \RecursiveIteratorIterator($di);
|
||||||
|
|
||||||
@ -136,13 +168,26 @@ class PhotoController extends Controller
|
|||||||
{
|
{
|
||||||
if ($fileInfo->isDir())
|
if ($fileInfo->isDir())
|
||||||
{
|
{
|
||||||
|
if ($fileInfo->getFilename() == '__MACOSX')
|
||||||
|
{
|
||||||
|
@rmdir($fileInfo->getPathname());
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = getimagesize($fileInfo->getPathname());
|
||||||
|
if ($result === false)
|
||||||
|
{
|
||||||
|
// Not an image file - skip
|
||||||
|
@unlink($fileInfo->getPathname());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$photoFile = new File($fileInfo->getPathname());
|
$photoFile = new File($fileInfo->getPathname());
|
||||||
|
|
||||||
/** @var File $savedFile */
|
/** @var File $savedFile */
|
||||||
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($album, $photoFile);
|
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($photoFile);
|
||||||
|
|
||||||
$photo = new Photo();
|
$photo = new Photo();
|
||||||
$photo->album_id = $album->id;
|
$photo->album_id = $album->id;
|
||||||
@ -151,22 +196,14 @@ class PhotoController extends Controller
|
|||||||
$photo->storage_file_name = $savedFile->getFilename();
|
$photo->storage_file_name = $savedFile->getFilename();
|
||||||
$photo->mime_type = $savedFile->getMimeType();
|
$photo->mime_type = $savedFile->getMimeType();
|
||||||
$photo->file_size = $savedFile->getSize();
|
$photo->file_size = $savedFile->getSize();
|
||||||
|
$photo->is_analysed = false;
|
||||||
$photo->save();
|
$photo->save();
|
||||||
|
|
||||||
$upload->number_photos++;
|
|
||||||
|
|
||||||
$uploadPhoto = new UploadPhoto();
|
|
||||||
$uploadPhoto->upload_id = $upload->id;
|
|
||||||
$uploadPhoto->photo_id = $photo->id;
|
|
||||||
$uploadPhoto->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$upload->is_ready = true;
|
@rmdir($tempFolder);
|
||||||
$upload->save();
|
|
||||||
|
|
||||||
return redirect(route('albums.monitorUpload', [
|
return redirect(route('albums.analyse', [
|
||||||
'id' => $album->id,
|
'id' => $album->id
|
||||||
'upload_id' => $upload->id
|
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,4 +280,20 @@ class PhotoController extends Controller
|
|||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $id
|
||||||
|
* @return Album
|
||||||
|
*/
|
||||||
|
private function loadAlbum($id)
|
||||||
|
{
|
||||||
|
$album = Album::where('id', intval($id))->first();
|
||||||
|
if (is_null($album))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $album;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,16 @@ use App\Facade\UserConfig;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests;
|
use App\Http\Requests;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class AlbumController extends Controller
|
class AlbumController extends Controller
|
||||||
{
|
{
|
||||||
public function index($albumUrlAlias)
|
public function index($albumUrlAlias)
|
||||||
{
|
{
|
||||||
$album = AlbumController::loadAlbum($albumUrlAlias);
|
$album = AlbumController::loadAlbum($albumUrlAlias);
|
||||||
$photos = $album->photos()->paginate(UserConfig::get('items_per_page'));
|
$photos = $album->photos()
|
||||||
|
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
||||||
|
->paginate(UserConfig::get('items_per_page_admin'));
|
||||||
|
|
||||||
return Theme::render('gallery.album', [
|
return Theme::render('gallery.album', [
|
||||||
'album' => $album,
|
'album' => $album,
|
||||||
|
@ -20,7 +20,7 @@ class PhotoController extends Controller
|
|||||||
$thumbnail = $request->get('t', $albumSource->getOriginalsFolder());
|
$thumbnail = $request->get('t', $albumSource->getOriginalsFolder());
|
||||||
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
|
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
|
||||||
|
|
||||||
return response()->file($albumSource->getPathToPhoto($album, $photo, $thumbnail));
|
return response()->file($albumSource->getPathToPhoto($photo, $thumbnail));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show($albumUrlAlias, $photoFilename)
|
public function show($albumUrlAlias, $photoFilename)
|
||||||
|
@ -28,7 +28,8 @@ class Photo extends Model
|
|||||||
'camera_model',
|
'camera_model',
|
||||||
'camera_software',
|
'camera_software',
|
||||||
'width',
|
'width',
|
||||||
'height'
|
'height',
|
||||||
|
'is_analysed'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,10 +47,7 @@ class Photo extends Model
|
|||||||
|
|
||||||
public function thumbnailUrl($thumbnailName = null)
|
public function thumbnailUrl($thumbnailName = null)
|
||||||
{
|
{
|
||||||
/** @var Album $album */
|
return $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName);
|
||||||
$album = $this->album;
|
|
||||||
|
|
||||||
return $album->getAlbumSource()->getUrlToPhoto($album, $this, $thumbnailName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function url()
|
public function url()
|
||||||
|
206
app/Services/PhotoService.php
Normal file
206
app/Services/PhotoService.php
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Album;
|
||||||
|
use App\AlbumSources\IAlbumSource;
|
||||||
|
use App\Helpers\ImageHelper;
|
||||||
|
use App\Helpers\ThemeHelper;
|
||||||
|
use App\Photo;
|
||||||
|
|
||||||
|
class PhotoService
|
||||||
|
{
|
||||||
|
const METADATA_VERSION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Album
|
||||||
|
*/
|
||||||
|
private $album;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var IAlbumSource
|
||||||
|
*/
|
||||||
|
private $albumSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ImageHelper
|
||||||
|
*/
|
||||||
|
private $imageHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Photo
|
||||||
|
*/
|
||||||
|
private $photo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ThemeHelper
|
||||||
|
*/
|
||||||
|
private $themeHelper;
|
||||||
|
|
||||||
|
public function __construct(Photo $photo)
|
||||||
|
{
|
||||||
|
$this->photo = $photo;
|
||||||
|
$this->album = $photo->album;
|
||||||
|
$this->albumSource = $this->album->getAlbumSource();
|
||||||
|
|
||||||
|
$this->imageHelper = new ImageHelper();
|
||||||
|
$this->themeHelper = new ThemeHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyse()
|
||||||
|
{
|
||||||
|
/** @var Album $album */
|
||||||
|
$album = $this->photo->album;
|
||||||
|
$albumSource = $album->getAlbumSource();
|
||||||
|
|
||||||
|
$photoFile = $albumSource->getPathToPhoto($this->photo);
|
||||||
|
|
||||||
|
$imageInfo = null;
|
||||||
|
$originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo);
|
||||||
|
if ($originalPhotoResource === false)
|
||||||
|
{
|
||||||
|
throw new \Exception(sprintf('The image "%s" does not appear to be a valid image, or cannot be read', pathinfo($photoFile, PATHINFO_FILENAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->photo->width = $imageInfo[0];
|
||||||
|
$this->photo->height = $imageInfo[1];
|
||||||
|
$this->photo->mime_type = $imageInfo['mime'];
|
||||||
|
|
||||||
|
// Read the Exif data
|
||||||
|
$exifData = @exif_read_data($photoFile);
|
||||||
|
$isExifDataFound = ($exifData !== false && is_array($exifData));
|
||||||
|
$angleToRotate = 0;
|
||||||
|
|
||||||
|
// If Exif data contains an Orientation, ensure we rotate the original image as such
|
||||||
|
if ($isExifDataFound && isset($exifData['Orientation']))
|
||||||
|
{
|
||||||
|
switch ($exifData['Orientation'])
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
$angleToRotate = 180;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
$angleToRotate = 270;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
$angleToRotate = 90;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($angleToRotate > 0)
|
||||||
|
{
|
||||||
|
$originalPhotoResource = $this->imageHelper->rotateImage($originalPhotoResource, $angleToRotate);
|
||||||
|
|
||||||
|
if ($angleToRotate == 90 || $angleToRotate == 270)
|
||||||
|
{
|
||||||
|
$this->photo->width = $imageInfo[1];
|
||||||
|
$this->photo->height = $imageInfo[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->imageHelper->saveImage($originalPhotoResource, $photoFile, $imageInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isExifDataFound)
|
||||||
|
{
|
||||||
|
$this->photo->metadata_version = self::METADATA_VERSION;
|
||||||
|
$this->photo->taken_at = $this->metadataDateTime($exifData);
|
||||||
|
$this->photo->camera_make = $this->metadataCameraMake($exifData);
|
||||||
|
$this->photo->camera_model = $this->metadataCameraModel($exifData);
|
||||||
|
$this->photo->camera_software = $this->metadataCameraSoftware($exifData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->photo->is_analysed = true;
|
||||||
|
$this->photo->save();
|
||||||
|
|
||||||
|
$this->regenerateThumbnails($originalPhotoResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function regenerateThumbnails($originalPhotoResource = null)
|
||||||
|
{
|
||||||
|
if (is_null($originalPhotoResource))
|
||||||
|
{
|
||||||
|
$imageInfo = null;
|
||||||
|
$originalPhotoResource = $this->imageHelper->openImage($this->albumSource->getPathToPhoto($this->photo), $imageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and save thumbnails
|
||||||
|
$themeInfo = $this->themeHelper->info();
|
||||||
|
$thumbnailsRequired = $themeInfo['thumbnails'];
|
||||||
|
|
||||||
|
/** @var mixed $thumbnail */
|
||||||
|
foreach ($thumbnailsRequired as $thumbnail)
|
||||||
|
{
|
||||||
|
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
|
||||||
|
$this->albumSource->saveThumbnail($this->photo, $thumbnail, $generatedThumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rotate($angle)
|
||||||
|
{
|
||||||
|
$imageInfo = array();
|
||||||
|
$photoPath = $this->albumSource->getPathToPhoto($this->photo);
|
||||||
|
$originalPhotoImage = $this->imageHelper->openImage($photoPath, $imageInfo);
|
||||||
|
$originalPhotoImage = $this->imageHelper->rotateImage($originalPhotoImage, intval($angle));
|
||||||
|
$this->imageHelper->saveImage($originalPhotoImage, $photoPath, $imageInfo);
|
||||||
|
|
||||||
|
if ($angle == 90 || $angle == 270)
|
||||||
|
{
|
||||||
|
$width = $this->photo->width;
|
||||||
|
$this->photo->width = $this->photo->height;
|
||||||
|
$this->photo->height = $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->regenerateThumbnails($originalPhotoImage);
|
||||||
|
|
||||||
|
$this->photo->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function metadataCameraMake(array $exifData)
|
||||||
|
{
|
||||||
|
if (isset($exifData['Make']))
|
||||||
|
{
|
||||||
|
return $exifData['Make'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function metadataCameraModel(array $exifData)
|
||||||
|
{
|
||||||
|
if (isset($exifData['Model']))
|
||||||
|
{
|
||||||
|
return $exifData['Model'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function metadataCameraSoftware(array $exifData)
|
||||||
|
{
|
||||||
|
if (isset($exifData['Software']))
|
||||||
|
{
|
||||||
|
return $exifData['Software'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function metadataDateTime(array $exifData)
|
||||||
|
{
|
||||||
|
$dateTime = null;
|
||||||
|
if (isset($exifData['DateTime']))
|
||||||
|
{
|
||||||
|
$dateTime = $exifData['DateTime'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($dateTime))
|
||||||
|
{
|
||||||
|
$dateTime = preg_replace('/^([\d]{4}):([\d]{2}):([\d]{2})/', '$1-$2-$3', $dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dateTime;
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
|
||||||
|
|
||||||
class Upload extends Model
|
|
||||||
{
|
|
||||||
use Notifiable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that are mass assignable.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $fillable = [
|
|
||||||
'is_completed', 'is_processing', 'number_photos', 'number_successful', 'number_failed'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that should be hidden for arrays.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $hidden = [
|
|
||||||
];
|
|
||||||
|
|
||||||
public function album()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Album::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadPhotos()
|
|
||||||
{
|
|
||||||
return $this->hasMany(UploadPhoto::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
|
||||||
|
|
||||||
class UploadPhoto extends Model
|
|
||||||
{
|
|
||||||
use Notifiable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that are mass assignable.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $fillable = [
|
|
||||||
'upload_id', 'photo_id'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that should be hidden for arrays.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $hidden = [
|
|
||||||
];
|
|
||||||
|
|
||||||
public function photo()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Photo::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -229,8 +229,9 @@ return [
|
|||||||
// Additional aliases added by AH
|
// Additional aliases added by AH
|
||||||
'Form' => Collective\Html\FormFacade::class,
|
'Form' => Collective\Html\FormFacade::class,
|
||||||
'Html' => Collective\Html\HtmlFacade::class,
|
'Html' => Collective\Html\HtmlFacade::class,
|
||||||
'Theme' => App\Facade\Theme::class,
|
'Image' => \App\Facade\Image::class,
|
||||||
'UserConfig' => App\Facade\UserConfig::class
|
'Theme' => \App\Facade\Theme::class,
|
||||||
|
'UserConfig' => \App\Facade\UserConfig::class
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class RemovePhotoRotation extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('photos', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('rotation');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('photos', function (Blueprint $table) {
|
||||||
|
$table->integer('rotation')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class RemoveUploadTables extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('upload_photos');
|
||||||
|
Schema::dropIfExists('uploads');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::create('uploads', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('album_id');
|
||||||
|
$table->boolean('is_completed');
|
||||||
|
$table->boolean('is_processing');
|
||||||
|
$table->integer('number_photos')->default(0);
|
||||||
|
$table->integer('number_successful')->default(0);
|
||||||
|
$table->integer('number_failed')->default(0);
|
||||||
|
$table->boolean('is_ready');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('album_id')
|
||||||
|
->references('id')->on('albums')
|
||||||
|
->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('upload_photos', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->unsignedInteger('upload_id');
|
||||||
|
$table->unsignedBigInteger('photo_id');
|
||||||
|
$table->boolean('is_ready');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('upload_id')
|
||||||
|
->references('id')->on('uploads')
|
||||||
|
->onDelete('cascade');
|
||||||
|
$table->foreign('photo_id')
|
||||||
|
->references('id')->on('photos')
|
||||||
|
->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddPhotoIsAnalysedColumn extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('photos', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_analysed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('photos', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_analysed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
@extends('themes.base.layout')
|
||||||
|
@section('title', 'Analysing...')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container" style="margin-top: 40px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-sm-8 col-sm-offset-2">
|
||||||
|
<div class="panel panel-default" id="status-panel">
|
||||||
|
<div class="panel-heading">Analysing...</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>Your uploaded photos are now being analysed.</p>
|
||||||
|
<div id="progress-bar-container">
|
||||||
|
<div class="progress"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="file-list" style="margin-top: 20px;">
|
||||||
|
@foreach ($photos as $photo)
|
||||||
|
<p data-photo-id="{{ $photo->id }}">{{ $photo->name }} ... <i class="fa fa-fw"></i></p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default" id="complete-panel" style="display: none;">
|
||||||
|
<div class="panel-heading">Upload completed</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>Your upload has completed.</p>
|
||||||
|
<div class="btn-toolbar btn-group-sm pull-right">
|
||||||
|
<a class="btn btn-default" href="{{ $album->url() }}">View album</a>
|
||||||
|
<a class="btn btn-primary" href="{{ route('albums.show', ['id' => $album->id]) }}">Back to album settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
var number_successful = 0;
|
||||||
|
var number_error = 0;
|
||||||
|
var number_total = 0;
|
||||||
|
|
||||||
|
function redrawProgressBar()
|
||||||
|
{
|
||||||
|
var successPercentage = (number_successful / number_total) * 100;
|
||||||
|
var failedPercentage = (number_error / number_total) * 100;
|
||||||
|
|
||||||
|
{{-- Render a Bootstrap-3 compatible progress bar --}}
|
||||||
|
var progressBar = $('<div/>').addClass('progress');
|
||||||
|
|
||||||
|
{{-- Successful --}}
|
||||||
|
var progressBarSuccess = $('<div/>')
|
||||||
|
.addClass('progress-bar')
|
||||||
|
.addClass('progress-bar-success')
|
||||||
|
.css('width', parseInt(successPercentage) + '%')
|
||||||
|
.appendTo(progressBar);
|
||||||
|
var progressBarSuccessSpan = $('<span/>').addClass('sr-only').html(parseInt(successPercentage) + '% successful').appendTo(progressBarSuccess);
|
||||||
|
|
||||||
|
{{-- Failed --}}
|
||||||
|
var progressBarError = $('<div/>')
|
||||||
|
.addClass('progress-bar')
|
||||||
|
.addClass('progress-bar-warning')
|
||||||
|
.css('width', parseInt(failedPercentage) + '%')
|
||||||
|
.appendTo(progressBar);
|
||||||
|
var progressBarErrorSpan = $('<span/>').addClass('sr-only').html(parseInt(failedPercentage) + '% failed').appendTo(progressBarError);
|
||||||
|
|
||||||
|
{{-- Add to DOM --}}
|
||||||
|
$('#progress-bar-container').html(progressBar[0].outerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
number_total = $('#file-list p').length;
|
||||||
|
|
||||||
|
if (number_total == 0) {
|
||||||
|
$('#status-panel').hide();
|
||||||
|
$('#complete-panel').show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#file-list p').each(function (index, element) {
|
||||||
|
var photo_id = $(element).data('photo-id');
|
||||||
|
var url = '{{ route('photos.analyse', ['id' => 0]) }}';
|
||||||
|
url = url.replace(/0$/, photo_id);
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
complete: function () {
|
||||||
|
redrawProgressBar();
|
||||||
|
|
||||||
|
if (number_successful + number_error >= number_total) {
|
||||||
|
$('#status-panel').hide();
|
||||||
|
$('#complete-panel').show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
|
$('i', '#file-list p[data-photo-id=' + photo_id + ']')
|
||||||
|
.addClass('text-danger')
|
||||||
|
.addClass('fa-times');
|
||||||
|
number_error++;
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
success: function (data) {
|
||||||
|
if (data.is_successful) {
|
||||||
|
$('i', '#file-list p[data-photo-id=' + photo_id + ']')
|
||||||
|
.addClass('text-success')
|
||||||
|
.addClass('fa-check');
|
||||||
|
number_successful++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('i', '#file-list p[data-photo-id=' + photo_id + ']')
|
||||||
|
.addClass('text-danger')
|
||||||
|
.addClass('fa-times');
|
||||||
|
number_error++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
@ -1,84 +0,0 @@
|
|||||||
@extends('themes.base.layout')
|
|
||||||
@section('title', 'Processing...')
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="container" style="margin-top: 40px;">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-sm-8 col-sm-offset-2">
|
|
||||||
<div class="panel panel-default" id="status-panel">
|
|
||||||
<div class="panel-heading">Processing...</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>Your upload is now being processed.</p>
|
|
||||||
<div id="progress-bar-container">
|
|
||||||
<div class="progress"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default" id="complete-panel" style="display: none;">
|
|
||||||
<div class="panel-heading">Upload completed</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>Your upload has completed.</p>
|
|
||||||
<div class="btn-toolbar btn-group-sm pull-right">
|
|
||||||
<a class="btn btn-default" href="{{ $album->url() }}">View album</a>
|
|
||||||
<a class="btn btn-primary" href="{{ route('albums.show', ['id' => $album->id]) }}">Back to album settings</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script type="text/javascript">
|
|
||||||
var refreshInterval = null;
|
|
||||||
|
|
||||||
function refreshStatus()
|
|
||||||
{
|
|
||||||
$.get('{{ route('albums.monitorUploadJson', ['id' => $album->id, 'upload_id' => $upload->id]) }}', function(data)
|
|
||||||
{
|
|
||||||
var total = data.number_photos;
|
|
||||||
|
|
||||||
if (data.is_completed)
|
|
||||||
{
|
|
||||||
// Stop the refresh
|
|
||||||
window.clearInterval(refreshInterval);
|
|
||||||
|
|
||||||
// Display the complete box
|
|
||||||
$('#status-panel').hide();
|
|
||||||
$('#complete-panel').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
var successPercentage = (data.number_successful / data.number_photos) * 100;
|
|
||||||
var failedPercentage = (data.number_failed / data.number_photos) * 100;
|
|
||||||
|
|
||||||
{{-- Render a Bootstrap-3 compatible progress bar --}}
|
|
||||||
var progressBar = $('<div/>').addClass('progress');
|
|
||||||
|
|
||||||
{{-- Successful --}}
|
|
||||||
var progressBarSuccess = $('<div/>')
|
|
||||||
.addClass('progress-bar')
|
|
||||||
.addClass('progress-bar-success')
|
|
||||||
.css('width', parseInt(successPercentage) + '%')
|
|
||||||
.appendTo(progressBar);
|
|
||||||
var progressBarSuccessSpan = $('<span/>').addClass('sr-only').html(parseInt(successPercentage) + '% successful').appendTo(progressBarSuccess);
|
|
||||||
|
|
||||||
{{-- Failed --}}
|
|
||||||
var progressBarError = $('<div/>')
|
|
||||||
.addClass('progress-bar')
|
|
||||||
.addClass('progress-bar-warning')
|
|
||||||
.css('width', parseInt(failedPercentage) + '%')
|
|
||||||
.appendTo(progressBar);
|
|
||||||
var progressBarErrorSpan = $('<span/>').addClass('sr-only').html(parseInt(failedPercentage) + '% failed').appendTo(progressBarError);
|
|
||||||
|
|
||||||
{{-- Add to DOM --}}
|
|
||||||
$('#progress-bar-container').html(progressBar[0].outerHTML);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
refreshInterval = window.setInterval(refreshStatus, 1000);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
@ -93,10 +93,41 @@
|
|||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
function rotatePhoto(photo_id, angle, parent)
|
||||||
|
{
|
||||||
|
var url = '{{ route('photos.rotate', ['id' => 0, 'angle' => 1]) }}';
|
||||||
|
url = url.replace('/0/', '/' + photo_id + '/');
|
||||||
|
url = url.replace(/\/1$/, '/' + angle);
|
||||||
|
|
||||||
|
$.post(url, function()
|
||||||
|
{
|
||||||
|
var image = $('img.photo-thumbnail', parent);
|
||||||
|
var originalUrl = image.data('original-src');
|
||||||
|
image.attr('src', originalUrl + "&_=" + new Date().getTime());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#upload-button').click(function() {
|
$('#upload-button').click(function() {
|
||||||
$('.nav-tabs a[href="#upload-tab"]').tab('show');
|
$('.nav-tabs a[href="#upload-tab"]').tab('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('a.rotate-photo-left').click(function() {
|
||||||
|
var parent = $(this).parents('.photo');
|
||||||
|
var photo_id = $(parent).data('photo-id');
|
||||||
|
|
||||||
|
rotatePhoto(photo_id, 90, parent);
|
||||||
|
$(this).dropdown('toggle');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('a.rotate-photo-right').click(function() {
|
||||||
|
var parent = $(this).parents('.photo');
|
||||||
|
var photo_id = $(parent).data('photo-id');
|
||||||
|
|
||||||
|
rotatePhoto(photo_id, 270, parent);
|
||||||
|
$(this).dropdown('toggle');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
@ -9,6 +9,8 @@
|
|||||||
<title>@yield('title') | {{ UserConfig::get('app_name') }}</title>
|
<title>@yield('title') | {{ UserConfig::get('app_name') }}</title>
|
||||||
<base href="{{ url('/') }}">
|
<base href="{{ url('/') }}">
|
||||||
|
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
{{-- Cannot use $theme_url here: if a theme uses the base layout, it would also have to provide all these dependencies! --}}
|
{{-- Cannot use $theme_url here: if a theme uses the base layout, it would also have to provide all these dependencies! --}}
|
||||||
{{-- As these files are shipped with core (not a theme) use the main app.version instead of the current theme's version --}}
|
{{-- As these files are shipped with core (not a theme) use the main app.version instead of the current theme's version --}}
|
||||||
<link href="themes/base/bootstrap/css/bootstrap.min.css?v={{ urlencode(config('app.version')) }}" rel="stylesheet">
|
<link href="themes/base/bootstrap/css/bootstrap.min.css?v={{ urlencode(config('app.version')) }}" rel="stylesheet">
|
||||||
@ -59,6 +61,13 @@
|
|||||||
<script src="themes/base/js/jquery.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
<script src="themes/base/js/jquery.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
||||||
<script src="themes/base/bootstrap/js/bootstrap.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
<script src="themes/base/bootstrap/js/bootstrap.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
||||||
<script src="themes/base/js/app.js?v={{ urlencode(config('app.version')) }}"></script>
|
<script src="themes/base/js/app.js?v={{ urlencode(config('app.version')) }}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@stack('scripts')
|
@stack('scripts')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
@php ($field_prefix = sprintf('photo[%d]', $photo->id))
|
@php ($field_prefix = sprintf('photo[%d]', $photo->id))
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="photo row">
|
<div class="photo row" data-photo-id="{{ $photo->id }}" style="position: relative;">
|
||||||
<div class="col-xs-12 col-sm-2 text-center">
|
<div class="col-xs-12 col-sm-2 text-center">
|
||||||
<a href="{{ $photo->thumbnailUrl() }}" target="_blank">
|
<a href="{{ $photo->thumbnailUrl() }}" target="_blank">
|
||||||
<img src="{{ $photo->thumbnailUrl('admin-preview') }}" style="max-width: 100%;"/>
|
<img class="photo-thumbnail" src="{{ $photo->thumbnailUrl('admin-preview') }}" data-original-src="{{ $photo->thumbnailUrl('admin-preview') }}" style="max-width: 100%;"/>
|
||||||
</a>
|
</a><br/>
|
||||||
|
<div class="btn-toolbar" role="toolbar" style="margin-top: 5px;">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fa fa-fw fa-rotate-right"></i> <span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" class="rotate-photo-left"><i class="fa fa-fw fa-rotate-left"></i> Rotate left</a></li>
|
||||||
|
<li><a href="#" class="rotate-photo-right"><i class="fa fa-fw fa-rotate-right"></i> Rotate right</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-10">
|
<div class="col-xs-12 col-sm-10">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -21,12 +21,13 @@ Route::group(['prefix' => 'admin'], function () {
|
|||||||
Route::get('settings', 'Admin\DefaultController@settings')->name('admin.settings');
|
Route::get('settings', 'Admin\DefaultController@settings')->name('admin.settings');
|
||||||
|
|
||||||
// Album management
|
// Album management
|
||||||
|
Route::get('albums/{id}/analyse', 'Admin\AlbumController@analyse')->name('albums.analyse');
|
||||||
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
|
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
|
||||||
Route::get('albums/{id}/monitor/{uploadId}.json', 'Admin\AlbumController@monitorUploadJson')->name('albums.monitorUploadJson');
|
|
||||||
Route::get('albums/{id}/monitor/{uploadId}', 'Admin\AlbumController@monitorUpload')->name('albums.monitorUpload');
|
|
||||||
Route::resource('albums', 'Admin\AlbumController');
|
Route::resource('albums', 'Admin\AlbumController');
|
||||||
|
|
||||||
// Photo management
|
// Photo management
|
||||||
|
Route::post('photos/analyse/{id}', 'Admin\PhotoController@analyse')->name('photos.analyse');
|
||||||
|
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
|
||||||
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
|
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
|
||||||
Route::put('photos/update-bulk/{albumId}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk');
|
Route::put('photos/update-bulk/{albumId}', 'Admin\PhotoController@updateBulk')->name('photos.updateBulk');
|
||||||
Route::resource('photos', 'Admin\PhotoController');
|
Route::resource('photos', 'Admin\PhotoController');
|
||||||
|
Loading…
Reference in New Issue
Block a user